2025-05-28 23:08:42 +00:00
|
|
|
'use server';
|
|
|
|
|
|
2025-05-03 18:33:58 +00:00
|
|
|
import escapeStringRegexp from "escape-string-regexp";
|
|
|
|
|
import { fileNotFound, ServiceError } from "../../lib/serviceError";
|
|
|
|
|
import { FileSourceRequest, FileSourceResponse } from "./types";
|
|
|
|
|
import { isServiceError } from "../../lib/utils";
|
|
|
|
|
import { search } from "./searchApi";
|
2025-05-15 20:42:58 +00:00
|
|
|
import { sew, withAuth, withOrgMembership } from "@/actions";
|
2025-05-28 23:08:42 +00:00
|
|
|
import { OrgRole } from "@sourcebot/db";
|
2025-06-18 17:50:36 +00:00
|
|
|
import { getAuditService } from "@/ee/features/audit/factory";
|
2025-05-03 18:33:58 +00:00
|
|
|
// @todo (bkellam) : We should really be using `git show <hash>:<path>` to fetch file contents here.
|
|
|
|
|
// This will allow us to support permalinks to files at a specific revision that may not be indexed
|
|
|
|
|
// by zoekt.
|
2025-06-18 17:50:36 +00:00
|
|
|
|
|
|
|
|
const auditService = getAuditService();
|
|
|
|
|
|
2025-05-28 23:08:42 +00:00
|
|
|
export const getFileSource = async ({ fileName, repository, branch }: FileSourceRequest, domain: string, apiKey: string | undefined = undefined): Promise<FileSourceResponse | ServiceError> => sew(() =>
|
2025-06-18 17:50:36 +00:00
|
|
|
withAuth((userId, apiKeyHash) =>
|
|
|
|
|
withOrgMembership(userId, domain, async ({ org }) => {
|
2025-05-15 20:42:58 +00:00
|
|
|
const escapedFileName = escapeStringRegexp(fileName);
|
|
|
|
|
const escapedRepository = escapeStringRegexp(repository);
|
2025-05-03 18:33:58 +00:00
|
|
|
|
2025-05-15 20:42:58 +00:00
|
|
|
let query = `file:${escapedFileName} repo:^${escapedRepository}$`;
|
|
|
|
|
if (branch) {
|
|
|
|
|
query = query.concat(` branch:${branch}`);
|
|
|
|
|
}
|
2025-05-03 18:33:58 +00:00
|
|
|
|
2025-05-15 20:42:58 +00:00
|
|
|
const searchResponse = await search({
|
|
|
|
|
query,
|
|
|
|
|
matches: 1,
|
|
|
|
|
whole: true,
|
2025-05-28 23:08:42 +00:00
|
|
|
}, domain, apiKey);
|
2025-05-03 18:33:58 +00:00
|
|
|
|
2025-05-15 20:42:58 +00:00
|
|
|
if (isServiceError(searchResponse)) {
|
|
|
|
|
return searchResponse;
|
|
|
|
|
}
|
2025-05-03 18:33:58 +00:00
|
|
|
|
2025-05-15 20:42:58 +00:00
|
|
|
const files = searchResponse.files;
|
2025-05-03 18:33:58 +00:00
|
|
|
|
2025-05-15 20:42:58 +00:00
|
|
|
if (!files || files.length === 0) {
|
|
|
|
|
return fileNotFound(fileName, repository);
|
|
|
|
|
}
|
2025-05-03 18:33:58 +00:00
|
|
|
|
2025-05-15 20:42:58 +00:00
|
|
|
const file = files[0];
|
|
|
|
|
const source = file.content ?? '';
|
|
|
|
|
const language = file.language;
|
2025-06-18 17:50:36 +00:00
|
|
|
|
|
|
|
|
await auditService.createAudit({
|
|
|
|
|
action: "query.file_source",
|
|
|
|
|
actor: {
|
|
|
|
|
id: apiKeyHash ?? userId,
|
|
|
|
|
type: apiKeyHash ? "api_key" : "user"
|
|
|
|
|
},
|
|
|
|
|
orgId: org.id,
|
|
|
|
|
target: {
|
|
|
|
|
id: `${escapedRepository}/${escapedFileName}${branch ? `:${branch}` : ''}`,
|
|
|
|
|
type: "file"
|
|
|
|
|
}
|
|
|
|
|
});
|
2025-05-15 20:42:58 +00:00
|
|
|
return {
|
|
|
|
|
source,
|
|
|
|
|
language,
|
2025-05-28 23:08:42 +00:00
|
|
|
webUrl: file.webUrl,
|
2025-05-15 20:42:58 +00:00
|
|
|
} satisfies FileSourceResponse;
|
2025-06-18 17:50:36 +00:00
|
|
|
|
2025-05-28 23:08:42 +00:00
|
|
|
}, /* minRequiredRole = */ OrgRole.GUEST), /* allowSingleTenantUnauthedAccess = */ true, apiKey ? { apiKey, domain } : undefined)
|
2025-05-15 20:42:58 +00:00
|
|
|
);
|