-
Notifications
You must be signed in to change notification settings - Fork 7.4k
feat(twenty-partners): expose partnerScope on list + by-slug endpoints #21126
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
37b7404
a38ac86
0137f3c
306ad94
e3f26d7
89239d2
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 |
|---|---|---|
| @@ -0,0 +1,107 @@ | ||
| import { CoreApiClient } from 'twenty-client-sdk/core'; | ||
| import { defineLogicFunction } from 'twenty-sdk/define'; | ||
|
|
||
| export const GET_PARTNER_BY_SLUG_LOGIC_FUNCTION_ID = | ||
| '5e3e7b88-2cf2-4f56-9a4a-46c4c1d6b0bb'; | ||
|
|
||
| type CurrencyValue = { amountMicros: number; currencyCode: string } | null; | ||
| type LinkValue = { primaryLinkUrl: string | null } | null; | ||
|
|
||
| type Partner = { | ||
| id: string; | ||
| name: string | null; | ||
| slug: string | null; | ||
| introduction: string | null; | ||
| languagesSpoken: string[] | null; | ||
| deploymentExpertise: string[] | null; | ||
| partnerScope: string[] | null; | ||
| region: string[] | null; | ||
| calendarLink: LinkValue; | ||
| hourlyRate: CurrencyValue; | ||
| projectBudgetMin: CurrencyValue; | ||
| projectBudgetTypical: CurrencyValue; | ||
| linkedin: LinkValue; | ||
| profilePicture: LinkValue; | ||
| skills: string[] | null; | ||
| city: string | null; | ||
| country: string | null; | ||
| }; | ||
|
|
||
| type GetPartnerBySlugResult = | ||
| | { ok: true; partner: Partner } | ||
| | { ok: false; reason: 'NOT_FOUND' | string }; | ||
|
|
||
| const handler = async (input: { | ||
| queryStringParameters?: { slug?: string }; | ||
| }): Promise<GetPartnerBySlugResult> => { | ||
| const slug = input?.queryStringParameters?.slug; | ||
| if (typeof slug !== 'string' || slug.length === 0) { | ||
| return { ok: false, reason: 'Missing slug query parameter' }; | ||
| } | ||
|
|
||
| try { | ||
| const client = new CoreApiClient(); | ||
|
|
||
| const result = await client.query({ | ||
| partners: { | ||
| __args: { | ||
| filter: { | ||
| slug: { eq: slug }, | ||
| validationStage: { eq: 'VALIDATED' }, | ||
| availability: { eq: 'AVAILABLE' }, | ||
| }, | ||
| first: 1, | ||
| }, | ||
| edges: { | ||
| node: { | ||
| id: true, | ||
| name: true, | ||
| slug: true, | ||
| introduction: true, | ||
| languagesSpoken: true, | ||
| deploymentExpertise: true, | ||
| partnerScope: true, | ||
| region: true, | ||
| calendarLink: { primaryLinkUrl: true }, | ||
| hourlyRate: { amountMicros: true, currencyCode: true }, | ||
| projectBudgetMin: { amountMicros: true, currencyCode: true }, | ||
| projectBudgetTypical: { amountMicros: true, currencyCode: true }, | ||
| linkedin: { primaryLinkUrl: true }, | ||
| profilePicture: { primaryLinkUrl: true }, | ||
| skills: true, | ||
| city: true, | ||
| country: true, | ||
| }, | ||
| }, | ||
| }, | ||
| } as any); | ||
|
|
||
| const edges = (result?.partners?.edges ?? []) as Array<{ node: Partner }>; | ||
| const partner = edges[0]?.node; | ||
|
|
||
| if (!partner) { | ||
| return { ok: false, reason: 'NOT_FOUND' }; | ||
| } | ||
|
|
||
| return { ok: true, partner }; | ||
| } catch (err) { | ||
| return { | ||
| ok: false, | ||
| reason: err instanceof Error ? err.message : String(err), | ||
|
Contributor
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. P2: Public route returns raw error message. This leaks internal backend details to anyone calling endpoint. Return a generic error string. Prompt for AI agents |
||
| }; | ||
| } | ||
| }; | ||
|
|
||
| export default defineLogicFunction({ | ||
| universalIdentifier: GET_PARTNER_BY_SLUG_LOGIC_FUNCTION_ID, | ||
| name: 'get-partner-by-slug', | ||
| description: | ||
| 'Returns a single VALIDATED + AVAILABLE partner by slug, or NOT_FOUND.', | ||
| timeoutSeconds: 10, | ||
| handler, | ||
| httpRouteTriggerSettings: { | ||
| path: '/partner-by-slug', | ||
| httpMethod: 'GET', | ||
| isAuthRequired: false, | ||
|
Member
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. @rashad @FelixMalfait are we happy about this one? we are exposing PII on a public endpoint here We should add authentication here
Member
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. I think I get it, this is supposed to be served on a public website?
Contributor
Author
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. even for the list? we have an authentication mecanism for the POST applications #21040 |
||
| }, | ||
| }); | ||
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.
P2: Big duplicate partner shape/query added. This will drift and break consistency when fields evolve. Extract shared type/query fragment.
Prompt for AI agents