From d9f359d8743c9510c247514dd987eb63bc79d72a Mon Sep 17 00:00:00 2001 From: Matt Date: Tue, 17 Jun 2025 16:49:43 +0200 Subject: [PATCH] FEAT: Implement debug and test endpoints for fetching and analyzing berth interested parties --- server/api/debug/berth-interested-parties.ts | 75 ++++++++++ server/api/get-berth-interested-parties.ts | 121 +++++++++++++++ server/api/test-berth-interested-parties.ts | 147 +++++++++++++++++++ server/utils/nocodb.ts | 47 ++++-- 4 files changed, 375 insertions(+), 15 deletions(-) create mode 100644 server/api/debug/berth-interested-parties.ts create mode 100644 server/api/get-berth-interested-parties.ts create mode 100644 server/api/test-berth-interested-parties.ts diff --git a/server/api/debug/berth-interested-parties.ts b/server/api/debug/berth-interested-parties.ts new file mode 100644 index 0000000..8f89c7f --- /dev/null +++ b/server/api/debug/berth-interested-parties.ts @@ -0,0 +1,75 @@ +import { requireAuth } from '~/server/utils/auth'; +import { getBerthById } from '~/server/utils/nocodb'; + +export default defineEventHandler(async (event) => { + console.log('[debug-berth-interested-parties] Request received'); + + // Check authentication + await requireAuth(event); + + try { + // Get a berth ID from query params + const query = getQuery(event); + const berthId = query.id as string; + + if (!berthId) { + throw createError({ + statusCode: 400, + statusMessage: 'Berth ID is required' + }); + } + + console.log('[debug-berth-interested-parties] Fetching berth:', berthId); + + // First, get the raw berth data without population + const config = useRuntimeConfig().nocodb; + const rawBerthResponse = await $fetch(`${config.url}/api/v2/tables/mczgos9hr3oa9qc/records/${berthId}`, { + headers: { + "xc-token": config.token, + }, + params: { + fields: '*' + } + }); + + console.log('[debug-berth-interested-parties] Raw berth data:', JSON.stringify(rawBerthResponse, null, 2)); + + // Check the structure of Interested Parties + const interestedParties = (rawBerthResponse as any)['Interested Parties']; + console.log('[debug-berth-interested-parties] Raw Interested Parties structure:', JSON.stringify(interestedParties, null, 2)); + console.log('[debug-berth-interested-parties] Interested Parties type:', typeof interestedParties); + console.log('[debug-berth-interested-parties] Is Array:', Array.isArray(interestedParties)); + + if (Array.isArray(interestedParties) && interestedParties.length > 0) { + console.log('[debug-berth-interested-parties] First party:', JSON.stringify(interestedParties[0], null, 2)); + console.log('[debug-berth-interested-parties] First party type:', typeof interestedParties[0]); + console.log('[debug-berth-interested-parties] First party keys:', Object.keys(interestedParties[0] || {})); + } + + // Now try the populated version + const populatedBerth = await getBerthById(berthId); + console.log('[debug-berth-interested-parties] Populated Interested Parties:', JSON.stringify(populatedBerth['Interested Parties'], null, 2)); + + return { + berthId, + raw: { + interestedParties: interestedParties, + type: typeof interestedParties, + isArray: Array.isArray(interestedParties), + length: Array.isArray(interestedParties) ? interestedParties.length : 0, + firstItem: Array.isArray(interestedParties) && interestedParties.length > 0 ? interestedParties[0] : null, + firstItemKeys: Array.isArray(interestedParties) && interestedParties.length > 0 && interestedParties[0] ? Object.keys(interestedParties[0]) : [] + }, + populated: { + interestedParties: populatedBerth['Interested Parties'], + success: !!populatedBerth['Interested Parties'] + } + }; + } catch (error: any) { + console.error('[debug-berth-interested-parties] Error:', error); + throw createError({ + statusCode: 500, + statusMessage: error.message || 'Failed to debug berth interested parties' + }); + } +}); diff --git a/server/api/get-berth-interested-parties.ts b/server/api/get-berth-interested-parties.ts new file mode 100644 index 0000000..87d1724 --- /dev/null +++ b/server/api/get-berth-interested-parties.ts @@ -0,0 +1,121 @@ +import { getNocoDbConfiguration } from "../utils/nocodb"; +import { requireAuth } from "../utils/auth"; + +export default defineEventHandler(async (event) => { + console.log('[get-berth-interested-parties] Request received'); + + // Check authentication + await requireAuth(event); + + try { + const query = getQuery(event); + const { berthId } = query; + console.log('[get-berth-interested-parties] Request params:', { berthId }); + + if (!berthId) { + throw createError({ + statusCode: 400, + statusMessage: "berthId is required" + }); + } + + const config = getNocoDbConfiguration(); + const berthsTableId = "mczgos9hr3oa9qc"; // Berth table ID + + // The "Interested Parties" field ID needs to be determined + // For now, let's try to fetch the berth first and see what we get + const berthUrl = `${config.url}/api/v2/tables/${berthsTableId}/records/${berthId}`; + console.log('[get-berth-interested-parties] Fetching berth:', berthUrl); + + const berth = await $fetch(berthUrl, { + headers: { + "xc-token": config.token, + } + }); + + console.log('[get-berth-interested-parties] Berth data:', JSON.stringify(berth, null, 2)); + + // Check if we have Interested Parties field + const interestedPartiesField = (berth as any)['Interested Parties']; + console.log('[get-berth-interested-parties] Interested Parties field:', JSON.stringify(interestedPartiesField, null, 2)); + + // If we have interested parties, fetch their details + if (interestedPartiesField && Array.isArray(interestedPartiesField)) { + // Extract IDs from the interested parties + const partyIds = interestedPartiesField.map((party: any) => { + // Handle different possible formats + if (typeof party === 'number') return party; + if (typeof party === 'string') return parseInt(party); + if (party && typeof party === 'object' && party.Id) return party.Id; + if (party && typeof party === 'object' && party.id) return party.id; + return null; + }).filter(id => id !== null); + + console.log('[get-berth-interested-parties] Party IDs to fetch:', partyIds); + + // Fetch full interest records + if (partyIds.length > 0) { + const interestsTableId = "mbs9hjauug4eseo"; + const interestPromises = partyIds.map(id => + $fetch(`${config.url}/api/v2/tables/${interestsTableId}/records/${id}`, { + headers: { + "xc-token": config.token, + } + }).catch(error => { + console.error(`[get-berth-interested-parties] Failed to fetch interest ${id}:`, error); + return null; + }) + ); + + const interests = await Promise.all(interestPromises); + const validInterests = interests.filter(i => i !== null); + + console.log('[get-berth-interested-parties] Fetched interests:', validInterests.length); + + return { + list: validInterests, + pageInfo: { + totalRows: validInterests.length, + page: 1, + pageSize: validInterests.length, + isFirstPage: true, + isLastPage: true + } + }; + } + } + + // Return empty list if no interested parties + return { + list: [], + pageInfo: { + totalRows: 0, + page: 1, + pageSize: 0, + isFirstPage: true, + isLastPage: true + } + }; + + } catch (error: any) { + console.error('[get-berth-interested-parties] Error occurred:', error); + console.error('[get-berth-interested-parties] Error details:', error instanceof Error ? error.message : 'Unknown error'); + + // Check if it's a 404 error + if (error.statusCode === 404 || error.status === 404) { + // Return empty list instead of throwing error + return { + list: [], + pageInfo: { + totalRows: 0, + page: 1, + pageSize: 0, + isFirstPage: true, + isLastPage: true + } + }; + } + + throw error; + } +}); diff --git a/server/api/test-berth-interested-parties.ts b/server/api/test-berth-interested-parties.ts new file mode 100644 index 0000000..8224246 --- /dev/null +++ b/server/api/test-berth-interested-parties.ts @@ -0,0 +1,147 @@ +import { requireAuth } from '~/server/utils/auth'; + +export default defineEventHandler(async (event) => { + console.log('[test-berth-interested-parties] Testing berth interested parties...'); + + // Check authentication + await requireAuth(event); + + try { + const config = useRuntimeConfig().nocodb; + + // First, let's get all berths and see which ones have interested parties + const berthsResponse = await $fetch(`${config.url}/api/v2/tables/mczgos9hr3oa9qc/records`, { + headers: { + "xc-token": config.token, + }, + params: { + limit: 100, + fields: '*' + } + }); + + const berths = (berthsResponse as any).list || []; + console.log('[test-berth-interested-parties] Total berths:', berths.length); + + // Find berths with interested parties + const berthsWithParties = berths.filter((b: any) => + b['Interested Parties'] && Array.isArray(b['Interested Parties']) && b['Interested Parties'].length > 0 + ); + + console.log('[test-berth-interested-parties] Berths with interested parties:', berthsWithParties.length); + + if (berthsWithParties.length === 0) { + return { + message: "No berths found with interested parties", + totalBerths: berths.length, + berthsWithParties: 0 + }; + } + + // Take the first berth with interested parties for testing + const testBerth = berthsWithParties[0]; + console.log('[test-berth-interested-parties] Test berth:', { + id: testBerth.Id, + mooringNumber: testBerth['Mooring Number'], + interestedPartiesCount: testBerth['Interested Parties'].length + }); + + // Log the raw interested parties data + console.log('[test-berth-interested-parties] Raw Interested Parties data:', + JSON.stringify(testBerth['Interested Parties'], null, 2) + ); + + // Analyze the structure + const firstParty = testBerth['Interested Parties'][0]; + const partyAnalysis = { + type: typeof firstParty, + isArray: Array.isArray(firstParty), + isObject: firstParty && typeof firstParty === 'object', + value: firstParty, + keys: firstParty && typeof firstParty === 'object' && !Array.isArray(firstParty) + ? Object.keys(firstParty) + : [] + }; + + console.log('[test-berth-interested-parties] First party analysis:', partyAnalysis); + + // Try to extract IDs + const extractedIds = testBerth['Interested Parties'].map((party: any) => { + if (typeof party === 'number') return { id: party, type: 'number' }; + if (typeof party === 'string') return { id: party, type: 'string' }; + if (party && typeof party === 'object') { + return { + id: party.Id || party.id || party.ID || party._id || null, + type: 'object', + keys: Object.keys(party) + }; + } + return { id: null, type: 'unknown' }; + }); + + console.log('[test-berth-interested-parties] Extracted IDs:', extractedIds); + + // Try to fetch one interest if we have an ID + let fetchedInterest = null; + const validId = extractedIds.find((item: any) => item.id !== null); + + if (validId && validId.id) { + try { + const interestId = typeof validId.id === 'string' ? validId.id : validId.id.toString(); + fetchedInterest = await $fetch(`${config.url}/api/v2/tables/mbs9hjauug4eseo/records/${interestId}`, { + headers: { + "xc-token": config.token, + } + }); + console.log('[test-berth-interested-parties] Successfully fetched interest:', interestId); + } catch (error) { + console.error('[test-berth-interested-parties] Failed to fetch interest:', error); + } + } + + return { + testBerth: { + id: testBerth.Id, + mooringNumber: testBerth['Mooring Number'], + interestedPartiesCount: testBerth['Interested Parties'].length + }, + rawInterestedParties: testBerth['Interested Parties'], + firstPartyAnalysis: partyAnalysis, + extractedIds: extractedIds, + fetchedInterest: fetchedInterest, + recommendation: determineRecommendation(testBerth['Interested Parties']) + }; + } catch (error: any) { + console.error('[test-berth-interested-parties] Error:', error); + return { + error: error.message || 'Unknown error', + details: error + }; + } +}); + +function determineRecommendation(interestedParties: any[]): string { + if (!interestedParties || interestedParties.length === 0) { + return "No interested parties found"; + } + + const firstParty = interestedParties[0]; + + if (typeof firstParty === 'number') { + return "Interested parties are stored as number IDs. Population logic should handle this format."; + } + + if (typeof firstParty === 'string') { + return "Interested parties are stored as string IDs. Population logic should parse these."; + } + + if (firstParty && typeof firstParty === 'object') { + const keys = Object.keys(firstParty); + if (keys.includes('Id') || keys.includes('id')) { + return "Interested parties are objects with ID fields. Population logic looks correct."; + } + return `Interested parties are objects but with unexpected keys: ${keys.join(', ')}`; + } + + return "Unknown format for interested parties data"; +} diff --git a/server/utils/nocodb.ts b/server/utils/nocodb.ts index 08bac17..ad3dc1b 100644 --- a/server/utils/nocodb.ts +++ b/server/utils/nocodb.ts @@ -523,30 +523,47 @@ export const getBerthById = async (id: string) => { }); console.log('[nocodb.getBerthById] Successfully fetched berth:', result.Id); + console.log('[nocodb.getBerthById] Raw Interested Parties:', JSON.stringify(result['Interested Parties'], null, 2)); // Now fetch and populate the interested parties details if (result['Interested Parties'] && Array.isArray(result['Interested Parties'])) { console.log('[nocodb.getBerthById] Fetching details for interested parties:', result['Interested Parties'].length); - const interestedPartiesDetails = await Promise.all( - result['Interested Parties'].map(async (party: any) => { - if (party && (party.Id || party.id)) { - const interestId = party.Id || party.id; + // Extract IDs from various possible formats + const partyIds = result['Interested Parties'].map((party: any) => { + // Handle different possible formats from NocoDB + if (typeof party === 'number') return party; + if (typeof party === 'string') return parseInt(party); + if (party && typeof party === 'object') { + // Check various possible ID field names + return party.Id || party.id || party.ID || party._id || null; + } + return null; + }).filter(id => id !== null && !isNaN(id)); + + console.log('[nocodb.getBerthById] Extracted party IDs:', partyIds); + + // Fetch full interest records + if (partyIds.length > 0) { + const interestedPartiesDetails = await Promise.all( + partyIds.map(async (partyId: number) => { try { - console.log('[nocodb.getBerthById] Fetching interest details for ID:', interestId); - const interestDetails = await getInterestById(interestId.toString()); + console.log('[nocodb.getBerthById] Fetching interest details for ID:', partyId); + const interestDetails = await getInterestById(partyId.toString()); return interestDetails; } catch (error) { - console.error('[nocodb.getBerthById] Failed to fetch interest details for ID:', interestId, error); - return party; // Return original party if fetch fails + console.error('[nocodb.getBerthById] Failed to fetch interest details for ID:', partyId, error); + // Return a placeholder object if fetch fails + return { Id: partyId, 'Full Name': `Interest #${partyId}` } as any; } - } - return party; - }) - ); - - result['Interested Parties'] = interestedPartiesDetails; - console.log('[nocodb.getBerthById] Populated interested parties details'); + }) + ); + + result['Interested Parties'] = interestedPartiesDetails; + console.log('[nocodb.getBerthById] Populated interested parties details:', interestedPartiesDetails.length); + } else { + console.log('[nocodb.getBerthById] No valid party IDs found to populate'); + } } return result;