Replace date-fns with native date formatting and remove unused code
All checks were successful
Build And Push Image / docker (push) Successful in 1m34s
All checks were successful
Build And Push Image / docker (push) Successful in 1m34s
Remove date-fns dependency in favor of native Intl.DateTimeFormat APIs, clean up obsolete admin endpoints, utility files, and archived documentation. Consolidate docs structure and remove unused plugins.
This commit is contained in:
@@ -1,104 +0,0 @@
|
||||
// server/api/admin/backfill-event-ids.post.ts
|
||||
export default defineEventHandler(async (event) => {
|
||||
console.log('[admin/backfill-event-ids] Starting event_id backfill process...');
|
||||
|
||||
try {
|
||||
// Verify admin access
|
||||
const sessionManager = createSessionManager();
|
||||
const cookieHeader = getHeader(event, 'cookie');
|
||||
const session = sessionManager.getSession(cookieHeader);
|
||||
|
||||
if (!session || session.user.tier !== 'admin') {
|
||||
throw createError({
|
||||
statusCode: 403,
|
||||
statusMessage: 'Admin access required'
|
||||
});
|
||||
}
|
||||
|
||||
console.log(`[admin/backfill-event-ids] Admin access verified for user: ${session.user.email}`);
|
||||
|
||||
const { createNocoDBEventsClient } = await import('~/server/utils/nocodb-events');
|
||||
const eventsClient = createNocoDBEventsClient();
|
||||
|
||||
// Get all events
|
||||
const response = await eventsClient.findAll({ limit: 1000 });
|
||||
const events = response.list || [];
|
||||
|
||||
console.log(`[admin/backfill-event-ids] Found ${events.length} events to process`);
|
||||
|
||||
const results = {
|
||||
processed: 0,
|
||||
updated: 0,
|
||||
skipped: 0,
|
||||
errors: 0,
|
||||
details: [] as any[]
|
||||
};
|
||||
|
||||
for (const eventItem of events) {
|
||||
results.processed++;
|
||||
const eventId = (eventItem as any).Id;
|
||||
|
||||
try {
|
||||
// Check if event_id already exists
|
||||
if (eventItem.event_id && eventItem.event_id.trim() !== '') {
|
||||
console.log(`[admin/backfill-event-ids] Event ${eventId} already has event_id: ${eventItem.event_id}`);
|
||||
results.skipped++;
|
||||
results.details.push({
|
||||
id: eventId,
|
||||
title: eventItem.title,
|
||||
status: 'skipped',
|
||||
reason: 'Already has event_id',
|
||||
existing_event_id: eventItem.event_id
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
// Generate unique event_id based on event date and title
|
||||
const eventDate = new Date(eventItem.start_datetime);
|
||||
const dateString = eventDate.toISOString().split('T')[0].replace(/-/g, ''); // YYYYMMDD
|
||||
const timeString = eventDate.toISOString().split('T')[1].split(':').slice(0,2).join(''); // HHMM
|
||||
const titleSlug = eventItem.title.toLowerCase().replace(/[^a-z0-9]/g, '').substring(0, 8);
|
||||
|
||||
const newEventId = `evt_${dateString}_${timeString}_${titleSlug}`;
|
||||
|
||||
console.log(`[admin/backfill-event-ids] Updating event ${eventId} (${eventItem.title}) with event_id: ${newEventId}`);
|
||||
|
||||
// Update the event with the new event_id
|
||||
await eventsClient.update(eventId.toString(), { event_id: newEventId });
|
||||
|
||||
results.updated++;
|
||||
results.details.push({
|
||||
id: eventId,
|
||||
title: eventItem.title,
|
||||
status: 'updated',
|
||||
new_event_id: newEventId
|
||||
});
|
||||
|
||||
} catch (updateError: any) {
|
||||
console.error(`[admin/backfill-event-ids] Error updating event ${eventId}:`, updateError);
|
||||
results.errors++;
|
||||
results.details.push({
|
||||
id: eventId,
|
||||
title: eventItem.title,
|
||||
status: 'error',
|
||||
error: updateError.message || 'Update failed'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`[admin/backfill-event-ids] Backfill completed. Processed: ${results.processed}, Updated: ${results.updated}, Skipped: ${results.skipped}, Errors: ${results.errors}`);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: `Event ID backfill completed successfully`,
|
||||
data: results
|
||||
};
|
||||
|
||||
} catch (error: any) {
|
||||
console.error('[admin/backfill-event-ids] Backfill process failed:', error);
|
||||
throw createError({
|
||||
statusCode: 500,
|
||||
statusMessage: error.message || 'Event ID backfill failed'
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -1,163 +0,0 @@
|
||||
import type { Member } from '~/utils/types';
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
console.log('[api/admin/cleanup-accounts.post] =========================');
|
||||
console.log('[api/admin/cleanup-accounts.post] POST /api/admin/cleanup-accounts - Account cleanup for expired members');
|
||||
|
||||
try {
|
||||
// Validate session and require admin privileges
|
||||
const sessionManager = createSessionManager();
|
||||
const cookieHeader = getCookie(event, 'monacousa-session') ? getHeader(event, 'cookie') : undefined;
|
||||
const session = sessionManager.getSession(cookieHeader);
|
||||
|
||||
if (!session?.user) {
|
||||
throw createError({
|
||||
statusCode: 401,
|
||||
statusMessage: 'Authentication required'
|
||||
});
|
||||
}
|
||||
|
||||
// Require admin privileges for account cleanup
|
||||
if (session.user.tier !== 'admin') {
|
||||
throw createError({
|
||||
statusCode: 403,
|
||||
statusMessage: 'Admin privileges required'
|
||||
});
|
||||
}
|
||||
|
||||
console.log('[api/admin/cleanup-accounts.post] Authorized admin:', session.user.email);
|
||||
|
||||
// Get cleanup options from request body (optional)
|
||||
const body = await readBody(event).catch(() => ({}));
|
||||
const dryRun = body?.dryRun === true;
|
||||
const monthsOverdue = body?.monthsOverdue || 3;
|
||||
|
||||
console.log('[api/admin/cleanup-accounts.post] Cleanup options:', { dryRun, monthsOverdue });
|
||||
|
||||
// Calculate cutoff date (default: 3 months ago)
|
||||
const cutoffDate = new Date();
|
||||
cutoffDate.setMonth(cutoffDate.getMonth() - monthsOverdue);
|
||||
|
||||
console.log('[api/admin/cleanup-accounts.post] Cutoff date:', cutoffDate.toISOString());
|
||||
|
||||
// Find members registered before cutoff date with unpaid dues
|
||||
const { getMembers } = await import('~/server/utils/nocodb');
|
||||
const membersResult = await getMembers();
|
||||
const allMembers = membersResult.list || [];
|
||||
|
||||
const expiredMembers = allMembers.filter((member: Member) => {
|
||||
// Must have a registration date
|
||||
if (!member.registration_date) return false;
|
||||
|
||||
// Must be registered before cutoff date
|
||||
const registrationDate = new Date(member.registration_date);
|
||||
if (registrationDate >= cutoffDate) return false;
|
||||
|
||||
// Must have unpaid dues
|
||||
if (member.current_year_dues_paid === 'true') return false;
|
||||
|
||||
// Must have a Keycloak ID (portal account)
|
||||
if (!member.keycloak_id) return false;
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
console.log('[api/admin/cleanup-accounts.post] Found', expiredMembers.length, 'expired members for cleanup');
|
||||
|
||||
const deletedAccounts = [];
|
||||
const failedDeletions = [];
|
||||
|
||||
if (!dryRun && expiredMembers.length > 0) {
|
||||
const { createKeycloakAdminClient } = await import('~/server/utils/keycloak-admin');
|
||||
const { deleteMember } = await import('~/server/utils/nocodb');
|
||||
const keycloakAdmin = createKeycloakAdminClient();
|
||||
|
||||
for (const member of expiredMembers) {
|
||||
try {
|
||||
console.log('[api/admin/cleanup-accounts.post] Processing cleanup for:', member.email);
|
||||
|
||||
// Delete from Keycloak first
|
||||
if (member.keycloak_id) {
|
||||
try {
|
||||
await keycloakAdmin.deleteUser(member.keycloak_id);
|
||||
console.log('[api/admin/cleanup-accounts.post] Deleted Keycloak user:', member.keycloak_id);
|
||||
} catch (keycloakError: any) {
|
||||
console.warn('[api/admin/cleanup-accounts.post] Failed to delete Keycloak user:', keycloakError.message);
|
||||
// Continue with member deletion even if Keycloak deletion fails
|
||||
}
|
||||
}
|
||||
|
||||
// Delete member record
|
||||
await deleteMember(member.Id);
|
||||
console.log('[api/admin/cleanup-accounts.post] Deleted member record:', member.Id);
|
||||
|
||||
deletedAccounts.push({
|
||||
id: member.Id,
|
||||
email: member.email,
|
||||
name: `${member.first_name} ${member.last_name}`,
|
||||
registrationDate: member.registration_date,
|
||||
keycloakId: member.keycloak_id
|
||||
});
|
||||
|
||||
} catch (error: any) {
|
||||
console.error('[api/admin/cleanup-accounts.post] Failed to delete account for', member.email, ':', error);
|
||||
failedDeletions.push({
|
||||
id: member.Id,
|
||||
email: member.email,
|
||||
name: `${member.first_name} ${member.last_name}`,
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const result = {
|
||||
success: true,
|
||||
dryRun,
|
||||
monthsOverdue,
|
||||
cutoffDate: cutoffDate.toISOString(),
|
||||
totalExpiredMembers: expiredMembers.length,
|
||||
deletedCount: deletedAccounts.length,
|
||||
failedCount: failedDeletions.length,
|
||||
message: dryRun
|
||||
? `Found ${expiredMembers.length} expired accounts that would be deleted (dry run)`
|
||||
: `Cleaned up ${deletedAccounts.length} expired accounts${failedDeletions.length > 0 ? ` (${failedDeletions.length} failed)` : ''}`,
|
||||
data: {
|
||||
expiredMembers: expiredMembers.map(m => ({
|
||||
id: m.Id,
|
||||
email: m.email,
|
||||
name: `${m.first_name} ${m.last_name}`,
|
||||
registrationDate: m.registration_date,
|
||||
daysSinceRegistration: Math.floor((Date.now() - new Date(m.registration_date || '').getTime()) / (1000 * 60 * 60 * 24)),
|
||||
hasKeycloakAccount: !!m.keycloak_id
|
||||
})),
|
||||
deleted: deletedAccounts,
|
||||
failed: failedDeletions
|
||||
}
|
||||
};
|
||||
|
||||
console.log('[api/admin/cleanup-accounts.post] ✅ Account cleanup completed');
|
||||
console.log('[api/admin/cleanup-accounts.post] Summary:', {
|
||||
found: expiredMembers.length,
|
||||
deleted: deletedAccounts.length,
|
||||
failed: failedDeletions.length,
|
||||
dryRun
|
||||
});
|
||||
|
||||
return result;
|
||||
|
||||
} catch (error: any) {
|
||||
console.error('[api/admin/cleanup-accounts.post] ❌ Account cleanup failed:', error);
|
||||
|
||||
// If it's already an HTTP error, re-throw it
|
||||
if (error.statusCode) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
// Otherwise, wrap it in a generic error
|
||||
throw createError({
|
||||
statusCode: 500,
|
||||
statusMessage: error.message || 'Account cleanup failed'
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -1,64 +0,0 @@
|
||||
// server/api/admin/debug-rsvp-config.get.ts
|
||||
import { createSessionManager } from '~/server/utils/session';
|
||||
import { getEffectiveNocoDBConfig } from '~/server/utils/admin-config';
|
||||
import { getEventTableId } from '~/server/utils/nocodb-events';
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
try {
|
||||
// Verify admin session
|
||||
const sessionManager = createSessionManager();
|
||||
const cookieHeader = getHeader(event, 'cookie');
|
||||
const session = sessionManager.getSession(cookieHeader);
|
||||
|
||||
if (!session?.user || session.user.tier !== 'admin') {
|
||||
throw createError({
|
||||
statusCode: 403,
|
||||
statusMessage: 'Admin access required'
|
||||
});
|
||||
}
|
||||
|
||||
console.log('[admin/debug-rsvp-config] =========================');
|
||||
console.log('[admin/debug-rsvp-config] 🔍 DEBUG: RSVP Configuration Analysis');
|
||||
|
||||
// Get effective config
|
||||
const effectiveConfig = getEffectiveNocoDBConfig();
|
||||
console.log('[admin/debug-rsvp-config] 🔍 Effective config:', JSON.stringify(effectiveConfig, null, 2));
|
||||
|
||||
// Test RSVP table ID retrieval
|
||||
const rsvpTableId = getEventTableId('EventRSVPs');
|
||||
console.log('[admin/debug-rsvp-config] 🔍 RSVP Table ID retrieved:', rsvpTableId);
|
||||
|
||||
// Test Events table ID retrieval
|
||||
const eventsTableId = getEventTableId('Events');
|
||||
console.log('[admin/debug-rsvp-config] 🔍 Events Table ID retrieved:', eventsTableId);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
debug: {
|
||||
effectiveConfig,
|
||||
rsvpTableId,
|
||||
eventsTableId,
|
||||
availableTableKeys: Object.keys(effectiveConfig?.tables || {}),
|
||||
rsvpTableInConfig: {
|
||||
'rsvps': effectiveConfig?.tables?.['rsvps'],
|
||||
'event_rsvps': effectiveConfig?.tables?.['event_rsvps'],
|
||||
'EventRSVPs': effectiveConfig?.tables?.['EventRSVPs'],
|
||||
'rsvp_table': effectiveConfig?.tables?.['rsvp_table'],
|
||||
'RSVPs': effectiveConfig?.tables?.['RSVPs']
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} catch (error: any) {
|
||||
console.error('[admin/debug-rsvp-config] ❌ Error:', error);
|
||||
|
||||
if (error.statusCode) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
throw createError({
|
||||
statusCode: 500,
|
||||
statusMessage: 'Failed to debug RSVP configuration'
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -1,96 +0,0 @@
|
||||
// server/api/admin/fix-event-attendee-counts.post.ts
|
||||
import { createSessionManager } from '~/server/utils/session';
|
||||
import { createNocoDBEventsClient } from '~/server/utils/nocodb-events';
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
try {
|
||||
console.log('[admin/fix-event-attendee-counts] Starting attendee count fix...');
|
||||
|
||||
// Verify admin session
|
||||
const sessionManager = createSessionManager();
|
||||
const cookieHeader = getHeader(event, 'cookie');
|
||||
const session = sessionManager.getSession(cookieHeader);
|
||||
|
||||
if (!session?.user || session.user.tier !== 'admin') {
|
||||
throw createError({
|
||||
statusCode: 403,
|
||||
statusMessage: 'Admin access required'
|
||||
});
|
||||
}
|
||||
|
||||
console.log('[admin/fix-event-attendee-counts] Authorized admin user:', session.user.email);
|
||||
|
||||
const eventsClient = createNocoDBEventsClient();
|
||||
|
||||
// Get all events
|
||||
const eventsResponse = await eventsClient.findAll({ limit: 1000 });
|
||||
const events = eventsResponse.list || [];
|
||||
|
||||
console.log('[admin/fix-event-attendee-counts] Found', events.length, 'events to process');
|
||||
|
||||
const results = [];
|
||||
|
||||
// Process each event
|
||||
for (const eventObj of events) {
|
||||
try {
|
||||
const eventId = eventObj.event_id || eventObj.id || (eventObj as any).Id;
|
||||
const eventTitle = eventObj.title || 'Unknown Event';
|
||||
|
||||
console.log('[admin/fix-event-attendee-counts] Processing event:', eventTitle, 'ID:', eventId);
|
||||
|
||||
// Force update the attendee count
|
||||
const newCount = await eventsClient.forceUpdateEventAttendeeCount(eventId.toString());
|
||||
|
||||
results.push({
|
||||
event_id: eventId,
|
||||
title: eventTitle,
|
||||
old_count: eventObj.current_attendees || 0,
|
||||
new_count: newCount,
|
||||
status: 'success'
|
||||
});
|
||||
|
||||
console.log('[admin/fix-event-attendee-counts] ✅ Fixed event:', eventTitle, 'Count:', newCount);
|
||||
|
||||
} catch (eventError: any) {
|
||||
console.error('[admin/fix-event-attendee-counts] ❌ Error processing event:', eventObj.title, eventError);
|
||||
|
||||
results.push({
|
||||
event_id: eventObj.event_id || eventObj.id,
|
||||
title: eventObj.title || 'Unknown Event',
|
||||
old_count: eventObj.current_attendees || 0,
|
||||
new_count: 0,
|
||||
status: 'error',
|
||||
error: eventError.message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const successCount = results.filter(r => r.status === 'success').length;
|
||||
const errorCount = results.filter(r => r.status === 'error').length;
|
||||
|
||||
console.log('[admin/fix-event-attendee-counts] ✅ Fix completed. Success:', successCount, 'Errors:', errorCount);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: `Fixed attendee counts for ${successCount} events (${errorCount} errors)`,
|
||||
data: {
|
||||
total_events: events.length,
|
||||
success_count: successCount,
|
||||
error_count: errorCount,
|
||||
results: results
|
||||
}
|
||||
};
|
||||
|
||||
} catch (error: any) {
|
||||
console.error('[admin/fix-event-attendee-counts] ❌ Error:', error);
|
||||
|
||||
if (error.statusCode) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
throw createError({
|
||||
statusCode: 500,
|
||||
statusMessage: 'Failed to fix event attendee counts'
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -1,206 +0,0 @@
|
||||
import { getNocoDbConfiguration } from '~/server/utils/nocodb';
|
||||
import { createSessionManager } from '~/server/utils/session';
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
console.log('[api/admin/membership-status-fix] POST /api/admin/membership-status-fix');
|
||||
|
||||
try {
|
||||
// Validate session and require admin privileges
|
||||
const sessionManager = createSessionManager();
|
||||
const cookieHeader = getCookie(event, 'monacousa-session') ? getHeader(event, 'cookie') : undefined;
|
||||
const session = sessionManager.getSession(cookieHeader);
|
||||
|
||||
if (!session?.user) {
|
||||
throw createError({
|
||||
statusCode: 401,
|
||||
statusMessage: 'Authentication required'
|
||||
});
|
||||
}
|
||||
|
||||
if (session.user.tier !== 'admin') {
|
||||
throw createError({
|
||||
statusCode: 403,
|
||||
statusMessage: 'Admin privileges required'
|
||||
});
|
||||
}
|
||||
|
||||
console.log('[api/admin/membership-status-fix] Authorized admin:', session.user.email);
|
||||
|
||||
const body = await readBody(event);
|
||||
const { action, tableId } = body;
|
||||
|
||||
const config = getNocoDbConfiguration();
|
||||
|
||||
if (action === 'check') {
|
||||
return await checkMembershipStatusField(config, tableId);
|
||||
} else if (action === 'fix') {
|
||||
return await fixMembershipStatusField(config, tableId);
|
||||
} else {
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: 'Invalid action. Use "check" or "fix"'
|
||||
});
|
||||
}
|
||||
|
||||
} catch (error: any) {
|
||||
console.error('[api/admin/membership-status-fix] Error:', error);
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
|
||||
async function checkMembershipStatusField(config: any, tableId: string) {
|
||||
console.log('[checkMembershipStatusField] Checking membership status field configuration');
|
||||
|
||||
try {
|
||||
// Get table schema to check the membership_status field configuration
|
||||
const tableSchema = await $fetch<any>(`${config.url}/api/v2/tables/${tableId}`, {
|
||||
headers: {
|
||||
"xc-token": config.token,
|
||||
}
|
||||
});
|
||||
|
||||
console.log('[checkMembershipStatusField] Table schema fetched');
|
||||
|
||||
// Find the membership_status field
|
||||
const membershipStatusField = tableSchema.columns?.find((col: any) =>
|
||||
col.column_name === 'membership_status' || col.title === 'Membership Status'
|
||||
);
|
||||
|
||||
if (!membershipStatusField) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Membership Status field not found in table schema',
|
||||
tableSchema: tableSchema.columns?.map((col: any) => ({
|
||||
name: col.column_name,
|
||||
title: col.title,
|
||||
uidt: col.uidt
|
||||
})) || []
|
||||
};
|
||||
}
|
||||
|
||||
console.log('[checkMembershipStatusField] Membership Status field found:', {
|
||||
column_name: membershipStatusField.column_name,
|
||||
title: membershipStatusField.title,
|
||||
uidt: membershipStatusField.uidt,
|
||||
dtxp: membershipStatusField.dtxp
|
||||
});
|
||||
|
||||
// Check if it's a Single Select field and what options are available
|
||||
if (membershipStatusField.uidt === 'SingleSelect') {
|
||||
const options = membershipStatusField.colOptions?.options || [];
|
||||
const allowedValues = options.map((opt: any) => opt.title);
|
||||
|
||||
const requiredValues = ['Active', 'Inactive', 'Pending', 'Expired'];
|
||||
const missingValues = requiredValues.filter(val => !allowedValues.includes(val));
|
||||
|
||||
return {
|
||||
success: true,
|
||||
fieldType: 'SingleSelect',
|
||||
currentOptions: allowedValues,
|
||||
requiredOptions: requiredValues,
|
||||
missingOptions: missingValues,
|
||||
needsFix: missingValues.length > 0,
|
||||
fieldId: membershipStatusField.id,
|
||||
message: missingValues.length > 0
|
||||
? `Missing options: ${missingValues.join(', ')}`
|
||||
: 'All required options are present'
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
success: true,
|
||||
fieldType: membershipStatusField.uidt,
|
||||
needsFix: false,
|
||||
message: `Field type is ${membershipStatusField.uidt}, not SingleSelect. This should work fine.`
|
||||
};
|
||||
}
|
||||
|
||||
} catch (error: any) {
|
||||
console.error('[checkMembershipStatusField] Error:', error);
|
||||
return {
|
||||
success: false,
|
||||
error: error.message || 'Failed to check field configuration'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async function fixMembershipStatusField(config: any, tableId: string) {
|
||||
console.log('[fixMembershipStatusField] Fixing membership status field configuration');
|
||||
|
||||
try {
|
||||
// First check the current state
|
||||
const checkResult = await checkMembershipStatusField(config, tableId);
|
||||
|
||||
if (!checkResult.success) {
|
||||
throw new Error(checkResult.error || 'Failed to check field configuration');
|
||||
}
|
||||
|
||||
if (!checkResult.needsFix) {
|
||||
return {
|
||||
success: true,
|
||||
message: 'No fix needed - all options are already present',
|
||||
currentOptions: checkResult.currentOptions
|
||||
};
|
||||
}
|
||||
|
||||
if (checkResult.fieldType !== 'SingleSelect') {
|
||||
return {
|
||||
success: false,
|
||||
error: `Cannot fix field type ${checkResult.fieldType}. Only SingleSelect fields can be updated.`
|
||||
};
|
||||
}
|
||||
|
||||
// Update the field to include all required options
|
||||
const fieldId = checkResult.fieldId;
|
||||
const currentOptions = checkResult.currentOptions || [];
|
||||
const requiredOptions = ['Active', 'Inactive', 'Pending', 'Expired'];
|
||||
|
||||
// Create options array with all required values
|
||||
const optionsToSet = requiredOptions.map((option, index) => ({
|
||||
title: option,
|
||||
color: getStatusColor(option),
|
||||
order: index + 1
|
||||
}));
|
||||
|
||||
console.log('[fixMembershipStatusField] Updating field with options:', optionsToSet);
|
||||
|
||||
// Update the field via NocoDB API
|
||||
const updateResult = await $fetch(`${config.url}/api/v2/tables/${tableId}/columns/${fieldId}`, {
|
||||
method: 'PATCH',
|
||||
headers: {
|
||||
"xc-token": config.token,
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: {
|
||||
colOptions: {
|
||||
options: optionsToSet
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
console.log('[fixMembershipStatusField] Field updated successfully');
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: 'Membership Status field updated successfully',
|
||||
addedOptions: checkResult.missingOptions,
|
||||
allOptions: requiredOptions
|
||||
};
|
||||
|
||||
} catch (error: any) {
|
||||
console.error('[fixMembershipStatusField] Error:', error);
|
||||
return {
|
||||
success: false,
|
||||
error: error.message || 'Failed to fix field configuration'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function getStatusColor(status: string): string {
|
||||
const colors = {
|
||||
'Active': '#22c55e', // Green
|
||||
'Inactive': '#6b7280', // Gray
|
||||
'Pending': '#f59e0b', // Yellow
|
||||
'Expired': '#ef4444' // Red
|
||||
};
|
||||
return colors[status as keyof typeof colors] || '#6b7280';
|
||||
}
|
||||
@@ -1,135 +0,0 @@
|
||||
import type { NocoDBSettings } from '~/utils/types';
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
console.log('[api/admin/nocodb-test.post] =========================');
|
||||
console.log('[api/admin/nocodb-test.post] POST /api/admin/nocodb-test');
|
||||
console.log('[api/admin/nocodb-test.post] Request from:', getClientIP(event));
|
||||
|
||||
try {
|
||||
// Check admin authorization
|
||||
const sessionManager = createSessionManager();
|
||||
const cookieHeader = getHeader(event, 'cookie');
|
||||
const session = sessionManager.getSession(cookieHeader);
|
||||
|
||||
if (!session?.user) {
|
||||
throw createError({
|
||||
statusCode: 401,
|
||||
statusMessage: 'Authentication required'
|
||||
});
|
||||
}
|
||||
|
||||
// Check if user is admin
|
||||
if (session.user.tier !== 'admin') {
|
||||
throw createError({
|
||||
statusCode: 403,
|
||||
statusMessage: 'Admin access required'
|
||||
});
|
||||
}
|
||||
|
||||
console.log('[api/admin/nocodb-test.post] Admin access confirmed for:', session.user.email);
|
||||
|
||||
// Get request body - this contains the settings to test
|
||||
const body = await readBody(event) as NocoDBSettings;
|
||||
|
||||
// Validate required fields
|
||||
if (!body.url || !body.apiKey || !body.baseId || !body.tables || Object.keys(body.tables).length === 0) {
|
||||
return {
|
||||
success: false,
|
||||
message: 'All fields are required to test connection (url, apiKey, baseId, and at least one table)'
|
||||
};
|
||||
}
|
||||
|
||||
// Validate URL format
|
||||
if (!body.url.startsWith('http://') && !body.url.startsWith('https://')) {
|
||||
return {
|
||||
success: false,
|
||||
message: 'URL must start with http:// or https://'
|
||||
};
|
||||
}
|
||||
|
||||
// Validate API token format - check for non-ASCII characters that would cause ByteString errors
|
||||
const apiKey = body.apiKey.trim();
|
||||
if (!/^[\x00-\xFF]*$/.test(apiKey)) {
|
||||
return {
|
||||
success: false,
|
||||
message: 'API token contains invalid characters. Please ensure you copied the token correctly without any special formatting characters.'
|
||||
};
|
||||
}
|
||||
|
||||
// Additional validation for common token issues
|
||||
if (apiKey.includes('•') || apiKey.includes('…') || apiKey.includes('"') || apiKey.includes('"')) {
|
||||
return {
|
||||
success: false,
|
||||
message: 'API token contains formatting characters (bullets, quotes, etc.). Please copy the raw token from NocoDB without any formatting.'
|
||||
};
|
||||
}
|
||||
|
||||
console.log('[api/admin/nocodb-test.post] Testing NocoDB connection...');
|
||||
console.log('[api/admin/nocodb-test.post] URL:', body.url);
|
||||
console.log('[api/admin/nocodb-test.post] Base ID:', body.baseId);
|
||||
console.log('[api/admin/nocodb-test.post] Tables:', Object.keys(body.tables));
|
||||
|
||||
try {
|
||||
// Test connection by making a simple request to NocoDB using the first table
|
||||
const firstTableName = Object.keys(body.tables)[0];
|
||||
const firstTableId = body.tables[firstTableName];
|
||||
const testUrl = `${body.url}/api/v2/tables/${firstTableId}/records?limit=1`;
|
||||
|
||||
console.log('[api/admin/nocodb-test.post] Testing URL:', testUrl);
|
||||
|
||||
const response = await $fetch(testUrl, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'xc-token': body.apiKey,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
// Add timeout to prevent hanging
|
||||
timeout: 10000
|
||||
});
|
||||
|
||||
console.log('[api/admin/nocodb-test.post] ✅ Connection successful');
|
||||
console.log('[api/admin/nocodb-test.post] Response received, type:', typeof response);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: 'Connection successful! NocoDB is responding.'
|
||||
};
|
||||
|
||||
} catch (connectionError: any) {
|
||||
console.error('[api/admin/nocodb-test.post] ❌ Connection failed:', connectionError);
|
||||
|
||||
let errorMessage = 'Connection failed';
|
||||
|
||||
if (connectionError.statusCode === 401 || connectionError.status === 401) {
|
||||
errorMessage = 'Authentication failed - please check your API token';
|
||||
} else if (connectionError.statusCode === 404 || connectionError.status === 404) {
|
||||
errorMessage = 'Table not found - please check your Base ID and Table ID';
|
||||
} else if (connectionError.statusCode === 403 || connectionError.status === 403) {
|
||||
errorMessage = 'Access denied - please check your API token permissions';
|
||||
} else if (connectionError.code === 'NETWORK_ERROR' || connectionError.message?.includes('fetch')) {
|
||||
errorMessage = 'Network error - please check the URL and ensure NocoDB is accessible';
|
||||
} else if (connectionError.message?.includes('timeout')) {
|
||||
errorMessage = 'Connection timeout - NocoDB server may be unavailable';
|
||||
} else if (connectionError.message) {
|
||||
errorMessage = connectionError.message;
|
||||
}
|
||||
|
||||
return {
|
||||
success: false,
|
||||
message: errorMessage
|
||||
};
|
||||
}
|
||||
|
||||
} catch (error: any) {
|
||||
console.error('[api/admin/nocodb-test.post] ❌ Error:', error);
|
||||
|
||||
if (error.statusCode) {
|
||||
throw error; // Re-throw HTTP errors
|
||||
}
|
||||
|
||||
return {
|
||||
success: false,
|
||||
message: 'Failed to test connection due to server error'
|
||||
};
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user