diff --git a/docs/api-reference/sourcebot-public.openapi.json b/docs/api-reference/sourcebot-public.openapi.json index 451781f16..b814c98a1 100644 --- a/docs/api-reference/sourcebot-public.openapi.json +++ b/docs/api-reference/sourcebot-public.openapi.json @@ -7,24 +7,28 @@ }, "tags": [ { - "name": "Search", - "description": "Code search endpoints." + "name": "Search & Navigation", + "description": "Code search and symbol navigation endpoints." }, { "name": "Repositories", "description": "Repository listing and metadata endpoints." }, { - "name": "Files", - "description": "File tree, file listing, and file content endpoints." + "name": "Git", + "description": "Git history, diff, and file content endpoints." }, { - "name": "Git", - "description": "Git history and diff endpoints." + "name": "System", + "description": "System health and version endpoints." + }, + { + "name": "User Management (EE)", + "description": "User management endpoints. Requires the `org-management` entitlement and OWNER role." }, { - "name": "Misc", - "description": "Miscellaneous public API endpoints." + "name": "Audit (EE)", + "description": "Audit log endpoints. Requires the `audit` entitlement and OWNER role." } ], "security": [ @@ -448,88 +452,93 @@ "matches" ] }, - "PublicStreamSearchSse": { - "type": "string", - "description": "Server-sent event stream. Each data frame contains one JSON object representing either a chunk update, a final summary, or an error.", - "example": "data: {\"type\":\"chunk\",\"stats\":{\"actualMatchCount\":1}}\n\n" + "PublicListReposResponse": { + "type": "array", + "items": { + "type": "object", + "properties": { + "codeHostType": { + "type": "string", + "enum": [ + "github", + "gitlab", + "gitea", + "gerrit", + "bitbucketServer", + "bitbucketCloud", + "genericGitHost", + "azuredevops" + ] + }, + "repoId": { + "type": "number" + }, + "repoName": { + "type": "string" + }, + "webUrl": { + "type": "string" + }, + "repoDisplayName": { + "type": "string" + }, + "externalWebUrl": { + "type": "string" + }, + "imageUrl": { + "type": "string" + }, + "indexedAt": { + "type": "string", + "nullable": true + }, + "pushedAt": { + "type": "string", + "nullable": true + }, + "defaultBranch": { + "type": "string" + }, + "isFork": { + "type": "boolean" + }, + "isArchived": { + "type": "boolean" + } + }, + "required": [ + "codeHostType", + "repoId", + "repoName", + "webUrl", + "isFork", + "isArchived" + ] + } }, - "PublicRepository": { + "PublicVersionResponse": { "type": "object", "properties": { - "codeHostType": { - "type": "string", - "enum": [ - "github", - "gitlab", - "gitea", - "gerrit", - "bitbucketServer", - "bitbucketCloud", - "genericGitHost", - "azuredevops" - ] - }, - "repoId": { - "type": "number" - }, - "repoName": { - "type": "string" - }, - "webUrl": { - "type": "string" - }, - "repoDisplayName": { - "type": "string" - }, - "externalWebUrl": { - "type": "string" - }, - "imageUrl": { - "type": "string" - }, - "indexedAt": { - "type": "string", - "format": "date-time" - }, - "pushedAt": { - "type": "string", - "format": "date-time" - }, - "defaultBranch": { + "version": { "type": "string" - }, - "isFork": { - "type": "boolean" - }, - "isArchived": { - "type": "boolean" } }, "required": [ - "codeHostType", - "repoId", - "repoName", - "webUrl", - "isFork", - "isArchived" + "version" ] }, - "PublicListReposResponse": { - "type": "array", - "items": { - "$ref": "#/components/schemas/PublicRepository" - } - }, - "PublicVersionResponse": { + "PublicHealthResponse": { "type": "object", "properties": { - "version": { + "status": { "type": "string", - "description": "Running Sourcebot version." + "enum": [ + "ok" + ] } }, "required": [ - "version" + "status" ] }, "PublicFileSourceResponse": { @@ -604,43 +613,6 @@ "paths" ] }, - "PublicGetFilesResponse": { - "type": "array", - "items": { - "type": "object", - "properties": { - "type": { - "type": "string" - }, - "path": { - "type": "string" - }, - "name": { - "type": "string" - } - }, - "required": [ - "type", - "path", - "name" - ] - } - }, - "PublicGetFilesRequest": { - "type": "object", - "properties": { - "repoName": { - "type": "string" - }, - "revisionName": { - "type": "string" - } - }, - "required": [ - "repoName", - "revisionName" - ] - }, "PublicGetDiffResponse": { "type": "object", "properties": { @@ -729,28 +701,394 @@ "files" ] }, - "PublicFileTreeNode": { + "PublicFindSymbolsResponse": { "type": "object", "properties": { - "type": { - "type": "string" - }, - "path": { - "type": "string" + "stats": { + "type": "object", + "properties": { + "matchCount": { + "type": "number" + } + }, + "required": [ + "matchCount" + ] }, - "name": { - "type": "string" + "files": { + "type": "array", + "items": { + "type": "object", + "properties": { + "fileName": { + "type": "string" + }, + "repository": { + "type": "string" + }, + "repositoryId": { + "type": "number" + }, + "webUrl": { + "type": "string" + }, + "language": { + "type": "string" + }, + "matches": { + "type": "array", + "items": { + "type": "object", + "properties": { + "lineContent": { + "type": "string" + }, + "range": { + "type": "object", + "properties": { + "start": { + "type": "object", + "properties": { + "byteOffset": { + "type": "number" + }, + "lineNumber": { + "type": "number" + }, + "column": { + "type": "number" + } + }, + "required": [ + "byteOffset", + "lineNumber", + "column" + ] + }, + "end": { + "type": "object", + "properties": { + "byteOffset": { + "type": "number" + }, + "lineNumber": { + "type": "number" + }, + "column": { + "type": "number" + } + }, + "required": [ + "byteOffset", + "lineNumber", + "column" + ] + } + }, + "required": [ + "start", + "end" + ] + } + }, + "required": [ + "lineContent", + "range" + ] + } + } + }, + "required": [ + "fileName", + "repository", + "repositoryId", + "webUrl", + "language", + "matches" + ] + } }, - "children": { + "repositoryInfo": { "type": "array", "items": { - "$ref": "#/components/schemas/PublicFileTreeNode" + "type": "object", + "properties": { + "id": { + "type": "number" + }, + "codeHostType": { + "type": "string", + "enum": [ + "github", + "gitlab", + "gitea", + "gerrit", + "bitbucketServer", + "bitbucketCloud", + "genericGitHost", + "azuredevops" + ] + }, + "name": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "webUrl": { + "type": "string" + } + }, + "required": [ + "id", + "codeHostType", + "name" + ] } } }, "required": [ - "type", - "path", + "stats", + "files", + "repositoryInfo" + ] + }, + "PublicFindSymbolsRequest": { + "type": "object", + "properties": { + "symbolName": { + "type": "string" + }, + "language": { + "type": "string" + }, + "revisionName": { + "type": "string" + }, + "repoName": { + "type": "string" + } + }, + "required": [ + "symbolName" + ] + }, + "PublicCommit": { + "type": "object", + "properties": { + "hash": { + "type": "string", + "description": "The full commit SHA." + }, + "date": { + "type": "string", + "description": "The commit date in ISO 8601 format." + }, + "message": { + "type": "string", + "description": "The commit subject line." + }, + "refs": { + "type": "string", + "description": "Refs pointing to this commit (e.g. branch or tag names)." + }, + "body": { + "type": "string", + "description": "The commit body (everything after the subject line)." + }, + "author_name": { + "type": "string" + }, + "author_email": { + "type": "string" + } + }, + "required": [ + "hash", + "date", + "message", + "refs", + "body", + "author_name", + "author_email" + ] + }, + "PublicListCommitsResponse": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PublicCommit" + } + }, + "PublicEeUser": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true + }, + "email": { + "type": "string", + "nullable": true + }, + "createdAt": { + "type": "string", + "format": "date-time" + }, + "updatedAt": { + "type": "string", + "format": "date-time" + } + }, + "required": [ + "name", + "email", + "createdAt", + "updatedAt" + ] + }, + "PublicEeDeleteUserResponse": { + "type": "object", + "properties": { + "success": { + "type": "boolean" + }, + "message": { + "type": "string" + } + }, + "required": [ + "success", + "message" + ] + }, + "PublicEeUserListItem": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string", + "nullable": true + }, + "email": { + "type": "string", + "nullable": true + }, + "role": { + "type": "string", + "enum": [ + "OWNER", + "MEMBER", + "GUEST" + ] + }, + "createdAt": { + "type": "string", + "format": "date-time" + }, + "lastActivityAt": { + "type": "string", + "nullable": true, + "format": "date-time" + } + }, + "required": [ + "id", + "name", + "email", + "role", + "createdAt", + "lastActivityAt" + ] + }, + "PublicEeUsersResponse": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PublicEeUserListItem" + } + }, + "PublicEeAuditRecord": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "timestamp": { + "type": "string", + "format": "date-time" + }, + "action": { + "type": "string", + "description": "The audited action (e.g. `user.read`, `user.delete`, `audit.fetch`)." + }, + "actorId": { + "type": "string" + }, + "actorType": { + "type": "string" + }, + "targetId": { + "type": "string" + }, + "targetType": { + "type": "string" + }, + "sourcebotVersion": { + "type": "string" + }, + "metadata": { + "type": "object", + "nullable": true, + "additionalProperties": { + "nullable": true + } + }, + "orgId": { + "type": "number" + } + }, + "required": [ + "id", + "timestamp", + "action", + "actorId", + "actorType", + "targetId", + "targetType", + "sourcebotVersion", + "metadata", + "orgId" + ] + }, + "PublicEeAuditResponse": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PublicEeAuditRecord" + } + }, + "PublicFileTreeNode": { + "type": "object", + "properties": { + "type": { + "type": "string" + }, + "path": { + "type": "string" + }, + "name": { + "type": "string" + }, + "children": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PublicFileTreeNode" + } + } + }, + "required": [ + "type", + "path", "name", "children" ], @@ -777,9 +1115,13 @@ "post": { "operationId": "search", "tags": [ - "Search" + "Search & Navigation" ], - "summary": "Run a blocking code search", + "summary": "Search code", + "description": "Executes a blocking code search and returns all matching file chunks.", + "x-mint": { + "content": "## Usage\n\nThe `query` field supports literal, regexp, and symbol searches with filters for repository, file, language, branch, and more. See the [search syntax reference](https://docs.sourcebot.dev/docs/features/search/syntax-reference) for the full query language." + }, "requestBody": { "required": true, "content": { @@ -824,58 +1166,6 @@ } } }, - "/api/stream_search": { - "post": { - "operationId": "streamSearch", - "tags": [ - "Search" - ], - "summary": "Run a streaming code search", - "description": "Returns a server-sent event stream. Each event data payload is a JSON object describing either a chunk, final summary, or error.", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicSearchRequest" - } - } - } - }, - "responses": { - "200": { - "description": "SSE stream of search results.", - "content": { - "text/event-stream": { - "schema": { - "$ref": "#/components/schemas/PublicStreamSearchSse" - } - } - } - }, - "400": { - "description": "Invalid request body.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicApiServiceError" - } - } - } - }, - "500": { - "description": "Unexpected search failure.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicApiServiceError" - } - } - } - } - } - } - }, "/api/repos": { "get": { "operationId": "listRepositories", @@ -883,6 +1173,7 @@ "Repositories" ], "summary": "List repositories", + "description": "Returns a paginated list of repositories indexed by this Sourcebot instance.", "parameters": [ { "schema": { @@ -996,9 +1287,10 @@ "get": { "operationId": "getVersion", "tags": [ - "Misc" + "System" ], "summary": "Get Sourcebot version", + "description": "Returns the currently running Sourcebot version string.", "responses": { "200": { "description": "Current Sourcebot version.", @@ -1013,13 +1305,35 @@ } } }, + "/api/health": { + "get": { + "operationId": "getHealth", + "tags": [ + "System" + ], + "summary": "Health check", + "responses": { + "200": { + "description": "Service is healthy.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicHealthResponse" + } + } + } + } + } + } + }, "/api/source": { "get": { "operationId": "getFileSource", "tags": [ - "Files" + "Git" ], "summary": "Get file contents", + "description": "Returns the raw source content of a file at a given repository path and optional git ref.", "parameters": [ { "schema": { @@ -1094,9 +1408,10 @@ "post": { "operationId": "getFileTree", "tags": [ - "Files" + "Git" ], "summary": "Get a file tree", + "description": "Returns the file tree for a repository at a given revision.", "requestBody": { "required": true, "content": { @@ -1160,38 +1475,61 @@ } } }, - "/api/files": { - "post": { - "operationId": "listFiles", + "/api/diff": { + "get": { + "operationId": "getDiff", "tags": [ - "Files" + "Git" ], - "summary": "List files in a repository revision", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicGetFilesRequest" - } - } - } - }, - "responses": { - "200": { - "description": "Flat list of files in the requested repository revision.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PublicGetFilesResponse" - } - } - } + "summary": "Get diff between two commits", + "description": "Returns a structured diff between two git refs using a two-dot comparison. See [git-diff](https://git-scm.com/docs/git-diff) for details.", + "parameters": [ + { + "schema": { + "type": "string", + "description": "The fully-qualified repository name." + }, + "required": true, + "description": "The fully-qualified repository name.", + "name": "repo", + "in": "query" }, - "400": { - "description": "Invalid request body.", - "content": { - "application/json": { + { + "schema": { + "type": "string", + "description": "The base git ref (branch, tag, or commit SHA) to diff from." + }, + "required": true, + "description": "The base git ref (branch, tag, or commit SHA) to diff from.", + "name": "base", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "The head git ref (branch, tag, or commit SHA) to diff to." + }, + "required": true, + "description": "The head git ref (branch, tag, or commit SHA) to diff to.", + "name": "head", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Structured diff between the two refs.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicGetDiffResponse" + } + } + } + }, + "400": { + "description": "Invalid query parameters or git ref.", + "content": { + "application/json": { "schema": { "$ref": "#/components/schemas/PublicApiServiceError" } @@ -1209,7 +1547,7 @@ } }, "500": { - "description": "Unexpected file listing failure.", + "description": "Unexpected diff failure.", "content": { "application/json": { "schema": { @@ -1221,13 +1559,118 @@ } } }, - "/api/diff": { + "/api/find_definitions": { + "post": { + "operationId": "findDefinitions", + "tags": [ + "Search & Navigation" + ], + "summary": "Find symbol definitions", + "description": "Returns all locations in the codebase where the given symbol is defined.", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicFindSymbolsRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Symbol definition locations.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicFindSymbolsResponse" + } + } + } + }, + "400": { + "description": "Invalid request body.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicApiServiceError" + } + } + } + }, + "500": { + "description": "Unexpected failure.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicApiServiceError" + } + } + } + } + } + } + }, + "/api/find_references": { + "post": { + "operationId": "findReferences", + "tags": [ + "Search & Navigation" + ], + "summary": "Find symbol references", + "description": "Returns all locations in the codebase where the given symbol is referenced.", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicFindSymbolsRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Symbol reference locations.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicFindSymbolsResponse" + } + } + } + }, + "400": { + "description": "Invalid request body.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicApiServiceError" + } + } + } + }, + "500": { + "description": "Unexpected failure.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicApiServiceError" + } + } + } + } + } + } + }, + "/api/commits": { "get": { + "operationId": "listCommits", "tags": [ "Git" ], - "summary": "Get diff between two commits", - "description": "Returns a structured diff between two git refs (branches, tags, or commit SHAs) using a two-dot comparison. See [git-diff](https://git-scm.com/docs/git-diff) for details.", + "summary": "List commits", + "description": "Returns a paginated list of commits for a repository.", "parameters": [ { "schema": { @@ -1242,37 +1685,260 @@ { "schema": { "type": "string", - "description": "The base git ref (branch, tag, or commit SHA) to diff from." + "description": "Filter commits by message content (case-insensitive)." + }, + "required": false, + "description": "Filter commits by message content (case-insensitive).", + "name": "query", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Return commits after this date. Accepts ISO 8601 or relative formats (e.g. `30 days ago`)." + }, + "required": false, + "description": "Return commits after this date. Accepts ISO 8601 or relative formats (e.g. `30 days ago`).", + "name": "since", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Return commits before this date. Accepts ISO 8601 or relative formats." + }, + "required": false, + "description": "Return commits before this date. Accepts ISO 8601 or relative formats.", + "name": "until", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Filter commits by author name or email (case-insensitive)." + }, + "required": false, + "description": "Filter commits by author name or email (case-insensitive).", + "name": "author", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "The git ref (branch, tag, or commit SHA) to list commits from. Defaults to `HEAD`." + }, + "required": false, + "description": "The git ref (branch, tag, or commit SHA) to list commits from. Defaults to `HEAD`.", + "name": "ref", + "in": "query" + }, + { + "schema": { + "type": "string", + "description": "Restrict commits to those that touch this file path." + }, + "required": false, + "description": "Restrict commits to those that touch this file path.", + "name": "path", + "in": "query" + }, + { + "schema": { + "type": "integer", + "minimum": 0, + "exclusiveMinimum": true, + "default": 1 + }, + "required": false, + "name": "page", + "in": "query" + }, + { + "schema": { + "type": "integer", + "minimum": 0, + "exclusiveMinimum": true, + "maximum": 100, + "default": 50 + }, + "required": false, + "name": "perPage", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Paginated commit list.", + "headers": { + "X-Total-Count": { + "description": "Total number of commits matching the query across all pages.", + "schema": { + "type": "integer" + } + }, + "Link": { + "description": "Pagination links formatted per RFC 8288.", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicListCommitsResponse" + } + } + } + }, + "400": { + "description": "Invalid query parameters.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicApiServiceError" + } + } + } + }, + "404": { + "description": "Repository not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicApiServiceError" + } + } + } + }, + "500": { + "description": "Unexpected failure.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicApiServiceError" + } + } + } + } + } + } + }, + "/api/ee/user": { + "get": { + "operationId": "getUser", + "tags": [ + "User Management (EE)" + ], + "summary": "Get a user", + "description": "Fetches profile details for a single organization member by `userId`. Only organization owners can access this endpoint.", + "parameters": [ + { + "schema": { + "type": "string", + "description": "The ID of the user to retrieve." }, "required": true, - "description": "The base git ref (branch, tag, or commit SHA) to diff from.", - "name": "base", + "description": "The ID of the user to retrieve.", + "name": "userId", "in": "query" + } + ], + "responses": { + "200": { + "description": "User details.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicEeUser" + } + } + } + }, + "400": { + "description": "Missing userId parameter.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicApiServiceError" + } + } + } + }, + "403": { + "description": "Insufficient permissions or entitlement not enabled.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicApiServiceError" + } + } + } + }, + "404": { + "description": "User not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicApiServiceError" + } + } + } }, + "500": { + "description": "Unexpected failure.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicApiServiceError" + } + } + } + } + } + }, + "delete": { + "operationId": "deleteUser", + "tags": [ + "User Management (EE)" + ], + "summary": "Delete a user", + "description": "Permanently deletes a user and all associated records. Only organization owners can delete other users.", + "parameters": [ { "schema": { "type": "string", - "description": "The head git ref (branch, tag, or commit SHA) to diff to." + "description": "The ID of the user to delete." }, "required": true, - "description": "The head git ref (branch, tag, or commit SHA) to diff to.", - "name": "head", + "description": "The ID of the user to delete.", + "name": "userId", "in": "query" } ], "responses": { "200": { - "description": "Structured diff between the two refs.", + "description": "User deleted successfully.", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/PublicGetDiffResponse" + "$ref": "#/components/schemas/PublicEeDeleteUserResponse" } } } }, "400": { - "description": "Invalid query parameters or git ref.", + "description": "Missing userId parameter or attempting to delete own account.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicApiServiceError" + } + } + } + }, + "403": { + "description": "Insufficient permissions.", "content": { "application/json": { "schema": { @@ -1282,7 +1948,7 @@ } }, "404": { - "description": "Repository not found.", + "description": "User not found.", "content": { "application/json": { "schema": { @@ -1292,7 +1958,162 @@ } }, "500": { - "description": "Unexpected diff failure.", + "description": "Unexpected failure.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicApiServiceError" + } + } + } + } + } + } + }, + "/api/ee/users": { + "get": { + "operationId": "listUsers", + "tags": [ + "User Management (EE)" + ], + "summary": "List users", + "description": "Returns all members of the organization. Only organization owners can access this endpoint.", + "responses": { + "200": { + "description": "List of organization members.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicEeUsersResponse" + } + } + } + }, + "403": { + "description": "Insufficient permissions or entitlement not enabled.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicApiServiceError" + } + } + } + }, + "500": { + "description": "Unexpected failure.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicApiServiceError" + } + } + } + } + } + } + }, + "/api/ee/audit": { + "get": { + "operationId": "listAuditRecords", + "tags": [ + "Audit (EE)" + ], + "summary": "List audit records", + "description": "Returns a paginated list of audit log entries. Only organization owners can access this endpoint.", + "parameters": [ + { + "schema": { + "type": "string", + "format": "date-time", + "description": "Return records at or after this timestamp (ISO 8601)." + }, + "required": false, + "description": "Return records at or after this timestamp (ISO 8601).", + "name": "since", + "in": "query" + }, + { + "schema": { + "type": "string", + "format": "date-time", + "description": "Return records at or before this timestamp (ISO 8601)." + }, + "required": false, + "description": "Return records at or before this timestamp (ISO 8601).", + "name": "until", + "in": "query" + }, + { + "schema": { + "type": "integer", + "minimum": 0, + "exclusiveMinimum": true, + "default": 1 + }, + "required": false, + "name": "page", + "in": "query" + }, + { + "schema": { + "type": "integer", + "minimum": 0, + "exclusiveMinimum": true, + "maximum": 100, + "default": 50 + }, + "required": false, + "name": "perPage", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Paginated audit log.", + "headers": { + "X-Total-Count": { + "description": "Total number of audit records matching the query across all pages.", + "schema": { + "type": "integer" + } + }, + "Link": { + "description": "Pagination links formatted per RFC 8288.", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicEeAuditResponse" + } + } + } + }, + "400": { + "description": "Invalid query parameters.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicApiServiceError" + } + } + } + }, + "403": { + "description": "Insufficient permissions or entitlement not enabled.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicApiServiceError" + } + } + } + }, + "500": { + "description": "Unexpected failure.", "content": { "application/json": { "schema": { diff --git a/docs/docs.json b/docs/docs.json index 641e390c9..e526bd312 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -1,6 +1,6 @@ { "$schema": "https://mintlify.com/docs.json", - "theme": "almond", + "theme": "aspen", "name": "Sourcebot", "colors": { "primary": "#851EE7", @@ -12,10 +12,28 @@ "eyebrows": "section" }, "navigation": { - "anchors": [ + "global": { + "anchors": [ + { + "anchor": "Changelog", + "href": "https://sourcebot.dev/changelog", + "icon": "list-check" + }, + { + "anchor": "Roadmap", + "href": "https://github.com/sourcebot-dev/sourcebot/issues/459", + "icon": "map" + }, + { + "anchor": "Support", + "href": "https://github.com/sourcebot-dev/sourcebot/issues/new?template=get_help.md", + "icon": "life-ring" + } + ] + }, + "tabs": [ { - "anchor": "Docs", - "icon": "books", + "tab": "Documentation", "groups": [ { "group": "Getting Started", @@ -73,17 +91,6 @@ } ] }, - { - "group": "API Reference", - "pages": [ - "docs/api-reference/overview", - { - "group": "Public API", - "openapi": "api-reference/sourcebot-public.openapi.json", - "directory": "docs/api-reference/public-api" - } - ] - }, { "group": "Configuration", "pages": [ @@ -134,19 +141,55 @@ ] }, { - "anchor": "Changelog", - "href": "https://sourcebot.dev/changelog", - "icon": "list-check" - }, - { - "anchor": "Roadmap", - "href": "https://github.com/sourcebot-dev/sourcebot/issues/459", - "icon": "map" - }, - { - "anchor": "Support", - "href": "https://github.com/sourcebot-dev/sourcebot/issues/new?template=get_help.md", - "icon": "life-ring" + "tab": "API Reference", + "icon": "code", + "openapi": "api-reference/sourcebot-public.openapi.json", + "groups": [ + { + "group": "Search & Navigation", + "icon": "magnifying-glass", + "pages": [ + "POST /api/search", + "POST /api/find_definitions", + "POST /api/find_references" + ] + }, + { + "group": "Repositories", + "icon": "database", + "pages": [ + "GET /api/repos" + ] + }, + { + "group": "Git", + "icon": "code-branch", + "pages": [ + "GET /api/diff", + "GET /api/commits", + "GET /api/source", + "POST /api/tree" + ] + }, + { + "group": "Enterprise Management", + "icon": "building-columns", + "pages": [ + "GET /api/ee/user", + "DELETE /api/ee/user", + "GET /api/ee/users", + "GET /api/ee/audit" + ] + }, + { + "group": "System", + "icon": "server", + "pages": [ + "GET /api/version", + "GET /api/health" + ] + } + ] } ] }, diff --git a/docs/docs/api-reference/overview.mdx b/docs/docs/api-reference/overview.mdx deleted file mode 100644 index b2c180758..000000000 --- a/docs/docs/api-reference/overview.mdx +++ /dev/null @@ -1,31 +0,0 @@ ---- -title: API Reference -sidebarTitle: API Reference ---- - -You can fetch the OpenAPI document for your Sourcebot instance from `/api/openapi.json`. - -This API reference is generated from the web app's Zod schemas and OpenAPI registry. Mintlify renders it from the checked-in spec at [`/api-reference/sourcebot-public.openapi.json`](/api-reference/sourcebot-public.openapi.json). - -For authenticated access, create an API key in Sourcebot from **Settings -> API Keys** and send it with either: - -- `X-Sourcebot-Api-Key: sbk_...` -- `Authorization: Bearer sbk_...` - -Some instances may also allow anonymous access for these endpoints. On EE instances with OAuth enabled, bearer tokens with the `sboa_...` prefix are also accepted. - -The first documented endpoints include: - -- `/api/search` -- `/api/stream_search` -- `/api/repos` -- `/api/version` -- `/api/source` -- `/api/tree` -- `/api/files` - -To refresh the spec after you change those contracts: - -```bash -yarn openapi:generate -``` diff --git a/packages/web/src/app/api/(server)/commits/route.ts b/packages/web/src/app/api/(server)/commits/route.ts index fb3f9cbe1..e45bdea88 100644 --- a/packages/web/src/app/api/(server)/commits/route.ts +++ b/packages/web/src/app/api/(server)/commits/route.ts @@ -1,22 +1,10 @@ import { listCommits } from "@/features/git"; +import { listCommitsQueryParamsSchema } from "@/features/git/schemas"; import { apiHandler } from "@/lib/apiHandler"; import { buildLinkHeader } from "@/lib/pagination"; import { serviceErrorResponse, queryParamsSchemaValidationError } from "@/lib/serviceError"; import { isServiceError } from "@/lib/utils"; import { NextRequest } from "next/server"; -import { z } from "zod"; - -const listCommitsQueryParamsSchema = z.object({ - repo: z.string(), - query: z.string().optional(), - since: z.string().optional(), - until: z.string().optional(), - author: z.string().optional(), - ref: z.string().optional(), - path: z.string().optional(), - page: z.coerce.number().int().positive().default(1), - perPage: z.coerce.number().int().positive().max(100).default(50), -}); export const GET = apiHandler(async (request: NextRequest): Promise => { const rawParams = Object.fromEntries( diff --git a/packages/web/src/features/git/listCommitsApi.ts b/packages/web/src/features/git/listCommitsApi.ts index 405dcaf9a..a57128058 100644 --- a/packages/web/src/features/git/listCommitsApi.ts +++ b/packages/web/src/features/git/listCommitsApi.ts @@ -2,19 +2,13 @@ import { sew } from '@/actions'; import { invalidGitRef, notFound, ServiceError, unexpectedError } from '@/lib/serviceError'; import { withOptionalAuthV2 } from '@/withAuthV2'; import { getRepoPath } from '@sourcebot/shared'; +import { z } from 'zod'; import { simpleGit } from 'simple-git'; import { toGitDate, validateDateRange } from './dateUtils'; +import { commitSchema } from './schemas'; import { isGitRefValid } from './utils'; -export interface Commit { - hash: string; - date: string; - message: string; - refs: string; - body: string; - author_name: string; - author_email: string; -} +export type Commit = z.infer; export interface SearchCommitsResult { commits: Commit[]; diff --git a/packages/web/src/features/git/schemas.ts b/packages/web/src/features/git/schemas.ts index 91ee3a9b8..4a41bba38 100644 --- a/packages/web/src/features/git/schemas.ts +++ b/packages/web/src/features/git/schemas.ts @@ -64,3 +64,25 @@ const fileDiffSchema = z.object({ export const getDiffResponseSchema = z.object({ files: z.array(fileDiffSchema).describe('The list of changed files.'), }); + +export const listCommitsQueryParamsSchema = z.object({ + repo: z.string().describe('The fully-qualified repository name.'), + query: z.string().optional().describe('Filter commits by message content (case-insensitive).'), + since: z.string().optional().describe('Return commits after this date. Accepts ISO 8601 or relative formats (e.g. `30 days ago`).'), + until: z.string().optional().describe('Return commits before this date. Accepts ISO 8601 or relative formats.'), + author: z.string().optional().describe('Filter commits by author name or email (case-insensitive).'), + ref: z.string().optional().describe('The git ref (branch, tag, or commit SHA) to list commits from. Defaults to `HEAD`.'), + path: z.string().optional().describe('Restrict commits to those that touch this file path.'), + page: z.coerce.number().int().positive().default(1), + perPage: z.coerce.number().int().positive().max(100).default(50), +}); + +export const commitSchema = z.object({ + hash: z.string().describe('The full commit SHA.'), + date: z.string().describe('The commit date in ISO 8601 format.'), + message: z.string().describe('The commit subject line.'), + refs: z.string().describe('Refs pointing to this commit (e.g. branch or tag names).'), + body: z.string().describe('The commit body (everything after the subject line).'), + author_name: z.string(), + author_email: z.string(), +}); diff --git a/packages/web/src/openapi/publicApiDocument.ts b/packages/web/src/openapi/publicApiDocument.ts index 1f05f89ea..b6ac5eccb 100644 --- a/packages/web/src/openapi/publicApiDocument.ts +++ b/packages/web/src/openapi/publicApiDocument.ts @@ -1,28 +1,38 @@ -import { OpenAPIRegistry, OpenApiGeneratorV3 } from '@asteasolutions/zod-to-openapi'; -import type { ZodTypeAny } from 'zod'; +import { OpenApiGeneratorV3, OpenAPIRegistry } from '@asteasolutions/zod-to-openapi'; import type { ComponentsObject, SchemaObject, SecuritySchemeObject } from 'openapi3-ts/oas30'; +import { type ZodTypeAny } from 'zod'; +import z from 'zod'; import { + publicEeAuditQuerySchema, + publicEeAuditResponseSchema, + publicEeDeleteUserResponseSchema, + publicEeUserSchema, + publicEeUsersResponseSchema, publicFileSourceRequestSchema, publicFileSourceResponseSchema, + publicFindSymbolsRequestSchema, + publicFindSymbolsResponseSchema, publicGetDiffRequestSchema, publicGetDiffResponseSchema, - publicGetFilesRequestSchema, - publicGetFilesResponseSchema, publicGetTreeRequestSchema, - publicListReposQuerySchema, + publicHealthResponseSchema, + publicListCommitsQuerySchema, + publicListCommitsResponseSchema, + publicListReposQueryParamsSchema, publicListReposResponseSchema, publicSearchRequestSchema, publicSearchResponseSchema, publicServiceErrorSchema, - publicStreamSearchSseSchema, publicVersionResponseSchema, } from './publicApiSchemas.js'; +import dedent from 'dedent'; -const searchTag = { name: 'Search', description: 'Code search endpoints.' }; +const searchTag = { name: 'Search & Navigation', description: 'Code search and symbol navigation endpoints.' }; const reposTag = { name: 'Repositories', description: 'Repository listing and metadata endpoints.' }; -const filesTag = { name: 'Files', description: 'File tree, file listing, and file content endpoints.' }; -const gitTag = { name: 'Git', description: 'Git history and diff endpoints.' }; -const miscTag = { name: 'Misc', description: 'Miscellaneous public API endpoints.' }; +const gitTag = { name: 'Git', description: 'Git history, diff, and file content endpoints.' }; +const systemTag = { name: 'System', description: 'System health and version endpoints.' }; +const eeUserManagementTag = { name: 'User Management (EE)', description: 'User management endpoints. Requires the `org-management` entitlement and OWNER role.' }; +const eeAuditTag = { name: 'Audit (EE)', description: 'Audit log endpoints. Requires the `audit` entitlement and OWNER role.' }; const publicFileTreeNodeSchema: SchemaObject = { type: 'object', @@ -90,7 +100,8 @@ export function createPublicOpenApiDocument(version: string) { path: '/api/search', operationId: 'search', tags: [searchTag.name], - summary: 'Run a blocking code search', + summary: 'Search code', + description: 'Executes a blocking code search and returns all matching file chunks.', request: { body: { required: true, @@ -105,32 +116,12 @@ export function createPublicOpenApiDocument(version: string) { 400: errorJson('Invalid request body.'), 500: errorJson('Unexpected search failure.'), }, - }); - - registry.registerPath({ - method: 'post', - path: '/api/stream_search', - operationId: 'streamSearch', - tags: [searchTag.name], - summary: 'Run a streaming code search', - description: 'Returns a server-sent event stream. Each event data payload is a JSON object describing either a chunk, final summary, or error.', - request: { - body: { - required: true, - content: jsonContent(publicSearchRequestSchema), - }, - }, - responses: { - 200: { - description: 'SSE stream of search results.', - content: { - 'text/event-stream': { - schema: publicStreamSearchSseSchema, - }, - }, - }, - 400: errorJson('Invalid request body.'), - 500: errorJson('Unexpected search failure.'), + 'x-mint': { + content: dedent` + ## Usage + + The \`query\` field supports literal, regexp, and symbol searches with filters for repository, file, language, branch, and more. See the [search syntax reference](https://docs.sourcebot.dev/docs/features/search/syntax-reference) for the full query language. + `, }, }); @@ -140,8 +131,9 @@ export function createPublicOpenApiDocument(version: string) { operationId: 'listRepositories', tags: [reposTag.name], summary: 'List repositories', + description: 'Returns a paginated list of repositories indexed by this Sourcebot instance.', request: { - query: publicListReposQuerySchema, + query: publicListReposQueryParamsSchema, }, responses: { 200: { @@ -173,8 +165,9 @@ export function createPublicOpenApiDocument(version: string) { method: 'get', path: '/api/version', operationId: 'getVersion', - tags: [miscTag.name], + tags: [systemTag.name], summary: 'Get Sourcebot version', + description: 'Returns the currently running Sourcebot version string.', responses: { 200: { description: 'Current Sourcebot version.', @@ -183,12 +176,27 @@ export function createPublicOpenApiDocument(version: string) { }, }); + registry.registerPath({ + method: 'get', + path: '/api/health', + operationId: 'getHealth', + tags: [systemTag.name], + summary: 'Health check', + responses: { + 200: { + description: 'Service is healthy.', + content: jsonContent(publicHealthResponseSchema), + }, + }, + }); + registry.registerPath({ method: 'get', path: '/api/source', operationId: 'getFileSource', - tags: [filesTag.name], + tags: [gitTag.name], summary: 'Get file contents', + description: 'Returns the raw source content of a file at a given repository path and optional git ref.', request: { query: publicFileSourceRequestSchema, }, @@ -207,8 +215,9 @@ export function createPublicOpenApiDocument(version: string) { method: 'post', path: '/api/tree', operationId: 'getFileTree', - tags: [filesTag.name], + tags: [gitTag.name], summary: 'Get a file tree', + description: 'Returns the file tree for a repository at a given revision.', request: { body: { required: true, @@ -226,47 +235,200 @@ export function createPublicOpenApiDocument(version: string) { }, }); + + registry.registerPath({ + method: 'get', + path: '/api/diff', + operationId: 'getDiff', + tags: [gitTag.name], + summary: 'Get diff between two commits', + description: 'Returns a structured diff between two git refs using a two-dot comparison. See [git-diff](https://git-scm.com/docs/git-diff) for details.', + request: { + query: publicGetDiffRequestSchema, + }, + responses: { + 200: { + description: 'Structured diff between the two refs.', + content: jsonContent(publicGetDiffResponseSchema), + }, + 400: errorJson('Invalid query parameters or git ref.'), + 404: errorJson('Repository not found.'), + 500: errorJson('Unexpected diff failure.'), + }, + }); + registry.registerPath({ method: 'post', - path: '/api/files', - operationId: 'listFiles', - tags: [filesTag.name], - summary: 'List files in a repository revision', + path: '/api/find_definitions', + operationId: 'findDefinitions', + tags: [searchTag.name], + summary: 'Find symbol definitions', + description: 'Returns all locations in the codebase where the given symbol is defined.', request: { body: { required: true, - content: jsonContent(publicGetFilesRequestSchema), + content: jsonContent(publicFindSymbolsRequestSchema), }, }, responses: { 200: { - description: 'Flat list of files in the requested repository revision.', - content: jsonContent(publicGetFilesResponseSchema), + description: 'Symbol definition locations.', + content: jsonContent(publicFindSymbolsResponseSchema), }, 400: errorJson('Invalid request body.'), - 404: errorJson('Repository not found.'), - 500: errorJson('Unexpected file listing failure.'), + 500: errorJson('Unexpected failure.'), + }, + }); + + registry.registerPath({ + method: 'post', + path: '/api/find_references', + operationId: 'findReferences', + tags: [searchTag.name], + summary: 'Find symbol references', + description: 'Returns all locations in the codebase where the given symbol is referenced.', + request: { + body: { + required: true, + content: jsonContent(publicFindSymbolsRequestSchema), + }, + }, + responses: { + 200: { + description: 'Symbol reference locations.', + content: jsonContent(publicFindSymbolsResponseSchema), + }, + 400: errorJson('Invalid request body.'), + 500: errorJson('Unexpected failure.'), }, }); registry.registerPath({ method: 'get', - path: '/api/diff', - operationId: 'getDiff', + path: '/api/commits', + operationId: 'listCommits', tags: [gitTag.name], - summary: 'Get diff between two commits', - description: 'Returns a structured diff between two git refs (branches, tags, or commit SHAs) using a two-dot comparison. See [git-diff](https://git-scm.com/docs/git-diff) for details.', + summary: 'List commits', + description: 'Returns a paginated list of commits for a repository.', request: { - query: publicGetDiffRequestSchema, + query: publicListCommitsQuerySchema, }, responses: { 200: { - description: 'Structured diff between the two refs.', - content: jsonContent(publicGetDiffResponseSchema), + description: 'Paginated commit list.', + headers: { + 'X-Total-Count': { + description: 'Total number of commits matching the query across all pages.', + schema: { type: 'integer' }, + }, + Link: { + description: 'Pagination links formatted per RFC 8288.', + schema: { type: 'string' }, + }, + }, + content: jsonContent(publicListCommitsResponseSchema), }, - 400: errorJson('Invalid query parameters or git ref.'), + 400: errorJson('Invalid query parameters.'), 404: errorJson('Repository not found.'), - 500: errorJson('Unexpected diff failure.'), + 500: errorJson('Unexpected failure.'), + }, + }); + + // EE: User Management + registry.registerPath({ + method: 'get', + path: '/api/ee/user', + operationId: 'getUser', + tags: [eeUserManagementTag.name], + summary: 'Get a user', + description: 'Fetches profile details for a single organization member by `userId`. Only organization owners can access this endpoint.', + request: { + query: z.object({ + userId: z.string().describe('The ID of the user to retrieve.'), + }), + }, + responses: { + 200: { + description: 'User details.', + content: jsonContent(publicEeUserSchema), + }, + 400: errorJson('Missing userId parameter.'), + 403: errorJson('Insufficient permissions or entitlement not enabled.'), + 404: errorJson('User not found.'), + 500: errorJson('Unexpected failure.'), + }, + }); + + registry.registerPath({ + method: 'delete', + path: '/api/ee/user', + operationId: 'deleteUser', + tags: [eeUserManagementTag.name], + summary: 'Delete a user', + description: 'Permanently deletes a user and all associated records. Only organization owners can delete other users.', + request: { + query: z.object({ + userId: z.string().describe('The ID of the user to delete.'), + }), + }, + responses: { + 200: { + description: 'User deleted successfully.', + content: jsonContent(publicEeDeleteUserResponseSchema), + }, + 400: errorJson('Missing userId parameter or attempting to delete own account.'), + 403: errorJson('Insufficient permissions.'), + 404: errorJson('User not found.'), + 500: errorJson('Unexpected failure.'), + }, + }); + + registry.registerPath({ + method: 'get', + path: '/api/ee/users', + operationId: 'listUsers', + tags: [eeUserManagementTag.name], + summary: 'List users', + description: 'Returns all members of the organization. Only organization owners can access this endpoint.', + responses: { + 200: { + description: 'List of organization members.', + content: jsonContent(publicEeUsersResponseSchema), + }, + 403: errorJson('Insufficient permissions or entitlement not enabled.'), + 500: errorJson('Unexpected failure.'), + }, + }); + + // EE: Audit + registry.registerPath({ + method: 'get', + path: '/api/ee/audit', + operationId: 'listAuditRecords', + tags: [eeAuditTag.name], + summary: 'List audit records', + description: 'Returns a paginated list of audit log entries. Only organization owners can access this endpoint.', + request: { + query: publicEeAuditQuerySchema, + }, + responses: { + 200: { + description: 'Paginated audit log.', + headers: { + 'X-Total-Count': { + description: 'Total number of audit records matching the query across all pages.', + schema: { type: 'integer' }, + }, + Link: { + description: 'Pagination links formatted per RFC 8288.', + schema: { type: 'string' }, + }, + }, + content: jsonContent(publicEeAuditResponseSchema), + }, + 400: errorJson('Invalid query parameters.'), + 403: errorJson('Insufficient permissions or entitlement not enabled.'), + 500: errorJson('Unexpected failure.'), }, }); @@ -279,7 +441,7 @@ export function createPublicOpenApiDocument(version: string) { version, description: 'OpenAPI description for the public Sourcebot REST endpoints used for search, repository listing, and file browsing. Authentication is instance-dependent: API keys are the standard integration mechanism, OAuth bearer tokens are EE-only, and some instances may allow anonymous access.', }, - tags: [searchTag, reposTag, filesTag, gitTag, miscTag], + tags: [searchTag, reposTag, gitTag, systemTag, eeUserManagementTag, eeAuditTag], security: [ { [securitySchemeNames.bearerToken]: [] }, { [securitySchemeNames.apiKeyHeader]: [] }, diff --git a/packages/web/src/openapi/publicApiSchemas.ts b/packages/web/src/openapi/publicApiSchemas.ts index b360195fa..99726090b 100644 --- a/packages/web/src/openapi/publicApiSchemas.ts +++ b/packages/web/src/openapi/publicApiSchemas.ts @@ -1,7 +1,11 @@ import { extendZodWithOpenApi } from '@asteasolutions/zod-to-openapi'; -import { CodeHostType } from '@sourcebot/db'; import z from 'zod'; import { + findRelatedSymbolsRequestSchema, + findRelatedSymbolsResponseSchema, +} from '../features/codeNav/types.js'; +import { + commitSchema, fileSourceRequestSchema, fileSourceResponseSchema, getDiffRequestSchema, @@ -9,13 +13,14 @@ import { getFilesRequestSchema, getFilesResponseSchema, getTreeRequestSchema, + listCommitsQueryParamsSchema, } from '../features/git/schemas.js'; import { searchRequestSchema, searchResponseSchema, - streamedSearchResponseSchema, } from '../features/search/types.js'; import { serviceErrorSchema } from '../lib/serviceError.js'; +import { getVersionResponseSchema, listReposQueryParamsSchema, listReposResponseSchema } from '../lib/schemas.js'; let hasExtendedZod = false; @@ -30,51 +35,70 @@ export const publicServiceErrorSchema = serviceErrorSchema.openapi('PublicApiSer export const publicSearchRequestSchema = searchRequestSchema.openapi('PublicSearchRequest'); export const publicSearchResponseSchema = searchResponseSchema.openapi('PublicSearchResponse'); -export const publicStreamedSearchEventSchema = streamedSearchResponseSchema.openapi('PublicStreamedSearchEvent'); - export const publicGetTreeRequestSchema = getTreeRequestSchema.openapi('PublicGetTreeRequest'); - export const publicGetFilesRequestSchema = getFilesRequestSchema.openapi('PublicGetFilesRequest'); export const publicGetFilesResponseSchema = getFilesResponseSchema.openapi('PublicGetFilesResponse'); - export const publicFileSourceRequestSchema = fileSourceRequestSchema.openapi('PublicFileSourceRequest'); export const publicFileSourceResponseSchema = fileSourceResponseSchema.openapi('PublicFileSourceResponse'); +export const publicVersionResponseSchema = getVersionResponseSchema.openapi('PublicVersionResponse'); +export const publicListReposQueryParamsSchema = listReposQueryParamsSchema.openapi('PublicListReposQuery'); +export const publicListReposResponseSchema = listReposResponseSchema.openapi('PublicListReposResponse'); +export const publicGetDiffRequestSchema = getDiffRequestSchema.openapi('PublicGetDiffRequest'); +export const publicGetDiffResponseSchema = getDiffResponseSchema.openapi('PublicGetDiffResponse'); +export const publicFindSymbolsRequestSchema = findRelatedSymbolsRequestSchema.openapi('PublicFindSymbolsRequest'); +export const publicFindSymbolsResponseSchema = findRelatedSymbolsResponseSchema.openapi('PublicFindSymbolsResponse'); +export const publicListCommitsQuerySchema = listCommitsQueryParamsSchema.openapi('PublicListCommitsQuery'); +export const publicCommitSchema = commitSchema.openapi('PublicCommit'); +export const publicListCommitsResponseSchema = z.array(publicCommitSchema).openapi('PublicListCommitsResponse'); -export const publicVersionResponseSchema = z.object({ - version: z.string().openapi({ - description: 'Running Sourcebot version.', - }), -}).openapi('PublicVersionResponse'); +export const publicHealthResponseSchema = z.object({ + status: z.enum(['ok']), +}).openapi('PublicHealthResponse'); -export const publicRepositorySchema = z.object({ - codeHostType: z.nativeEnum(CodeHostType), - repoId: z.number(), - repoName: z.string(), - webUrl: z.string(), - repoDisplayName: z.string().optional(), - externalWebUrl: z.string().optional(), - imageUrl: z.string().optional(), - indexedAt: z.string().datetime().optional(), - pushedAt: z.string().datetime().optional(), - defaultBranch: z.string().optional(), - isFork: z.boolean(), - isArchived: z.boolean(), -}).openapi('PublicRepository'); +// EE: User Management +export const publicEeUserSchema = z.object({ + name: z.string().nullable(), + email: z.string().nullable(), + createdAt: z.string().datetime(), + updatedAt: z.string().datetime(), +}).openapi('PublicEeUser'); -export const publicListReposQuerySchema = z.object({ +export const publicEeUserListItemSchema = z.object({ + id: z.string(), + name: z.string().nullable(), + email: z.string().nullable(), + role: z.enum(['OWNER', 'MEMBER', 'GUEST']), + createdAt: z.string().datetime(), + lastActivityAt: z.string().datetime().nullable(), +}).openapi('PublicEeUserListItem'); + +export const publicEeUsersResponseSchema = z.array(publicEeUserListItemSchema).openapi('PublicEeUsersResponse'); + +export const publicEeDeleteUserResponseSchema = z.object({ + success: z.boolean(), + message: z.string(), +}).openapi('PublicEeDeleteUserResponse'); + +// EE: Audit +export const publicEeAuditQuerySchema = z.object({ + since: z.string().datetime().optional().describe('Return records at or after this timestamp (ISO 8601).'), + until: z.string().datetime().optional().describe('Return records at or before this timestamp (ISO 8601).'), page: z.coerce.number().int().positive().default(1), - perPage: z.coerce.number().int().positive().max(100).default(30), - sort: z.enum(['name', 'pushed']).default('name'), - direction: z.enum(['asc', 'desc']).default('asc'), - query: z.string().optional(), -}).openapi('PublicListReposQuery'); + perPage: z.coerce.number().int().positive().max(100).default(50), +}).openapi('PublicEeAuditQuery'); -export const publicListReposResponseSchema = z.array(publicRepositorySchema).openapi('PublicListReposResponse'); +export const publicEeAuditRecordSchema = z.object({ + id: z.string(), + timestamp: z.string().datetime(), + action: z.string().describe('The audited action (e.g. `user.read`, `user.delete`, `audit.fetch`).'), + actorId: z.string(), + actorType: z.string(), + targetId: z.string(), + targetType: z.string(), + sourcebotVersion: z.string(), + metadata: z.record(z.unknown()).nullable(), + orgId: z.number(), +}).openapi('PublicEeAuditRecord'); -export const publicGetDiffRequestSchema = getDiffRequestSchema.openapi('PublicGetDiffRequest'); -export const publicGetDiffResponseSchema = getDiffResponseSchema.openapi('PublicGetDiffResponse'); +export const publicEeAuditResponseSchema = z.array(publicEeAuditRecordSchema).openapi('PublicEeAuditResponse'); -export const publicStreamSearchSseSchema = z.string().openapi('PublicStreamSearchSse', { - description: 'Server-sent event stream. Each data frame contains one JSON object representing either a chunk update, a final summary, or an error.', - example: 'data: {"type":"chunk","stats":{"actualMatchCount":1}}\n\n', -});