Skip to content

Commit a310cfd

Browse files
author
Ubuntu
committed
backend
1 parent 9db327e commit a310cfd

File tree

9 files changed

+1496
-2835
lines changed

9 files changed

+1496
-2835
lines changed

package-lock.json

Lines changed: 1165 additions & 2710 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -10,40 +10,42 @@
1010
"prisma:migrate": "prisma migrate deploy"
1111
},
1212
"dependencies": {
13-
"@prisma/client": "^3.15.2",
14-
"axios": "^0.27.2",
13+
"@prisma/client": "^5.0.0",
14+
"axios": "^1.6.0",
1515
"compression": "^1.7.4",
1616
"cors": "^2.8.5",
1717
"dotenv": "^16.4.5",
18-
"express": "^4.18.1",
18+
"express": "^4.18.2",
1919
"express-session": "^1.18.0",
20-
"googleapis": "^140.0.0",
21-
"helmet": "^5.1.1",
20+
"googleapis": "^130.0.0",
21+
"helmet": "^7.1.0",
2222
"joi": "^17.13.3",
23-
"jsonwebtoken": "^8.5.1",
23+
"jsonwebtoken": "^9.0.0",
24+
"masterchat": "^1.1.0",
2425
"node-cron": "^3.0.3",
25-
"passport": "^0.6.0",
26+
"passport": "^0.7.0",
2627
"passport-google-oauth20": "^2.0.0",
2728
"passport-jwt": "^4.0.1",
2829
"redis": "^4.6.14",
29-
"winston": "^3.8.1",
30+
"socket.io": "^4.7.5",
31+
"winston": "^3.11.0",
3032
"youtube-chat": "^2.2.0"
3133
},
3234
"devDependencies": {
33-
"@types/compression": "^1.7.2",
34-
"@types/cors": "^2.8.12",
35-
"@types/express": "^4.17.13",
35+
"@types/compression": "^1.7.5",
36+
"@types/cors": "^2.8.17",
37+
"@types/express": "^4.17.21",
3638
"@types/express-session": "^1.18.0",
3739
"@types/gapi": "^0.0.47",
3840
"@types/gapi.auth2": "^0.0.60",
39-
"@types/jsonwebtoken": "^8.5.8",
40-
"@types/node": "^18.0.0",
41+
"@types/jsonwebtoken": "^9.0.5",
42+
"@types/node": "^20.10.0",
4143
"@types/node-cron": "^3.0.11",
42-
"@types/passport": "^1.0.9",
43-
"@types/passport-google-oauth20": "^2.0.11",
44+
"@types/passport": "^1.0.16",
45+
"@types/passport-google-oauth20": "^2.0.14",
4446
"@types/passport-jwt": "^4.0.1",
45-
"prisma": "^3.15.2",
47+
"prisma": "^5.0.0",
4648
"ts-node-dev": "^2.0.0",
47-
"typescript": "^4.7.4"
49+
"typescript": "^5.3.0"
4850
}
4951
}

src/app.ts

Lines changed: 40 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,31 @@ import cors from 'cors';
44
import helmet from 'helmet';
55
import compression from 'compression';
66
import session from 'express-session';
7+
import http from 'http';
8+
import { Server } from 'socket.io';
79
import { errorHandler } from './middlewares/errorHandler';
810
import authRoutes from './routes/auth';
9-
import chessRoutes from './routes/chess';// Import the test routes
11+
import chessRoutes from './routes/chess';
12+
import setupChatRoutes from './routes/chat';
13+
import { setupChatSocket } from './controllers/chatController';
1014
import './config/passport';
15+
import { logger } from './utils/logger';
1116
import './utils/scheduler';
12-
import chatRoutes from './routes/chat'; // Added import for chatRoutes
1317

1418
const app = express();
1519

