Skip to content

Commit 437768d

Browse files
committed
refactor project team
1 parent e15ef4e commit 437768d

30 files changed

+1976
-824
lines changed

packages/api/src/authz/service.ts

Lines changed: 0 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -372,46 +372,6 @@ export class AuthorizationService {
372372
return permissions.includes(action);
373373
}
374374

375-
/**
376-
* Sync user organization role
377-
*/
378-
async syncUserOrganizationRole(
379-
userId: string,
380-
organizationId: string,
381-
role: string
382-
): Promise<void> {
383-
try {
384-
// This would typically update the user's role in the database
385-
// For now, we'll just log it
386-
console.log(
387-
`Syncing user ${userId} role ${role} for organization ${organizationId}`
388-
);
389-
} catch (error) {
390-
console.error("Error syncing user organization role:", error);
391-
throw new ApiError("Failed to sync user role", 500);
392-
}
393-
}
394-
395-
/**
396-
* Sync user project role
397-
*/
398-
async syncUserProjectRole(
399-
userId: string,
400-
projectId: string,
401-
role: string
402-
): Promise<void> {
403-
try {
404-
// This would typically update the user's role in the database
405-
// For now, we'll just log it
406-
console.log(
407-
`Syncing user ${userId} role ${role} for project ${projectId}`
408-
);
409-
} catch (error) {
410-
console.error("Error syncing user project role:", error);
411-
throw new ApiError("Failed to sync user role", 500);
412-
}
413-
}
414-
415375
/**
416376
* Remove user roles
417377
*/

packages/api/src/config/index.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,22 @@
1-
import dotenv from 'dotenv';
1+
import dotenv from "dotenv";
22

33
dotenv.config();
44

55
export const config = {
6-
port: parseInt(process.env.PORT || '3001', 10),
7-
nodeEnv: process.env.NODE_ENV || 'development',
6+
port: parseInt(process.env.PORT || "3001", 10),
7+
nodeEnv: process.env.NODE_ENV || "development",
88
database: {
9-
url: process.env.DATABASE_URL || '',
9+
url: process.env.DATABASE_URL || "",
1010
},
1111
clerk: {
12-
secretKey: process.env.CLERK_SECRET_KEY || '',
12+
secretKey: process.env.CLERK_SECRET_KEY || "",
1313
},
1414
cors: {
15-
origin: process.env.CORS_ORIGIN || 'http://localhost:3000',
15+
origin: process.env.CORS_ORIGIN || "http://localhost:3000",
1616
},
1717
rateLimit: {
18-
windowMs: parseInt(process.env.RATE_LIMIT_WINDOW_MS || '900000', 10), // 15 minutes
19-
maxRequests: parseInt(process.env.RATE_LIMIT_MAX_REQUESTS || '100', 10),
18+
windowMs: parseInt(process.env.RATE_LIMIT_WINDOW_MS || "900000", 10), // 15 minutes
19+
maxRequests: parseInt(process.env.RATE_LIMIT_MAX_REQUESTS || "1000", 10),
2020
},
2121
} as const;
2222

packages/api/src/routes/organizations.ts

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -129,9 +129,20 @@ router.get(
129129
validateParams(getOrganizationParams),
130130
asyncHandler(async (req, res) => {
131131
const { organizationId } = req.params as { organizationId: string };
132-
133-
const members = await getOrganizationMembers(organizationId);
134-
sendSuccess(res, members, "Organization members retrieved successfully");
132+
const search = req.query.search as string;
133+
const page = parseInt(req.query.page as string) || 1;
134+
const pageSize = parseInt(req.query.pageSize as string) || 100;
135+
const excludeProjectMembers = req.query.excludeProjectMembers as string;
136+
137+
const result = await getOrganizationMembers(organizationId, {
138+
search,
139+
page,
140+
pageSize,
141+
...(excludeProjectMembers && {
142+
excludeProjectMembers: excludeProjectMembers.split(","),
143+
}),
144+
});
145+
sendSuccess(res, result, "Organization members retrieved successfully");
135146
})
136147
);
137148

packages/api/src/routes/projects.ts

Lines changed: 178 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -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";
2231
import {
2332
getDashboardStats,
2433
getZones,
2534
getProjectCentroidGeoJSON,
2635
getLocationsForProject,
2736
getProjectTeamMembers,
2837
} from "../services/db/dashboard";
38+
import { getOrganizationMembers } from "../services/db/organizations";
2939
import { ApiError } from "../types";
3040
import { 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
5984
router.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)
216241
router.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

Comments
 (0)