1+ import { and , eq , sql , inArray } from 'drizzle-orm'
2+ import { NextRequest , NextResponse } from 'next/server'
3+ import { getSession } from '@/lib/auth'
4+ import { db } from '@/db'
5+ import { workspace , workspaceMember , workspaceInvitation , user } from '@/db/schema'
6+ import { randomUUID } from 'crypto'
7+ import { Resend } from 'resend'
8+ import { WorkspaceInvitationEmail } from '@/components/emails/workspace-invitation'
9+ import { render } from '@react-email/render'
10+
11+ // Initialize Resend for email sending
12+ const resend = new Resend ( process . env . RESEND_API_KEY )
13+
14+ // GET /api/workspaces/invitations - Get all invitations for the user's workspaces
15+ export async function GET ( req : NextRequest ) {
16+ const session = await getSession ( )
17+
18+ if ( ! session ?. user ?. id ) {
19+ return NextResponse . json ( { error : 'Unauthorized' } , { status : 401 } )
20+ }
21+
22+ try {
23+ // First get all workspaces where the user is a member with owner role
24+ const userWorkspaces = await db
25+ . select ( { id : workspace . id } )
26+ . from ( workspace )
27+ . innerJoin (
28+ workspaceMember ,
29+ and (
30+ eq ( workspaceMember . workspaceId , workspace . id ) ,
31+ eq ( workspaceMember . userId , session . user . id ) ,
32+ eq ( workspaceMember . role , 'owner' )
33+ )
34+ )
35+
36+ if ( userWorkspaces . length === 0 ) {
37+ return NextResponse . json ( { invitations : [ ] } )
38+ }
39+
40+ // Get all workspaceIds where the user is an owner
41+ const workspaceIds = userWorkspaces . map ( w => w . id )
42+
43+ // Find all invitations for those workspaces
44+ const invitations = await db
45+ . select ( )
46+ . from ( workspaceInvitation )
47+ . where (
48+ inArray ( workspaceInvitation . workspaceId , workspaceIds )
49+ )
50+
51+ return NextResponse . json ( { invitations } )
52+ } catch ( error ) {
53+ console . error ( 'Error fetching workspace invitations:' , error )
54+ return NextResponse . json ( { error : 'Failed to fetch invitations' } , { status : 500 } )
55+ }
56+ }
57+
58+ // POST /api/workspaces/invitations - Create a new invitation
59+ export async function POST ( req : NextRequest ) {
60+ const session = await getSession ( )
61+
62+ if ( ! session ?. user ?. id ) {
63+ return NextResponse . json ( { error : 'Unauthorized' } , { status : 401 } )
64+ }
65+
66+ try {
67+ const { workspaceId, email, role = 'member' } = await req . json ( )
68+
69+ if ( ! workspaceId || ! email ) {
70+ return NextResponse . json ( { error : 'Workspace ID and email are required' } , { status : 400 } )
71+ }
72+
73+ // Check if user is authorized to invite to this workspace (must be owner)
74+ const membership = await db
75+ . select ( )
76+ . from ( workspaceMember )
77+ . where (
78+ and (
79+ eq ( workspaceMember . workspaceId , workspaceId ) ,
80+ eq ( workspaceMember . userId , session . user . id )
81+ )
82+ )
83+ . then ( rows => rows [ 0 ] )
84+
85+ if ( ! membership || membership . role !== 'owner' ) {
86+ return NextResponse . json ( { error : 'You are not authorized to invite to this workspace' } , { status : 403 } )
87+ }
88+
89+ // Get the workspace details for the email
90+ const workspaceDetails = await db
91+ . select ( )
92+ . from ( workspace )
93+ . where ( eq ( workspace . id , workspaceId ) )
94+ . then ( rows => rows [ 0 ] )
95+
96+ if ( ! workspaceDetails ) {
97+ return NextResponse . json ( { error : 'Workspace not found' } , { status : 404 } )
98+ }
99+
100+ // Check if the user is already a member
101+ // First find if a user with this email exists
102+ const existingUser = await db
103+ . select ( )
104+ . from ( user )
105+ . where ( eq ( user . email , email ) )
106+ . then ( rows => rows [ 0 ] )
107+
108+ if ( existingUser ) {
109+ // Check if the user is already a member of this workspace
110+ const existingMembership = await db
111+ . select ( )
112+ . from ( workspaceMember )
113+ . where (
114+ and (
115+ eq ( workspaceMember . workspaceId , workspaceId ) ,
116+ eq ( workspaceMember . userId , existingUser . id )
117+ )
118+ )
119+ . then ( rows => rows [ 0 ] )
120+
121+ if ( existingMembership ) {
122+ return NextResponse . json ( {
123+ error : `${ email } is already a member of this workspace` ,
124+ email
125+ } , { status : 400 } )
126+ }
127+ }
128+
129+ // Check if there's already a pending invitation
130+ const existingInvitation = await db
131+ . select ( )
132+ . from ( workspaceInvitation )
133+ . where (
134+ and (
135+ eq ( workspaceInvitation . workspaceId , workspaceId ) ,
136+ eq ( workspaceInvitation . email , email ) ,
137+ eq ( workspaceInvitation . status , 'pending' )
138+ )
139+ )
140+ . then ( rows => rows [ 0 ] )
141+
142+ if ( existingInvitation ) {
143+ return NextResponse . json ( {
144+ error : `${ email } has already been invited to this workspace` ,
145+ email
146+ } , { status : 400 } )
147+ }
148+
149+ // Generate a unique token and set expiry date (1 week from now)
150+ const token = randomUUID ( )
151+ const expiresAt = new Date ( )
152+ expiresAt . setDate ( expiresAt . getDate ( ) + 7 ) // 7 days expiry
153+
154+ // Create the invitation
155+ const invitation = await db
156+ . insert ( workspaceInvitation )
157+ . values ( {
158+ id : randomUUID ( ) ,
159+ workspaceId,
160+ email,
161+ inviterId : session . user . id ,
162+ role,
163+ status : 'pending' ,
164+ token,
165+ expiresAt,
166+ createdAt : new Date ( ) ,
167+ updatedAt : new Date ( ) ,
168+ } )
169+ . returning ( )
170+ . then ( rows => rows [ 0 ] )
171+
172+ // Send the invitation email
173+ await sendInvitationEmail ( {
174+ to : email ,
175+ inviterName : session . user . name || session . user . email || 'A user' ,
176+ workspaceName : workspaceDetails . name ,
177+ token : token ,
178+ } )
179+
180+ return NextResponse . json ( { success : true , invitation } )
181+ } catch ( error ) {
182+ console . error ( 'Error creating workspace invitation:' , error )
183+ return NextResponse . json ( { error : 'Failed to create invitation' } , { status : 500 } )
184+ }
185+ }
186+
187+ // Helper function to send invitation email using the Resend API
188+ async function sendInvitationEmail ( {
189+ to,
190+ inviterName,
191+ workspaceName,
192+ token
193+ } : {
194+ to : string ;
195+ inviterName : string ;
196+ workspaceName : string ;
197+ token : string ;
198+ } ) {
199+ try {
200+ const baseUrl = process . env . NEXT_PUBLIC_APP_URL || 'https://simstudio.ai'
201+ const invitationLink = `${ baseUrl } /api/workspaces/invitations/accept?token=${ token } `
202+
203+ const emailHtml = await render (
204+ WorkspaceInvitationEmail ( {
205+ workspaceName,
206+ inviterName,
207+ invitationLink,
208+ } )
209+ )
210+
211+ await resend . emails . send ( {
212+ from : process . env . RESEND_FROM_EMAIL || 'noreply@simstudio.ai' ,
213+ to,
214+ subject : `You've been invited to join "${ workspaceName } " on SimStudio` ,
215+ html : emailHtml ,
216+ } )
217+
218+ console . log ( `Invitation email sent to ${ to } ` )
219+ } catch ( error ) {
220+ console . error ( 'Error sending invitation email:' , error )
221+ // Continue even if email fails - the invitation is still created
222+ }
223+ }
0 commit comments