Skip to content

Commit 199c2bd

Browse files
refactor
1 parent b5a682f commit 199c2bd

60 files changed

Lines changed: 2714 additions & 1807 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

packages/db/prisma/migrations/20260612235524_add_scim_users_support/migration.sql renamed to packages/db/prisma/migrations/20260619214548_add_scim_users_support/migration.sql

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
-- AlterTable
2+
ALTER TABLE "Org" ADD COLUMN "isScimEnabled" BOOLEAN NOT NULL DEFAULT false;
3+
14
-- AlterTable
25
ALTER TABLE "UserToOrg" ADD COLUMN "isActive" BOOLEAN NOT NULL DEFAULT true,
36
ADD COLUMN "scimExternalId" TEXT;

packages/db/prisma/schema.prisma

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,8 @@ model Org {
276276
isOnboarded Boolean @default(false)
277277
imageUrl String?
278278
279+
isScimEnabled Boolean @default(false)
280+
279281
/// @deprecated This property can be controlled by the environment
280282
/// variable `REQUIRE_APPROVAL_NEW_MEMBERS`. To ensure that we use
281283
/// the correct setting, use the helper function `isMemberApprovalRequired`

packages/web/src/__mocks__/prisma.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export const MOCK_ORG: Org = {
1717
updatedAt: new Date(),
1818
isOnboarded: true,
1919
imageUrl: null,
20-
metadata: null,
20+
isScimEnabled: false,
2121
memberApprovalRequired: false,
2222
isCredentialsLoginEnabled: true,
2323
isEmailCodeLoginEnabled: false,

packages/web/src/actions.ts

Lines changed: 6 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,20 @@
11
'use server';
22

33
import { createAudit } from "@/ee/features/audit/audit";
4-
import { env, getSMTPConnectionURL } from "@sourcebot/shared";
54
import { ErrorCode } from "@/lib/errorCodes";
6-
import { notAuthenticated, notFound, ServiceError } from "@/lib/serviceError";
7-
import { __unsafePrisma } from "@/prisma";
8-
import { render } from "@react-email/components";
9-
import { generateApiKey, getTokenFromConfig } from "@sourcebot/shared";
5+
import { notFound, ServiceError } from "@/lib/serviceError";
6+
import { sew } from "@/middleware/sew";
107
import { ConnectionSyncJobStatus, OrgRole, Prisma, RepoIndexingJobStatus, RepoIndexingJobType } from "@sourcebot/db";
11-
import { createLogger } from "@sourcebot/shared";
128
import { GiteaConnectionConfig } from "@sourcebot/schemas/v3/gitea.type";
139
import { GithubConnectionConfig } from "@sourcebot/schemas/v3/github.type";
1410
import { GitlabConnectionConfig } from "@sourcebot/schemas/v3/gitlab.type";
11+
import { createLogger, env, generateApiKey, getTokenFromConfig } from "@sourcebot/shared";
1512
import { StatusCodes } from "http-status-codes";
1613
import { cookies } from "next/headers";
17-
import { createTransport } from "nodemailer";
18-
import JoinRequestSubmittedEmail from "./emails/joinRequestSubmittedEmail";
19-
import { AGENTIC_SEARCH_TUTORIAL_DISMISSED_COOKIE_NAME, MOBILE_UNSUPPORTED_SPLASH_SCREEN_DISMISSED_COOKIE_NAME, SINGLE_TENANT_ORG_ID } from "./lib/constants";
20-
import { RepositoryQuery } from "./lib/types";
21-
import { getAuthenticatedUser, withAuth, withOptionalAuth } from "./middleware/withAuth";
2214
import { getBrowsePath } from "./app/(app)/browse/hooks/utils";
23-
import { sew } from "@/middleware/sew";
15+
import { AGENTIC_SEARCH_TUTORIAL_DISMISSED_COOKIE_NAME, MOBILE_UNSUPPORTED_SPLASH_SCREEN_DISMISSED_COOKIE_NAME } from "./lib/constants";
16+
import { RepositoryQuery } from "./lib/types";
17+
import { withAuth, withOptionalAuth } from "./middleware/withAuth";
2418

2519
const logger = createLogger('web-actions');
2620

@@ -375,110 +369,6 @@ export const getRepoInfoByName = async (repoName: string) => sew(() =>
375369
}
376370
}));
377371

