fix(events): Convert events NocoDB client from v1 to v2 API
Build And Push Image / docker (push) Successful in 3m15s Details

- Updated all NocoDB API calls from v1/db/data/v1/ to v2/tables/ endpoints
- Fixed 422 Unprocessable Entity errors on events calendar page
- Ensures consistency with members system which already uses v2 API
- Updated methods: findAll, findOne, create, update, delete, createRSVP, findEventRSVPs, findUserRSVP, updateRSVP, updateAttendeeCount, findUserEvents
- Maintains same functionality while using correct API version

Resolves continuous 422 errors when loading events calendar.
This commit is contained in:
Matt 2025-08-12 13:02:13 +02:00
parent e75579e3e4
commit 85e8a20f40
1 changed files with 43 additions and 12 deletions

View File

@ -20,6 +20,33 @@ export function createNocoDBEventsClient() {
throw new Error('Events NocoDB configuration is incomplete. Please check environment variables.'); 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 = { const headers = {
'xc-token': token, 'xc-token': token,
'Content-Type': 'application/json' 'Content-Type': 'application/json'
@ -76,7 +103,7 @@ export function createNocoDBEventsClient() {
// Sort by start date // Sort by start date
queryParams.set('sort', 'start_datetime'); 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, { const response = await $fetch(url, {
method: 'GET', method: 'GET',
@ -90,7 +117,7 @@ export function createNocoDBEventsClient() {
* Find a single event by ID * Find a single event by ID
*/ */
async findOne(id: string) { 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<Event>(url, { return await $fetch<Event>(url, {
method: 'GET', method: 'GET',
@ -102,7 +129,7 @@ export function createNocoDBEventsClient() {
* Create a new event * Create a new event
*/ */
async create(eventData: Partial<Event>) { async create(eventData: Partial<Event>) {
const url = `${baseUrl}/api/v1/db/data/v1/${eventsBaseId}/${eventsTableId}`; const url = `${baseUrl}/api/v2/tables/${eventsTableId}/records`;
// Set default values // Set default values
const data = { const data = {
@ -123,9 +150,10 @@ export function createNocoDBEventsClient() {
* Update an existing event * Update an existing event
*/ */
async update(id: string, eventData: Partial<Event>) { async update(id: string, eventData: Partial<Event>) {
const url = `${baseUrl}/api/v1/db/data/v1/${eventsBaseId}/${eventsTableId}/${id}`; const url = `${baseUrl}/api/v2/tables/${eventsTableId}/records`;
const data = { const data = {
Id: parseInt(id),
...eventData, ...eventData,
updated_at: new Date().toISOString() updated_at: new Date().toISOString()
}; };
@ -141,11 +169,12 @@ export function createNocoDBEventsClient() {
* Delete an event * Delete an event
*/ */
async delete(id: string) { 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, { return await $fetch(url, {
method: 'DELETE', method: 'DELETE',
headers headers,
body: { Id: parseInt(id) }
}); });
}, },
@ -153,7 +182,7 @@ export function createNocoDBEventsClient() {
* Create an RSVP record for an event * Create an RSVP record for an event
*/ */
async createRSVP(rsvpData: Partial<EventRSVP>) { async createRSVP(rsvpData: Partial<EventRSVP>) {
const url = `${baseUrl}/api/v1/db/data/v1/${eventsBaseId}/${rsvpTableId}`; const url = `${baseUrl}/api/v2/tables/${rsvpTableId}/records`;
const data = { const data = {
...rsvpData, ...rsvpData,
@ -176,7 +205,7 @@ export function createNocoDBEventsClient() {
queryParams.set('where', `(event_id = '${eventId}')`); queryParams.set('where', `(event_id = '${eventId}')`);
queryParams.set('sort', 'created_time'); 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, { return await $fetch(url, {
method: 'GET', method: 'GET',
@ -192,7 +221,7 @@ export function createNocoDBEventsClient() {
queryParams.set('where', `(event_id = '${eventId}' AND member_id = '${memberId}')`); queryParams.set('where', `(event_id = '${eventId}' AND member_id = '${memberId}')`);
queryParams.set('limit', '1'); 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, { const response = await $fetch<{ list?: EventRSVP[]; PageInfo?: any }>(url, {
method: 'GET', method: 'GET',
@ -206,9 +235,10 @@ export function createNocoDBEventsClient() {
* Update an RSVP record * Update an RSVP record
*/ */
async updateRSVP(id: string, rsvpData: Partial<EventRSVP>) { async updateRSVP(id: string, rsvpData: Partial<EventRSVP>) {
const url = `${baseUrl}/api/v1/db/data/v1/${eventsBaseId}/${rsvpTableId}/${id}`; const url = `${baseUrl}/api/v2/tables/${rsvpTableId}/records`;
const data = { const data = {
Id: parseInt(id),
...rsvpData, ...rsvpData,
updated_time: new Date().toISOString() updated_time: new Date().toISOString()
}; };
@ -224,12 +254,13 @@ export function createNocoDBEventsClient() {
* Update event attendance count (for optimization) * Update event attendance count (for optimization)
*/ */
async updateAttendeeCount(eventId: string, count: number) { 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, { return await $fetch(url, {
method: 'PATCH', method: 'PATCH',
headers, headers,
body: { body: {
Id: parseInt(eventId),
current_attendees: count.toString(), current_attendees: count.toString(),
updated_at: new Date().toISOString() updated_at: new Date().toISOString()
} }
@ -252,7 +283,7 @@ export function createNocoDBEventsClient() {
const rsvpQueryParams = new URLSearchParams(); const rsvpQueryParams = new URLSearchParams();
rsvpQueryParams.set('where', `(member_id = '${memberId}' AND event_id IN (${eventIds.map((id: string) => `'${id}'`).join(',')}))`); 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, { const rsvps = await $fetch<{ list?: EventRSVP[]; PageInfo?: any }>(rsvpUrl, {
method: 'GET', method: 'GET',