Files
mobile-android/app/src/main/java/com/rosetta/messenger/database/MessageEntities.kt

318 lines
11 KiB
Kotlin
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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<MessageEntity>)
/**
* Получить сообщения диалога (постранично)
*/
@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<MessageEntity>
/**
* Получить сообщения диалога как Flow
*/
@Query("""
SELECT * FROM messages
WHERE account = :account AND dialog_key = :dialogKey
ORDER BY timestamp ASC
""")
fun getMessagesFlow(account: String, dialogKey: String): Flow<List<MessageEntity>>
/**
* Получить последние 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<MessageEntity>
/**
* Найти сообщение по 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<List<DialogEntity>>
/**
* Получить диалог
*/
@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
)
}