Add NocoDB events/RSVPs table config and improve session handling
All checks were successful
Build And Push Image / docker (push) Successful in 3m24s
All checks were successful
Build And Push Image / docker (push) Successful in 3m24s
- 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
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<NocoDBSettings> {
|
||||
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<NocoDBSettings> {
|
||||
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'
|
||||
};
|
||||
|
||||
@@ -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<EventRSVP>) {
|
||||
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<EventRSVP>(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<EventRSVP>) {
|
||||
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<EventRSVP>(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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user