import {
DEFAULT_ACCESS_TOKEN_SECRET,
DEFAULT_CLIENT_SECRET_SECRET,
DEFAULT_INTEGRATION_NAME,
DEFAULT_OAUTH_SCOPES,
PRODUCTHUNT_APPLICATIONS_URL,
PRODUCTHUNT_AUTHORIZE_URL,
PRODUCTHUNT_CALLBACK_URL,
PRODUCTHUNT_GRAPHQL_URL,
PRODUCTHUNT_REQUIRED_HOST,
PRODUCTHUNT_TOKEN_URL,
accessTokenSetupUrl,
clientSecretSetupUrl,
oauthConnectUrl,
reconnectUrl,
} from './setup.ts'
import { optionalString, requireRecord } from './types.ts'
export const DEFAULT_KODY_ORIGIN = 'https://kody.codes'
export function getConnectUrl(origin = DEFAULT_KODY_ORIGIN) {
if (origin === DEFAULT_KODY_ORIGIN) return oauthConnectUrl()
const params = new URLSearchParams({
provider: DEFAULT_INTEGRATION_NAME,
authorizeUrl: PRODUCTHUNT_AUTHORIZE_URL,
tokenUrl: PRODUCTHUNT_TOKEN_URL,
apiBaseUrl: PRODUCTHUNT_GRAPHQL_URL,
flow: 'confidential',
allowedHosts: PRODUCTHUNT_REQUIRED_HOST,
dashboardUrl: PRODUCTHUNT_APPLICATIONS_URL,
scopes: DEFAULT_OAUTH_SCOPES.join(' '),
})
return `${origin.replace(/\/$/, '')}/connect/oauth?${params.toString()}`
}
export function getSetupGuide(origin = DEFAULT_KODY_ORIGIN) {
return {
integration: DEFAULT_INTEGRATION_NAME,
connectUrl: getConnectUrl(origin),
reconnectUrl: origin === DEFAULT_KODY_ORIGIN ? reconnectUrl() : `${origin.replace(/\/$/, '')}/connect/oauth?provider=producthunt`,
redirectUri: `${origin.replace(/\/$/, '')}/connect/oauth`,
callbackUrl: PRODUCTHUNT_CALLBACK_URL,
applicationUrl: PRODUCTHUNT_APPLICATIONS_URL,
clientType: 'confidential',
scopes: [...DEFAULT_OAUTH_SCOPES],
requiredSecrets: [DEFAULT_ACCESS_TOKEN_SECRET, DEFAULT_CLIENT_SECRET_SECRET],
accessTokenUrl: accessTokenSetupUrl(),
clientSecretUrl: clientSecretSetupUrl(),
recommendedApprovedHosts: [PRODUCTHUNT_REQUIRED_HOST],
notes: [
'Choose Confidential when creating the Product Hunt app. Kody stores the client secret server-side.',
'Register the redirect URI exactly as shown. A truncated URI will fail the callback.',
'Private scope is required for viewer.user. Public is enough for featured posts and comments.',
'The public API can list comments but does not expose a comment-create mutation.',
'Write mutations (goals, follow) need Product Hunt app approval plus the write scope, then confirm: true.',
],
}
}
/**
* Return Product Hunt OAuth and developer-token setup guidance.
* @example
* import guide from 'kody:@kody/producthunt/guide'
* const info = await guide()
*/
export default async function guideEntrypoint(
params: { origin?: string } & Record<string, unknown> = {},
) {
const input = requireRecord(params, 'guide')
return getSetupGuide(optionalString(input.origin, 'origin') ?? DEFAULT_KODY_ORIGIN)
}