core: support postgres backend (#5403)

* postgres: modules structure (#5401)

* postgres: schema, field conversions (#5430)

* postgres: rework chat list pagination query (#5441)

* prepare cabal for merge

* restore cabal changes

* simplexmq

* postgres: implementation wip (tests don't pass) (#5481)

* restore ios file

* postgres: implementation - tests pass (#5487)

* refactor DB options

* refactor

* line

* style

* style

* refactor

* $

* update simplexmq

* constraintError

* handleDBErrors

* fix

* remove param

* Ok

* case

* case

* case

* comment

---------

Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>
This commit is contained in:
spaced4ndy 2025-01-10 15:27:29 +04:00 committed by GitHub
parent 13fae855fc
commit e05a35e26e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
187 changed files with 2847 additions and 1291 deletions

View file

@ -31,9 +31,9 @@ main = do
welcomeGetOpts :: IO ChatOpts welcomeGetOpts :: IO ChatOpts
welcomeGetOpts = do welcomeGetOpts = do
appDir <- getAppUserDataDirectory "simplex" appDir <- getAppUserDataDirectory "simplex"
opts@ChatOpts {coreOptions = CoreChatOpts {dbFilePrefix}} <- getChatOpts appDir "simplex_bot" opts@ChatOpts {coreOptions} <- getChatOpts appDir "simplex_bot"
putStrLn $ "SimpleX Chat Bot v" ++ versionNumber putStrLn $ "SimpleX Chat Bot v" ++ versionNumber
putStrLn $ "db: " <> dbFilePrefix <> "_chat.db, " <> dbFilePrefix <> "_agent.db" printDbOpts coreOptions
pure opts pure opts
welcomeMessage :: Text welcomeMessage :: Text

View file

@ -25,7 +25,7 @@ welcomeMessage = "Hello! I am a simple squaring bot.\nIf you send me a number, I
welcomeGetOpts :: IO ChatOpts welcomeGetOpts :: IO ChatOpts
welcomeGetOpts = do welcomeGetOpts = do
appDir <- getAppUserDataDirectory "simplex" appDir <- getAppUserDataDirectory "simplex"
opts@ChatOpts {coreOptions = CoreChatOpts {dbFilePrefix}} <- getChatOpts appDir "simplex_bot" opts@ChatOpts {coreOptions} <- getChatOpts appDir "simplex_bot"
putStrLn $ "SimpleX Chat Bot v" ++ versionNumber putStrLn $ "SimpleX Chat Bot v" ++ versionNumber
putStrLn $ "db: " <> dbFilePrefix <> "_chat.db, " <> dbFilePrefix <> "_agent.db" printDbOpts coreOptions
pure opts pure opts

View file

@ -27,9 +27,9 @@ import System.Directory (getAppUserDataDirectory)
welcomeGetOpts :: IO BroadcastBotOpts welcomeGetOpts :: IO BroadcastBotOpts
welcomeGetOpts = do welcomeGetOpts = do
appDir <- getAppUserDataDirectory "simplex" appDir <- getAppUserDataDirectory "simplex"
opts@BroadcastBotOpts {coreOptions = CoreChatOpts {dbFilePrefix}} <- getBroadcastBotOpts appDir "simplex_status_bot" opts@BroadcastBotOpts {coreOptions} <- getBroadcastBotOpts appDir "simplex_status_bot"
putStrLn $ "SimpleX Chat Bot v" ++ versionNumber putStrLn $ "SimpleX Chat Bot v" ++ versionNumber
putStrLn $ "db: " <> dbFilePrefix <> "_chat.db, " <> dbFilePrefix <> "_agent.db" printDbOpts coreOptions
pure opts pure opts
broadcastBot :: BroadcastBotOpts -> User -> ChatController -> IO () broadcastBot :: BroadcastBotOpts -> User -> ChatController -> IO ()

View file

@ -27,8 +27,8 @@ defaultProhibitedMessage :: [KnownContact] -> Text
defaultProhibitedMessage ps = "Sorry, only these users can broadcast messages: " <> knownContactNames ps <> ". Your message is deleted." defaultProhibitedMessage ps = "Sorry, only these users can broadcast messages: " <> knownContactNames ps <> ". Your message is deleted."
broadcastBotOpts :: FilePath -> FilePath -> Parser BroadcastBotOpts broadcastBotOpts :: FilePath -> FilePath -> Parser BroadcastBotOpts
broadcastBotOpts appDir defaultDbFileName = do broadcastBotOpts appDir defaultDbName = do
coreOptions <- coreChatOptsP appDir defaultDbFileName coreOptions <- coreChatOptsP appDir defaultDbName
publishers <- publishers <-
option option
parseKnownContacts parseKnownContacts
@ -61,10 +61,10 @@ broadcastBotOpts appDir defaultDbFileName = do
} }
getBroadcastBotOpts :: FilePath -> FilePath -> IO BroadcastBotOpts getBroadcastBotOpts :: FilePath -> FilePath -> IO BroadcastBotOpts
getBroadcastBotOpts appDir defaultDbFileName = getBroadcastBotOpts appDir defaultDbName =
execParser $ execParser $
info info
(helper <*> versionOption <*> broadcastBotOpts appDir defaultDbFileName) (helper <*> versionOption <*> broadcastBotOpts appDir defaultDbName)
(header versionStr <> fullDesc <> progDesc "Start chat bot with DB_FILE file and use SERVER as SMP server") (header versionStr <> fullDesc <> progDesc "Start chat bot with DB_FILE file and use SERVER as SMP server")
where where
versionStr = versionString versionNumber versionStr = versionString versionNumber

View file

@ -15,7 +15,7 @@ import qualified Data.Text as T
import Options.Applicative import Options.Applicative
import Simplex.Chat.Bot.KnownContacts import Simplex.Chat.Bot.KnownContacts
import Simplex.Chat.Controller (updateStr, versionNumber, versionString) import Simplex.Chat.Controller (updateStr, versionNumber, versionString)
import Simplex.Chat.Options (ChatOpts (..), ChatCmdLog (..), CoreChatOpts, coreChatOptsP) import Simplex.Chat.Options (ChatCmdLog (..), ChatOpts (..), CoreChatOpts, coreChatOptsP)
data DirectoryOpts = DirectoryOpts data DirectoryOpts = DirectoryOpts
{ coreOptions :: CoreChatOpts, { coreOptions :: CoreChatOpts,
@ -29,8 +29,8 @@ data DirectoryOpts = DirectoryOpts
} }
directoryOpts :: FilePath -> FilePath -> Parser DirectoryOpts directoryOpts :: FilePath -> FilePath -> Parser DirectoryOpts
directoryOpts appDir defaultDbFileName = do directoryOpts appDir defaultDbName = do
coreOptions <- coreChatOptsP appDir defaultDbFileName coreOptions <- coreChatOptsP appDir defaultDbName
adminUsers <- adminUsers <-
option option
parseKnownContacts parseKnownContacts
@ -77,10 +77,10 @@ directoryOpts appDir defaultDbFileName = do
} }
getDirectoryOpts :: FilePath -> FilePath -> IO DirectoryOpts getDirectoryOpts :: FilePath -> FilePath -> IO DirectoryOpts
getDirectoryOpts appDir defaultDbFileName = getDirectoryOpts appDir defaultDbName =
execParser $ execParser $
info info
(helper <*> versionOption <*> directoryOpts appDir defaultDbFileName) (helper <*> versionOption <*> directoryOpts appDir defaultDbName)
(header versionStr <> fullDesc <> progDesc "Start SimpleX Directory Service with DB_FILE, DIRECTORY_FILE and SUPER_USERS options") (header versionStr <> fullDesc <> progDesc "Start SimpleX Directory Service with DB_FILE, DIRECTORY_FILE and SUPER_USERS options")
where where
versionStr = versionString versionNumber versionStr = versionString versionNumber

View file

@ -74,10 +74,10 @@ newServiceState = do
welcomeGetOpts :: IO DirectoryOpts welcomeGetOpts :: IO DirectoryOpts
welcomeGetOpts = do welcomeGetOpts = do
appDir <- getAppUserDataDirectory "simplex" appDir <- getAppUserDataDirectory "simplex"
opts@DirectoryOpts {coreOptions = CoreChatOpts {dbFilePrefix}, testing} <- getDirectoryOpts appDir "simplex_directory_service" opts@DirectoryOpts {coreOptions, testing} <- getDirectoryOpts appDir "simplex_directory_service"
unless testing $ do unless testing $ do
putStrLn $ "SimpleX Directory Service Bot v" ++ versionNumber putStrLn $ "SimpleX Directory Service Bot v" ++ versionNumber
putStrLn $ "db: " <> dbFilePrefix <> "_chat.db, " <> dbFilePrefix <> "_agent.db" printDbOpts coreOptions
pure opts pure opts
directoryServiceCLI :: DirectoryStore -> DirectoryOpts -> IO () directoryServiceCLI :: DirectoryStore -> DirectoryOpts -> IO ()

View file

@ -12,7 +12,7 @@ constraints: zip +disable-bzip2 +disable-zstd
source-repository-package source-repository-package
type: git type: git
location: https://github.com/simplex-chat/simplexmq.git location: https://github.com/simplex-chat/simplexmq.git
tag: 992b42e92224ec663684923aaa40ed1f9a683f61 tag: 9d9ec8cd0b171b2058c59c4e7292ccafa96b6e2b
source-repository-package source-repository-package
type: git type: git

View file

@ -1,5 +1,5 @@
{ {
"https://github.com/simplex-chat/simplexmq.git"."992b42e92224ec663684923aaa40ed1f9a683f61" = "08bhkqm2hvgql63hrayas7izvxbv99pdzwvn3kj6z0j02pnwng6d"; "https://github.com/simplex-chat/simplexmq.git"."9d9ec8cd0b171b2058c59c4e7292ccafa96b6e2b" = "0mvg9yrwb835vf2kz8k0ac4i7vzjpvbpcwg895n3kcfdkdcnxh14";
"https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38"; "https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38";
"https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d"; "https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d";
"https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl"; "https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl";

View file

@ -24,11 +24,15 @@ flag swift
manual: True manual: True
default: False default: False
flag client_postgres
description: Build with PostgreSQL instead of SQLite.
manual: True
default: False
library library
exposed-modules: exposed-modules:
Simplex.Chat Simplex.Chat
Simplex.Chat.AppSettings Simplex.Chat.AppSettings
Simplex.Chat.Archive
Simplex.Chat.Bot Simplex.Chat.Bot
Simplex.Chat.Bot.KnownContacts Simplex.Chat.Bot.KnownContacts
Simplex.Chat.Call Simplex.Chat.Call
@ -44,132 +48,12 @@ library
Simplex.Chat.Messages.Batch Simplex.Chat.Messages.Batch
Simplex.Chat.Messages.CIContent Simplex.Chat.Messages.CIContent
Simplex.Chat.Messages.CIContent.Events Simplex.Chat.Messages.CIContent.Events
Simplex.Chat.Migrations.M20220101_initial
Simplex.Chat.Migrations.M20220122_v1_1
Simplex.Chat.Migrations.M20220205_chat_item_status
Simplex.Chat.Migrations.M20220210_deduplicate_contact_requests
Simplex.Chat.Migrations.M20220224_messages_fks
Simplex.Chat.Migrations.M20220301_smp_servers
Simplex.Chat.Migrations.M20220302_profile_images
Simplex.Chat.Migrations.M20220304_msg_quotes
Simplex.Chat.Migrations.M20220321_chat_item_edited
Simplex.Chat.Migrations.M20220404_files_status_fields
Simplex.Chat.Migrations.M20220514_profiles_user_id
Simplex.Chat.Migrations.M20220626_auto_reply
Simplex.Chat.Migrations.M20220702_calls
Simplex.Chat.Migrations.M20220715_groups_chat_item_id
Simplex.Chat.Migrations.M20220811_chat_items_indices
Simplex.Chat.Migrations.M20220812_incognito_profiles
Simplex.Chat.Migrations.M20220818_chat_notifications
Simplex.Chat.Migrations.M20220822_groups_host_conn_custom_user_profile_id
Simplex.Chat.Migrations.M20220823_delete_broken_group_event_chat_items
Simplex.Chat.Migrations.M20220824_profiles_local_alias
Simplex.Chat.Migrations.M20220909_commands
Simplex.Chat.Migrations.M20220926_connection_alias
Simplex.Chat.Migrations.M20220928_settings
Simplex.Chat.Migrations.M20221001_shared_msg_id_indices
Simplex.Chat.Migrations.M20221003_delete_broken_integrity_error_chat_items
Simplex.Chat.Migrations.M20221004_idx_msg_deliveries_message_id
Simplex.Chat.Migrations.M20221011_user_contact_links_group_id
Simplex.Chat.Migrations.M20221012_inline_files
Simplex.Chat.Migrations.M20221019_unread_chat
Simplex.Chat.Migrations.M20221021_auto_accept__group_links
Simplex.Chat.Migrations.M20221024_contact_used
Simplex.Chat.Migrations.M20221025_chat_settings
Simplex.Chat.Migrations.M20221029_group_link_id
Simplex.Chat.Migrations.M20221112_server_password
Simplex.Chat.Migrations.M20221115_server_cfg
Simplex.Chat.Migrations.M20221129_delete_group_feature_items
Simplex.Chat.Migrations.M20221130_delete_item_deleted
Simplex.Chat.Migrations.M20221209_verified_connection
Simplex.Chat.Migrations.M20221210_idxs
Simplex.Chat.Migrations.M20221211_group_description
Simplex.Chat.Migrations.M20221212_chat_items_timed
Simplex.Chat.Migrations.M20221214_live_message
Simplex.Chat.Migrations.M20221222_chat_ts
Simplex.Chat.Migrations.M20221223_idx_chat_items_item_status
Simplex.Chat.Migrations.M20221230_idxs
Simplex.Chat.Migrations.M20230107_connections_auth_err_counter
Simplex.Chat.Migrations.M20230111_users_agent_user_id
Simplex.Chat.Migrations.M20230117_fkey_indexes
Simplex.Chat.Migrations.M20230118_recreate_smp_servers
Simplex.Chat.Migrations.M20230129_drop_chat_items_group_idx
Simplex.Chat.Migrations.M20230206_item_deleted_by_group_member_id
Simplex.Chat.Migrations.M20230303_group_link_role
Simplex.Chat.Migrations.M20230317_hidden_profiles
Simplex.Chat.Migrations.M20230318_file_description
Simplex.Chat.Migrations.M20230321_agent_file_deleted
Simplex.Chat.Migrations.M20230328_files_protocol
Simplex.Chat.Migrations.M20230402_protocol_servers
Simplex.Chat.Migrations.M20230411_extra_xftp_file_descriptions
Simplex.Chat.Migrations.M20230420_rcv_files_to_receive
Simplex.Chat.Migrations.M20230422_profile_contact_links
Simplex.Chat.Migrations.M20230504_recreate_msg_delivery_events_cleanup_messages
Simplex.Chat.Migrations.M20230505_chat_item_versions
Simplex.Chat.Migrations.M20230511_reactions
Simplex.Chat.Migrations.M20230519_item_deleted_ts
Simplex.Chat.Migrations.M20230526_indexes
Simplex.Chat.Migrations.M20230529_indexes
Simplex.Chat.Migrations.M20230608_deleted_contacts
Simplex.Chat.Migrations.M20230618_favorite_chats
Simplex.Chat.Migrations.M20230621_chat_item_moderations
Simplex.Chat.Migrations.M20230705_delivery_receipts
Simplex.Chat.Migrations.M20230721_group_snd_item_statuses
Simplex.Chat.Migrations.M20230814_indexes
Simplex.Chat.Migrations.M20230827_file_encryption
Simplex.Chat.Migrations.M20230829_connections_chat_vrange
Simplex.Chat.Migrations.M20230903_connections_to_subscribe
Simplex.Chat.Migrations.M20230913_member_contacts
Simplex.Chat.Migrations.M20230914_member_probes
Simplex.Chat.Migrations.M20230926_contact_status
Simplex.Chat.Migrations.M20231002_conn_initiated
Simplex.Chat.Migrations.M20231009_via_group_link_uri_hash
Simplex.Chat.Migrations.M20231010_member_settings
Simplex.Chat.Migrations.M20231019_indexes
Simplex.Chat.Migrations.M20231030_xgrplinkmem_received
Simplex.Chat.Migrations.M20231107_indexes
Simplex.Chat.Migrations.M20231113_group_forward
Simplex.Chat.Migrations.M20231114_remote_control
Simplex.Chat.Migrations.M20231126_remote_ctrl_address
Simplex.Chat.Migrations.M20231207_chat_list_pagination
Simplex.Chat.Migrations.M20231214_item_content_tag
Simplex.Chat.Migrations.M20231215_recreate_msg_deliveries
Simplex.Chat.Migrations.M20240102_note_folders
Simplex.Chat.Migrations.M20240104_members_profile_update
Simplex.Chat.Migrations.M20240115_block_member_for_all
Simplex.Chat.Migrations.M20240122_indexes
Simplex.Chat.Migrations.M20240214_redirect_file_id
Simplex.Chat.Migrations.M20240222_app_settings
Simplex.Chat.Migrations.M20240226_users_restrict
Simplex.Chat.Migrations.M20240228_pq
Simplex.Chat.Migrations.M20240313_drop_agent_ack_cmd_id
Simplex.Chat.Migrations.M20240324_custom_data
Simplex.Chat.Migrations.M20240402_item_forwarded
Simplex.Chat.Migrations.M20240430_ui_theme
Simplex.Chat.Migrations.M20240501_chat_deleted
Simplex.Chat.Migrations.M20240510_chat_items_via_proxy
Simplex.Chat.Migrations.M20240515_rcv_files_user_approved_relays
Simplex.Chat.Migrations.M20240528_quota_err_counter
Simplex.Chat.Migrations.M20240827_calls_uuid
Simplex.Chat.Migrations.M20240920_user_order
Simplex.Chat.Migrations.M20241008_indexes
Simplex.Chat.Migrations.M20241010_contact_requests_contact_id
Simplex.Chat.Migrations.M20241023_chat_item_autoincrement_id
Simplex.Chat.Migrations.M20241027_server_operators
Simplex.Chat.Migrations.M20241125_indexes
Simplex.Chat.Migrations.M20241128_business_chats
Simplex.Chat.Migrations.M20241205_business_chat_members
Simplex.Chat.Migrations.M20241222_operator_conditions
Simplex.Chat.Migrations.M20241223_chat_tags
Simplex.Chat.Migrations.M20241230_reports
Simplex.Chat.Migrations.M20250105_indexes
Simplex.Chat.Mobile
Simplex.Chat.Mobile.File Simplex.Chat.Mobile.File
Simplex.Chat.Mobile.Shared Simplex.Chat.Mobile.Shared
Simplex.Chat.Mobile.WebRTC
Simplex.Chat.Operators Simplex.Chat.Operators
Simplex.Chat.Operators.Conditions Simplex.Chat.Operators.Conditions
Simplex.Chat.Options Simplex.Chat.Options
Simplex.Chat.Options.DB
Simplex.Chat.ProfileGenerator Simplex.Chat.ProfileGenerator
Simplex.Chat.Protocol Simplex.Chat.Protocol
Simplex.Chat.Remote Simplex.Chat.Remote
@ -187,7 +71,6 @@ library
Simplex.Chat.Store.Files Simplex.Chat.Store.Files
Simplex.Chat.Store.Groups Simplex.Chat.Store.Groups
Simplex.Chat.Store.Messages Simplex.Chat.Store.Messages
Simplex.Chat.Store.Migrations
Simplex.Chat.Store.NoteFolders Simplex.Chat.Store.NoteFolders
Simplex.Chat.Store.Profiles Simplex.Chat.Store.Profiles
Simplex.Chat.Store.Remote Simplex.Chat.Store.Remote
@ -205,6 +88,137 @@ library
Simplex.Chat.Types.Util Simplex.Chat.Types.Util
Simplex.Chat.Util Simplex.Chat.Util
Simplex.Chat.View Simplex.Chat.View
if flag(client_postgres)
exposed-modules:
Simplex.Chat.Options.Postgres
Simplex.Chat.Store.Postgres.Migrations
Simplex.Chat.Store.Postgres.Migrations.M20241220_initial
else
exposed-modules:
Simplex.Chat.Archive
Simplex.Chat.Mobile
Simplex.Chat.Mobile.WebRTC
Simplex.Chat.Options.SQLite
Simplex.Chat.Store.SQLite.Migrations
Simplex.Chat.Store.SQLite.Migrations.M20220101_initial
Simplex.Chat.Store.SQLite.Migrations.M20220122_v1_1
Simplex.Chat.Store.SQLite.Migrations.M20220205_chat_item_status
Simplex.Chat.Store.SQLite.Migrations.M20220210_deduplicate_contact_requests
Simplex.Chat.Store.SQLite.Migrations.M20220224_messages_fks
Simplex.Chat.Store.SQLite.Migrations.M20220301_smp_servers
Simplex.Chat.Store.SQLite.Migrations.M20220302_profile_images
Simplex.Chat.Store.SQLite.Migrations.M20220304_msg_quotes
Simplex.Chat.Store.SQLite.Migrations.M20220321_chat_item_edited
Simplex.Chat.Store.SQLite.Migrations.M20220404_files_status_fields
Simplex.Chat.Store.SQLite.Migrations.M20220514_profiles_user_id
Simplex.Chat.Store.SQLite.Migrations.M20220626_auto_reply
Simplex.Chat.Store.SQLite.Migrations.M20220702_calls
Simplex.Chat.Store.SQLite.Migrations.M20220715_groups_chat_item_id
Simplex.Chat.Store.SQLite.Migrations.M20220811_chat_items_indices
Simplex.Chat.Store.SQLite.Migrations.M20220812_incognito_profiles
Simplex.Chat.Store.SQLite.Migrations.M20220818_chat_notifications
Simplex.Chat.Store.SQLite.Migrations.M20220822_groups_host_conn_custom_user_profile_id
Simplex.Chat.Store.SQLite.Migrations.M20220823_delete_broken_group_event_chat_items
Simplex.Chat.Store.SQLite.Migrations.M20220824_profiles_local_alias
Simplex.Chat.Store.SQLite.Migrations.M20220909_commands
Simplex.Chat.Store.SQLite.Migrations.M20220926_connection_alias
Simplex.Chat.Store.SQLite.Migrations.M20220928_settings
Simplex.Chat.Store.SQLite.Migrations.M20221001_shared_msg_id_indices
Simplex.Chat.Store.SQLite.Migrations.M20221003_delete_broken_integrity_error_chat_items
Simplex.Chat.Store.SQLite.Migrations.M20221004_idx_msg_deliveries_message_id
Simplex.Chat.Store.SQLite.Migrations.M20221011_user_contact_links_group_id
Simplex.Chat.Store.SQLite.Migrations.M20221012_inline_files
Simplex.Chat.Store.SQLite.Migrations.M20221019_unread_chat
Simplex.Chat.Store.SQLite.Migrations.M20221021_auto_accept__group_links
Simplex.Chat.Store.SQLite.Migrations.M20221024_contact_used
Simplex.Chat.Store.SQLite.Migrations.M20221025_chat_settings
Simplex.Chat.Store.SQLite.Migrations.M20221029_group_link_id
Simplex.Chat.Store.SQLite.Migrations.M20221112_server_password
Simplex.Chat.Store.SQLite.Migrations.M20221115_server_cfg
Simplex.Chat.Store.SQLite.Migrations.M20221129_delete_group_feature_items
Simplex.Chat.Store.SQLite.Migrations.M20221130_delete_item_deleted
Simplex.Chat.Store.SQLite.Migrations.M20221209_verified_connection
Simplex.Chat.Store.SQLite.Migrations.M20221210_idxs
Simplex.Chat.Store.SQLite.Migrations.M20221211_group_description
Simplex.Chat.Store.SQLite.Migrations.M20221212_chat_items_timed
Simplex.Chat.Store.SQLite.Migrations.M20221214_live_message
Simplex.Chat.Store.SQLite.Migrations.M20221222_chat_ts
Simplex.Chat.Store.SQLite.Migrations.M20221223_idx_chat_items_item_status
Simplex.Chat.Store.SQLite.Migrations.M20221230_idxs
Simplex.Chat.Store.SQLite.Migrations.M20230107_connections_auth_err_counter
Simplex.Chat.Store.SQLite.Migrations.M20230111_users_agent_user_id
Simplex.Chat.Store.SQLite.Migrations.M20230117_fkey_indexes
Simplex.Chat.Store.SQLite.Migrations.M20230118_recreate_smp_servers
Simplex.Chat.Store.SQLite.Migrations.M20230129_drop_chat_items_group_idx
Simplex.Chat.Store.SQLite.Migrations.M20230206_item_deleted_by_group_member_id
Simplex.Chat.Store.SQLite.Migrations.M20230303_group_link_role
Simplex.Chat.Store.SQLite.Migrations.M20230317_hidden_profiles
Simplex.Chat.Store.SQLite.Migrations.M20230318_file_description
Simplex.Chat.Store.SQLite.Migrations.M20230321_agent_file_deleted
Simplex.Chat.Store.SQLite.Migrations.M20230328_files_protocol
Simplex.Chat.Store.SQLite.Migrations.M20230402_protocol_servers
Simplex.Chat.Store.SQLite.Migrations.M20230411_extra_xftp_file_descriptions
Simplex.Chat.Store.SQLite.Migrations.M20230420_rcv_files_to_receive
Simplex.Chat.Store.SQLite.Migrations.M20230422_profile_contact_links
Simplex.Chat.Store.SQLite.Migrations.M20230504_recreate_msg_delivery_events_cleanup_messages
Simplex.Chat.Store.SQLite.Migrations.M20230505_chat_item_versions
Simplex.Chat.Store.SQLite.Migrations.M20230511_reactions
Simplex.Chat.Store.SQLite.Migrations.M20230519_item_deleted_ts
Simplex.Chat.Store.SQLite.Migrations.M20230526_indexes
Simplex.Chat.Store.SQLite.Migrations.M20230529_indexes
Simplex.Chat.Store.SQLite.Migrations.M20230608_deleted_contacts
Simplex.Chat.Store.SQLite.Migrations.M20230618_favorite_chats
Simplex.Chat.Store.SQLite.Migrations.M20230621_chat_item_moderations
Simplex.Chat.Store.SQLite.Migrations.M20230705_delivery_receipts
Simplex.Chat.Store.SQLite.Migrations.M20230721_group_snd_item_statuses
Simplex.Chat.Store.SQLite.Migrations.M20230814_indexes
Simplex.Chat.Store.SQLite.Migrations.M20230827_file_encryption
Simplex.Chat.Store.SQLite.Migrations.M20230829_connections_chat_vrange
Simplex.Chat.Store.SQLite.Migrations.M20230903_connections_to_subscribe
Simplex.Chat.Store.SQLite.Migrations.M20230913_member_contacts
Simplex.Chat.Store.SQLite.Migrations.M20230914_member_probes
Simplex.Chat.Store.SQLite.Migrations.M20230926_contact_status
Simplex.Chat.Store.SQLite.Migrations.M20231002_conn_initiated
Simplex.Chat.Store.SQLite.Migrations.M20231009_via_group_link_uri_hash
Simplex.Chat.Store.SQLite.Migrations.M20231010_member_settings
Simplex.Chat.Store.SQLite.Migrations.M20231019_indexes
Simplex.Chat.Store.SQLite.Migrations.M20231030_xgrplinkmem_received
Simplex.Chat.Store.SQLite.Migrations.M20231107_indexes
Simplex.Chat.Store.SQLite.Migrations.M20231113_group_forward
Simplex.Chat.Store.SQLite.Migrations.M20231114_remote_control
Simplex.Chat.Store.SQLite.Migrations.M20231126_remote_ctrl_address
Simplex.Chat.Store.SQLite.Migrations.M20231207_chat_list_pagination
Simplex.Chat.Store.SQLite.Migrations.M20231214_item_content_tag
Simplex.Chat.Store.SQLite.Migrations.M20231215_recreate_msg_deliveries
Simplex.Chat.Store.SQLite.Migrations.M20240102_note_folders
Simplex.Chat.Store.SQLite.Migrations.M20240104_members_profile_update
Simplex.Chat.Store.SQLite.Migrations.M20240115_block_member_for_all
Simplex.Chat.Store.SQLite.Migrations.M20240122_indexes
Simplex.Chat.Store.SQLite.Migrations.M20240214_redirect_file_id
Simplex.Chat.Store.SQLite.Migrations.M20240222_app_settings
Simplex.Chat.Store.SQLite.Migrations.M20240226_users_restrict
Simplex.Chat.Store.SQLite.Migrations.M20240228_pq
Simplex.Chat.Store.SQLite.Migrations.M20240313_drop_agent_ack_cmd_id
Simplex.Chat.Store.SQLite.Migrations.M20240324_custom_data
Simplex.Chat.Store.SQLite.Migrations.M20240402_item_forwarded
Simplex.Chat.Store.SQLite.Migrations.M20240430_ui_theme
Simplex.Chat.Store.SQLite.Migrations.M20240501_chat_deleted
Simplex.Chat.Store.SQLite.Migrations.M20240510_chat_items_via_proxy
Simplex.Chat.Store.SQLite.Migrations.M20240515_rcv_files_user_approved_relays
Simplex.Chat.Store.SQLite.Migrations.M20240528_quota_err_counter
Simplex.Chat.Store.SQLite.Migrations.M20240827_calls_uuid
Simplex.Chat.Store.SQLite.Migrations.M20240920_user_order
Simplex.Chat.Store.SQLite.Migrations.M20241008_indexes
Simplex.Chat.Store.SQLite.Migrations.M20241010_contact_requests_contact_id
Simplex.Chat.Store.SQLite.Migrations.M20241023_chat_item_autoincrement_id
Simplex.Chat.Store.SQLite.Migrations.M20241027_server_operators
Simplex.Chat.Store.SQLite.Migrations.M20241125_indexes
Simplex.Chat.Store.SQLite.Migrations.M20241128_business_chats
Simplex.Chat.Store.SQLite.Migrations.M20241205_business_chat_members
Simplex.Chat.Store.SQLite.Migrations.M20241222_operator_conditions
Simplex.Chat.Store.SQLite.Migrations.M20241223_chat_tags
Simplex.Chat.Store.SQLite.Migrations.M20241230_reports
Simplex.Chat.Store.SQLite.Migrations.M20250105_indexes
other-modules: other-modules:
Paths_simplex_chat Paths_simplex_chat
hs-source-dirs: hs-source-dirs:
@ -224,7 +238,6 @@ library
, containers ==0.6.* , containers ==0.6.*
, crypton ==0.34.* , crypton ==0.34.*
, data-default ==0.7.* , data-default ==0.7.*
, direct-sqlcipher ==2.3.*
, directory ==1.3.* , directory ==1.3.*
, email-validate ==2.3.* , email-validate ==2.3.*
, exceptions ==0.10.* , exceptions ==0.10.*
@ -243,7 +256,6 @@ library
, simple-logger ==0.1.* , simple-logger ==0.1.*
, simplexmq >=6.3 , simplexmq >=6.3
, socks ==0.6.* , socks ==0.6.*
, sqlcipher-simple ==0.4.*
, stm ==2.5.* , stm ==2.5.*
, terminal ==0.2.* , terminal ==0.2.*
, time ==1.12.* , time ==1.12.*
@ -255,6 +267,16 @@ library
default-language: Haskell2010 default-language: Haskell2010
if flag(swift) if flag(swift)
cpp-options: -DswiftJSON cpp-options: -DswiftJSON
if flag(client_postgres)
build-depends:
postgresql-libpq >=0.10.0.0
, postgresql-simple ==0.7.*
, raw-strings-qq ==1.1.*
cpp-options: -DdbPostgres
else
build-depends:
direct-sqlcipher ==2.3.*
, sqlcipher-simple ==0.4.*
if impl(ghc >= 9.6.2) if impl(ghc >= 9.6.2)
build-depends: build-depends:
bytestring ==0.11.* bytestring ==0.11.*
@ -282,6 +304,8 @@ executable simplex-bot
, directory ==1.3.* , directory ==1.3.*
, simplex-chat , simplex-chat
default-language: Haskell2010 default-language: Haskell2010
if flag(client_postgres)
cpp-options: -DdbPostgres
executable simplex-bot-advanced executable simplex-bot-advanced
main-is: Main.hs main-is: Main.hs
@ -300,6 +324,8 @@ executable simplex-bot-advanced
, simplexmq >=6.3 , simplexmq >=6.3
, stm ==2.5.* , stm ==2.5.*
default-language: Haskell2010 default-language: Haskell2010
if flag(client_postgres)
cpp-options: -DdbPostgres
if impl(ghc >= 9.6.2) if impl(ghc >= 9.6.2)
build-depends: build-depends:
text >=2.0.1 && <2.2 text >=2.0.1 && <2.2
@ -328,6 +354,8 @@ executable simplex-broadcast-bot
, simplexmq >=6.3 , simplexmq >=6.3
, stm ==2.5.* , stm ==2.5.*
default-language: Haskell2010 default-language: Haskell2010
if flag(client_postgres)
cpp-options: -DdbPostgres
if impl(ghc >= 9.6.2) if impl(ghc >= 9.6.2)
build-depends: build-depends:
text >=2.0.1 && <2.2 text >=2.0.1 && <2.2
@ -357,6 +385,8 @@ executable simplex-chat
, unliftio ==0.2.* , unliftio ==0.2.*
, websockets ==0.12.* , websockets ==0.12.*
default-language: Haskell2010 default-language: Haskell2010
if flag(client_postgres)
cpp-options: -DdbPostgres
if impl(ghc >= 9.6.2) if impl(ghc >= 9.6.2)
build-depends: build-depends:
text >=2.0.1 && <2.2 text >=2.0.1 && <2.2
@ -393,6 +423,8 @@ executable simplex-directory-service
, stm ==2.5.* , stm ==2.5.*
, time ==1.12.* , time ==1.12.*
default-language: Haskell2010 default-language: Haskell2010
if flag(client_postgres)
cpp-options: -DdbPostgres
if impl(ghc >= 9.6.2) if impl(ghc >= 9.6.2)
build-depends: build-depends:
bytestring ==0.11.* bytestring ==0.11.*
@ -418,18 +450,16 @@ test-suite simplex-chat-test
ChatTests.Local ChatTests.Local
ChatTests.Profiles ChatTests.Profiles
ChatTests.Utils ChatTests.Utils
JSONFixtures
JSONTests JSONTests
MarkdownTests MarkdownTests
MessageBatching MessageBatching
MobileTests
OperatorTests OperatorTests
ProtocolTests ProtocolTests
RandomServers RandomServers
RemoteTests RemoteTests
SchemaDump
ValidNames ValidNames
ViewTests ViewTests
WebRTCTests
Broadcast.Bot Broadcast.Bot
Broadcast.Options Broadcast.Options
Directory.Events Directory.Events
@ -438,6 +468,11 @@ test-suite simplex-chat-test
Directory.Service Directory.Service
Directory.Store Directory.Store
Paths_simplex_chat Paths_simplex_chat
if !flag(client_postgres)
other-modules:
MobileTests
SchemaDump
WebRTCTests
hs-source-dirs: hs-source-dirs:
tests tests
apps/simplex-broadcast-bot/src apps/simplex-broadcast-bot/src
@ -469,12 +504,18 @@ test-suite simplex-chat-test
, simple-logger ==0.1.* , simple-logger ==0.1.*
, simplex-chat , simplex-chat
, simplexmq >=6.3 , simplexmq >=6.3
, sqlcipher-simple ==0.4.*
, stm ==2.5.* , stm ==2.5.*
, terminal ==0.2.* , terminal ==0.2.*
, time ==1.12.* , time ==1.12.*
, unliftio ==0.2.* , unliftio ==0.2.*
default-language: Haskell2010 default-language: Haskell2010
if flag(client_postgres)
build-depends:
postgresql-simple ==0.7.*
cpp-options: -DdbPostgres
else
build-depends:
sqlcipher-simple ==0.4.*
if impl(ghc >= 9.6.2) if impl(ghc >= 9.6.2)
build-depends: build-depends:
bytestring ==0.11.* bytestring ==0.11.*

View file

@ -1,3 +1,4 @@
{-# LANGUAGE CPP #-}
{-# LANGUAGE DataKinds #-} {-# LANGUAGE DataKinds #-}
{-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleContexts #-}
@ -20,7 +21,6 @@ import Control.Monad
import Control.Monad.Except import Control.Monad.Except
import Control.Monad.IO.Unlift import Control.Monad.IO.Unlift
import Data.Bifunctor (bimap, second) import Data.Bifunctor (bimap, second)
import Data.ByteArray (ScrubbedBytes)
import Data.List (partition, sortOn) import Data.List (partition, sortOn)
import Data.List.NonEmpty (NonEmpty (..)) import Data.List.NonEmpty (NonEmpty (..))
import qualified Data.List.NonEmpty as L import qualified Data.List.NonEmpty as L
@ -32,6 +32,7 @@ import Simplex.Chat.Controller
import Simplex.Chat.Library.Commands import Simplex.Chat.Library.Commands
import Simplex.Chat.Operators import Simplex.Chat.Operators
import Simplex.Chat.Options import Simplex.Chat.Options
import Simplex.Chat.Options.DB
import Simplex.Chat.Protocol import Simplex.Chat.Protocol
import Simplex.Chat.Store import Simplex.Chat.Store
import Simplex.Chat.Store.Profiles import Simplex.Chat.Store.Profiles
@ -42,7 +43,7 @@ import Simplex.Messaging.Agent as Agent
import Simplex.Messaging.Agent.Env.SQLite (AgentConfig (..), InitialAgentServers (..), ServerCfg (..), ServerRoles (..), allRoles, createAgentStore, defaultAgentConfig, presetServerCfg) import Simplex.Messaging.Agent.Env.SQLite (AgentConfig (..), InitialAgentServers (..), ServerCfg (..), ServerRoles (..), allRoles, createAgentStore, defaultAgentConfig, presetServerCfg)
import Simplex.Messaging.Agent.Protocol import Simplex.Messaging.Agent.Protocol
import Simplex.Messaging.Agent.Store.Common (DBStore (dbNew)) import Simplex.Messaging.Agent.Store.Common (DBStore (dbNew))
import qualified Simplex.Messaging.Agent.Store.SQLite.DB as DB import qualified Simplex.Messaging.Agent.Store.DB as DB
import Simplex.Messaging.Agent.Store.Shared (MigrationConfirmation (..), MigrationError) import Simplex.Messaging.Agent.Store.Shared (MigrationConfirmation (..), MigrationError)
import Simplex.Messaging.Client (defaultNetworkConfig) import Simplex.Messaging.Client (defaultNetworkConfig)
import qualified Simplex.Messaging.Crypto as C import qualified Simplex.Messaging.Crypto as C
@ -50,6 +51,9 @@ import Simplex.Messaging.Protocol (ProtoServerWithAuth (..), ProtocolType (..),
import qualified Simplex.Messaging.TMap as TM import qualified Simplex.Messaging.TMap as TM
import qualified UnliftIO.Exception as E import qualified UnliftIO.Exception as E
import UnliftIO.STM import UnliftIO.STM
#if defined(dbPostgres)
import Database.PostgreSQL.Simple (ConnectInfo (..), defaultConnectInfo)
#endif
operatorSimpleXChat :: NewServerOperator operatorSimpleXChat :: NewServerOperator
operatorSimpleXChat = operatorSimpleXChat =
@ -183,11 +187,20 @@ fluxXFTPServers =
logCfg :: LogConfig logCfg :: LogConfig
logCfg = LogConfig {lc_file = Nothing, lc_stderr = True} logCfg = LogConfig {lc_file = Nothing, lc_stderr = True}
createChatDatabase :: FilePath -> ScrubbedBytes -> Bool -> MigrationConfirmation -> Bool -> IO (Either MigrationError ChatDatabase) createChatDatabase :: ChatDbOpts -> MigrationConfirmation -> IO (Either MigrationError ChatDatabase)
createChatDatabase filePrefix key keepKey confirmMigrations vacuum = runExceptT $ do createChatDatabase dbOpts confirmMigrations = runExceptT $ do
chatStore <- ExceptT $ createChatStore (chatStoreFile filePrefix) key keepKey confirmMigrations vacuum #if defined(dbPostgres)
agentStore <- ExceptT $ createAgentStore (agentStoreFile filePrefix) key keepKey confirmMigrations vacuum let ChatDbOpts {dbName, dbUser, dbSchemaPrefix} = dbOpts
connectInfo = defaultConnectInfo {connectUser = dbUser, connectDatabase = dbName}
chatStore <- ExceptT $ createChatStore connectInfo (chatSchema dbSchemaPrefix) confirmMigrations
agentStore <- ExceptT $ createAgentStore connectInfo (agentSchema dbSchemaPrefix) confirmMigrations
pure ChatDatabase {chatStore, agentStore} pure ChatDatabase {chatStore, agentStore}
#else
let ChatDbOpts {dbFilePrefix, dbKey, vacuumOnMigration} = dbOpts
chatStore <- ExceptT $ createChatStore (chatStoreFile dbFilePrefix) dbKey False confirmMigrations vacuumOnMigration
agentStore <- ExceptT $ createAgentStore (agentStoreFile dbFilePrefix) dbKey False confirmMigrations vacuumOnMigration
pure ChatDatabase {chatStore, agentStore}
#endif
newChatController :: ChatDatabase -> Maybe User -> ChatConfig -> ChatOpts -> Bool -> IO ChatController newChatController :: ChatDatabase -> Maybe User -> ChatConfig -> ChatOpts -> Bool -> IO ChatController
newChatController newChatController

View file

@ -11,7 +11,6 @@ module Simplex.Chat.Archive
deleteStorage, deleteStorage,
sqlCipherExport, sqlCipherExport,
sqlCipherTestKey, sqlCipherTestKey,
archiveFilesFolder,
) )
where where
@ -112,7 +111,7 @@ copyValidDirectoryFiles isFileError fromDir toDir = do
Nothing -> Nothing ->
(copyDirectoryFile f $> fileErrs) (copyDirectoryFile f $> fileErrs)
`E.catch` \(e :: E.SomeException) -> addErr $ show e `E.catch` \(e :: E.SomeException) -> addErr $ show e
Just e -> addErr e Just e -> addErr e
where where
addErr e = pure $ AEFileError f e : fileErrs addErr e = pure $ AEFileError f e : fileErrs
copyDirectoryFile f = do copyDirectoryFile f = do

View file

@ -1,9 +1,13 @@
{-# LANGUAGE CPP #-}
{-# LANGUAGE DataKinds #-} {-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE LambdaCase #-} {-# LANGUAGE LambdaCase #-}
{-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TemplateHaskell #-}
{-# OPTIONS_GHC -Wno-unrecognised-pragmas #-} {-# OPTIONS_GHC -Wno-unrecognised-pragmas #-}
@ -18,13 +22,19 @@ import Data.ByteString.Char8 (ByteString)
import Data.Int (Int64) import Data.Int (Int64)
import Data.Text (Text) import Data.Text (Text)
import Data.Time.Clock (UTCTime) import Data.Time.Clock (UTCTime)
import Database.SQLite.Simple.FromField (FromField (..))
import Database.SQLite.Simple.ToField (ToField (..))
import Simplex.Chat.Types (Contact, ContactId, User) import Simplex.Chat.Types (Contact, ContactId, User)
import Simplex.Messaging.Agent.Store.DB (Binary (..))
import qualified Simplex.Messaging.Crypto as C import qualified Simplex.Messaging.Crypto as C
import Simplex.Messaging.Encoding.String import Simplex.Messaging.Encoding.String
import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, enumJSON, fromTextField_, fstToLower, singleFieldJSON) import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, enumJSON, fromTextField_, fstToLower, singleFieldJSON)
import Simplex.Messaging.Util (decodeJSON, encodeJSON) import Simplex.Messaging.Util (decodeJSON, encodeJSON)
#if defined(dbPostgres)
import Database.PostgreSQL.Simple.FromField (FromField (..))
import Database.PostgreSQL.Simple.ToField (ToField (..))
#else
import Database.SQLite.Simple.FromField (FromField (..))
import Database.SQLite.Simple.ToField (ToField (..))
#endif
data Call = Call data Call = Call
{ contactId :: ContactId, { contactId :: ContactId,
@ -90,6 +100,9 @@ data CallState
newtype CallId = CallId ByteString newtype CallId = CallId ByteString
deriving (Eq, Show) deriving (Eq, Show)
deriving newtype (FromField)
instance ToField CallId where toField (CallId m) = toField $ Binary m
instance StrEncoding CallId where instance StrEncoding CallId where
strEncode (CallId m) = strEncode m strEncode (CallId m) = strEncode m
@ -103,10 +116,6 @@ instance ToJSON CallId where
toJSON = strToJSON toJSON = strToJSON
toEncoding = strToJEncoding toEncoding = strToJEncoding
instance FromField CallId where fromField f = CallId <$> fromField f
instance ToField CallId where toField (CallId m) = toField m
data RcvCallInvitation = RcvCallInvitation data RcvCallInvitation = RcvCallInvitation
{ user :: User, { user :: User,
contact :: Contact, contact :: Contact,

View file

@ -46,8 +46,6 @@ import Data.Time (NominalDiffTime, UTCTime)
import Data.Time.Clock.System (SystemTime (..), systemToUTCTime) import Data.Time.Clock.System (SystemTime (..), systemToUTCTime)
import Data.Version (showVersion) import Data.Version (showVersion)
import Data.Word (Word16) import Data.Word (Word16)
import Database.SQLite.Simple (SQLError)
import qualified Database.SQLite.Simple as SQL
import Language.Haskell.TH (Exp, Q, runIO) import Language.Haskell.TH (Exp, Q, runIO)
import Numeric.Natural import Numeric.Natural
import qualified Paths_simplex_chat as SC import qualified Paths_simplex_chat as SC
@ -73,10 +71,9 @@ import Simplex.Messaging.Agent.Client (AgentLocks, AgentQueuesInfo (..), AgentWo
import Simplex.Messaging.Agent.Env.SQLite (AgentConfig, NetworkConfig, ServerCfg) import Simplex.Messaging.Agent.Env.SQLite (AgentConfig, NetworkConfig, ServerCfg)
import Simplex.Messaging.Agent.Lock import Simplex.Messaging.Agent.Lock
import Simplex.Messaging.Agent.Protocol import Simplex.Messaging.Agent.Protocol
import Simplex.Messaging.Agent.Store.SQLite.Common (DBStore, withTransaction, withTransactionPriority) import Simplex.Messaging.Agent.Store.Common (DBStore, withTransaction, withTransactionPriority)
import Simplex.Messaging.Agent.Store.SQLite.DB (SlowQueryStats (..))
import qualified Simplex.Messaging.Agent.Store.SQLite.DB as DB
import Simplex.Messaging.Agent.Store.Shared (MigrationConfirmation, UpMigration) import Simplex.Messaging.Agent.Store.Shared (MigrationConfirmation, UpMigration)
import qualified Simplex.Messaging.Agent.Store.DB as DB
import Simplex.Messaging.Client (HostMode (..), SMPProxyFallback (..), SMPProxyMode (..), SocksMode (..)) import Simplex.Messaging.Client (HostMode (..), SMPProxyFallback (..), SMPProxyMode (..), SocksMode (..))
import qualified Simplex.Messaging.Crypto as C import qualified Simplex.Messaging.Crypto as C
import Simplex.Messaging.Crypto.File (CryptoFile (..)) import Simplex.Messaging.Crypto.File (CryptoFile (..))
@ -97,6 +94,11 @@ import System.IO (Handle)
import System.Mem.Weak (Weak) import System.Mem.Weak (Weak)
import qualified UnliftIO.Exception as E import qualified UnliftIO.Exception as E
import UnliftIO.STM import UnliftIO.STM
#if !defined(dbPostgres)
import Database.SQLite.Simple (SQLError)
import qualified Database.SQLite.Simple as SQL
import Simplex.Messaging.Agent.Store.SQLite.DB (SlowQueryStats (..))
#endif
versionNumber :: String versionNumber :: String
versionNumber = showVersion SC.version versionNumber = showVersion SC.version
@ -284,17 +286,19 @@ data ChatCommand
| APISetAppFilePaths AppFilePathsConfig | APISetAppFilePaths AppFilePathsConfig
| APISetEncryptLocalFiles Bool | APISetEncryptLocalFiles Bool
| SetContactMergeEnabled Bool | SetContactMergeEnabled Bool
#if !defined(dbPostgres)
| APIExportArchive ArchiveConfig | APIExportArchive ArchiveConfig
| ExportArchive | ExportArchive
| APIImportArchive ArchiveConfig | APIImportArchive ArchiveConfig
| APISaveAppSettings AppSettings
| APIGetAppSettings (Maybe AppSettings)
| APIDeleteStorage | APIDeleteStorage
| APIStorageEncryption DBEncryptionConfig | APIStorageEncryption DBEncryptionConfig
| TestStorageEncryption DBEncryptionKey | TestStorageEncryption DBEncryptionKey
| SlowSQLQueries
#endif
| ExecChatStoreSQL Text | ExecChatStoreSQL Text
| ExecAgentStoreSQL Text | ExecAgentStoreSQL Text
| SlowSQLQueries | APISaveAppSettings AppSettings
| APIGetAppSettings (Maybe AppSettings)
| APIGetChatTags UserId | APIGetChatTags UserId
| APIGetChats {userId :: UserId, pendingConnections :: Bool, pagination :: PaginationByTime, query :: ChatListQuery} | APIGetChats {userId :: UserId, pendingConnections :: Bool, pagination :: PaginationByTime, query :: ChatListQuery}
| APIGetChat ChatRef (Maybe ContentFilter) ChatPagination (Maybe String) | APIGetChat ChatRef (Maybe ContentFilter) ChatPagination (Maybe String)
@ -559,11 +563,14 @@ allowRemoteCommand = \case
SetFilesFolder _ -> False SetFilesFolder _ -> False
SetRemoteHostsFolder _ -> False SetRemoteHostsFolder _ -> False
APISetEncryptLocalFiles _ -> False APISetEncryptLocalFiles _ -> False
#if !defined(dbPostgres)
APIExportArchive _ -> False APIExportArchive _ -> False
APIImportArchive _ -> False APIImportArchive _ -> False
ExportArchive -> False ExportArchive -> False
APIDeleteStorage -> False APIDeleteStorage -> False
APIStorageEncryption _ -> False APIStorageEncryption _ -> False
SlowSQLQueries -> False
#endif
APISetNetworkConfig _ -> False APISetNetworkConfig _ -> False
APIGetNetworkConfig -> False APIGetNetworkConfig -> False
SetLocalDeviceName _ -> False SetLocalDeviceName _ -> False
@ -583,7 +590,6 @@ allowRemoteCommand = \case
DeleteRemoteCtrl _ -> False DeleteRemoteCtrl _ -> False
ExecChatStoreSQL _ -> False ExecChatStoreSQL _ -> False
ExecAgentStoreSQL _ -> False ExecAgentStoreSQL _ -> False
SlowSQLQueries -> False
_ -> True _ -> True
data ChatResponse data ChatResponse
@ -798,7 +804,11 @@ data ChatResponse
| CRRemoteCtrlStopped {rcsState :: RemoteCtrlSessionState, rcStopReason :: RemoteCtrlStopReason} | CRRemoteCtrlStopped {rcsState :: RemoteCtrlSessionState, rcStopReason :: RemoteCtrlStopReason}
| CRContactPQEnabled {user :: User, contact :: Contact, pqEnabled :: PQEncryption} | CRContactPQEnabled {user :: User, contact :: Contact, pqEnabled :: PQEncryption}
| CRSQLResult {rows :: [Text]} | CRSQLResult {rows :: [Text]}
#if !defined(dbPostgres)
| CRArchiveExported {archiveErrors :: [ArchiveError]}
| CRArchiveImported {archiveErrors :: [ArchiveError]}
| CRSlowSQLQueries {chatQueries :: [SlowSQLQuery], agentQueries :: [SlowSQLQuery]} | CRSlowSQLQueries {chatQueries :: [SlowSQLQuery], agentQueries :: [SlowSQLQuery]}
#endif
| CRDebugLocks {chatLockName :: Maybe String, chatEntityLocks :: Map String String, agentLocks :: AgentLocks} | CRDebugLocks {chatLockName :: Maybe String, chatEntityLocks :: Map String String, agentLocks :: AgentLocks}
| CRAgentSubsTotal {user :: User, subsTotal :: SMPServerSubs, hasSession :: Bool} | CRAgentSubsTotal {user :: User, subsTotal :: SMPServerSubs, hasSession :: Bool}
| CRAgentServersSummary {user :: User, serversSummary :: PresentedServersSummary} | CRAgentServersSummary {user :: User, serversSummary :: PresentedServersSummary}
@ -817,8 +827,6 @@ data ChatResponse
| CRChatCmdError {user_ :: Maybe User, chatError :: ChatError} | CRChatCmdError {user_ :: Maybe User, chatError :: ChatError}
| CRChatError {user_ :: Maybe User, chatError :: ChatError} | CRChatError {user_ :: Maybe User, chatError :: ChatError}
| CRChatErrors {user_ :: Maybe User, chatErrors :: [ChatError]} | CRChatErrors {user_ :: Maybe User, chatErrors :: [ChatError]}
| CRArchiveExported {archiveErrors :: [ArchiveError]}
| CRArchiveImported {archiveErrors :: [ArchiveError]}
| CRAppSettings {appSettings :: AppSettings} | CRAppSettings {appSettings :: AppSettings}
| CRTimedAction {action :: String, durationMilliseconds :: Int64} | CRTimedAction {action :: String, durationMilliseconds :: Int64}
| CRCustomChatResponse {user_ :: Maybe User, response :: Text} | CRCustomChatResponse {user_ :: Maybe User, response :: Text}
@ -846,7 +854,9 @@ allowRemoteEvent = \case
CRRemoteCtrlConnected _ -> False CRRemoteCtrlConnected _ -> False
CRRemoteCtrlStopped {} -> False CRRemoteCtrlStopped {} -> False
CRSQLResult _ -> False CRSQLResult _ -> False
#if !defined(dbPostgres)
CRSlowSQLQueries {} -> False CRSlowSQLQueries {} -> False
#endif
_ -> True _ -> True
logResponseToFile :: ChatResponse -> Bool logResponseToFile :: ChatResponse -> Bool
@ -1181,11 +1191,13 @@ data CoreVersionInfo = CoreVersionInfo
} }
deriving (Show) deriving (Show)
#if !defined(dbPostgres)
data SlowSQLQuery = SlowSQLQuery data SlowSQLQuery = SlowSQLQuery
{ query :: Text, { query :: Text,
queryStats :: SlowQueryStats queryStats :: SlowQueryStats
} }
deriving (Show) deriving (Show)
#endif
data ChatError data ChatError
= ChatError {errorType :: ChatErrorType} = ChatError {errorType :: ChatErrorType}
@ -1512,13 +1524,17 @@ withStoreBatch actions = do
ChatController {chatStore} <- ask ChatController {chatStore} <- ask
liftIO $ withTransaction chatStore $ mapM (`E.catches` handleDBErrors) . actions liftIO $ withTransaction chatStore $ mapM (`E.catches` handleDBErrors) . actions
-- TODO [postgres] postgres specific error handling
handleDBErrors :: [E.Handler IO (Either ChatError a)] handleDBErrors :: [E.Handler IO (Either ChatError a)]
handleDBErrors = handleDBErrors =
[ E.Handler $ \(e :: SQLError) -> #if !defined(dbPostgres)
( E.Handler $ \(e :: SQLError) ->
let se = SQL.sqlError e let se = SQL.sqlError e
busy = se == SQL.ErrorBusy || se == SQL.ErrorLocked busy = se == SQL.ErrorBusy || se == SQL.ErrorLocked
in pure . Left . ChatErrorStore $ if busy then SEDBBusyError $ show se else SEDBException $ show e, in pure . Left . ChatErrorStore $ if busy then SEDBBusyError $ show se else SEDBException $ show e
E.Handler $ \(E.SomeException e) -> pure . Left . ChatErrorStore . SEDBException $ show e ) :
#endif
[ E.Handler $ \(E.SomeException e) -> pure . Left . ChatErrorStore . SEDBException $ show e
] ]
withStoreBatch' :: Traversable t => (DB.Connection -> t (IO a)) -> CM' (t (Either ChatError a)) withStoreBatch' :: Traversable t => (DB.Connection -> t (IO a)) -> CM' (t (Either ChatError a))
@ -1591,7 +1607,9 @@ $(JQ.deriveJSON defaultJSON ''ChatItemDeletion)
$(JQ.deriveJSON defaultJSON ''CoreVersionInfo) $(JQ.deriveJSON defaultJSON ''CoreVersionInfo)
#if !defined(dbPostgres)
$(JQ.deriveJSON defaultJSON ''SlowSQLQuery) $(JQ.deriveJSON defaultJSON ''SlowSQLQuery)
#endif
-- instance ProtocolTypeI p => FromJSON (ProtoServersConfig p) where -- instance ProtocolTypeI p => FromJSON (ProtoServersConfig p) where
-- parseJSON = $(JQ.mkParseJSON defaultJSON ''ProtoServersConfig) -- parseJSON = $(JQ.mkParseJSON defaultJSON ''ProtoServersConfig)

View file

@ -26,22 +26,22 @@ import Simplex.Chat.Options (ChatOpts (..), CoreChatOpts (..))
import Simplex.Chat.Store.Profiles import Simplex.Chat.Store.Profiles
import Simplex.Chat.Types import Simplex.Chat.Types
import Simplex.Chat.View (serializeChatResponse) import Simplex.Chat.View (serializeChatResponse)
import Simplex.Messaging.Agent.Store.SQLite.Common (DBStore (..), withTransaction)
import Simplex.Messaging.Agent.Store.Shared (MigrationConfirmation (..)) import Simplex.Messaging.Agent.Store.Shared (MigrationConfirmation (..))
import Simplex.Messaging.Agent.Store.Common (DBStore, withTransaction)
import System.Exit (exitFailure) import System.Exit (exitFailure)
import System.IO (hFlush, stdout) import System.IO (hFlush, stdout)
import Text.Read (readMaybe) import Text.Read (readMaybe)
import UnliftIO.Async import UnliftIO.Async
simplexChatCore :: ChatConfig -> ChatOpts -> (User -> ChatController -> IO ()) -> IO () simplexChatCore :: ChatConfig -> ChatOpts -> (User -> ChatController -> IO ()) -> IO ()
simplexChatCore cfg@ChatConfig {confirmMigrations, testView} opts@ChatOpts {coreOptions = CoreChatOpts {dbFilePrefix, dbKey, logAgent, yesToUpMigrations, vacuumOnMigration}} chat = simplexChatCore cfg@ChatConfig {confirmMigrations, testView} opts@ChatOpts {coreOptions = CoreChatOpts {dbOptions, logAgent, yesToUpMigrations}} chat =
case logAgent of case logAgent of
Just level -> do Just level -> do
setLogLevel level setLogLevel level
withGlobalLogging logCfg initRun withGlobalLogging logCfg initRun
_ -> initRun _ -> initRun
where where
initRun = createChatDatabase dbFilePrefix dbKey False confirm' vacuumOnMigration >>= either exit run initRun = createChatDatabase dbOptions confirm' >>= either exit run
confirm' = if confirmMigrations == MCConsole && yesToUpMigrations then MCYesUp else confirmMigrations confirm' = if confirmMigrations == MCConsole && yesToUpMigrations then MCYesUp else confirmMigrations
exit e = do exit e = do
putStrLn $ "Error opening database: " <> show e putStrLn $ "Error opening database: " <> show e

View file

@ -1,3 +1,4 @@
{-# LANGUAGE CPP #-}
{-# LANGUAGE DataKinds #-} {-# LANGUAGE DataKinds #-}
{-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleContexts #-}
@ -26,8 +27,6 @@ import Control.Monad.Reader
import qualified Data.Aeson as J import qualified Data.Aeson as J
import Data.Attoparsec.ByteString.Char8 (Parser) import Data.Attoparsec.ByteString.Char8 (Parser)
import qualified Data.Attoparsec.ByteString.Char8 as A import qualified Data.Attoparsec.ByteString.Char8 as A
import Data.Bifunctor (bimap, first, second)
import qualified Data.ByteArray as BA
import qualified Data.ByteString.Base64 as B64 import qualified Data.ByteString.Base64 as B64
import Data.ByteString.Char8 (ByteString) import Data.ByteString.Char8 (ByteString)
import qualified Data.ByteString.Char8 as B import qualified Data.ByteString.Char8 as B
@ -47,14 +46,11 @@ import Data.Maybe (catMaybes, fromMaybe, isJust, isNothing, listToMaybe, mapMayb
import Data.Text (Text) import Data.Text (Text)
import qualified Data.Text as T import qualified Data.Text as T
import Data.Text.Encoding (decodeLatin1, encodeUtf8) import Data.Text.Encoding (decodeLatin1, encodeUtf8)
import Data.Time (NominalDiffTime, addUTCTime, defaultTimeLocale, formatTime)
import Data.Time.Clock (UTCTime, getCurrentTime, nominalDay) import Data.Time.Clock (UTCTime, getCurrentTime, nominalDay)
import Data.Type.Equality import Data.Type.Equality
import qualified Data.UUID as UUID import qualified Data.UUID as UUID
import qualified Data.UUID.V4 as V4 import qualified Data.UUID.V4 as V4
import qualified Database.SQLite.Simple as SQL
import Simplex.Chat.Library.Subscriber import Simplex.Chat.Library.Subscriber
import Simplex.Chat.Archive
import Simplex.Chat.Call import Simplex.Chat.Call
import Simplex.Chat.Controller import Simplex.Chat.Controller
import Simplex.Chat.Files import Simplex.Chat.Files
@ -87,15 +83,12 @@ import Simplex.Chat.Util (liftIOEither)
import qualified Simplex.Chat.Util as U import qualified Simplex.Chat.Util as U
import Simplex.FileTransfer.Description (FileDescriptionURI (..), maxFileSize, maxFileSizeHard) import Simplex.FileTransfer.Description (FileDescriptionURI (..), maxFileSize, maxFileSizeHard)
import Simplex.Messaging.Agent as Agent import Simplex.Messaging.Agent as Agent
import Simplex.Messaging.Agent.Client (SubInfo (..), agentClientStore, getAgentQueuesInfo, getAgentWorkersDetails, getAgentWorkersSummary)
import Simplex.Messaging.Agent.Env.SQLite (ServerCfg (..), ServerRoles (..), allRoles) import Simplex.Messaging.Agent.Env.SQLite (ServerCfg (..), ServerRoles (..), allRoles)
import Simplex.Messaging.Agent.Protocol import Simplex.Messaging.Agent.Protocol
import Simplex.Messaging.Agent.Store.SQLite (execSQL)
import Simplex.Messaging.Agent.Store.SQLite.Common (withConnection)
import Simplex.Messaging.Agent.Store.SQLite.DB (SlowQueryStats (..))
import qualified Simplex.Messaging.Agent.Store.SQLite.DB as DB
import qualified Simplex.Messaging.Agent.Store.SQLite.Migrations as Migrations
import Simplex.Messaging.Agent.Store.Shared (upMigration) import Simplex.Messaging.Agent.Store.Shared (upMigration)
import Simplex.Messaging.Agent.Store (execSQL)
import qualified Simplex.Messaging.Agent.Store.DB as DB
import qualified Simplex.Messaging.Agent.Store.Migrations as Migrations
import Simplex.Messaging.Client (NetworkConfig (..), SocksMode (SMAlways), textToHostMode) import Simplex.Messaging.Client (NetworkConfig (..), SocksMode (SMAlways), textToHostMode)
import qualified Simplex.Messaging.Crypto as C import qualified Simplex.Messaging.Crypto as C
import Simplex.Messaging.Crypto.File (CryptoFile (..), CryptoFileArgs (..)) import Simplex.Messaging.Crypto.File (CryptoFile (..), CryptoFileArgs (..))
@ -122,6 +115,20 @@ import UnliftIO.Directory
import qualified UnliftIO.Exception as E import qualified UnliftIO.Exception as E
import UnliftIO.IO (hClose) import UnliftIO.IO (hClose)
import UnliftIO.STM import UnliftIO.STM
#if defined(dbPostgres)
import Data.Bifunctor (bimap, second)
import Data.Time (NominalDiffTime, addUTCTime)
import Simplex.Messaging.Agent.Client (SubInfo (..), getAgentQueuesInfo, getAgentWorkersDetails, getAgentWorkersSummary)
#else
import Data.Bifunctor (bimap, first, second)
import qualified Data.ByteArray as BA
import Data.Time (NominalDiffTime, addUTCTime, defaultTimeLocale, formatTime)
import qualified Database.SQLite.Simple as SQL
import Simplex.Chat.Archive
import Simplex.Messaging.Agent.Client (SubInfo (..), agentClientStore, getAgentQueuesInfo, getAgentWorkersDetails, getAgentWorkersSummary)
import Simplex.Messaging.Agent.Store.Common (withConnection)
import Simplex.Messaging.Agent.Store.SQLite.DB (SlowQueryStats (..))
#endif
_defaultNtfServers :: [NtfServer] _defaultNtfServers :: [NtfServer]
_defaultNtfServers = _defaultNtfServers =
@ -446,6 +453,7 @@ processChatCommand' vr = \case
chatWriteVar sel $ Just f chatWriteVar sel $ Just f
APISetEncryptLocalFiles on -> chatWriteVar encryptLocalFiles on >> ok_ APISetEncryptLocalFiles on -> chatWriteVar encryptLocalFiles on >> ok_
SetContactMergeEnabled onOff -> chatWriteVar contactMergeEnabled onOff >> ok_ SetContactMergeEnabled onOff -> chatWriteVar contactMergeEnabled onOff >> ok_
#if !defined(dbPostgres)
APIExportArchive cfg -> checkChatStopped $ CRArchiveExported <$> lift (exportArchive cfg) APIExportArchive cfg -> checkChatStopped $ CRArchiveExported <$> lift (exportArchive cfg)
ExportArchive -> do ExportArchive -> do
ts <- liftIO getCurrentTime ts <- liftIO getCurrentTime
@ -455,13 +463,9 @@ processChatCommand' vr = \case
fileErrs <- lift $ importArchive cfg fileErrs <- lift $ importArchive cfg
setStoreChanged setStoreChanged
pure $ CRArchiveImported fileErrs pure $ CRArchiveImported fileErrs
APISaveAppSettings as -> withFastStore' (`saveAppSettings` as) >> ok_
APIGetAppSettings platformDefaults -> CRAppSettings <$> withFastStore' (`getAppSettings` platformDefaults)
APIDeleteStorage -> withStoreChanged deleteStorage APIDeleteStorage -> withStoreChanged deleteStorage
APIStorageEncryption cfg -> withStoreChanged $ sqlCipherExport cfg APIStorageEncryption cfg -> withStoreChanged $ sqlCipherExport cfg
TestStorageEncryption key -> sqlCipherTestKey key >> ok_ TestStorageEncryption key -> sqlCipherTestKey key >> ok_
ExecChatStoreSQL query -> CRSQLResult <$> withStore' (`execSQL` query)
ExecAgentStoreSQL query -> CRSQLResult <$> withAgent (`execAgentStoreSQL` query)
SlowSQLQueries -> do SlowSQLQueries -> do
ChatController {chatStore, smpAgent} <- ask ChatController {chatStore, smpAgent} <- ask
chatQueries <- slowQueries chatStore chatQueries <- slowQueries chatStore
@ -474,6 +478,11 @@ processChatCommand' vr = \case
. sortOn (timeAvg . snd) . sortOn (timeAvg . snd)
. M.assocs . M.assocs
<$> withConnection st (readTVarIO . DB.slow) <$> withConnection st (readTVarIO . DB.slow)
#endif
ExecChatStoreSQL query -> CRSQLResult <$> withStore' (`execSQL` query)
ExecAgentStoreSQL query -> CRSQLResult <$> withAgent (`execAgentStoreSQL` query)
APISaveAppSettings as -> withFastStore' (`saveAppSettings` as) >> ok_
APIGetAppSettings platformDefaults -> CRAppSettings <$> withFastStore' (`getAppSettings` platformDefaults)
APIGetChatTags userId -> withUserId' userId $ \user -> do APIGetChatTags userId -> withUserId' userId $ \user -> do
tags <- withFastStore' (`getUserChatTags` user) tags <- withFastStore' (`getUserChatTags` user)
pure $ CRChatTags user tags pure $ CRChatTags user tags
@ -2421,12 +2430,14 @@ processChatCommand' vr = \case
| name == "" -> withFastStore (`getUserNoteFolderId` user) | name == "" -> withFastStore (`getUserNoteFolderId` user)
| otherwise -> throwChatError $ CECommandError "not supported" | otherwise -> throwChatError $ CECommandError "not supported"
_ -> throwChatError $ CECommandError "not supported" _ -> throwChatError $ CECommandError "not supported"
#if !defined(dbPostgres)
checkChatStopped :: CM ChatResponse -> CM ChatResponse checkChatStopped :: CM ChatResponse -> CM ChatResponse
checkChatStopped a = asks agentAsync >>= readTVarIO >>= maybe a (const $ throwChatError CEChatNotStopped) checkChatStopped a = asks agentAsync >>= readTVarIO >>= maybe a (const $ throwChatError CEChatNotStopped)
setStoreChanged :: CM () setStoreChanged :: CM ()
setStoreChanged = asks chatStoreChanged >>= atomically . (`writeTVar` True) setStoreChanged = asks chatStoreChanged >>= atomically . (`writeTVar` True)
withStoreChanged :: CM () -> CM ChatResponse withStoreChanged :: CM () -> CM ChatResponse
withStoreChanged a = checkChatStopped $ a >> setStoreChanged >> ok_ withStoreChanged a = checkChatStopped $ a >> setStoreChanged >> ok_
#endif
checkStoreNotChanged :: CM ChatResponse -> CM ChatResponse checkStoreNotChanged :: CM ChatResponse -> CM ChatResponse
checkStoreNotChanged = ifM (asks chatStoreChanged >>= readTVarIO) (throwChatError CEChatStoreChanged) checkStoreNotChanged = ifM (asks chatStoreChanged >>= readTVarIO) (throwChatError CEChatStoreChanged)
withUserName :: UserName -> (UserId -> ChatCommand) -> CM ChatResponse withUserName :: UserName -> (UserId -> ChatCommand) -> CM ChatResponse
@ -3558,6 +3569,7 @@ chatCommandP =
"/set file paths " *> (APISetAppFilePaths <$> jsonP), "/set file paths " *> (APISetAppFilePaths <$> jsonP),
"/_files_encrypt " *> (APISetEncryptLocalFiles <$> onOffP), "/_files_encrypt " *> (APISetEncryptLocalFiles <$> onOffP),
"/contact_merge " *> (SetContactMergeEnabled <$> onOffP), "/contact_merge " *> (SetContactMergeEnabled <$> onOffP),
#if !defined(dbPostgres)
"/_db export " *> (APIExportArchive <$> jsonP), "/_db export " *> (APIExportArchive <$> jsonP),
"/db export" $> ExportArchive, "/db export" $> ExportArchive,
"/_db import " *> (APIImportArchive <$> jsonP), "/_db import " *> (APIImportArchive <$> jsonP),
@ -3567,11 +3579,12 @@ chatCommandP =
"/db key " *> (APIStorageEncryption <$> (dbEncryptionConfig <$> dbKeyP <* A.space <*> dbKeyP)), "/db key " *> (APIStorageEncryption <$> (dbEncryptionConfig <$> dbKeyP <* A.space <*> dbKeyP)),
"/db decrypt " *> (APIStorageEncryption . (`dbEncryptionConfig` "") <$> dbKeyP), "/db decrypt " *> (APIStorageEncryption . (`dbEncryptionConfig` "") <$> dbKeyP),
"/db test key " *> (TestStorageEncryption <$> dbKeyP), "/db test key " *> (TestStorageEncryption <$> dbKeyP),
"/sql slow" $> SlowSQLQueries,
#endif
"/_save app settings" *> (APISaveAppSettings <$> jsonP), "/_save app settings" *> (APISaveAppSettings <$> jsonP),
"/_get app settings" *> (APIGetAppSettings <$> optional (A.space *> jsonP)), "/_get app settings" *> (APIGetAppSettings <$> optional (A.space *> jsonP)),
"/sql chat " *> (ExecChatStoreSQL <$> textP), "/sql chat " *> (ExecChatStoreSQL <$> textP),
"/sql agent " *> (ExecAgentStoreSQL <$> textP), "/sql agent " *> (ExecAgentStoreSQL <$> textP),
"/sql slow" $> SlowSQLQueries,
"/_get tags " *> (APIGetChatTags <$> A.decimal), "/_get tags " *> (APIGetChatTags <$> A.decimal),
"/_get chats " "/_get chats "
*> ( APIGetChats *> ( APIGetChats
@ -4005,9 +4018,11 @@ chatCommandP =
logTLSErrors <- " log=" *> onOffP <|> pure False logTLSErrors <- " log=" *> onOffP <|> pure False
let tcpTimeout_ = (1000000 *) <$> t_ let tcpTimeout_ = (1000000 *) <$> t_
pure $ SimpleNetCfg {socksProxy, socksMode, hostMode, requiredHostMode, smpProxyMode_, smpProxyFallback_, smpWebPort, tcpTimeout_, logTLSErrors} pure $ SimpleNetCfg {socksProxy, socksMode, hostMode, requiredHostMode, smpProxyMode_, smpProxyFallback_, smpWebPort, tcpTimeout_, logTLSErrors}
#if !defined(dbPostgres)
dbKeyP = nonEmptyKey <$?> strP dbKeyP = nonEmptyKey <$?> strP
nonEmptyKey k@(DBEncryptionKey s) = if BA.null s then Left "empty key" else Right k nonEmptyKey k@(DBEncryptionKey s) = if BA.null s then Left "empty key" else Right k
dbEncryptionConfig currentKey newKey = DBEncryptionConfig {currentKey, newKey, keepKey = Just False} dbEncryptionConfig currentKey newKey = DBEncryptionConfig {currentKey, newKey, keepKey = Just False}
#endif
autoAcceptP = ifM onOffP (Just <$> (businessAA <|> addressAA)) (pure Nothing) autoAcceptP = ifM onOffP (Just <$> (businessAA <|> addressAA)) (pure Nothing)
where where
addressAA = AutoAccept False <$> (" incognito=" *> onOffP <|> pure False) <*> autoReply addressAA = AutoAccept False <$> (" incognito=" *> onOffP <|> pure False) <*> autoReply

View file

@ -79,7 +79,7 @@ import Simplex.Messaging.Agent.Env.SQLite (ServerCfg (..))
import Simplex.Messaging.Agent.Lock (withLock) import Simplex.Messaging.Agent.Lock (withLock)
import Simplex.Messaging.Agent.Protocol import Simplex.Messaging.Agent.Protocol
import qualified Simplex.Messaging.Agent.Protocol as AP (AgentErrorType (..)) import qualified Simplex.Messaging.Agent.Protocol as AP (AgentErrorType (..))
import qualified Simplex.Messaging.Agent.Store.SQLite.DB as DB import qualified Simplex.Messaging.Agent.Store.DB as DB
import Simplex.Messaging.Client (NetworkConfig (..)) import Simplex.Messaging.Client (NetworkConfig (..))
import Simplex.Messaging.Crypto.File (CryptoFile (..), CryptoFileArgs (..)) import Simplex.Messaging.Crypto.File (CryptoFile (..), CryptoFileArgs (..))
import qualified Simplex.Messaging.Crypto.File as CF import qualified Simplex.Messaging.Crypto.File as CF

View file

@ -43,12 +43,12 @@ import qualified Data.UUID.V4 as V4
import Data.Word (Word32) import Data.Word (Word32)
import Simplex.Chat.Call import Simplex.Chat.Call
import Simplex.Chat.Controller import Simplex.Chat.Controller
import Simplex.Chat.Library.Internal
import Simplex.Chat.Messages import Simplex.Chat.Messages
import Simplex.Chat.Messages.CIContent import Simplex.Chat.Messages.CIContent
import Simplex.Chat.Messages.CIContent.Events import Simplex.Chat.Messages.CIContent.Events
import Simplex.Chat.ProfileGenerator (generateRandomProfile) import Simplex.Chat.ProfileGenerator (generateRandomProfile)
import Simplex.Chat.Protocol import Simplex.Chat.Protocol
import Simplex.Chat.Library.Internal
import Simplex.Chat.Store import Simplex.Chat.Store
import Simplex.Chat.Store.Connections import Simplex.Chat.Store.Connections
import Simplex.Chat.Store.Direct import Simplex.Chat.Store.Direct
@ -70,7 +70,7 @@ import Simplex.Messaging.Agent as Agent
import Simplex.Messaging.Agent.Env.SQLite (AgentConfig (..)) import Simplex.Messaging.Agent.Env.SQLite (AgentConfig (..))
import Simplex.Messaging.Agent.Protocol import Simplex.Messaging.Agent.Protocol
import qualified Simplex.Messaging.Agent.Protocol as AP (AgentErrorType (..)) import qualified Simplex.Messaging.Agent.Protocol as AP (AgentErrorType (..))
import qualified Simplex.Messaging.Agent.Store.SQLite.DB as DB import qualified Simplex.Messaging.Agent.Store.DB as DB
import Simplex.Messaging.Client (ProxyClientError (..)) import Simplex.Messaging.Client (ProxyClientError (..))
import qualified Simplex.Messaging.Crypto as C import qualified Simplex.Messaging.Crypto as C
import Simplex.Messaging.Crypto.File (CryptoFile (..)) import Simplex.Messaging.Crypto.File (CryptoFile (..))

View file

@ -1,3 +1,4 @@
{-# LANGUAGE CPP #-}
{-# LANGUAGE DataKinds #-} {-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE DuplicateRecordFields #-}
@ -38,8 +39,6 @@ import Data.Text.Encoding (decodeLatin1, encodeUtf8)
import Data.Time.Clock (NominalDiffTime, UTCTime, diffUTCTime, nominalDay) import Data.Time.Clock (NominalDiffTime, UTCTime, diffUTCTime, nominalDay)
import Data.Type.Equality import Data.Type.Equality
import Data.Typeable (Typeable) import Data.Typeable (Typeable)
import Database.SQLite.Simple.FromField (FromField (..))
import Database.SQLite.Simple.ToField (ToField (..))
import GHC.TypeLits (ErrorMessage (ShowType, type (:<>:)), TypeError) import GHC.TypeLits (ErrorMessage (ShowType, type (:<>:)), TypeError)
import qualified GHC.TypeLits as Type import qualified GHC.TypeLits as Type
import Simplex.Chat.Markdown import Simplex.Chat.Markdown
@ -55,6 +54,13 @@ import Simplex.Messaging.Encoding.String
import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, enumJSON, fromTextField_, parseAll, sumTypeJSON) import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, enumJSON, fromTextField_, parseAll, sumTypeJSON)
import Simplex.Messaging.Protocol (MsgBody) import Simplex.Messaging.Protocol (MsgBody)
import Simplex.Messaging.Util (eitherToMaybe, safeDecodeUtf8, (<$?>)) import Simplex.Messaging.Util (eitherToMaybe, safeDecodeUtf8, (<$?>))
#if defined(dbPostgres)
import Database.PostgreSQL.Simple.FromField (FromField (..))
import Database.PostgreSQL.Simple.ToField (ToField (..))
#else
import Database.SQLite.Simple.FromField (FromField (..))
import Database.SQLite.Simple.ToField (ToField (..))
#endif
data ChatType = CTDirect | CTGroup | CTLocal | CTContactRequest | CTContactConnection data ChatType = CTDirect | CTGroup | CTLocal | CTContactRequest | CTContactConnection
deriving (Eq, Show, Ord) deriving (Eq, Show, Ord)

View file

@ -1,3 +1,4 @@
{-# LANGUAGE CPP #-}
{-# LANGUAGE DataKinds #-} {-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE DuplicateRecordFields #-}
@ -23,8 +24,6 @@ import Data.Text (Text)
import Data.Text.Encoding (decodeLatin1, encodeUtf8) import Data.Text.Encoding (decodeLatin1, encodeUtf8)
import Data.Type.Equality import Data.Type.Equality
import Data.Word (Word32) import Data.Word (Word32)
import Database.SQLite.Simple.FromField (FromField (..))
import Database.SQLite.Simple.ToField (ToField (..))
import Simplex.Chat.Messages.CIContent.Events import Simplex.Chat.Messages.CIContent.Events
import Simplex.Chat.Protocol import Simplex.Chat.Protocol
import Simplex.Chat.Types import Simplex.Chat.Types
@ -35,6 +34,13 @@ import Simplex.Messaging.Crypto.Ratchet (PQEncryption, pattern PQEncOff, pattern
import Simplex.Messaging.Encoding.String import Simplex.Messaging.Encoding.String
import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, enumJSON, fstToLower, singleFieldJSON, sumTypeJSON) import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, enumJSON, fstToLower, singleFieldJSON, sumTypeJSON)
import Simplex.Messaging.Util (encodeJSON, safeDecodeUtf8, tshow, (<$?>)) import Simplex.Messaging.Util (encodeJSON, safeDecodeUtf8, tshow, (<$?>))
#if defined(dbPostgres)
import Database.PostgreSQL.Simple.FromField (FromField (..))
import Database.PostgreSQL.Simple.ToField (ToField (..))
#else
import Database.SQLite.Simple.FromField (FromField (..))
import Database.SQLite.Simple.ToField (ToField (..))
#endif
data MsgDirection = MDRcv | MDSnd data MsgDirection = MDRcv | MDSnd
deriving (Eq, Show) deriving (Eq, Show)

View file

@ -42,6 +42,7 @@ import Simplex.Chat.Mobile.File
import Simplex.Chat.Mobile.Shared import Simplex.Chat.Mobile.Shared
import Simplex.Chat.Mobile.WebRTC import Simplex.Chat.Mobile.WebRTC
import Simplex.Chat.Options import Simplex.Chat.Options
import Simplex.Chat.Options.DB
import Simplex.Chat.Remote.Types import Simplex.Chat.Remote.Types
import Simplex.Chat.Store import Simplex.Chat.Store
import Simplex.Chat.Store.Profiles import Simplex.Chat.Store.Profiles
@ -189,8 +190,12 @@ mobileChatOpts dbFilePrefix =
ChatOpts ChatOpts
{ coreOptions = { coreOptions =
CoreChatOpts CoreChatOpts
{ dbFilePrefix, { dbOptions =
dbKey = "", -- for API database is already opened, and the key in options is not used ChatDbOpts
{ dbFilePrefix,
dbKey = "", -- for API database is already opened, and the key in options is not used
vacuumOnMigration = True
},
smpServers = [], smpServers = [],
xftpServers = [], xftpServers = [],
simpleNetCfg = defaultSimpleNetCfg, simpleNetCfg = defaultSimpleNetCfg,
@ -201,8 +206,7 @@ mobileChatOpts dbFilePrefix =
logFile = Nothing, logFile = Nothing,
tbqSize = 1024, tbqSize = 1024,
highlyAvailable = False, highlyAvailable = False,
yesToUpMigrations = False, yesToUpMigrations = False
vacuumOnMigration = True
}, },
deviceName = Nothing, deviceName = Nothing,
chatCmd = "", chatCmd = "",
@ -247,7 +251,7 @@ chatMigrateInitKey dbFilePrefix dbKey keepKey confirm backgroundMode = runExcept
newChatController db user_ defaultMobileConfig opts backgroundMode newChatController db user_ defaultMobileConfig opts backgroundMode
migrate createStore dbFile confirmMigrations = migrate createStore dbFile confirmMigrations =
ExceptT $ ExceptT $
(first (DBMErrorMigration dbFile) <$> createStore dbFile dbKey keepKey confirmMigrations (vacuumOnMigration $ coreOptions opts)) (first (DBMErrorMigration dbFile) <$> createStore dbFile dbKey keepKey confirmMigrations (vacuumOnMigration $ dbOptions $ coreOptions opts))
`catch` (pure . checkDBError) `catch` (pure . checkDBError)
`catchAll` (pure . dbError) `catchAll` (pure . dbError)
where where

View file

@ -1,3 +1,4 @@
{-# LANGUAGE CPP #-}
{-# LANGUAGE DataKinds #-} {-# LANGUAGE DataKinds #-}
{-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleContexts #-}
@ -43,8 +44,6 @@ import Data.Text (Text)
import qualified Data.Text as T import qualified Data.Text as T
import Data.Time (addUTCTime) import Data.Time (addUTCTime)
import Data.Time.Clock (UTCTime, nominalDay) import Data.Time.Clock (UTCTime, nominalDay)
import Database.SQLite.Simple.FromField (FromField (..))
import Database.SQLite.Simple.ToField (ToField (..))
import Language.Haskell.TH.Syntax (lift) import Language.Haskell.TH.Syntax (lift)
import Simplex.Chat.Operators.Conditions import Simplex.Chat.Operators.Conditions
import Simplex.Chat.Types (User) import Simplex.Chat.Types (User)
@ -55,6 +54,13 @@ import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, fromTextField_, sumTy
import Simplex.Messaging.Protocol (AProtocolType (..), ProtoServerWithAuth (..), ProtocolServer (..), ProtocolType (..), ProtocolTypeI, SProtocolType (..), UserProtocol) import Simplex.Messaging.Protocol (AProtocolType (..), ProtoServerWithAuth (..), ProtocolServer (..), ProtocolType (..), ProtocolTypeI, SProtocolType (..), UserProtocol)
import Simplex.Messaging.Transport.Client (TransportHost (..)) import Simplex.Messaging.Transport.Client (TransportHost (..))
import Simplex.Messaging.Util (atomicModifyIORef'_, safeDecodeUtf8) import Simplex.Messaging.Util (atomicModifyIORef'_, safeDecodeUtf8)
#if defined(dbPostgres)
import Database.PostgreSQL.Simple.FromField (FromField (..))
import Database.PostgreSQL.Simple.ToField (ToField (..))
#else
import Database.SQLite.Simple.FromField (FromField (..))
import Database.SQLite.Simple.ToField (ToField (..))
#endif
usageConditionsCommit :: Text usageConditionsCommit :: Text
usageConditionsCommit = "a5061f3147165a05979d6ace33960aced2d6ac03" usageConditionsCommit = "a5061f3147165a05979d6ace33960aced2d6ac03"
@ -119,7 +125,14 @@ instance TextEncoding OperatorTag where
-- this and other types only define instances of serialization for known DB IDs only, -- this and other types only define instances of serialization for known DB IDs only,
-- entities without IDs cannot be serialized to JSON -- entities without IDs cannot be serialized to JSON
instance FromField DBEntityId where fromField f = DBEntityId <$> fromField f instance FromField DBEntityId
#if defined(dbPostgres)
where
fromField f dat = DBEntityId <$> fromField f dat
#else
where
fromField f = DBEntityId <$> fromField f
#endif
instance ToField DBEntityId where toField (DBEntityId i) = toField i instance ToField DBEntityId where toField (DBEntityId i) = toField i
@ -445,7 +458,7 @@ validateUserServers curr others = currUserErrs <> concatMap otherUserErrs others
where where
currUserErrs = noServersErrs SPSMP Nothing curr <> noServersErrs SPXFTP Nothing curr <> serverErrs SPSMP curr <> serverErrs SPXFTP curr currUserErrs = noServersErrs SPSMP Nothing curr <> noServersErrs SPXFTP Nothing curr <> serverErrs SPSMP curr <> serverErrs SPXFTP curr
otherUserErrs (user, uss) = noServersErrs SPSMP (Just user) uss <> noServersErrs SPXFTP (Just user) uss otherUserErrs (user, uss) = noServersErrs SPSMP (Just user) uss <> noServersErrs SPXFTP (Just user) uss
noServersErrs :: (UserServersClass u, ProtocolTypeI p, UserProtocol p) => SProtocolType p -> Maybe User -> [u] -> [UserServersError] noServersErrs :: (UserServersClass u, ProtocolTypeI p, UserProtocol p) => SProtocolType p -> Maybe User -> [u] -> [UserServersError]
noServersErrs p user uss noServersErrs p user uss
| noServers opEnabled = [USENoServers p' user] | noServers opEnabled = [USENoServers p' user]
| otherwise = [USEStorageMissing p' user | noServers (hasRole storage)] <> [USEProxyMissing p' user | noServers (hasRole proxy)] | otherwise = [USEStorageMissing p' user | noServers (hasRole storage)] <> [USEProxyMissing p' user | noServers (hasRole proxy)]

View file

@ -14,12 +14,12 @@ module Simplex.Chat.Options
getChatOpts, getChatOpts,
protocolServersP, protocolServersP,
defaultHostMode, defaultHostMode,
printDbOpts,
) )
where where
import Control.Logger.Simple (LogLevel (..)) import Control.Logger.Simple (LogLevel (..))
import qualified Data.Attoparsec.ByteString.Char8 as A import qualified Data.Attoparsec.ByteString.Char8 as A
import Data.ByteArray (ScrubbedBytes)
import qualified Data.ByteString.Char8 as B import qualified Data.ByteString.Char8 as B
import Data.Maybe (fromMaybe) import Data.Maybe (fromMaybe)
import Data.Text (Text) import Data.Text (Text)
@ -34,7 +34,7 @@ import Simplex.Messaging.Encoding.String
import Simplex.Messaging.Parsers (parseAll) import Simplex.Messaging.Parsers (parseAll)
import Simplex.Messaging.Protocol (ProtoServerWithAuth, ProtocolTypeI, SMPServerWithAuth, XFTPServerWithAuth) import Simplex.Messaging.Protocol (ProtoServerWithAuth, ProtocolTypeI, SMPServerWithAuth, XFTPServerWithAuth)
import Simplex.Messaging.Transport.Client (SocksProxyWithAuth (..), SocksAuth (..), defaultSocksProxyWithAuth) import Simplex.Messaging.Transport.Client (SocksProxyWithAuth (..), SocksAuth (..), defaultSocksProxyWithAuth)
import System.FilePath (combine) import Simplex.Chat.Options.DB
data ChatOpts = ChatOpts data ChatOpts = ChatOpts
{ coreOptions :: CoreChatOpts, { coreOptions :: CoreChatOpts,
@ -54,8 +54,7 @@ data ChatOpts = ChatOpts
} }
data CoreChatOpts = CoreChatOpts data CoreChatOpts = CoreChatOpts
{ dbFilePrefix :: String, { dbOptions :: ChatDbOpts,
dbKey :: ScrubbedBytes,
smpServers :: [SMPServerWithAuth], smpServers :: [SMPServerWithAuth],
xftpServers :: [XFTPServerWithAuth], xftpServers :: [XFTPServerWithAuth],
simpleNetCfg :: SimpleNetCfg, simpleNetCfg :: SimpleNetCfg,
@ -66,8 +65,7 @@ data CoreChatOpts = CoreChatOpts
logFile :: Maybe FilePath, logFile :: Maybe FilePath,
tbqSize :: Natural, tbqSize :: Natural,
highlyAvailable :: Bool, highlyAvailable :: Bool,
yesToUpMigrations :: Bool, yesToUpMigrations :: Bool
vacuumOnMigration :: Bool
} }
data ChatCmdLog = CCLAll | CCLMessages | CCLNone data ChatCmdLog = CCLAll | CCLMessages | CCLNone
@ -82,24 +80,8 @@ agentLogLevel = \case
CLLImportant -> LogInfo CLLImportant -> LogInfo
coreChatOptsP :: FilePath -> FilePath -> Parser CoreChatOpts coreChatOptsP :: FilePath -> FilePath -> Parser CoreChatOpts
coreChatOptsP appDir defaultDbFileName = do coreChatOptsP appDir defaultDbName = do
dbFilePrefix <- dbOptions <- chatDbOptsP appDir defaultDbName
strOption
( long "database"
<> short 'd'
<> metavar "DB_FILE"
<> help "Path prefix to chat and agent database files"
<> value defaultDbFilePath
<> showDefault
)
dbKey <-
strOption
( long "key"
<> short 'k'
<> metavar "KEY"
<> help "Database encryption key/pass-phrase"
<> value ""
)
smpServers <- smpServers <-
option option
parseProtocolServers parseProtocolServers
@ -241,15 +223,9 @@ coreChatOptsP appDir defaultDbFileName = do
<> short 'y' <> short 'y'
<> help "Automatically confirm \"up\" database migrations" <> help "Automatically confirm \"up\" database migrations"
) )
disableVacuum <-
switch
( long "disable-vacuum"
<> help "Do not vacuum database after migrations"
)
pure pure
CoreChatOpts CoreChatOpts
{ dbFilePrefix, { dbOptions,
dbKey,
smpServers, smpServers,
xftpServers, xftpServers,
simpleNetCfg = simpleNetCfg =
@ -271,12 +247,10 @@ coreChatOptsP appDir defaultDbFileName = do
logFile, logFile,
tbqSize, tbqSize,
highlyAvailable, highlyAvailable,
yesToUpMigrations, yesToUpMigrations
vacuumOnMigration = not disableVacuum
} }
where where
useTcpTimeout p t = 1000000 * if t > 0 then t else maybe 7 (const 15) p useTcpTimeout p t = 1000000 * if t > 0 then t else maybe 7 (const 15) p
defaultDbFilePath = combine appDir defaultDbFileName
defaultHostMode :: Maybe SocksProxyWithAuth -> HostMode defaultHostMode :: Maybe SocksProxyWithAuth -> HostMode
defaultHostMode = \case defaultHostMode = \case
@ -284,8 +258,8 @@ defaultHostMode = \case
_ -> HMPublic _ -> HMPublic
chatOptsP :: FilePath -> FilePath -> Parser ChatOpts chatOptsP :: FilePath -> FilePath -> Parser ChatOpts
chatOptsP appDir defaultDbFileName = do chatOptsP appDir defaultDbName = do
coreOptions <- coreChatOptsP appDir defaultDbFileName coreOptions <- coreChatOptsP appDir defaultDbName
deviceName <- deviceName <-
optional $ optional $
strOption strOption
@ -432,12 +406,15 @@ parseChatCmdLog = eitherReader $ \case
_ -> Left "Invalid chat command log level" _ -> Left "Invalid chat command log level"
getChatOpts :: FilePath -> FilePath -> IO ChatOpts getChatOpts :: FilePath -> FilePath -> IO ChatOpts
getChatOpts appDir defaultDbFileName = getChatOpts appDir defaultDbName =
execParser $ execParser $
info info
(helper <*> versionOption <*> chatOptsP appDir defaultDbFileName) (helper <*> versionOption <*> chatOptsP appDir defaultDbName)
(header versionStr <> fullDesc <> progDesc "Start chat with DB_FILE file and use SERVER as SMP server") (header versionStr <> fullDesc <> progDesc "Start chat with DB_FILE file and use SERVER as SMP server")
where where
versionStr = versionString versionNumber versionStr = versionString versionNumber
versionOption = infoOption versionAndUpdate (long "version" <> short 'v' <> help "Show version") versionOption = infoOption versionAndUpdate (long "version" <> short 'v' <> help "Show version")
versionAndUpdate = versionStr <> "\n" <> updateStr versionAndUpdate = versionStr <> "\n" <> updateStr
printDbOpts :: CoreChatOpts -> IO ()
printDbOpts opts = putStrLn $ "db: " <> dbString (dbOptions opts)

View file

@ -0,0 +1,14 @@
{-# LANGUAGE CPP #-}
module Simplex.Chat.Options.DB
#if defined(dbPostgres)
( module Simplex.Chat.Options.Postgres,
)
where
import Simplex.Chat.Options.Postgres
#else
( module Simplex.Chat.Options.SQLite,
)
where
import Simplex.Chat.Options.SQLite
#endif

View file

@ -0,0 +1,37 @@
{-# LANGUAGE ApplicativeDo #-}
{-# LANGUAGE NamedFieldPuns #-}
module Simplex.Chat.Options.Postgres where
import Options.Applicative
data ChatDbOpts = ChatDbOpts
{ dbName :: String,
dbUser :: String,
dbSchemaPrefix :: String
}
chatDbOptsP :: FilePath -> String -> Parser ChatDbOpts
chatDbOptsP _appDir defaultDbName = do
dbName <-
strOption
( long "database"
<> short 'd'
<> metavar "DB_NAME"
<> help "Database name"
<> value defaultDbName
<> showDefault
)
dbUser <-
strOption
( long "database-user"
<> short 'u'
<> metavar "DB_USER"
<> help "Database user"
<> value "simplex"
<> showDefault
)
pure ChatDbOpts {dbName, dbUser, dbSchemaPrefix = ""}
dbString :: ChatDbOpts -> String
dbString ChatDbOpts {dbName} = dbName

View file

@ -0,0 +1,44 @@
{-# LANGUAGE ApplicativeDo #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}
module Simplex.Chat.Options.SQLite where
import Data.ByteArray (ScrubbedBytes)
import Options.Applicative
import System.FilePath (combine)
data ChatDbOpts = ChatDbOpts
{ dbFilePrefix :: String,
dbKey :: ScrubbedBytes,
vacuumOnMigration :: Bool
}
chatDbOptsP :: FilePath -> FilePath -> Parser ChatDbOpts
chatDbOptsP appDir defaultDbName = do
dbFilePrefix <-
strOption
( long "database"
<> short 'd'
<> metavar "DB_FILE"
<> help "Path prefix to chat and agent database files"
<> value (combine appDir defaultDbName)
<> showDefault
)
dbKey <-
strOption
( long "key"
<> short 'k'
<> metavar "KEY"
<> help "Database encryption key/pass-phrase"
<> value ""
)
disableVacuum <-
switch
( long "disable-vacuum"
<> help "Do not vacuum database after migrations"
)
pure ChatDbOpts {dbFilePrefix, dbKey, vacuumOnMigration = not disableVacuum}
dbString :: ChatDbOpts -> String
dbString ChatDbOpts {dbFilePrefix} = dbFilePrefix <> "_chat.db, " <> dbFilePrefix <> "_agent.db"

View file

@ -1,8 +1,11 @@
{-# LANGUAGE CPP #-}
{-# LANGUAGE DataKinds #-} {-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-} {-# LANGUAGE GADTs #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE KindSignatures #-} {-# LANGUAGE KindSignatures #-}
{-# LANGUAGE LambdaCase #-} {-# LANGUAGE LambdaCase #-}
{-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE NamedFieldPuns #-}
@ -42,21 +45,26 @@ import Data.Time.Clock (UTCTime)
import Data.Type.Equality import Data.Type.Equality
import Data.Typeable (Typeable) import Data.Typeable (Typeable)
import Data.Word (Word32) import Data.Word (Word32)
import Database.SQLite.Simple.FromField (FromField (..))
import Database.SQLite.Simple.ToField (ToField (..))
import Simplex.Chat.Call import Simplex.Chat.Call
import Simplex.Chat.Types import Simplex.Chat.Types
import Simplex.Chat.Types.Preferences import Simplex.Chat.Types.Preferences
import Simplex.Chat.Types.Shared import Simplex.Chat.Types.Shared
import Simplex.Chat.Types.Util
import Simplex.Messaging.Agent.Protocol (VersionSMPA, pqdrSMPAgentVersion) import Simplex.Messaging.Agent.Protocol (VersionSMPA, pqdrSMPAgentVersion)
import qualified Simplex.Messaging.Agent.Store.DB as DB
import Simplex.Messaging.Compression (Compressed, compress1, decompress1) import Simplex.Messaging.Compression (Compressed, compress1, decompress1)
import Simplex.Messaging.Encoding import Simplex.Messaging.Encoding
import Simplex.Messaging.Encoding.String import Simplex.Messaging.Encoding.String
import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, fromTextField_, fstToLower, parseAll, sumTypeJSON, taggedObjectJSON) import Simplex.Messaging.Parsers (blobFieldDecoder, defaultJSON, dropPrefix, fromTextField_, fstToLower, parseAll, sumTypeJSON, taggedObjectJSON)
import Simplex.Messaging.Protocol (MsgBody) import Simplex.Messaging.Protocol (MsgBody)
import Simplex.Messaging.Util (decodeJSON, eitherToMaybe, encodeJSON, safeDecodeUtf8, (<$?>)) import Simplex.Messaging.Util (decodeJSON, eitherToMaybe, encodeJSON, safeDecodeUtf8, (<$?>))
import Simplex.Messaging.Version hiding (version) import Simplex.Messaging.Version hiding (version)
#if defined(dbPostgres)
import Database.PostgreSQL.Simple.FromField (FromField (..))
import Database.PostgreSQL.Simple.ToField (ToField (..))
#else
import Database.SQLite.Simple.FromField (FromField (..))
import Database.SQLite.Simple.ToField (ToField (..))
#endif
-- Chat version history: -- Chat version history:
-- 1 - support chat versions in connections (9/1/2023) -- 1 - support chat versions in connections (9/1/2023)
@ -217,10 +225,9 @@ instance StrEncoding AppMessageBinary where
newtype SharedMsgId = SharedMsgId ByteString newtype SharedMsgId = SharedMsgId ByteString
deriving (Eq, Show) deriving (Eq, Show)
deriving newtype (FromField)
instance FromField SharedMsgId where fromField f = SharedMsgId <$> fromField f instance ToField SharedMsgId where toField (SharedMsgId m) = toField $ DB.Binary m
instance ToField SharedMsgId where toField (SharedMsgId m) = toField m
instance StrEncoding SharedMsgId where instance StrEncoding SharedMsgId where
strEncode (SharedMsgId m) = strEncode m strEncode (SharedMsgId m) = strEncode m
@ -253,7 +260,7 @@ data LinkContent = LCPage | LCImage | LCVideo {duration :: Maybe Int} | LCUnknow
deriving (Eq, Show) deriving (Eq, Show)
data ReportReason = RRSpam | RRContent | RRCommunity | RRProfile | RROther | RRUnknown Text data ReportReason = RRSpam | RRContent | RRCommunity | RRProfile | RROther | RRUnknown Text
deriving (Eq, Show) deriving (Eq, Show)
$(pure []) $(pure [])
@ -515,7 +522,7 @@ instance ToJSON MsgContentTag where
toJSON = strToJSON toJSON = strToJSON
toEncoding = strToJEncoding toEncoding = strToJEncoding
instance FromField MsgContentTag where fromField = fromBlobField_ strDecode instance FromField MsgContentTag where fromField = blobFieldDecoder strDecode
instance ToField MsgContentTag where toField = toField . strEncode instance ToField MsgContentTag where toField = toField . strEncode
@ -570,9 +577,10 @@ durationText duration =
| otherwise = show n | otherwise = show n
msgContentHasText :: MsgContent -> Bool msgContentHasText :: MsgContent -> Bool
msgContentHasText = not . T.null . \case msgContentHasText =
MCVoice {text} -> text not . T.null . \case
mc -> msgContentText mc MCVoice {text} -> text
mc -> msgContentText mc
isVoice :: MsgContent -> Bool isVoice :: MsgContent -> Bool
isVoice = \case isVoice = \case

View file

@ -37,7 +37,6 @@ import Data.Word (Word16, Word32)
import qualified Network.HTTP.Types as N import qualified Network.HTTP.Types as N
import Network.HTTP2.Server (responseStreaming) import Network.HTTP2.Server (responseStreaming)
import qualified Paths_simplex_chat as SC import qualified Paths_simplex_chat as SC
import Simplex.Chat.Archive (archiveFilesFolder)
import Simplex.Chat.Controller import Simplex.Chat.Controller
import Simplex.Chat.Files import Simplex.Chat.Files
import Simplex.Chat.Messages (chatNameStr) import Simplex.Chat.Messages (chatNameStr)
@ -71,6 +70,9 @@ import UnliftIO
import UnliftIO.Concurrent (forkIO) import UnliftIO.Concurrent (forkIO)
import UnliftIO.Directory (copyFile, createDirectoryIfMissing, doesDirectoryExist, removeDirectoryRecursive, renameFile) import UnliftIO.Directory (copyFile, createDirectoryIfMissing, doesDirectoryExist, removeDirectoryRecursive, renameFile)
remoteFilesFolder :: String
remoteFilesFolder = "simplex_v1_files"
-- when acting as host -- when acting as host
minRemoteCtrlVersion :: AppVersion minRemoteCtrlVersion :: AppVersion
minRemoteCtrlVersion = AppVersion [6, 3, 0, 0] minRemoteCtrlVersion = AppVersion [6, 3, 0, 0]
@ -342,7 +344,7 @@ storeRemoteFile rhId encrypted_ localPath = do
filePath' <- liftRH rhId $ remoteStoreFile c filePath (takeFileName localPath) filePath' <- liftRH rhId $ remoteStoreFile c filePath (takeFileName localPath)
hf_ <- chatReadVar remoteHostsFolder hf_ <- chatReadVar remoteHostsFolder
forM_ hf_ $ \hf -> do forM_ hf_ $ \hf -> do
let rhf = hf </> storePath </> archiveFilesFolder let rhf = hf </> storePath </> remoteFilesFolder
hPath = rhf </> takeFileName filePath' hPath = rhf </> takeFileName filePath'
createDirectoryIfMissing True rhf createDirectoryIfMissing True rhf
(if encrypt then renameFile else copyFile) filePath hPath (if encrypt then renameFile else copyFile) filePath hPath
@ -360,7 +362,7 @@ storeRemoteFile rhId encrypted_ localPath = do
getRemoteFile :: RemoteHostId -> RemoteFile -> CM () getRemoteFile :: RemoteHostId -> RemoteFile -> CM ()
getRemoteFile rhId rf = do getRemoteFile rhId rf = do
c@RemoteHostClient {storePath} <- getRemoteHostClient rhId c@RemoteHostClient {storePath} <- getRemoteHostClient rhId
dir <- lift $ (</> storePath </> archiveFilesFolder) <$> (maybe getDefaultFilesFolder pure =<< chatReadVar' remoteHostsFolder) dir <- lift $ (</> storePath </> remoteFilesFolder) <$> (maybe getDefaultFilesFolder pure =<< chatReadVar' remoteHostsFolder)
createDirectoryIfMissing True dir createDirectoryIfMissing True dir
liftRH rhId $ remoteGetFile c dir rf liftRH rhId $ remoteGetFile c dir rf

View file

@ -1,3 +1,5 @@
{-# LANGUAGE CPP #-}
module Simplex.Chat.Store module Simplex.Chat.Store
( DBStore, ( DBStore,
StoreError (..), StoreError (..),
@ -7,20 +9,43 @@ module Simplex.Chat.Store
AutoAccept (..), AutoAccept (..),
createChatStore, createChatStore,
migrations, -- used in tests migrations, -- used in tests
#if defined(dbPostgres)
chatSchema,
agentSchema,
#else
chatStoreFile, chatStoreFile,
agentStoreFile, agentStoreFile,
#endif
withTransaction, withTransaction,
) )
where where
import Data.ByteArray (ScrubbedBytes)
import Simplex.Chat.Store.Migrations
import Simplex.Chat.Store.Profiles import Simplex.Chat.Store.Profiles
import Simplex.Chat.Store.Shared import Simplex.Chat.Store.Shared
import Simplex.Messaging.Agent.Store.SQLite (createDBStore) import Simplex.Messaging.Agent.Store.Common (DBStore (..), withTransaction)
import Simplex.Messaging.Agent.Store.SQLite.Common (DBStore (..), withTransaction)
import Simplex.Messaging.Agent.Store.Shared (MigrationConfirmation, MigrationError) import Simplex.Messaging.Agent.Store.Shared (MigrationConfirmation, MigrationError)
#if defined(dbPostgres)
import Database.PostgreSQL.Simple (ConnectInfo (..))
import Simplex.Chat.Store.Postgres.Migrations
import Simplex.Messaging.Agent.Store.Postgres (createDBStore)
#else
import Data.ByteArray (ScrubbedBytes)
import Simplex.Chat.Store.SQLite.Migrations
import Simplex.Messaging.Agent.Store.SQLite (createDBStore)
#endif
#if defined(dbPostgres)
createChatStore :: ConnectInfo -> String -> MigrationConfirmation -> IO (Either MigrationError DBStore)
createChatStore connectInfo schema = createDBStore connectInfo schema migrations
chatSchema :: String -> String
chatSchema "" = "chat_schema"
chatSchema prefix = prefix <> "_chat_schema"
agentSchema :: String -> String
agentSchema "" = "agent_schema"
agentSchema prefix = prefix <> "_agent_schema"
#else
createChatStore :: FilePath -> ScrubbedBytes -> Bool -> MigrationConfirmation -> Bool -> IO (Either MigrationError DBStore) createChatStore :: FilePath -> ScrubbedBytes -> Bool -> MigrationConfirmation -> Bool -> IO (Either MigrationError DBStore)
createChatStore dbPath key keepKey = createDBStore dbPath key keepKey migrations createChatStore dbPath key keepKey = createDBStore dbPath key keepKey migrations
@ -29,3 +54,4 @@ chatStoreFile = (<> "_chat.db")
agentStoreFile :: FilePath -> FilePath agentStoreFile :: FilePath -> FilePath
agentStoreFile = (<> "_agent.db") agentStoreFile = (<> "_agent.db")
#endif

View file

@ -1,3 +1,4 @@
{-# LANGUAGE CPP #-}
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE OverloadedStrings #-}
module Simplex.Chat.Store.AppSettings where module Simplex.Chat.Store.AppSettings where
@ -6,10 +7,14 @@ import Control.Monad (join)
import Control.Monad.IO.Class (liftIO) import Control.Monad.IO.Class (liftIO)
import qualified Data.Aeson as J import qualified Data.Aeson as J
import Data.Maybe (fromMaybe) import Data.Maybe (fromMaybe)
import Database.SQLite.Simple (Only (..))
import Simplex.Chat.AppSettings (AppSettings (..), combineAppSettings, defaultAppSettings, defaultParseAppSettings) import Simplex.Chat.AppSettings (AppSettings (..), combineAppSettings, defaultAppSettings, defaultParseAppSettings)
import Simplex.Messaging.Agent.Store.AgentStore (maybeFirstRow) import Simplex.Messaging.Agent.Store.AgentStore (maybeFirstRow)
import qualified Simplex.Messaging.Agent.Store.SQLite.DB as DB import qualified Simplex.Messaging.Agent.Store.DB as DB
#if defined(dbPostgres)
import Database.PostgreSQL.Simple (Only (..))
#else
import Database.SQLite.Simple (Only (..))
#endif
saveAppSettings :: DB.Connection -> AppSettings -> IO () saveAppSettings :: DB.Connection -> AppSettings -> IO ()
saveAppSettings db appSettings = do saveAppSettings db appSettings = do

View file

@ -1,3 +1,4 @@
{-# LANGUAGE CPP #-}
{-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE LambdaCase #-} {-# LANGUAGE LambdaCase #-}
{-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE NamedFieldPuns #-}
@ -25,8 +26,6 @@ import Control.Monad.IO.Class
import Data.Bitraversable (bitraverse) import Data.Bitraversable (bitraverse)
import Data.Int (Int64) import Data.Int (Int64)
import Data.Maybe (catMaybes, fromMaybe) import Data.Maybe (catMaybes, fromMaybe)
import Database.SQLite.Simple (Only (..), (:.) (..))
import Database.SQLite.Simple.QQ (sql)
import Simplex.Chat.Protocol import Simplex.Chat.Protocol
import Simplex.Chat.Store.Direct import Simplex.Chat.Store.Direct
import Simplex.Chat.Store.Files import Simplex.Chat.Store.Files
@ -36,8 +35,16 @@ import Simplex.Chat.Store.Shared
import Simplex.Chat.Types import Simplex.Chat.Types
import Simplex.Messaging.Agent.Protocol (ConnId) import Simplex.Messaging.Agent.Protocol (ConnId)
import Simplex.Messaging.Agent.Store.AgentStore (firstRow, firstRow', maybeFirstRow) import Simplex.Messaging.Agent.Store.AgentStore (firstRow, firstRow', maybeFirstRow)
import qualified Simplex.Messaging.Agent.Store.SQLite.DB as DB import Simplex.Messaging.Agent.Store.DB (BoolInt (..))
import qualified Simplex.Messaging.Agent.Store.DB as DB
import Simplex.Messaging.Util (eitherToMaybe) import Simplex.Messaging.Util (eitherToMaybe)
#if defined(dbPostgres)
import Database.PostgreSQL.Simple (Only (..), (:.) (..))
import Database.PostgreSQL.Simple.SqlQQ (sql)
#else
import Database.SQLite.Simple (Only (..), (:.) (..))
import Database.SQLite.Simple.QQ (sql)
#endif
getChatLockEntity :: DB.Connection -> AgentConnId -> ExceptT StoreError IO ChatLockEntity getChatLockEntity :: DB.Connection -> AgentConnId -> ExceptT StoreError IO ChatLockEntity
getChatLockEntity db agentConnId = do getChatLockEntity db agentConnId = do
@ -110,40 +117,42 @@ getConnectionEntity db vr user@User {userId, userContactId} agentConnId = do
|] |]
(userId, contactId) (userId, contactId)
toContact' :: Int64 -> Connection -> [ChatTagId] -> ContactRow' -> Contact toContact' :: Int64 -> Connection -> [ChatTagId] -> ContactRow' -> Contact
toContact' contactId conn chatTags ((profileId, localDisplayName, viaGroup, displayName, fullName, image, contactLink, localAlias, contactUsed, contactStatus) :. (enableNtfs_, sendRcpts, favorite, preferences, userPreferences, createdAt, updatedAt, chatTs) :. (contactGroupMemberId, contactGrpInvSent, uiThemes, chatDeleted, customData)) = toContact' contactId conn chatTags ((profileId, localDisplayName, viaGroup, displayName, fullName, image, contactLink, localAlias, BI contactUsed, contactStatus) :. (enableNtfs_, sendRcpts, BI favorite, preferences, userPreferences, createdAt, updatedAt, chatTs) :. (contactGroupMemberId, BI contactGrpInvSent, uiThemes, BI chatDeleted, customData)) =
let profile = LocalProfile {profileId, displayName, fullName, image, contactLink, preferences, localAlias} let profile = LocalProfile {profileId, displayName, fullName, image, contactLink, preferences, localAlias}
chatSettings = ChatSettings {enableNtfs = fromMaybe MFAll enableNtfs_, sendRcpts, favorite} chatSettings = ChatSettings {enableNtfs = fromMaybe MFAll enableNtfs_, sendRcpts = unBI <$> sendRcpts, favorite}
mergedPreferences = contactUserPreferences user userPreferences preferences $ connIncognito conn mergedPreferences = contactUserPreferences user userPreferences preferences $ connIncognito conn
activeConn = Just conn activeConn = Just conn
in Contact {contactId, localDisplayName, profile, activeConn, viaGroup, contactUsed, contactStatus, chatSettings, userPreferences, mergedPreferences, createdAt, updatedAt, chatTs, contactGroupMemberId, contactGrpInvSent, chatTags, uiThemes, chatDeleted, customData} in Contact {contactId, localDisplayName, profile, activeConn, viaGroup, contactUsed, contactStatus, chatSettings, userPreferences, mergedPreferences, createdAt, updatedAt, chatTs, contactGroupMemberId, contactGrpInvSent, chatTags, uiThemes, chatDeleted, customData}
getGroupAndMember_ :: Int64 -> Connection -> ExceptT StoreError IO (GroupInfo, GroupMember) getGroupAndMember_ :: Int64 -> Connection -> ExceptT StoreError IO (GroupInfo, GroupMember)
getGroupAndMember_ groupMemberId c = do getGroupAndMember_ groupMemberId c = do
gm <- ExceptT $ firstRow (toGroupAndMember c) (SEInternalError "referenced group member not found") $ gm <-
DB.query ExceptT $
db firstRow (toGroupAndMember c) (SEInternalError "referenced group member not found") $
[sql| DB.query
SELECT db
-- GroupInfo [sql|
g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.description, gp.image, SELECT
g.host_conn_custom_user_profile_id, g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, -- GroupInfo
g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_chat, g.business_member_id, g.customer_member_id, g.ui_themes, g.custom_data, g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.description, gp.image,
-- GroupInfo {membership} g.host_conn_custom_user_profile_id, g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences,
mu.group_member_id, mu.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category, g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_chat, g.business_member_id, g.customer_member_id, g.ui_themes, g.custom_data,
mu.member_status, mu.show_messages, mu.member_restriction, mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id, -- GroupInfo {membership}
-- GroupInfo {membership = GroupMember {memberProfile}} mu.group_member_id, mu.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category,
pu.display_name, pu.full_name, pu.image, pu.contact_link, pu.local_alias, pu.preferences, mu.member_status, mu.show_messages, mu.member_restriction, mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id,
-- from GroupMember -- GroupInfo {membership = GroupMember {memberProfile}}
m.group_member_id, m.group_id, m.member_id, m.peer_chat_min_version, m.peer_chat_max_version, m.member_role, m.member_category, m.member_status, m.show_messages, m.member_restriction, pu.display_name, pu.full_name, pu.image, pu.contact_link, pu.local_alias, pu.preferences,
m.invited_by, m.invited_by_group_member_id, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, p.display_name, p.full_name, p.image, p.contact_link, p.local_alias, p.preferences -- from GroupMember
FROM group_members m m.group_member_id, m.group_id, m.member_id, m.peer_chat_min_version, m.peer_chat_max_version, m.member_role, m.member_category, m.member_status, m.show_messages, m.member_restriction,
JOIN contact_profiles p ON p.contact_profile_id = COALESCE(m.member_profile_id, m.contact_profile_id) m.invited_by, m.invited_by_group_member_id, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, p.display_name, p.full_name, p.image, p.contact_link, p.local_alias, p.preferences
JOIN groups g ON g.group_id = m.group_id FROM group_members m
JOIN group_profiles gp USING (group_profile_id) JOIN contact_profiles p ON p.contact_profile_id = COALESCE(m.member_profile_id, m.contact_profile_id)
JOIN group_members mu ON g.group_id = mu.group_id JOIN groups g ON g.group_id = m.group_id
JOIN contact_profiles pu ON pu.contact_profile_id = COALESCE(mu.member_profile_id, mu.contact_profile_id) JOIN group_profiles gp USING (group_profile_id)
WHERE m.group_member_id = ? AND g.user_id = ? AND mu.contact_id = ? JOIN group_members mu ON g.group_id = mu.group_id
|] JOIN contact_profiles pu ON pu.contact_profile_id = COALESCE(mu.member_profile_id, mu.contact_profile_id)
(groupMemberId, userId, userContactId) WHERE m.group_member_id = ? AND g.user_id = ? AND mu.contact_id = ?
|]
(groupMemberId, userId, userContactId)
liftIO $ bitraverse (addGroupChatTags db) pure gm liftIO $ bitraverse (addGroupChatTags db) pure gm
toGroupAndMember :: Connection -> GroupInfoRow :. GroupMemberRow -> (GroupInfo, GroupMember) toGroupAndMember :: Connection -> GroupInfoRow :. GroupMemberRow -> (GroupInfo, GroupMember)
toGroupAndMember c (groupInfoRow :. memberRow) = toGroupAndMember c (groupInfoRow :. memberRow) =
@ -212,7 +221,7 @@ getContactConnEntityByConnReqHash db vr user@User {userId} (cReqHash1, cReqHash2
WHERE user_id = ? AND via_contact_uri_hash IN (?,?) AND conn_status != ? WHERE user_id = ? AND via_contact_uri_hash IN (?,?) AND conn_status != ?
ORDER BY conn_ord DESC, created_at DESC ORDER BY conn_ord DESC, created_at DESC
LIMIT 1 LIMIT 1
) ) c
|] |]
(userId, cReqHash1, cReqHash2, ConnDeleted) (userId, cReqHash1, cReqHash2, ConnDeleted)
maybe (pure Nothing) (fmap eitherToMaybe . runExceptT . getConnectionEntity db vr user) connId_ maybe (pure Nothing) (fmap eitherToMaybe . runExceptT . getConnectionEntity db vr user) connId_

View file

@ -1,3 +1,4 @@
{-# LANGUAGE CPP #-}
{-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE LambdaCase #-} {-# LANGUAGE LambdaCase #-}
@ -93,8 +94,6 @@ import Data.Int (Int64)
import Data.Maybe (fromMaybe, isJust, isNothing) import Data.Maybe (fromMaybe, isJust, isNothing)
import Data.Text (Text) import Data.Text (Text)
import Data.Time.Clock (UTCTime (..), getCurrentTime) import Data.Time.Clock (UTCTime (..), getCurrentTime)
import Database.SQLite.Simple (NamedParam (..), Only (..), (:.) (..))
import Database.SQLite.Simple.QQ (sql)
import Simplex.Chat.Messages import Simplex.Chat.Messages
import Simplex.Chat.Store.Shared import Simplex.Chat.Store.Shared
import Simplex.Chat.Types import Simplex.Chat.Types
@ -102,11 +101,19 @@ import Simplex.Chat.Types.Preferences
import Simplex.Chat.Types.UITheme import Simplex.Chat.Types.UITheme
import Simplex.Messaging.Agent.Protocol (ConnId, InvitationId, UserId) import Simplex.Messaging.Agent.Protocol (ConnId, InvitationId, UserId)
import Simplex.Messaging.Agent.Store.AgentStore (firstRow, maybeFirstRow) import Simplex.Messaging.Agent.Store.AgentStore (firstRow, maybeFirstRow)
import qualified Simplex.Messaging.Agent.Store.SQLite.DB as DB import Simplex.Messaging.Agent.Store.DB (Binary (..), BoolInt (..))
import qualified Simplex.Messaging.Agent.Store.DB as DB
import Simplex.Messaging.Crypto.Ratchet (PQSupport) import Simplex.Messaging.Crypto.Ratchet (PQSupport)
import Simplex.Messaging.Protocol (SubscriptionMode (..)) import Simplex.Messaging.Protocol (SubscriptionMode (..))
import Simplex.Messaging.Util ((<$$>)) import Simplex.Messaging.Util ((<$$>))
import Simplex.Messaging.Version import Simplex.Messaging.Version
#if defined(dbPostgres)
import Database.PostgreSQL.Simple (Only (..), (:.) (..))
import Database.PostgreSQL.Simple.SqlQQ (sql)
#else
import Database.SQLite.Simple (Only (..), (:.) (..))
import Database.SQLite.Simple.QQ (sql)
#endif
getPendingContactConnection :: DB.Connection -> UserId -> Int64 -> ExceptT StoreError IO PendingContactConnection getPendingContactConnection :: DB.Connection -> UserId -> Int64 -> ExceptT StoreError IO PendingContactConnection
getPendingContactConnection db userId connId = do getPendingContactConnection db userId connId = do
@ -160,9 +167,9 @@ createConnReqConnection db userId acId cReqHash xContactId incognitoProfile grou
created_at, updated_at, to_subscribe, conn_chat_version, pq_support, pq_encryption created_at, updated_at, to_subscribe, conn_chat_version, pq_support, pq_encryption
) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) ) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
|] |]
( (userId, acId, pccConnStatus, ConnContact, True, cReqHash, xContactId) ( (userId, acId, pccConnStatus, ConnContact, BI True, cReqHash, xContactId)
:. (customUserProfileId, isJust groupLinkId, groupLinkId) :. (customUserProfileId, BI (isJust groupLinkId), groupLinkId)
:. (createdAt, createdAt, subMode == SMOnlyCreate, chatV, pqSup, pqSup) :. (createdAt, createdAt, BI (subMode == SMOnlyCreate), chatV, pqSup, pqSup)
) )
pccConnId <- insertedRowId db pccConnId <- insertedRowId db
pure PendingContactConnection {pccConnId, pccAgentConnId = AgentConnId acId, pccConnStatus, viaContactUri = True, viaUserContactLink = Nothing, groupLinkId, customUserProfileId, connReqInv = Nothing, localAlias = "", createdAt, updatedAt = createdAt} pure PendingContactConnection {pccConnId, pccAgentConnId = AgentConnId acId, pccConnStatus, viaContactUri = True, viaUserContactLink = Nothing, groupLinkId, customUserProfileId, connReqInv = Nothing, localAlias = "", createdAt, updatedAt = createdAt}
@ -183,26 +190,27 @@ getConnReqContactXContactId db vr user@User {userId} cReqHash = do
getContactByConnReqHash :: DB.Connection -> VersionRangeChat -> User -> ConnReqUriHash -> IO (Maybe Contact) getContactByConnReqHash :: DB.Connection -> VersionRangeChat -> User -> ConnReqUriHash -> IO (Maybe Contact)
getContactByConnReqHash db vr user@User {userId} cReqHash = do getContactByConnReqHash db vr user@User {userId} cReqHash = do
ct_ <- maybeFirstRow (toContact vr user []) $ ct_ <-
DB.query maybeFirstRow (toContact vr user []) $
db DB.query
[sql| db
SELECT [sql|
-- Contact SELECT
ct.contact_id, ct.contact_profile_id, ct.local_display_name, ct.via_group, cp.display_name, cp.full_name, cp.image, cp.contact_link, cp.local_alias, ct.contact_used, ct.contact_status, ct.enable_ntfs, ct.send_rcpts, ct.favorite, -- Contact
cp.preferences, ct.user_preferences, ct.created_at, ct.updated_at, ct.chat_ts, ct.contact_group_member_id, ct.contact_grp_inv_sent, ct.ui_themes, ct.chat_deleted, ct.custom_data, ct.contact_id, ct.contact_profile_id, ct.local_display_name, ct.via_group, cp.display_name, cp.full_name, cp.image, cp.contact_link, cp.local_alias, ct.contact_used, ct.contact_status, ct.enable_ntfs, ct.send_rcpts, ct.favorite,
-- Connection cp.preferences, ct.user_preferences, ct.created_at, ct.updated_at, ct.chat_ts, ct.contact_group_member_id, ct.contact_grp_inv_sent, ct.ui_themes, ct.chat_deleted, ct.custom_data,
c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id, c.conn_status, c.conn_type, c.contact_conn_initiated, c.local_alias, -- Connection
c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at, c.security_code, c.security_code_verified_at, c.pq_support, c.pq_encryption, c.pq_snd_enabled, c.pq_rcv_enabled, c.auth_err_counter, c.quota_err_counter, c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id, c.conn_status, c.conn_type, c.contact_conn_initiated, c.local_alias,
c.conn_chat_version, c.peer_chat_min_version, c.peer_chat_max_version c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at, c.security_code, c.security_code_verified_at, c.pq_support, c.pq_encryption, c.pq_snd_enabled, c.pq_rcv_enabled, c.auth_err_counter, c.quota_err_counter,
FROM contacts ct c.conn_chat_version, c.peer_chat_min_version, c.peer_chat_max_version
JOIN contact_profiles cp ON ct.contact_profile_id = cp.contact_profile_id FROM contacts ct
JOIN connections c ON c.contact_id = ct.contact_id JOIN contact_profiles cp ON ct.contact_profile_id = cp.contact_profile_id
WHERE c.user_id = ? AND c.via_contact_uri_hash = ? AND ct.contact_status = ? AND ct.deleted = 0 JOIN connections c ON c.contact_id = ct.contact_id
ORDER BY c.created_at DESC WHERE c.user_id = ? AND c.via_contact_uri_hash = ? AND ct.contact_status = ? AND ct.deleted = 0
LIMIT 1 ORDER BY c.created_at DESC
|] LIMIT 1
(userId, cReqHash, CSActive) |]
(userId, cReqHash, CSActive)
mapM (addDirectChatTags db) ct_ mapM (addDirectChatTags db) ct_
createDirectConnection :: DB.Connection -> User -> ConnId -> ConnReqInvitation -> ConnStatus -> Maybe Profile -> SubscriptionMode -> VersionChat -> PQSupport -> IO PendingContactConnection createDirectConnection :: DB.Connection -> User -> ConnId -> ConnReqInvitation -> ConnStatus -> Maybe Profile -> SubscriptionMode -> VersionChat -> PQSupport -> IO PendingContactConnection
@ -218,8 +226,8 @@ createDirectConnection db User {userId} acId cReq pccConnStatus incognitoProfile
created_at, updated_at, to_subscribe, conn_chat_version, pq_support, pq_encryption) created_at, updated_at, to_subscribe, conn_chat_version, pq_support, pq_encryption)
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)
|] |]
( (userId, acId, cReq, pccConnStatus, ConnContact, contactConnInitiated, customUserProfileId) ( (userId, acId, cReq, pccConnStatus, ConnContact, BI contactConnInitiated, customUserProfileId)
:. (createdAt, createdAt, subMode == SMOnlyCreate, chatV, pqSup, pqSup) :. (createdAt, createdAt, BI (subMode == SMOnlyCreate), chatV, pqSup, pqSup)
) )
pccConnId <- insertedRowId db pccConnId <- insertedRowId db
pure PendingContactConnection {pccConnId, pccAgentConnId = AgentConnId acId, pccConnStatus, viaContactUri = False, viaUserContactLink = Nothing, groupLinkId = Nothing, customUserProfileId, connReqInv = Just cReq, localAlias = "", createdAt, updatedAt = createdAt} pure PendingContactConnection {pccConnId, pccAgentConnId = AgentConnId acId, pccConnStatus, viaContactUri = False, viaUserContactLink = Nothing, groupLinkId = Nothing, customUserProfileId, connReqInv = Just cReq, localAlias = "", createdAt, updatedAt = createdAt}
@ -342,31 +350,33 @@ deleteContactProfile_ db userId contactId =
deleteUnusedProfile_ :: DB.Connection -> UserId -> ProfileId -> IO () deleteUnusedProfile_ :: DB.Connection -> UserId -> ProfileId -> IO ()
deleteUnusedProfile_ db userId profileId = deleteUnusedProfile_ db userId profileId =
DB.executeNamed DB.execute
db db
[sql| [sql|
DELETE FROM contact_profiles DELETE FROM contact_profiles
WHERE user_id = :user_id AND contact_profile_id = :profile_id WHERE user_id = ? AND contact_profile_id = ?
AND 1 NOT IN ( AND 1 NOT IN (
SELECT 1 FROM connections SELECT 1 FROM connections
WHERE user_id = :user_id AND custom_user_profile_id = :profile_id LIMIT 1 WHERE user_id = ? AND custom_user_profile_id = ? LIMIT 1
) )
AND 1 NOT IN ( AND 1 NOT IN (
SELECT 1 FROM contacts SELECT 1 FROM contacts
WHERE user_id = :user_id AND contact_profile_id = :profile_id LIMIT 1 WHERE user_id = ? AND contact_profile_id = ? LIMIT 1
) )
AND 1 NOT IN ( AND 1 NOT IN (
SELECT 1 FROM contact_requests SELECT 1 FROM contact_requests
WHERE user_id = :user_id AND contact_profile_id = :profile_id LIMIT 1 WHERE user_id = ? AND contact_profile_id = ? LIMIT 1
) )
AND 1 NOT IN ( AND 1 NOT IN (
SELECT 1 FROM group_members SELECT 1 FROM group_members
WHERE user_id = :user_id WHERE user_id = ?
AND (member_profile_id = :profile_id OR contact_profile_id = :profile_id) AND (member_profile_id = ? OR contact_profile_id = ?)
LIMIT 1 LIMIT 1
) )
|] |]
[":user_id" := userId, ":profile_id" := profileId] ( (userId, profileId, userId, profileId, userId, profileId)
:. (userId, profileId, userId, profileId, profileId)
)
updateContactProfile :: DB.Connection -> User -> Contact -> Profile -> ExceptT StoreError IO Contact updateContactProfile :: DB.Connection -> User -> Contact -> Profile -> ExceptT StoreError IO Contact
updateContactProfile db user@User {userId} c p' updateContactProfile db user@User {userId} c p'
@ -465,14 +475,14 @@ updateContactUsed db User {userId} Contact {contactId} = do
updateContactUnreadChat :: DB.Connection -> User -> Contact -> Bool -> IO () updateContactUnreadChat :: DB.Connection -> User -> Contact -> Bool -> IO ()
updateContactUnreadChat db User {userId} Contact {contactId} unreadChat = do updateContactUnreadChat db User {userId} Contact {contactId} unreadChat = do
updatedAt <- getCurrentTime updatedAt <- getCurrentTime
DB.execute db "UPDATE contacts SET unread_chat = ?, updated_at = ? WHERE user_id = ? AND contact_id = ?" (unreadChat, updatedAt, userId, contactId) DB.execute db "UPDATE contacts SET unread_chat = ?, updated_at = ? WHERE user_id = ? AND contact_id = ?" (BI unreadChat, updatedAt, userId, contactId)
setUserChatsRead :: DB.Connection -> User -> IO () setUserChatsRead :: DB.Connection -> User -> IO ()
setUserChatsRead db User {userId} = do setUserChatsRead db User {userId} = do
updatedAt <- getCurrentTime updatedAt <- getCurrentTime
DB.execute db "UPDATE contacts SET unread_chat = ?, updated_at = ? WHERE user_id = ? AND unread_chat = ?" (False, updatedAt, userId, True) DB.execute db "UPDATE contacts SET unread_chat = ?, updated_at = ? WHERE user_id = ? AND unread_chat = ?" (BI False, updatedAt, userId, BI True)
DB.execute db "UPDATE groups SET unread_chat = ?, updated_at = ? WHERE user_id = ? AND unread_chat = ?" (False, updatedAt, userId, True) DB.execute db "UPDATE groups SET unread_chat = ?, updated_at = ? WHERE user_id = ? AND unread_chat = ?" (BI False, updatedAt, userId, BI True)
DB.execute db "UPDATE note_folders SET unread_chat = ?, updated_at = ? WHERE user_id = ? AND unread_chat = ?" (False, updatedAt, userId, True) DB.execute db "UPDATE note_folders SET unread_chat = ?, updated_at = ? WHERE user_id = ? AND unread_chat = ?" (BI False, updatedAt, userId, BI True)
DB.execute db "UPDATE chat_items SET item_status = ?, updated_at = ? WHERE user_id = ? AND item_status = ?" (CISRcvRead, updatedAt, userId, CISRcvNew) DB.execute db "UPDATE chat_items SET item_status = ?, updated_at = ? WHERE user_id = ? AND item_status = ?" (CISRcvRead, updatedAt, userId, CISRcvNew)
updateContactStatus :: DB.Connection -> User -> Contact -> ContactStatus -> IO Contact updateContactStatus :: DB.Connection -> User -> Contact -> ContactStatus -> IO Contact
@ -491,7 +501,7 @@ updateContactStatus db User {userId} ct@Contact {contactId} contactStatus = do
updateGroupUnreadChat :: DB.Connection -> User -> GroupInfo -> Bool -> IO () updateGroupUnreadChat :: DB.Connection -> User -> GroupInfo -> Bool -> IO ()
updateGroupUnreadChat db User {userId} GroupInfo {groupId} unreadChat = do updateGroupUnreadChat db User {userId} GroupInfo {groupId} unreadChat = do
updatedAt <- getCurrentTime updatedAt <- getCurrentTime
DB.execute db "UPDATE groups SET unread_chat = ?, updated_at = ? WHERE user_id = ? AND group_id = ?" (unreadChat, updatedAt, userId, groupId) DB.execute db "UPDATE groups SET unread_chat = ?, updated_at = ? WHERE user_id = ? AND group_id = ?" (BI unreadChat, updatedAt, userId, groupId)
setConnectionVerified :: DB.Connection -> User -> Int64 -> Maybe Text -> IO () setConnectionVerified :: DB.Connection -> User -> Int64 -> Maybe Text -> IO ()
setConnectionVerified db User {userId} connId code = do setConnectionVerified db User {userId} connId code = do
@ -635,40 +645,42 @@ createOrUpdateContactRequest db vr user@User {userId, userContactId} userContact
created_at, updated_at, xcontact_id, pq_support) created_at, updated_at, xcontact_id, pq_support)
VALUES (?,?,?,?,?,?,?,?,?,?,?) VALUES (?,?,?,?,?,?,?,?,?,?,?)
|] |]
( (userContactLinkId, invId, minV, maxV, profileId, ldn, userId) ( (userContactLinkId, Binary invId, minV, maxV, profileId, ldn, userId)
:. (currentTs, currentTs, xContactId_, pqSup) :. (currentTs, currentTs, xContactId_, pqSup)
) )
insertedRowId db insertedRowId db
getContact' :: XContactId -> IO (Maybe Contact) getContact' :: XContactId -> IO (Maybe Contact)
getContact' xContactId = do getContact' xContactId = do
ct_ <- maybeFirstRow (toContact vr user []) $ ct_ <-
DB.query maybeFirstRow (toContact vr user []) $
db DB.query
[sql| db
SELECT [sql|
-- Contact SELECT
ct.contact_id, ct.contact_profile_id, ct.local_display_name, ct.via_group, cp.display_name, cp.full_name, cp.image, cp.contact_link, cp.local_alias, ct.contact_used, ct.contact_status, ct.enable_ntfs, ct.send_rcpts, ct.favorite, -- Contact
cp.preferences, ct.user_preferences, ct.created_at, ct.updated_at, ct.chat_ts, ct.contact_group_member_id, ct.contact_grp_inv_sent, ct.ui_themes, ct.chat_deleted, ct.custom_data, ct.contact_id, ct.contact_profile_id, ct.local_display_name, ct.via_group, cp.display_name, cp.full_name, cp.image, cp.contact_link, cp.local_alias, ct.contact_used, ct.contact_status, ct.enable_ntfs, ct.send_rcpts, ct.favorite,
-- Connection cp.preferences, ct.user_preferences, ct.created_at, ct.updated_at, ct.chat_ts, ct.contact_group_member_id, ct.contact_grp_inv_sent, ct.ui_themes, ct.chat_deleted, ct.custom_data,
c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id, c.conn_status, c.conn_type, c.contact_conn_initiated, c.local_alias, -- Connection
c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at, c.security_code, c.security_code_verified_at, c.pq_support, c.pq_encryption, c.pq_snd_enabled, c.pq_rcv_enabled, c.auth_err_counter, c.quota_err_counter, c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id, c.conn_status, c.conn_type, c.contact_conn_initiated, c.local_alias,
c.conn_chat_version, c.peer_chat_min_version, c.peer_chat_max_version c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at, c.security_code, c.security_code_verified_at, c.pq_support, c.pq_encryption, c.pq_snd_enabled, c.pq_rcv_enabled, c.auth_err_counter, c.quota_err_counter,
FROM contacts ct c.conn_chat_version, c.peer_chat_min_version, c.peer_chat_max_version
JOIN contact_profiles cp ON ct.contact_profile_id = cp.contact_profile_id FROM contacts ct
LEFT JOIN connections c ON c.contact_id = ct.contact_id JOIN contact_profiles cp ON ct.contact_profile_id = cp.contact_profile_id
WHERE ct.user_id = ? AND ct.xcontact_id = ? AND ct.deleted = 0 LEFT JOIN connections c ON c.contact_id = ct.contact_id
ORDER BY c.created_at DESC WHERE ct.user_id = ? AND ct.xcontact_id = ? AND ct.deleted = 0
LIMIT 1 ORDER BY c.created_at DESC
|] LIMIT 1
(userId, xContactId) |]
(userId, xContactId)
mapM (addDirectChatTags db) ct_ mapM (addDirectChatTags db) ct_
getGroupInfo' :: XContactId -> IO (Maybe GroupInfo) getGroupInfo' :: XContactId -> IO (Maybe GroupInfo)
getGroupInfo' xContactId = do getGroupInfo' xContactId = do
g_ <- maybeFirstRow (toGroupInfo vr userContactId []) $ g_ <-
DB.query maybeFirstRow (toGroupInfo vr userContactId []) $
db DB.query
(groupInfoQuery <> " WHERE g.business_xcontact_id = ? AND g.user_id = ? AND mu.contact_id = ?") db
(xContactId, userId, userContactId) (groupInfoQuery <> " WHERE g.business_xcontact_id = ? AND g.user_id = ? AND mu.contact_id = ?")
(xContactId, userId, userContactId)
mapM (addGroupChatTags db) g_ mapM (addGroupChatTags db) g_
getContactRequestByXContactId :: XContactId -> IO (Maybe UserContactRequest) getContactRequestByXContactId :: XContactId -> IO (Maybe UserContactRequest)
getContactRequestByXContactId xContactId = getContactRequestByXContactId xContactId =
@ -702,7 +714,7 @@ createOrUpdateContactRequest db vr user@User {userId, userContactId} userContact
SET agent_invitation_id = ?, pq_support = ?, peer_chat_min_version = ?, peer_chat_max_version = ?, updated_at = ? SET agent_invitation_id = ?, pq_support = ?, peer_chat_min_version = ?, peer_chat_max_version = ?, updated_at = ?
WHERE user_id = ? AND contact_request_id = ? WHERE user_id = ? AND contact_request_id = ?
|] |]
(invId, pqSup, minV, maxV, currentTs, userId, cReqId) (Binary invId, pqSup, minV, maxV, currentTs, userId, cReqId)
else withLocalDisplayName db userId displayName $ \ldn -> else withLocalDisplayName db userId displayName $ \ldn ->
Right <$> do Right <$> do
DB.execute DB.execute
@ -712,7 +724,7 @@ createOrUpdateContactRequest db vr user@User {userId, userContactId} userContact
SET agent_invitation_id = ?, pq_support = ?, peer_chat_min_version = ?, peer_chat_max_version = ?, local_display_name = ?, updated_at = ? SET agent_invitation_id = ?, pq_support = ?, peer_chat_min_version = ?, peer_chat_max_version = ?, local_display_name = ?, updated_at = ?
WHERE user_id = ? AND contact_request_id = ? WHERE user_id = ? AND contact_request_id = ?
|] |]
(invId, pqSup, minV, maxV, ldn, currentTs, userId, cReqId) (Binary invId, pqSup, minV, maxV, ldn, currentTs, userId, cReqId)
safeDeleteLDN db user oldLdn safeDeleteLDN db user oldLdn
where where
updateProfile currentTs = updateProfile currentTs =
@ -803,7 +815,7 @@ createAcceptedContact db user@User {userId, profile = LocalProfile {preferences}
DB.execute DB.execute
db db
"INSERT INTO contacts (user_id, local_display_name, contact_profile_id, enable_ntfs, user_preferences, created_at, updated_at, chat_ts, xcontact_id, contact_used) VALUES (?,?,?,?,?,?,?,?,?,?)" "INSERT INTO contacts (user_id, local_display_name, contact_profile_id, enable_ntfs, user_preferences, created_at, updated_at, chat_ts, xcontact_id, contact_used) VALUES (?,?,?,?,?,?,?,?,?,?)"
(userId, localDisplayName, profileId, True, userPreferences, createdAt, createdAt, createdAt, xContactId, contactUsed) (userId, localDisplayName, profileId, BI True, userPreferences, createdAt, createdAt, createdAt, xContactId, BI contactUsed)
contactId <- insertedRowId db contactId <- insertedRowId db
DB.execute db "UPDATE contact_requests SET contact_id = ? WHERE user_id = ? AND local_display_name = ?" (contactId, userId, localDisplayName) DB.execute db "UPDATE contact_requests SET contact_id = ? WHERE user_id = ? AND local_display_name = ?" (contactId, userId, localDisplayName)
conn <- createConnection_ db userId ConnContact (Just contactId) agentConnId ConnNew connChatVersion cReqChatVRange Nothing (Just userContactLinkId) customUserProfileId 0 createdAt subMode pqSup conn <- createConnection_ db userId ConnContact (Just contactId) agentConnId ConnNew connChatVersion cReqChatVRange Nothing (Just userContactLinkId) customUserProfileId 0 createdAt subMode pqSup
@ -841,7 +853,7 @@ updateContactAccepted db User {userId} Contact {contactId} contactUsed =
DB.execute DB.execute
db db
"UPDATE contacts SET contact_used = ? WHERE user_id = ? AND contact_id = ?" "UPDATE contacts SET contact_used = ? WHERE user_id = ? AND contact_id = ?"
(contactUsed, userId, contactId) (BI contactUsed, userId, contactId)
getContactIdByName :: DB.Connection -> User -> ContactName -> ExceptT StoreError IO Int64 getContactIdByName :: DB.Connection -> User -> ContactName -> ExceptT StoreError IO Int64
getContactIdByName db User {userId} cName = getContactIdByName db User {userId} cName =
@ -882,12 +894,12 @@ getContact_ db vr user@User {userId} contactId deleted = do
WHERE cc.user_id = ct.user_id AND cc.contact_id = ct.contact_id WHERE cc.user_id = ct.user_id AND cc.contact_id = ct.contact_id
ORDER BY cc_conn_status_ord DESC, cc_created_at DESC ORDER BY cc_conn_status_ord DESC, cc_created_at DESC
LIMIT 1 LIMIT 1
) ) cc
) )
OR c.connection_id IS NULL OR c.connection_id IS NULL
) )
|] |]
(userId, contactId, deleted, ConnReady, ConnSndReady) (userId, contactId, BI deleted, ConnReady, ConnSndReady)
getUserByContactRequestId :: DB.Connection -> Int64 -> ExceptT StoreError IO User getUserByContactRequestId :: DB.Connection -> Int64 -> ExceptT StoreError IO User
getUserByContactRequestId db contactRequestId = getUserByContactRequestId db contactRequestId =
@ -897,16 +909,16 @@ getUserByContactRequestId db contactRequestId =
getPendingContactConnections :: DB.Connection -> User -> IO [PendingContactConnection] getPendingContactConnections :: DB.Connection -> User -> IO [PendingContactConnection]
getPendingContactConnections db User {userId} = do getPendingContactConnections db User {userId} = do
map toPendingContactConnection map toPendingContactConnection
<$> DB.queryNamed <$> DB.query
db db
[sql| [sql|
SELECT connection_id, agent_conn_id, conn_status, via_contact_uri_hash, via_user_contact_link, group_link_id, custom_user_profile_id, conn_req_inv, local_alias, created_at, updated_at SELECT connection_id, agent_conn_id, conn_status, via_contact_uri_hash, via_user_contact_link, group_link_id, custom_user_profile_id, conn_req_inv, local_alias, created_at, updated_at
FROM connections FROM connections
WHERE user_id = :user_id WHERE user_id = ?
AND conn_type = :conn_type AND conn_type = ?
AND contact_id IS NULL AND contact_id IS NULL
|] |]
[":user_id" := userId, ":conn_type" := ConnContact] (userId, ConnContact)
getContactConnections :: DB.Connection -> VersionRangeChat -> UserId -> Contact -> IO [Connection] getContactConnections :: DB.Connection -> VersionRangeChat -> UserId -> Contact -> IO [Connection]
getContactConnections db vr userId Contact {contactId} = getContactConnections db vr userId Contact {contactId} =
@ -945,9 +957,13 @@ getConnectionById db vr User {userId} connId = ExceptT $ do
getConnectionsContacts :: DB.Connection -> [ConnId] -> IO [ContactRef] getConnectionsContacts :: DB.Connection -> [ConnId] -> IO [ContactRef]
getConnectionsContacts db agentConnIds = do getConnectionsContacts db agentConnIds = do
DB.execute_ db "DROP TABLE IF EXISTS temp.conn_ids" DB.execute_ db "DROP TABLE IF EXISTS temp_conn_ids"
DB.execute_ db "CREATE TABLE temp.conn_ids (conn_id BLOB)" #if defined(dbPostgres)
DB.executeMany db "INSERT INTO temp.conn_ids (conn_id) VALUES (?)" $ map Only agentConnIds DB.execute_ db "CREATE TABLE temp_conn_ids (conn_id BYTEA)"
#else
DB.execute_ db "CREATE TABLE temp_conn_ids (conn_id BLOB)"
#endif
DB.executeMany db "INSERT INTO temp_conn_ids (conn_id) VALUES (?)" $ map Only agentConnIds
conns <- conns <-
map toContactRef map toContactRef
<$> DB.query <$> DB.query
@ -956,12 +972,12 @@ getConnectionsContacts db agentConnIds = do
SELECT ct.contact_id, c.connection_id, c.agent_conn_id, ct.local_display_name SELECT ct.contact_id, c.connection_id, c.agent_conn_id, ct.local_display_name
FROM contacts ct FROM contacts ct
JOIN connections c ON c.contact_id = ct.contact_id JOIN connections c ON c.contact_id = ct.contact_id
WHERE c.agent_conn_id IN (SELECT conn_id FROM temp.conn_ids) WHERE c.agent_conn_id IN (SELECT conn_id FROM temp_conn_ids)
AND c.conn_type = ? AND c.conn_type = ?
AND ct.deleted = 0 AND ct.deleted = 0
|] |]
(Only ConnContact) (Only ConnContact)
DB.execute_ db "DROP TABLE temp.conn_ids" DB.execute_ db "DROP TABLE temp_conn_ids"
pure conns pure conns
where where
toContactRef :: (ContactId, Int64, ConnId, ContactName) -> ContactRef toContactRef :: (ContactId, Int64, ConnId, ContactName) -> ContactRef
@ -986,7 +1002,7 @@ updateConnectionStatus_ db connId connStatus = do
updateContactSettings :: DB.Connection -> User -> Int64 -> ChatSettings -> IO () updateContactSettings :: DB.Connection -> User -> Int64 -> ChatSettings -> IO ()
updateContactSettings db User {userId} contactId ChatSettings {enableNtfs, sendRcpts, favorite} = updateContactSettings db User {userId} contactId ChatSettings {enableNtfs, sendRcpts, favorite} =
DB.execute db "UPDATE contacts SET enable_ntfs = ?, send_rcpts = ?, favorite = ? WHERE user_id = ? AND contact_id = ?" (enableNtfs, sendRcpts, favorite, userId, contactId) DB.execute db "UPDATE contacts SET enable_ntfs = ?, send_rcpts = ?, favorite = ? WHERE user_id = ? AND contact_id = ?" (enableNtfs, BI <$> sendRcpts, BI favorite, userId, contactId)
setConnConnReqInv :: DB.Connection -> User -> Int64 -> ConnReqInvitation -> IO () setConnConnReqInv :: DB.Connection -> User -> Int64 -> ConnReqInvitation -> IO ()
setConnConnReqInv db User {userId} connId connReq = do setConnConnReqInv db User {userId} connId connReq = do
@ -1025,7 +1041,7 @@ setContactUIThemes db User {userId} Contact {contactId} uiThemes = do
setContactChatDeleted :: DB.Connection -> User -> Contact -> Bool -> IO () setContactChatDeleted :: DB.Connection -> User -> Contact -> Bool -> IO ()
setContactChatDeleted db User {userId} Contact {contactId} chatDeleted = do setContactChatDeleted db User {userId} Contact {contactId} chatDeleted = do
updatedAt <- getCurrentTime updatedAt <- getCurrentTime
DB.execute db "UPDATE contacts SET chat_deleted = ?, updated_at = ? WHERE user_id = ? AND contact_id = ?" (chatDeleted, updatedAt, userId, contactId) DB.execute db "UPDATE contacts SET chat_deleted = ?, updated_at = ? WHERE user_id = ? AND contact_id = ?" (BI chatDeleted, updatedAt, userId, contactId)
updateDirectChatTags :: DB.Connection -> ContactId -> [ChatTagId] -> IO () updateDirectChatTags :: DB.Connection -> ContactId -> [ChatTagId] -> IO ()
updateDirectChatTags db contactId tIds = do updateDirectChatTags db contactId tIds = do

View file

@ -1,3 +1,4 @@
{-# LANGUAGE CPP #-}
{-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GADTs #-} {-# LANGUAGE GADTs #-}
@ -96,9 +97,6 @@ import Data.Time (addUTCTime)
import Data.Time.Clock (UTCTime (..), getCurrentTime, nominalDay) import Data.Time.Clock (UTCTime (..), getCurrentTime, nominalDay)
import Data.Type.Equality import Data.Type.Equality
import Data.Word (Word32) import Data.Word (Word32)
import Database.SQLite.Simple (Only (..), (:.) (..))
import Database.SQLite.Simple.QQ (sql)
import Database.SQLite.Simple.ToField (ToField)
import Simplex.Chat.Messages import Simplex.Chat.Messages
import Simplex.Chat.Messages.CIContent import Simplex.Chat.Messages.CIContent
import Simplex.Chat.Protocol import Simplex.Chat.Protocol
@ -110,7 +108,8 @@ import Simplex.Chat.Types
import Simplex.Chat.Util (week) import Simplex.Chat.Util (week)
import Simplex.Messaging.Agent.Protocol (AgentMsgId, ConnId, UserId) import Simplex.Messaging.Agent.Protocol (AgentMsgId, ConnId, UserId)
import Simplex.Messaging.Agent.Store.AgentStore (firstRow, firstRow', maybeFirstRow) import Simplex.Messaging.Agent.Store.AgentStore (firstRow, firstRow', maybeFirstRow)
import qualified Simplex.Messaging.Agent.Store.SQLite.DB as DB import Simplex.Messaging.Agent.Store.DB (BoolInt (..))
import qualified Simplex.Messaging.Agent.Store.DB as DB
import qualified Simplex.Messaging.Crypto as C import qualified Simplex.Messaging.Crypto as C
import Simplex.Messaging.Crypto.File (CryptoFile (..), CryptoFileArgs (..)) import Simplex.Messaging.Crypto.File (CryptoFile (..), CryptoFileArgs (..))
import qualified Simplex.Messaging.Crypto.File as CF import qualified Simplex.Messaging.Crypto.File as CF
@ -118,6 +117,15 @@ import Simplex.Messaging.Crypto.Ratchet as CR
import Simplex.Messaging.Protocol (SubscriptionMode (..)) import Simplex.Messaging.Protocol (SubscriptionMode (..))
import Simplex.Messaging.Version import Simplex.Messaging.Version
import System.FilePath (takeFileName) import System.FilePath (takeFileName)
#if defined(dbPostgres)
import Database.PostgreSQL.Simple (Only (..), (:.) (..))
import Database.PostgreSQL.Simple.SqlQQ (sql)
import Database.PostgreSQL.Simple.ToField (ToField)
#else
import Database.SQLite.Simple (Only (..), (:.) (..))
import Database.SQLite.Simple.QQ (sql)
import Database.SQLite.Simple.ToField (ToField)
#endif
getLiveSndFileTransfers :: DB.Connection -> User -> IO [SndFileTransfer] getLiveSndFileTransfers :: DB.Connection -> User -> IO [SndFileTransfer]
getLiveSndFileTransfers db User {userId} = do getLiveSndFileTransfers db User {userId} = do
@ -283,7 +291,7 @@ createSndFTDescrXFTP db User {userId} m Connection {connId} FileTransferMeta {fi
DB.execute DB.execute
db db
"INSERT INTO xftp_file_descriptions (user_id, file_descr_text, file_descr_part_no, file_descr_complete, created_at, updated_at) VALUES (?,?,?,?,?,?)" "INSERT INTO xftp_file_descriptions (user_id, file_descr_text, file_descr_part_no, file_descr_complete, created_at, updated_at) VALUES (?,?,?,?,?,?)"
(userId, fileDescrText, fileDescrPartNo, fileDescrComplete, currentTs, currentTs) (userId, fileDescrText, fileDescrPartNo, BI fileDescrComplete, currentTs, currentTs)
fileDescrId <- insertedRowId db fileDescrId <- insertedRowId db
DB.execute DB.execute
db db
@ -308,7 +316,7 @@ updateSndFTDescrXFTP db user@User {userId} sft@SndFileTransfer {fileId, fileDesc
SET file_descr_text = ?, file_descr_part_no = ?, file_descr_complete = ?, updated_at = ? SET file_descr_text = ?, file_descr_part_no = ?, file_descr_complete = ?, updated_at = ?
WHERE user_id = ? AND file_descr_id = ? WHERE user_id = ? AND file_descr_id = ?
|] |]
(rfdText, 1 :: Int, True, currentTs, userId, fileDescrId) (rfdText, 1 :: Int, BI True, currentTs, userId, fileDescrId)
updateCIFileStatus db user fileId $ CIFSSndTransfer 1 1 updateCIFileStatus db user fileId $ CIFSSndTransfer 1 1
updateSndFileStatus db sft FSConnected updateSndFileStatus db sft FSConnected
@ -574,7 +582,7 @@ createRcvFD_ db userId currentTs FileDescr {fileDescrText, fileDescrPartNo, file
DB.execute DB.execute
db db
"INSERT INTO xftp_file_descriptions (user_id, file_descr_text, file_descr_part_no, file_descr_complete, created_at, updated_at) VALUES (?,?,?,?,?,?)" "INSERT INTO xftp_file_descriptions (user_id, file_descr_text, file_descr_part_no, file_descr_complete, created_at, updated_at) VALUES (?,?,?,?,?,?)"
(userId, fileDescrText, fileDescrPartNo, fileDescrComplete, currentTs, currentTs) (userId, fileDescrText, fileDescrPartNo, BI fileDescrComplete, currentTs, currentTs)
insertedRowId db insertedRowId db
pure RcvFileDescr {fileDescrId, fileDescrPartNo, fileDescrText, fileDescrComplete} pure RcvFileDescr {fileDescrId, fileDescrPartNo, fileDescrText, fileDescrComplete}
@ -607,7 +615,7 @@ appendRcvFD db userId fileId fd@FileDescr {fileDescrText, fileDescrPartNo, fileD
SET file_descr_text = ?, file_descr_part_no = ?, file_descr_complete = ? SET file_descr_text = ?, file_descr_part_no = ?, file_descr_complete = ?
WHERE file_descr_id = ? WHERE file_descr_id = ?
|] |]
(fileDescrText', fileDescrPartNo, fileDescrComplete, fileDescrId) (fileDescrText', fileDescrPartNo, BI fileDescrComplete, fileDescrId)
pure RcvFileDescr {fileDescrId, fileDescrText = fileDescrText', fileDescrPartNo, fileDescrComplete} pure RcvFileDescr {fileDescrId, fileDescrText = fileDescrText', fileDescrPartNo, fileDescrComplete}
getRcvFileDescrByRcvFileId :: DB.Connection -> FileTransferId -> ExceptT StoreError IO RcvFileDescr getRcvFileDescrByRcvFileId :: DB.Connection -> FileTransferId -> ExceptT StoreError IO RcvFileDescr
@ -650,8 +658,8 @@ getRcvFileDescrBySndFileId_ db fileId =
|] |]
(Only fileId) (Only fileId)
toRcvFileDescr :: (Int64, Text, Int, Bool) -> RcvFileDescr toRcvFileDescr :: (Int64, Text, Int, BoolInt) -> RcvFileDescr
toRcvFileDescr (fileDescrId, fileDescrText, fileDescrPartNo, fileDescrComplete) = toRcvFileDescr (fileDescrId, fileDescrText, fileDescrPartNo, BI fileDescrComplete) =
RcvFileDescr {fileDescrId, fileDescrText, fileDescrPartNo, fileDescrComplete} RcvFileDescr {fileDescrId, fileDescrText, fileDescrPartNo, fileDescrComplete}
updateRcvFileAgentId :: DB.Connection -> FileTransferId -> Maybe AgentRcvFileId -> IO () updateRcvFileAgentId :: DB.Connection -> FileTransferId -> Maybe AgentRcvFileId -> IO ()
@ -682,8 +690,8 @@ getRcvFileTransfer_ db userId fileId = do
FROM rcv_files r FROM rcv_files r
JOIN files f USING (file_id) JOIN files f USING (file_id)
LEFT JOIN connections c ON r.file_id = c.rcv_file_id LEFT JOIN connections c ON r.file_id = c.rcv_file_id
LEFT JOIN contacts cs USING (contact_id) LEFT JOIN contacts cs ON cs.contact_id = f.contact_id
LEFT JOIN group_members m USING (group_member_id) LEFT JOIN group_members m ON m.group_member_id = r.group_member_id
WHERE f.user_id = ? AND f.file_id = ? WHERE f.user_id = ? AND f.file_id = ?
|] |]
(userId, fileId) (userId, fileId)
@ -692,9 +700,9 @@ getRcvFileTransfer_ db userId fileId = do
where where
rcvFileTransfer :: rcvFileTransfer ::
Maybe RcvFileDescr -> Maybe RcvFileDescr ->
(FileStatus, Maybe ConnReqInvitation, Maybe Int64, String, Integer, Integer, Maybe Bool) :. (Maybe ContactName, Maybe ContactName, Maybe FilePath, Maybe C.SbKey, Maybe C.CbNonce, Maybe InlineFileMode, Maybe InlineFileMode, Maybe AgentRcvFileId, Bool, Bool) :. (Maybe Int64, Maybe AgentConnId) -> (FileStatus, Maybe ConnReqInvitation, Maybe Int64, String, Integer, Integer, Maybe BoolInt) :. (Maybe ContactName, Maybe ContactName, Maybe FilePath, Maybe C.SbKey, Maybe C.CbNonce, Maybe InlineFileMode, Maybe InlineFileMode, Maybe AgentRcvFileId, BoolInt, BoolInt) :. (Maybe Int64, Maybe AgentConnId) ->
ExceptT StoreError IO RcvFileTransfer ExceptT StoreError IO RcvFileTransfer
rcvFileTransfer rfd_ ((fileStatus', fileConnReq, grpMemberId, fileName, fileSize, chunkSize, cancelled_) :. (contactName_, memberName_, filePath_, fileKey, fileNonce, fileInline, rcvFileInline, agentRcvFileId, agentRcvFileDeleted, userApprovedRelays) :. (connId_, agentConnId_)) = rcvFileTransfer rfd_ ((fileStatus', fileConnReq, grpMemberId, fileName, fileSize, chunkSize, cancelled_) :. (contactName_, memberName_, filePath_, fileKey, fileNonce, fileInline, rcvFileInline, agentRcvFileId, BI agentRcvFileDeleted, BI userApprovedRelays) :. (connId_, agentConnId_)) =
case contactName_ <|> memberName_ <|> standaloneName_ of case contactName_ <|> memberName_ <|> standaloneName_ of
Nothing -> throwError $ SERcvFileInvalid fileId Nothing -> throwError $ SERcvFileInvalid fileId
Just name -> Just name ->
@ -717,7 +725,7 @@ getRcvFileTransfer_ db userId fileId = do
rfi_ = case (filePath_, connId_, agentConnId_) of rfi_ = case (filePath_, connId_, agentConnId_) of
(Just filePath, connId, agentConnId) -> pure $ Just RcvFileInfo {filePath, connId, agentConnId} (Just filePath, connId, agentConnId) -> pure $ Just RcvFileInfo {filePath, connId, agentConnId}
_ -> pure Nothing _ -> pure Nothing
cancelled = fromMaybe False cancelled_ cancelled = maybe False unBI cancelled_
acceptRcvFileTransfer :: DB.Connection -> VersionRangeChat -> User -> Int64 -> (CommandId, ConnId) -> ConnStatus -> FilePath -> SubscriptionMode -> ExceptT StoreError IO AChatItem acceptRcvFileTransfer :: DB.Connection -> VersionRangeChat -> User -> Int64 -> (CommandId, ConnId) -> ConnStatus -> FilePath -> SubscriptionMode -> ExceptT StoreError IO AChatItem
acceptRcvFileTransfer db vr user@User {userId} fileId (cmdId, acId) connStatus filePath subMode = ExceptT $ do acceptRcvFileTransfer db vr user@User {userId} fileId (cmdId, acId) connStatus filePath subMode = ExceptT $ do
@ -726,7 +734,7 @@ acceptRcvFileTransfer db vr user@User {userId} fileId (cmdId, acId) connStatus f
DB.execute DB.execute
db db
"INSERT INTO connections (agent_conn_id, conn_status, conn_type, rcv_file_id, user_id, created_at, updated_at, to_subscribe) VALUES (?,?,?,?,?,?,?,?)" "INSERT INTO connections (agent_conn_id, conn_status, conn_type, rcv_file_id, user_id, created_at, updated_at, to_subscribe) VALUES (?,?,?,?,?,?,?,?)"
(acId, connStatus, ConnRcvFile, fileId, userId, currentTs, currentTs, subMode == SMOnlyCreate) (acId, connStatus, ConnRcvFile, fileId, userId, currentTs, currentTs, BI (subMode == SMOnlyCreate))
connId <- insertedRowId db connId <- insertedRowId db
setCommandConnId db user cmdId connId setCommandConnId db user cmdId connId
runExceptT $ getChatItemByFileId db vr user fileId runExceptT $ getChatItemByFileId db vr user fileId
@ -763,7 +771,7 @@ acceptRcvFT_ db User {userId} fileId filePath userApprovedRelays rcvFileInline c
DB.execute DB.execute
db db
"UPDATE rcv_files SET user_approved_relays = ?, rcv_file_inline = ?, file_status = ?, updated_at = ? WHERE file_id = ?" "UPDATE rcv_files SET user_approved_relays = ?, rcv_file_inline = ?, file_status = ?, updated_at = ? WHERE file_id = ?"
(userApprovedRelays, rcvFileInline, FSAccepted, currentTs, fileId) (BI userApprovedRelays, rcvFileInline, FSAccepted, currentTs, fileId)
setRcvFileToReceive :: DB.Connection -> FileTransferId -> Bool -> Maybe CryptoFileArgs -> IO () setRcvFileToReceive :: DB.Connection -> FileTransferId -> Bool -> Maybe CryptoFileArgs -> IO ()
setRcvFileToReceive db fileId userApprovedRelays cfArgs_ = do setRcvFileToReceive db fileId userApprovedRelays cfArgs_ = do
@ -775,7 +783,7 @@ setRcvFileToReceive db fileId userApprovedRelays cfArgs_ = do
SET to_receive = 1, user_approved_relays = ?, updated_at = ? SET to_receive = 1, user_approved_relays = ?, updated_at = ?
WHERE file_id = ? WHERE file_id = ?
|] |]
(userApprovedRelays, currentTs, fileId) (BI userApprovedRelays, currentTs, fileId)
forM_ cfArgs_ $ \cfArgs -> setFileCryptoArgs_ db fileId cfArgs currentTs forM_ cfArgs_ $ \cfArgs -> setFileCryptoArgs_ db fileId cfArgs currentTs
setFileCryptoArgs :: DB.Connection -> FileTransferId -> CryptoFileArgs -> IO () setFileCryptoArgs :: DB.Connection -> FileTransferId -> CryptoFileArgs -> IO ()
@ -928,8 +936,8 @@ getSndFileTransfers_ db userId fileId =
FROM snd_files s FROM snd_files s
JOIN files f USING (file_id) JOIN files f USING (file_id)
JOIN connections c USING (connection_id) JOIN connections c USING (connection_id)
LEFT JOIN contacts cs USING (contact_id) LEFT JOIN contacts cs ON cs.contact_id = f.contact_id
LEFT JOIN group_members m USING (group_member_id) LEFT JOIN group_members m ON m.group_member_id = s.group_member_id
WHERE f.user_id = ? AND f.file_id = ? WHERE f.user_id = ? AND f.file_id = ?
|] |]
(userId, fileId) (userId, fileId)
@ -955,11 +963,11 @@ getFileTransferMeta_ db userId fileId =
|] |]
(userId, fileId) (userId, fileId)
where where
fileTransferMeta :: (String, Integer, Integer, FilePath, Maybe C.SbKey, Maybe C.CbNonce, Maybe InlineFileMode, Maybe AgentSndFileId, Bool, Maybe Text, Maybe Bool, Maybe FileTransferId) -> FileTransferMeta fileTransferMeta :: (String, Integer, Integer, FilePath, Maybe C.SbKey, Maybe C.CbNonce, Maybe InlineFileMode, Maybe AgentSndFileId, BoolInt, Maybe Text, Maybe BoolInt, Maybe FileTransferId) -> FileTransferMeta
fileTransferMeta (fileName, fileSize, chunkSize, filePath, fileKey, fileNonce, fileInline, aSndFileId_, agentSndFileDeleted, privateSndFileDescr, cancelled_, xftpRedirectFor) = fileTransferMeta (fileName, fileSize, chunkSize, filePath, fileKey, fileNonce, fileInline, aSndFileId_, BI agentSndFileDeleted, privateSndFileDescr, cancelled_, xftpRedirectFor) =
let cryptoArgs = CFArgs <$> fileKey <*> fileNonce let cryptoArgs = CFArgs <$> fileKey <*> fileNonce
xftpSndFile = (\fId -> XFTPSndFile {agentSndFileId = fId, privateSndFileDescr, agentSndFileDeleted, cryptoArgs}) <$> aSndFileId_ xftpSndFile = (\fId -> XFTPSndFile {agentSndFileId = fId, privateSndFileDescr, agentSndFileDeleted, cryptoArgs}) <$> aSndFileId_
in FileTransferMeta {fileId, xftpSndFile, xftpRedirectFor, fileName, fileSize, chunkSize, filePath, fileInline, cancelled = fromMaybe False cancelled_} in FileTransferMeta {fileId, xftpSndFile, xftpRedirectFor, fileName, fileSize, chunkSize, filePath, fileInline, cancelled = maybe False unBI cancelled_}
lookupFileTransferRedirectMeta :: DB.Connection -> User -> Int64 -> IO [FileTransferMeta] lookupFileTransferRedirectMeta :: DB.Connection -> User -> Int64 -> IO [FileTransferMeta]
lookupFileTransferRedirectMeta db User {userId} fileId = do lookupFileTransferRedirectMeta db User {userId} fileId = do

View file

@ -1,3 +1,4 @@
{-# LANGUAGE CPP #-}
{-# LANGUAGE DataKinds #-} {-# LANGUAGE DataKinds #-}
{-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE LambdaCase #-} {-# LANGUAGE LambdaCase #-}
@ -141,8 +142,6 @@ import Data.Maybe (catMaybes, fromMaybe, isJust, isNothing)
import Data.Ord (Down (..)) import Data.Ord (Down (..))
import Data.Text (Text) import Data.Text (Text)
import Data.Time.Clock (UTCTime (..), getCurrentTime) import Data.Time.Clock (UTCTime (..), getCurrentTime)
import Database.SQLite.Simple (NamedParam (..), Only (..), Query (..), (:.) (..))
import Database.SQLite.Simple.QQ (sql)
import Simplex.Chat.Messages import Simplex.Chat.Messages
import Simplex.Chat.Protocol (groupForwardVersion) import Simplex.Chat.Protocol (groupForwardVersion)
import Simplex.Chat.Store.Direct import Simplex.Chat.Store.Direct
@ -152,16 +151,24 @@ import Simplex.Chat.Types.Preferences
import Simplex.Chat.Types.Shared import Simplex.Chat.Types.Shared
import Simplex.Chat.Types.UITheme import Simplex.Chat.Types.UITheme
import Simplex.Messaging.Agent.Protocol (ConnId, UserId) import Simplex.Messaging.Agent.Protocol (ConnId, UserId)
import Simplex.Messaging.Agent.Store.AgentStore (firstRow, maybeFirstRow) import Simplex.Messaging.Agent.Store.AgentStore (firstRow, fromOnlyBI, maybeFirstRow)
import qualified Simplex.Messaging.Agent.Store.SQLite.DB as DB import Simplex.Messaging.Agent.Store.DB (Binary (..), BoolInt (..))
import qualified Simplex.Messaging.Agent.Store.DB as DB
import qualified Simplex.Messaging.Crypto as C import qualified Simplex.Messaging.Crypto as C
import Simplex.Messaging.Crypto.Ratchet (pattern PQEncOff, pattern PQSupportOff) import Simplex.Messaging.Crypto.Ratchet (pattern PQEncOff, pattern PQSupportOff)
import Simplex.Messaging.Protocol (SubscriptionMode (..)) import Simplex.Messaging.Protocol (SubscriptionMode (..))
import Simplex.Messaging.Util (eitherToMaybe, ($>>=), (<$$>)) import Simplex.Messaging.Util (eitherToMaybe, ($>>=), (<$$>))
import Simplex.Messaging.Version import Simplex.Messaging.Version
import UnliftIO.STM import UnliftIO.STM
#if defined(dbPostgres)
import Database.PostgreSQL.Simple (Only (..), Query, (:.) (..))
import Database.PostgreSQL.Simple.SqlQQ (sql)
#else
import Database.SQLite.Simple (Only (..), Query, (:.) (..))
import Database.SQLite.Simple.QQ (sql)
#endif
type MaybeGroupMemberRow = ((Maybe Int64, Maybe Int64, Maybe MemberId, Maybe VersionChat, Maybe VersionChat, Maybe GroupMemberRole, Maybe GroupMemberCategory, Maybe GroupMemberStatus, Maybe Bool, Maybe MemberRestrictionStatus) :. (Maybe Int64, Maybe GroupMemberId, Maybe ContactName, Maybe ContactId, Maybe ProfileId, Maybe ProfileId, Maybe ContactName, Maybe Text, Maybe ImageData, Maybe ConnReqContact, Maybe LocalAlias, Maybe Preferences)) type MaybeGroupMemberRow = ((Maybe Int64, Maybe Int64, Maybe MemberId, Maybe VersionChat, Maybe VersionChat, Maybe GroupMemberRole, Maybe GroupMemberCategory, Maybe GroupMemberStatus, Maybe BoolInt, Maybe MemberRestrictionStatus) :. (Maybe Int64, Maybe GroupMemberId, Maybe ContactName, Maybe ContactId, Maybe ProfileId, Maybe ProfileId, Maybe ContactName, Maybe Text, Maybe ImageData, Maybe ConnReqContact, Maybe LocalAlias, Maybe Preferences))
toMaybeGroupMember :: Int64 -> MaybeGroupMemberRow -> Maybe GroupMember toMaybeGroupMember :: Int64 -> MaybeGroupMemberRow -> Maybe GroupMember
toMaybeGroupMember userContactId ((Just groupMemberId, Just groupId, Just memberId, Just minVer, Just maxVer, Just memberRole, Just memberCategory, Just memberStatus, Just showMessages, memberBlocked) :. (invitedById, invitedByGroupMemberId, Just localDisplayName, memberContactId, Just memberContactProfileId, Just profileId, Just displayName, Just fullName, image, contactLink, Just localAlias, contactPreferences)) = toMaybeGroupMember userContactId ((Just groupMemberId, Just groupId, Just memberId, Just minVer, Just maxVer, Just memberRole, Just memberCategory, Just memberStatus, Just showMessages, memberBlocked) :. (invitedById, invitedByGroupMemberId, Just localDisplayName, memberContactId, Just memberContactProfileId, Just profileId, Just displayName, Just fullName, image, contactLink, Just localAlias, contactPreferences)) =
@ -175,7 +182,7 @@ createGroupLink db User {userId} groupInfo@GroupInfo {groupId, localDisplayName}
DB.execute DB.execute
db db
"INSERT INTO user_contact_links (user_id, group_id, group_link_id, local_display_name, conn_req_contact, group_link_member_role, auto_accept, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?,?)" "INSERT INTO user_contact_links (user_id, group_id, group_link_id, local_display_name, conn_req_contact, group_link_member_role, auto_accept, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?,?)"
(userId, groupId, groupLinkId, "group_link_" <> localDisplayName, cReq, memberRole, True, currentTs, currentTs) (userId, groupId, groupLinkId, "group_link_" <> localDisplayName, cReq, memberRole, BI True, currentTs, currentTs)
userContactLinkId <- insertedRowId db userContactLinkId <- insertedRowId db
void $ createConnection_ db userId ConnUserContact (Just userContactLinkId) agentConnId ConnNew initialChatVersion chatInitialVRange Nothing Nothing Nothing 0 currentTs subMode PQSupportOff void $ createConnection_ db userId ConnUserContact (Just userContactLinkId) agentConnId ConnNew initialChatVersion chatInitialVRange Nothing Nothing Nothing 0 currentTs subMode PQSupportOff
@ -254,41 +261,42 @@ setGroupLinkMemberRole db User {userId} userContactLinkId memberRole =
getGroupAndMember :: DB.Connection -> User -> Int64 -> VersionRangeChat -> ExceptT StoreError IO (GroupInfo, GroupMember) getGroupAndMember :: DB.Connection -> User -> Int64 -> VersionRangeChat -> ExceptT StoreError IO (GroupInfo, GroupMember)
getGroupAndMember db User {userId, userContactId} groupMemberId vr = do getGroupAndMember db User {userId, userContactId} groupMemberId vr = do
gm <- ExceptT . firstRow toGroupAndMember (SEInternalError "referenced group member not found") $ gm <-
DB.query ExceptT . firstRow toGroupAndMember (SEInternalError "referenced group member not found") $
db DB.query
[sql| db
SELECT [sql|
-- GroupInfo SELECT
g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.description, gp.image, -- GroupInfo
g.host_conn_custom_user_profile_id, g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.description, gp.image,
g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_chat, g.business_member_id, g.customer_member_id, g.ui_themes, g.custom_data, g.host_conn_custom_user_profile_id, g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences,
-- GroupInfo {membership} g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_chat, g.business_member_id, g.customer_member_id, g.ui_themes, g.custom_data,
mu.group_member_id, mu.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category, -- GroupInfo {membership}
mu.member_status, mu.show_messages, mu.member_restriction, mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id, mu.group_member_id, mu.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category,
-- GroupInfo {membership = GroupMember {memberProfile}} mu.member_status, mu.show_messages, mu.member_restriction, mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id,
pu.display_name, pu.full_name, pu.image, pu.contact_link, pu.local_alias, pu.preferences, -- GroupInfo {membership = GroupMember {memberProfile}}
-- from GroupMember pu.display_name, pu.full_name, pu.image, pu.contact_link, pu.local_alias, pu.preferences,
m.group_member_id, m.group_id, m.member_id, m.peer_chat_min_version, m.peer_chat_max_version, m.member_role, m.member_category, m.member_status, m.show_messages, m.member_restriction, -- from GroupMember
m.invited_by, m.invited_by_group_member_id, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, p.display_name, p.full_name, p.image, p.contact_link, p.local_alias, p.preferences, m.group_member_id, m.group_id, m.member_id, m.peer_chat_min_version, m.peer_chat_max_version, m.member_role, m.member_category, m.member_status, m.show_messages, m.member_restriction,
c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id, m.invited_by, m.invited_by_group_member_id, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, p.display_name, p.full_name, p.image, p.contact_link, p.local_alias, p.preferences,
c.conn_status, c.conn_type, c.contact_conn_initiated, c.local_alias, c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id,
c.created_at, c.security_code, c.security_code_verified_at, c.pq_support, c.pq_encryption, c.pq_snd_enabled, c.pq_rcv_enabled, c.auth_err_counter, c.quota_err_counter, c.conn_status, c.conn_type, c.contact_conn_initiated, c.local_alias, c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id,
c.conn_chat_version, c.peer_chat_min_version, c.peer_chat_max_version c.created_at, c.security_code, c.security_code_verified_at, c.pq_support, c.pq_encryption, c.pq_snd_enabled, c.pq_rcv_enabled, c.auth_err_counter, c.quota_err_counter,
FROM group_members m c.conn_chat_version, c.peer_chat_min_version, c.peer_chat_max_version
JOIN contact_profiles p ON p.contact_profile_id = COALESCE(m.member_profile_id, m.contact_profile_id) FROM group_members m
JOIN groups g ON g.group_id = m.group_id JOIN contact_profiles p ON p.contact_profile_id = COALESCE(m.member_profile_id, m.contact_profile_id)
JOIN group_profiles gp USING (group_profile_id) JOIN groups g ON g.group_id = m.group_id
JOIN group_members mu ON g.group_id = mu.group_id JOIN group_profiles gp USING (group_profile_id)
JOIN contact_profiles pu ON pu.contact_profile_id = COALESCE(mu.member_profile_id, mu.contact_profile_id) JOIN group_members mu ON g.group_id = mu.group_id
LEFT JOIN connections c ON c.connection_id = ( JOIN contact_profiles pu ON pu.contact_profile_id = COALESCE(mu.member_profile_id, mu.contact_profile_id)
SELECT max(cc.connection_id) LEFT JOIN connections c ON c.connection_id = (
FROM connections cc SELECT max(cc.connection_id)
where cc.user_id = ? AND cc.group_member_id = m.group_member_id FROM connections cc
) where cc.user_id = ? AND cc.group_member_id = m.group_member_id
WHERE m.group_member_id = ? AND g.user_id = ? AND mu.contact_id = ? )
|] WHERE m.group_member_id = ? AND g.user_id = ? AND mu.contact_id = ?
(userId, groupMemberId, userId, userContactId) |]
(userId, groupMemberId, userId, userContactId)
liftIO $ bitraverse (addGroupChatTags db) pure gm liftIO $ bitraverse (addGroupChatTags db) pure gm
where where
toGroupAndMember :: (GroupInfoRow :. GroupMemberRow :. MaybeConnectionRow) -> (GroupInfo, GroupMember) toGroupAndMember :: (GroupInfoRow :. GroupMemberRow :. MaybeConnectionRow) -> (GroupInfo, GroupMember)
@ -319,7 +327,7 @@ createNewGroup db vr gVar user@User {userId} groupProfile incognitoProfile = Exc
created_at, updated_at, chat_ts, user_member_profile_sent_at) created_at, updated_at, chat_ts, user_member_profile_sent_at)
VALUES (?,?,?,?,?,?,?,?) VALUES (?,?,?,?,?,?,?,?)
|] |]
(ldn, userId, profileId, True, currentTs, currentTs, currentTs, currentTs) (ldn, userId, profileId, BI True, currentTs, currentTs, currentTs, currentTs)
insertedRowId db insertedRowId db
memberId <- liftIO $ encodedRandomBytes gVar 12 memberId <- liftIO $ encodedRandomBytes gVar 12
membership <- createContactMemberInv_ db user groupId Nothing user (MemberIdRole (MemberId memberId) GROwner) GCUserMember GSMemCreator IBUser customUserProfileId currentTs vr membership <- createContactMemberInv_ db user groupId Nothing user (MemberIdRole (MemberId memberId) GROwner) GCUserMember GSMemCreator IBUser customUserProfileId currentTs vr
@ -387,7 +395,7 @@ createGroupInvitation db vr user@User {userId} contact@Contact {contactId, activ
created_at, updated_at, chat_ts, user_member_profile_sent_at, business_chat, business_member_id, customer_member_id) created_at, updated_at, chat_ts, user_member_profile_sent_at, business_chat, business_member_id, customer_member_id)
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)
|] |]
((profileId, localDisplayName, connRequest, customUserProfileId, userId, True, currentTs, currentTs, currentTs, currentTs) :. businessChatInfoRow business) ((profileId, localDisplayName, connRequest, customUserProfileId, userId, BI True, currentTs, currentTs, currentTs, currentTs) :. businessChatInfoRow business)
insertedRowId db insertedRowId db
let hostVRange = adjustedMemberVRange vr peerChatVRange let hostVRange = adjustedMemberVRange vr peerChatVRange
GroupMember {groupMemberId} <- createContactMemberInv_ db user groupId Nothing contact fromMember GCHostMember GSMemInvited IBUnknown Nothing currentTs hostVRange GroupMember {groupMemberId} <- createContactMemberInv_ db user groupId Nothing contact fromMember GCHostMember GSMemInvited IBUnknown Nothing currentTs hostVRange
@ -532,7 +540,7 @@ createGroupInvitedViaLink
created_at, updated_at, chat_ts, user_member_profile_sent_at, business_chat, business_member_id, customer_member_id) created_at, updated_at, chat_ts, user_member_profile_sent_at, business_chat, business_member_id, customer_member_id)
VALUES (?,?,?,?,?,?,?,?,?,?,?,?) VALUES (?,?,?,?,?,?,?,?,?,?,?,?)
|] |]
((profileId, localDisplayName, customUserProfileId, userId, True, currentTs, currentTs, currentTs, currentTs) :. businessChatInfoRow business) ((profileId, localDisplayName, customUserProfileId, userId, BI True, currentTs, currentTs, currentTs, currentTs) :. businessChatInfoRow business)
insertedRowId db insertedRowId db
insertHost_ currentTs groupId = do insertHost_ currentTs groupId = do
let fromMemberProfile = profileFromName fromMemberName let fromMemberProfile = profileFromName fromMemberName
@ -632,24 +640,28 @@ getUserGroups db vr user@User {userId} = do
getUserGroupDetails :: DB.Connection -> VersionRangeChat -> User -> Maybe ContactId -> Maybe String -> IO [GroupInfo] getUserGroupDetails :: DB.Connection -> VersionRangeChat -> User -> Maybe ContactId -> Maybe String -> IO [GroupInfo]
getUserGroupDetails db vr User {userId, userContactId} _contactId_ search_ = do getUserGroupDetails db vr User {userId, userContactId} _contactId_ search_ = do
g_ <- map (toGroupInfo vr userContactId []) g_ <-
<$> DB.query map (toGroupInfo vr userContactId [])
db <$> DB.query
[sql| db
SELECT [sql|
g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.description, gp.image, SELECT
g.host_conn_custom_user_profile_id, g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.description, gp.image,
g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_chat, g.business_member_id, g.customer_member_id, g.ui_themes, g.custom_data, g.host_conn_custom_user_profile_id, g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences,
mu.group_member_id, g.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category, mu.member_status, mu.show_messages, mu.member_restriction, g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_chat, g.business_member_id, g.customer_member_id, g.ui_themes, g.custom_data,
mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id, pu.display_name, pu.full_name, pu.image, pu.contact_link, pu.local_alias, pu.preferences mu.group_member_id, g.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category, mu.member_status, mu.show_messages, mu.member_restriction,
FROM groups g mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id, pu.display_name, pu.full_name, pu.image, pu.contact_link, pu.local_alias, pu.preferences
JOIN group_profiles gp USING (group_profile_id) FROM groups g
JOIN group_members mu USING (group_id) JOIN group_profiles gp USING (group_profile_id)
JOIN contact_profiles pu ON pu.contact_profile_id = COALESCE(mu.member_profile_id, mu.contact_profile_id) JOIN group_members mu USING (group_id)
WHERE g.user_id = ? AND mu.contact_id = ? JOIN contact_profiles pu ON pu.contact_profile_id = COALESCE(mu.member_profile_id, mu.contact_profile_id)
AND (gp.display_name LIKE '%' || ? || '%' OR gp.full_name LIKE '%' || ? || '%' OR gp.description LIKE '%' || ? || '%') WHERE g.user_id = ? AND mu.contact_id = ?
|] AND (LOWER(gp.display_name) LIKE '%' || LOWER(?) || '%'
(userId, userContactId, search, search, search) OR LOWER(gp.full_name) LIKE '%' || LOWER(?) || '%'
OR LOWER(gp.description) LIKE '%' || LOWER(?) || '%'
)
|]
(userId, userContactId, search, search, search)
mapM (addGroupChatTags db) g_ mapM (addGroupChatTags db) g_
where where
search = fromMaybe "" search_ search = fromMaybe "" search_
@ -958,7 +970,7 @@ createBusinessRequestGroup
created_at, updated_at, chat_ts, user_member_profile_sent_at, business_chat, business_xcontact_id) created_at, updated_at, chat_ts, user_member_profile_sent_at, business_chat, business_xcontact_id)
VALUES (?,?,?,?,?,?,?,?,?,?) VALUES (?,?,?,?,?,?,?,?,?,?)
|] |]
(profileId, localDisplayName, userId, True, currentTs, currentTs, currentTs, currentTs, BCCustomer, xContactId) (profileId, localDisplayName, userId, BI True, currentTs, currentTs, currentTs, currentTs, BCCustomer, xContactId)
insertedRowId db insertedRowId db
memberId <- liftIO $ encodedRandomBytes gVar 12 memberId <- liftIO $ encodedRandomBytes gVar 12
membership <- createContactMemberInv_ db user groupId Nothing user (MemberIdRole (MemberId memberId) GROwner) GCUserMember GSMemCreator IBUser Nothing currentTs vr membership <- createContactMemberInv_ db user groupId Nothing user (MemberIdRole (MemberId memberId) GROwner) GCUserMember GSMemCreator IBUser Nothing currentTs vr
@ -1193,57 +1205,47 @@ createIntroductions db chatV members toMember = do
updateIntroStatus :: DB.Connection -> Int64 -> GroupMemberIntroStatus -> IO () updateIntroStatus :: DB.Connection -> Int64 -> GroupMemberIntroStatus -> IO ()
updateIntroStatus db introId introStatus = do updateIntroStatus db introId introStatus = do
currentTs <- getCurrentTime currentTs <- getCurrentTime
DB.executeNamed DB.execute
db db
[sql| [sql|
UPDATE group_member_intros UPDATE group_member_intros
SET intro_status = :intro_status, updated_at = :updated_at SET intro_status = ?, updated_at = ?
WHERE group_member_intro_id = :intro_id WHERE group_member_intro_id = ?
|] |]
[":intro_status" := introStatus, ":updated_at" := currentTs, ":intro_id" := introId] (introStatus, currentTs, introId)
saveIntroInvitation :: DB.Connection -> GroupMember -> GroupMember -> IntroInvitation -> ExceptT StoreError IO GroupMemberIntro saveIntroInvitation :: DB.Connection -> GroupMember -> GroupMember -> IntroInvitation -> ExceptT StoreError IO GroupMemberIntro
saveIntroInvitation db reMember toMember introInv@IntroInvitation {groupConnReq} = do saveIntroInvitation db reMember toMember introInv@IntroInvitation {groupConnReq} = do
intro <- getIntroduction db reMember toMember intro <- getIntroduction db reMember toMember
liftIO $ do liftIO $ do
currentTs <- getCurrentTime currentTs <- getCurrentTime
DB.executeNamed DB.execute
db db
[sql| [sql|
UPDATE group_member_intros UPDATE group_member_intros
SET intro_status = :intro_status, SET intro_status = ?,
group_queue_info = :group_queue_info, group_queue_info = ?,
direct_queue_info = :direct_queue_info, direct_queue_info = ?,
updated_at = :updated_at updated_at = ?
WHERE group_member_intro_id = :intro_id WHERE group_member_intro_id = ?
|] |]
[ ":intro_status" := GMIntroInvReceived, (GMIntroInvReceived, groupConnReq, directConnReq introInv, currentTs, introId intro)
":group_queue_info" := groupConnReq,
":direct_queue_info" := directConnReq introInv,
":updated_at" := currentTs,
":intro_id" := introId intro
]
pure intro {introInvitation = Just introInv, introStatus = GMIntroInvReceived} pure intro {introInvitation = Just introInv, introStatus = GMIntroInvReceived}
saveMemberInvitation :: DB.Connection -> GroupMember -> IntroInvitation -> IO () saveMemberInvitation :: DB.Connection -> GroupMember -> IntroInvitation -> IO ()
saveMemberInvitation db GroupMember {groupMemberId} IntroInvitation {groupConnReq, directConnReq} = do saveMemberInvitation db GroupMember {groupMemberId} IntroInvitation {groupConnReq, directConnReq} = do
currentTs <- getCurrentTime currentTs <- getCurrentTime
DB.executeNamed DB.execute
db db
[sql| [sql|
UPDATE group_members UPDATE group_members
SET member_status = :member_status, SET member_status = ?,
group_queue_info = :group_queue_info, group_queue_info = ?,
direct_queue_info = :direct_queue_info, direct_queue_info = ?,
updated_at = :updated_at updated_at = ?
WHERE group_member_id = :group_member_id WHERE group_member_id = ?
|] |]
[ ":member_status" := GSMemIntroInvited, (GSMemIntroInvited, groupConnReq, directConnReq, currentTs, groupMemberId)
":group_queue_info" := groupConnReq,
":direct_queue_info" := directConnReq,
":updated_at" := currentTs,
":group_member_id" := groupMemberId
]
getIntroduction :: DB.Connection -> GroupMember -> GroupMember -> ExceptT StoreError IO GroupMemberIntro getIntroduction :: DB.Connection -> GroupMember -> GroupMember -> ExceptT StoreError IO GroupMemberIntro
getIntroduction db reMember toMember = ExceptT $ do getIntroduction db reMember toMember = ExceptT $ do
@ -1364,14 +1366,14 @@ createIntroToMemberContact db user@User {userId} GroupMember {memberContactId =
pure contactId pure contactId
updateMember_ :: Int64 -> UTCTime -> IO () updateMember_ :: Int64 -> UTCTime -> IO ()
updateMember_ contactId ts = updateMember_ contactId ts =
DB.executeNamed DB.execute
db db
[sql| [sql|
UPDATE group_members UPDATE group_members
SET contact_id = :contact_id, updated_at = :updated_at SET contact_id = ?, updated_at = ?
WHERE group_member_id = :group_member_id WHERE group_member_id = ?
|] |]
[":contact_id" := contactId, ":updated_at" := ts, ":group_member_id" := groupMemberId] (contactId, ts, groupMemberId)
createMemberConnection_ :: DB.Connection -> UserId -> Int64 -> ConnId -> VersionChat -> VersionRangeChat -> Maybe Int64 -> Int -> UTCTime -> SubscriptionMode -> IO Connection createMemberConnection_ :: DB.Connection -> UserId -> Int64 -> ConnId -> VersionChat -> VersionRangeChat -> Maybe Int64 -> Int -> UTCTime -> SubscriptionMode -> IO Connection
createMemberConnection_ db userId groupMemberId agentConnId chatV peerChatVRange viaContact connLevel currentTs subMode = createMemberConnection_ db userId groupMemberId agentConnId chatV peerChatVRange viaContact connLevel currentTs subMode =
@ -1379,42 +1381,43 @@ createMemberConnection_ db userId groupMemberId agentConnId chatV peerChatVRange
getViaGroupMember :: DB.Connection -> VersionRangeChat -> User -> Contact -> IO (Maybe (GroupInfo, GroupMember)) getViaGroupMember :: DB.Connection -> VersionRangeChat -> User -> Contact -> IO (Maybe (GroupInfo, GroupMember))
getViaGroupMember db vr User {userId, userContactId} Contact {contactId} = do getViaGroupMember db vr User {userId, userContactId} Contact {contactId} = do
gm_ <- maybeFirstRow toGroupAndMember $ gm_ <-
DB.query maybeFirstRow toGroupAndMember $
db DB.query
[sql| db
SELECT [sql|
-- GroupInfo SELECT
g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.description, gp.image, -- GroupInfo
g.host_conn_custom_user_profile_id, g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.description, gp.image,
g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_chat, g.business_member_id, g.customer_member_id, g.ui_themes, g.custom_data, g.host_conn_custom_user_profile_id, g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences,
-- GroupInfo {membership} g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_chat, g.business_member_id, g.customer_member_id, g.ui_themes, g.custom_data,
mu.group_member_id, mu.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category, -- GroupInfo {membership}
mu.member_status, mu.show_messages, mu.member_restriction, mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id, mu.group_member_id, mu.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category,
-- GroupInfo {membership = GroupMember {memberProfile}} mu.member_status, mu.show_messages, mu.member_restriction, mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id,
pu.display_name, pu.full_name, pu.image, pu.contact_link, pu.local_alias, pu.preferences, -- GroupInfo {membership = GroupMember {memberProfile}}
-- via GroupMember pu.display_name, pu.full_name, pu.image, pu.contact_link, pu.local_alias, pu.preferences,
m.group_member_id, m.group_id, m.member_id, m.peer_chat_min_version, m.peer_chat_max_version, m.member_role, m.member_category, m.member_status, m.show_messages, m.member_restriction, -- via GroupMember
m.invited_by, m.invited_by_group_member_id, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, p.display_name, p.full_name, p.image, p.contact_link, p.local_alias, p.preferences, m.group_member_id, m.group_id, m.member_id, m.peer_chat_min_version, m.peer_chat_max_version, m.member_role, m.member_category, m.member_status, m.show_messages, m.member_restriction,
c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id, m.invited_by, m.invited_by_group_member_id, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, p.display_name, p.full_name, p.image, p.contact_link, p.local_alias, p.preferences,
c.conn_status, c.conn_type, c.contact_conn_initiated, c.local_alias, c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id,
c.created_at, c.security_code, c.security_code_verified_at, c.pq_support, c.pq_encryption, c.pq_snd_enabled, c.pq_rcv_enabled, c.auth_err_counter, c.quota_err_counter, c.conn_status, c.conn_type, c.contact_conn_initiated, c.local_alias, c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id,
c.conn_chat_version, c.peer_chat_min_version, c.peer_chat_max_version c.created_at, c.security_code, c.security_code_verified_at, c.pq_support, c.pq_encryption, c.pq_snd_enabled, c.pq_rcv_enabled, c.auth_err_counter, c.quota_err_counter,
FROM group_members m c.conn_chat_version, c.peer_chat_min_version, c.peer_chat_max_version
JOIN contacts ct ON ct.contact_id = m.contact_id FROM group_members m
JOIN contact_profiles p ON p.contact_profile_id = COALESCE(m.member_profile_id, m.contact_profile_id) JOIN contacts ct ON ct.contact_id = m.contact_id
JOIN groups g ON g.group_id = m.group_id AND g.group_id = ct.via_group JOIN contact_profiles p ON p.contact_profile_id = COALESCE(m.member_profile_id, m.contact_profile_id)
JOIN group_profiles gp USING (group_profile_id) JOIN groups g ON g.group_id = m.group_id AND g.group_id = ct.via_group
JOIN group_members mu ON g.group_id = mu.group_id JOIN group_profiles gp USING (group_profile_id)
JOIN contact_profiles pu ON pu.contact_profile_id = COALESCE(mu.member_profile_id, mu.contact_profile_id) JOIN group_members mu ON g.group_id = mu.group_id
LEFT JOIN connections c ON c.connection_id = ( JOIN contact_profiles pu ON pu.contact_profile_id = COALESCE(mu.member_profile_id, mu.contact_profile_id)
SELECT max(cc.connection_id) LEFT JOIN connections c ON c.connection_id = (
FROM connections cc SELECT max(cc.connection_id)
where cc.user_id = ? AND cc.group_member_id = m.group_member_id FROM connections cc
) where cc.user_id = ? AND cc.group_member_id = m.group_member_id
WHERE ct.user_id = ? AND ct.contact_id = ? AND mu.contact_id = ? AND ct.deleted = 0 )
|] WHERE ct.user_id = ? AND ct.contact_id = ? AND mu.contact_id = ? AND ct.deleted = 0
(userId, userId, contactId, userContactId) |]
(userId, userId, contactId, userContactId)
mapM (bitraverse (addGroupChatTags db) pure) gm_ mapM (bitraverse (addGroupChatTags db) pure) gm_
where where
toGroupAndMember :: (GroupInfoRow :. GroupMemberRow :. MaybeConnectionRow) -> (GroupInfo, GroupMember) toGroupAndMember :: (GroupInfoRow :. GroupMemberRow :. MaybeConnectionRow) -> (GroupInfo, GroupMember)
@ -1650,7 +1653,7 @@ createSentProbe db gVar userId to =
DB.execute DB.execute
db db
"INSERT INTO sent_probes (contact_id, group_member_id, probe, user_id, created_at, updated_at) VALUES (?,?,?,?,?,?)" "INSERT INTO sent_probes (contact_id, group_member_id, probe, user_id, created_at, updated_at) VALUES (?,?,?,?,?,?)"
(ctId, gmId, probe, userId, currentTs, currentTs) (ctId, gmId, Binary probe, userId, currentTs, currentTs)
(Probe probe,) <$> insertedRowId db (Probe probe,) <$> insertedRowId db
createSentProbeHash :: DB.Connection -> UserId -> Int64 -> ContactOrMember -> IO () createSentProbeHash :: DB.Connection -> UserId -> Int64 -> ContactOrMember -> IO ()
@ -1676,13 +1679,13 @@ matchReceivedProbe db vr user@User {userId} from (Probe probe) = do
LEFT JOIN groups g ON g.group_id = m.group_id LEFT JOIN groups g ON g.group_id = m.group_id
WHERE r.user_id = ? AND r.probe_hash = ? AND r.probe IS NULL WHERE r.user_id = ? AND r.probe_hash = ? AND r.probe IS NULL
|] |]
(userId, probeHash) (userId, Binary probeHash)
currentTs <- getCurrentTime currentTs <- getCurrentTime
let (ctId, gmId) = contactOrMemberIds from let (ctId, gmId) = contactOrMemberIds from
DB.execute DB.execute
db db
"INSERT INTO received_probes (contact_id, group_member_id, probe, probe_hash, user_id, created_at, updated_at) VALUES (?,?,?,?,?,?,?)" "INSERT INTO received_probes (contact_id, group_member_id, probe, probe_hash, user_id, created_at, updated_at) VALUES (?,?,?,?,?,?,?)"
(ctId, gmId, probe, probeHash, userId, currentTs, currentTs) (ctId, gmId, Binary probe, Binary probeHash, userId, currentTs, currentTs)
let cgmIds' = filterFirstContactId cgmIds let cgmIds' = filterFirstContactId cgmIds
catMaybes <$> mapM (getContactOrMember_ db vr user) cgmIds' catMaybes <$> mapM (getContactOrMember_ db vr user) cgmIds'
where where
@ -1708,13 +1711,13 @@ matchReceivedProbeHash db vr user@User {userId} from (ProbeHash probeHash) = do
LEFT JOIN groups g ON g.group_id = m.group_id LEFT JOIN groups g ON g.group_id = m.group_id
WHERE r.user_id = ? AND r.probe_hash = ? AND r.probe IS NOT NULL WHERE r.user_id = ? AND r.probe_hash = ? AND r.probe IS NOT NULL
|] |]
(userId, probeHash) (userId, Binary probeHash)
currentTs <- getCurrentTime currentTs <- getCurrentTime
let (ctId, gmId) = contactOrMemberIds from let (ctId, gmId) = contactOrMemberIds from
DB.execute DB.execute
db db
"INSERT INTO received_probes (contact_id, group_member_id, probe_hash, user_id, created_at, updated_at) VALUES (?,?,?,?,?,?)" "INSERT INTO received_probes (contact_id, group_member_id, probe_hash, user_id, created_at, updated_at) VALUES (?,?,?,?,?,?)"
(ctId, gmId, probeHash, userId, currentTs, currentTs) (ctId, gmId, Binary probeHash, userId, currentTs, currentTs)
pure probeIds $>>= \(Only probe :. cgmIds) -> (,Probe probe) <$$> getContactOrMember_ db vr user cgmIds pure probeIds $>>= \(Only probe :. cgmIds) -> (,Probe probe) <$$> getContactOrMember_ db vr user cgmIds
matchSentProbe :: DB.Connection -> VersionRangeChat -> User -> ContactOrMember -> Probe -> IO (Maybe ContactOrMember) matchSentProbe :: DB.Connection -> VersionRangeChat -> User -> ContactOrMember -> Probe -> IO (Maybe ContactOrMember)
@ -1736,7 +1739,7 @@ matchSentProbe db vr user@User {userId} _from (Probe probe) = do
WHERE s.user_id = ? AND s.probe = ? WHERE s.user_id = ? AND s.probe = ?
AND (h.contact_id = ? OR h.group_member_id = ?) AND (h.contact_id = ? OR h.group_member_id = ?)
|] |]
(userId, probe, ctId, gmId) (userId, Binary probe, ctId, gmId)
getContactOrMember_ :: DB.Connection -> VersionRangeChat -> User -> (Maybe ContactId, Maybe GroupId, Maybe GroupMemberId) -> IO (Maybe ContactOrMember) getContactOrMember_ :: DB.Connection -> VersionRangeChat -> User -> (Maybe ContactId, Maybe GroupId, Maybe GroupMemberId) -> IO (Maybe ContactOrMember)
getContactOrMember_ db vr user ids = getContactOrMember_ db vr user ids =
@ -1777,22 +1780,18 @@ mergeContactRecords db vr user@User {userId} to@Contact {localDisplayName = keep
db db
"UPDATE chat_items SET contact_id = ?, updated_at = ? WHERE contact_id = ? AND user_id = ?" "UPDATE chat_items SET contact_id = ?, updated_at = ? WHERE contact_id = ? AND user_id = ?"
(toContactId, currentTs, fromContactId, userId) (toContactId, currentTs, fromContactId, userId)
DB.executeNamed DB.execute
db db
[sql| [sql|
UPDATE group_members UPDATE group_members
SET contact_id = :to_contact_id, SET contact_id = ?,
local_display_name = (SELECT local_display_name FROM contacts WHERE contact_id = :to_contact_id), local_display_name = (SELECT local_display_name FROM contacts WHERE contact_id = ?),
contact_profile_id = (SELECT contact_profile_id FROM contacts WHERE contact_id = :to_contact_id), contact_profile_id = (SELECT contact_profile_id FROM contacts WHERE contact_id = ?),
updated_at = :updated_at updated_at = ?
WHERE contact_id = :from_contact_id WHERE contact_id = ?
AND user_id = :user_id AND user_id = ?
|] |]
[ ":to_contact_id" := toContactId, (toContactId, toContactId, toContactId, currentTs, fromContactId, userId)
":from_contact_id" := fromContactId,
":user_id" := userId,
":updated_at" := currentTs
]
deleteContactProfile_ db userId fromContactId deleteContactProfile_ db userId fromContactId
DB.execute db "DELETE FROM contacts WHERE contact_id = ? AND user_id = ?" (fromContactId, userId) DB.execute db "DELETE FROM contacts WHERE contact_id = ? AND user_id = ?" (fromContactId, userId)
deleteUnusedDisplayName_ db userId fromLDN deleteUnusedDisplayName_ db userId fromLDN
@ -1867,41 +1866,44 @@ associateContactWithMemberRecord
deleteUnusedDisplayName_ :: DB.Connection -> UserId -> ContactName -> IO () deleteUnusedDisplayName_ :: DB.Connection -> UserId -> ContactName -> IO ()
deleteUnusedDisplayName_ db userId localDisplayName = deleteUnusedDisplayName_ db userId localDisplayName =
DB.executeNamed DB.execute
db db
[sql| [sql|
DELETE FROM display_names DELETE FROM display_names
WHERE user_id = :user_id AND local_display_name = :local_display_name WHERE user_id = ? AND local_display_name = ?
AND 1 NOT IN ( AND 1 NOT IN (
SELECT 1 FROM users SELECT 1 FROM users
WHERE local_display_name = :local_display_name LIMIT 1 WHERE local_display_name = ? LIMIT 1
) )
AND 1 NOT IN ( AND 1 NOT IN (
SELECT 1 FROM contacts SELECT 1 FROM contacts
WHERE user_id = :user_id AND local_display_name = :local_display_name LIMIT 1 WHERE user_id = ? AND local_display_name = ? LIMIT 1
) )
AND 1 NOT IN ( AND 1 NOT IN (
SELECT 1 FROM groups SELECT 1 FROM groups
WHERE user_id = :user_id AND local_display_name = :local_display_name LIMIT 1 WHERE user_id = ? AND local_display_name = ? LIMIT 1
) )
AND 1 NOT IN ( AND 1 NOT IN (
SELECT 1 FROM group_members SELECT 1 FROM group_members
WHERE user_id = :user_id AND local_display_name = :local_display_name LIMIT 1 WHERE user_id = ? AND local_display_name = ? LIMIT 1
) )
AND 1 NOT IN ( AND 1 NOT IN (
SELECT 1 FROM user_contact_links SELECT 1 FROM user_contact_links
WHERE user_id = :user_id AND local_display_name = :local_display_name LIMIT 1 WHERE user_id = ? AND local_display_name = ? LIMIT 1
) )
AND 1 NOT IN ( AND 1 NOT IN (
SELECT 1 FROM contact_requests SELECT 1 FROM contact_requests
WHERE user_id = :user_id AND local_display_name = :local_display_name LIMIT 1 WHERE user_id = ? AND local_display_name = ? LIMIT 1
) )
AND 1 NOT IN ( AND 1 NOT IN (
SELECT 1 FROM contact_requests SELECT 1 FROM contact_requests
WHERE user_id = :user_id AND local_display_name = :local_display_name LIMIT 1 WHERE user_id = ? AND local_display_name = ? LIMIT 1
) )
|] |]
[":user_id" := userId, ":local_display_name" := localDisplayName] ( (userId, localDisplayName, localDisplayName, userId, localDisplayName, userId, localDisplayName)
:. (userId, localDisplayName, userId, localDisplayName, userId, localDisplayName)
:. (userId, localDisplayName)
)
deleteOldProbes :: DB.Connection -> UTCTime -> IO () deleteOldProbes :: DB.Connection -> UTCTime -> IO ()
deleteOldProbes db createdAtCutoff = do deleteOldProbes db createdAtCutoff = do
@ -1911,7 +1913,7 @@ deleteOldProbes db createdAtCutoff = do
updateGroupSettings :: DB.Connection -> User -> Int64 -> ChatSettings -> IO () updateGroupSettings :: DB.Connection -> User -> Int64 -> ChatSettings -> IO ()
updateGroupSettings db User {userId} groupId ChatSettings {enableNtfs, sendRcpts, favorite} = updateGroupSettings db User {userId} groupId ChatSettings {enableNtfs, sendRcpts, favorite} =
DB.execute db "UPDATE groups SET enable_ntfs = ?, send_rcpts = ?, favorite = ? WHERE user_id = ? AND group_id = ?" (enableNtfs, sendRcpts, favorite, userId, groupId) DB.execute db "UPDATE groups SET enable_ntfs = ?, send_rcpts = ?, favorite = ? WHERE user_id = ? AND group_id = ?" (enableNtfs, BI <$> sendRcpts, BI favorite, userId, groupId)
updateGroupMemberSettings :: DB.Connection -> User -> GroupId -> GroupMemberId -> GroupMemberSettings -> IO () updateGroupMemberSettings :: DB.Connection -> User -> GroupId -> GroupMemberId -> GroupMemberSettings -> IO ()
updateGroupMemberSettings db User {userId} gId gMemberId GroupMemberSettings {showMessages} = do updateGroupMemberSettings db User {userId} gId gMemberId GroupMemberSettings {showMessages} = do
@ -1923,7 +1925,7 @@ updateGroupMemberSettings db User {userId} gId gMemberId GroupMemberSettings {sh
SET show_messages = ?, updated_at = ? SET show_messages = ?, updated_at = ?
WHERE user_id = ? AND group_id = ? AND group_member_id = ? WHERE user_id = ? AND group_id = ? AND group_member_id = ?
|] |]
(showMessages, currentTs, userId, gId, gMemberId) (BI showMessages, currentTs, userId, gId, gMemberId)
updateGroupMemberBlocked :: DB.Connection -> User -> GroupId -> GroupMemberId -> MemberRestrictionStatus -> IO () updateGroupMemberBlocked :: DB.Connection -> User -> GroupId -> GroupMemberId -> MemberRestrictionStatus -> IO ()
updateGroupMemberBlocked db User {userId} gId gMemberId memberBlocked = do updateGroupMemberBlocked db User {userId} gId gMemberId memberBlocked = do
@ -2025,8 +2027,8 @@ createMemberContact
contact_group_member_id, contact_grp_inv_sent, created_at, updated_at, chat_ts contact_group_member_id, contact_grp_inv_sent, created_at, updated_at, chat_ts
) VALUES (?,?,?,?,?,?,?,?,?,?,?) ) VALUES (?,?,?,?,?,?,?,?,?,?,?)
|] |]
( (userId, localDisplayName, memberContactProfileId, True, userPreferences, True) ( (userId, localDisplayName, memberContactProfileId, BI True, userPreferences, BI True)
:. (groupMemberId, False, currentTs, currentTs, currentTs) :. (groupMemberId, BI False, currentTs, currentTs, currentTs)
) )
contactId <- insertedRowId db contactId <- insertedRowId db
DB.execute DB.execute
@ -2041,8 +2043,8 @@ createMemberContact
conn_chat_version, peer_chat_min_version, peer_chat_max_version, created_at, updated_at, to_subscribe conn_chat_version, peer_chat_min_version, peer_chat_max_version, created_at, updated_at, to_subscribe
) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) ) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
|] |]
( (userId, acId, cReq, connLevel, ConnNew, ConnContact, True, contactId, customUserProfileId) ( (userId, acId, cReq, connLevel, ConnNew, ConnContact, BI True, contactId, customUserProfileId)
:. (connChatVersion, minV, maxV, currentTs, currentTs, subMode == SMOnlyCreate) :. (connChatVersion, minV, maxV, currentTs, currentTs, BI (subMode == SMOnlyCreate))
) )
connId <- insertedRowId db connId <- insertedRowId db
let ctConn = let ctConn =
@ -2093,7 +2095,7 @@ setContactGrpInvSent db Contact {contactId} xGrpDirectInvSent = do
DB.execute DB.execute
db db
"UPDATE contacts SET contact_grp_inv_sent = ?, updated_at = ? WHERE contact_id = ?" "UPDATE contacts SET contact_grp_inv_sent = ?, updated_at = ? WHERE contact_id = ?"
(xGrpDirectInvSent, currentTs, contactId) (BI xGrpDirectInvSent, currentTs, contactId)
createMemberContactInvited :: DB.Connection -> User -> (CommandId, ConnId) -> GroupInfo -> GroupMember -> Connection -> SubscriptionMode -> IO (Contact, GroupMember) createMemberContactInvited :: DB.Connection -> User -> (CommandId, ConnId) -> GroupInfo -> GroupMember -> Connection -> SubscriptionMode -> IO (Contact, GroupMember)
createMemberContactInvited createMemberContactInvited
@ -2123,7 +2125,7 @@ createMemberContactInvited
created_at, updated_at, chat_ts created_at, updated_at, chat_ts
) VALUES (?,?,?,?,?,?,?,?,?) ) VALUES (?,?,?,?,?,?,?,?,?)
|] |]
( (userId, memberLDN, memberContactProfileId, True, userPreferences, True) ( (userId, memberLDN, memberContactProfileId, BI True, userPreferences, BI True)
:. (currentTs, currentTs, currentTs) :. (currentTs, currentTs, currentTs)
) )
contactId <- insertedRowId db contactId <- insertedRowId db
@ -2175,7 +2177,7 @@ createMemberContactConn_
) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?) ) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)
|] |]
( (userId, acId, connLevel, ConnJoined, ConnContact, contactId, customUserProfileId) ( (userId, acId, connLevel, ConnJoined, ConnContact, contactId, customUserProfileId)
:. (connChatVersion, minV, maxV, currentTs, currentTs, subMode == SMOnlyCreate) :. (connChatVersion, minV, maxV, currentTs, currentTs, BI (subMode == SMOnlyCreate))
) )
connId <- insertedRowId db connId <- insertedRowId db
setCommandConnId db user cmdId connId setCommandConnId db user cmdId connId
@ -2244,7 +2246,7 @@ updateContactMemberProfile db user@User {userId} m ct@Contact {contactId} p'
getXGrpLinkMemReceived :: DB.Connection -> GroupMemberId -> ExceptT StoreError IO Bool getXGrpLinkMemReceived :: DB.Connection -> GroupMemberId -> ExceptT StoreError IO Bool
getXGrpLinkMemReceived db mId = getXGrpLinkMemReceived db mId =
ExceptT . firstRow fromOnly (SEGroupMemberNotFound mId) $ ExceptT . firstRow fromOnlyBI (SEGroupMemberNotFound mId) $
DB.query db "SELECT xgrplinkmem_received FROM group_members WHERE group_member_id = ?" (Only mId) DB.query db "SELECT xgrplinkmem_received FROM group_members WHERE group_member_id = ?" (Only mId)
setXGrpLinkMemReceived :: DB.Connection -> GroupMemberId -> Bool -> IO () setXGrpLinkMemReceived :: DB.Connection -> GroupMemberId -> Bool -> IO ()
@ -2253,7 +2255,7 @@ setXGrpLinkMemReceived db mId xGrpLinkMemReceived = do
DB.execute DB.execute
db db
"UPDATE group_members SET xgrplinkmem_received = ?, updated_at = ? WHERE group_member_id = ?" "UPDATE group_members SET xgrplinkmem_received = ?, updated_at = ? WHERE group_member_id = ?"
(xGrpLinkMemReceived, currentTs, mId) (BI xGrpLinkMemReceived, currentTs, mId)
createNewUnknownGroupMember :: DB.Connection -> VersionRangeChat -> User -> GroupInfo -> MemberId -> Text -> ExceptT StoreError IO GroupMember createNewUnknownGroupMember :: DB.Connection -> VersionRangeChat -> User -> GroupInfo -> MemberId -> Text -> ExceptT StoreError IO GroupMember
createNewUnknownGroupMember db vr user@User {userId, userContactId} GroupInfo {groupId} memberId memberName = do createNewUnknownGroupMember db vr user@User {userId, userContactId} GroupInfo {groupId} memberId memberName = do

View file

@ -1,3 +1,4 @@
{-# LANGUAGE CPP #-}
{-# LANGUAGE DataKinds #-} {-# LANGUAGE DataKinds #-}
{-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE GADTs #-} {-# LANGUAGE GADTs #-}
@ -140,8 +141,6 @@ import Data.Text (Text)
import qualified Data.Text as T import qualified Data.Text as T
import Data.Time (addUTCTime) import Data.Time (addUTCTime)
import Data.Time.Clock (UTCTime (..), getCurrentTime) import Data.Time.Clock (UTCTime (..), getCurrentTime)
import Database.SQLite.Simple (FromRow, NamedParam (..), Only (..), Query, ToRow, (:.) (..))
import Database.SQLite.Simple.QQ (sql)
import Simplex.Chat.Controller (ChatListQuery (..), ChatPagination (..), ContentFilter (..), PaginationByTime (..)) import Simplex.Chat.Controller (ChatListQuery (..), ChatPagination (..), ContentFilter (..), PaginationByTime (..))
import Simplex.Chat.Markdown import Simplex.Chat.Markdown
import Simplex.Chat.Messages import Simplex.Chat.Messages
@ -160,6 +159,13 @@ import qualified Simplex.Messaging.Crypto as C
import Simplex.Messaging.Crypto.File (CryptoFile (..), CryptoFileArgs (..)) import Simplex.Messaging.Crypto.File (CryptoFile (..), CryptoFileArgs (..))
import Simplex.Messaging.Util (eitherToMaybe) import Simplex.Messaging.Util (eitherToMaybe)
import UnliftIO.STM import UnliftIO.STM
#if defined(dbPostgres)
import Database.PostgreSQL.Simple (FromRow, Only (..), Query, ToRow, (:.) (..))
import Database.PostgreSQL.Simple.SqlQQ (sql)
#else
import Database.SQLite.Simple (FromRow, Only (..), Query, ToRow, (:.) (..))
import Database.SQLite.Simple.QQ (sql)
#endif
deleteContactCIs :: DB.Connection -> User -> Contact -> IO () deleteContactCIs :: DB.Connection -> User -> Contact -> IO ()
deleteContactCIs db user@User {userId} ct@Contact {contactId} = do deleteContactCIs db user@User {userId} ct@Contact {contactId} = do
@ -200,7 +206,7 @@ createNewSndMessage db gVar connOrGroupId chatMsgEvent encodeMessage =
shared_msg_id, shared_msg_id_user, created_at, updated_at shared_msg_id, shared_msg_id_user, created_at, updated_at
) VALUES (?,?,?,?,?,?,?,?,?) ) VALUES (?,?,?,?,?,?,?,?,?)
|] |]
(MDSnd, toCMEventTag chatMsgEvent, msgBody, connId_, groupId_, sharedMsgId, Just True, createdAt, createdAt) (MDSnd, toCMEventTag chatMsgEvent, DB.Binary msgBody, connId_, groupId_, DB.Binary sharedMsgId, Just (BI True), createdAt, createdAt)
msgId <- insertedRowId db msgId <- insertedRowId db
pure $ Right SndMessage {msgId, sharedMsgId = SharedMsgId sharedMsgId, msgBody} pure $ Right SndMessage {msgId, sharedMsgId = SharedMsgId sharedMsgId, msgBody}
where where
@ -285,7 +291,7 @@ createNewRcvMessage db connOrGroupId NewRcvMessage {chatMsgEvent, msgBody} share
(msg_sent, chat_msg_event, msg_body, created_at, updated_at, connection_id, group_id, shared_msg_id, author_group_member_id, forwarded_by_group_member_id) (msg_sent, chat_msg_event, msg_body, created_at, updated_at, connection_id, group_id, shared_msg_id, author_group_member_id, forwarded_by_group_member_id)
VALUES (?,?,?,?,?,?,?,?,?,?) VALUES (?,?,?,?,?,?,?,?,?,?)
|] |]
(MDRcv, toCMEventTag chatMsgEvent, msgBody, currentTs, currentTs, connId_, groupId_, sharedMsgId_, authorMember, forwardedByMember) (MDRcv, toCMEventTag chatMsgEvent, DB.Binary msgBody, currentTs, currentTs, connId_, groupId_, sharedMsgId_, authorMember, forwardedByMember)
msgId <- insertedRowId db msgId <- insertedRowId db
pure RcvMessage {msgId, chatMsgEvent = ACME (encoding @e) chatMsgEvent, sharedMsgId_, msgBody, authorMember, forwardedByMember} pure RcvMessage {msgId, chatMsgEvent = ACME (encoding @e) chatMsgEvent, sharedMsgId_, msgBody, authorMember, forwardedByMember}
@ -415,13 +421,14 @@ createNewChatItem_ db User {userId} chatDirection msgId_ sharedMsgId ciContent q
fwd_from_tag, fwd_from_chat_name, fwd_from_msg_dir, fwd_from_contact_id, fwd_from_group_id, fwd_from_chat_item_id fwd_from_tag, fwd_from_chat_name, fwd_from_msg_dir, fwd_from_contact_id, fwd_from_group_id, fwd_from_chat_item_id
) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) ) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
|] |]
((userId, msgId_) :. idsRow :. itemRow :. quoteRow :. forwardedFromRow) ((userId, msgId_) :. idsRow :. itemRow :. quoteRow' :. forwardedFromRow)
ciId <- insertedRowId db ciId <- insertedRowId db
forM_ msgId_ $ \msgId -> insertChatItemMessage_ db ciId msgId createdAt forM_ msgId_ $ \msgId -> insertChatItemMessage_ db ciId msgId createdAt
pure ciId pure ciId
where where
itemRow :: (SMsgDirection d, UTCTime, CIContent d, Text, Text, CIStatus d, Maybe MsgContentTag, Maybe SharedMsgId, Maybe GroupMemberId) :. (UTCTime, UTCTime, Maybe Bool) :. (Maybe Int, Maybe UTCTime) itemRow :: (SMsgDirection d, UTCTime, CIContent d, Text, Text, CIStatus d, Maybe MsgContentTag, Maybe SharedMsgId, Maybe GroupMemberId) :. (UTCTime, UTCTime, Maybe BoolInt) :. (Maybe Int, Maybe UTCTime)
itemRow = (msgDirection @d, itemTs, ciContent, toCIContentTag ciContent, ciContentToText ciContent, ciCreateStatus ciContent, msgContentTag <$> ciMsgContent ciContent, sharedMsgId, forwardedByMember) :. (createdAt, createdAt, justTrue live) :. ciTimedRow timed itemRow = (msgDirection @d, itemTs, ciContent, toCIContentTag ciContent, ciContentToText ciContent, ciCreateStatus ciContent, msgContentTag <$> ciMsgContent ciContent, sharedMsgId, forwardedByMember) :. (createdAt, createdAt, BI <$> (justTrue live)) :. ciTimedRow timed
quoteRow' = let (a, b, c, d, e) = quoteRow in (a, b, c, BI <$> d, e)
idsRow :: (Maybe Int64, Maybe Int64, Maybe Int64, Maybe Int64) idsRow :: (Maybe Int64, Maybe Int64, Maybe Int64, Maybe Int64)
idsRow = case chatDirection of idsRow = case chatDirection of
CDDirectRcv Contact {contactId} -> (Just contactId, Nothing, Nothing, Nothing) CDDirectRcv Contact {contactId} -> (Just contactId, Nothing, Nothing, Nothing)
@ -452,11 +459,11 @@ getChatItemQuote_ :: ChatTypeQuotable c => DB.Connection -> User -> ChatDirectio
getChatItemQuote_ db User {userId, userContactId} chatDirection QuotedMsg {msgRef = MsgRef {msgId, sentAt, sent, memberId}, content} = getChatItemQuote_ db User {userId, userContactId} chatDirection QuotedMsg {msgRef = MsgRef {msgId, sentAt, sent, memberId}, content} =
case chatDirection of case chatDirection of
CDDirectRcv Contact {contactId} -> getDirectChatItemQuote_ contactId (not sent) CDDirectRcv Contact {contactId} -> getDirectChatItemQuote_ contactId (not sent)
CDGroupRcv GroupInfo {groupId, membership = GroupMember {memberId = userMemberId}} sender@GroupMember {memberId = senderMemberId} -> CDGroupRcv GroupInfo {groupId, membership = GroupMember {memberId = userMemberId}} sender@GroupMember {groupMemberId = senderGMId, memberId = senderMemberId} ->
case memberId of case memberId of
Just mId Just mId
| mId == userMemberId -> (`ciQuote` CIQGroupSnd) <$> getUserGroupChatItemId_ groupId | mId == userMemberId -> (`ciQuote` CIQGroupSnd) <$> getUserGroupChatItemId_ groupId
| mId == senderMemberId -> (`ciQuote` CIQGroupRcv (Just sender)) <$> getGroupChatItemId_ groupId mId | mId == senderMemberId -> (`ciQuote` CIQGroupRcv (Just sender)) <$> getGroupChatItemId_ groupId senderGMId
| otherwise -> getGroupChatItemQuote_ groupId mId | otherwise -> getGroupChatItemQuote_ groupId mId
_ -> pure . ciQuote Nothing $ CIQGroupRcv Nothing _ -> pure . ciQuote Nothing $ CIQGroupRcv Nothing
where where
@ -468,7 +475,7 @@ getChatItemQuote_ db User {userId, userContactId} chatDirection QuotedMsg {msgRe
DB.query DB.query
db db
"SELECT chat_item_id FROM chat_items WHERE user_id = ? AND contact_id = ? AND shared_msg_id = ? AND item_sent = ?" "SELECT chat_item_id FROM chat_items WHERE user_id = ? AND contact_id = ? AND shared_msg_id = ? AND item_sent = ?"
(userId, contactId, msgId, userSent) (userId, contactId, msgId, BI userSent)
where where
ciQuoteDirect :: Maybe ChatItemId -> CIQuote 'CTDirect ciQuoteDirect :: Maybe ChatItemId -> CIQuote 'CTDirect
ciQuoteDirect = (`ciQuote` if userSent then CIQDirectSnd else CIQDirectRcv) ciQuoteDirect = (`ciQuote` if userSent then CIQDirectSnd else CIQDirectRcv)
@ -479,17 +486,17 @@ getChatItemQuote_ db User {userId, userContactId} chatDirection QuotedMsg {msgRe
db db
"SELECT chat_item_id FROM chat_items WHERE user_id = ? AND group_id = ? AND shared_msg_id = ? AND item_sent = ? AND group_member_id IS NULL" "SELECT chat_item_id FROM chat_items WHERE user_id = ? AND group_id = ? AND shared_msg_id = ? AND item_sent = ? AND group_member_id IS NULL"
(userId, groupId, msgId, MDSnd) (userId, groupId, msgId, MDSnd)
getGroupChatItemId_ :: Int64 -> MemberId -> IO (Maybe ChatItemId) getGroupChatItemId_ :: Int64 -> GroupMemberId -> IO (Maybe ChatItemId)
getGroupChatItemId_ groupId mId = getGroupChatItemId_ groupId groupMemberId =
maybeFirstRow fromOnly $ maybeFirstRow fromOnly $
DB.query DB.query
db db
"SELECT chat_item_id FROM chat_items WHERE user_id = ? AND group_id = ? AND shared_msg_id = ? AND item_sent = ? AND group_member_id = ?" "SELECT chat_item_id FROM chat_items WHERE user_id = ? AND group_id = ? AND shared_msg_id = ? AND item_sent = ? AND group_member_id = ?"
(userId, groupId, msgId, MDRcv, mId) (userId, groupId, msgId, MDRcv, groupMemberId)
getGroupChatItemQuote_ :: Int64 -> MemberId -> IO (CIQuote 'CTGroup) getGroupChatItemQuote_ :: Int64 -> MemberId -> IO (CIQuote 'CTGroup)
getGroupChatItemQuote_ groupId mId = do getGroupChatItemQuote_ groupId mId = do
ciQuoteGroup ciQuoteGroup
<$> DB.queryNamed <$> DB.query
db db
[sql| [sql|
SELECT i.chat_item_id, SELECT i.chat_item_id,
@ -503,10 +510,10 @@ getChatItemQuote_ db User {userId, userContactId} chatDirection QuotedMsg {msgRe
LEFT JOIN chat_items i ON i.user_id = m.user_id LEFT JOIN chat_items i ON i.user_id = m.user_id
AND i.group_id = m.group_id AND i.group_id = m.group_id
AND m.group_member_id = i.group_member_id AND m.group_member_id = i.group_member_id
AND i.shared_msg_id = :msg_id AND i.shared_msg_id = ?
WHERE m.user_id = :user_id AND m.group_id = :group_id AND m.member_id = :member_id WHERE m.user_id = ? AND m.group_id = ? AND m.member_id = ?
|] |]
[":user_id" := userId, ":group_id" := groupId, ":member_id" := mId, ":msg_id" := msgId] (msgId, userId, groupId, mId)
where where
ciQuoteGroup :: [Only (Maybe ChatItemId) :. GroupMemberRow] -> CIQuote 'CTGroup ciQuoteGroup :: [Only (Maybe ChatItemId) :. GroupMemberRow] -> CIQuote 'CTGroup
ciQuoteGroup [] = ciQuote Nothing $ CIQGroupRcv Nothing ciQuoteGroup [] = ciQuote Nothing $ CIQGroupRcv Nothing
@ -564,14 +571,21 @@ findDirectChatPreviews_ db User {userId} pagination clq =
ACPD SCTDirect $ DirectChatPD ts contactId lastItemId_ (toChatStats statsRow) ACPD SCTDirect $ DirectChatPD ts contactId lastItemId_ (toChatStats statsRow)
baseQuery = baseQuery =
[sql| [sql|
SELECT ct.contact_id, ct.chat_ts as ts, LastItems.chat_item_id, COALESCE(ChatStats.UnreadCount, 0), 0, COALESCE(ChatStats.MinUnread, 0), ct.unread_chat SELECT
ct.contact_id,
ct.chat_ts,
(
SELECT chat_item_id
FROM chat_items ci
WHERE ci.user_id = ? AND ci.contact_id = ct.contact_id
ORDER BY ci.created_at DESC
LIMIT 1
) AS chat_item_id,
COALESCE(ChatStats.UnreadCount, 0),
0,
COALESCE(ChatStats.MinUnread, 0),
ct.unread_chat
FROM contacts ct FROM contacts ct
LEFT JOIN (
SELECT contact_id, chat_item_id, MAX(created_at)
FROM chat_items
WHERE user_id = ? AND contact_id IS NOT NULL
GROUP BY contact_id
) LastItems ON LastItems.contact_id = ct.contact_id
LEFT JOIN ( LEFT JOIN (
SELECT contact_id, COUNT(1) AS UnreadCount, MIN(chat_item_id) AS MinUnread SELECT contact_id, COUNT(1) AS UnreadCount, MIN(chat_item_id) AS MinUnread
FROM chat_items FROM chat_items
@ -582,58 +596,61 @@ findDirectChatPreviews_ db User {userId} pagination clq =
baseParams = (userId, userId, CISRcvNew) baseParams = (userId, userId, CISRcvNew)
getPreviews = case clq of getPreviews = case clq of
CLQFilters {favorite = False, unread = False} -> do CLQFilters {favorite = False, unread = False} -> do
let q = baseQuery <> " WHERE ct.user_id = ? AND ct.is_user = 0 AND ct.deleted = 0 AND ct.contact_used" let q = baseQuery <> " WHERE ct.user_id = ? AND ct.is_user = 0 AND ct.deleted = 0 AND ct.contact_used = 1"
p = baseParams :. Only userId p = baseParams :. Only userId
queryWithPagination db q p pagination queryWithPagination q p
CLQFilters {favorite = True, unread = False} -> do CLQFilters {favorite = True, unread = False} -> do
let q = let q =
baseQuery baseQuery
<> " "
<> [sql| <> [sql|
WHERE ct.user_id = ? AND ct.is_user = 0 AND ct.deleted = 0 AND ct.contact_used WHERE ct.user_id = ? AND ct.is_user = 0 AND ct.deleted = 0 AND ct.contact_used = 1
AND ct.favorite = 1 AND ct.favorite = 1
|] |]
p = baseParams :. Only userId p = baseParams :. Only userId
queryWithPagination db q p pagination queryWithPagination q p
CLQFilters {favorite = False, unread = True} -> do CLQFilters {favorite = False, unread = True} -> do
let q = let q =
baseQuery baseQuery
<> " "
<> [sql| <> [sql|
WHERE ct.user_id = ? AND ct.is_user = 0 AND ct.deleted = 0 AND ct.contact_used WHERE ct.user_id = ? AND ct.is_user = 0 AND ct.deleted = 0 AND ct.contact_used = 1
AND (ct.unread_chat = 1 OR ChatStats.UnreadCount > 0) AND (ct.unread_chat = 1 OR ChatStats.UnreadCount > 0)
|] |]
p = baseParams :. Only userId p = baseParams :. Only userId
queryWithPagination db q p pagination queryWithPagination q p
CLQFilters {favorite = True, unread = True} -> do CLQFilters {favorite = True, unread = True} -> do
let q = let q =
baseQuery baseQuery
<> " "
<> [sql| <> [sql|
WHERE ct.user_id = ? AND ct.is_user = 0 AND ct.deleted = 0 AND ct.contact_used WHERE ct.user_id = ? AND ct.is_user = 0 AND ct.deleted = 0 AND ct.contact_used = 1
AND (ct.favorite = 1 AND (ct.favorite = 1
OR ct.unread_chat = 1 OR ChatStats.UnreadCount > 0) OR ct.unread_chat = 1 OR ChatStats.UnreadCount > 0)
|] |]
p = baseParams :. Only userId p = baseParams :. Only userId
queryWithPagination db q p pagination queryWithPagination q p
CLQSearch {search} -> do CLQSearch {search} -> do
let q = let q =
baseQuery baseQuery
<> " "
<> [sql| <> [sql|
JOIN contact_profiles cp ON ct.contact_profile_id = cp.contact_profile_id JOIN contact_profiles cp ON ct.contact_profile_id = cp.contact_profile_id
WHERE ct.user_id = ? AND ct.is_user = 0 AND ct.deleted = 0 AND ct.contact_used WHERE ct.user_id = ? AND ct.is_user = 0 AND ct.deleted = 0 AND ct.contact_used = 1
AND ( AND (
ct.local_display_name LIKE '%' || ? || '%' LOWER(ct.local_display_name) LIKE '%' || LOWER(?) || '%'
OR cp.display_name LIKE '%' || ? || '%' OR LOWER(cp.display_name) LIKE '%' || LOWER(?) || '%'
OR cp.full_name LIKE '%' || ? || '%' OR LOWER(cp.full_name) LIKE '%' || LOWER(?) || '%'
OR cp.local_alias LIKE '%' || ? || '%' OR LOWER(cp.local_alias) LIKE '%' || LOWER(?) || '%'
) )
|] |]
p = baseParams :. (userId, search, search, search, search) p = baseParams :. (userId, search, search, search, search)
queryWithPagination db q p pagination queryWithPagination q p
queryWithPagination :: ToRow p => Query -> p -> IO [(ContactId, UTCTime, Maybe ChatItemId) :. ChatStatsRow]
queryWithPagination :: ToRow p => DB.Connection -> Query -> p -> PaginationByTime -> IO [(ContactId, UTCTime, Maybe ChatItemId) :. ChatStatsRow] queryWithPagination query params = case pagination of
queryWithPagination db query params = \case PTLast count -> DB.query db (query <> " ORDER BY ct.chat_ts DESC LIMIT ?") (params :. Only count)
PTLast count -> DB.query db (query <> " ORDER BY ts DESC LIMIT ?") (params :. Only count) PTAfter ts count -> DB.query db (query <> " AND ct.chat_ts > ? ORDER BY ct.chat_ts ASC LIMIT ?") (params :. (ts, count))
PTAfter ts count -> DB.query db (query <> " AND ts > ? ORDER BY ts ASC LIMIT ?") (params :. (ts, count)) PTBefore ts count -> DB.query db (query <> " AND ct.chat_ts < ? ORDER BY ct.chat_ts DESC LIMIT ?") (params :. (ts, count))
PTBefore ts count -> DB.query db (query <> " AND ts < ? ORDER BY ts DESC LIMIT ?") (params :. (ts, count))
getDirectChatPreview_ :: DB.Connection -> VersionRangeChat -> User -> ChatPreviewData 'CTDirect -> ExceptT StoreError IO AChat getDirectChatPreview_ :: DB.Connection -> VersionRangeChat -> User -> ChatPreviewData 'CTDirect -> ExceptT StoreError IO AChat
getDirectChatPreview_ db vr user (DirectChatPD _ contactId lastItemId_ stats) = do getDirectChatPreview_ db vr user (DirectChatPD _ contactId lastItemId_ stats) = do
@ -652,14 +669,21 @@ findGroupChatPreviews_ db User {userId} pagination clq =
ACPD SCTGroup $ GroupChatPD ts groupId lastItemId_ (toChatStats statsRow) ACPD SCTGroup $ GroupChatPD ts groupId lastItemId_ (toChatStats statsRow)
baseQuery = baseQuery =
[sql| [sql|
SELECT g.group_id, g.chat_ts as ts, LastItems.chat_item_id, COALESCE(ChatStats.UnreadCount, 0), COALESCE(ReportCount.Count, 0), COALESCE(ChatStats.MinUnread, 0), g.unread_chat SELECT
g.group_id,
g.chat_ts,
(
SELECT chat_item_id
FROM chat_items ci
WHERE ci.user_id = ? AND ci.group_id = g.group_id
ORDER BY ci.item_ts DESC
LIMIT 1
) AS chat_item_id,
COALESCE(ChatStats.UnreadCount, 0),
COALESCE(ReportCount.Count, 0),
COALESCE(ChatStats.MinUnread, 0),
g.unread_chat
FROM groups g FROM groups g
LEFT JOIN (
SELECT group_id, chat_item_id, MAX(item_ts)
FROM chat_items
WHERE user_id = ? AND group_id IS NOT NULL
GROUP BY group_id
) LastItems ON LastItems.group_id = g.group_id
LEFT JOIN ( LEFT JOIN (
SELECT group_id, COUNT(1) AS UnreadCount, MIN(chat_item_id) AS MinUnread SELECT group_id, COUNT(1) AS UnreadCount, MIN(chat_item_id) AS MinUnread
FROM chat_items FROM chat_items
@ -679,50 +703,59 @@ findGroupChatPreviews_ db User {userId} pagination clq =
CLQFilters {favorite = False, unread = False} -> do CLQFilters {favorite = False, unread = False} -> do
let q = baseQuery <> " WHERE g.user_id = ?" let q = baseQuery <> " WHERE g.user_id = ?"
p = baseParams :. Only userId p = baseParams :. Only userId
queryWithPagination db q p pagination queryWithPagination q p
CLQFilters {favorite = True, unread = False} -> do CLQFilters {favorite = True, unread = False} -> do
let q = let q =
baseQuery baseQuery
<> " "
<> [sql| <> [sql|
WHERE g.user_id = ? WHERE g.user_id = ?
AND g.favorite = 1 AND g.favorite = 1
|] |]
p = baseParams :. Only userId p = baseParams :. Only userId
queryWithPagination db q p pagination queryWithPagination q p
CLQFilters {favorite = False, unread = True} -> do CLQFilters {favorite = False, unread = True} -> do
let q = let q =
baseQuery baseQuery
<> " "
<> [sql| <> [sql|
WHERE g.user_id = ? WHERE g.user_id = ?
AND (g.unread_chat = 1 OR ChatStats.UnreadCount > 0) AND (g.unread_chat = 1 OR ChatStats.UnreadCount > 0)
|] |]
p = baseParams :. Only userId p = baseParams :. Only userId
queryWithPagination db q p pagination queryWithPagination q p
CLQFilters {favorite = True, unread = True} -> do CLQFilters {favorite = True, unread = True} -> do
let q = let q =
baseQuery baseQuery
<> " "
<> [sql| <> [sql|
WHERE g.user_id = ? WHERE g.user_id = ?
AND (g.favorite = 1 AND (g.favorite = 1
OR g.unread_chat = 1 OR ChatStats.UnreadCount > 0) OR g.unread_chat = 1 OR ChatStats.UnreadCount > 0)
|] |]
p = baseParams :. Only userId p = baseParams :. Only userId
queryWithPagination db q p pagination queryWithPagination q p
CLQSearch {search} -> do CLQSearch {search} -> do
let q = let q =
baseQuery baseQuery
<> " "
<> [sql| <> [sql|
JOIN group_profiles gp ON gp.group_profile_id = g.group_profile_id JOIN group_profiles gp ON gp.group_profile_id = g.group_profile_id
WHERE g.user_id = ? WHERE g.user_id = ?
AND ( AND (
g.local_display_name LIKE '%' || ? || '%' LOWER(g.local_display_name) LIKE '%' || LOWER(?) || '%'
OR gp.display_name LIKE '%' || ? || '%' OR LOWER(gp.display_name) LIKE '%' || LOWER(?) || '%'
OR gp.full_name LIKE '%' || ? || '%' OR LOWER(gp.full_name) LIKE '%' || LOWER(?) || '%'
OR gp.description LIKE '%' || ? || '%' OR LOWER(gp.description) LIKE '%' || LOWER(?) || '%'
) )
|] |]
p = baseParams :. (userId, search, search, search, search) p = baseParams :. (userId, search, search, search, search)
queryWithPagination db q p pagination queryWithPagination q p
queryWithPagination :: ToRow p => Query -> p -> IO [(GroupId, UTCTime, Maybe ChatItemId) :. ChatStatsRow]
queryWithPagination query params = case pagination of
PTLast count -> DB.query db (query <> " ORDER BY g.chat_ts DESC LIMIT ?") (params :. Only count)
PTAfter ts count -> DB.query db (query <> " AND g.chat_ts > ? ORDER BY g.chat_ts ASC LIMIT ?") (params :. (ts, count))
PTBefore ts count -> DB.query db (query <> " AND g.chat_ts < ? ORDER BY g.chat_ts DESC LIMIT ?") (params :. (ts, count))
getGroupChatPreview_ :: DB.Connection -> VersionRangeChat -> User -> ChatPreviewData 'CTGroup -> ExceptT StoreError IO AChat getGroupChatPreview_ :: DB.Connection -> VersionRangeChat -> User -> ChatPreviewData 'CTGroup -> ExceptT StoreError IO AChat
getGroupChatPreview_ db vr user (GroupChatPD _ groupId lastItemId_ stats) = do getGroupChatPreview_ db vr user (GroupChatPD _ groupId lastItemId_ stats) = do
@ -741,14 +774,21 @@ findLocalChatPreviews_ db User {userId} pagination clq =
ACPD SCTLocal $ LocalChatPD ts noteFolderId lastItemId_ (toChatStats statsRow) ACPD SCTLocal $ LocalChatPD ts noteFolderId lastItemId_ (toChatStats statsRow)
baseQuery = baseQuery =
[sql| [sql|
SELECT nf.note_folder_id, nf.chat_ts as ts, LastItems.chat_item_id, COALESCE(ChatStats.UnreadCount, 0), 0, COALESCE(ChatStats.MinUnread, 0), nf.unread_chat SELECT
nf.note_folder_id,
nf.chat_ts,
(
SELECT chat_item_id
FROM chat_items ci
WHERE ci.user_id = ? AND ci.note_folder_id = nf.note_folder_id
ORDER BY ci.created_at DESC
LIMIT 1
) AS chat_item_id,
COALESCE(ChatStats.UnreadCount, 0),
0,
COALESCE(ChatStats.MinUnread, 0),
nf.unread_chat
FROM note_folders nf FROM note_folders nf
LEFT JOIN (
SELECT note_folder_id, chat_item_id, MAX(created_at)
FROM chat_items
WHERE user_id = ? AND note_folder_id IS NOT NULL
GROUP BY note_folder_id
) LastItems ON LastItems.note_folder_id = nf.note_folder_id
LEFT JOIN ( LEFT JOIN (
SELECT note_folder_id, COUNT(1) AS UnreadCount, MIN(chat_item_id) AS MinUnread SELECT note_folder_id, COUNT(1) AS UnreadCount, MIN(chat_item_id) AS MinUnread
FROM chat_items FROM chat_items
@ -761,36 +801,44 @@ findLocalChatPreviews_ db User {userId} pagination clq =
CLQFilters {favorite = False, unread = False} -> do CLQFilters {favorite = False, unread = False} -> do
let q = baseQuery <> " WHERE nf.user_id = ?" let q = baseQuery <> " WHERE nf.user_id = ?"
p = baseParams :. Only userId p = baseParams :. Only userId
queryWithPagination db q p pagination queryWithPagination q p
CLQFilters {favorite = True, unread = False} -> do CLQFilters {favorite = True, unread = False} -> do
let q = let q =
baseQuery baseQuery
<> " "
<> [sql| <> [sql|
WHERE nf.user_id = ? WHERE nf.user_id = ?
AND nf.favorite = 1 AND nf.favorite = 1
|] |]
p = baseParams :. Only userId p = baseParams :. Only userId
queryWithPagination db q p pagination queryWithPagination q p
CLQFilters {favorite = False, unread = True} -> do CLQFilters {favorite = False, unread = True} -> do
let q = let q =
baseQuery baseQuery
<> " "
<> [sql| <> [sql|
WHERE nf.user_id = ? WHERE nf.user_id = ?
AND (nf.unread_chat = 1 OR ChatStats.UnreadCount > 0) AND (nf.unread_chat = 1 OR ChatStats.UnreadCount > 0)
|] |]
p = baseParams :. Only userId p = baseParams :. Only userId
queryWithPagination db q p pagination queryWithPagination q p
CLQFilters {favorite = True, unread = True} -> do CLQFilters {favorite = True, unread = True} -> do
let q = let q =
baseQuery baseQuery
<> " "
<> [sql| <> [sql|
WHERE nf.user_id = ? WHERE nf.user_id = ?
AND (nf.favorite = 1 AND (nf.favorite = 1
OR nf.unread_chat = 1 OR ChatStats.UnreadCount > 0) OR nf.unread_chat = 1 OR ChatStats.UnreadCount > 0)
|] |]
p = baseParams :. Only userId p = baseParams :. Only userId
queryWithPagination db q p pagination queryWithPagination q p
CLQSearch {} -> pure [] CLQSearch {} -> pure []
queryWithPagination :: ToRow p => Query -> p -> IO [(NoteFolderId, UTCTime, Maybe ChatItemId) :. ChatStatsRow]
queryWithPagination query params = case pagination of
PTLast count -> DB.query db (query <> " ORDER BY nf.chat_ts DESC LIMIT ?") (params :. Only count)
PTAfter ts count -> DB.query db (query <> " AND nf.chat_ts > ? ORDER BY nf.chat_ts ASC LIMIT ?") (params :. (ts, count))
PTBefore ts count -> DB.query db (query <> " AND nf.chat_ts < ? ORDER BY nf.chat_ts DESC LIMIT ?") (params :. (ts, count))
getLocalChatPreview_ :: DB.Connection -> User -> ChatPreviewData 'CTLocal -> ExceptT StoreError IO AChat getLocalChatPreview_ :: DB.Connection -> User -> ChatPreviewData 'CTLocal -> ExceptT StoreError IO AChat
getLocalChatPreview_ db user (LocalChatPD _ noteFolderId lastItemId_ stats) = do getLocalChatPreview_ db user (LocalChatPD _ noteFolderId lastItemId_ stats) = do
@ -833,9 +881,9 @@ toLocalChatItem currentTs ((itemId, itemTs, AMsgDirection msgDir, itemContentTex
let itemDeleted' = case itemDeleted of let itemDeleted' = case itemDeleted of
DBCINotDeleted -> Nothing DBCINotDeleted -> Nothing
_ -> Just (CIDeleted @'CTLocal deletedTs) _ -> Just (CIDeleted @'CTLocal deletedTs)
itemEdited' = fromMaybe False itemEdited itemEdited' = maybe False unBI itemEdited
itemForwarded = toCIForwardedFrom forwardedFromRow itemForwarded = toCIForwardedFrom forwardedFromRow
in mkCIMeta itemId content itemText status sentViaProxy sharedMsgId itemForwarded itemDeleted' itemEdited' ciTimed itemLive currentTs itemTs Nothing createdAt updatedAt in mkCIMeta itemId content itemText status (unBI <$> sentViaProxy) sharedMsgId itemForwarded itemDeleted' itemEdited' ciTimed (unBI <$> itemLive) currentTs itemTs Nothing createdAt updatedAt
ciTimed :: Maybe CITimed ciTimed :: Maybe CITimed
ciTimed = timedTTL >>= \ttl -> Just CITimed {ttl, deleteAt = timedDeleteAt} ciTimed = timedTTL >>= \ttl -> Just CITimed {ttl, deleteAt = timedDeleteAt}
@ -852,7 +900,7 @@ getContactRequestChatPreviews_ db User {userId} pagination clq = case clq of
SELECT SELECT
cr.contact_request_id, cr.local_display_name, cr.agent_invitation_id, cr.contact_id, cr.user_contact_link_id, cr.contact_request_id, cr.local_display_name, cr.agent_invitation_id, cr.contact_id, cr.user_contact_link_id,
c.agent_conn_id, cr.contact_profile_id, p.display_name, p.full_name, p.image, p.contact_link, cr.xcontact_id, cr.pq_support, p.preferences, c.agent_conn_id, cr.contact_profile_id, p.display_name, p.full_name, p.image, p.contact_link, cr.xcontact_id, cr.pq_support, p.preferences,
cr.created_at, cr.updated_at as ts, cr.created_at, cr.updated_at,
cr.peer_chat_min_version, cr.peer_chat_max_version cr.peer_chat_min_version, cr.peer_chat_max_version
FROM contact_requests cr FROM contact_requests cr
JOIN connections c ON c.user_contact_link_id = cr.user_contact_link_id JOIN connections c ON c.user_contact_link_id = cr.user_contact_link_id
@ -863,16 +911,16 @@ getContactRequestChatPreviews_ db User {userId} pagination clq = case clq of
AND uc.local_display_name = '' AND uc.local_display_name = ''
AND uc.group_id IS NULL AND uc.group_id IS NULL
AND ( AND (
cr.local_display_name LIKE '%' || ? || '%' LOWER(cr.local_display_name) LIKE '%' || LOWER(?) || '%'
OR p.display_name LIKE '%' || ? || '%' OR LOWER(p.display_name) LIKE '%' || LOWER(?) || '%'
OR p.full_name LIKE '%' || ? || '%' OR LOWER(p.full_name) LIKE '%' || LOWER(?) || '%'
) )
|] |]
params search = (userId, userId, search, search, search) params search = (userId, userId, search, search, search)
getPreviews search = case pagination of getPreviews search = case pagination of
PTLast count -> DB.query db (query <> " ORDER BY ts DESC LIMIT ?") (params search :. Only count) PTLast count -> DB.query db (query <> " ORDER BY cr.updated_at DESC LIMIT ?") (params search :. Only count)
PTAfter ts count -> DB.query db (query <> " AND ts > ? ORDER BY ts ASC LIMIT ?") (params search :. (ts, count)) PTAfter ts count -> DB.query db (query <> " AND cr.updated_at > ? ORDER BY cr.updated_at ASC LIMIT ?") (params search :. (ts, count))
PTBefore ts count -> DB.query db (query <> " AND ts < ? ORDER BY ts DESC LIMIT ?") (params search :. (ts, count)) PTBefore ts count -> DB.query db (query <> " AND cr.updated_at < ? ORDER BY cr.updated_at DESC LIMIT ?") (params search :. (ts, count))
toPreview :: ContactRequestRow -> AChatPreviewData toPreview :: ContactRequestRow -> AChatPreviewData
toPreview cReqRow = toPreview cReqRow =
let cReq@UserContactRequest {updatedAt} = toContactRequest cReqRow let cReq@UserContactRequest {updatedAt} = toContactRequest cReqRow
@ -891,7 +939,7 @@ getContactConnectionChatPreviews_ db User {userId} pagination clq = case clq of
[sql| [sql|
SELECT SELECT
connection_id, agent_conn_id, conn_status, via_contact_uri_hash, via_user_contact_link, group_link_id, connection_id, agent_conn_id, conn_status, via_contact_uri_hash, via_user_contact_link, group_link_id,
custom_user_profile_id, conn_req_inv, local_alias, created_at, updated_at as ts custom_user_profile_id, conn_req_inv, local_alias, created_at, updated_at
FROM connections FROM connections
WHERE user_id = ? WHERE user_id = ?
AND conn_type = ? AND conn_type = ?
@ -899,14 +947,14 @@ getContactConnectionChatPreviews_ db User {userId} pagination clq = case clq of
AND contact_id IS NULL AND contact_id IS NULL
AND conn_level = 0 AND conn_level = 0
AND via_contact IS NULL AND via_contact IS NULL
AND (via_group_link = 0 || (via_group_link = 1 AND group_link_id IS NOT NULL)) AND (via_group_link = 0 OR (via_group_link = 1 AND group_link_id IS NOT NULL))
AND local_alias LIKE '%' || ? || '%' AND LOWER(local_alias) LIKE '%' || LOWER(?) || '%'
|] |]
params search = (userId, ConnContact, ConnPrepared, search) params search = (userId, ConnContact, ConnPrepared, search)
getPreviews search = case pagination of getPreviews search = case pagination of
PTLast count -> DB.query db (query <> " ORDER BY ts DESC LIMIT ?") (params search :. Only count) PTLast count -> DB.query db (query <> " ORDER BY updated_at DESC LIMIT ?") (params search :. Only count)
PTAfter ts count -> DB.query db (query <> " AND ts > ? ORDER BY ts ASC LIMIT ?") (params search :. (ts, count)) PTAfter ts count -> DB.query db (query <> " AND updated_at > ? ORDER BY updated_at ASC LIMIT ?") (params search :. (ts, count))
PTBefore ts count -> DB.query db (query <> " AND ts < ? ORDER BY ts DESC LIMIT ?") (params search :. (ts, count)) PTBefore ts count -> DB.query db (query <> " AND updated_at < ? ORDER BY updated_at DESC LIMIT ?") (params search :. (ts, count))
toPreview :: (Int64, ConnId, ConnStatus, Maybe ByteString, Maybe Int64, Maybe GroupLinkId, Maybe Int64, Maybe ConnReqInvitation, LocalAlias, UTCTime, UTCTime) -> AChatPreviewData toPreview :: (Int64, ConnId, ConnStatus, Maybe ByteString, Maybe Int64, Maybe GroupLinkId, Maybe Int64, Maybe ConnReqInvitation, LocalAlias, UTCTime, UTCTime) -> AChatPreviewData
toPreview connRow = toPreview connRow =
let conn@PendingContactConnection {updatedAt} = toPendingContactConnection connRow let conn@PendingContactConnection {updatedAt} = toPendingContactConnection connRow
@ -942,7 +990,7 @@ getDirectChatItemIdsLast_ db User {userId} Contact {contactId} count search =
[sql| [sql|
SELECT chat_item_id SELECT chat_item_id
FROM chat_items FROM chat_items
WHERE user_id = ? AND contact_id = ? AND item_text LIKE '%' || ? || '%' WHERE user_id = ? AND contact_id = ? AND LOWER(item_text) LIKE '%' || LOWER(?) || '%'
ORDER BY created_at DESC, chat_item_id DESC ORDER BY created_at DESC, chat_item_id DESC
LIMIT ? LIMIT ?
|] |]
@ -1006,7 +1054,7 @@ getDirectCIsAfter_ db User {userId} Contact {contactId} afterCI count search =
[sql| [sql|
SELECT chat_item_id SELECT chat_item_id
FROM chat_items FROM chat_items
WHERE user_id = ? AND contact_id = ? AND item_text LIKE '%' || ? || '%' WHERE user_id = ? AND contact_id = ? AND LOWER(item_text) LIKE '%' || LOWER(?) || '%'
AND (created_at > ? OR (created_at = ? AND chat_item_id > ?)) AND (created_at > ? OR (created_at = ? AND chat_item_id > ?))
ORDER BY created_at ASC, chat_item_id ASC ORDER BY created_at ASC, chat_item_id ASC
LIMIT ? LIMIT ?
@ -1029,7 +1077,7 @@ getDirectCIsBefore_ db User {userId} Contact {contactId} beforeCI count search =
[sql| [sql|
SELECT chat_item_id SELECT chat_item_id
FROM chat_items FROM chat_items
WHERE user_id = ? AND contact_id = ? AND item_text LIKE '%' || ? || '%' WHERE user_id = ? AND contact_id = ? AND LOWER(item_text) LIKE '%' || LOWER(?) || '%'
AND (created_at < ? OR (created_at = ? AND chat_item_id < ?)) AND (created_at < ? OR (created_at = ? AND chat_item_id < ?))
ORDER BY created_at DESC, chat_item_id DESC ORDER BY created_at DESC, chat_item_id DESC
LIMIT ? LIMIT ?
@ -1121,7 +1169,7 @@ getContactNavInfo_ db User {userId} Contact {contactId} afterCI = do
FROM chat_items FROM chat_items
WHERE user_id = ? AND contact_id = ? AND item_status = ? WHERE user_id = ? AND contact_id = ? AND item_status = ?
AND created_at = ? AND chat_item_id > ? AND created_at = ? AND chat_item_id > ?
) ) ci
|] |]
( (userId, contactId, CISRcvNew, ciCreatedAt afterCI) ( (userId, contactId, CISRcvNew, ciCreatedAt afterCI)
:. (userId, contactId, CISRcvNew, ciCreatedAt afterCI, cChatItemId afterCI) :. (userId, contactId, CISRcvNew, ciCreatedAt afterCI, cChatItemId afterCI)
@ -1143,7 +1191,7 @@ getContactNavInfo_ db User {userId} Contact {contactId} afterCI = do
FROM chat_items FROM chat_items
WHERE user_id = ? AND contact_id = ? WHERE user_id = ? AND contact_id = ?
AND created_at = ? AND chat_item_id > ? AND created_at = ? AND chat_item_id > ?
) ) ci
|] |]
( (userId, contactId, ciCreatedAt afterCI) ( (userId, contactId, ciCreatedAt afterCI)
:. (userId, contactId, ciCreatedAt afterCI, cChatItemId afterCI) :. (userId, contactId, ciCreatedAt afterCI, cChatItemId afterCI)
@ -1199,7 +1247,7 @@ getGroupChatItemIDs db User {userId} GroupInfo {groupId} contentFilter range cou
rangeQuery :: ToRow p => Query -> p -> Query -> IO [ChatItemId] rangeQuery :: ToRow p => Query -> p -> Query -> IO [ChatItemId]
rangeQuery c p ob rangeQuery c p ob
| null search = searchQuery "" () | null search = searchQuery "" ()
| otherwise = searchQuery " AND item_text LIKE '%' || ? || '%' " (Only search) | otherwise = searchQuery " AND LOWER(item_text) LIKE '%' || LOWER(?) || '%' " (Only search)
where where
searchQuery :: ToRow p' => Query -> p' -> IO [ChatItemId] searchQuery :: ToRow p' => Query -> p' -> IO [ChatItemId]
searchQuery c' p' = searchQuery c' p' =
@ -1372,7 +1420,7 @@ getGroupNavInfo_ db User {userId} GroupInfo {groupId} afterCI = do
FROM chat_items FROM chat_items
WHERE user_id = ? AND group_id = ? AND item_status = ? WHERE user_id = ? AND group_id = ? AND item_status = ?
AND item_ts = ? AND chat_item_id > ? AND item_ts = ? AND chat_item_id > ?
) ) ci
|] |]
( (userId, groupId, CISRcvNew, chatItemTs afterCI) ( (userId, groupId, CISRcvNew, chatItemTs afterCI)
:. (userId, groupId, CISRcvNew, chatItemTs afterCI, cChatItemId afterCI) :. (userId, groupId, CISRcvNew, chatItemTs afterCI, cChatItemId afterCI)
@ -1394,7 +1442,7 @@ getGroupNavInfo_ db User {userId} GroupInfo {groupId} afterCI = do
FROM chat_items FROM chat_items
WHERE user_id = ? AND group_id = ? WHERE user_id = ? AND group_id = ?
AND item_ts = ? AND chat_item_id > ? AND item_ts = ? AND chat_item_id > ?
) ) ci
|] |]
( (userId, groupId, chatItemTs afterCI) ( (userId, groupId, chatItemTs afterCI)
:. (userId, groupId, chatItemTs afterCI, cChatItemId afterCI) :. (userId, groupId, chatItemTs afterCI, cChatItemId afterCI)
@ -1428,7 +1476,7 @@ getLocalChatItemIdsLast_ db User {userId} NoteFolder {noteFolderId} count search
[sql| [sql|
SELECT chat_item_id SELECT chat_item_id
FROM chat_items FROM chat_items
WHERE user_id = ? AND note_folder_id = ? AND item_text LIKE '%' || ? || '%' WHERE user_id = ? AND note_folder_id = ? AND LOWER(item_text) LIKE '%' || LOWER(?) || '%'
ORDER BY created_at DESC, chat_item_id DESC ORDER BY created_at DESC, chat_item_id DESC
LIMIT ? LIMIT ?
|] |]
@ -1476,7 +1524,7 @@ getLocalCIsAfter_ db User {userId} NoteFolder {noteFolderId} afterCI count searc
[sql| [sql|
SELECT chat_item_id SELECT chat_item_id
FROM chat_items FROM chat_items
WHERE user_id = ? AND note_folder_id = ? AND item_text LIKE '%' || ? || '%' WHERE user_id = ? AND note_folder_id = ? AND LOWER(item_text) LIKE '%' || LOWER(?) || '%'
AND (created_at > ? OR (created_at = ? AND chat_item_id > ?)) AND (created_at > ? OR (created_at = ? AND chat_item_id > ?))
ORDER BY created_at ASC, chat_item_id ASC ORDER BY created_at ASC, chat_item_id ASC
LIMIT ? LIMIT ?
@ -1499,7 +1547,7 @@ getLocalCIsBefore_ db User {userId} NoteFolder {noteFolderId} beforeCI count sea
[sql| [sql|
SELECT chat_item_id SELECT chat_item_id
FROM chat_items FROM chat_items
WHERE user_id = ? AND note_folder_id = ? AND item_text LIKE '%' || ? || '%' WHERE user_id = ? AND note_folder_id = ? AND LOWER(item_text) LIKE '%' || LOWER(?) || '%'
AND (created_at < ? OR (created_at = ? AND chat_item_id < ?)) AND (created_at < ? OR (created_at = ? AND chat_item_id < ?))
ORDER BY created_at DESC, chat_item_id DESC ORDER BY created_at DESC, chat_item_id DESC
LIMIT ? LIMIT ?
@ -1591,7 +1639,7 @@ getLocalNavInfo_ db User {userId} NoteFolder {noteFolderId} afterCI = do
FROM chat_items FROM chat_items
WHERE user_id = ? AND note_folder_id = ? AND item_status = ? WHERE user_id = ? AND note_folder_id = ? AND item_status = ?
AND created_at = ? AND chat_item_id > ? AND created_at = ? AND chat_item_id > ?
) ) ci
|] |]
( (userId, noteFolderId, CISRcvNew, ciCreatedAt afterCI) ( (userId, noteFolderId, CISRcvNew, ciCreatedAt afterCI)
:. (userId, noteFolderId, CISRcvNew, ciCreatedAt afterCI, cChatItemId afterCI) :. (userId, noteFolderId, CISRcvNew, ciCreatedAt afterCI, cChatItemId afterCI)
@ -1613,7 +1661,7 @@ getLocalNavInfo_ db User {userId} NoteFolder {noteFolderId} afterCI = do
FROM chat_items FROM chat_items
WHERE user_id = ? AND note_folder_id = ? WHERE user_id = ? AND note_folder_id = ?
AND created_at = ? AND chat_item_id > ? AND created_at = ? AND chat_item_id > ?
) ) ci
|] |]
( (userId, noteFolderId, ciCreatedAt afterCI) ( (userId, noteFolderId, ciCreatedAt afterCI)
:. (userId, noteFolderId, ciCreatedAt afterCI, cChatItemId afterCI) :. (userId, noteFolderId, ciCreatedAt afterCI, cChatItemId afterCI)
@ -1763,21 +1811,21 @@ updateLocalChatItemsRead db User {userId} noteFolderId = do
type MaybeCIFIleRow = (Maybe Int64, Maybe String, Maybe Integer, Maybe FilePath, Maybe C.SbKey, Maybe C.CbNonce, Maybe ACIFileStatus, Maybe FileProtocol) type MaybeCIFIleRow = (Maybe Int64, Maybe String, Maybe Integer, Maybe FilePath, Maybe C.SbKey, Maybe C.CbNonce, Maybe ACIFileStatus, Maybe FileProtocol)
type ChatItemModeRow = (Maybe Int, Maybe UTCTime, Maybe Bool) type ChatItemModeRow = (Maybe Int, Maybe UTCTime, Maybe BoolInt)
type ChatItemForwardedFromRow = (Maybe CIForwardedFromTag, Maybe Text, Maybe MsgDirection, Maybe Int64, Maybe Int64, Maybe Int64) type ChatItemForwardedFromRow = (Maybe CIForwardedFromTag, Maybe Text, Maybe MsgDirection, Maybe Int64, Maybe Int64, Maybe Int64)
type ChatItemRow = type ChatItemRow =
(Int64, ChatItemTs, AMsgDirection, Text, Text, ACIStatus, Maybe Bool, Maybe SharedMsgId) (Int64, ChatItemTs, AMsgDirection, Text, Text, ACIStatus, Maybe BoolInt, Maybe SharedMsgId)
:. (Int, Maybe UTCTime, Maybe Bool, UTCTime, UTCTime) :. (Int, Maybe UTCTime, Maybe BoolInt, UTCTime, UTCTime)
:. ChatItemForwardedFromRow :. ChatItemForwardedFromRow
:. ChatItemModeRow :. ChatItemModeRow
:. MaybeCIFIleRow :. MaybeCIFIleRow
type QuoteRow = (Maybe ChatItemId, Maybe SharedMsgId, Maybe UTCTime, Maybe MsgContent, Maybe Bool) type QuoteRow = (Maybe ChatItemId, Maybe SharedMsgId, Maybe UTCTime, Maybe MsgContent, Maybe BoolInt)
toDirectQuote :: QuoteRow -> Maybe (CIQuote 'CTDirect) toDirectQuote :: QuoteRow -> Maybe (CIQuote 'CTDirect)
toDirectQuote qr@(_, _, _, _, quotedSent) = toQuote qr $ direction <$> quotedSent toDirectQuote qr@(_, _, _, _, quotedSent) = toQuote qr $ direction . unBI <$> quotedSent
where where
direction sent = if sent then CIQDirectSnd else CIQDirectRcv direction sent = if sent then CIQDirectSnd else CIQDirectRcv
@ -1818,9 +1866,9 @@ toDirectChatItem currentTs (((itemId, itemTs, AMsgDirection msgDir, itemContentT
let itemDeleted' = case itemDeleted of let itemDeleted' = case itemDeleted of
DBCINotDeleted -> Nothing DBCINotDeleted -> Nothing
_ -> Just (CIDeleted @'CTDirect deletedTs) _ -> Just (CIDeleted @'CTDirect deletedTs)
itemEdited' = fromMaybe False itemEdited itemEdited' = maybe False unBI itemEdited
itemForwarded = toCIForwardedFrom forwardedFromRow itemForwarded = toCIForwardedFrom forwardedFromRow
in mkCIMeta itemId content itemText status sentViaProxy sharedMsgId itemForwarded itemDeleted' itemEdited' ciTimed itemLive currentTs itemTs Nothing createdAt updatedAt in mkCIMeta itemId content itemText status (unBI <$> sentViaProxy) sharedMsgId itemForwarded itemDeleted' itemEdited' ciTimed (unBI <$> itemLive) currentTs itemTs Nothing createdAt updatedAt
ciTimed :: Maybe CITimed ciTimed :: Maybe CITimed
ciTimed = timedTTL >>= \ttl -> Just CITimed {ttl, deleteAt = timedDeleteAt} ciTimed = timedTTL >>= \ttl -> Just CITimed {ttl, deleteAt = timedDeleteAt}
@ -1837,9 +1885,9 @@ type GroupQuoteRow = QuoteRow :. MaybeGroupMemberRow
toGroupQuote :: QuoteRow -> Maybe GroupMember -> Maybe (CIQuote 'CTGroup) toGroupQuote :: QuoteRow -> Maybe GroupMember -> Maybe (CIQuote 'CTGroup)
toGroupQuote qr@(_, _, _, _, quotedSent) quotedMember_ = toQuote qr $ direction quotedSent quotedMember_ toGroupQuote qr@(_, _, _, _, quotedSent) quotedMember_ = toQuote qr $ direction quotedSent quotedMember_
where where
direction (Just True) _ = Just CIQGroupSnd direction (Just (BI True)) _ = Just CIQGroupSnd
direction (Just False) (Just member) = Just . CIQGroupRcv $ Just member direction (Just (BI False)) (Just member) = Just . CIQGroupRcv $ Just member
direction (Just False) Nothing = Just $ CIQGroupRcv Nothing direction (Just (BI False)) Nothing = Just $ CIQGroupRcv Nothing
direction _ _ = Nothing direction _ _ = Nothing
-- this function can be changed so it never fails, not only avoid failure on invalid json -- this function can be changed so it never fails, not only avoid failure on invalid json
@ -1880,9 +1928,9 @@ toGroupChatItem currentTs userContactId (((itemId, itemTs, AMsgDirection msgDir,
DBCIBlocked -> Just (CIBlocked deletedTs) DBCIBlocked -> Just (CIBlocked deletedTs)
DBCIBlockedByAdmin -> Just (CIBlockedByAdmin deletedTs) DBCIBlockedByAdmin -> Just (CIBlockedByAdmin deletedTs)
_ -> Just (maybe (CIDeleted @'CTGroup deletedTs) (CIModerated deletedTs) deletedByGroupMember_) _ -> Just (maybe (CIDeleted @'CTGroup deletedTs) (CIModerated deletedTs) deletedByGroupMember_)
itemEdited' = fromMaybe False itemEdited itemEdited' = maybe False unBI itemEdited
itemForwarded = toCIForwardedFrom forwardedFromRow itemForwarded = toCIForwardedFrom forwardedFromRow
in mkCIMeta itemId content itemText status sentViaProxy sharedMsgId itemForwarded itemDeleted' itemEdited' ciTimed itemLive currentTs itemTs forwardedByMember createdAt updatedAt in mkCIMeta itemId content itemText status (unBI <$> sentViaProxy) sharedMsgId itemForwarded itemDeleted' itemEdited' ciTimed (unBI <$> itemLive) currentTs itemTs forwardedByMember createdAt updatedAt
ciTimed :: Maybe CITimed ciTimed :: Maybe CITimed
ciTimed = timedTTL >>= \ttl -> Just CITimed {ttl, deleteAt = timedDeleteAt} ciTimed = timedTTL >>= \ttl -> Just CITimed {ttl, deleteAt = timedDeleteAt}
@ -1912,7 +1960,7 @@ getAllChatItems db vr user@User {userId} pagination search_ = do
[sql| [sql|
SELECT chat_item_id, contact_id, group_id, note_folder_id SELECT chat_item_id, contact_id, group_id, note_folder_id
FROM chat_items FROM chat_items
WHERE user_id = ? AND item_text LIKE '%' || ? || '%' WHERE user_id = ? AND LOWER(item_text) LIKE '%' || LOWER(?) || '%'
ORDER BY item_ts DESC, chat_item_id DESC ORDER BY item_ts DESC, chat_item_id DESC
LIMIT ? LIMIT ?
|] |]
@ -1923,7 +1971,7 @@ getAllChatItems db vr user@User {userId} pagination search_ = do
[sql| [sql|
SELECT chat_item_id, contact_id, group_id, note_folder_id SELECT chat_item_id, contact_id, group_id, note_folder_id
FROM chat_items FROM chat_items
WHERE user_id = ? AND item_text LIKE '%' || ? || '%' WHERE user_id = ? AND LOWER(item_text) LIKE '%' || LOWER(?) || '%'
AND (item_ts > ? OR (item_ts = ? AND chat_item_id > ?)) AND (item_ts > ? OR (item_ts = ? AND chat_item_id > ?))
ORDER BY item_ts ASC, chat_item_id ASC ORDER BY item_ts ASC, chat_item_id ASC
LIMIT ? LIMIT ?
@ -1936,7 +1984,7 @@ getAllChatItems db vr user@User {userId} pagination search_ = do
[sql| [sql|
SELECT chat_item_id, contact_id, group_id, note_folder_id SELECT chat_item_id, contact_id, group_id, note_folder_id
FROM chat_items FROM chat_items
WHERE user_id = ? AND item_text LIKE '%' || ? || '%' WHERE user_id = ? AND LOWER(item_text) LIKE '%' || LOWER(?) || '%'
AND (item_ts < ? OR (item_ts = ? AND chat_item_id < ?)) AND (item_ts < ? OR (item_ts = ? AND chat_item_id < ?))
ORDER BY item_ts DESC, chat_item_id DESC ORDER BY item_ts DESC, chat_item_id DESC
LIMIT ? LIMIT ?
@ -1992,7 +2040,7 @@ updateDirectChatItemStatus db user@User {userId} ct@Contact {contactId} itemId i
setDirectSndChatItemViaProxy :: DB.Connection -> User -> Contact -> ChatItem 'CTDirect 'MDSnd -> Bool -> IO (ChatItem 'CTDirect 'MDSnd) setDirectSndChatItemViaProxy :: DB.Connection -> User -> Contact -> ChatItem 'CTDirect 'MDSnd -> Bool -> IO (ChatItem 'CTDirect 'MDSnd)
setDirectSndChatItemViaProxy db User {userId} Contact {contactId} ci viaProxy = do setDirectSndChatItemViaProxy db User {userId} Contact {contactId} ci viaProxy = do
DB.execute db "UPDATE chat_items SET via_proxy = ? WHERE user_id = ? AND contact_id = ? AND chat_item_id = ?" (viaProxy, userId, contactId, chatItemId' ci) DB.execute db "UPDATE chat_items SET via_proxy = ? WHERE user_id = ? AND contact_id = ? AND chat_item_id = ?" (BI viaProxy, userId, contactId, chatItemId' ci)
pure ci {meta = (meta ci) {sentViaProxy = Just viaProxy}} pure ci {meta = (meta ci) {sentViaProxy = Just viaProxy}}
updateDirectChatItem :: MsgDirectionI d => DB.Connection -> User -> Contact -> ChatItemId -> CIContent d -> Bool -> Bool -> Maybe CITimed -> Maybe MessageId -> ExceptT StoreError IO (ChatItem 'CTDirect d) updateDirectChatItem :: MsgDirectionI d => DB.Connection -> User -> Contact -> ChatItemId -> CIContent d -> Bool -> Bool -> Maybe CITimed -> Maybe MessageId -> ExceptT StoreError IO (ChatItem 'CTDirect d)
@ -2044,7 +2092,7 @@ updateDirectChatItem_ db userId contactId ChatItem {meta, content} msgId_ = do
SET item_content = ?, item_text = ?, item_status = ?, item_deleted = ?, item_deleted_ts = ?, item_edited = ?, item_live = ?, updated_at = ?, timed_ttl = ?, timed_delete_at = ? SET item_content = ?, item_text = ?, item_status = ?, item_deleted = ?, item_deleted_ts = ?, item_edited = ?, item_live = ?, updated_at = ?, timed_ttl = ?, timed_delete_at = ?
WHERE user_id = ? AND contact_id = ? AND chat_item_id = ? WHERE user_id = ? AND contact_id = ? AND chat_item_id = ?
|] |]
((content, itemText, itemStatus, itemDeleted', itemDeletedTs', itemEdited, itemLive, updatedAt) :. ciTimedRow itemTimed :. (userId, contactId, itemId)) ((content, itemText, itemStatus, BI itemDeleted', itemDeletedTs', BI itemEdited, BI <$> itemLive, updatedAt) :. ciTimedRow itemTimed :. (userId, contactId, itemId))
forM_ msgId_ $ \msgId -> liftIO $ insertChatItemMessage_ db itemId msgId updatedAt forM_ msgId_ $ \msgId -> liftIO $ insertChatItemMessage_ db itemId msgId updatedAt
addInitialAndNewCIVersions :: DB.Connection -> ChatItemId -> (UTCTime, MsgContent) -> (UTCTime, MsgContent) -> IO () addInitialAndNewCIVersions :: DB.Connection -> ChatItemId -> (UTCTime, MsgContent) -> (UTCTime, MsgContent) -> IO ()
@ -2235,7 +2283,7 @@ updateGroupChatItem_ db User {userId} groupId ChatItem {content, meta} msgId_ =
SET item_content = ?, item_text = ?, item_status = ?, item_deleted = ?, item_deleted_ts = ?, item_edited = ?, item_live = ?, updated_at = ?, timed_ttl = ?, timed_delete_at = ? SET item_content = ?, item_text = ?, item_status = ?, item_deleted = ?, item_deleted_ts = ?, item_edited = ?, item_live = ?, updated_at = ?, timed_ttl = ?, timed_delete_at = ?
WHERE user_id = ? AND group_id = ? AND chat_item_id = ? WHERE user_id = ? AND group_id = ? AND chat_item_id = ?
|] |]
((content, itemText, itemStatus, itemDeleted', itemDeletedTs', itemEdited, itemLive, updatedAt) :. ciTimedRow itemTimed :. (userId, groupId, itemId)) ((content, itemText, itemStatus, BI itemDeleted', itemDeletedTs', BI itemEdited, BI <$> itemLive, updatedAt) :. ciTimedRow itemTimed :. (userId, groupId, itemId))
forM_ msgId_ $ \msgId -> insertChatItemMessage_ db itemId msgId updatedAt forM_ msgId_ $ \msgId -> insertChatItemMessage_ db itemId msgId updatedAt
deleteGroupChatItem :: DB.Connection -> User -> GroupInfo -> ChatItem 'CTGroup d -> IO () deleteGroupChatItem :: DB.Connection -> User -> GroupInfo -> ChatItem 'CTGroup d -> IO ()
@ -2573,7 +2621,7 @@ updateLocalChatItem_ db userId noteFolderId ChatItem {meta, content} = do
SET item_content = ?, item_text = ?, item_status = ?, item_deleted = ?, item_deleted_ts = ?, item_edited = ?, updated_at = ? SET item_content = ?, item_text = ?, item_status = ?, item_deleted = ?, item_deleted_ts = ?, item_edited = ?, updated_at = ?
WHERE user_id = ? AND note_folder_id = ? AND chat_item_id = ? WHERE user_id = ? AND note_folder_id = ? AND chat_item_id = ?
|] |]
((content, itemText, itemStatus, itemDeleted', itemDeletedTs', itemEdited, updatedAt) :. (userId, noteFolderId, itemId)) ((content, itemText, itemStatus, BI itemDeleted', itemDeletedTs', BI itemEdited, updatedAt) :. (userId, noteFolderId, itemId))
deleteLocalChatItem :: DB.Connection -> User -> NoteFolder -> ChatItem 'CTLocal d -> IO () deleteLocalChatItem :: DB.Connection -> User -> NoteFolder -> ChatItem 'CTLocal d -> IO ()
deleteLocalChatItem db User {userId} NoteFolder {noteFolderId} ci = do deleteLocalChatItem db User {userId} NoteFolder {noteFolderId} ci = do
@ -2740,8 +2788,8 @@ deleteGroupCIReactions_ db g@GroupInfo {groupId} ci@ChatItem {meta = CIMeta {ite
"DELETE FROM chat_item_reactions WHERE group_id = ? AND shared_msg_id = ? AND item_member_id = ?" "DELETE FROM chat_item_reactions WHERE group_id = ? AND shared_msg_id = ? AND item_member_id = ?"
(groupId, itemSharedMId, memberId) (groupId, itemSharedMId, memberId)
toCIReaction :: (MsgReaction, Bool, Int) -> CIReactionCount toCIReaction :: (MsgReaction, BoolInt, Int) -> CIReactionCount
toCIReaction (reaction, userReacted, totalReacted) = CIReactionCount {reaction, userReacted, totalReacted} toCIReaction (reaction, BI userReacted, totalReacted) = CIReactionCount {reaction, userReacted, totalReacted}
getDirectReactions :: DB.Connection -> Contact -> SharedMsgId -> Bool -> IO [MsgReaction] getDirectReactions :: DB.Connection -> Contact -> SharedMsgId -> Bool -> IO [MsgReaction]
getDirectReactions db ct itemSharedMId sent = getDirectReactions db ct itemSharedMId sent =
@ -2753,7 +2801,7 @@ getDirectReactions db ct itemSharedMId sent =
FROM chat_item_reactions FROM chat_item_reactions
WHERE contact_id = ? AND shared_msg_id = ? AND reaction_sent = ? WHERE contact_id = ? AND shared_msg_id = ? AND reaction_sent = ?
|] |]
(contactId' ct, itemSharedMId, sent) (contactId' ct, itemSharedMId, BI sent)
setDirectReaction :: DB.Connection -> Contact -> SharedMsgId -> Bool -> MsgReaction -> Bool -> MessageId -> UTCTime -> IO () setDirectReaction :: DB.Connection -> Contact -> SharedMsgId -> Bool -> MsgReaction -> Bool -> MessageId -> UTCTime -> IO ()
setDirectReaction db ct itemSharedMId sent reaction add msgId reactionTs setDirectReaction db ct itemSharedMId sent reaction add msgId reactionTs
@ -2765,7 +2813,7 @@ setDirectReaction db ct itemSharedMId sent reaction add msgId reactionTs
(contact_id, shared_msg_id, reaction_sent, reaction, created_by_msg_id, reaction_ts) (contact_id, shared_msg_id, reaction_sent, reaction, created_by_msg_id, reaction_ts)
VALUES (?,?,?,?,?,?) VALUES (?,?,?,?,?,?)
|] |]
(contactId' ct, itemSharedMId, sent, reaction, msgId, reactionTs) (contactId' ct, itemSharedMId, BI sent, reaction, msgId, reactionTs)
| otherwise = | otherwise =
DB.execute DB.execute
db db
@ -2773,7 +2821,7 @@ setDirectReaction db ct itemSharedMId sent reaction add msgId reactionTs
DELETE FROM chat_item_reactions DELETE FROM chat_item_reactions
WHERE contact_id = ? AND shared_msg_id = ? AND reaction_sent = ? AND reaction = ? WHERE contact_id = ? AND shared_msg_id = ? AND reaction_sent = ? AND reaction = ?
|] |]
(contactId' ct, itemSharedMId, sent, reaction) (contactId' ct, itemSharedMId, BI sent, reaction)
getGroupReactions :: DB.Connection -> GroupInfo -> GroupMember -> MemberId -> SharedMsgId -> Bool -> IO [MsgReaction] getGroupReactions :: DB.Connection -> GroupInfo -> GroupMember -> MemberId -> SharedMsgId -> Bool -> IO [MsgReaction]
getGroupReactions db GroupInfo {groupId} m itemMemberId itemSharedMId sent = getGroupReactions db GroupInfo {groupId} m itemMemberId itemSharedMId sent =
@ -2785,7 +2833,7 @@ getGroupReactions db GroupInfo {groupId} m itemMemberId itemSharedMId sent =
FROM chat_item_reactions FROM chat_item_reactions
WHERE group_id = ? AND group_member_id = ? AND item_member_id = ? AND shared_msg_id = ? AND reaction_sent = ? WHERE group_id = ? AND group_member_id = ? AND item_member_id = ? AND shared_msg_id = ? AND reaction_sent = ?
|] |]
(groupId, groupMemberId' m, itemMemberId, itemSharedMId, sent) (groupId, groupMemberId' m, itemMemberId, itemSharedMId, BI sent)
setGroupReaction :: DB.Connection -> GroupInfo -> GroupMember -> MemberId -> SharedMsgId -> Bool -> MsgReaction -> Bool -> MessageId -> UTCTime -> IO () setGroupReaction :: DB.Connection -> GroupInfo -> GroupMember -> MemberId -> SharedMsgId -> Bool -> MsgReaction -> Bool -> MessageId -> UTCTime -> IO ()
setGroupReaction db GroupInfo {groupId} m itemMemberId itemSharedMId sent reaction add msgId reactionTs setGroupReaction db GroupInfo {groupId} m itemMemberId itemSharedMId sent reaction add msgId reactionTs
@ -2797,7 +2845,7 @@ setGroupReaction db GroupInfo {groupId} m itemMemberId itemSharedMId sent reacti
(group_id, group_member_id, item_member_id, shared_msg_id, reaction_sent, reaction, created_by_msg_id, reaction_ts) (group_id, group_member_id, item_member_id, shared_msg_id, reaction_sent, reaction, created_by_msg_id, reaction_ts)
VALUES (?,?,?,?,?,?,?,?) VALUES (?,?,?,?,?,?,?,?)
|] |]
(groupId, groupMemberId' m, itemMemberId, itemSharedMId, sent, reaction, msgId, reactionTs) (groupId, groupMemberId' m, itemMemberId, itemSharedMId, BI sent, reaction, msgId, reactionTs)
| otherwise = | otherwise =
DB.execute DB.execute
db db
@ -2805,7 +2853,7 @@ setGroupReaction db GroupInfo {groupId} m itemMemberId itemSharedMId sent reacti
DELETE FROM chat_item_reactions DELETE FROM chat_item_reactions
WHERE group_id = ? AND group_member_id = ? AND shared_msg_id = ? AND item_member_id = ? AND reaction_sent = ? AND reaction = ? WHERE group_id = ? AND group_member_id = ? AND shared_msg_id = ? AND item_member_id = ? AND reaction_sent = ? AND reaction = ?
|] |]
(groupId, groupMemberId' m, itemSharedMId, itemMemberId, sent, reaction) (groupId, groupMemberId' m, itemSharedMId, itemMemberId, BI sent, reaction)
getReactionMembers :: DB.Connection -> VersionRangeChat -> User -> GroupId -> SharedMsgId -> MsgReaction -> IO [MemberReaction] getReactionMembers :: DB.Connection -> VersionRangeChat -> User -> GroupId -> SharedMsgId -> MsgReaction -> IO [MemberReaction]
getReactionMembers db vr user groupId itemSharedMId reaction = do getReactionMembers db vr user groupId itemSharedMId reaction = do
@ -2974,7 +3022,7 @@ setGroupSndViaProxy db itemId memberId viaProxy =
SET via_proxy = ? SET via_proxy = ?
WHERE chat_item_id = ? AND group_member_id = ? WHERE chat_item_id = ? AND group_member_id = ?
|] |]
(viaProxy, itemId, memberId) (BI viaProxy, itemId, memberId)
getGroupSndStatuses :: DB.Connection -> ChatItemId -> IO [MemberDeliveryStatus] getGroupSndStatuses :: DB.Connection -> ChatItemId -> IO [MemberDeliveryStatus]
getGroupSndStatuses db itemId = getGroupSndStatuses db itemId =
@ -2989,7 +3037,7 @@ getGroupSndStatuses db itemId =
(Only itemId) (Only itemId)
where where
memStatus (groupMemberId, memberDeliveryStatus, sentViaProxy) = memStatus (groupMemberId, memberDeliveryStatus, sentViaProxy) =
MemberDeliveryStatus {groupMemberId, memberDeliveryStatus, sentViaProxy} MemberDeliveryStatus {groupMemberId, memberDeliveryStatus, sentViaProxy = unBI <$> sentViaProxy}
getGroupSndStatusCounts :: DB.Connection -> ChatItemId -> IO [(GroupSndStatus, Int)] getGroupSndStatusCounts :: DB.Connection -> ChatItemId -> IO [(GroupSndStatus, Int)]
getGroupSndStatusCounts db itemId = getGroupSndStatusCounts db itemId =

View file

@ -1,3 +1,4 @@
{-# LANGUAGE CPP #-}
{-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE LambdaCase #-} {-# LANGUAGE LambdaCase #-}
{-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE NamedFieldPuns #-}
@ -10,13 +11,19 @@ module Simplex.Chat.Store.NoteFolders where
import Control.Monad.Except (ExceptT (..), throwError) import Control.Monad.Except (ExceptT (..), throwError)
import Control.Monad.IO.Class (liftIO) import Control.Monad.IO.Class (liftIO)
import Data.Time (getCurrentTime) import Data.Time (getCurrentTime)
import Database.SQLite.Simple (Only (..))
import Database.SQLite.Simple.QQ (sql)
import Simplex.Chat.Store.Shared (StoreError (..)) import Simplex.Chat.Store.Shared (StoreError (..))
import Simplex.Chat.Types (NoteFolder (..), NoteFolderId, User (..)) import Simplex.Chat.Types (NoteFolder (..), NoteFolderId, User (..))
import Simplex.Messaging.Agent.Protocol (UserId) import Simplex.Messaging.Agent.Protocol (UserId)
import Simplex.Messaging.Agent.Store.AgentStore (firstRow) import Simplex.Messaging.Agent.Store.AgentStore (firstRow)
import qualified Simplex.Messaging.Agent.Store.SQLite.DB as DB import Simplex.Messaging.Agent.Store.DB (BoolInt (..))
import qualified Simplex.Messaging.Agent.Store.DB as DB
#if defined(dbPostgres)
import Database.PostgreSQL.Simple (Only (..))
import Database.PostgreSQL.Simple.SqlQQ (sql)
#else
import Database.SQLite.Simple (Only (..))
import Database.SQLite.Simple.QQ (sql)
#endif
createNoteFolder :: DB.Connection -> User -> ExceptT StoreError IO () createNoteFolder :: DB.Connection -> User -> ExceptT StoreError IO ()
createNoteFolder db User {userId} = do createNoteFolder db User {userId} = do
@ -43,13 +50,13 @@ getNoteFolder db User {userId} noteFolderId =
|] |]
(userId, noteFolderId) (userId, noteFolderId)
where where
toNoteFolder (createdAt, updatedAt, chatTs, favorite, unread) = toNoteFolder (createdAt, updatedAt, chatTs, BI favorite, BI unread) =
NoteFolder {noteFolderId, userId, createdAt, updatedAt, chatTs, favorite, unread} NoteFolder {noteFolderId, userId, createdAt, updatedAt, chatTs, favorite, unread}
updateNoteFolderUnreadChat :: DB.Connection -> User -> NoteFolder -> Bool -> IO () updateNoteFolderUnreadChat :: DB.Connection -> User -> NoteFolder -> Bool -> IO ()
updateNoteFolderUnreadChat db User {userId} NoteFolder {noteFolderId} unreadChat = do updateNoteFolderUnreadChat db User {userId} NoteFolder {noteFolderId} unreadChat = do
updatedAt <- getCurrentTime updatedAt <- getCurrentTime
DB.execute db "UPDATE note_folders SET unread_chat = ?, updated_at = ? WHERE user_id = ? AND note_folder_id = ?" (unreadChat, updatedAt, userId, noteFolderId) DB.execute db "UPDATE note_folders SET unread_chat = ?, updated_at = ? WHERE user_id = ? AND note_folder_id = ?" (BI unreadChat, updatedAt, userId, noteFolderId)
deleteNoteFolderFiles :: DB.Connection -> UserId -> NoteFolder -> IO () deleteNoteFolderFiles :: DB.Connection -> UserId -> NoteFolder -> IO ()
deleteNoteFolderFiles db userId NoteFolder {noteFolderId} = do deleteNoteFolderFiles db userId NoteFolder {noteFolderId} = do

View file

@ -0,0 +1,19 @@
{-# LANGUAGE NamedFieldPuns #-}
module Simplex.Chat.Store.Postgres.Migrations (migrations) where
import Data.List (sortOn)
import Data.Text (Text)
import Simplex.Chat.Store.Postgres.Migrations.M20241220_initial
import Simplex.Messaging.Agent.Store.Shared (Migration (..))
schemaMigrations :: [(String, Text, Maybe Text)]
schemaMigrations =
[ ("20241220_initial", m20241220_initial, Nothing)
]
-- | The list of migrations in ascending order by date
migrations :: [Migration]
migrations = sortOn name $ map migration schemaMigrations
where
migration (name, up, down) = Migration {name, up, down}

File diff suppressed because it is too large Load diff

View file

@ -1,3 +1,4 @@
{-# LANGUAGE CPP #-}
{-# LANGUAGE DataKinds #-} {-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE DuplicateRecordFields #-}
@ -86,8 +87,6 @@ import Data.Text (Text)
import qualified Data.Text as T import qualified Data.Text as T
import Data.Text.Encoding (decodeLatin1, encodeUtf8) import Data.Text.Encoding (decodeLatin1, encodeUtf8)
import Data.Time.Clock (UTCTime (..), getCurrentTime) import Data.Time.Clock (UTCTime (..), getCurrentTime)
import Database.SQLite.Simple (NamedParam (..), Only (..), Query, (:.) (..))
import Database.SQLite.Simple.QQ (sql)
import Simplex.Chat.Call import Simplex.Chat.Call
import Simplex.Chat.Messages import Simplex.Chat.Messages
import Simplex.Chat.Operators import Simplex.Chat.Operators
@ -101,7 +100,8 @@ import Simplex.Chat.Types.UITheme
import Simplex.Messaging.Agent.Env.SQLite (ServerRoles (..)) import Simplex.Messaging.Agent.Env.SQLite (ServerRoles (..))
import Simplex.Messaging.Agent.Protocol (ACorrId, ConnId, UserId) import Simplex.Messaging.Agent.Protocol (ACorrId, ConnId, UserId)
import Simplex.Messaging.Agent.Store.AgentStore (firstRow, maybeFirstRow) import Simplex.Messaging.Agent.Store.AgentStore (firstRow, maybeFirstRow)
import qualified Simplex.Messaging.Agent.Store.SQLite.DB as DB import Simplex.Messaging.Agent.Store.DB (BoolInt (..))
import qualified Simplex.Messaging.Agent.Store.DB as DB
import qualified Simplex.Messaging.Crypto as C import qualified Simplex.Messaging.Crypto as C
import qualified Simplex.Messaging.Crypto.Ratchet as CR import qualified Simplex.Messaging.Crypto.Ratchet as CR
import Simplex.Messaging.Encoding.String import Simplex.Messaging.Encoding.String
@ -109,6 +109,13 @@ import Simplex.Messaging.Parsers (defaultJSON)
import Simplex.Messaging.Protocol (BasicAuth (..), ProtoServerWithAuth (..), ProtocolServer (..), ProtocolType (..), ProtocolTypeI (..), SProtocolType (..), SubscriptionMode) import Simplex.Messaging.Protocol (BasicAuth (..), ProtoServerWithAuth (..), ProtocolServer (..), ProtocolType (..), ProtocolTypeI (..), SProtocolType (..), SubscriptionMode)
import Simplex.Messaging.Transport.Client (TransportHost) import Simplex.Messaging.Transport.Client (TransportHost)
import Simplex.Messaging.Util (eitherToMaybe, safeDecodeUtf8) import Simplex.Messaging.Util (eitherToMaybe, safeDecodeUtf8)
#if defined(dbPostgres)
import Database.PostgreSQL.Simple (Only (..), Query, (:.) (..))
import Database.PostgreSQL.Simple.SqlQQ (sql)
#else
import Database.SQLite.Simple (Only (..), Query, (:.) (..))
import Database.SQLite.Simple.QQ (sql)
#endif
createUserRecord :: DB.Connection -> AgentUserId -> Profile -> Bool -> ExceptT StoreError IO User createUserRecord :: DB.Connection -> AgentUserId -> Profile -> Bool -> ExceptT StoreError IO User
createUserRecord db auId p activeUser = createUserRecordAt db auId p activeUser =<< liftIO getCurrentTime createUserRecord db auId p activeUser = createUserRecordAt db auId p activeUser =<< liftIO getCurrentTime
@ -124,7 +131,7 @@ createUserRecordAt db (AgentUserId auId) Profile {displayName, fullName, image,
DB.execute DB.execute
db db
"INSERT INTO users (agent_user_id, local_display_name, active_user, active_order, contact_id, show_ntfs, send_rcpts_contacts, send_rcpts_small_groups, created_at, updated_at) VALUES (?,?,?,?,0,?,?,?,?,?)" "INSERT INTO users (agent_user_id, local_display_name, active_user, active_order, contact_id, show_ntfs, send_rcpts_contacts, send_rcpts_small_groups, created_at, updated_at) VALUES (?,?,?,?,0,?,?,?,?,?)"
(auId, displayName, activeUser, order, showNtfs, sendRcptsContacts, sendRcptsSmallGroups, currentTs, currentTs) (auId, displayName, BI activeUser, order, BI showNtfs, BI sendRcptsContacts, BI sendRcptsSmallGroups, currentTs, currentTs)
userId <- insertedRowId db userId <- insertedRowId db
DB.execute DB.execute
db db
@ -138,10 +145,10 @@ createUserRecordAt db (AgentUserId auId) Profile {displayName, fullName, image,
DB.execute DB.execute
db db
"INSERT INTO contacts (contact_profile_id, local_display_name, user_id, is_user, created_at, updated_at, chat_ts) VALUES (?,?,?,?,?,?,?)" "INSERT INTO contacts (contact_profile_id, local_display_name, user_id, is_user, created_at, updated_at, chat_ts) VALUES (?,?,?,?,?,?,?)"
(profileId, displayName, userId, True, currentTs, currentTs, currentTs) (profileId, displayName, userId, BI True, currentTs, currentTs, currentTs)
contactId <- insertedRowId db contactId <- insertedRowId db
DB.execute db "UPDATE users SET contact_id = ? WHERE user_id = ?" (contactId, userId) DB.execute db "UPDATE users SET contact_id = ? WHERE user_id = ?" (contactId, userId)
pure $ toUser $ (userId, auId, contactId, profileId, activeUser, order, displayName, fullName, image, Nothing, userPreferences) :. (showNtfs, sendRcptsContacts, sendRcptsSmallGroups, Nothing, Nothing, Nothing, Nothing) pure $ toUser $ (userId, auId, contactId, profileId, BI activeUser, order, displayName, fullName, image, Nothing, userPreferences) :. (BI showNtfs, BI sendRcptsContacts, BI sendRcptsSmallGroups, Nothing, Nothing, Nothing, Nothing)
getUsersInfo :: DB.Connection -> IO [UserInfo] getUsersInfo :: DB.Connection -> IO [UserInfo]
getUsersInfo db = getUsers db >>= mapM getUserInfo getUsersInfo db = getUsers db >>= mapM getUserInfo
@ -253,7 +260,7 @@ updateUserPrivacy db User {userId, showNtfs, viewPwdHash} =
SET view_pwd_hash = ?, view_pwd_salt = ?, show_ntfs = ? SET view_pwd_hash = ?, view_pwd_salt = ?, show_ntfs = ?
WHERE user_id = ? WHERE user_id = ?
|] |]
(hashSalt viewPwdHash :. (showNtfs, userId)) (hashSalt viewPwdHash :. (BI showNtfs, userId))
where where
hashSalt = L.unzip . fmap (\UserPwdHash {hash, salt} -> (hash, salt)) hashSalt = L.unzip . fmap (\UserPwdHash {hash, salt} -> (hash, salt))
@ -262,16 +269,16 @@ updateAllContactReceipts db onOff =
DB.execute DB.execute
db db
"UPDATE users SET send_rcpts_contacts = ?, send_rcpts_small_groups = ? WHERE view_pwd_hash IS NULL" "UPDATE users SET send_rcpts_contacts = ?, send_rcpts_small_groups = ? WHERE view_pwd_hash IS NULL"
(onOff, onOff) (BI onOff, BI onOff)
updateUserContactReceipts :: DB.Connection -> User -> UserMsgReceiptSettings -> IO () updateUserContactReceipts :: DB.Connection -> User -> UserMsgReceiptSettings -> IO ()
updateUserContactReceipts db User {userId} UserMsgReceiptSettings {enable, clearOverrides} = do updateUserContactReceipts db User {userId} UserMsgReceiptSettings {enable, clearOverrides} = do
DB.execute db "UPDATE users SET send_rcpts_contacts = ? WHERE user_id = ?" (enable, userId) DB.execute db "UPDATE users SET send_rcpts_contacts = ? WHERE user_id = ?" (BI enable, userId)
when clearOverrides $ DB.execute_ db "UPDATE contacts SET send_rcpts = NULL" when clearOverrides $ DB.execute_ db "UPDATE contacts SET send_rcpts = NULL"
updateUserGroupReceipts :: DB.Connection -> User -> UserMsgReceiptSettings -> IO () updateUserGroupReceipts :: DB.Connection -> User -> UserMsgReceiptSettings -> IO ()
updateUserGroupReceipts db User {userId} UserMsgReceiptSettings {enable, clearOverrides} = do updateUserGroupReceipts db User {userId} UserMsgReceiptSettings {enable, clearOverrides} = do
DB.execute db "UPDATE users SET send_rcpts_small_groups = ? WHERE user_id = ?" (enable, userId) DB.execute db "UPDATE users SET send_rcpts_small_groups = ? WHERE user_id = ?" (BI enable, userId)
when clearOverrides $ DB.execute_ db "UPDATE groups SET send_rcpts = NULL" when clearOverrides $ DB.execute_ db "UPDATE groups SET send_rcpts = NULL"
updateUserProfile :: DB.Connection -> User -> Profile -> ExceptT StoreError IO User updateUserProfile :: DB.Connection -> User -> Profile -> ExceptT StoreError IO User
@ -403,21 +410,21 @@ deleteUserAddress db user@User {userId} = do
) )
|] |]
(Only userId) (Only userId)
DB.executeNamed DB.execute
db db
[sql| [sql|
DELETE FROM display_names DELETE FROM display_names
WHERE user_id = :user_id WHERE user_id = ?
AND local_display_name in ( AND local_display_name in (
SELECT cr.local_display_name SELECT cr.local_display_name
FROM contact_requests cr FROM contact_requests cr
JOIN user_contact_links uc USING (user_contact_link_id) JOIN user_contact_links uc USING (user_contact_link_id)
WHERE uc.user_id = :user_id AND uc.local_display_name = '' AND uc.group_id IS NULL WHERE uc.user_id = ? AND uc.local_display_name = '' AND uc.group_id IS NULL
) )
AND local_display_name NOT IN (SELECT local_display_name FROM users WHERE user_id = :user_id) AND local_display_name NOT IN (SELECT local_display_name FROM users WHERE user_id = ?)
|] |]
[":user_id" := userId] (userId, userId, userId)
DB.executeNamed DB.execute
db db
[sql| [sql|
DELETE FROM contact_profiles DELETE FROM contact_profiles
@ -425,10 +432,10 @@ deleteUserAddress db user@User {userId} = do
SELECT cr.contact_profile_id SELECT cr.contact_profile_id
FROM contact_requests cr FROM contact_requests cr
JOIN user_contact_links uc USING (user_contact_link_id) JOIN user_contact_links uc USING (user_contact_link_id)
WHERE uc.user_id = :user_id AND uc.local_display_name = '' AND uc.group_id IS NULL WHERE uc.user_id = ? AND uc.local_display_name = '' AND uc.group_id IS NULL
) )
|] |]
[":user_id" := userId] (Only userId)
void $ setUserProfileContactLink db user Nothing void $ setUserProfileContactLink db user Nothing
DB.execute db "DELETE FROM user_contact_links WHERE user_id = ? AND local_display_name = '' AND group_id IS NULL" (Only userId) DB.execute db "DELETE FROM user_contact_links WHERE user_id = ? AND local_display_name = '' AND group_id IS NULL" (Only userId)
@ -455,8 +462,8 @@ $(J.deriveJSON defaultJSON ''AutoAccept)
$(J.deriveJSON defaultJSON ''UserContactLink) $(J.deriveJSON defaultJSON ''UserContactLink)
toUserContactLink :: (ConnReqContact, Bool, Bool, IncognitoEnabled, Maybe MsgContent) -> UserContactLink toUserContactLink :: (ConnReqContact, BoolInt, BoolInt, BoolInt, Maybe MsgContent) -> UserContactLink
toUserContactLink (connReq, autoAccept, businessAddress, acceptIncognito, autoReply) = toUserContactLink (connReq, BI autoAccept, BI businessAddress, BI acceptIncognito, autoReply) =
UserContactLink connReq $ UserContactLink connReq $
if autoAccept then Just AutoAccept {businessAddress, acceptIncognito, autoReply} else Nothing if autoAccept then Just AutoAccept {businessAddress, acceptIncognito, autoReply} else Nothing
@ -528,8 +535,8 @@ updateUserAddressAutoAccept db user@User {userId} autoAccept = do
|] |]
(ucl :. Only userId) (ucl :. Only userId)
ucl = case autoAccept of ucl = case autoAccept of
Just AutoAccept {businessAddress, acceptIncognito, autoReply} -> (True, businessAddress, acceptIncognito, autoReply) Just AutoAccept {businessAddress, acceptIncognito, autoReply} -> (BI True, BI businessAddress, BI acceptIncognito, autoReply)
_ -> (False, False, False, Nothing) _ -> (BI False, BI False, BI False, Nothing)
getProtocolServers :: forall p. ProtocolTypeI p => DB.Connection -> SProtocolType p -> User -> IO [UserServer p] getProtocolServers :: forall p. ProtocolTypeI p => DB.Connection -> SProtocolType p -> User -> IO [UserServer p]
getProtocolServers db p User {userId} = getProtocolServers db p User {userId} =
@ -543,10 +550,10 @@ getProtocolServers db p User {userId} =
|] |]
(userId, decodeLatin1 $ strEncode p) (userId, decodeLatin1 $ strEncode p)
where where
toUserServer :: (DBEntityId, NonEmpty TransportHost, String, C.KeyHash, Maybe Text, Bool, Maybe Bool, Bool) -> UserServer p toUserServer :: (DBEntityId, NonEmpty TransportHost, String, C.KeyHash, Maybe Text, BoolInt, Maybe BoolInt, BoolInt) -> UserServer p
toUserServer (serverId, host, port, keyHash, auth_, preset, tested, enabled) = toUserServer (serverId, host, port, keyHash, auth_, BI preset, tested, BI enabled) =
let server = ProtoServerWithAuth (ProtocolServer p host port keyHash) (BasicAuth . encodeUtf8 <$> auth_) let server = ProtoServerWithAuth (ProtocolServer p host port keyHash) (BasicAuth . encodeUtf8 <$> auth_)
in UserServer {serverId, server, preset, tested, enabled, deleted = False} in UserServer {serverId, server, preset, tested = unBI <$> tested, enabled, deleted = False}
insertProtocolServer :: forall p. ProtocolTypeI p => DB.Connection -> SProtocolType p -> User -> UTCTime -> NewUserServer p -> IO (UserServer p) insertProtocolServer :: forall p. ProtocolTypeI p => DB.Connection -> SProtocolType p -> User -> UTCTime -> NewUserServer p -> IO (UserServer p)
insertProtocolServer db p User {userId} ts srv@UserServer {server, preset, tested, enabled} = do insertProtocolServer db p User {userId} ts srv@UserServer {server, preset, tested, enabled} = do
@ -557,7 +564,7 @@ insertProtocolServer db p User {userId} ts srv@UserServer {server, preset, teste
(protocol, host, port, key_hash, basic_auth, preset, tested, enabled, user_id, created_at, updated_at) (protocol, host, port, key_hash, basic_auth, preset, tested, enabled, user_id, created_at, updated_at)
VALUES (?,?,?,?,?,?,?,?,?,?,?) VALUES (?,?,?,?,?,?,?,?,?,?,?)
|] |]
(serverColumns p server :. (preset, tested, enabled, userId, ts, ts)) (serverColumns p server :. (BI preset, BI <$> tested, BI enabled, userId, ts, ts))
sId <- insertedRowId db sId <- insertedRowId db
pure (srv :: NewUserServer p) {serverId = DBEntityId sId} pure (srv :: NewUserServer p) {serverId = DBEntityId sId}
@ -571,7 +578,7 @@ updateProtocolServer db p ts UserServer {serverId, server, preset, tested, enabl
preset = ?, tested = ?, enabled = ?, updated_at = ? preset = ?, tested = ?, enabled = ?, updated_at = ?
WHERE smp_server_id = ? WHERE smp_server_id = ?
|] |]
(serverColumns p server :. (preset, tested, enabled, ts, serverId)) (serverColumns p server :. (BI preset, BI <$> tested, BI enabled, ts, serverId))
serverColumns :: ProtocolTypeI p => SProtocolType p -> ProtoServerWithAuth p -> (Text, NonEmpty TransportHost, String, C.KeyHash, Maybe Text) serverColumns :: ProtocolTypeI p => SProtocolType p -> ProtoServerWithAuth p -> (Text, NonEmpty TransportHost, String, C.KeyHash, Maybe Text)
serverColumns p (ProtoServerWithAuth ProtocolServer {host, port, keyHash} auth_) = serverColumns p (ProtoServerWithAuth ProtocolServer {host, port, keyHash} auth_) =
@ -611,7 +618,7 @@ updateServerOperator db currentTs ServerOperator {operatorId, enabled, smpRoles,
SET enabled = ?, smp_role_storage = ?, smp_role_proxy = ?, xftp_role_storage = ?, xftp_role_proxy = ?, updated_at = ? SET enabled = ?, smp_role_storage = ?, smp_role_proxy = ?, xftp_role_storage = ?, xftp_role_proxy = ?, updated_at = ?
WHERE server_operator_id = ? WHERE server_operator_id = ?
|] |]
(enabled, storage smpRoles, proxy smpRoles, storage xftpRoles, proxy xftpRoles, currentTs, operatorId) (BI enabled, BI (storage smpRoles), BI (proxy smpRoles), BI (storage xftpRoles), BI (proxy xftpRoles), currentTs, operatorId)
getUpdateServerOperators :: DB.Connection -> NonEmpty PresetOperator -> Bool -> IO [(Maybe PresetOperator, Maybe ServerOperator)] getUpdateServerOperators :: DB.Connection -> NonEmpty PresetOperator -> Bool -> IO [(Maybe PresetOperator, Maybe ServerOperator)]
getUpdateServerOperators db presetOps newUser = do getUpdateServerOperators db presetOps newUser = do
@ -649,7 +656,7 @@ getUpdateServerOperators db presetOps newUser = do
SET trade_name = ?, legal_name = ?, server_domains = ?, enabled = ?, smp_role_storage = ?, smp_role_proxy = ?, xftp_role_storage = ?, xftp_role_proxy = ? SET trade_name = ?, legal_name = ?, server_domains = ?, enabled = ?, smp_role_storage = ?, smp_role_proxy = ?, xftp_role_storage = ?, xftp_role_proxy = ?
WHERE server_operator_id = ? WHERE server_operator_id = ?
|] |]
(tradeName, legalName, T.intercalate "," serverDomains, enabled, storage smpRoles, proxy smpRoles, storage xftpRoles, proxy xftpRoles, operatorId) (tradeName, legalName, T.intercalate "," serverDomains, BI enabled, BI (storage smpRoles), BI (proxy smpRoles), BI (storage xftpRoles), BI (proxy xftpRoles), operatorId)
insertOperator :: NewServerOperator -> IO ServerOperator insertOperator :: NewServerOperator -> IO ServerOperator
insertOperator op@ServerOperator {operatorTag, tradeName, legalName, serverDomains, enabled, smpRoles, xftpRoles} = do insertOperator op@ServerOperator {operatorTag, tradeName, legalName, serverDomains, enabled, smpRoles, xftpRoles} = do
DB.execute DB.execute
@ -659,7 +666,7 @@ getUpdateServerOperators db presetOps newUser = do
(server_operator_tag, trade_name, legal_name, server_domains, enabled, smp_role_storage, smp_role_proxy, xftp_role_storage, xftp_role_proxy) (server_operator_tag, trade_name, legal_name, server_domains, enabled, smp_role_storage, smp_role_proxy, xftp_role_storage, xftp_role_proxy)
VALUES (?,?,?,?,?,?,?,?,?) VALUES (?,?,?,?,?,?,?,?,?)
|] |]
(operatorTag, tradeName, legalName, T.intercalate "," serverDomains, enabled, storage smpRoles, proxy smpRoles, storage xftpRoles, proxy xftpRoles) (operatorTag, tradeName, legalName, T.intercalate "," serverDomains, BI enabled, BI (storage smpRoles), BI (proxy smpRoles), BI (storage xftpRoles), BI (proxy xftpRoles))
opId <- insertedRowId db opId <- insertedRowId db
pure op {operatorId = DBEntityId opId} pure op {operatorId = DBEntityId opId}
autoAcceptConditions op UsageConditions {conditionsCommit} now = autoAcceptConditions op UsageConditions {conditionsCommit} now =
@ -677,8 +684,8 @@ serverOperatorQuery =
getServerOperators_ :: DB.Connection -> IO [ServerOperator] getServerOperators_ :: DB.Connection -> IO [ServerOperator]
getServerOperators_ db = map toServerOperator <$> DB.query_ db serverOperatorQuery getServerOperators_ db = map toServerOperator <$> DB.query_ db serverOperatorQuery
toServerOperator :: (DBEntityId, Maybe OperatorTag, Text, Maybe Text, Text, Bool) :. (Bool, Bool) :. (Bool, Bool) -> ServerOperator toServerOperator :: (DBEntityId, Maybe OperatorTag, Text, Maybe Text, Text, BoolInt) :. (BoolInt, BoolInt) :. (BoolInt, BoolInt) -> ServerOperator
toServerOperator ((operatorId, operatorTag, tradeName, legalName, domains, enabled) :. smpRoles' :. xftpRoles') = toServerOperator ((operatorId, operatorTag, tradeName, legalName, domains, BI enabled) :. smpRoles' :. xftpRoles') =
ServerOperator ServerOperator
{ operatorId, { operatorId,
operatorTag, operatorTag,
@ -691,7 +698,7 @@ toServerOperator ((operatorId, operatorTag, tradeName, legalName, domains, enabl
xftpRoles = serverRoles xftpRoles' xftpRoles = serverRoles xftpRoles'
} }
where where
serverRoles (storage, proxy) = ServerRoles {storage, proxy} serverRoles (BI storage, BI proxy) = ServerRoles {storage, proxy}
getOperatorConditions_ :: DB.Connection -> ServerOperator -> UsageConditions -> Maybe UsageConditions -> UTCTime -> IO ConditionsAcceptance getOperatorConditions_ :: DB.Connection -> ServerOperator -> UsageConditions -> Maybe UsageConditions -> UTCTime -> IO ConditionsAcceptance
getOperatorConditions_ db ServerOperator {operatorId} UsageConditions {conditionsCommit = currentCommit, createdAt, notifiedAt} latestAcceptedConds_ now = do getOperatorConditions_ db ServerOperator {operatorId} UsageConditions {conditionsCommit = currentCommit, createdAt, notifiedAt} latestAcceptedConds_ now = do
@ -711,7 +718,7 @@ getOperatorConditions_ db ServerOperator {operatorId} UsageConditions {condition
|] |]
(Only operatorId) (Only operatorId)
pure $ case operatorAcceptedConds_ of pure $ case operatorAcceptedConds_ of
Just (operatorCommit, acceptedAt_, autoAccept) Just (operatorCommit, acceptedAt_, BI autoAccept)
| operatorCommit /= latestAcceptedCommit -> CARequired Nothing -- TODO should we consider this operator disabled? | operatorCommit /= latestAcceptedCommit -> CARequired Nothing -- TODO should we consider this operator disabled?
| currentCommit /= latestAcceptedCommit -> CARequired $ conditionsRequiredOrDeadline createdAt (fromMaybe now notifiedAt) | currentCommit /= latestAcceptedCommit -> CARequired $ conditionsRequiredOrDeadline createdAt (fromMaybe now notifiedAt)
| otherwise -> CAAccepted acceptedAt_ autoAccept | otherwise -> CAAccepted acceptedAt_ autoAccept
@ -767,23 +774,23 @@ acceptConditions db condId opIds acceptedAt = do
acceptConditions_ :: DB.Connection -> ServerOperator -> Text -> UTCTime -> Bool -> IO () acceptConditions_ :: DB.Connection -> ServerOperator -> Text -> UTCTime -> Bool -> IO ()
acceptConditions_ db ServerOperator {operatorId, operatorTag} conditionsCommit acceptedAt autoAccepted = do acceptConditions_ db ServerOperator {operatorId, operatorTag} conditionsCommit acceptedAt autoAccepted = do
acceptedAt_ :: Maybe (Maybe UTCTime) <- maybeFirstRow fromOnly $ DB.query db "SELECT accepted_at FROM operator_usage_conditions WHERE server_operator_id = ? AND conditions_commit == ?" (operatorId, conditionsCommit) acceptedAt_ :: Maybe (Maybe UTCTime) <- maybeFirstRow fromOnly $ DB.query db "SELECT accepted_at FROM operator_usage_conditions WHERE server_operator_id = ? AND conditions_commit = ?" (operatorId, conditionsCommit)
case acceptedAt_ of case acceptedAt_ of
Just Nothing -> Just Nothing ->
DB.execute DB.execute
db db
(q <> "ON CONFLICT (server_operator_id, conditions_commit) DO UPDATE SET accepted_at = ?, auto_accepted = ?") (q <> "ON CONFLICT (server_operator_id, conditions_commit) DO UPDATE SET accepted_at = ?, auto_accepted = ?")
(operatorId, operatorTag, conditionsCommit, acceptedAt, autoAccepted, acceptedAt, autoAccepted) (operatorId, operatorTag, conditionsCommit, acceptedAt, BI autoAccepted, acceptedAt, BI autoAccepted)
Just (Just _) -> Just (Just _) ->
DB.execute DB.execute
db db
(q <> "ON CONFLICT (server_operator_id, conditions_commit) DO NOTHING") (q <> "ON CONFLICT (server_operator_id, conditions_commit) DO NOTHING")
(operatorId, operatorTag, conditionsCommit, acceptedAt, autoAccepted) (operatorId, operatorTag, conditionsCommit, acceptedAt, BI autoAccepted)
Nothing -> Nothing ->
DB.execute DB.execute
db db
q q
(operatorId, operatorTag, conditionsCommit, acceptedAt, autoAccepted) (operatorId, operatorTag, conditionsCommit, acceptedAt, BI autoAccepted)
where where
q = q =
[sql| [sql|
@ -820,7 +827,7 @@ setUserServers' db user@User {userId} ts UpdatedUserOperatorServers {operator, s
| deleted -> pure Nothing | deleted -> pure Nothing
| otherwise -> Just <$> insertProtocolServer db p user ts s | otherwise -> Just <$> insertProtocolServer db p user ts s
DBEntityId srvId DBEntityId srvId
| deleted -> Nothing <$ DB.execute db "DELETE FROM protocol_servers WHERE user_id = ? AND smp_server_id = ? AND preset = ?" (userId, srvId, False) | deleted -> Nothing <$ DB.execute db "DELETE FROM protocol_servers WHERE user_id = ? AND smp_server_id = ? AND preset = ?" (userId, srvId, BI False)
| otherwise -> Just s <$ updateProtocolServer db p ts s | otherwise -> Just s <$ updateProtocolServer db p ts s
createCall :: DB.Connection -> User -> Call -> UTCTime -> IO () createCall :: DB.Connection -> User -> Call -> UTCTime -> IO ()

View file

@ -1,3 +1,4 @@
{-# LANGUAGE CPP #-}
{-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE OverloadedStrings #-}
@ -8,19 +9,23 @@ module Simplex.Chat.Store.Remote where
import Control.Monad.Except import Control.Monad.Except
import Data.Int (Int64) import Data.Int (Int64)
import Data.Text (Text) import Data.Text (Text)
import Data.Text.Encoding (encodeUtf8, decodeASCII) import Data.Text.Encoding (decodeASCII, encodeUtf8)
import Data.Word (Word16) import Data.Word (Word16)
import Database.SQLite.Simple (Only (..))
import qualified Database.SQLite.Simple as SQL
import Database.SQLite.Simple.QQ (sql)
import Simplex.Chat.Remote.Types import Simplex.Chat.Remote.Types
import Simplex.Chat.Store.Shared import Simplex.Chat.Store.Shared
import Simplex.Messaging.Agent.Store.AgentStore (firstRow, maybeFirstRow) import Simplex.Messaging.Agent.Store.AgentStore (firstRow, maybeFirstRow)
import qualified Simplex.Messaging.Agent.Store.SQLite.DB as DB import qualified Simplex.Messaging.Agent.Store.DB as DB
import qualified Simplex.Messaging.Crypto as C import qualified Simplex.Messaging.Crypto as C
import Simplex.Messaging.Encoding.String (StrEncoding (..)) import Simplex.Messaging.Encoding.String (StrEncoding (..))
import Simplex.RemoteControl.Types import Simplex.RemoteControl.Types
import UnliftIO import UnliftIO
#if defined(dbPostgres)
import Database.PostgreSQL.Simple (Only (..), Query)
import Database.PostgreSQL.Simple.SqlQQ (sql)
#else
import Database.SQLite.Simple (Only (..), Query)
import Database.SQLite.Simple.QQ (sql)
#endif
insertRemoteHost :: DB.Connection -> Text -> FilePath -> Maybe RCCtrlAddress -> Maybe Word16 -> RCHostPairing -> ExceptT StoreError IO RemoteHostId insertRemoteHost :: DB.Connection -> Text -> FilePath -> Maybe RCCtrlAddress -> Maybe Word16 -> RCHostPairing -> ExceptT StoreError IO RemoteHostId
insertRemoteHost db hostDeviceName storePath rcAddr_ bindPort_ RCHostPairing {caKey, caCert, idPrivKey, knownHost = kh_} = do insertRemoteHost db hostDeviceName storePath rcAddr_ bindPort_ RCHostPairing {caKey, caCert, idPrivKey, knownHost = kh_} = do
@ -54,7 +59,7 @@ getRemoteHostByFingerprint db fingerprint =
maybeFirstRow toRemoteHost $ maybeFirstRow toRemoteHost $
DB.query db (remoteHostQuery <> " WHERE host_fingerprint = ?") (Only fingerprint) DB.query db (remoteHostQuery <> " WHERE host_fingerprint = ?") (Only fingerprint)
remoteHostQuery :: SQL.Query remoteHostQuery :: Query
remoteHostQuery = remoteHostQuery =
[sql| [sql|
SELECT remote_host_id, host_device_name, store_path, ca_key, ca_cert, id_key, host_fingerprint, host_dh_pub, bind_iface, bind_addr, bind_port SELECT remote_host_id, host_device_name, store_path, ca_key, ca_cert, id_key, host_fingerprint, host_dh_pub, bind_iface, bind_addr, bind_port
@ -117,7 +122,7 @@ getRemoteCtrlByFingerprint db fingerprint =
maybeFirstRow toRemoteCtrl $ maybeFirstRow toRemoteCtrl $
DB.query db (remoteCtrlQuery <> " WHERE ctrl_fingerprint = ?") (Only fingerprint) DB.query db (remoteCtrlQuery <> " WHERE ctrl_fingerprint = ?") (Only fingerprint)
remoteCtrlQuery :: SQL.Query remoteCtrlQuery :: Query
remoteCtrlQuery = remoteCtrlQuery =
[sql| [sql|
SELECT remote_ctrl_id, ctrl_device_name, ca_key, ca_cert, ctrl_fingerprint, id_pub, dh_priv_key, prev_dh_priv_key SELECT remote_ctrl_id, ctrl_device_name, ca_key, ca_cert, ctrl_fingerprint, id_pub, dh_priv_key, prev_dh_priv_key

View file

@ -1,128 +1,128 @@
{-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE NamedFieldPuns #-}
module Simplex.Chat.Store.Migrations (migrations) where module Simplex.Chat.Store.SQLite.Migrations (migrations) where
import Data.List (sortOn) import Data.List (sortOn)
import Database.SQLite.Simple (Query (..)) import Database.SQLite.Simple (Query (..))
import Simplex.Chat.Migrations.M20220101_initial import Simplex.Chat.Store.SQLite.Migrations.M20220101_initial
import Simplex.Chat.Migrations.M20220122_v1_1 import Simplex.Chat.Store.SQLite.Migrations.M20220122_v1_1
import Simplex.Chat.Migrations.M20220205_chat_item_status import Simplex.Chat.Store.SQLite.Migrations.M20220205_chat_item_status
import Simplex.Chat.Migrations.M20220210_deduplicate_contact_requests import Simplex.Chat.Store.SQLite.Migrations.M20220210_deduplicate_contact_requests
import Simplex.Chat.Migrations.M20220224_messages_fks import Simplex.Chat.Store.SQLite.Migrations.M20220224_messages_fks
import Simplex.Chat.Migrations.M20220301_smp_servers import Simplex.Chat.Store.SQLite.Migrations.M20220301_smp_servers
import Simplex.Chat.Migrations.M20220302_profile_images import Simplex.Chat.Store.SQLite.Migrations.M20220302_profile_images
import Simplex.Chat.Migrations.M20220304_msg_quotes import Simplex.Chat.Store.SQLite.Migrations.M20220304_msg_quotes
import Simplex.Chat.Migrations.M20220321_chat_item_edited import Simplex.Chat.Store.SQLite.Migrations.M20220321_chat_item_edited
import Simplex.Chat.Migrations.M20220404_files_status_fields import Simplex.Chat.Store.SQLite.Migrations.M20220404_files_status_fields
import Simplex.Chat.Migrations.M20220514_profiles_user_id import Simplex.Chat.Store.SQLite.Migrations.M20220514_profiles_user_id
import Simplex.Chat.Migrations.M20220626_auto_reply import Simplex.Chat.Store.SQLite.Migrations.M20220626_auto_reply
import Simplex.Chat.Migrations.M20220702_calls import Simplex.Chat.Store.SQLite.Migrations.M20220702_calls
import Simplex.Chat.Migrations.M20220715_groups_chat_item_id import Simplex.Chat.Store.SQLite.Migrations.M20220715_groups_chat_item_id
import Simplex.Chat.Migrations.M20220811_chat_items_indices import Simplex.Chat.Store.SQLite.Migrations.M20220811_chat_items_indices
import Simplex.Chat.Migrations.M20220812_incognito_profiles import Simplex.Chat.Store.SQLite.Migrations.M20220812_incognito_profiles
import Simplex.Chat.Migrations.M20220818_chat_notifications import Simplex.Chat.Store.SQLite.Migrations.M20220818_chat_notifications
import Simplex.Chat.Migrations.M20220822_groups_host_conn_custom_user_profile_id import Simplex.Chat.Store.SQLite.Migrations.M20220822_groups_host_conn_custom_user_profile_id
import Simplex.Chat.Migrations.M20220823_delete_broken_group_event_chat_items import Simplex.Chat.Store.SQLite.Migrations.M20220823_delete_broken_group_event_chat_items
import Simplex.Chat.Migrations.M20220824_profiles_local_alias import Simplex.Chat.Store.SQLite.Migrations.M20220824_profiles_local_alias
import Simplex.Chat.Migrations.M20220909_commands import Simplex.Chat.Store.SQLite.Migrations.M20220909_commands
import Simplex.Chat.Migrations.M20220926_connection_alias import Simplex.Chat.Store.SQLite.Migrations.M20220926_connection_alias
import Simplex.Chat.Migrations.M20220928_settings import Simplex.Chat.Store.SQLite.Migrations.M20220928_settings
import Simplex.Chat.Migrations.M20221001_shared_msg_id_indices import Simplex.Chat.Store.SQLite.Migrations.M20221001_shared_msg_id_indices
import Simplex.Chat.Migrations.M20221003_delete_broken_integrity_error_chat_items import Simplex.Chat.Store.SQLite.Migrations.M20221003_delete_broken_integrity_error_chat_items
import Simplex.Chat.Migrations.M20221004_idx_msg_deliveries_message_id import Simplex.Chat.Store.SQLite.Migrations.M20221004_idx_msg_deliveries_message_id
import Simplex.Chat.Migrations.M20221011_user_contact_links_group_id import Simplex.Chat.Store.SQLite.Migrations.M20221011_user_contact_links_group_id
import Simplex.Chat.Migrations.M20221012_inline_files import Simplex.Chat.Store.SQLite.Migrations.M20221012_inline_files
import Simplex.Chat.Migrations.M20221019_unread_chat import Simplex.Chat.Store.SQLite.Migrations.M20221019_unread_chat
import Simplex.Chat.Migrations.M20221021_auto_accept__group_links import Simplex.Chat.Store.SQLite.Migrations.M20221021_auto_accept__group_links
import Simplex.Chat.Migrations.M20221024_contact_used import Simplex.Chat.Store.SQLite.Migrations.M20221024_contact_used
import Simplex.Chat.Migrations.M20221025_chat_settings import Simplex.Chat.Store.SQLite.Migrations.M20221025_chat_settings
import Simplex.Chat.Migrations.M20221029_group_link_id import Simplex.Chat.Store.SQLite.Migrations.M20221029_group_link_id
import Simplex.Chat.Migrations.M20221112_server_password import Simplex.Chat.Store.SQLite.Migrations.M20221112_server_password
import Simplex.Chat.Migrations.M20221115_server_cfg import Simplex.Chat.Store.SQLite.Migrations.M20221115_server_cfg
import Simplex.Chat.Migrations.M20221129_delete_group_feature_items import Simplex.Chat.Store.SQLite.Migrations.M20221129_delete_group_feature_items
import Simplex.Chat.Migrations.M20221130_delete_item_deleted import Simplex.Chat.Store.SQLite.Migrations.M20221130_delete_item_deleted
import Simplex.Chat.Migrations.M20221209_verified_connection import Simplex.Chat.Store.SQLite.Migrations.M20221209_verified_connection
import Simplex.Chat.Migrations.M20221210_idxs import Simplex.Chat.Store.SQLite.Migrations.M20221210_idxs
import Simplex.Chat.Migrations.M20221211_group_description import Simplex.Chat.Store.SQLite.Migrations.M20221211_group_description
import Simplex.Chat.Migrations.M20221212_chat_items_timed import Simplex.Chat.Store.SQLite.Migrations.M20221212_chat_items_timed
import Simplex.Chat.Migrations.M20221214_live_message import Simplex.Chat.Store.SQLite.Migrations.M20221214_live_message
import Simplex.Chat.Migrations.M20221222_chat_ts import Simplex.Chat.Store.SQLite.Migrations.M20221222_chat_ts
import Simplex.Chat.Migrations.M20221223_idx_chat_items_item_status import Simplex.Chat.Store.SQLite.Migrations.M20221223_idx_chat_items_item_status
import Simplex.Chat.Migrations.M20221230_idxs import Simplex.Chat.Store.SQLite.Migrations.M20221230_idxs
import Simplex.Chat.Migrations.M20230107_connections_auth_err_counter import Simplex.Chat.Store.SQLite.Migrations.M20230107_connections_auth_err_counter
import Simplex.Chat.Migrations.M20230111_users_agent_user_id import Simplex.Chat.Store.SQLite.Migrations.M20230111_users_agent_user_id
import Simplex.Chat.Migrations.M20230117_fkey_indexes import Simplex.Chat.Store.SQLite.Migrations.M20230117_fkey_indexes
import Simplex.Chat.Migrations.M20230118_recreate_smp_servers import Simplex.Chat.Store.SQLite.Migrations.M20230118_recreate_smp_servers
import Simplex.Chat.Migrations.M20230129_drop_chat_items_group_idx import Simplex.Chat.Store.SQLite.Migrations.M20230129_drop_chat_items_group_idx
import Simplex.Chat.Migrations.M20230206_item_deleted_by_group_member_id import Simplex.Chat.Store.SQLite.Migrations.M20230206_item_deleted_by_group_member_id
import Simplex.Chat.Migrations.M20230303_group_link_role import Simplex.Chat.Store.SQLite.Migrations.M20230303_group_link_role
import Simplex.Chat.Migrations.M20230317_hidden_profiles import Simplex.Chat.Store.SQLite.Migrations.M20230317_hidden_profiles
import Simplex.Chat.Migrations.M20230318_file_description import Simplex.Chat.Store.SQLite.Migrations.M20230318_file_description
import Simplex.Chat.Migrations.M20230321_agent_file_deleted import Simplex.Chat.Store.SQLite.Migrations.M20230321_agent_file_deleted
import Simplex.Chat.Migrations.M20230328_files_protocol import Simplex.Chat.Store.SQLite.Migrations.M20230328_files_protocol
import Simplex.Chat.Migrations.M20230402_protocol_servers import Simplex.Chat.Store.SQLite.Migrations.M20230402_protocol_servers
import Simplex.Chat.Migrations.M20230411_extra_xftp_file_descriptions import Simplex.Chat.Store.SQLite.Migrations.M20230411_extra_xftp_file_descriptions
import Simplex.Chat.Migrations.M20230420_rcv_files_to_receive import Simplex.Chat.Store.SQLite.Migrations.M20230420_rcv_files_to_receive
import Simplex.Chat.Migrations.M20230422_profile_contact_links import Simplex.Chat.Store.SQLite.Migrations.M20230422_profile_contact_links
import Simplex.Chat.Migrations.M20230504_recreate_msg_delivery_events_cleanup_messages import Simplex.Chat.Store.SQLite.Migrations.M20230504_recreate_msg_delivery_events_cleanup_messages
import Simplex.Chat.Migrations.M20230505_chat_item_versions import Simplex.Chat.Store.SQLite.Migrations.M20230505_chat_item_versions
import Simplex.Chat.Migrations.M20230511_reactions import Simplex.Chat.Store.SQLite.Migrations.M20230511_reactions
import Simplex.Chat.Migrations.M20230519_item_deleted_ts import Simplex.Chat.Store.SQLite.Migrations.M20230519_item_deleted_ts
import Simplex.Chat.Migrations.M20230526_indexes import Simplex.Chat.Store.SQLite.Migrations.M20230526_indexes
import Simplex.Chat.Migrations.M20230529_indexes import Simplex.Chat.Store.SQLite.Migrations.M20230529_indexes
import Simplex.Chat.Migrations.M20230608_deleted_contacts import Simplex.Chat.Store.SQLite.Migrations.M20230608_deleted_contacts
import Simplex.Chat.Migrations.M20230618_favorite_chats import Simplex.Chat.Store.SQLite.Migrations.M20230618_favorite_chats
import Simplex.Chat.Migrations.M20230621_chat_item_moderations import Simplex.Chat.Store.SQLite.Migrations.M20230621_chat_item_moderations
import Simplex.Chat.Migrations.M20230705_delivery_receipts import Simplex.Chat.Store.SQLite.Migrations.M20230705_delivery_receipts
import Simplex.Chat.Migrations.M20230721_group_snd_item_statuses import Simplex.Chat.Store.SQLite.Migrations.M20230721_group_snd_item_statuses
import Simplex.Chat.Migrations.M20230814_indexes import Simplex.Chat.Store.SQLite.Migrations.M20230814_indexes
import Simplex.Chat.Migrations.M20230827_file_encryption import Simplex.Chat.Store.SQLite.Migrations.M20230827_file_encryption
import Simplex.Chat.Migrations.M20230829_connections_chat_vrange import Simplex.Chat.Store.SQLite.Migrations.M20230829_connections_chat_vrange
import Simplex.Chat.Migrations.M20230903_connections_to_subscribe import Simplex.Chat.Store.SQLite.Migrations.M20230903_connections_to_subscribe
import Simplex.Chat.Migrations.M20230913_member_contacts import Simplex.Chat.Store.SQLite.Migrations.M20230913_member_contacts
import Simplex.Chat.Migrations.M20230914_member_probes import Simplex.Chat.Store.SQLite.Migrations.M20230914_member_probes
import Simplex.Chat.Migrations.M20230926_contact_status import Simplex.Chat.Store.SQLite.Migrations.M20230926_contact_status
import Simplex.Chat.Migrations.M20231002_conn_initiated import Simplex.Chat.Store.SQLite.Migrations.M20231002_conn_initiated
import Simplex.Chat.Migrations.M20231009_via_group_link_uri_hash import Simplex.Chat.Store.SQLite.Migrations.M20231009_via_group_link_uri_hash
import Simplex.Chat.Migrations.M20231010_member_settings import Simplex.Chat.Store.SQLite.Migrations.M20231010_member_settings
import Simplex.Chat.Migrations.M20231019_indexes import Simplex.Chat.Store.SQLite.Migrations.M20231019_indexes
import Simplex.Chat.Migrations.M20231030_xgrplinkmem_received import Simplex.Chat.Store.SQLite.Migrations.M20231030_xgrplinkmem_received
import Simplex.Chat.Migrations.M20231107_indexes import Simplex.Chat.Store.SQLite.Migrations.M20231107_indexes
import Simplex.Chat.Migrations.M20231113_group_forward import Simplex.Chat.Store.SQLite.Migrations.M20231113_group_forward
import Simplex.Chat.Migrations.M20231114_remote_control import Simplex.Chat.Store.SQLite.Migrations.M20231114_remote_control
import Simplex.Chat.Migrations.M20231126_remote_ctrl_address import Simplex.Chat.Store.SQLite.Migrations.M20231126_remote_ctrl_address
import Simplex.Chat.Migrations.M20231207_chat_list_pagination import Simplex.Chat.Store.SQLite.Migrations.M20231207_chat_list_pagination
import Simplex.Chat.Migrations.M20231214_item_content_tag import Simplex.Chat.Store.SQLite.Migrations.M20231214_item_content_tag
import Simplex.Chat.Migrations.M20231215_recreate_msg_deliveries import Simplex.Chat.Store.SQLite.Migrations.M20231215_recreate_msg_deliveries
import Simplex.Chat.Migrations.M20240102_note_folders import Simplex.Chat.Store.SQLite.Migrations.M20240102_note_folders
import Simplex.Chat.Migrations.M20240104_members_profile_update import Simplex.Chat.Store.SQLite.Migrations.M20240104_members_profile_update
import Simplex.Chat.Migrations.M20240115_block_member_for_all import Simplex.Chat.Store.SQLite.Migrations.M20240115_block_member_for_all
import Simplex.Chat.Migrations.M20240122_indexes import Simplex.Chat.Store.SQLite.Migrations.M20240122_indexes
import Simplex.Chat.Migrations.M20240214_redirect_file_id import Simplex.Chat.Store.SQLite.Migrations.M20240214_redirect_file_id
import Simplex.Chat.Migrations.M20240222_app_settings import Simplex.Chat.Store.SQLite.Migrations.M20240222_app_settings
import Simplex.Chat.Migrations.M20240226_users_restrict import Simplex.Chat.Store.SQLite.Migrations.M20240226_users_restrict
import Simplex.Chat.Migrations.M20240228_pq import Simplex.Chat.Store.SQLite.Migrations.M20240228_pq
import Simplex.Chat.Migrations.M20240313_drop_agent_ack_cmd_id import Simplex.Chat.Store.SQLite.Migrations.M20240313_drop_agent_ack_cmd_id
import Simplex.Chat.Migrations.M20240324_custom_data import Simplex.Chat.Store.SQLite.Migrations.M20240324_custom_data
import Simplex.Chat.Migrations.M20240402_item_forwarded import Simplex.Chat.Store.SQLite.Migrations.M20240402_item_forwarded
import Simplex.Chat.Migrations.M20240430_ui_theme import Simplex.Chat.Store.SQLite.Migrations.M20240430_ui_theme
import Simplex.Chat.Migrations.M20240501_chat_deleted import Simplex.Chat.Store.SQLite.Migrations.M20240501_chat_deleted
import Simplex.Chat.Migrations.M20240510_chat_items_via_proxy import Simplex.Chat.Store.SQLite.Migrations.M20240510_chat_items_via_proxy
import Simplex.Chat.Migrations.M20240515_rcv_files_user_approved_relays import Simplex.Chat.Store.SQLite.Migrations.M20240515_rcv_files_user_approved_relays
import Simplex.Chat.Migrations.M20240528_quota_err_counter import Simplex.Chat.Store.SQLite.Migrations.M20240528_quota_err_counter
import Simplex.Chat.Migrations.M20240827_calls_uuid import Simplex.Chat.Store.SQLite.Migrations.M20240827_calls_uuid
import Simplex.Chat.Migrations.M20240920_user_order import Simplex.Chat.Store.SQLite.Migrations.M20240920_user_order
import Simplex.Chat.Migrations.M20241008_indexes import Simplex.Chat.Store.SQLite.Migrations.M20241008_indexes
import Simplex.Chat.Migrations.M20241010_contact_requests_contact_id import Simplex.Chat.Store.SQLite.Migrations.M20241010_contact_requests_contact_id
import Simplex.Chat.Migrations.M20241023_chat_item_autoincrement_id import Simplex.Chat.Store.SQLite.Migrations.M20241023_chat_item_autoincrement_id
import Simplex.Chat.Migrations.M20241027_server_operators import Simplex.Chat.Store.SQLite.Migrations.M20241027_server_operators
import Simplex.Chat.Migrations.M20241125_indexes import Simplex.Chat.Store.SQLite.Migrations.M20241125_indexes
import Simplex.Chat.Migrations.M20241128_business_chats import Simplex.Chat.Store.SQLite.Migrations.M20241128_business_chats
import Simplex.Chat.Migrations.M20241205_business_chat_members import Simplex.Chat.Store.SQLite.Migrations.M20241205_business_chat_members
import Simplex.Chat.Migrations.M20241222_operator_conditions import Simplex.Chat.Store.SQLite.Migrations.M20241222_operator_conditions
import Simplex.Chat.Migrations.M20241223_chat_tags import Simplex.Chat.Store.SQLite.Migrations.M20241223_chat_tags
import Simplex.Chat.Migrations.M20241230_reports import Simplex.Chat.Store.SQLite.Migrations.M20241230_reports
import Simplex.Chat.Migrations.M20250105_indexes import Simplex.Chat.Store.SQLite.Migrations.M20250105_indexes
import Simplex.Messaging.Agent.Store.Shared (Migration (..)) import Simplex.Messaging.Agent.Store.Shared (Migration (..))
schemaMigrations :: [(String, Query, Maybe Query)] schemaMigrations :: [(String, Query, Maybe Query)]

View file

@ -1,6 +1,6 @@
{-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE QuasiQuotes #-}
module Simplex.Chat.Migrations.M20220101_initial where module Simplex.Chat.Store.SQLite.Migrations.M20220101_initial where
import Database.SQLite.Simple (Query) import Database.SQLite.Simple (Query)
import Database.SQLite.Simple.QQ (sql) import Database.SQLite.Simple.QQ (sql)

View file

@ -1,6 +1,6 @@
{-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE QuasiQuotes #-}
module Simplex.Chat.Migrations.M20220122_v1_1 where module Simplex.Chat.Store.SQLite.Migrations.M20220122_v1_1 where
import Database.SQLite.Simple (Query) import Database.SQLite.Simple (Query)
import Database.SQLite.Simple.QQ (sql) import Database.SQLite.Simple.QQ (sql)

View file

@ -1,6 +1,6 @@
{-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE QuasiQuotes #-}
module Simplex.Chat.Migrations.M20220205_chat_item_status where module Simplex.Chat.Store.SQLite.Migrations.M20220205_chat_item_status where
import Database.SQLite.Simple (Query) import Database.SQLite.Simple (Query)
import Database.SQLite.Simple.QQ (sql) import Database.SQLite.Simple.QQ (sql)

View file

@ -1,6 +1,6 @@
{-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE QuasiQuotes #-}
module Simplex.Chat.Migrations.M20220210_deduplicate_contact_requests where module Simplex.Chat.Store.SQLite.Migrations.M20220210_deduplicate_contact_requests where
import Database.SQLite.Simple (Query) import Database.SQLite.Simple (Query)
import Database.SQLite.Simple.QQ (sql) import Database.SQLite.Simple.QQ (sql)

View file

@ -1,6 +1,6 @@
{-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE QuasiQuotes #-}
module Simplex.Chat.Migrations.M20220224_messages_fks where module Simplex.Chat.Store.SQLite.Migrations.M20220224_messages_fks where
import Database.SQLite.Simple (Query) import Database.SQLite.Simple (Query)
import Database.SQLite.Simple.QQ (sql) import Database.SQLite.Simple.QQ (sql)

View file

@ -1,6 +1,6 @@
{-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE QuasiQuotes #-}
module Simplex.Chat.Migrations.M20220301_smp_servers where module Simplex.Chat.Store.SQLite.Migrations.M20220301_smp_servers where
import Database.SQLite.Simple (Query) import Database.SQLite.Simple (Query)
import Database.SQLite.Simple.QQ (sql) import Database.SQLite.Simple.QQ (sql)

View file

@ -1,6 +1,6 @@
{-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE QuasiQuotes #-}
module Simplex.Chat.Migrations.M20220302_profile_images where module Simplex.Chat.Store.SQLite.Migrations.M20220302_profile_images where
import Database.SQLite.Simple (Query) import Database.SQLite.Simple (Query)
import Database.SQLite.Simple.QQ (sql) import Database.SQLite.Simple.QQ (sql)

View file

@ -1,6 +1,6 @@
{-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE QuasiQuotes #-}
module Simplex.Chat.Migrations.M20220304_msg_quotes where module Simplex.Chat.Store.SQLite.Migrations.M20220304_msg_quotes where
import Database.SQLite.Simple (Query) import Database.SQLite.Simple (Query)
import Database.SQLite.Simple.QQ (sql) import Database.SQLite.Simple.QQ (sql)

View file

@ -1,6 +1,6 @@
{-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE QuasiQuotes #-}
module Simplex.Chat.Migrations.M20220321_chat_item_edited where module Simplex.Chat.Store.SQLite.Migrations.M20220321_chat_item_edited where
import Database.SQLite.Simple (Query) import Database.SQLite.Simple (Query)
import Database.SQLite.Simple.QQ (sql) import Database.SQLite.Simple.QQ (sql)

View file

@ -1,6 +1,6 @@
{-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE QuasiQuotes #-}
module Simplex.Chat.Migrations.M20220404_files_status_fields where module Simplex.Chat.Store.SQLite.Migrations.M20220404_files_status_fields where
import Database.SQLite.Simple (Query) import Database.SQLite.Simple (Query)
import Database.SQLite.Simple.QQ (sql) import Database.SQLite.Simple.QQ (sql)

View file

@ -1,6 +1,6 @@
{-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE QuasiQuotes #-}
module Simplex.Chat.Migrations.M20220514_profiles_user_id where module Simplex.Chat.Store.SQLite.Migrations.M20220514_profiles_user_id where
import Database.SQLite.Simple (Query) import Database.SQLite.Simple (Query)
import Database.SQLite.Simple.QQ (sql) import Database.SQLite.Simple.QQ (sql)

View file

@ -1,6 +1,6 @@
{-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE QuasiQuotes #-}
module Simplex.Chat.Migrations.M20220626_auto_reply where module Simplex.Chat.Store.SQLite.Migrations.M20220626_auto_reply where
import Database.SQLite.Simple (Query) import Database.SQLite.Simple (Query)
import Database.SQLite.Simple.QQ (sql) import Database.SQLite.Simple.QQ (sql)

View file

@ -1,6 +1,6 @@
{-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE QuasiQuotes #-}
module Simplex.Chat.Migrations.M20220702_calls where module Simplex.Chat.Store.SQLite.Migrations.M20220702_calls where
import Database.SQLite.Simple (Query) import Database.SQLite.Simple (Query)
import Database.SQLite.Simple.QQ (sql) import Database.SQLite.Simple.QQ (sql)

View file

@ -1,6 +1,6 @@
{-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE QuasiQuotes #-}
module Simplex.Chat.Migrations.M20220715_groups_chat_item_id where module Simplex.Chat.Store.SQLite.Migrations.M20220715_groups_chat_item_id where
import Database.SQLite.Simple (Query) import Database.SQLite.Simple (Query)
import Database.SQLite.Simple.QQ (sql) import Database.SQLite.Simple.QQ (sql)

View file

@ -1,6 +1,6 @@
{-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE QuasiQuotes #-}
module Simplex.Chat.Migrations.M20220811_chat_items_indices where module Simplex.Chat.Store.SQLite.Migrations.M20220811_chat_items_indices where
import Database.SQLite.Simple (Query) import Database.SQLite.Simple (Query)
import Database.SQLite.Simple.QQ (sql) import Database.SQLite.Simple.QQ (sql)

View file

@ -1,6 +1,6 @@
{-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE QuasiQuotes #-}
module Simplex.Chat.Migrations.M20220812_incognito_profiles where module Simplex.Chat.Store.SQLite.Migrations.M20220812_incognito_profiles where
import Database.SQLite.Simple (Query) import Database.SQLite.Simple (Query)
import Database.SQLite.Simple.QQ (sql) import Database.SQLite.Simple.QQ (sql)

View file

@ -1,6 +1,6 @@
{-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE QuasiQuotes #-}
module Simplex.Chat.Migrations.M20220818_chat_notifications where module Simplex.Chat.Store.SQLite.Migrations.M20220818_chat_notifications where
import Database.SQLite.Simple (Query) import Database.SQLite.Simple (Query)
import Database.SQLite.Simple.QQ (sql) import Database.SQLite.Simple.QQ (sql)

View file

@ -1,6 +1,6 @@
{-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE QuasiQuotes #-}
module Simplex.Chat.Migrations.M20220822_groups_host_conn_custom_user_profile_id where module Simplex.Chat.Store.SQLite.Migrations.M20220822_groups_host_conn_custom_user_profile_id where
import Database.SQLite.Simple (Query) import Database.SQLite.Simple (Query)
import Database.SQLite.Simple.QQ (sql) import Database.SQLite.Simple.QQ (sql)

View file

@ -1,6 +1,6 @@
{-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE QuasiQuotes #-}
module Simplex.Chat.Migrations.M20220823_delete_broken_group_event_chat_items where module Simplex.Chat.Store.SQLite.Migrations.M20220823_delete_broken_group_event_chat_items where
import Database.SQLite.Simple (Query) import Database.SQLite.Simple (Query)
import Database.SQLite.Simple.QQ (sql) import Database.SQLite.Simple.QQ (sql)

View file

@ -1,6 +1,6 @@
{-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE QuasiQuotes #-}
module Simplex.Chat.Migrations.M20220824_profiles_local_alias where module Simplex.Chat.Store.SQLite.Migrations.M20220824_profiles_local_alias where
import Database.SQLite.Simple (Query) import Database.SQLite.Simple (Query)
import Database.SQLite.Simple.QQ (sql) import Database.SQLite.Simple.QQ (sql)

View file

@ -1,6 +1,6 @@
{-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE QuasiQuotes #-}
module Simplex.Chat.Migrations.M20220909_commands where module Simplex.Chat.Store.SQLite.Migrations.M20220909_commands where
import Database.SQLite.Simple (Query) import Database.SQLite.Simple (Query)
import Database.SQLite.Simple.QQ (sql) import Database.SQLite.Simple.QQ (sql)

View file

@ -1,6 +1,6 @@
{-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE QuasiQuotes #-}
module Simplex.Chat.Migrations.M20220926_connection_alias where module Simplex.Chat.Store.SQLite.Migrations.M20220926_connection_alias where
import Database.SQLite.Simple (Query) import Database.SQLite.Simple (Query)
import Database.SQLite.Simple.QQ (sql) import Database.SQLite.Simple.QQ (sql)

View file

@ -1,6 +1,6 @@
{-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE QuasiQuotes #-}
module Simplex.Chat.Migrations.M20220928_settings where module Simplex.Chat.Store.SQLite.Migrations.M20220928_settings where
import Database.SQLite.Simple (Query) import Database.SQLite.Simple (Query)
import Database.SQLite.Simple.QQ (sql) import Database.SQLite.Simple.QQ (sql)

View file

@ -1,6 +1,6 @@
{-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE QuasiQuotes #-}
module Simplex.Chat.Migrations.M20221001_shared_msg_id_indices where module Simplex.Chat.Store.SQLite.Migrations.M20221001_shared_msg_id_indices where
import Database.SQLite.Simple (Query) import Database.SQLite.Simple (Query)
import Database.SQLite.Simple.QQ (sql) import Database.SQLite.Simple.QQ (sql)

View file

@ -1,6 +1,6 @@
{-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE QuasiQuotes #-}
module Simplex.Chat.Migrations.M20221003_delete_broken_integrity_error_chat_items where module Simplex.Chat.Store.SQLite.Migrations.M20221003_delete_broken_integrity_error_chat_items where
import Database.SQLite.Simple (Query) import Database.SQLite.Simple (Query)
import Database.SQLite.Simple.QQ (sql) import Database.SQLite.Simple.QQ (sql)

View file

@ -1,6 +1,6 @@
{-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE QuasiQuotes #-}
module Simplex.Chat.Migrations.M20221004_idx_msg_deliveries_message_id where module Simplex.Chat.Store.SQLite.Migrations.M20221004_idx_msg_deliveries_message_id where
import Database.SQLite.Simple (Query) import Database.SQLite.Simple (Query)
import Database.SQLite.Simple.QQ (sql) import Database.SQLite.Simple.QQ (sql)

View file

@ -1,6 +1,6 @@
{-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE QuasiQuotes #-}
module Simplex.Chat.Migrations.M20221011_user_contact_links_group_id where module Simplex.Chat.Store.SQLite.Migrations.M20221011_user_contact_links_group_id where
import Database.SQLite.Simple (Query) import Database.SQLite.Simple (Query)
import Database.SQLite.Simple.QQ (sql) import Database.SQLite.Simple.QQ (sql)

View file

@ -1,6 +1,6 @@
{-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE QuasiQuotes #-}
module Simplex.Chat.Migrations.M20221012_inline_files where module Simplex.Chat.Store.SQLite.Migrations.M20221012_inline_files where
import Database.SQLite.Simple (Query) import Database.SQLite.Simple (Query)
import Database.SQLite.Simple.QQ (sql) import Database.SQLite.Simple.QQ (sql)

View file

@ -1,6 +1,6 @@
{-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE QuasiQuotes #-}
module Simplex.Chat.Migrations.M20221019_unread_chat where module Simplex.Chat.Store.SQLite.Migrations.M20221019_unread_chat where
import Database.SQLite.Simple (Query) import Database.SQLite.Simple (Query)
import Database.SQLite.Simple.QQ (sql) import Database.SQLite.Simple.QQ (sql)

View file

@ -1,6 +1,6 @@
{-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE QuasiQuotes #-}
module Simplex.Chat.Migrations.M20221021_auto_accept__group_links where module Simplex.Chat.Store.SQLite.Migrations.M20221021_auto_accept__group_links where
import Database.SQLite.Simple (Query) import Database.SQLite.Simple (Query)
import Database.SQLite.Simple.QQ (sql) import Database.SQLite.Simple.QQ (sql)

View file

@ -1,6 +1,6 @@
{-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE QuasiQuotes #-}
module Simplex.Chat.Migrations.M20221024_contact_used where module Simplex.Chat.Store.SQLite.Migrations.M20221024_contact_used where
import Database.SQLite.Simple (Query) import Database.SQLite.Simple (Query)
import Database.SQLite.Simple.QQ (sql) import Database.SQLite.Simple.QQ (sql)

View file

@ -1,6 +1,6 @@
{-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE QuasiQuotes #-}
module Simplex.Chat.Migrations.M20221025_chat_settings where module Simplex.Chat.Store.SQLite.Migrations.M20221025_chat_settings where
import Database.SQLite.Simple (Query) import Database.SQLite.Simple (Query)
import Database.SQLite.Simple.QQ (sql) import Database.SQLite.Simple.QQ (sql)

View file

@ -1,6 +1,6 @@
{-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE QuasiQuotes #-}
module Simplex.Chat.Migrations.M20221029_group_link_id where module Simplex.Chat.Store.SQLite.Migrations.M20221029_group_link_id where
import Database.SQLite.Simple (Query) import Database.SQLite.Simple (Query)
import Database.SQLite.Simple.QQ (sql) import Database.SQLite.Simple.QQ (sql)

View file

@ -1,6 +1,6 @@
{-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE QuasiQuotes #-}
module Simplex.Chat.Migrations.M20221112_server_password where module Simplex.Chat.Store.SQLite.Migrations.M20221112_server_password where
import Database.SQLite.Simple (Query) import Database.SQLite.Simple (Query)
import Database.SQLite.Simple.QQ (sql) import Database.SQLite.Simple.QQ (sql)

View file

@ -1,6 +1,6 @@
{-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE QuasiQuotes #-}
module Simplex.Chat.Migrations.M20221115_server_cfg where module Simplex.Chat.Store.SQLite.Migrations.M20221115_server_cfg where
import Database.SQLite.Simple (Query) import Database.SQLite.Simple (Query)
import Database.SQLite.Simple.QQ (sql) import Database.SQLite.Simple.QQ (sql)

View file

@ -1,6 +1,6 @@
{-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE QuasiQuotes #-}
module Simplex.Chat.Migrations.M20221129_delete_group_feature_items where module Simplex.Chat.Store.SQLite.Migrations.M20221129_delete_group_feature_items where
import Database.SQLite.Simple (Query) import Database.SQLite.Simple (Query)
import Database.SQLite.Simple.QQ (sql) import Database.SQLite.Simple.QQ (sql)

View file

@ -1,6 +1,6 @@
{-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE QuasiQuotes #-}
module Simplex.Chat.Migrations.M20221130_delete_item_deleted where module Simplex.Chat.Store.SQLite.Migrations.M20221130_delete_item_deleted where
import Database.SQLite.Simple (Query) import Database.SQLite.Simple (Query)
import Database.SQLite.Simple.QQ (sql) import Database.SQLite.Simple.QQ (sql)

View file

@ -1,6 +1,6 @@
{-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE QuasiQuotes #-}
module Simplex.Chat.Migrations.M20221209_verified_connection where module Simplex.Chat.Store.SQLite.Migrations.M20221209_verified_connection where
import Database.SQLite.Simple (Query) import Database.SQLite.Simple (Query)
import Database.SQLite.Simple.QQ (sql) import Database.SQLite.Simple.QQ (sql)

View file

@ -1,6 +1,6 @@
{-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE QuasiQuotes #-}
module Simplex.Chat.Migrations.M20221210_idxs where module Simplex.Chat.Store.SQLite.Migrations.M20221210_idxs where
import Database.SQLite.Simple (Query) import Database.SQLite.Simple (Query)
import Database.SQLite.Simple.QQ (sql) import Database.SQLite.Simple.QQ (sql)

View file

@ -1,6 +1,6 @@
{-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE QuasiQuotes #-}
module Simplex.Chat.Migrations.M20221211_group_description where module Simplex.Chat.Store.SQLite.Migrations.M20221211_group_description where
import Database.SQLite.Simple (Query) import Database.SQLite.Simple (Query)
import Database.SQLite.Simple.QQ (sql) import Database.SQLite.Simple.QQ (sql)

View file

@ -1,6 +1,6 @@
{-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE QuasiQuotes #-}
module Simplex.Chat.Migrations.M20221212_chat_items_timed where module Simplex.Chat.Store.SQLite.Migrations.M20221212_chat_items_timed where
import Database.SQLite.Simple (Query) import Database.SQLite.Simple (Query)
import Database.SQLite.Simple.QQ (sql) import Database.SQLite.Simple.QQ (sql)

View file

@ -1,6 +1,6 @@
{-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE QuasiQuotes #-}
module Simplex.Chat.Migrations.M20221214_live_message where module Simplex.Chat.Store.SQLite.Migrations.M20221214_live_message where
import Database.SQLite.Simple (Query) import Database.SQLite.Simple (Query)
import Database.SQLite.Simple.QQ (sql) import Database.SQLite.Simple.QQ (sql)

View file

@ -1,6 +1,6 @@
{-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE QuasiQuotes #-}
module Simplex.Chat.Migrations.M20221222_chat_ts where module Simplex.Chat.Store.SQLite.Migrations.M20221222_chat_ts where
import Database.SQLite.Simple (Query) import Database.SQLite.Simple (Query)
import Database.SQLite.Simple.QQ (sql) import Database.SQLite.Simple.QQ (sql)

View file

@ -1,6 +1,6 @@
{-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE QuasiQuotes #-}
module Simplex.Chat.Migrations.M20221223_idx_chat_items_item_status where module Simplex.Chat.Store.SQLite.Migrations.M20221223_idx_chat_items_item_status where
import Database.SQLite.Simple (Query) import Database.SQLite.Simple (Query)
import Database.SQLite.Simple.QQ (sql) import Database.SQLite.Simple.QQ (sql)

View file

@ -1,6 +1,6 @@
{-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE QuasiQuotes #-}
module Simplex.Chat.Migrations.M20221230_idxs where module Simplex.Chat.Store.SQLite.Migrations.M20221230_idxs where
import Database.SQLite.Simple (Query) import Database.SQLite.Simple (Query)
import Database.SQLite.Simple.QQ (sql) import Database.SQLite.Simple.QQ (sql)

View file

@ -1,6 +1,6 @@
{-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE QuasiQuotes #-}
module Simplex.Chat.Migrations.M20230107_connections_auth_err_counter where module Simplex.Chat.Store.SQLite.Migrations.M20230107_connections_auth_err_counter where
import Database.SQLite.Simple (Query) import Database.SQLite.Simple (Query)
import Database.SQLite.Simple.QQ (sql) import Database.SQLite.Simple.QQ (sql)

View file

@ -1,6 +1,6 @@
{-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE QuasiQuotes #-}
module Simplex.Chat.Migrations.M20230111_users_agent_user_id where module Simplex.Chat.Store.SQLite.Migrations.M20230111_users_agent_user_id where
import Database.SQLite.Simple (Query) import Database.SQLite.Simple (Query)
import Database.SQLite.Simple.QQ (sql) import Database.SQLite.Simple.QQ (sql)

View file

@ -1,6 +1,6 @@
{-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE QuasiQuotes #-}
module Simplex.Chat.Migrations.M20230117_fkey_indexes where module Simplex.Chat.Store.SQLite.Migrations.M20230117_fkey_indexes where
import Database.SQLite.Simple (Query) import Database.SQLite.Simple (Query)
import Database.SQLite.Simple.QQ (sql) import Database.SQLite.Simple.QQ (sql)

View file

@ -1,6 +1,6 @@
{-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE QuasiQuotes #-}
module Simplex.Chat.Migrations.M20230118_recreate_smp_servers where module Simplex.Chat.Store.SQLite.Migrations.M20230118_recreate_smp_servers where
import Database.SQLite.Simple (Query) import Database.SQLite.Simple (Query)
import Database.SQLite.Simple.QQ (sql) import Database.SQLite.Simple.QQ (sql)

View file

@ -1,6 +1,6 @@
{-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE QuasiQuotes #-}
module Simplex.Chat.Migrations.M20230129_drop_chat_items_group_idx where module Simplex.Chat.Store.SQLite.Migrations.M20230129_drop_chat_items_group_idx where
import Database.SQLite.Simple (Query) import Database.SQLite.Simple (Query)
import Database.SQLite.Simple.QQ (sql) import Database.SQLite.Simple.QQ (sql)

View file

@ -1,6 +1,6 @@
{-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE QuasiQuotes #-}
module Simplex.Chat.Migrations.M20230206_item_deleted_by_group_member_id where module Simplex.Chat.Store.SQLite.Migrations.M20230206_item_deleted_by_group_member_id where
import Database.SQLite.Simple (Query) import Database.SQLite.Simple (Query)
import Database.SQLite.Simple.QQ (sql) import Database.SQLite.Simple.QQ (sql)

View file

@ -1,6 +1,6 @@
{-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE QuasiQuotes #-}
module Simplex.Chat.Migrations.M20230303_group_link_role where module Simplex.Chat.Store.SQLite.Migrations.M20230303_group_link_role where
import Database.SQLite.Simple (Query) import Database.SQLite.Simple (Query)
import Database.SQLite.Simple.QQ (sql) import Database.SQLite.Simple.QQ (sql)

View file

@ -1,6 +1,6 @@
{-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE QuasiQuotes #-}
module Simplex.Chat.Migrations.M20230317_hidden_profiles where module Simplex.Chat.Store.SQLite.Migrations.M20230317_hidden_profiles where
import Database.SQLite.Simple (Query) import Database.SQLite.Simple (Query)
import Database.SQLite.Simple.QQ (sql) import Database.SQLite.Simple.QQ (sql)

View file

@ -1,6 +1,6 @@
{-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE QuasiQuotes #-}
module Simplex.Chat.Migrations.M20230318_file_description where module Simplex.Chat.Store.SQLite.Migrations.M20230318_file_description where
import Database.SQLite.Simple (Query) import Database.SQLite.Simple (Query)
import Database.SQLite.Simple.QQ (sql) import Database.SQLite.Simple.QQ (sql)

View file

@ -1,6 +1,6 @@
{-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE QuasiQuotes #-}
module Simplex.Chat.Migrations.M20230321_agent_file_deleted where module Simplex.Chat.Store.SQLite.Migrations.M20230321_agent_file_deleted where
import Database.SQLite.Simple (Query) import Database.SQLite.Simple (Query)
import Database.SQLite.Simple.QQ (sql) import Database.SQLite.Simple.QQ (sql)

View file

@ -1,6 +1,6 @@
{-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE QuasiQuotes #-}
module Simplex.Chat.Migrations.M20230328_files_protocol where module Simplex.Chat.Store.SQLite.Migrations.M20230328_files_protocol where
import Database.SQLite.Simple (Query) import Database.SQLite.Simple (Query)
import Database.SQLite.Simple.QQ (sql) import Database.SQLite.Simple.QQ (sql)

View file

@ -1,6 +1,6 @@
{-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE QuasiQuotes #-}
module Simplex.Chat.Migrations.M20230402_protocol_servers where module Simplex.Chat.Store.SQLite.Migrations.M20230402_protocol_servers where
import Database.SQLite.Simple (Query) import Database.SQLite.Simple (Query)
import Database.SQLite.Simple.QQ (sql) import Database.SQLite.Simple.QQ (sql)

View file

@ -1,6 +1,6 @@
{-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE QuasiQuotes #-}
module Simplex.Chat.Migrations.M20230411_extra_xftp_file_descriptions where module Simplex.Chat.Store.SQLite.Migrations.M20230411_extra_xftp_file_descriptions where
import Database.SQLite.Simple (Query) import Database.SQLite.Simple (Query)
import Database.SQLite.Simple.QQ (sql) import Database.SQLite.Simple.QQ (sql)

View file

@ -1,6 +1,6 @@
{-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE QuasiQuotes #-}
module Simplex.Chat.Migrations.M20230420_rcv_files_to_receive where module Simplex.Chat.Store.SQLite.Migrations.M20230420_rcv_files_to_receive where
import Database.SQLite.Simple (Query) import Database.SQLite.Simple (Query)
import Database.SQLite.Simple.QQ (sql) import Database.SQLite.Simple.QQ (sql)

View file

@ -1,6 +1,6 @@
{-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE QuasiQuotes #-}
module Simplex.Chat.Migrations.M20230422_profile_contact_links where module Simplex.Chat.Store.SQLite.Migrations.M20230422_profile_contact_links where
import Database.SQLite.Simple (Query) import Database.SQLite.Simple (Query)
import Database.SQLite.Simple.QQ (sql) import Database.SQLite.Simple.QQ (sql)

Some files were not shown because too many files have changed in this diff Show more