627 lines
22 KiB
TypeScript
627 lines
22 KiB
TypeScript
import type { Member, MembershipStatus, MemberResponse, NocoDBSettings } from '~/utils/types';
|
|
|
|
// Data normalization functions
|
|
export const normalizePersonName = (name: string): string => {
|
|
if (!name) return 'Unknown';
|
|
|
|
// Trim whitespace and normalize case
|
|
return name.trim()
|
|
.split(' ')
|
|
.map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
|
|
.join(' ');
|
|
};
|
|
|
|
// Pagination interface
|
|
export interface PageInfo {
|
|
pageSize: number;
|
|
totalRows: number;
|
|
isFirstPage: boolean;
|
|
isLastPage: boolean;
|
|
page: number;
|
|
}
|
|
|
|
// Response interfaces
|
|
export interface EntityResponse<T> {
|
|
list: T[];
|
|
PageInfo: PageInfo;
|
|
}
|
|
|
|
// Dynamic table ID getter - will use configured table ID from admin panel
|
|
export const getTableId = (tableName: 'Members'): string => {
|
|
// Try to get table ID from global configuration first
|
|
if (globalNocoDBConfig?.tables && tableName === 'Members') {
|
|
const tableId = globalNocoDBConfig.tables['members'] || globalNocoDBConfig.tables['Members'];
|
|
if (tableId) {
|
|
console.log(`[nocodb] Using global table ID for ${tableName}:`, tableId);
|
|
return tableId;
|
|
}
|
|
}
|
|
|
|
// Fallback to default
|
|
const defaultTableId = 'members-table-id';
|
|
console.log(`[nocodb] Using fallback table ID for ${tableName}:`, defaultTableId);
|
|
return defaultTableId;
|
|
};
|
|
|
|
// Table ID enumeration - Replace with your actual table IDs
|
|
export enum Table {
|
|
Members = "members-table-id", // Will be configured via admin panel
|
|
}
|
|
|
|
/**
|
|
* Convert date from DD-MM-YYYY format to YYYY-MM-DD format for PostgreSQL
|
|
*/
|
|
const convertDateFormat = (dateString: string): string => {
|
|
if (!dateString) return dateString;
|
|
|
|
// If it's already in ISO format or contains 'T', return as is
|
|
if (dateString.includes('T') || dateString.match(/^\d{4}-\d{2}-\d{2}/)) {
|
|
return dateString;
|
|
}
|
|
|
|
// Handle DD-MM-YYYY format
|
|
const ddmmyyyyMatch = dateString.match(/^(\d{1,2})-(\d{1,2})-(\d{4})$/);
|
|
if (ddmmyyyyMatch) {
|
|
const [, day, month, year] = ddmmyyyyMatch;
|
|
const convertedDate = `${year}-${month.padStart(2, '0')}-${day.padStart(2, '0')}`;
|
|
console.log(`[convertDateFormat] Converted ${dateString} to ${convertedDate}`);
|
|
return convertedDate;
|
|
}
|
|
|
|
// Handle DD/MM/YYYY format
|
|
const ddmmyyyySlashMatch = dateString.match(/^(\d{1,2})\/(\d{1,2})\/(\d{4})$/);
|
|
if (ddmmyyyySlashMatch) {
|
|
const [, day, month, year] = ddmmyyyySlashMatch;
|
|
const convertedDate = `${year}-${month.padStart(2, '0')}-${day.padStart(2, '0')}`;
|
|
console.log(`[convertDateFormat] Converted ${dateString} to ${convertedDate}`);
|
|
return convertedDate;
|
|
}
|
|
|
|
console.warn(`[convertDateFormat] Could not parse date format: ${dateString}`);
|
|
return dateString;
|
|
};
|
|
|
|
// String data handling functions
|
|
export const parseStringBoolean = (value: string): boolean => {
|
|
return value === 'true';
|
|
};
|
|
|
|
export const formatBooleanAsString = (value: boolean): string => {
|
|
return value ? 'true' : 'false';
|
|
};
|
|
|
|
export const parseNationalities = (nationalityString: string): string[] => {
|
|
return nationalityString ? nationalityString.split(',').map(n => n.trim()).filter(n => n.length > 0) : [];
|
|
};
|
|
|
|
export const formatNationalitiesAsString = (nationalities: string[]): string => {
|
|
return nationalities.filter(n => n && n.trim()).join(',');
|
|
};
|
|
|
|
// Field normalization functions
|
|
export const normalizeFieldsFromNocoDB = (data: any): Member => {
|
|
console.log('[normalizeFieldsFromNocoDB] Input data keys:', Object.keys(data));
|
|
|
|
const normalized: any = { ...data };
|
|
|
|
// Field mapping for display names to snake_case (READ operations)
|
|
const readFieldMap: Record<string, string> = {
|
|
'First Name': 'first_name',
|
|
'Last Name': 'last_name',
|
|
'Email': 'email',
|
|
'Email Address': 'email',
|
|
'Phone': 'phone',
|
|
'Phone Number': 'phone',
|
|
'Date of Birth': 'date_of_birth',
|
|
'Nationality': 'nationality',
|
|
'Address': 'address',
|
|
'Membership Status': 'membership_status',
|
|
'Member Since': 'member_since',
|
|
'Member ID': 'member_id', // Added field mapping for member_id
|
|
'Current Year Dues Paid': 'current_year_dues_paid',
|
|
'Membership Date Paid': 'membership_date_paid',
|
|
'Payment Due Date': 'payment_due_date',
|
|
'Keycloak ID': 'keycloak_id',
|
|
'keycloak_id': 'keycloak_id',
|
|
// Also handle reverse mapping in case data comes in snake_case already
|
|
'first_name': 'first_name',
|
|
'last_name': 'last_name',
|
|
'email': 'email',
|
|
'phone': 'phone',
|
|
'date_of_birth': 'date_of_birth',
|
|
'nationality': 'nationality',
|
|
'address': 'address',
|
|
'membership_status': 'membership_status',
|
|
'member_since': 'member_since',
|
|
'member_id': 'member_id',
|
|
'current_year_dues_paid': 'current_year_dues_paid',
|
|
'membership_date_paid': 'membership_date_paid',
|
|
'payment_due_date': 'payment_due_date'
|
|
};
|
|
|
|
// Apply field mapping
|
|
for (const [sourceKey, targetKey] of Object.entries(readFieldMap)) {
|
|
if (sourceKey in data && data[sourceKey] !== undefined && data[sourceKey] !== null) {
|
|
normalized[targetKey] = data[sourceKey];
|
|
console.log(`[normalizeFieldsFromNocoDB] Mapped "${sourceKey}" -> "${targetKey}":`, data[sourceKey]);
|
|
}
|
|
}
|
|
|
|
// Ensure required fields exist with fallbacks
|
|
normalized.first_name = normalized.first_name || normalized['First Name'] || '';
|
|
normalized.last_name = normalized.last_name || normalized['Last Name'] || '';
|
|
normalized.email = normalized.email || normalized['Email'] || normalized['Email Address'] || '';
|
|
|
|
console.log('[normalizeFieldsFromNocoDB] Normalized member fields:', Object.keys(normalized));
|
|
console.log('[normalizeFieldsFromNocoDB] Final first_name:', normalized.first_name);
|
|
console.log('[normalizeFieldsFromNocoDB] Final last_name:', normalized.last_name);
|
|
|
|
return normalized as Member;
|
|
};
|
|
|
|
export const normalizeFieldsForNocoDB = (data: any): Record<string, any> => {
|
|
console.log('[normalizeFieldsForNocoDB] Input data keys:', Object.keys(data));
|
|
|
|
// Field mapping for snake_case to display names (WRITE operations)
|
|
const writeFieldMap: Record<string, string> = {
|
|
'first_name': 'First Name',
|
|
'last_name': 'Last Name',
|
|
'email': 'Email',
|
|
'phone': 'Phone',
|
|
'date_of_birth': 'Date of Birth',
|
|
'nationality': 'Nationality',
|
|
'address': 'Address',
|
|
'membership_status': 'Membership Status',
|
|
'member_since': 'Member Since',
|
|
'member_id': 'Member ID', // Added field mapping for member_id
|
|
'current_year_dues_paid': 'Current Year Dues Paid',
|
|
'membership_date_paid': 'Membership Date Paid',
|
|
'payment_due_date': 'Payment Due Date',
|
|
'keycloak_id': 'Keycloak ID'
|
|
};
|
|
|
|
const normalized: any = {};
|
|
|
|
// First, try direct mapping using write field map
|
|
for (const [sourceKey, targetKey] of Object.entries(writeFieldMap)) {
|
|
if (sourceKey in data && data[sourceKey] !== undefined) {
|
|
normalized[targetKey] = data[sourceKey];
|
|
console.log(`[normalizeFieldsForNocoDB] Mapped "${sourceKey}" -> "${targetKey}":`, data[sourceKey]);
|
|
}
|
|
}
|
|
|
|
// If no mappings worked, try to use the data as-is (fallback)
|
|
if (Object.keys(normalized).length === 0) {
|
|
console.log('[normalizeFieldsForNocoDB] No field mappings applied, using original data');
|
|
return { ...data };
|
|
}
|
|
|
|
console.log('[normalizeFieldsForNocoDB] Final normalized keys:', Object.keys(normalized));
|
|
return normalized;
|
|
};
|
|
|
|
// Global variable to store effective configuration
|
|
let globalNocoDBConfig: any = null;
|
|
|
|
// Function to set the global configuration (called by the admin-config system)
|
|
export const setGlobalNocoDBConfig = (config: any) => {
|
|
globalNocoDBConfig = config;
|
|
console.log('[nocodb] Global configuration updated:', config.url);
|
|
};
|
|
|
|
export const getNocoDbConfiguration = () => {
|
|
let configToUse: any = null;
|
|
|
|
// 1. PRIORITY: Check for environment variables first (container-friendly)
|
|
const envConfig = {
|
|
url: process.env.NUXT_NOCODB_URL || process.env.NOCODB_URL,
|
|
token: process.env.NUXT_NOCODB_TOKEN || process.env.NOCODB_TOKEN || process.env.NOCODB_API_TOKEN,
|
|
baseId: process.env.NUXT_NOCODB_BASE_ID || process.env.NOCODB_BASE_ID
|
|
};
|
|
|
|
if (envConfig.url && envConfig.token) {
|
|
console.log('[nocodb] ✅ Using environment variables - URL:', envConfig.url);
|
|
configToUse = envConfig;
|
|
}
|
|
// 2. FALLBACK: Try to use the global admin panel configuration
|
|
else if (globalNocoDBConfig) {
|
|
console.log('[nocodb] Using admin panel configuration - URL:', globalNocoDBConfig.url);
|
|
configToUse = {
|
|
url: globalNocoDBConfig.url,
|
|
token: globalNocoDBConfig.token,
|
|
baseId: globalNocoDBConfig.baseId
|
|
};
|
|
}
|
|
// 3. LAST RESORT: Runtime config
|
|
else {
|
|
console.log('[nocodb] ⚠️ Using fallback runtime config');
|
|
const config = useRuntimeConfig().nocodb;
|
|
configToUse = {
|
|
...config,
|
|
url: config.url || 'https://database.monacousa.org'
|
|
};
|
|
console.log('[nocodb] Fallback configuration URL:', configToUse.url);
|
|
}
|
|
|
|
// Validate configuration completeness
|
|
if (!configToUse.url || !configToUse.token) {
|
|
console.error('[nocodb] ❌ CRITICAL ERROR: Incomplete NocoDB configuration!');
|
|
console.error('[nocodb] URL:', configToUse.url ? 'SET' : 'MISSING');
|
|
console.error('[nocodb] Token:', configToUse.token ? 'SET' : 'MISSING');
|
|
console.error('[nocodb] Set environment variables: NUXT_NOCODB_URL, NUXT_NOCODB_TOKEN');
|
|
throw createError({
|
|
statusCode: 500,
|
|
statusMessage: 'NocoDB configuration incomplete. Set NUXT_NOCODB_URL and NUXT_NOCODB_TOKEN environment variables.'
|
|
});
|
|
}
|
|
|
|
// Validate API token before using it
|
|
const token = configToUse.token.trim();
|
|
|
|
// Check for non-ASCII characters that would cause ByteString errors
|
|
if (!/^[\x00-\xFF]*$/.test(token)) {
|
|
console.error('[nocodb] ❌ CRITICAL ERROR: API token contains invalid Unicode characters!');
|
|
console.error('[nocodb] This will cause ByteString conversion errors in HTTP headers.');
|
|
throw createError({
|
|
statusCode: 500,
|
|
statusMessage: 'NocoDB API token contains invalid characters. Please set a valid NUXT_NOCODB_TOKEN environment variable.'
|
|
});
|
|
}
|
|
|
|
// Additional validation for common token issues
|
|
if (token.includes('•') || token.includes('…') || token.includes('"') || token.includes('"')) {
|
|
console.error('[nocodb] ❌ CRITICAL ERROR: API token contains formatting characters!');
|
|
console.error('[nocodb] Found characters like bullets (•), quotes, etc. that break HTTP headers.');
|
|
throw createError({
|
|
statusCode: 500,
|
|
statusMessage: 'NocoDB API token contains formatting characters. Please set a clean NUXT_NOCODB_TOKEN environment variable.'
|
|
});
|
|
}
|
|
|
|
console.log('[nocodb] ✅ Configuration validated successfully');
|
|
return configToUse;
|
|
};
|
|
|
|
export const createTableUrl = (table: Table | string) => {
|
|
let tableId: string;
|
|
|
|
if (table === Table.Members || table === 'Members') {
|
|
tableId = getTableId('Members');
|
|
} else {
|
|
tableId = table.toString();
|
|
}
|
|
|
|
const url = `${getNocoDbConfiguration().url}/api/v2/tables/${tableId}/records`;
|
|
console.log('[nocodb] Table URL:', url);
|
|
return url;
|
|
};
|
|
|
|
// CRUD operations for Members table
|
|
export const getMembers = async (): Promise<EntityResponse<Member>> => {
|
|
console.log('[nocodb.getMembers] Fetching all members...');
|
|
const startTime = Date.now();
|
|
|
|
try {
|
|
const result = await $fetch<EntityResponse<Member>>(createTableUrl(Table.Members), {
|
|
headers: {
|
|
"xc-token": getNocoDbConfiguration().token,
|
|
},
|
|
params: {
|
|
limit: 1000,
|
|
},
|
|
});
|
|
|
|
console.log('[nocodb.getMembers] Successfully fetched members, count:', result.list?.length || 0);
|
|
console.log('[nocodb.getMembers] Request duration:', Date.now() - startTime, 'ms');
|
|
|
|
// DIAGNOSTIC: Log raw member data structure to identify schema issues
|
|
if (result.list && result.list.length > 0) {
|
|
const sampleMember = result.list[0];
|
|
console.log('[nocodb.getMembers] DIAGNOSTIC - Raw member fields from NocoDB:', Object.keys(sampleMember));
|
|
console.log('[nocodb.getMembers] DIAGNOSTIC - Sample member data:', JSON.stringify(sampleMember, null, 2));
|
|
console.log('[nocodb.getMembers] DIAGNOSTIC - first_name value:', sampleMember.first_name);
|
|
console.log('[nocodb.getMembers] DIAGNOSTIC - last_name value:', sampleMember.last_name);
|
|
console.log('[nocodb.getMembers] DIAGNOSTIC - First Name value:', (sampleMember as any)['First Name']);
|
|
console.log('[nocodb.getMembers] DIAGNOSTIC - Last Name value:', (sampleMember as any)['Last Name']);
|
|
}
|
|
|
|
return result;
|
|
} catch (error: any) {
|
|
console.error('[nocodb.getMembers] Error fetching members:', error);
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
export const getMemberById = async (id: string): Promise<Member> => {
|
|
console.log('[nocodb.getMemberById] Fetching member ID:', id);
|
|
|
|
const result = await $fetch<Member>(`${createTableUrl(Table.Members)}/${id}`, {
|
|
headers: {
|
|
"xc-token": getNocoDbConfiguration().token,
|
|
},
|
|
});
|
|
|
|
console.log('[nocodb.getMemberById] Successfully retrieved member:', result.Id);
|
|
return result;
|
|
};
|
|
|
|
export const getMemberByKeycloakId = async (keycloakId: string): Promise<Member | null> => {
|
|
console.log('[nocodb.getMemberByKeycloakId] Searching for member with Keycloak ID:', keycloakId);
|
|
|
|
try {
|
|
// Use the where parameter to search by keycloak_id
|
|
const result = await $fetch<EntityResponse<Member>>(createTableUrl(Table.Members), {
|
|
headers: {
|
|
"xc-token": getNocoDbConfiguration().token,
|
|
},
|
|
params: {
|
|
where: `(keycloak_id,eq,${keycloakId})`,
|
|
limit: 1,
|
|
},
|
|
});
|
|
|
|
if (result.list && result.list.length > 0) {
|
|
const member = normalizeFieldsFromNocoDB(result.list[0]);
|
|
console.log('[nocodb.getMemberByKeycloakId] Found member:', member.member_id || member.Id);
|
|
return member;
|
|
}
|
|
|
|
console.log('[nocodb.getMemberByKeycloakId] No member found with Keycloak ID:', keycloakId);
|
|
return null;
|
|
} catch (error: any) {
|
|
console.error('[nocodb.getMemberByKeycloakId] Error searching for member:', error);
|
|
// Return null instead of throwing to allow graceful handling
|
|
return null;
|
|
}
|
|
};
|
|
|
|
export const createMember = async (data: Partial<Member>): Promise<Member> => {
|
|
console.log('[nocodb.createMember] Creating member with fields:', Object.keys(data));
|
|
|
|
// Create a clean data object that matches the member schema
|
|
const cleanData: Record<string, any> = {};
|
|
|
|
// Only include fields that are part of the member schema
|
|
const allowedFields = [
|
|
"first_name",
|
|
"last_name",
|
|
"email",
|
|
"phone",
|
|
"current_year_dues_paid",
|
|
"nationality",
|
|
"date_of_birth",
|
|
"membership_date_paid",
|
|
"payment_due_date",
|
|
"membership_status",
|
|
"address",
|
|
"member_since",
|
|
"member_id",
|
|
"keycloak_id"
|
|
];
|
|
|
|
// Filter the data to only include allowed fields
|
|
for (const field of allowedFields) {
|
|
if (field in data) {
|
|
cleanData[field] = (data as any)[field];
|
|
}
|
|
}
|
|
|
|
// Remove any computed or relation fields that shouldn't be sent
|
|
delete cleanData.Id;
|
|
delete cleanData.CreatedAt;
|
|
delete cleanData.UpdatedAt;
|
|
delete cleanData.FullName;
|
|
delete cleanData.FormattedPhone;
|
|
|
|
// Fix date formatting for PostgreSQL
|
|
if (cleanData['date_of_birth']) {
|
|
cleanData['date_of_birth'] = convertDateFormat(cleanData['date_of_birth']);
|
|
}
|
|
if (cleanData['membership_date_paid']) {
|
|
cleanData['membership_date_paid'] = convertDateFormat(cleanData['membership_date_paid']);
|
|
}
|
|
if (cleanData['payment_due_date']) {
|
|
cleanData['payment_due_date'] = convertDateFormat(cleanData['payment_due_date']);
|
|
}
|
|
|
|
console.log('[nocodb.createMember] Clean data fields:', Object.keys(cleanData));
|
|
const url = createTableUrl(Table.Members);
|
|
|
|
try {
|
|
const result = await $fetch<Member>(url, {
|
|
method: "POST",
|
|
headers: {
|
|
"xc-token": getNocoDbConfiguration().token,
|
|
},
|
|
body: cleanData,
|
|
});
|
|
console.log('[nocodb.createMember] Created member with ID:', result.Id);
|
|
return result;
|
|
} catch (error) {
|
|
console.error('[nocodb.createMember] Create failed:', error);
|
|
console.error('[nocodb.createMember] Error details:', error instanceof Error ? error.message : 'Unknown error');
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
export const updateMember = async (id: string, data: Partial<Member>, retryCount = 0): Promise<Member> => {
|
|
console.log('[nocodb.updateMember] Updating member:', id, 'Retry:', retryCount);
|
|
console.log('[nocodb.updateMember] Data fields:', Object.keys(data));
|
|
|
|
// First, try to verify the record exists
|
|
if (retryCount === 0) {
|
|
try {
|
|
console.log('[nocodb.updateMember] Verifying record exists...');
|
|
const existingRecord = await getMemberById(id);
|
|
console.log('[nocodb.updateMember] Record exists with ID:', existingRecord.Id);
|
|
} catch (verifyError: any) {
|
|
console.error('[nocodb.updateMember] Failed to verify record:', verifyError);
|
|
if (verifyError.statusCode === 404 || verifyError.status === 404) {
|
|
console.error('[nocodb.updateMember] Record verification failed - record not found');
|
|
}
|
|
}
|
|
}
|
|
|
|
// Create a clean data object
|
|
const cleanData: Record<string, any> = {};
|
|
|
|
// Only include fields that are part of the member schema
|
|
const allowedFields = [
|
|
"first_name",
|
|
"last_name",
|
|
"email",
|
|
"phone",
|
|
"current_year_dues_paid",
|
|
"nationality",
|
|
"date_of_birth",
|
|
"membership_date_paid",
|
|
"payment_due_date",
|
|
"membership_status",
|
|
"address",
|
|
"member_since",
|
|
"keycloak_id"
|
|
];
|
|
|
|
// 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.updateMember] Converting undefined to null for field: ${field}`);
|
|
} else {
|
|
cleanData[field] = value;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Fix date formatting for PostgreSQL
|
|
if (cleanData['date_of_birth']) {
|
|
cleanData['date_of_birth'] = convertDateFormat(cleanData['date_of_birth']);
|
|
}
|
|
if (cleanData['membership_date_paid']) {
|
|
cleanData['membership_date_paid'] = convertDateFormat(cleanData['membership_date_paid']);
|
|
}
|
|
if (cleanData['payment_due_date']) {
|
|
cleanData['payment_due_date'] = convertDateFormat(cleanData['payment_due_date']);
|
|
}
|
|
|
|
console.log('[nocodb.updateMember] Clean data fields:', Object.keys(cleanData));
|
|
|
|
// PATCH requires ID in the body (not in URL)
|
|
cleanData.Id = parseInt(id);
|
|
|
|
const url = createTableUrl(Table.Members);
|
|
|
|
try {
|
|
console.log('[nocodb.updateMember] Sending PATCH request');
|
|
|
|
const result = await $fetch<Member>(url, {
|
|
method: "PATCH",
|
|
headers: {
|
|
"xc-token": getNocoDbConfiguration().token,
|
|
"Content-Type": "application/json"
|
|
},
|
|
body: cleanData
|
|
});
|
|
console.log('[nocodb.updateMember] Update successful for ID:', id);
|
|
return result;
|
|
} catch (error: any) {
|
|
console.error('[nocodb.updateMember] Update failed:', error);
|
|
console.error('[nocodb.updateMember] Error details:', error instanceof Error ? error.message : 'Unknown error');
|
|
|
|
// If it's a 404 error and we haven't retried too many times, wait and retry
|
|
if ((error.statusCode === 404 || error.status === 404) && retryCount < 3) {
|
|
console.error('[nocodb.updateMember] 404 Error - Record not found. This might be a sync delay.');
|
|
console.error(`Retrying in ${(retryCount + 1) * 1000}ms... (Attempt ${retryCount + 1}/3)`);
|
|
|
|
// Wait with exponential backoff
|
|
await new Promise(resolve => setTimeout(resolve, (retryCount + 1) * 1000));
|
|
|
|
// Retry the update
|
|
return updateMember(id, data, retryCount + 1);
|
|
}
|
|
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
export const deleteMember = async (id: string) => {
|
|
const startTime = Date.now();
|
|
console.log('[nocodb.deleteMember] =========================');
|
|
console.log('[nocodb.deleteMember] DELETE operation started at:', new Date().toISOString());
|
|
console.log('[nocodb.deleteMember] Target ID:', id);
|
|
|
|
const url = createTableUrl(Table.Members);
|
|
console.log('[nocodb.deleteMember] URL:', url);
|
|
|
|
const requestBody = {
|
|
"Id": parseInt(id)
|
|
};
|
|
|
|
console.log('[nocodb.deleteMember] Request configuration:');
|
|
console.log(' Method: DELETE');
|
|
console.log(' URL:', url);
|
|
console.log(' Body:', JSON.stringify(requestBody, null, 2));
|
|
|
|
try {
|
|
const result = await $fetch(url, {
|
|
method: "DELETE",
|
|
headers: {
|
|
"xc-token": getNocoDbConfiguration().token,
|
|
"Content-Type": "application/json"
|
|
},
|
|
body: requestBody
|
|
});
|
|
|
|
console.log('[nocodb.deleteMember] DELETE successful');
|
|
console.log('[nocodb.deleteMember] Duration:', Date.now() - startTime, 'ms');
|
|
|
|
return result;
|
|
} catch (error: any) {
|
|
console.error('[nocodb.deleteMember] DELETE FAILED');
|
|
console.error('[nocodb.deleteMember] Error type:', error.constructor.name);
|
|
console.error('[nocodb.deleteMember] Error message:', error.message);
|
|
console.error('[nocodb.deleteMember] Duration:', Date.now() - startTime, 'ms');
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
// Centralized error handling
|
|
export const handleNocoDbError = (error: any, operation: string, entityType: string = 'Member') => {
|
|
console.error(`[nocodb.${operation}] =========================`);
|
|
console.error(`[nocodb.${operation}] ERROR in ${operation} for ${entityType}`);
|
|
console.error(`[nocodb.${operation}] Error type:`, error.constructor?.name || 'Unknown');
|
|
console.error(`[nocodb.${operation}] Error status:`, error.statusCode || error.status || 'Unknown');
|
|
console.error(`[nocodb.${operation}] Error message:`, error.message || 'Unknown error');
|
|
console.error(`[nocodb.${operation}] Error data:`, error.data);
|
|
console.error(`[nocodb.${operation}] =========================`);
|
|
|
|
// Provide more specific error messages
|
|
if (error.statusCode === 401 || error.status === 401) {
|
|
throw createError({
|
|
statusCode: 401,
|
|
statusMessage: `Authentication failed when accessing ${entityType}. Please check your access permissions.`
|
|
});
|
|
} else if (error.statusCode === 403 || error.status === 403) {
|
|
throw createError({
|
|
statusCode: 403,
|
|
statusMessage: `Access denied to ${entityType}. This feature requires appropriate privileges.`
|
|
});
|
|
} else if (error.statusCode === 404 || error.status === 404) {
|
|
throw createError({
|
|
statusCode: 404,
|
|
statusMessage: `${entityType} not found. Please verify the record exists.`
|
|
});
|
|
} else if (error.code === 'NETWORK_ERROR' || error.code === 'TIMEOUT') {
|
|
throw createError({
|
|
statusCode: 503,
|
|
statusMessage: `${entityType} database is temporarily unavailable. Please try again in a moment.`
|
|
});
|
|
}
|
|
|
|
throw error;
|
|
};
|