378-
// eslint-disable-next-line authz/require-auth-wrapper -- calls getAuthenticatedUser() directly; runs pre-org-membership so cannot use withAuth
379-
export const createAccountRequest = async () => sew(async () => {
380-
const authResult = await getAuthenticatedUser();
381-
if (!authResult) {
382-
return notAuthenticated();
383-
}
384-
385-
const { user } = authResult;
386-
387-
const org = await __unsafePrisma.org.findUnique({
388-
where: {
389-
id: SINGLE_TENANT_ORG_ID,
390-
},
391-
});
392-
393-
if (!org) {
394-
return notFound("Organization not found");
395-
}
396-
397-
const existingRequest = await __unsafePrisma.accountRequest.findUnique({
398-
where: {
399-
requestedById_orgId: {
400-
requestedById: user.id,
401-
orgId: org.id,
402-
},
403-
},
404-
});
405-
406-
if (existingRequest) {
407-
logger.warn(`User ${user.id} already has an account request for org ${org.id}. Skipping account request creation.`);
408-
return {
409-
success: true,
410-
existingRequest: true,
411-
}
412-
}
413-
414-
if (!existingRequest) {
415-
await __unsafePrisma.accountRequest.create({
416-
data: {
417-
requestedById: user.id,
418-
orgId: org.id,
419-
},
420-
});
421-
422-
const smtpConnectionUrl = getSMTPConnectionURL();
423-
if (smtpConnectionUrl && env.EMAIL_FROM_ADDRESS) {
424-
// TODO: This is needed because we can't fetch the origin from the request headers when this is called
425-
// on user creation (the header isn't set when next-auth calls onCreateUser for some reason)
426-
const deploymentUrl = env.AUTH_URL;
427-
428-
const owners = await __unsafePrisma.user.findMany({
429-
where: {
430-
orgs: {
431-
some: {
432-
orgId: org.id,
433-
role: "OWNER",
434-
},
435-
},
436-
},
437-
});
438-
439-
if (owners.length === 0) {
440-
logger.error(`Failed to find any owners for org ${org.id} when drafting email for account request from ${user.id}`);
441-
} else {
442-
const html = await render(JoinRequestSubmittedEmail({
443-
baseUrl: deploymentUrl,
444-
requestor: {
445-
name: user.name ?? undefined,
446-
email: user.email,
447-
avatarUrl: user.image ?? undefined,
448-
},
449-
orgName: org.name,
450-
orgImageUrl: org.imageUrl ?? undefined,
451-
}));
452-
453-
const ownerEmails = owners
454-
.map((owner) => owner.email)
455-
.filter((email): email is string => email !== null);
456-
457-
const transport = createTransport(smtpConnectionUrl);
458-
const result = await transport.sendMail({
459-
to: ownerEmails,
460-
from: env.EMAIL_FROM_ADDRESS,
461-
subject: `New account request for ${org.name} on Sourcebot`,
462-
html,
463-
text: `New account request for ${org.name} on Sourcebot by ${user.name ?? user.email}`,
464-
});
465-
466-
const failed = result.rejected.concat(result.pending).filter(Boolean);
467-
if (failed.length > 0) {
468-
logger.error(`Failed to send account request email to ${ownerEmails.join(', ')}: ${failed}`);
469-
}
470-
}
471-
} else {
472-
logger.warn(`SMTP_CONNECTION_URL or EMAIL_FROM_ADDRESS not set. Skipping account request email to owner`);
473-
}
474-
}
475-
476-
return {
477-
success: true,
478-
existingRequest: false,
479-
}
480-
});
481-
482372
export const getSearchContexts = async () => sew(() =>
483373
withOptionalAuth(async ({ org, prisma }) => {
484374
const searchContexts = await prisma.searchContext.findMany({

packages/web/src/app/(app)/@sidebar/components/defaultSidebar/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { auth } from "@/auth";
33
import { HOME_VIEW_COOKIE_NAME } from "@/lib/constants";
44
import { HomeView } from "@/hooks/useHomeView";
55
import { getConnectionStats } from "@/actions";
6-
import { getOrgAccountRequests } from "@/features/userManagement/actions";
6+
import { getOrgAccountRequests } from "@/features/membership/actions";
77
import { isServiceError } from "@/lib/utils";
88
import { ServiceErrorException } from "@/lib/serviceError";
99
import { OrgRole } from "@prisma/client";

packages/web/src/app/(app)/components/submitAccountRequestButton.tsx

Lines changed: 0 additions & 62 deletions
This file was deleted.

packages/web/src/app/(app)/components/submitJoinRequest.tsx

Lines changed: 0 additions & 43 deletions
This file was deleted.

packages/web/src/app/(app)/layout.tsx

Lines changed: 22 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,14 @@ import { MOBILE_UNSUPPORTED_SPLASH_SCREEN_DISMISSED_COOKIE_NAME, OPTIONAL_PROVID
1010
import { SyntaxReferenceGuide } from "./components/syntaxReferenceGuide";
1111
import { SyntaxGuideProvider } from "./components/syntaxGuideProvider";
1212
import { notFound, redirect } from "next/navigation";
13-
import { PendingApprovalCard } from "./components/pendingApproval";
14-
import { SubmitJoinRequest } from "./components/submitJoinRequest";
13+
import { PendingApprovalCard } from "../../features/membership/components/pendingApprovalCard";
14+
import { SubmitJoinRequestCard } from "../../features/membership/components/submitJoinRequestCard";
15+
import { NotProvisionedCard } from "@/features/membership/components/notProvisionedCard";
16+
import { isScimEnabled } from "@/features/scim/utils";
1517
import { env, getOfflineLicenseMetadata, SOURCEBOT_VERSION, isMemberApprovalRequired } from "@sourcebot/shared";
1618
import { hasEntitlement, isAnonymousAccessEnabled } from "@/lib/entitlements";
1719
import { GcpIapAuth } from "./components/gcpIapAuth";
18-
import { JoinOrganizationCard } from "@/app/components/joinOrganizationCard";
20+
import { JoinOrganizationCard } from "@/features/membership/components/joinOrganizationCard";
1921
import { LogoutEscapeHatch } from "@/app/components/logoutEscapeHatch";
2022
import { GitHubStarToast } from "./components/githubStarToast";
2123
import { getLinkedAccounts } from "@/ee/features/sso/actions";
@@ -76,26 +78,25 @@ export default async function Layout(props: LayoutProps) {
7678
// the join organization card to allow them to join the org if seat capacity is freed up. This card handles checking if the org has available seats.
7779
// 2. The org requires member approval, and they haven't been approved yet. In this case, we allow them to submit a request to join the org.
7880
if (!membership) {
81+
if (await isScimEnabled(org)) {
82+
return <NotProvisionedCard />;
83+
}
84+
7985
if (!isMemberApprovalRequired(org)) {
80-
return (
81-
<div className="min-h-screen flex items-center justify-center p-6">
82-
<LogoutEscapeHatch className="absolute top-0 right-0 p-6" />
83-
<JoinOrganizationCard />
84-
</div>
85-
)
86-
} else {
87-
const hasPendingApproval = await __unsafePrisma.accountRequest.findFirst({
88-
where: {
89-
orgId: org.id,
90-
requestedById: session.user.id
91-
}
92-
});
93-
94-
if (hasPendingApproval) {
95-
return <PendingApprovalCard />
96-
} else {
97-
return <SubmitJoinRequest />
86+
return <JoinOrganizationCard />;
87+
}
88+
89+
const hasPendingApproval = await __unsafePrisma.accountRequest.findFirst({
90+
where: {
91+
orgId: org.id,
92+
requestedById: session.user.id
9893
}
94+
});
95+
96+
if (hasPendingApproval) {
97+
return <PendingApprovalCard />
98+
} else {
99+
return <SubmitJoinRequestCard />
99100
}
100101
}
101102

packages/web/src/app/(app)/settings/components/settingsCard.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,15 +30,19 @@ interface BasicSettingsCardProps {
3030
description?: string;
3131
children: ReactNode;
3232
footer?: ReactNode;
33+
badge?: ReactNode;
3334
className?: string;
3435
}
3536

36-
export function BasicSettingsCard({ name, description, children, footer, className }: BasicSettingsCardProps) {
37+
export function BasicSettingsCard({ name, description, children, footer, badge, className }: BasicSettingsCardProps) {
3738
return (
3839
<SettingsCard className={className}>
3940
<div className="flex items-center justify-between gap-4">
4041
<div className="flex-1 min-w-0">
41-
<p className="font-medium text-sm">{name}</p>
42+
<div className="flex items-center gap-2">
43+
<p className="font-medium text-sm">{name}</p>
44+
{badge}
45+
</div>
4246
{description && (
4347
<p className="text-sm text-muted-foreground mt-1">{description}</p>
4448
)}

packages/web/src/app/(app)/settings/layout.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { redirect } from "next/navigation";
44
import { auth } from "@/auth";
55
import { isServiceError } from "@/lib/utils";
66
import { getConnectionStats } from "@/actions";
7-
import { getOrgAccountRequests } from "@/features/userManagement/actions";
7+
import { getOrgAccountRequests } from "@/features/membership/actions";
88
import { ServiceErrorException } from "@/lib/serviceError";
99
import { OrgRole } from "@prisma/client";
1010
import { env } from "@sourcebot/shared";

0 commit comments

Comments
 (0)