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:
parent
0e85cb40bc
commit
adf226a38a
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
};
|
||||
}
|
||||
});
|
||||
|
|
@ -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');
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import type { Interest, Berth } from "@/utils/types";
|
||||
|
||||
export interface PageInfo {
|
||||
pageSize: number;
|
||||
totalRows: number;
|
||||
|
|
@ -11,8 +13,14 @@ export interface InterestsResponse {
|
|||
PageInfo: PageInfo;
|
||||
}
|
||||
|
||||
export interface BerthsResponse {
|
||||
list: Berth[];
|
||||
PageInfo: PageInfo;
|
||||
}
|
||||
|
||||
export enum Table {
|
||||
Interest = "mbs9hjauug4eseo",
|
||||
Berth = "mczgos9hr3oa9qc",
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -414,3 +422,146 @@ export const getInterestByFieldAsync = async (fieldName: string, value: any): Pr
|
|||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
// Berth functions
|
||||
export const getBerths = async () => {
|
||||
console.log('[nocodb.getBerths] Fetching berths from NocoDB...');
|
||||
|
||||
const result = await $fetch<BerthsResponse>(createTableUrl(Table.Berth), {
|
||||
headers: {
|
||||
"xc-token": getNocoDbConfiguration().token,
|
||||
},
|
||||
params: {
|
||||
limit: 1000,
|
||||
// Include interested parties (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'
|
||||
},
|
||||
});
|
||||
|
||||
console.log('[nocodb.getBerths] Successfully fetched berths, count:', result.list?.length || 0);
|
||||
|
||||
// Sort berths by letter zone and then by number using Mooring Number
|
||||
if (result.list && Array.isArray(result.list)) {
|
||||
result.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('[nocodb.getBerths] Berths sorted by zone and number');
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
export const getBerthById = async (id: string) => {
|
||||
console.log('[nocodb.getBerthById] Fetching berth ID:', id);
|
||||
|
||||
const result = await $fetch<Berth>(`${createTableUrl(Table.Berth)}/${id}`, {
|
||||
headers: {
|
||||
"xc-token": getNocoDbConfiguration().token,
|
||||
},
|
||||
params: {
|
||||
// Include interested parties (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'
|
||||
}
|
||||
});
|
||||
|
||||
console.log('[nocodb.getBerthById] Successfully fetched berth:', result.Id);
|
||||
return result;
|
||||
};
|
||||
|
||||
export const updateBerth = async (id: string, data: Partial<Berth>): Promise<Berth> => {
|
||||
console.log('[nocodb.updateBerth] Updating berth:', id);
|
||||
console.log('[nocodb.updateBerth] Data fields:', Object.keys(data));
|
||||
|
||||
// Create a clean data object that matches the Berth schema
|
||||
const cleanData: Record<string, any> = {};
|
||||
|
||||
// Only include fields that are part of the Berth schema
|
||||
const allowedFields = [
|
||||
"Mooring Number",
|
||||
"Area",
|
||||
"Status",
|
||||
"Nominal Boat Size",
|
||||
"Water Depth",
|
||||
"Length",
|
||||
"Width",
|
||||
"Depth",
|
||||
"Side Pontoon",
|
||||
"Power Capacity",
|
||||
"Voltage",
|
||||
"Mooring Type",
|
||||
"Access",
|
||||
"Cleat Type",
|
||||
"Cleat Capacity",
|
||||
"Bollard Type",
|
||||
"Bollard Capacity",
|
||||
"Price",
|
||||
"Bow Facing"
|
||||
];
|
||||
|
||||
// Filter the data to only include allowed fields
|
||||
for (const field of allowedFields) {
|
||||
if (field in data) {
|
||||
const value = (data as any)[field];
|
||||
|
||||
// Handle clearing fields - NocoDB requires null for clearing, not undefined
|
||||
if (value === undefined) {
|
||||
cleanData[field] = null;
|
||||
console.log(`[nocodb.updateBerth] Converting undefined to null for field: ${field}`);
|
||||
} else {
|
||||
cleanData[field] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log('[nocodb.updateBerth] Clean data fields:', Object.keys(cleanData));
|
||||
|
||||
// PATCH requires ID in the body (not in URL)
|
||||
// Ensure ID is an integer
|
||||
cleanData.Id = parseInt(id);
|
||||
|
||||
const url = createTableUrl(Table.Berth);
|
||||
console.log('[nocodb.updateBerth] URL:', url);
|
||||
|
||||
try {
|
||||
console.log('[nocodb.updateBerth] Sending PATCH request');
|
||||
|
||||
const result = await $fetch<Berth>(url, {
|
||||
method: "PATCH",
|
||||
headers: {
|
||||
"xc-token": getNocoDbConfiguration().token,
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: cleanData
|
||||
});
|
||||
|
||||
console.log('[nocodb.updateBerth] Update successful for ID:', id);
|
||||
return result;
|
||||
} catch (error: any) {
|
||||
console.error('[nocodb.updateBerth] Update failed:', error);
|
||||
console.error('[nocodb.updateBerth] Error details:', error instanceof Error ? error.message : 'Unknown error');
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in New Issue