Skip to content

Commit a422456

Browse files
Merge pull request #107 from ChangePlusPlusVandy/devs
dev setup
2 parents 41d06f6 + fff7230 commit a422456

30 files changed

Lines changed: 2190 additions & 24 deletions

backend/app.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,7 @@ import contactRoutes from './routes/contactRoutes.js';
1717
import fileUploadRoutes from './routes/fileUploadRoutes.js';
1818
import pageContentRoutes from './routes/pageContentRoutes.js';
1919
import mapRoutes from './routes/mapRoutes.js';
20-
import { prisma } from './config/prisma.js';
21-
import { clerkClient, clerkMiddleware } from '@clerk/express';
20+
import { clerkMiddleware } from '@clerk/express';
2221
import { connectRedis } from './config/redis.js';
2322
import { warmCache } from './utils/cacheWarmer.js';
2423

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

77+
// Add new route imports and register new routes
78+
79+
// For Stripe webhook (MUST be before express.json() middleware):
80+
// Add this line BEFORE app.use(express.json()):
81+
// app.use('/api/stripe/webhook', express.raw({ type: 'application/json' }), stripeRoutes);
82+
7883
export default app;

backend/config/socket.ts

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
import { Server as SocketIOServer } from 'socket.io';
2+
import { Server as HTTPServer } from 'http';
3+
import { verifyToken } from '@clerk/express';
4+
5+
let io: SocketIOServer | null = null;
6+
7+
/**
8+
* Call this function in server.ts after creating the HTTP server
9+
*/
10+
export const initializeSocket = (httpServer: HTTPServer): SocketIOServer => {
11+
io = new SocketIOServer(httpServer, {
12+
cors: {
13+
origin: process.env.FRONTEND_URL || 'http://localhost:5173',
14+
methods: ['GET', 'POST'],
15+
credentials: true,
16+
},
17+
});
18+
19+
io.use(async (socket, next) => {
20+
try {
21+
const token = socket.handshake.auth.token;
22+
23+
if (!token) {
24+
return next(new Error('Authentication token required'));
25+
}
26+
27+
// Verify Clerk token
28+
const verified = await verifyToken(token, {
29+
secretKey: process.env.CLERK_SECRET_KEY!,
30+
clockSkewInMs: 5000,
31+
});
32+
33+
if (!verified) {
34+
return next(new Error('Invalid authentication token'));
35+
}
36+
37+
socket.data.userId = verified.sub;
38+
socket.data.sessionId = verified.sid;
39+
40+
next();
41+
} catch (error) {
42+
console.error('Socket authentication error:', error);
43+
next(new Error('Authentication failed'));
44+
}
45+
});
46+
47+
io.on('connection', (socket) => {
48+
console.log(`User connected: ${socket.data.userId} (Socket: ${socket.id})`);
49+
50+
socket.on('disconnect', () => {
51+
console.log(`User disconnected: ${socket.data.userId} (Socket: ${socket.id})`);
52+
});
53+
54+
// Uncomment and implement these handlers as needed
55+
56+
/*
57+
// Join a conversation room
58+
socket.on('join_conversation', (data: { conversationId: string }) => {
59+
socket.join(`conversation:${data.conversationId}`);
60+
console.log(`User ${socket.data.userId} joined conversation ${data.conversationId}`);
61+
});
62+
63+
// Leave a conversation room
64+
socket.on('leave_conversation', (data: { conversationId: string }) => {
65+
socket.leave(`conversation:${data.conversationId}`);
66+
console.log(`User ${socket.data.userId} left conversation ${data.conversationId}`);
67+
});
68+
69+
// Send message event (you'll also save to DB in the controller)
70+
socket.on('send_message', async (data: { conversationId: string; content: string }) => {
71+
try {
72+
// Verify user is part of this conversation (check DB)
73+
// Save message to database via controller
74+
// Then emit to all users in the conversation room
75+
io?.to(`conversation:${data.conversationId}`).emit('new_message', {
76+
conversationId: data.conversationId,
77+
message: {
78+
// message data from DB
79+
}
80+
});
81+
} catch (error) {
82+
socket.emit('error', { message: 'Failed to send message' });
83+
}
84+
});
85+
86+
// Mark messages as read
87+
socket.on('mark_as_read', async (data: { conversationId: string }) => {
88+
try {
89+
// Update DB to mark messages as read
90+
// Emit to conversation room
91+
io?.to(`conversation:${data.conversationId}`).emit('messages_read', {
92+
conversationId: data.conversationId,
93+
userId: socket.data.userId,
94+
readAt: new Date().toISOString()
95+
});
96+
} catch (error) {
97+
socket.emit('error', { message: 'Failed to mark as read' });
98+
}
99+
});
100+
*/
101+
});
102+
103+
return io;
104+
};
105+
106+
export const getSocketIO = (): SocketIOServer => {
107+
if (!io) {
108+
throw new Error('Socket.IO not initialized. Call initializeSocket() first.');
109+
}
110+
return io;
111+
};
112+
113+
export const emitToConversation = (
114+
conversationId: string,
115+
event: string,
116+
data: any
117+
): void => {
118+
if (io) {
119+
io.to(`conversation:${conversationId}`).emit(event, data);
120+
}
121+
};
122+
123+
124+
export const emitToUser = (userId: string, event: string, data: any): void => {
125+
// Implement userId -> socketId mapping
126+
// You can store this mapping when users connect
127+
// For now, this is a placeholder
128+
console.warn('emitToUser not fully implemented. Needs userId -> socketId mapping.');
129+
};

