154 行
4.6 KiB
TypeScript
154 行
4.6 KiB
TypeScript
|
|
import { Database, Q } from '@nozbe/watermelondb'
|
||
|
|
import SQLiteAdapter from '@nozbe/watermelondb/adapters/sqlite'
|
||
|
|
import { imDbSchema } from './schema'
|
||
|
|
import { ConversationModel } from './ConversationModel'
|
||
|
|
import { MessageModel } from './MessageModel'
|
||
|
|
import type { ImMessage } from '../types'
|
||
|
|
|
||
|
|
let _db: Database | null = null
|
||
|
|
|
||
|
|
function getDb(): Database {
|
||
|
|
if (!_db) throw new Error('[ImDatabase] Not initialized — call ImDatabase.init() first.')
|
||
|
|
return _db
|
||
|
|
}
|
||
|
|
|
||
|
|
function conversationId(appId: string, userId: string, targetId: string, chatType: string): string {
|
||
|
|
if (chatType === 'GROUP') return `${appId}:G:${targetId}`
|
||
|
|
const [a, b] = [userId, targetId].sort()
|
||
|
|
return `${appId}:S:${a}:${b}`
|
||
|
|
}
|
||
|
|
|
||
|
|
export const ImDatabase = {
|
||
|
|
init(dbName = 'xuqm_im') {
|
||
|
|
if (_db) return
|
||
|
|
const adapter = new SQLiteAdapter({
|
||
|
|
schema: imDbSchema,
|
||
|
|
dbName,
|
||
|
|
jsi: true,
|
||
|
|
onSetUpError: (err) => console.error('[ImDatabase] setup error', err),
|
||
|
|
})
|
||
|
|
_db = new Database({
|
||
|
|
adapter,
|
||
|
|
modelClasses: [ConversationModel, MessageModel],
|
||
|
|
})
|
||
|
|
},
|
||
|
|
|
||
|
|
async saveMessage(msg: ImMessage, currentUserId: string): Promise<void> {
|
||
|
|
const db = getDb()
|
||
|
|
const convId = conversationId(msg.appId, currentUserId, msg.toId, msg.chatType)
|
||
|
|
const now = Date.now()
|
||
|
|
|
||
|
|
await db.write(async () => {
|
||
|
|
// upsert message
|
||
|
|
const existing = await db
|
||
|
|
.get<MessageModel>('im_messages')
|
||
|
|
.query(Q.where('server_id', msg.id))
|
||
|
|
.fetch()
|
||
|
|
|
||
|
|
if (existing.length === 0) {
|
||
|
|
await db.get<MessageModel>('im_messages').create(m => {
|
||
|
|
m.serverId = msg.id
|
||
|
|
m.appId = msg.appId
|
||
|
|
m.conversationId = convId
|
||
|
|
m.fromUserId = msg.fromUserId
|
||
|
|
m.toId = msg.toId
|
||
|
|
m.chatType = msg.chatType
|
||
|
|
m.msgType = msg.msgType
|
||
|
|
m.content = msg.content
|
||
|
|
m.status = msg.status
|
||
|
|
m.mentionedUserIds = msg.mentionedUserIds ?? null
|
||
|
|
m.serverCreatedAt = new Date(msg.createdAt).getTime()
|
||
|
|
m.syncedAt = now
|
||
|
|
})
|
||
|
|
} else {
|
||
|
|
await existing[0].update(m => {
|
||
|
|
m.status = msg.status
|
||
|
|
m.content = msg.content
|
||
|
|
m.syncedAt = now
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
// upsert conversation
|
||
|
|
const convs = await db
|
||
|
|
.get<ConversationModel>('im_conversations')
|
||
|
|
.query(Q.where('app_id', msg.appId), Q.where('target_id', msg.toId))
|
||
|
|
.fetch()
|
||
|
|
|
||
|
|
const msgTime = new Date(msg.createdAt).getTime()
|
||
|
|
if (convs.length === 0) {
|
||
|
|
await db.get<ConversationModel>('im_conversations').create(c => {
|
||
|
|
c.appId = msg.appId
|
||
|
|
c.targetId = msg.toId
|
||
|
|
c.chatType = msg.chatType
|
||
|
|
c.lastMsgId = msg.id
|
||
|
|
c.lastMsgContent = msg.content
|
||
|
|
c.lastMsgType = msg.msgType
|
||
|
|
c.lastMsgTime = msgTime
|
||
|
|
c.unreadCount = msg.fromUserId !== currentUserId ? 1 : 0
|
||
|
|
c.updatedAt = new Date()
|
||
|
|
})
|
||
|
|
} else {
|
||
|
|
await convs[0].update(c => {
|
||
|
|
if (msgTime >= c.lastMsgTime) {
|
||
|
|
c.lastMsgId = msg.id
|
||
|
|
c.lastMsgContent = msg.content
|
||
|
|
c.lastMsgType = msg.msgType
|
||
|
|
c.lastMsgTime = msgTime
|
||
|
|
}
|
||
|
|
if (msg.fromUserId !== currentUserId) {
|
||
|
|
c.unreadCount = c.unreadCount + 1
|
||
|
|
}
|
||
|
|
c.updatedAt = new Date()
|
||
|
|
})
|
||
|
|
}
|
||
|
|
})
|
||
|
|
},
|
||
|
|
|
||
|
|
async getMessages(appId: string, targetId: string, chatType: string, currentUserId: string, limit = 50): Promise<MessageModel[]> {
|
||
|
|
const db = getDb()
|
||
|
|
const convId = conversationId(appId, currentUserId, targetId, chatType)
|
||
|
|
return db
|
||
|
|
.get<MessageModel>('im_messages')
|
||
|
|
.query(
|
||
|
|
Q.where('conversation_id', convId),
|
||
|
|
Q.sortBy('server_created_at', Q.desc),
|
||
|
|
Q.take(limit),
|
||
|
|
)
|
||
|
|
.fetch()
|
||
|
|
},
|
||
|
|
|
||
|
|
async getConversations(appId: string): Promise<ConversationModel[]> {
|
||
|
|
const db = getDb()
|
||
|
|
return db
|
||
|
|
.get<ConversationModel>('im_conversations')
|
||
|
|
.query(
|
||
|
|
Q.where('app_id', appId),
|
||
|
|
Q.sortBy('last_msg_time', Q.desc),
|
||
|
|
)
|
||
|
|
.fetch()
|
||
|
|
},
|
||
|
|
|
||
|
|
async markRead(appId: string, targetId: string): Promise<void> {
|
||
|
|
const db = getDb()
|
||
|
|
const convs = await db
|
||
|
|
.get<ConversationModel>('im_conversations')
|
||
|
|
.query(Q.where('app_id', appId), Q.where('target_id', targetId))
|
||
|
|
.fetch()
|
||
|
|
if (convs.length > 0) {
|
||
|
|
await db.write(async () => {
|
||
|
|
await convs[0].update(c => { c.unreadCount = 0 })
|
||
|
|
})
|
||
|
|
}
|
||
|
|
},
|
||
|
|
|
||
|
|
async bulkSave(messages: ImMessage[], currentUserId: string): Promise<void> {
|
||
|
|
for (const msg of messages) {
|
||
|
|
await this.saveMessage(msg, currentUserId)
|
||
|
|
}
|
||
|
|
},
|
||
|
|
|
||
|
|
isInitialized(): boolean {
|
||
|
|
return _db !== null
|
||
|
|
},
|
||
|
|
}
|