From 67d4d5236b122db59392b808574883e1a7268865 Mon Sep 17 00:00:00 2001 From: Matt Date: Tue, 12 Aug 2025 12:24:16 +0200 Subject: [PATCH] Add NocoDB events/RSVPs table config and improve session handling - Add events and rsvps table ID fields to NocoDB settings dialog - Replace mock getUserSession with proper SessionManager integration - Improve type safety with explicit type casting and error handling - Add comprehensive events system bug analysis documentation --- EVENTS_SYSTEM_BUGS_COMPREHENSIVE_ANALYSIS.md | 148 +++++++++++++++++++ components/NocoDBSettingsDialog.vue | 52 ++++++- nuxt.config.ts | 1 + server/api/events/[id]/attendees.patch.ts | 27 +--- server/api/events/[id]/rsvp.post.ts | 29 ++-- server/api/events/index.get.ts | 37 ++--- server/api/events/index.post.ts | 35 ++--- server/utils/admin-config.ts | 24 ++- server/utils/nocodb-events.ts | 41 ++--- utils/types.ts | 17 ++- 10 files changed, 289 insertions(+), 122 deletions(-) create mode 100644 EVENTS_SYSTEM_BUGS_COMPREHENSIVE_ANALYSIS.md 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/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/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.get.ts b/server/api/events/index.get.ts index 6c59fa3..cef642a 100644 --- a/server/api/events/index.get.ts +++ b/server/api/events/index.get.ts @@ -1,5 +1,6 @@ // server/api/events/index.get.ts import { createNocoDBEventsClient, transformEventForCalendar } from '~/server/utils/nocodb-events'; +import { createSessionManager } from '~/server/utils/session'; import type { EventFilters } from '~/utils/types'; export default defineEventHandler(async (event) => { @@ -10,8 +11,11 @@ export default defineEventHandler(async (event) => { calendar_format?: string }; - // Get user session for role-based filtering - 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, @@ -59,32 +63,17 @@ export default defineEventHandler(async (event) => { pagination: response.PageInfo }; - } catch (error) { + } catch (error: any) { console.error('Error fetching events:', error); + + // Re-throw authentication errors as-is + if (error.statusCode === 401) { + throw error; + } + throw createError({ statusCode: 500, statusMessage: 'Failed to fetch events' }); } }); - -// Helper function to get user session (you may need to adjust this based on your auth implementation) -async function getUserSession(event: any) { - // This should be replaced with your actual session retrieval logic - // For now, assuming you have a session utility similar to your auth system - try { - const sessionCookie = getCookie(event, 'session') || getHeader(event, 'authorization'); - if (!sessionCookie) return null; - - // Decode session - adjust based on your session implementation - // This is a placeholder that should be replaced with your actual session logic - return { - user: { - id: 'user-id', // This should come from your session - tier: 'user' // This should come from your session - } - }; - } catch { - return null; - } -} diff --git a/server/api/events/index.post.ts b/server/api/events/index.post.ts index 8f1acb6..b62120f 100644 --- a/server/api/events/index.post.ts +++ b/server/api/events/index.post.ts @@ -1,13 +1,17 @@ // server/api/events/index.post.ts import { createNocoDBEventsClient } from '~/server/utils/nocodb-events'; +import { createSessionManager } from '~/server/utils/session'; import type { EventCreateRequest } from '~/utils/types'; export default defineEventHandler(async (event) => { try { const body = await readBody(event) as EventCreateRequest; - // Get user session for authentication and authorization - 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, @@ -74,7 +78,7 @@ export default defineEventHandler(async (event) => { const eventData = { title: body.title.trim(), description: body.description?.trim() || '', - event_type: body.event_type, + event_type: body.event_type as 'meeting' | 'social' | 'fundraiser' | 'workshop' | 'board-only', start_datetime: body.start_datetime, end_datetime: body.end_datetime, location: body.location?.trim() || '', @@ -85,10 +89,10 @@ export default defineEventHandler(async (event) => { cost_members: body.cost_members || '', cost_non_members: body.cost_non_members || '', member_pricing_enabled: body.member_pricing_enabled || 'true', - visibility: body.visibility, - status: body.status || 'active', + visibility: body.visibility as 'public' | 'board-only' | 'admin-only', + status: (body.status as 'active' | 'cancelled' | 'completed' | 'draft') || 'active', creator: session.user.id, - current_attendees: '0' + current_attendees: 0 }; // Create the event @@ -100,7 +104,7 @@ export default defineEventHandler(async (event) => { message: 'Event created successfully' }; - } catch (error) { + } catch (error: any) { console.error('Error creating event:', error); // Re-throw createError instances @@ -114,20 +118,3 @@ export default defineEventHandler(async (event) => { }); } }); - -// Helper function to get user session (same as in index.get.ts) -async function getUserSession(event: any) { - try { - const sessionCookie = getCookie(event, 'session') || getHeader(event, 'authorization'); - if (!sessionCookie) return null; - - return { - user: { - id: 'user-id', // Replace with actual session logic - tier: 'board' // Replace with actual session logic - } - }; - } catch { - return null; - } -} 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 f135575..cb440ca 100644 --- a/server/utils/nocodb-events.ts +++ b/server/utils/nocodb-events.ts @@ -6,12 +6,15 @@ import type { Event, EventRSVP, EventsResponse, EventFilters } from '~/utils/typ * Provides CRUD operations and specialized queries for events and RSVPs */ export function createNocoDBEventsClient() { - const config = useRuntimeConfig(); + // Get effective configuration from admin config system + const { getEffectiveNocoDBConfig } = require('./admin-config'); + const effectiveConfig = getEffectiveNocoDBConfig(); - const baseUrl = config.nocodb.url; - const token = config.nocodb.token; - const eventsBaseId = config.nocodb.eventsBaseId; - const eventsTableId = config.nocodb.eventsTableId || 'events'; // fallback to table name + const baseUrl = effectiveConfig.url; + const token = effectiveConfig.token; + const eventsBaseId = effectiveConfig.baseId; + const eventsTableId = effectiveConfig.tables.events || 'events'; + const rsvpTableId = effectiveConfig.tables.rsvps || 'event_rsvps'; if (!baseUrl || !token || !eventsBaseId) { throw new Error('Events NocoDB configuration is incomplete. Please check environment variables.'); @@ -150,12 +153,12 @@ export function createNocoDBEventsClient() { * Create an RSVP record for an event */ async createRSVP(rsvpData: Partial) { - const url = `${baseUrl}/api/v1/db/data/v1/${eventsBaseId}/${eventsTableId}`; + const url = `${baseUrl}/api/v1/db/data/v1/${eventsBaseId}/${rsvpTableId}`; const data = { ...rsvpData, - created_at: new Date().toISOString(), - updated_at: new Date().toISOString() + created_time: new Date().toISOString(), + updated_time: new Date().toISOString() }; return await $fetch(url, { @@ -171,9 +174,9 @@ export function createNocoDBEventsClient() { async findEventRSVPs(eventId: string) { const queryParams = new URLSearchParams(); queryParams.set('where', `(event_id = '${eventId}')`); - queryParams.set('sort', 'created_at'); + queryParams.set('sort', 'created_time'); - const url = `${baseUrl}/api/v1/db/data/v1/${eventsBaseId}/${eventsTableId}?${queryParams.toString()}`; + const url = `${baseUrl}/api/v1/db/data/v1/${eventsBaseId}/${rsvpTableId}?${queryParams.toString()}`; return await $fetch(url, { method: 'GET', @@ -189,9 +192,9 @@ export function createNocoDBEventsClient() { queryParams.set('where', `(event_id = '${eventId}' AND member_id = '${memberId}')`); queryParams.set('limit', '1'); - const url = `${baseUrl}/api/v1/db/data/v1/${eventsBaseId}/${eventsTableId}?${queryParams.toString()}`; + const url = `${baseUrl}/api/v1/db/data/v1/${eventsBaseId}/${rsvpTableId}?${queryParams.toString()}`; - const response = await $fetch(url, { + const response = await $fetch<{ list?: EventRSVP[]; PageInfo?: any }>(url, { method: 'GET', headers }); @@ -203,11 +206,11 @@ export function createNocoDBEventsClient() { * Update an RSVP record */ async updateRSVP(id: string, rsvpData: Partial) { - const url = `${baseUrl}/api/v1/db/data/v1/${eventsBaseId}/${eventsTableId}/${id}`; + const url = `${baseUrl}/api/v1/db/data/v1/${eventsBaseId}/${rsvpTableId}/${id}`; const data = { ...rsvpData, - updated_at: new Date().toISOString() + updated_time: new Date().toISOString() }; return await $fetch(url, { @@ -238,7 +241,7 @@ export function createNocoDBEventsClient() { */ async findUserEvents(memberId: string, filters?: EventFilters) { // First get all visible events - const events = await this.findAll(filters); + const events = await this.findAll(filters) as { list?: Event[]; PageInfo?: any }; if (!events.list || events.list.length === 0) { return { list: [], PageInfo: events.PageInfo }; @@ -247,11 +250,11 @@ export function createNocoDBEventsClient() { // Get user's RSVPs for these events const eventIds = events.list.map((e: Event) => e.id); const rsvpQueryParams = new URLSearchParams(); - rsvpQueryParams.set('where', `(member_id = '${memberId}' AND event_id IN (${eventIds.map(id => `'${id}'`).join(',')}))`); + rsvpQueryParams.set('where', `(member_id = '${memberId}' AND event_id IN (${eventIds.map((id: string) => `'${id}'`).join(',')}))`); - const url = `${baseUrl}/api/v1/db/data/v1/${eventsBaseId}/${eventsTableId}?${rsvpQueryParams.toString()}`; + const rsvpUrl = `${baseUrl}/api/v1/db/data/v1/${eventsBaseId}/${rsvpTableId}?${rsvpQueryParams.toString()}`; - const rsvps = await $fetch(url, { + const rsvps = await $fetch<{ list?: EventRSVP[]; PageInfo?: any }>(rsvpUrl, { method: 'GET', headers }); @@ -294,7 +297,7 @@ export function createNocoDBEventsClient() { if (!event.max_attendees) return false; // Unlimited capacity const maxAttendees = parseInt(event.max_attendees); - const currentAttendees = event.current_attendees || 0; + const currentAttendees = parseInt(String(event.current_attendees || 0)); return currentAttendees >= maxAttendees; } 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;