MCPcopy Create free account
hub / github.com/adcontextprotocol/adcp / canAddSeat

Function canAddSeat

server/src/db/organization-db.ts:422–484  ·  view source on GitHub ↗
(
  orgId: string,
  seatType: SeatType
)

Source from the content-addressed store, hash-verified

420 * and counts pending invitations as reserved seats.
421 */
422export async function canAddSeat(
423 orgId: string,
424 seatType: SeatType
425): Promise<{ allowed: boolean; reason?: string }> {
426 const pool = getPool();
427 const client = await pool.connect();
428 try {
429 await client.query('BEGIN');
430
431 // Lock the org row to serialize concurrent seat checks and resolve
432 // the tier from the same (locked) read.
433 const tier = await readMembershipTierFromClient(client, orgId, { forUpdate: true });
434 const limits = getSeatLimits(tier);
435
436 // Count active members (contributor = admin-assigned OR Slack-mapped OR in working group)
437 const memberResult = await client.query<{ total: string; contributor: string }>(
438 `SELECT
439 COUNT(*) as total,
440 COUNT(*) FILTER (WHERE
441 om.seat_type = 'contributor'
442 OR EXISTS (SELECT 1 FROM slack_user_mappings sm WHERE sm.workos_user_id = om.workos_user_id AND sm.mapping_status = 'mapped')
443 OR EXISTS (SELECT 1 FROM working_group_memberships wgm WHERE wgm.workos_user_id = om.workos_user_id AND wgm.status = 'active')
444 ) as contributor
445 FROM organization_memberships om
446 WHERE om.workos_organization_id = $1`,
447 [orgId]
448 );
449 const total = parseInt(memberResult.rows[0]?.total ?? '0', 10);
450 const contributors = parseInt(memberResult.rows[0]?.contributor ?? '0', 10);
451
452 // Pending invitations also reserve seats
453 const pendingResult = await client.query<{ seat_type: string; count: string }>(
454 `SELECT seat_type, COUNT(*) as count FROM invitation_seat_types WHERE workos_organization_id = $1 GROUP BY seat_type`,
455 [orgId]
456 );
457 let pendingContributors = 0;
458 let pendingCommunity = 0;
459 for (const row of pendingResult.rows) {
460 if (row.seat_type === 'contributor') pendingContributors = parseInt(row.count, 10);
461 if (row.seat_type === 'community_only') pendingCommunity = parseInt(row.count, 10);
462 }
463
464 const usage = {
465 contributor: contributors + pendingContributors,
466 community_only: (total - contributors) + pendingCommunity,
467 };
468
469 await client.query('COMMIT');
470
471 const limit = seatType === 'contributor' ? limits.contributor : limits.community;
472 const used = seatType === 'contributor' ? usage.contributor : usage.community_only;
473
474 if (limit === -1) return { allowed: true };
475 if (limit === 0) return { allowed: false, reason: `Your membership tier does not include ${seatType === 'contributor' ? 'contributor' : 'community'} seats. Upgrade at /membership.` };
476 if (used >= limit) return { allowed: false, reason: `All ${limit} ${seatType === 'contributor' ? 'contributor' : 'community'} seats are in use. Upgrade at /membership to add more.` };
477 return { allowed: true };
478 } catch (error) {
479 await client.query('ROLLBACK');

Callers 2

Calls 4

getPoolFunction · 0.85
getSeatLimitsFunction · 0.85
queryMethod · 0.80

Tested by

no test coverage detected