Skip to content

Commit 4375f51

Browse files
authored
feat: initialize MongoDB necessary indexes on startup (#660)
Signed-off-by: Bob Du <[email protected]>
1 parent a0bb0ef commit 4375f51

File tree

2 files changed

+161
-0
lines changed

2 files changed

+161
-0
lines changed

service/src/index.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import {
2525
getUserById,
2626
getUsers,
2727
getUserStatisticsByDay,
28+
initializeMongoDB,
2829
updateApiKeyStatus,
2930
updateConfig,
3031
updateGiftCard,
@@ -999,4 +1000,11 @@ app.use('/api', uploadRouter)
9991000
app.use('', router)
10001001
app.use('/api', router)
10011002

1003+
// Initialize MongoDB connection and indexes on startup
1004+
initializeMongoDB().catch((error) => {
1005+
console.error('Failed to initialize MongoDB:', error)
1006+
// Continue startup even if initialization fails
1007+
// MongoDB operations will fail gracefully if connection is not established
1008+
})
1009+
10021010
app.listen(3002, () => globalThis.console.log('Server is running on port 3002'))

service/src/storage/mongo.ts

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ const url = process.env.MONGODB_URL
2424

2525
let client: MongoClient
2626
let dbName: string
27+
let isInitialized = false
28+
2729
try {
2830
client = new MongoClient(url)
2931
const parsedUrl = new URL(url)
@@ -53,6 +55,157 @@ const userPromptCol = client.db(dbName).collection<UserPrompt>('user_prompt')
5355
// }
5456
const redeemCol = client.db(dbName).collection<GiftCard>('giftcards')
5557

58+
/**
59+
* Initialize all database indexes
60+
* This should be called once when the application starts
61+
* Note: createIndex is idempotent - it won't fail if index already exists
62+
*/
63+
64+
async function initializeIndexes() {
65+
try {
66+
// ============================================
67+
// chat_room collection indexes
68+
// ============================================
69+
// Index for /api/chatrooms: getChatRooms(userId)
70+
// Query: { userId, status: { $ne: Status.Deleted } }
71+
await roomCol.createIndex({ userId: 1, status: 1 }, { name: 'idx_userId_status' })
72+
73+
// Index for getChatRoom: { userId, roomId, status: { $ne: Status.Deleted } }
74+
await roomCol.createIndex({ userId: 1, roomId: 1, status: 1 }, { name: 'idx_userId_roomId_status' })
75+
76+
// Index for existsChatRoom: { roomId, userId }
77+
await roomCol.createIndex({ roomId: 1, userId: 1 }, { name: 'idx_roomId_userId' })
78+
79+
// Index for getChatRoomsCount aggregation lookup
80+
await roomCol.createIndex({ roomId: 1 }, { name: 'idx_roomId' })
81+
82+
globalThis.console.log('✓ chat_room collection indexes created')
83+
84+
// ============================================
85+
// chat collection indexes
86+
// ============================================
87+
// Index for /api/chat-history: getChats(roomId, lastId, all)
88+
// Query: { roomId, uuid: { $lt: lastId }, status: { $ne: Status.Deleted } }
89+
// Sort: { dateTime: -1 }
90+
await chatCol.createIndex(
91+
{ roomId: 1, uuid: -1, dateTime: -1, status: 1 },
92+
{ name: 'idx_roomId_uuid_dateTime_status' },
93+
)
94+
95+
// Alternative index for queries without status filter
96+
await chatCol.createIndex(
97+
{ roomId: 1, uuid: -1, dateTime: -1 },
98+
{ name: 'idx_roomId_uuid_dateTime' },
99+
)
100+
101+
// Index for getChat: { roomId, uuid }
102+
await chatCol.createIndex({ roomId: 1, uuid: 1 }, { name: 'idx_roomId_uuid' })
103+
104+
// Index for getChatByMessageId: { 'options.messageId': messageId }
105+
await chatCol.createIndex({ 'options.messageId': 1 }, { name: 'idx_options_messageId' })
106+
107+
// Index for getChatRoomsCount aggregation lookup
108+
await chatCol.createIndex({ roomId: 1, dateTime: -1 }, { name: 'idx_roomId_dateTime' })
109+
110+
// Index for deleteAllChatRooms: updateMany({ userId, status: Status.Normal }, ...)
111+
await chatCol.createIndex({ userId: 1, status: 1 }, { name: 'idx_userId_status' })
112+
113+
globalThis.console.log('✓ chat collection indexes created')
114+
115+
// ============================================
116+
// user collection indexes
117+
// ============================================
118+
// Index for getUser: { email }
119+
try {
120+
await userCol.createIndex({ email: 1 }, { name: 'idx_email', unique: true })
121+
}
122+
catch (error: any) {
123+
// Ignore error if unique index already exists
124+
if (!error.message?.includes('E11000') && !error.message?.includes('duplicate key')) {
125+
throw error
126+
}
127+
}
128+
129+
// Index for getUsers: { status: { $ne: Status.Deleted } } with sort by createTime
130+
await userCol.createIndex({ status: 1, createTime: -1 }, { name: 'idx_status_createTime' })
131+
132+
globalThis.console.log('✓ user collection indexes created')
133+
134+
// ============================================
135+
// chat_usage collection indexes
136+
// ============================================
137+
// Index for getUserStatisticsByDay: { dateTime, userId }
138+
await usageCol.createIndex({ dateTime: 1, userId: 1 }, { name: 'idx_dateTime_userId' })
139+
140+
globalThis.console.log('✓ chat_usage collection indexes created')
141+
142+
// ============================================
143+
// giftcards collection indexes
144+
// ============================================
145+
// Index for getAmtByCardNo: { cardno }
146+
try {
147+
await redeemCol.createIndex({ cardno: 1 }, { name: 'idx_cardno', unique: true })
148+
}
149+
catch (error: any) {
150+
// Ignore error if unique index already exists
151+
if (!error.message?.includes('E11000') && !error.message?.includes('duplicate key')) {
152+
throw error
153+
}
154+
}
155+
156+
globalThis.console.log('✓ giftcards collection indexes created')
157+
158+
// ============================================
159+
// user_prompt collection indexes
160+
// ============================================
161+
// Index for getUserPromptList: { userId }
162+
await userPromptCol.createIndex({ userId: 1 }, { name: 'idx_userId' })
163+
164+
globalThis.console.log('✓ user_prompt collection indexes created')
165+
166+
// ============================================
167+
// key_config collection indexes
168+
// ============================================
169+
// Index for getKeys: { status: { $ne: Status.Disabled } }
170+
await keyCol.createIndex({ status: 1 }, { name: 'idx_status' })
171+
172+
globalThis.console.log('✓ key_config collection indexes created')
173+
174+
globalThis.console.log('✓ All database indexes initialized successfully')
175+
}
176+
catch (error: any) {
177+
// Log error but don't throw - allow application to start even if index creation fails
178+
globalThis.console.error('⚠ Warning: Error initializing database indexes:', error.message)
179+
globalThis.console.error(' Application will continue to start. You may need to create indexes manually.')
180+
}
181+
}
182+
183+
/**
184+
* Initialize MongoDB connection and indexes
185+
* This should be called once when the application starts
186+
*/
187+
export async function initializeMongoDB() {
188+
if (isInitialized) {
189+
return
190+
}
191+
192+
try {
193+
// Connect to MongoDB
194+
await client.connect()
195+
globalThis.console.log('✓ MongoDB connected successfully')
196+
197+
// Initialize indexes
198+
await initializeIndexes()
199+
200+
isInitialized = true
201+
}
202+
catch (error: any) {
203+
globalThis.console.error('✗ Error initializing MongoDB:', error.message)
204+
// Don't throw - allow application to continue
205+
// MongoDB operations will fail gracefully if connection is not established
206+
}
207+
}
208+
56209
/**
57210
* 插入聊天信息
58211
* @param uuid

0 commit comments

Comments
 (0)