File size: 2,453 Bytes
24727a3
 
c3b49db
24727a3
 
 
 
 
 
05cee1f
24727a3
 
c3b49db
24727a3
 
 
05cee1f
24727a3
05cee1f
24727a3
 
 
 
05cee1f
24727a3
05cee1f
24727a3
 
 
c3b49db
05cee1f
47e2259
05cee1f
c3b49db
47e2259
4a31b63
47e2259
05cee1f
4a31b63
47e2259
4a31b63
e8bface
 
24727a3
 
 
 
 
 
 
 
 
4b59f7c
24727a3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
05cee1f
24727a3
 
 
05cee1f
24727a3
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
import http from 'http'
import { Server } from 'socket.io'
import { execSync } from 'child_process'
import app from './app.js'
import { config } from './config/index.js'
import { prisma } from './config/database.js'
import { redis, safeRedis } from './config/redis.js'
import { setupChatSocket } from './modules/chat/chat.socket.js'
import { verifySocketToken } from './lib/jwt.js'
import { logger } from './lib/logger.js'

async function bootstrap() {
  try { await redis.connect() } catch {}

  try {
    await redis.ping()
    logger.info('Redis connected')
  } catch {
    logger.warn('Redis unavailable, running without cache')
  }

  try {
    await prisma.$connect()
    logger.info('Database connected')
  } catch (err) {
    logger.fatal(err, 'Database connection failed')
    process.exit(1)
  }

  try {
    logger.info('Running database migrations...')
    execSync('npx --yes prisma migrate deploy', { stdio: 'inherit', cwd: process.cwd(), timeout: 30000 })
    logger.info('Database schema synced')
  } catch (err) {
    logger.warn({ err: err.message }, 'Migrate deploy failed, trying db push...')
    try {
      execSync('npx --yes prisma db push --skip-generate', { stdio: 'inherit', cwd: process.cwd(), timeout: 30000 })
      logger.info('Database schema synced via push')
    } catch (err2) {
      logger.warn('Db push also failed, server will start anyway — schema may be out of date')
    }
  }

  const server = http.createServer(app)

  const io = new Server(server, {
    cors: {
      origin: config.FRONTEND_URL,
      credentials: true,
    },
    pingTimeout: 60000,
    pingInterval: 25000,
    transports: ['websocket', 'polling'],
  })

  io.use(async (socket, next) => {
    const token = socket.handshake.auth.token
    if (!token) return next(new Error('Authentication required'))
    const decoded = await verifySocketToken(token)
    if (!decoded) return next(new Error('Invalid token'))
    socket.userId = decoded.sub
    socket.userRole = decoded.role
    next()
  })

  setupChatSocket(io)
  app.set('io', io)

  server.listen(config.PORT, () => {
    logger.info({ port: config.PORT, env: config.NODE_ENV }, 'Server started')
  })

  const shutdown = async () => {
    logger.info('Shutting down gracefully...')
    io.close()
    server.close()
    await prisma.$disconnect()
    await redis.quit()
    process.exit(0)
  }

  process.on('SIGINT', shutdown)
  process.on('SIGTERM', shutdown)
}

bootstrap()