package com.rosetta.messenger.database import androidx.room.* import kotlinx.coroutines.flow.Flow /** * Entity для сообщений - как в React Native версии */ @Entity( tableName = "messages", indices = [ Index(value = ["account", "from_public_key", "to_public_key", "timestamp"]), Index(value = ["account", "message_id"], unique = true), Index(value = ["account", "dialog_key", "timestamp"]) ] ) data class MessageEntity( @PrimaryKey(autoGenerate = true) val id: Long = 0, @ColumnInfo(name = "account") val account: String, // Мой публичный ключ @ColumnInfo(name = "from_public_key") val fromPublicKey: String, // Отправитель @ColumnInfo(name = "to_public_key") val toPublicKey: String, // Получатель @ColumnInfo(name = "content") val content: String, // Зашифрованное содержимое @ColumnInfo(name = "timestamp") val timestamp: Long, // Unix timestamp @ColumnInfo(name = "chacha_key") val chachaKey: String, // Зашифрованный ключ @ColumnInfo(name = "read") val read: Int = 0, // Прочитано (0/1) @ColumnInfo(name = "from_me") val fromMe: Int = 0, // Мое сообщение (0/1) @ColumnInfo(name = "delivered") val delivered: Int = 0, // Статус доставки (0=WAITING, 1=DELIVERED, 2=ERROR) @ColumnInfo(name = "message_id") val messageId: String, // UUID сообщения @ColumnInfo(name = "plain_message") val plainMessage: String, // Расшифрованный текст (для быстрого доступа) @ColumnInfo(name = "attachments") val attachments: String = "[]", // JSON массив вложений @ColumnInfo(name = "reply_to_message_id") val replyToMessageId: String? = null, // ID цитируемого сообщения @ColumnInfo(name = "dialog_key") val dialogKey: String // Ключ диалога для быстрой выборки ) /** * Entity для диалогов (кэш последнего сообщения) */ @Entity( tableName = "dialogs", indices = [ Index(value = ["account", "opponent_key"], unique = true), Index(value = ["account", "last_message_timestamp"]) ] ) data class DialogEntity( @PrimaryKey(autoGenerate = true) val id: Long = 0, @ColumnInfo(name = "account") val account: String, // Мой публичный ключ @ColumnInfo(name = "opponent_key") val opponentKey: String, // Публичный ключ собеседника @ColumnInfo(name = "opponent_title") val opponentTitle: String = "", // Имя собеседника @ColumnInfo(name = "opponent_username") val opponentUsername: String = "", // Username собеседника @ColumnInfo(name = "last_message") val lastMessage: String = "", // Последнее сообщение (текст) @ColumnInfo(name = "last_message_timestamp") val lastMessageTimestamp: Long = 0, // Timestamp последнего сообщения @ColumnInfo(name = "unread_count") val unreadCount: Int = 0, // Количество непрочитанных @ColumnInfo(name = "is_online") val isOnline: Int = 0, // Онлайн статус @ColumnInfo(name = "last_seen") val lastSeen: Long = 0, // Последний раз онлайн @ColumnInfo(name = "verified") val verified: Int = 0 // Верифицирован ) /** * DAO для работы с сообщениями */ @Dao interface MessageDao { /** * Вставка нового сообщения */ @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun insertMessage(message: MessageEntity): Long /** * Вставка нескольких сообщений */ @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun insertMessages(messages: List) /** * Получить сообщения диалога (постранично) */ @Query(""" SELECT * FROM messages WHERE account = :account AND dialog_key = :dialogKey ORDER BY timestamp DESC LIMIT :limit OFFSET :offset """) suspend fun getMessages(account: String, dialogKey: String, limit: Int, offset: Int): List /** * Получить сообщения диалога как Flow */ @Query(""" SELECT * FROM messages WHERE account = :account AND dialog_key = :dialogKey ORDER BY timestamp ASC """) fun getMessagesFlow(account: String, dialogKey: String): Flow> /** * Получить последние N сообщений диалога */ @Query(""" SELECT * FROM messages WHERE account = :account AND dialog_key = :dialogKey ORDER BY timestamp DESC LIMIT :limit """) suspend fun getRecentMessages(account: String, dialogKey: String, limit: Int): List /** * Найти сообщение по ID */ @Query("SELECT * FROM messages WHERE account = :account AND message_id = :messageId LIMIT 1") suspend fun getMessageById(account: String, messageId: String): MessageEntity? /** * Обновить статус доставки */ @Query("UPDATE messages SET delivered = :status WHERE account = :account AND message_id = :messageId") suspend fun updateDeliveryStatus(account: String, messageId: String, status: Int) /** * Обновить статус прочтения */ @Query("UPDATE messages SET read = 1 WHERE account = :account AND message_id = :messageId") suspend fun markAsRead(account: String, messageId: String) /** * Отметить все сообщения диалога как прочитанные */ @Query(""" UPDATE messages SET read = 1 WHERE account = :account AND dialog_key = :dialogKey AND from_me = 0 """) suspend fun markDialogAsRead(account: String, dialogKey: String) /** * Удалить сообщение */ @Query("DELETE FROM messages WHERE account = :account AND message_id = :messageId") suspend fun deleteMessage(account: String, messageId: String) /** * Удалить все сообщения диалога */ @Query("DELETE FROM messages WHERE account = :account AND dialog_key = :dialogKey") suspend fun deleteDialog(account: String, dialogKey: String) /** * Удалить все сообщения между двумя пользователями */ @Query(""" DELETE FROM messages WHERE account = :account AND ( (from_public_key = :user1 AND to_public_key = :user2) OR (from_public_key = :user2 AND to_public_key = :user1) ) """) suspend fun deleteMessagesBetweenUsers(account: String, user1: String, user2: String) /** * Количество непрочитанных сообщений в диалоге */ @Query(""" SELECT COUNT(*) FROM messages WHERE account = :account AND dialog_key = :dialogKey AND from_me = 0 AND read = 0 """) suspend fun getUnreadCount(account: String, dialogKey: String): Int /** * Проверить существование сообщения */ @Query("SELECT EXISTS(SELECT 1 FROM messages WHERE account = :account AND message_id = :messageId)") suspend fun messageExists(account: String, messageId: String): Boolean } /** * DAO для работы с диалогами */ @Dao interface DialogDao { /** * Вставка/обновление диалога */ @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun insertDialog(dialog: DialogEntity): Long /** * Получить все диалоги отсортированные по последнему сообщению */ @Query(""" SELECT * FROM dialogs WHERE account = :account ORDER BY last_message_timestamp DESC """) fun getDialogsFlow(account: String): Flow> /** * Получить диалог */ @Query("SELECT * FROM dialogs WHERE account = :account AND opponent_key = :opponentKey LIMIT 1") suspend fun getDialog(account: String, opponentKey: String): DialogEntity? /** * Обновить последнее сообщение */ @Query(""" UPDATE dialogs SET last_message = :lastMessage, last_message_timestamp = :timestamp WHERE account = :account AND opponent_key = :opponentKey """) suspend fun updateLastMessage(account: String, opponentKey: String, lastMessage: String, timestamp: Long) /** * Обновить количество непрочитанных */ @Query("UPDATE dialogs SET unread_count = :count WHERE account = :account AND opponent_key = :opponentKey") suspend fun updateUnreadCount(account: String, opponentKey: String, count: Int) /** * Инкрементировать непрочитанные */ @Query("UPDATE dialogs SET unread_count = unread_count + 1 WHERE account = :account AND opponent_key = :opponentKey") suspend fun incrementUnreadCount(account: String, opponentKey: String) /** * Сбросить непрочитанные */ @Query("UPDATE dialogs SET unread_count = 0 WHERE account = :account AND opponent_key = :opponentKey") suspend fun clearUnreadCount(account: String, opponentKey: String) /** * Обновить онлайн статус */ @Query(""" UPDATE dialogs SET is_online = :isOnline, last_seen = :lastSeen WHERE account = :account AND opponent_key = :opponentKey """) suspend fun updateOnlineStatus(account: String, opponentKey: String, isOnline: Int, lastSeen: Long) /** * Удалить диалог */ @Query("DELETE FROM dialogs WHERE account = :account AND opponent_key = :opponentKey") suspend fun deleteDialog(account: String, opponentKey: String) /** * Обновить информацию о собеседнике */ @Query(""" UPDATE dialogs SET opponent_title = :title, opponent_username = :username, verified = :verified WHERE account = :account AND opponent_key = :opponentKey """) suspend fun updateOpponentInfo( account: String, opponentKey: String, title: String, username: String, verified: Int ) }