-
Notifications
You must be signed in to change notification settings - Fork 3.4k
feat(selectors): add dropdown selectors for 14 integrations #3433
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
Open
waleedlatif1
wants to merge
29
commits into
staging
Choose a base branch
from
waleedlatif1/airtable-selectors
base: staging
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+3,339
−231
Open
Changes from all commits
Commits
Show all changes
29 commits
Select commit
Hold shift + click to select a range
665e22c
feat(selectors): add dropdown selectors for 14 integrations
waleedlatif1 c644a23
fix(selectors): secure OAuth tokens in JSM and Confluence selector ro…
waleedlatif1 8cf988c
fix(selectors): use sanitized serviceDeskId and encode SharePoint siteId
waleedlatif1 d55216a
lint
waleedlatif1 b532a37
fix(selectors): revert encodeURIComponent on SharePoint siteId
waleedlatif1 a6e695a
fix(selectors): use sanitized cloudId in Confluence and JSM route URLs
waleedlatif1 c2041af
fix(selectors): add missing context fields to resolution, ensureCrede…
waleedlatif1 4e30161
fix(selectors): rename advanced subBlock IDs to avoid canonicalParamI…
waleedlatif1 517d400
Revert "fix(selectors): rename advanced subBlock IDs to avoid canonic…
waleedlatif1 8512dbd
fix(selectors): rename canonicalParamIds to avoid subBlock ID clashes
waleedlatif1 4c6c9e8
fix(selectors): rename pre-existing driveId and files canonicalParamI…
waleedlatif1 a56451a
style: format long lines in calcom, pipedrive, and sharepoint blocks
waleedlatif1 fe812d3
fix(selectors): resolve cascading context for selected_ canonical par…
waleedlatif1 20b621d
fix(selectors): replace hacky prefix stripping with explicit CANONICA…
waleedlatif1 8d231ce
refactor(selectors): remove unnecessary selected_ prefix from canonic…
waleedlatif1 06cc970
fix(selectors): add spaceId selector pair to Confluence V2 block
waleedlatif1 613d3e1
refactor(selectors): revert V1 block changes, add selectors to Notion…
waleedlatif1 1d24842
fix(selectors): audit fixes for auth patterns, registry gaps, and dis…
waleedlatif1 92e96e8
style: lint formatting fixes
waleedlatif1 fe34462
fix(selectors): consolidate Notion canonical param pairs into array c…
waleedlatif1 789b8d7
fix(selectors): add missing selectorKey to Confluence V1 page selector
waleedlatif1 8ad6660
fix(selectors): use sanitized IDs in URLs, convert SharePoint routes …
waleedlatif1 b527d23
fix(selectors): revert Zoom meetings type to scheduled for broader co…
waleedlatif1 ed6feb3
fix(selectors): add SharePoint site ID validator, fix cascading selec…
waleedlatif1 13c25ec
fix(selectors): hoist requestId before try block in all selector routes
waleedlatif1 706751a
fix(selectors): hoist requestId before try block in Trello boards route
waleedlatif1 61d4cbf
fix(selectors): guard selector queries against unresolved variable re…
waleedlatif1 b2cb783
refactor(selectors): replace hardcoded display name fallbacks with ca…
waleedlatif1 3c76b47
fix(selectors): tighten SharePoint site ID validation to exclude unde…
waleedlatif1 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,79 @@ | ||
| import { createLogger } from '@sim/logger' | ||
| import { NextResponse } from 'next/server' | ||
| import { authorizeCredentialUse } from '@/lib/auth/credential-access' | ||
| import { generateRequestId } from '@/lib/core/utils/request' | ||
| import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils' | ||
|
|
||
| const logger = createLogger('AirtableBasesAPI') | ||
|
|
||
| export const dynamic = 'force-dynamic' | ||
|
|
||
| export async function POST(request: Request) { | ||
| const requestId = generateRequestId() | ||
| try { | ||
| const body = await request.json() | ||
| const { credential, workflowId } = body | ||
|
|
||
| if (!credential) { | ||
| logger.error('Missing credential in request') | ||
| return NextResponse.json({ error: 'Credential is required' }, { status: 400 }) | ||
| } | ||
|
|
||
| const authz = await authorizeCredentialUse(request as any, { | ||
| credentialId: credential, | ||
| workflowId, | ||
| }) | ||
| if (!authz.ok || !authz.credentialOwnerUserId) { | ||
| return NextResponse.json({ error: authz.error || 'Unauthorized' }, { status: 403 }) | ||
| } | ||
|
|
||
| const accessToken = await refreshAccessTokenIfNeeded( | ||
| credential, | ||
| authz.credentialOwnerUserId, | ||
| requestId | ||
| ) | ||
| if (!accessToken) { | ||
| logger.error('Failed to get access token', { | ||
| credentialId: credential, | ||
| userId: authz.credentialOwnerUserId, | ||
| }) | ||
| return NextResponse.json( | ||
| { error: 'Could not retrieve access token', authRequired: true }, | ||
| { status: 401 } | ||
| ) | ||
| } | ||
|
|
||
| const response = await fetch('https://api.airtable.com/v0/meta/bases', { | ||
| headers: { | ||
| Authorization: `Bearer ${accessToken}`, | ||
| 'Content-Type': 'application/json', | ||
| }, | ||
| }) | ||
|
|
||
| if (!response.ok) { | ||
| const errorData = await response.json().catch(() => ({})) | ||
| logger.error('Failed to fetch Airtable bases', { | ||
| status: response.status, | ||
| error: errorData, | ||
| }) | ||
| return NextResponse.json( | ||
| { error: 'Failed to fetch Airtable bases', details: errorData }, | ||
| { status: response.status } | ||
| ) | ||
| } | ||
|
|
||
| const data = await response.json() | ||
| const bases = (data.bases || []).map((base: { id: string; name: string }) => ({ | ||
| id: base.id, | ||
| name: base.name, | ||
| })) | ||
|
|
||
| return NextResponse.json({ bases }) | ||
| } catch (error) { | ||
| logger.error('Error processing Airtable bases request:', error) | ||
| return NextResponse.json( | ||
| { error: 'Failed to retrieve Airtable bases', details: (error as Error).message }, | ||
| { status: 500 } | ||
| ) | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,95 @@ | ||
| import { createLogger } from '@sim/logger' | ||
| import { NextResponse } from 'next/server' | ||
| import { authorizeCredentialUse } from '@/lib/auth/credential-access' | ||
| import { validateAirtableId } from '@/lib/core/security/input-validation' | ||
| import { generateRequestId } from '@/lib/core/utils/request' | ||
| import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils' | ||
|
|
||
| const logger = createLogger('AirtableTablesAPI') | ||
|
|
||
| export const dynamic = 'force-dynamic' | ||
|
|
||
| export async function POST(request: Request) { | ||
| const requestId = generateRequestId() | ||
| try { | ||
| const body = await request.json() | ||
| const { credential, workflowId, baseId } = body | ||
|
|
||
| if (!credential) { | ||
| logger.error('Missing credential in request') | ||
| return NextResponse.json({ error: 'Credential is required' }, { status: 400 }) | ||
| } | ||
|
|
||
| if (!baseId) { | ||
| logger.error('Missing baseId in request') | ||
| return NextResponse.json({ error: 'Base ID is required' }, { status: 400 }) | ||
| } | ||
|
|
||
| const baseIdValidation = validateAirtableId(baseId, 'app', 'baseId') | ||
| if (!baseIdValidation.isValid) { | ||
| logger.error('Invalid baseId', { error: baseIdValidation.error }) | ||
| return NextResponse.json({ error: baseIdValidation.error }, { status: 400 }) | ||
| } | ||
|
|
||
| const authz = await authorizeCredentialUse(request as any, { | ||
| credentialId: credential, | ||
| workflowId, | ||
| }) | ||
| if (!authz.ok || !authz.credentialOwnerUserId) { | ||
| return NextResponse.json({ error: authz.error || 'Unauthorized' }, { status: 403 }) | ||
| } | ||
|
|
||
| const accessToken = await refreshAccessTokenIfNeeded( | ||
| credential, | ||
| authz.credentialOwnerUserId, | ||
| requestId | ||
| ) | ||
| if (!accessToken) { | ||
| logger.error('Failed to get access token', { | ||
| credentialId: credential, | ||
| userId: authz.credentialOwnerUserId, | ||
| }) | ||
| return NextResponse.json( | ||
| { error: 'Could not retrieve access token', authRequired: true }, | ||
| { status: 401 } | ||
| ) | ||
| } | ||
|
|
||
| const response = await fetch( | ||
| `https://api.airtable.com/v0/meta/bases/${baseIdValidation.sanitized}/tables`, | ||
| { | ||
| headers: { | ||
| Authorization: `Bearer ${accessToken}`, | ||
| 'Content-Type': 'application/json', | ||
| }, | ||
| } | ||
| ) | ||
|
|
||
| if (!response.ok) { | ||
| const errorData = await response.json().catch(() => ({})) | ||
| logger.error('Failed to fetch Airtable tables', { | ||
| status: response.status, | ||
| error: errorData, | ||
| baseId, | ||
| }) | ||
| return NextResponse.json( | ||
| { error: 'Failed to fetch Airtable tables', details: errorData }, | ||
| { status: response.status } | ||
| ) | ||
| } | ||
|
|
||
| const data = await response.json() | ||
| const tables = (data.tables || []).map((table: { id: string; name: string }) => ({ | ||
| id: table.id, | ||
| name: table.name, | ||
| })) | ||
|
|
||
| return NextResponse.json({ tables }) | ||
| } catch (error) { | ||
| logger.error('Error processing Airtable tables request:', error) | ||
| return NextResponse.json( | ||
| { error: 'Failed to retrieve Airtable tables', details: (error as Error).message }, | ||
| { status: 500 } | ||
| ) | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,79 @@ | ||
| import { createLogger } from '@sim/logger' | ||
| import { NextResponse } from 'next/server' | ||
| import { authorizeCredentialUse } from '@/lib/auth/credential-access' | ||
| import { generateRequestId } from '@/lib/core/utils/request' | ||
| import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils' | ||
|
|
||
| const logger = createLogger('AsanaWorkspacesAPI') | ||
|
|
||
| export const dynamic = 'force-dynamic' | ||
|
|
||
| export async function POST(request: Request) { | ||
| const requestId = generateRequestId() | ||
| try { | ||
| const body = await request.json() | ||
| const { credential, workflowId } = body | ||
|
|
||
| if (!credential) { | ||
| logger.error('Missing credential in request') | ||
| return NextResponse.json({ error: 'Credential is required' }, { status: 400 }) | ||
| } | ||
|
|
||
| const authz = await authorizeCredentialUse(request as any, { | ||
| credentialId: credential, | ||
| workflowId, | ||
| }) | ||
| if (!authz.ok || !authz.credentialOwnerUserId) { | ||
| return NextResponse.json({ error: authz.error || 'Unauthorized' }, { status: 403 }) | ||
| } | ||
|
|
||
| const accessToken = await refreshAccessTokenIfNeeded( | ||
| credential, | ||
| authz.credentialOwnerUserId, | ||
| requestId | ||
| ) | ||
| if (!accessToken) { | ||
| logger.error('Failed to get access token', { | ||
| credentialId: credential, | ||
| userId: authz.credentialOwnerUserId, | ||
| }) | ||
| return NextResponse.json( | ||
| { error: 'Could not retrieve access token', authRequired: true }, | ||
| { status: 401 } | ||
| ) | ||
| } | ||
|
|
||
| const response = await fetch('https://app.asana.com/api/1.0/workspaces', { | ||
| headers: { | ||
| Authorization: `Bearer ${accessToken}`, | ||
| Accept: 'application/json', | ||
| }, | ||
| }) | ||
|
|
||
| if (!response.ok) { | ||
| const errorData = await response.json().catch(() => ({})) | ||
| logger.error('Failed to fetch Asana workspaces', { | ||
| status: response.status, | ||
| error: errorData, | ||
| }) | ||
| return NextResponse.json( | ||
| { error: 'Failed to fetch Asana workspaces', details: errorData }, | ||
| { status: response.status } | ||
| ) | ||
| } | ||
|
|
||
| const data = await response.json() | ||
| const workspaces = (data.data || []).map((workspace: { gid: string; name: string }) => ({ | ||
| id: workspace.gid, | ||
| name: workspace.name, | ||
| })) | ||
waleedlatif1 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| return NextResponse.json({ workspaces }) | ||
| } catch (error) { | ||
| logger.error('Error processing Asana workspaces request:', error) | ||
| return NextResponse.json( | ||
| { error: 'Failed to retrieve Asana workspaces', details: (error as Error).message }, | ||
| { status: 500 } | ||
| ) | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,79 @@ | ||
| import { createLogger } from '@sim/logger' | ||
| import { NextResponse } from 'next/server' | ||
| import { authorizeCredentialUse } from '@/lib/auth/credential-access' | ||
| import { generateRequestId } from '@/lib/core/utils/request' | ||
| import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils' | ||
|
|
||
| const logger = createLogger('AttioListsAPI') | ||
|
|
||
| export const dynamic = 'force-dynamic' | ||
|
|
||
| export async function POST(request: Request) { | ||
| const requestId = generateRequestId() | ||
| try { | ||
| const body = await request.json() | ||
| const { credential, workflowId } = body | ||
|
|
||
| if (!credential) { | ||
| logger.error('Missing credential in request') | ||
| return NextResponse.json({ error: 'Credential is required' }, { status: 400 }) | ||
| } | ||
|
|
||
| const authz = await authorizeCredentialUse(request as any, { | ||
| credentialId: credential, | ||
| workflowId, | ||
| }) | ||
| if (!authz.ok || !authz.credentialOwnerUserId) { | ||
| return NextResponse.json({ error: authz.error || 'Unauthorized' }, { status: 403 }) | ||
| } | ||
|
|
||
| const accessToken = await refreshAccessTokenIfNeeded( | ||
| credential, | ||
| authz.credentialOwnerUserId, | ||
| requestId | ||
| ) | ||
| if (!accessToken) { | ||
| logger.error('Failed to get access token', { | ||
| credentialId: credential, | ||
| userId: authz.credentialOwnerUserId, | ||
| }) | ||
| return NextResponse.json( | ||
| { error: 'Could not retrieve access token', authRequired: true }, | ||
| { status: 401 } | ||
| ) | ||
| } | ||
|
|
||
| const response = await fetch('https://api.attio.com/v2/lists', { | ||
| headers: { | ||
| Authorization: `Bearer ${accessToken}`, | ||
| Accept: 'application/json', | ||
| }, | ||
| }) | ||
|
|
||
| if (!response.ok) { | ||
| const errorData = await response.json().catch(() => ({})) | ||
| logger.error('Failed to fetch Attio lists', { | ||
| status: response.status, | ||
| error: errorData, | ||
| }) | ||
| return NextResponse.json( | ||
| { error: 'Failed to fetch Attio lists', details: errorData }, | ||
| { status: response.status } | ||
| ) | ||
| } | ||
|
|
||
| const data = await response.json() | ||
| const lists = (data.data || []).map((list: { api_slug: string; name: string }) => ({ | ||
| id: list.api_slug, | ||
| name: list.name, | ||
| })) | ||
|
|
||
| return NextResponse.json({ lists }) | ||
| } catch (error) { | ||
| logger.error('Error processing Attio lists request:', error) | ||
| return NextResponse.json( | ||
| { error: 'Failed to retrieve Attio lists', details: (error as Error).message }, | ||
| { status: 500 } | ||
| ) | ||
| } | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.