@@ -7,6 +7,9 @@ import TeamJoinRequest from "@/models/TeamJoinRequest";
77
88export const dynamic = 'force-dynamic' ;
99
10+ // Max team members
11+ const MAX_TEAM_MEMBERS = 2 ;
12+
1013export async function PUT (
1114 request : NextRequest ,
1215 { params } : { params : { requestId : string } }
@@ -86,13 +89,6 @@ export async function PUT(
8689 }
8790
8891 if ( action === 'accept' ) {
89- if ( team . memberCount >= 2 ) {
90- return NextResponse . json (
91- { message : "Team is full" } ,
92- { status : 409 }
93- ) ;
94- }
95-
9692 const requestingUser = await User . findOne ( { uid : joinRequest . userId } ) ;
9793 if ( ! requestingUser ) {
9894 return NextResponse . json (
@@ -101,29 +97,76 @@ export async function PUT(
10197 ) ;
10298 }
10399
104- if ( requestingUser . teamCode ) {
105- joinRequest . status = 'declined' ;
106- joinRequest . respondedAt = new Date ( ) ;
107- joinRequest . respondedBy = authResult . user . uid ;
108- await joinRequest . save ( ) ;
100+ const targetCode = team . teamCode ;
101+
102+ // Atomically seat the requesting user. The conditional update enforces the
103+ // size cap (a seat must be free), guards against re-adding the same member,
104+ // and respects the team status — all in one document write, so a racing
105+ // accept/join cannot oversize the team. The unique teamMembers.uid index is
106+ // the DB backstop that prevents seating a user who is already in another team.
107+ let updatedTeam ;
108+ try {
109+ const seatFilter : Record < string , any > = {
110+ teamCode : targetCode ,
111+ teamStatus : { $nin : [ 'submitted' , 'shortlisted' , 'rsvped' ] } ,
112+ "teamMembers.uid" : { $ne : joinRequest . userId } ,
113+ } ;
114+ seatFilter [ `teamMembers.${ MAX_TEAM_MEMBERS - 1 } ` ] = { $exists : false } ;
115+
116+ updatedTeam = await Team . findOneAndUpdate (
117+ seatFilter ,
118+ {
119+ $push : {
120+ teamMembers : {
121+ uid : joinRequest . userId ,
122+ joinedAt : new Date ( ) ,
123+ role : 'Member' ,
124+ } ,
125+ } ,
126+ $inc : { memberCount : 1 } ,
127+ } ,
128+ { new : true }
129+ ) ;
130+ } catch ( err : any ) {
131+ // Duplicate key on teamMembers.uid => user already belongs to another team.
132+ if ( err ?. code === 11000 ) {
133+ joinRequest . status = 'declined' ;
134+ joinRequest . respondedAt = new Date ( ) ;
135+ joinRequest . respondedBy = authResult . user . uid ;
136+ await joinRequest . save ( ) ;
137+
138+ return NextResponse . json (
139+ { message : "User is already in another team. Request/Invite declined." } ,
140+ { status : 409 }
141+ ) ;
142+ }
143+ throw err ;
144+ }
109145
146+ if ( ! updatedTeam ) {
147+ // No seat taken — distinguish "already a member here" from "team full".
148+ const current = await Team . findOne ( { teamCode : targetCode } ) ;
149+ if ( current ?. teamMembers ?. some ( ( m : any ) => m . uid === joinRequest . userId ) ) {
150+ joinRequest . status = 'declined' ;
151+ joinRequest . respondedAt = new Date ( ) ;
152+ joinRequest . respondedBy = authResult . user . uid ;
153+ await joinRequest . save ( ) ;
154+
155+ return NextResponse . json (
156+ { message : "User is already in this team. Request/Invite declined." } ,
157+ { status : 409 }
158+ ) ;
159+ }
110160 return NextResponse . json (
111- { message : "User is already in another team. Request/Invite declined. " } ,
161+ { message : "Team is full " } ,
112162 { status : 409 }
113163 ) ;
114164 }
115165
116- team . teamMembers . push ( {
117- uid : joinRequest . userId ,
118- joinedAt : new Date ( ) ,
119- role : 'Member' ,
120- } ) ;
121-
122- await team . save ( ) ;
123-
166+ // Keep the user's cached teamCode in sync (the team array is the authority).
124167 await User . findOneAndUpdate (
125168 { uid : joinRequest . userId } ,
126- { teamCode : team . teamCode , isLooking : false }
169+ { teamCode : targetCode , isLooking : false }
127170 ) ;
128171
129172 joinRequest . status = 'accepted' ;
@@ -148,8 +191,8 @@ export async function PUT(
148191 message : isInvite ? "Invitation accepted. You have joined the team." : "Join request accepted. User added to team." ,
149192 data : {
150193 requestId : joinRequest . _id . toString ( ) ,
151- teamCode : team . teamCode ,
152- teamName : team . teamName ,
194+ teamCode : updatedTeam . teamCode ,
195+ teamName : updatedTeam . teamName ,
153196 status : 'accepted' ,
154197 } ,
155198 } ) ;
0 commit comments