Skip to content

Commit cf2a402

Browse files
wip
1 parent 727ee71 commit cf2a402

File tree

6 files changed

+172
-170
lines changed

6 files changed

+172
-170
lines changed

packages/web/src/actions.ts

Lines changed: 1 addition & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import JoinRequestApprovedEmail from "./emails/joinRequestApprovedEmail";
2424
import JoinRequestSubmittedEmail from "./emails/joinRequestSubmittedEmail";
2525
import { AGENTIC_SEARCH_TUTORIAL_DISMISSED_COOKIE_NAME, MOBILE_UNSUPPORTED_SPLASH_SCREEN_DISMISSED_COOKIE_NAME, SINGLE_TENANT_ORG_ID, SOURCEBOT_SUPPORT_EMAIL } from "./lib/constants";
2626
import { RepositoryQuery } from "./lib/types";
27-
import { withAuth, withOptionalAuth, withAuth_skipOrgMembershipCheck } from "./middleware/withAuth";
27+
import { withAuth, withOptionalAuth } from "./middleware/withAuth";
2828
import { withMinimumOrgRole } from "./middleware/withMinimumOrgRole";
2929
import { getBrowsePath } from "./app/(app)/browse/hooks/utils";
3030
import { sew } from "@/middleware/sew";
@@ -758,116 +758,6 @@ export const getMe = async () => sew(() =>
758758
}
759759
}));
760760

