@@ -18,14 +18,24 @@ import {
1818 getProjectForUser ,
1919 createProject ,
2020 updateProject ,
21+ addTeamMember ,
22+ updateTeamMemberRole ,
23+ removeTeamMember ,
24+ deleteProject ,
2125} from "../services/db/projects" ;
26+ import {
27+ sendProjectInvitation ,
28+ acceptProjectInvitation ,
29+ declineProjectInvitation ,
30+ } from "../services/db/project-invitations" ;
2231import {
2332 getDashboardStats ,
2433 getZones ,
2534 getProjectCentroidGeoJSON ,
2635 getLocationsForProject ,
2736 getProjectTeamMembers ,
2837} from "../services/db/dashboard" ;
38+ import { getOrganizationMembers } from "../services/db/organizations" ;
2939import { ApiError } from "../types" ;
3040import { projectInsertSchema } from "@common/db/schema/project" ;
3141
@@ -55,6 +65,21 @@ const getProjectParams = z.object({
5565 projectId : z . string ( ) ,
5666} ) ;
5767
68+ const addTeamMemberBodySchema = z . object ( {
69+ userId : z . string ( ) ,
70+ role : z . enum ( [ "ADMIN" , "CONTRIBUTOR" , "VIEWER" ] ) ,
71+ } ) ;
72+
73+ const updateTeamMemberRoleBodySchema = z . object ( {
74+ role : z . enum ( [ "ADMIN" , "CONTRIBUTOR" , "VIEWER" ] ) ,
75+ } ) ;
76+
77+ const sendProjectInvitationBodySchema = z . object ( {
78+ email : z . string ( ) . email ( "Invalid email address" ) ,
79+ role : z . enum ( [ "ADMIN" , "CONTRIBUTOR" , "VIEWER" ] ) ,
80+ message : z . string ( ) . optional ( ) ,
81+ } ) ;
82+
5883// Create a new project
5984router . post (
6085 "/" ,
@@ -212,25 +237,172 @@ router.get(
212237 } )
213238) ;
214239
215- // Get project team members
240+ // Get project team access (both direct and organization access)
216241router . get (
217- "/:projectId/dashboard/ team" ,
242+ "/:projectId/team-access " ,
218243 extractOrganizationContext ,
219244 requireProjectView ( ) ,
220245 validateParams ( getProjectParams ) ,
221246 asyncHandler ( async ( req , res ) => {
222247 const { projectId } = req . params as { projectId : string } ;
223- const page = parseInt ( req . query . page as string ) || 1 ;
224- const pageSize = parseInt ( req . query . pageSize as string ) || 100 ;
248+ const organizationId = req . organizationId ! ;
225249 const search = req . query . search as string ;
250+ const page = parseInt ( req . query . page as string ) || 1 ;
251+ const pageSize = parseInt ( req . query . pageSize as string ) || 10 ;
226252
227- const team = await getProjectTeamMembers ( projectId , {
253+ // Get direct project team members
254+ const directTeam = await getProjectTeamMembers ( projectId , {
228255 search,
229256 page,
230257 pageSize,
231258 } ) ;
232259
233- sendSuccess ( res , team , "Project team members retrieved successfully" ) ;
260+ // Get organization members (excluding direct project members)
261+ const directMemberUserIds = directTeam . users . map (
262+ ( member ) => member . user . id
263+ ) ;
264+ const organizationMembers = await getOrganizationMembers ( organizationId , {
265+ search,
266+ page,
267+ pageSize,
268+ excludeProjectMembers : directMemberUserIds ,
269+ } ) ;
270+
271+ const result = {
272+ directAccess : directTeam ,
273+ organizationAccess : organizationMembers ,
274+ } ;
275+
276+ sendSuccess ( res , result , "Project team access retrieved successfully" ) ;
277+ } )
278+ ) ;
279+
280+ // Add team member to project
281+ router . post (
282+ "/:projectId/team-members" ,
283+ extractOrganizationContext ,
284+ requireProjectManage ( ) ,
285+ validateParams ( getProjectParams ) ,
286+ validateBody ( addTeamMemberBodySchema ) ,
287+ asyncHandler ( async ( req , res ) => {
288+ const { projectId } = req . params as { projectId : string } ;
289+ const { userId, role } = req . body as z . infer <
290+ typeof addTeamMemberBodySchema
291+ > ;
292+
293+ await addTeamMember ( projectId , userId , role ) ;
294+
295+ sendSuccess ( res , null , "Team member added successfully" ) ;
296+ } )
297+ ) ;
298+
299+ // Update team member role
300+ router . put (
301+ "/:projectId/team-members/:userId" ,
302+ extractOrganizationContext ,
303+ requireProjectManage ( ) ,
304+ validateParams ( getProjectParams . extend ( { userId : z . string ( ) } ) ) ,
305+ validateBody ( updateTeamMemberRoleBodySchema ) ,
306+ asyncHandler ( async ( req , res ) => {
307+ const { projectId, userId } = req . params as {
308+ projectId : string ;
309+ userId : string ;
310+ } ;
311+ const { role } = req . body as z . infer < typeof updateTeamMemberRoleBodySchema > ;
312+
313+ await updateTeamMemberRole ( projectId , userId , role ) ;
314+
315+ sendSuccess ( res , null , "Team member role updated successfully" ) ;
316+ } )
317+ ) ;
318+
319+ // Remove team member from project
320+ router . delete (
321+ "/:projectId/team-members/:userId" ,
322+ extractOrganizationContext ,
323+ requireProjectManage ( ) ,
324+ validateParams ( getProjectParams . extend ( { userId : z . string ( ) } ) ) ,
325+ asyncHandler ( async ( req , res ) => {
326+ const { projectId, userId } = req . params as {
327+ projectId : string ;
328+ userId : string ;
329+ } ;
330+
331+ await removeTeamMember ( projectId , userId ) ;
332+
333+ sendSuccess ( res , null , "Team member removed successfully" ) ;
334+ } )
335+ ) ;
336+
337+ // Delete project
338+ router . delete (
339+ "/:projectId" ,
340+ extractOrganizationContext ,
341+ requireProjectManage ( ) ,
342+ validateParams ( getProjectParams ) ,
343+ asyncHandler ( async ( req , res ) => {
344+ const { projectId } = req . params as { projectId : string } ;
345+
346+ await deleteProject ( projectId ) ;
347+
348+ sendSuccess ( res , null , "Project deleted successfully" ) ;
349+ } )
350+ ) ;
351+
352+ // Send project invitation
353+ router . post (
354+ "/:projectId/invitations" ,
355+ extractOrganizationContext ,
356+ requireProjectManage ( ) ,
357+ validateParams ( getProjectParams ) ,
358+ validateBody ( sendProjectInvitationBodySchema ) ,
359+ asyncHandler ( async ( req , res ) => {
360+ const { projectId } = req . params as { projectId : string } ;
361+ const userId = req . user ! . id ;
362+ const { email, role, message } = req . body as z . infer <
363+ typeof sendProjectInvitationBodySchema
364+ > ;
365+
366+ const invitation = await sendProjectInvitation (
367+ projectId ,
368+ userId ,
369+ email ,
370+ role ,
371+ message
372+ ) ;
373+
374+ sendSuccess ( res , invitation , "Project invitation sent successfully" , 201 ) ;
375+ } )
376+ ) ;
377+
378+ // Accept project invitation
379+ router . post (
380+ "/invitations/:invitationId/accept" ,
381+ authenticateUser ,
382+ mapAuthToUser ,
383+ validateParams ( z . object ( { invitationId : z . string ( ) } ) ) ,
384+ asyncHandler ( async ( req , res ) => {
385+ const { invitationId } = req . params as { invitationId : string } ;
386+ const userId = req . user ! . id ;
387+
388+ await acceptProjectInvitation ( invitationId , userId ) ;
389+
390+ sendSuccess ( res , null , "Project invitation accepted successfully" ) ;
391+ } )
392+ ) ;
393+
394+ // Decline project invitation
395+ router . post (
396+ "/invitations/:invitationId/decline" ,
397+ authenticateUser ,
398+ mapAuthToUser ,
399+ validateParams ( z . object ( { invitationId : z . string ( ) } ) ) ,
400+ asyncHandler ( async ( req , res ) => {
401+ const { invitationId } = req . params as { invitationId : string } ;
402+
403+ await declineProjectInvitation ( invitationId ) ;
404+
405+ sendSuccess ( res , null , "Project invitation declined successfully" ) ;
234406 } )
235407) ;
236408
0 commit comments