Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,111 changes: 1,111 additions & 0 deletions docs/api-reference/sourcebot-public.openapi.json

Large diffs are not rendered by default.

13 changes: 12 additions & 1 deletion docs/docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,17 @@
}
]
},
{
"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": [
Expand Down Expand Up @@ -160,4 +171,4 @@
"default": "dark",
"strict": false
}
}
}
24 changes: 24 additions & 0 deletions docs/docs/api-reference/overview.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
---
title: API Reference
sidebarTitle: API Reference
---

Sourcebot exposes a public REST API for code search, repository listing, and file browsing.

The endpoint reference in this section is generated from the web app's Zod schemas and OpenAPI registry, then rendered by Mintlify from [`api-reference/sourcebot-public.openapi.json`](/api-reference/sourcebot-public.openapi.json).
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

The first documented surface includes:

- `/api/search`
- `/api/stream_search`
- `/api/repos`
- `/api/version`
- `/api/source`
- `/api/tree`
- `/api/files`

To refresh the spec after changing those contracts:

```bash
yarn openapi:generate
```
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"dev:prisma:migrate:reset": "yarn with-env yarn workspace @sourcebot/db prisma:migrate:reset",
"dev:prisma:db:push": "yarn with-env yarn workspace @sourcebot/db prisma:db:push",
"build:deps": "yarn workspaces foreach --recursive --topological --from '{@sourcebot/schemas,@sourcebot/db,@sourcebot/shared,@sourcebot/query-language}' run build",
"openapi:generate": "yarn workspace @sourcebot/web openapi:generate",
"tool:decrypt-jwe": "yarn with-env yarn workspace @sourcebot/web tool:decrypt-jwe"
},
"devDependencies": {
Expand Down
4 changes: 2 additions & 2 deletions packages/backend/src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ import { createLogger, env, hasEntitlement, PERMISSION_SYNC_SUPPORTED_IDENTITY_P
import express, { Request, Response } from 'express';
import 'express-async-errors';
import * as http from "http";
import z from 'zod';
import { ConnectionManager } from './connectionManager.js';
import { AccountPermissionSyncer } from './ee/accountPermissionSyncer.js';
import { PromClient } from './promClient.js';
import { RepoIndexManager } from './repoIndexManager.js';
import { createGitHubRepoRecord } from './repoCompileUtils.js';
import { Octokit } from '@octokit/rest';
import { SINGLE_TENANT_ORG_ID } from './constants.js';
import z from 'zod';

const logger = createLogger('api');
const PORT = 3060;
Expand Down Expand Up @@ -183,4 +183,4 @@ export class Api {
});
});
}
}
}
2 changes: 2 additions & 0 deletions packages/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"start": "next start",
"lint": "cross-env SKIP_ENV_VALIDATION=1 eslint .",
"test": "cross-env SKIP_ENV_VALIDATION=1 vitest",
"openapi:generate": "tsx tools/generateOpenApi.ts",
"generate:protos": "proto-loader-gen-types --includeComments --longs=Number --enums=String --defaults --oneofs --grpcLib=@grpc/grpc-js --keepCase --includeDirs=../../vendor/zoekt/grpc/protos --outDir=src/proto zoekt/webserver/v1/webserver.proto zoekt/webserver/v1/query.proto",
"dev:emails": "email dev --dir ./src/emails",
"stripe:listen": "stripe listen --forward-to http://localhost:3000/api/stripe",
Expand Down Expand Up @@ -194,6 +195,7 @@
"zod-to-json-schema": "^3.24.5"
},
"devDependencies": {
"@asteasolutions/zod-to-openapi": "7.3.4",
"@eslint/eslintrc": "^3",
"@react-email/preview-server": "5.2.8",
"@react-grab/mcp": "^0.1.23",
Expand Down
35 changes: 35 additions & 0 deletions packages/web/src/app/api/(server)/openapi.json/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import fs from 'node:fs/promises';
import path from 'node:path';
import { apiHandler } from '@/lib/apiHandler';

export const dynamic = 'force-dynamic';

const openApiPathCandidates = [
Comment thread
brendan-kellam marked this conversation as resolved.
Outdated
path.resolve(process.cwd(), 'docs/api-reference/sourcebot-public.openapi.json'),
path.resolve(process.cwd(), '../docs/api-reference/sourcebot-public.openapi.json'),
path.resolve(process.cwd(), '../../docs/api-reference/sourcebot-public.openapi.json'),
];

async function loadOpenApiDocument() {
for (const candidate of openApiPathCandidates) {
try {
return JSON.parse(await fs.readFile(candidate, 'utf8'));
} catch (error) {
if ((error as NodeJS.ErrnoException).code !== 'ENOENT') {
throw error;
}
}
}

throw new Error('OpenAPI spec file not found');
}

export const GET = apiHandler(async () => {
const document = await loadOpenApiDocument();

return Response.json(document, {
headers: {
'Content-Type': 'application/vnd.oai.openapi+json;version=3.0.3',
},
});
}, { track: false });
12 changes: 3 additions & 9 deletions packages/web/src/app/api/(server)/source/route.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,20 @@
'use server';

import { getFileSource } from '@/features/git';
import { fileSourceRequestSchema } from '@/features/git/schemas';
import { apiHandler } from "@/lib/apiHandler";
import { queryParamsSchemaValidationError, serviceErrorResponse } from "@/lib/serviceError";
import { isServiceError } from "@/lib/utils";
import { NextRequest } from "next/server";
import { z } from "zod";

const querySchema = z.object({
repo: z.string(),
path: z.string(),
ref: z.string().optional(),
});

export const GET = apiHandler(async (request: NextRequest) => {
const rawParams = Object.fromEntries(
Object.keys(querySchema.shape).map(key => [
Object.keys(fileSourceRequestSchema.shape).map(key => [
key,
request.nextUrl.searchParams.get(key) ?? undefined
])
);
const parsed = querySchema.safeParse(rawParams);
const parsed = fileSourceRequestSchema.safeParse(rawParams);

if (!parsed.success) {
return serviceErrorResponse(
Expand Down
22 changes: 3 additions & 19 deletions packages/web/src/features/git/getFileSourceApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,28 +9,12 @@ import { withOptionalAuthV2 } from '@/withAuthV2';
import { getRepoPath } from '@sourcebot/shared';
import { headers } from 'next/headers';
import simpleGit from 'simple-git';
import z from 'zod';
import type z from 'zod';
import { isGitRefValid, isPathValid } from './utils';
import { CodeHostType } from '@sourcebot/db';
import { fileSourceRequestSchema, fileSourceResponseSchema } from './schemas';

export const fileSourceRequestSchema = z.object({
path: z.string(),
repo: z.string(),
ref: z.string().optional(),
});
export { fileSourceRequestSchema, fileSourceResponseSchema } from './schemas';
export type FileSourceRequest = z.infer<typeof fileSourceRequestSchema>;

export const fileSourceResponseSchema = z.object({
source: z.string(),
language: z.string(),
path: z.string(),
repo: z.string(),
repoCodeHostType: z.nativeEnum(CodeHostType),
repoDisplayName: z.string().optional(),
repoExternalWebUrl: z.string().optional(),
webUrl: z.string(),
externalWebUrl: z.string().optional(),
});
export type FileSourceResponse = z.infer<typeof fileSourceResponseSchema>;

export const getFileSource = async ({ path: filePath, repo: repoName, ref }: FileSourceRequest, { source }: { source?: string } = {}): Promise<FileSourceResponse | ServiceError> => sew(() => withOptionalAuthV2(async ({ org, prisma, user }) => {
Expand Down
12 changes: 4 additions & 8 deletions packages/web/src/features/git/getFilesApi.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,15 @@
import { sew } from '@/actions';
import { FileTreeItem, fileTreeItemSchema } from "./types";
import { FileTreeItem } from "./types";
import { notFound, ServiceError, unexpectedError } from '@/lib/serviceError';
import { withOptionalAuthV2 } from "@/withAuthV2";
import { getRepoPath } from '@sourcebot/shared';
import simpleGit from 'simple-git';
import z from 'zod';
import type z from 'zod';
import { getFilesRequestSchema, getFilesResponseSchema } from './schemas';
import { logger } from './utils';

export const getFilesRequestSchema = z.object({
repoName: z.string(),
revisionName: z.string(),
});
export { getFilesRequestSchema, getFilesResponseSchema } from './schemas';
export type GetFilesRequest = z.infer<typeof getFilesRequestSchema>;

export const getFilesResponseSchema = z.array(fileTreeItemSchema);
export type GetFilesResponse = z.infer<typeof getFilesResponseSchema>;

export const getFiles = async ({ repoName, revisionName }: GetFilesRequest): Promise<GetFilesResponse | ServiceError> => sew(() =>
Expand Down
14 changes: 3 additions & 11 deletions packages/web/src/features/git/getTreeApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,12 @@ import { withOptionalAuthV2 } from "@/withAuthV2";
import { getRepoPath } from '@sourcebot/shared';
import { headers } from 'next/headers';
import simpleGit from 'simple-git';
import z from 'zod';
import { fileTreeNodeSchema } from './types';
import type z from 'zod';
import { getTreeRequestSchema, getTreeResponseSchema } from './schemas';
import { buildFileTree, isGitRefValid, isPathValid, logger, normalizePath } from './utils';

export const getTreeRequestSchema = z.object({
repoName: z.string(),
revisionName: z.string(),
paths: z.array(z.string()),
});
export { getTreeRequestSchema, getTreeResponseSchema } from './schemas';
export type GetTreeRequest = z.infer<typeof getTreeRequestSchema>;

export const getTreeResponseSchema = z.object({
tree: fileTreeNodeSchema,
});
export type GetTreeResponse = z.infer<typeof getTreeResponseSchema>;

/**
Expand Down
38 changes: 38 additions & 0 deletions packages/web/src/features/git/schemas.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { CodeHostType } from '@sourcebot/db';
import z from 'zod';
import { fileTreeItemSchema, fileTreeNodeSchema } from './types';

export const getTreeRequestSchema = z.object({
repoName: z.string(),
revisionName: z.string(),
paths: z.array(z.string()),
});

export const getTreeResponseSchema = z.object({
tree: fileTreeNodeSchema,
});

export const getFilesRequestSchema = z.object({
repoName: z.string(),
revisionName: z.string(),
});

export const getFilesResponseSchema = z.array(fileTreeItemSchema);

export const fileSourceRequestSchema = z.object({
path: z.string(),
repo: z.string(),
ref: z.string().optional(),
});

export const fileSourceResponseSchema = z.object({
source: z.string(),
language: z.string(),
path: z.string(),
repo: z.string(),
repoCodeHostType: z.nativeEnum(CodeHostType),
repoDisplayName: z.string().optional(),
repoExternalWebUrl: z.string().optional(),
webUrl: z.string(),
externalWebUrl: z.string().optional(),
});
Loading
Loading