FEAT: Refactor berth API functions to use dedicated utility methods for fetching and updating berths, and add connection test for NocoDB

This commit is contained in:
2025-06-17 16:07:15 +02:00
parent 0e85cb40bc
commit adf226a38a
5 changed files with 235 additions and 154 deletions

View File

@@ -1,4 +1,4 @@
import { getNocoDbConfiguration } from "../utils/nocodb";
import { getBerthById } from "../utils/nocodb";
import { requireAuth } from "../utils/auth";
export default defineEventHandler(async (event) => {
@@ -18,22 +18,8 @@ export default defineEventHandler(async (event) => {
});
}
const config = getNocoDbConfiguration();
const berthsTableId = "mczgos9hr3oa9qc";
console.log('[get-berth-by-id] Fetching berth with ID:', berthId);
// Fetch berth with linked interested parties
const berth = await $fetch(`${config.url}/api/v2/tables/${berthsTableId}/records/${berthId}`, {
headers: {
"xc-token": config.token,
},
params: {
// Expand the "Interested Parties" linked field to get full interest records
fields: '*,Interested Parties.*'
}
});
const berth = await getBerthById(berthId as string);
console.log('[get-berth-by-id] Successfully fetched berth:', berthId);
return berth;

View File

@@ -1,4 +1,4 @@
import { getNocoDbConfiguration } from "../utils/nocodb";
import { getBerths } from "../utils/nocodb";
import { requireAuth } from "../utils/auth";
export default defineEventHandler(async (event) => {
@@ -8,54 +8,9 @@ export default defineEventHandler(async (event) => {
await requireAuth(event);
try {
const config = getNocoDbConfiguration();
const berthsTableId = "mczgos9hr3oa9qc";
console.log('[get-berths] Fetching berths...');
const berths = await $fetch<{ list: any[] }>(`${config.url}/api/v2/tables/${berthsTableId}/records`, {
headers: {
"xc-token": config.token,
},
params: {
limit: 1000,
// Include interested parties count (expand the linked field)
fields: '*,Interested Parties.Id,Interested Parties.Full Name,Interested Parties.Sales Process Level,Interested Parties.EOI Status,Interested Parties.Contract Status'
},
});
const berths = await getBerths();
console.log('[get-berths] Successfully fetched berths, count:', berths.list?.length || 0);
// Sort berths by letter zone and then by number using Mooring Number
if (berths.list && Array.isArray(berths.list)) {
berths.list.sort((a, b) => {
const berthA = a['Mooring Number'] || '';
const berthB = b['Mooring Number'] || '';
// Extract letter and number parts
const matchA = berthA.match(/^([A-Za-z]+)(\d+)$/);
const matchB = berthB.match(/^([A-Za-z]+)(\d+)$/);
if (matchA && matchB) {
const [, letterA, numberA] = matchA;
const [, letterB, numberB] = matchB;
// First sort by letter zone
const letterCompare = letterA.localeCompare(letterB);
if (letterCompare !== 0) {
return letterCompare;
}
// Then sort by number within the same letter zone
return parseInt(numberA) - parseInt(numberB);
}
// Fallback to string comparison if pattern doesn't match
return berthA.localeCompare(berthB);
});
console.log('[get-berths] Berths sorted by zone and number');
}
return berths;
} catch (error) {
console.error('[get-berths] Error occurred:', error);

View File

@@ -0,0 +1,63 @@
import { getBerths, getBerthById } from "../utils/nocodb";
import { requireAuth } from "../utils/auth";
export default defineEventHandler(async (event) => {
console.log('[test-berth-connection] Testing berth NocoDB connection...');
// Check authentication (x-tag header OR Keycloak session)
await requireAuth(event);
try {
// Test fetching all berths
console.log('[test-berth-connection] Testing getBerths()...');
const berthsResponse = await getBerths();
console.log('[test-berth-connection] Berths fetched successfully:', {
count: berthsResponse.list?.length || 0,
firstBerth: berthsResponse.list?.[0] ? {
id: berthsResponse.list[0].Id,
mooringNumber: berthsResponse.list[0]['Mooring Number'],
status: berthsResponse.list[0].Status,
area: berthsResponse.list[0].Area
} : null
});
let testBerth = null;
if (berthsResponse.list && berthsResponse.list.length > 0) {
const firstBerthId = berthsResponse.list[0].Id.toString();
console.log('[test-berth-connection] Testing getBerthById() with ID:', firstBerthId);
testBerth = await getBerthById(firstBerthId);
console.log('[test-berth-connection] Individual berth fetched successfully:', {
id: testBerth.Id,
mooringNumber: testBerth['Mooring Number'],
interestedPartiesCount: testBerth['Interested Parties']?.length || 0
});
}
return {
success: true,
message: "Berth NocoDB connection test successful",
data: {
totalBerths: berthsResponse.list?.length || 0,
testBerth: testBerth ? {
id: testBerth.Id,
mooringNumber: testBerth['Mooring Number'],
status: testBerth.Status,
area: testBerth.Area,
price: testBerth.Price,
interestedPartiesCount: testBerth['Interested Parties']?.length || 0
} : null,
tableId: "mczgos9hr3oa9qc"
}
};
} catch (error) {
console.error('[test-berth-connection] Error occurred:', error);
console.error('[test-berth-connection] Error details:', error instanceof Error ? error.message : 'Unknown error');
return {
success: false,
message: "Berth NocoDB connection test failed",
error: error instanceof Error ? error.message : 'Unknown error',
details: error
};
}
});

View File

@@ -1,17 +1,5 @@
import { withBerthQueue } from '~/server/utils/operation-lock';
import { getNocoDbConfiguration } from '~/server/utils/nocodb';
import { requireAuth } from '~/server/utils/auth';
import {
BerthStatus,
BerthArea,
SidePontoon,
MooringType,
CleatType,
CleatCapacity,
BollardType,
BollardCapacity,
Access
} from '~/utils/types';
import { updateBerth } from "../utils/nocodb";
import { requireAuth } from "../utils/auth";
export default defineEventHandler(async (event) => {
console.log('[update-berth] Request received');
@@ -22,90 +10,28 @@ export default defineEventHandler(async (event) => {
try {
const body = await readBody(event);
const { berthId, updates } = body;
console.log('[update-berth] Request body:', { berthId, updates });
if (!berthId || !updates) {
if (!berthId) {
throw createError({
statusCode: 400,
statusMessage: "berthId and updates object are required"
statusMessage: "Berth ID is required"
});
}
// Validate enum fields
const validEnumFields: Record<string, string[]> = {
'Status': Object.values(BerthStatus),
'Area': Object.values(BerthArea),
'Side Pontoon': Object.values(SidePontoon),
'Mooring Type': Object.values(MooringType),
'Cleat Type': Object.values(CleatType),
'Cleat Capacity': Object.values(CleatCapacity),
'Bollard Type': Object.values(BollardType),
'Bollard Capacity': Object.values(BollardCapacity),
'Access': Object.values(Access)
};
// Validate enum values
for (const [field, value] of Object.entries(updates)) {
if (validEnumFields[field] && value !== null && value !== undefined) {
if (!validEnumFields[field].includes(value as string)) {
throw createError({
statusCode: 400,
statusMessage: `Invalid value for ${field}: ${value}. Must be one of: ${validEnumFields[field].join(', ')}`
});
}
}
}
// Handle measurement conversions
// If metric values are being updated, convert them to imperial for storage
const measurementFields = ['Nominal Boat Size', 'Water Depth', 'Length', 'Width', 'Depth'];
const processedUpdates = { ...updates };
for (const field of measurementFields) {
if (processedUpdates[field] !== undefined) {
const value = processedUpdates[field];
if (typeof value === 'string') {
// Parse user input and convert metric to imperial if needed
const cleanInput = value.replace(/[^\d.]/g, '');
const numericValue = parseFloat(cleanInput);
if (!isNaN(numericValue)) {
const isMetric = value.toLowerCase().includes('m') && !value.toLowerCase().includes('ft');
if (isMetric) {
// Convert metric to imperial for NocoDB storage
const imperial = numericValue / 0.3048;
processedUpdates[field] = parseFloat(imperial.toFixed(2));
console.log(`[update-berth] Converted ${field} from ${numericValue}m to ${processedUpdates[field]}ft`);
} else {
// Assume imperial, store as is
processedUpdates[field] = numericValue;
}
}
}
}
}
// Use queuing system to handle concurrent updates
return await withBerthQueue(berthId, async () => {
const config = getNocoDbConfiguration();
const berthsTableId = "mczgos9hr3oa9qc";
const url = `${config.url}/api/v2/tables/${berthsTableId}/records/${berthId}`;
console.log('[update-berth] URL:', url);
console.log('[update-berth] Processed updates:', processedUpdates);
const result = await $fetch(url, {
method: 'PATCH',
headers: {
"xc-token": config.token,
},
body: processedUpdates,
if (!updates || typeof updates !== 'object') {
throw createError({
statusCode: 400,
statusMessage: "Updates object is required"
});
console.log('[update-berth] Successfully updated berth:', berthId);
return result;
});
}
console.log('[update-berth] Updating berth ID:', berthId);
console.log('[update-berth] Updates:', Object.keys(updates));
const updatedBerth = await updateBerth(berthId.toString(), updates);
console.log('[update-berth] Successfully updated berth:', berthId);
return updatedBerth;
} catch (error) {
console.error('[update-berth] Error occurred:', error);
console.error('[update-berth] Error details:', error instanceof Error ? error.message : 'Unknown error');