20+
const allowedOrigins = [process.env.FRONTEND, 'http://localhost:5173', 'https://www.bmsamay.com'];
21+
1622
// Configure CORS to allow requests from the frontend
1723
app.use(cors({
18-
origin: process.env.FRONTEND || 'wa', // Use the FRONTEND environment variable or default to localhost
19-
credentials: true // Allow credentials (cookies, authorization headers, etc.)
24+
origin: (origin, callback) => {
25+
if (!origin || allowedOrigins.indexOf(origin) !== -1) {
26+
callback(null, true);
27+
} else {
28+
callback(new Error('Not allowed by CORS'));
29+
}
30+
},
31+
credentials: true
2032
}));
2133

2234
app.use(helmet());
@@ -26,18 +38,38 @@ app.use(express.urlencoded({ extended: true }));
2638

2739
// Configure express-session
2840
app.use(session({
29-
secret: process.env.SESSION_SECRET || 'your_secret_key', // Use a strong secret key
41+
secret: process.env.SESSION_SECRET || 'your_secret_key',
3042
resave: false,
3143
saveUninitialized: false,
32-
cookie: { secure: process.env.NODE_ENV === 'production' } // Use secure cookies in production
44+
cookie: { secure: process.env.NODE_ENV === 'production' }
3345
}));
3446

3547
app.use(passport.initialize());
3648
app.use(passport.session());
3749

50+
// Create HTTP server and setup Socket.IO
51+
const server = http.createServer(app);
52+
const io = new Server(server, {
53+
cors: {
54+
origin: allowedOrigins,
55+
methods: ["GET", "POST"],
56+
credentials: true
57+
},
58+
pingTimeout: 60000,
59+
pingInterval: 25000,
60+
});
61+
62+
// Setup routes
3863
app.use('/api/auth', authRoutes);
39-
app.use('/api/chess', chessRoutes);// Use the test routes
40-
app.use('/api/chat', chatRoutes); // Use the imported chatRoutes
64+
app.use('/api/chess', chessRoutes);
65+
app.use('/api/chat', setupChatRoutes(io));
4166
app.use(errorHandler);
4267

68+
// Setup chat socket connections
69+
setupChatSocket(io);
70+
71+
server.listen(2999, () => {
72+
logger.info(`Server is running on port ${2999}`);
73+
});
74+
4375
export default app;

src/chatController.ts

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import { Server, Socket } from 'socket.io';
2+
import { Masterchat, stringify } from 'masterchat';
3+
4+
let masterchat: Masterchat | null = null;
5+
let activeSockets: Map<string, Socket> = new Map();
6+
7+
const HEARTBEAT_INTERVAL = 30000; // 30 seconds
8+
const HEARTBEAT_TIMEOUT = 60000; // 60 seconds
9+
10+
const setupChatSocket = (io: Server) => {
11+
const startMasterchat = async (videoUrl: string) => {
12+
console.log(`Attempting to initialize Masterchat for video: ${videoUrl}`);
13+
if (masterchat) {
14+
console.log('Stopping existing Masterchat instance');
15+
await masterchat.stop();
16+
}
17+
18+
try {
19+
console.log('Creating new Masterchat instance');
20+
masterchat = await Masterchat.init(videoUrl);
21+
console.log('Masterchat initialized successfully');
22+
23+
const chats = masterchat.iter().filter((action) => action.type === "addChatItemAction");
24+
25+
for await (const chat of chats) {
26+
console.log('Emitting chat message:', chat);
27+
io.emit('chatMessage', {
28+
author: {
29+
name: chat.authorName,
30+
channelId: chat.authorChannelId,
31+
},
32+
message: stringify(chat.message),
33+
timestamp: new Date(),
34+
});
35+
}
36+
} catch (error) {
37+
console.error('Error initializing Masterchat:', error);
38+
io.emit('error', 'Failed to start listening for live chat messages');
39+
masterchat = null;
40+
}
41+
};
42+
43+
io.on('connection', (socket: Socket) => {
44+
console.log(`New client connected: ${socket.id}`);
45+
activeSockets.set(socket.id, socket);
46+
console.log(`Active connections: ${activeSockets.size}`);
47+
48+
socket.on('switchChannel', (videoUrl: string) => {
49+
startMasterchat(videoUrl);
50+
});
51+
52+
socket.on('disconnect', (reason) => {
53+
console.log(`Client disconnected: ${socket.id}, Reason: ${reason}`);
54+
activeSockets.delete(socket.id);
55+
console.log(`Active connections: ${activeSockets.size}`);
56+
});
57+
58+
// Implement heartbeat
59+
let heartbeatTimeout: NodeJS.Timeout;
60+
const resetHeartbeat = () => {
61+
if (heartbeatTimeout) clearTimeout(heartbeatTimeout);
62+
heartbeatTimeout = setTimeout(() => {
63+
console.log(`Heartbeat timeout for client: ${socket.id}`);
64+
socket.disconnect(true);
65+
}, HEARTBEAT_TIMEOUT);
66+
};
67+
68+
socket.on('heartbeat', () => {
69+
resetHeartbeat();
70+
});
71+
72+
resetHeartbeat();
73+
});
74+
75+
// Periodic cleanup of stale connections
76+
setInterval(() => {
77+
for (const [id, socket] of activeSockets) {
78+
if (!socket.connected) {
79+
activeSockets.delete(id);
80+
socket.disconnect(true);
81+
console.log(`Removed stale connection: ${id}`);
82+
}
83+
}
84+
85+
console.log(`Active connections after cleanup: ${activeSockets.size}`);
86+
}, HEARTBEAT_INTERVAL);
87+
};
88+
89+
export { setupChatSocket };

