Skip to content

Commit b9a91c2

Browse files
Add data model for user <> repo permission link
1 parent 83a8d30 commit b9a91c2

6 files changed

Lines changed: 143 additions & 51 deletions

File tree

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
-- CreateTable
2+
CREATE TABLE "UserToRepoPermission" (
3+
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
4+
"repoId" INTEGER NOT NULL,
5+
"userId" TEXT NOT NULL,
6+
7+
CONSTRAINT "UserToRepoPermission_pkey" PRIMARY KEY ("repoId","userId")
8+
);
9+
10+
-- AddForeignKey
11+
ALTER TABLE "UserToRepoPermission" ADD CONSTRAINT "UserToRepoPermission_repoId_fkey" FOREIGN KEY ("repoId") REFERENCES "Repo"("id") ON DELETE CASCADE ON UPDATE CASCADE;
12+
13+
-- AddForeignKey
14+
ALTER TABLE "UserToRepoPermission" ADD CONSTRAINT "UserToRepoPermission_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;

packages/db/prisma/schema.prisma

Lines changed: 42 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,11 @@ enum ChatVisibility {
4141
}
4242

4343
model Repo {
44-
id Int @id @default(autoincrement())
44+
id Int @id @default(autoincrement())
4545
name String
4646
displayName String?
47-
createdAt DateTime @default(now())
48-
updatedAt DateTime @updatedAt
47+
createdAt DateTime @default(now())
48+
updatedAt DateTime @updatedAt
4949
/// When the repo was last indexed successfully.
5050
indexedAt DateTime?
5151
isFork Boolean
@@ -55,7 +55,8 @@ model Repo {
5555
webUrl String?
5656
connections RepoToConnection[]
5757
imageUrl String?
58-
repoIndexingStatus RepoIndexingStatus @default(NEW)
58+
repoIndexingStatus RepoIndexingStatus @default(NEW)
59+
permittedUsers UserToRepoPermission[]
5960
6061
// The id of the repo in the external service
6162
external_id String
@@ -75,9 +76,9 @@ model Repo {
7576
model SearchContext {
7677
id Int @id @default(autoincrement())
7778
78-
name String
79+
name String
7980
description String?
80-
repos Repo[]
81+
repos Repo[]
8182
8283
org Org @relation(fields: [orgId], references: [id], onDelete: Cascade)
8384
orgId Int
@@ -146,7 +147,7 @@ model AccountRequest {
146147
147148
createdAt DateTime @default(now())
148149
149-
requestedBy User @relation(fields: [requestedById], references: [id], onDelete: Cascade)
150+
requestedBy User @relation(fields: [requestedById], references: [id], onDelete: Cascade)
150151
requestedById String @unique
151152
152153
org Org @relation(fields: [orgId], references: [id], onDelete: Cascade)
@@ -168,7 +169,7 @@ model Org {
168169
apiKeys ApiKey[]
169170
isOnboarded Boolean @default(false)
170171
imageUrl String?
171-
metadata Json? // For schema see orgMetadataSchema in packages/web/src/types.ts
172+
metadata Json? // For schema see orgMetadataSchema in packages/web/src/types.ts
172173
173174
memberApprovalRequired Boolean @default(true)
174175
@@ -178,10 +179,10 @@ model Org {
178179
179180
/// List of pending invites to this organization
180181
invites Invite[]
181-
182+
182183
/// The invite id for this organization
183184
inviteLinkEnabled Boolean @default(false)
184-
inviteLinkId String?
185+
inviteLinkId String?
185186
186187
audits Audit[]
187188
@@ -228,55 +229,53 @@ model Secret {
228229
}
229230

230231
model ApiKey {
231-
name String
232-
hash String @id @unique
232+
name String
233+
hash String @id @unique
233234
234-
createdAt DateTime @default(now())
235+
createdAt DateTime @default(now())
235236
lastUsedAt DateTime?
236237
237238
org Org @relation(fields: [orgId], references: [id], onDelete: Cascade)
238239
orgId Int
239240
240-
createdBy User @relation(fields: [createdById], references: [id], onDelete: Cascade)
241+
createdBy User @relation(fields: [createdById], references: [id], onDelete: Cascade)
241242
createdById String
242-
243243
}
244244

245245
model Audit {
246-
id String @id @default(cuid())
246+
id String @id @default(cuid())
247247
timestamp DateTime @default(now())
248-
249-
action String
250-
actorId String
251-
actorType String
252-
targetId String
253-
targetType String
248+
249+
action String
250+
actorId String
251+
actorType String
252+
targetId String
253+
targetType String
254254
sourcebotVersion String
255-
metadata Json?
255+
metadata Json?
256256
257257
org Org @relation(fields: [orgId], references: [id], onDelete: Cascade)
258258
orgId Int
259259
260260
@@index([actorId, actorType, targetId, targetType, orgId])
261-
262261
// Fast path for analytics queries – orgId is first because we assume most deployments are single tenant
263262
@@index([orgId, timestamp, action, actorId], map: "idx_audit_core_actions_full")
264-
265263
// Fast path for analytics queries for a specific user
266264
@@index([actorId, timestamp], map: "idx_audit_actor_time_full")
267265
}
268266

269267
// @see : https://authjs.dev/concepts/database-models#user
270268
model User {
271-
id String @id @default(cuid())
269+
id String @id @default(cuid())
272270
name String?
273-
email String? @unique
271+
email String? @unique
274272
hashedPassword String?
275273
emailVerified DateTime?
276274
image String?
277275
accounts Account[]
278276
orgs UserToOrg[]
279277
accountRequest AccountRequest?
278+
accessibleRepos UserToRepoPermission[]
280279
281280
/// List of pending invites that the user has created
282281
invites Invite[]
@@ -289,6 +288,18 @@ model User {
289288
updatedAt DateTime @updatedAt
290289
}
291290

291+
model UserToRepoPermission {
292+
createdAt DateTime @default(now())
293+
294+
repo Repo @relation(fields: [repoId], references: [id], onDelete: Cascade)
295+
repoId Int
296+
297+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
298+
userId String
299+
300+
@@id([repoId, userId])
301+
}
302+
292303
// @see : https://authjs.dev/concepts/database-models#account
293304
model Account {
294305
id String @id @default(cuid())
@@ -326,17 +337,17 @@ model Chat {
326337
327338
name String?
328339
329-
createdBy User @relation(fields: [createdById], references: [id], onDelete: Cascade)
340+
createdBy User @relation(fields: [createdById], references: [id], onDelete: Cascade)
330341
createdById String
331342
332343
createdAt DateTime @default(now())
333344
updatedAt DateTime @updatedAt
334-
345+
335346
org Org @relation(fields: [orgId], references: [id], onDelete: Cascade)
336347
orgId Int
337348
338349
visibility ChatVisibility @default(PRIVATE)
339-
isReadonly Boolean @default(false)
350+
isReadonly Boolean @default(false)
340351
341352
messages Json // This is a JSON array of `Message` types from @ai-sdk/ui-utils.
342-
}
353+
}

packages/web/src/actions.ts

Lines changed: 42 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -639,7 +639,7 @@ export const getConnectionInfo = async (connectionId: number, domain: string) =>
639639
})));
640640

641641
export const getRepos = async (filter: { status?: RepoIndexingStatus[], connectionId?: number } = {}) => sew(() =>
642-
withOptionalAuthV2(async ({ org }) => {
642+
withOptionalAuthV2(async ({ org, user }) => {
643643
const repos = await prisma.repo.findMany({
644644
where: {
645645
orgId: org.id,
@@ -653,6 +653,13 @@ export const getRepos = async (filter: { status?: RepoIndexingStatus[], connecti
653653
}
654654
}
655655
} : {}),
656+
...(env.EXPERIMENT_PERMISSION_SYNC_ENABLED === 'true' ? {
657+
permittedUsers: {
658+
some: {
659+
userId: user?.id,
660+
}
661+
}
662+
} : {})
656663
},
657664
include: {
658665
connections: {
@@ -722,6 +729,13 @@ export const getRepoInfoByName = async (repoName: string, domain: string) => sew
722729
where: {
723730
name: repoName,
724731
orgId: org.id,
732+
...(env.EXPERIMENT_PERMISSION_SYNC_ENABLED === 'true' ? {
733+
permittedUsers: {
734+
some: {
735+
userId: userId,
736+
}
737+
}
738+
} : {})
725739
},
726740
});
727741

@@ -804,7 +818,7 @@ export const experimental_addGithubRepositoryByUrl = async (repositoryUrl: strin
804818
// Parse repository URL to extract owner/repo
805819
const repoInfo = (() => {
806820
const url = repositoryUrl.trim();
807-
821+
808822
// Handle various GitHub URL formats
809823
const patterns = [
810824
// https://github.com/owner/repo or https://github.com/owner/repo.git
@@ -814,7 +828,7 @@ export const experimental_addGithubRepositoryByUrl = async (repositoryUrl: strin
814828
// owner/repo
815829
/^([a-zA-Z0-9_.-]+)\/([a-zA-Z0-9_.-]+)$/
816830
];
817-
831+
818832
for (const pattern of patterns) {
819833
const match = url.match(pattern);
820834
if (match) {
@@ -824,7 +838,7 @@ export const experimental_addGithubRepositoryByUrl = async (repositoryUrl: strin
824838
};
825839
}
826840
}
827-
841+
828842
return null;
829843
})();
830844

@@ -837,7 +851,7 @@ export const experimental_addGithubRepositoryByUrl = async (repositoryUrl: strin
837851
}
838852

839853
const { owner, repo } = repoInfo;
840-
854+
841855
// Use GitHub API to fetch repository information and get the external_id
842856
const octokit = new Octokit({
843857
auth: env.EXPERIMENT_SELF_SERVE_REPO_INDEXING_GITHUB_TOKEN
@@ -866,7 +880,7 @@ export const experimental_addGithubRepositoryByUrl = async (repositoryUrl: strin
866880
message: `Access to repository '${owner}/${repo}' is forbidden. Only public repositories can be added.`,
867881
} satisfies ServiceError;
868882
}
869-
883+
870884
return {
871885
statusCode: StatusCodes.INTERNAL_SERVER_ERROR,
872886
errorCode: ErrorCode.INVALID_REQUEST_BODY,
@@ -889,6 +903,13 @@ export const experimental_addGithubRepositoryByUrl = async (repositoryUrl: strin
889903
external_id: githubRepo.id.toString(),
890904
external_codeHostType: 'github',
891905
external_codeHostUrl: 'https://github.com',
906+
...(env.EXPERIMENT_PERMISSION_SYNC_ENABLED === 'true' ? {
907+
permittedUsers: {
908+
some: {
909+
userId: userId,
910+
}
911+
}
912+
} : {})
892913
}
893914
});
894915

@@ -1039,6 +1060,13 @@ export const flagReposForIndex = async (repoIds: number[], domain: string) => se
10391060
where: {
10401061
id: { in: repoIds },
10411062
orgId: org.id,
1063+
...(env.EXPERIMENT_PERMISSION_SYNC_ENABLED === 'true' ? {
1064+
permittedUsers: {
1065+
some: {
1066+
userId: userId,
1067+
}
1068+
}
1069+
} : {})
10421070
},
10431071
data: {
10441072
repoIndexingStatus: RepoIndexingStatus.NEW,
@@ -2021,14 +2049,21 @@ export const getRepoImage = async (repoId: number, domain: string): Promise<Arra
20212049
where: {
20222050
id: repoId,
20232051
orgId: org.id,
2052+
...(env.EXPERIMENT_PERMISSION_SYNC_ENABLED === 'true' ? {
2053+
permittedUsers: {
2054+
some: {
2055+
userId: userId,
2056+
}
2057+
}
2058+
} : {})
20242059
},
20252060
include: {
20262061
connections: {
20272062
include: {
20282063
connection: true,
20292064
}
20302065
}
2031-
}
2066+
},
20322067
});
20332068

20342069
if (!repo || !repo.imageUrl) {

packages/web/src/env.mjs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,8 @@ export const env = createEnv({
136136
EXPERIMENT_SELF_SERVE_REPO_INDEXING_ENABLED: booleanSchema.default('false'),
137137
// @NOTE: Take care to update actions.ts when changing the name of this.
138138
EXPERIMENT_SELF_SERVE_REPO_INDEXING_GITHUB_TOKEN: z.string().optional(),
139+
140+
EXPERIMENT_PERMISSION_SYNC_ENABLED: booleanSchema.default('false'),
139141
},
140142
// @NOTE: Please make sure of the following:
141143
// - Make sure you destructure all client variables in

0 commit comments

Comments
 (0)