11"use server" ;
22
33import { isServiceError } from "@/lib/utils" ;
4- import { orgNotFound , ServiceError } from "@/lib/serviceError" ;
4+ import { notAuthenticated , notFound , orgNotFound , ServiceError } from "@/lib/serviceError" ;
55import { sew } from "@/middleware/sew" ;
6- import { addUserToOrganization } from "@/lib/authUtils" ;
7- import { withAuth_skipOrgMembershipCheck } from "@/middleware/withAuth" ;
6+ import { addUserToOrganization , orgHasAvailability } from "@/lib/authUtils" ;
87import { StatusCodes } from "http-status-codes" ;
98import { 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+
0 commit comments