761-
export const redeemInvite = async (inviteId: string): Promise<{ success: boolean } | ServiceError> => sew(() =>
762-
withAuth_skipOrgMembershipCheck(async ({ user, prisma }) => {
763-
const invite = await prisma.invite.findUnique({
764-
where: {
765-
id: inviteId,
766-
},
767-
include: {
768-
org: true,
769-
}
770-
});
771-
772-
if (!invite) {
773-
return notFound();
774-
}
775-
776-
const failAuditCallback = async (error: string) => {
777-
await auditService.createAudit({
778-
action: "user.invite_accept_failed",
779-
actor: {
780-
id: user.id,
781-
type: "user"
782-
},
783-
target: {
784-
id: inviteId,
785-
type: "invite"
786-
},
787-
orgId: invite.org.id,
788-
metadata: {
789-
message: error
790-
}
791-
});
792-
}
793-
794-
795-
const hasAvailability = await orgHasAvailability();
796-
if (!hasAvailability) {
797-
await failAuditCallback("Organization is at max capacity");
798-
return {
799-
statusCode: StatusCodes.BAD_REQUEST,
800-
errorCode: ErrorCode.ORG_SEAT_COUNT_REACHED,
801-
message: "Organization is at max capacity",
802-
} satisfies ServiceError;
803-
}
804-
805-
// Check if the user is the recipient of the invite
806-
if (user.email !== invite.recipientEmail) {
807-
await failAuditCallback("User is not the recipient of the invite");
808-
return notFound();
809-
}
810-
811-
const addUserToOrgRes = await addUserToOrganization(user.id, invite.orgId);
812-
if (isServiceError(addUserToOrgRes)) {
813-
await failAuditCallback(addUserToOrgRes.message);
814-
return addUserToOrgRes;
815-
}
816-
817-
await auditService.createAudit({
818-
action: "user.invite_accepted",
819-
actor: {
820-
id: user.id,
821-
type: "user"
822-
},
823-
orgId: invite.org.id,
824-
target: {
825-
id: inviteId,
826-
type: "invite"
827-
}
828-
});
829-
830-
return {
831-
success: true,
832-
}
833-
}));
834-
835-
export const getInviteInfo = async (inviteId: string) => sew(() =>
836-
withAuth_skipOrgMembershipCheck(async ({ user, prisma }) => {
837-
const invite = await prisma.invite.findUnique({
838-
where: {
839-
id: inviteId,
840-
},
841-
include: {
842-
org: true,
843-
host: true,
844-
}
845-
});
846-
847-
if (!invite) {
848-
return notFound();
849-
}
850-
851-
if (invite.recipientEmail !== user.email) {
852-
return notFound();
853-
}
854-
855-
return {
856-
id: invite.id,
857-
orgName: invite.org.name,
858-
orgImageUrl: invite.org.imageUrl ?? undefined,
859-
host: {
860-
name: invite.host.name ?? undefined,
861-
email: invite.host.email!,
862-
avatarUrl: invite.host.image ?? undefined,
863-
},
864-
recipient: {
865-
name: user.name ?? undefined,
866-
email: user.email!,
867-
}
868-
}
869-
}));
870-
871761
export const getOrgMembers = async () => sew(() =>
872762
withAuth(async ({ org, prisma }) => {
873763
const members = await prisma.userToOrg.findMany({

packages/web/src/app/components/joinOrganizationButton.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import { useState } from "react";
77
import { Loader2 } from "lucide-react";
88
import { joinOrganization } from "../invite/actions";
99
import { isServiceError } from "@/lib/utils";
10-
import { SINGLE_TENANT_ORG_ID } from "@/lib/constants";
1110

1211
export function JoinOrganizationButton({ inviteLinkId }: { inviteLinkId?: string }) {
1312
const [isLoading, setIsLoading] = useState(false);
@@ -18,7 +17,7 @@ export function JoinOrganizationButton({ inviteLinkId }: { inviteLinkId?: string
1817
setIsLoading(true);
1918

2019
try {
21-
const result = await joinOrganization(SINGLE_TENANT_ORG_ID, inviteLinkId);
20+
const result = await joinOrganization(inviteLinkId);
2221

2322
if (isServiceError(result)) {
2423
toast({
Lines changed: 168 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,185 @@
11
"use server";
22

33
import { isServiceError } from "@/lib/utils";
4-
import { orgNotFound, ServiceError } from "@/lib/serviceError";
4+
import { notAuthenticated, notFound, orgNotFound, ServiceError } from "@/lib/serviceError";
55
import { sew } from "@/middleware/sew";
6-
import { addUserToOrganization } from "@/lib/authUtils";
7-
import { withAuth_skipOrgMembershipCheck } from "@/middleware/withAuth";
6+
import { addUserToOrganization, orgHasAvailability } from "@/lib/authUtils";
87
import { StatusCodes } from "http-status-codes";
98
import { ErrorCode } from "@/lib/errorCodes";
9+
import { getAuthenticatedUser } from "@/middleware/withAuth";
10+
import { __unsafePrisma } from "@/prisma";
11+
import { SINGLE_TENANT_ORG_ID } from "@/lib/constants";
12+
import { getAuditService } from "@/ee/features/audit/factory";
1013

11-
export const joinOrganization = async (orgId: number, inviteLinkId?: string) => sew(async () =>
12-
withAuth_skipOrgMembershipCheck(async ({ user, prisma }) => {
13-
const org = await prisma.org.findUnique({
14-
where: {
15-
id: orgId,
16-
},
17-
});
14+
const auditService = getAuditService();
15+
16+
export const joinOrganization = async (inviteLinkId?: string) => sew(async () => {
17+
const authResult = await getAuthenticatedUser();
18+
if (!authResult) {
19+
return notAuthenticated();
20+
}
21+
22+
const { user } = authResult;
1823

19-
if (!org) {
20-
return orgNotFound();
24+
const org = await __unsafePrisma.org.findUnique({
25+
where: {
26+
id: SINGLE_TENANT_ORG_ID,
27+
},
28+
});
29+
30+
if (!org) {
31+
return orgNotFound();
32+
}
33+
34+
35+
// If member approval is required we must be using a valid invite link
36+
if (org.memberApprovalRequired) {
37+
if (!org.inviteLinkEnabled) {
38+
return {
39+
statusCode: StatusCodes.BAD_REQUEST,
40+
errorCode: ErrorCode.INVITE_LINK_NOT_ENABLED,
41+
message: "Invite link is not enabled.",
42+
} satisfies ServiceError;
2143
}
2244

23-
// If member approval is required we must be using a valid invite link
24-
if (org.memberApprovalRequired) {
25-
if (!org.inviteLinkEnabled) {
26-
return {
27-
statusCode: StatusCodes.BAD_REQUEST,
28-
errorCode: ErrorCode.INVITE_LINK_NOT_ENABLED,
29-
message: "Invite link is not enabled.",
30-
} satisfies ServiceError;
31-
}
45+
if (org.inviteLinkId !== inviteLinkId) {
46+
return {
47+
statusCode: StatusCodes.BAD_REQUEST,
48+
errorCode: ErrorCode.INVALID_INVITE_LINK,
49+
message: "Invalid invite link.",
50+
} satisfies ServiceError;
51+
}
52+
}
53+
54+
const addUserToOrgRes = await addUserToOrganization(user.id, org.id);
55+
if (isServiceError(addUserToOrgRes)) {
56+
return addUserToOrgRes;
57+
}
58+
59+
return {
60+
success: true,
61+
}
62+
});
63+
64+
export const redeemInvite = async (inviteId: string): Promise<{ success: boolean; } | ServiceError> => sew(async () => {
65+
const authResult = await getAuthenticatedUser();
66+
if (!authResult) {
67+
return notAuthenticated();
68+
}
69+
70+
const { user } = authResult;
71+
72+
const invite = await __unsafePrisma.invite.findUnique({
73+
where: {
74+
id: inviteId,
75+
},
76+
include: {
77+
org: true,
78+
}
79+
});
80+
81+
if (!invite) {
82+
return notFound();
83+
}
3284

33-
if (org.inviteLinkId !== inviteLinkId) {
34-
return {
35-
statusCode: StatusCodes.BAD_REQUEST,
36-
errorCode: ErrorCode.INVALID_INVITE_LINK,
37-
message: "Invalid invite link.",
38-
} satisfies ServiceError;
85+
const failAuditCallback = async (error: string) => {
86+
await auditService.createAudit({
87+
action: "user.invite_accept_failed",
88+
actor: {
89+
id: user.id,
90+
type: "user"
91+
},
92+
target: {
93+
id: inviteId,
94+
type: "invite"
95+
},
96+
orgId: invite.org.id,
97+
metadata: {
98+
message: error
3999
}
100+
});
101+
};
102+
103+
const hasAvailability = await orgHasAvailability();
104+
if (!hasAvailability) {
105+
await failAuditCallback("Organization is at max capacity");
106+
return {
107+
statusCode: StatusCodes.BAD_REQUEST,
108+
errorCode: ErrorCode.ORG_SEAT_COUNT_REACHED,
109+
message: "Organization is at max capacity",
110+
} satisfies ServiceError;
111+
}
112+
113+
// Check if the user is the recipient of the invite
114+
if (user.email !== invite.recipientEmail) {
115+
await failAuditCallback("User is not the recipient of the invite");
116+
return notFound();
117+
}
118+
119+
const addUserToOrgRes = await addUserToOrganization(user.id, invite.orgId);
120+
if (isServiceError(addUserToOrgRes)) {
121+
await failAuditCallback(addUserToOrgRes.message);
122+
return addUserToOrgRes;
123+
}
124+
125+
await auditService.createAudit({
126+
action: "user.invite_accepted",
127+
actor: {
128+
id: user.id,
129+
type: "user"
130+
},
131+
orgId: invite.org.id,
132+
target: {
133+
id: inviteId,
134+
type: "invite"
40135
}
136+
});
41137

42-
const addUserToOrgRes = await addUserToOrganization(user.id, org.id);
43-
if (isServiceError(addUserToOrgRes)) {
44-
return addUserToOrgRes;
138+
return {
139+
success: true,
140+
};
141+
});
142+
143+
144+
export const getInviteInfo = async (inviteId: string) => sew(async () => {
145+
const authResult = await getAuthenticatedUser();
146+
if (!authResult) {
147+
return notAuthenticated();
148+
}
149+
150+
const { user } = authResult;
151+
152+
const invite = await __unsafePrisma.invite.findUnique({
153+
where: {
154+
id: inviteId,
155+
},
156+
include: {
157+
org: true,
158+
host: true,
45159
}
160+
});
46161

47-
return {
48-
success: true,
162+
if (!invite) {
163+
return notFound();
164+
}
165+
166+
if (invite.recipientEmail !== user.email) {
167+
return notFound();
168+
}
169+
170+
return {
171+
id: invite.id,
172+
orgName: invite.org.name,
173+
orgImageUrl: invite.org.imageUrl ?? undefined,
174+
host: {
175+
name: invite.host.name ?? undefined,
176+
email: invite.host.email!,
177+
avatarUrl: invite.host.image ?? undefined,
178+
},
179+
recipient: {
180+
name: user.name ?? undefined,
181+
email: user.email!,
49182
}
50-
})
51-
)
183+
};
184+
});
185+

packages/web/src/app/redeem/components/acceptInviteCard.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import placeholderAvatar from "@/public/placeholder_avatar.png";
99
import { ArrowRight, Loader2 } from "lucide-react";
1010
import { Button } from "@/components/ui/button";
1111
import { useCallback, useState } from "react";
12-
import { redeemInvite } from "@/actions";
12+
import { redeemInvite } from "@/app/invite/actions";
1313
import { useRouter } from "next/navigation";
1414
import { useToast } from "@/components/hooks/use-toast";
1515
import { isServiceError } from "@/lib/utils";

packages/web/src/app/redeem/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { notFound, redirect } from 'next/navigation';
22
import { auth } from "@/auth";
3-
import { getInviteInfo } from "@/actions";
3+
import { getInviteInfo } from "../invite/actions";
44
import { isServiceError } from "@/lib/utils";
55
import { AcceptInviteCard } from './components/acceptInviteCard';
66
import { LogoutEscapeHatch } from '../components/logoutEscapeHatch';

0 commit comments

Comments
 (0)