diff --git a/server/utils/nocodb-events.ts b/server/utils/nocodb-events.ts index b334299..b1dc3d5 100644 --- a/server/utils/nocodb-events.ts +++ b/server/utils/nocodb-events.ts @@ -20,6 +20,33 @@ export function createNocoDBEventsClient() { throw new Error('Events NocoDB configuration is incomplete. Please check environment variables.'); } + // Validate API token before using it + 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!'); + console.error('[nocodb-events] This will cause ByteString conversion errors in HTTP headers.'); + console.error('[nocodb-events] Please update the API token in the admin configuration.'); + throw createError({ + statusCode: 500, + statusMessage: 'Events system: 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 (cleanToken.includes('•') || cleanToken.includes('…') || cleanToken.includes('"') || cleanToken.includes('"')) { + console.error('[nocodb-events] ❌ CRITICAL ERROR: API token contains formatting characters!'); + console.error('[nocodb-events] Found characters like bullets (•), quotes, etc. that break HTTP headers.'); + console.error('[nocodb-events] Please copy the raw API token from NocoDB without any formatting.'); + throw createError({ + statusCode: 500, + statusMessage: 'Events system: NocoDB API token contains formatting characters (bullets, quotes, etc.). Please reconfigure with the raw token from NocoDB.' + }); + } + } + const headers = { 'xc-token': token, 'Content-Type': 'application/json' @@ -76,7 +103,7 @@ export function createNocoDBEventsClient() { // Sort by start date queryParams.set('sort', 'start_datetime'); - const url = `${baseUrl}/api/v1/db/data/v1/${eventsBaseId}/${eventsTableId}?${queryParams.toString()}`; + const url = `${baseUrl}/api/v2/tables/${eventsTableId}/records?${queryParams.toString()}`; const response = await $fetch(url, { method: 'GET', @@ -90,7 +117,7 @@ export function createNocoDBEventsClient() { * Find a single event by ID */ async findOne(id: string) { - const url = `${baseUrl}/api/v1/db/data/v1/${eventsBaseId}/${eventsTableId}/${id}`; + const url = `${baseUrl}/api/v2/tables/${eventsTableId}/records/${id}`; return await $fetch(url, { method: 'GET', @@ -102,7 +129,7 @@ export function createNocoDBEventsClient() { * Create a new event */ async create(eventData: Partial) { - const url = `${baseUrl}/api/v1/db/data/v1/${eventsBaseId}/${eventsTableId}`; + const url = `${baseUrl}/api/v2/tables/${eventsTableId}/records`; // Set default values const data = { @@ -123,9 +150,10 @@ export function createNocoDBEventsClient() { * Update an existing event */ async update(id: string, eventData: Partial) { - const url = `${baseUrl}/api/v1/db/data/v1/${eventsBaseId}/${eventsTableId}/${id}`; + const url = `${baseUrl}/api/v2/tables/${eventsTableId}/records`; const data = { + Id: parseInt(id), ...eventData, updated_at: new Date().toISOString() }; @@ -141,11 +169,12 @@ export function createNocoDBEventsClient() { * Delete an event */ async delete(id: string) { - const url = `${baseUrl}/api/v1/db/data/v1/${eventsBaseId}/${eventsTableId}/${id}`; + const url = `${baseUrl}/api/v2/tables/${eventsTableId}/records`; return await $fetch(url, { method: 'DELETE', - headers + headers, + body: { Id: parseInt(id) } }); }, @@ -153,7 +182,7 @@ export function createNocoDBEventsClient() { * Create an RSVP record for an event */ async createRSVP(rsvpData: Partial) { - const url = `${baseUrl}/api/v1/db/data/v1/${eventsBaseId}/${rsvpTableId}`; + const url = `${baseUrl}/api/v2/tables/${rsvpTableId}/records`; const data = { ...rsvpData, @@ -176,7 +205,7 @@ export function createNocoDBEventsClient() { queryParams.set('where', `(event_id = '${eventId}')`); queryParams.set('sort', 'created_time'); - const url = `${baseUrl}/api/v1/db/data/v1/${eventsBaseId}/${rsvpTableId}?${queryParams.toString()}`; + const url = `${baseUrl}/api/v2/tables/${rsvpTableId}/records?${queryParams.toString()}`; return await $fetch(url, { method: 'GET', @@ -192,7 +221,7 @@ 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}/${rsvpTableId}?${queryParams.toString()}`; + const url = `${baseUrl}/api/v2/tables/${rsvpTableId}/records?${queryParams.toString()}`; const response = await $fetch<{ list?: EventRSVP[]; PageInfo?: any }>(url, { method: 'GET', @@ -206,9 +235,10 @@ export function createNocoDBEventsClient() { * Update an RSVP record */ async updateRSVP(id: string, rsvpData: Partial) { - const url = `${baseUrl}/api/v1/db/data/v1/${eventsBaseId}/${rsvpTableId}/${id}`; + const url = `${baseUrl}/api/v2/tables/${rsvpTableId}/records`; const data = { + Id: parseInt(id), ...rsvpData, updated_time: new Date().toISOString() }; @@ -224,12 +254,13 @@ export function createNocoDBEventsClient() { * Update event attendance count (for optimization) */ async updateAttendeeCount(eventId: string, count: number) { - const url = `${baseUrl}/api/v1/db/data/v1/${eventsBaseId}/${eventsTableId}/${eventId}`; + const url = `${baseUrl}/api/v2/tables/${eventsTableId}/records`; return await $fetch(url, { method: 'PATCH', headers, body: { + Id: parseInt(eventId), current_attendees: count.toString(), updated_at: new Date().toISOString() } @@ -252,7 +283,7 @@ export function createNocoDBEventsClient() { const rsvpQueryParams = new URLSearchParams(); rsvpQueryParams.set('where', `(member_id = '${memberId}' AND event_id IN (${eventIds.map((id: string) => `'${id}'`).join(',')}))`); - const rsvpUrl = `${baseUrl}/api/v1/db/data/v1/${eventsBaseId}/${rsvpTableId}?${rsvpQueryParams.toString()}`; + const rsvpUrl = `${baseUrl}/api/v2/tables/${rsvpTableId}/records?${rsvpQueryParams.toString()}`; const rsvps = await $fetch<{ list?: EventRSVP[]; PageInfo?: any }>(rsvpUrl, { method: 'GET',