Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions backend/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,7 @@ import contactRoutes from './routes/contactRoutes.js';
import fileUploadRoutes from './routes/fileUploadRoutes.js';
import pageContentRoutes from './routes/pageContentRoutes.js';
import mapRoutes from './routes/mapRoutes.js';
import { prisma } from './config/prisma.js';
import { clerkClient, clerkMiddleware } from '@clerk/express';
import { clerkMiddleware } from '@clerk/express';
import { connectRedis } from './config/redis.js';
import { warmCache } from './utils/cacheWarmer.js';

Expand Down Expand Up @@ -75,4 +74,10 @@ app.use('/api/files', fileUploadRoutes);
app.use('/api/page-content', pageContentRoutes);
app.use('/api/map', mapRoutes);

// Add new route imports and register new routes

// For Stripe webhook (MUST be before express.json() middleware):
// Add this line BEFORE app.use(express.json()):
// app.use('/api/stripe/webhook', express.raw({ type: 'application/json' }), stripeRoutes);

export default app;
129 changes: 129 additions & 0 deletions backend/config/socket.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import { Server as SocketIOServer } from 'socket.io';
import { Server as HTTPServer } from 'http';
import { verifyToken } from '@clerk/express';

let io: SocketIOServer | null = null;

/**
* Call this function in server.ts after creating the HTTP server
*/
export const initializeSocket = (httpServer: HTTPServer): SocketIOServer => {
io = new SocketIOServer(httpServer, {
cors: {
origin: process.env.FRONTEND_URL || 'http://localhost:5173',
methods: ['GET', 'POST'],
credentials: true,
},
});

io.use(async (socket, next) => {
try {
const token = socket.handshake.auth.token;

if (!token) {
return next(new Error('Authentication token required'));
}

// Verify Clerk token
const verified = await verifyToken(token, {
secretKey: process.env.CLERK_SECRET_KEY!,
clockSkewInMs: 5000,
});

if (!verified) {
return next(new Error('Invalid authentication token'));
}

socket.data.userId = verified.sub;
socket.data.sessionId = verified.sid;

next();
} catch (error) {
console.error('Socket authentication error:', error);
next(new Error('Authentication failed'));
}
});

io.on('connection', (socket) => {
console.log(`User connected: ${socket.data.userId} (Socket: ${socket.id})`);

socket.on('disconnect', () => {
console.log(`User disconnected: ${socket.data.userId} (Socket: ${socket.id})`);
});

// Uncomment and implement these handlers as needed

/*
// Join a conversation room
socket.on('join_conversation', (data: { conversationId: string }) => {
socket.join(`conversation:${data.conversationId}`);
console.log(`User ${socket.data.userId} joined conversation ${data.conversationId}`);
});

// Leave a conversation room
socket.on('leave_conversation', (data: { conversationId: string }) => {
socket.leave(`conversation:${data.conversationId}`);
console.log(`User ${socket.data.userId} left conversation ${data.conversationId}`);
});

// Send message event (you'll also save to DB in the controller)
socket.on('send_message', async (data: { conversationId: string; content: string }) => {
try {
// Verify user is part of this conversation (check DB)
// Save message to database via controller
// Then emit to all users in the conversation room
io?.to(`conversation:${data.conversationId}`).emit('new_message', {
conversationId: data.conversationId,
message: {
// message data from DB
}
});
} catch (error) {
socket.emit('error', { message: 'Failed to send message' });
}
});

// Mark messages as read
socket.on('mark_as_read', async (data: { conversationId: string }) => {
try {
// Update DB to mark messages as read
// Emit to conversation room
io?.to(`conversation:${data.conversationId}`).emit('messages_read', {
conversationId: data.conversationId,
userId: socket.data.userId,
readAt: new Date().toISOString()
});
} catch (error) {
socket.emit('error', { message: 'Failed to mark as read' });
}
});
*/
});

return io;
};

export const getSocketIO = (): SocketIOServer => {
if (!io) {
throw new Error('Socket.IO not initialized. Call initializeSocket() first.');
}
return io;
};

export const emitToConversation = (
conversationId: string,
event: string,
data: any
): void => {
if (io) {
io.to(`conversation:${conversationId}`).emit(event, data);
}
};


export const emitToUser = (userId: string, event: string, data: any): void => {
// Implement userId -> socketId mapping
// You can store this mapping when users connect
// For now, this is a placeholder
console.warn('emitToUser not fully implemented. Needs userId -> socketId mapping.');
};
9 changes: 8 additions & 1 deletion backend/config/stripe.ts
Original file line number Diff line number Diff line change
@@ -1 +1,8 @@
// Stripe config later
import Stripe from 'stripe';

export const stripe = new Stripe(process.env.STRIPE_SECRET_KEY || '', {
apiVersion: '2025-02-24.acacia',
typescript: true,
});

export const STRIPE_WEBHOOK_SECRET = process.env.STRIPE_WEBHOOK_SECRET || '';
121 changes: 121 additions & 0 deletions backend/controllers/eventsController.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import { Request, Response } from 'express';
// import { prisma } from '../config/prisma.js';

