-
Couldn't load subscription status.
- Fork 0
Seperate Exec membership from Org admin #343
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
a1bb10c
8c268b8
2b4672f
0fa1fa8
196f234
cc72e32
1fe80d0
0a12700
2022c0a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,9 +1,5 @@ | ||
| import { FastifyError, FastifyPluginAsync } from "fastify"; | ||
| import { | ||
| AllOrganizationNameList, | ||
| getOrgByName, | ||
| Organizations, | ||
| } from "@acm-uiuc/js-shared"; | ||
| import { FastifyPluginAsync } from "fastify"; | ||
| import { AllOrganizationNameList, getOrgByName } from "@acm-uiuc/js-shared"; | ||
| import rateLimiter from "api/plugins/rateLimiter.js"; | ||
| import { withRoles, withTags } from "api/components/index.js"; | ||
| import { z } from "zod/v4"; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chainVerify Zod v4 compatibility across the codebase. The import has been updated to use
🏁 Script executed: #!/bin/bash
# Description: Search for Zod patterns that may be affected by v4 breaking changes
echo "=== Checking for .default() usage with transforms ==="
rg -n "\.transform\(" --type=ts -A 5 | rg "\.default\("
echo -e "\n=== Checking for z.record() without explicit schemas ==="
rg -n "z\.record\([^,)]*\)" --type=ts
echo -e "\n=== Checking for z.function() usage ==="
rg -n "z\.function\(" --type=ts
echo -e "\n=== Checking for number validation with potential infinity ==="
rg -n "z\.number\(\)" --type=ts -A 2 | rg -i "infinite|infinity"Length of output: 309 🏁 Script executed: #!/bin/bash
# Get the full line from events.ts for the z.record() call
echo "=== Full z.record() line from events.ts ==="
sed -n '8p' src/common/types/events.ts
echo -e "\n=== Verify z.record() context (lines 5-12) ==="
sed -n '5,12p' src/common/types/events.ts
echo -e "\n=== Check all Zod version imports in the codebase ==="
rg -n "from\s+[\"']zod" --type=ts
echo -e "\n=== Check for .merge() chains or other schema composition ==="
rg -n "\.merge\(" --type=ts | head -20
echo -e "\n=== Check for .pick() or .omit() usage ==="
rg -n "\.pick\(|\.omit\(" --type=ts | head -20Length of output: 3811 🏁 Script executed: #!/bin/bash
# Check if api/functions/organizations.ts is related to the reviewed file
echo "=== Check what's imported from api/functions/organizations.ts ==="
rg -n "from.*api/functions/organizations" --type=ts
echo -e "\n=== Check what api/functions/organizations.ts exports ==="
head -50 src/api/functions/organizations.ts | grep -E "export|^import"
echo -e "\n=== Check if api/functions/organizations.ts was modified in this PR ==="
rg -n "import.*organizations" src/api/routes/organizations.ts
echo -e "\n=== Verify Zod import resolution - check if 'zod' resolves to v4 ==="
rg -n "\"zod\"[^/]" --type=ts | head -10Length of output: 1825 Update The reviewed file imports from import z from "zod/v4";Additionally, verify all other files importing from this module are compatible with the updated import. 🤖 Prompt for AI Agents |
||
|
|
@@ -23,7 +19,6 @@ import { | |
| } from "common/errors/index.js"; | ||
| import { | ||
| addLead, | ||
| getLeadsM365DynamicQuery, | ||
| getOrgInfo, | ||
| removeLead, | ||
| SQSMessage, | ||
|
|
@@ -35,8 +30,6 @@ import { | |
| TransactWriteItemsCommand, | ||
| } from "@aws-sdk/client-dynamodb"; | ||
| import { | ||
| execCouncilGroupId, | ||
| execCouncilTestingGroupId, | ||
| genericConfig, | ||
| notificationRecipients, | ||
| roleArns, | ||
|
|
@@ -46,20 +39,12 @@ import { buildAuditLogTransactPut } from "api/functions/auditLog.js"; | |
| import { Modules } from "common/modules.js"; | ||
| import { authorizeByOrgRoleOrSchema } from "api/functions/authorization.js"; | ||
| import { checkPaidMembership } from "api/functions/membership.js"; | ||
| import { | ||
| createM365Group, | ||
| getEntraIdToken, | ||
| setGroupMembershipRule, | ||
| } from "api/functions/entraId.js"; | ||
| import { createM365Group, getEntraIdToken } from "api/functions/entraId.js"; | ||
| import { SecretsManagerClient } from "@aws-sdk/client-secrets-manager"; | ||
| import { getRoleCredentials } from "api/functions/sts.js"; | ||
| import { SendMessageCommand, SQSClient } from "@aws-sdk/client-sqs"; | ||
| import { SQSClient } from "@aws-sdk/client-sqs"; | ||
| import { sendSqsMessagesInBatches } from "api/functions/sqs.js"; | ||
| import { retryDynamoTransactionWithBackoff } from "api/utils.js"; | ||
| import { | ||
| assignIdpGroupsToTeam, | ||
| createGithubTeam, | ||
| } from "api/functions/github.js"; | ||
| import { SKIP_EXTERNAL_ORG_LEAD_UPDATE } from "common/overrides.js"; | ||
| import { AvailableSQSFunctions, SQSPayload } from "common/types/sqsMessage.js"; | ||
|
|
||
|
|
@@ -499,20 +484,8 @@ const organizationsPlugin: FastifyPluginAsync = async (fastify, _options) => { | |
| `Store Entra group ID for ${request.params.orgId}`, | ||
| ); | ||
|
|
||
| // Update dynamic membership query | ||
| const newQuery = await getLeadsM365DynamicQuery({ | ||
| dynamoClient: fastify.dynamoClient, | ||
| includeGroupIds: [entraGroupId], | ||
| }); | ||
| if (newQuery) { | ||
| const groupToUpdate = | ||
| fastify.runEnvironment === "prod" | ||
| ? execCouncilGroupId | ||
| : execCouncilTestingGroupId; | ||
| request.log.info("Changing Exec group membership dynamic query..."); | ||
| await setGroupMembershipRule(entraIdToken, groupToUpdate, newQuery); | ||
| request.log.info("Changed Exec group membership dynamic query!"); | ||
| } | ||
| // Note: Exec council membership is now managed via SQS sync handler | ||
| // instead of dynamic membership rules | ||
| } catch (e) { | ||
| request.log.error(e, "Failed to create Entra group"); | ||
| throw new InternalServerError({ | ||
|
|
@@ -589,6 +562,19 @@ const organizationsPlugin: FastifyPluginAsync = async (fastify, _options) => { | |
| }; | ||
| sqsPayloads.push(sqsPayload); | ||
| } | ||
|
|
||
| // Queue exec council sync to ensure voting members are added/removed | ||
| const syncExecPayload: SQSPayload<AvailableSQSFunctions.SyncExecCouncil> = | ||
| { | ||
| function: AvailableSQSFunctions.SyncExecCouncil, | ||
| metadata: { | ||
| initiator: request.username!, | ||
| reqId: request.id, | ||
| }, | ||
| payload: {}, | ||
| }; | ||
| sqsPayloads.push(syncExecPayload); | ||
|
|
||
| if (sqsPayloads.length > 0) { | ||
| await sendSqsMessagesInBatches({ | ||
| sqsClient: fastify.sqsClient, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,3 +4,4 @@ export { provisionNewMemberHandler } from "./provisionNewMember.js"; | |
| export { sendSaleEmailHandler } from "./sendSaleEmailHandler.js"; | ||
| export { emailNotificationsHandler } from "./emailNotifications.js"; | ||
| export { createOrgGithubTeamHandler } from "./createOrgGithubTeam.js"; | ||
| export { syncExecCouncilHandler } from "./syncExecCouncil.js"; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chainESLint import/extensions warning. If using TS + ESM with NodeNext and emitted .js paths, keep the extension and relax the rule; otherwise drop the extension for local exports. 🏁 Script executed: #!/bin/bash
# Show existing pattern in this repo
rg -nP '^export\s+\{.*\}\s+from\s+"\.\/.*\.js";' src/api/sqs/handlers | wc -lLength of output: 1 Length of output: 0 🏁 Script executed: #!/bin/bash
# Check for ESLint configuration files and import/extensions rule
find . -maxdepth 2 -type f \( -name ".eslintrc*" -o -name "eslint.config.*" \) 2>/dev/null | head -5Length of output: 19 🏁 Script executed: #!/bin/bash
# Check package.json for ESLint config and import/extensions rule mention
cat package.json 2>/dev/null | grep -A20 '"eslintConfig"' | head -30Length of output: 0 🏁 Script executed: #!/bin/bash
# Show actual exports with .js in src/api/sqs/handlers to confirm pattern
rg -nP '^export\s+\{.*\}\s+from\s+"\.\/.*\.js";' src/api/sqs/handlersLength of output: 680 Length of output: 2565 🏁 Script executed: #!/bin/bash
# Read full tsconfig.json
cat tsconfig.jsonLength of output: 45 🏁 Script executed: #!/bin/bash
# Check if .js extensions are used elsewhere in codebase (not just handlers)
rg -nP '^export\s+\{.*\}\s+from\s+"\.\/.*\.js";' --type=ts --max-count=20 | head -20Length of output: 680 Remove .js extensions from all export statements—they violate the ESLint rule. The project's ESLint configuration explicitly forbids .js extensions on local imports and exports ( 🧰 Tools🪛 ESLint[error] 7-7: Unexpected use of file extension "js" for "./syncExecCouncil.js" (import/extensions) 🤖 Prompt for AI Agents |
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix return type for title (can be undefined) + trim payload + optional resiliency
Apply:
export async function getAllVotingLeads({ dynamoClient, logger, }: { dynamoClient: DynamoDBClient; - logger: ValidLoggers; -}): Promise< - Array<{ username: string; org: string; name: string; title: string }> -> { + logger: ValidLoggers; +}): Promise< + Array<{ username: string; org: string; name: string; title?: string }> +> { // Query all organizations in parallel for better performance const queryPromises = AllOrganizationNameList.map(async (orgName) => { const leadsQuery = new QueryCommand({ TableName: genericConfig.SigInfoTableName, KeyConditionExpression: "primaryKey = :leadName", ExpressionAttributeValues: { ":leadName": { S: `LEAD#${orgName}` }, }, + ProjectionExpression: "#u, #n, title, nonVotingMember", + ExpressionAttributeNames: { "#u": "username", "#n": "name" }, ConsistentRead: true, }); try { const responseMarshall = await dynamoClient.send(leadsQuery); if (responseMarshall.Items) { return responseMarshall.Items.map((x) => unmarshall(x)) .filter((x) => x.username && !x.nonVotingMember) .map((x) => ({ username: x.username as string, org: orgName, - name: x.name as string, - title: x.title as string, + name: x.name as string, + title: x.title, })); } return []; } catch (e) { if (e instanceof BaseError) { throw e; } logger.error(e); throw new DatabaseFetchError({ message: `Failed to get leads for org ${orgName}.`, }); } }); - const results = await Promise.all(queryPromises); - return results.flat(); + // Option B (resiliency): tolerate one-org failures and return partial results + // const settled = await Promise.allSettled(queryPromises); + // const results: Array<{ username: string; org: string; name: string; title?: string }> = []; + // for (const s of settled) { + // if (s.status === "fulfilled") results.push(...s.value); + // else logger.error(s.reason); + // } + // return results; + const results = await Promise.all(queryPromises); + return results.flat(); }📝 Committable suggestion
🤖 Prompt for AI Agents