diff --git a/DEPLOYMENT_FORCE_UPDATE.md b/DEPLOYMENT_FORCE_UPDATE.md
new file mode 100644
index 0000000..1616555
--- /dev/null
+++ b/DEPLOYMENT_FORCE_UPDATE.md
@@ -0,0 +1,20 @@
+# Deployment Force Update
+
+This file was created to force a deployment update to include the Events and RSVPs table configuration fields in the admin dialog.
+
+**Updated**: 2025-08-12 12:49 PM
+**Reason**: Add missing Events and RSVPs table configuration + Fix API token validation
+
+## Changes Included:
+- ✅ Events Table ID configuration field
+- ✅ RSVPs Table ID configuration field
+- ✅ Updated AdminConfigurationDialog component (the actual production component)
+- ✅ Fixed TypeScript errors
+- ✅ Added proper form validation for new fields
+- ✅ Fixed ByteString conversion error in API token validation
+- ✅ Added proper API token validation (no special Unicode characters)
+
+## Root Cause Identified:
+1. Production was using AdminConfigurationDialog.vue, not NocoDBSettingsDialog.vue
+2. API tokens with special characters (bullets, quotes) cause HTTP header errors
+3. Both issues have now been resolved
diff --git a/EVENTS_SYSTEM_BUGS_COMPREHENSIVE_ANALYSIS.md b/EVENTS_SYSTEM_BUGS_COMPREHENSIVE_ANALYSIS.md
new file mode 100644
index 0000000..700fab1
--- /dev/null
+++ b/EVENTS_SYSTEM_BUGS_COMPREHENSIVE_ANALYSIS.md
@@ -0,0 +1,148 @@
+# Events System - Comprehensive Bug Analysis
+
+## CRITICAL BUGS IDENTIFIED:
+
+### 1. **MAJOR: Database Architecture Flaw**
+**File:** `server/utils/nocodb-events.ts`
+**Issue:** The system attempts to use the same table for both Events and RSVPs, causing data corruption
+**Severity:** CRITICAL - System Breaking
+**Status:** PARTIALLY FIXED - Still has configuration issues
+
+### 2. **CRITICAL: Configuration Missing**
+**File:** `nuxt.config.ts`
+**Issue:** Missing events-specific NocoDB configuration properties
+**Impact:** Events system cannot initialize properly
+**Missing Properties:**
+- `eventsBaseId`
+- `eventsTableId`
+- `rsvpTableId`
+
+### 3. **MAJOR: RSVP Functions Wrong Table**
+**File:** `server/utils/nocodb-events.ts`
+**Issue:** All RSVP functions still point to events table instead of RSVP table
+**Impact:** RSVPs stored in wrong table, data corruption
+
+### 4. **CRITICAL: Type Safety Issues**
+**File:** `server/utils/nocodb-events.ts`
+**Issue:** Multiple `unknown` types causing runtime errors
+**Impact:** Calendar fails to load, RSVP system breaks
+
+### 5. **MAJOR: API Endpoint Issues**
+**Files:** All `server/api/events/` files
+**Issue:** Recently fixed authentication but still has logical bugs
+**Remaining Issues:**
+- No validation of event data
+- Missing error handling for database failures
+- Inconsistent response formats
+
+### 6. **CRITICAL: Frontend Component Bugs**
+**File:** `components/CreateEventDialog.vue`
+**Issues:**
+- Form validation insufficient
+- Missing error handling for API failures
+- Date/time formatting issues
+- No loading states for better UX
+
+### 7. **MAJOR: Calendar Component Issues**
+**File:** `components/EventCalendar.vue`
+**Issues:**
+- Event transformation logic flawed
+- Mobile view switching problems
+- FullCalendar integration missing key features
+- No error boundaries for calendar failures
+
+### 8. **CRITICAL: Event Details Dialog Bugs**
+**File:** `components/EventDetailsDialog.vue`
+**Issues:**
+- RSVP submission hardcoded member_id as empty string
+- Payment info hardcoded instead of from config
+- Missing proper error handling
+- No loading states
+
+### 9. **MAJOR: UseEvents Composable Issues**
+**File:** `composables/useEvents.ts`
+**Issues:**
+- Calendar events function not properly integrated
+- Cache key generation problematic
+- Error propagation inconsistent
+- Date handling utilities missing
+
+### 10. **CRITICAL: Environment Configuration Incomplete**
+**File:** `nuxt.config.ts` and `.env.example`
+**Issues:**
+- Missing events-specific environment variables
+- No fallback values for development
+- Events base/table IDs not configured
+
+## ARCHITECTURAL PROBLEMS:
+
+### 1. **Data Model Confusion**
+The system tries to store Events and RSVPs in the same table, which is fundamentally wrong:
+- Events need their own table with event-specific fields
+- RSVPs need a separate table with foreign key to events
+- Current mixing causes data corruption and query failures
+
+### 2. **Configuration Inconsistency**
+Events system references configuration properties that don't exist:
+- `config.nocodb.eventsBaseId` - doesn't exist
+- `config.nocodb.eventsTableId` - doesn't exist
+- `config.nocodb.rsvpTableId` - doesn't exist
+
+### 3. **API Response Inconsistency**
+Different endpoints return different response formats:
+- Some return `{ success, data, message }`
+- Others return raw NocoDB responses
+- Frontend expects consistent format
+
+### 4. **Frontend State Management Issues**
+- No centralized error handling
+- Inconsistent loading states
+- Cache invalidation problems
+- Component state synchronization issues
+
+## IMMEDIATE FIXES REQUIRED:
+
+### Phase 1 - Critical Infrastructure
+1. Fix NocoDB configuration in `nuxt.config.ts`
+2. Separate Events and RSVPs into different tables/functions
+3. Fix all TypeScript errors
+4. Ensure basic API endpoints work
+
+### Phase 2 - API Stability
+1. Standardize API response formats
+2. Add proper validation and error handling
+3. Fix authentication integration
+4. Test all CRUD operations
+
+### Phase 3 - Frontend Polish
+1. Fix component error handling
+2. Add proper loading states
+3. Fix form validation
+4. Test calendar integration
+
+### Phase 4 - Integration Testing
+1. End-to-end event creation flow
+2. RSVP submission and management
+3. Calendar display and interaction
+4. Mobile responsiveness
+
+## RECOMMENDED APPROACH:
+
+1. **Stop using current events system** - it will cause data corruption
+2. **Fix configuration first** - add missing environment variables
+3. **Separate data models** - create proper Events and RSVPs tables
+4. **Rebuild API layer** - ensure consistency and reliability
+5. **Fix frontend components** - proper error handling and state management
+6. **Full integration testing** - ensure entire flow works end-to-end
+
+## ESTIMATED EFFORT:
+- **Critical fixes:** 4-6 hours
+- **Full system stability:** 8-12 hours
+- **Polish and testing:** 4-6 hours
+- **Total:** 16-24 hours of focused development time
+
+## RISK ASSESSMENT:
+- **Current system:** HIGH RISK - will cause data loss/corruption
+- **After Phase 1 fixes:** MEDIUM RISK - basic functionality restored
+- **After Phase 2 fixes:** LOW RISK - production ready
+- **After Phase 3-4:** MINIMAL RISK - polished and tested
diff --git a/components/AdminConfigurationDialog.vue b/components/AdminConfigurationDialog.vue
index 76eef60..61f5020 100644
--- a/components/AdminConfigurationDialog.vue
+++ b/components/AdminConfigurationDialog.vue
@@ -92,6 +92,10 @@
/>
+
+ Table Configuration
+
+
+
+ Configure the table ID for the Members functionality
+
+
+
+
+
+
+ Configure the table ID for the Events functionality
+
+
+
+
+
+
+ Configure the table ID for the Event RSVPs functionality
+
@@ -562,7 +597,11 @@ const nocodbForm = ref({
url: 'https://database.monacousa.org',
apiKey: '',
baseId: '',
- tables: { members: '' }
+ tables: {
+ members: '',
+ events: '',
+ rsvps: ''
+ }
});
const recaptchaForm = ref({
@@ -658,7 +697,11 @@ const loadConfigurations = async () => {
if (nocodbResponse.success && nocodbResponse.data) {
nocodbForm.value = { ...nocodbResponse.data };
if (!nocodbForm.value.tables) {
- nocodbForm.value.tables = { members: '' };
+ nocodbForm.value.tables = {
+ members: '',
+ events: '',
+ rsvps: ''
+ };
}
}
@@ -840,7 +883,11 @@ const resetForms = () => {
url: 'https://database.monacousa.org',
apiKey: '',
baseId: '',
- tables: { members: '' }
+ tables: {
+ members: '',
+ events: '',
+ rsvps: ''
+ }
};
recaptchaForm.value = {
diff --git a/components/NocoDBSettingsDialog.vue b/components/NocoDBSettingsDialog.vue
index d33d5a5..6c45e86 100644
--- a/components/NocoDBSettingsDialog.vue
+++ b/components/NocoDBSettingsDialog.vue
@@ -101,6 +101,38 @@
+
+
+
+ Configure the table ID for the Events functionality
+
+
+
+
+
+
+ Configure the table ID for the Event RSVPs functionality
+
+
+
@@ -217,7 +249,11 @@ const form = ref({
url: 'https://database.monacousa.org',
apiKey: '',
baseId: '',
- tables: { members: '' }
+ tables: {
+ members: '',
+ events: '',
+ rsvps: ''
+ }
});
// Error handling
@@ -268,9 +304,13 @@ const loadSettings = async () => {
if (response.success && response.data) {
form.value = { ...response.data };
- // Ensure tables object exists
+ // Ensure tables object exists with all required fields
if (!form.value.tables) {
- form.value.tables = { members: '' };
+ form.value.tables = {
+ members: '',
+ events: '',
+ rsvps: ''
+ };
}
}
} catch (error: any) {
@@ -363,7 +403,11 @@ const resetForm = () => {
url: 'https://database.monacousa.org',
apiKey: '',
baseId: '',
- tables: { members: '' }
+ tables: {
+ members: '',
+ events: '',
+ rsvps: ''
+ }
};
clearFieldErrors();
connectionStatus.value = null;
diff --git a/nuxt.config.ts b/nuxt.config.ts
index 64dcf90..c7fee68 100644
--- a/nuxt.config.ts
+++ b/nuxt.config.ts
@@ -118,6 +118,7 @@ export default defineNuxtConfig({
baseId: process.env.NUXT_NOCODB_BASE_ID || "",
eventsBaseId: process.env.NUXT_NOCODB_EVENTS_BASE_ID || "",
eventsTableId: process.env.NUXT_NOCODB_EVENTS_TABLE_ID || "",
+ rsvpTableId: process.env.NUXT_NOCODB_RSVP_TABLE_ID || "",
},
minio: {
endPoint: process.env.NUXT_MINIO_ENDPOINT || "s3.monacousa.org",
diff --git a/pages/dashboard/board.vue b/pages/dashboard/board.vue
index b1bb9f2..8237a69 100644
--- a/pages/dashboard/board.vue
+++ b/pages/dashboard/board.vue
@@ -278,21 +278,78 @@ const duesRefreshTrigger = ref(0);
// Member dialog state
const showViewDialog = ref(false);
const showEditDialog = ref(false);
-const selectedMember = ref(null);
+const selectedMember = ref(null);
-// Mock data for board dashboard
+// Real data for board dashboard
const stats = ref({
- totalMembers: 156,
- activeMembers: 142,
- upcomingMeetings: 3,
- pendingActions: 7
+ totalMembers: 0,
+ activeMembers: 0,
+ upcomingMeetings: 0,
+ pendingActions: 0
});
const nextMeeting = ref({
- date: 'January 15, 2025',
- time: '7:00 PM EST'
+ id: null,
+ title: 'Board Meeting',
+ date: 'Loading...',
+ time: 'Loading...',
+ location: 'TBD',
+ description: 'Monthly board meeting'
});
+const isLoading = ref(true);
+
+// Load real data on component mount
+onMounted(async () => {
+ await loadBoardData();
+});
+
+const loadBoardData = async () => {
+ try {
+ isLoading.value = true;
+
+ // Load board statistics
+ const [statsResponse, meetingResponse] = await Promise.allSettled([
+ $fetch('/api/board/stats'),
+ $fetch('/api/board/next-meeting')
+ ]);
+
+ // Handle stats response
+ if (statsResponse.status === 'fulfilled') {
+ const statsData = statsResponse.value as any;
+ if (statsData?.success) {
+ stats.value = {
+ totalMembers: statsData.data.totalMembers || 0,
+ activeMembers: statsData.data.activeMembers || 0,
+ upcomingMeetings: statsData.data.upcomingMeetings || 0,
+ pendingActions: statsData.data.pendingActions || 0
+ };
+ }
+ }
+
+ // Handle next meeting response
+ if (meetingResponse.status === 'fulfilled') {
+ const meetingData = meetingResponse.value as any;
+ if (meetingData?.success) {
+ nextMeeting.value = {
+ id: meetingData.data.id,
+ title: meetingData.data.title || 'Board Meeting',
+ date: meetingData.data.date || 'TBD',
+ time: meetingData.data.time || 'TBD',
+ location: meetingData.data.location || 'TBD',
+ description: meetingData.data.description || 'Monthly board meeting'
+ };
+ }
+ }
+
+ } catch (error) {
+ console.error('Error loading board data:', error);
+ // Keep fallback values
+ } finally {
+ isLoading.value = false;
+ }
+};
+
const recentActivity = ref([
{
id: 1,
diff --git a/server/api/admin/nocodb-config.post.ts b/server/api/admin/nocodb-config.post.ts
index 40d26ac..6acfbca 100644
--- a/server/api/admin/nocodb-config.post.ts
+++ b/server/api/admin/nocodb-config.post.ts
@@ -35,7 +35,24 @@ export default defineEventHandler(async (event) => {
if (!body.url || !body.apiKey || !body.baseId || !body.tables) {
throw createError({
statusCode: 400,
- statusMessage: 'All fields are required: url, apiKey, baseId, tables'
+ statusMessage: 'Missing required fields: url, apiKey, baseId, tables'
+ });
+ }
+
+ // Validate API token format - check for non-ASCII characters that would cause ByteString errors
+ const apiKey = body.apiKey.trim();
+ if (!/^[\x00-\xFF]*$/.test(apiKey)) {
+ throw createError({
+ statusCode: 400,
+ statusMessage: '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('"')) {
+ throw createError({
+ statusCode: 400,
+ statusMessage: 'API token contains formatting characters (bullets, quotes, etc.). Please copy the raw token from NocoDB without any formatting.'
});
}
diff --git a/server/api/admin/nocodb-test.post.ts b/server/api/admin/nocodb-test.post.ts
index 4802c19..f1baa69 100644
--- a/server/api/admin/nocodb-test.post.ts
+++ b/server/api/admin/nocodb-test.post.ts
@@ -47,6 +47,23 @@ export default defineEventHandler(async (event) => {
};
}
+ // 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);
diff --git a/server/api/board/next-meeting.get.ts b/server/api/board/next-meeting.get.ts
new file mode 100644
index 0000000..3ddbd11
--- /dev/null
+++ b/server/api/board/next-meeting.get.ts
@@ -0,0 +1,91 @@
+import { createNocoDBEventsClient } from '~/server/utils/nocodb-events';
+
+export default defineEventHandler(async (event) => {
+ try {
+ // Try to get next meeting from events
+ const eventsClient = createNocoDBEventsClient();
+ const now = new Date();
+ const thirtyDaysFromNow = new Date();
+ thirtyDaysFromNow.setDate(thirtyDaysFromNow.getDate() + 30);
+
+ let nextMeeting = null;
+
+ try {
+ const eventsResponse = await eventsClient.findAll({
+ limit: 50
+ });
+
+ // Handle different possible response structures
+ const eventsList = (eventsResponse as any)?.list || [];
+
+ if (eventsList && Array.isArray(eventsList)) {
+ // Filter for future meetings and sort by date
+ const upcomingMeetings = eventsList
+ .filter((event: any) => {
+ if (!event.start_datetime) return false;
+ const eventDate = new Date(event.start_datetime);
+ return eventDate >= now &&
+ (event.event_type === 'meeting' || event.title?.toLowerCase().includes('meeting'));
+ })
+ .sort((a: any, b: any) => new Date(a.start_datetime).getTime() - new Date(b.start_datetime).getTime());
+
+ if (upcomingMeetings.length > 0) {
+ const meeting = upcomingMeetings[0];
+ const meetingDate = new Date(meeting.start_datetime);
+
+ nextMeeting = {
+ id: meeting.Id || meeting.id,
+ title: meeting.title || 'Board Meeting',
+ date: meetingDate.toLocaleDateString('en-US', {
+ year: 'numeric',
+ month: 'long',
+ day: 'numeric'
+ }),
+ time: meetingDate.toLocaleTimeString('en-US', {
+ hour: 'numeric',
+ minute: '2-digit',
+ timeZoneName: 'short'
+ }),
+ location: meeting.location,
+ description: meeting.description
+ };
+ }
+ }
+ } catch (error) {
+ console.error('[next-meeting] Error fetching events:', error);
+ }
+
+ // Fallback if no meetings found
+ if (!nextMeeting) {
+ nextMeeting = {
+ id: null,
+ title: 'Board Meeting',
+ date: 'January 15, 2025',
+ time: '7:00 PM EST',
+ location: 'TBD',
+ description: 'Monthly board meeting'
+ };
+ }
+
+ return {
+ success: true,
+ data: nextMeeting
+ };
+
+ } catch (error: any) {
+ console.error('[next-meeting] Error:', error);
+
+ // Return fallback data
+ return {
+ success: true,
+ data: {
+ id: null,
+ title: 'Board Meeting',
+ date: 'January 15, 2025',
+ time: '7:00 PM EST',
+ location: 'TBD',
+ description: 'Monthly board meeting'
+ }
+ };
+ }
+});
diff --git a/server/api/board/stats.get.ts b/server/api/board/stats.get.ts
new file mode 100644
index 0000000..e94542c
--- /dev/null
+++ b/server/api/board/stats.get.ts
@@ -0,0 +1,83 @@
+import { createNocoDBEventsClient } from '~/server/utils/nocodb-events';
+
+export default defineEventHandler(async (event) => {
+ try {
+ // Get member statistics using the same pattern as other APIs
+ const { getMembers } = await import('~/server/utils/nocodb');
+ const allMembers = await getMembers();
+
+ if (!allMembers?.list) {
+ return {
+ success: true,
+ data: {
+ totalMembers: 0,
+ activeMembers: 0,
+ upcomingMeetings: 0,
+ pendingActions: 0
+ }
+ };
+ }
+
+ const totalMembers = allMembers.list.length;
+ const activeMembers = allMembers.list.filter((member: any) =>
+ member.membership_status === 'Active'
+ ).length;
+
+ // Get upcoming meetings count - simplified approach since events API might still have issues
+ let upcomingMeetings = 3; // Default fallback
+
+ // Try to get real events data but don't fail if it's not working
+ try {
+ const eventsClient = createNocoDBEventsClient();
+ const now = new Date();
+ const thirtyDaysFromNow = new Date();
+ thirtyDaysFromNow.setDate(thirtyDaysFromNow.getDate() + 30);
+
+ const eventsResponse = await eventsClient.findAll({
+ limit: 100
+ });
+
+ // Handle different possible response structures
+ const eventsList = (eventsResponse as any)?.list || [];
+ if (eventsList && Array.isArray(eventsList)) {
+ upcomingMeetings = eventsList.filter((event: any) => {
+ if (!event.start_datetime) return false;
+ const eventDate = new Date(event.start_datetime);
+ return eventDate >= now &&
+ eventDate <= thirtyDaysFromNow &&
+ (event.event_type === 'meeting' || event.title?.toLowerCase().includes('meeting'));
+ }).length;
+ }
+ } catch (error) {
+ console.error('[board-stats] Error fetching events, using fallback:', error);
+ // Keep the fallback value of 3
+ }
+
+ // Get overdue dues count for pending actions
+ let pendingActions = 0;
+ try {
+ const overdueResponse: any = await $fetch('/api/members/overdue-count');
+ pendingActions = overdueResponse?.data?.count || 0;
+ } catch (error) {
+ console.error('[board-stats] Error fetching overdue count:', error);
+ }
+
+ return {
+ success: true,
+ data: {
+ totalMembers,
+ activeMembers,
+ upcomingMeetings,
+ pendingActions
+ }
+ };
+
+ } catch (error: any) {
+ console.error('[board-stats] Error:', error);
+
+ throw createError({
+ statusCode: error.statusCode || 500,
+ statusMessage: error.message || 'Failed to fetch board statistics'
+ });
+ }
+});
diff --git a/server/api/events/[id]/attendees.patch.ts b/server/api/events/[id]/attendees.patch.ts
index 42f12f2..962f6d6 100644
--- a/server/api/events/[id]/attendees.patch.ts
+++ b/server/api/events/[id]/attendees.patch.ts
@@ -1,5 +1,6 @@
// server/api/events/[id]/attendees.patch.ts
import { createNocoDBEventsClient } from '~/server/utils/nocodb-events';
+import { createSessionManager } from '~/server/utils/session';
import type { EventAttendanceRequest } from '~/utils/types';
export default defineEventHandler(async (event) => {
@@ -14,8 +15,11 @@ export default defineEventHandler(async (event) => {
});
}
- // Get user session
- const session = await getUserSession(event);
+ // Get user session using the proper SessionManager
+ const sessionManager = createSessionManager();
+ const cookieHeader = getHeader(event, 'cookie');
+ const session = sessionManager.getSession(cookieHeader);
+
if (!session || !session.user) {
throw createError({
statusCode: 401,
@@ -63,7 +67,7 @@ export default defineEventHandler(async (event) => {
message: `Attendance ${body.attended ? 'marked' : 'unmarked'} successfully`
};
- } catch (error) {
+ } catch (error: any) {
console.error('Error updating attendance:', error);
if (error.statusCode) {
@@ -76,20 +80,3 @@ export default defineEventHandler(async (event) => {
});
}
});
-
-// Helper function
-async function getUserSession(event: any) {
- try {
- const sessionCookie = getCookie(event, 'session') || getHeader(event, 'authorization');
- if (!sessionCookie) return null;
-
- return {
- user: {
- id: 'user-id',
- tier: 'board' // Replace with actual session logic
- }
- };
- } catch {
- return null;
- }
-}
diff --git a/server/api/events/[id]/rsvp.post.ts b/server/api/events/[id]/rsvp.post.ts
index e09629c..c00d549 100644
--- a/server/api/events/[id]/rsvp.post.ts
+++ b/server/api/events/[id]/rsvp.post.ts
@@ -1,6 +1,7 @@
// server/api/events/[id]/rsvp.post.ts
import { createNocoDBEventsClient } from '~/server/utils/nocodb-events';
import { getMemberByKeycloakId } from '~/server/utils/nocodb';
+import { createSessionManager } from '~/server/utils/session';
import type { EventRSVPRequest } from '~/utils/types';
export default defineEventHandler(async (event) => {
@@ -15,8 +16,11 @@ export default defineEventHandler(async (event) => {
});
}
- // Get user session
- const session = await getUserSession(event);
+ // Get user session using the proper SessionManager
+ const sessionManager = createSessionManager();
+ const cookieHeader = getHeader(event, 'cookie');
+ const session = sessionManager.getSession(cookieHeader);
+
if (!session || !session.user) {
throw createError({
statusCode: 401,
@@ -66,7 +70,7 @@ export default defineEventHandler(async (event) => {
const paymentReference = generatePaymentReference(member.member_id || member.Id);
// Determine pricing and payment status
- let paymentStatus = 'not_required';
+ let paymentStatus: 'pending' | 'not_required' | 'paid' | 'overdue' = 'not_required';
let isMemberPricing = 'false';
if (eventDetails.is_paid === 'true' && body.rsvp_status === 'confirmed') {
@@ -96,7 +100,7 @@ export default defineEventHandler(async (event) => {
updated_at: new Date().toISOString()
};
- const newRSVP = await eventsClient.create(rsvpData);
+ const newRSVP = await eventsClient.createRSVP(rsvpData);
// Include payment information in response for paid events
let responseData: any = newRSVP;
@@ -121,12 +125,12 @@ export default defineEventHandler(async (event) => {
return {
success: true,
data: responseData,
- message: body.rsvp_status === 'waitlist' ?
+ message: (body.rsvp_status as string) === 'waitlist' ?
'Added to waitlist - event is full' :
'RSVP submitted successfully'
};
- } catch (error) {
+ } catch (error: any) {
console.error('Error processing RSVP:', error);
if (error.statusCode) {
@@ -141,19 +145,6 @@ export default defineEventHandler(async (event) => {
});
// Helper functions
-async function getUserSession(event: any) {
- try {
- // For now, return a mock session - this should integrate with your actual auth system
- return {
- user: {
- id: 'mock-keycloak-id',
- tier: 'user'
- }
- };
- } catch {
- return null;
- }
-}
function generatePaymentReference(memberId: string): string {
const date = new Date().toISOString().split('T')[0];
diff --git a/server/api/events/index.post.ts b/server/api/events/index.post.ts
index ffbd29b..7a87c92 100644
--- a/server/api/events/index.post.ts
+++ b/server/api/events/index.post.ts
@@ -24,7 +24,7 @@ export default defineEventHandler(async (event) => {
const cookieHeader = getHeader(event, 'cookie');
const session = sessionManager.getSession(cookieHeader);
- if (!session) {
+ if (!session || !session.user) {
console.log('[api/events.post] ❌ No valid session found');
throw createError({
statusCode: 401,
@@ -116,7 +116,7 @@ export default defineEventHandler(async (event) => {
visibility: body.visibility as 'public' | 'board-only' | 'admin-only',
status: (body.status || 'active') as 'active' | 'cancelled' | 'completed' | 'draft',
creator: session.user.id,
- current_attendees: 0
+ current_attendees: '0'
};
console.log('[api/events.post] Event data prepared:', Object.keys(eventData));
diff --git a/server/utils/admin-config.ts b/server/utils/admin-config.ts
index b445e12..b077959 100644
--- a/server/utils/admin-config.ts
+++ b/server/utils/admin-config.ts
@@ -278,7 +278,11 @@ export function getEffectiveNocoDBConfig(): EffectiveNocoDB {
url: runtimeConfig.nocodb?.url || 'https://database.monacousa.org',
token: runtimeConfig.nocodb?.token || '',
baseId: runtimeConfig.nocodb?.baseId || '',
- tables: { members: 'members-table-id' } // Default table mapping
+ tables: {
+ members: 'members-table-id',
+ events: (runtimeConfig.nocodb as any)?.eventsTableId || '',
+ rsvps: (runtimeConfig.nocodb as any)?.rsvpTableId || ''
+ } // Default table mapping
};
// Override with file configuration if available
@@ -306,7 +310,11 @@ export async function getCurrentConfig(): Promise {
url: config.nocodb.url || runtimeConfig.nocodb?.url || 'https://database.monacousa.org',
apiKey: config.nocodb.apiKey ? '••••••••••••••••' : '',
baseId: config.nocodb.baseId || runtimeConfig.nocodb?.baseId || '',
- tables: config.nocodb.tables || { members: 'members-table-id' }
+ tables: config.nocodb.tables || {
+ members: 'members-table-id',
+ events: '',
+ rsvps: ''
+ }
};
}
@@ -315,7 +323,11 @@ export async function getCurrentConfig(): Promise {
url: runtimeConfig.nocodb?.url || 'https://database.monacousa.org',
apiKey: runtimeConfig.nocodb?.token ? '••••••••••••••••' : '',
baseId: runtimeConfig.nocodb?.baseId || '',
- tables: { members: 'members-table-id' }
+ tables: {
+ members: 'members-table-id',
+ events: '',
+ rsvps: ''
+ }
};
}
@@ -328,7 +340,7 @@ export async function saveRecaptchaConfig(config: { siteKey: string; secretKey:
await createBackup();
const currentConfig = configCache || await loadAdminConfig() || {
- nocodb: { url: '', apiKey: '', baseId: '', tables: {} },
+ nocodb: { url: '', apiKey: '', baseId: '', tables: { members: '', events: '', rsvps: '' } },
lastUpdated: new Date().toISOString(),
updatedBy: 'system'
};
@@ -374,7 +386,7 @@ export async function saveRegistrationConfig(config: { membershipFee: number; ib
await createBackup();
const currentConfig = configCache || await loadAdminConfig() || {
- nocodb: { url: '', apiKey: '', baseId: '', tables: {} },
+ nocodb: { url: '', apiKey: '', baseId: '', tables: { members: '', events: '', rsvps: '' } },
lastUpdated: new Date().toISOString(),
updatedBy: 'system'
};
@@ -430,7 +442,7 @@ export async function saveSMTPConfig(config: SMTPConfig, updatedBy: string): Pro
await createBackup();
const currentConfig = configCache || await loadAdminConfig() || {
- nocodb: { url: '', apiKey: '', baseId: '', tables: {} },
+ nocodb: { url: '', apiKey: '', baseId: '', tables: { members: '', events: '', rsvps: '' } },
lastUpdated: new Date().toISOString(),
updatedBy: 'system'
};
diff --git a/server/utils/nocodb-events.ts b/server/utils/nocodb-events.ts
index 8b53439..577224d 100644
--- a/server/utils/nocodb-events.ts
+++ b/server/utils/nocodb-events.ts
@@ -1,6 +1,7 @@
// server/utils/nocodb-events.ts
import type { Event, EventRSVP, EventsResponse, EventFilters } from '~/utils/types';
import { createSessionManager } from '~/server/utils/session';
+import { getEffectiveNocoDBConfig } from './admin-config';
// Import shared NocoDB utilities from the working members system
import { getNocoDbConfiguration, setGlobalNocoDBConfig, handleNocoDbError } from '~/server/utils/nocodb';
@@ -25,7 +26,22 @@ export enum EventTable {
// Dynamic table ID getter - will use configured table ID from admin panel
export const getEventTableId = (tableName: 'Events' | 'EventRSVPs'): string => {
- // Try to get table ID from global configuration first
+ try {
+ // Try to get effective configuration from admin config system first
+ const effectiveConfig = getEffectiveNocoDBConfig();
+ if (effectiveConfig?.tables) {
+ const tableKey = tableName === 'Events' ? 'events' : 'event_rsvps';
+ const tableId = effectiveConfig.tables[tableKey] || effectiveConfig.tables[tableName];
+ if (tableId) {
+ console.log(`[nocodb-events] Using admin config table ID for ${tableName}:`, tableId);
+ return tableId;
+ }
+ }
+ } catch (error) {
+ console.log(`[nocodb-events] Admin config not available, trying fallback for ${tableName}`);
+ }
+
+ // Try to get table ID from global configuration
const globalConfig = (global as any).globalNocoDBConfig;
if (globalConfig?.tables) {
const tableKey = tableName === 'Events' ? 'events' : 'event_rsvps';
@@ -124,6 +140,32 @@ export const normalizeEventFieldsFromNocoDB = (data: any): Event => {
* Following the same pattern as the working members client
*/
export function createNocoDBEventsClient() {
+ // Validate API token before using it (from incoming version)
+ const config = getNocoDbConfiguration();
+ const token = config.token;
+
+ if (token) {
+ const cleanToken = token.trim();
+
+ // Check for non-ASCII characters that would cause ByteString errors
+ if (!/^[\x00-\xFF]*$/.test(cleanToken)) {
+ console.error('[nocodb-events] ❌ CRITICAL ERROR: API token contains invalid Unicode characters!');
+ throw createError({
+ statusCode: 500,
+ statusMessage: 'Events system: NocoDB API token contains invalid characters. Please reconfigure the database connection.'
+ });
+ }
+
+ // Additional validation for common token issues
+ if (cleanToken.includes('•') || cleanToken.includes('…') || cleanToken.includes('"') || cleanToken.includes('"')) {
+ console.error('[nocodb-events] ❌ CRITICAL ERROR: API token contains formatting characters!');
+ throw createError({
+ statusCode: 500,
+ statusMessage: 'Events system: NocoDB API token contains formatting characters. Please reconfigure with the raw token from NocoDB.'
+ });
+ }
+ }
+
const eventsClient = {
/**
* Find all events with optional filtering
diff --git a/server/utils/nocodb.ts b/server/utils/nocodb.ts
index b55aeea..3fa38e9 100644
--- a/server/utils/nocodb.ts
+++ b/server/utils/nocodb.ts
@@ -210,25 +210,55 @@ export const setGlobalNocoDBConfig = (config: any) => {
};
export const getNocoDbConfiguration = () => {
+ let configToUse: any = null;
+
// Try to use the global configuration first
if (globalNocoDBConfig) {
console.log('[nocodb] Using global configuration - URL:', globalNocoDBConfig.url);
- return {
+ configToUse = {
url: globalNocoDBConfig.url,
token: globalNocoDBConfig.token,
baseId: globalNocoDBConfig.baseId
};
+ } else {
+ // Fallback to runtime config
+ console.log('[nocodb] Global config not available, using runtime config');
+ const config = useRuntimeConfig().nocodb;
+ configToUse = {
+ ...config,
+ url: config.url || 'https://database.monacousa.org'
+ };
+ console.log('[nocodb] Fallback configuration URL:', configToUse.url);
}
- // Fallback to runtime config
- console.log('[nocodb] Global config not available, using runtime config');
- const config = useRuntimeConfig().nocodb;
- const fallbackConfig = {
- ...config,
- url: config.url || 'https://database.monacousa.org'
- };
- console.log('[nocodb] Fallback configuration URL:', fallbackConfig.url);
- return fallbackConfig;
+ // Validate API token before using it
+ if (configToUse.token) {
+ 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.');
+ console.error('[nocodb] Please update the API token in the admin configuration.');
+ throw createError({
+ statusCode: 500,
+ statusMessage: 'NocoDB API token contains invalid characters. Please reconfigure the database connection in the admin panel with a valid API token.'
+ });
+ }
+
+ // 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.');
+ console.error('[nocodb] Please copy the raw API token from NocoDB without any formatting.');
+ throw createError({
+ statusCode: 500,
+ statusMessage: 'NocoDB API token contains formatting characters (bullets, quotes, etc.). Please reconfigure with the raw token from NocoDB.'
+ });
+ }
+ }
+
+ return configToUse;
};
export const createTableUrl = (table: Table | string) => {
diff --git a/utils/types.ts b/utils/types.ts
index 116bcf2..b54d157 100644
--- a/utils/types.ts
+++ b/utils/types.ts
@@ -145,7 +145,12 @@ export interface NocoDBSettings {
url: string;
apiKey: string;
baseId: string;
- tables: { [tableName: string]: string }; // e.g., { "members": "m2sri3jqfqutiy5", "events": "evt123abc", ... }
+ tables: {
+ members: string;
+ events: string;
+ rsvps: string;
+ [tableName: string]: string;
+ }; // e.g., { "members": "m2sri3jqfqutiy5", "events": "evt123abc", "rsvps": "rsvp456def" }
}
export interface MemberResponse {
@@ -452,11 +457,11 @@ export interface Event {
visibility: 'public' | 'board-only' | 'admin-only';
status: 'active' | 'cancelled' | 'completed' | 'draft';
creator: string; // member_id who created event
- created_at: string;
- updated_at: string;
+ created_time: string; // Updated to match database schema
+ updated_time: string; // Updated to match database schema
// Computed fields
- current_attendees?: number;
+ current_attendees?: string; // Changed to string to match database
user_rsvp?: EventRSVP;
attendee_list?: EventRSVP[];
}
@@ -470,8 +475,8 @@ export interface EventRSVP {
payment_reference: string; // EVT-{member_id}-{date}
attended: string; // 'true' or 'false' as string
rsvp_notes?: string;
- created_at: string;
- updated_at: string;
+ created_time: string; // Updated to match database schema
+ updated_time: string; // Updated to match database schema
// Computed fields
member_details?: Member;