export const eventsController = {
/**
* Get all events
*/
getAll: async (req: Request, res: Response) => {
try {
// Get filters from query params (status, upcoming)
// Fetch events with RSVP counts
res.status(501).json({ error: 'Not implemented' });
} catch (error: any) {
console.error('Error getting events:', error);
res.status(500).json({ error: error.message });
}
},

/**
* Get single event
*/
getById: async (req: Request, res: Response) => {
try {
// Get event ID from params
// Fetch event with RSVPs and creator info
res.status(501).json({ error: 'Not implemented' });
} catch (error: any) {
console.error('Error getting event:', error);
res.status(500).json({ error: error.message });
}
},

/**
* Create event (Admin only)
*/
create: async (req: Request, res: Response) => {
try {
// Verify user is admin
// Get event details from request body
// Create event in database
res.status(501).json({ error: 'Not implemented' });
} catch (error: any) {
console.error('Error creating event:', error);
res.status(500).json({ error: error.message });
}
},

/**
* Update event (Admin only)
*/
update: async (req: Request, res: Response) => {
try {
// Verify user is admin
// Get event ID from params and updates from body
// Update event in database
res.status(501).json({ error: 'Not implemented' });
} catch (error: any) {
console.error('Error updating event:', error);
res.status(500).json({ error: error.message });
}
},

/**
* Publish event (Admin only)
*/
publish: async (req: Request, res: Response) => {
try {
// Verify user is admin
// Update event status to PUBLISHED
// Send notification email to all organizations
res.status(501).json({ error: 'Not implemented' });
} catch (error: any) {
console.error('Error publishing event:', error);
res.status(500).json({ error: error.message });
}
},

/**
* Delete event (Admin only)
*/
delete: async (req: Request, res: Response) => {
try {
// Verify user is admin
// Delete event from database
res.status(501).json({ error: 'Not implemented' });
} catch (error: any) {
console.error('Error deleting event:', error);
res.status(500).json({ error: error.message });
}
},

/**
* RSVP to event
*/
rsvp: async (req: Request, res: Response) => {
try {
// Get eventId from params
// Get RSVP details from request body
// Check if event is full
// Create or update RSVP
res.status(501).json({ error: 'Not implemented' });
} catch (error: any) {
console.error('Error creating RSVP:', error);
res.status(500).json({ error: error.message });
}
},

/**
* Get user's RSVPs
*/
getMyRSVPs: async (req: Request, res: Response) => {
try {
// Get organizationId from req.user
// Fetch all RSVPs for organization
res.status(501).json({ error: 'Not implemented' });
} catch (error: any) {
console.error('Error getting RSVPs:', error);
res.status(500).json({ error: error.message });
}
},
};
96 changes: 96 additions & 0 deletions backend/controllers/messagesController.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { Request, Response } from 'express';
// import { prisma } from '../config/prisma.js';

export const messagesController = {
/**
* Get all conversations for current user
*/
getConversations: async (req: Request, res: Response) => {
try {
// Get userId and userRole from req.user
// Query conversations based on role (admin vs organization)
// Include unread message count
res.status(501).json({ error: 'Not implemented' });
} catch (error: any) {
console.error('Error getting conversations:', error);
res.status(500).json({ error: error.message });
}
},

/**
* Get messages in a conversation
*/
getMessages: async (req: Request, res: Response) => {
try {
// Get conversationId from params
// Verify user is part of conversation
// Fetch messages with sender info
// Mark messages as read
res.status(501).json({ error: 'Not implemented' });
} catch (error: any) {
console.error('Error getting messages:', error);
res.status(500).json({ error: error.message });
}
},

/**
* Create a new conversation
*/
createConversation: async (req: Request, res: Response) => {
try {
// Get recipientId, type, subject, initialMessage from request body
// Create conversation with correct participant relationships
// Create initial message if provided
res.status(501).json({ error: 'Not implemented' });
} catch (error: any) {
console.error('Error creating conversation:', error);
res.status(500).json({ error: error.message });
}
},

/**
* Send a message
*/
sendMessage: async (req: Request, res: Response) => {
try {
// Get conversationId, content, attachments from request body
// Verify user is part of conversation
// Create message with correct sender info
// Update conversation lastMessageAt
// Send email notification to recipient
// Emit Socket.io event for real-time update (optional)
res.status(501).json({ error: 'Not implemented' });
} catch (error: any) {
console.error('Error sending message:', error);
res.status(500).json({ error: error.message });
}
},

/**
* Mark messages as read
*/
markAsRead: async (req: Request, res: Response) => {
try {
// Get conversationId from params
// Mark unread messages as read for current user
res.status(501).json({ error: 'Not implemented' });
} catch (error: any) {
console.error('Error marking messages as read:', error);
res.status(500).json({ error: error.message });
}
},

/**
* Get unread message count
*/
getUnreadCount: async (req: Request, res: Response) => {
try {
// Get userId and userRole from req.user
// Count unread messages for user
res.status(501).json({ error: 'Not implemented' });
} catch (error: any) {
console.error('Error getting unread count:', error);
res.status(500).json({ error: error.message });
}
},
};
Loading
Loading