2025-08-12 04:25:35 +02:00
// server/utils/nocodb-events.ts
import type { Event , EventRSVP , EventsResponse , EventFilters } from '~/utils/types' ;
2025-08-12 12:29:59 +02:00
import { getEffectiveNocoDBConfig } from './admin-config' ;
2025-08-12 04:25:35 +02:00
/ * *
* Creates a client for interacting with the Events NocoDB table
* Provides CRUD operations and specialized queries for events and RSVPs
* /
export function createNocoDBEventsClient() {
2025-08-12 12:24:16 +02:00
// Get effective configuration from admin config system
const effectiveConfig = getEffectiveNocoDBConfig ( ) ;
2025-08-12 04:25:35 +02:00
2025-08-12 12:24:16 +02:00
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' ;
2025-08-12 04:25:35 +02:00
if ( ! baseUrl || ! token || ! eventsBaseId ) {
throw new Error ( 'Events NocoDB configuration is incomplete. Please check environment variables.' ) ;
}
fix(events): Convert events NocoDB client from v1 to v2 API
- 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.
2025-08-12 13:02:13 +02:00
// 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.'
} ) ;
}
}
2025-08-12 04:25:35 +02:00
const headers = {
'xc-token' : token ,
'Content-Type' : 'application/json'
} ;
const eventsClient = {
/ * *
* Find all events with optional filtering
* /
async findAll ( filters? : EventFilters & { limit? : number ; offset? : number } ) {
const queryParams = new URLSearchParams ( ) ;
if ( filters ? . limit ) queryParams . set ( 'limit' , filters . limit . toString ( ) ) ;
if ( filters ? . offset ) queryParams . set ( 'offset' , filters . offset . toString ( ) ) ;
// Build where clause for filtering
const whereConditions : string [ ] = [ ] ;
if ( filters ? . start_date && filters ? . end_date ) {
whereConditions . push ( ` (start_datetime >= ' ${ filters . start_date } ' AND start_datetime <= ' ${ filters . end_date } ') ` ) ;
}
if ( filters ? . event_type ) {
whereConditions . push ( ` (event_type = ' ${ filters . event_type } ') ` ) ;
}
if ( filters ? . visibility ) {
whereConditions . push ( ` (visibility = ' ${ filters . visibility } ') ` ) ;
} else if ( filters ? . user_role ) {
// Role-based visibility filtering
if ( filters . user_role === 'user' ) {
whereConditions . push ( ` (visibility = 'public') ` ) ;
} else if ( filters . user_role === 'board' ) {
whereConditions . push ( ` (visibility = 'public' OR visibility = 'board-only') ` ) ;
}
// Admin sees all events (no filter)
}
if ( filters ? . status ) {
whereConditions . push ( ` (status = ' ${ filters . status } ') ` ) ;
} else {
// Default to active events only
whereConditions . push ( ` (status = 'active') ` ) ;
}
if ( filters ? . search ) {
whereConditions . push ( ` (title LIKE '% ${ filters . search } %' OR description LIKE '% ${ filters . search } %') ` ) ;
}
if ( whereConditions . length > 0 ) {
queryParams . set ( 'where' , whereConditions . join ( ' AND ' ) ) ;
}
// Sort by start date
queryParams . set ( 'sort' , 'start_datetime' ) ;
fix(events): Convert events NocoDB client from v1 to v2 API
- 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.
2025-08-12 13:02:13 +02:00
const url = ` ${ baseUrl } /api/v2/tables/ ${ eventsTableId } /records? ${ queryParams . toString ( ) } ` ;
2025-08-12 04:25:35 +02:00
const response = await $fetch ( url , {
method : 'GET' ,
headers
} ) ;
return response ;
} ,
/ * *
* Find a single event by ID
* /
async findOne ( id : string ) {
fix(events): Convert events NocoDB client from v1 to v2 API
- 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.
2025-08-12 13:02:13 +02:00
const url = ` ${ baseUrl } /api/v2/tables/ ${ eventsTableId } /records/ ${ id } ` ;
2025-08-12 04:25:35 +02:00
return await $fetch < Event > ( url , {
method : 'GET' ,
headers
} ) ;
} ,
/ * *
* Create a new event
* /
async create ( eventData : Partial < Event > ) {
fix(events): Convert events NocoDB client from v1 to v2 API
- 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.
2025-08-12 13:02:13 +02:00
const url = ` ${ baseUrl } /api/v2/tables/ ${ eventsTableId } /records ` ;
2025-08-12 04:25:35 +02:00
// Set default values
const data = {
. . . eventData ,
status : eventData.status || 'active' ,
created_at : new Date ( ) . toISOString ( ) ,
updated_at : new Date ( ) . toISOString ( )
} ;
return await $fetch < Event > ( url , {
method : 'POST' ,
headers ,
body : data
} ) ;
} ,
/ * *
* Update an existing event
* /
async update ( id : string , eventData : Partial < Event > ) {
fix(events): Convert events NocoDB client from v1 to v2 API
- 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.
2025-08-12 13:02:13 +02:00
const url = ` ${ baseUrl } /api/v2/tables/ ${ eventsTableId } /records ` ;
2025-08-12 04:25:35 +02:00
const data = {
fix(events): Convert events NocoDB client from v1 to v2 API
- 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.
2025-08-12 13:02:13 +02:00
Id : parseInt ( id ) ,
2025-08-12 04:25:35 +02:00
. . . eventData ,
updated_at : new Date ( ) . toISOString ( )
} ;
return await $fetch < Event > ( url , {
method : 'PATCH' ,
headers ,
body : data
} ) ;
} ,
/ * *
* Delete an event
* /
async delete ( id : string ) {
fix(events): Convert events NocoDB client from v1 to v2 API
- 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.
2025-08-12 13:02:13 +02:00
const url = ` ${ baseUrl } /api/v2/tables/ ${ eventsTableId } /records ` ;
2025-08-12 04:25:35 +02:00
return await $fetch ( url , {
method : 'DELETE' ,
fix(events): Convert events NocoDB client from v1 to v2 API
- 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.
2025-08-12 13:02:13 +02:00
headers ,
body : { Id : parseInt ( id ) }
2025-08-12 04:25:35 +02:00
} ) ;
} ,
/ * *
* Create an RSVP record for an event
* /
async createRSVP ( rsvpData : Partial < EventRSVP > ) {
fix(events): Convert events NocoDB client from v1 to v2 API
- 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.
2025-08-12 13:02:13 +02:00
const url = ` ${ baseUrl } /api/v2/tables/ ${ rsvpTableId } /records ` ;
2025-08-12 04:25:35 +02:00
const data = {
. . . rsvpData ,
2025-08-12 12:24:16 +02:00
created_time : new Date ( ) . toISOString ( ) ,
updated_time : new Date ( ) . toISOString ( )
2025-08-12 04:25:35 +02:00
} ;
return await $fetch < EventRSVP > ( url , {
method : 'POST' ,
headers ,
body : data
} ) ;
} ,
/ * *
* Find RSVPs for a specific event
* /
async findEventRSVPs ( eventId : string ) {
const queryParams = new URLSearchParams ( ) ;
queryParams . set ( 'where' , ` (event_id = ' ${ eventId } ') ` ) ;
2025-08-12 12:24:16 +02:00
queryParams . set ( 'sort' , 'created_time' ) ;
2025-08-12 04:25:35 +02:00
fix(events): Convert events NocoDB client from v1 to v2 API
- 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.
2025-08-12 13:02:13 +02:00
const url = ` ${ baseUrl } /api/v2/tables/ ${ rsvpTableId } /records? ${ queryParams . toString ( ) } ` ;
2025-08-12 04:25:35 +02:00
return await $fetch ( url , {
method : 'GET' ,
headers
} ) ;
} ,
/ * *
* Find a user ' s RSVP for a specific event
* /
async findUserRSVP ( eventId : string , memberId : string ) {
const queryParams = new URLSearchParams ( ) ;
queryParams . set ( 'where' , ` (event_id = ' ${ eventId } ' AND member_id = ' ${ memberId } ') ` ) ;
queryParams . set ( 'limit' , '1' ) ;
fix(events): Convert events NocoDB client from v1 to v2 API
- 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.
2025-08-12 13:02:13 +02:00
const url = ` ${ baseUrl } /api/v2/tables/ ${ rsvpTableId } /records? ${ queryParams . toString ( ) } ` ;
2025-08-12 04:25:35 +02:00
2025-08-12 12:24:16 +02:00
const response = await $fetch < { list? : EventRSVP [ ] ; PageInfo? : any } > ( url , {
2025-08-12 04:25:35 +02:00
method : 'GET' ,
headers
} ) ;
return response ? . list ? . [ 0 ] || null ;
} ,
/ * *
* Update an RSVP record
* /
async updateRSVP ( id : string , rsvpData : Partial < EventRSVP > ) {
fix(events): Convert events NocoDB client from v1 to v2 API
- 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.
2025-08-12 13:02:13 +02:00
const url = ` ${ baseUrl } /api/v2/tables/ ${ rsvpTableId } /records ` ;
2025-08-12 04:25:35 +02:00
const data = {
fix(events): Convert events NocoDB client from v1 to v2 API
- 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.
2025-08-12 13:02:13 +02:00
Id : parseInt ( id ) ,
2025-08-12 04:25:35 +02:00
. . . rsvpData ,
2025-08-12 12:24:16 +02:00
updated_time : new Date ( ) . toISOString ( )
2025-08-12 04:25:35 +02:00
} ;
return await $fetch < EventRSVP > ( url , {
method : 'PATCH' ,
headers ,
body : data
} ) ;
} ,
/ * *
* Update event attendance count ( for optimization )
* /
async updateAttendeeCount ( eventId : string , count : number ) {
fix(events): Convert events NocoDB client from v1 to v2 API
- 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.
2025-08-12 13:02:13 +02:00
const url = ` ${ baseUrl } /api/v2/tables/ ${ eventsTableId } /records ` ;
2025-08-12 04:25:35 +02:00
return await $fetch ( url , {
method : 'PATCH' ,
headers ,
body : {
fix(events): Convert events NocoDB client from v1 to v2 API
- 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.
2025-08-12 13:02:13 +02:00
Id : parseInt ( eventId ) ,
2025-08-12 04:25:35 +02:00
current_attendees : count.toString ( ) ,
updated_at : new Date ( ) . toISOString ( )
}
} ) ;
} ,
/ * *
* Get events for a specific user with their RSVP status
* /
async findUserEvents ( memberId : string , filters? : EventFilters ) {
// First get all visible events
2025-08-12 12:24:16 +02:00
const events = await this . findAll ( filters ) as { list? : Event [ ] ; PageInfo? : any } ;
2025-08-12 04:25:35 +02:00
if ( ! events . list || events . list . length === 0 ) {
return { list : [ ] , PageInfo : events.PageInfo } ;
}
// Get user's RSVPs for these events
const eventIds = events . list . map ( ( e : Event ) = > e . id ) ;
const rsvpQueryParams = new URLSearchParams ( ) ;
2025-08-12 12:24:16 +02:00
rsvpQueryParams . set ( 'where' , ` (member_id = ' ${ memberId } ' AND event_id IN ( ${ eventIds . map ( ( id : string ) = > ` ' ${ id } ' ` ) . join ( ',' ) } )) ` ) ;
2025-08-12 04:25:35 +02:00
fix(events): Convert events NocoDB client from v1 to v2 API
- 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.
2025-08-12 13:02:13 +02:00
const rsvpUrl = ` ${ baseUrl } /api/v2/tables/ ${ rsvpTableId } /records? ${ rsvpQueryParams . toString ( ) } ` ;
2025-08-12 04:25:35 +02:00
2025-08-12 12:24:16 +02:00
const rsvps = await $fetch < { list? : EventRSVP [ ] ; PageInfo? : any } > ( rsvpUrl , {
2025-08-12 04:25:35 +02:00
method : 'GET' ,
headers
} ) ;
// Map RSVPs to events
const rsvpMap = new Map ( ) ;
if ( rsvps . list ) {
rsvps . list . forEach ( ( rsvp : EventRSVP ) = > {
rsvpMap . set ( rsvp . event_id , rsvp ) ;
} ) ;
}
// Add RSVP information to events
const eventsWithRSVP = events . list . map ( ( event : Event ) = > ( {
. . . event ,
user_rsvp : rsvpMap.get ( event . id ) || null
} ) ) ;
return {
list : eventsWithRSVP ,
PageInfo : events.PageInfo
} ;
} ,
/ * *
* Generate payment reference for RSVP
* /
generatePaymentReference ( memberId : string , date? : Date ) : string {
const referenceDate = date || new Date ( ) ;
const dateString = referenceDate . toISOString ( ) . split ( 'T' ) [ 0 ] ; // YYYY-MM-DD
return ` EVT- ${ memberId } - ${ dateString } ` ;
} ,
/ * *
* Check if event has reached capacity
* /
async isEventFull ( eventId : string ) : Promise < boolean > {
const event = await this . findOne ( eventId ) ;
if ( ! event . max_attendees ) return false ; // Unlimited capacity
const maxAttendees = parseInt ( event . max_attendees ) ;
2025-08-12 12:24:16 +02:00
const currentAttendees = parseInt ( String ( event . current_attendees || 0 ) ) ;
2025-08-12 04:25:35 +02:00
return currentAttendees >= maxAttendees ;
}
} ;
return eventsClient ;
}
/ * *
* Utility function to transform Event data for FullCalendar
* /
export function transformEventForCalendar ( event : Event ) : any {
const eventTypeColors = {
'meeting' : { bg : '#2196f3' , border : '#1976d2' } ,
'social' : { bg : '#4caf50' , border : '#388e3c' } ,
'fundraiser' : { bg : '#ff9800' , border : '#f57c00' } ,
'workshop' : { bg : '#9c27b0' , border : '#7b1fa2' } ,
'board-only' : { bg : '#a31515' , border : '#8b1212' }
} ;
const colors = eventTypeColors [ event . event_type as keyof typeof eventTypeColors ] ||
{ bg : '#757575' , border : '#424242' } ;
return {
id : event.id ,
title : event.title ,
start : event.start_datetime ,
end : event.end_datetime ,
backgroundColor : colors.bg ,
borderColor : colors.border ,
textColor : '#ffffff' ,
extendedProps : {
description : event.description ,
location : event.location ,
event_type : event.event_type ,
is_paid : event.is_paid === 'true' ,
cost_members : event.cost_members ,
cost_non_members : event.cost_non_members ,
max_attendees : event.max_attendees ? parseInt ( event . max_attendees ) : null ,
current_attendees : event.current_attendees || 0 ,
user_rsvp : event.user_rsvp ,
visibility : event.visibility ,
creator : event.creator ,
status : event.status
}
} ;
}