src/config/passport.ts

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ interface ProfileJson {
1919

2020
const clientID = process.env.GOOGLE_CLIENT_ID;
2121
const clientSecret = process.env.GOOGLE_CLIENT_SECRET;
22-
const callbackURL = process.env.GOOGLE_CALLBACK_URL || 'https://api.bmsamay.com/api/auth/google/callback';
22+
const callbackURL = process.env.GOOGLE_REDIRECT_URI;
2323

2424
console.log('Google Client ID:', clientID);
2525
console.log('Google Callback URL:', callbackURL);
@@ -38,17 +38,6 @@ passport.use(
3838
},
3939
async (accessToken, refreshToken, profile: any, done) => {
4040
try {
41-
const oauth2Client = new google.auth.OAuth2();
42-
oauth2Client.setCredentials({ access_token: accessToken });
43-
const youtube = google.youtube({ version: 'v3', auth: oauth2Client });
44-
45-
const response = await youtube.channels.list({
46-
part: ['id'],
47-
mine: true,
48-
});
49-
50-
const youtubeChannelId = response.data.items?.[0]?.id;
51-
5241
let user = await prisma.user.findUnique({
5342
where: { googleId: profile.id },
5443
include: { chessInfo: true },
@@ -61,7 +50,6 @@ passport.use(
6150
data: {
6251
email: profile.emails?.[0].value,
6352
name: profile.displayName,
64-
youtubeChannelId: youtubeChannelId,
6553
},
6654
include: { chessInfo: true },
6755
});
@@ -70,7 +58,24 @@ passport.use(
7058
await createOrUpdateChessInfo(user.chessUsername, user.id);
7159
}
7260
} else {
73-
// New user: Create a new user record
61+
// New user: Create a new user record and attempt to fetch YouTube channel ID
62+
let youtubeChannelId = null;
63+
try {
64+
const oauth2Client = new google.auth.OAuth2();
65+
oauth2Client.setCredentials({ access_token: accessToken });
66+
const youtube = google.youtube({ version: 'v3', auth: oauth2Client });
67+
68+
const response = await youtube.channels.list({
69+
part: ['id'],
70+
mine: true,
71+
});
72+
73+
youtubeChannelId = response.data.items?.[0]?.id || null;
74+
} catch (error) {
75+
console.error('Error fetching YouTube channel ID:', error);
76+
// Continue with account creation even if YouTube ID fetch fails
77+
}
78+
7479
user = await prisma.user.create({
7580
data: {
7681
googleId: profile.id,

0 commit comments

Comments
 (0)