Initial marketplace implementation

Full-stack marketplace for buying/selling second-hand items.
React 19 + TypeScript + Tailwind CSS v4 frontend with 17 screens,
Express + Prisma + Socket.io backend, Stripe payments, JWT auth.

Deployed at https://marketplace.173.212.212.157.sslip.io/

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
delta-lynx-89e8
2026-02-22 07:00:44 -08:00
commit b37b734c82
95 changed files with 10921 additions and 0 deletions

View File

@@ -0,0 +1,88 @@
import type { Server as HTTPServer } from 'http';
import { Server } from 'socket.io';
import { verifyAccessToken } from '../utils/jwt.js';
import { prisma } from '../config/database.js';
export function setupSocket(httpServer: HTTPServer) {
const io = new Server(httpServer, {
cors: {
origin: process.env['CLIENT_URL'] || 'http://localhost:5173',
credentials: true,
},
});
io.use((socket, next) => {
const token = socket.handshake.auth.token;
if (!token) return next(new Error('Authentication required'));
try {
const payload = verifyAccessToken(token);
socket.data.userId = payload.userId;
next();
} catch {
next(new Error('Invalid token'));
}
});
io.on('connection', (socket) => {
const userId = socket.data.userId;
socket.join(`user:${userId}`);
socket.on('join_conversation', (conversationId: string) => {
socket.join(`conversation:${conversationId}`);
});
socket.on('leave_conversation', (conversationId: string) => {
socket.leave(`conversation:${conversationId}`);
});
socket.on('send_message', async (data: { conversationId: string; content: string; offerAmount?: number }) => {
try {
const message = await prisma.message.create({
data: {
content: data.content,
senderId: userId,
conversationId: data.conversationId,
offerAmount: data.offerAmount,
},
include: { sender: { select: { id: true, fullName: true, avatar: true } } },
});
await prisma.conversation.update({
where: { id: data.conversationId },
data: { updatedAt: new Date() },
});
io.to(`conversation:${data.conversationId}`).emit('new_message', message);
const conversation = await prisma.conversation.findUnique({ where: { id: data.conversationId } });
if (conversation) {
const recipientId = conversation.user1Id === userId ? conversation.user2Id : conversation.user1Id;
io.to(`user:${recipientId}`).emit('message_notification', { conversationId: data.conversationId, message });
}
} catch (error) {
socket.emit('error', { message: 'Failed to send message' });
}
});
socket.on('typing', (conversationId: string) => {
socket.to(`conversation:${conversationId}`).emit('user_typing', { userId, conversationId });
});
socket.on('stop_typing', (conversationId: string) => {
socket.to(`conversation:${conversationId}`).emit('user_stop_typing', { userId, conversationId });
});
socket.on('mark_read', async (conversationId: string) => {
await prisma.message.updateMany({
where: { conversationId, senderId: { not: userId }, isRead: false },
data: { isRead: true },
});
});
socket.on('disconnect', () => {
// Cleanup handled by Socket.io
});
});
return io;
}