mirror of
https://github.com/sourcebot-dev/sourcebot.git
synced 2025-12-12 12:25:22 +00:00
Add data model for user <> repo permission link
This commit is contained in:
parent
83a8d306db
commit
b9a91c20fe
6 changed files with 143 additions and 51 deletions
|
|
@ -0,0 +1,14 @@
|
|||
-- CreateTable
|
||||
CREATE TABLE "UserToRepoPermission" (
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"repoId" INTEGER NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
|
||||
CONSTRAINT "UserToRepoPermission_pkey" PRIMARY KEY ("repoId","userId")
|
||||
);
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "UserToRepoPermission" ADD CONSTRAINT "UserToRepoPermission_repoId_fkey" FOREIGN KEY ("repoId") REFERENCES "Repo"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "UserToRepoPermission" ADD CONSTRAINT "UserToRepoPermission_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
|
@ -56,6 +56,7 @@ model Repo {
|
|||
connections RepoToConnection[]
|
||||
imageUrl String?
|
||||
repoIndexingStatus RepoIndexingStatus @default(NEW)
|
||||
permittedUsers UserToRepoPermission[]
|
||||
|
||||
// The id of the repo in the external service
|
||||
external_id String
|
||||
|
|
@ -239,7 +240,6 @@ model ApiKey {
|
|||
|
||||
createdBy User @relation(fields: [createdById], references: [id], onDelete: Cascade)
|
||||
createdById String
|
||||
|
||||
}
|
||||
|
||||
model Audit {
|
||||
|
|
@ -258,10 +258,8 @@ model Audit {
|
|||
orgId Int
|
||||
|
||||
@@index([actorId, actorType, targetId, targetType, orgId])
|
||||
|
||||
// Fast path for analytics queries – orgId is first because we assume most deployments are single tenant
|
||||
@@index([orgId, timestamp, action, actorId], map: "idx_audit_core_actions_full")
|
||||
|
||||
// Fast path for analytics queries for a specific user
|
||||
@@index([actorId, timestamp], map: "idx_audit_actor_time_full")
|
||||
}
|
||||
|
|
@ -277,6 +275,7 @@ model User {
|
|||
accounts Account[]
|
||||
orgs UserToOrg[]
|
||||
accountRequest AccountRequest?
|
||||
accessibleRepos UserToRepoPermission[]
|
||||
|
||||
/// List of pending invites that the user has created
|
||||
invites Invite[]
|
||||
|
|
@ -289,6 +288,18 @@ model User {
|
|||
updatedAt DateTime @updatedAt
|
||||
}
|
||||
|
||||
model UserToRepoPermission {
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
repo Repo @relation(fields: [repoId], references: [id], onDelete: Cascade)
|
||||
repoId Int
|
||||
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
userId String
|
||||
|
||||
@@id([repoId, userId])
|
||||
}
|
||||
|
||||
// @see : https://authjs.dev/concepts/database-models#account
|
||||
model Account {
|
||||
id String @id @default(cuid())
|
||||
|
|
|
|||
|
|
@ -639,7 +639,7 @@ export const getConnectionInfo = async (connectionId: number, domain: string) =>
|
|||
})));
|
||||
|
||||
export const getRepos = async (filter: { status?: RepoIndexingStatus[], connectionId?: number } = {}) => sew(() =>
|
||||
withOptionalAuthV2(async ({ org }) => {
|
||||
withOptionalAuthV2(async ({ org, user }) => {
|
||||
const repos = await prisma.repo.findMany({
|
||||
where: {
|
||||
orgId: org.id,
|
||||
|
|
@ -653,6 +653,13 @@ export const getRepos = async (filter: { status?: RepoIndexingStatus[], connecti
|
|||
}
|
||||
}
|
||||
} : {}),
|
||||
...(env.EXPERIMENT_PERMISSION_SYNC_ENABLED === 'true' ? {
|
||||
permittedUsers: {
|
||||
some: {
|
||||
userId: user?.id,
|
||||
}
|
||||
}
|
||||
} : {})
|
||||
},
|
||||
include: {
|
||||
connections: {
|
||||
|
|
@ -722,6 +729,13 @@ export const getRepoInfoByName = async (repoName: string, domain: string) => sew
|
|||
where: {
|
||||
name: repoName,
|
||||
orgId: org.id,
|
||||
...(env.EXPERIMENT_PERMISSION_SYNC_ENABLED === 'true' ? {
|
||||
permittedUsers: {
|
||||
some: {
|
||||
userId: userId,
|
||||
}
|
||||
}
|
||||
} : {})
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -889,6 +903,13 @@ export const experimental_addGithubRepositoryByUrl = async (repositoryUrl: strin
|
|||
external_id: githubRepo.id.toString(),
|
||||
external_codeHostType: 'github',
|
||||
external_codeHostUrl: 'https://github.com',
|
||||
...(env.EXPERIMENT_PERMISSION_SYNC_ENABLED === 'true' ? {
|
||||
permittedUsers: {
|
||||
some: {
|
||||
userId: userId,
|
||||
}
|
||||
}
|
||||
} : {})
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -1039,6 +1060,13 @@ export const flagReposForIndex = async (repoIds: number[], domain: string) => se
|
|||
where: {
|
||||
id: { in: repoIds },
|
||||
orgId: org.id,
|
||||
...(env.EXPERIMENT_PERMISSION_SYNC_ENABLED === 'true' ? {
|
||||
permittedUsers: {
|
||||
some: {
|
||||
userId: userId,
|
||||
}
|
||||
}
|
||||
} : {})
|
||||
},
|
||||
data: {
|
||||
repoIndexingStatus: RepoIndexingStatus.NEW,
|
||||
|
|
@ -2021,6 +2049,13 @@ export const getRepoImage = async (repoId: number, domain: string): Promise<Arra
|
|||
where: {
|
||||
id: repoId,
|
||||
orgId: org.id,
|
||||
...(env.EXPERIMENT_PERMISSION_SYNC_ENABLED === 'true' ? {
|
||||
permittedUsers: {
|
||||
some: {
|
||||
userId: userId,
|
||||
}
|
||||
}
|
||||
} : {})
|
||||
},
|
||||
include: {
|
||||
connections: {
|
||||
|
|
@ -2028,7 +2063,7 @@ export const getRepoImage = async (repoId: number, domain: string): Promise<Arra
|
|||
connection: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
if (!repo || !repo.imageUrl) {
|
||||
|
|
|
|||
|
|
@ -136,6 +136,8 @@ export const env = createEnv({
|
|||
EXPERIMENT_SELF_SERVE_REPO_INDEXING_ENABLED: booleanSchema.default('false'),
|
||||
// @NOTE: Take care to update actions.ts when changing the name of this.
|
||||
EXPERIMENT_SELF_SERVE_REPO_INDEXING_GITHUB_TOKEN: z.string().optional(),
|
||||
|
||||
EXPERIMENT_PERMISSION_SYNC_ENABLED: booleanSchema.default('false'),
|
||||
},
|
||||
// @NOTE: Please make sure of the following:
|
||||
// - Make sure you destructure all client variables in
|
||||
|
|
|
|||
|
|
@ -26,13 +26,20 @@ export type FileTreeNode = FileTreeItem & {
|
|||
* at a given revision.
|
||||
*/
|
||||
export const getTree = async (params: { repoName: string, revisionName: string }, domain: string) => sew(() =>
|
||||
withAuth((session) =>
|
||||
withOrgMembership(session, domain, async ({ org }) => {
|
||||
withAuth((userId) =>
|
||||
withOrgMembership(userId, domain, async ({ org }) => {
|
||||
const { repoName, revisionName } = params;
|
||||
const repo = await prisma.repo.findFirst({
|
||||
where: {
|
||||
name: repoName,
|
||||
orgId: org.id,
|
||||
...(env.EXPERIMENT_PERMISSION_SYNC_ENABLED === 'true' ? {
|
||||
permittedUsers: {
|
||||
some: {
|
||||
userId: userId,
|
||||
}
|
||||
}
|
||||
} : {})
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -85,13 +92,20 @@ export const getTree = async (params: { repoName: string, revisionName: string }
|
|||
* at a given revision.
|
||||
*/
|
||||
export const getFolderContents = async (params: { repoName: string, revisionName: string, path: string }, domain: string) => sew(() =>
|
||||
withAuth((session) =>
|
||||
withOrgMembership(session, domain, async ({ org }) => {
|
||||
withAuth((userId) =>
|
||||
withOrgMembership(userId, domain, async ({ org }) => {
|
||||
const { repoName, revisionName, path } = params;
|
||||
const repo = await prisma.repo.findFirst({
|
||||
where: {
|
||||
name: repoName,
|
||||
orgId: org.id,
|
||||
...(env.EXPERIMENT_PERMISSION_SYNC_ENABLED === 'true' ? {
|
||||
permittedUsers: {
|
||||
some: {
|
||||
userId: userId,
|
||||
}
|
||||
}
|
||||
} : {})
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -158,14 +172,21 @@ export const getFolderContents = async (params: { repoName: string, revisionName
|
|||
);
|
||||
|
||||
export const getFiles = async (params: { repoName: string, revisionName: string }, domain: string) => sew(() =>
|
||||
withAuth((session) =>
|
||||
withOrgMembership(session, domain, async ({ org }) => {
|
||||
withAuth((userId) =>
|
||||
withOrgMembership(userId, domain, async ({ org }) => {
|
||||
const { repoName, revisionName } = params;
|
||||
|
||||
const repo = await prisma.repo.findFirst({
|
||||
where: {
|
||||
name: repoName,
|
||||
orgId: org.id,
|
||||
...(env.EXPERIMENT_PERMISSION_SYNC_ENABLED === 'true' ? {
|
||||
permittedUsers: {
|
||||
some: {
|
||||
userId: userId,
|
||||
}
|
||||
}
|
||||
} : {})
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ import { StatusCodes } from "http-status-codes";
|
|||
import { zoektSearchResponseSchema } from "./zoektSchema";
|
||||
import { SearchRequest, SearchResponse, SourceRange } from "./types";
|
||||
import { OrgRole, Repo } from "@sourcebot/db";
|
||||
import * as Sentry from "@sentry/nextjs";
|
||||
import { sew, withAuth, withOrgMembership } from "@/actions";
|
||||
import { base64Decode } from "@sourcebot/shared";
|
||||
|
||||
|
|
@ -204,6 +203,13 @@ export const search = async ({ query, matches, contextLines, whole }: SearchRequ
|
|||
in: Array.from(repoIdentifiers).filter((id) => typeof id === "number"),
|
||||
},
|
||||
orgId: org.id,
|
||||
...(env.EXPERIMENT_PERMISSION_SYNC_ENABLED === 'true' ? {
|
||||
permittedUsers: {
|
||||
some: {
|
||||
userId,
|
||||
}
|
||||
}
|
||||
} : {})
|
||||
}
|
||||
})).forEach(repo => repos.set(repo.id, repo));
|
||||
|
||||
|
|
@ -213,6 +219,13 @@ export const search = async ({ query, matches, contextLines, whole }: SearchRequ
|
|||
in: Array.from(repoIdentifiers).filter((id) => typeof id === "string"),
|
||||
},
|
||||
orgId: org.id,
|
||||
...(env.EXPERIMENT_PERMISSION_SYNC_ENABLED === 'true' ? {
|
||||
permittedUsers: {
|
||||
some: {
|
||||
userId,
|
||||
}
|
||||
}
|
||||
} : {})
|
||||
}
|
||||
})).forEach(repo => repos.set(repo.name, repo));
|
||||
|
||||
|
|
@ -234,12 +247,8 @@ export const search = async ({ query, matches, contextLines, whole }: SearchRequ
|
|||
const identifier = file.RepositoryID ?? file.Repository;
|
||||
const repo = repos.get(identifier);
|
||||
|
||||
// This should never happen... but if it does, we skip the file.
|
||||
// This can happen if the user doesn't have access to the repository.
|
||||
if (!repo) {
|
||||
Sentry.captureMessage(
|
||||
`Repository not found for identifier: ${identifier}; skipping file "${file.FileName}"`,
|
||||
'warning'
|
||||
);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue