UNPKG

gatsby-source-guru

Version:

A Gatsby source plugin for fetching content from GetGuru knowledge base and creating pages from your cards

206 lines (167 loc) 6.18 kB
// API layer for gatsby-source-guru const fetch = require('node-fetch') const { createAuthHeaders } = require('./utils') // Configuration constants const GURU_API_BASE = 'https://api.getguru.com/api/v1' const GURU_SEARCH_BASE = 'https://api.getguru.com/api/v1/search/query' /** * Fetch cards from Guru search API (collection mode) with Link header pagination */ const fetchCardsFromSearch = async (pluginOptions) => { const headers = createAuthHeaders(pluginOptions) console.log('Using search API for collection auth mode') let allCards = [] let currentUrl = `${GURU_SEARCH_BASE}?q=` let pageCount = 1 while (currentUrl) { console.log(`Fetching page ${pageCount}: ${currentUrl}`) const searchResponse = await fetch(currentUrl, { headers }) if (!searchResponse.ok) { const errorBody = await searchResponse.text() console.error('Response status:', searchResponse.status) console.error('Response headers:', searchResponse.headers) console.error('Error body:', errorBody) throw new Error(`Failed to fetch cards via search: ${searchResponse.status} ${searchResponse.statusText} - ${errorBody}`) } const searchResults = await searchResponse.json() // Handle null responses if (searchResults === null) { console.log(`Found 0 cards on page ${pageCount} (null response)`) break } // Handle non-array responses if (!Array.isArray(searchResults)) { console.log(`Found 0 cards on page ${pageCount} (invalid response)`) break } console.log(`Found ${searchResults.length || 0} cards on page ${pageCount}`) // Add cards to collection if (searchResults.length > 0) { allCards = allCards.concat(searchResults) // Log the first card structure on first page if (pageCount === 1) { console.log('Sample card structure:', JSON.stringify(searchResults[0], null, 2)) } } // Check for Link header to get next page URL const linkHeader = searchResponse.headers && searchResponse.headers.get ? searchResponse.headers.get('link') : null currentUrl = null // Default to no more pages if (linkHeader) { console.log(`Link header: ${linkHeader}`) // Parse Link header to find next page // Format: <https://api.getguru.com/api/v1/search/query?q=&cursor=xyz>; rel="next" const nextMatch = linkHeader.match(/<([^>]+)>;\s*rel=["']?next["']?/i) if (nextMatch) { currentUrl = nextMatch[1] console.log(`Next page URL: ${currentUrl}`) } else { console.log('No next page found in Link header') } } else { console.log('No Link header found - assuming last page') } pageCount++ } console.log(`Total cards fetched: ${allCards.length} across ${pageCount - 1} pages`) return allCards } /** * Fetch cards from team API (user mode) */ const fetchCardsFromTeam = async (teamName, pluginOptions) => { const headers = createAuthHeaders(pluginOptions) const cardsResponse = await fetch( `${GURU_API_BASE}/teams/${teamName}/cards`, { headers } ) if (!cardsResponse.ok) { throw new Error(`Failed to fetch cards: ${cardsResponse.status} ${cardsResponse.statusText}`) } return await cardsResponse.json() } /** * Fetch parent information for boards */ const fetchBoardParents = async (boards, headers) => { const boardsWithParents = new Map() console.log(`Fetching parent information for ${boards.size} boards...`) for (const [boardId, board] of boards) { try { const parentResponse = await fetch(`${GURU_API_BASE}/folders/${boardId}/parent`, { headers }) let parentFolder = null if (parentResponse.ok) { parentFolder = await parentResponse.json() console.log(`Board ${board.title} has parent: ${parentFolder.title}`) } else { console.log(`Board ${board.title} has no parent (${parentResponse.status})`) } boardsWithParents.set(boardId, { ...board, parentFolder }) } catch (error) { console.warn(`Could not fetch parent for board ${boardId}:`, error.message) boardsWithParents.set(boardId, { ...board, parentFolder: null }) } } return boardsWithParents } /** * Fetch collections for a team (user mode only) */ const fetchCollections = async (teamName, headers) => { const collectionsResponse = await fetch( `${GURU_API_BASE}/teams/${teamName}/collections`, { headers } ) if (!collectionsResponse.ok) { throw new Error(`Failed to fetch collections: ${collectionsResponse.status} ${collectionsResponse.statusText}`) } return await collectionsResponse.json() } /** * Fetch boards for a team (user mode only) */ const fetchBoards = async (teamName, headers) => { const boardsResponse = await fetch( `${GURU_API_BASE}/teams/${teamName}/boards`, { headers } ) if (!boardsResponse.ok) { throw new Error(`Failed to fetch boards: ${boardsResponse.status} ${boardsResponse.statusText}`) } return await boardsResponse.json() } /** * Download a file from a URL */ const downloadFile = async (url, headers) => { console.log(`Downloading file from: ${url}`) try { let requestHeaders = { ...headers } if (url.includes('content.api.getguru.com')) { requestHeaders['Accept'] = '*/*' delete requestHeaders['Accept-Language'] } const response = await fetch(url, { headers: requestHeaders }) console.log(`Response status: ${response.status}, content-type: ${response.headers.get('content-type')}`) if (!response.ok) { console.warn(`Failed to download file from ${url}: ${response.status}`) return null } return { buffer: await response.buffer(), contentType: response.headers.get('content-type') || 'application/octet-stream' } } catch (error) { console.warn(`Network error downloading file from ${url}: ${error.message}`) return null } } module.exports = { fetchCardsFromSearch, fetchCardsFromTeam, fetchBoardParents, fetchCollections, fetchBoards, downloadFile, GURU_API_BASE, GURU_SEARCH_BASE }