backend/config/stripe.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,8 @@
1-
// Stripe config later
1+
import Stripe from 'stripe';
2+
3+
export const stripe = new Stripe(process.env.STRIPE_SECRET_KEY || '', {
4+
apiVersion: '2025-02-24.acacia',
5+
typescript: true,
6+
});
7+
8+
export const STRIPE_WEBHOOK_SECRET = process.env.STRIPE_WEBHOOK_SECRET || '';
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
import { Request, Response } from 'express';
2+
// import { prisma } from '../config/prisma.js';
3+
4+
export const eventsController = {
5+
/**
6+
* Get all events
7+
*/
8+
getAll: async (req: Request, res: Response) => {
9+
try {
10+
// Get filters from query params (status, upcoming)
11+
// Fetch events with RSVP counts
12+
res.status(501).json({ error: 'Not implemented' });
13+
} catch (error: any) {
14+
console.error('Error getting events:', error);
15+
res.status(500).json({ error: error.message });
16+
}
17+
},
18+
19+
/**
20+
* Get single event
21+
*/
22+
getById: async (req: Request, res: Response) => {
23+
try {
24+
// Get event ID from params
25+
// Fetch event with RSVPs and creator info
26+
res.status(501).json({ error: 'Not implemented' });
27+
} catch (error: any) {
28+
console.error('Error getting event:', error);
29+
res.status(500).json({ error: error.message });
30+
}
31+
},
32+
33+
/**
34+
* Create event (Admin only)
35+
*/
36+
create: async (req: Request, res: Response) => {
37+
try {
38+
// Verify user is admin
39+
// Get event details from request body
40+
// Create event in database
41+
res.status(501).json({ error: 'Not implemented' });
42+
} catch (error: any) {
43+
console.error('Error creating event:', error);
44+
res.status(500).json({ error: error.message });
45+
}
46+
},
47+
48+
/**
49+
* Update event (Admin only)
50+
*/
51+
update: async (req: Request, res: Response) => {
52+
try {
53+
// Verify user is admin
54+
// Get event ID from params and updates from body
55+
// Update event in database
56+
res.status(501).json({ error: 'Not implemented' });
57+
} catch (error: any) {
58+
console.error('Error updating event:', error);
59+
res.status(500).json({ error: error.message });
60+
}
61+
},
62+
63+
/**
64+
* Publish event (Admin only)
65+
*/
66+
publish: async (req: Request, res: Response) => {
67+
try {
68+
// Verify user is admin
69+
// Update event status to PUBLISHED
70+
// Send notification email to all organizations
71+
res.status(501).json({ error: 'Not implemented' });
72+
} catch (error: any) {
73+
console.error('Error publishing event:', error);
74+
res.status(500).json({ error: error.message });
75+
}
76+
},
77+
78+
/**
79+
* Delete event (Admin only)
80+
*/
81+
delete: async (req: Request, res: Response) => {
82+
try {
83+
// Verify user is admin
84+
// Delete event from database
85+
res.status(501).json({ error: 'Not implemented' });
86+
} catch (error: any) {
87+
console.error('Error deleting event:', error);
88+
res.status(500).json({ error: error.message });
89+
}
90+
},
91+
92+
/**
93+
* RSVP to event
94+
*/
95+
rsvp: async (req: Request, res: Response) => {
96+
try {
97+
// Get eventId from params
98+
// Get RSVP details from request body
99+
// Check if event is full
100+
// Create or update RSVP
101+
res.status(501).json({ error: 'Not implemented' });
102+
} catch (error: any) {
103+
console.error('Error creating RSVP:', error);
104+
res.status(500).json({ error: error.message });
105+
}
106+
},
107+
108+
/**
109+
* Get user's RSVPs
110+
*/
111+
getMyRSVPs: async (req: Request, res: Response) => {
112+
try {
113+
// Get organizationId from req.user
114+
// Fetch all RSVPs for organization
115+
res.status(501).json({ error: 'Not implemented' });
116+
} catch (error: any) {
117+
console.error('Error getting RSVPs:', error);
118+
res.status(500).json({ error: error.message });
119+
}
120+
},
121+
};
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import { Request, Response } from 'express';
2+
// import { prisma } from '../config/prisma.js';
3+
4+
export const messagesController = {
5+
/**
6+
* Get all conversations for current user
7+
*/
8+
getConversations: async (req: Request, res: Response) => {
9+
try {
10+
// Get userId and userRole from req.user
11+
// Query conversations based on role (admin vs organization)
12+
// Include unread message count
13+
res.status(501).json({ error: 'Not implemented' });
14+
} catch (error: any) {
15+
console.error('Error getting conversations:', error);
16+
res.status(500).json({ error: error.message });
17+
}
18+
},
19+
20+
/**
21+
* Get messages in a conversation
22+
*/
23+
getMessages: async (req: Request, res: Response) => {
24+
try {
25+
// Get conversationId from params
26+
// Verify user is part of conversation
27+
// Fetch messages with sender info
28+
// Mark messages as read
29+
res.status(501).json({ error: 'Not implemented' });
30+
} catch (error: any) {
31+
console.error('Error getting messages:', error);
32+
res.status(500).json({ error: error.message });
33+
}
34+
},
35+
36+
/**
37+
* Create a new conversation
38+
*/
39+
createConversation: async (req: Request, res: Response) => {
40+
try {
41+
// Get recipientId, type, subject, initialMessage from request body
42+
// Create conversation with correct participant relationships
43+
// Create initial message if provided
44+
res.status(501).json({ error: 'Not implemented' });
45+
} catch (error: any) {
46+
console.error('Error creating conversation:', error);
47+
res.status(500).json({ error: error.message });
48+
}
49+
},
50+
51+
/**
52+
* Send a message
53+
*/
54+
sendMessage: async (req: Request, res: Response) => {
55+
try {
56+
// Get conversationId, content, attachments from request body
57+
// Verify user is part of conversation
58+
// Create message with correct sender info
59+
// Update conversation lastMessageAt
60+
// Send email notification to recipient
61+
// Emit Socket.io event for real-time update (optional)
62+
res.status(501).json({ error: 'Not implemented' });
63+
} catch (error: any) {
64+
console.error('Error sending message:', error);
65+
res.status(500).json({ error: error.message });
66+
}
67+
},
68+
69+
/**
70+
* Mark messages as read
71+
*/
72+
markAsRead: async (req: Request, res: Response) => {
73+
try {
74+
// Get conversationId from params
75+
// Mark unread messages as read for current user
76+
res.status(501).json({ error: 'Not implemented' });
77+
} catch (error: any) {
78+
console.error('Error marking messages as read:', error);
79+
res.status(500).json({ error: error.message });
80+
}
81+
},
82+
83+
/**
84+
* Get unread message count
85+
*/
86+
getUnreadCount: async (req: Request, res: Response) => {
87+
try {
88+
// Get userId and userRole from req.user
89+
// Count unread messages for user
90+
res.status(501).json({ error: 'Not implemented' });
91+
} catch (error: any) {
92+
console.error('Error getting unread count:', error);
93+
res.status(500).json({ error: error.message });
94+
}
95+
},
96+
};

0 commit comments

Comments
 (0)