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/Makefile b/Makefile index cffe394874..2927a6e109 100644 --- a/Makefile +++ b/Makefile @@ -338,6 +338,12 @@ else @golangci-lint run -v ./... --new-from-rev=origin/main --timeout 15m --fix endif +swagger: + @echo 'Generating swagger docs...' + @swag init --v3.1 -q -d core/api -g service.go -o core/api/docs + @echo 'Formatting swagger docs...' + @swag fmt -d core/api + ### Tantivy Section REPO := anyproto/tantivy-go diff --git a/core/api/docs/docs.go b/core/api/docs/docs.go index 838673118d..5698574b87 100644 --- a/core/api/docs/docs.go +++ b/core/api/docs/docs.go @@ -1,1630 +1,24 @@ -// Package docs Code generated by swaggo/swag. DO NOT EDIT +// Code generated by swaggo/swag. DO NOT EDIT. + package docs -import "github.com/swaggo/swag" +import "github.com/swaggo/swag/v2" const docTemplate = `{ "schemes": {{ marshal .Schemes }}, - "swagger": "2.0", - "info": { - "description": "{{escape .Description}}", - "title": "{{.Title}}", - "termsOfService": "https://anytype.io/terms_of_use", - "contact": { - "name": "Anytype Support", - "url": "https://anytype.io/contact", - "email": "support@anytype.io" - }, - "license": { - "name": "Any Source Available License 1.0", - "url": "https://github.com/anyproto/anytype-ts/blob/main/LICENSE.md" - }, - "version": "{{.Version}}" - }, - "host": "{{.Host}}", - "basePath": "{{.BasePath}}", - "paths": { - "/auth/display_code": { - "post": { - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "auth" - ], - "summary": "Start new challenge", - "parameters": [ - { - "type": "string", - "description": "App name requesting the challenge", - "name": "app_name", - "in": "query", - "required": true - } - ], - "responses": { - "200": { - "description": "Challenge ID", - "schema": { - "$ref": "#/definitions/auth.DisplayCodeResponse" - } - }, - "400": { - "description": "Invalid input", - "schema": { - "$ref": "#/definitions/util.ValidationError" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/util.ServerError" - } - } - } - } - }, - "/auth/token": { - "post": { - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "auth" - ], - "summary": "Retrieve token", - "parameters": [ - { - "type": "string", - "description": "Challenge ID", - "name": "challenge_id", - "in": "query", - "required": true - }, - { - "type": "string", - "description": "4-digit code retrieved from Anytype Desktop app", - "name": "code", - "in": "query", - "required": true - } - ], - "responses": { - "200": { - "description": "Authentication token", - "schema": { - "$ref": "#/definitions/auth.TokenResponse" - } - }, - "400": { - "description": "Invalid input", - "schema": { - "$ref": "#/definitions/util.ValidationError" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/util.ServerError" - } - } - } - } - }, - "/search": { - "post": { - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "search" - ], - "summary": "Search objects across all spaces", - "parameters": [ - { - "type": "integer", - "default": 0, - "description": "The number of items to skip before starting to collect the result set", - "name": "offset", - "in": "query" - }, - { - "maximum": 1000, - "type": "integer", - "default": 100, - "description": "The number of items to return", - "name": "limit", - "in": "query" - }, - { - "description": "Search parameters", - "name": "request", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/search.SearchRequest" - } - } - ], - "responses": { - "200": { - "description": "List of objects", - "schema": { - "$ref": "#/definitions/pagination.PaginatedResponse-object_Object" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/util.UnauthorizedError" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/util.ServerError" - } - } - } - } - }, - "/spaces": { - "get": { - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "spaces" - ], - "summary": "List spaces", - "parameters": [ - { - "type": "integer", - "default": 0, - "description": "The number of items to skip before starting to collect the result set", - "name": "offset", - "in": "query" - }, - { - "maximum": 1000, - "type": "integer", - "default": 100, - "description": "The number of items to return", - "name": "limit", - "in": "query" - } - ], - "responses": { - "200": { - "description": "List of spaces", - "schema": { - "$ref": "#/definitions/pagination.PaginatedResponse-space_Space" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/util.UnauthorizedError" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/util.ServerError" - } - } - } - }, - "post": { - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "spaces" - ], - "summary": "Create space", - "parameters": [ - { - "description": "Space to create", - "name": "name", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/space.CreateSpaceRequest" - } - } - ], - "responses": { - "200": { - "description": "Space created successfully", - "schema": { - "$ref": "#/definitions/space.CreateSpaceResponse" - } - }, - "400": { - "description": "Bad request", - "schema": { - "$ref": "#/definitions/util.ValidationError" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/util.UnauthorizedError" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/util.ServerError" - } - } - } - } - }, - "/spaces/{space_id}/members": { - "get": { - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "spaces" - ], - "summary": "List members", - "parameters": [ - { - "type": "string", - "description": "Space ID", - "name": "space_id", - "in": "path", - "required": true - }, - { - "type": "integer", - "default": 0, - "description": "The number of items to skip before starting to collect the result set", - "name": "offset", - "in": "query" - }, - { - "maximum": 1000, - "type": "integer", - "default": 100, - "description": "The number of items to return", - "name": "limit", - "in": "query" - } - ], - "responses": { - "200": { - "description": "List of members", - "schema": { - "$ref": "#/definitions/pagination.PaginatedResponse-space_Member" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/util.UnauthorizedError" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/util.ServerError" - } - } - } - } - }, - "/spaces/{space_id}/objects": { - "get": { - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "objects" - ], - "summary": "List objects", - "parameters": [ - { - "type": "string", - "description": "Space ID", - "name": "space_id", - "in": "path", - "required": true - }, - { - "type": "integer", - "default": 0, - "description": "The number of items to skip before starting to collect the result set", - "name": "offset", - "in": "query" - }, - { - "maximum": 1000, - "type": "integer", - "default": 100, - "description": "The number of items to return", - "name": "limit", - "in": "query" - } - ], - "responses": { - "200": { - "description": "List of objects", - "schema": { - "$ref": "#/definitions/pagination.PaginatedResponse-object_Object" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/util.UnauthorizedError" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/util.ServerError" - } - } - } - }, - "post": { - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "objects" - ], - "summary": "Create object", - "parameters": [ - { - "type": "string", - "description": "Space ID", - "name": "space_id", - "in": "path", - "required": true - }, - { - "description": "Object to create", - "name": "object", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/object.CreateObjectRequest" - } - } - ], - "responses": { - "200": { - "description": "The created object", - "schema": { - "$ref": "#/definitions/object.ObjectResponse" - } - }, - "400": { - "description": "Bad request", - "schema": { - "$ref": "#/definitions/util.ValidationError" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/util.UnauthorizedError" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/util.ServerError" - } - } - } - } - }, - "/spaces/{space_id}/objects/{object_id}": { - "get": { - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "objects" - ], - "summary": "Get object", - "parameters": [ - { - "type": "string", - "description": "Space ID", - "name": "space_id", - "in": "path", - "required": true - }, - { - "type": "string", - "description": "Object ID", - "name": "object_id", - "in": "path", - "required": true - } - ], - "responses": { - "200": { - "description": "The requested object", - "schema": { - "$ref": "#/definitions/object.ObjectResponse" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/util.UnauthorizedError" - } - }, - "404": { - "description": "Resource not found", - "schema": { - "$ref": "#/definitions/util.NotFoundError" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/util.ServerError" - } - } - } - }, - "delete": { - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "objects" - ], - "summary": "Delete object", - "parameters": [ - { - "type": "string", - "description": "Space ID", - "name": "space_id", - "in": "path", - "required": true - }, - { - "type": "string", - "description": "Object ID", - "name": "object_id", - "in": "path", - "required": true - } - ], - "responses": { - "200": { - "description": "The deleted object", - "schema": { - "$ref": "#/definitions/object.ObjectResponse" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/util.UnauthorizedError" - } - }, - "403": { - "description": "Forbidden", - "schema": { - "$ref": "#/definitions/util.ForbiddenError" - } - }, - "404": { - "description": "Resource not found", - "schema": { - "$ref": "#/definitions/util.NotFoundError" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/util.ServerError" - } - } - } - } - }, - "/spaces/{space_id}/objects/{object_id}/export/{format}": { - "post": { - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "export" - ], - "summary": "Export object", - "parameters": [ - { - "type": "string", - "description": "Space ID", - "name": "space_id", - "in": "path", - "required": true - }, - { - "type": "string", - "description": "Object ID", - "name": "object_id", - "in": "path", - "required": true - }, - { - "enum": [ - "markdown", - "protobuf" - ], - "type": "string", - "description": "Export format", - "name": "format", - "in": "path", - "required": true - } - ], - "responses": { - "200": { - "description": "Object exported successfully", - "schema": { - "$ref": "#/definitions/export.ObjectExportResponse" - } - }, - "400": { - "description": "Bad request", - "schema": { - "$ref": "#/definitions/util.ValidationError" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/util.UnauthorizedError" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/util.ServerError" - } - } - } - } - }, - "/spaces/{space_id}/search": { - "post": { - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "search" - ], - "summary": "Search objects within a space", - "parameters": [ - { - "type": "string", - "description": "Space ID", - "name": "space_id", - "in": "path", - "required": true - }, - { - "type": "integer", - "default": 0, - "description": "The number of items to skip before starting to collect the result set", - "name": "offset", - "in": "query" - }, - { - "maximum": 1000, - "type": "integer", - "default": 100, - "description": "The number of items to return", - "name": "limit", - "in": "query" - }, - { - "description": "Search parameters", - "name": "request", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/search.SearchRequest" - } - } - ], - "responses": { - "200": { - "description": "List of objects", - "schema": { - "$ref": "#/definitions/pagination.PaginatedResponse-object_Object" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/util.UnauthorizedError" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/util.ServerError" - } - } - } - } - }, - "/spaces/{space_id}/types": { - "get": { - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "types" - ], - "summary": "List types", - "parameters": [ - { - "type": "string", - "description": "Space ID", - "name": "space_id", - "in": "path", - "required": true - }, - { - "type": "integer", - "default": 0, - "description": "The number of items to skip before starting to collect the result set", - "name": "offset", - "in": "query" - }, - { - "maximum": 1000, - "type": "integer", - "default": 100, - "description": "The number of items to return", - "name": "limit", - "in": "query" - } - ], - "responses": { - "200": { - "description": "List of types", - "schema": { - "$ref": "#/definitions/pagination.PaginatedResponse-object_Type" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/util.UnauthorizedError" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/util.ServerError" - } - } - } - } - }, - "/spaces/{space_id}/types/{type_id}": { - "get": { - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "types" - ], - "summary": "Get type", - "parameters": [ - { - "type": "string", - "description": "Space ID", - "name": "space_id", - "in": "path", - "required": true - }, - { - "type": "string", - "description": "Type ID", - "name": "type_id", - "in": "path", - "required": true - } - ], - "responses": { - "200": { - "description": "The requested type", - "schema": { - "$ref": "#/definitions/object.TypeResponse" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/util.UnauthorizedError" - } - }, - "404": { - "description": "Resource not found", - "schema": { - "$ref": "#/definitions/util.NotFoundError" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/util.ServerError" - } - } - } - } - }, - "/spaces/{space_id}/types/{type_id}/templates": { - "get": { - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "types" - ], - "summary": "List templates", - "parameters": [ - { - "type": "string", - "description": "Space ID", - "name": "space_id", - "in": "path", - "required": true - }, - { - "type": "string", - "description": "Type ID", - "name": "type_id", - "in": "path", - "required": true - }, - { - "type": "integer", - "default": 0, - "description": "The number of items to skip before starting to collect the result set", - "name": "offset", - "in": "query" - }, - { - "maximum": 1000, - "type": "integer", - "default": 100, - "description": "The number of items to return", - "name": "limit", - "in": "query" - } - ], - "responses": { - "200": { - "description": "List of templates", - "schema": { - "$ref": "#/definitions/pagination.PaginatedResponse-object_Template" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/util.UnauthorizedError" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/util.ServerError" - } - } - } - } - }, - "/spaces/{space_id}/types/{type_id}/templates/{template_id}": { - "get": { - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "types" - ], - "summary": "Get template", - "parameters": [ - { - "type": "string", - "description": "Space ID", - "name": "space_id", - "in": "path", - "required": true - }, - { - "type": "string", - "description": "Type ID", - "name": "type_id", - "in": "path", - "required": true - }, - { - "type": "string", - "description": "Template ID", - "name": "template_id", - "in": "path", - "required": true - } - ], - "responses": { - "200": { - "description": "The requested template", - "schema": { - "$ref": "#/definitions/object.TemplateResponse" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/util.UnauthorizedError" - } - }, - "404": { - "description": "Resource not found", - "schema": { - "$ref": "#/definitions/util.NotFoundError" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/util.ServerError" - } - } - } - } - } - }, - "definitions": { - "auth.DisplayCodeResponse": { - "type": "object", - "properties": { - "challenge_id": { - "type": "string", - "example": "67647f5ecda913e9a2e11b26" - } - } - }, - "auth.TokenResponse": { - "type": "object", - "properties": { - "app_key": { - "type": "string", - "example": "zhSG/zQRmgADyilWPtgdnfo1qD60oK02/SVgi1GaFt6=" - }, - "session_token": { - "type": "string", - "example": "eyJhbGciOeJIRzI1NiIsInR5cCI6IkpXVCJ1.eyJzZWVkIjaiY0dmVndlUnAifQ.Y1EZecYnwmvMkrXKOa2XJnAbaRt34urBabe06tmDQII" - } - } - }, - "export.ObjectExportResponse": { - "type": "object", - "properties": { - "path": { - "type": "string", - "example": "/path/to/export" - } - } - }, - "object.Block": { - "type": "object", - "properties": { - "align": { - "type": "string", - "enum": [ - "AlignLeft", - "AlignCenter", - "AlignRight", - "AlignJustify" - ], - "example": "AlignLeft" - }, - "background_color": { - "type": "string", - "example": "red" - }, - "children_ids": { - "type": "array", - "items": { - "type": "string" - }, - "example": [ - "['6797ce8ecda913cde14b02dc']" - ] - }, - "file": { - "$ref": "#/definitions/object.File" - }, - "id": { - "type": "string", - "example": "64394517de52ad5acb89c66c" - }, - "text": { - "$ref": "#/definitions/object.Text" - }, - "vertical_align": { - "type": "string", - "enum": [ - "VerticalAlignTop", - "VerticalAlignMiddle", - "VerticalAlignBottom" - ], - "example": "VerticalAlignTop" - } - } - }, - "object.CreateObjectRequest": { - "type": "object", - "properties": { - "body": { - "type": "string", - "example": "Object Body" - }, - "description": { - "type": "string", - "example": "Object Description" - }, - "icon": { - "type": "string", - "example": "๐Ÿ“„" - }, - "name": { - "type": "string", - "example": "Object Name" - }, - "object_type_unique_key": { - "type": "string", - "example": "ot-page" - }, - "source": { - "type": "string", - "example": "https://source.com" - }, - "template_id": { - "type": "string", - "example": "bafyreictrp3obmnf6dwejy5o4p7bderaaia4bdg2psxbfzf44yya5uutge" - } - } - }, - "object.Detail": { - "type": "object", - "properties": { - "details": { - "type": "object", - "additionalProperties": true - }, - "id": { - "type": "string", - "enum": [ - "last_modified_date", - "last_modified_by", - "created_date", - "created_by", - "last_opened_date", - "tags" - ], - "example": "last_modified_date" - } - } - }, - "object.File": { - "type": "object", - "properties": { - "added_at": { - "type": "integer" - }, - "hash": { - "type": "string" - }, - "mime": { - "type": "string" - }, - "name": { - "type": "string" - }, - "size": { - "type": "integer" - }, - "state": { - "type": "string" - }, - "style": { - "type": "string" - }, - "target_object_id": { - "type": "string" - }, - "type": { - "type": "string" - } - } - }, - "object.Object": { - "type": "object", - "properties": { - "blocks": { - "type": "array", - "items": { - "$ref": "#/definitions/object.Block" - } - }, - "details": { - "type": "array", - "items": { - "$ref": "#/definitions/object.Detail" - } - }, - "icon": { - "type": "string", - "example": "๐Ÿ“„" - }, - "id": { - "type": "string", - "example": "bafyreie6n5l5nkbjal37su54cha4coy7qzuhrnajluzv5qd5jvtsrxkequ" - }, - "layout": { - "type": "string", - "example": "basic" - }, - "name": { - "type": "string", - "example": "Object Name" - }, - "root_id": { - "type": "string", - "example": "bafyreicypzj6uvu54664ucv3hmbsd5cmdy2dv4fwua26sciq74khzpyn4u" - }, - "snippet": { - "type": "string", - "example": "The beginning of the object body..." - }, - "space_id": { - "type": "string", - "example": "bafyreigyfkt6rbv24sbv5aq2hko3bhmv5xxlf22b4bypdu6j7hnphm3psq.23me69r569oi1" - }, - "type": { - "type": "string", - "example": "Page" - } - } - }, - "object.ObjectResponse": { - "type": "object", - "properties": { - "object": { - "$ref": "#/definitions/object.Object" - } - } - }, - "object.Template": { - "type": "object", - "properties": { - "icon": { - "type": "string", - "example": "๐Ÿ“„" - }, - "id": { - "type": "string", - "example": "bafyreictrp3obmnf6dwejy5o4p7bderaaia4bdg2psxbfzf44yya5uutge" - }, - "name": { - "type": "string", - "example": "Template Name" - }, - "type": { - "type": "string", - "example": "template" - } - } - }, - "object.TemplateResponse": { - "type": "object", - "properties": { - "template": { - "$ref": "#/definitions/object.Template" - } - } - }, - "object.Text": { - "type": "object", - "properties": { - "checked": { - "type": "boolean", - "example": true - }, - "color": { - "type": "string", - "example": "red" - }, - "icon": { - "type": "string", - "example": "๐Ÿ“„" - }, - "style": { - "type": "string", - "enum": [ - "Paragraph", - "Header1", - "Header2", - "Header3", - "Header4", - "Quote", - "Code", - "Title", - "Checkbox", - "Marked", - "Numbered", - "Toggle", - "Description", - "Callout" - ], - "example": "Paragraph" - }, - "text": { - "type": "string", - "example": "Some text" - } - } - }, - "object.Type": { - "type": "object", - "properties": { - "icon": { - "type": "string", - "example": "๐Ÿ“„" - }, - "id": { - "type": "string", - "example": "bafyreigyb6l5szohs32ts26ku2j42yd65e6hqy2u3gtzgdwqv6hzftsetu" - }, - "name": { - "type": "string", - "example": "Page" - }, - "recommended_layout": { - "type": "string", - "example": "todo" - }, - "type": { - "type": "string", - "example": "type" - }, - "unique_key": { - "type": "string", - "example": "ot-page" - } - } - }, - "object.TypeResponse": { - "type": "object", - "properties": { - "type": { - "$ref": "#/definitions/object.Type" - } - } - }, - "pagination.PaginatedResponse-object_Object": { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/definitions/object.Object" - } - }, - "pagination": { - "$ref": "#/definitions/pagination.PaginationMeta" - } - } - }, - "pagination.PaginatedResponse-object_Template": { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/definitions/object.Template" - } - }, - "pagination": { - "$ref": "#/definitions/pagination.PaginationMeta" - } - } - }, - "pagination.PaginatedResponse-object_Type": { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/definitions/object.Type" - } - }, - "pagination": { - "$ref": "#/definitions/pagination.PaginationMeta" - } - } - }, - "pagination.PaginatedResponse-space_Member": { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/definitions/space.Member" - } - }, - "pagination": { - "$ref": "#/definitions/pagination.PaginationMeta" - } - } - }, - "pagination.PaginatedResponse-space_Space": { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/definitions/space.Space" - } - }, - "pagination": { - "$ref": "#/definitions/pagination.PaginationMeta" - } - } - }, - "pagination.PaginationMeta": { - "type": "object", - "properties": { - "has_more": { - "description": "whether there are more items available", - "type": "boolean", - "example": true - }, - "limit": { - "description": "the current limit", - "type": "integer", - "example": 100 - }, - "offset": { - "description": "the current offset", - "type": "integer", - "example": 0 - }, - "total": { - "description": "the total number of items available on that endpoint", - "type": "integer", - "example": 1024 - } - } - }, - "search.SearchRequest": { - "type": "object", - "properties": { - "query": { - "type": "string" - }, - "sort": { - "$ref": "#/definitions/search.SortOptions" - }, - "types": { - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "search.SortOptions": { - "type": "object", - "properties": { - "direction": { - "type": "string", - "default": "desc", - "enum": [ - "asc", - "desc" - ] - }, - "timestamp": { - "type": "string", - "default": "last_modified_date", - "enum": [ - "created_date", - "last_modified_date", - "last_opened_date" - ] - } - } - }, - "space.CreateSpaceRequest": { - "type": "object", - "properties": { - "name": { - "type": "string", - "example": "New Space" - } - } - }, - "space.CreateSpaceResponse": { - "type": "object", - "properties": { - "space": { - "$ref": "#/definitions/space.Space" - } - } - }, - "space.Member": { - "type": "object", - "properties": { - "global_name": { - "type": "string", - "example": "john.any" - }, - "icon": { - "type": "string", - "example": "http://127.0.0.1:31006/image/bafybeieptz5hvcy6txplcvphjbbh5yjc2zqhmihs3owkh5oab4ezauzqay?width=100" - }, - "id": { - "type": "string", - "example": "_participant_bafyreigyfkt6rbv24sbv5aq2hko1bhmv5xxlf22b4bypdu6j7hnphm3psq_23me69r569oi1_AAjEaEwPF4nkEh9AWkqEnzcQ8HziBB4ETjiTpvRCQvWnSMDZ" - }, - "identity": { - "type": "string", - "example": "AAjEaEwPF4nkEh7AWkqEnzcQ8HziGB4ETjiTpvRCQvWnSMDZ" - }, - "name": { - "type": "string", - "example": "John Doe" - }, - "role": { - "type": "string", - "enum": [ - "Reader", - "Writer", - "Owner", - "NoPermission" - ], - "example": "Owner" - }, - "type": { - "type": "string", - "example": "member" - } - } - }, - "space.Space": { - "type": "object", - "properties": { - "account_space_id": { - "type": "string", - "example": "bafyreihpd2knon5wbljhtfeg3fcqtg3i2pomhhnigui6lrjmzcjzep7gcy.23me69r569oi1" - }, - "analytics_id": { - "type": "string", - "example": "624aecdd-4797-4611-9d61-a2ae5f53cf1c" - }, - "archive_object_id": { - "type": "string", - "example": "bafyreialsgoyflf3etjm3parzurivyaukzivwortf32b4twnlwpwocsrri" - }, - "device_id": { - "type": "string", - "example": "12D3KooWGZMJ4kQVyQVXaj7gJPZr3RZ2nvd9M2Eq2pprEoPih9WF" - }, - "gateway_url": { - "type": "string", - "example": "http://127.0.0.1:31006" - }, - "home_object_id": { - "type": "string", - "example": "bafyreie4qcl3wczb4cw5hrfyycikhjyh6oljdis3ewqrk5boaav3sbwqya" - }, - "icon": { - "type": "string", - "example": "http://127.0.0.1:31006/image/bafybeieptz5hvcy6txplcvphjbbh5yjc2zqhmihs3owkh5oab4ezauzqay" - }, - "id": { - "type": "string", - "example": "bafyreigyfkt6rbv24sbv5aq2hko3bhmv5xxlf22b4bypdu6j7hnphm3psq.23me69r569oi1" - }, - "local_storage_path": { - "type": "string", - "example": "/Users/johndoe/Library/Application Support/Anytype/data/AAHTtt1wuQEnaYBNZ2Cyfcvs6DqPqxgn8VXDVk4avsUkMuha" - }, - "marketplace_workspace_id": { - "type": "string", - "example": "_anytype_marketplace" - }, - "name": { - "type": "string", - "example": "Space Name" - }, - "network_id": { - "type": "string", - "example": "N83gJpVd9MuNRZAuJLZ7LiMntTThhPc6DtzWWVjb1M3PouVU" - }, - "profile_object_id": { - "type": "string", - "example": "bafyreiaxhwreshjqwndpwtdsu4mtihaqhhmlygqnyqpfyfwlqfq3rm3gw4" - }, - "space_view_id": { - "type": "string", - "example": "bafyreigzv3vq7qwlrsin6njoduq727ssnhwd6bgyfj6nm4hv3pxoc2rxhy" - }, - "tech_space_id": { - "type": "string", - "example": "bafyreif4xuwncrjl6jajt4zrrfnylpki476nv2w64yf42ovt7gia7oypii.23me69r569oi1" - }, - "timezone": { - "type": "string", - "example": "" - }, - "type": { - "type": "string", - "example": "space" - }, - "widgets_id": { - "type": "string", - "example": "bafyreialj7pceh53mifm5dixlho47ke4qjmsn2uh4wsjf7xq2pnlo5xfva" - }, - "workspace_object_id": { - "type": "string", - "example": "bafyreiapey2g6e6za4zfxvlgwdy4hbbfu676gmwrhnqvjbxvrchr7elr3y" - } - } - }, - "util.ForbiddenError": { - "type": "object", - "properties": { - "error": { - "type": "object", - "properties": { - "message": { - "type": "string" - } - } - } - } - }, - "util.NotFoundError": { - "type": "object", - "properties": { - "error": { - "type": "object", - "properties": { - "message": { - "type": "string" - } - } - } - } - }, - "util.ServerError": { - "type": "object", - "properties": { - "error": { - "type": "object", - "properties": { - "message": { - "type": "string" - } - } - } - } - }, - "util.UnauthorizedError": { - "type": "object", - "properties": { - "error": { - "type": "object", - "properties": { - "message": { - "type": "string" - } - } - } - } - }, - "util.ValidationError": { - "type": "object", - "properties": { - "error": { - "type": "object", - "properties": { - "message": { - "type": "string" - } - } - } - } - } - }, - "securityDefinitions": { - "BasicAuth": { - "type": "basic" - } - }, - "externalDocs": { - "description": "OpenAPI", - "url": "https://swagger.io/resources/open-api/" - } + "components": {"schemas":{"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","AlignRight","AlignJustify"],"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","uniqueItems":false},"file":{"$ref":"#/components/schemas/object.File"},"id":{"description":"The id of the block","example":"64394517de52ad5acb89c66c","type":"string"},"relation":{"$ref":"#/components/schemas/object.Relation"},"text":{"$ref":"#/components/schemas/object.Text"},"vertical_align":{"description":"The vertical alignment of the block","enum":["VerticalAlignTop","VerticalAlignMiddle","VerticalAlignBottom"],"example":"VerticalAlignTop","type":"string"}},"type":"object"},"object.CreateObjectRequest":{"properties":{"body":{"description":"The body of the object","example":"This is the body of the object. Markdown syntax is supported here.","type":"string"},"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":"๐Ÿ“„","type":"string"},"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":{"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"},"object.Detail":{"properties":{"details":{"additionalProperties":{},"description":"The details","type":"object"},"id":{"description":"The id of the detail","enum":["last_modified_date","last_modified_by","created_date","created_by","last_opened_date","tags"],"example":"last_modified_date","type":"string"}},"type":"object"},"object.File":{"description":"The file of the block, if applicable","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":{"description":"The object","properties":{"blocks":{"description":"The blocks of the object","items":{"$ref":"#/components/schemas/object.Block"},"type":"array","uniqueItems":false},"details":{"description":"The details of the object","items":{"$ref":"#/components/schemas/object.Detail"},"type":"array","uniqueItems":false},"icon":{"description":"The icon of the object","example":"๐Ÿ“„","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":{"description":"The name of the object","example":"My object","type":"string"},"object":{"description":"The data model of the object","example":"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":{"$ref":"#/components/schemas/object.Type"}},"type":"object"},"object.ObjectResponse":{"properties":{"object":{"$ref":"#/components/schemas/object.Object"}},"type":"object"},"object.Relation":{"description":"The relation of the block, if applicable","properties":{"id":{"type":"string"}},"type":"object"},"object.Template":{"description":"The template","properties":{"icon":{"description":"The icon of the template","example":"๐Ÿ“„","type":"string"},"id":{"description":"The id of the template","example":"bafyreictrp3obmnf6dwejy5o4p7bderaaia4bdg2psxbfzf44yya5uutge","type":"string"},"name":{"description":"The name of the template","example":"My template","type":"string"},"object":{"description":"The data model of the object","example":"template","type":"string"}},"type":"object"},"object.TemplateResponse":{"properties":{"template":{"$ref":"#/components/schemas/object.Template"}},"type":"object"},"object.Text":{"description":"The text of the block, if applicable","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":"๐Ÿ“„","type":"string"},"style":{"description":"The style of the text","enum":["Paragraph","Header1","Header2","Header3","Header4","Quote","Code","Title","Checkbox","Marked","Numbered","Toggle","Description","Callout"],"example":"Paragraph","type":"string"},"text":{"description":"The text","example":"Some text...","type":"string"}},"type":"object"},"object.Type":{"description":"The type of the object","properties":{"icon":{"description":"The icon of the type","example":"๐Ÿ“„","type":"string"},"id":{"description":"The id of the type","example":"bafyreigyb6l5szohs32ts26ku2j42yd65e6hqy2u3gtzgdwqv6hzftsetu","type":"string"},"name":{"description":"The name of the type","example":"Page","type":"string"},"object":{"description":"The data model of the object","example":"type","type":"string"},"recommended_layout":{"description":"The recommended layout of the type","example":"todo","type":"string"},"unique_key":{"description":"The unique key of the type","example":"ot-page","type":"string"}},"type":"object"},"object.TypeResponse":{"properties":{"type":{"$ref":"#/components/schemas/object.Type"}},"type":"object"},"pagination.PaginatedResponse-object_Object":{"properties":{"data":{"description":"The list of items in the current result set","items":{"$ref":"#/components/schemas/object.Object"},"type":"array","uniqueItems":false},"pagination":{"$ref":"#/components/schemas/pagination.PaginationMeta"}},"type":"object"},"pagination.PaginatedResponse-object_Template":{"properties":{"data":{"description":"The list of items in the current result set","items":{"$ref":"#/components/schemas/object.Template"},"type":"array","uniqueItems":false},"pagination":{"$ref":"#/components/schemas/pagination.PaginationMeta"}},"type":"object"},"pagination.PaginatedResponse-object_Type":{"properties":{"data":{"description":"The list of items in the current result set","items":{"$ref":"#/components/schemas/object.Type"},"type":"array","uniqueItems":false},"pagination":{"$ref":"#/components/schemas/pagination.PaginationMeta"}},"type":"object"},"pagination.PaginatedResponse-space_Member":{"properties":{"data":{"description":"The list of items in the current result set","items":{"$ref":"#/components/schemas/space.Member"},"type":"array","uniqueItems":false},"pagination":{"$ref":"#/components/schemas/pagination.PaginationMeta"}},"type":"object"},"pagination.PaginatedResponse-space_Space":{"properties":{"data":{"description":"The list of items in the current result set","items":{"$ref":"#/components/schemas/space.Space"},"type":"array","uniqueItems":false},"pagination":{"$ref":"#/components/schemas/pagination.PaginationMeta"}},"type":"object"},"pagination.PaginationMeta":{"description":"The pagination metadata for the response","properties":{"has_more":{"description":"Indicates if there are more items available beyond the current result set","example":true,"type":"boolean"},"limit":{"description":"The maximum number of items returned in the result set","example":100,"type":"integer"},"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 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":"#/components/schemas/search.SortOptions"},"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","uniqueItems":false}},"type":"object"},"search.SortOptions":{"description":"The sorting criteria and direction for the search results","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","last_opened_date"],"type":"string"}},"type":"object"},"space.CreateSpaceRequest":{"properties":{"name":{"description":"The name of the space","example":"New Space","type":"string"}},"type":"object"},"space.Member":{"description":"The 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"},"object":{"description":"The data model of the object","example":"member","type":"string"},"role":{"description":"The role of the member","enum":["Reader","Writer","Owner","NoPermission"],"example":"Owner","type":"string"}},"type":"object"},"space.MemberResponse":{"properties":{"member":{"$ref":"#/components/schemas/space.Member"}},"type":"object"},"space.Space":{"description":"The 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":{"description":"The name of the space","example":"My Space","type":"string"},"network_id":{"description":"The network id of the space","example":"N83gJpVd9MuNRZAuJLZ7LiMntTThhPc6DtzWWVjb1M3PouVU","type":"string"},"object":{"description":"The data model of the object","example":"space","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"},"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":{"$ref":"#/components/schemas/space.Space"}},"type":"object"},"util.ForbiddenError":{"properties":{"error":{"properties":{"message":{"example":"Forbidden","type":"string"}},"type":"object"}},"type":"object"},"util.NotFoundError":{"properties":{"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"},"util.ServerError":{"properties":{"error":{"properties":{"message":{"example":"Internal server error","type":"string"}},"type":"object"}},"type":"object"},"util.UnauthorizedError":{"properties":{"error":{"properties":{"message":{"example":"Unauthorized","type":"string"}},"type":"object"}},"type":"object"},"util.ValidationError":{"properties":{"error":{"properties":{"message":{"example":"Bad request","type":"string"}},"type":"object"}},"type":"object"}},"securitySchemes":{"bearerauth":{"bearerFormat":"JWT","scheme":"bearer","type":"http"}}}, + "info": {"contact":{"email":"support@anytype.io","name":"Anytype Support","url":"https://anytype.io/contact"},"description":"{{escape .Description}}","license":{"name":"Any Source Available License 1.0","url":"https://github.com/anyproto/anytype-ts/blob/main/LICENSE.md"},"termsOfService":"https://anytype.io/terms_of_use","title":"{{.Title}}","version":"{{.Version}}"}, + "externalDocs": {"description":"OpenAPI","url":"https://swagger.io/resources/open-api/"}, + "paths": {"/auth/display_code":{"post":{"description":"This endpoint initiates a secure authentication flow by generating a new challenge. Clients must supply the name of the application (via a query parameter) that is requesting authentication. On success, the service returns a unique challenge ID. This challenge ID must then be used with the token endpoint (see below) to solve the challenge and retrieve an authentication token. In essence, this endpoint โ€œboots upโ€ the login process and is the first step in a multi-phase authentication sequence.","parameters":[{"description":"App name requesting the challenge","in":"query","name":"app_name","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/auth.DisplayCodeResponse"}}},"description":"Challenge ID"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.ValidationError"}}},"description":"Invalid input"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.ServerError"}}},"description":"Internal server error"}},"summary":"Start new challenge","tags":["auth"]}},"/auth/token":{"post":{"description":"After receiving a challenge ID from the display_code endpoint, the client calls this endpoint to provide the corresponding 4-digit code (also via a query parameter) along with the challenge ID. The endpoint verifies that the challenge solution is correct and, if it is, returns an ephemeral session token together with a permanent app key. These tokens are then used in subsequent API requests to authorize access. This endpoint is central to ensuring that only properly authenticated sessions can access further resources.","parameters":[{"description":"Challenge ID","in":"query","name":"challenge_id","required":true,"schema":{"type":"string"}},{"description":"4-digit code retrieved from Anytype Desktop app","in":"query","name":"code","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/auth.TokenResponse"}}},"description":"Authentication token"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.ValidationError"}}},"description":"Invalid input"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.ServerError"}}},"description":"Internal server error"}},"summary":"Retrieve token","tags":["auth"]}},"/search":{"post":{"description":"This endpoint executes a global search over every space the user has access to. It accepts pagination parameters (offset and limit) and a JSON body containing search criteria. The criteria include a search query string, an optional list of object types, and sort options (e.g. ascending/descending by creation, modification, or last opened dates). Internally, the endpoint aggregates results from each space, merges and sorts them (after last modified date by default), and returns a unified, paginated list of objects that match the search parameters.","parameters":[{"description":"The number of items to skip before starting to collect the result set","in":"query","name":"offset","schema":{"default":0,"type":"integer"}},{"description":"The number of items to return","in":"query","name":"limit","schema":{"default":100,"maximum":1000,"type":"integer"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/search.SearchRequest"}}},"description":"Search parameters","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/pagination.PaginatedResponse-object_Object"}}},"description":"List of objects"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.UnauthorizedError"}}},"description":"Unauthorized"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.ServerError"}}},"description":"Internal server error"}},"security":[{"bearerauth":[]}],"summary":"Search objects across all spaces","tags":["search"]}},"/spaces":{"get":{"description":"Retrieves a paginated list of all spaces that are accessible by the authenticated user. Each space record contains detailed information such as the space ID, name, icon (derived either from an emoji or image URL), and additional metadata. This endpoint is key to displaying a userโ€™s workspaces.","parameters":[{"description":"The number of items to skip before starting to collect the result set","in":"query","name":"offset","schema":{"default":0,"type":"integer"}},{"description":"The number of items to return","in":"query","name":"limit","schema":{"default":100,"maximum":1000,"type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/pagination.PaginatedResponse-space_Space"}}},"description":"List of spaces"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.UnauthorizedError"}}},"description":"Unauthorized"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.ServerError"}}},"description":"Internal server error"}},"security":[{"bearerauth":[]}],"summary":"List spaces","tags":["spaces"]},"post":{"description":"Creates a new workspace (or space) based on a supplied name in the JSON request body. The endpoint is subject to rate limiting and automatically applies default configurations such as generating a random icon and initializing the workspace with default settings (for example, a default dashboard or home page). On success, the new spaceโ€™s full metadata is returned, enabling the client to immediately switch context to the new space.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/space.CreateSpaceRequest"}}},"description":"Space to create","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/space.SpaceResponse"}}},"description":"Space created successfully"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.ValidationError"}}},"description":"Bad request"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.UnauthorizedError"}}},"description":"Unauthorized"},"423":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.RateLimitError"}}},"description":"Rate limit exceeded"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.ServerError"}}},"description":"Internal server error"}},"security":[{"bearerauth":[]}],"summary":"Create space","tags":["spaces"]}},"/spaces/{space_id}":{"get":{"description":"Fetches full details about a single space identified by its space ID. The response includes metadata such as the space name, icon, and various workspace IDs (home, archive, profile, etc.). This detailed view supports use cases such as displaying space-specific settings.","parameters":[{"description":"Space ID","in":"path","name":"space_id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/space.SpaceResponse"}}},"description":"Space"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.UnauthorizedError"}}},"description":"Unauthorized"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.NotFoundError"}}},"description":"Space not found"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.ServerError"}}},"description":"Internal server error"}},"security":[{"bearerauth":[]}],"summary":"Get space","tags":["spaces"]}},"/spaces/{space_id}/members":{"get":{"description":"Returns a paginated list of members belonging to the specified space. Each member record includes the memberโ€™s profile ID, name, icon (which may be derived from an emoji or image), network identity, global name, and role (e.g. Reader, Writer, Owner). This endpoint supports collaborative features by allowing clients to show who is in a space and manage access rights.","parameters":[{"description":"Space ID","in":"path","name":"space_id","required":true,"schema":{"type":"string"}},{"description":"The number of items to skip before starting to collect the result set","in":"query","name":"offset","schema":{"default":0,"type":"integer"}},{"description":"The number of items to return","in":"query","name":"limit","schema":{"default":100,"maximum":1000,"type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/pagination.PaginatedResponse-space_Member"}}},"description":"List of members"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.UnauthorizedError"}}},"description":"Unauthorized"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.ServerError"}}},"description":"Internal server error"}},"security":[{"bearerauth":[]}],"summary":"List members","tags":["spaces"]}},"/spaces/{space_id}/members/{member_id}":{"get":{"description":"Fetches detailed information about a single member within a space. The endpoint returns the memberโ€™s identifier, name, icon, identity, global name, and role. This is useful for user profile pages, permission management, and displaying member-specific information in collaborative environments.","parameters":[{"description":"Space ID","in":"path","name":"space_id","required":true,"schema":{"type":"string"}},{"description":"Member ID","in":"path","name":"member_id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/space.MemberResponse"}}},"description":"Member"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.UnauthorizedError"}}},"description":"Unauthorized"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.NotFoundError"}}},"description":"Member not found"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.ServerError"}}},"description":"Internal server error"}},"security":[{"bearerauth":[]}],"summary":"Get member","tags":["spaces"]}},"/spaces/{space_id}/objects":{"get":{"description":"Retrieves a paginated list of objects in the given space. The endpoint takes query parameters for pagination (offset and limit) and returns detailed data about each object including its ID, name, icon, type information, a snippet of the content (if applicable), layout, space ID, blocks and details. It is intended for building views where users can see all objects in a space at a glance.","parameters":[{"description":"Space ID","in":"path","name":"space_id","required":true,"schema":{"type":"string"}},{"description":"The number of items to skip before starting to collect the result set","in":"query","name":"offset","schema":{"default":0,"type":"integer"}},{"description":"The number of items to return","in":"query","name":"limit","schema":{"default":100,"maximum":1000,"type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/pagination.PaginatedResponse-object_Object"}}},"description":"List of objects"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.UnauthorizedError"}}},"description":"Unauthorized"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.ServerError"}}},"description":"Internal server error"}},"security":[{"bearerauth":[]}],"summary":"List objects","tags":["objects"]},"post":{"description":"Creates a new object in the specified space using a JSON payload. The creation process is subject to rate limiting. The payload must include key details such as the object name, icon, description, body content (which may support Markdown), source URL (required for bookmark objects), template identifier, and the unique key for the object type. Post-creation, additional operations (like setting featured relations or fetching bookmark metadata) may occur. The endpoint then returns the full object data, ready for further interactions.","parameters":[{"description":"Space ID","in":"path","name":"space_id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/object.CreateObjectRequest"}}},"description":"Object to create","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/object.ObjectResponse"}}},"description":"The created object"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.ValidationError"}}},"description":"Bad request"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.UnauthorizedError"}}},"description":"Unauthorized"},"423":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.RateLimitError"}}},"description":"Rate limit exceeded"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.ServerError"}}},"description":"Internal server error"}},"security":[{"bearerauth":[]}],"summary":"Create object","tags":["objects"]}},"/spaces/{space_id}/objects/{object_id}":{"delete":{"description":"This endpoint โ€œdeletesโ€ an object by marking it as archived. The deletion process is performed safely and is subject to rate limiting. It returns the objectโ€™s details after it has been archived. Proper error handling is in place for situations such as when the object isnโ€™t found or the deletion cannot be performed because of permission issues.","parameters":[{"description":"Space ID","in":"path","name":"space_id","required":true,"schema":{"type":"string"}},{"description":"Object ID","in":"path","name":"object_id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/object.ObjectResponse"}}},"description":"The deleted object"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.UnauthorizedError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.ForbiddenError"}}},"description":"Forbidden"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.NotFoundError"}}},"description":"Resource not found"},"423":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.RateLimitError"}}},"description":"Rate limit exceeded"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.ServerError"}}},"description":"Internal server error"}},"security":[{"bearerauth":[]}],"summary":"Delete object","tags":["objects"]},"get":{"description":"Fetches the full details of a single object identified by the object ID within the specified space. The response includes not only basic metadata (ID, name, icon, type) but also the complete set of blocks (which may include text, files, and relations) and extra details (such as timestamps and linked member information). This endpoint is essential when a client needs to render or edit the full object view.","parameters":[{"description":"Space ID","in":"path","name":"space_id","required":true,"schema":{"type":"string"}},{"description":"Object ID","in":"path","name":"object_id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/object.ObjectResponse"}}},"description":"The requested object"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.UnauthorizedError"}}},"description":"Unauthorized"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.NotFoundError"}}},"description":"Resource not found"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.ServerError"}}},"description":"Internal server error"}},"security":[{"bearerauth":[]}],"summary":"Get object","tags":["objects"]}},"/spaces/{space_id}/objects/{object_id}/export/{format}":{"post":{"description":"This endpoint exports a single object from the specified space into a desired format. The export format is provided as a path parameter (currently supporting โ€œmarkdownโ€ and โ€œprotobufโ€), and clients can optionally specify an export path in the request body. The endpoint calls an export service which converts the objectโ€™s content into the requested format and returns the file path where the exported data is stored. It is useful for data backup, sharing, or further processing.","parameters":[{"description":"Space ID","in":"path","name":"space_id","required":true,"schema":{"type":"string"}},{"description":"Object ID","in":"path","name":"object_id","required":true,"schema":{"type":"string"}},{"description":"Export format","in":"path","name":"format","required":true,"schema":{"enum":["markdown","protobuf"],"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/export.ObjectExportResponse"}}},"description":"Object exported successfully"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.ValidationError"}}},"description":"Bad request"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.UnauthorizedError"}}},"description":"Unauthorized"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.ServerError"}}},"description":"Internal server error"}},"security":[{"bearerauth":[]}],"summary":"Export object","tags":["export"]}},"/spaces/{space_id}/search":{"post":{"description":"This endpoint performs a focused search within a single space (specified by the space_id path parameter). Like the global search, it accepts pagination parameters and a JSON payload containing the search query, object types, and sorting preferences. The search is limited to the provided space and returns a list of objects that match the query. This allows clients to implement spaceโ€‘specific filtering without having to process extraneous results.","parameters":[{"description":"Space ID","in":"path","name":"space_id","required":true,"schema":{"type":"string"}},{"description":"The number of items to skip before starting to collect the result set","in":"query","name":"offset","schema":{"default":0,"type":"integer"}},{"description":"The number of items to return","in":"query","name":"limit","schema":{"default":100,"maximum":1000,"type":"integer"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/search.SearchRequest"}}},"description":"Search parameters","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/pagination.PaginatedResponse-object_Object"}}},"description":"List of objects"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.UnauthorizedError"}}},"description":"Unauthorized"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.ServerError"}}},"description":"Internal server error"}},"security":[{"bearerauth":[]}],"summary":"Search objects within a space","tags":["search"]}},"/spaces/{space_id}/types":{"get":{"description":"This endpoint retrieves a paginated list of object types (e.g. 'Page', 'Note', 'Task') available within the specified space. Each typeโ€™s record includes its unique identifier, unique key, display name, icon, and a recommended layout. Clients use this information when offering choices for object creation or for filtering objects by type.","parameters":[{"description":"Space ID","in":"path","name":"space_id","required":true,"schema":{"type":"string"}},{"description":"The number of items to skip before starting to collect the result set","in":"query","name":"offset","schema":{"default":0,"type":"integer"}},{"description":"The number of items to return","in":"query","name":"limit","schema":{"default":100,"maximum":1000,"type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/pagination.PaginatedResponse-object_Type"}}},"description":"List of types"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.UnauthorizedError"}}},"description":"Unauthorized"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.ServerError"}}},"description":"Internal server error"}},"security":[{"bearerauth":[]}],"summary":"List types","tags":["types"]}},"/spaces/{space_id}/types/{type_id}":{"get":{"description":"Fetches detailed information about one specific object type by its ID. This includes the typeโ€™s unique key, name, icon, and recommended layout. This detailed view assists clients in understanding the expected structure and style for objects of that type and in guiding the user interface (such as displaying appropriate icons or layout hints).","parameters":[{"description":"Space ID","in":"path","name":"space_id","required":true,"schema":{"type":"string"}},{"description":"Type ID","in":"path","name":"type_id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/object.TypeResponse"}}},"description":"The requested type"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.UnauthorizedError"}}},"description":"Unauthorized"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.NotFoundError"}}},"description":"Resource not found"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.ServerError"}}},"description":"Internal server error"}},"security":[{"bearerauth":[]}],"summary":"Get type","tags":["types"]}},"/spaces/{space_id}/types/{type_id}/templates":{"get":{"description":"This endpoint returns a paginated list of templates that are associated with a specific object type within a space. Templates provide preโ€‘configured structures for creating new objects. Each template record contains its identifier, name, and icon, so that clients can offer users a selection of templates when creating objects.","parameters":[{"description":"Space ID","in":"path","name":"space_id","required":true,"schema":{"type":"string"}},{"description":"Type ID","in":"path","name":"type_id","required":true,"schema":{"type":"string"}},{"description":"The number of items to skip before starting to collect the result set","in":"query","name":"offset","schema":{"default":0,"type":"integer"}},{"description":"The number of items to return","in":"query","name":"limit","schema":{"default":100,"maximum":1000,"type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/pagination.PaginatedResponse-object_Template"}}},"description":"List of templates"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.UnauthorizedError"}}},"description":"Unauthorized"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.ServerError"}}},"description":"Internal server error"}},"security":[{"bearerauth":[]}],"summary":"List templates","tags":["types"]}},"/spaces/{space_id}/types/{type_id}/templates/{template_id}":{"get":{"description":"Fetches full details for one template associated with a particular object type in a space. The response provides the templateโ€™s identifier, name, icon, and any other relevant metadata. This endpoint is useful when a client needs to preview or apply a template to prefill object creation fields.","parameters":[{"description":"Space ID","in":"path","name":"space_id","required":true,"schema":{"type":"string"}},{"description":"Type ID","in":"path","name":"type_id","required":true,"schema":{"type":"string"}},{"description":"Template ID","in":"path","name":"template_id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/object.TemplateResponse"}}},"description":"The requested template"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.UnauthorizedError"}}},"description":"Unauthorized"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.NotFoundError"}}},"description":"Resource not found"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.ServerError"}}},"description":"Internal server error"}},"security":[{"bearerauth":[]}],"summary":"Get template","tags":["types"]}},"/v1/spaces/{space_id}/lists/{list_id}/objects":{"get":{"description":"Returns a paginated list of objects that are associated with a specific list (or collection) within a space. This endpoint helps clients to manage grouped objects (for example, tasks within a list) by returning detailed object information for each item of the list.","parameters":[{"description":"Space ID","in":"path","name":"space_id","required":true,"schema":{"type":"string"}},{"description":"List ID","in":"path","name":"list_id","required":true,"schema":{"type":"string"}},{"description":"The number of items to skip before starting to collect the result set","in":"query","name":"offset","schema":{"default":0,"type":"integer"}},{"description":"The number of items to return","in":"query","name":"limit","schema":{"type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/pagination.PaginatedResponse-object_Object"}}},"description":"List of objects"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.UnauthorizedError"}}},"description":"Unauthorized"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.NotFoundError"}}},"description":"Not found"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.ServerError"}}},"description":"Internal server error"}},"security":[{"bearerauth":[]}],"summary":"Get objects in list","tags":["lists"]},"post":{"description":"Enables clients to add one or more objects to a specific list by submitting a JSON array of object IDs. Upon success, the endpoint returns a confirmation message. This endpoint is vital for building user interfaces that allow dragโ€‘andโ€‘drop or multiโ€‘select additions to collections.","parameters":[{"description":"Space ID","in":"path","name":"space_id","required":true,"schema":{"type":"string"}},{"description":"List ID","in":"path","name":"list_id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"items":{"type":"string"},"type":"array"}}},"description":"List of object IDs","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Objects added successfully"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.ValidationError"}}},"description":"Bad request"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.UnauthorizedError"}}},"description":"Unauthorized"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.NotFoundError"}}},"description":"Not found"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.ServerError"}}},"description":"Internal server error"}},"security":[{"bearerauth":[]}],"summary":"Add objects to list","tags":["lists"]}},"/v1/spaces/{space_id}/lists/{list_id}/objects/{object_id}":{"delete":{"description":"Removes a given object from the specified list in a space. The endpoint takes the space, list, and object identifiers as path parameters. It's subject to rate limiting and returns a success message on completion. It is used for dynamically managing collections without affecting the underlying object data.","parameters":[{"description":"Space ID","in":"path","name":"space_id","required":true,"schema":{"type":"string"}},{"description":"List ID","in":"path","name":"list_id","required":true,"schema":{"type":"string"}},{"description":"Object ID","in":"path","name":"object_id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Objects removed successfully"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.ValidationError"}}},"description":"Bad request"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.UnauthorizedError"}}},"description":"Unauthorized"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.NotFoundError"}}},"description":"Not found"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.ServerError"}}},"description":"Internal server error"}},"security":[{"bearerauth":[]}],"summary":"Remove object from list","tags":["lists"]}}}, + "openapi": "3.1.0", + "servers": [ + {"url":"http://localhost:31009/v1"} + ] }` // SwaggerInfo holds exported Swagger Info so clients can modify it var SwaggerInfo = &swag.Spec{ Version: "1.0", - Host: "localhost:31009", - BasePath: "/v1", - Schemes: []string{}, Title: "Anytype API", Description: "This API allows interaction with Anytype resources such as spaces, objects and types.", InfoInstanceName: "swagger", diff --git a/core/api/docs/swagger.json b/core/api/docs/swagger.json index cf8fe16d36..903f69158a 100644 --- a/core/api/docs/swagger.json +++ b/core/api/docs/swagger.json @@ -1,1614 +1,10 @@ { - "swagger": "2.0", - "info": { - "description": "This API allows interaction with Anytype resources such as spaces, objects and types.", - "title": "Anytype API", - "termsOfService": "https://anytype.io/terms_of_use", - "contact": { - "name": "Anytype Support", - "url": "https://anytype.io/contact", - "email": "support@anytype.io" - }, - "license": { - "name": "Any Source Available License 1.0", - "url": "https://github.com/anyproto/anytype-ts/blob/main/LICENSE.md" - }, - "version": "1.0" - }, - "host": "localhost:31009", - "basePath": "/v1", - "paths": { - "/auth/display_code": { - "post": { - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "auth" - ], - "summary": "Start new challenge", - "parameters": [ - { - "type": "string", - "description": "App name requesting the challenge", - "name": "app_name", - "in": "query", - "required": true - } - ], - "responses": { - "200": { - "description": "Challenge ID", - "schema": { - "$ref": "#/definitions/auth.DisplayCodeResponse" - } - }, - "400": { - "description": "Invalid input", - "schema": { - "$ref": "#/definitions/util.ValidationError" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/util.ServerError" - } - } - } - } - }, - "/auth/token": { - "post": { - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "auth" - ], - "summary": "Retrieve token", - "parameters": [ - { - "type": "string", - "description": "Challenge ID", - "name": "challenge_id", - "in": "query", - "required": true - }, - { - "type": "string", - "description": "4-digit code retrieved from Anytype Desktop app", - "name": "code", - "in": "query", - "required": true - } - ], - "responses": { - "200": { - "description": "Authentication token", - "schema": { - "$ref": "#/definitions/auth.TokenResponse" - } - }, - "400": { - "description": "Invalid input", - "schema": { - "$ref": "#/definitions/util.ValidationError" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/util.ServerError" - } - } - } - } - }, - "/search": { - "post": { - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "search" - ], - "summary": "Search objects across all spaces", - "parameters": [ - { - "type": "integer", - "default": 0, - "description": "The number of items to skip before starting to collect the result set", - "name": "offset", - "in": "query" - }, - { - "maximum": 1000, - "type": "integer", - "default": 100, - "description": "The number of items to return", - "name": "limit", - "in": "query" - }, - { - "description": "Search parameters", - "name": "request", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/search.SearchRequest" - } - } - ], - "responses": { - "200": { - "description": "List of objects", - "schema": { - "$ref": "#/definitions/pagination.PaginatedResponse-object_Object" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/util.UnauthorizedError" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/util.ServerError" - } - } - } - } - }, - "/spaces": { - "get": { - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "spaces" - ], - "summary": "List spaces", - "parameters": [ - { - "type": "integer", - "default": 0, - "description": "The number of items to skip before starting to collect the result set", - "name": "offset", - "in": "query" - }, - { - "maximum": 1000, - "type": "integer", - "default": 100, - "description": "The number of items to return", - "name": "limit", - "in": "query" - } - ], - "responses": { - "200": { - "description": "List of spaces", - "schema": { - "$ref": "#/definitions/pagination.PaginatedResponse-space_Space" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/util.UnauthorizedError" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/util.ServerError" - } - } - } - }, - "post": { - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "spaces" - ], - "summary": "Create space", - "parameters": [ - { - "description": "Space to create", - "name": "name", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/space.CreateSpaceRequest" - } - } - ], - "responses": { - "200": { - "description": "Space created successfully", - "schema": { - "$ref": "#/definitions/space.CreateSpaceResponse" - } - }, - "400": { - "description": "Bad request", - "schema": { - "$ref": "#/definitions/util.ValidationError" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/util.UnauthorizedError" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/util.ServerError" - } - } - } - } - }, - "/spaces/{space_id}/members": { - "get": { - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "spaces" - ], - "summary": "List members", - "parameters": [ - { - "type": "string", - "description": "Space ID", - "name": "space_id", - "in": "path", - "required": true - }, - { - "type": "integer", - "default": 0, - "description": "The number of items to skip before starting to collect the result set", - "name": "offset", - "in": "query" - }, - { - "maximum": 1000, - "type": "integer", - "default": 100, - "description": "The number of items to return", - "name": "limit", - "in": "query" - } - ], - "responses": { - "200": { - "description": "List of members", - "schema": { - "$ref": "#/definitions/pagination.PaginatedResponse-space_Member" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/util.UnauthorizedError" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/util.ServerError" - } - } - } - } - }, - "/spaces/{space_id}/objects": { - "get": { - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "objects" - ], - "summary": "List objects", - "parameters": [ - { - "type": "string", - "description": "Space ID", - "name": "space_id", - "in": "path", - "required": true - }, - { - "type": "integer", - "default": 0, - "description": "The number of items to skip before starting to collect the result set", - "name": "offset", - "in": "query" - }, - { - "maximum": 1000, - "type": "integer", - "default": 100, - "description": "The number of items to return", - "name": "limit", - "in": "query" - } - ], - "responses": { - "200": { - "description": "List of objects", - "schema": { - "$ref": "#/definitions/pagination.PaginatedResponse-object_Object" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/util.UnauthorizedError" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/util.ServerError" - } - } - } - }, - "post": { - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "objects" - ], - "summary": "Create object", - "parameters": [ - { - "type": "string", - "description": "Space ID", - "name": "space_id", - "in": "path", - "required": true - }, - { - "description": "Object to create", - "name": "object", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/object.CreateObjectRequest" - } - } - ], - "responses": { - "200": { - "description": "The created object", - "schema": { - "$ref": "#/definitions/object.ObjectResponse" - } - }, - "400": { - "description": "Bad request", - "schema": { - "$ref": "#/definitions/util.ValidationError" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/util.UnauthorizedError" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/util.ServerError" - } - } - } - } - }, - "/spaces/{space_id}/objects/{object_id}": { - "get": { - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "objects" - ], - "summary": "Get object", - "parameters": [ - { - "type": "string", - "description": "Space ID", - "name": "space_id", - "in": "path", - "required": true - }, - { - "type": "string", - "description": "Object ID", - "name": "object_id", - "in": "path", - "required": true - } - ], - "responses": { - "200": { - "description": "The requested object", - "schema": { - "$ref": "#/definitions/object.ObjectResponse" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/util.UnauthorizedError" - } - }, - "404": { - "description": "Resource not found", - "schema": { - "$ref": "#/definitions/util.NotFoundError" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/util.ServerError" - } - } - } - }, - "delete": { - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "objects" - ], - "summary": "Delete object", - "parameters": [ - { - "type": "string", - "description": "Space ID", - "name": "space_id", - "in": "path", - "required": true - }, - { - "type": "string", - "description": "Object ID", - "name": "object_id", - "in": "path", - "required": true - } - ], - "responses": { - "200": { - "description": "The deleted object", - "schema": { - "$ref": "#/definitions/object.ObjectResponse" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/util.UnauthorizedError" - } - }, - "403": { - "description": "Forbidden", - "schema": { - "$ref": "#/definitions/util.ForbiddenError" - } - }, - "404": { - "description": "Resource not found", - "schema": { - "$ref": "#/definitions/util.NotFoundError" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/util.ServerError" - } - } - } - } - }, - "/spaces/{space_id}/objects/{object_id}/export/{format}": { - "post": { - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "export" - ], - "summary": "Export object", - "parameters": [ - { - "type": "string", - "description": "Space ID", - "name": "space_id", - "in": "path", - "required": true - }, - { - "type": "string", - "description": "Object ID", - "name": "object_id", - "in": "path", - "required": true - }, - { - "enum": [ - "markdown", - "protobuf" - ], - "type": "string", - "description": "Export format", - "name": "format", - "in": "path", - "required": true - } - ], - "responses": { - "200": { - "description": "Object exported successfully", - "schema": { - "$ref": "#/definitions/export.ObjectExportResponse" - } - }, - "400": { - "description": "Bad request", - "schema": { - "$ref": "#/definitions/util.ValidationError" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/util.UnauthorizedError" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/util.ServerError" - } - } - } - } - }, - "/spaces/{space_id}/search": { - "post": { - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "search" - ], - "summary": "Search objects within a space", - "parameters": [ - { - "type": "string", - "description": "Space ID", - "name": "space_id", - "in": "path", - "required": true - }, - { - "type": "integer", - "default": 0, - "description": "The number of items to skip before starting to collect the result set", - "name": "offset", - "in": "query" - }, - { - "maximum": 1000, - "type": "integer", - "default": 100, - "description": "The number of items to return", - "name": "limit", - "in": "query" - }, - { - "description": "Search parameters", - "name": "request", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/search.SearchRequest" - } - } - ], - "responses": { - "200": { - "description": "List of objects", - "schema": { - "$ref": "#/definitions/pagination.PaginatedResponse-object_Object" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/util.UnauthorizedError" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/util.ServerError" - } - } - } - } - }, - "/spaces/{space_id}/types": { - "get": { - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "types" - ], - "summary": "List types", - "parameters": [ - { - "type": "string", - "description": "Space ID", - "name": "space_id", - "in": "path", - "required": true - }, - { - "type": "integer", - "default": 0, - "description": "The number of items to skip before starting to collect the result set", - "name": "offset", - "in": "query" - }, - { - "maximum": 1000, - "type": "integer", - "default": 100, - "description": "The number of items to return", - "name": "limit", - "in": "query" - } - ], - "responses": { - "200": { - "description": "List of types", - "schema": { - "$ref": "#/definitions/pagination.PaginatedResponse-object_Type" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/util.UnauthorizedError" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/util.ServerError" - } - } - } - } - }, - "/spaces/{space_id}/types/{type_id}": { - "get": { - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "types" - ], - "summary": "Get type", - "parameters": [ - { - "type": "string", - "description": "Space ID", - "name": "space_id", - "in": "path", - "required": true - }, - { - "type": "string", - "description": "Type ID", - "name": "type_id", - "in": "path", - "required": true - } - ], - "responses": { - "200": { - "description": "The requested type", - "schema": { - "$ref": "#/definitions/object.TypeResponse" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/util.UnauthorizedError" - } - }, - "404": { - "description": "Resource not found", - "schema": { - "$ref": "#/definitions/util.NotFoundError" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/util.ServerError" - } - } - } - } - }, - "/spaces/{space_id}/types/{type_id}/templates": { - "get": { - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "types" - ], - "summary": "List templates", - "parameters": [ - { - "type": "string", - "description": "Space ID", - "name": "space_id", - "in": "path", - "required": true - }, - { - "type": "string", - "description": "Type ID", - "name": "type_id", - "in": "path", - "required": true - }, - { - "type": "integer", - "default": 0, - "description": "The number of items to skip before starting to collect the result set", - "name": "offset", - "in": "query" - }, - { - "maximum": 1000, - "type": "integer", - "default": 100, - "description": "The number of items to return", - "name": "limit", - "in": "query" - } - ], - "responses": { - "200": { - "description": "List of templates", - "schema": { - "$ref": "#/definitions/pagination.PaginatedResponse-object_Template" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/util.UnauthorizedError" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/util.ServerError" - } - } - } - } - }, - "/spaces/{space_id}/types/{type_id}/templates/{template_id}": { - "get": { - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "types" - ], - "summary": "Get template", - "parameters": [ - { - "type": "string", - "description": "Space ID", - "name": "space_id", - "in": "path", - "required": true - }, - { - "type": "string", - "description": "Type ID", - "name": "type_id", - "in": "path", - "required": true - }, - { - "type": "string", - "description": "Template ID", - "name": "template_id", - "in": "path", - "required": true - } - ], - "responses": { - "200": { - "description": "The requested template", - "schema": { - "$ref": "#/definitions/object.TemplateResponse" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/util.UnauthorizedError" - } - }, - "404": { - "description": "Resource not found", - "schema": { - "$ref": "#/definitions/util.NotFoundError" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/util.ServerError" - } - } - } - } - } - }, - "definitions": { - "auth.DisplayCodeResponse": { - "type": "object", - "properties": { - "challenge_id": { - "type": "string", - "example": "67647f5ecda913e9a2e11b26" - } - } - }, - "auth.TokenResponse": { - "type": "object", - "properties": { - "app_key": { - "type": "string", - "example": "zhSG/zQRmgADyilWPtgdnfo1qD60oK02/SVgi1GaFt6=" - }, - "session_token": { - "type": "string", - "example": "eyJhbGciOeJIRzI1NiIsInR5cCI6IkpXVCJ1.eyJzZWVkIjaiY0dmVndlUnAifQ.Y1EZecYnwmvMkrXKOa2XJnAbaRt34urBabe06tmDQII" - } - } - }, - "export.ObjectExportResponse": { - "type": "object", - "properties": { - "path": { - "type": "string", - "example": "/path/to/export" - } - } - }, - "object.Block": { - "type": "object", - "properties": { - "align": { - "type": "string", - "enum": [ - "AlignLeft", - "AlignCenter", - "AlignRight", - "AlignJustify" - ], - "example": "AlignLeft" - }, - "background_color": { - "type": "string", - "example": "red" - }, - "children_ids": { - "type": "array", - "items": { - "type": "string" - }, - "example": [ - "['6797ce8ecda913cde14b02dc']" - ] - }, - "file": { - "$ref": "#/definitions/object.File" - }, - "id": { - "type": "string", - "example": "64394517de52ad5acb89c66c" - }, - "text": { - "$ref": "#/definitions/object.Text" - }, - "vertical_align": { - "type": "string", - "enum": [ - "VerticalAlignTop", - "VerticalAlignMiddle", - "VerticalAlignBottom" - ], - "example": "VerticalAlignTop" - } - } - }, - "object.CreateObjectRequest": { - "type": "object", - "properties": { - "body": { - "type": "string", - "example": "Object Body" - }, - "description": { - "type": "string", - "example": "Object Description" - }, - "icon": { - "type": "string", - "example": "๐Ÿ“„" - }, - "name": { - "type": "string", - "example": "Object Name" - }, - "object_type_unique_key": { - "type": "string", - "example": "ot-page" - }, - "source": { - "type": "string", - "example": "https://source.com" - }, - "template_id": { - "type": "string", - "example": "bafyreictrp3obmnf6dwejy5o4p7bderaaia4bdg2psxbfzf44yya5uutge" - } - } - }, - "object.Detail": { - "type": "object", - "properties": { - "details": { - "type": "object", - "additionalProperties": true - }, - "id": { - "type": "string", - "enum": [ - "last_modified_date", - "last_modified_by", - "created_date", - "created_by", - "last_opened_date", - "tags" - ], - "example": "last_modified_date" - } - } - }, - "object.File": { - "type": "object", - "properties": { - "added_at": { - "type": "integer" - }, - "hash": { - "type": "string" - }, - "mime": { - "type": "string" - }, - "name": { - "type": "string" - }, - "size": { - "type": "integer" - }, - "state": { - "type": "string" - }, - "style": { - "type": "string" - }, - "target_object_id": { - "type": "string" - }, - "type": { - "type": "string" - } - } - }, - "object.Object": { - "type": "object", - "properties": { - "blocks": { - "type": "array", - "items": { - "$ref": "#/definitions/object.Block" - } - }, - "details": { - "type": "array", - "items": { - "$ref": "#/definitions/object.Detail" - } - }, - "icon": { - "type": "string", - "example": "๐Ÿ“„" - }, - "id": { - "type": "string", - "example": "bafyreie6n5l5nkbjal37su54cha4coy7qzuhrnajluzv5qd5jvtsrxkequ" - }, - "layout": { - "type": "string", - "example": "basic" - }, - "name": { - "type": "string", - "example": "Object Name" - }, - "root_id": { - "type": "string", - "example": "bafyreicypzj6uvu54664ucv3hmbsd5cmdy2dv4fwua26sciq74khzpyn4u" - }, - "snippet": { - "type": "string", - "example": "The beginning of the object body..." - }, - "space_id": { - "type": "string", - "example": "bafyreigyfkt6rbv24sbv5aq2hko3bhmv5xxlf22b4bypdu6j7hnphm3psq.23me69r569oi1" - }, - "type": { - "type": "string", - "example": "Page" - } - } - }, - "object.ObjectResponse": { - "type": "object", - "properties": { - "object": { - "$ref": "#/definitions/object.Object" - } - } - }, - "object.Template": { - "type": "object", - "properties": { - "icon": { - "type": "string", - "example": "๐Ÿ“„" - }, - "id": { - "type": "string", - "example": "bafyreictrp3obmnf6dwejy5o4p7bderaaia4bdg2psxbfzf44yya5uutge" - }, - "name": { - "type": "string", - "example": "Template Name" - }, - "type": { - "type": "string", - "example": "template" - } - } - }, - "object.TemplateResponse": { - "type": "object", - "properties": { - "template": { - "$ref": "#/definitions/object.Template" - } - } - }, - "object.Text": { - "type": "object", - "properties": { - "checked": { - "type": "boolean", - "example": true - }, - "color": { - "type": "string", - "example": "red" - }, - "icon": { - "type": "string", - "example": "๐Ÿ“„" - }, - "style": { - "type": "string", - "enum": [ - "Paragraph", - "Header1", - "Header2", - "Header3", - "Header4", - "Quote", - "Code", - "Title", - "Checkbox", - "Marked", - "Numbered", - "Toggle", - "Description", - "Callout" - ], - "example": "Paragraph" - }, - "text": { - "type": "string", - "example": "Some text" - } - } - }, - "object.Type": { - "type": "object", - "properties": { - "icon": { - "type": "string", - "example": "๐Ÿ“„" - }, - "id": { - "type": "string", - "example": "bafyreigyb6l5szohs32ts26ku2j42yd65e6hqy2u3gtzgdwqv6hzftsetu" - }, - "name": { - "type": "string", - "example": "Page" - }, - "recommended_layout": { - "type": "string", - "example": "todo" - }, - "type": { - "type": "string", - "example": "type" - }, - "unique_key": { - "type": "string", - "example": "ot-page" - } - } - }, - "object.TypeResponse": { - "type": "object", - "properties": { - "type": { - "$ref": "#/definitions/object.Type" - } - } - }, - "pagination.PaginatedResponse-object_Object": { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/definitions/object.Object" - } - }, - "pagination": { - "$ref": "#/definitions/pagination.PaginationMeta" - } - } - }, - "pagination.PaginatedResponse-object_Template": { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/definitions/object.Template" - } - }, - "pagination": { - "$ref": "#/definitions/pagination.PaginationMeta" - } - } - }, - "pagination.PaginatedResponse-object_Type": { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/definitions/object.Type" - } - }, - "pagination": { - "$ref": "#/definitions/pagination.PaginationMeta" - } - } - }, - "pagination.PaginatedResponse-space_Member": { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/definitions/space.Member" - } - }, - "pagination": { - "$ref": "#/definitions/pagination.PaginationMeta" - } - } - }, - "pagination.PaginatedResponse-space_Space": { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/definitions/space.Space" - } - }, - "pagination": { - "$ref": "#/definitions/pagination.PaginationMeta" - } - } - }, - "pagination.PaginationMeta": { - "type": "object", - "properties": { - "has_more": { - "description": "whether there are more items available", - "type": "boolean", - "example": true - }, - "limit": { - "description": "the current limit", - "type": "integer", - "example": 100 - }, - "offset": { - "description": "the current offset", - "type": "integer", - "example": 0 - }, - "total": { - "description": "the total number of items available on that endpoint", - "type": "integer", - "example": 1024 - } - } - }, - "search.SearchRequest": { - "type": "object", - "properties": { - "query": { - "type": "string" - }, - "sort": { - "$ref": "#/definitions/search.SortOptions" - }, - "types": { - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "search.SortOptions": { - "type": "object", - "properties": { - "direction": { - "type": "string", - "default": "desc", - "enum": [ - "asc", - "desc" - ] - }, - "timestamp": { - "type": "string", - "default": "last_modified_date", - "enum": [ - "created_date", - "last_modified_date", - "last_opened_date" - ] - } - } - }, - "space.CreateSpaceRequest": { - "type": "object", - "properties": { - "name": { - "type": "string", - "example": "New Space" - } - } - }, - "space.CreateSpaceResponse": { - "type": "object", - "properties": { - "space": { - "$ref": "#/definitions/space.Space" - } - } - }, - "space.Member": { - "type": "object", - "properties": { - "global_name": { - "type": "string", - "example": "john.any" - }, - "icon": { - "type": "string", - "example": "http://127.0.0.1:31006/image/bafybeieptz5hvcy6txplcvphjbbh5yjc2zqhmihs3owkh5oab4ezauzqay?width=100" - }, - "id": { - "type": "string", - "example": "_participant_bafyreigyfkt6rbv24sbv5aq2hko1bhmv5xxlf22b4bypdu6j7hnphm3psq_23me69r569oi1_AAjEaEwPF4nkEh9AWkqEnzcQ8HziBB4ETjiTpvRCQvWnSMDZ" - }, - "identity": { - "type": "string", - "example": "AAjEaEwPF4nkEh7AWkqEnzcQ8HziGB4ETjiTpvRCQvWnSMDZ" - }, - "name": { - "type": "string", - "example": "John Doe" - }, - "role": { - "type": "string", - "enum": [ - "Reader", - "Writer", - "Owner", - "NoPermission" - ], - "example": "Owner" - }, - "type": { - "type": "string", - "example": "member" - } - } - }, - "space.Space": { - "type": "object", - "properties": { - "account_space_id": { - "type": "string", - "example": "bafyreihpd2knon5wbljhtfeg3fcqtg3i2pomhhnigui6lrjmzcjzep7gcy.23me69r569oi1" - }, - "analytics_id": { - "type": "string", - "example": "624aecdd-4797-4611-9d61-a2ae5f53cf1c" - }, - "archive_object_id": { - "type": "string", - "example": "bafyreialsgoyflf3etjm3parzurivyaukzivwortf32b4twnlwpwocsrri" - }, - "device_id": { - "type": "string", - "example": "12D3KooWGZMJ4kQVyQVXaj7gJPZr3RZ2nvd9M2Eq2pprEoPih9WF" - }, - "gateway_url": { - "type": "string", - "example": "http://127.0.0.1:31006" - }, - "home_object_id": { - "type": "string", - "example": "bafyreie4qcl3wczb4cw5hrfyycikhjyh6oljdis3ewqrk5boaav3sbwqya" - }, - "icon": { - "type": "string", - "example": "http://127.0.0.1:31006/image/bafybeieptz5hvcy6txplcvphjbbh5yjc2zqhmihs3owkh5oab4ezauzqay" - }, - "id": { - "type": "string", - "example": "bafyreigyfkt6rbv24sbv5aq2hko3bhmv5xxlf22b4bypdu6j7hnphm3psq.23me69r569oi1" - }, - "local_storage_path": { - "type": "string", - "example": "/Users/johndoe/Library/Application Support/Anytype/data/AAHTtt1wuQEnaYBNZ2Cyfcvs6DqPqxgn8VXDVk4avsUkMuha" - }, - "marketplace_workspace_id": { - "type": "string", - "example": "_anytype_marketplace" - }, - "name": { - "type": "string", - "example": "Space Name" - }, - "network_id": { - "type": "string", - "example": "N83gJpVd9MuNRZAuJLZ7LiMntTThhPc6DtzWWVjb1M3PouVU" - }, - "profile_object_id": { - "type": "string", - "example": "bafyreiaxhwreshjqwndpwtdsu4mtihaqhhmlygqnyqpfyfwlqfq3rm3gw4" - }, - "space_view_id": { - "type": "string", - "example": "bafyreigzv3vq7qwlrsin6njoduq727ssnhwd6bgyfj6nm4hv3pxoc2rxhy" - }, - "tech_space_id": { - "type": "string", - "example": "bafyreif4xuwncrjl6jajt4zrrfnylpki476nv2w64yf42ovt7gia7oypii.23me69r569oi1" - }, - "timezone": { - "type": "string", - "example": "" - }, - "type": { - "type": "string", - "example": "space" - }, - "widgets_id": { - "type": "string", - "example": "bafyreialj7pceh53mifm5dixlho47ke4qjmsn2uh4wsjf7xq2pnlo5xfva" - }, - "workspace_object_id": { - "type": "string", - "example": "bafyreiapey2g6e6za4zfxvlgwdy4hbbfu676gmwrhnqvjbxvrchr7elr3y" - } - } - }, - "util.ForbiddenError": { - "type": "object", - "properties": { - "error": { - "type": "object", - "properties": { - "message": { - "type": "string" - } - } - } - } - }, - "util.NotFoundError": { - "type": "object", - "properties": { - "error": { - "type": "object", - "properties": { - "message": { - "type": "string" - } - } - } - } - }, - "util.ServerError": { - "type": "object", - "properties": { - "error": { - "type": "object", - "properties": { - "message": { - "type": "string" - } - } - } - } - }, - "util.UnauthorizedError": { - "type": "object", - "properties": { - "error": { - "type": "object", - "properties": { - "message": { - "type": "string" - } - } - } - } - }, - "util.ValidationError": { - "type": "object", - "properties": { - "error": { - "type": "object", - "properties": { - "message": { - "type": "string" - } - } - } - } - } - }, - "securityDefinitions": { - "BasicAuth": { - "type": "basic" - } - }, - "externalDocs": { - "description": "OpenAPI", - "url": "https://swagger.io/resources/open-api/" - } + "components": {"schemas":{"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","AlignRight","AlignJustify"],"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","uniqueItems":false},"file":{"$ref":"#/components/schemas/object.File"},"id":{"description":"The id of the block","example":"64394517de52ad5acb89c66c","type":"string"},"relation":{"$ref":"#/components/schemas/object.Relation"},"text":{"$ref":"#/components/schemas/object.Text"},"vertical_align":{"description":"The vertical alignment of the block","enum":["VerticalAlignTop","VerticalAlignMiddle","VerticalAlignBottom"],"example":"VerticalAlignTop","type":"string"}},"type":"object"},"object.CreateObjectRequest":{"properties":{"body":{"description":"The body of the object","example":"This is the body of the object. Markdown syntax is supported here.","type":"string"},"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":"๐Ÿ“„","type":"string"},"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":{"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"},"object.Detail":{"properties":{"details":{"additionalProperties":{},"description":"The details","type":"object"},"id":{"description":"The id of the detail","enum":["last_modified_date","last_modified_by","created_date","created_by","last_opened_date","tags"],"example":"last_modified_date","type":"string"}},"type":"object"},"object.File":{"description":"The file of the block, if applicable","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":{"description":"The object","properties":{"blocks":{"description":"The blocks of the object","items":{"$ref":"#/components/schemas/object.Block"},"type":"array","uniqueItems":false},"details":{"description":"The details of the object","items":{"$ref":"#/components/schemas/object.Detail"},"type":"array","uniqueItems":false},"icon":{"description":"The icon of the object","example":"๐Ÿ“„","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":{"description":"The name of the object","example":"My object","type":"string"},"object":{"description":"The data model of the object","example":"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":{"$ref":"#/components/schemas/object.Type"}},"type":"object"},"object.ObjectResponse":{"properties":{"object":{"$ref":"#/components/schemas/object.Object"}},"type":"object"},"object.Relation":{"description":"The relation of the block, if applicable","properties":{"id":{"type":"string"}},"type":"object"},"object.Template":{"description":"The template","properties":{"icon":{"description":"The icon of the template","example":"๐Ÿ“„","type":"string"},"id":{"description":"The id of the template","example":"bafyreictrp3obmnf6dwejy5o4p7bderaaia4bdg2psxbfzf44yya5uutge","type":"string"},"name":{"description":"The name of the template","example":"My template","type":"string"},"object":{"description":"The data model of the object","example":"template","type":"string"}},"type":"object"},"object.TemplateResponse":{"properties":{"template":{"$ref":"#/components/schemas/object.Template"}},"type":"object"},"object.Text":{"description":"The text of the block, if applicable","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":"๐Ÿ“„","type":"string"},"style":{"description":"The style of the text","enum":["Paragraph","Header1","Header2","Header3","Header4","Quote","Code","Title","Checkbox","Marked","Numbered","Toggle","Description","Callout"],"example":"Paragraph","type":"string"},"text":{"description":"The text","example":"Some text...","type":"string"}},"type":"object"},"object.Type":{"description":"The type of the object","properties":{"icon":{"description":"The icon of the type","example":"๐Ÿ“„","type":"string"},"id":{"description":"The id of the type","example":"bafyreigyb6l5szohs32ts26ku2j42yd65e6hqy2u3gtzgdwqv6hzftsetu","type":"string"},"name":{"description":"The name of the type","example":"Page","type":"string"},"object":{"description":"The data model of the object","example":"type","type":"string"},"recommended_layout":{"description":"The recommended layout of the type","example":"todo","type":"string"},"unique_key":{"description":"The unique key of the type","example":"ot-page","type":"string"}},"type":"object"},"object.TypeResponse":{"properties":{"type":{"$ref":"#/components/schemas/object.Type"}},"type":"object"},"pagination.PaginatedResponse-object_Object":{"properties":{"data":{"description":"The list of items in the current result set","items":{"$ref":"#/components/schemas/object.Object"},"type":"array","uniqueItems":false},"pagination":{"$ref":"#/components/schemas/pagination.PaginationMeta"}},"type":"object"},"pagination.PaginatedResponse-object_Template":{"properties":{"data":{"description":"The list of items in the current result set","items":{"$ref":"#/components/schemas/object.Template"},"type":"array","uniqueItems":false},"pagination":{"$ref":"#/components/schemas/pagination.PaginationMeta"}},"type":"object"},"pagination.PaginatedResponse-object_Type":{"properties":{"data":{"description":"The list of items in the current result set","items":{"$ref":"#/components/schemas/object.Type"},"type":"array","uniqueItems":false},"pagination":{"$ref":"#/components/schemas/pagination.PaginationMeta"}},"type":"object"},"pagination.PaginatedResponse-space_Member":{"properties":{"data":{"description":"The list of items in the current result set","items":{"$ref":"#/components/schemas/space.Member"},"type":"array","uniqueItems":false},"pagination":{"$ref":"#/components/schemas/pagination.PaginationMeta"}},"type":"object"},"pagination.PaginatedResponse-space_Space":{"properties":{"data":{"description":"The list of items in the current result set","items":{"$ref":"#/components/schemas/space.Space"},"type":"array","uniqueItems":false},"pagination":{"$ref":"#/components/schemas/pagination.PaginationMeta"}},"type":"object"},"pagination.PaginationMeta":{"description":"The pagination metadata for the response","properties":{"has_more":{"description":"Indicates if there are more items available beyond the current result set","example":true,"type":"boolean"},"limit":{"description":"The maximum number of items returned in the result set","example":100,"type":"integer"},"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 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":"#/components/schemas/search.SortOptions"},"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","uniqueItems":false}},"type":"object"},"search.SortOptions":{"description":"The sorting criteria and direction for the search results","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","last_opened_date"],"type":"string"}},"type":"object"},"space.CreateSpaceRequest":{"properties":{"name":{"description":"The name of the space","example":"New Space","type":"string"}},"type":"object"},"space.Member":{"description":"The 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"},"object":{"description":"The data model of the object","example":"member","type":"string"},"role":{"description":"The role of the member","enum":["Reader","Writer","Owner","NoPermission"],"example":"Owner","type":"string"}},"type":"object"},"space.MemberResponse":{"properties":{"member":{"$ref":"#/components/schemas/space.Member"}},"type":"object"},"space.Space":{"description":"The 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":{"description":"The name of the space","example":"My Space","type":"string"},"network_id":{"description":"The network id of the space","example":"N83gJpVd9MuNRZAuJLZ7LiMntTThhPc6DtzWWVjb1M3PouVU","type":"string"},"object":{"description":"The data model of the object","example":"space","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"},"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":{"$ref":"#/components/schemas/space.Space"}},"type":"object"},"util.ForbiddenError":{"properties":{"error":{"properties":{"message":{"example":"Forbidden","type":"string"}},"type":"object"}},"type":"object"},"util.NotFoundError":{"properties":{"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"},"util.ServerError":{"properties":{"error":{"properties":{"message":{"example":"Internal server error","type":"string"}},"type":"object"}},"type":"object"},"util.UnauthorizedError":{"properties":{"error":{"properties":{"message":{"example":"Unauthorized","type":"string"}},"type":"object"}},"type":"object"},"util.ValidationError":{"properties":{"error":{"properties":{"message":{"example":"Bad request","type":"string"}},"type":"object"}},"type":"object"}},"securitySchemes":{"bearerauth":{"bearerFormat":"JWT","scheme":"bearer","type":"http"}}}, + "info": {"contact":{"email":"support@anytype.io","name":"Anytype Support","url":"https://anytype.io/contact"},"description":"This API allows interaction with Anytype resources such as spaces, objects and types.","license":{"name":"Any Source Available License 1.0","url":"https://github.com/anyproto/anytype-ts/blob/main/LICENSE.md"},"termsOfService":"https://anytype.io/terms_of_use","title":"Anytype API","version":"1.0"}, + "externalDocs": {"description":"OpenAPI","url":"https://swagger.io/resources/open-api/"}, + "paths": {"/auth/display_code":{"post":{"description":"This endpoint initiates a secure authentication flow by generating a new challenge. Clients must supply the name of the application (via a query parameter) that is requesting authentication. On success, the service returns a unique challenge ID. This challenge ID must then be used with the token endpoint (see below) to solve the challenge and retrieve an authentication token. In essence, this endpoint โ€œboots upโ€ the login process and is the first step in a multi-phase authentication sequence.","parameters":[{"description":"App name requesting the challenge","in":"query","name":"app_name","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/auth.DisplayCodeResponse"}}},"description":"Challenge ID"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.ValidationError"}}},"description":"Invalid input"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.ServerError"}}},"description":"Internal server error"}},"summary":"Start new challenge","tags":["auth"]}},"/auth/token":{"post":{"description":"After receiving a challenge ID from the display_code endpoint, the client calls this endpoint to provide the corresponding 4-digit code (also via a query parameter) along with the challenge ID. The endpoint verifies that the challenge solution is correct and, if it is, returns an ephemeral session token together with a permanent app key. These tokens are then used in subsequent API requests to authorize access. This endpoint is central to ensuring that only properly authenticated sessions can access further resources.","parameters":[{"description":"Challenge ID","in":"query","name":"challenge_id","required":true,"schema":{"type":"string"}},{"description":"4-digit code retrieved from Anytype Desktop app","in":"query","name":"code","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/auth.TokenResponse"}}},"description":"Authentication token"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.ValidationError"}}},"description":"Invalid input"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.ServerError"}}},"description":"Internal server error"}},"summary":"Retrieve token","tags":["auth"]}},"/search":{"post":{"description":"This endpoint executes a global search over every space the user has access to. It accepts pagination parameters (offset and limit) and a JSON body containing search criteria. The criteria include a search query string, an optional list of object types, and sort options (e.g. ascending/descending by creation, modification, or last opened dates). Internally, the endpoint aggregates results from each space, merges and sorts them (after last modified date by default), and returns a unified, paginated list of objects that match the search parameters.","parameters":[{"description":"The number of items to skip before starting to collect the result set","in":"query","name":"offset","schema":{"default":0,"type":"integer"}},{"description":"The number of items to return","in":"query","name":"limit","schema":{"default":100,"maximum":1000,"type":"integer"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/search.SearchRequest"}}},"description":"Search parameters","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/pagination.PaginatedResponse-object_Object"}}},"description":"List of objects"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.UnauthorizedError"}}},"description":"Unauthorized"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.ServerError"}}},"description":"Internal server error"}},"security":[{"bearerauth":[]}],"summary":"Search objects across all spaces","tags":["search"]}},"/spaces":{"get":{"description":"Retrieves a paginated list of all spaces that are accessible by the authenticated user. Each space record contains detailed information such as the space ID, name, icon (derived either from an emoji or image URL), and additional metadata. This endpoint is key to displaying a userโ€™s workspaces.","parameters":[{"description":"The number of items to skip before starting to collect the result set","in":"query","name":"offset","schema":{"default":0,"type":"integer"}},{"description":"The number of items to return","in":"query","name":"limit","schema":{"default":100,"maximum":1000,"type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/pagination.PaginatedResponse-space_Space"}}},"description":"List of spaces"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.UnauthorizedError"}}},"description":"Unauthorized"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.ServerError"}}},"description":"Internal server error"}},"security":[{"bearerauth":[]}],"summary":"List spaces","tags":["spaces"]},"post":{"description":"Creates a new workspace (or space) based on a supplied name in the JSON request body. The endpoint is subject to rate limiting and automatically applies default configurations such as generating a random icon and initializing the workspace with default settings (for example, a default dashboard or home page). On success, the new spaceโ€™s full metadata is returned, enabling the client to immediately switch context to the new space.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/space.CreateSpaceRequest"}}},"description":"Space to create","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/space.SpaceResponse"}}},"description":"Space created successfully"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.ValidationError"}}},"description":"Bad request"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.UnauthorizedError"}}},"description":"Unauthorized"},"423":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.RateLimitError"}}},"description":"Rate limit exceeded"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.ServerError"}}},"description":"Internal server error"}},"security":[{"bearerauth":[]}],"summary":"Create space","tags":["spaces"]}},"/spaces/{space_id}":{"get":{"description":"Fetches full details about a single space identified by its space ID. The response includes metadata such as the space name, icon, and various workspace IDs (home, archive, profile, etc.). This detailed view supports use cases such as displaying space-specific settings.","parameters":[{"description":"Space ID","in":"path","name":"space_id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/space.SpaceResponse"}}},"description":"Space"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.UnauthorizedError"}}},"description":"Unauthorized"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.NotFoundError"}}},"description":"Space not found"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.ServerError"}}},"description":"Internal server error"}},"security":[{"bearerauth":[]}],"summary":"Get space","tags":["spaces"]}},"/spaces/{space_id}/members":{"get":{"description":"Returns a paginated list of members belonging to the specified space. Each member record includes the memberโ€™s profile ID, name, icon (which may be derived from an emoji or image), network identity, global name, and role (e.g. Reader, Writer, Owner). This endpoint supports collaborative features by allowing clients to show who is in a space and manage access rights.","parameters":[{"description":"Space ID","in":"path","name":"space_id","required":true,"schema":{"type":"string"}},{"description":"The number of items to skip before starting to collect the result set","in":"query","name":"offset","schema":{"default":0,"type":"integer"}},{"description":"The number of items to return","in":"query","name":"limit","schema":{"default":100,"maximum":1000,"type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/pagination.PaginatedResponse-space_Member"}}},"description":"List of members"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.UnauthorizedError"}}},"description":"Unauthorized"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.ServerError"}}},"description":"Internal server error"}},"security":[{"bearerauth":[]}],"summary":"List members","tags":["spaces"]}},"/spaces/{space_id}/members/{member_id}":{"get":{"description":"Fetches detailed information about a single member within a space. The endpoint returns the memberโ€™s identifier, name, icon, identity, global name, and role. This is useful for user profile pages, permission management, and displaying member-specific information in collaborative environments.","parameters":[{"description":"Space ID","in":"path","name":"space_id","required":true,"schema":{"type":"string"}},{"description":"Member ID","in":"path","name":"member_id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/space.MemberResponse"}}},"description":"Member"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.UnauthorizedError"}}},"description":"Unauthorized"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.NotFoundError"}}},"description":"Member not found"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.ServerError"}}},"description":"Internal server error"}},"security":[{"bearerauth":[]}],"summary":"Get member","tags":["spaces"]}},"/spaces/{space_id}/objects":{"get":{"description":"Retrieves a paginated list of objects in the given space. The endpoint takes query parameters for pagination (offset and limit) and returns detailed data about each object including its ID, name, icon, type information, a snippet of the content (if applicable), layout, space ID, blocks and details. It is intended for building views where users can see all objects in a space at a glance.","parameters":[{"description":"Space ID","in":"path","name":"space_id","required":true,"schema":{"type":"string"}},{"description":"The number of items to skip before starting to collect the result set","in":"query","name":"offset","schema":{"default":0,"type":"integer"}},{"description":"The number of items to return","in":"query","name":"limit","schema":{"default":100,"maximum":1000,"type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/pagination.PaginatedResponse-object_Object"}}},"description":"List of objects"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.UnauthorizedError"}}},"description":"Unauthorized"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.ServerError"}}},"description":"Internal server error"}},"security":[{"bearerauth":[]}],"summary":"List objects","tags":["objects"]},"post":{"description":"Creates a new object in the specified space using a JSON payload. The creation process is subject to rate limiting. The payload must include key details such as the object name, icon, description, body content (which may support Markdown), source URL (required for bookmark objects), template identifier, and the unique key for the object type. Post-creation, additional operations (like setting featured relations or fetching bookmark metadata) may occur. The endpoint then returns the full object data, ready for further interactions.","parameters":[{"description":"Space ID","in":"path","name":"space_id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/object.CreateObjectRequest"}}},"description":"Object to create","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/object.ObjectResponse"}}},"description":"The created object"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.ValidationError"}}},"description":"Bad request"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.UnauthorizedError"}}},"description":"Unauthorized"},"423":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.RateLimitError"}}},"description":"Rate limit exceeded"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.ServerError"}}},"description":"Internal server error"}},"security":[{"bearerauth":[]}],"summary":"Create object","tags":["objects"]}},"/spaces/{space_id}/objects/{object_id}":{"delete":{"description":"This endpoint โ€œdeletesโ€ an object by marking it as archived. The deletion process is performed safely and is subject to rate limiting. It returns the objectโ€™s details after it has been archived. Proper error handling is in place for situations such as when the object isnโ€™t found or the deletion cannot be performed because of permission issues.","parameters":[{"description":"Space ID","in":"path","name":"space_id","required":true,"schema":{"type":"string"}},{"description":"Object ID","in":"path","name":"object_id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/object.ObjectResponse"}}},"description":"The deleted object"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.UnauthorizedError"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.ForbiddenError"}}},"description":"Forbidden"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.NotFoundError"}}},"description":"Resource not found"},"423":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.RateLimitError"}}},"description":"Rate limit exceeded"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.ServerError"}}},"description":"Internal server error"}},"security":[{"bearerauth":[]}],"summary":"Delete object","tags":["objects"]},"get":{"description":"Fetches the full details of a single object identified by the object ID within the specified space. The response includes not only basic metadata (ID, name, icon, type) but also the complete set of blocks (which may include text, files, and relations) and extra details (such as timestamps and linked member information). This endpoint is essential when a client needs to render or edit the full object view.","parameters":[{"description":"Space ID","in":"path","name":"space_id","required":true,"schema":{"type":"string"}},{"description":"Object ID","in":"path","name":"object_id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/object.ObjectResponse"}}},"description":"The requested object"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.UnauthorizedError"}}},"description":"Unauthorized"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.NotFoundError"}}},"description":"Resource not found"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.ServerError"}}},"description":"Internal server error"}},"security":[{"bearerauth":[]}],"summary":"Get object","tags":["objects"]}},"/spaces/{space_id}/objects/{object_id}/export/{format}":{"post":{"description":"This endpoint exports a single object from the specified space into a desired format. The export format is provided as a path parameter (currently supporting โ€œmarkdownโ€ and โ€œprotobufโ€), and clients can optionally specify an export path in the request body. The endpoint calls an export service which converts the objectโ€™s content into the requested format and returns the file path where the exported data is stored. It is useful for data backup, sharing, or further processing.","parameters":[{"description":"Space ID","in":"path","name":"space_id","required":true,"schema":{"type":"string"}},{"description":"Object ID","in":"path","name":"object_id","required":true,"schema":{"type":"string"}},{"description":"Export format","in":"path","name":"format","required":true,"schema":{"enum":["markdown","protobuf"],"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/export.ObjectExportResponse"}}},"description":"Object exported successfully"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.ValidationError"}}},"description":"Bad request"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.UnauthorizedError"}}},"description":"Unauthorized"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.ServerError"}}},"description":"Internal server error"}},"security":[{"bearerauth":[]}],"summary":"Export object","tags":["export"]}},"/spaces/{space_id}/search":{"post":{"description":"This endpoint performs a focused search within a single space (specified by the space_id path parameter). Like the global search, it accepts pagination parameters and a JSON payload containing the search query, object types, and sorting preferences. The search is limited to the provided space and returns a list of objects that match the query. This allows clients to implement spaceโ€‘specific filtering without having to process extraneous results.","parameters":[{"description":"Space ID","in":"path","name":"space_id","required":true,"schema":{"type":"string"}},{"description":"The number of items to skip before starting to collect the result set","in":"query","name":"offset","schema":{"default":0,"type":"integer"}},{"description":"The number of items to return","in":"query","name":"limit","schema":{"default":100,"maximum":1000,"type":"integer"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/search.SearchRequest"}}},"description":"Search parameters","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/pagination.PaginatedResponse-object_Object"}}},"description":"List of objects"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.UnauthorizedError"}}},"description":"Unauthorized"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.ServerError"}}},"description":"Internal server error"}},"security":[{"bearerauth":[]}],"summary":"Search objects within a space","tags":["search"]}},"/spaces/{space_id}/types":{"get":{"description":"This endpoint retrieves a paginated list of object types (e.g. 'Page', 'Note', 'Task') available within the specified space. Each typeโ€™s record includes its unique identifier, unique key, display name, icon, and a recommended layout. Clients use this information when offering choices for object creation or for filtering objects by type.","parameters":[{"description":"Space ID","in":"path","name":"space_id","required":true,"schema":{"type":"string"}},{"description":"The number of items to skip before starting to collect the result set","in":"query","name":"offset","schema":{"default":0,"type":"integer"}},{"description":"The number of items to return","in":"query","name":"limit","schema":{"default":100,"maximum":1000,"type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/pagination.PaginatedResponse-object_Type"}}},"description":"List of types"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.UnauthorizedError"}}},"description":"Unauthorized"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.ServerError"}}},"description":"Internal server error"}},"security":[{"bearerauth":[]}],"summary":"List types","tags":["types"]}},"/spaces/{space_id}/types/{type_id}":{"get":{"description":"Fetches detailed information about one specific object type by its ID. This includes the typeโ€™s unique key, name, icon, and recommended layout. This detailed view assists clients in understanding the expected structure and style for objects of that type and in guiding the user interface (such as displaying appropriate icons or layout hints).","parameters":[{"description":"Space ID","in":"path","name":"space_id","required":true,"schema":{"type":"string"}},{"description":"Type ID","in":"path","name":"type_id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/object.TypeResponse"}}},"description":"The requested type"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.UnauthorizedError"}}},"description":"Unauthorized"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.NotFoundError"}}},"description":"Resource not found"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.ServerError"}}},"description":"Internal server error"}},"security":[{"bearerauth":[]}],"summary":"Get type","tags":["types"]}},"/spaces/{space_id}/types/{type_id}/templates":{"get":{"description":"This endpoint returns a paginated list of templates that are associated with a specific object type within a space. Templates provide preโ€‘configured structures for creating new objects. Each template record contains its identifier, name, and icon, so that clients can offer users a selection of templates when creating objects.","parameters":[{"description":"Space ID","in":"path","name":"space_id","required":true,"schema":{"type":"string"}},{"description":"Type ID","in":"path","name":"type_id","required":true,"schema":{"type":"string"}},{"description":"The number of items to skip before starting to collect the result set","in":"query","name":"offset","schema":{"default":0,"type":"integer"}},{"description":"The number of items to return","in":"query","name":"limit","schema":{"default":100,"maximum":1000,"type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/pagination.PaginatedResponse-object_Template"}}},"description":"List of templates"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.UnauthorizedError"}}},"description":"Unauthorized"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.ServerError"}}},"description":"Internal server error"}},"security":[{"bearerauth":[]}],"summary":"List templates","tags":["types"]}},"/spaces/{space_id}/types/{type_id}/templates/{template_id}":{"get":{"description":"Fetches full details for one template associated with a particular object type in a space. The response provides the templateโ€™s identifier, name, icon, and any other relevant metadata. This endpoint is useful when a client needs to preview or apply a template to prefill object creation fields.","parameters":[{"description":"Space ID","in":"path","name":"space_id","required":true,"schema":{"type":"string"}},{"description":"Type ID","in":"path","name":"type_id","required":true,"schema":{"type":"string"}},{"description":"Template ID","in":"path","name":"template_id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/object.TemplateResponse"}}},"description":"The requested template"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.UnauthorizedError"}}},"description":"Unauthorized"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.NotFoundError"}}},"description":"Resource not found"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.ServerError"}}},"description":"Internal server error"}},"security":[{"bearerauth":[]}],"summary":"Get template","tags":["types"]}},"/v1/spaces/{space_id}/lists/{list_id}/objects":{"get":{"description":"Returns a paginated list of objects that are associated with a specific list (or collection) within a space. This endpoint helps clients to manage grouped objects (for example, tasks within a list) by returning detailed object information for each item of the list.","parameters":[{"description":"Space ID","in":"path","name":"space_id","required":true,"schema":{"type":"string"}},{"description":"List ID","in":"path","name":"list_id","required":true,"schema":{"type":"string"}},{"description":"The number of items to skip before starting to collect the result set","in":"query","name":"offset","schema":{"default":0,"type":"integer"}},{"description":"The number of items to return","in":"query","name":"limit","schema":{"type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/pagination.PaginatedResponse-object_Object"}}},"description":"List of objects"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.UnauthorizedError"}}},"description":"Unauthorized"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.NotFoundError"}}},"description":"Not found"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.ServerError"}}},"description":"Internal server error"}},"security":[{"bearerauth":[]}],"summary":"Get objects in list","tags":["lists"]},"post":{"description":"Enables clients to add one or more objects to a specific list by submitting a JSON array of object IDs. Upon success, the endpoint returns a confirmation message. This endpoint is vital for building user interfaces that allow dragโ€‘andโ€‘drop or multiโ€‘select additions to collections.","parameters":[{"description":"Space ID","in":"path","name":"space_id","required":true,"schema":{"type":"string"}},{"description":"List ID","in":"path","name":"list_id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"items":{"type":"string"},"type":"array"}}},"description":"List of object IDs","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Objects added successfully"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.ValidationError"}}},"description":"Bad request"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.UnauthorizedError"}}},"description":"Unauthorized"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.NotFoundError"}}},"description":"Not found"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.ServerError"}}},"description":"Internal server error"}},"security":[{"bearerauth":[]}],"summary":"Add objects to list","tags":["lists"]}},"/v1/spaces/{space_id}/lists/{list_id}/objects/{object_id}":{"delete":{"description":"Removes a given object from the specified list in a space. The endpoint takes the space, list, and object identifiers as path parameters. It's subject to rate limiting and returns a success message on completion. It is used for dynamically managing collections without affecting the underlying object data.","parameters":[{"description":"Space ID","in":"path","name":"space_id","required":true,"schema":{"type":"string"}},{"description":"List ID","in":"path","name":"list_id","required":true,"schema":{"type":"string"}},{"description":"Object ID","in":"path","name":"object_id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Objects removed successfully"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.ValidationError"}}},"description":"Bad request"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.UnauthorizedError"}}},"description":"Unauthorized"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.NotFoundError"}}},"description":"Not found"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/util.ServerError"}}},"description":"Internal server error"}},"security":[{"bearerauth":[]}],"summary":"Remove object from list","tags":["lists"]}}}, + "openapi": "3.1.0", + "servers": [ + {"url":"http://localhost:31009/v1"} + ] } \ No newline at end of file diff --git a/core/api/docs/swagger.yaml b/core/api/docs/swagger.yaml index 550a59d1c6..13e0363eba 100644 --- a/core/api/docs/swagger.yaml +++ b/core/api/docs/swagger.yaml @@ -1,475 +1,626 @@ -basePath: /v1 -definitions: - auth.DisplayCodeResponse: - properties: - challenge_id: - example: 67647f5ecda913e9a2e11b26 - type: string - type: object - auth.TokenResponse: - properties: - app_key: - example: zhSG/zQRmgADyilWPtgdnfo1qD60oK02/SVgi1GaFt6= - type: string - session_token: - example: eyJhbGciOeJIRzI1NiIsInR5cCI6IkpXVCJ1.eyJzZWVkIjaiY0dmVndlUnAifQ.Y1EZecYnwmvMkrXKOa2XJnAbaRt34urBabe06tmDQII - type: string - type: object - export.ObjectExportResponse: - properties: - path: - example: /path/to/export - type: string - type: object - object.Block: - properties: - align: - enum: - - AlignLeft - - AlignCenter - - AlignRight - - AlignJustify - example: AlignLeft - type: string - background_color: - example: red - type: string - children_ids: - example: - - '[''6797ce8ecda913cde14b02dc'']' - items: - type: string - type: array - file: - $ref: '#/definitions/object.File' - id: - example: 64394517de52ad5acb89c66c - type: string - text: - $ref: '#/definitions/object.Text' - vertical_align: - enum: - - VerticalAlignTop - - VerticalAlignMiddle - - VerticalAlignBottom - example: VerticalAlignTop - type: string - type: object - object.CreateObjectRequest: - properties: - body: - example: Object Body - type: string - description: - example: Object Description - type: string - icon: - example: "\U0001F4C4" - type: string - name: - example: Object Name - type: string - object_type_unique_key: - example: ot-page - type: string - source: - example: https://source.com - type: string - template_id: - example: bafyreictrp3obmnf6dwejy5o4p7bderaaia4bdg2psxbfzf44yya5uutge - type: string - type: object - object.Detail: - properties: - details: - additionalProperties: true - type: object - id: - enum: - - last_modified_date - - last_modified_by - - created_date - - created_by - - last_opened_date - - tags - example: last_modified_date - type: string - type: object - object.File: - properties: - added_at: - type: integer - hash: - type: string - mime: - type: string - name: - type: string - size: - type: integer - state: - type: string - style: - type: string - target_object_id: - type: string - type: - type: string - type: object - object.Object: - properties: - blocks: - items: - $ref: '#/definitions/object.Block' - type: array - details: - items: - $ref: '#/definitions/object.Detail' - type: array - icon: - example: "\U0001F4C4" - type: string - id: - example: bafyreie6n5l5nkbjal37su54cha4coy7qzuhrnajluzv5qd5jvtsrxkequ - type: string - layout: - example: basic - type: string - name: - example: Object Name - type: string - root_id: - example: bafyreicypzj6uvu54664ucv3hmbsd5cmdy2dv4fwua26sciq74khzpyn4u - type: string - snippet: - example: The beginning of the object body... - type: string - space_id: - example: bafyreigyfkt6rbv24sbv5aq2hko3bhmv5xxlf22b4bypdu6j7hnphm3psq.23me69r569oi1 - type: string - type: - example: Page - type: string - type: object - object.ObjectResponse: - properties: - object: - $ref: '#/definitions/object.Object' - type: object - object.Template: - properties: - icon: - example: "\U0001F4C4" - type: string - id: - example: bafyreictrp3obmnf6dwejy5o4p7bderaaia4bdg2psxbfzf44yya5uutge - type: string - name: - example: Template Name - type: string - type: - example: template - type: string - type: object - object.TemplateResponse: - properties: - template: - $ref: '#/definitions/object.Template' - type: object - object.Text: - properties: - checked: - example: true - type: boolean - color: - example: red - type: string - icon: - example: "\U0001F4C4" - type: string - style: - enum: - - Paragraph - - Header1 - - Header2 - - Header3 - - Header4 - - Quote - - Code - - Title - - Checkbox - - Marked - - Numbered - - Toggle - - Description - - Callout - example: Paragraph - type: string - text: - example: Some text - type: string - type: object - object.Type: - properties: - icon: - example: "\U0001F4C4" - type: string - id: - example: bafyreigyb6l5szohs32ts26ku2j42yd65e6hqy2u3gtzgdwqv6hzftsetu - type: string - name: - example: Page - type: string - recommended_layout: - example: todo - type: string - type: - example: type - type: string - unique_key: - example: ot-page - type: string - type: object - object.TypeResponse: - properties: - type: - $ref: '#/definitions/object.Type' - type: object - pagination.PaginatedResponse-object_Object: - properties: - data: - items: - $ref: '#/definitions/object.Object' - type: array - pagination: - $ref: '#/definitions/pagination.PaginationMeta' - type: object - pagination.PaginatedResponse-object_Template: - properties: - data: - items: - $ref: '#/definitions/object.Template' - type: array - pagination: - $ref: '#/definitions/pagination.PaginationMeta' - type: object - pagination.PaginatedResponse-object_Type: - properties: - data: - items: - $ref: '#/definitions/object.Type' - type: array - pagination: - $ref: '#/definitions/pagination.PaginationMeta' - type: object - pagination.PaginatedResponse-space_Member: - properties: - data: - items: - $ref: '#/definitions/space.Member' - type: array - pagination: - $ref: '#/definitions/pagination.PaginationMeta' - type: object - pagination.PaginatedResponse-space_Space: - properties: - data: - items: - $ref: '#/definitions/space.Space' - type: array - pagination: - $ref: '#/definitions/pagination.PaginationMeta' - type: object - pagination.PaginationMeta: - properties: - has_more: - description: whether there are more items available - example: true - type: boolean - limit: - description: the current limit - example: 100 - type: integer - offset: - description: the current offset - example: 0 - type: integer - total: - description: the total number of items available on that endpoint - example: 1024 - type: integer - type: object - search.SearchRequest: - properties: - query: - type: string - sort: - $ref: '#/definitions/search.SortOptions' - types: - items: - type: string - type: array - type: object - search.SortOptions: - properties: - direction: - default: desc - enum: - - asc - - desc - type: string - timestamp: - default: last_modified_date - enum: - - created_date - - last_modified_date - - last_opened_date - type: string - type: object - space.CreateSpaceRequest: - properties: - name: - example: New Space - type: string - type: object - space.CreateSpaceResponse: - properties: - space: - $ref: '#/definitions/space.Space' - type: object - space.Member: - properties: - global_name: - example: john.any - type: string - icon: - example: http://127.0.0.1:31006/image/bafybeieptz5hvcy6txplcvphjbbh5yjc2zqhmihs3owkh5oab4ezauzqay?width=100 - type: string - id: - example: _participant_bafyreigyfkt6rbv24sbv5aq2hko1bhmv5xxlf22b4bypdu6j7hnphm3psq_23me69r569oi1_AAjEaEwPF4nkEh9AWkqEnzcQ8HziBB4ETjiTpvRCQvWnSMDZ - type: string - identity: - example: AAjEaEwPF4nkEh7AWkqEnzcQ8HziGB4ETjiTpvRCQvWnSMDZ - type: string - name: - example: John Doe - type: string - role: - enum: - - Reader - - Writer - - Owner - - NoPermission - example: Owner - type: string - type: - example: member - type: string - type: object - space.Space: - properties: - account_space_id: - example: bafyreihpd2knon5wbljhtfeg3fcqtg3i2pomhhnigui6lrjmzcjzep7gcy.23me69r569oi1 - type: string - analytics_id: - example: 624aecdd-4797-4611-9d61-a2ae5f53cf1c - type: string - archive_object_id: - example: bafyreialsgoyflf3etjm3parzurivyaukzivwortf32b4twnlwpwocsrri - type: string - device_id: - example: 12D3KooWGZMJ4kQVyQVXaj7gJPZr3RZ2nvd9M2Eq2pprEoPih9WF - type: string - gateway_url: - example: http://127.0.0.1:31006 - type: string - home_object_id: - example: bafyreie4qcl3wczb4cw5hrfyycikhjyh6oljdis3ewqrk5boaav3sbwqya - type: string - icon: - example: http://127.0.0.1:31006/image/bafybeieptz5hvcy6txplcvphjbbh5yjc2zqhmihs3owkh5oab4ezauzqay - type: string - id: - example: bafyreigyfkt6rbv24sbv5aq2hko3bhmv5xxlf22b4bypdu6j7hnphm3psq.23me69r569oi1 - type: string - local_storage_path: - example: /Users/johndoe/Library/Application Support/Anytype/data/AAHTtt1wuQEnaYBNZ2Cyfcvs6DqPqxgn8VXDVk4avsUkMuha - type: string - marketplace_workspace_id: - example: _anytype_marketplace - type: string - name: - example: Space Name - type: string - network_id: - example: N83gJpVd9MuNRZAuJLZ7LiMntTThhPc6DtzWWVjb1M3PouVU - type: string - profile_object_id: - example: bafyreiaxhwreshjqwndpwtdsu4mtihaqhhmlygqnyqpfyfwlqfq3rm3gw4 - type: string - space_view_id: - example: bafyreigzv3vq7qwlrsin6njoduq727ssnhwd6bgyfj6nm4hv3pxoc2rxhy - type: string - tech_space_id: - example: bafyreif4xuwncrjl6jajt4zrrfnylpki476nv2w64yf42ovt7gia7oypii.23me69r569oi1 - type: string - timezone: - example: "" - type: string - type: - example: space - type: string - widgets_id: - example: bafyreialj7pceh53mifm5dixlho47ke4qjmsn2uh4wsjf7xq2pnlo5xfva - type: string - workspace_object_id: - example: bafyreiapey2g6e6za4zfxvlgwdy4hbbfu676gmwrhnqvjbxvrchr7elr3y - type: string - type: object - util.ForbiddenError: - properties: - error: - properties: - message: - type: string - type: object - type: object - util.NotFoundError: - properties: - error: - properties: - message: - type: string - type: object - type: object - util.ServerError: - properties: - error: - properties: - message: - type: string - type: object - type: object - util.UnauthorizedError: - properties: - error: - properties: - message: +components: + schemas: + 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 + - AlignRight + - AlignJustify + 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: object - type: object - util.ValidationError: - properties: - error: - properties: - message: + type: array + uniqueItems: false + file: + $ref: '#/components/schemas/object.File' + id: + description: The id of the block + example: 64394517de52ad5acb89c66c + type: string + relation: + $ref: '#/components/schemas/object.Relation' + text: + $ref: '#/components/schemas/object.Text' + vertical_align: + description: The vertical alignment of the block + enum: + - VerticalAlignTop + - VerticalAlignMiddle + - VerticalAlignBottom + example: VerticalAlignTop + type: string + type: object + object.CreateObjectRequest: + properties: + body: + description: The body of the object + example: This is the body of the object. Markdown syntax is supported here. + type: string + 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: + 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: + 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 + object.Detail: + properties: + details: + additionalProperties: {} + description: The details + type: object + id: + description: The id of the detail + enum: + - last_modified_date + - last_modified_by + - created_date + - created_by + - last_opened_date + - tags + example: last_modified_date + type: string + type: object + object.File: + description: The file of the block, if applicable + 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: + description: The object + properties: + blocks: + description: The blocks of the object + items: + $ref: '#/components/schemas/object.Block' + type: array + uniqueItems: false + details: + description: The details of the object + items: + $ref: '#/components/schemas/object.Detail' + type: array + uniqueItems: false + 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: + description: The name of the object + example: My object + type: string + object: + description: The data model of the object + example: 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: + $ref: '#/components/schemas/object.Type' + type: object + object.ObjectResponse: + properties: + object: + $ref: '#/components/schemas/object.Object' + type: object + object.Relation: + description: The relation of the block, if applicable + properties: + id: + type: string + type: object + object.Template: + description: The 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: + description: The name of the template + example: My template + type: string + object: + description: The data model of the object + example: template + type: string + type: object + object.TemplateResponse: + properties: + template: + $ref: '#/components/schemas/object.Template' + type: object + object.Text: + description: The text of the block, if applicable + 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 + - Header2 + - Header3 + - Header4 + - Quote + - Code + - Title + - Checkbox + - Marked + - Numbered + - Toggle + - Description + - Callout + example: Paragraph + type: string + text: + description: The text + example: Some text... + type: string + type: object + object.Type: + description: The type of the object + 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 + object: + description: The data model of the object + example: type + type: string + recommended_layout: + description: The recommended layout of the type + example: todo + type: string + unique_key: + description: The unique key of the type + example: ot-page + type: string + type: object + object.TypeResponse: + properties: + type: + $ref: '#/components/schemas/object.Type' + type: object + pagination.PaginatedResponse-object_Object: + properties: + data: + description: The list of items in the current result set + items: + $ref: '#/components/schemas/object.Object' + type: array + uniqueItems: false + pagination: + $ref: '#/components/schemas/pagination.PaginationMeta' + type: object + pagination.PaginatedResponse-object_Template: + properties: + data: + description: The list of items in the current result set + items: + $ref: '#/components/schemas/object.Template' + type: array + uniqueItems: false + pagination: + $ref: '#/components/schemas/pagination.PaginationMeta' + type: object + pagination.PaginatedResponse-object_Type: + properties: + data: + description: The list of items in the current result set + items: + $ref: '#/components/schemas/object.Type' + type: array + uniqueItems: false + pagination: + $ref: '#/components/schemas/pagination.PaginationMeta' + type: object + pagination.PaginatedResponse-space_Member: + properties: + data: + description: The list of items in the current result set + items: + $ref: '#/components/schemas/space.Member' + type: array + uniqueItems: false + pagination: + $ref: '#/components/schemas/pagination.PaginationMeta' + type: object + pagination.PaginatedResponse-space_Space: + properties: + data: + description: The list of items in the current result set + items: + $ref: '#/components/schemas/space.Space' + type: array + uniqueItems: false + pagination: + $ref: '#/components/schemas/pagination.PaginationMeta' + type: object + pagination.PaginationMeta: + description: The pagination metadata for the response + properties: + has_more: + description: Indicates if there are more items available beyond the current + result set + example: true + type: boolean + limit: + description: The maximum number of items returned in the result set + example: 100 + type: integer + 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 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: '#/components/schemas/search.SortOptions' + 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: object - type: object + type: array + uniqueItems: false + type: object + search.SortOptions: + description: The sorting criteria and direction for the search results + 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 + - last_opened_date + type: string + type: object + space.CreateSpaceRequest: + properties: + name: + description: The name of the space + example: New Space + type: string + type: object + space.Member: + description: The 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 + object: + description: The data model of the object + example: member + type: string + role: + description: The role of the member + enum: + - Reader + - Writer + - Owner + - NoPermission + example: Owner + type: string + type: object + space.MemberResponse: + properties: + member: + $ref: '#/components/schemas/space.Member' + type: object + space.Space: + description: The 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: + description: The name of the space + example: My Space + type: string + network_id: + description: The network id of the space + example: N83gJpVd9MuNRZAuJLZ7LiMntTThhPc6DtzWWVjb1M3PouVU + type: string + object: + description: The data model of the object + example: space + 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 + 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: + $ref: '#/components/schemas/space.Space' + type: object + util.ForbiddenError: + properties: + error: + properties: + message: + example: Forbidden + type: string + type: object + type: object + util.NotFoundError: + properties: + 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 + util.ServerError: + properties: + error: + properties: + message: + example: Internal server error + type: string + type: object + type: object + util.UnauthorizedError: + properties: + error: + properties: + message: + example: Unauthorized + type: string + type: object + type: object + util.ValidationError: + properties: + error: + properties: + message: + example: Bad request + type: string + type: object + type: object + securitySchemes: + bearerauth: + bearerFormat: JWT + scheme: bearer + type: http externalDocs: description: OpenAPI url: https://swagger.io/resources/open-api/ -host: localhost:31009 info: contact: email: support@anytype.io @@ -483,626 +634,1145 @@ info: termsOfService: https://anytype.io/terms_of_use title: Anytype API version: "1.0" +openapi: 3.1.0 paths: /auth/display_code: post: - consumes: - - application/json + description: This endpoint initiates a secure authentication flow by generating + a new challenge. Clients must supply the name of the application (via a query + parameter) that is requesting authentication. On success, the service returns + a unique challenge ID. This challenge ID must then be used with the token + endpoint (see below) to solve the challenge and retrieve an authentication + token. In essence, this endpoint โ€œboots upโ€ the login process and is the first + step in a multi-phase authentication sequence. parameters: - description: App name requesting the challenge in: query name: app_name required: true - type: string - produces: - - application/json + schema: + type: string + requestBody: + content: + application/json: + schema: + type: object responses: "200": + content: + application/json: + schema: + $ref: '#/components/schemas/auth.DisplayCodeResponse' description: Challenge ID - schema: - $ref: '#/definitions/auth.DisplayCodeResponse' "400": + content: + application/json: + schema: + $ref: '#/components/schemas/util.ValidationError' description: Invalid input - schema: - $ref: '#/definitions/util.ValidationError' "500": + content: + application/json: + schema: + $ref: '#/components/schemas/util.ServerError' description: Internal server error - schema: - $ref: '#/definitions/util.ServerError' summary: Start new challenge tags: - auth /auth/token: post: - consumes: - - application/json + description: After receiving a challenge ID from the display_code endpoint, + the client calls this endpoint to provide the corresponding 4-digit code (also + via a query parameter) along with the challenge ID. The endpoint verifies + that the challenge solution is correct and, if it is, returns an ephemeral + session token together with a permanent app key. These tokens are then used + in subsequent API requests to authorize access. This endpoint is central to + ensuring that only properly authenticated sessions can access further resources. parameters: - description: Challenge ID in: query name: challenge_id required: true - type: string + schema: + type: string - description: 4-digit code retrieved from Anytype Desktop app in: query name: code required: true - type: string - produces: - - application/json + schema: + type: string + requestBody: + content: + application/json: + schema: + type: object responses: "200": + content: + application/json: + schema: + $ref: '#/components/schemas/auth.TokenResponse' description: Authentication token - schema: - $ref: '#/definitions/auth.TokenResponse' "400": + content: + application/json: + schema: + $ref: '#/components/schemas/util.ValidationError' description: Invalid input - schema: - $ref: '#/definitions/util.ValidationError' "500": + content: + application/json: + schema: + $ref: '#/components/schemas/util.ServerError' description: Internal server error - schema: - $ref: '#/definitions/util.ServerError' summary: Retrieve token tags: - auth /search: post: - consumes: - - application/json + description: This endpoint executes a global search over every space the user + has access to. It accepts pagination parameters (offset and limit) and a JSON + body containing search criteria. The criteria include a search query string, + an optional list of object types, and sort options (e.g. ascending/descending + by creation, modification, or last opened dates). Internally, the endpoint + aggregates results from each space, merges and sorts them (after last modified + date by default), and returns a unified, paginated list of objects that match + the search parameters. parameters: - - default: 0 - description: The number of items to skip before starting to collect the result + - description: The number of items to skip before starting to collect the result set in: query name: offset - type: integer - - default: 100 - description: The number of items to return + schema: + default: 0 + type: integer + - description: The number of items to return in: query - maximum: 1000 name: limit - type: integer - - description: Search parameters - in: body - name: request - required: true schema: - $ref: '#/definitions/search.SearchRequest' - produces: - - application/json + default: 100 + maximum: 1000 + type: integer + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/search.SearchRequest' + description: Search parameters + required: true responses: "200": + content: + application/json: + schema: + $ref: '#/components/schemas/pagination.PaginatedResponse-object_Object' description: List of objects - schema: - $ref: '#/definitions/pagination.PaginatedResponse-object_Object' "401": + content: + application/json: + schema: + $ref: '#/components/schemas/util.UnauthorizedError' description: Unauthorized - schema: - $ref: '#/definitions/util.UnauthorizedError' "500": + content: + application/json: + schema: + $ref: '#/components/schemas/util.ServerError' description: Internal server error - schema: - $ref: '#/definitions/util.ServerError' + security: + - bearerauth: [] summary: Search objects across all spaces tags: - search /spaces: get: - consumes: - - application/json + description: Retrieves a paginated list of all spaces that are accessible by + the authenticated user. Each space record contains detailed information such + as the space ID, name, icon (derived either from an emoji or image URL), and + additional metadata. This endpoint is key to displaying a userโ€™s workspaces. parameters: - - default: 0 - description: The number of items to skip before starting to collect the result + - description: The number of items to skip before starting to collect the result set in: query name: offset - type: integer - - default: 100 - description: The number of items to return + schema: + default: 0 + type: integer + - description: The number of items to return in: query - maximum: 1000 name: limit - type: integer - produces: - - application/json + schema: + default: 100 + maximum: 1000 + type: integer responses: "200": + content: + application/json: + schema: + $ref: '#/components/schemas/pagination.PaginatedResponse-space_Space' description: List of spaces - schema: - $ref: '#/definitions/pagination.PaginatedResponse-space_Space' "401": + content: + application/json: + schema: + $ref: '#/components/schemas/util.UnauthorizedError' description: Unauthorized - schema: - $ref: '#/definitions/util.UnauthorizedError' "500": + content: + application/json: + schema: + $ref: '#/components/schemas/util.ServerError' description: Internal server error - schema: - $ref: '#/definitions/util.ServerError' + security: + - bearerauth: [] summary: List spaces tags: - spaces post: - consumes: - - application/json - parameters: - - description: Space to create - in: body - name: name + description: Creates a new workspace (or space) based on a supplied name in + the JSON request body. The endpoint is subject to rate limiting and automatically + applies default configurations such as generating a random icon and initializing + the workspace with default settings (for example, a default dashboard or home + page). On success, the new spaceโ€™s full metadata is returned, enabling the + client to immediately switch context to the new space. + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/space.CreateSpaceRequest' + description: Space to create required: true - schema: - $ref: '#/definitions/space.CreateSpaceRequest' - produces: - - application/json responses: "200": + content: + application/json: + schema: + $ref: '#/components/schemas/space.SpaceResponse' description: Space created successfully - schema: - $ref: '#/definitions/space.CreateSpaceResponse' "400": + content: + application/json: + schema: + $ref: '#/components/schemas/util.ValidationError' description: Bad request - schema: - $ref: '#/definitions/util.ValidationError' "401": + content: + application/json: + schema: + $ref: '#/components/schemas/util.UnauthorizedError' description: Unauthorized - schema: - $ref: '#/definitions/util.UnauthorizedError' + "423": + content: + application/json: + schema: + $ref: '#/components/schemas/util.RateLimitError' + description: Rate limit exceeded "500": + content: + application/json: + schema: + $ref: '#/components/schemas/util.ServerError' description: Internal server error - schema: - $ref: '#/definitions/util.ServerError' + security: + - bearerauth: [] summary: Create space tags: - spaces + /spaces/{space_id}: + get: + description: Fetches full details about a single space identified by its space + ID. The response includes metadata such as the space name, icon, and various + workspace IDs (home, archive, profile, etc.). This detailed view supports + use cases such as displaying space-specific settings. + parameters: + - description: Space ID + in: path + name: space_id + required: true + schema: + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/space.SpaceResponse' + description: Space + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/util.UnauthorizedError' + description: Unauthorized + "404": + content: + application/json: + schema: + $ref: '#/components/schemas/util.NotFoundError' + description: Space not found + "500": + content: + application/json: + schema: + $ref: '#/components/schemas/util.ServerError' + description: Internal server error + security: + - bearerauth: [] + summary: Get space + tags: + - spaces /spaces/{space_id}/members: get: - consumes: - - application/json + description: Returns a paginated list of members belonging to the specified + space. Each member record includes the memberโ€™s profile ID, name, icon (which + may be derived from an emoji or image), network identity, global name, and + role (e.g. Reader, Writer, Owner). This endpoint supports collaborative features + by allowing clients to show who is in a space and manage access rights. parameters: - description: Space ID in: path name: space_id required: true - type: string - - default: 0 - description: The number of items to skip before starting to collect the result + schema: + type: string + - description: The number of items to skip before starting to collect the result set in: query name: offset - type: integer - - default: 100 - description: The number of items to return + schema: + default: 0 + type: integer + - description: The number of items to return in: query - maximum: 1000 name: limit - type: integer - produces: - - application/json + schema: + default: 100 + maximum: 1000 + type: integer responses: "200": + content: + application/json: + schema: + $ref: '#/components/schemas/pagination.PaginatedResponse-space_Member' description: List of members - schema: - $ref: '#/definitions/pagination.PaginatedResponse-space_Member' "401": + content: + application/json: + schema: + $ref: '#/components/schemas/util.UnauthorizedError' description: Unauthorized - schema: - $ref: '#/definitions/util.UnauthorizedError' "500": + content: + application/json: + schema: + $ref: '#/components/schemas/util.ServerError' description: Internal server error - schema: - $ref: '#/definitions/util.ServerError' + security: + - bearerauth: [] summary: List members tags: - spaces + /spaces/{space_id}/members/{member_id}: + get: + description: Fetches detailed information about a single member within a space. + The endpoint returns the memberโ€™s identifier, name, icon, identity, global + name, and role. This is useful for user profile pages, permission management, + and displaying member-specific information in collaborative environments. + parameters: + - description: Space ID + in: path + name: space_id + required: true + schema: + type: string + - description: Member ID + in: path + name: member_id + required: true + schema: + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/space.MemberResponse' + description: Member + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/util.UnauthorizedError' + description: Unauthorized + "404": + content: + application/json: + schema: + $ref: '#/components/schemas/util.NotFoundError' + description: Member not found + "500": + content: + application/json: + schema: + $ref: '#/components/schemas/util.ServerError' + description: Internal server error + security: + - bearerauth: [] + summary: Get member + tags: + - spaces /spaces/{space_id}/objects: get: - consumes: - - application/json + description: Retrieves a paginated list of objects in the given space. The endpoint + takes query parameters for pagination (offset and limit) and returns detailed + data about each object including its ID, name, icon, type information, a snippet + of the content (if applicable), layout, space ID, blocks and details. It is + intended for building views where users can see all objects in a space at + a glance. parameters: - description: Space ID in: path name: space_id required: true - type: string - - default: 0 - description: The number of items to skip before starting to collect the result + schema: + type: string + - description: The number of items to skip before starting to collect the result set in: query name: offset - type: integer - - default: 100 - description: The number of items to return + schema: + default: 0 + type: integer + - description: The number of items to return in: query - maximum: 1000 name: limit - type: integer - produces: - - application/json + schema: + default: 100 + maximum: 1000 + type: integer responses: "200": + content: + application/json: + schema: + $ref: '#/components/schemas/pagination.PaginatedResponse-object_Object' description: List of objects - schema: - $ref: '#/definitions/pagination.PaginatedResponse-object_Object' "401": + content: + application/json: + schema: + $ref: '#/components/schemas/util.UnauthorizedError' description: Unauthorized - schema: - $ref: '#/definitions/util.UnauthorizedError' "500": + content: + application/json: + schema: + $ref: '#/components/schemas/util.ServerError' description: Internal server error - schema: - $ref: '#/definitions/util.ServerError' + security: + - bearerauth: [] summary: List objects tags: - objects post: - consumes: - - application/json + description: Creates a new object in the specified space using a JSON payload. + The creation process is subject to rate limiting. The payload must include + key details such as the object name, icon, description, body content (which + may support Markdown), source URL (required for bookmark objects), template + identifier, and the unique key for the object type. Post-creation, additional + operations (like setting featured relations or fetching bookmark metadata) + may occur. The endpoint then returns the full object data, ready for further + interactions. parameters: - description: Space ID in: path name: space_id required: true - type: string - - description: Object to create - in: body - name: object - required: true schema: - $ref: '#/definitions/object.CreateObjectRequest' - produces: - - application/json + type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/object.CreateObjectRequest' + description: Object to create + required: true responses: "200": + content: + application/json: + schema: + $ref: '#/components/schemas/object.ObjectResponse' description: The created object - schema: - $ref: '#/definitions/object.ObjectResponse' "400": + content: + application/json: + schema: + $ref: '#/components/schemas/util.ValidationError' description: Bad request - schema: - $ref: '#/definitions/util.ValidationError' "401": + content: + application/json: + schema: + $ref: '#/components/schemas/util.UnauthorizedError' description: Unauthorized - schema: - $ref: '#/definitions/util.UnauthorizedError' + "423": + content: + application/json: + schema: + $ref: '#/components/schemas/util.RateLimitError' + description: Rate limit exceeded "500": + content: + application/json: + schema: + $ref: '#/components/schemas/util.ServerError' description: Internal server error - schema: - $ref: '#/definitions/util.ServerError' + security: + - bearerauth: [] summary: Create object tags: - objects /spaces/{space_id}/objects/{object_id}: delete: - consumes: - - application/json + description: This endpoint โ€œdeletesโ€ an object by marking it as archived. The + deletion process is performed safely and is subject to rate limiting. It returns + the objectโ€™s details after it has been archived. Proper error handling is + in place for situations such as when the object isnโ€™t found or the deletion + cannot be performed because of permission issues. parameters: - description: Space ID in: path name: space_id required: true - type: string + schema: + type: string - description: Object ID in: path name: object_id required: true - type: string - produces: - - application/json + schema: + type: string responses: "200": + content: + application/json: + schema: + $ref: '#/components/schemas/object.ObjectResponse' description: The deleted object - schema: - $ref: '#/definitions/object.ObjectResponse' "401": + content: + application/json: + schema: + $ref: '#/components/schemas/util.UnauthorizedError' description: Unauthorized - schema: - $ref: '#/definitions/util.UnauthorizedError' "403": + content: + application/json: + schema: + $ref: '#/components/schemas/util.ForbiddenError' description: Forbidden - schema: - $ref: '#/definitions/util.ForbiddenError' "404": + content: + application/json: + schema: + $ref: '#/components/schemas/util.NotFoundError' description: Resource not found - schema: - $ref: '#/definitions/util.NotFoundError' + "423": + content: + application/json: + schema: + $ref: '#/components/schemas/util.RateLimitError' + description: Rate limit exceeded "500": + content: + application/json: + schema: + $ref: '#/components/schemas/util.ServerError' description: Internal server error - schema: - $ref: '#/definitions/util.ServerError' + security: + - bearerauth: [] summary: Delete object tags: - objects get: - consumes: - - application/json + description: Fetches the full details of a single object identified by the object + ID within the specified space. The response includes not only basic metadata + (ID, name, icon, type) but also the complete set of blocks (which may include + text, files, and relations) and extra details (such as timestamps and linked + member information). This endpoint is essential when a client needs to render + or edit the full object view. parameters: - description: Space ID in: path name: space_id required: true - type: string + schema: + type: string - description: Object ID in: path name: object_id required: true - type: string - produces: - - application/json + schema: + type: string responses: "200": + content: + application/json: + schema: + $ref: '#/components/schemas/object.ObjectResponse' description: The requested object - schema: - $ref: '#/definitions/object.ObjectResponse' "401": + content: + application/json: + schema: + $ref: '#/components/schemas/util.UnauthorizedError' description: Unauthorized - schema: - $ref: '#/definitions/util.UnauthorizedError' "404": + content: + application/json: + schema: + $ref: '#/components/schemas/util.NotFoundError' description: Resource not found - schema: - $ref: '#/definitions/util.NotFoundError' "500": + content: + application/json: + schema: + $ref: '#/components/schemas/util.ServerError' description: Internal server error - schema: - $ref: '#/definitions/util.ServerError' + security: + - bearerauth: [] summary: Get object tags: - objects /spaces/{space_id}/objects/{object_id}/export/{format}: post: - consumes: - - application/json + description: This endpoint exports a single object from the specified space + into a desired format. The export format is provided as a path parameter (currently + supporting โ€œmarkdownโ€ and โ€œprotobufโ€), and clients can optionally specify + an export path in the request body. The endpoint calls an export service which + converts the objectโ€™s content into the requested format and returns the file + path where the exported data is stored. It is useful for data backup, sharing, + or further processing. parameters: - description: Space ID in: path name: space_id required: true - type: string + schema: + type: string - description: Object ID in: path name: object_id required: true - type: string + schema: + type: string - description: Export format - enum: - - markdown - - protobuf in: path name: format required: true - type: string - produces: - - application/json + schema: + enum: + - markdown + - protobuf + type: string + requestBody: + content: + application/json: + schema: + type: object responses: "200": + content: + application/json: + schema: + $ref: '#/components/schemas/export.ObjectExportResponse' description: Object exported successfully - schema: - $ref: '#/definitions/export.ObjectExportResponse' "400": + content: + application/json: + schema: + $ref: '#/components/schemas/util.ValidationError' description: Bad request - schema: - $ref: '#/definitions/util.ValidationError' "401": + content: + application/json: + schema: + $ref: '#/components/schemas/util.UnauthorizedError' description: Unauthorized - schema: - $ref: '#/definitions/util.UnauthorizedError' "500": + content: + application/json: + schema: + $ref: '#/components/schemas/util.ServerError' description: Internal server error - schema: - $ref: '#/definitions/util.ServerError' + security: + - bearerauth: [] summary: Export object tags: - export /spaces/{space_id}/search: post: - consumes: - - application/json + description: This endpoint performs a focused search within a single space (specified + by the space_id path parameter). Like the global search, it accepts pagination + parameters and a JSON payload containing the search query, object types, and + sorting preferences. The search is limited to the provided space and returns + a list of objects that match the query. This allows clients to implement spaceโ€‘specific + filtering without having to process extraneous results. parameters: - description: Space ID in: path name: space_id required: true - type: string - - default: 0 - description: The number of items to skip before starting to collect the result + schema: + type: string + - description: The number of items to skip before starting to collect the result set in: query name: offset - type: integer - - default: 100 - description: The number of items to return + schema: + default: 0 + type: integer + - description: The number of items to return in: query - maximum: 1000 name: limit - type: integer - - description: Search parameters - in: body - name: request - required: true schema: - $ref: '#/definitions/search.SearchRequest' - produces: - - application/json + default: 100 + maximum: 1000 + type: integer + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/search.SearchRequest' + description: Search parameters + required: true responses: "200": + content: + application/json: + schema: + $ref: '#/components/schemas/pagination.PaginatedResponse-object_Object' description: List of objects - schema: - $ref: '#/definitions/pagination.PaginatedResponse-object_Object' "401": + content: + application/json: + schema: + $ref: '#/components/schemas/util.UnauthorizedError' description: Unauthorized - schema: - $ref: '#/definitions/util.UnauthorizedError' "500": + content: + application/json: + schema: + $ref: '#/components/schemas/util.ServerError' description: Internal server error - schema: - $ref: '#/definitions/util.ServerError' + security: + - bearerauth: [] summary: Search objects within a space tags: - search /spaces/{space_id}/types: get: - consumes: - - application/json + description: This endpoint retrieves a paginated list of object types (e.g. + 'Page', 'Note', 'Task') available within the specified space. Each typeโ€™s + record includes its unique identifier, unique key, display name, icon, and + a recommended layout. Clients use this information when offering choices for + object creation or for filtering objects by type. parameters: - description: Space ID in: path name: space_id required: true - type: string - - default: 0 - description: The number of items to skip before starting to collect the result + schema: + type: string + - description: The number of items to skip before starting to collect the result set in: query name: offset - type: integer - - default: 100 - description: The number of items to return + schema: + default: 0 + type: integer + - description: The number of items to return in: query - maximum: 1000 name: limit - type: integer - produces: - - application/json + schema: + default: 100 + maximum: 1000 + type: integer responses: "200": + content: + application/json: + schema: + $ref: '#/components/schemas/pagination.PaginatedResponse-object_Type' description: List of types - schema: - $ref: '#/definitions/pagination.PaginatedResponse-object_Type' "401": + content: + application/json: + schema: + $ref: '#/components/schemas/util.UnauthorizedError' description: Unauthorized - schema: - $ref: '#/definitions/util.UnauthorizedError' "500": + content: + application/json: + schema: + $ref: '#/components/schemas/util.ServerError' description: Internal server error - schema: - $ref: '#/definitions/util.ServerError' + security: + - bearerauth: [] summary: List types tags: - types /spaces/{space_id}/types/{type_id}: get: - consumes: - - application/json + description: Fetches detailed information about one specific object type by + its ID. This includes the typeโ€™s unique key, name, icon, and recommended layout. + This detailed view assists clients in understanding the expected structure + and style for objects of that type and in guiding the user interface (such + as displaying appropriate icons or layout hints). parameters: - description: Space ID in: path name: space_id required: true - type: string + schema: + type: string - description: Type ID in: path name: type_id required: true - type: string - produces: - - application/json + schema: + type: string responses: "200": + content: + application/json: + schema: + $ref: '#/components/schemas/object.TypeResponse' description: The requested type - schema: - $ref: '#/definitions/object.TypeResponse' "401": + content: + application/json: + schema: + $ref: '#/components/schemas/util.UnauthorizedError' description: Unauthorized - schema: - $ref: '#/definitions/util.UnauthorizedError' "404": + content: + application/json: + schema: + $ref: '#/components/schemas/util.NotFoundError' description: Resource not found - schema: - $ref: '#/definitions/util.NotFoundError' "500": + content: + application/json: + schema: + $ref: '#/components/schemas/util.ServerError' description: Internal server error - schema: - $ref: '#/definitions/util.ServerError' + security: + - bearerauth: [] summary: Get type tags: - types /spaces/{space_id}/types/{type_id}/templates: get: - consumes: - - application/json + description: This endpoint returns a paginated list of templates that are associated + with a specific object type within a space. Templates provide preโ€‘configured + structures for creating new objects. Each template record contains its identifier, + name, and icon, so that clients can offer users a selection of templates when + creating objects. parameters: - description: Space ID in: path name: space_id required: true - type: string + schema: + type: string - description: Type ID in: path name: type_id required: true - type: string - - default: 0 - description: The number of items to skip before starting to collect the result + schema: + type: string + - description: The number of items to skip before starting to collect the result set in: query name: offset - type: integer - - default: 100 - description: The number of items to return + schema: + default: 0 + type: integer + - description: The number of items to return in: query - maximum: 1000 name: limit - type: integer - produces: - - application/json + schema: + default: 100 + maximum: 1000 + type: integer responses: "200": + content: + application/json: + schema: + $ref: '#/components/schemas/pagination.PaginatedResponse-object_Template' description: List of templates - schema: - $ref: '#/definitions/pagination.PaginatedResponse-object_Template' "401": + content: + application/json: + schema: + $ref: '#/components/schemas/util.UnauthorizedError' description: Unauthorized - schema: - $ref: '#/definitions/util.UnauthorizedError' "500": + content: + application/json: + schema: + $ref: '#/components/schemas/util.ServerError' description: Internal server error - schema: - $ref: '#/definitions/util.ServerError' + security: + - bearerauth: [] summary: List templates tags: - types /spaces/{space_id}/types/{type_id}/templates/{template_id}: get: - consumes: - - application/json + description: Fetches full details for one template associated with a particular + object type in a space. The response provides the templateโ€™s identifier, name, + icon, and any other relevant metadata. This endpoint is useful when a client + needs to preview or apply a template to prefill object creation fields. parameters: - description: Space ID in: path name: space_id required: true - type: string + schema: + type: string - description: Type ID in: path name: type_id required: true - type: string + schema: + type: string - description: Template ID in: path name: template_id required: true - type: string - produces: - - application/json + schema: + type: string responses: "200": + content: + application/json: + schema: + $ref: '#/components/schemas/object.TemplateResponse' description: The requested template - schema: - $ref: '#/definitions/object.TemplateResponse' "401": + content: + application/json: + schema: + $ref: '#/components/schemas/util.UnauthorizedError' description: Unauthorized - schema: - $ref: '#/definitions/util.UnauthorizedError' "404": + content: + application/json: + schema: + $ref: '#/components/schemas/util.NotFoundError' description: Resource not found - schema: - $ref: '#/definitions/util.NotFoundError' "500": + content: + application/json: + schema: + $ref: '#/components/schemas/util.ServerError' description: Internal server error - schema: - $ref: '#/definitions/util.ServerError' + security: + - bearerauth: [] summary: Get template tags: - types -securityDefinitions: - BasicAuth: - type: basic -swagger: "2.0" + /v1/spaces/{space_id}/lists/{list_id}/objects: + get: + description: Returns a paginated list of objects that are associated with a + specific list (or collection) within a space. This endpoint helps clients + to manage grouped objects (for example, tasks within a list) by returning + detailed object information for each item of the list. + parameters: + - description: Space ID + in: path + name: space_id + required: true + schema: + type: string + - description: List ID + in: path + name: list_id + required: true + schema: + type: string + - description: The number of items to skip before starting to collect the result + set + in: query + name: offset + schema: + default: 0 + type: integer + - description: The number of items to return + in: query + name: limit + schema: + type: integer + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/pagination.PaginatedResponse-object_Object' + description: List of objects + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/util.UnauthorizedError' + description: Unauthorized + "404": + content: + application/json: + schema: + $ref: '#/components/schemas/util.NotFoundError' + description: Not found + "500": + content: + application/json: + schema: + $ref: '#/components/schemas/util.ServerError' + description: Internal server error + security: + - bearerauth: [] + summary: Get objects in list + tags: + - lists + post: + description: Enables clients to add one or more objects to a specific list by + submitting a JSON array of object IDs. Upon success, the endpoint returns + a confirmation message. This endpoint is vital for building user interfaces + that allow dragโ€‘andโ€‘drop or multiโ€‘select additions to collections. + parameters: + - description: Space ID + in: path + name: space_id + required: true + schema: + type: string + - description: List ID + in: path + name: list_id + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + items: + type: string + type: array + description: List of object IDs + required: true + responses: + "200": + content: + application/json: + schema: + type: string + description: Objects added successfully + "400": + content: + application/json: + schema: + $ref: '#/components/schemas/util.ValidationError' + description: Bad request + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/util.UnauthorizedError' + description: Unauthorized + "404": + content: + application/json: + schema: + $ref: '#/components/schemas/util.NotFoundError' + description: Not found + "500": + content: + application/json: + schema: + $ref: '#/components/schemas/util.ServerError' + description: Internal server error + security: + - bearerauth: [] + summary: Add objects to list + tags: + - lists + /v1/spaces/{space_id}/lists/{list_id}/objects/{object_id}: + delete: + description: Removes a given object from the specified list in a space. The + endpoint takes the space, list, and object identifiers as path parameters. + It's subject to rate limiting and returns a success message on completion. + It is used for dynamically managing collections without affecting the underlying + object data. + parameters: + - description: Space ID + in: path + name: space_id + required: true + schema: + type: string + - description: List ID + in: path + name: list_id + required: true + schema: + type: string + - description: Object ID + in: path + name: object_id + required: true + schema: + type: string + responses: + "200": + content: + application/json: + schema: + type: string + description: Objects removed successfully + "400": + content: + application/json: + schema: + $ref: '#/components/schemas/util.ValidationError' + description: Bad request + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/util.UnauthorizedError' + description: Unauthorized + "404": + content: + application/json: + schema: + $ref: '#/components/schemas/util.NotFoundError' + description: Not found + "500": + content: + application/json: + schema: + $ref: '#/components/schemas/util.ServerError' + description: Internal server error + security: + - bearerauth: [] + summary: Remove object from list + tags: + - lists +servers: +- url: http://localhost:31009/v1 diff --git a/core/api/internal/auth/handler.go b/core/api/internal/auth/handler.go index 21900c9946..46cdbb1887 100644 --- a/core/api/internal/auth/handler.go +++ b/core/api/internal/auth/handler.go @@ -10,15 +10,16 @@ import ( // DisplayCodeHandler starts a new challenge and returns the challenge ID // -// @Summary Start new challenge -// @Tags auth -// @Accept json -// @Produce json -// @Param app_name query string true "App name requesting the challenge" -// @Success 200 {object} DisplayCodeResponse "Challenge ID" -// @Failure 400 {object} util.ValidationError "Invalid input" -// @Failure 500 {object} util.ServerError "Internal server error" -// @Router /auth/display_code [post] +// @Summary Start new challenge +// @Description This endpoint initiates a secure authentication flow by generating a new challenge. Clients must supply the name of the application (via a query parameter) that is requesting authentication. On success, the service returns a unique challenge ID. This challenge ID must then be used with the token endpoint (see below) to solve the challenge and retrieve an authentication token. In essence, this endpoint โ€œboots upโ€ the login process and is the first step in a multi-phase authentication sequence. +// @Tags auth +// @Accept json +// @Produce json +// @Param app_name query string true "App name requesting the challenge" +// @Success 200 {object} DisplayCodeResponse "Challenge ID" +// @Failure 400 {object} util.ValidationError "Invalid input" +// @Failure 500 {object} util.ServerError "Internal server error" +// @Router /auth/display_code [post] func DisplayCodeHandler(s *AuthService) gin.HandlerFunc { return func(c *gin.Context) { appName := c.Query("app_name") @@ -40,16 +41,17 @@ func DisplayCodeHandler(s *AuthService) gin.HandlerFunc { // TokenHandler retrieves an authentication token using a code and challenge ID // -// @Summary Retrieve token -// @Tags auth -// @Accept json -// @Produce json -// @Param challenge_id query string true "Challenge ID" -// @Param code query string true "4-digit code retrieved from Anytype Desktop app" -// @Success 200 {object} TokenResponse "Authentication token" -// @Failure 400 {object} util.ValidationError "Invalid input" -// @Failure 500 {object} util.ServerError "Internal server error" -// @Router /auth/token [post] +// @Summary Retrieve token +// @Description After receiving a challenge ID from the display_code endpoint, the client calls this endpoint to provide the corresponding 4-digit code (also via a query parameter) along with the challenge ID. The endpoint verifies that the challenge solution is correct and, if it is, returns an ephemeral session token together with a permanent app key. These tokens are then used in subsequent API requests to authorize access. This endpoint is central to ensuring that only properly authenticated sessions can access further resources. +// @Tags auth +// @Accept json +// @Produce json +// @Param challenge_id query string true "Challenge ID" +// @Param code query string true "4-digit code retrieved from Anytype Desktop app" +// @Success 200 {object} TokenResponse "Authentication token" +// @Failure 400 {object} util.ValidationError "Invalid input" +// @Failure 500 {object} util.ServerError "Internal server error" +// @Router /auth/token [post] func TokenHandler(s *AuthService) gin.HandlerFunc { return func(c *gin.Context) { challengeId := c.Query("challenge_id") 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/handler.go b/core/api/internal/export/handler.go index c2ae3a2f86..cc4c058134 100644 --- a/core/api/internal/export/handler.go +++ b/core/api/internal/export/handler.go @@ -10,18 +10,20 @@ import ( // GetObjectExportHandler exports an object in specified format // -// @Summary Export object -// @Tags export -// @Accept json -// @Produce json -// @Param space_id path string true "Space ID" -// @Param object_id path string true "Object ID" -// @Param format path string true "Export format" Enums(markdown,protobuf) -// @Success 200 {object} ObjectExportResponse "Object exported successfully" -// @Failure 400 {object} util.ValidationError "Bad request" -// @Failure 401 {object} util.UnauthorizedError "Unauthorized" -// @Failure 500 {object} util.ServerError "Internal server error" -// @Router /spaces/{space_id}/objects/{object_id}/export/{format} [post] +// @Summary Export object +// @Description This endpoint exports a single object from the specified space into a desired format. The export format is provided as a path parameter (currently supporting โ€œmarkdownโ€ and โ€œprotobufโ€), and clients can optionally specify an export path in the request body. The endpoint calls an export service which converts the objectโ€™s content into the requested format and returns the file path where the exported data is stored. It is useful for data backup, sharing, or further processing. +// @Tags export +// @Accept json +// @Produce json +// @Param space_id path string true "Space ID" +// @Param object_id path string true "Object ID" +// @Param format path string true "Export format" Enums(markdown,protobuf) +// @Success 200 {object} ObjectExportResponse "Object exported successfully" +// @Failure 400 {object} util.ValidationError "Bad request" +// @Failure 401 {object} util.UnauthorizedError "Unauthorized" +// @Failure 500 {object} util.ServerError "Internal server error" +// @Security bearerauth +// @Router /spaces/{space_id}/objects/{object_id}/export/{format} [post] func GetObjectExportHandler(s *ExportService) gin.HandlerFunc { return func(c *gin.Context) { spaceId := c.Param("space_id") 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/export/service.go b/core/api/internal/export/service.go index b20361351c..bcb4e4e338 100644 --- a/core/api/internal/export/service.go +++ b/core/api/internal/export/service.go @@ -19,8 +19,7 @@ type Service interface { } type ExportService struct { - mw service.ClientCommandsServer - AccountInfo *model.AccountInfo + mw service.ClientCommandsServer } func NewService(mw service.ClientCommandsServer) *ExportService { diff --git a/core/api/internal/list/handler.go b/core/api/internal/list/handler.go new file mode 100644 index 0000000000..761795a117 --- /dev/null +++ b/core/api/internal/list/handler.go @@ -0,0 +1,129 @@ +package list + +import ( + "net/http" + + "github.com/gin-gonic/gin" + + "github.com/anyproto/anytype-heart/core/api/pagination" + "github.com/anyproto/anytype-heart/core/api/util" +) + +// GetObjectsInListHandler +// +// @Summary Get objects in list +// @Description Returns a paginated list of objects that are associated with a specific list (or collection) within a space. This endpoint helps clients to manage grouped objects (for example, tasks within a list) by returning detailed object information for each item of the list. +// @Tags lists +// @Produce json +// @Param space_id path string true "Space ID" +// @Param list_id path string true "List ID" +// @Param offset query int false "The number of items to skip before starting to collect the result set" default(0) +// @Param limit query int false "The number of items to return" +// @Success 200 {object} pagination.PaginatedResponse[object.Object] "List of objects" +// @Failure 401 {object} util.UnauthorizedError "Unauthorized" +// @Failure 404 {object} util.NotFoundError "Not found" +// @Failure 500 {object} util.ServerError "Internal server error" +// @Security bearerauth +// @Router /v1/spaces/{space_id}/lists/{list_id}/objects [get] +func GetObjectsInListHandler(s *ListService) gin.HandlerFunc { + return func(c *gin.Context) { + spaceId := c.Param("space_id") + listId := c.Param("list_id") + offset := c.GetInt("offset") + limit := c.GetInt("limit") + + objects, total, hasMore, err := s.GetObjectsInList(c, spaceId, listId, offset, limit) + code := util.MapErrorCode(err, + util.ErrToCode(ErrFailedGetObjectsInList, http.StatusInternalServerError), + ) + + if code != http.StatusOK { + apiErr := util.CodeToAPIError(code, err.Error()) + c.JSON(code, apiErr) + return + } + + pagination.RespondWithPagination(c, http.StatusOK, objects, total, offset, limit, hasMore) + } +} + +// AddObjectsToListHandler +// +// @Summary Add objects to list +// @Description Enables clients to add one or more objects to a specific list by submitting a JSON array of object IDs. Upon success, the endpoint returns a confirmation message. This endpoint is vital for building user interfaces that allow dragโ€‘andโ€‘drop or multiโ€‘select additions to collections. +// @Tags lists +// @Accept json +// @Produce json +// @Param space_id path string true "Space ID" +// @Param list_id path string true "List ID" +// @Param objects body []string true "List of object IDs" +// @Success 200 {object} string "Objects added successfully" +// @Failure 400 {object} util.ValidationError "Bad request" +// @Failure 401 {object} util.UnauthorizedError "Unauthorized" +// @Failure 404 {object} util.NotFoundError "Not found" +// @Failure 500 {object} util.ServerError "Internal server error" +// @Security bearerauth +// @Router /v1/spaces/{space_id}/lists/{list_id}/objects [post] +func AddObjectsToListHandler(s *ListService) gin.HandlerFunc { + return func(c *gin.Context) { + spaceId := c.Param("space_id") + listId := c.Param("list_id") + + objects := []string{} + if err := c.ShouldBindJSON(&objects); err != nil { + apiErr := util.CodeToAPIError(http.StatusBadRequest, err.Error()) + c.JSON(http.StatusBadRequest, apiErr) + return + } + + err := s.AddObjectsToList(c, spaceId, listId, objects) + code := util.MapErrorCode(err, + util.ErrToCode(ErrFailedAddObjectsToList, http.StatusInternalServerError), + ) + + if code != http.StatusOK { + apiErr := util.CodeToAPIError(code, err.Error()) + c.JSON(code, apiErr) + return + } + + c.JSON(http.StatusOK, "Objects added successfully") + } +} + +// RemoveObjectFromListHandler +// +// @Summary Remove object from list +// @Description Removes a given object from the specified list in a space. The endpoint takes the space, list, and object identifiers as path parameters. It's subject to rate limiting and returns a success message on completion. It is used for dynamically managing collections without affecting the underlying object data. +// @Tags lists +// @Produce json +// @Param space_id path string true "Space ID" +// @Param list_id path string true "List ID" +// @Param object_id path string true "Object ID" +// @Success 200 {object} string "Objects removed successfully" +// @Failure 400 {object} util.ValidationError "Bad request" +// @Failure 401 {object} util.UnauthorizedError "Unauthorized" +// @Failure 404 {object} util.NotFoundError "Not found" +// @Failure 500 {object} util.ServerError "Internal server error" +// @Security bearerauth +// @Router /v1/spaces/{space_id}/lists/{list_id}/objects/{object_id} [delete] +func RemoveObjectFromListHandler(s *ListService) gin.HandlerFunc { + return func(c *gin.Context) { + spaceId := c.Param("space_id") + listId := c.Param("list_id") + objectId := c.Param("object_id") + + err := s.RemoveObjectFromList(c, spaceId, listId, objectId) + code := util.MapErrorCode(err, + util.ErrToCode(ErrFailedRemoveObjectsFromList, http.StatusInternalServerError), + ) + + if code != http.StatusOK { + apiErr := util.CodeToAPIError(code, err.Error()) + c.JSON(code, apiErr) + return + } + + c.JSON(http.StatusOK, "Objects removed successfully") + } +} diff --git a/core/api/internal/list/service.go b/core/api/internal/list/service.go new file mode 100644 index 0000000000..75e6f8407c --- /dev/null +++ b/core/api/internal/list/service.go @@ -0,0 +1,91 @@ +package list + +import ( + "context" + "errors" + + "github.com/anyproto/anytype-heart/core/api/internal/object" + "github.com/anyproto/anytype-heart/core/api/pagination" + "github.com/anyproto/anytype-heart/pb" + "github.com/anyproto/anytype-heart/pb/service" + "github.com/anyproto/anytype-heart/pkg/lib/bundle" +) + +var ( + ErrFailedGetObjectsInList = errors.New("failed to get objects in list") + ErrFailedAddObjectsToList = errors.New("failed to add objects to list") + ErrFailedRemoveObjectsFromList = errors.New("failed to remove objects from list") +) + +type Service interface { + GetObjectsInList(ctx context.Context, spaceId string, listId string, offset, limit int) ([]object.Object, int, bool, error) + AddObjectsToList(ctx context.Context, spaceId string, listId string, objectIds []string) error + RemoveObjectFromList(ctx context.Context, spaceId string, listId string, objectIds []string) error +} + +type ListService struct { + mw service.ClientCommandsServer + objectService *object.ObjectService +} + +func NewService(mw service.ClientCommandsServer, objectService *object.ObjectService) *ListService { + return &ListService{mw: mw, objectService: objectService} +} + +// GetObjectsInList retrieves objects in a list +func (s *ListService) GetObjectsInList(ctx context.Context, spaceId string, listId string, offset, limit int) ([]object.Object, int, bool, error) { + resp := s.mw.ObjectSearchSubscribe(ctx, &pb.RpcObjectSearchSubscribeRequest{ + SpaceId: spaceId, + Limit: int64(limit), // nolint: gosec + Offset: int64(offset), // nolint: gosec + Keys: []string{bundle.RelationKeyId.String()}, + CollectionId: listId, + }) + + // TODO: returned error from ObjectSearchSubscribe is inconsistent with other RPCs: Error is nil instead of Code being NULL + if resp.Error != nil && resp.Error.Code != pb.RpcObjectSearchSubscribeResponseError_NULL { + return nil, 0, false, ErrFailedGetObjectsInList + } + + total := int(resp.Counters.Total) + paginatedRecords, hasMore := pagination.Paginate(resp.Records, offset, limit) + + objects := make([]object.Object, 0, len(paginatedRecords)) + for _, record := range paginatedRecords { + object, err := s.objectService.GetObject(ctx, spaceId, record.Fields[bundle.RelationKeyId.String()].GetStringValue()) + if err != nil { + return nil, 0, false, err + } + objects = append(objects, object) + } + + return objects, total, hasMore, nil +} + +// AddObjectsToList adds objects to a list +func (s *ListService) AddObjectsToList(ctx context.Context, spaceId string, listId string, objectIds []string) error { + resp := s.mw.ObjectCollectionAdd(ctx, &pb.RpcObjectCollectionAddRequest{ + ContextId: listId, + ObjectIds: objectIds, + }) + + if resp.Error.Code != pb.RpcObjectCollectionAddResponseError_NULL { + return ErrFailedAddObjectsToList + } + + return nil +} + +// RemoveObjectFromList removes objects from a list +func (s *ListService) RemoveObjectFromList(ctx context.Context, spaceId string, listId string, objectId string) error { + resp := s.mw.ObjectCollectionRemove(ctx, &pb.RpcObjectCollectionRemoveRequest{ + ContextId: spaceId, + ObjectIds: []string{objectId}, + }) + + if resp.Error.Code != pb.RpcObjectCollectionRemoveResponseError_NULL { + return ErrFailedRemoveObjectsFromList + } + + return nil +} diff --git a/core/api/internal/object/handler.go b/core/api/internal/object/handler.go index 671f255839..c62f18768e 100644 --- a/core/api/internal/object/handler.go +++ b/core/api/internal/object/handler.go @@ -11,17 +11,18 @@ import ( // GetObjectsHandler retrieves a list of objects in a space // -// @Summary List objects -// @Tags objects -// @Accept json -// @Produce json -// @Param space_id path string true "Space ID" -// @Param offset query int false "The number of items to skip before starting to collect the result set" default(0) -// @Param limit query int false "The number of items to return" default(100) maximum(1000) -// @Success 200 {object} pagination.PaginatedResponse[Object] "List of objects" -// @Failure 401 {object} util.UnauthorizedError "Unauthorized" -// @Failure 500 {object} util.ServerError "Internal server error" -// @Router /spaces/{space_id}/objects [get] +// @Summary List objects +// @Description Retrieves a paginated list of objects in the given space. The endpoint takes query parameters for pagination (offset and limit) and returns detailed data about each object including its ID, name, icon, type information, a snippet of the content (if applicable), layout, space ID, blocks and details. It is intended for building views where users can see all objects in a space at a glance. +// @Tags objects +// @Produce json +// @Param space_id path string true "Space ID" +// @Param offset query int false "The number of items to skip before starting to collect the result set" default(0) +// @Param limit query int false "The number of items to return" default(100) maximum(1000) +// @Success 200 {object} pagination.PaginatedResponse[Object] "List of objects" +// @Failure 401 {object} util.UnauthorizedError "Unauthorized" +// @Failure 500 {object} util.ServerError "Internal server error" +// @Security bearerauth +// @Router /spaces/{space_id}/objects [get] func GetObjectsHandler(s *ObjectService) gin.HandlerFunc { return func(c *gin.Context) { spaceId := c.Param("space_id") @@ -47,17 +48,18 @@ func GetObjectsHandler(s *ObjectService) gin.HandlerFunc { // GetObjectHandler retrieves an object in a space // -// @Summary Get object -// @Tags objects -// @Accept json -// @Produce json -// @Param space_id path string true "Space ID" -// @Param object_id path string true "Object ID" -// @Success 200 {object} ObjectResponse "The requested object" -// @Failure 401 {object} util.UnauthorizedError "Unauthorized" -// @Failure 404 {object} util.NotFoundError "Resource not found" -// @Failure 500 {object} util.ServerError "Internal server error" -// @Router /spaces/{space_id}/objects/{object_id} [get] +// @Summary Get object +// @Description Fetches the full details of a single object identified by the object ID within the specified space. The response includes not only basic metadata (ID, name, icon, type) but also the complete set of blocks (which may include text, files, and relations) and extra details (such as timestamps and linked member information). This endpoint is essential when a client needs to render or edit the full object view. +// @Tags objects +// @Produce json +// @Param space_id path string true "Space ID" +// @Param object_id path string true "Object ID" +// @Success 200 {object} ObjectResponse "The requested object" +// @Failure 401 {object} util.UnauthorizedError "Unauthorized" +// @Failure 404 {object} util.NotFoundError "Resource not found" +// @Failure 500 {object} util.ServerError "Internal server error" +// @Security bearerauth +// @Router /spaces/{space_id}/objects/{object_id} [get] func GetObjectHandler(s *ObjectService) gin.HandlerFunc { return func(c *gin.Context) { spaceId := c.Param("space_id") @@ -81,18 +83,20 @@ func GetObjectHandler(s *ObjectService) gin.HandlerFunc { // DeleteObjectHandler deletes an object in a space // -// @Summary Delete object -// @Tags objects -// @Accept json -// @Produce json -// @Param space_id path string true "Space ID" -// @Param object_id path string true "Object ID" -// @Success 200 {object} ObjectResponse "The deleted object" -// @Failure 401 {object} util.UnauthorizedError "Unauthorized" -// @Failure 403 {object} util.ForbiddenError "Forbidden" -// @Failure 404 {object} util.NotFoundError "Resource not found" -// @Failure 500 {object} util.ServerError "Internal server error" -// @Router /spaces/{space_id}/objects/{object_id} [delete] +// @Summary Delete object +// @Description This endpoint โ€œdeletesโ€ an object by marking it as archived. The deletion process is performed safely and is subject to rate limiting. It returns the objectโ€™s details after it has been archived. Proper error handling is in place for situations such as when the object isnโ€™t found or the deletion cannot be performed because of permission issues. +// @Tags objects +// @Produce json +// @Param space_id path string true "Space ID" +// @Param object_id path string true "Object ID" +// @Success 200 {object} ObjectResponse "The deleted object" +// @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" +// @Security bearerauth +// @Router /spaces/{space_id}/objects/{object_id} [delete] func DeleteObjectHandler(s *ObjectService) gin.HandlerFunc { return func(c *gin.Context) { spaceId := c.Param("space_id") @@ -117,17 +121,20 @@ func DeleteObjectHandler(s *ObjectService) gin.HandlerFunc { // CreateObjectHandler creates a new object in a space // -// @Summary Create object -// @Tags objects -// @Accept json -// @Produce json -// @Param space_id path string true "Space ID" -// @Param object body CreateObjectRequest true "Object to create" -// @Success 200 {object} ObjectResponse "The created object" -// @Failure 400 {object} util.ValidationError "Bad request" -// @Failure 401 {object} util.UnauthorizedError "Unauthorized" -// @Failure 500 {object} util.ServerError "Internal server error" -// @Router /spaces/{space_id}/objects [post] +// @Summary Create object +// @Description Creates a new object in the specified space using a JSON payload. The creation process is subject to rate limiting. The payload must include key details such as the object name, icon, description, body content (which may support Markdown), source URL (required for bookmark objects), template identifier, and the unique key for the object type. Post-creation, additional operations (like setting featured relations or fetching bookmark metadata) may occur. The endpoint then returns the full object data, ready for further interactions. +// @Tags objects +// @Accept json +// @Produce json +// @Param space_id path string true "Space ID" +// @Param object body CreateObjectRequest true "Object to create" +// @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" +// @Security bearerauth +// @Router /spaces/{space_id}/objects [post] func CreateObjectHandler(s *ObjectService) gin.HandlerFunc { return func(c *gin.Context) { spaceId := c.Param("space_id") @@ -161,17 +168,18 @@ func CreateObjectHandler(s *ObjectService) gin.HandlerFunc { // GetTypesHandler retrieves a list of types in a space // -// @Summary List types -// @Tags types -// @Accept json -// @Produce json -// @Param space_id path string true "Space ID" -// @Param offset query int false "The number of items to skip before starting to collect the result set" default(0) -// @Param limit query int false "The number of items to return" default(100) maximum(1000) -// @Success 200 {object} pagination.PaginatedResponse[Type] "List of types" -// @Failure 401 {object} util.UnauthorizedError "Unauthorized" -// @Failure 500 {object} util.ServerError "Internal server error" -// @Router /spaces/{space_id}/types [get] +// @Summary List types +// @Description This endpoint retrieves a paginated list of object types (e.g. 'Page', 'Note', 'Task') available within the specified space. Each typeโ€™s record includes its unique identifier, unique key, display name, icon, and a recommended layout. Clients use this information when offering choices for object creation or for filtering objects by type. +// @Tags types +// @Produce json +// @Param space_id path string true "Space ID" +// @Param offset query int false "The number of items to skip before starting to collect the result set" default(0) +// @Param limit query int false "The number of items to return" default(100) maximum(1000) +// @Success 200 {object} pagination.PaginatedResponse[Type] "List of types" +// @Failure 401 {object} util.UnauthorizedError "Unauthorized" +// @Failure 500 {object} util.ServerError "Internal server error" +// @Security bearerauth +// @Router /spaces/{space_id}/types [get] func GetTypesHandler(s *ObjectService) gin.HandlerFunc { return func(c *gin.Context) { spaceId := c.Param("space_id") @@ -195,17 +203,18 @@ func GetTypesHandler(s *ObjectService) gin.HandlerFunc { // GetTypeHandler retrieves a type in a space // -// @Summary Get type -// @Tags types -// @Accept json -// @Produce json -// @Param space_id path string true "Space ID" -// @Param type_id path string true "Type ID" -// @Success 200 {object} TypeResponse "The requested type" -// @Failure 401 {object} util.UnauthorizedError "Unauthorized" -// @Failure 404 {object} util.NotFoundError "Resource not found" -// @Failure 500 {object} util.ServerError "Internal server error" -// @Router /spaces/{space_id}/types/{type_id} [get] +// @Summary Get type +// @Description Fetches detailed information about one specific object type by its ID. This includes the typeโ€™s unique key, name, icon, and recommended layout. This detailed view assists clients in understanding the expected structure and style for objects of that type and in guiding the user interface (such as displaying appropriate icons or layout hints). +// @Tags types +// @Produce json +// @Param space_id path string true "Space ID" +// @Param type_id path string true "Type ID" +// @Success 200 {object} TypeResponse "The requested type" +// @Failure 401 {object} util.UnauthorizedError "Unauthorized" +// @Failure 404 {object} util.NotFoundError "Resource not found" +// @Failure 500 {object} util.ServerError "Internal server error" +// @Security bearerauth +// @Router /spaces/{space_id}/types/{type_id} [get] func GetTypeHandler(s *ObjectService) gin.HandlerFunc { return func(c *gin.Context) { spaceId := c.Param("space_id") @@ -229,18 +238,19 @@ func GetTypeHandler(s *ObjectService) gin.HandlerFunc { // GetTemplatesHandler retrieves a list of templates for a type in a space // -// @Summary List templates -// @Tags types -// @Accept json -// @Produce json -// @Param space_id path string true "Space ID" -// @Param type_id path string true "Type ID" -// @Param offset query int false "The number of items to skip before starting to collect the result set" default(0) -// @Param limit query int false "The number of items to return" default(100) maximum(1000) -// @Success 200 {object} pagination.PaginatedResponse[Template] "List of templates" -// @Failure 401 {object} util.UnauthorizedError "Unauthorized" -// @Failure 500 {object} util.ServerError "Internal server error" -// @Router /spaces/{space_id}/types/{type_id}/templates [get] +// @Summary List templates +// @Description This endpoint returns a paginated list of templates that are associated with a specific object type within a space. Templates provide preโ€‘configured structures for creating new objects. Each template record contains its identifier, name, and icon, so that clients can offer users a selection of templates when creating objects. +// @Tags types +// @Produce json +// @Param space_id path string true "Space ID" +// @Param type_id path string true "Type ID" +// @Param offset query int false "The number of items to skip before starting to collect the result set" default(0) +// @Param limit query int false "The number of items to return" default(100) maximum(1000) +// @Success 200 {object} pagination.PaginatedResponse[Template] "List of templates" +// @Failure 401 {object} util.UnauthorizedError "Unauthorized" +// @Failure 500 {object} util.ServerError "Internal server error" +// @Security bearerauth +// @Router /spaces/{space_id}/types/{type_id}/templates [get] func GetTemplatesHandler(s *ObjectService) gin.HandlerFunc { return func(c *gin.Context) { spaceId := c.Param("space_id") @@ -268,18 +278,19 @@ func GetTemplatesHandler(s *ObjectService) gin.HandlerFunc { // GetTemplateHandler retrieves a template for a type in a space // -// @Summary Get template -// @Tags types -// @Accept json -// @Produce json -// @Param space_id path string true "Space ID" -// @Param type_id path string true "Type ID" -// @Param template_id path string true "Template ID" -// @Success 200 {object} TemplateResponse "The requested template" -// @Failure 401 {object} util.UnauthorizedError "Unauthorized" -// @Failure 404 {object} util.NotFoundError "Resource not found" -// @Failure 500 {object} util.ServerError "Internal server error" -// @Router /spaces/{space_id}/types/{type_id}/templates/{template_id} [get] +// @Summary Get template +// @Description Fetches full details for one template associated with a particular object type in a space. The response provides the templateโ€™s identifier, name, icon, and any other relevant metadata. This endpoint is useful when a client needs to preview or apply a template to prefill object creation fields. +// @Tags types +// @Produce json +// @Param space_id path string true "Space ID" +// @Param type_id path string true "Type ID" +// @Param template_id path string true "Template ID" +// @Success 200 {object} TemplateResponse "The requested template" +// @Failure 401 {object} util.UnauthorizedError "Unauthorized" +// @Failure 404 {object} util.NotFoundError "Resource not found" +// @Failure 500 {object} util.ServerError "Internal server error" +// @Security bearerauth +// @Router /spaces/{space_id}/types/{type_id}/templates/{template_id} [get] func GetTemplateHandler(s *ObjectService) gin.HandlerFunc { return func(c *gin.Context) { spaceId := c.Param("space_id") diff --git a/core/api/internal/object/model.go b/core/api/internal/object/model.go index 70ba449aa0..b83284b3ce 100644 --- a/core/api/internal/object/model.go +++ b/core/api/internal/object/model.go @@ -1,93 +1,100 @@ 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"` + Object string `json:"object" example:"object"` // The data model 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 + Type Type `json:"type"` // The type 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 + Relation *Relation `json:"relation,omitempty"` // The relation 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 +} + +// TODO: fill in the relation struct +type Relation struct { + Id string } 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"` + Object string `json:"object" example:"type"` // The data model 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:"๐Ÿ“„"` + Object string `json:"object" example:"template"` // The data model 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..9fdc624405 100644 --- a/core/api/internal/object/service.go +++ b/core/api/internal/object/service.go @@ -3,13 +3,14 @@ package object import ( "context" "errors" - "time" "github.com/gogo/protobuf/types" + "github.com/iancoleman/strcase" "github.com/anyproto/anytype-heart/core/api/internal/space" "github.com/anyproto/anytype-heart/core/api/pagination" "github.com/anyproto/anytype-heart/core/api/util" + "github.com/anyproto/anytype-heart/core/domain" "github.com/anyproto/anytype-heart/pb" "github.com/anyproto/anytype-heart/pb/service" "github.com/anyproto/anytype-heart/pkg/lib/bundle" @@ -63,10 +64,13 @@ func NewService(mw service.ClientCommandsServer, spaceService *space.SpaceServic // ListObjects retrieves a paginated list of objects in a specific space. func (s *ObjectService) ListObjects(ctx context.Context, spaceId string, offset int, limit int) (objects []Object, total int, hasMore bool, err error) { + typeId, err := util.ResolveUniqueKeyToTypeId(s.mw, spaceId, "ot-template") + resp := s.mw.ObjectSearch(ctx, &pb.RpcObjectSearchRequest{ SpaceId: spaceId, Filters: []*model.BlockContentDataviewFilter{ { + Operator: model.BlockContentDataviewFilter_No, RelationKey: bundle.RelationKeyLayout.String(), Condition: model.BlockContentDataviewFilter_In, Value: pbtypes.IntList([]int{ @@ -80,6 +84,18 @@ func (s *ObjectService) ListObjects(ctx context.Context, spaceId string, offset int(model.ObjectType_participant), }...), }, + { + Operator: model.BlockContentDataviewFilter_No, + RelationKey: bundle.RelationKeyType.String(), + Condition: model.BlockContentDataviewFilter_NotEqual, + Value: pbtypes.String(typeId), + }, + { + Operator: model.BlockContentDataviewFilter_No, + RelationKey: bundle.RelationKeyIsHidden.String(), + Condition: model.BlockContentDataviewFilter_NotEqual, + Value: pbtypes.Bool(true), + }, }, Sorts: []*model.BlockContentDataviewSort{{ RelationKey: bundle.RelationKeyLastModifiedDate.String(), @@ -117,7 +133,7 @@ func (s *ObjectService) GetObject(ctx context.Context, spaceId string, objectId ObjectId: objectId, }) - if resp.Error.Code == pb.RpcObjectShowResponseError_NOT_FOUND { + if resp.Error.Code == pb.RpcObjectShowResponseError_NOT_FOUND || resp.ObjectView.Details[0].Details.Fields[bundle.RelationKeyIsArchived.String()].GetBoolValue() { return Object{}, ErrObjectNotFound } @@ -126,22 +142,19 @@ func (s *ObjectService) GetObject(ctx context.Context, spaceId string, objectId } icon := util.GetIconFromEmojiOrImage(s.AccountInfo, resp.ObjectView.Details[0].Details.Fields[bundle.RelationKeyIconEmoji.String()].GetStringValue(), resp.ObjectView.Details[0].Details.Fields[bundle.RelationKeyIconImage.String()].GetStringValue()) - objectTypeName, err := util.ResolveTypeToName(s.mw, spaceId, resp.ObjectView.Details[0].Details.Fields[bundle.RelationKeyType.String()].GetStringValue()) - if err != nil { - return Object{}, err - } object := Object{ - Type: objectTypeName, + Object: "object", Id: resp.ObjectView.Details[0].Details.Fields[bundle.RelationKeyId.String()].GetStringValue(), Name: resp.ObjectView.Details[0].Details.Fields[bundle.RelationKeyName.String()].GetStringValue(), Icon: icon, + Type: s.getTypeFromDetails(resp.ObjectView.Details[0].Details.Fields[bundle.RelationKeyType.String()].GetStringValue(), resp.ObjectView.Details), Snippet: resp.ObjectView.Details[0].Details.Fields[bundle.RelationKeySnippet.String()].GetStringValue(), Layout: model.ObjectTypeLayout_name[int32(resp.ObjectView.Details[0].Details.Fields[bundle.RelationKeyLayout.String()].GetNumberValue())], SpaceId: resp.ObjectView.Details[0].Details.Fields[bundle.RelationKeySpaceId.String()].GetStringValue(), RootId: resp.ObjectView.RootId, - Blocks: s.GetBlocks(resp), - Details: s.GetDetails(resp), + Blocks: s.getBlocks(resp), + Details: s.getDetails(resp), } return object, nil @@ -270,11 +283,13 @@ func (s *ObjectService) ListTypes(ctx context.Context, spaceId string, offset in SpaceId: spaceId, Filters: []*model.BlockContentDataviewFilter{ { + Operator: model.BlockContentDataviewFilter_No, RelationKey: bundle.RelationKeyLayout.String(), Condition: model.BlockContentDataviewFilter_Equal, Value: pbtypes.Int64(int64(model.ObjectType_objectType)), }, { + Operator: model.BlockContentDataviewFilter_No, RelationKey: bundle.RelationKeyIsHidden.String(), Condition: model.BlockContentDataviewFilter_NotEqual, Value: pbtypes.Bool(true), @@ -299,7 +314,7 @@ func (s *ObjectService) ListTypes(ctx context.Context, spaceId string, offset in for _, record := range paginatedTypes { types = append(types, Type{ - Type: "type", + Object: "type", Id: record.Fields[bundle.RelationKeyId.String()].GetStringValue(), UniqueKey: record.Fields[bundle.RelationKeyUniqueKey.String()].GetStringValue(), Name: record.Fields[bundle.RelationKeyName.String()].GetStringValue(), @@ -326,7 +341,7 @@ func (s *ObjectService) GetType(ctx context.Context, spaceId string, typeId stri } return Type{ - Type: "type", + Object: "type", Id: typeId, UniqueKey: resp.ObjectView.Details[0].Details.Fields[bundle.RelationKeyUniqueKey.String()].GetStringValue(), Name: resp.ObjectView.Details[0].Details.Fields[bundle.RelationKeyName.String()].GetStringValue(), @@ -342,6 +357,7 @@ func (s *ObjectService) ListTemplates(ctx context.Context, spaceId string, typeI SpaceId: spaceId, Filters: []*model.BlockContentDataviewFilter{ { + Operator: model.BlockContentDataviewFilter_No, RelationKey: bundle.RelationKeyUniqueKey.String(), Condition: model.BlockContentDataviewFilter_Equal, Value: pbtypes.String("ot-template"), @@ -364,6 +380,7 @@ func (s *ObjectService) ListTemplates(ctx context.Context, spaceId string, typeI SpaceId: spaceId, Filters: []*model.BlockContentDataviewFilter{ { + Operator: model.BlockContentDataviewFilter_No, RelationKey: bundle.RelationKeyType.String(), Condition: model.BlockContentDataviewFilter_Equal, Value: pbtypes.String(templateTypeId), @@ -399,10 +416,10 @@ func (s *ObjectService) ListTemplates(ctx context.Context, spaceId string, typeI } templates = append(templates, Template{ - Type: "template", - Id: templateId, - Name: templateResp.ObjectView.Details[0].Details.Fields[bundle.RelationKeyName.String()].GetStringValue(), - Icon: templateResp.ObjectView.Details[0].Details.Fields[bundle.RelationKeyIconEmoji.String()].GetStringValue(), + Object: "template", + Id: templateId, + Name: templateResp.ObjectView.Details[0].Details.Fields[bundle.RelationKeyName.String()].GetStringValue(), + Icon: templateResp.ObjectView.Details[0].Details.Fields[bundle.RelationKeyIconEmoji.String()].GetStringValue(), }) } @@ -425,80 +442,229 @@ func (s *ObjectService) GetTemplate(ctx context.Context, spaceId string, typeId } return Template{ - Type: "template", - Id: templateId, - Name: resp.ObjectView.Details[0].Details.Fields[bundle.RelationKeyName.String()].GetStringValue(), - Icon: resp.ObjectView.Details[0].Details.Fields[bundle.RelationKeyIconEmoji.String()].GetStringValue(), + Object: "template", + Id: templateId, + Name: resp.ObjectView.Details[0].Details.Fields[bundle.RelationKeyName.String()].GetStringValue(), + Icon: resp.ObjectView.Details[0].Details.Fields[bundle.RelationKeyIconEmoji.String()].GetStringValue(), }, nil } -// GetDetails returns the list of details from the ObjectShowResponse. -func (s *ObjectService) GetDetails(resp *pb.RpcObjectShowResponse) []Detail { - creator := resp.ObjectView.Details[0].Details.Fields[bundle.RelationKeyCreator.String()].GetStringValue() - lastModifiedBy := resp.ObjectView.Details[0].Details.Fields[bundle.RelationKeyLastModifiedBy.String()].GetStringValue() +// getTypeFromDetails returns the type from the details of the ObjectShowResponse. +func (s *ObjectService) getTypeFromDetails(typeId string, details []*model.ObjectViewDetailsSet) Type { + var objectTypeDetail *types.Struct + for _, detail := range details { + if detail.Id == typeId { + objectTypeDetail = detail.GetDetails() + break + } + } + + if objectTypeDetail == nil { + return Type{} + } + + return Type{ + Object: "type", + Id: typeId, + UniqueKey: objectTypeDetail.Fields[bundle.RelationKeyUniqueKey.String()].GetStringValue(), + Name: objectTypeDetail.Fields[bundle.RelationKeyName.String()].GetStringValue(), + Icon: objectTypeDetail.Fields[bundle.RelationKeyIconEmoji.String()].GetStringValue(), + RecommendedLayout: model.ObjectTypeLayout_name[int32(objectTypeDetail.Fields[bundle.RelationKeyRecommendedLayout.String()].GetNumberValue())], + } +} + +// getDetails returns a list of details by iterating over all relations found in the RelationLinks and mapping their format and value. +func (s *ObjectService) getDetails(resp *pb.RpcObjectShowResponse) []Detail { + relationFormatMap := s.getRelationFormatMap(resp.ObjectView.RelationLinks) + linkedRelations := resp.ObjectView.RelationLinks + primaryDetailFields := resp.ObjectView.Details[0].Details.Fields + + // system relations to be excluded + excludeRelations := map[string]bool{ + bundle.RelationKeyId.String(): true, + bundle.RelationKeySpaceId.String(): true, + bundle.RelationKeyName.String(): true, + bundle.RelationKeyIconEmoji.String(): true, + bundle.RelationKeyIconImage.String(): true, + bundle.RelationKeyType.String(): true, + bundle.RelationKeyLayout.String(): true, + bundle.RelationKeyIsFavorite.String(): true, + bundle.RelationKeyIsArchived.String(): true, + bundle.RelationKeyIsDeleted.String(): true, + bundle.RelationKeyIsHidden.String(): true, + bundle.RelationKeyWorkspaceId.String(): true, + bundle.RelationKeyInternalFlags.String(): true, + bundle.RelationKeyRestrictions.String(): true, + bundle.RelationKeyOrigin.String(): true, + bundle.RelationKeySnippet.String(): true, + bundle.RelationKeySyncStatus.String(): true, + bundle.RelationKeySyncError.String(): true, + bundle.RelationKeySyncDate.String(): true, + bundle.RelationKeyCoverId.String(): true, + bundle.RelationKeyCoverType.String(): true, + bundle.RelationKeyCoverScale.String(): true, + bundle.RelationKeyCoverX.String(): true, + bundle.RelationKeyCoverY.String(): true, + bundle.RelationKeyMentions.String(): true, + bundle.RelationKeyOldAnytypeID.String(): true, + bundle.RelationKeySource.String(): true, + bundle.RelationKeySourceFilePath.String(): true, + bundle.RelationKeyImportType.String(): true, + bundle.RelationKeyTargetObjectType.String(): true, + bundle.RelationKeyFeaturedRelations.String(): true, + bundle.RelationKeySetOf.String(): true, + bundle.RelationKeyLinks.String(): true, + bundle.RelationKeyBacklinks.String(): true, + bundle.RelationKeySourceObject.String(): true, + bundle.RelationKeyLayoutAlign.String(): true, + } - var creatorId, lastModifiedById string - for _, detail := range resp.ObjectView.Details { - if detail.Id == creator { - creatorId = detail.Id + var details []Detail + for _, r := range linkedRelations { + key := r.Key + if _, isExcluded := excludeRelations[key]; isExcluded { + continue } - if detail.Id == lastModifiedBy { - lastModifiedById = detail.Id + + if val, ok := primaryDetailFields[key]; ok { + id, name := s.getRelation(key, resp) + format := relationFormatMap[key] + details = append(details, Detail{ + Id: id, + Details: map[string]interface{}{ + "name": name, + "type": format, + format: s.convertValue(key, val, format, resp.ObjectView.Details), + }, + }) } } + return details +} - return []Detail{ - { - Id: "last_modified_date", - Details: map[string]interface{}{ - "last_modified_date": PosixToISO8601(resp.ObjectView.Details[0].Details.Fields[bundle.RelationKeyLastModifiedDate.String()].GetNumberValue()), - }, - }, - { - 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), - }, - }, - { - Id: "created_date", - Details: map[string]interface{}{ - "created_date": PosixToISO8601(resp.ObjectView.Details[0].Details.Fields[bundle.RelationKeyCreatedDate.String()].GetNumberValue()), - }, - }, - { - Id: "created_by", - Details: map[string]interface{}{ - "details": s.spaceService.GetParticipantDetails(s.mw, resp.ObjectView.Details[0].Details.Fields[bundle.RelationKeySpaceId.String()].GetStringValue(), creatorId), - }, - }, - { - Id: "last_opened_date", - Details: map[string]interface{}{ - "last_opened_date": PosixToISO8601(resp.ObjectView.Details[0].Details.Fields[bundle.RelationKeyLastOpenedDate.String()].GetNumberValue()), - }, - }, - { - Id: "tags", - Details: map[string]interface{}{ - "tags": s.getTags(resp), - }, - }, +// getRelationName returns the relation id and relation name from the ObjectShowResponse. +func (s *ObjectService) getRelation(key string, resp *pb.RpcObjectShowResponse) (id string, name string) { + relation, err := bundle.GetRelation(domain.RelationKey(key)) + if err != nil { + name, err = util.ResolveRelationKeyToRelationName(s.mw, resp.ObjectView.Details[0].Details.Fields[bundle.RelationKeySpaceId.String()].GetStringValue(), key) + if err != nil { + return key, key + } + return key, name + } + + // special cases of relation keys and names + if key == bundle.RelationKeyCreator.String() { + return "created_by", "Created By" + } else if key == bundle.RelationKeyCreatedDate.String() { + return "created_date", "Created Date" + } + + return strcase.ToSnake(key), relation.Name +} + +// convertValue converts a protobuf types.Value into a native Go value. +func (s *ObjectService) convertValue(key string, value *types.Value, format string, details []*model.ObjectViewDetailsSet) interface{} { + switch kind := value.Kind.(type) { + case *types.Value_NullValue: + return nil + case *types.Value_NumberValue: + if format == "date" { + return util.PosixToISO8601(kind.NumberValue) + } + return kind.NumberValue + case *types.Value_StringValue: + if key == bundle.RelationKeyCreator.String() || key == bundle.RelationKeyLastModifiedBy.String() { + member, err := s.spaceService.GetMember(context.Background(), details[0].Details.Fields[bundle.RelationKeySpaceId.String()].GetStringValue(), kind.StringValue) + if err != nil { + return nil + } + return member + } + + // TODO: investigate how this is possible? select option not list and not returned in further details + if format == "select" || format == "multi_select" { + return s.resolveTag(details[0].Details.Fields[bundle.RelationKeySpaceId.String()].GetStringValue(), kind.StringValue) + } + + return kind.StringValue + case *types.Value_BoolValue: + return kind.BoolValue + case *types.Value_StructValue: + m := make(map[string]interface{}) + for k, v := range kind.StructValue.Fields { + m[k] = s.convertValue(key, v, format, details) + } + return m + case *types.Value_ListValue: + var list []interface{} + for _, v := range kind.ListValue.Values { + list = append(list, s.convertValue(key, v, format, details)) + } + + if format == "select" || format == "multi_select" { + return s.getTags(key, details) + } + + return list + default: + return nil + } +} + +// getRelationFormatMapFromResponse returns the map of relation key to relation format from the ObjectShowResponse. +func (s *ObjectService) getRelationFormatMap(relationLinks []*model.RelationLink) map[string]string { + relationFormatToName := make(map[int32]string, len(model.RelationFormat_name)) + for k, v := range model.RelationFormat_name { + relationFormatToName[k] = v + } + relationFormatToName[int32(model.RelationFormat_longtext)] = "text" + relationFormatToName[int32(model.RelationFormat_shorttext)] = "text" + relationFormatToName[int32(model.RelationFormat_tag)] = "multi_select" + relationFormatToName[int32(model.RelationFormat_status)] = "select" + + relationFormatMap := map[string]string{} + for _, detail := range relationLinks { + relationFormatMap[detail.Key] = relationFormatToName[int32(detail.Format)] + } + + return relationFormatMap +} + +// TODO: remove once bug of select option not being returned in details is fixed +func (s *ObjectService) resolveTag(spaceId, tagId string) Tag { + if tagId == "" { + return Tag{} + } + + resp := s.mw.ObjectShow(context.Background(), &pb.RpcObjectShowRequest{ + SpaceId: spaceId, + ObjectId: tagId, + }) + + if resp.Error.Code != pb.RpcObjectShowResponseError_NULL { + return Tag{} + } + + return Tag{ + Id: tagId, + Name: resp.ObjectView.Details[0].Details.Fields[bundle.RelationKeyName.String()].GetStringValue(), + Color: resp.ObjectView.Details[0].Details.Fields[bundle.RelationKeyRelationOptionColor.String()].GetStringValue(), } } // getTags returns the list of tags from the ObjectShowResponse -func (s *ObjectService) getTags(resp *pb.RpcObjectShowResponse) []Tag { +func (s *ObjectService) getTags(key string, details []*model.ObjectViewDetailsSet) []Tag { tags := []Tag{} - tagField, ok := resp.ObjectView.Details[0].Details.Fields["tag"] + tagField, ok := details[0].Details.Fields[key] if !ok || tagField.GetListValue() == nil { return tags } for _, tagId := range tagField.GetListValue().Values { id := tagId.GetStringValue() - for _, detail := range resp.ObjectView.Details { + for _, detail := range details { if detail.Id == id { tags = append(tags, Tag{ Id: id, @@ -512,13 +678,14 @@ func (s *ObjectService) getTags(resp *pb.RpcObjectShowResponse) []Tag { return tags } -// GetBlocks returns the list of blocks from the ObjectShowResponse. -func (s *ObjectService) GetBlocks(resp *pb.RpcObjectShowResponse) []Block { +// getBlocks returns the list of blocks from the ObjectShowResponse. +func (s *ObjectService) getBlocks(resp *pb.RpcObjectShowResponse) []Block { blocks := []Block{} for _, block := range resp.ObjectView.Blocks { var text *Text var file *File + var relation *Relation switch content := block.Content.(type) { case *model.BlockContentOfText: @@ -541,8 +708,12 @@ func (s *ObjectService) GetBlocks(resp *pb.RpcObjectShowResponse) []Block { State: model.BlockContentFileState_name[int32(content.File.State)], Style: model.BlockContentFileStyle_name[int32(content.File.Style)], } - // TODO: other content types? + case *model.BlockContentOfRelation: + relation = &Relation{ + Id: content.Relation.Key, + } } + // TODO: other content types? blocks = append(blocks, Block{ Id: block.Id, @@ -552,13 +723,9 @@ func (s *ObjectService) GetBlocks(resp *pb.RpcObjectShowResponse) []Block { VerticalAlign: model.BlockVerticalAlign_name[int32(block.VerticalAlign)], Text: text, File: file, + Relation: relation, }) } return blocks } - -func PosixToISO8601(posix float64) string { - t := time.Unix(int64(posix), 0).UTC() - return t.Format(time.RFC3339) -} diff --git a/core/api/internal/object/service_test.go b/core/api/internal/object/service_test.go index 10ba7e1c4d..1c78386322 100644 --- a/core/api/internal/object/service_test.go +++ b/core/api/internal/object/service_test.go @@ -23,11 +23,11 @@ const ( gatewayUrl = "http://localhost:31006" mockedSpaceId = "mocked-space-id" mockedObjectId = "mocked-object-id" - mockedObjectType = "mocked-object-type" mockedNewObjectId = "mocked-new-object-id" mockedObjectName = "mocked-object-name" mockedObjectSnippet = "mocked-object-snippet" mockedObjectIcon = "๐Ÿ”" + mockedParticipantId = "mocked-participant-id" mockedObjectTypeUniqueKey = "ot-page" mockedTypeId = "mocked-type-id" mockedTypeName = "mocked-type-name" @@ -65,6 +65,29 @@ func TestObjectService_ListObjects(t *testing.T) { ctx := context.Background() fx := newFixture(t) + // Mock template type resolution + fx.mwMock.On("ObjectSearch", mock.Anything, &pb.RpcObjectSearchRequest{ + SpaceId: mockedSpaceId, + Filters: []*model.BlockContentDataviewFilter{ + { + RelationKey: bundle.RelationKeyUniqueKey.String(), + Condition: model.BlockContentDataviewFilter_Equal, + Value: pbtypes.String("ot-template"), + }, + }, + Keys: []string{bundle.RelationKeyId.String()}, + }).Return(&pb.RpcObjectSearchResponse{ + Error: &pb.RpcObjectSearchResponseError{Code: pb.RpcObjectSearchResponseError_NULL}, + Records: []*types.Struct{ + { + Fields: map[string]*types.Value{ + bundle.RelationKeyId.String(): pbtypes.String(mockedTypeId), + }, + }, + }, + }).Once() + + // Mock object search fx.mwMock.On("ObjectSearch", mock.Anything, &pb.RpcObjectSearchRequest{ SpaceId: mockedSpaceId, Filters: []*model.BlockContentDataviewFilter{ @@ -82,6 +105,18 @@ func TestObjectService_ListObjects(t *testing.T) { int(model.ObjectType_participant), }...), }, + { + Operator: model.BlockContentDataviewFilter_No, + RelationKey: bundle.RelationKeyType.String(), + Condition: model.BlockContentDataviewFilter_NotEqual, + Value: pbtypes.String(mockedTypeId), + }, + { + Operator: model.BlockContentDataviewFilter_No, + RelationKey: bundle.RelationKeyIsHidden.String(), + Condition: model.BlockContentDataviewFilter_NotEqual, + Value: pbtypes.Bool(true), + }, }, Sorts: []*model.BlockContentDataviewSort{{ RelationKey: bundle.RelationKeyLastModifiedDate.String(), @@ -99,7 +134,7 @@ func TestObjectService_ListObjects(t *testing.T) { bundle.RelationKeyName.String(): pbtypes.String(mockedObjectName), bundle.RelationKeySnippet.String(): pbtypes.String(mockedObjectSnippet), bundle.RelationKeyIconEmoji.String(): pbtypes.String(mockedObjectIcon), - bundle.RelationKeyType.String(): pbtypes.String(mockedObjectTypeUniqueKey), + bundle.RelationKeyType.String(): pbtypes.String(mockedTypeId), bundle.RelationKeyLayout.String(): pbtypes.Float64(float64(model.ObjectType_basic)), }, }, @@ -116,45 +151,71 @@ func TestObjectService_ListObjects(t *testing.T) { RootId: mockedObjectId, Details: []*model.ObjectViewDetailsSet{ { + Id: mockedObjectId, Details: &types.Struct{ Fields: map[string]*types.Value{ bundle.RelationKeyId.String(): pbtypes.String(mockedObjectId), bundle.RelationKeyName.String(): pbtypes.String(mockedObjectName), bundle.RelationKeySnippet.String(): pbtypes.String(mockedObjectSnippet), bundle.RelationKeyIconEmoji.String(): pbtypes.String(mockedObjectIcon), - bundle.RelationKeyType.String(): pbtypes.String(mockedObjectTypeUniqueKey), bundle.RelationKeyCreatedDate.String(): pbtypes.Float64(888888), + bundle.RelationKeyLastModifiedBy.String(): pbtypes.String(mockedParticipantId), bundle.RelationKeyLastModifiedDate.String(): pbtypes.Float64(999999), + bundle.RelationKeyCreator.String(): pbtypes.String(mockedParticipantId), bundle.RelationKeyLastOpenedDate.String(): pbtypes.Float64(0), bundle.RelationKeySpaceId.String(): pbtypes.String(mockedSpaceId), + bundle.RelationKeyType.String(): pbtypes.String(mockedTypeId), + }, + }, + }, + { + Id: mockedParticipantId, + Details: &types.Struct{ + Fields: map[string]*types.Value{ + bundle.RelationKeyId.String(): pbtypes.String(mockedParticipantId), + }, + }, + }, + { + Id: mockedTypeId, + Details: &types.Struct{ + Fields: map[string]*types.Value{ + bundle.RelationKeyId.String(): pbtypes.String(mockedTypeId), + bundle.RelationKeyName.String(): pbtypes.String(mockedTypeName), + bundle.RelationKeyUniqueKey.String(): pbtypes.String(mockedTypeUniqueKey), + bundle.RelationKeyIconEmoji.String(): pbtypes.String(mockedTypeIcon), }, }, }, }, - }, - Error: &pb.RpcObjectShowResponseError{Code: pb.RpcObjectShowResponseError_NULL}, - }).Once() - - // Mock type resolution - fx.mwMock.On("ObjectSearch", mock.Anything, &pb.RpcObjectSearchRequest{ - SpaceId: mockedSpaceId, - Filters: []*model.BlockContentDataviewFilter{ - { - RelationKey: bundle.RelationKeyUniqueKey.String(), - Condition: model.BlockContentDataviewFilter_Equal, - Value: pbtypes.String(mockedObjectTypeUniqueKey), - }, - }, - Keys: []string{bundle.RelationKeyName.String()}, - }).Return(&pb.RpcObjectSearchResponse{ - Records: []*types.Struct{ - { - Fields: map[string]*types.Value{ - bundle.RelationKeyName.String(): pbtypes.String(mockedObjectType), + RelationLinks: []*model.RelationLink{ + { + Key: bundle.RelationKeyLastModifiedDate.String(), + Format: model.RelationFormat_date, + }, + { + Key: bundle.RelationKeyLastModifiedBy.String(), + Format: model.RelationFormat_object, + }, + { + Key: bundle.RelationKeyCreatedDate.String(), + Format: model.RelationFormat_date, + }, + { + Key: bundle.RelationKeyCreator.String(), + Format: model.RelationFormat_object, + }, + { + Key: bundle.RelationKeyLastOpenedDate.String(), + Format: model.RelationFormat_date, + }, + { + Key: bundle.RelationKeyTag.String(), + Format: model.RelationFormat_tag, }, }, }, - Error: &pb.RpcObjectSearchResponseError{Code: pb.RpcObjectSearchResponseError_NULL}, + Error: &pb.RpcObjectShowResponseError{Code: pb.RpcObjectShowResponseError_NULL}, }).Once() // Mock participant details @@ -165,7 +226,7 @@ func TestObjectService_ListObjects(t *testing.T) { Operator: model.BlockContentDataviewFilter_No, RelationKey: bundle.RelationKeyId.String(), Condition: model.BlockContentDataviewFilter_Equal, - Value: pbtypes.String(""), + Value: pbtypes.String(mockedParticipantId), }, }, Keys: []string{ @@ -180,7 +241,11 @@ func TestObjectService_ListObjects(t *testing.T) { }).Return(&pb.RpcObjectSearchResponse{ Error: &pb.RpcObjectSearchResponseError{Code: pb.RpcObjectSearchResponseError_NULL}, Records: []*types.Struct{ - {}, + { + Fields: map[string]*types.Value{ + bundle.RelationKeyId.String(): pbtypes.String(mockedParticipantId), + }, + }, }, }).Twice() @@ -190,24 +255,27 @@ func TestObjectService_ListObjects(t *testing.T) { // then require.NoError(t, err) require.Len(t, objects, 1) - require.Equal(t, mockedObjectType, objects[0].Type) + require.Equal(t, mockedTypeId, objects[0].Type.Id) + require.Equal(t, mockedTypeName, objects[0].Type.Name) + require.Equal(t, mockedTypeUniqueKey, objects[0].Type.UniqueKey) + require.Equal(t, mockedTypeIcon, objects[0].Type.Icon) require.Equal(t, mockedObjectId, objects[0].Id) require.Equal(t, mockedObjectName, objects[0].Name) require.Equal(t, mockedObjectSnippet, objects[0].Snippet) require.Equal(t, mockedObjectIcon, objects[0].Icon) - require.Equal(t, 6, len(objects[0].Details)) + require.Equal(t, 5, len(objects[0].Details)) for _, detail := range objects[0].Details { if detail.Id == "created_date" { - require.Equal(t, "1970-01-11T06:54:48Z", detail.Details["created_date"]) + require.Equal(t, "1970-01-11T06:54:48Z", detail.Details["date"]) } else if detail.Id == "created_by" { - require.Empty(t, detail.Details["created_by"]) + require.Equal(t, mockedParticipantId, detail.Details["object"].(space.Member).Id) } else if detail.Id == "last_modified_date" { - require.Equal(t, "1970-01-12T13:46:39Z", detail.Details["last_modified_date"]) + require.Equal(t, "1970-01-12T13:46:39Z", detail.Details["date"]) } else if detail.Id == "last_modified_by" { - require.Empty(t, detail.Details["last_modified_by"]) + require.Equal(t, mockedParticipantId, detail.Details["object"].(space.Member).Id) } else if detail.Id == "last_opened_date" { - require.Equal(t, "1970-01-01T00:00:00Z", detail.Details["last_opened_date"]) + require.Equal(t, "1970-01-01T00:00:00Z", detail.Details["date"]) } else if detail.Id == "tags" { require.Empty(t, detail.Details["tags"]) } else { @@ -224,14 +292,74 @@ func TestObjectService_ListObjects(t *testing.T) { ctx := context.Background() fx := newFixture(t) - fx.mwMock.On("ObjectSearch", mock.Anything, mock.Anything). - Return(&pb.RpcObjectSearchResponse{ - Records: []*types.Struct{}, - Error: &pb.RpcObjectSearchResponseError{Code: pb.RpcObjectSearchResponseError_NULL}, - }).Once() + // Mock template type resolution + fx.mwMock.On("ObjectSearch", mock.Anything, &pb.RpcObjectSearchRequest{ + SpaceId: mockedSpaceId, + Filters: []*model.BlockContentDataviewFilter{ + { + RelationKey: bundle.RelationKeyUniqueKey.String(), + Condition: model.BlockContentDataviewFilter_Equal, + Value: pbtypes.String("ot-template"), + }, + }, + Keys: []string{bundle.RelationKeyId.String()}, + }).Return(&pb.RpcObjectSearchResponse{ + Error: &pb.RpcObjectSearchResponseError{Code: pb.RpcObjectSearchResponseError_NULL}, + Records: []*types.Struct{ + { + Fields: map[string]*types.Value{ + bundle.RelationKeyId.String(): pbtypes.String(mockedTypeId), + }, + }, + }, + }).Once() + + // Mock object search + fx.mwMock.On("ObjectSearch", mock.Anything, &pb.RpcObjectSearchRequest{ + SpaceId: mockedSpaceId, + Filters: []*model.BlockContentDataviewFilter{ + { + RelationKey: bundle.RelationKeyLayout.String(), + Condition: model.BlockContentDataviewFilter_In, + Value: pbtypes.IntList([]int{ + int(model.ObjectType_basic), + int(model.ObjectType_profile), + int(model.ObjectType_todo), + int(model.ObjectType_note), + int(model.ObjectType_bookmark), + int(model.ObjectType_set), + int(model.ObjectType_collection), + int(model.ObjectType_participant), + }...), + }, + { + Operator: model.BlockContentDataviewFilter_No, + RelationKey: bundle.RelationKeyType.String(), + Condition: model.BlockContentDataviewFilter_NotEqual, + Value: pbtypes.String(mockedTypeId), + }, + { + Operator: model.BlockContentDataviewFilter_No, + RelationKey: bundle.RelationKeyIsHidden.String(), + Condition: model.BlockContentDataviewFilter_NotEqual, + Value: pbtypes.Bool(true), + }, + }, + Sorts: []*model.BlockContentDataviewSort{{ + RelationKey: bundle.RelationKeyLastModifiedDate.String(), + Type: model.BlockContentDataviewSort_Desc, + Format: model.RelationFormat_longtext, + IncludeTime: true, + EmptyPlacement: model.BlockContentDataviewSort_NotSpecified, + }}, + Keys: []string{bundle.RelationKeyId.String(), bundle.RelationKeyName.String()}, + }).Return(&pb.RpcObjectSearchResponse{ + Records: []*types.Struct{}, + Error: &pb.RpcObjectSearchResponseError{Code: pb.RpcObjectSearchResponseError_NULL}, + }).Once() // when - objects, total, hasMore, err := fx.ListObjects(ctx, "empty-space", offset, limit) + objects, total, hasMore, err := fx.ListObjects(ctx, mockedSpaceId, offset, limit) // then require.NoError(t, err) @@ -257,13 +385,14 @@ func TestObjectService_GetObject(t *testing.T) { RootId: mockedObjectId, Details: []*model.ObjectViewDetailsSet{ { + Id: mockedObjectId, Details: &types.Struct{ Fields: map[string]*types.Value{ bundle.RelationKeyId.String(): pbtypes.String(mockedObjectId), bundle.RelationKeyName.String(): pbtypes.String(mockedObjectName), bundle.RelationKeySnippet.String(): pbtypes.String(mockedObjectSnippet), bundle.RelationKeyIconEmoji.String(): pbtypes.String(mockedObjectName), - bundle.RelationKeyType.String(): pbtypes.String(mockedObjectTypeUniqueKey), + bundle.RelationKeyType.String(): pbtypes.String(mockedTypeId), bundle.RelationKeyLastModifiedDate.String(): pbtypes.Float64(999999), bundle.RelationKeyCreatedDate.String(): pbtypes.Float64(888888), bundle.RelationKeyLastOpenedDate.String(): pbtypes.Float64(0), @@ -271,82 +400,74 @@ func TestObjectService_GetObject(t *testing.T) { }, }, }, + { + Id: mockedTypeId, + Details: &types.Struct{ + Fields: map[string]*types.Value{ + bundle.RelationKeyId.String(): pbtypes.String(mockedTypeId), + bundle.RelationKeyName.String(): pbtypes.String(mockedTypeName), + bundle.RelationKeyUniqueKey.String(): pbtypes.String(mockedTypeUniqueKey), + bundle.RelationKeyIconEmoji.String(): pbtypes.String(mockedTypeIcon), + }, + }, + }, }, - }, - }, nil).Once() - - // Mock type resolution - fx.mwMock.On("ObjectSearch", mock.Anything, &pb.RpcObjectSearchRequest{ - SpaceId: mockedSpaceId, - Filters: []*model.BlockContentDataviewFilter{ - { - RelationKey: bundle.RelationKeyUniqueKey.String(), - Condition: model.BlockContentDataviewFilter_Equal, - Value: pbtypes.String(mockedObjectTypeUniqueKey), - }, - }, - Keys: []string{bundle.RelationKeyName.String()}, - }).Return(&pb.RpcObjectSearchResponse{ - Error: &pb.RpcObjectSearchResponseError{Code: pb.RpcObjectSearchResponseError_NULL}, - Records: []*types.Struct{ - { - Fields: map[string]*types.Value{ - bundle.RelationKeyName.String(): pbtypes.String(mockedObjectType), + RelationLinks: []*model.RelationLink{ + { + Key: bundle.RelationKeyLastModifiedDate.String(), + Format: model.RelationFormat_date, + }, + { + Key: bundle.RelationKeyLastModifiedBy.String(), + Format: model.RelationFormat_object, + }, + { + Key: bundle.RelationKeyCreatedDate.String(), + Format: model.RelationFormat_date, + }, + { + Key: bundle.RelationKeyCreator.String(), + Format: model.RelationFormat_object, + }, + { + Key: bundle.RelationKeyLastOpenedDate.String(), + Format: model.RelationFormat_date, + }, + { + Key: bundle.RelationKeyTag.String(), + Format: model.RelationFormat_tag, + }, }, }, - }, - }, nil).Once() - - // Mock participant details - fx.mwMock.On("ObjectSearch", mock.Anything, &pb.RpcObjectSearchRequest{ - SpaceId: mockedSpaceId, - Filters: []*model.BlockContentDataviewFilter{ - { - Operator: model.BlockContentDataviewFilter_No, - RelationKey: bundle.RelationKeyId.String(), - Condition: model.BlockContentDataviewFilter_Equal, - Value: pbtypes.String(""), - }, - }, - Keys: []string{ - bundle.RelationKeyId.String(), - bundle.RelationKeyName.String(), - bundle.RelationKeyIconEmoji.String(), - bundle.RelationKeyIconImage.String(), - bundle.RelationKeyIdentity.String(), - bundle.RelationKeyGlobalName.String(), - bundle.RelationKeyParticipantPermissions.String(), - }, - }).Return(&pb.RpcObjectSearchResponse{ - Error: &pb.RpcObjectSearchResponseError{Code: pb.RpcObjectSearchResponseError_NULL}, - Records: []*types.Struct{ - {}, - }, - }).Twice() + }, nil).Once() // when object, err := fx.GetObject(ctx, mockedSpaceId, mockedObjectId) // then require.NoError(t, err) - require.Equal(t, mockedObjectType, object.Type) + require.Equal(t, "object", object.Object) + require.Equal(t, mockedTypeId, object.Type.Id) + require.Equal(t, mockedTypeName, object.Type.Name) + require.Equal(t, mockedTypeUniqueKey, object.Type.UniqueKey) + require.Equal(t, mockedTypeIcon, object.Type.Icon) require.Equal(t, mockedObjectId, object.Id) require.Equal(t, mockedObjectName, object.Name) require.Equal(t, mockedObjectSnippet, object.Snippet) require.Equal(t, mockedObjectName, object.Icon) - require.Equal(t, 6, len(object.Details)) + require.Equal(t, 3, len(object.Details)) for _, detail := range object.Details { if detail.Id == "created_date" { - require.Equal(t, "1970-01-11T06:54:48Z", detail.Details["created_date"]) + require.Equal(t, "1970-01-11T06:54:48Z", detail.Details["date"]) } else if detail.Id == "created_by" { require.Empty(t, detail.Details["created_by"]) } else if detail.Id == "last_modified_date" { - require.Equal(t, "1970-01-12T13:46:39Z", detail.Details["last_modified_date"]) + require.Equal(t, "1970-01-12T13:46:39Z", detail.Details["date"]) } else if detail.Id == "last_modified_by" { require.Empty(t, detail.Details["last_modified_by"]) } else if detail.Id == "last_opened_date" { - require.Equal(t, "1970-01-01T00:00:00Z", detail.Details["last_opened_date"]) + require.Equal(t, "1970-01-01T00:00:00Z", detail.Details["date"]) } else if detail.Id == "tags" { require.Empty(t, detail.Details["tags"]) } else { @@ -390,7 +511,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, @@ -416,83 +537,49 @@ func TestObjectService_CreateObject(t *testing.T) { RootId: mockedNewObjectId, Details: []*model.ObjectViewDetailsSet{ { + Id: mockedNewObjectId, Details: &types.Struct{ Fields: map[string]*types.Value{ bundle.RelationKeyId.String(): pbtypes.String(mockedNewObjectId), bundle.RelationKeyName.String(): pbtypes.String(mockedObjectName), bundle.RelationKeyLayout.String(): pbtypes.Float64(float64(model.ObjectType_basic)), - bundle.RelationKeyType.String(): pbtypes.String(mockedObjectTypeUniqueKey), + bundle.RelationKeyType.String(): pbtypes.String(mockedTypeId), bundle.RelationKeyIconEmoji.String(): pbtypes.String(mockedObjectIcon), bundle.RelationKeySpaceId.String(): pbtypes.String(mockedSpaceId), }, }, }, - }, - }, - Error: &pb.RpcObjectShowResponseError{Code: pb.RpcObjectShowResponseError_NULL}, - }).Once() - - // Mock type resolution - fx.mwMock.On("ObjectSearch", mock.Anything, &pb.RpcObjectSearchRequest{ - SpaceId: mockedSpaceId, - Filters: []*model.BlockContentDataviewFilter{ - { - RelationKey: bundle.RelationKeyUniqueKey.String(), - Condition: model.BlockContentDataviewFilter_Equal, - Value: pbtypes.String(mockedObjectTypeUniqueKey), - }, - }, - Keys: []string{bundle.RelationKeyName.String()}, - }).Return(&pb.RpcObjectSearchResponse{ - Error: &pb.RpcObjectSearchResponseError{Code: pb.RpcObjectSearchResponseError_NULL}, - Records: []*types.Struct{ - { - Fields: map[string]*types.Value{ - bundle.RelationKeyName.String(): pbtypes.String(mockedObjectType), + { + Id: mockedTypeId, + Details: &types.Struct{ + Fields: map[string]*types.Value{ + bundle.RelationKeyId.String(): pbtypes.String(mockedTypeId), + bundle.RelationKeyName.String(): pbtypes.String(mockedTypeName), + bundle.RelationKeyUniqueKey.String(): pbtypes.String(mockedTypeUniqueKey), + bundle.RelationKeyIconEmoji.String(): pbtypes.String(mockedTypeIcon), + }, + }, }, }, }, + Error: &pb.RpcObjectShowResponseError{Code: pb.RpcObjectShowResponseError_NULL}, }).Once() - // Mock participant details - fx.mwMock.On("ObjectSearch", mock.Anything, &pb.RpcObjectSearchRequest{ - SpaceId: mockedSpaceId, - Filters: []*model.BlockContentDataviewFilter{ - { - Operator: model.BlockContentDataviewFilter_No, - RelationKey: bundle.RelationKeyId.String(), - Condition: model.BlockContentDataviewFilter_Equal, - Value: pbtypes.String(""), - }, - }, - Keys: []string{ - bundle.RelationKeyId.String(), - bundle.RelationKeyName.String(), - bundle.RelationKeyIconEmoji.String(), - bundle.RelationKeyIconImage.String(), - bundle.RelationKeyIdentity.String(), - bundle.RelationKeyGlobalName.String(), - bundle.RelationKeyParticipantPermissions.String(), - }, - }).Return(&pb.RpcObjectSearchResponse{ - Error: &pb.RpcObjectSearchResponseError{Code: pb.RpcObjectSearchResponseError_NULL}, - Records: []*types.Struct{ - {}, - }, - }).Twice() - // 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, }) // then require.NoError(t, err) - require.Equal(t, mockedObjectType, object.Type) + require.Equal(t, "object", object.Object) + require.Equal(t, mockedTypeId, object.Type.Id) + require.Equal(t, mockedTypeName, object.Type.Name) + require.Equal(t, mockedTypeUniqueKey, object.Type.UniqueKey) + require.Equal(t, mockedTypeIcon, object.Type.Icon) require.Equal(t, mockedNewObjectId, object.Id) require.Equal(t, mockedObjectName, object.Name) require.Equal(t, mockedObjectIcon, object.Icon) @@ -750,7 +837,7 @@ func TestObjectService_GetTemplate(t *testing.T) { }).Once() // when - template, err := fx.GetTemplate(ctx, mockedSpaceId, mockedObjectType, mockedTemplateId) + template, err := fx.GetTemplate(ctx, mockedSpaceId, mockedTypeId, mockedTemplateId) // then require.NoError(t, err) diff --git a/core/api/internal/search/handler.go b/core/api/internal/search/handler.go index e3ffe62ed8..9113b30e2a 100644 --- a/core/api/internal/search/handler.go +++ b/core/api/internal/search/handler.go @@ -11,17 +11,19 @@ import ( // GlobalSearchHandler searches and retrieves objects across all spaces // -// @Summary Search objects across all spaces -// @Tags search -// @Accept json -// @Produce json -// @Param offset query int false "The number of items to skip before starting to collect the result set" default(0) -// @Param limit query int false "The number of items to return" default(100) maximum(1000) -// @Param request body SearchRequest true "Search parameters" -// @Success 200 {object} pagination.PaginatedResponse[object.Object] "List of objects" -// @Failure 401 {object} util.UnauthorizedError "Unauthorized" -// @Failure 500 {object} util.ServerError "Internal server error" -// @Router /search [post] +// @Summary Search objects across all spaces +// @Description This endpoint executes a global search over every space the user has access to. It accepts pagination parameters (offset and limit) and a JSON body containing search criteria. The criteria include a search query string, an optional list of object types, and sort options (e.g. ascending/descending by creation, modification, or last opened dates). Internally, the endpoint aggregates results from each space, merges and sorts them (after last modified date by default), and returns a unified, paginated list of objects that match the search parameters. +// @Tags search +// @Accept json +// @Produce json +// @Param offset query int false "The number of items to skip before starting to collect the result set" default(0) +// @Param limit query int false "The number of items to return" default(100) maximum(1000) +// @Param request body SearchRequest true "Search parameters" +// @Success 200 {object} pagination.PaginatedResponse[object.Object] "List of objects" +// @Failure 401 {object} util.UnauthorizedError "Unauthorized" +// @Failure 500 {object} util.ServerError "Internal server error" +// @Security bearerauth +// @Router /search [post] func GlobalSearchHandler(s *SearchService) gin.HandlerFunc { return func(c *gin.Context) { offset := c.GetInt("offset") @@ -51,18 +53,20 @@ func GlobalSearchHandler(s *SearchService) gin.HandlerFunc { // SearchHandler searches and retrieves objects within a space // -// @Summary Search objects within a space -// @Tags search -// @Accept json -// @Produce json -// @Param space_id path string true "Space ID" -// @Param offset query int false "The number of items to skip before starting to collect the result set" default(0) -// @Param limit query int false "The number of items to return" default(100) maximum(1000) -// @Param request body SearchRequest true "Search parameters" -// @Success 200 {object} pagination.PaginatedResponse[object.Object] "List of objects" -// @Failure 401 {object} util.UnauthorizedError "Unauthorized" -// @Failure 500 {object} util.ServerError "Internal server error" -// @Router /spaces/{space_id}/search [post] +// @Summary Search objects within a space +// @Description This endpoint performs a focused search within a single space (specified by the space_id path parameter). Like the global search, it accepts pagination parameters and a JSON payload containing the search query, object types, and sorting preferences. The search is limited to the provided space and returns a list of objects that match the query. This allows clients to implement spaceโ€‘specific filtering without having to process extraneous results. +// @Tags search +// @Accept json +// @Produce json +// @Param space_id path string true "Space ID" +// @Param offset query int false "The number of items to skip before starting to collect the result set" default(0) +// @Param limit query int false "The number of items to return" default(100) maximum(1000) +// @Param request body SearchRequest true "Search parameters" +// @Success 200 {object} pagination.PaginatedResponse[object.Object] "List of objects" +// @Failure 401 {object} util.UnauthorizedError "Unauthorized" +// @Failure 500 {object} util.ServerError "Internal server error" +// @Security bearerauth +// @Router /spaces/{space_id}/search [post] func SearchHandler(s *SearchService) gin.HandlerFunc { return func(c *gin.Context) { spaceID := c.Param("space_id") 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..c325560e3a 100644 --- a/core/api/internal/search/service.go +++ b/core/api/internal/search/service.go @@ -48,18 +48,19 @@ 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 { - // Resolve object type IDs per space, as they are unique per space + // Resolve template type and object type IDs per space, as they are unique per space + templateFilter := s.prepareTemplateFilter(space.Id) typeFilters := s.prepareObjectTypeFilters(space.Id, request.Types) - filters := s.combineFilters(model.BlockContentDataviewFilter_And, baseFilters, queryFilters, typeFilters) + filters := s.combineFilters(model.BlockContentDataviewFilter_And, baseFilters, templateFilter, queryFilters, typeFilters) 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 +92,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 }) @@ -113,17 +114,18 @@ func (s *SearchService) GlobalSearch(ctx context.Context, request SearchRequest, // Search retrieves a paginated list of objects from a specific space that match the search parameters. func (s *SearchService) Search(ctx context.Context, spaceId string, request SearchRequest, offset int, limit int) (objects []object.Object, total int, hasMore bool, err error) { baseFilters := s.prepareBaseFilters() + templateFilter := s.prepareTemplateFilter(spaceId) queryFilters := s.prepareQueryFilter(request.Query) typeFilters := s.prepareObjectTypeFilters(spaceId, request.Types) - filters := s.combineFilters(model.BlockContentDataviewFilter_And, baseFilters, queryFilters, typeFilters) + filters := s.combineFilters(model.BlockContentDataviewFilter_And, baseFilters, templateFilter, 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}, }) @@ -194,6 +196,23 @@ func (s *SearchService) prepareBaseFilters() []*model.BlockContentDataviewFilter } } +// prepareTemplateFilter returns a filter that excludes templates from the search results. +func (s *SearchService) prepareTemplateFilter(spaceId string) []*model.BlockContentDataviewFilter { + typeId, err := util.ResolveUniqueKeyToTypeId(s.mw, spaceId, "ot-template") + if err != nil { + return nil + } + + return []*model.BlockContentDataviewFilter{ + { + Operator: model.BlockContentDataviewFilter_No, + RelationKey: bundle.RelationKeyType.String(), + Condition: model.BlockContentDataviewFilter_NotEqual, + Value: pbtypes.String(typeId), + }, + } +} + // prepareQueryFilter combines object name and snippet filters with an OR condition. func (s *SearchService) prepareQueryFilter(searchQuery string) []*model.BlockContentDataviewFilter { if searchQuery == "" { @@ -243,7 +262,7 @@ func (s *SearchService) prepareObjectTypeFilters(spaceId string, objectTypes []s nestedFilters = append(nestedFilters, &model.BlockContentDataviewFilter{ Operator: model.BlockContentDataviewFilter_No, RelationKey: bundle.RelationKeyType.String(), - Condition: model.BlockContentDataviewFilter_Equal, + Condition: model.BlockContentDataviewFilter_NotEqual, Value: pbtypes.String(typeId), }) } @@ -262,14 +281,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/search/service_test.go b/core/api/internal/search/service_test.go index 1f248690f3..2e0eac3ee8 100644 --- a/core/api/internal/search/service_test.go +++ b/core/api/internal/search/service_test.go @@ -35,7 +35,6 @@ const ( mockedTagId2 = "mocked-tag-id-2" mockedTagValue2 = "mocked-tag-value-2" mockedTagColor2 = "mocked-tag-color-2" - mockedObjectTypeName = "mocked-object-type-name" mockedParticipantName = "mocked-participant-name" mockedParticipantIcon = "mocked-participant-icon" mockedParticipantImage = "mocked-participant-image" @@ -121,6 +120,28 @@ func TestSearchService_GlobalSearch(t *testing.T) { Error: &pb.RpcWorkspaceOpenResponseError{Code: pb.RpcWorkspaceOpenResponseError_NULL}, }).Once() + // Mock template type resolution + fx.mwMock.On("ObjectSearch", mock.Anything, &pb.RpcObjectSearchRequest{ + SpaceId: mockedSpaceId, + Filters: []*model.BlockContentDataviewFilter{ + { + RelationKey: bundle.RelationKeyUniqueKey.String(), + Condition: model.BlockContentDataviewFilter_Equal, + Value: pbtypes.String("ot-template"), + }, + }, + Keys: []string{bundle.RelationKeyId.String()}, + }).Return(&pb.RpcObjectSearchResponse{ + Error: &pb.RpcObjectSearchResponseError{Code: pb.RpcObjectSearchResponseError_NULL}, + Records: []*types.Struct{ + { + Fields: map[string]*types.Value{ + bundle.RelationKeyId.String(): pbtypes.String(mockedType), + }, + }, + }, + }).Once() + // Mock objects in space fx.mwMock.On("ObjectSearch", mock.Anything, &pb.RpcObjectSearchRequest{ SpaceId: mockedSpaceId, @@ -149,6 +170,12 @@ func TestSearchService_GlobalSearch(t *testing.T) { Condition: model.BlockContentDataviewFilter_NotEqual, Value: pbtypes.Bool(true), }, + { + Operator: model.BlockContentDataviewFilter_No, + RelationKey: bundle.RelationKeyType.String(), + Condition: model.BlockContentDataviewFilter_NotEqual, + Value: pbtypes.String(mockedType), + }, { Operator: model.BlockContentDataviewFilter_Or, NestedFilters: []*model.BlockContentDataviewFilter{ @@ -276,33 +303,41 @@ func TestSearchService_GlobalSearch(t *testing.T) { }, }, }, + { + Id: mockedType, + Details: &types.Struct{ + Fields: map[string]*types.Value{ + bundle.RelationKeyId.String(): pbtypes.String(mockedType), + }, + }, + }, }, - }, - Error: &pb.RpcObjectShowResponseError{Code: pb.RpcObjectShowResponseError_NULL}, - }, nil).Once() - - // Mock type resolution - fx.mwMock.On("ObjectSearch", mock.Anything, &pb.RpcObjectSearchRequest{ - SpaceId: mockedSpaceId, - Filters: []*model.BlockContentDataviewFilter{ - { - Operator: model.BlockContentDataviewFilter_No, - RelationKey: bundle.RelationKeyId.String(), - Condition: model.BlockContentDataviewFilter_Equal, - Value: pbtypes.String(mockedType), - }, - }, - Keys: []string{bundle.RelationKeyName.String()}, - }).Return(&pb.RpcObjectSearchResponse{ - Records: []*types.Struct{ - { - Fields: map[string]*types.Value{ - bundle.RelationKeyName.String(): pbtypes.String(mockedObjectTypeName), + RelationLinks: []*model.RelationLink{ + { + Key: bundle.RelationKeyLastModifiedDate.String(), + Format: model.RelationFormat_date, + }, + { + Key: bundle.RelationKeyLastModifiedBy.String(), + Format: model.RelationFormat_object, + }, + { + Key: bundle.RelationKeyCreatedDate.String(), + Format: model.RelationFormat_date, + }, + { + Key: bundle.RelationKeyCreator.String(), + Format: model.RelationFormat_object, + }, + { + Key: bundle.RelationKeyTag.String(), + Format: model.RelationFormat_tag, }, }, }, - Error: &pb.RpcObjectSearchResponseError{Code: pb.RpcObjectSearchResponseError_NULL}, - }).Once() + + Error: &pb.RpcObjectShowResponseError{Code: pb.RpcObjectShowResponseError_NULL}, + }, nil).Once() // Mock participant details fx.mwMock.On("ObjectSearch", mock.Anything, &pb.RpcObjectSearchRequest{ @@ -340,16 +375,56 @@ func TestSearchService_GlobalSearch(t *testing.T) { Error: &pb.RpcObjectSearchResponseError{Code: pb.RpcObjectSearchResponseError_NULL}, }).Twice() + // Mock tag-1 open + fx.mwMock.On("ObjectShow", mock.Anything, &pb.RpcObjectShowRequest{ + SpaceId: mockedSpaceId, + ObjectId: mockedTagId1, + }).Return(&pb.RpcObjectShowResponse{ + Error: &pb.RpcObjectShowResponseError{Code: pb.RpcObjectShowResponseError_NULL}, + ObjectView: &model.ObjectView{ + Details: []*model.ObjectViewDetailsSet{ + { + Details: &types.Struct{ + Fields: map[string]*types.Value{ + bundle.RelationKeyName.String(): pbtypes.String(mockedTagValue1), + bundle.RelationKeyRelationOptionColor.String(): pbtypes.String(mockedTagColor1), + }, + }, + }, + }, + }, + }, nil).Once() + + // Mock tag-2 open + fx.mwMock.On("ObjectShow", mock.Anything, &pb.RpcObjectShowRequest{ + SpaceId: mockedSpaceId, + ObjectId: mockedTagId2, + }).Return(&pb.RpcObjectShowResponse{ + Error: &pb.RpcObjectShowResponseError{Code: pb.RpcObjectShowResponseError_NULL}, + ObjectView: &model.ObjectView{ + Details: []*model.ObjectViewDetailsSet{ + { + Details: &types.Struct{ + Fields: map[string]*types.Value{ + bundle.RelationKeyName.String(): pbtypes.String(mockedTagValue2), + bundle.RelationKeyRelationOptionColor.String(): pbtypes.String(mockedTagColor2), + }, + }, + }, + }, + }, + }, nil).Once() + // when objects, total, hasMore, err := fx.GlobalSearch(ctx, SearchRequest{Query: mockedSearchTerm, Types: []string{}, Sort: SortOptions{Direction: "desc", Timestamp: "last_modified_date"}}, offset, limit) // then require.NoError(t, err) require.Len(t, objects, 1) - require.Equal(t, mockedObjectTypeName, objects[0].Type) - require.Equal(t, mockedSpaceId, objects[0].SpaceId) - require.Equal(t, mockedObjectName, objects[0].Name) require.Equal(t, mockedObjectId, objects[0].Id) + require.Equal(t, mockedObjectName, objects[0].Name) + require.Equal(t, mockedType, objects[0].Type.Id) + require.Equal(t, mockedSpaceId, objects[0].SpaceId) require.Equal(t, model.ObjectTypeLayout_name[int32(model.ObjectType_basic)], objects[0].Layout) require.Equal(t, "๐ŸŒ", objects[0].Icon) require.Equal(t, "This is a sample text block", objects[0].Blocks[2].Text.Text) @@ -357,24 +432,24 @@ func TestSearchService_GlobalSearch(t *testing.T) { // check details for _, detail := range objects[0].Details { if detail.Id == "created_date" { - require.Equal(t, "1970-01-11T06:54:48Z", detail.Details["created_date"]) + require.Equal(t, "1970-01-11T06:54:48Z", detail.Details["date"]) } else if detail.Id == "last_modified_date" { - require.Equal(t, "1970-01-12T13:46:39Z", detail.Details["last_modified_date"]) + require.Equal(t, "1970-01-12T13:46:39Z", detail.Details["date"]) } else if detail.Id == "created_by" { - require.Equal(t, mockedParticipantId, detail.Details["details"].(space.Member).Id) - require.Equal(t, mockedParticipantName, detail.Details["details"].(space.Member).Name) - require.Equal(t, gatewayUrl+"/image/"+mockedParticipantImage, detail.Details["details"].(space.Member).Icon) - require.Equal(t, mockedParticipantIdentity, detail.Details["details"].(space.Member).Identity) - require.Equal(t, mockedParticipantGlobalName, detail.Details["details"].(space.Member).GlobalName) + require.Equal(t, mockedParticipantId, detail.Details["object"].(space.Member).Id) + require.Equal(t, mockedParticipantName, detail.Details["object"].(space.Member).Name) + require.Equal(t, gatewayUrl+"/image/"+mockedParticipantImage, detail.Details["object"].(space.Member).Icon) + require.Equal(t, mockedParticipantIdentity, detail.Details["object"].(space.Member).Identity) + require.Equal(t, mockedParticipantGlobalName, detail.Details["object"].(space.Member).GlobalName) } else if detail.Id == "last_modified_by" { - require.Equal(t, mockedParticipantId, detail.Details["details"].(space.Member).Id) + require.Equal(t, mockedParticipantId, detail.Details["object"].(space.Member).Id) } } // check tags tags := []object.Tag{} for _, detail := range objects[0].Details { - if tagList, ok := detail.Details["tags"].([]object.Tag); ok { + if tagList, ok := detail.Details["multi_select"].([]object.Tag); ok { for _, tag := range tagList { tags = append(tags, tag) } @@ -399,6 +474,28 @@ func TestSearchService_Search(t *testing.T) { ctx := context.Background() fx := newFixture(t) + // Mock template type resolution + fx.mwMock.On("ObjectSearch", mock.Anything, &pb.RpcObjectSearchRequest{ + SpaceId: mockedSpaceId, + Filters: []*model.BlockContentDataviewFilter{ + { + RelationKey: bundle.RelationKeyUniqueKey.String(), + Condition: model.BlockContentDataviewFilter_Equal, + Value: pbtypes.String("ot-template"), + }, + }, + Keys: []string{bundle.RelationKeyId.String()}, + }).Return(&pb.RpcObjectSearchResponse{ + Error: &pb.RpcObjectSearchResponseError{Code: pb.RpcObjectSearchResponseError_NULL}, + Records: []*types.Struct{ + { + Fields: map[string]*types.Value{ + bundle.RelationKeyId.String(): pbtypes.String(mockedType), + }, + }, + }, + }).Once() + // Mock objects in space fx.mwMock.On("ObjectSearch", mock.Anything, &pb.RpcObjectSearchRequest{ SpaceId: mockedSpaceId, @@ -427,6 +524,12 @@ func TestSearchService_Search(t *testing.T) { Condition: model.BlockContentDataviewFilter_NotEqual, Value: pbtypes.Bool(true), }, + { + Operator: model.BlockContentDataviewFilter_No, + RelationKey: bundle.RelationKeyType.String(), + Condition: model.BlockContentDataviewFilter_NotEqual, + Value: pbtypes.String(mockedType), + }, { Operator: model.BlockContentDataviewFilter_Or, NestedFilters: []*model.BlockContentDataviewFilter{ @@ -477,6 +580,7 @@ func TestSearchService_Search(t *testing.T) { RootId: mockedRootId, Details: []*model.ObjectViewDetailsSet{ { + Id: mockedRootId, Details: &types.Struct{ Fields: map[string]*types.Value{ bundle.RelationKeyId.String(): pbtypes.String(mockedObjectId), @@ -488,66 +592,28 @@ func TestSearchService_Search(t *testing.T) { }, }, }, - }, - }, - Error: &pb.RpcObjectShowResponseError{Code: pb.RpcObjectShowResponseError_NULL}, - }).Once() - - // Mock type resolution - fx.mwMock.On("ObjectSearch", mock.Anything, &pb.RpcObjectSearchRequest{ - SpaceId: mockedSpaceId, - Filters: []*model.BlockContentDataviewFilter{ - { - Operator: model.BlockContentDataviewFilter_No, - RelationKey: bundle.RelationKeyId.String(), - Condition: model.BlockContentDataviewFilter_Equal, - Value: pbtypes.String(mockedType), - }, - }, - Keys: []string{bundle.RelationKeyName.String()}, - }).Return(&pb.RpcObjectSearchResponse{ - Records: []*types.Struct{ - { - Fields: map[string]*types.Value{ - bundle.RelationKeyName.String(): pbtypes.String(mockedObjectTypeName), + { + Id: mockedType, + Details: &types.Struct{ + Fields: map[string]*types.Value{ + bundle.RelationKeyId.String(): pbtypes.String(mockedType), + }, + }, }, }, }, - Error: &pb.RpcObjectSearchResponseError{Code: pb.RpcObjectSearchResponseError_NULL}, + Error: &pb.RpcObjectShowResponseError{Code: pb.RpcObjectShowResponseError_NULL}, }).Once() - // Mock participant details - fx.mwMock.On("ObjectSearch", mock.Anything, &pb.RpcObjectSearchRequest{ - SpaceId: mockedSpaceId, - Filters: []*model.BlockContentDataviewFilter{ - { - Operator: model.BlockContentDataviewFilter_No, - RelationKey: bundle.RelationKeyId.String(), - Condition: model.BlockContentDataviewFilter_Equal, - Value: pbtypes.String(""), - }, - }, - Keys: []string{bundle.RelationKeyId.String(), - bundle.RelationKeyName.String(), - bundle.RelationKeyIconEmoji.String(), - bundle.RelationKeyIconImage.String(), - bundle.RelationKeyIdentity.String(), - bundle.RelationKeyGlobalName.String(), - bundle.RelationKeyParticipantPermissions.String(), - }, - }).Return(&pb.RpcObjectSearchResponse{ - Records: []*types.Struct{}, - Error: &pb.RpcObjectSearchResponseError{Code: pb.RpcObjectSearchResponseError_NULL}, - }).Twice() - // when objects, total, hasMore, err := fx.Search(ctx, mockedSpaceId, SearchRequest{Query: mockedSearchTerm, Types: []string{}, Sort: SortOptions{Direction: "desc", Timestamp: "last_modified_date"}}, offset, limit) // then require.NoError(t, err) require.Len(t, objects, 1) - require.Equal(t, mockedObjectName, objects[0].Name) require.Equal(t, mockedObjectId, objects[0].Id) + require.Equal(t, mockedObjectName, objects[0].Name) + require.Equal(t, mockedType, objects[0].Type.Id) require.Equal(t, mockedSpaceId, objects[0].SpaceId) require.Equal(t, model.ObjectTypeLayout_name[int32(model.ObjectType_basic)], objects[0].Layout) @@ -560,6 +626,28 @@ func TestSearchService_Search(t *testing.T) { ctx := context.Background() fx := newFixture(t) + // Mock template type resolution + fx.mwMock.On("ObjectSearch", mock.Anything, &pb.RpcObjectSearchRequest{ + SpaceId: mockedSpaceId, + Filters: []*model.BlockContentDataviewFilter{ + { + RelationKey: bundle.RelationKeyUniqueKey.String(), + Condition: model.BlockContentDataviewFilter_Equal, + Value: pbtypes.String("ot-template"), + }, + }, + Keys: []string{bundle.RelationKeyId.String()}, + }).Return(&pb.RpcObjectSearchResponse{ + Error: &pb.RpcObjectSearchResponseError{Code: pb.RpcObjectSearchResponseError_NULL}, + Records: []*types.Struct{ + { + Fields: map[string]*types.Value{ + bundle.RelationKeyId.String(): pbtypes.String(mockedType), + }, + }, + }, + }).Once() + fx.mwMock.On("ObjectSearch", mock.Anything, mock.Anything).Return(&pb.RpcObjectSearchResponse{ Records: []*types.Struct{}, Error: &pb.RpcObjectSearchResponseError{Code: pb.RpcObjectSearchResponseError_NULL}, @@ -580,6 +668,28 @@ func TestSearchService_Search(t *testing.T) { ctx := context.Background() fx := newFixture(t) + // Mock template type resolution + fx.mwMock.On("ObjectSearch", mock.Anything, &pb.RpcObjectSearchRequest{ + SpaceId: mockedSpaceId, + Filters: []*model.BlockContentDataviewFilter{ + { + RelationKey: bundle.RelationKeyUniqueKey.String(), + Condition: model.BlockContentDataviewFilter_Equal, + Value: pbtypes.String("ot-template"), + }, + }, + Keys: []string{bundle.RelationKeyId.String()}, + }).Return(&pb.RpcObjectSearchResponse{ + Error: &pb.RpcObjectSearchResponseError{Code: pb.RpcObjectSearchResponseError_NULL}, + Records: []*types.Struct{ + { + Fields: map[string]*types.Value{ + bundle.RelationKeyId.String(): pbtypes.String(mockedType), + }, + }, + }, + }).Once() + fx.mwMock.On("ObjectSearch", mock.Anything, mock.Anything).Return(&pb.RpcObjectSearchResponse{ Error: &pb.RpcObjectSearchResponseError{Code: pb.RpcObjectSearchResponseError_UNKNOWN_ERROR}, }).Once() diff --git a/core/api/internal/space/handler.go b/core/api/internal/space/handler.go index 2a6ca043de..e52dcb2f62 100644 --- a/core/api/internal/space/handler.go +++ b/core/api/internal/space/handler.go @@ -11,16 +11,17 @@ import ( // GetSpacesHandler retrieves a list of spaces // -// @Summary List spaces -// @Tags spaces -// @Accept json -// @Produce json -// @Param offset query int false "The number of items to skip before starting to collect the result set" default(0) -// @Param limit query int false "The number of items to return" default(100) maximum(1000) -// @Success 200 {object} pagination.PaginatedResponse[Space] "List of spaces" -// @Failure 401 {object} util.UnauthorizedError "Unauthorized" -// @Failure 500 {object} util.ServerError "Internal server error" -// @Router /spaces [get] +// @Summary List spaces +// @Description Retrieves a paginated list of all spaces that are accessible by the authenticated user. Each space record contains detailed information such as the space ID, name, icon (derived either from an emoji or image URL), and additional metadata. This endpoint is key to displaying a userโ€™s workspaces. +// @Tags spaces +// @Produce json +// @Param offset query int false "The number of items to skip before starting to collect the result set" default(0) +// @Param limit query int false "The number of items to return" default(100) maximum(1000) +// @Success 200 {object} pagination.PaginatedResponse[Space] "List of spaces" +// @Failure 401 {object} util.UnauthorizedError "Unauthorized" +// @Failure 500 {object} util.ServerError "Internal server error" +// @Security bearerauth +// @Router /spaces [get] func GetSpacesHandler(s *SpaceService) gin.HandlerFunc { return func(c *gin.Context) { offset := c.GetInt("offset") @@ -42,18 +43,54 @@ func GetSpacesHandler(s *SpaceService) gin.HandlerFunc { } } +// GetSpaceHandler retrieves a space +// +// @Summary Get space +// @Description Fetches full details about a single space identified by its space ID. The response includes metadata such as the space name, icon, and various workspace IDs (home, archive, profile, etc.). This detailed view supports use cases such as displaying space-specific settings. +// @Tags spaces +// @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" +// @Security bearerauth +// @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 -// @Tags spaces -// @Accept json -// @Produce json -// @Param name body CreateSpaceRequest true "Space to create" -// @Success 200 {object} CreateSpaceResponse "Space created successfully" -// @Failure 400 {object} util.ValidationError "Bad request" -// @Failure 401 {object} util.UnauthorizedError "Unauthorized" -// @Failure 500 {object} util.ServerError "Internal server error" -// @Router /spaces [post] +// @Summary Create space +// @Description Creates a new workspace (or space) based on a supplied name in the JSON request body. The endpoint is subject to rate limiting and automatically applies default configurations such as generating a random icon and initializing the workspace with default settings (for example, a default dashboard or home page). On success, the new spaceโ€™s full metadata is returned, enabling the client to immediately switch context to the new space. +// @Tags spaces +// @Accept json +// @Produce json +// @Param name body CreateSpaceRequest true "Space to create" +// @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" +// @Security bearerauth +// @Router /spaces [post] func CreateSpaceHandler(s *SpaceService) gin.HandlerFunc { return func(c *gin.Context) { nameRequest := CreateSpaceRequest{} @@ -74,23 +111,24 @@ func CreateSpaceHandler(s *SpaceService) gin.HandlerFunc { return } - c.JSON(http.StatusOK, CreateSpaceResponse{Space: space}) + c.JSON(http.StatusOK, SpaceResponse{Space: space}) } } // GetMembersHandler retrieves a list of members in a space // -// @Summary List members -// @Tags spaces -// @Accept json -// @Produce json -// @Param space_id path string true "Space ID" -// @Param offset query int false "The number of items to skip before starting to collect the result set" default(0) -// @Param limit query int false "The number of items to return" default(100) maximum(1000) -// @Success 200 {object} pagination.PaginatedResponse[Member] "List of members" -// @Failure 401 {object} util.UnauthorizedError "Unauthorized" -// @Failure 500 {object} util.ServerError "Internal server error" -// @Router /spaces/{space_id}/members [get] +// @Summary List members +// @Description Returns a paginated list of members belonging to the specified space. Each member record includes the memberโ€™s profile ID, name, icon (which may be derived from an emoji or image), network identity, global name, and role (e.g. Reader, Writer, Owner). This endpoint supports collaborative features by allowing clients to show who is in a space and manage access rights. +// @Tags spaces +// @Produce json +// @Param space_id path string true "Space ID" +// @Param offset query int false "The number of items to skip before starting to collect the result set" default(0) +// @Param limit query int false "The number of items to return" default(100) maximum(1000) +// @Success 200 {object} pagination.PaginatedResponse[Member] "List of members" +// @Failure 401 {object} util.UnauthorizedError "Unauthorized" +// @Failure 500 {object} util.ServerError "Internal server error" +// @Security bearerauth +// @Router /spaces/{space_id}/members [get] func GetMembersHandler(s *SpaceService) gin.HandlerFunc { return func(c *gin.Context) { spaceId := c.Param("space_id") @@ -111,3 +149,38 @@ 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 +// @Description Fetches detailed information about a single member within a space. The endpoint returns the memberโ€™s identifier, name, icon, identity, global name, and role. This is useful for user profile pages, permission management, and displaying member-specific information in collaborative environments. +// @Tags spaces +// @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" +// @Security bearerauth +// @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..16ebe1fb5b 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"` + Object string `json:"object" example:"space"` // The data model 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"` + Object string `json:"object" example:"member"` // The data model 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..1b36244e0b 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. @@ -159,7 +198,7 @@ func (s *SpaceService) ListMembers(ctx context.Context, spaceId string, offset i icon := util.GetIconFromEmojiOrImage(s.AccountInfo, record.Fields[bundle.RelationKeyIconEmoji.String()].GetStringValue(), record.Fields[bundle.RelationKeyIconImage.String()].GetStringValue()) member := Member{ - Type: "member", + Object: "member", Id: record.Fields[bundle.RelationKeyId.String()].GetStringValue(), Name: record.Fields[bundle.RelationKeyName.String()].GetStringValue(), Icon: icon, @@ -174,43 +213,44 @@ 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()) return Member{ - Type: "member", + Object: "member", Id: resp.Records[0].Fields[bundle.RelationKeyId.String()].GetStringValue(), Name: resp.Records[0].Fields[bundle.RelationKeyName.String()].GetStringValue(), Icon: icon, 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, @@ -221,8 +261,10 @@ func (s *SpaceService) getWorkspaceInfo(spaceId string) (space Space, err error) } return Space{ - Type: "space", + Object: "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..2317c935f0 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,11 +83,11 @@ 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 } - s.exportService.AccountInfo = accInfo s.objectService.AccountInfo = accInfo s.spaceService.AccountInfo = accInfo s.searchService.AccountInfo = accInfo @@ -90,3 +95,11 @@ func (s *Server) ensureAccountInfo(accountService account.Service) gin.HandlerFu c.Next() } } + +// ensureMetadataHeader is a middleware that ensures the metadata header is set. +func (s *Server) ensureMetadataHeader() gin.HandlerFunc { + return func(c *gin.Context) { + c.Writer.Header().Set("X-API-Version", "0.0.2") + c.Next() + } +} diff --git a/core/api/server/router.go b/core/api/server/router.go index e33206a325..67237092c8 100644 --- a/core/api/server/router.go +++ b/core/api/server/router.go @@ -12,6 +12,7 @@ import ( "github.com/anyproto/anytype-heart/core/anytype/account" "github.com/anyproto/anytype-heart/core/api/internal/auth" "github.com/anyproto/anytype-heart/core/api/internal/export" + "github.com/anyproto/anytype-heart/core/api/internal/list" "github.com/anyproto/anytype-heart/core/api/internal/object" "github.com/anyproto/anytype-heart/core/api/internal/search" "github.com/anyproto/anytype-heart/core/api/internal/space" @@ -36,6 +37,7 @@ func (s *Server) NewRouter(accountService account.Service, mw service.ClientComm router := gin.New() router.Use(gin.Recovery()) + router.Use(s.ensureMetadataHeader()) if debug { router.Use(gin.Logger()) @@ -66,6 +68,11 @@ func (s *Server) NewRouter(accountService account.Service, mw service.ClientComm // Export v1.POST("/spaces/:space_id/objects/:object_id/export/:format", export.GetObjectExportHandler(s.exportService)) + // List + v1.GET("/spaces/:space_id/lists/:list_id/objects", list.GetObjectsInListHandler(s.listService)) + v1.POST("/spaces/:space_id/lists/:list_id/objects", list.AddObjectsToListHandler(s.listService)) + v1.DELETE("/spaces/:space_id/lists/:list_id/objects/:object_id", s.rateLimit(maxWriteRequestsPerSecond), list.RemoveObjectFromListHandler(s.listService)) + // Object v1.GET("/spaces/:space_id/objects", object.GetObjectsHandler(s.objectService)) v1.GET("/spaces/:space_id/objects/:object_id", object.GetObjectHandler(s.objectService)) @@ -78,7 +85,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/server/server.go b/core/api/server/server.go index 78fb467932..f38dc6c7e7 100644 --- a/core/api/server/server.go +++ b/core/api/server/server.go @@ -8,6 +8,7 @@ import ( "github.com/anyproto/anytype-heart/core/anytype/account" "github.com/anyproto/anytype-heart/core/api/internal/auth" "github.com/anyproto/anytype-heart/core/api/internal/export" + "github.com/anyproto/anytype-heart/core/api/internal/list" "github.com/anyproto/anytype-heart/core/api/internal/object" "github.com/anyproto/anytype-heart/core/api/internal/search" "github.com/anyproto/anytype-heart/core/api/internal/space" @@ -20,6 +21,7 @@ type Server struct { authService *auth.AuthService exportService *export.ExportService + listService *list.ListService objectService *object.ObjectService spaceService *space.SpaceService searchService *search.SearchService @@ -37,6 +39,7 @@ func NewServer(accountService account.Service, mw service.ClientCommandsServer) } s.objectService = object.NewService(mw, s.spaceService) + s.listService = list.NewService(mw, s.objectService) s.searchService = search.NewService(mw, s.spaceService, s.objectService) s.engine = s.NewRouter(accountService, mw) s.KeyToToken = make(map[string]string) diff --git a/core/api/service.go b/core/api/service.go index 6027337b64..4d98e334d7 100644 --- a/core/api/service.go +++ b/core/api/service.go @@ -49,20 +49,20 @@ func (s *apiService) Name() (name string) { // Init initializes the API service. // -// @title Anytype API -// @version 1.0 -// @description This API allows interaction with Anytype resources such as spaces, objects and types. -// @termsOfService https://anytype.io/terms_of_use -// @contact.name Anytype Support -// @contact.url https://anytype.io/contact -// @contact.email support@anytype.io -// @license.name Any Source Available License 1.0 -// @license.url https://github.com/anyproto/anytype-ts/blob/main/LICENSE.md -// @host localhost:31009 -// @BasePath /v1 -// @securityDefinitions.basic BasicAuth -// @externalDocs.description OpenAPI -// @externalDocs.url https://swagger.io/resources/open-api/ +// @title Anytype API +// @version 1.0 +// @description This API allows interaction with Anytype resources such as spaces, objects and types. +// @termsOfService https://anytype.io/terms_of_use +// @contact.name Anytype Support +// @contact.url https://anytype.io/contact +// @contact.email support@anytype.io +// @license.name Any Source Available License 1.0 +// @license.url https://github.com/anyproto/anytype-ts/blob/main/LICENSE.md +// @host http://localhost:31009 +// @BasePath /v1 +// @securitydefinitions.bearerauth BearerAuth +// @externalDocs.description OpenAPI +// @externalDocs.url https://swagger.io/resources/open-api/ func (s *apiService) Init(a *app.App) (err error) { s.listenAddr = a.MustComponent(config.CName).(*config.Config).JsonApiListenAddr s.accountService = a.MustComponent(account.CName).(account.Service) 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, }, diff --git a/core/api/util/util.go b/core/api/util/util.go index 6c5a74eb98..568a5c3bc5 100644 --- a/core/api/util/util.go +++ b/core/api/util/util.go @@ -4,7 +4,7 @@ import ( "context" "errors" "fmt" - "strings" + "time" "github.com/anyproto/anytype-heart/pb" "github.com/anyproto/anytype-heart/pb/service" @@ -18,6 +18,11 @@ var ( ErrorTypeNotFound = errors.New("type not found") ) +func PosixToISO8601(posix float64) string { + t := time.Unix(int64(posix), 0).UTC() + return t.Format(time.RFC3339) +} + // GetIconFromEmojiOrImage returns the icon to use for the object, which can be either an emoji or an image url func GetIconFromEmojiOrImage(accountInfo *model.AccountInfo, iconEmoji string, iconImage string) string { if iconEmoji != "" { @@ -31,26 +36,18 @@ func GetIconFromEmojiOrImage(accountInfo *model.AccountInfo, iconEmoji string, i return "" } -// ResolveTypeToName resolves the type ID to the name of the type, e.g. "ot-page" to "Page" or "bafyreigyb6l5szohs32ts26ku2j42yd65e6hqy2u3gtzgdwqv6hzftsetu" to "Custom Type" -func ResolveTypeToName(mw service.ClientCommandsServer, spaceId string, typeId string) (typeName string, err error) { - // Can't look up preinstalled types based on relation key, therefore need to use unique key - relKey := bundle.RelationKeyId.String() - if strings.HasPrefix(typeId, "ot-") { - relKey = bundle.RelationKeyUniqueKey.String() - } - - // Call ObjectSearch for object of specified type and return the name +func ResolveUniqueKeyToTypeId(mw service.ClientCommandsServer, spaceId string, uniqueKey string) (typeId string, err error) { + // Call ObjectSearch for type with unique key and return the type's ID resp := mw.ObjectSearch(context.Background(), &pb.RpcObjectSearchRequest{ SpaceId: spaceId, Filters: []*model.BlockContentDataviewFilter{ { - Operator: model.BlockContentDataviewFilter_No, - RelationKey: relKey, + RelationKey: bundle.RelationKeyUniqueKey.String(), Condition: model.BlockContentDataviewFilter_Equal, - Value: pbtypes.String(typeId), + Value: pbtypes.String(uniqueKey), }, }, - Keys: []string{bundle.RelationKeyName.String()}, + Keys: []string{bundle.RelationKeyId.String()}, }) if resp.Error.Code != pb.RpcObjectSearchResponseError_NULL { @@ -61,21 +58,25 @@ func ResolveTypeToName(mw service.ClientCommandsServer, spaceId string, typeId s return "", ErrorTypeNotFound } - return resp.Records[0].Fields[bundle.RelationKeyName.String()].GetStringValue(), nil + return resp.Records[0].Fields[bundle.RelationKeyId.String()].GetStringValue(), nil } -func ResolveUniqueKeyToTypeId(mw service.ClientCommandsServer, spaceId string, uniqueKey string) (typeId string, err error) { - // Call ObjectSearch for type with unique key and return the type's ID +func ResolveRelationKeyToRelationName(mw service.ClientCommandsServer, spaceId string, relationKey string) (relation string, err error) { resp := mw.ObjectSearch(context.Background(), &pb.RpcObjectSearchRequest{ SpaceId: spaceId, Filters: []*model.BlockContentDataviewFilter{ { - RelationKey: bundle.RelationKeyUniqueKey.String(), + RelationKey: bundle.RelationKeyRelationKey.String(), Condition: model.BlockContentDataviewFilter_Equal, - Value: pbtypes.String(uniqueKey), + Value: pbtypes.String(relationKey), + }, + { + RelationKey: bundle.RelationKeyLayout.String(), + Condition: model.BlockContentDataviewFilter_Equal, + Value: pbtypes.Int64(int64(model.ObjectType_relation)), }, }, - Keys: []string{bundle.RelationKeyId.String()}, + Keys: []string{bundle.RelationKeyId.String(), bundle.RelationKeyName.String(), bundle.RelationKeyLayout.String()}, }) if resp.Error.Code != pb.RpcObjectSearchResponseError_NULL { @@ -86,5 +87,5 @@ func ResolveUniqueKeyToTypeId(mw service.ClientCommandsServer, spaceId string, u return "", ErrorTypeNotFound } - return resp.Records[0].Fields[bundle.RelationKeyId.String()].GetStringValue(), nil + return resp.Records[0].Fields[bundle.RelationKeyName.String()].GetStringValue(), nil } diff --git a/go.mod b/go.mod index 96c28da040..02aa477cdb 100644 --- a/go.mod +++ b/go.mod @@ -168,10 +168,10 @@ require ( github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ole/go-ole v1.3.0 // indirect - github.com/go-openapi/jsonpointer v0.19.5 // indirect - github.com/go-openapi/jsonreference v0.19.6 // indirect - github.com/go-openapi/spec v0.20.4 // indirect - github.com/go-openapi/swag v0.19.15 // indirect + github.com/go-openapi/jsonpointer v0.19.6 // indirect + github.com/go-openapi/jsonreference v0.20.2 // indirect + github.com/go-openapi/spec v0.20.9 // indirect + github.com/go-openapi/swag v0.22.3 // indirect github.com/go-pkgz/expirable-cache/v3 v3.0.0 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect @@ -219,7 +219,7 @@ require ( github.com/libp2p/go-buffer-pool v0.1.0 // indirect github.com/libp2p/go-libp2p v0.38.2 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect - github.com/mailru/easyjson v0.7.6 // indirect + github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/miekg/dns v1.1.62 // indirect @@ -264,6 +264,8 @@ require ( github.com/spf13/viper v1.15.0 // indirect github.com/stretchr/objx v0.5.2 // indirect github.com/subosito/gotenv v1.4.2 // indirect + github.com/sv-tools/openapi v0.2.1 // indirect + github.com/swaggo/swag/v2 v2.0.0-rc4 // indirect github.com/tetratelabs/wazero v1.8.1 // indirect github.com/tklauser/go-sysconf v0.3.12 // indirect github.com/tklauser/numcpus v0.6.1 // indirect diff --git a/go.sum b/go.sum index 61f3c63844..2150e80380 100644 --- a/go.sum +++ b/go.sum @@ -351,13 +351,22 @@ github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= +github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= github.com/go-openapi/jsonreference v0.19.6 h1:UBIxjkht+AWIgYzCDSv2GN+E/togfwXUJFRTWhl2Jjs= github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns= +github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo= +github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= +github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= github.com/go-openapi/spec v0.20.4 h1:O8hJrt0UMnhHcluhIdUgCLRWyM2x7QkBXRvOs7m+O1M= github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I= +github.com/go-openapi/spec v0.20.9 h1:xnlYNQAwKd2VQRRfwTEI0DcK+2cbuvI/0c7jx3gA8/8= +github.com/go-openapi/spec v0.20.9/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM= github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= +github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= +github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= github.com/go-pkgz/expirable-cache/v3 v3.0.0 h1:u3/gcu3sabLYiTCevoRKv+WzjIn5oo7P8XtiXBeRDLw= github.com/go-pkgz/expirable-cache/v3 v3.0.0/go.mod h1:2OQiDyEGQalYecLWmXprm3maPXeVb5/6/X7yRPYTzec= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= @@ -682,6 +691,7 @@ github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= @@ -726,6 +736,8 @@ github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/matishsiao/goInfo v0.0.0-20240924010139-10388a85396f h1:XDrsC/9hdgiU9ecceSmYsS2E3fBtFiYc34dAMFgegnM= github.com/matishsiao/goInfo v0.0.0-20240924010139-10388a85396f/go.mod h1:aEt7p9Rvh67BYApmZwNDPpgircTO2kgdmDUoF/1QmwA= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= @@ -1054,12 +1066,16 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= +github.com/sv-tools/openapi v0.2.1 h1:ES1tMQMJFGibWndMagvdoo34T1Vllxr1Nlm5wz6b1aA= +github.com/sv-tools/openapi v0.2.1/go.mod h1:k5VuZamTw1HuiS9p2Wl5YIDWzYnHG6/FgPOSFXLAhGg= github.com/swaggo/files v1.0.1 h1:J1bVJ4XHZNq0I46UU90611i9/YzdrF7x92oX1ig5IdE= github.com/swaggo/files v1.0.1/go.mod h1:0qXmMNH6sXNf+73t65aKeB+ApmgxdnkQzVTAj2uaMUg= github.com/swaggo/gin-swagger v1.6.0 h1:y8sxvQ3E20/RCyrXeFfg60r6H0Z+SwpTjMYsMm+zy8M= github.com/swaggo/gin-swagger v1.6.0/go.mod h1:BG00cCEy294xtVpyIAHG6+e2Qzj/xKlRdOqDkvq0uzo= github.com/swaggo/swag v1.16.4 h1:clWJtd9LStiG3VeijiCfOVODP6VpHtKdQy9ELFG3s1A= github.com/swaggo/swag v1.16.4/go.mod h1:VBsHJRsDvfYvqoiMKnsdwhNV9LEMHgEDZcyVYX0sxPg= +github.com/swaggo/swag/v2 v2.0.0-rc4 h1:SZ8cK68gcV6cslwrJMIOqPkJELRwq4gmjvk77MrvHvY= +github.com/swaggo/swag/v2 v2.0.0-rc4/go.mod h1:Ow7Y8gF16BTCDn8YxZbyKn8FkMLRUHekv1kROJZpbvE= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= github.com/tetratelabs/wazero v1.8.1 h1:NrcgVbWfkWvVc4UtT4LRLDf91PsOzDzefMdwhLfA550= github.com/tetratelabs/wazero v1.8.1/go.mod h1:yAI0XTsMBhREkM/YDAK/zNou3GoiAce1P6+rp/wQhjs=