From e05a35e26ef975ced39cc45c435ad4cf9f3c9695 Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Fri, 10 Jan 2025 15:27:29 +0400 Subject: [PATCH] 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 --- apps/simplex-bot-advanced/Main.hs | 4 +- apps/simplex-bot/Main.hs | 4 +- .../src/Broadcast/Bot.hs | 4 +- .../src/Broadcast/Options.hs | 8 +- .../src/Directory/Options.hs | 10 +- .../src/Directory/Service.hs | 4 +- cabal.project | 2 +- scripts/nix/sha256map.nix | 2 +- simplex-chat.cabal | 299 ++--- src/Simplex/Chat.hs | 25 +- src/Simplex/Chat/Archive.hs | 3 +- src/Simplex/Chat/Call.hs | 21 +- src/Simplex/Chat/Controller.hs | 46 +- src/Simplex/Chat/Core.hs | 6 +- src/Simplex/Chat/Library/Commands.hs | 47 +- src/Simplex/Chat/Library/Internal.hs | 2 +- src/Simplex/Chat/Library/Subscriber.hs | 4 +- src/Simplex/Chat/Messages.hs | 10 +- src/Simplex/Chat/Messages/CIContent.hs | 10 +- src/Simplex/Chat/Mobile.hs | 14 +- src/Simplex/Chat/Operators.hs | 25 +- src/Simplex/Chat/Options.hs | 53 +- src/Simplex/Chat/Options/DB.hs | 14 + src/Simplex/Chat/Options/Postgres.hs | 37 + src/Simplex/Chat/Options/SQLite.hs | 44 + src/Simplex/Chat/Protocol.hs | 32 +- src/Simplex/Chat/Remote.hs | 8 +- src/Simplex/Chat/Store.hs | 34 +- src/Simplex/Chat/Store/AppSettings.hs | 9 +- src/Simplex/Chat/Store/Connections.hs | 73 +- src/Simplex/Chat/Store/Direct.hs | 184 +-- src/Simplex/Chat/Store/Files.hs | 54 +- src/Simplex/Chat/Store/Groups.hs | 342 +++--- src/Simplex/Chat/Store/Messages.hs | 328 +++--- src/Simplex/Chat/Store/NoteFolders.hs | 17 +- src/Simplex/Chat/Store/Postgres/Migrations.hs | 19 + .../Postgres/Migrations/M20241220_initial.hs | 1012 +++++++++++++++++ src/Simplex/Chat/Store/Profiles.hs | 109 +- src/Simplex/Chat/Store/Remote.hs | 19 +- .../Chat/Store/{ => SQLite}/Migrations.hs | 240 ++-- .../SQLite}/Migrations/M20220101_initial.hs | 2 +- .../SQLite}/Migrations/M20220122_v1_1.hs | 2 +- .../Migrations/M20220205_chat_item_status.hs | 2 +- .../M20220210_deduplicate_contact_requests.hs | 2 +- .../Migrations/M20220224_messages_fks.hs | 2 +- .../Migrations/M20220301_smp_servers.hs | 2 +- .../Migrations/M20220302_profile_images.hs | 2 +- .../Migrations/M20220304_msg_quotes.hs | 2 +- .../Migrations/M20220321_chat_item_edited.hs | 2 +- .../M20220404_files_status_fields.hs | 2 +- .../Migrations/M20220514_profiles_user_id.hs | 2 +- .../Migrations/M20220626_auto_reply.hs | 2 +- .../SQLite}/Migrations/M20220702_calls.hs | 2 +- .../M20220715_groups_chat_item_id.hs | 2 +- .../M20220811_chat_items_indices.hs | 2 +- .../M20220812_incognito_profiles.hs | 2 +- .../M20220818_chat_notifications.hs | 2 +- ...groups_host_conn_custom_user_profile_id.hs | 2 +- ...23_delete_broken_group_event_chat_items.hs | 2 +- .../M20220824_profiles_local_alias.hs | 2 +- .../SQLite}/Migrations/M20220909_commands.hs | 2 +- .../Migrations/M20220926_connection_alias.hs | 2 +- .../SQLite}/Migrations/M20220928_settings.hs | 2 +- .../M20221001_shared_msg_id_indices.hs | 2 +- ...elete_broken_integrity_error_chat_items.hs | 2 +- ...M20221004_idx_msg_deliveries_message_id.hs | 2 +- .../M20221011_user_contact_links_group_id.hs | 2 +- .../Migrations/M20221012_inline_files.hs | 2 +- .../Migrations/M20221019_unread_chat.hs | 2 +- .../M20221021_auto_accept__group_links.hs | 2 +- .../Migrations/M20221024_contact_used.hs | 2 +- .../Migrations/M20221025_chat_settings.hs | 2 +- .../Migrations/M20221029_group_link_id.hs | 2 +- .../Migrations/M20221112_server_password.hs | 2 +- .../Migrations/M20221115_server_cfg.hs | 2 +- .../M20221129_delete_group_feature_items.hs | 2 +- .../M20221130_delete_item_deleted.hs | 2 +- .../M20221209_verified_connection.hs | 2 +- .../SQLite}/Migrations/M20221210_idxs.hs | 2 +- .../Migrations/M20221211_group_description.hs | 2 +- .../Migrations/M20221212_chat_items_timed.hs | 2 +- .../Migrations/M20221214_live_message.hs | 2 +- .../SQLite}/Migrations/M20221222_chat_ts.hs | 2 +- .../M20221223_idx_chat_items_item_status.hs | 2 +- .../SQLite}/Migrations/M20221230_idxs.hs | 2 +- .../M20230107_connections_auth_err_counter.hs | 2 +- .../M20230111_users_agent_user_id.hs | 2 +- .../Migrations/M20230117_fkey_indexes.hs | 2 +- .../M20230118_recreate_smp_servers.hs | 2 +- .../M20230129_drop_chat_items_group_idx.hs | 2 +- ...0230206_item_deleted_by_group_member_id.hs | 2 +- .../Migrations/M20230303_group_link_role.hs | 2 +- .../Migrations/M20230317_hidden_profiles.hs | 2 +- .../Migrations/M20230318_file_description.hs | 2 +- .../M20230321_agent_file_deleted.hs | 2 +- .../Migrations/M20230328_files_protocol.hs | 2 +- .../Migrations/M20230402_protocol_servers.hs | 2 +- .../M20230411_extra_xftp_file_descriptions.hs | 2 +- .../M20230420_rcv_files_to_receive.hs | 2 +- .../M20230422_profile_contact_links.hs | 2 +- ...te_msg_delivery_events_cleanup_messages.hs | 2 +- .../M20230505_chat_item_versions.hs | 2 +- .../SQLite}/Migrations/M20230511_reactions.hs | 2 +- .../Migrations/M20230519_item_deleted_ts.hs | 2 +- .../SQLite}/Migrations/M20230526_indexes.hs | 2 +- .../SQLite}/Migrations/M20230529_indexes.hs | 2 +- .../Migrations/M20230608_deleted_contacts.hs | 2 +- .../Migrations/M20230618_favorite_chats.hs | 2 +- .../M20230621_chat_item_moderations.hs | 2 +- .../Migrations/M20230705_delivery_receipts.hs | 2 +- .../M20230721_group_snd_item_statuses.hs | 2 +- .../SQLite}/Migrations/M20230814_indexes.hs | 2 +- .../Migrations/M20230827_file_encryption.hs | 2 +- .../M20230829_connections_chat_vrange.hs | 2 +- .../M20230903_connections_to_subscribe.hs | 2 +- .../Migrations/M20230913_member_contacts.hs | 2 +- .../Migrations/M20230914_member_probes.hs | 2 +- .../Migrations/M20230926_contact_status.hs | 2 +- .../Migrations/M20231002_conn_initiated.hs | 2 +- .../M20231009_via_group_link_uri_hash.hs | 2 +- .../Migrations/M20231010_member_settings.hs | 2 +- .../SQLite}/Migrations/M20231019_indexes.hs | 2 +- .../M20231030_xgrplinkmem_received.hs | 2 +- .../SQLite}/Migrations/M20231107_indexes.hs | 2 +- .../Migrations/M20231113_group_forward.hs | 2 +- .../Migrations/M20231114_remote_control.hs | 2 +- .../M20231126_remote_ctrl_address.hs | 2 +- .../M20231207_chat_list_pagination.hs | 2 +- .../Migrations/M20231214_item_content_tag.hs | 2 +- .../M20231215_recreate_msg_deliveries.hs | 2 +- .../Migrations/M20240102_note_folders.hs | 2 +- .../M20240104_members_profile_update.hs | 2 +- .../M20240115_block_member_for_all.hs | 2 +- .../SQLite}/Migrations/M20240122_indexes.hs | 2 +- .../Migrations/M20240214_redirect_file_id.hs | 2 +- .../Migrations/M20240222_app_settings.hs | 2 +- .../Migrations/M20240226_users_restrict.hs | 2 +- .../SQLite}/Migrations/M20240228_pq.hs | 2 +- .../M20240313_drop_agent_ack_cmd_id.hs | 2 +- .../Migrations/M20240324_custom_data.hs | 2 +- .../Migrations/M20240402_item_forwarded.hs | 2 +- .../SQLite}/Migrations/M20240430_ui_theme.hs | 2 +- .../Migrations/M20240501_chat_deleted.hs | 2 +- .../M20240510_chat_items_via_proxy.hs | 2 +- ...20240515_rcv_files_user_approved_relays.hs | 2 +- .../Migrations/M20240528_quota_err_counter.hs | 2 +- .../Migrations/M20240827_calls_uuid.hs | 2 +- .../Migrations/M20240920_user_order.hs | 2 +- .../SQLite}/Migrations/M20241008_indexes.hs | 2 +- .../M20241010_contact_requests_contact_id.hs | 2 +- .../M20241023_chat_item_autoincrement_id.hs | 2 +- .../Migrations/M20241027_server_operators.hs | 2 +- .../SQLite}/Migrations/M20241125_indexes.hs | 2 +- .../Migrations/M20241128_business_chats.hs | 2 +- .../M20241205_business_chat_members.hs | 2 +- .../M20241222_operator_conditions.hs | 2 +- .../SQLite}/Migrations/M20241223_chat_tags.hs | 2 +- .../SQLite}/Migrations/M20241230_reports.hs | 2 +- .../SQLite}/Migrations/M20250105_indexes.hs | 2 +- .../SQLite}/Migrations/chat_lint.sql | 0 .../SQLite}/Migrations/chat_schema.sql | 0 src/Simplex/Chat/Store/Shared.hs | 92 +- src/Simplex/Chat/Terminal.hs | 30 +- src/Simplex/Chat/Terminal/Input.hs | 17 +- src/Simplex/Chat/Terminal/Main.hs | 5 +- src/Simplex/Chat/Types.hs | 97 +- src/Simplex/Chat/Types/Preferences.hs | 17 +- src/Simplex/Chat/Types/Shared.hs | 14 +- src/Simplex/Chat/Types/UITheme.hs | 10 +- src/Simplex/Chat/Types/Util.hs | 16 - src/Simplex/Chat/View.hs | 14 +- tests/Bots/BroadcastTests.hs | 18 +- tests/Bots/DirectoryTests.hs | 14 +- tests/ChatClient.hs | 83 +- tests/ChatTests/ChatList.hs | 4 +- tests/ChatTests/Direct.hs | 23 +- tests/ChatTests/Forward.hs | 6 +- tests/ChatTests/Groups.hs | 18 +- tests/ChatTests/Local.hs | 20 +- tests/ChatTests/Profiles.hs | 16 +- tests/ChatTests/Utils.hs | 16 +- tests/JSONFixtures.hs | 62 + tests/JSONTests.hs | 2 +- tests/MobileTests.hs | 58 +- tests/RemoteTests.hs | 6 +- tests/SchemaDump.hs | 6 +- tests/Test.hs | 58 +- 187 files changed, 2847 insertions(+), 1291 deletions(-) create mode 100644 src/Simplex/Chat/Options/DB.hs create mode 100644 src/Simplex/Chat/Options/Postgres.hs create mode 100644 src/Simplex/Chat/Options/SQLite.hs create mode 100644 src/Simplex/Chat/Store/Postgres/Migrations.hs create mode 100644 src/Simplex/Chat/Store/Postgres/Migrations/M20241220_initial.hs rename src/Simplex/Chat/Store/{ => SQLite}/Migrations.hs (56%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20220101_initial.hs (99%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20220122_v1_1.hs (99%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20220205_chat_item_status.hs (86%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20220210_deduplicate_contact_requests.hs (91%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20220224_messages_fks.hs (84%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20220301_smp_servers.hs (87%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20220302_profile_images.hs (79%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20220304_msg_quotes.hs (94%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20220321_chat_item_edited.hs (76%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20220404_files_status_fields.hs (83%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20220514_profiles_user_id.hs (88%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20220626_auto_reply.hs (85%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20220702_calls.hs (91%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20220715_groups_chat_item_id.hs (78%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20220811_chat_items_indices.hs (82%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20220812_incognito_profiles.hs (89%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20220818_chat_notifications.hs (78%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20220822_groups_host_conn_custom_user_profile_id.hs (80%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20220823_delete_broken_group_event_chat_items.hs (80%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20220824_profiles_local_alias.hs (83%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20220909_commands.hs (91%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20220926_connection_alias.hs (83%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20220928_settings.hs (86%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20221001_shared_msg_id_indices.hs (85%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20221003_delete_broken_integrity_error_chat_items.hs (75%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20221004_idx_msg_deliveries_message_id.hs (76%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20221011_user_contact_links_group_id.hs (81%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20221012_inline_files.hs (92%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20221019_unread_chat.hs (87%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20221021_auto_accept__group_links.hs (88%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20221024_contact_used.hs (87%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20221025_chat_settings.hs (87%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20221029_group_link_id.hs (80%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20221112_server_password.hs (75%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20221115_server_cfg.hs (87%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20221129_delete_group_feature_items.hs (80%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20221130_delete_item_deleted.hs (78%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20221209_verified_connection.hs (80%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20221210_idxs.hs (85%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20221211_group_description.hs (76%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20221212_chat_items_timed.hs (82%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20221214_live_message.hs (75%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20221222_chat_ts.hs (81%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20221223_idx_chat_items_item_status.hs (75%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20221230_idxs.hs (82%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20230107_connections_auth_err_counter.hs (82%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20230111_users_agent_user_id.hs (82%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20230117_fkey_indexes.hs (98%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20230118_recreate_smp_servers.hs (93%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20230129_drop_chat_items_group_idx.hs (73%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20230206_item_deleted_by_group_member_id.hs (82%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20230303_group_link_role.hs (78%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20230317_hidden_profiles.hs (88%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20230318_file_description.hs (95%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20230321_agent_file_deleted.hs (90%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20230328_files_protocol.hs (83%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20230402_protocol_servers.hs (86%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20230411_extra_xftp_file_descriptions.hs (92%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20230420_rcv_files_to_receive.hs (82%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20230422_profile_contact_links.hs (83%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20230504_recreate_msg_delivery_events_cleanup_messages.hs (92%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20230505_chat_item_versions.hs (91%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20230511_reactions.hs (96%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20230519_item_deleted_ts.hs (83%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20230526_indexes.hs (87%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20230529_indexes.hs (91%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20230608_deleted_contacts.hs (87%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20230618_favorite_chats.hs (86%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20230621_chat_item_moderations.hs (95%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20230705_delivery_receipts.hs (90%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20230721_group_snd_item_statuses.hs (92%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20230814_indexes.hs (84%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20230827_file_encryption.hs (86%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20230829_connections_chat_vrange.hs (91%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20230903_connections_to_subscribe.hs (86%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20230913_member_contacts.hs (90%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20230914_member_probes.hs (98%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20230926_contact_status.hs (83%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20231002_conn_initiated.hs (91%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20231009_via_group_link_uri_hash.hs (88%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20231010_member_settings.hs (84%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20231019_indexes.hs (93%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20231030_xgrplinkmem_received.hs (84%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20231107_indexes.hs (84%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20231113_group_forward.hs (97%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20231114_remote_control.hs (95%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20231126_remote_ctrl_address.hs (88%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20231207_chat_list_pagination.hs (92%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20231214_item_content_tag.hs (83%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20231215_recreate_msg_deliveries.hs (98%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20240102_note_folders.hs (94%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20240104_members_profile_update.hs (86%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20240115_block_member_for_all.hs (83%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20240122_indexes.hs (93%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20240214_redirect_file_id.hs (87%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20240222_app_settings.hs (82%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20240226_users_restrict.hs (89%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20240228_pq.hs (93%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20240313_drop_agent_ack_cmd_id.hs (87%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20240324_custom_data.hs (85%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20240402_item_forwarded.hs (95%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20240430_ui_theme.hs (88%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20240501_chat_deleted.hs (83%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20240510_chat_items_via_proxy.hs (86%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20240515_rcv_files_user_approved_relays.hs (83%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20240528_quota_err_counter.hs (84%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20240827_calls_uuid.hs (83%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20240920_user_order.hs (83%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20241008_indexes.hs (84%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20241010_contact_requests_contact_id.hs (87%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20241023_chat_item_autoincrement_id.hs (90%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20241027_server_operators.hs (96%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20241125_indexes.hs (95%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20241128_business_chats.hs (91%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20241205_business_chat_members.hs (83%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20241222_operator_conditions.hs (84%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20241223_chat_tags.hs (95%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20241230_reports.hs (83%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/M20250105_indexes.hs (90%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/chat_lint.sql (100%) rename src/Simplex/Chat/{ => Store/SQLite}/Migrations/chat_schema.sql (100%) create mode 100644 tests/JSONFixtures.hs diff --git a/apps/simplex-bot-advanced/Main.hs b/apps/simplex-bot-advanced/Main.hs index cedbd4fe34..6c3d8240e4 100644 --- a/apps/simplex-bot-advanced/Main.hs +++ b/apps/simplex-bot-advanced/Main.hs @@ -31,9 +31,9 @@ main = do welcomeGetOpts :: IO ChatOpts welcomeGetOpts = do 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 $ "db: " <> dbFilePrefix <> "_chat.db, " <> dbFilePrefix <> "_agent.db" + printDbOpts coreOptions pure opts welcomeMessage :: Text diff --git a/apps/simplex-bot/Main.hs b/apps/simplex-bot/Main.hs index c24f9c251f..290e6286b1 100644 --- a/apps/simplex-bot/Main.hs +++ b/apps/simplex-bot/Main.hs @@ -25,7 +25,7 @@ welcomeMessage = "Hello! I am a simple squaring bot.\nIf you send me a number, I welcomeGetOpts :: IO ChatOpts welcomeGetOpts = do 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 $ "db: " <> dbFilePrefix <> "_chat.db, " <> dbFilePrefix <> "_agent.db" + printDbOpts coreOptions pure opts diff --git a/apps/simplex-broadcast-bot/src/Broadcast/Bot.hs b/apps/simplex-broadcast-bot/src/Broadcast/Bot.hs index c526d64886..9dc927af9e 100644 --- a/apps/simplex-broadcast-bot/src/Broadcast/Bot.hs +++ b/apps/simplex-broadcast-bot/src/Broadcast/Bot.hs @@ -27,9 +27,9 @@ import System.Directory (getAppUserDataDirectory) welcomeGetOpts :: IO BroadcastBotOpts welcomeGetOpts = do 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 $ "db: " <> dbFilePrefix <> "_chat.db, " <> dbFilePrefix <> "_agent.db" + printDbOpts coreOptions pure opts broadcastBot :: BroadcastBotOpts -> User -> ChatController -> IO () diff --git a/apps/simplex-broadcast-bot/src/Broadcast/Options.hs b/apps/simplex-broadcast-bot/src/Broadcast/Options.hs index 5bc4ffef25..e695b5069d 100644 --- a/apps/simplex-broadcast-bot/src/Broadcast/Options.hs +++ b/apps/simplex-broadcast-bot/src/Broadcast/Options.hs @@ -27,8 +27,8 @@ defaultProhibitedMessage :: [KnownContact] -> Text defaultProhibitedMessage ps = "Sorry, only these users can broadcast messages: " <> knownContactNames ps <> ". Your message is deleted." broadcastBotOpts :: FilePath -> FilePath -> Parser BroadcastBotOpts -broadcastBotOpts appDir defaultDbFileName = do - coreOptions <- coreChatOptsP appDir defaultDbFileName +broadcastBotOpts appDir defaultDbName = do + coreOptions <- coreChatOptsP appDir defaultDbName publishers <- option parseKnownContacts @@ -61,10 +61,10 @@ broadcastBotOpts appDir defaultDbFileName = do } getBroadcastBotOpts :: FilePath -> FilePath -> IO BroadcastBotOpts -getBroadcastBotOpts appDir defaultDbFileName = +getBroadcastBotOpts appDir defaultDbName = execParser $ 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") where versionStr = versionString versionNumber diff --git a/apps/simplex-directory-service/src/Directory/Options.hs b/apps/simplex-directory-service/src/Directory/Options.hs index 70135e4ccf..3017f82c8c 100644 --- a/apps/simplex-directory-service/src/Directory/Options.hs +++ b/apps/simplex-directory-service/src/Directory/Options.hs @@ -15,7 +15,7 @@ import qualified Data.Text as T import Options.Applicative import Simplex.Chat.Bot.KnownContacts 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 { coreOptions :: CoreChatOpts, @@ -29,8 +29,8 @@ data DirectoryOpts = DirectoryOpts } directoryOpts :: FilePath -> FilePath -> Parser DirectoryOpts -directoryOpts appDir defaultDbFileName = do - coreOptions <- coreChatOptsP appDir defaultDbFileName +directoryOpts appDir defaultDbName = do + coreOptions <- coreChatOptsP appDir defaultDbName adminUsers <- option parseKnownContacts @@ -77,10 +77,10 @@ directoryOpts appDir defaultDbFileName = do } getDirectoryOpts :: FilePath -> FilePath -> IO DirectoryOpts -getDirectoryOpts appDir defaultDbFileName = +getDirectoryOpts appDir defaultDbName = execParser $ 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") where versionStr = versionString versionNumber diff --git a/apps/simplex-directory-service/src/Directory/Service.hs b/apps/simplex-directory-service/src/Directory/Service.hs index 5b96603f68..102fe4cf7e 100644 --- a/apps/simplex-directory-service/src/Directory/Service.hs +++ b/apps/simplex-directory-service/src/Directory/Service.hs @@ -74,10 +74,10 @@ newServiceState = do welcomeGetOpts :: IO DirectoryOpts welcomeGetOpts = do 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 putStrLn $ "SimpleX Directory Service Bot v" ++ versionNumber - putStrLn $ "db: " <> dbFilePrefix <> "_chat.db, " <> dbFilePrefix <> "_agent.db" + printDbOpts coreOptions pure opts directoryServiceCLI :: DirectoryStore -> DirectoryOpts -> IO () diff --git a/cabal.project b/cabal.project index 5fbfede730..bcbf01d365 100644 --- a/cabal.project +++ b/cabal.project @@ -12,7 +12,7 @@ constraints: zip +disable-bzip2 +disable-zstd source-repository-package type: git location: https://github.com/simplex-chat/simplexmq.git - tag: 992b42e92224ec663684923aaa40ed1f9a683f61 + tag: 9d9ec8cd0b171b2058c59c4e7292ccafa96b6e2b source-repository-package type: git diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix index bae65f4c17..7c00706d33 100644 --- a/scripts/nix/sha256map.nix +++ b/scripts/nix/sha256map.nix @@ -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/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d"; "https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl"; diff --git a/simplex-chat.cabal b/simplex-chat.cabal index 66eecf141d..81858fe9b2 100644 --- a/simplex-chat.cabal +++ b/simplex-chat.cabal @@ -24,11 +24,15 @@ flag swift manual: True default: False +flag client_postgres + description: Build with PostgreSQL instead of SQLite. + manual: True + default: False + library exposed-modules: Simplex.Chat Simplex.Chat.AppSettings - Simplex.Chat.Archive Simplex.Chat.Bot Simplex.Chat.Bot.KnownContacts Simplex.Chat.Call @@ -44,132 +48,12 @@ library Simplex.Chat.Messages.Batch Simplex.Chat.Messages.CIContent 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.Shared - Simplex.Chat.Mobile.WebRTC Simplex.Chat.Operators Simplex.Chat.Operators.Conditions Simplex.Chat.Options + Simplex.Chat.Options.DB Simplex.Chat.ProfileGenerator Simplex.Chat.Protocol Simplex.Chat.Remote @@ -187,7 +71,6 @@ library Simplex.Chat.Store.Files Simplex.Chat.Store.Groups Simplex.Chat.Store.Messages - Simplex.Chat.Store.Migrations Simplex.Chat.Store.NoteFolders Simplex.Chat.Store.Profiles Simplex.Chat.Store.Remote @@ -205,6 +88,137 @@ library Simplex.Chat.Types.Util Simplex.Chat.Util 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: Paths_simplex_chat hs-source-dirs: @@ -224,7 +238,6 @@ library , containers ==0.6.* , crypton ==0.34.* , data-default ==0.7.* - , direct-sqlcipher ==2.3.* , directory ==1.3.* , email-validate ==2.3.* , exceptions ==0.10.* @@ -243,7 +256,6 @@ library , simple-logger ==0.1.* , simplexmq >=6.3 , socks ==0.6.* - , sqlcipher-simple ==0.4.* , stm ==2.5.* , terminal ==0.2.* , time ==1.12.* @@ -255,6 +267,16 @@ library default-language: Haskell2010 if flag(swift) 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) build-depends: bytestring ==0.11.* @@ -282,6 +304,8 @@ executable simplex-bot , directory ==1.3.* , simplex-chat default-language: Haskell2010 + if flag(client_postgres) + cpp-options: -DdbPostgres executable simplex-bot-advanced main-is: Main.hs @@ -300,6 +324,8 @@ executable simplex-bot-advanced , simplexmq >=6.3 , stm ==2.5.* default-language: Haskell2010 + if flag(client_postgres) + cpp-options: -DdbPostgres if impl(ghc >= 9.6.2) build-depends: text >=2.0.1 && <2.2 @@ -328,6 +354,8 @@ executable simplex-broadcast-bot , simplexmq >=6.3 , stm ==2.5.* default-language: Haskell2010 + if flag(client_postgres) + cpp-options: -DdbPostgres if impl(ghc >= 9.6.2) build-depends: text >=2.0.1 && <2.2 @@ -357,6 +385,8 @@ executable simplex-chat , unliftio ==0.2.* , websockets ==0.12.* default-language: Haskell2010 + if flag(client_postgres) + cpp-options: -DdbPostgres if impl(ghc >= 9.6.2) build-depends: text >=2.0.1 && <2.2 @@ -393,6 +423,8 @@ executable simplex-directory-service , stm ==2.5.* , time ==1.12.* default-language: Haskell2010 + if flag(client_postgres) + cpp-options: -DdbPostgres if impl(ghc >= 9.6.2) build-depends: bytestring ==0.11.* @@ -418,18 +450,16 @@ test-suite simplex-chat-test ChatTests.Local ChatTests.Profiles ChatTests.Utils + JSONFixtures JSONTests MarkdownTests MessageBatching - MobileTests OperatorTests ProtocolTests RandomServers RemoteTests - SchemaDump ValidNames ViewTests - WebRTCTests Broadcast.Bot Broadcast.Options Directory.Events @@ -438,6 +468,11 @@ test-suite simplex-chat-test Directory.Service Directory.Store Paths_simplex_chat + if !flag(client_postgres) + other-modules: + MobileTests + SchemaDump + WebRTCTests hs-source-dirs: tests apps/simplex-broadcast-bot/src @@ -469,12 +504,18 @@ test-suite simplex-chat-test , simple-logger ==0.1.* , simplex-chat , simplexmq >=6.3 - , sqlcipher-simple ==0.4.* , stm ==2.5.* , terminal ==0.2.* , time ==1.12.* , unliftio ==0.2.* 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) build-depends: bytestring ==0.11.* diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index f3be6cbbdb..ce15d29022 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -1,3 +1,4 @@ +{-# LANGUAGE CPP #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE FlexibleContexts #-} @@ -20,7 +21,6 @@ import Control.Monad import Control.Monad.Except import Control.Monad.IO.Unlift import Data.Bifunctor (bimap, second) -import Data.ByteArray (ScrubbedBytes) import Data.List (partition, sortOn) import Data.List.NonEmpty (NonEmpty (..)) import qualified Data.List.NonEmpty as L @@ -32,6 +32,7 @@ import Simplex.Chat.Controller import Simplex.Chat.Library.Commands import Simplex.Chat.Operators import Simplex.Chat.Options +import Simplex.Chat.Options.DB import Simplex.Chat.Protocol import Simplex.Chat.Store 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.Protocol 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.Client (defaultNetworkConfig) 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 UnliftIO.Exception as E import UnliftIO.STM +#if defined(dbPostgres) +import Database.PostgreSQL.Simple (ConnectInfo (..), defaultConnectInfo) +#endif operatorSimpleXChat :: NewServerOperator operatorSimpleXChat = @@ -183,11 +187,20 @@ fluxXFTPServers = logCfg :: LogConfig logCfg = LogConfig {lc_file = Nothing, lc_stderr = True} -createChatDatabase :: FilePath -> ScrubbedBytes -> Bool -> MigrationConfirmation -> Bool -> IO (Either MigrationError ChatDatabase) -createChatDatabase filePrefix key keepKey confirmMigrations vacuum = runExceptT $ do - chatStore <- ExceptT $ createChatStore (chatStoreFile filePrefix) key keepKey confirmMigrations vacuum - agentStore <- ExceptT $ createAgentStore (agentStoreFile filePrefix) key keepKey confirmMigrations vacuum +createChatDatabase :: ChatDbOpts -> MigrationConfirmation -> IO (Either MigrationError ChatDatabase) +createChatDatabase dbOpts confirmMigrations = runExceptT $ do +#if defined(dbPostgres) + 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} +#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 diff --git a/src/Simplex/Chat/Archive.hs b/src/Simplex/Chat/Archive.hs index 693e2fe29f..2cbc941b44 100644 --- a/src/Simplex/Chat/Archive.hs +++ b/src/Simplex/Chat/Archive.hs @@ -11,7 +11,6 @@ module Simplex.Chat.Archive deleteStorage, sqlCipherExport, sqlCipherTestKey, - archiveFilesFolder, ) where @@ -112,7 +111,7 @@ copyValidDirectoryFiles isFileError fromDir toDir = do Nothing -> (copyDirectoryFile f $> fileErrs) `E.catch` \(e :: E.SomeException) -> addErr $ show e - Just e -> addErr e + Just e -> addErr e where addErr e = pure $ AEFileError f e : fileErrs copyDirectoryFile f = do diff --git a/src/Simplex/Chat/Call.hs b/src/Simplex/Chat/Call.hs index 882ec8ccd0..3fd52e8493 100644 --- a/src/Simplex/Chat/Call.hs +++ b/src/Simplex/Chat/Call.hs @@ -1,9 +1,13 @@ +{-# LANGUAGE CPP #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE DeriveAnyClass #-} +{-# LANGUAGE DerivingStrategies #-} {-# LANGUAGE DuplicateRecordFields #-} +{-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE TemplateHaskell #-} {-# OPTIONS_GHC -Wno-unrecognised-pragmas #-} @@ -18,13 +22,19 @@ import Data.ByteString.Char8 (ByteString) import Data.Int (Int64) import Data.Text (Text) 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.Messaging.Agent.Store.DB (Binary (..)) import qualified Simplex.Messaging.Crypto as C import Simplex.Messaging.Encoding.String import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, enumJSON, fromTextField_, fstToLower, singleFieldJSON) 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 { contactId :: ContactId, @@ -90,6 +100,9 @@ data CallState newtype CallId = CallId ByteString deriving (Eq, Show) + deriving newtype (FromField) + +instance ToField CallId where toField (CallId m) = toField $ Binary m instance StrEncoding CallId where strEncode (CallId m) = strEncode m @@ -103,10 +116,6 @@ instance ToJSON CallId where toJSON = strToJSON toEncoding = strToJEncoding -instance FromField CallId where fromField f = CallId <$> fromField f - -instance ToField CallId where toField (CallId m) = toField m - data RcvCallInvitation = RcvCallInvitation { user :: User, contact :: Contact, diff --git a/src/Simplex/Chat/Controller.hs b/src/Simplex/Chat/Controller.hs index 3854b0662e..ff534f252f 100644 --- a/src/Simplex/Chat/Controller.hs +++ b/src/Simplex/Chat/Controller.hs @@ -46,8 +46,6 @@ import Data.Time (NominalDiffTime, UTCTime) import Data.Time.Clock.System (SystemTime (..), systemToUTCTime) import Data.Version (showVersion) import Data.Word (Word16) -import Database.SQLite.Simple (SQLError) -import qualified Database.SQLite.Simple as SQL import Language.Haskell.TH (Exp, Q, runIO) import Numeric.Natural 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.Lock import Simplex.Messaging.Agent.Protocol -import Simplex.Messaging.Agent.Store.SQLite.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.Common (DBStore, withTransaction, withTransactionPriority) 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 qualified Simplex.Messaging.Crypto as C import Simplex.Messaging.Crypto.File (CryptoFile (..)) @@ -97,6 +94,11 @@ import System.IO (Handle) import System.Mem.Weak (Weak) import qualified UnliftIO.Exception as E 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 = showVersion SC.version @@ -284,17 +286,19 @@ data ChatCommand | APISetAppFilePaths AppFilePathsConfig | APISetEncryptLocalFiles Bool | SetContactMergeEnabled Bool +#if !defined(dbPostgres) | APIExportArchive ArchiveConfig | ExportArchive | APIImportArchive ArchiveConfig - | APISaveAppSettings AppSettings - | APIGetAppSettings (Maybe AppSettings) | APIDeleteStorage | APIStorageEncryption DBEncryptionConfig | TestStorageEncryption DBEncryptionKey + | SlowSQLQueries +#endif | ExecChatStoreSQL Text | ExecAgentStoreSQL Text - | SlowSQLQueries + | APISaveAppSettings AppSettings + | APIGetAppSettings (Maybe AppSettings) | APIGetChatTags UserId | APIGetChats {userId :: UserId, pendingConnections :: Bool, pagination :: PaginationByTime, query :: ChatListQuery} | APIGetChat ChatRef (Maybe ContentFilter) ChatPagination (Maybe String) @@ -559,11 +563,14 @@ allowRemoteCommand = \case SetFilesFolder _ -> False SetRemoteHostsFolder _ -> False APISetEncryptLocalFiles _ -> False +#if !defined(dbPostgres) APIExportArchive _ -> False APIImportArchive _ -> False ExportArchive -> False APIDeleteStorage -> False APIStorageEncryption _ -> False + SlowSQLQueries -> False +#endif APISetNetworkConfig _ -> False APIGetNetworkConfig -> False SetLocalDeviceName _ -> False @@ -583,7 +590,6 @@ allowRemoteCommand = \case DeleteRemoteCtrl _ -> False ExecChatStoreSQL _ -> False ExecAgentStoreSQL _ -> False - SlowSQLQueries -> False _ -> True data ChatResponse @@ -798,7 +804,11 @@ data ChatResponse | CRRemoteCtrlStopped {rcsState :: RemoteCtrlSessionState, rcStopReason :: RemoteCtrlStopReason} | CRContactPQEnabled {user :: User, contact :: Contact, pqEnabled :: PQEncryption} | CRSQLResult {rows :: [Text]} +#if !defined(dbPostgres) + | CRArchiveExported {archiveErrors :: [ArchiveError]} + | CRArchiveImported {archiveErrors :: [ArchiveError]} | CRSlowSQLQueries {chatQueries :: [SlowSQLQuery], agentQueries :: [SlowSQLQuery]} +#endif | CRDebugLocks {chatLockName :: Maybe String, chatEntityLocks :: Map String String, agentLocks :: AgentLocks} | CRAgentSubsTotal {user :: User, subsTotal :: SMPServerSubs, hasSession :: Bool} | CRAgentServersSummary {user :: User, serversSummary :: PresentedServersSummary} @@ -817,8 +827,6 @@ data ChatResponse | CRChatCmdError {user_ :: Maybe User, chatError :: ChatError} | CRChatError {user_ :: Maybe User, chatError :: ChatError} | CRChatErrors {user_ :: Maybe User, chatErrors :: [ChatError]} - | CRArchiveExported {archiveErrors :: [ArchiveError]} - | CRArchiveImported {archiveErrors :: [ArchiveError]} | CRAppSettings {appSettings :: AppSettings} | CRTimedAction {action :: String, durationMilliseconds :: Int64} | CRCustomChatResponse {user_ :: Maybe User, response :: Text} @@ -846,7 +854,9 @@ allowRemoteEvent = \case CRRemoteCtrlConnected _ -> False CRRemoteCtrlStopped {} -> False CRSQLResult _ -> False +#if !defined(dbPostgres) CRSlowSQLQueries {} -> False +#endif _ -> True logResponseToFile :: ChatResponse -> Bool @@ -1181,11 +1191,13 @@ data CoreVersionInfo = CoreVersionInfo } deriving (Show) +#if !defined(dbPostgres) data SlowSQLQuery = SlowSQLQuery { query :: Text, queryStats :: SlowQueryStats } deriving (Show) +#endif data ChatError = ChatError {errorType :: ChatErrorType} @@ -1512,13 +1524,17 @@ withStoreBatch actions = do ChatController {chatStore} <- ask liftIO $ withTransaction chatStore $ mapM (`E.catches` handleDBErrors) . actions +-- TODO [postgres] postgres specific error handling handleDBErrors :: [E.Handler IO (Either ChatError a)] handleDBErrors = - [ E.Handler $ \(e :: SQLError) -> +#if !defined(dbPostgres) + ( E.Handler $ \(e :: SQLError) -> let se = SQL.sqlError e busy = se == SQL.ErrorBusy || se == SQL.ErrorLocked - 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 + in pure . Left . ChatErrorStore $ if busy then SEDBBusyError $ show se else 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)) @@ -1591,7 +1607,9 @@ $(JQ.deriveJSON defaultJSON ''ChatItemDeletion) $(JQ.deriveJSON defaultJSON ''CoreVersionInfo) +#if !defined(dbPostgres) $(JQ.deriveJSON defaultJSON ''SlowSQLQuery) +#endif -- instance ProtocolTypeI p => FromJSON (ProtoServersConfig p) where -- parseJSON = $(JQ.mkParseJSON defaultJSON ''ProtoServersConfig) diff --git a/src/Simplex/Chat/Core.hs b/src/Simplex/Chat/Core.hs index 8e40469d84..0dbee1542e 100644 --- a/src/Simplex/Chat/Core.hs +++ b/src/Simplex/Chat/Core.hs @@ -26,22 +26,22 @@ import Simplex.Chat.Options (ChatOpts (..), CoreChatOpts (..)) import Simplex.Chat.Store.Profiles import Simplex.Chat.Types 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.Common (DBStore, withTransaction) import System.Exit (exitFailure) import System.IO (hFlush, stdout) import Text.Read (readMaybe) import UnliftIO.Async 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 Just level -> do setLogLevel level withGlobalLogging logCfg initRun _ -> initRun 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 exit e = do putStrLn $ "Error opening database: " <> show e diff --git a/src/Simplex/Chat/Library/Commands.hs b/src/Simplex/Chat/Library/Commands.hs index 85dd765e7a..fa79db81d1 100644 --- a/src/Simplex/Chat/Library/Commands.hs +++ b/src/Simplex/Chat/Library/Commands.hs @@ -1,3 +1,4 @@ +{-# LANGUAGE CPP #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE FlexibleContexts #-} @@ -26,8 +27,6 @@ import Control.Monad.Reader import qualified Data.Aeson as J import Data.Attoparsec.ByteString.Char8 (Parser) 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 Data.ByteString.Char8 (ByteString) 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 qualified Data.Text as T import Data.Text.Encoding (decodeLatin1, encodeUtf8) -import Data.Time (NominalDiffTime, addUTCTime, defaultTimeLocale, formatTime) import Data.Time.Clock (UTCTime, getCurrentTime, nominalDay) import Data.Type.Equality import qualified Data.UUID as UUID import qualified Data.UUID.V4 as V4 -import qualified Database.SQLite.Simple as SQL import Simplex.Chat.Library.Subscriber -import Simplex.Chat.Archive import Simplex.Chat.Call import Simplex.Chat.Controller import Simplex.Chat.Files @@ -87,15 +83,12 @@ import Simplex.Chat.Util (liftIOEither) import qualified Simplex.Chat.Util as U import Simplex.FileTransfer.Description (FileDescriptionURI (..), maxFileSize, maxFileSizeHard) 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.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 (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 qualified Simplex.Messaging.Crypto as C import Simplex.Messaging.Crypto.File (CryptoFile (..), CryptoFileArgs (..)) @@ -122,6 +115,20 @@ import UnliftIO.Directory import qualified UnliftIO.Exception as E import UnliftIO.IO (hClose) 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 = @@ -446,6 +453,7 @@ processChatCommand' vr = \case chatWriteVar sel $ Just f APISetEncryptLocalFiles on -> chatWriteVar encryptLocalFiles on >> ok_ SetContactMergeEnabled onOff -> chatWriteVar contactMergeEnabled onOff >> ok_ +#if !defined(dbPostgres) APIExportArchive cfg -> checkChatStopped $ CRArchiveExported <$> lift (exportArchive cfg) ExportArchive -> do ts <- liftIO getCurrentTime @@ -455,13 +463,9 @@ processChatCommand' vr = \case fileErrs <- lift $ importArchive cfg setStoreChanged pure $ CRArchiveImported fileErrs - APISaveAppSettings as -> withFastStore' (`saveAppSettings` as) >> ok_ - APIGetAppSettings platformDefaults -> CRAppSettings <$> withFastStore' (`getAppSettings` platformDefaults) APIDeleteStorage -> withStoreChanged deleteStorage APIStorageEncryption cfg -> withStoreChanged $ sqlCipherExport cfg TestStorageEncryption key -> sqlCipherTestKey key >> ok_ - ExecChatStoreSQL query -> CRSQLResult <$> withStore' (`execSQL` query) - ExecAgentStoreSQL query -> CRSQLResult <$> withAgent (`execAgentStoreSQL` query) SlowSQLQueries -> do ChatController {chatStore, smpAgent} <- ask chatQueries <- slowQueries chatStore @@ -474,6 +478,11 @@ processChatCommand' vr = \case . sortOn (timeAvg . snd) . M.assocs <$> 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 tags <- withFastStore' (`getUserChatTags` user) pure $ CRChatTags user tags @@ -2421,12 +2430,14 @@ processChatCommand' vr = \case | name == "" -> withFastStore (`getUserNoteFolderId` user) | otherwise -> throwChatError $ CECommandError "not supported" _ -> throwChatError $ CECommandError "not supported" +#if !defined(dbPostgres) checkChatStopped :: CM ChatResponse -> CM ChatResponse checkChatStopped a = asks agentAsync >>= readTVarIO >>= maybe a (const $ throwChatError CEChatNotStopped) setStoreChanged :: CM () setStoreChanged = asks chatStoreChanged >>= atomically . (`writeTVar` True) withStoreChanged :: CM () -> CM ChatResponse withStoreChanged a = checkChatStopped $ a >> setStoreChanged >> ok_ +#endif checkStoreNotChanged :: CM ChatResponse -> CM ChatResponse checkStoreNotChanged = ifM (asks chatStoreChanged >>= readTVarIO) (throwChatError CEChatStoreChanged) withUserName :: UserName -> (UserId -> ChatCommand) -> CM ChatResponse @@ -3558,6 +3569,7 @@ chatCommandP = "/set file paths " *> (APISetAppFilePaths <$> jsonP), "/_files_encrypt " *> (APISetEncryptLocalFiles <$> onOffP), "/contact_merge " *> (SetContactMergeEnabled <$> onOffP), +#if !defined(dbPostgres) "/_db export " *> (APIExportArchive <$> jsonP), "/db export" $> ExportArchive, "/_db import " *> (APIImportArchive <$> jsonP), @@ -3567,11 +3579,12 @@ chatCommandP = "/db key " *> (APIStorageEncryption <$> (dbEncryptionConfig <$> dbKeyP <* A.space <*> dbKeyP)), "/db decrypt " *> (APIStorageEncryption . (`dbEncryptionConfig` "") <$> dbKeyP), "/db test key " *> (TestStorageEncryption <$> dbKeyP), + "/sql slow" $> SlowSQLQueries, +#endif "/_save app settings" *> (APISaveAppSettings <$> jsonP), "/_get app settings" *> (APIGetAppSettings <$> optional (A.space *> jsonP)), "/sql chat " *> (ExecChatStoreSQL <$> textP), "/sql agent " *> (ExecAgentStoreSQL <$> textP), - "/sql slow" $> SlowSQLQueries, "/_get tags " *> (APIGetChatTags <$> A.decimal), "/_get chats " *> ( APIGetChats @@ -4005,9 +4018,11 @@ chatCommandP = logTLSErrors <- " log=" *> onOffP <|> pure False let tcpTimeout_ = (1000000 *) <$> t_ pure $ SimpleNetCfg {socksProxy, socksMode, hostMode, requiredHostMode, smpProxyMode_, smpProxyFallback_, smpWebPort, tcpTimeout_, logTLSErrors} +#if !defined(dbPostgres) dbKeyP = nonEmptyKey <$?> strP nonEmptyKey k@(DBEncryptionKey s) = if BA.null s then Left "empty key" else Right k dbEncryptionConfig currentKey newKey = DBEncryptionConfig {currentKey, newKey, keepKey = Just False} +#endif autoAcceptP = ifM onOffP (Just <$> (businessAA <|> addressAA)) (pure Nothing) where addressAA = AutoAccept False <$> (" incognito=" *> onOffP <|> pure False) <*> autoReply diff --git a/src/Simplex/Chat/Library/Internal.hs b/src/Simplex/Chat/Library/Internal.hs index 9414675924..1bcbf9f20d 100644 --- a/src/Simplex/Chat/Library/Internal.hs +++ b/src/Simplex/Chat/Library/Internal.hs @@ -79,7 +79,7 @@ import Simplex.Messaging.Agent.Env.SQLite (ServerCfg (..)) import Simplex.Messaging.Agent.Lock (withLock) import Simplex.Messaging.Agent.Protocol 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.Crypto.File (CryptoFile (..), CryptoFileArgs (..)) import qualified Simplex.Messaging.Crypto.File as CF diff --git a/src/Simplex/Chat/Library/Subscriber.hs b/src/Simplex/Chat/Library/Subscriber.hs index b47855a18f..e7ca4ed2e9 100644 --- a/src/Simplex/Chat/Library/Subscriber.hs +++ b/src/Simplex/Chat/Library/Subscriber.hs @@ -43,12 +43,12 @@ import qualified Data.UUID.V4 as V4 import Data.Word (Word32) import Simplex.Chat.Call import Simplex.Chat.Controller +import Simplex.Chat.Library.Internal import Simplex.Chat.Messages import Simplex.Chat.Messages.CIContent import Simplex.Chat.Messages.CIContent.Events import Simplex.Chat.ProfileGenerator (generateRandomProfile) import Simplex.Chat.Protocol -import Simplex.Chat.Library.Internal import Simplex.Chat.Store import Simplex.Chat.Store.Connections 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.Protocol 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 qualified Simplex.Messaging.Crypto as C import Simplex.Messaging.Crypto.File (CryptoFile (..)) diff --git a/src/Simplex/Chat/Messages.hs b/src/Simplex/Chat/Messages.hs index 25bea24e74..3fd927daff 100644 --- a/src/Simplex/Chat/Messages.hs +++ b/src/Simplex/Chat/Messages.hs @@ -1,3 +1,4 @@ +{-# LANGUAGE CPP #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DuplicateRecordFields #-} @@ -38,8 +39,6 @@ import Data.Text.Encoding (decodeLatin1, encodeUtf8) import Data.Time.Clock (NominalDiffTime, UTCTime, diffUTCTime, nominalDay) import Data.Type.Equality import Data.Typeable (Typeable) -import Database.SQLite.Simple.FromField (FromField (..)) -import Database.SQLite.Simple.ToField (ToField (..)) import GHC.TypeLits (ErrorMessage (ShowType, type (:<>:)), TypeError) import qualified GHC.TypeLits as Type 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.Protocol (MsgBody) 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 deriving (Eq, Show, Ord) diff --git a/src/Simplex/Chat/Messages/CIContent.hs b/src/Simplex/Chat/Messages/CIContent.hs index 96a4f9f6c8..16bd749f30 100644 --- a/src/Simplex/Chat/Messages/CIContent.hs +++ b/src/Simplex/Chat/Messages/CIContent.hs @@ -1,3 +1,4 @@ +{-# LANGUAGE CPP #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DuplicateRecordFields #-} @@ -23,8 +24,6 @@ import Data.Text (Text) import Data.Text.Encoding (decodeLatin1, encodeUtf8) import Data.Type.Equality 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.Protocol 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.Parsers (defaultJSON, dropPrefix, enumJSON, fstToLower, singleFieldJSON, sumTypeJSON) 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 deriving (Eq, Show) diff --git a/src/Simplex/Chat/Mobile.hs b/src/Simplex/Chat/Mobile.hs index f8b044676a..8c332f9902 100644 --- a/src/Simplex/Chat/Mobile.hs +++ b/src/Simplex/Chat/Mobile.hs @@ -42,6 +42,7 @@ import Simplex.Chat.Mobile.File import Simplex.Chat.Mobile.Shared import Simplex.Chat.Mobile.WebRTC import Simplex.Chat.Options +import Simplex.Chat.Options.DB import Simplex.Chat.Remote.Types import Simplex.Chat.Store import Simplex.Chat.Store.Profiles @@ -189,8 +190,12 @@ mobileChatOpts dbFilePrefix = ChatOpts { coreOptions = CoreChatOpts - { dbFilePrefix, - dbKey = "", -- for API database is already opened, and the key in options is not used + { dbOptions = + ChatDbOpts + { dbFilePrefix, + dbKey = "", -- for API database is already opened, and the key in options is not used + vacuumOnMigration = True + }, smpServers = [], xftpServers = [], simpleNetCfg = defaultSimpleNetCfg, @@ -201,8 +206,7 @@ mobileChatOpts dbFilePrefix = logFile = Nothing, tbqSize = 1024, highlyAvailable = False, - yesToUpMigrations = False, - vacuumOnMigration = True + yesToUpMigrations = False }, deviceName = Nothing, chatCmd = "", @@ -247,7 +251,7 @@ chatMigrateInitKey dbFilePrefix dbKey keepKey confirm backgroundMode = runExcept newChatController db user_ defaultMobileConfig opts backgroundMode migrate createStore dbFile confirmMigrations = 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) `catchAll` (pure . dbError) where diff --git a/src/Simplex/Chat/Operators.hs b/src/Simplex/Chat/Operators.hs index 07225856b6..6441d651da 100644 --- a/src/Simplex/Chat/Operators.hs +++ b/src/Simplex/Chat/Operators.hs @@ -1,3 +1,4 @@ +{-# LANGUAGE CPP #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE FlexibleContexts #-} @@ -43,8 +44,6 @@ import Data.Text (Text) import qualified Data.Text as T import Data.Time (addUTCTime) 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 Simplex.Chat.Operators.Conditions 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.Transport.Client (TransportHost (..)) 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 = "a5061f3147165a05979d6ace33960aced2d6ac03" @@ -119,7 +125,14 @@ instance TextEncoding OperatorTag where -- this and other types only define instances of serialization for known DB IDs only, -- 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 @@ -338,7 +351,7 @@ updatedServerOperators presetOps storedOps = <> map (\op -> (Nothing, Just $ ASO SDBStored op)) (filter (isNothing . operatorTag) storedOps) where -- TODO remove domains of preset operators from custom - addPreset op = ((Just op, storedOp' <$> pOperator op) :) + addPreset op = ((Just op, storedOp' <$> pOperator op) :) where storedOp' presetOp = case find ((operatorTag presetOp ==) . operatorTag) storedOps of Just ServerOperator {operatorId, conditionsAcceptance, enabled, smpRoles, xftpRoles} -> @@ -427,7 +440,7 @@ groupByOperator_ (ops, smpSrvs, xftpSrvs) = do where mkUS op = UserOperatorServers op [] [] addServer :: [([Text], IORef (f UserOperatorServers))] -> IORef (f UserOperatorServers) -> (UserServer p -> UserOperatorServers -> UserOperatorServers) -> UserServer p -> IO () - addServer ss custom add srv = + addServer ss custom add srv = let v = maybe custom snd $ find (\(ds, _) -> any (\d -> any (matchingHost d) (srvHost srv)) ds) ss in atomicModifyIORef'_ v (add srv <$>) addSMP srv s@UserOperatorServers {smpServers} = (s :: UserOperatorServers) {smpServers = srv : smpServers} @@ -445,7 +458,7 @@ validateUserServers curr others = currUserErrs <> concatMap otherUserErrs others where 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 - 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 | noServers opEnabled = [USENoServers p' user] | otherwise = [USEStorageMissing p' user | noServers (hasRole storage)] <> [USEProxyMissing p' user | noServers (hasRole proxy)] diff --git a/src/Simplex/Chat/Options.hs b/src/Simplex/Chat/Options.hs index e034bd03f0..c58c792819 100644 --- a/src/Simplex/Chat/Options.hs +++ b/src/Simplex/Chat/Options.hs @@ -14,12 +14,12 @@ module Simplex.Chat.Options getChatOpts, protocolServersP, defaultHostMode, + printDbOpts, ) where import Control.Logger.Simple (LogLevel (..)) import qualified Data.Attoparsec.ByteString.Char8 as A -import Data.ByteArray (ScrubbedBytes) import qualified Data.ByteString.Char8 as B import Data.Maybe (fromMaybe) import Data.Text (Text) @@ -34,7 +34,7 @@ import Simplex.Messaging.Encoding.String import Simplex.Messaging.Parsers (parseAll) import Simplex.Messaging.Protocol (ProtoServerWithAuth, ProtocolTypeI, SMPServerWithAuth, XFTPServerWithAuth) import Simplex.Messaging.Transport.Client (SocksProxyWithAuth (..), SocksAuth (..), defaultSocksProxyWithAuth) -import System.FilePath (combine) +import Simplex.Chat.Options.DB data ChatOpts = ChatOpts { coreOptions :: CoreChatOpts, @@ -54,8 +54,7 @@ data ChatOpts = ChatOpts } data CoreChatOpts = CoreChatOpts - { dbFilePrefix :: String, - dbKey :: ScrubbedBytes, + { dbOptions :: ChatDbOpts, smpServers :: [SMPServerWithAuth], xftpServers :: [XFTPServerWithAuth], simpleNetCfg :: SimpleNetCfg, @@ -66,8 +65,7 @@ data CoreChatOpts = CoreChatOpts logFile :: Maybe FilePath, tbqSize :: Natural, highlyAvailable :: Bool, - yesToUpMigrations :: Bool, - vacuumOnMigration :: Bool + yesToUpMigrations :: Bool } data ChatCmdLog = CCLAll | CCLMessages | CCLNone @@ -82,24 +80,8 @@ agentLogLevel = \case CLLImportant -> LogInfo coreChatOptsP :: FilePath -> FilePath -> Parser CoreChatOpts -coreChatOptsP appDir defaultDbFileName = do - dbFilePrefix <- - 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 "" - ) +coreChatOptsP appDir defaultDbName = do + dbOptions <- chatDbOptsP appDir defaultDbName smpServers <- option parseProtocolServers @@ -241,15 +223,9 @@ coreChatOptsP appDir defaultDbFileName = do <> short 'y' <> help "Automatically confirm \"up\" database migrations" ) - disableVacuum <- - switch - ( long "disable-vacuum" - <> help "Do not vacuum database after migrations" - ) pure CoreChatOpts - { dbFilePrefix, - dbKey, + { dbOptions, smpServers, xftpServers, simpleNetCfg = @@ -271,12 +247,10 @@ coreChatOptsP appDir defaultDbFileName = do logFile, tbqSize, highlyAvailable, - yesToUpMigrations, - vacuumOnMigration = not disableVacuum + yesToUpMigrations } where useTcpTimeout p t = 1000000 * if t > 0 then t else maybe 7 (const 15) p - defaultDbFilePath = combine appDir defaultDbFileName defaultHostMode :: Maybe SocksProxyWithAuth -> HostMode defaultHostMode = \case @@ -284,8 +258,8 @@ defaultHostMode = \case _ -> HMPublic chatOptsP :: FilePath -> FilePath -> Parser ChatOpts -chatOptsP appDir defaultDbFileName = do - coreOptions <- coreChatOptsP appDir defaultDbFileName +chatOptsP appDir defaultDbName = do + coreOptions <- coreChatOptsP appDir defaultDbName deviceName <- optional $ strOption @@ -432,12 +406,15 @@ parseChatCmdLog = eitherReader $ \case _ -> Left "Invalid chat command log level" getChatOpts :: FilePath -> FilePath -> IO ChatOpts -getChatOpts appDir defaultDbFileName = +getChatOpts appDir defaultDbName = execParser $ 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") where versionStr = versionString versionNumber versionOption = infoOption versionAndUpdate (long "version" <> short 'v' <> help "Show version") versionAndUpdate = versionStr <> "\n" <> updateStr + +printDbOpts :: CoreChatOpts -> IO () +printDbOpts opts = putStrLn $ "db: " <> dbString (dbOptions opts) diff --git a/src/Simplex/Chat/Options/DB.hs b/src/Simplex/Chat/Options/DB.hs new file mode 100644 index 0000000000..1796baa5db --- /dev/null +++ b/src/Simplex/Chat/Options/DB.hs @@ -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 diff --git a/src/Simplex/Chat/Options/Postgres.hs b/src/Simplex/Chat/Options/Postgres.hs new file mode 100644 index 0000000000..635223152c --- /dev/null +++ b/src/Simplex/Chat/Options/Postgres.hs @@ -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 diff --git a/src/Simplex/Chat/Options/SQLite.hs b/src/Simplex/Chat/Options/SQLite.hs new file mode 100644 index 0000000000..dc81356784 --- /dev/null +++ b/src/Simplex/Chat/Options/SQLite.hs @@ -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" diff --git a/src/Simplex/Chat/Protocol.hs b/src/Simplex/Chat/Protocol.hs index cda8cdf04c..f2f1b912a0 100644 --- a/src/Simplex/Chat/Protocol.hs +++ b/src/Simplex/Chat/Protocol.hs @@ -1,8 +1,11 @@ +{-# LANGUAGE CPP #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE DeriveAnyClass #-} +{-# LANGUAGE DerivingStrategies #-} {-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GADTs #-} +{-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE KindSignatures #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE NamedFieldPuns #-} @@ -42,21 +45,26 @@ import Data.Time.Clock (UTCTime) import Data.Type.Equality import Data.Typeable (Typeable) import Data.Word (Word32) -import Database.SQLite.Simple.FromField (FromField (..)) -import Database.SQLite.Simple.ToField (ToField (..)) import Simplex.Chat.Call import Simplex.Chat.Types import Simplex.Chat.Types.Preferences import Simplex.Chat.Types.Shared -import Simplex.Chat.Types.Util 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.Encoding 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.Util (decodeJSON, eitherToMaybe, encodeJSON, safeDecodeUtf8, (<$?>)) 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: -- 1 - support chat versions in connections (9/1/2023) @@ -217,10 +225,9 @@ instance StrEncoding AppMessageBinary where newtype SharedMsgId = SharedMsgId ByteString deriving (Eq, Show) + deriving newtype (FromField) -instance FromField SharedMsgId where fromField f = SharedMsgId <$> fromField f - -instance ToField SharedMsgId where toField (SharedMsgId m) = toField m +instance ToField SharedMsgId where toField (SharedMsgId m) = toField $ DB.Binary m instance StrEncoding SharedMsgId where strEncode (SharedMsgId m) = strEncode m @@ -253,7 +260,7 @@ data LinkContent = LCPage | LCImage | LCVideo {duration :: Maybe Int} | LCUnknow deriving (Eq, Show) data ReportReason = RRSpam | RRContent | RRCommunity | RRProfile | RROther | RRUnknown Text - deriving (Eq, Show) + deriving (Eq, Show) $(pure []) @@ -515,7 +522,7 @@ instance ToJSON MsgContentTag where toJSON = strToJSON toEncoding = strToJEncoding -instance FromField MsgContentTag where fromField = fromBlobField_ strDecode +instance FromField MsgContentTag where fromField = blobFieldDecoder strDecode instance ToField MsgContentTag where toField = toField . strEncode @@ -570,9 +577,10 @@ durationText duration = | otherwise = show n msgContentHasText :: MsgContent -> Bool -msgContentHasText = not . T.null . \case - MCVoice {text} -> text - mc -> msgContentText mc +msgContentHasText = + not . T.null . \case + MCVoice {text} -> text + mc -> msgContentText mc isVoice :: MsgContent -> Bool isVoice = \case diff --git a/src/Simplex/Chat/Remote.hs b/src/Simplex/Chat/Remote.hs index 8c7a0bc552..8e23af24e9 100644 --- a/src/Simplex/Chat/Remote.hs +++ b/src/Simplex/Chat/Remote.hs @@ -37,7 +37,6 @@ import Data.Word (Word16, Word32) import qualified Network.HTTP.Types as N import Network.HTTP2.Server (responseStreaming) import qualified Paths_simplex_chat as SC -import Simplex.Chat.Archive (archiveFilesFolder) import Simplex.Chat.Controller import Simplex.Chat.Files import Simplex.Chat.Messages (chatNameStr) @@ -71,6 +70,9 @@ import UnliftIO import UnliftIO.Concurrent (forkIO) import UnliftIO.Directory (copyFile, createDirectoryIfMissing, doesDirectoryExist, removeDirectoryRecursive, renameFile) +remoteFilesFolder :: String +remoteFilesFolder = "simplex_v1_files" + -- when acting as host minRemoteCtrlVersion :: AppVersion minRemoteCtrlVersion = AppVersion [6, 3, 0, 0] @@ -342,7 +344,7 @@ storeRemoteFile rhId encrypted_ localPath = do filePath' <- liftRH rhId $ remoteStoreFile c filePath (takeFileName localPath) hf_ <- chatReadVar remoteHostsFolder forM_ hf_ $ \hf -> do - let rhf = hf storePath archiveFilesFolder + let rhf = hf storePath remoteFilesFolder hPath = rhf takeFileName filePath' createDirectoryIfMissing True rhf (if encrypt then renameFile else copyFile) filePath hPath @@ -360,7 +362,7 @@ storeRemoteFile rhId encrypted_ localPath = do getRemoteFile :: RemoteHostId -> RemoteFile -> CM () getRemoteFile rhId rf = do 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 liftRH rhId $ remoteGetFile c dir rf diff --git a/src/Simplex/Chat/Store.hs b/src/Simplex/Chat/Store.hs index 7ae1b4a32a..dbb932740c 100644 --- a/src/Simplex/Chat/Store.hs +++ b/src/Simplex/Chat/Store.hs @@ -1,3 +1,5 @@ +{-# LANGUAGE CPP #-} + module Simplex.Chat.Store ( DBStore, StoreError (..), @@ -7,20 +9,43 @@ module Simplex.Chat.Store AutoAccept (..), createChatStore, migrations, -- used in tests +#if defined(dbPostgres) + chatSchema, + agentSchema, +#else chatStoreFile, agentStoreFile, +#endif withTransaction, ) where -import Data.ByteArray (ScrubbedBytes) -import Simplex.Chat.Store.Migrations import Simplex.Chat.Store.Profiles import Simplex.Chat.Store.Shared -import Simplex.Messaging.Agent.Store.SQLite (createDBStore) -import Simplex.Messaging.Agent.Store.SQLite.Common (DBStore (..), withTransaction) +import Simplex.Messaging.Agent.Store.Common (DBStore (..), withTransaction) 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 dbPath key keepKey = createDBStore dbPath key keepKey migrations @@ -29,3 +54,4 @@ chatStoreFile = (<> "_chat.db") agentStoreFile :: FilePath -> FilePath agentStoreFile = (<> "_agent.db") +#endif diff --git a/src/Simplex/Chat/Store/AppSettings.hs b/src/Simplex/Chat/Store/AppSettings.hs index acecc577ca..dbdd538cf4 100644 --- a/src/Simplex/Chat/Store/AppSettings.hs +++ b/src/Simplex/Chat/Store/AppSettings.hs @@ -1,3 +1,4 @@ +{-# LANGUAGE CPP #-} {-# LANGUAGE OverloadedStrings #-} module Simplex.Chat.Store.AppSettings where @@ -6,10 +7,14 @@ import Control.Monad (join) import Control.Monad.IO.Class (liftIO) import qualified Data.Aeson as J import Data.Maybe (fromMaybe) -import Database.SQLite.Simple (Only (..)) import Simplex.Chat.AppSettings (AppSettings (..), combineAppSettings, defaultAppSettings, defaultParseAppSettings) 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 appSettings = do diff --git a/src/Simplex/Chat/Store/Connections.hs b/src/Simplex/Chat/Store/Connections.hs index 49f5656cb0..d8c154f1e0 100644 --- a/src/Simplex/Chat/Store/Connections.hs +++ b/src/Simplex/Chat/Store/Connections.hs @@ -1,3 +1,4 @@ +{-# LANGUAGE CPP #-} {-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE NamedFieldPuns #-} @@ -25,8 +26,6 @@ import Control.Monad.IO.Class import Data.Bitraversable (bitraverse) import Data.Int (Int64) import Data.Maybe (catMaybes, fromMaybe) -import Database.SQLite.Simple (Only (..), (:.) (..)) -import Database.SQLite.Simple.QQ (sql) import Simplex.Chat.Protocol import Simplex.Chat.Store.Direct import Simplex.Chat.Store.Files @@ -36,8 +35,16 @@ import Simplex.Chat.Store.Shared import Simplex.Chat.Types import Simplex.Messaging.Agent.Protocol (ConnId) 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) +#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 agentConnId = do @@ -110,40 +117,42 @@ getConnectionEntity db vr user@User {userId, userContactId} agentConnId = do |] (userId, contactId) 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} - 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 activeConn = Just conn 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_ groupMemberId c = do - gm <- ExceptT $ firstRow (toGroupAndMember c) (SEInternalError "referenced group member not found") $ - DB.query - db - [sql| - SELECT - -- GroupInfo - g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.description, gp.image, - g.host_conn_custom_user_profile_id, g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, - 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, - -- GroupInfo {membership} - 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, - 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 = GroupMember {memberProfile}} - pu.display_name, pu.full_name, pu.image, pu.contact_link, pu.local_alias, pu.preferences, - -- from GroupMember - 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, - 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 group_members m - JOIN contact_profiles p ON p.contact_profile_id = COALESCE(m.member_profile_id, m.contact_profile_id) - JOIN groups g ON g.group_id = m.group_id - JOIN group_profiles gp USING (group_profile_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) - WHERE m.group_member_id = ? AND g.user_id = ? AND mu.contact_id = ? - |] - (groupMemberId, userId, userContactId) + gm <- + ExceptT $ + firstRow (toGroupAndMember c) (SEInternalError "referenced group member not found") $ + DB.query + db + [sql| + SELECT + -- GroupInfo + g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.description, gp.image, + g.host_conn_custom_user_profile_id, g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, + 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, + -- GroupInfo {membership} + 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, + 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 = GroupMember {memberProfile}} + pu.display_name, pu.full_name, pu.image, pu.contact_link, pu.local_alias, pu.preferences, + -- from GroupMember + 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, + 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 group_members m + JOIN contact_profiles p ON p.contact_profile_id = COALESCE(m.member_profile_id, m.contact_profile_id) + JOIN groups g ON g.group_id = m.group_id + JOIN group_profiles gp USING (group_profile_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) + WHERE m.group_member_id = ? AND g.user_id = ? AND mu.contact_id = ? + |] + (groupMemberId, userId, userContactId) liftIO $ bitraverse (addGroupChatTags db) pure gm toGroupAndMember :: Connection -> GroupInfoRow :. GroupMemberRow -> (GroupInfo, GroupMember) 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 != ? ORDER BY conn_ord DESC, created_at DESC LIMIT 1 - ) + ) c |] (userId, cReqHash1, cReqHash2, ConnDeleted) maybe (pure Nothing) (fmap eitherToMaybe . runExceptT . getConnectionEntity db vr user) connId_ diff --git a/src/Simplex/Chat/Store/Direct.hs b/src/Simplex/Chat/Store/Direct.hs index 80172fc1eb..cd7a87b443 100644 --- a/src/Simplex/Chat/Store/Direct.hs +++ b/src/Simplex/Chat/Store/Direct.hs @@ -1,3 +1,4 @@ +{-# LANGUAGE CPP #-} {-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE LambdaCase #-} @@ -93,8 +94,6 @@ import Data.Int (Int64) import Data.Maybe (fromMaybe, isJust, isNothing) import Data.Text (Text) 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.Store.Shared import Simplex.Chat.Types @@ -102,11 +101,19 @@ import Simplex.Chat.Types.Preferences import Simplex.Chat.Types.UITheme import Simplex.Messaging.Agent.Protocol (ConnId, InvitationId, UserId) 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.Protocol (SubscriptionMode (..)) import Simplex.Messaging.Util ((<$$>)) 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 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 ) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) |] - ( (userId, acId, pccConnStatus, ConnContact, True, cReqHash, xContactId) - :. (customUserProfileId, isJust groupLinkId, groupLinkId) - :. (createdAt, createdAt, subMode == SMOnlyCreate, chatV, pqSup, pqSup) + ( (userId, acId, pccConnStatus, ConnContact, BI True, cReqHash, xContactId) + :. (customUserProfileId, BI (isJust groupLinkId), groupLinkId) + :. (createdAt, createdAt, BI (subMode == SMOnlyCreate), chatV, pqSup, pqSup) ) pccConnId <- insertedRowId db 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 vr user@User {userId} cReqHash = do - ct_ <- maybeFirstRow (toContact vr user []) $ - DB.query - db - [sql| - SELECT - -- Contact - 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, - 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, - -- Connection - 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.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.conn_chat_version, c.peer_chat_min_version, c.peer_chat_max_version - FROM contacts ct - JOIN contact_profiles cp ON ct.contact_profile_id = cp.contact_profile_id - JOIN connections c ON c.contact_id = ct.contact_id - WHERE c.user_id = ? AND c.via_contact_uri_hash = ? AND ct.contact_status = ? AND ct.deleted = 0 - ORDER BY c.created_at DESC - LIMIT 1 - |] - (userId, cReqHash, CSActive) + ct_ <- + maybeFirstRow (toContact vr user []) $ + DB.query + db + [sql| + SELECT + -- Contact + 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, + 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, + -- Connection + 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.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.conn_chat_version, c.peer_chat_min_version, c.peer_chat_max_version + FROM contacts ct + JOIN contact_profiles cp ON ct.contact_profile_id = cp.contact_profile_id + JOIN connections c ON c.contact_id = ct.contact_id + WHERE c.user_id = ? AND c.via_contact_uri_hash = ? AND ct.contact_status = ? AND ct.deleted = 0 + ORDER BY c.created_at DESC + LIMIT 1 + |] + (userId, cReqHash, CSActive) mapM (addDirectChatTags db) ct_ 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) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?) |] - ( (userId, acId, cReq, pccConnStatus, ConnContact, contactConnInitiated, customUserProfileId) - :. (createdAt, createdAt, subMode == SMOnlyCreate, chatV, pqSup, pqSup) + ( (userId, acId, cReq, pccConnStatus, ConnContact, BI contactConnInitiated, customUserProfileId) + :. (createdAt, createdAt, BI (subMode == SMOnlyCreate), chatV, pqSup, pqSup) ) pccConnId <- insertedRowId db 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 userId profileId = - DB.executeNamed + DB.execute db [sql| 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 ( 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 ( 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 ( 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 ( SELECT 1 FROM group_members - WHERE user_id = :user_id - AND (member_profile_id = :profile_id OR contact_profile_id = :profile_id) + WHERE user_id = ? + AND (member_profile_id = ? OR contact_profile_id = ?) 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 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 User {userId} Contact {contactId} unreadChat = do 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 User {userId} = do 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 groups 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 = ?" (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 = ?" (BI False, updatedAt, userId, BI 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) 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 User {userId} GroupInfo {groupId} unreadChat = do 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 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) VALUES (?,?,?,?,?,?,?,?,?,?,?) |] - ( (userContactLinkId, invId, minV, maxV, profileId, ldn, userId) + ( (userContactLinkId, Binary invId, minV, maxV, profileId, ldn, userId) :. (currentTs, currentTs, xContactId_, pqSup) ) insertedRowId db getContact' :: XContactId -> IO (Maybe Contact) getContact' xContactId = do - ct_ <- maybeFirstRow (toContact vr user []) $ - DB.query - db - [sql| - SELECT - -- Contact - 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, - 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, - -- Connection - 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.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.conn_chat_version, c.peer_chat_min_version, c.peer_chat_max_version - FROM contacts ct - JOIN contact_profiles cp ON ct.contact_profile_id = cp.contact_profile_id - LEFT JOIN connections c ON c.contact_id = ct.contact_id - WHERE ct.user_id = ? AND ct.xcontact_id = ? AND ct.deleted = 0 - ORDER BY c.created_at DESC - LIMIT 1 - |] - (userId, xContactId) + ct_ <- + maybeFirstRow (toContact vr user []) $ + DB.query + db + [sql| + SELECT + -- Contact + 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, + 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, + -- Connection + 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.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.conn_chat_version, c.peer_chat_min_version, c.peer_chat_max_version + FROM contacts ct + JOIN contact_profiles cp ON ct.contact_profile_id = cp.contact_profile_id + LEFT JOIN connections c ON c.contact_id = ct.contact_id + WHERE ct.user_id = ? AND ct.xcontact_id = ? AND ct.deleted = 0 + ORDER BY c.created_at DESC + LIMIT 1 + |] + (userId, xContactId) mapM (addDirectChatTags db) ct_ getGroupInfo' :: XContactId -> IO (Maybe GroupInfo) getGroupInfo' xContactId = do - g_ <- maybeFirstRow (toGroupInfo vr userContactId []) $ - DB.query - db - (groupInfoQuery <> " WHERE g.business_xcontact_id = ? AND g.user_id = ? AND mu.contact_id = ?") - (xContactId, userId, userContactId) + g_ <- + maybeFirstRow (toGroupInfo vr userContactId []) $ + DB.query + db + (groupInfoQuery <> " WHERE g.business_xcontact_id = ? AND g.user_id = ? AND mu.contact_id = ?") + (xContactId, userId, userContactId) mapM (addGroupChatTags db) g_ getContactRequestByXContactId :: XContactId -> IO (Maybe UserContactRequest) 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 = ? 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 -> Right <$> do 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 = ? 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 where updateProfile currentTs = @@ -803,7 +815,7 @@ createAcceptedContact db user@User {userId, profile = LocalProfile {preferences} DB.execute 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 (?,?,?,?,?,?,?,?,?,?)" - (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 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 @@ -841,7 +853,7 @@ updateContactAccepted db User {userId} Contact {contactId} contactUsed = DB.execute db "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 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 ORDER BY cc_conn_status_ord DESC, cc_created_at DESC LIMIT 1 - ) + ) cc ) 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 contactRequestId = @@ -897,16 +909,16 @@ getUserByContactRequestId db contactRequestId = getPendingContactConnections :: DB.Connection -> User -> IO [PendingContactConnection] getPendingContactConnections db User {userId} = do map toPendingContactConnection - <$> DB.queryNamed + <$> DB.query db [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 FROM connections - WHERE user_id = :user_id - AND conn_type = :conn_type + WHERE user_id = ? + AND conn_type = ? AND contact_id IS NULL |] - [":user_id" := userId, ":conn_type" := ConnContact] + (userId, ConnContact) getContactConnections :: DB.Connection -> VersionRangeChat -> UserId -> Contact -> IO [Connection] 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 agentConnIds = do - DB.execute_ db "DROP TABLE IF EXISTS temp.conn_ids" - DB.execute_ db "CREATE TABLE temp.conn_ids (conn_id BLOB)" - DB.executeMany db "INSERT INTO temp.conn_ids (conn_id) VALUES (?)" $ map Only agentConnIds + DB.execute_ db "DROP TABLE IF EXISTS temp_conn_ids" +#if defined(dbPostgres) + 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 <- map toContactRef <$> 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 FROM contacts ct 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 ct.deleted = 0 |] (Only ConnContact) - DB.execute_ db "DROP TABLE temp.conn_ids" + DB.execute_ db "DROP TABLE temp_conn_ids" pure conns where toContactRef :: (ContactId, Int64, ConnId, ContactName) -> ContactRef @@ -986,7 +1002,7 @@ updateConnectionStatus_ db connId connStatus = do updateContactSettings :: DB.Connection -> User -> Int64 -> ChatSettings -> IO () 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 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 User {userId} Contact {contactId} chatDeleted = do 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 contactId tIds = do diff --git a/src/Simplex/Chat/Store/Files.hs b/src/Simplex/Chat/Store/Files.hs index e4390decf1..95e169e400 100644 --- a/src/Simplex/Chat/Store/Files.hs +++ b/src/Simplex/Chat/Store/Files.hs @@ -1,3 +1,4 @@ +{-# LANGUAGE CPP #-} {-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE GADTs #-} @@ -96,9 +97,6 @@ import Data.Time (addUTCTime) import Data.Time.Clock (UTCTime (..), getCurrentTime, nominalDay) import Data.Type.Equality 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.CIContent import Simplex.Chat.Protocol @@ -110,7 +108,8 @@ import Simplex.Chat.Types import Simplex.Chat.Util (week) import Simplex.Messaging.Agent.Protocol (AgentMsgId, ConnId, UserId) 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 Simplex.Messaging.Crypto.File (CryptoFile (..), CryptoFileArgs (..)) 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.Version 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 User {userId} = do @@ -283,7 +291,7 @@ createSndFTDescrXFTP db User {userId} m Connection {connId} FileTransferMeta {fi DB.execute db "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 DB.execute 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 = ? 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 updateSndFileStatus db sft FSConnected @@ -574,7 +582,7 @@ createRcvFD_ db userId currentTs FileDescr {fileDescrText, fileDescrPartNo, file DB.execute db "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 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 = ? WHERE file_descr_id = ? |] - (fileDescrText', fileDescrPartNo, fileDescrComplete, fileDescrId) + (fileDescrText', fileDescrPartNo, BI fileDescrComplete, fileDescrId) pure RcvFileDescr {fileDescrId, fileDescrText = fileDescrText', fileDescrPartNo, fileDescrComplete} getRcvFileDescrByRcvFileId :: DB.Connection -> FileTransferId -> ExceptT StoreError IO RcvFileDescr @@ -650,8 +658,8 @@ getRcvFileDescrBySndFileId_ db fileId = |] (Only fileId) -toRcvFileDescr :: (Int64, Text, Int, Bool) -> RcvFileDescr -toRcvFileDescr (fileDescrId, fileDescrText, fileDescrPartNo, fileDescrComplete) = +toRcvFileDescr :: (Int64, Text, Int, BoolInt) -> RcvFileDescr +toRcvFileDescr (fileDescrId, fileDescrText, fileDescrPartNo, BI fileDescrComplete) = RcvFileDescr {fileDescrId, fileDescrText, fileDescrPartNo, fileDescrComplete} updateRcvFileAgentId :: DB.Connection -> FileTransferId -> Maybe AgentRcvFileId -> IO () @@ -682,8 +690,8 @@ getRcvFileTransfer_ db userId fileId = do FROM rcv_files r JOIN files f USING (file_id) LEFT JOIN connections c ON r.file_id = c.rcv_file_id - LEFT JOIN contacts cs USING (contact_id) - LEFT JOIN group_members m USING (group_member_id) + LEFT JOIN contacts cs ON cs.contact_id = f.contact_id + LEFT JOIN group_members m ON m.group_member_id = r.group_member_id WHERE f.user_id = ? AND f.file_id = ? |] (userId, fileId) @@ -692,9 +700,9 @@ getRcvFileTransfer_ db userId fileId = do where rcvFileTransfer :: 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 - 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 Nothing -> throwError $ SERcvFileInvalid fileId Just name -> @@ -717,7 +725,7 @@ getRcvFileTransfer_ db userId fileId = do rfi_ = case (filePath_, connId_, agentConnId_) of (Just filePath, connId, agentConnId) -> pure $ Just RcvFileInfo {filePath, connId, agentConnId} _ -> 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 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 "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 setCommandConnId db user cmdId connId runExceptT $ getChatItemByFileId db vr user fileId @@ -763,7 +771,7 @@ acceptRcvFT_ db User {userId} fileId filePath userApprovedRelays rcvFileInline c DB.execute db "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 fileId userApprovedRelays cfArgs_ = do @@ -775,7 +783,7 @@ setRcvFileToReceive db fileId userApprovedRelays cfArgs_ = do SET to_receive = 1, user_approved_relays = ?, updated_at = ? WHERE file_id = ? |] - (userApprovedRelays, currentTs, fileId) + (BI userApprovedRelays, currentTs, fileId) forM_ cfArgs_ $ \cfArgs -> setFileCryptoArgs_ db fileId cfArgs currentTs setFileCryptoArgs :: DB.Connection -> FileTransferId -> CryptoFileArgs -> IO () @@ -928,8 +936,8 @@ getSndFileTransfers_ db userId fileId = FROM snd_files s JOIN files f USING (file_id) JOIN connections c USING (connection_id) - LEFT JOIN contacts cs USING (contact_id) - LEFT JOIN group_members m USING (group_member_id) + LEFT JOIN contacts cs ON cs.contact_id = f.contact_id + LEFT JOIN group_members m ON m.group_member_id = s.group_member_id WHERE f.user_id = ? AND f.file_id = ? |] (userId, fileId) @@ -955,11 +963,11 @@ getFileTransferMeta_ db userId fileId = |] (userId, fileId) 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 (fileName, fileSize, chunkSize, filePath, fileKey, fileNonce, fileInline, aSndFileId_, agentSndFileDeleted, privateSndFileDescr, cancelled_, xftpRedirectFor) = + 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_, BI agentSndFileDeleted, privateSndFileDescr, cancelled_, xftpRedirectFor) = let cryptoArgs = CFArgs <$> fileKey <*> fileNonce 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 User {userId} fileId = do diff --git a/src/Simplex/Chat/Store/Groups.hs b/src/Simplex/Chat/Store/Groups.hs index 004c297073..2e0fca19ca 100644 --- a/src/Simplex/Chat/Store/Groups.hs +++ b/src/Simplex/Chat/Store/Groups.hs @@ -1,3 +1,4 @@ +{-# LANGUAGE CPP #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE LambdaCase #-} @@ -141,8 +142,6 @@ import Data.Maybe (catMaybes, fromMaybe, isJust, isNothing) import Data.Ord (Down (..)) import Data.Text (Text) 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.Protocol (groupForwardVersion) import Simplex.Chat.Store.Direct @@ -152,16 +151,24 @@ import Simplex.Chat.Types.Preferences import Simplex.Chat.Types.Shared import Simplex.Chat.Types.UITheme import Simplex.Messaging.Agent.Protocol (ConnId, UserId) -import Simplex.Messaging.Agent.Store.AgentStore (firstRow, maybeFirstRow) -import qualified Simplex.Messaging.Agent.Store.SQLite.DB as DB +import Simplex.Messaging.Agent.Store.AgentStore (firstRow, fromOnlyBI, maybeFirstRow) +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 Simplex.Messaging.Crypto.Ratchet (pattern PQEncOff, pattern PQSupportOff) import Simplex.Messaging.Protocol (SubscriptionMode (..)) import Simplex.Messaging.Util (eitherToMaybe, ($>>=), (<$$>)) import Simplex.Messaging.Version 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 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 "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 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 User {userId, userContactId} groupMemberId vr = do - gm <- ExceptT . firstRow toGroupAndMember (SEInternalError "referenced group member not found") $ - DB.query - db - [sql| - SELECT - -- GroupInfo - g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.description, gp.image, - g.host_conn_custom_user_profile_id, g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, - 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, - -- GroupInfo {membership} - 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, - 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 = GroupMember {memberProfile}} - pu.display_name, pu.full_name, pu.image, pu.contact_link, pu.local_alias, pu.preferences, - -- from GroupMember - 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, - 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.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.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.conn_chat_version, c.peer_chat_min_version, c.peer_chat_max_version - FROM group_members m - JOIN contact_profiles p ON p.contact_profile_id = COALESCE(m.member_profile_id, m.contact_profile_id) - JOIN groups g ON g.group_id = m.group_id - JOIN group_profiles gp USING (group_profile_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) - LEFT JOIN connections c ON c.connection_id = ( - SELECT max(cc.connection_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 = ? - |] - (userId, groupMemberId, userId, userContactId) + gm <- + ExceptT . firstRow toGroupAndMember (SEInternalError "referenced group member not found") $ + DB.query + db + [sql| + SELECT + -- GroupInfo + g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.description, gp.image, + g.host_conn_custom_user_profile_id, g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, + 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, + -- GroupInfo {membership} + 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, + 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 = GroupMember {memberProfile}} + pu.display_name, pu.full_name, pu.image, pu.contact_link, pu.local_alias, pu.preferences, + -- from GroupMember + 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, + 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.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.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.conn_chat_version, c.peer_chat_min_version, c.peer_chat_max_version + FROM group_members m + JOIN contact_profiles p ON p.contact_profile_id = COALESCE(m.member_profile_id, m.contact_profile_id) + JOIN groups g ON g.group_id = m.group_id + JOIN group_profiles gp USING (group_profile_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) + LEFT JOIN connections c ON c.connection_id = ( + SELECT max(cc.connection_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 = ? + |] + (userId, groupMemberId, userId, userContactId) liftIO $ bitraverse (addGroupChatTags db) pure gm where 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) VALUES (?,?,?,?,?,?,?,?) |] - (ldn, userId, profileId, True, currentTs, currentTs, currentTs, currentTs) + (ldn, userId, profileId, BI True, currentTs, currentTs, currentTs, currentTs) insertedRowId db memberId <- liftIO $ encodedRandomBytes gVar 12 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) 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 let hostVRange = adjustedMemberVRange vr peerChatVRange 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) 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 insertHost_ currentTs groupId = do 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 vr User {userId, userContactId} _contactId_ search_ = do - g_ <- map (toGroupInfo vr userContactId []) - <$> DB.query - db - [sql| - SELECT - g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.description, gp.image, - g.host_conn_custom_user_profile_id, g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, - 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, 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, - 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 - FROM groups g - JOIN group_profiles gp USING (group_profile_id) - JOIN group_members mu USING (group_id) - JOIN contact_profiles pu ON pu.contact_profile_id = COALESCE(mu.member_profile_id, mu.contact_profile_id) - WHERE g.user_id = ? AND mu.contact_id = ? - AND (gp.display_name LIKE '%' || ? || '%' OR gp.full_name LIKE '%' || ? || '%' OR gp.description LIKE '%' || ? || '%') - |] - (userId, userContactId, search, search, search) + g_ <- + map (toGroupInfo vr userContactId []) + <$> DB.query + db + [sql| + SELECT + g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.description, gp.image, + g.host_conn_custom_user_profile_id, g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, + 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, 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, + 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 + FROM groups g + JOIN group_profiles gp USING (group_profile_id) + JOIN group_members mu USING (group_id) + JOIN contact_profiles pu ON pu.contact_profile_id = COALESCE(mu.member_profile_id, mu.contact_profile_id) + WHERE g.user_id = ? AND mu.contact_id = ? + AND (LOWER(gp.display_name) LIKE '%' || LOWER(?) || '%' + OR LOWER(gp.full_name) LIKE '%' || LOWER(?) || '%' + OR LOWER(gp.description) LIKE '%' || LOWER(?) || '%' + ) + |] + (userId, userContactId, search, search, search) mapM (addGroupChatTags db) g_ where search = fromMaybe "" search_ @@ -958,7 +970,7 @@ createBusinessRequestGroup created_at, updated_at, chat_ts, user_member_profile_sent_at, business_chat, business_xcontact_id) VALUES (?,?,?,?,?,?,?,?,?,?) |] - (profileId, localDisplayName, userId, True, currentTs, currentTs, currentTs, currentTs, BCCustomer, xContactId) + (profileId, localDisplayName, userId, BI True, currentTs, currentTs, currentTs, currentTs, BCCustomer, xContactId) insertedRowId db memberId <- liftIO $ encodedRandomBytes gVar 12 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 introId introStatus = do currentTs <- getCurrentTime - DB.executeNamed + DB.execute db [sql| UPDATE group_member_intros - SET intro_status = :intro_status, updated_at = :updated_at - WHERE group_member_intro_id = :intro_id + SET intro_status = ?, updated_at = ? + 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 reMember toMember introInv@IntroInvitation {groupConnReq} = do intro <- getIntroduction db reMember toMember liftIO $ do currentTs <- getCurrentTime - DB.executeNamed + DB.execute db [sql| UPDATE group_member_intros - SET intro_status = :intro_status, - group_queue_info = :group_queue_info, - direct_queue_info = :direct_queue_info, - updated_at = :updated_at - WHERE group_member_intro_id = :intro_id + SET intro_status = ?, + group_queue_info = ?, + direct_queue_info = ?, + updated_at = ? + WHERE group_member_intro_id = ? |] - [ ":intro_status" := GMIntroInvReceived, - ":group_queue_info" := groupConnReq, - ":direct_queue_info" := directConnReq introInv, - ":updated_at" := currentTs, - ":intro_id" := introId intro - ] + (GMIntroInvReceived, groupConnReq, directConnReq introInv, currentTs, introId intro) pure intro {introInvitation = Just introInv, introStatus = GMIntroInvReceived} saveMemberInvitation :: DB.Connection -> GroupMember -> IntroInvitation -> IO () saveMemberInvitation db GroupMember {groupMemberId} IntroInvitation {groupConnReq, directConnReq} = do currentTs <- getCurrentTime - DB.executeNamed + DB.execute db [sql| UPDATE group_members - SET member_status = :member_status, - group_queue_info = :group_queue_info, - direct_queue_info = :direct_queue_info, - updated_at = :updated_at - WHERE group_member_id = :group_member_id + SET member_status = ?, + group_queue_info = ?, + direct_queue_info = ?, + updated_at = ? + WHERE group_member_id = ? |] - [ ":member_status" := GSMemIntroInvited, - ":group_queue_info" := groupConnReq, - ":direct_queue_info" := directConnReq, - ":updated_at" := currentTs, - ":group_member_id" := groupMemberId - ] + (GSMemIntroInvited, groupConnReq, directConnReq, currentTs, groupMemberId) getIntroduction :: DB.Connection -> GroupMember -> GroupMember -> ExceptT StoreError IO GroupMemberIntro getIntroduction db reMember toMember = ExceptT $ do @@ -1364,14 +1366,14 @@ createIntroToMemberContact db user@User {userId} GroupMember {memberContactId = pure contactId updateMember_ :: Int64 -> UTCTime -> IO () updateMember_ contactId ts = - DB.executeNamed + DB.execute db [sql| UPDATE group_members - SET contact_id = :contact_id, updated_at = :updated_at - WHERE group_member_id = :group_member_id + SET contact_id = ?, updated_at = ? + 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 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 vr User {userId, userContactId} Contact {contactId} = do - gm_ <- maybeFirstRow toGroupAndMember $ - DB.query - db - [sql| - SELECT - -- GroupInfo - g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.description, gp.image, - g.host_conn_custom_user_profile_id, g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, - 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, - -- GroupInfo {membership} - 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, - 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 = GroupMember {memberProfile}} - pu.display_name, pu.full_name, pu.image, pu.contact_link, pu.local_alias, pu.preferences, - -- via GroupMember - 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, - 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.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.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.conn_chat_version, c.peer_chat_min_version, c.peer_chat_max_version - FROM group_members m - JOIN contacts ct ON ct.contact_id = m.contact_id - JOIN contact_profiles p ON p.contact_profile_id = COALESCE(m.member_profile_id, m.contact_profile_id) - JOIN groups g ON g.group_id = m.group_id AND g.group_id = ct.via_group - JOIN group_profiles gp USING (group_profile_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) - LEFT JOIN connections c ON c.connection_id = ( - SELECT max(cc.connection_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 - |] - (userId, userId, contactId, userContactId) + gm_ <- + maybeFirstRow toGroupAndMember $ + DB.query + db + [sql| + SELECT + -- GroupInfo + g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.description, gp.image, + g.host_conn_custom_user_profile_id, g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, + 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, + -- GroupInfo {membership} + 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, + 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 = GroupMember {memberProfile}} + pu.display_name, pu.full_name, pu.image, pu.contact_link, pu.local_alias, pu.preferences, + -- via GroupMember + 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, + 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.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.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.conn_chat_version, c.peer_chat_min_version, c.peer_chat_max_version + FROM group_members m + JOIN contacts ct ON ct.contact_id = m.contact_id + JOIN contact_profiles p ON p.contact_profile_id = COALESCE(m.member_profile_id, m.contact_profile_id) + JOIN groups g ON g.group_id = m.group_id AND g.group_id = ct.via_group + JOIN group_profiles gp USING (group_profile_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) + LEFT JOIN connections c ON c.connection_id = ( + SELECT max(cc.connection_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 + |] + (userId, userId, contactId, userContactId) mapM (bitraverse (addGroupChatTags db) pure) gm_ where toGroupAndMember :: (GroupInfoRow :. GroupMemberRow :. MaybeConnectionRow) -> (GroupInfo, GroupMember) @@ -1650,7 +1653,7 @@ createSentProbe db gVar userId to = DB.execute db "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 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 WHERE r.user_id = ? AND r.probe_hash = ? AND r.probe IS NULL |] - (userId, probeHash) + (userId, Binary probeHash) currentTs <- getCurrentTime let (ctId, gmId) = contactOrMemberIds from DB.execute db "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 catMaybes <$> mapM (getContactOrMember_ db vr user) cgmIds' 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 WHERE r.user_id = ? AND r.probe_hash = ? AND r.probe IS NOT NULL |] - (userId, probeHash) + (userId, Binary probeHash) currentTs <- getCurrentTime let (ctId, gmId) = contactOrMemberIds from DB.execute db "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 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 = ? 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 vr user ids = @@ -1777,22 +1780,18 @@ mergeContactRecords db vr user@User {userId} to@Contact {localDisplayName = keep db "UPDATE chat_items SET contact_id = ?, updated_at = ? WHERE contact_id = ? AND user_id = ?" (toContactId, currentTs, fromContactId, userId) - DB.executeNamed + DB.execute db [sql| UPDATE group_members - SET contact_id = :to_contact_id, - local_display_name = (SELECT local_display_name FROM contacts WHERE contact_id = :to_contact_id), - contact_profile_id = (SELECT contact_profile_id FROM contacts WHERE contact_id = :to_contact_id), - updated_at = :updated_at - WHERE contact_id = :from_contact_id - AND user_id = :user_id + SET 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 = ?), + updated_at = ? + WHERE contact_id = ? + AND user_id = ? |] - [ ":to_contact_id" := toContactId, - ":from_contact_id" := fromContactId, - ":user_id" := userId, - ":updated_at" := currentTs - ] + (toContactId, toContactId, toContactId, currentTs, fromContactId, userId) deleteContactProfile_ db userId fromContactId DB.execute db "DELETE FROM contacts WHERE contact_id = ? AND user_id = ?" (fromContactId, userId) deleteUnusedDisplayName_ db userId fromLDN @@ -1867,41 +1866,44 @@ associateContactWithMemberRecord deleteUnusedDisplayName_ :: DB.Connection -> UserId -> ContactName -> IO () deleteUnusedDisplayName_ db userId localDisplayName = - DB.executeNamed + DB.execute db [sql| 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 ( SELECT 1 FROM users - WHERE local_display_name = :local_display_name LIMIT 1 + WHERE local_display_name = ? LIMIT 1 ) AND 1 NOT IN ( 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 ( 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 ( 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 ( 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 ( 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 ( 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 createdAtCutoff = do @@ -1911,7 +1913,7 @@ deleteOldProbes db createdAtCutoff = do updateGroupSettings :: DB.Connection -> User -> Int64 -> ChatSettings -> IO () 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 User {userId} gId gMemberId GroupMemberSettings {showMessages} = do @@ -1923,7 +1925,7 @@ updateGroupMemberSettings db User {userId} gId gMemberId GroupMemberSettings {sh SET show_messages = ?, updated_at = ? 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 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 ) VALUES (?,?,?,?,?,?,?,?,?,?,?) |] - ( (userId, localDisplayName, memberContactProfileId, True, userPreferences, True) - :. (groupMemberId, False, currentTs, currentTs, currentTs) + ( (userId, localDisplayName, memberContactProfileId, BI True, userPreferences, BI True) + :. (groupMemberId, BI False, currentTs, currentTs, currentTs) ) contactId <- insertedRowId db DB.execute @@ -2041,8 +2043,8 @@ createMemberContact conn_chat_version, peer_chat_min_version, peer_chat_max_version, created_at, updated_at, to_subscribe ) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) |] - ( (userId, acId, cReq, connLevel, ConnNew, ConnContact, True, contactId, customUserProfileId) - :. (connChatVersion, minV, maxV, currentTs, currentTs, subMode == SMOnlyCreate) + ( (userId, acId, cReq, connLevel, ConnNew, ConnContact, BI True, contactId, customUserProfileId) + :. (connChatVersion, minV, maxV, currentTs, currentTs, BI (subMode == SMOnlyCreate)) ) connId <- insertedRowId db let ctConn = @@ -2093,7 +2095,7 @@ setContactGrpInvSent db Contact {contactId} xGrpDirectInvSent = do DB.execute db "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 @@ -2123,7 +2125,7 @@ createMemberContactInvited created_at, updated_at, chat_ts ) VALUES (?,?,?,?,?,?,?,?,?) |] - ( (userId, memberLDN, memberContactProfileId, True, userPreferences, True) + ( (userId, memberLDN, memberContactProfileId, BI True, userPreferences, BI True) :. (currentTs, currentTs, currentTs) ) contactId <- insertedRowId db @@ -2175,7 +2177,7 @@ createMemberContactConn_ ) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?) |] ( (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 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 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) setXGrpLinkMemReceived :: DB.Connection -> GroupMemberId -> Bool -> IO () @@ -2253,7 +2255,7 @@ setXGrpLinkMemReceived db mId xGrpLinkMemReceived = do DB.execute db "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 vr user@User {userId, userContactId} GroupInfo {groupId} memberId memberName = do diff --git a/src/Simplex/Chat/Store/Messages.hs b/src/Simplex/Chat/Store/Messages.hs index 03ba45f23f..b5c2acc36a 100644 --- a/src/Simplex/Chat/Store/Messages.hs +++ b/src/Simplex/Chat/Store/Messages.hs @@ -1,3 +1,4 @@ +{-# LANGUAGE CPP #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE GADTs #-} @@ -140,8 +141,6 @@ import Data.Text (Text) import qualified Data.Text as T import Data.Time (addUTCTime) 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.Markdown 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.Util (eitherToMaybe) 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 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 ) 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 pure $ Right SndMessage {msgId, sharedMsgId = SharedMsgId sharedMsgId, msgBody} 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) 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 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 ) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) |] - ((userId, msgId_) :. idsRow :. itemRow :. quoteRow :. forwardedFromRow) + ((userId, msgId_) :. idsRow :. itemRow :. quoteRow' :. forwardedFromRow) ciId <- insertedRowId db forM_ msgId_ $ \msgId -> insertChatItemMessage_ db ciId msgId createdAt pure ciId 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 = (msgDirection @d, itemTs, ciContent, toCIContentTag ciContent, ciContentToText ciContent, ciCreateStatus ciContent, msgContentTag <$> ciMsgContent ciContent, sharedMsgId, forwardedByMember) :. (createdAt, createdAt, justTrue live) :. ciTimedRow timed + 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, 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 = case chatDirection of 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} = case chatDirection of 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 Just mId | 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 _ -> pure . ciQuote Nothing $ CIQGroupRcv Nothing where @@ -468,7 +475,7 @@ getChatItemQuote_ db User {userId, userContactId} chatDirection QuotedMsg {msgRe DB.query db "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 ciQuoteDirect :: Maybe ChatItemId -> CIQuote 'CTDirect ciQuoteDirect = (`ciQuote` if userSent then CIQDirectSnd else CIQDirectRcv) @@ -479,17 +486,17 @@ getChatItemQuote_ db User {userId, userContactId} chatDirection QuotedMsg {msgRe 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" (userId, groupId, msgId, MDSnd) - getGroupChatItemId_ :: Int64 -> MemberId -> IO (Maybe ChatItemId) - getGroupChatItemId_ groupId mId = + getGroupChatItemId_ :: Int64 -> GroupMemberId -> IO (Maybe ChatItemId) + getGroupChatItemId_ groupId groupMemberId = maybeFirstRow fromOnly $ DB.query 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 = ?" - (userId, groupId, msgId, MDRcv, mId) + (userId, groupId, msgId, MDRcv, groupMemberId) getGroupChatItemQuote_ :: Int64 -> MemberId -> IO (CIQuote 'CTGroup) getGroupChatItemQuote_ groupId mId = do ciQuoteGroup - <$> DB.queryNamed + <$> DB.query db [sql| 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 AND i.group_id = m.group_id AND m.group_member_id = i.group_member_id - AND i.shared_msg_id = :msg_id - WHERE m.user_id = :user_id AND m.group_id = :group_id AND m.member_id = :member_id + AND i.shared_msg_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 ciQuoteGroup :: [Only (Maybe ChatItemId) :. GroupMemberRow] -> CIQuote 'CTGroup ciQuoteGroup [] = ciQuote Nothing $ CIQGroupRcv Nothing @@ -564,14 +571,21 @@ findDirectChatPreviews_ db User {userId} pagination clq = ACPD SCTDirect $ DirectChatPD ts contactId lastItemId_ (toChatStats statsRow) baseQuery = [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 - 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 ( SELECT contact_id, COUNT(1) AS UnreadCount, MIN(chat_item_id) AS MinUnread FROM chat_items @@ -582,58 +596,61 @@ findDirectChatPreviews_ db User {userId} pagination clq = baseParams = (userId, userId, CISRcvNew) getPreviews = case clq of 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 - queryWithPagination db q p pagination + queryWithPagination q p CLQFilters {favorite = True, unread = False} -> do let q = baseQuery + <> " " <> [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 - |] + |] p = baseParams :. Only userId - queryWithPagination db q p pagination + queryWithPagination q p CLQFilters {favorite = False, unread = True} -> do let q = baseQuery + <> " " <> [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) - |] + |] p = baseParams :. Only userId - queryWithPagination db q p pagination + queryWithPagination q p CLQFilters {favorite = True, unread = True} -> do let q = baseQuery + <> " " <> [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 OR ct.unread_chat = 1 OR ChatStats.UnreadCount > 0) - |] + |] p = baseParams :. Only userId - queryWithPagination db q p pagination + queryWithPagination q p CLQSearch {search} -> do let q = baseQuery + <> " " <> [sql| 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 ( - ct.local_display_name LIKE '%' || ? || '%' - OR cp.display_name LIKE '%' || ? || '%' - OR cp.full_name LIKE '%' || ? || '%' - OR cp.local_alias LIKE '%' || ? || '%' + LOWER(ct.local_display_name) LIKE '%' || LOWER(?) || '%' + OR LOWER(cp.display_name) LIKE '%' || LOWER(?) || '%' + OR LOWER(cp.full_name) LIKE '%' || LOWER(?) || '%' + OR LOWER(cp.local_alias) LIKE '%' || LOWER(?) || '%' ) - |] + |] p = baseParams :. (userId, search, search, search, search) - queryWithPagination db q p pagination - -queryWithPagination :: ToRow p => DB.Connection -> Query -> p -> PaginationByTime -> IO [(ContactId, UTCTime, Maybe ChatItemId) :. ChatStatsRow] -queryWithPagination db query params = \case - PTLast count -> DB.query db (query <> " ORDER BY ts DESC LIMIT ?") (params :. Only 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 ts < ? ORDER BY ts DESC LIMIT ?") (params :. (ts, count)) + queryWithPagination q p + queryWithPagination :: ToRow p => Query -> p -> IO [(ContactId, UTCTime, Maybe ChatItemId) :. ChatStatsRow] + queryWithPagination query params = case pagination of + PTLast count -> DB.query db (query <> " ORDER BY ct.chat_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)) + PTBefore ts count -> DB.query db (query <> " AND ct.chat_ts < ? ORDER BY ct.chat_ts DESC LIMIT ?") (params :. (ts, count)) getDirectChatPreview_ :: DB.Connection -> VersionRangeChat -> User -> ChatPreviewData 'CTDirect -> ExceptT StoreError IO AChat 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) baseQuery = [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 - 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 ( SELECT group_id, COUNT(1) AS UnreadCount, MIN(chat_item_id) AS MinUnread FROM chat_items @@ -679,50 +703,59 @@ findGroupChatPreviews_ db User {userId} pagination clq = CLQFilters {favorite = False, unread = False} -> do let q = baseQuery <> " WHERE g.user_id = ?" p = baseParams :. Only userId - queryWithPagination db q p pagination + queryWithPagination q p CLQFilters {favorite = True, unread = False} -> do let q = baseQuery + <> " " <> [sql| WHERE g.user_id = ? AND g.favorite = 1 - |] + |] p = baseParams :. Only userId - queryWithPagination db q p pagination + queryWithPagination q p CLQFilters {favorite = False, unread = True} -> do let q = baseQuery + <> " " <> [sql| WHERE g.user_id = ? AND (g.unread_chat = 1 OR ChatStats.UnreadCount > 0) - |] + |] p = baseParams :. Only userId - queryWithPagination db q p pagination + queryWithPagination q p CLQFilters {favorite = True, unread = True} -> do let q = baseQuery + <> " " <> [sql| WHERE g.user_id = ? AND (g.favorite = 1 OR g.unread_chat = 1 OR ChatStats.UnreadCount > 0) - |] + |] p = baseParams :. Only userId - queryWithPagination db q p pagination + queryWithPagination q p CLQSearch {search} -> do let q = baseQuery + <> " " <> [sql| JOIN group_profiles gp ON gp.group_profile_id = g.group_profile_id WHERE g.user_id = ? AND ( - g.local_display_name LIKE '%' || ? || '%' - OR gp.display_name LIKE '%' || ? || '%' - OR gp.full_name LIKE '%' || ? || '%' - OR gp.description LIKE '%' || ? || '%' + LOWER(g.local_display_name) LIKE '%' || LOWER(?) || '%' + OR LOWER(gp.display_name) LIKE '%' || LOWER(?) || '%' + OR LOWER(gp.full_name) LIKE '%' || LOWER(?) || '%' + OR LOWER(gp.description) LIKE '%' || LOWER(?) || '%' ) - |] + |] 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 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) baseQuery = [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 - 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 ( SELECT note_folder_id, COUNT(1) AS UnreadCount, MIN(chat_item_id) AS MinUnread FROM chat_items @@ -761,36 +801,44 @@ findLocalChatPreviews_ db User {userId} pagination clq = CLQFilters {favorite = False, unread = False} -> do let q = baseQuery <> " WHERE nf.user_id = ?" p = baseParams :. Only userId - queryWithPagination db q p pagination + queryWithPagination q p CLQFilters {favorite = True, unread = False} -> do let q = baseQuery + <> " " <> [sql| WHERE nf.user_id = ? AND nf.favorite = 1 - |] + |] p = baseParams :. Only userId - queryWithPagination db q p pagination + queryWithPagination q p CLQFilters {favorite = False, unread = True} -> do let q = baseQuery + <> " " <> [sql| WHERE nf.user_id = ? AND (nf.unread_chat = 1 OR ChatStats.UnreadCount > 0) - |] + |] p = baseParams :. Only userId - queryWithPagination db q p pagination + queryWithPagination q p CLQFilters {favorite = True, unread = True} -> do let q = baseQuery + <> " " <> [sql| WHERE nf.user_id = ? AND (nf.favorite = 1 OR nf.unread_chat = 1 OR ChatStats.UnreadCount > 0) - |] + |] p = baseParams :. Only userId - queryWithPagination db q p pagination + queryWithPagination q p 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 user (LocalChatPD _ noteFolderId lastItemId_ stats) = do @@ -833,9 +881,9 @@ toLocalChatItem currentTs ((itemId, itemTs, AMsgDirection msgDir, itemContentTex let itemDeleted' = case itemDeleted of DBCINotDeleted -> Nothing _ -> Just (CIDeleted @'CTLocal deletedTs) - itemEdited' = fromMaybe False itemEdited + itemEdited' = maybe False unBI itemEdited 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 = timedTTL >>= \ttl -> Just CITimed {ttl, deleteAt = timedDeleteAt} @@ -852,7 +900,7 @@ getContactRequestChatPreviews_ db User {userId} pagination clq = case clq of SELECT 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, - cr.created_at, cr.updated_at as ts, + cr.created_at, cr.updated_at, cr.peer_chat_min_version, cr.peer_chat_max_version FROM contact_requests cr 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.group_id IS NULL AND ( - cr.local_display_name LIKE '%' || ? || '%' - OR p.display_name LIKE '%' || ? || '%' - OR p.full_name LIKE '%' || ? || '%' + LOWER(cr.local_display_name) LIKE '%' || LOWER(?) || '%' + OR LOWER(p.display_name) LIKE '%' || LOWER(?) || '%' + OR LOWER(p.full_name) LIKE '%' || LOWER(?) || '%' ) |] params search = (userId, userId, search, search, search) getPreviews search = case pagination of - PTLast count -> DB.query db (query <> " ORDER BY ts DESC LIMIT ?") (params search :. Only count) - PTAfter ts count -> DB.query db (query <> " AND ts > ? ORDER BY ts ASC LIMIT ?") (params search :. (ts, count)) - PTBefore ts count -> DB.query db (query <> " AND ts < ? ORDER BY ts DESC LIMIT ?") (params search :. (ts, 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 cr.updated_at > ? ORDER BY cr.updated_at ASC 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 cReqRow = let cReq@UserContactRequest {updatedAt} = toContactRequest cReqRow @@ -891,7 +939,7 @@ getContactConnectionChatPreviews_ db User {userId} pagination clq = case clq of [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 as ts + custom_user_profile_id, conn_req_inv, local_alias, created_at, updated_at FROM connections WHERE user_id = ? AND conn_type = ? @@ -899,14 +947,14 @@ getContactConnectionChatPreviews_ db User {userId} pagination clq = case clq of AND contact_id IS NULL AND conn_level = 0 AND via_contact IS NULL - AND (via_group_link = 0 || (via_group_link = 1 AND group_link_id IS NOT NULL)) - AND local_alias LIKE '%' || ? || '%' + AND (via_group_link = 0 OR (via_group_link = 1 AND group_link_id IS NOT NULL)) + AND LOWER(local_alias) LIKE '%' || LOWER(?) || '%' |] params search = (userId, ConnContact, ConnPrepared, search) getPreviews search = case pagination of - PTLast count -> DB.query db (query <> " ORDER BY ts DESC LIMIT ?") (params search :. Only count) - PTAfter ts count -> DB.query db (query <> " AND ts > ? ORDER BY ts ASC LIMIT ?") (params search :. (ts, count)) - PTBefore ts count -> DB.query db (query <> " AND ts < ? ORDER BY ts DESC LIMIT ?") (params search :. (ts, 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 updated_at > ? ORDER BY updated_at ASC 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 connRow = let conn@PendingContactConnection {updatedAt} = toPendingContactConnection connRow @@ -942,7 +990,7 @@ getDirectChatItemIdsLast_ db User {userId} Contact {contactId} count search = [sql| SELECT chat_item_id 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 LIMIT ? |] @@ -1006,7 +1054,7 @@ getDirectCIsAfter_ db User {userId} Contact {contactId} afterCI count search = [sql| SELECT chat_item_id 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 > ?)) ORDER BY created_at ASC, chat_item_id ASC LIMIT ? @@ -1029,7 +1077,7 @@ getDirectCIsBefore_ db User {userId} Contact {contactId} beforeCI count search = [sql| SELECT chat_item_id 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 < ?)) ORDER BY created_at DESC, chat_item_id DESC LIMIT ? @@ -1121,7 +1169,7 @@ getContactNavInfo_ db User {userId} Contact {contactId} afterCI = do FROM chat_items WHERE user_id = ? AND contact_id = ? AND item_status = ? AND created_at = ? AND chat_item_id > ? - ) + ) ci |] ( (userId, contactId, CISRcvNew, ciCreatedAt afterCI) :. (userId, contactId, CISRcvNew, ciCreatedAt afterCI, cChatItemId afterCI) @@ -1143,7 +1191,7 @@ getContactNavInfo_ db User {userId} Contact {contactId} afterCI = do FROM chat_items WHERE user_id = ? AND contact_id = ? AND created_at = ? AND chat_item_id > ? - ) + ) ci |] ( (userId, contactId, ciCreatedAt 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 c p ob | null search = searchQuery "" () - | otherwise = searchQuery " AND item_text LIKE '%' || ? || '%' " (Only search) + | otherwise = searchQuery " AND LOWER(item_text) LIKE '%' || LOWER(?) || '%' " (Only search) where searchQuery :: ToRow p' => Query -> p' -> IO [ChatItemId] searchQuery c' p' = @@ -1313,7 +1361,7 @@ getGroupMinUnreadId_ db user g contentFilter = queryUnreadGroupItems db user g contentFilter baseQuery orderLimit where baseQuery = "SELECT chat_item_id FROM chat_items WHERE user_id = ? AND group_id = ? " - orderLimit = " ORDER BY item_ts ASC, chat_item_id ASC LIMIT 1" + orderLimit = " ORDER BY item_ts ASC, chat_item_id ASC LIMIT 1" getGroupUnreadCount_ :: DB.Connection -> User -> GroupInfo -> Maybe ContentFilter -> IO Int getGroupUnreadCount_ db user g contentFilter = @@ -1372,7 +1420,7 @@ getGroupNavInfo_ db User {userId} GroupInfo {groupId} afterCI = do FROM chat_items WHERE user_id = ? AND group_id = ? AND item_status = ? AND item_ts = ? AND chat_item_id > ? - ) + ) ci |] ( (userId, groupId, CISRcvNew, chatItemTs afterCI) :. (userId, groupId, CISRcvNew, chatItemTs afterCI, cChatItemId afterCI) @@ -1394,7 +1442,7 @@ getGroupNavInfo_ db User {userId} GroupInfo {groupId} afterCI = do FROM chat_items WHERE user_id = ? AND group_id = ? AND item_ts = ? AND chat_item_id > ? - ) + ) ci |] ( (userId, groupId, chatItemTs afterCI) :. (userId, groupId, chatItemTs afterCI, cChatItemId afterCI) @@ -1428,7 +1476,7 @@ getLocalChatItemIdsLast_ db User {userId} NoteFolder {noteFolderId} count search [sql| SELECT chat_item_id 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 LIMIT ? |] @@ -1476,7 +1524,7 @@ getLocalCIsAfter_ db User {userId} NoteFolder {noteFolderId} afterCI count searc [sql| SELECT chat_item_id 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 > ?)) ORDER BY created_at ASC, chat_item_id ASC LIMIT ? @@ -1499,7 +1547,7 @@ getLocalCIsBefore_ db User {userId} NoteFolder {noteFolderId} beforeCI count sea [sql| SELECT chat_item_id 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 < ?)) ORDER BY created_at DESC, chat_item_id DESC LIMIT ? @@ -1591,7 +1639,7 @@ getLocalNavInfo_ db User {userId} NoteFolder {noteFolderId} afterCI = do FROM chat_items WHERE user_id = ? AND note_folder_id = ? AND item_status = ? AND created_at = ? AND chat_item_id > ? - ) + ) ci |] ( (userId, noteFolderId, CISRcvNew, ciCreatedAt afterCI) :. (userId, noteFolderId, CISRcvNew, ciCreatedAt afterCI, cChatItemId afterCI) @@ -1613,7 +1661,7 @@ getLocalNavInfo_ db User {userId} NoteFolder {noteFolderId} afterCI = do FROM chat_items WHERE user_id = ? AND note_folder_id = ? AND created_at = ? AND chat_item_id > ? - ) + ) ci |] ( (userId, noteFolderId, ciCreatedAt 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 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 ChatItemRow = - (Int64, ChatItemTs, AMsgDirection, Text, Text, ACIStatus, Maybe Bool, Maybe SharedMsgId) - :. (Int, Maybe UTCTime, Maybe Bool, UTCTime, UTCTime) + (Int64, ChatItemTs, AMsgDirection, Text, Text, ACIStatus, Maybe BoolInt, Maybe SharedMsgId) + :. (Int, Maybe UTCTime, Maybe BoolInt, UTCTime, UTCTime) :. ChatItemForwardedFromRow :. ChatItemModeRow :. 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 qr@(_, _, _, _, quotedSent) = toQuote qr $ direction <$> quotedSent +toDirectQuote qr@(_, _, _, _, quotedSent) = toQuote qr $ direction . unBI <$> quotedSent where direction sent = if sent then CIQDirectSnd else CIQDirectRcv @@ -1818,9 +1866,9 @@ toDirectChatItem currentTs (((itemId, itemTs, AMsgDirection msgDir, itemContentT let itemDeleted' = case itemDeleted of DBCINotDeleted -> Nothing _ -> Just (CIDeleted @'CTDirect deletedTs) - itemEdited' = fromMaybe False itemEdited + itemEdited' = maybe False unBI itemEdited 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 = timedTTL >>= \ttl -> Just CITimed {ttl, deleteAt = timedDeleteAt} @@ -1837,9 +1885,9 @@ type GroupQuoteRow = QuoteRow :. MaybeGroupMemberRow toGroupQuote :: QuoteRow -> Maybe GroupMember -> Maybe (CIQuote 'CTGroup) toGroupQuote qr@(_, _, _, _, quotedSent) quotedMember_ = toQuote qr $ direction quotedSent quotedMember_ where - direction (Just True) _ = Just CIQGroupSnd - direction (Just False) (Just member) = Just . CIQGroupRcv $ Just member - direction (Just False) Nothing = Just $ CIQGroupRcv Nothing + direction (Just (BI True)) _ = Just CIQGroupSnd + direction (Just (BI False)) (Just member) = Just . CIQGroupRcv $ Just member + direction (Just (BI False)) Nothing = Just $ CIQGroupRcv Nothing direction _ _ = Nothing -- 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) DBCIBlockedByAdmin -> Just (CIBlockedByAdmin deletedTs) _ -> Just (maybe (CIDeleted @'CTGroup deletedTs) (CIModerated deletedTs) deletedByGroupMember_) - itemEdited' = fromMaybe False itemEdited + itemEdited' = maybe False unBI itemEdited 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 = timedTTL >>= \ttl -> Just CITimed {ttl, deleteAt = timedDeleteAt} @@ -1912,7 +1960,7 @@ getAllChatItems db vr user@User {userId} pagination search_ = do [sql| SELECT chat_item_id, contact_id, group_id, note_folder_id 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 LIMIT ? |] @@ -1923,7 +1971,7 @@ getAllChatItems db vr user@User {userId} pagination search_ = do [sql| SELECT chat_item_id, contact_id, group_id, note_folder_id 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 > ?)) ORDER BY item_ts ASC, chat_item_id ASC LIMIT ? @@ -1936,7 +1984,7 @@ getAllChatItems db vr user@User {userId} pagination search_ = do [sql| SELECT chat_item_id, contact_id, group_id, note_folder_id 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 < ?)) ORDER BY item_ts DESC, chat_item_id DESC 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 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}} 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 = ? 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 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 = ? 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 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 = ? 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 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 = ?" (groupId, itemSharedMId, memberId) -toCIReaction :: (MsgReaction, Bool, Int) -> CIReactionCount -toCIReaction (reaction, userReacted, totalReacted) = CIReactionCount {reaction, userReacted, totalReacted} +toCIReaction :: (MsgReaction, BoolInt, Int) -> CIReactionCount +toCIReaction (reaction, BI userReacted, totalReacted) = CIReactionCount {reaction, userReacted, totalReacted} getDirectReactions :: DB.Connection -> Contact -> SharedMsgId -> Bool -> IO [MsgReaction] getDirectReactions db ct itemSharedMId sent = @@ -2753,7 +2801,7 @@ getDirectReactions db ct itemSharedMId sent = FROM chat_item_reactions 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 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) VALUES (?,?,?,?,?,?) |] - (contactId' ct, itemSharedMId, sent, reaction, msgId, reactionTs) + (contactId' ct, itemSharedMId, BI sent, reaction, msgId, reactionTs) | otherwise = DB.execute db @@ -2773,7 +2821,7 @@ setDirectReaction db ct itemSharedMId sent reaction add msgId reactionTs DELETE FROM chat_item_reactions 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 GroupInfo {groupId} m itemMemberId itemSharedMId sent = @@ -2785,7 +2833,7 @@ getGroupReactions db GroupInfo {groupId} m itemMemberId itemSharedMId sent = FROM chat_item_reactions 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 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) VALUES (?,?,?,?,?,?,?,?) |] - (groupId, groupMemberId' m, itemMemberId, itemSharedMId, sent, reaction, msgId, reactionTs) + (groupId, groupMemberId' m, itemMemberId, itemSharedMId, BI sent, reaction, msgId, reactionTs) | otherwise = DB.execute db @@ -2805,7 +2853,7 @@ setGroupReaction db GroupInfo {groupId} m itemMemberId itemSharedMId sent reacti 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 = ? |] - (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 vr user groupId itemSharedMId reaction = do @@ -2974,7 +3022,7 @@ setGroupSndViaProxy db itemId memberId viaProxy = SET via_proxy = ? WHERE chat_item_id = ? AND group_member_id = ? |] - (viaProxy, itemId, memberId) + (BI viaProxy, itemId, memberId) getGroupSndStatuses :: DB.Connection -> ChatItemId -> IO [MemberDeliveryStatus] getGroupSndStatuses db itemId = @@ -2989,7 +3037,7 @@ getGroupSndStatuses db itemId = (Only itemId) where memStatus (groupMemberId, memberDeliveryStatus, sentViaProxy) = - MemberDeliveryStatus {groupMemberId, memberDeliveryStatus, sentViaProxy} + MemberDeliveryStatus {groupMemberId, memberDeliveryStatus, sentViaProxy = unBI <$> sentViaProxy} getGroupSndStatusCounts :: DB.Connection -> ChatItemId -> IO [(GroupSndStatus, Int)] getGroupSndStatusCounts db itemId = diff --git a/src/Simplex/Chat/Store/NoteFolders.hs b/src/Simplex/Chat/Store/NoteFolders.hs index feb687f2ff..8f71f3f21e 100644 --- a/src/Simplex/Chat/Store/NoteFolders.hs +++ b/src/Simplex/Chat/Store/NoteFolders.hs @@ -1,3 +1,4 @@ +{-# LANGUAGE CPP #-} {-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE NamedFieldPuns #-} @@ -10,13 +11,19 @@ module Simplex.Chat.Store.NoteFolders where import Control.Monad.Except (ExceptT (..), throwError) import Control.Monad.IO.Class (liftIO) import Data.Time (getCurrentTime) -import Database.SQLite.Simple (Only (..)) -import Database.SQLite.Simple.QQ (sql) import Simplex.Chat.Store.Shared (StoreError (..)) import Simplex.Chat.Types (NoteFolder (..), NoteFolderId, User (..)) import Simplex.Messaging.Agent.Protocol (UserId) 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 User {userId} = do @@ -43,13 +50,13 @@ getNoteFolder db User {userId} noteFolderId = |] (userId, noteFolderId) where - toNoteFolder (createdAt, updatedAt, chatTs, favorite, unread) = + toNoteFolder (createdAt, updatedAt, chatTs, BI favorite, BI unread) = NoteFolder {noteFolderId, userId, createdAt, updatedAt, chatTs, favorite, unread} updateNoteFolderUnreadChat :: DB.Connection -> User -> NoteFolder -> Bool -> IO () updateNoteFolderUnreadChat db User {userId} NoteFolder {noteFolderId} unreadChat = do 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 userId NoteFolder {noteFolderId} = do diff --git a/src/Simplex/Chat/Store/Postgres/Migrations.hs b/src/Simplex/Chat/Store/Postgres/Migrations.hs new file mode 100644 index 0000000000..285a952279 --- /dev/null +++ b/src/Simplex/Chat/Store/Postgres/Migrations.hs @@ -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} diff --git a/src/Simplex/Chat/Store/Postgres/Migrations/M20241220_initial.hs b/src/Simplex/Chat/Store/Postgres/Migrations/M20241220_initial.hs new file mode 100644 index 0000000000..24624cdf37 --- /dev/null +++ b/src/Simplex/Chat/Store/Postgres/Migrations/M20241220_initial.hs @@ -0,0 +1,1012 @@ +{-# LANGUAGE QuasiQuotes #-} + +module Simplex.Chat.Store.Postgres.Migrations.M20241220_initial where + +import Data.Text (Text) +import qualified Data.Text as T +import Text.RawString.QQ (r) + +m20241220_initial :: Text +m20241220_initial = + T.pack + [r| +CREATE TABLE users( + user_id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY, + contact_id BIGINT NOT NULL UNIQUE, + local_display_name TEXT NOT NULL UNIQUE, + active_user SMALLINT NOT NULL DEFAULT 0, + created_at TIMESTAMPTZ NOT NULL, + updated_at TIMESTAMPTZ NOT NULL, + agent_user_id BIGINT NOT NULL, + view_pwd_hash BYTEA, + view_pwd_salt BYTEA, + show_ntfs SMALLINT NOT NULL DEFAULT 1, + send_rcpts_contacts SMALLINT NOT NULL DEFAULT 0, + send_rcpts_small_groups SMALLINT NOT NULL DEFAULT 0, + user_member_profile_updated_at TIMESTAMPTZ, + ui_themes TEXT, + active_order BIGINT NOT NULL DEFAULT 0 +); +CREATE TABLE contact_profiles( + contact_profile_id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY, + display_name TEXT NOT NULL, + full_name TEXT NOT NULL, + properties TEXT NOT NULL DEFAULT '{}', + created_at TIMESTAMPTZ NOT NULL, + updated_at TIMESTAMPTZ NOT NULL, + image TEXT, + user_id BIGINT DEFAULT NULL REFERENCES users ON DELETE CASCADE, + incognito SMALLINT, + local_alias TEXT NOT NULL DEFAULT '', + preferences TEXT, + contact_link BYTEA +); +CREATE TABLE display_names( + user_id BIGINT NOT NULL REFERENCES users ON DELETE CASCADE, + local_display_name TEXT NOT NULL, + ldn_base TEXT NOT NULL, + ldn_suffix BIGINT NOT NULL DEFAULT 0, + created_at TIMESTAMPTZ NOT NULL, + updated_at TIMESTAMPTZ NOT NULL, + PRIMARY KEY(user_id, local_display_name), + UNIQUE(user_id, ldn_base, ldn_suffix) +); +ALTER TABLE users +ADD CONSTRAINT fk_users_display_names + FOREIGN KEY(user_id, local_display_name) + REFERENCES display_names(user_id, local_display_name) + ON DELETE RESTRICT + ON UPDATE CASCADE + DEFERRABLE INITIALLY DEFERRED; +CREATE TABLE contacts( + contact_id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY, + contact_profile_id BIGINT REFERENCES contact_profiles ON DELETE SET NULL, + user_id BIGINT NOT NULL REFERENCES users ON DELETE CASCADE, + local_display_name TEXT NOT NULL, + is_user SMALLINT NOT NULL DEFAULT 0, + via_group BIGINT, + created_at TIMESTAMPTZ NOT NULL DEFAULT (now()), + updated_at TIMESTAMPTZ NOT NULL, + xcontact_id BYTEA, + enable_ntfs SMALLINT, + unread_chat SMALLINT NOT NULL DEFAULT 0, + contact_used SMALLINT NOT NULL DEFAULT 0, + user_preferences TEXT NOT NULL DEFAULT '{}', + chat_ts TIMESTAMPTZ, + deleted SMALLINT NOT NULL DEFAULT 0, + favorite SMALLINT NOT NULL DEFAULT 0, + send_rcpts SMALLINT, + contact_group_member_id BIGINT, + contact_grp_inv_sent SMALLINT NOT NULL DEFAULT 0, + contact_status TEXT NOT NULL DEFAULT 'active', + custom_data BYTEA, + ui_themes TEXT, + chat_deleted SMALLINT NOT NULL DEFAULT 0, + FOREIGN KEY(user_id, local_display_name) + REFERENCES display_names(user_id, local_display_name) + ON DELETE CASCADE + ON UPDATE CASCADE, + UNIQUE(user_id, local_display_name), + UNIQUE(user_id, contact_profile_id) +); +ALTER TABLE users +ADD CONSTRAINT fk_users_contacts + FOREIGN KEY(contact_id) + REFERENCES contacts(contact_id) + ON DELETE RESTRICT + DEFERRABLE INITIALLY DEFERRED; +CREATE TABLE known_servers( + server_id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY, + host TEXT NOT NULL, + port TEXT NOT NULL, + key_hash BYTEA, + user_id BIGINT NOT NULL REFERENCES users ON DELETE CASCADE, + created_at TIMESTAMPTZ NOT NULL, + updated_at TIMESTAMPTZ NOT NULL, + UNIQUE(user_id, host, port) +); +CREATE TABLE group_profiles( + group_profile_id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY, + display_name TEXT NOT NULL, + full_name TEXT NOT NULL, + properties TEXT NOT NULL DEFAULT '{}', + created_at TIMESTAMPTZ NOT NULL, + updated_at TIMESTAMPTZ NOT NULL, + image TEXT, + user_id BIGINT DEFAULT NULL REFERENCES users ON DELETE CASCADE, + preferences TEXT, + description TEXT NULL +); +CREATE TABLE groups( + group_id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY, + user_id BIGINT NOT NULL REFERENCES users ON DELETE CASCADE, + local_display_name TEXT NOT NULL, + group_profile_id BIGINT REFERENCES group_profiles ON DELETE SET NULL, + inv_queue_info BYTEA, + created_at TIMESTAMPTZ NOT NULL, + updated_at TIMESTAMPTZ NOT NULL, + chat_item_id BIGINT DEFAULT NULL, + enable_ntfs SMALLINT, + host_conn_custom_user_profile_id BIGINT REFERENCES contact_profiles ON DELETE SET NULL, + unread_chat SMALLINT NOT NULL DEFAULT 0, + chat_ts TIMESTAMPTZ, + favorite SMALLINT NOT NULL DEFAULT 0, + send_rcpts SMALLINT, + via_group_link_uri_hash BYTEA, + user_member_profile_sent_at TIMESTAMPTZ, + custom_data BYTEA, + ui_themes TEXT, + business_member_id BYTEA NULL, + business_chat TEXT NULL, + business_xcontact_id BYTEA NULL, + customer_member_id BYTEA NULL, + FOREIGN KEY(user_id, local_display_name) + REFERENCES display_names(user_id, local_display_name) + ON DELETE CASCADE + ON UPDATE CASCADE, + UNIQUE(user_id, local_display_name), + UNIQUE(user_id, group_profile_id) +); +ALTER TABLE contacts +ADD CONSTRAINT fk_contacts_groups + FOREIGN KEY(via_group) + REFERENCES groups(group_id) ON DELETE SET NULL; +CREATE TABLE group_members( + group_member_id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY, + group_id BIGINT NOT NULL REFERENCES groups ON DELETE CASCADE, + member_id BYTEA NOT NULL, + member_role TEXT NOT NULL, + member_category TEXT NOT NULL, + member_status TEXT NOT NULL, + invited_by BIGINT REFERENCES contacts(contact_id) ON DELETE SET NULL, + sent_inv_queue_info BYTEA, + group_queue_info BYTEA, + direct_queue_info BYTEA, + user_id BIGINT NOT NULL REFERENCES users ON DELETE CASCADE, + local_display_name TEXT NOT NULL, + contact_profile_id BIGINT NOT NULL REFERENCES contact_profiles ON DELETE CASCADE, + contact_id BIGINT REFERENCES contacts ON DELETE CASCADE, + created_at TIMESTAMPTZ NOT NULL, + updated_at TIMESTAMPTZ NOT NULL, + member_profile_id BIGINT REFERENCES contact_profiles ON DELETE SET NULL, + show_messages SMALLINT NOT NULL DEFAULT 1, + xgrplinkmem_received SMALLINT NOT NULL DEFAULT 0, + invited_by_group_member_id BIGINT REFERENCES group_members ON DELETE SET NULL, + peer_chat_min_version INTEGER NOT NULL DEFAULT 1, + peer_chat_max_version INTEGER NOT NULL DEFAULT 1, + member_restriction TEXT, + FOREIGN KEY(user_id, local_display_name) + REFERENCES display_names(user_id, local_display_name) + ON DELETE CASCADE + ON UPDATE CASCADE, + UNIQUE(group_id, member_id) +); +ALTER TABLE contacts +ADD CONSTRAINT fk_contacts_group_members + FOREIGN KEY(contact_group_member_id) + REFERENCES group_members(group_member_id) ON DELETE SET NULL; +CREATE TABLE group_member_intros( + group_member_intro_id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY, + re_group_member_id BIGINT NOT NULL REFERENCES group_members(group_member_id) ON DELETE CASCADE, + to_group_member_id BIGINT NOT NULL REFERENCES group_members(group_member_id) ON DELETE CASCADE, + group_queue_info BYTEA, + direct_queue_info BYTEA, + intro_status TEXT NOT NULL, + created_at TIMESTAMPTZ NOT NULL, + updated_at TIMESTAMPTZ NOT NULL, + intro_chat_protocol_version INTEGER NOT NULL DEFAULT 3, + UNIQUE(re_group_member_id, to_group_member_id) +); +CREATE TABLE files( + file_id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY, + contact_id BIGINT REFERENCES contacts ON DELETE CASCADE, + group_id BIGINT REFERENCES groups ON DELETE CASCADE, + file_name TEXT NOT NULL, + file_path TEXT, + file_size BIGINT NOT NULL, + chunk_size BIGINT NOT NULL, + created_at TIMESTAMPTZ NOT NULL DEFAULT (now()), + user_id BIGINT NOT NULL REFERENCES users ON DELETE CASCADE, + chat_item_id BIGINT DEFAULT NULL, + updated_at TIMESTAMPTZ NOT NULL, + cancelled SMALLINT, + ci_file_status TEXT, + file_inline TEXT, + agent_snd_file_id BYTEA NULL, + private_snd_file_descr TEXT NULL, + agent_snd_file_deleted SMALLINT NOT NULL DEFAULT 0, + protocol TEXT NOT NULL DEFAULT 'smp', + file_crypto_key BYTEA, + file_crypto_nonce BYTEA, + note_folder_id BIGINT DEFAULT NULL, + redirect_file_id BIGINT REFERENCES files ON DELETE CASCADE +); +CREATE TABLE snd_files( + file_id BIGINT NOT NULL REFERENCES files ON DELETE CASCADE, + connection_id BIGINT NOT NULL, + file_status TEXT NOT NULL, + group_member_id BIGINT REFERENCES group_members ON DELETE CASCADE, + created_at TIMESTAMPTZ NOT NULL, + updated_at TIMESTAMPTZ NOT NULL, + file_inline TEXT, + last_inline_msg_delivery_id BIGINT, + file_descr_id BIGINT NULL, + PRIMARY KEY(file_id, connection_id) +); +CREATE TABLE rcv_files( + file_id BIGINT PRIMARY KEY REFERENCES files ON DELETE CASCADE, + file_status TEXT NOT NULL, + group_member_id BIGINT REFERENCES group_members ON DELETE CASCADE, + file_queue_info BYTEA, + created_at TIMESTAMPTZ NOT NULL, + updated_at TIMESTAMPTZ NOT NULL, + rcv_file_inline TEXT, + file_inline TEXT, + file_descr_id BIGINT NULL, + agent_rcv_file_id BYTEA NULL, + agent_rcv_file_deleted SMALLINT NOT NULL DEFAULT 0, + to_receive SMALLINT, + user_approved_relays SMALLINT NOT NULL DEFAULT 0 +); +CREATE TABLE snd_file_chunks( + file_id BIGINT NOT NULL, + connection_id BIGINT NOT NULL, + chunk_number BIGINT NOT NULL, + chunk_agent_msg_id BIGINT, + chunk_sent SMALLINT NOT NULL DEFAULT 0, + created_at TIMESTAMPTZ NOT NULL, + updated_at TIMESTAMPTZ NOT NULL, + FOREIGN KEY(file_id, connection_id) REFERENCES snd_files ON DELETE CASCADE, + PRIMARY KEY(file_id, connection_id, chunk_number) +); +CREATE TABLE rcv_file_chunks( + file_id BIGINT NOT NULL REFERENCES rcv_files ON DELETE CASCADE, + chunk_number BIGINT NOT NULL, + chunk_agent_msg_id BIGINT NOT NULL, + chunk_stored SMALLINT NOT NULL DEFAULT 0, + created_at TIMESTAMPTZ NOT NULL, + updated_at TIMESTAMPTZ NOT NULL, + PRIMARY KEY(file_id, chunk_number) +); +CREATE TABLE connections( + connection_id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY, + agent_conn_id BYTEA NOT NULL UNIQUE, + conn_level BIGINT NOT NULL DEFAULT 0, + via_contact BIGINT REFERENCES contacts(contact_id) ON DELETE SET NULL, + conn_status TEXT NOT NULL, + conn_type TEXT NOT NULL, + user_contact_link_id BIGINT, + contact_id BIGINT REFERENCES contacts ON DELETE CASCADE, + group_member_id BIGINT REFERENCES group_members ON DELETE CASCADE, + snd_file_id BIGINT, + rcv_file_id BIGINT REFERENCES rcv_files(file_id) ON DELETE CASCADE, + created_at TIMESTAMPTZ NOT NULL DEFAULT (now()), + user_id BIGINT NOT NULL REFERENCES users ON DELETE CASCADE, + updated_at TIMESTAMPTZ NOT NULL, + via_contact_uri_hash BYTEA, + xcontact_id BYTEA, + via_user_contact_link BIGINT DEFAULT NULL, + custom_user_profile_id BIGINT REFERENCES contact_profiles ON DELETE SET NULL, + conn_req_inv BYTEA, + local_alias TEXT NOT NULL DEFAULT '', + via_group_link SMALLINT NOT NULL DEFAULT 0, + group_link_id BYTEA, + security_code TEXT NULL, + security_code_verified_at TIMESTAMPTZ NULL, + auth_err_counter BIGINT NOT NULL DEFAULT 0, + peer_chat_min_version INTEGER NOT NULL DEFAULT 1, + peer_chat_max_version INTEGER NOT NULL DEFAULT 1, + to_subscribe SMALLINT DEFAULT 0 NOT NULL, + contact_conn_initiated SMALLINT NOT NULL DEFAULT 0, + conn_chat_version INTEGER, + pq_support SMALLINT NOT NULL DEFAULT 0, + pq_encryption SMALLINT NOT NULL DEFAULT 0, + pq_snd_enabled SMALLINT, + pq_rcv_enabled SMALLINT, + quota_err_counter BIGINT NOT NULL DEFAULT 0, + FOREIGN KEY(snd_file_id, connection_id) + REFERENCES snd_files(file_id, connection_id) + ON DELETE CASCADE + DEFERRABLE INITIALLY DEFERRED +); +ALTER TABLE snd_files +ADD CONSTRAINT fk_snd_files_connections + FOREIGN KEY(connection_id) + REFERENCES connections(connection_id) ON DELETE CASCADE; +CREATE TABLE user_contact_links( + user_contact_link_id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY, + conn_req_contact BYTEA NOT NULL, + local_display_name TEXT NOT NULL DEFAULT '', + created_at TIMESTAMPTZ NOT NULL DEFAULT (now()), + user_id BIGINT NOT NULL REFERENCES users ON DELETE CASCADE, + updated_at TIMESTAMPTZ NOT NULL, + auto_accept SMALLINT DEFAULT 0, + auto_reply_msg_content TEXT DEFAULT NULL, + group_id BIGINT REFERENCES groups ON DELETE CASCADE, + auto_accept_incognito SMALLINT NOT NULL DEFAULT 0, + group_link_id BYTEA, + group_link_member_role TEXT NULL, + business_address SMALLINT DEFAULT 0, + UNIQUE(user_id, local_display_name) +); +ALTER TABLE connections +ADD CONSTRAINT fk_connections_user_contact_links_user_contact_link_id + FOREIGN KEY(user_contact_link_id) + REFERENCES user_contact_links(user_contact_link_id) ON DELETE CASCADE; +ALTER TABLE connections +ADD CONSTRAINT fk_connections_user_contact_links_via_user_contact_link + FOREIGN KEY(via_user_contact_link) + REFERENCES user_contact_links(user_contact_link_id) ON DELETE SET NULL; +CREATE TABLE contact_requests( + contact_request_id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY, + user_contact_link_id BIGINT NOT NULL REFERENCES user_contact_links + ON UPDATE CASCADE ON DELETE CASCADE, + agent_invitation_id BYTEA NOT NULL, + contact_profile_id BIGINT REFERENCES contact_profiles + ON DELETE SET NULL + DEFERRABLE INITIALLY DEFERRED, + local_display_name TEXT NOT NULL, + created_at TIMESTAMPTZ NOT NULL DEFAULT (now()), + user_id BIGINT NOT NULL REFERENCES users ON DELETE CASCADE, + updated_at TIMESTAMPTZ NOT NULL, + xcontact_id BYTEA, + peer_chat_min_version INTEGER NOT NULL DEFAULT 1, + peer_chat_max_version INTEGER NOT NULL DEFAULT 1, + pq_support SMALLINT NOT NULL DEFAULT 0, + contact_id BIGINT REFERENCES contacts ON DELETE CASCADE, + FOREIGN KEY(user_id, local_display_name) + REFERENCES display_names(user_id, local_display_name) + ON UPDATE CASCADE + ON DELETE CASCADE + DEFERRABLE INITIALLY DEFERRED, + UNIQUE(user_id, local_display_name), + UNIQUE(user_id, contact_profile_id) +); +CREATE TABLE messages( + message_id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY, + msg_sent SMALLINT NOT NULL, + chat_msg_event TEXT NOT NULL, + msg_body BYTEA, + created_at TIMESTAMPTZ NOT NULL DEFAULT (now()), + updated_at TIMESTAMPTZ NOT NULL, + connection_id BIGINT DEFAULT NULL REFERENCES connections ON DELETE CASCADE, + group_id BIGINT DEFAULT NULL REFERENCES groups ON DELETE CASCADE, + shared_msg_id BYTEA, + shared_msg_id_user SMALLINT, + author_group_member_id BIGINT REFERENCES group_members ON DELETE SET NULL, + forwarded_by_group_member_id BIGINT REFERENCES group_members ON DELETE SET NULL +); +CREATE TABLE pending_group_messages( + pending_group_message_id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY, + group_member_id BIGINT NOT NULL REFERENCES group_members ON DELETE CASCADE, + message_id BIGINT NOT NULL REFERENCES messages ON DELETE CASCADE, + group_member_intro_id BIGINT REFERENCES group_member_intros ON DELETE CASCADE, + created_at TIMESTAMPTZ NOT NULL DEFAULT (now()), + updated_at TIMESTAMPTZ NOT NULL DEFAULT (now()) +); +CREATE TABLE chat_items( + chat_item_id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY, + user_id BIGINT NOT NULL REFERENCES users ON DELETE CASCADE, + contact_id BIGINT REFERENCES contacts ON DELETE CASCADE, + group_id BIGINT REFERENCES groups ON DELETE CASCADE, + group_member_id BIGINT REFERENCES group_members ON DELETE SET NULL, + chat_msg_id BIGINT, + created_by_msg_id BIGINT UNIQUE REFERENCES messages(message_id) ON DELETE SET NULL, + item_sent SMALLINT NOT NULL, + item_ts TIMESTAMPTZ NOT NULL, + item_deleted SMALLINT NOT NULL DEFAULT 0, + item_content TEXT NOT NULL, + item_text TEXT NOT NULL, + created_at TIMESTAMPTZ NOT NULL DEFAULT (now()), + updated_at TIMESTAMPTZ NOT NULL DEFAULT (now()), + item_status TEXT NOT NULL, + shared_msg_id BYTEA, + quoted_shared_msg_id BYTEA, + quoted_sent_at TIMESTAMPTZ, + quoted_content TEXT, + quoted_sent SMALLINT, + quoted_member_id BYTEA, + item_edited SMALLINT, + timed_ttl BIGINT, + timed_delete_at TIMESTAMPTZ, + item_live SMALLINT, + item_deleted_by_group_member_id BIGINT REFERENCES group_members ON DELETE SET NULL, + item_deleted_ts TIMESTAMPTZ, + forwarded_by_group_member_id BIGINT REFERENCES group_members ON DELETE SET NULL, + item_content_tag TEXT, + note_folder_id BIGINT DEFAULT NULL, + fwd_from_tag TEXT, + fwd_from_chat_name TEXT, + fwd_from_msg_dir SMALLINT, + fwd_from_contact_id BIGINT REFERENCES contacts ON DELETE SET NULL, + fwd_from_group_id BIGINT REFERENCES groups ON DELETE SET NULL, + fwd_from_chat_item_id BIGINT REFERENCES chat_items ON DELETE SET NULL, + via_proxy SMALLINT, + msg_content_tag TEXT +); +ALTER TABLE groups +ADD CONSTRAINT fk_groups_chat_items + FOREIGN KEY(chat_item_id) + REFERENCES chat_items(chat_item_id) ON DELETE SET NULL; +ALTER TABLE files +ADD CONSTRAINT fk_files_chat_items + FOREIGN KEY(chat_item_id) + REFERENCES chat_items(chat_item_id) ON DELETE CASCADE; +CREATE TABLE chat_item_messages( + chat_item_id BIGINT NOT NULL REFERENCES chat_items ON DELETE CASCADE, + message_id BIGINT NOT NULL UNIQUE REFERENCES messages ON DELETE CASCADE, + created_at TIMESTAMPTZ NOT NULL DEFAULT (now()), + updated_at TIMESTAMPTZ NOT NULL DEFAULT (now()), + UNIQUE(chat_item_id, message_id) +); +CREATE TABLE calls( + call_id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY, + contact_id BIGINT NOT NULL REFERENCES contacts ON DELETE CASCADE, + shared_call_id BYTEA NOT NULL, + chat_item_id BIGINT NOT NULL REFERENCES chat_items ON DELETE CASCADE, + call_state BYTEA NOT NULL, + call_ts TIMESTAMPTZ NOT NULL, + user_id BIGINT NOT NULL REFERENCES users ON DELETE CASCADE, + created_at TIMESTAMPTZ NOT NULL DEFAULT (now()), + updated_at TIMESTAMPTZ NOT NULL DEFAULT (now()), + call_uuid TEXT NOT NULL DEFAULT '' +); +CREATE TABLE commands( + command_id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY, + connection_id BIGINT REFERENCES connections ON DELETE CASCADE, + command_function TEXT NOT NULL, + command_status TEXT NOT NULL, + user_id BIGINT NOT NULL REFERENCES users ON DELETE CASCADE, + created_at TIMESTAMPTZ NOT NULL DEFAULT (now()), + updated_at TIMESTAMPTZ NOT NULL DEFAULT (now()) +); +CREATE TABLE settings( + settings_id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY, + chat_item_ttl BIGINT, + user_id BIGINT NOT NULL REFERENCES users ON DELETE CASCADE, + created_at TIMESTAMPTZ NOT NULL DEFAULT (now()), + updated_at TIMESTAMPTZ NOT NULL DEFAULT (now()) +); +CREATE TABLE protocol_servers( + smp_server_id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY, + host TEXT NOT NULL, + port TEXT NOT NULL, + key_hash BYTEA NOT NULL, + basic_auth TEXT, + preset SMALLINT NOT NULL DEFAULT 0, + tested SMALLINT, + enabled SMALLINT NOT NULL DEFAULT 1, + user_id BIGINT NOT NULL REFERENCES users ON DELETE CASCADE, + created_at TIMESTAMPTZ NOT NULL DEFAULT (now()), + updated_at TIMESTAMPTZ NOT NULL DEFAULT (now()), + protocol TEXT NOT NULL DEFAULT 'smp', + UNIQUE(user_id, host, port) +); +CREATE TABLE xftp_file_descriptions( + file_descr_id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY, + user_id BIGINT NOT NULL REFERENCES users ON DELETE CASCADE, + file_descr_text TEXT NOT NULL, + file_descr_part_no BIGINT NOT NULL DEFAULT(0), + file_descr_complete SMALLINT NOT NULL DEFAULT(0), + created_at TIMESTAMPTZ NOT NULL DEFAULT (now()), + updated_at TIMESTAMPTZ NOT NULL DEFAULT (now()) +); +ALTER TABLE snd_files +ADD CONSTRAINT fk_snd_files_xftp_file_descriptions + FOREIGN KEY(file_descr_id) + REFERENCES xftp_file_descriptions(file_descr_id) ON DELETE SET NULL; +ALTER TABLE rcv_files +ADD CONSTRAINT fk_rcv_files_xftp_file_descriptions + FOREIGN KEY(file_descr_id) + REFERENCES xftp_file_descriptions(file_descr_id) ON DELETE SET NULL; +CREATE TABLE extra_xftp_file_descriptions( + extra_file_descr_id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY, + file_id BIGINT NOT NULL REFERENCES files ON DELETE CASCADE, + user_id BIGINT NOT NULL REFERENCES users ON DELETE CASCADE, + file_descr_text TEXT NOT NULL, + created_at TIMESTAMPTZ NOT NULL DEFAULT (now()), + updated_at TIMESTAMPTZ NOT NULL DEFAULT (now()) +); +CREATE TABLE chat_item_versions( + chat_item_version_id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY, + chat_item_id BIGINT NOT NULL REFERENCES chat_items ON DELETE CASCADE, + msg_content TEXT NOT NULL, + item_version_ts TIMESTAMPTZ NOT NULL, + created_at TIMESTAMPTZ NOT NULL DEFAULT (now()), + updated_at TIMESTAMPTZ NOT NULL DEFAULT (now()) +); +CREATE TABLE chat_item_reactions( + chat_item_reaction_id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY, + item_member_id BYTEA, + shared_msg_id BYTEA NOT NULL, + contact_id BIGINT REFERENCES contacts ON DELETE CASCADE, + group_id BIGINT REFERENCES groups ON DELETE CASCADE, + group_member_id BIGINT REFERENCES group_members ON DELETE SET NULL, + created_by_msg_id BIGINT REFERENCES messages(message_id) ON DELETE SET NULL, + reaction TEXT NOT NULL, + reaction_sent SMALLINT NOT NULL, + reaction_ts TIMESTAMPTZ NOT NULL, + created_at TIMESTAMPTZ NOT NULL DEFAULT (now()), + updated_at TIMESTAMPTZ NOT NULL DEFAULT (now()) +); +CREATE TABLE chat_item_moderations( + chat_item_moderation_id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY, + group_id BIGINT NOT NULL REFERENCES groups ON DELETE CASCADE, + moderator_member_id BIGINT NOT NULL REFERENCES group_members ON DELETE CASCADE, + item_member_id BYTEA NOT NULL, + shared_msg_id BYTEA NOT NULL, + created_by_msg_id BIGINT REFERENCES messages(message_id) ON DELETE SET NULL, + moderated_at TIMESTAMPTZ NOT NULL, + created_at TIMESTAMPTZ NOT NULL DEFAULT (now()), + updated_at TIMESTAMPTZ NOT NULL DEFAULT (now()) +); +CREATE TABLE group_snd_item_statuses( + group_snd_item_status_id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY, + chat_item_id BIGINT NOT NULL REFERENCES chat_items ON DELETE CASCADE, + group_member_id BIGINT NOT NULL REFERENCES group_members ON DELETE CASCADE, + group_snd_item_status TEXT NOT NULL, + created_at TIMESTAMPTZ NOT NULL DEFAULT (now()), + updated_at TIMESTAMPTZ NOT NULL DEFAULT (now()), + via_proxy SMALLINT +); +CREATE TABLE sent_probes( + sent_probe_id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY, + contact_id BIGINT REFERENCES contacts ON DELETE CASCADE, + group_member_id BIGINT REFERENCES group_members ON DELETE CASCADE, + probe BYTEA NOT NULL, + user_id BIGINT NOT NULL REFERENCES users ON DELETE CASCADE, + created_at TIMESTAMPTZ NOT NULL, + updated_at TIMESTAMPTZ NOT NULL, + UNIQUE(user_id, probe) +); +CREATE TABLE sent_probe_hashes( + sent_probe_hash_id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY, + sent_probe_id BIGINT NOT NULL REFERENCES sent_probes ON DELETE CASCADE, + contact_id BIGINT REFERENCES contacts ON DELETE CASCADE, + group_member_id BIGINT REFERENCES group_members ON DELETE CASCADE, + user_id BIGINT NOT NULL REFERENCES users ON DELETE CASCADE, + created_at TIMESTAMPTZ NOT NULL, + updated_at TIMESTAMPTZ NOT NULL +); +CREATE TABLE received_probes( + received_probe_id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY, + contact_id BIGINT REFERENCES contacts ON DELETE CASCADE, + group_member_id BIGINT REFERENCES group_members ON DELETE CASCADE, + probe BYTEA, + probe_hash BYTEA NOT NULL, + user_id BIGINT NOT NULL REFERENCES users ON DELETE CASCADE, + created_at TIMESTAMPTZ NOT NULL, + updated_at TIMESTAMPTZ NOT NULL +); +CREATE TABLE remote_hosts( + remote_host_id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY, + host_device_name TEXT NOT NULL, + store_path TEXT NOT NULL, + ca_key BYTEA NOT NULL, + ca_cert BYTEA NOT NULL, + id_key BYTEA NOT NULL, + host_fingerprint BYTEA NOT NULL, + host_dh_pub BYTEA NOT NULL, + bind_addr TEXT, + bind_iface TEXT, + bind_port INTEGER +); +CREATE TABLE remote_controllers( + remote_ctrl_id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY, + ctrl_device_name TEXT NOT NULL, + ca_key BYTEA NOT NULL, + ca_cert BYTEA NOT NULL, + ctrl_fingerprint BYTEA NOT NULL, + id_pub BYTEA NOT NULL, + dh_priv_key BYTEA NOT NULL, + prev_dh_priv_key BYTEA +); +CREATE TABLE msg_deliveries( + msg_delivery_id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY, + message_id BIGINT NOT NULL REFERENCES messages ON DELETE CASCADE, + connection_id BIGINT NOT NULL REFERENCES connections ON DELETE CASCADE, + agent_msg_id BIGINT, + agent_msg_meta TEXT, + chat_ts TIMESTAMPTZ NOT NULL DEFAULT (now()), + created_at TIMESTAMPTZ NOT NULL, + updated_at TIMESTAMPTZ NOT NULL, + delivery_status TEXT +); +CREATE TABLE note_folders( + note_folder_id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY, + user_id BIGINT NOT NULL REFERENCES users ON DELETE CASCADE, + created_at TIMESTAMPTZ NOT NULL DEFAULT (now()), + updated_at TIMESTAMPTZ NOT NULL DEFAULT (now()), + chat_ts TIMESTAMPTZ NOT NULL DEFAULT (now()), + favorite SMALLINT NOT NULL DEFAULT 0, + unread_chat SMALLINT NOT NULL DEFAULT 0 +); +ALTER TABLE files +ADD CONSTRAINT fk_files_note_folders + FOREIGN KEY(note_folder_id) + REFERENCES note_folders(note_folder_id) ON DELETE CASCADE; +ALTER TABLE chat_items +ADD CONSTRAINT fk_chat_items_note_folders + FOREIGN KEY(note_folder_id) + REFERENCES note_folders(note_folder_id) ON DELETE CASCADE; +CREATE TABLE app_settings(app_settings TEXT NOT NULL); +CREATE TABLE server_operators( + server_operator_id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY, + server_operator_tag TEXT, + trade_name TEXT NOT NULL, + legal_name TEXT, + server_domains TEXT, + enabled SMALLINT NOT NULL DEFAULT 1, + smp_role_storage SMALLINT NOT NULL DEFAULT 1, + smp_role_proxy SMALLINT NOT NULL DEFAULT 1, + xftp_role_storage SMALLINT NOT NULL DEFAULT 1, + xftp_role_proxy SMALLINT NOT NULL DEFAULT 1, + created_at TIMESTAMPTZ NOT NULL DEFAULT (now()), + updated_at TIMESTAMPTZ NOT NULL DEFAULT (now()) +); +CREATE TABLE usage_conditions( + usage_conditions_id BIGINT PRIMARY KEY, + conditions_commit TEXT NOT NULL UNIQUE, + notified_at TIMESTAMPTZ, + created_at TIMESTAMPTZ NOT NULL DEFAULT (now()), + updated_at TIMESTAMPTZ NOT NULL DEFAULT (now()) +); +CREATE TABLE operator_usage_conditions( + operator_usage_conditions_id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY, + server_operator_id BIGINT REFERENCES server_operators(server_operator_id) ON DELETE SET NULL ON UPDATE CASCADE, + server_operator_tag TEXT, + conditions_commit TEXT NOT NULL, + accepted_at TIMESTAMPTZ, + created_at TIMESTAMPTZ NOT NULL DEFAULT (now()), + auto_accepted SMALLINT DEFAULT 0 +); +CREATE TABLE chat_tags( + chat_tag_id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY, + user_id BIGINT REFERENCES users, + chat_tag_text TEXT NOT NULL, + chat_tag_emoji TEXT, + tag_order BIGINT NOT NULL +); +CREATE TABLE chat_tags_chats( + contact_id BIGINT REFERENCES contacts ON DELETE CASCADE, + group_id BIGINT REFERENCES groups ON DELETE CASCADE, + chat_tag_id BIGINT NOT NULL REFERENCES chat_tags ON DELETE CASCADE +); +CREATE INDEX contact_profiles_index ON contact_profiles( + display_name, + full_name +); +CREATE INDEX idx_groups_inv_queue_info ON groups(inv_queue_info); +CREATE INDEX idx_contact_requests_xcontact_id ON contact_requests(xcontact_id); +CREATE INDEX idx_contacts_xcontact_id ON contacts(xcontact_id); +CREATE INDEX idx_messages_shared_msg_id ON messages(shared_msg_id); +CREATE INDEX idx_chat_items_shared_msg_id ON chat_items(shared_msg_id); +CREATE UNIQUE INDEX idx_chat_items_direct_shared_msg_id ON chat_items( + user_id, + contact_id, + shared_msg_id +); +CREATE UNIQUE INDEX idx_chat_items_group_shared_msg_id ON chat_items( + user_id, + group_id, + group_member_id, + shared_msg_id +); +CREATE UNIQUE INDEX idx_user_contact_links_group_id ON user_contact_links( + group_id +); +CREATE UNIQUE INDEX idx_snd_files_last_inline_msg_delivery_id ON snd_files( + last_inline_msg_delivery_id +); +CREATE INDEX idx_messages_connection_id ON messages(connection_id); +CREATE INDEX idx_chat_items_group_member_id ON chat_items(group_member_id); +CREATE INDEX idx_chat_items_contact_id ON chat_items(contact_id); +CREATE INDEX idx_chat_items_item_status ON chat_items(item_status); +CREATE INDEX idx_connections_group_member ON connections( + user_id, + group_member_id +); +CREATE INDEX idx_commands_connection_id ON commands(connection_id); +CREATE INDEX idx_calls_user_id ON calls(user_id); +CREATE INDEX idx_calls_chat_item_id ON calls(chat_item_id); +CREATE INDEX idx_calls_contact_id ON calls(contact_id); +CREATE INDEX idx_commands_user_id ON commands(user_id); +CREATE INDEX idx_connections_custom_user_profile_id ON connections( + custom_user_profile_id +); +CREATE INDEX idx_connections_via_user_contact_link ON connections( + via_user_contact_link +); +CREATE INDEX idx_connections_rcv_file_id ON connections(rcv_file_id); +CREATE INDEX idx_connections_contact_id ON connections(contact_id); +CREATE INDEX idx_connections_user_contact_link_id ON connections( + user_contact_link_id +); +CREATE INDEX idx_connections_via_contact ON connections(via_contact); +CREATE INDEX idx_contact_profiles_user_id ON contact_profiles(user_id); +CREATE INDEX idx_contact_requests_contact_profile_id ON contact_requests( + contact_profile_id +); +CREATE INDEX idx_contact_requests_user_contact_link_id ON contact_requests( + user_contact_link_id +); +CREATE INDEX idx_contacts_via_group ON contacts(via_group); +CREATE INDEX idx_contacts_contact_profile_id ON contacts(contact_profile_id); +CREATE INDEX idx_files_chat_item_id ON files(chat_item_id); +CREATE INDEX idx_files_user_id ON files(user_id); +CREATE INDEX idx_files_group_id ON files(group_id); +CREATE INDEX idx_files_contact_id ON files(contact_id); +CREATE INDEX idx_group_member_intros_to_group_member_id ON group_member_intros( + to_group_member_id +); +CREATE INDEX idx_group_members_user_id_local_display_name ON group_members( + user_id, + local_display_name +); +CREATE INDEX idx_group_members_member_profile_id ON group_members( + member_profile_id +); +CREATE INDEX idx_group_members_contact_id ON group_members(contact_id); +CREATE INDEX idx_group_members_contact_profile_id ON group_members( + contact_profile_id +); +CREATE INDEX idx_group_members_user_id ON group_members(user_id); +CREATE INDEX idx_group_members_invited_by ON group_members(invited_by); +CREATE INDEX idx_group_profiles_user_id ON group_profiles(user_id); +CREATE INDEX idx_groups_host_conn_custom_user_profile_id ON groups( + host_conn_custom_user_profile_id +); +CREATE INDEX idx_groups_chat_item_id ON groups(chat_item_id); +CREATE INDEX idx_groups_group_profile_id ON groups(group_profile_id); +CREATE INDEX idx_messages_group_id ON messages(group_id); +CREATE INDEX idx_pending_group_messages_group_member_intro_id ON pending_group_messages( + group_member_intro_id +); +CREATE INDEX idx_pending_group_messages_message_id ON pending_group_messages( + message_id +); +CREATE INDEX idx_pending_group_messages_group_member_id ON pending_group_messages( + group_member_id +); +CREATE INDEX idx_rcv_file_chunks_file_id ON rcv_file_chunks(file_id); +CREATE INDEX idx_rcv_files_group_member_id ON rcv_files(group_member_id); +CREATE INDEX idx_settings_user_id ON settings(user_id); +CREATE INDEX idx_snd_file_chunks_file_id_connection_id ON snd_file_chunks( + file_id, + connection_id +); +CREATE INDEX idx_snd_files_group_member_id ON snd_files(group_member_id); +CREATE INDEX idx_snd_files_connection_id ON snd_files(connection_id); +CREATE INDEX idx_snd_files_file_id ON snd_files(file_id); +CREATE INDEX idx_smp_servers_user_id ON protocol_servers(user_id); +CREATE INDEX idx_chat_items_item_deleted_by_group_member_id ON chat_items( + item_deleted_by_group_member_id +); +CREATE INDEX idx_snd_files_file_descr_id ON snd_files(file_descr_id); +CREATE INDEX idx_rcv_files_file_descr_id ON rcv_files(file_descr_id); +CREATE INDEX idx_extra_xftp_file_descriptions_file_id ON extra_xftp_file_descriptions( + file_id +); +CREATE INDEX idx_extra_xftp_file_descriptions_user_id ON extra_xftp_file_descriptions( + user_id +); +CREATE INDEX idx_xftp_file_descriptions_user_id ON xftp_file_descriptions( + user_id +); +CREATE INDEX idx_chat_item_versions_chat_item_id ON chat_item_versions( + chat_item_id +); +CREATE INDEX idx_chat_item_reactions_shared_msg_id ON chat_item_reactions( + shared_msg_id +); +CREATE INDEX idx_chat_item_reactions_contact_id ON chat_item_reactions( + contact_id +); +CREATE INDEX idx_chat_item_reactions_group_id ON chat_item_reactions(group_id); +CREATE INDEX idx_chat_item_reactions_group_member_id ON chat_item_reactions( + group_member_id +); +CREATE INDEX idx_chat_item_reactions_contact ON chat_item_reactions( + contact_id, + shared_msg_id +); +CREATE INDEX idx_chat_item_reactions_group ON chat_item_reactions( + group_id, + shared_msg_id +); +CREATE INDEX idx_messages_created_at ON messages(created_at); +CREATE INDEX idx_chat_item_reactions_created_by_msg_id ON chat_item_reactions( + created_by_msg_id +); +CREATE INDEX idx_chat_items_timed_delete_at ON chat_items( + user_id, + timed_delete_at +); +CREATE INDEX idx_group_members_group_id ON group_members(user_id, group_id); +CREATE INDEX idx_chat_item_moderations_group_id ON chat_item_moderations( + group_id +); +CREATE INDEX idx_chat_item_moderations_moderator_member_id ON chat_item_moderations( + moderator_member_id +); +CREATE INDEX idx_chat_item_moderations_created_by_msg_id ON chat_item_moderations( + created_by_msg_id +); +CREATE INDEX idx_chat_item_moderations_group ON chat_item_moderations( + group_id, + item_member_id, + shared_msg_id +); +CREATE INDEX idx_group_snd_item_statuses_chat_item_id ON group_snd_item_statuses( + chat_item_id +); +CREATE INDEX idx_group_snd_item_statuses_group_member_id ON group_snd_item_statuses( + group_member_id +); +CREATE INDEX idx_chat_items_user_id_item_status ON chat_items( + user_id, + item_status +); +CREATE INDEX idx_connections_to_subscribe ON connections(to_subscribe); +CREATE INDEX idx_contacts_contact_group_member_id ON contacts( + contact_group_member_id +); +CREATE INDEX idx_sent_probes_user_id ON sent_probes(user_id); +CREATE INDEX idx_sent_probes_contact_id ON sent_probes(contact_id); +CREATE INDEX idx_sent_probes_group_member_id ON sent_probes(group_member_id); +CREATE INDEX idx_sent_probe_hashes_user_id ON sent_probe_hashes(user_id); +CREATE INDEX idx_sent_probe_hashes_sent_probe_id ON sent_probe_hashes( + sent_probe_id +); +CREATE INDEX idx_sent_probe_hashes_contact_id ON sent_probe_hashes(contact_id); +CREATE INDEX idx_sent_probe_hashes_group_member_id ON sent_probe_hashes( + group_member_id +); +CREATE INDEX idx_received_probes_user_id ON received_probes(user_id); +CREATE INDEX idx_received_probes_contact_id ON received_probes(contact_id); +CREATE INDEX idx_received_probes_probe ON received_probes(probe); +CREATE INDEX idx_received_probes_probe_hash ON received_probes(probe_hash); +CREATE INDEX idx_sent_probes_created_at ON sent_probes(created_at); +CREATE INDEX idx_sent_probe_hashes_created_at ON sent_probe_hashes(created_at); +CREATE INDEX idx_received_probes_created_at ON received_probes(created_at); +CREATE INDEX idx_connections_conn_req_inv ON connections( + user_id, + conn_req_inv +); +CREATE INDEX idx_groups_via_group_link_uri_hash ON groups( + user_id, + via_group_link_uri_hash +); +CREATE INDEX idx_connections_via_contact_uri_hash ON connections( + user_id, + via_contact_uri_hash +); +CREATE INDEX idx_contact_profiles_contact_link ON contact_profiles( + user_id, + contact_link +); +CREATE INDEX idx_group_member_intros_re_group_member_id ON group_member_intros( + re_group_member_id +); +CREATE INDEX idx_group_members_invited_by_group_member_id ON group_members( + invited_by_group_member_id +); +CREATE INDEX idx_messages_author_group_member_id ON messages( + author_group_member_id +); +CREATE INDEX idx_messages_forwarded_by_group_member_id ON messages( + forwarded_by_group_member_id +); +CREATE INDEX idx_messages_group_id_shared_msg_id ON messages( + group_id, + shared_msg_id +); +CREATE INDEX idx_chat_items_forwarded_by_group_member_id ON chat_items( + forwarded_by_group_member_id +); +CREATE UNIQUE INDEX idx_remote_hosts_host_fingerprint ON remote_hosts( + host_fingerprint +); +CREATE UNIQUE INDEX idx_remote_controllers_ctrl_fingerprint ON remote_controllers( + ctrl_fingerprint +); +CREATE INDEX idx_contacts_chat_ts ON contacts(user_id, chat_ts); +CREATE INDEX idx_groups_chat_ts ON groups(user_id, chat_ts); +CREATE INDEX idx_contact_requests_updated_at ON contact_requests( + user_id, + updated_at +); +CREATE INDEX idx_connections_updated_at ON connections(user_id, updated_at); +CREATE INDEX idx_msg_deliveries_message_id ON msg_deliveries(message_id); +CREATE INDEX idx_msg_deliveries_agent_msg_id ON msg_deliveries( + connection_id, + agent_msg_id +); +CREATE INDEX chat_items_note_folder_id ON chat_items(note_folder_id); +CREATE INDEX files_note_folder_id ON files(note_folder_id); +CREATE INDEX note_folders_user_id ON note_folders(user_id); +CREATE INDEX idx_chat_items_contacts_created_at on chat_items( + user_id, + contact_id, + created_at +); +CREATE INDEX idx_chat_items_notes_created_at on chat_items( + user_id, + note_folder_id, + created_at +); +CREATE INDEX idx_files_redirect_file_id on files(redirect_file_id); +CREATE INDEX idx_chat_items_fwd_from_contact_id ON chat_items( + fwd_from_contact_id +); +CREATE INDEX idx_chat_items_fwd_from_group_id ON chat_items(fwd_from_group_id); +CREATE INDEX idx_chat_items_fwd_from_chat_item_id ON chat_items( + fwd_from_chat_item_id +); +CREATE INDEX idx_received_probes_group_member_id on received_probes( + group_member_id +); +CREATE INDEX idx_contact_requests_contact_id ON contact_requests(contact_id); +CREATE INDEX idx_operator_usage_conditions_server_operator_id ON operator_usage_conditions( + server_operator_id +); +CREATE UNIQUE INDEX idx_operator_usage_conditions_conditions_commit ON operator_usage_conditions( + conditions_commit, + server_operator_id +); +CREATE INDEX idx_chat_items_contacts ON chat_items( + user_id, + contact_id, + item_status, + created_at +); +CREATE INDEX idx_chat_items_groups ON chat_items( + user_id, + group_id, + item_status, + item_ts +); +CREATE INDEX idx_chat_items_groups_item_ts ON chat_items( + user_id, + group_id, + item_ts +); +CREATE INDEX idx_chat_items_notes ON chat_items( + user_id, + note_folder_id, + item_status, + created_at +); +CREATE INDEX idx_groups_business_xcontact_id ON groups(business_xcontact_id); +CREATE INDEX idx_chat_tags_user_id ON chat_tags(user_id); +CREATE UNIQUE INDEX idx_chat_tags_user_id_chat_tag_text ON chat_tags( + user_id, + chat_tag_text +); +CREATE UNIQUE INDEX idx_chat_tags_user_id_chat_tag_emoji ON chat_tags( + user_id, + chat_tag_emoji +); +CREATE INDEX idx_chat_tags_chats_chat_tag_id ON chat_tags_chats(chat_tag_id); +CREATE UNIQUE INDEX idx_chat_tags_chats_chat_tag_id_contact_id ON chat_tags_chats( + contact_id, + chat_tag_id +); +CREATE UNIQUE INDEX idx_chat_tags_chats_chat_tag_id_group_id ON chat_tags_chats( + group_id, + chat_tag_id +); +CREATE INDEX idx_chat_items_groups_msg_content_tag_item_ts ON chat_items( + user_id, + group_id, + msg_content_tag, + item_ts +); +CREATE INDEX idx_chat_items_groups_msg_content_tag_item_deleted_item_ts ON chat_items( + user_id, + group_id, + msg_content_tag, + item_deleted, + item_ts +); +|] diff --git a/src/Simplex/Chat/Store/Profiles.hs b/src/Simplex/Chat/Store/Profiles.hs index 809155d557..4fca5fb1a4 100644 --- a/src/Simplex/Chat/Store/Profiles.hs +++ b/src/Simplex/Chat/Store/Profiles.hs @@ -1,3 +1,4 @@ +{-# LANGUAGE CPP #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DuplicateRecordFields #-} @@ -86,8 +87,6 @@ import Data.Text (Text) import qualified Data.Text as T import Data.Text.Encoding (decodeLatin1, encodeUtf8) 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.Messages import Simplex.Chat.Operators @@ -101,7 +100,8 @@ import Simplex.Chat.Types.UITheme import Simplex.Messaging.Agent.Env.SQLite (ServerRoles (..)) import Simplex.Messaging.Agent.Protocol (ACorrId, ConnId, UserId) 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.Ratchet as CR 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.Transport.Client (TransportHost) 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 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 "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 DB.execute db @@ -138,10 +145,10 @@ createUserRecordAt db (AgentUserId auId) Profile {displayName, fullName, image, DB.execute db "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 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 = getUsers db >>= mapM getUserInfo @@ -253,7 +260,7 @@ updateUserPrivacy db User {userId, showNtfs, viewPwdHash} = SET view_pwd_hash = ?, view_pwd_salt = ?, show_ntfs = ? WHERE user_id = ? |] - (hashSalt viewPwdHash :. (showNtfs, userId)) + (hashSalt viewPwdHash :. (BI showNtfs, userId)) where hashSalt = L.unzip . fmap (\UserPwdHash {hash, salt} -> (hash, salt)) @@ -262,16 +269,16 @@ updateAllContactReceipts db onOff = DB.execute db "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 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" updateUserGroupReceipts :: DB.Connection -> User -> UserMsgReceiptSettings -> IO () 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" updateUserProfile :: DB.Connection -> User -> Profile -> ExceptT StoreError IO User @@ -403,21 +410,21 @@ deleteUserAddress db user@User {userId} = do ) |] (Only userId) - DB.executeNamed + DB.execute db [sql| DELETE FROM display_names - WHERE user_id = :user_id + WHERE user_id = ? AND local_display_name in ( SELECT cr.local_display_name FROM contact_requests cr 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] - DB.executeNamed + (userId, userId, userId) + DB.execute db [sql| DELETE FROM contact_profiles @@ -425,10 +432,10 @@ deleteUserAddress db user@User {userId} = do SELECT cr.contact_profile_id FROM contact_requests cr 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 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) -toUserContactLink :: (ConnReqContact, Bool, Bool, IncognitoEnabled, Maybe MsgContent) -> UserContactLink -toUserContactLink (connReq, autoAccept, businessAddress, acceptIncognito, autoReply) = +toUserContactLink :: (ConnReqContact, BoolInt, BoolInt, BoolInt, Maybe MsgContent) -> UserContactLink +toUserContactLink (connReq, BI autoAccept, BI businessAddress, BI acceptIncognito, autoReply) = UserContactLink connReq $ 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 = case autoAccept of - Just AutoAccept {businessAddress, acceptIncognito, autoReply} -> (True, businessAddress, acceptIncognito, autoReply) - _ -> (False, False, False, Nothing) + Just AutoAccept {businessAddress, acceptIncognito, autoReply} -> (BI True, BI businessAddress, BI acceptIncognito, autoReply) + _ -> (BI False, BI False, BI False, Nothing) getProtocolServers :: forall p. ProtocolTypeI p => DB.Connection -> SProtocolType p -> User -> IO [UserServer p] getProtocolServers db p User {userId} = @@ -543,10 +550,10 @@ getProtocolServers db p User {userId} = |] (userId, decodeLatin1 $ strEncode p) where - toUserServer :: (DBEntityId, NonEmpty TransportHost, String, C.KeyHash, Maybe Text, Bool, Maybe Bool, Bool) -> UserServer p - toUserServer (serverId, host, port, keyHash, auth_, preset, tested, enabled) = + toUserServer :: (DBEntityId, NonEmpty TransportHost, String, C.KeyHash, Maybe Text, BoolInt, Maybe BoolInt, BoolInt) -> UserServer p + toUserServer (serverId, host, port, keyHash, auth_, BI preset, tested, BI enabled) = 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 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) 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 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 = ? 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 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 = ? 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 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 = ? 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 op@ServerOperator {operatorTag, tradeName, legalName, serverDomains, enabled, smpRoles, xftpRoles} = do 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) 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 pure op {operatorId = DBEntityId opId} autoAcceptConditions op UsageConditions {conditionsCommit} now = @@ -677,8 +684,8 @@ serverOperatorQuery = getServerOperators_ :: DB.Connection -> IO [ServerOperator] getServerOperators_ db = map toServerOperator <$> DB.query_ db serverOperatorQuery -toServerOperator :: (DBEntityId, Maybe OperatorTag, Text, Maybe Text, Text, Bool) :. (Bool, Bool) :. (Bool, Bool) -> ServerOperator -toServerOperator ((operatorId, operatorTag, tradeName, legalName, domains, enabled) :. smpRoles' :. xftpRoles') = +toServerOperator :: (DBEntityId, Maybe OperatorTag, Text, Maybe Text, Text, BoolInt) :. (BoolInt, BoolInt) :. (BoolInt, BoolInt) -> ServerOperator +toServerOperator ((operatorId, operatorTag, tradeName, legalName, domains, BI enabled) :. smpRoles' :. xftpRoles') = ServerOperator { operatorId, operatorTag, @@ -691,7 +698,7 @@ toServerOperator ((operatorId, operatorTag, tradeName, legalName, domains, enabl xftpRoles = serverRoles xftpRoles' } 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 ServerOperator {operatorId} UsageConditions {conditionsCommit = currentCommit, createdAt, notifiedAt} latestAcceptedConds_ now = do @@ -711,7 +718,7 @@ getOperatorConditions_ db ServerOperator {operatorId} UsageConditions {condition |] (Only operatorId) pure $ case operatorAcceptedConds_ of - Just (operatorCommit, acceptedAt_, autoAccept) + Just (operatorCommit, acceptedAt_, BI autoAccept) | operatorCommit /= latestAcceptedCommit -> CARequired Nothing -- TODO should we consider this operator disabled? | currentCommit /= latestAcceptedCommit -> CARequired $ conditionsRequiredOrDeadline createdAt (fromMaybe now notifiedAt) | otherwise -> CAAccepted acceptedAt_ autoAccept @@ -767,23 +774,23 @@ acceptConditions db condId opIds acceptedAt = do acceptConditions_ :: DB.Connection -> ServerOperator -> Text -> UTCTime -> Bool -> IO () 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 - Just Nothing -> - DB.execute - db - (q <> "ON CONFLICT (server_operator_id, conditions_commit) DO UPDATE SET accepted_at = ?, auto_accepted = ?") - (operatorId, operatorTag, conditionsCommit, acceptedAt, autoAccepted, acceptedAt, autoAccepted) - Just (Just _) -> - DB.execute - db - (q <> "ON CONFLICT (server_operator_id, conditions_commit) DO NOTHING") - (operatorId, operatorTag, conditionsCommit, acceptedAt, autoAccepted) - Nothing -> - DB.execute - db - q - (operatorId, operatorTag, conditionsCommit, acceptedAt, autoAccepted) + Just Nothing -> + DB.execute + db + (q <> "ON CONFLICT (server_operator_id, conditions_commit) DO UPDATE SET accepted_at = ?, auto_accepted = ?") + (operatorId, operatorTag, conditionsCommit, acceptedAt, BI autoAccepted, acceptedAt, BI autoAccepted) + Just (Just _) -> + DB.execute + db + (q <> "ON CONFLICT (server_operator_id, conditions_commit) DO NOTHING") + (operatorId, operatorTag, conditionsCommit, acceptedAt, BI autoAccepted) + Nothing -> + DB.execute + db + q + (operatorId, operatorTag, conditionsCommit, acceptedAt, BI autoAccepted) where q = [sql| @@ -820,7 +827,7 @@ setUserServers' db user@User {userId} ts UpdatedUserOperatorServers {operator, s | deleted -> pure Nothing | otherwise -> Just <$> insertProtocolServer db p user ts s 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 createCall :: DB.Connection -> User -> Call -> UTCTime -> IO () diff --git a/src/Simplex/Chat/Store/Remote.hs b/src/Simplex/Chat/Store/Remote.hs index 68cd1281e7..4921369b10 100644 --- a/src/Simplex/Chat/Store/Remote.hs +++ b/src/Simplex/Chat/Store/Remote.hs @@ -1,3 +1,4 @@ +{-# LANGUAGE CPP #-} {-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE OverloadedStrings #-} @@ -8,19 +9,23 @@ module Simplex.Chat.Store.Remote where import Control.Monad.Except import Data.Int (Int64) import Data.Text (Text) -import Data.Text.Encoding (encodeUtf8, decodeASCII) +import Data.Text.Encoding (decodeASCII, encodeUtf8) 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.Store.Shared 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 Simplex.Messaging.Encoding.String (StrEncoding (..)) import Simplex.RemoteControl.Types 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 hostDeviceName storePath rcAddr_ bindPort_ RCHostPairing {caKey, caCert, idPrivKey, knownHost = kh_} = do @@ -54,7 +59,7 @@ getRemoteHostByFingerprint db fingerprint = maybeFirstRow toRemoteHost $ DB.query db (remoteHostQuery <> " WHERE host_fingerprint = ?") (Only fingerprint) -remoteHostQuery :: SQL.Query +remoteHostQuery :: Query remoteHostQuery = [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 @@ -117,7 +122,7 @@ getRemoteCtrlByFingerprint db fingerprint = maybeFirstRow toRemoteCtrl $ DB.query db (remoteCtrlQuery <> " WHERE ctrl_fingerprint = ?") (Only fingerprint) -remoteCtrlQuery :: SQL.Query +remoteCtrlQuery :: Query remoteCtrlQuery = [sql| SELECT remote_ctrl_id, ctrl_device_name, ca_key, ca_cert, ctrl_fingerprint, id_pub, dh_priv_key, prev_dh_priv_key diff --git a/src/Simplex/Chat/Store/Migrations.hs b/src/Simplex/Chat/Store/SQLite/Migrations.hs similarity index 56% rename from src/Simplex/Chat/Store/Migrations.hs rename to src/Simplex/Chat/Store/SQLite/Migrations.hs index a6cb562aa1..0126fc600f 100644 --- a/src/Simplex/Chat/Store/Migrations.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations.hs @@ -1,128 +1,128 @@ {-# LANGUAGE NamedFieldPuns #-} -module Simplex.Chat.Store.Migrations (migrations) where +module Simplex.Chat.Store.SQLite.Migrations (migrations) where import Data.List (sortOn) import Database.SQLite.Simple (Query (..)) -import Simplex.Chat.Migrations.M20220101_initial -import Simplex.Chat.Migrations.M20220122_v1_1 -import Simplex.Chat.Migrations.M20220205_chat_item_status -import Simplex.Chat.Migrations.M20220210_deduplicate_contact_requests -import Simplex.Chat.Migrations.M20220224_messages_fks -import Simplex.Chat.Migrations.M20220301_smp_servers -import Simplex.Chat.Migrations.M20220302_profile_images -import Simplex.Chat.Migrations.M20220304_msg_quotes -import Simplex.Chat.Migrations.M20220321_chat_item_edited -import Simplex.Chat.Migrations.M20220404_files_status_fields -import Simplex.Chat.Migrations.M20220514_profiles_user_id -import Simplex.Chat.Migrations.M20220626_auto_reply -import Simplex.Chat.Migrations.M20220702_calls -import Simplex.Chat.Migrations.M20220715_groups_chat_item_id -import Simplex.Chat.Migrations.M20220811_chat_items_indices -import Simplex.Chat.Migrations.M20220812_incognito_profiles -import Simplex.Chat.Migrations.M20220818_chat_notifications -import Simplex.Chat.Migrations.M20220822_groups_host_conn_custom_user_profile_id -import Simplex.Chat.Migrations.M20220823_delete_broken_group_event_chat_items -import Simplex.Chat.Migrations.M20220824_profiles_local_alias -import Simplex.Chat.Migrations.M20220909_commands -import Simplex.Chat.Migrations.M20220926_connection_alias -import Simplex.Chat.Migrations.M20220928_settings -import Simplex.Chat.Migrations.M20221001_shared_msg_id_indices -import Simplex.Chat.Migrations.M20221003_delete_broken_integrity_error_chat_items -import Simplex.Chat.Migrations.M20221004_idx_msg_deliveries_message_id -import Simplex.Chat.Migrations.M20221011_user_contact_links_group_id -import Simplex.Chat.Migrations.M20221012_inline_files -import Simplex.Chat.Migrations.M20221019_unread_chat -import Simplex.Chat.Migrations.M20221021_auto_accept__group_links -import Simplex.Chat.Migrations.M20221024_contact_used -import Simplex.Chat.Migrations.M20221025_chat_settings -import Simplex.Chat.Migrations.M20221029_group_link_id -import Simplex.Chat.Migrations.M20221112_server_password -import Simplex.Chat.Migrations.M20221115_server_cfg -import Simplex.Chat.Migrations.M20221129_delete_group_feature_items -import Simplex.Chat.Migrations.M20221130_delete_item_deleted -import Simplex.Chat.Migrations.M20221209_verified_connection -import Simplex.Chat.Migrations.M20221210_idxs -import Simplex.Chat.Migrations.M20221211_group_description -import Simplex.Chat.Migrations.M20221212_chat_items_timed -import Simplex.Chat.Migrations.M20221214_live_message -import Simplex.Chat.Migrations.M20221222_chat_ts -import Simplex.Chat.Migrations.M20221223_idx_chat_items_item_status -import Simplex.Chat.Migrations.M20221230_idxs -import Simplex.Chat.Migrations.M20230107_connections_auth_err_counter -import Simplex.Chat.Migrations.M20230111_users_agent_user_id -import Simplex.Chat.Migrations.M20230117_fkey_indexes -import Simplex.Chat.Migrations.M20230118_recreate_smp_servers -import Simplex.Chat.Migrations.M20230129_drop_chat_items_group_idx -import Simplex.Chat.Migrations.M20230206_item_deleted_by_group_member_id -import Simplex.Chat.Migrations.M20230303_group_link_role -import Simplex.Chat.Migrations.M20230317_hidden_profiles -import Simplex.Chat.Migrations.M20230318_file_description -import Simplex.Chat.Migrations.M20230321_agent_file_deleted -import Simplex.Chat.Migrations.M20230328_files_protocol -import Simplex.Chat.Migrations.M20230402_protocol_servers -import Simplex.Chat.Migrations.M20230411_extra_xftp_file_descriptions -import Simplex.Chat.Migrations.M20230420_rcv_files_to_receive -import Simplex.Chat.Migrations.M20230422_profile_contact_links -import Simplex.Chat.Migrations.M20230504_recreate_msg_delivery_events_cleanup_messages -import Simplex.Chat.Migrations.M20230505_chat_item_versions -import Simplex.Chat.Migrations.M20230511_reactions -import Simplex.Chat.Migrations.M20230519_item_deleted_ts -import Simplex.Chat.Migrations.M20230526_indexes -import Simplex.Chat.Migrations.M20230529_indexes -import Simplex.Chat.Migrations.M20230608_deleted_contacts -import Simplex.Chat.Migrations.M20230618_favorite_chats -import Simplex.Chat.Migrations.M20230621_chat_item_moderations -import Simplex.Chat.Migrations.M20230705_delivery_receipts -import Simplex.Chat.Migrations.M20230721_group_snd_item_statuses -import Simplex.Chat.Migrations.M20230814_indexes -import Simplex.Chat.Migrations.M20230827_file_encryption -import Simplex.Chat.Migrations.M20230829_connections_chat_vrange -import Simplex.Chat.Migrations.M20230903_connections_to_subscribe -import Simplex.Chat.Migrations.M20230913_member_contacts -import Simplex.Chat.Migrations.M20230914_member_probes -import Simplex.Chat.Migrations.M20230926_contact_status -import Simplex.Chat.Migrations.M20231002_conn_initiated -import Simplex.Chat.Migrations.M20231009_via_group_link_uri_hash -import Simplex.Chat.Migrations.M20231010_member_settings -import Simplex.Chat.Migrations.M20231019_indexes -import Simplex.Chat.Migrations.M20231030_xgrplinkmem_received -import Simplex.Chat.Migrations.M20231107_indexes -import Simplex.Chat.Migrations.M20231113_group_forward -import Simplex.Chat.Migrations.M20231114_remote_control -import Simplex.Chat.Migrations.M20231126_remote_ctrl_address -import Simplex.Chat.Migrations.M20231207_chat_list_pagination -import Simplex.Chat.Migrations.M20231214_item_content_tag -import Simplex.Chat.Migrations.M20231215_recreate_msg_deliveries -import Simplex.Chat.Migrations.M20240102_note_folders -import Simplex.Chat.Migrations.M20240104_members_profile_update -import Simplex.Chat.Migrations.M20240115_block_member_for_all -import Simplex.Chat.Migrations.M20240122_indexes -import Simplex.Chat.Migrations.M20240214_redirect_file_id -import Simplex.Chat.Migrations.M20240222_app_settings -import Simplex.Chat.Migrations.M20240226_users_restrict -import Simplex.Chat.Migrations.M20240228_pq -import Simplex.Chat.Migrations.M20240313_drop_agent_ack_cmd_id -import Simplex.Chat.Migrations.M20240324_custom_data -import Simplex.Chat.Migrations.M20240402_item_forwarded -import Simplex.Chat.Migrations.M20240430_ui_theme -import Simplex.Chat.Migrations.M20240501_chat_deleted -import Simplex.Chat.Migrations.M20240510_chat_items_via_proxy -import Simplex.Chat.Migrations.M20240515_rcv_files_user_approved_relays -import Simplex.Chat.Migrations.M20240528_quota_err_counter -import Simplex.Chat.Migrations.M20240827_calls_uuid -import Simplex.Chat.Migrations.M20240920_user_order -import Simplex.Chat.Migrations.M20241008_indexes -import Simplex.Chat.Migrations.M20241010_contact_requests_contact_id -import Simplex.Chat.Migrations.M20241023_chat_item_autoincrement_id -import Simplex.Chat.Migrations.M20241027_server_operators -import Simplex.Chat.Migrations.M20241125_indexes -import Simplex.Chat.Migrations.M20241128_business_chats -import Simplex.Chat.Migrations.M20241205_business_chat_members -import Simplex.Chat.Migrations.M20241222_operator_conditions -import Simplex.Chat.Migrations.M20241223_chat_tags -import Simplex.Chat.Migrations.M20241230_reports -import Simplex.Chat.Migrations.M20250105_indexes +import Simplex.Chat.Store.SQLite.Migrations.M20220101_initial +import Simplex.Chat.Store.SQLite.Migrations.M20220122_v1_1 +import Simplex.Chat.Store.SQLite.Migrations.M20220205_chat_item_status +import Simplex.Chat.Store.SQLite.Migrations.M20220210_deduplicate_contact_requests +import Simplex.Chat.Store.SQLite.Migrations.M20220224_messages_fks +import Simplex.Chat.Store.SQLite.Migrations.M20220301_smp_servers +import Simplex.Chat.Store.SQLite.Migrations.M20220302_profile_images +import Simplex.Chat.Store.SQLite.Migrations.M20220304_msg_quotes +import Simplex.Chat.Store.SQLite.Migrations.M20220321_chat_item_edited +import Simplex.Chat.Store.SQLite.Migrations.M20220404_files_status_fields +import Simplex.Chat.Store.SQLite.Migrations.M20220514_profiles_user_id +import Simplex.Chat.Store.SQLite.Migrations.M20220626_auto_reply +import Simplex.Chat.Store.SQLite.Migrations.M20220702_calls +import Simplex.Chat.Store.SQLite.Migrations.M20220715_groups_chat_item_id +import Simplex.Chat.Store.SQLite.Migrations.M20220811_chat_items_indices +import Simplex.Chat.Store.SQLite.Migrations.M20220812_incognito_profiles +import Simplex.Chat.Store.SQLite.Migrations.M20220818_chat_notifications +import Simplex.Chat.Store.SQLite.Migrations.M20220822_groups_host_conn_custom_user_profile_id +import Simplex.Chat.Store.SQLite.Migrations.M20220823_delete_broken_group_event_chat_items +import Simplex.Chat.Store.SQLite.Migrations.M20220824_profiles_local_alias +import Simplex.Chat.Store.SQLite.Migrations.M20220909_commands +import Simplex.Chat.Store.SQLite.Migrations.M20220926_connection_alias +import Simplex.Chat.Store.SQLite.Migrations.M20220928_settings +import Simplex.Chat.Store.SQLite.Migrations.M20221001_shared_msg_id_indices +import Simplex.Chat.Store.SQLite.Migrations.M20221003_delete_broken_integrity_error_chat_items +import Simplex.Chat.Store.SQLite.Migrations.M20221004_idx_msg_deliveries_message_id +import Simplex.Chat.Store.SQLite.Migrations.M20221011_user_contact_links_group_id +import Simplex.Chat.Store.SQLite.Migrations.M20221012_inline_files +import Simplex.Chat.Store.SQLite.Migrations.M20221019_unread_chat +import Simplex.Chat.Store.SQLite.Migrations.M20221021_auto_accept__group_links +import Simplex.Chat.Store.SQLite.Migrations.M20221024_contact_used +import Simplex.Chat.Store.SQLite.Migrations.M20221025_chat_settings +import Simplex.Chat.Store.SQLite.Migrations.M20221029_group_link_id +import Simplex.Chat.Store.SQLite.Migrations.M20221112_server_password +import Simplex.Chat.Store.SQLite.Migrations.M20221115_server_cfg +import Simplex.Chat.Store.SQLite.Migrations.M20221129_delete_group_feature_items +import Simplex.Chat.Store.SQLite.Migrations.M20221130_delete_item_deleted +import Simplex.Chat.Store.SQLite.Migrations.M20221209_verified_connection +import Simplex.Chat.Store.SQLite.Migrations.M20221210_idxs +import Simplex.Chat.Store.SQLite.Migrations.M20221211_group_description +import Simplex.Chat.Store.SQLite.Migrations.M20221212_chat_items_timed +import Simplex.Chat.Store.SQLite.Migrations.M20221214_live_message +import Simplex.Chat.Store.SQLite.Migrations.M20221222_chat_ts +import Simplex.Chat.Store.SQLite.Migrations.M20221223_idx_chat_items_item_status +import Simplex.Chat.Store.SQLite.Migrations.M20221230_idxs +import Simplex.Chat.Store.SQLite.Migrations.M20230107_connections_auth_err_counter +import Simplex.Chat.Store.SQLite.Migrations.M20230111_users_agent_user_id +import Simplex.Chat.Store.SQLite.Migrations.M20230117_fkey_indexes +import Simplex.Chat.Store.SQLite.Migrations.M20230118_recreate_smp_servers +import Simplex.Chat.Store.SQLite.Migrations.M20230129_drop_chat_items_group_idx +import Simplex.Chat.Store.SQLite.Migrations.M20230206_item_deleted_by_group_member_id +import Simplex.Chat.Store.SQLite.Migrations.M20230303_group_link_role +import Simplex.Chat.Store.SQLite.Migrations.M20230317_hidden_profiles +import Simplex.Chat.Store.SQLite.Migrations.M20230318_file_description +import Simplex.Chat.Store.SQLite.Migrations.M20230321_agent_file_deleted +import Simplex.Chat.Store.SQLite.Migrations.M20230328_files_protocol +import Simplex.Chat.Store.SQLite.Migrations.M20230402_protocol_servers +import Simplex.Chat.Store.SQLite.Migrations.M20230411_extra_xftp_file_descriptions +import Simplex.Chat.Store.SQLite.Migrations.M20230420_rcv_files_to_receive +import Simplex.Chat.Store.SQLite.Migrations.M20230422_profile_contact_links +import Simplex.Chat.Store.SQLite.Migrations.M20230504_recreate_msg_delivery_events_cleanup_messages +import Simplex.Chat.Store.SQLite.Migrations.M20230505_chat_item_versions +import Simplex.Chat.Store.SQLite.Migrations.M20230511_reactions +import Simplex.Chat.Store.SQLite.Migrations.M20230519_item_deleted_ts +import Simplex.Chat.Store.SQLite.Migrations.M20230526_indexes +import Simplex.Chat.Store.SQLite.Migrations.M20230529_indexes +import Simplex.Chat.Store.SQLite.Migrations.M20230608_deleted_contacts +import Simplex.Chat.Store.SQLite.Migrations.M20230618_favorite_chats +import Simplex.Chat.Store.SQLite.Migrations.M20230621_chat_item_moderations +import Simplex.Chat.Store.SQLite.Migrations.M20230705_delivery_receipts +import Simplex.Chat.Store.SQLite.Migrations.M20230721_group_snd_item_statuses +import Simplex.Chat.Store.SQLite.Migrations.M20230814_indexes +import Simplex.Chat.Store.SQLite.Migrations.M20230827_file_encryption +import Simplex.Chat.Store.SQLite.Migrations.M20230829_connections_chat_vrange +import Simplex.Chat.Store.SQLite.Migrations.M20230903_connections_to_subscribe +import Simplex.Chat.Store.SQLite.Migrations.M20230913_member_contacts +import Simplex.Chat.Store.SQLite.Migrations.M20230914_member_probes +import Simplex.Chat.Store.SQLite.Migrations.M20230926_contact_status +import Simplex.Chat.Store.SQLite.Migrations.M20231002_conn_initiated +import Simplex.Chat.Store.SQLite.Migrations.M20231009_via_group_link_uri_hash +import Simplex.Chat.Store.SQLite.Migrations.M20231010_member_settings +import Simplex.Chat.Store.SQLite.Migrations.M20231019_indexes +import Simplex.Chat.Store.SQLite.Migrations.M20231030_xgrplinkmem_received +import Simplex.Chat.Store.SQLite.Migrations.M20231107_indexes +import Simplex.Chat.Store.SQLite.Migrations.M20231113_group_forward +import Simplex.Chat.Store.SQLite.Migrations.M20231114_remote_control +import Simplex.Chat.Store.SQLite.Migrations.M20231126_remote_ctrl_address +import Simplex.Chat.Store.SQLite.Migrations.M20231207_chat_list_pagination +import Simplex.Chat.Store.SQLite.Migrations.M20231214_item_content_tag +import Simplex.Chat.Store.SQLite.Migrations.M20231215_recreate_msg_deliveries +import Simplex.Chat.Store.SQLite.Migrations.M20240102_note_folders +import Simplex.Chat.Store.SQLite.Migrations.M20240104_members_profile_update +import Simplex.Chat.Store.SQLite.Migrations.M20240115_block_member_for_all +import Simplex.Chat.Store.SQLite.Migrations.M20240122_indexes +import Simplex.Chat.Store.SQLite.Migrations.M20240214_redirect_file_id +import Simplex.Chat.Store.SQLite.Migrations.M20240222_app_settings +import Simplex.Chat.Store.SQLite.Migrations.M20240226_users_restrict +import Simplex.Chat.Store.SQLite.Migrations.M20240228_pq +import Simplex.Chat.Store.SQLite.Migrations.M20240313_drop_agent_ack_cmd_id +import Simplex.Chat.Store.SQLite.Migrations.M20240324_custom_data +import Simplex.Chat.Store.SQLite.Migrations.M20240402_item_forwarded +import Simplex.Chat.Store.SQLite.Migrations.M20240430_ui_theme +import Simplex.Chat.Store.SQLite.Migrations.M20240501_chat_deleted +import Simplex.Chat.Store.SQLite.Migrations.M20240510_chat_items_via_proxy +import Simplex.Chat.Store.SQLite.Migrations.M20240515_rcv_files_user_approved_relays +import Simplex.Chat.Store.SQLite.Migrations.M20240528_quota_err_counter +import Simplex.Chat.Store.SQLite.Migrations.M20240827_calls_uuid +import Simplex.Chat.Store.SQLite.Migrations.M20240920_user_order +import Simplex.Chat.Store.SQLite.Migrations.M20241008_indexes +import Simplex.Chat.Store.SQLite.Migrations.M20241010_contact_requests_contact_id +import Simplex.Chat.Store.SQLite.Migrations.M20241023_chat_item_autoincrement_id +import Simplex.Chat.Store.SQLite.Migrations.M20241027_server_operators +import Simplex.Chat.Store.SQLite.Migrations.M20241125_indexes +import Simplex.Chat.Store.SQLite.Migrations.M20241128_business_chats +import Simplex.Chat.Store.SQLite.Migrations.M20241205_business_chat_members +import Simplex.Chat.Store.SQLite.Migrations.M20241222_operator_conditions +import Simplex.Chat.Store.SQLite.Migrations.M20241223_chat_tags +import Simplex.Chat.Store.SQLite.Migrations.M20241230_reports +import Simplex.Chat.Store.SQLite.Migrations.M20250105_indexes import Simplex.Messaging.Agent.Store.Shared (Migration (..)) schemaMigrations :: [(String, Query, Maybe Query)] diff --git a/src/Simplex/Chat/Migrations/M20220101_initial.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20220101_initial.hs similarity index 99% rename from src/Simplex/Chat/Migrations/M20220101_initial.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20220101_initial.hs index 2568b0b672..601a90d5d9 100644 --- a/src/Simplex/Chat/Migrations/M20220101_initial.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20220101_initial.hs @@ -1,6 +1,6 @@ {-# 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.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20220122_v1_1.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20220122_v1_1.hs similarity index 99% rename from src/Simplex/Chat/Migrations/M20220122_v1_1.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20220122_v1_1.hs index 157f97c333..c84bbdd03f 100644 --- a/src/Simplex/Chat/Migrations/M20220122_v1_1.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20220122_v1_1.hs @@ -1,6 +1,6 @@ {-# 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.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20220205_chat_item_status.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20220205_chat_item_status.hs similarity index 86% rename from src/Simplex/Chat/Migrations/M20220205_chat_item_status.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20220205_chat_item_status.hs index 6baca156fb..fe1a6382eb 100644 --- a/src/Simplex/Chat/Migrations/M20220205_chat_item_status.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20220205_chat_item_status.hs @@ -1,6 +1,6 @@ {-# 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.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20220210_deduplicate_contact_requests.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20220210_deduplicate_contact_requests.hs similarity index 91% rename from src/Simplex/Chat/Migrations/M20220210_deduplicate_contact_requests.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20220210_deduplicate_contact_requests.hs index e2c26e35e0..1c97f82dc0 100644 --- a/src/Simplex/Chat/Migrations/M20220210_deduplicate_contact_requests.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20220210_deduplicate_contact_requests.hs @@ -1,6 +1,6 @@ {-# 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.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20220224_messages_fks.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20220224_messages_fks.hs similarity index 84% rename from src/Simplex/Chat/Migrations/M20220224_messages_fks.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20220224_messages_fks.hs index 9bb5db57a5..b9842dd33d 100644 --- a/src/Simplex/Chat/Migrations/M20220224_messages_fks.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20220224_messages_fks.hs @@ -1,6 +1,6 @@ {-# 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.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20220301_smp_servers.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20220301_smp_servers.hs similarity index 87% rename from src/Simplex/Chat/Migrations/M20220301_smp_servers.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20220301_smp_servers.hs index 774f2e0168..91bd3194ca 100644 --- a/src/Simplex/Chat/Migrations/M20220301_smp_servers.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20220301_smp_servers.hs @@ -1,6 +1,6 @@ {-# 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.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20220302_profile_images.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20220302_profile_images.hs similarity index 79% rename from src/Simplex/Chat/Migrations/M20220302_profile_images.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20220302_profile_images.hs index 72c22b89cb..f6a9444ce1 100644 --- a/src/Simplex/Chat/Migrations/M20220302_profile_images.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20220302_profile_images.hs @@ -1,6 +1,6 @@ {-# 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.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20220304_msg_quotes.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20220304_msg_quotes.hs similarity index 94% rename from src/Simplex/Chat/Migrations/M20220304_msg_quotes.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20220304_msg_quotes.hs index 129c3616a0..fa0df67b10 100644 --- a/src/Simplex/Chat/Migrations/M20220304_msg_quotes.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20220304_msg_quotes.hs @@ -1,6 +1,6 @@ {-# 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.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20220321_chat_item_edited.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20220321_chat_item_edited.hs similarity index 76% rename from src/Simplex/Chat/Migrations/M20220321_chat_item_edited.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20220321_chat_item_edited.hs index 7a77f00262..5e11ef7519 100644 --- a/src/Simplex/Chat/Migrations/M20220321_chat_item_edited.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20220321_chat_item_edited.hs @@ -1,6 +1,6 @@ {-# 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.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20220404_files_status_fields.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20220404_files_status_fields.hs similarity index 83% rename from src/Simplex/Chat/Migrations/M20220404_files_status_fields.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20220404_files_status_fields.hs index 40623a3be6..f38fe1163a 100644 --- a/src/Simplex/Chat/Migrations/M20220404_files_status_fields.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20220404_files_status_fields.hs @@ -1,6 +1,6 @@ {-# 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.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20220514_profiles_user_id.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20220514_profiles_user_id.hs similarity index 88% rename from src/Simplex/Chat/Migrations/M20220514_profiles_user_id.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20220514_profiles_user_id.hs index 239f124576..b4b99a27a2 100644 --- a/src/Simplex/Chat/Migrations/M20220514_profiles_user_id.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20220514_profiles_user_id.hs @@ -1,6 +1,6 @@ {-# 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.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20220626_auto_reply.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20220626_auto_reply.hs similarity index 85% rename from src/Simplex/Chat/Migrations/M20220626_auto_reply.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20220626_auto_reply.hs index 6ac72ac804..e85a6438ae 100644 --- a/src/Simplex/Chat/Migrations/M20220626_auto_reply.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20220626_auto_reply.hs @@ -1,6 +1,6 @@ {-# 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.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20220702_calls.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20220702_calls.hs similarity index 91% rename from src/Simplex/Chat/Migrations/M20220702_calls.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20220702_calls.hs index 4cbf3dbad6..fc0bdd568c 100644 --- a/src/Simplex/Chat/Migrations/M20220702_calls.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20220702_calls.hs @@ -1,6 +1,6 @@ {-# 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.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20220715_groups_chat_item_id.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20220715_groups_chat_item_id.hs similarity index 78% rename from src/Simplex/Chat/Migrations/M20220715_groups_chat_item_id.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20220715_groups_chat_item_id.hs index 0fa5310bfb..44ed8d2e6e 100644 --- a/src/Simplex/Chat/Migrations/M20220715_groups_chat_item_id.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20220715_groups_chat_item_id.hs @@ -1,6 +1,6 @@ {-# 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.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20220811_chat_items_indices.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20220811_chat_items_indices.hs similarity index 82% rename from src/Simplex/Chat/Migrations/M20220811_chat_items_indices.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20220811_chat_items_indices.hs index a43617d439..18eabbfacd 100644 --- a/src/Simplex/Chat/Migrations/M20220811_chat_items_indices.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20220811_chat_items_indices.hs @@ -1,6 +1,6 @@ {-# 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.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20220812_incognito_profiles.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20220812_incognito_profiles.hs similarity index 89% rename from src/Simplex/Chat/Migrations/M20220812_incognito_profiles.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20220812_incognito_profiles.hs index e03eda2358..59ff18caf8 100644 --- a/src/Simplex/Chat/Migrations/M20220812_incognito_profiles.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20220812_incognito_profiles.hs @@ -1,6 +1,6 @@ {-# 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.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20220818_chat_notifications.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20220818_chat_notifications.hs similarity index 78% rename from src/Simplex/Chat/Migrations/M20220818_chat_notifications.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20220818_chat_notifications.hs index ffb2b15967..42c439e8bc 100644 --- a/src/Simplex/Chat/Migrations/M20220818_chat_notifications.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20220818_chat_notifications.hs @@ -1,6 +1,6 @@ {-# 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.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20220822_groups_host_conn_custom_user_profile_id.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20220822_groups_host_conn_custom_user_profile_id.hs similarity index 80% rename from src/Simplex/Chat/Migrations/M20220822_groups_host_conn_custom_user_profile_id.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20220822_groups_host_conn_custom_user_profile_id.hs index bbadbd5524..1cd49ccf26 100644 --- a/src/Simplex/Chat/Migrations/M20220822_groups_host_conn_custom_user_profile_id.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20220822_groups_host_conn_custom_user_profile_id.hs @@ -1,6 +1,6 @@ {-# 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.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20220823_delete_broken_group_event_chat_items.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20220823_delete_broken_group_event_chat_items.hs similarity index 80% rename from src/Simplex/Chat/Migrations/M20220823_delete_broken_group_event_chat_items.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20220823_delete_broken_group_event_chat_items.hs index 40657f3421..f4f8df826a 100644 --- a/src/Simplex/Chat/Migrations/M20220823_delete_broken_group_event_chat_items.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20220823_delete_broken_group_event_chat_items.hs @@ -1,6 +1,6 @@ {-# 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.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20220824_profiles_local_alias.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20220824_profiles_local_alias.hs similarity index 83% rename from src/Simplex/Chat/Migrations/M20220824_profiles_local_alias.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20220824_profiles_local_alias.hs index f0b0ca8385..9252bc43fb 100644 --- a/src/Simplex/Chat/Migrations/M20220824_profiles_local_alias.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20220824_profiles_local_alias.hs @@ -1,6 +1,6 @@ {-# 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.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20220909_commands.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20220909_commands.hs similarity index 91% rename from src/Simplex/Chat/Migrations/M20220909_commands.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20220909_commands.hs index 745dff4165..3cc359d56b 100644 --- a/src/Simplex/Chat/Migrations/M20220909_commands.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20220909_commands.hs @@ -1,6 +1,6 @@ {-# 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.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20220926_connection_alias.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20220926_connection_alias.hs similarity index 83% rename from src/Simplex/Chat/Migrations/M20220926_connection_alias.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20220926_connection_alias.hs index ede7cc3cfc..7b56ba5fb9 100644 --- a/src/Simplex/Chat/Migrations/M20220926_connection_alias.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20220926_connection_alias.hs @@ -1,6 +1,6 @@ {-# 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.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20220928_settings.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20220928_settings.hs similarity index 86% rename from src/Simplex/Chat/Migrations/M20220928_settings.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20220928_settings.hs index 56b3613b05..a159ef1cc4 100644 --- a/src/Simplex/Chat/Migrations/M20220928_settings.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20220928_settings.hs @@ -1,6 +1,6 @@ {-# 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.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20221001_shared_msg_id_indices.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20221001_shared_msg_id_indices.hs similarity index 85% rename from src/Simplex/Chat/Migrations/M20221001_shared_msg_id_indices.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20221001_shared_msg_id_indices.hs index 10ac0fa5e1..61c5800a1c 100644 --- a/src/Simplex/Chat/Migrations/M20221001_shared_msg_id_indices.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20221001_shared_msg_id_indices.hs @@ -1,6 +1,6 @@ {-# 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.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20221003_delete_broken_integrity_error_chat_items.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20221003_delete_broken_integrity_error_chat_items.hs similarity index 75% rename from src/Simplex/Chat/Migrations/M20221003_delete_broken_integrity_error_chat_items.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20221003_delete_broken_integrity_error_chat_items.hs index 2d451766cd..1843f3316a 100644 --- a/src/Simplex/Chat/Migrations/M20221003_delete_broken_integrity_error_chat_items.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20221003_delete_broken_integrity_error_chat_items.hs @@ -1,6 +1,6 @@ {-# 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.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20221004_idx_msg_deliveries_message_id.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20221004_idx_msg_deliveries_message_id.hs similarity index 76% rename from src/Simplex/Chat/Migrations/M20221004_idx_msg_deliveries_message_id.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20221004_idx_msg_deliveries_message_id.hs index 0e53923b58..8dba932549 100644 --- a/src/Simplex/Chat/Migrations/M20221004_idx_msg_deliveries_message_id.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20221004_idx_msg_deliveries_message_id.hs @@ -1,6 +1,6 @@ {-# 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.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20221011_user_contact_links_group_id.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20221011_user_contact_links_group_id.hs similarity index 81% rename from src/Simplex/Chat/Migrations/M20221011_user_contact_links_group_id.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20221011_user_contact_links_group_id.hs index 4ad6fcb8dc..a8c64d32b0 100644 --- a/src/Simplex/Chat/Migrations/M20221011_user_contact_links_group_id.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20221011_user_contact_links_group_id.hs @@ -1,6 +1,6 @@ {-# 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.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20221012_inline_files.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20221012_inline_files.hs similarity index 92% rename from src/Simplex/Chat/Migrations/M20221012_inline_files.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20221012_inline_files.hs index 4b069f0882..cb765d4e64 100644 --- a/src/Simplex/Chat/Migrations/M20221012_inline_files.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20221012_inline_files.hs @@ -1,6 +1,6 @@ {-# 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.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20221019_unread_chat.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20221019_unread_chat.hs similarity index 87% rename from src/Simplex/Chat/Migrations/M20221019_unread_chat.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20221019_unread_chat.hs index db24db947b..c8bccb81d3 100644 --- a/src/Simplex/Chat/Migrations/M20221019_unread_chat.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20221019_unread_chat.hs @@ -1,6 +1,6 @@ {-# 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.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20221021_auto_accept__group_links.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20221021_auto_accept__group_links.hs similarity index 88% rename from src/Simplex/Chat/Migrations/M20221021_auto_accept__group_links.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20221021_auto_accept__group_links.hs index cb945cce97..54042855d9 100644 --- a/src/Simplex/Chat/Migrations/M20221021_auto_accept__group_links.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20221021_auto_accept__group_links.hs @@ -1,6 +1,6 @@ {-# 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.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20221024_contact_used.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20221024_contact_used.hs similarity index 87% rename from src/Simplex/Chat/Migrations/M20221024_contact_used.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20221024_contact_used.hs index 6f677f1c77..de0164d81b 100644 --- a/src/Simplex/Chat/Migrations/M20221024_contact_used.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20221024_contact_used.hs @@ -1,6 +1,6 @@ {-# 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.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20221025_chat_settings.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20221025_chat_settings.hs similarity index 87% rename from src/Simplex/Chat/Migrations/M20221025_chat_settings.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20221025_chat_settings.hs index 712902e85c..0e768e8fb1 100644 --- a/src/Simplex/Chat/Migrations/M20221025_chat_settings.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20221025_chat_settings.hs @@ -1,6 +1,6 @@ {-# 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.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20221029_group_link_id.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20221029_group_link_id.hs similarity index 80% rename from src/Simplex/Chat/Migrations/M20221029_group_link_id.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20221029_group_link_id.hs index da290e4158..5d3cf7ee3c 100644 --- a/src/Simplex/Chat/Migrations/M20221029_group_link_id.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20221029_group_link_id.hs @@ -1,6 +1,6 @@ {-# 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.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20221112_server_password.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20221112_server_password.hs similarity index 75% rename from src/Simplex/Chat/Migrations/M20221112_server_password.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20221112_server_password.hs index ee8d0e470d..4afc63fe1c 100644 --- a/src/Simplex/Chat/Migrations/M20221112_server_password.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20221112_server_password.hs @@ -1,6 +1,6 @@ {-# 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.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20221115_server_cfg.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20221115_server_cfg.hs similarity index 87% rename from src/Simplex/Chat/Migrations/M20221115_server_cfg.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20221115_server_cfg.hs index 409da91db0..1def0c4c17 100644 --- a/src/Simplex/Chat/Migrations/M20221115_server_cfg.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20221115_server_cfg.hs @@ -1,6 +1,6 @@ {-# 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.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20221129_delete_group_feature_items.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20221129_delete_group_feature_items.hs similarity index 80% rename from src/Simplex/Chat/Migrations/M20221129_delete_group_feature_items.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20221129_delete_group_feature_items.hs index 7baf6b584e..5e631ab88c 100644 --- a/src/Simplex/Chat/Migrations/M20221129_delete_group_feature_items.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20221129_delete_group_feature_items.hs @@ -1,6 +1,6 @@ {-# 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.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20221130_delete_item_deleted.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20221130_delete_item_deleted.hs similarity index 78% rename from src/Simplex/Chat/Migrations/M20221130_delete_item_deleted.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20221130_delete_item_deleted.hs index 487cb7dceb..f64c3a2b56 100644 --- a/src/Simplex/Chat/Migrations/M20221130_delete_item_deleted.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20221130_delete_item_deleted.hs @@ -1,6 +1,6 @@ {-# 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.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20221209_verified_connection.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20221209_verified_connection.hs similarity index 80% rename from src/Simplex/Chat/Migrations/M20221209_verified_connection.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20221209_verified_connection.hs index f7e4a8aee9..0643c3f873 100644 --- a/src/Simplex/Chat/Migrations/M20221209_verified_connection.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20221209_verified_connection.hs @@ -1,6 +1,6 @@ {-# 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.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20221210_idxs.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20221210_idxs.hs similarity index 85% rename from src/Simplex/Chat/Migrations/M20221210_idxs.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20221210_idxs.hs index cd18611a53..cea66cf117 100644 --- a/src/Simplex/Chat/Migrations/M20221210_idxs.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20221210_idxs.hs @@ -1,6 +1,6 @@ {-# 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.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20221211_group_description.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20221211_group_description.hs similarity index 76% rename from src/Simplex/Chat/Migrations/M20221211_group_description.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20221211_group_description.hs index ba406862cb..8b0ca88f67 100644 --- a/src/Simplex/Chat/Migrations/M20221211_group_description.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20221211_group_description.hs @@ -1,6 +1,6 @@ {-# 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.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20221212_chat_items_timed.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20221212_chat_items_timed.hs similarity index 82% rename from src/Simplex/Chat/Migrations/M20221212_chat_items_timed.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20221212_chat_items_timed.hs index b82b66f3d4..edfcd39c70 100644 --- a/src/Simplex/Chat/Migrations/M20221212_chat_items_timed.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20221212_chat_items_timed.hs @@ -1,6 +1,6 @@ {-# 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.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20221214_live_message.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20221214_live_message.hs similarity index 75% rename from src/Simplex/Chat/Migrations/M20221214_live_message.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20221214_live_message.hs index 959ec75ae4..ff64defef3 100644 --- a/src/Simplex/Chat/Migrations/M20221214_live_message.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20221214_live_message.hs @@ -1,6 +1,6 @@ {-# 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.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20221222_chat_ts.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20221222_chat_ts.hs similarity index 81% rename from src/Simplex/Chat/Migrations/M20221222_chat_ts.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20221222_chat_ts.hs index 9a83c81821..1a4025b1ae 100644 --- a/src/Simplex/Chat/Migrations/M20221222_chat_ts.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20221222_chat_ts.hs @@ -1,6 +1,6 @@ {-# 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.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20221223_idx_chat_items_item_status.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20221223_idx_chat_items_item_status.hs similarity index 75% rename from src/Simplex/Chat/Migrations/M20221223_idx_chat_items_item_status.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20221223_idx_chat_items_item_status.hs index f29b4a9d5c..d834396df2 100644 --- a/src/Simplex/Chat/Migrations/M20221223_idx_chat_items_item_status.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20221223_idx_chat_items_item_status.hs @@ -1,6 +1,6 @@ {-# 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.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20221230_idxs.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20221230_idxs.hs similarity index 82% rename from src/Simplex/Chat/Migrations/M20221230_idxs.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20221230_idxs.hs index dbe84357aa..ca58d0cb70 100644 --- a/src/Simplex/Chat/Migrations/M20221230_idxs.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20221230_idxs.hs @@ -1,6 +1,6 @@ {-# 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.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20230107_connections_auth_err_counter.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20230107_connections_auth_err_counter.hs similarity index 82% rename from src/Simplex/Chat/Migrations/M20230107_connections_auth_err_counter.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20230107_connections_auth_err_counter.hs index b3c724e938..3b5062144a 100644 --- a/src/Simplex/Chat/Migrations/M20230107_connections_auth_err_counter.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20230107_connections_auth_err_counter.hs @@ -1,6 +1,6 @@ {-# 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.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20230111_users_agent_user_id.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20230111_users_agent_user_id.hs similarity index 82% rename from src/Simplex/Chat/Migrations/M20230111_users_agent_user_id.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20230111_users_agent_user_id.hs index 531c776a33..9fd108809c 100644 --- a/src/Simplex/Chat/Migrations/M20230111_users_agent_user_id.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20230111_users_agent_user_id.hs @@ -1,6 +1,6 @@ {-# 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.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20230117_fkey_indexes.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20230117_fkey_indexes.hs similarity index 98% rename from src/Simplex/Chat/Migrations/M20230117_fkey_indexes.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20230117_fkey_indexes.hs index 5986863093..50f6cf4a21 100644 --- a/src/Simplex/Chat/Migrations/M20230117_fkey_indexes.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20230117_fkey_indexes.hs @@ -1,6 +1,6 @@ {-# 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.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20230118_recreate_smp_servers.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20230118_recreate_smp_servers.hs similarity index 93% rename from src/Simplex/Chat/Migrations/M20230118_recreate_smp_servers.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20230118_recreate_smp_servers.hs index 6253a3a37e..19e60aa792 100644 --- a/src/Simplex/Chat/Migrations/M20230118_recreate_smp_servers.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20230118_recreate_smp_servers.hs @@ -1,6 +1,6 @@ {-# 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.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20230129_drop_chat_items_group_idx.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20230129_drop_chat_items_group_idx.hs similarity index 73% rename from src/Simplex/Chat/Migrations/M20230129_drop_chat_items_group_idx.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20230129_drop_chat_items_group_idx.hs index c45f513765..be31997235 100644 --- a/src/Simplex/Chat/Migrations/M20230129_drop_chat_items_group_idx.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20230129_drop_chat_items_group_idx.hs @@ -1,6 +1,6 @@ {-# 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.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20230206_item_deleted_by_group_member_id.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20230206_item_deleted_by_group_member_id.hs similarity index 82% rename from src/Simplex/Chat/Migrations/M20230206_item_deleted_by_group_member_id.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20230206_item_deleted_by_group_member_id.hs index 085e7f7525..9c3d01795a 100644 --- a/src/Simplex/Chat/Migrations/M20230206_item_deleted_by_group_member_id.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20230206_item_deleted_by_group_member_id.hs @@ -1,6 +1,6 @@ {-# 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.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20230303_group_link_role.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20230303_group_link_role.hs similarity index 78% rename from src/Simplex/Chat/Migrations/M20230303_group_link_role.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20230303_group_link_role.hs index ae67e7d776..cf25fb4f6e 100644 --- a/src/Simplex/Chat/Migrations/M20230303_group_link_role.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20230303_group_link_role.hs @@ -1,6 +1,6 @@ {-# 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.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20230317_hidden_profiles.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20230317_hidden_profiles.hs similarity index 88% rename from src/Simplex/Chat/Migrations/M20230317_hidden_profiles.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20230317_hidden_profiles.hs index 65e9cfeadd..0106d29118 100644 --- a/src/Simplex/Chat/Migrations/M20230317_hidden_profiles.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20230317_hidden_profiles.hs @@ -1,6 +1,6 @@ {-# 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.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20230318_file_description.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20230318_file_description.hs similarity index 95% rename from src/Simplex/Chat/Migrations/M20230318_file_description.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20230318_file_description.hs index 39f56b2a48..3bb15037ed 100644 --- a/src/Simplex/Chat/Migrations/M20230318_file_description.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20230318_file_description.hs @@ -1,6 +1,6 @@ {-# 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.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20230321_agent_file_deleted.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20230321_agent_file_deleted.hs similarity index 90% rename from src/Simplex/Chat/Migrations/M20230321_agent_file_deleted.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20230321_agent_file_deleted.hs index 97c213ea48..2a54f05c4c 100644 --- a/src/Simplex/Chat/Migrations/M20230321_agent_file_deleted.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20230321_agent_file_deleted.hs @@ -1,6 +1,6 @@ {-# 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.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20230328_files_protocol.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20230328_files_protocol.hs similarity index 83% rename from src/Simplex/Chat/Migrations/M20230328_files_protocol.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20230328_files_protocol.hs index 5690378301..7ea2b4e34f 100644 --- a/src/Simplex/Chat/Migrations/M20230328_files_protocol.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20230328_files_protocol.hs @@ -1,6 +1,6 @@ {-# 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.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20230402_protocol_servers.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20230402_protocol_servers.hs similarity index 86% rename from src/Simplex/Chat/Migrations/M20230402_protocol_servers.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20230402_protocol_servers.hs index bffe7ac813..be2d0b96b2 100644 --- a/src/Simplex/Chat/Migrations/M20230402_protocol_servers.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20230402_protocol_servers.hs @@ -1,6 +1,6 @@ {-# 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.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20230411_extra_xftp_file_descriptions.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20230411_extra_xftp_file_descriptions.hs similarity index 92% rename from src/Simplex/Chat/Migrations/M20230411_extra_xftp_file_descriptions.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20230411_extra_xftp_file_descriptions.hs index 9bfd773c44..739770d84f 100644 --- a/src/Simplex/Chat/Migrations/M20230411_extra_xftp_file_descriptions.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20230411_extra_xftp_file_descriptions.hs @@ -1,6 +1,6 @@ {-# 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.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20230420_rcv_files_to_receive.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20230420_rcv_files_to_receive.hs similarity index 82% rename from src/Simplex/Chat/Migrations/M20230420_rcv_files_to_receive.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20230420_rcv_files_to_receive.hs index 0b6329bc6d..cd97e16c03 100644 --- a/src/Simplex/Chat/Migrations/M20230420_rcv_files_to_receive.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20230420_rcv_files_to_receive.hs @@ -1,6 +1,6 @@ {-# 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.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20230422_profile_contact_links.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20230422_profile_contact_links.hs similarity index 83% rename from src/Simplex/Chat/Migrations/M20230422_profile_contact_links.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20230422_profile_contact_links.hs index ee7ff053d5..f9513acff1 100644 --- a/src/Simplex/Chat/Migrations/M20230422_profile_contact_links.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20230422_profile_contact_links.hs @@ -1,6 +1,6 @@ {-# 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.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20230504_recreate_msg_delivery_events_cleanup_messages.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20230504_recreate_msg_delivery_events_cleanup_messages.hs similarity index 92% rename from src/Simplex/Chat/Migrations/M20230504_recreate_msg_delivery_events_cleanup_messages.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20230504_recreate_msg_delivery_events_cleanup_messages.hs index 009b537b6c..c51db6905b 100644 --- a/src/Simplex/Chat/Migrations/M20230504_recreate_msg_delivery_events_cleanup_messages.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20230504_recreate_msg_delivery_events_cleanup_messages.hs @@ -1,6 +1,6 @@ {-# LANGUAGE QuasiQuotes #-} -module Simplex.Chat.Migrations.M20230504_recreate_msg_delivery_events_cleanup_messages where +module Simplex.Chat.Store.SQLite.Migrations.M20230504_recreate_msg_delivery_events_cleanup_messages where import Database.SQLite.Simple (Query) import Database.SQLite.Simple.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20230505_chat_item_versions.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20230505_chat_item_versions.hs similarity index 91% rename from src/Simplex/Chat/Migrations/M20230505_chat_item_versions.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20230505_chat_item_versions.hs index 7e2e0f7719..bfc675c87c 100644 --- a/src/Simplex/Chat/Migrations/M20230505_chat_item_versions.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20230505_chat_item_versions.hs @@ -1,6 +1,6 @@ {-# LANGUAGE QuasiQuotes #-} -module Simplex.Chat.Migrations.M20230505_chat_item_versions where +module Simplex.Chat.Store.SQLite.Migrations.M20230505_chat_item_versions where import Database.SQLite.Simple (Query) import Database.SQLite.Simple.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20230511_reactions.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20230511_reactions.hs similarity index 96% rename from src/Simplex/Chat/Migrations/M20230511_reactions.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20230511_reactions.hs index f01954a373..17ecb97649 100644 --- a/src/Simplex/Chat/Migrations/M20230511_reactions.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20230511_reactions.hs @@ -1,6 +1,6 @@ {-# LANGUAGE QuasiQuotes #-} -module Simplex.Chat.Migrations.M20230511_reactions where +module Simplex.Chat.Store.SQLite.Migrations.M20230511_reactions where import Database.SQLite.Simple (Query) import Database.SQLite.Simple.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20230519_item_deleted_ts.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20230519_item_deleted_ts.hs similarity index 83% rename from src/Simplex/Chat/Migrations/M20230519_item_deleted_ts.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20230519_item_deleted_ts.hs index 20d5fd7a8d..c05687347c 100644 --- a/src/Simplex/Chat/Migrations/M20230519_item_deleted_ts.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20230519_item_deleted_ts.hs @@ -1,6 +1,6 @@ {-# LANGUAGE QuasiQuotes #-} -module Simplex.Chat.Migrations.M20230519_item_deleted_ts where +module Simplex.Chat.Store.SQLite.Migrations.M20230519_item_deleted_ts where import Database.SQLite.Simple (Query) import Database.SQLite.Simple.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20230526_indexes.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20230526_indexes.hs similarity index 87% rename from src/Simplex/Chat/Migrations/M20230526_indexes.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20230526_indexes.hs index 56c61b521b..19a9985c80 100644 --- a/src/Simplex/Chat/Migrations/M20230526_indexes.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20230526_indexes.hs @@ -1,6 +1,6 @@ {-# LANGUAGE QuasiQuotes #-} -module Simplex.Chat.Migrations.M20230526_indexes where +module Simplex.Chat.Store.SQLite.Migrations.M20230526_indexes where import Database.SQLite.Simple (Query) import Database.SQLite.Simple.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20230529_indexes.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20230529_indexes.hs similarity index 91% rename from src/Simplex/Chat/Migrations/M20230529_indexes.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20230529_indexes.hs index afb12a5c6d..d112c861e5 100644 --- a/src/Simplex/Chat/Migrations/M20230529_indexes.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20230529_indexes.hs @@ -1,6 +1,6 @@ {-# LANGUAGE QuasiQuotes #-} -module Simplex.Chat.Migrations.M20230529_indexes where +module Simplex.Chat.Store.SQLite.Migrations.M20230529_indexes where import Database.SQLite.Simple (Query) import Database.SQLite.Simple.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20230608_deleted_contacts.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20230608_deleted_contacts.hs similarity index 87% rename from src/Simplex/Chat/Migrations/M20230608_deleted_contacts.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20230608_deleted_contacts.hs index b7193300df..354fa49ca4 100644 --- a/src/Simplex/Chat/Migrations/M20230608_deleted_contacts.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20230608_deleted_contacts.hs @@ -1,6 +1,6 @@ {-# LANGUAGE QuasiQuotes #-} -module Simplex.Chat.Migrations.M20230608_deleted_contacts where +module Simplex.Chat.Store.SQLite.Migrations.M20230608_deleted_contacts where import Database.SQLite.Simple (Query) import Database.SQLite.Simple.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20230618_favorite_chats.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20230618_favorite_chats.hs similarity index 86% rename from src/Simplex/Chat/Migrations/M20230618_favorite_chats.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20230618_favorite_chats.hs index 66f65a926b..4905093b0f 100644 --- a/src/Simplex/Chat/Migrations/M20230618_favorite_chats.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20230618_favorite_chats.hs @@ -1,6 +1,6 @@ {-# LANGUAGE QuasiQuotes #-} -module Simplex.Chat.Migrations.M20230618_favorite_chats where +module Simplex.Chat.Store.SQLite.Migrations.M20230618_favorite_chats where import Database.SQLite.Simple (Query) import Database.SQLite.Simple.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20230621_chat_item_moderations.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20230621_chat_item_moderations.hs similarity index 95% rename from src/Simplex/Chat/Migrations/M20230621_chat_item_moderations.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20230621_chat_item_moderations.hs index 449e21e209..015a22f8b2 100644 --- a/src/Simplex/Chat/Migrations/M20230621_chat_item_moderations.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20230621_chat_item_moderations.hs @@ -1,6 +1,6 @@ {-# LANGUAGE QuasiQuotes #-} -module Simplex.Chat.Migrations.M20230621_chat_item_moderations where +module Simplex.Chat.Store.SQLite.Migrations.M20230621_chat_item_moderations where import Database.SQLite.Simple (Query) import Database.SQLite.Simple.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20230705_delivery_receipts.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20230705_delivery_receipts.hs similarity index 90% rename from src/Simplex/Chat/Migrations/M20230705_delivery_receipts.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20230705_delivery_receipts.hs index ec59209d6b..1f4a13cc56 100644 --- a/src/Simplex/Chat/Migrations/M20230705_delivery_receipts.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20230705_delivery_receipts.hs @@ -1,6 +1,6 @@ {-# LANGUAGE QuasiQuotes #-} -module Simplex.Chat.Migrations.M20230705_delivery_receipts where +module Simplex.Chat.Store.SQLite.Migrations.M20230705_delivery_receipts where import Database.SQLite.Simple (Query) import Database.SQLite.Simple.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20230721_group_snd_item_statuses.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20230721_group_snd_item_statuses.hs similarity index 92% rename from src/Simplex/Chat/Migrations/M20230721_group_snd_item_statuses.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20230721_group_snd_item_statuses.hs index 8453da88f5..6ce99450a4 100644 --- a/src/Simplex/Chat/Migrations/M20230721_group_snd_item_statuses.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20230721_group_snd_item_statuses.hs @@ -1,6 +1,6 @@ {-# LANGUAGE QuasiQuotes #-} -module Simplex.Chat.Migrations.M20230721_group_snd_item_statuses where +module Simplex.Chat.Store.SQLite.Migrations.M20230721_group_snd_item_statuses where import Database.SQLite.Simple (Query) import Database.SQLite.Simple.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20230814_indexes.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20230814_indexes.hs similarity index 84% rename from src/Simplex/Chat/Migrations/M20230814_indexes.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20230814_indexes.hs index a7419037ef..5e5b2122ca 100644 --- a/src/Simplex/Chat/Migrations/M20230814_indexes.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20230814_indexes.hs @@ -1,6 +1,6 @@ {-# LANGUAGE QuasiQuotes #-} -module Simplex.Chat.Migrations.M20230814_indexes where +module Simplex.Chat.Store.SQLite.Migrations.M20230814_indexes where import Database.SQLite.Simple (Query) import Database.SQLite.Simple.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20230827_file_encryption.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20230827_file_encryption.hs similarity index 86% rename from src/Simplex/Chat/Migrations/M20230827_file_encryption.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20230827_file_encryption.hs index 2e659cac84..2378df1b61 100644 --- a/src/Simplex/Chat/Migrations/M20230827_file_encryption.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20230827_file_encryption.hs @@ -1,6 +1,6 @@ {-# LANGUAGE QuasiQuotes #-} -module Simplex.Chat.Migrations.M20230827_file_encryption where +module Simplex.Chat.Store.SQLite.Migrations.M20230827_file_encryption where import Database.SQLite.Simple (Query) import Database.SQLite.Simple.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20230829_connections_chat_vrange.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20230829_connections_chat_vrange.hs similarity index 91% rename from src/Simplex/Chat/Migrations/M20230829_connections_chat_vrange.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20230829_connections_chat_vrange.hs index 2588553a92..66d4f32d70 100644 --- a/src/Simplex/Chat/Migrations/M20230829_connections_chat_vrange.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20230829_connections_chat_vrange.hs @@ -1,6 +1,6 @@ {-# LANGUAGE QuasiQuotes #-} -module Simplex.Chat.Migrations.M20230829_connections_chat_vrange where +module Simplex.Chat.Store.SQLite.Migrations.M20230829_connections_chat_vrange where import Database.SQLite.Simple (Query) import Database.SQLite.Simple.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20230903_connections_to_subscribe.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20230903_connections_to_subscribe.hs similarity index 86% rename from src/Simplex/Chat/Migrations/M20230903_connections_to_subscribe.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20230903_connections_to_subscribe.hs index 48ad8dbf86..449e94510d 100644 --- a/src/Simplex/Chat/Migrations/M20230903_connections_to_subscribe.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20230903_connections_to_subscribe.hs @@ -1,6 +1,6 @@ {-# LANGUAGE QuasiQuotes #-} -module Simplex.Chat.Migrations.M20230903_connections_to_subscribe where +module Simplex.Chat.Store.SQLite.Migrations.M20230903_connections_to_subscribe where import Database.SQLite.Simple (Query) import Database.SQLite.Simple.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20230913_member_contacts.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20230913_member_contacts.hs similarity index 90% rename from src/Simplex/Chat/Migrations/M20230913_member_contacts.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20230913_member_contacts.hs index b116373518..b3202745fd 100644 --- a/src/Simplex/Chat/Migrations/M20230913_member_contacts.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20230913_member_contacts.hs @@ -1,6 +1,6 @@ {-# LANGUAGE QuasiQuotes #-} -module Simplex.Chat.Migrations.M20230913_member_contacts where +module Simplex.Chat.Store.SQLite.Migrations.M20230913_member_contacts where import Database.SQLite.Simple (Query) import Database.SQLite.Simple.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20230914_member_probes.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20230914_member_probes.hs similarity index 98% rename from src/Simplex/Chat/Migrations/M20230914_member_probes.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20230914_member_probes.hs index 8772b6cdad..f5725c3f39 100644 --- a/src/Simplex/Chat/Migrations/M20230914_member_probes.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20230914_member_probes.hs @@ -1,6 +1,6 @@ {-# LANGUAGE QuasiQuotes #-} -module Simplex.Chat.Migrations.M20230914_member_probes where +module Simplex.Chat.Store.SQLite.Migrations.M20230914_member_probes where import Database.SQLite.Simple (Query) import Database.SQLite.Simple.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20230926_contact_status.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20230926_contact_status.hs similarity index 83% rename from src/Simplex/Chat/Migrations/M20230926_contact_status.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20230926_contact_status.hs index b6c5dd9557..0ec499e5f6 100644 --- a/src/Simplex/Chat/Migrations/M20230926_contact_status.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20230926_contact_status.hs @@ -1,6 +1,6 @@ {-# LANGUAGE QuasiQuotes #-} -module Simplex.Chat.Migrations.M20230926_contact_status where +module Simplex.Chat.Store.SQLite.Migrations.M20230926_contact_status where import Database.SQLite.Simple (Query) import Database.SQLite.Simple.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20231002_conn_initiated.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20231002_conn_initiated.hs similarity index 91% rename from src/Simplex/Chat/Migrations/M20231002_conn_initiated.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20231002_conn_initiated.hs index a0f6009af2..c7c84717bd 100644 --- a/src/Simplex/Chat/Migrations/M20231002_conn_initiated.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20231002_conn_initiated.hs @@ -1,6 +1,6 @@ {-# LANGUAGE QuasiQuotes #-} -module Simplex.Chat.Migrations.M20231002_conn_initiated where +module Simplex.Chat.Store.SQLite.Migrations.M20231002_conn_initiated where import Database.SQLite.Simple (Query) import Database.SQLite.Simple.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20231009_via_group_link_uri_hash.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20231009_via_group_link_uri_hash.hs similarity index 88% rename from src/Simplex/Chat/Migrations/M20231009_via_group_link_uri_hash.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20231009_via_group_link_uri_hash.hs index 41c9887a04..87111c77d4 100644 --- a/src/Simplex/Chat/Migrations/M20231009_via_group_link_uri_hash.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20231009_via_group_link_uri_hash.hs @@ -1,6 +1,6 @@ {-# LANGUAGE QuasiQuotes #-} -module Simplex.Chat.Migrations.M20231009_via_group_link_uri_hash where +module Simplex.Chat.Store.SQLite.Migrations.M20231009_via_group_link_uri_hash where import Database.SQLite.Simple (Query) import Database.SQLite.Simple.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20231010_member_settings.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20231010_member_settings.hs similarity index 84% rename from src/Simplex/Chat/Migrations/M20231010_member_settings.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20231010_member_settings.hs index e31203e572..faf6639ce3 100644 --- a/src/Simplex/Chat/Migrations/M20231010_member_settings.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20231010_member_settings.hs @@ -1,6 +1,6 @@ {-# LANGUAGE QuasiQuotes #-} -module Simplex.Chat.Migrations.M20231010_member_settings where +module Simplex.Chat.Store.SQLite.Migrations.M20231010_member_settings where import Database.SQLite.Simple (Query) import Database.SQLite.Simple.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20231019_indexes.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20231019_indexes.hs similarity index 93% rename from src/Simplex/Chat/Migrations/M20231019_indexes.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20231019_indexes.hs index 40412e1778..cb1f5ec104 100644 --- a/src/Simplex/Chat/Migrations/M20231019_indexes.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20231019_indexes.hs @@ -1,6 +1,6 @@ {-# LANGUAGE QuasiQuotes #-} -module Simplex.Chat.Migrations.M20231019_indexes where +module Simplex.Chat.Store.SQLite.Migrations.M20231019_indexes where import Database.SQLite.Simple (Query) import Database.SQLite.Simple.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20231030_xgrplinkmem_received.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20231030_xgrplinkmem_received.hs similarity index 84% rename from src/Simplex/Chat/Migrations/M20231030_xgrplinkmem_received.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20231030_xgrplinkmem_received.hs index cf4aee2531..1a82aa6d70 100644 --- a/src/Simplex/Chat/Migrations/M20231030_xgrplinkmem_received.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20231030_xgrplinkmem_received.hs @@ -1,6 +1,6 @@ {-# LANGUAGE QuasiQuotes #-} -module Simplex.Chat.Migrations.M20231030_xgrplinkmem_received where +module Simplex.Chat.Store.SQLite.Migrations.M20231030_xgrplinkmem_received where import Database.SQLite.Simple (Query) import Database.SQLite.Simple.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20231107_indexes.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20231107_indexes.hs similarity index 84% rename from src/Simplex/Chat/Migrations/M20231107_indexes.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20231107_indexes.hs index a4c9c5295a..07e6ce5888 100644 --- a/src/Simplex/Chat/Migrations/M20231107_indexes.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20231107_indexes.hs @@ -1,6 +1,6 @@ {-# LANGUAGE QuasiQuotes #-} -module Simplex.Chat.Migrations.M20231107_indexes where +module Simplex.Chat.Store.SQLite.Migrations.M20231107_indexes where import Database.SQLite.Simple (Query) import Database.SQLite.Simple.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20231113_group_forward.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20231113_group_forward.hs similarity index 97% rename from src/Simplex/Chat/Migrations/M20231113_group_forward.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20231113_group_forward.hs index f23387f011..b83a2c780d 100644 --- a/src/Simplex/Chat/Migrations/M20231113_group_forward.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20231113_group_forward.hs @@ -1,6 +1,6 @@ {-# LANGUAGE QuasiQuotes #-} -module Simplex.Chat.Migrations.M20231113_group_forward where +module Simplex.Chat.Store.SQLite.Migrations.M20231113_group_forward where import Database.SQLite.Simple (Query) import Database.SQLite.Simple.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20231114_remote_control.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20231114_remote_control.hs similarity index 95% rename from src/Simplex/Chat/Migrations/M20231114_remote_control.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20231114_remote_control.hs index e716b2aa63..f002c9dd3d 100644 --- a/src/Simplex/Chat/Migrations/M20231114_remote_control.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20231114_remote_control.hs @@ -1,6 +1,6 @@ {-# LANGUAGE QuasiQuotes #-} -module Simplex.Chat.Migrations.M20231114_remote_control where +module Simplex.Chat.Store.SQLite.Migrations.M20231114_remote_control where import Database.SQLite.Simple (Query) import Database.SQLite.Simple.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20231126_remote_ctrl_address.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20231126_remote_ctrl_address.hs similarity index 88% rename from src/Simplex/Chat/Migrations/M20231126_remote_ctrl_address.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20231126_remote_ctrl_address.hs index 343e4ca6fa..98a464219c 100644 --- a/src/Simplex/Chat/Migrations/M20231126_remote_ctrl_address.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20231126_remote_ctrl_address.hs @@ -1,6 +1,6 @@ {-# LANGUAGE QuasiQuotes #-} -module Simplex.Chat.Migrations.M20231126_remote_ctrl_address where +module Simplex.Chat.Store.SQLite.Migrations.M20231126_remote_ctrl_address where import Database.SQLite.Simple (Query) import Database.SQLite.Simple.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20231207_chat_list_pagination.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20231207_chat_list_pagination.hs similarity index 92% rename from src/Simplex/Chat/Migrations/M20231207_chat_list_pagination.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20231207_chat_list_pagination.hs index 9a8944c5c5..f02be82919 100644 --- a/src/Simplex/Chat/Migrations/M20231207_chat_list_pagination.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20231207_chat_list_pagination.hs @@ -1,6 +1,6 @@ {-# LANGUAGE QuasiQuotes #-} -module Simplex.Chat.Migrations.M20231207_chat_list_pagination where +module Simplex.Chat.Store.SQLite.Migrations.M20231207_chat_list_pagination where import Database.SQLite.Simple (Query) import Database.SQLite.Simple.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20231214_item_content_tag.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20231214_item_content_tag.hs similarity index 83% rename from src/Simplex/Chat/Migrations/M20231214_item_content_tag.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20231214_item_content_tag.hs index cd4cd136e5..06ef294702 100644 --- a/src/Simplex/Chat/Migrations/M20231214_item_content_tag.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20231214_item_content_tag.hs @@ -1,6 +1,6 @@ {-# LANGUAGE QuasiQuotes #-} -module Simplex.Chat.Migrations.M20231214_item_content_tag where +module Simplex.Chat.Store.SQLite.Migrations.M20231214_item_content_tag where import Database.SQLite.Simple (Query) import Database.SQLite.Simple.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20231215_recreate_msg_deliveries.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20231215_recreate_msg_deliveries.hs similarity index 98% rename from src/Simplex/Chat/Migrations/M20231215_recreate_msg_deliveries.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20231215_recreate_msg_deliveries.hs index 4b39606d8d..fa2d55e7bb 100644 --- a/src/Simplex/Chat/Migrations/M20231215_recreate_msg_deliveries.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20231215_recreate_msg_deliveries.hs @@ -1,6 +1,6 @@ {-# LANGUAGE QuasiQuotes #-} -module Simplex.Chat.Migrations.M20231215_recreate_msg_deliveries where +module Simplex.Chat.Store.SQLite.Migrations.M20231215_recreate_msg_deliveries where import Database.SQLite.Simple (Query) import Database.SQLite.Simple.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20240102_note_folders.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20240102_note_folders.hs similarity index 94% rename from src/Simplex/Chat/Migrations/M20240102_note_folders.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20240102_note_folders.hs index 02ad741662..f06b3aa0ed 100644 --- a/src/Simplex/Chat/Migrations/M20240102_note_folders.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20240102_note_folders.hs @@ -1,6 +1,6 @@ {-# LANGUAGE QuasiQuotes #-} -module Simplex.Chat.Migrations.M20240102_note_folders where +module Simplex.Chat.Store.SQLite.Migrations.M20240102_note_folders where import Database.SQLite.Simple (Query) import Database.SQLite.Simple.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20240104_members_profile_update.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20240104_members_profile_update.hs similarity index 86% rename from src/Simplex/Chat/Migrations/M20240104_members_profile_update.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20240104_members_profile_update.hs index 5591c4bdcd..9e9f813a22 100644 --- a/src/Simplex/Chat/Migrations/M20240104_members_profile_update.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20240104_members_profile_update.hs @@ -1,6 +1,6 @@ {-# LANGUAGE QuasiQuotes #-} -module Simplex.Chat.Migrations.M20240104_members_profile_update where +module Simplex.Chat.Store.SQLite.Migrations.M20240104_members_profile_update where import Database.SQLite.Simple (Query) import Database.SQLite.Simple.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20240115_block_member_for_all.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20240115_block_member_for_all.hs similarity index 83% rename from src/Simplex/Chat/Migrations/M20240115_block_member_for_all.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20240115_block_member_for_all.hs index af2448e42c..9a43ffa55e 100644 --- a/src/Simplex/Chat/Migrations/M20240115_block_member_for_all.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20240115_block_member_for_all.hs @@ -1,6 +1,6 @@ {-# LANGUAGE QuasiQuotes #-} -module Simplex.Chat.Migrations.M20240115_block_member_for_all where +module Simplex.Chat.Store.SQLite.Migrations.M20240115_block_member_for_all where import Database.SQLite.Simple (Query) import Database.SQLite.Simple.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20240122_indexes.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20240122_indexes.hs similarity index 93% rename from src/Simplex/Chat/Migrations/M20240122_indexes.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20240122_indexes.hs index 7b708f8bbe..cefc5eda7b 100644 --- a/src/Simplex/Chat/Migrations/M20240122_indexes.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20240122_indexes.hs @@ -1,6 +1,6 @@ {-# LANGUAGE QuasiQuotes #-} -module Simplex.Chat.Migrations.M20240122_indexes where +module Simplex.Chat.Store.SQLite.Migrations.M20240122_indexes where import Database.SQLite.Simple (Query) import Database.SQLite.Simple.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20240214_redirect_file_id.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20240214_redirect_file_id.hs similarity index 87% rename from src/Simplex/Chat/Migrations/M20240214_redirect_file_id.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20240214_redirect_file_id.hs index da8f4d413b..010cee5ca7 100644 --- a/src/Simplex/Chat/Migrations/M20240214_redirect_file_id.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20240214_redirect_file_id.hs @@ -1,6 +1,6 @@ {-# LANGUAGE QuasiQuotes #-} -module Simplex.Chat.Migrations.M20240214_redirect_file_id where +module Simplex.Chat.Store.SQLite.Migrations.M20240214_redirect_file_id where import Database.SQLite.Simple (Query) import Database.SQLite.Simple.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20240222_app_settings.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20240222_app_settings.hs similarity index 82% rename from src/Simplex/Chat/Migrations/M20240222_app_settings.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20240222_app_settings.hs index e7fda06a2e..caa9b8ab77 100644 --- a/src/Simplex/Chat/Migrations/M20240222_app_settings.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20240222_app_settings.hs @@ -1,6 +1,6 @@ {-# LANGUAGE QuasiQuotes #-} -module Simplex.Chat.Migrations.M20240222_app_settings where +module Simplex.Chat.Store.SQLite.Migrations.M20240222_app_settings where import Database.SQLite.Simple (Query) import Database.SQLite.Simple.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20240226_users_restrict.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20240226_users_restrict.hs similarity index 89% rename from src/Simplex/Chat/Migrations/M20240226_users_restrict.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20240226_users_restrict.hs index a68923142c..eb1bc2bfea 100644 --- a/src/Simplex/Chat/Migrations/M20240226_users_restrict.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20240226_users_restrict.hs @@ -1,6 +1,6 @@ {-# LANGUAGE QuasiQuotes #-} -module Simplex.Chat.Migrations.M20240226_users_restrict where +module Simplex.Chat.Store.SQLite.Migrations.M20240226_users_restrict where import Database.SQLite.Simple (Query) import Database.SQLite.Simple.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20240228_pq.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20240228_pq.hs similarity index 93% rename from src/Simplex/Chat/Migrations/M20240228_pq.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20240228_pq.hs index c496d33b4b..5be3dcc458 100644 --- a/src/Simplex/Chat/Migrations/M20240228_pq.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20240228_pq.hs @@ -1,6 +1,6 @@ {-# LANGUAGE QuasiQuotes #-} -module Simplex.Chat.Migrations.M20240228_pq where +module Simplex.Chat.Store.SQLite.Migrations.M20240228_pq where import Database.SQLite.Simple (Query) import Database.SQLite.Simple.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20240313_drop_agent_ack_cmd_id.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20240313_drop_agent_ack_cmd_id.hs similarity index 87% rename from src/Simplex/Chat/Migrations/M20240313_drop_agent_ack_cmd_id.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20240313_drop_agent_ack_cmd_id.hs index c14f08447e..6c5d82ab80 100644 --- a/src/Simplex/Chat/Migrations/M20240313_drop_agent_ack_cmd_id.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20240313_drop_agent_ack_cmd_id.hs @@ -1,6 +1,6 @@ {-# LANGUAGE QuasiQuotes #-} -module Simplex.Chat.Migrations.M20240313_drop_agent_ack_cmd_id where +module Simplex.Chat.Store.SQLite.Migrations.M20240313_drop_agent_ack_cmd_id where import Database.SQLite.Simple (Query) import Database.SQLite.Simple.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20240324_custom_data.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20240324_custom_data.hs similarity index 85% rename from src/Simplex/Chat/Migrations/M20240324_custom_data.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20240324_custom_data.hs index bc1c4807eb..e084920ab0 100644 --- a/src/Simplex/Chat/Migrations/M20240324_custom_data.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20240324_custom_data.hs @@ -1,6 +1,6 @@ {-# LANGUAGE QuasiQuotes #-} -module Simplex.Chat.Migrations.M20240324_custom_data where +module Simplex.Chat.Store.SQLite.Migrations.M20240324_custom_data where import Database.SQLite.Simple (Query) import Database.SQLite.Simple.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20240402_item_forwarded.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20240402_item_forwarded.hs similarity index 95% rename from src/Simplex/Chat/Migrations/M20240402_item_forwarded.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20240402_item_forwarded.hs index 850c8be2d9..a32f210e3c 100644 --- a/src/Simplex/Chat/Migrations/M20240402_item_forwarded.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20240402_item_forwarded.hs @@ -1,6 +1,6 @@ {-# LANGUAGE QuasiQuotes #-} -module Simplex.Chat.Migrations.M20240402_item_forwarded where +module Simplex.Chat.Store.SQLite.Migrations.M20240402_item_forwarded where import Database.SQLite.Simple (Query) import Database.SQLite.Simple.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20240430_ui_theme.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20240430_ui_theme.hs similarity index 88% rename from src/Simplex/Chat/Migrations/M20240430_ui_theme.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20240430_ui_theme.hs index 1f4b9805cf..a646582a89 100644 --- a/src/Simplex/Chat/Migrations/M20240430_ui_theme.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20240430_ui_theme.hs @@ -1,6 +1,6 @@ {-# LANGUAGE QuasiQuotes #-} -module Simplex.Chat.Migrations.M20240430_ui_theme where +module Simplex.Chat.Store.SQLite.Migrations.M20240430_ui_theme where import Database.SQLite.Simple (Query) import Database.SQLite.Simple.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20240501_chat_deleted.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20240501_chat_deleted.hs similarity index 83% rename from src/Simplex/Chat/Migrations/M20240501_chat_deleted.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20240501_chat_deleted.hs index a7faf33472..de8135b066 100644 --- a/src/Simplex/Chat/Migrations/M20240501_chat_deleted.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20240501_chat_deleted.hs @@ -1,6 +1,6 @@ {-# LANGUAGE QuasiQuotes #-} -module Simplex.Chat.Migrations.M20240501_chat_deleted where +module Simplex.Chat.Store.SQLite.Migrations.M20240501_chat_deleted where import Database.SQLite.Simple (Query) import Database.SQLite.Simple.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20240510_chat_items_via_proxy.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20240510_chat_items_via_proxy.hs similarity index 86% rename from src/Simplex/Chat/Migrations/M20240510_chat_items_via_proxy.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20240510_chat_items_via_proxy.hs index 3c32034344..a2fc2cef85 100644 --- a/src/Simplex/Chat/Migrations/M20240510_chat_items_via_proxy.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20240510_chat_items_via_proxy.hs @@ -1,6 +1,6 @@ {-# LANGUAGE QuasiQuotes #-} -module Simplex.Chat.Migrations.M20240510_chat_items_via_proxy where +module Simplex.Chat.Store.SQLite.Migrations.M20240510_chat_items_via_proxy where import Database.SQLite.Simple (Query) import Database.SQLite.Simple.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20240515_rcv_files_user_approved_relays.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20240515_rcv_files_user_approved_relays.hs similarity index 83% rename from src/Simplex/Chat/Migrations/M20240515_rcv_files_user_approved_relays.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20240515_rcv_files_user_approved_relays.hs index cd4f647685..341b6c2c41 100644 --- a/src/Simplex/Chat/Migrations/M20240515_rcv_files_user_approved_relays.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20240515_rcv_files_user_approved_relays.hs @@ -1,6 +1,6 @@ {-# LANGUAGE QuasiQuotes #-} -module Simplex.Chat.Migrations.M20240515_rcv_files_user_approved_relays where +module Simplex.Chat.Store.SQLite.Migrations.M20240515_rcv_files_user_approved_relays where import Database.SQLite.Simple (Query) import Database.SQLite.Simple.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20240528_quota_err_counter.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20240528_quota_err_counter.hs similarity index 84% rename from src/Simplex/Chat/Migrations/M20240528_quota_err_counter.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20240528_quota_err_counter.hs index ea1f3a78e7..c4d121e068 100644 --- a/src/Simplex/Chat/Migrations/M20240528_quota_err_counter.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20240528_quota_err_counter.hs @@ -1,6 +1,6 @@ {-# LANGUAGE QuasiQuotes #-} -module Simplex.Chat.Migrations.M20240528_quota_err_counter where +module Simplex.Chat.Store.SQLite.Migrations.M20240528_quota_err_counter where import Database.SQLite.Simple (Query) import Database.SQLite.Simple.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20240827_calls_uuid.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20240827_calls_uuid.hs similarity index 83% rename from src/Simplex/Chat/Migrations/M20240827_calls_uuid.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20240827_calls_uuid.hs index eb1e8db65a..1d24eabb77 100644 --- a/src/Simplex/Chat/Migrations/M20240827_calls_uuid.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20240827_calls_uuid.hs @@ -1,6 +1,6 @@ {-# LANGUAGE QuasiQuotes #-} -module Simplex.Chat.Migrations.M20240827_calls_uuid where +module Simplex.Chat.Store.SQLite.Migrations.M20240827_calls_uuid where import Database.SQLite.Simple (Query) import Database.SQLite.Simple.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20240920_user_order.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20240920_user_order.hs similarity index 83% rename from src/Simplex/Chat/Migrations/M20240920_user_order.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20240920_user_order.hs index 29fd1532f2..02fcf37245 100644 --- a/src/Simplex/Chat/Migrations/M20240920_user_order.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20240920_user_order.hs @@ -1,6 +1,6 @@ {-# LANGUAGE QuasiQuotes #-} -module Simplex.Chat.Migrations.M20240920_user_order where +module Simplex.Chat.Store.SQLite.Migrations.M20240920_user_order where import Database.SQLite.Simple (Query) import Database.SQLite.Simple.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20241008_indexes.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20241008_indexes.hs similarity index 84% rename from src/Simplex/Chat/Migrations/M20241008_indexes.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20241008_indexes.hs index 94cffa8d74..a6a905a703 100644 --- a/src/Simplex/Chat/Migrations/M20241008_indexes.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20241008_indexes.hs @@ -1,6 +1,6 @@ {-# LANGUAGE QuasiQuotes #-} -module Simplex.Chat.Migrations.M20241008_indexes where +module Simplex.Chat.Store.SQLite.Migrations.M20241008_indexes where import Database.SQLite.Simple (Query) import Database.SQLite.Simple.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20241010_contact_requests_contact_id.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20241010_contact_requests_contact_id.hs similarity index 87% rename from src/Simplex/Chat/Migrations/M20241010_contact_requests_contact_id.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20241010_contact_requests_contact_id.hs index 24e7f3a98e..b7a9b74d14 100644 --- a/src/Simplex/Chat/Migrations/M20241010_contact_requests_contact_id.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20241010_contact_requests_contact_id.hs @@ -1,6 +1,6 @@ {-# LANGUAGE QuasiQuotes #-} -module Simplex.Chat.Migrations.M20241010_contact_requests_contact_id where +module Simplex.Chat.Store.SQLite.Migrations.M20241010_contact_requests_contact_id where import Database.SQLite.Simple (Query) import Database.SQLite.Simple.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20241023_chat_item_autoincrement_id.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20241023_chat_item_autoincrement_id.hs similarity index 90% rename from src/Simplex/Chat/Migrations/M20241023_chat_item_autoincrement_id.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20241023_chat_item_autoincrement_id.hs index 7f1e272026..03b5c40ed3 100644 --- a/src/Simplex/Chat/Migrations/M20241023_chat_item_autoincrement_id.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20241023_chat_item_autoincrement_id.hs @@ -1,6 +1,6 @@ {-# LANGUAGE QuasiQuotes #-} -module Simplex.Chat.Migrations.M20241023_chat_item_autoincrement_id where +module Simplex.Chat.Store.SQLite.Migrations.M20241023_chat_item_autoincrement_id where import Database.SQLite.Simple (Query) import Database.SQLite.Simple.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20241027_server_operators.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20241027_server_operators.hs similarity index 96% rename from src/Simplex/Chat/Migrations/M20241027_server_operators.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20241027_server_operators.hs index 1316e3c006..462ab09f5d 100644 --- a/src/Simplex/Chat/Migrations/M20241027_server_operators.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20241027_server_operators.hs @@ -1,6 +1,6 @@ {-# LANGUAGE QuasiQuotes #-} -module Simplex.Chat.Migrations.M20241027_server_operators where +module Simplex.Chat.Store.SQLite.Migrations.M20241027_server_operators where import Database.SQLite.Simple (Query) import Database.SQLite.Simple.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20241125_indexes.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20241125_indexes.hs similarity index 95% rename from src/Simplex/Chat/Migrations/M20241125_indexes.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20241125_indexes.hs index 2115de09a3..e05b111e99 100644 --- a/src/Simplex/Chat/Migrations/M20241125_indexes.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20241125_indexes.hs @@ -1,6 +1,6 @@ {-# LANGUAGE QuasiQuotes #-} -module Simplex.Chat.Migrations.M20241125_indexes where +module Simplex.Chat.Store.SQLite.Migrations.M20241125_indexes where import Database.SQLite.Simple (Query) import Database.SQLite.Simple.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20241128_business_chats.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20241128_business_chats.hs similarity index 91% rename from src/Simplex/Chat/Migrations/M20241128_business_chats.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20241128_business_chats.hs index 2b3be38030..486250295a 100644 --- a/src/Simplex/Chat/Migrations/M20241128_business_chats.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20241128_business_chats.hs @@ -1,6 +1,6 @@ {-# LANGUAGE QuasiQuotes #-} -module Simplex.Chat.Migrations.M20241128_business_chats where +module Simplex.Chat.Store.SQLite.Migrations.M20241128_business_chats where import Database.SQLite.Simple (Query) import Database.SQLite.Simple.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20241205_business_chat_members.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20241205_business_chat_members.hs similarity index 83% rename from src/Simplex/Chat/Migrations/M20241205_business_chat_members.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20241205_business_chat_members.hs index 5d019d73e1..fa0cbe36da 100644 --- a/src/Simplex/Chat/Migrations/M20241205_business_chat_members.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20241205_business_chat_members.hs @@ -1,6 +1,6 @@ {-# LANGUAGE QuasiQuotes #-} -module Simplex.Chat.Migrations.M20241205_business_chat_members where +module Simplex.Chat.Store.SQLite.Migrations.M20241205_business_chat_members where import Database.SQLite.Simple (Query) import Database.SQLite.Simple.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20241222_operator_conditions.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20241222_operator_conditions.hs similarity index 84% rename from src/Simplex/Chat/Migrations/M20241222_operator_conditions.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20241222_operator_conditions.hs index c0c4304313..761d883ead 100644 --- a/src/Simplex/Chat/Migrations/M20241222_operator_conditions.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20241222_operator_conditions.hs @@ -1,6 +1,6 @@ {-# LANGUAGE QuasiQuotes #-} -module Simplex.Chat.Migrations.M20241222_operator_conditions where +module Simplex.Chat.Store.SQLite.Migrations.M20241222_operator_conditions where import Database.SQLite.Simple (Query) import Database.SQLite.Simple.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20241223_chat_tags.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20241223_chat_tags.hs similarity index 95% rename from src/Simplex/Chat/Migrations/M20241223_chat_tags.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20241223_chat_tags.hs index a83be7549d..4c2d4d1745 100644 --- a/src/Simplex/Chat/Migrations/M20241223_chat_tags.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20241223_chat_tags.hs @@ -1,6 +1,6 @@ {-# LANGUAGE QuasiQuotes #-} -module Simplex.Chat.Migrations.M20241223_chat_tags where +module Simplex.Chat.Store.SQLite.Migrations.M20241223_chat_tags where import Database.SQLite.Simple (Query) import Database.SQLite.Simple.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20241230_reports.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20241230_reports.hs similarity index 83% rename from src/Simplex/Chat/Migrations/M20241230_reports.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20241230_reports.hs index 7d605824f5..60c2b51525 100644 --- a/src/Simplex/Chat/Migrations/M20241230_reports.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20241230_reports.hs @@ -1,6 +1,6 @@ {-# LANGUAGE QuasiQuotes #-} -module Simplex.Chat.Migrations.M20241230_reports where +module Simplex.Chat.Store.SQLite.Migrations.M20241230_reports where import Database.SQLite.Simple (Query) import Database.SQLite.Simple.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/M20250105_indexes.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20250105_indexes.hs similarity index 90% rename from src/Simplex/Chat/Migrations/M20250105_indexes.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20250105_indexes.hs index dd01f21389..09e3246292 100644 --- a/src/Simplex/Chat/Migrations/M20250105_indexes.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20250105_indexes.hs @@ -1,6 +1,6 @@ {-# LANGUAGE QuasiQuotes #-} -module Simplex.Chat.Migrations.M20250105_indexes where +module Simplex.Chat.Store.SQLite.Migrations.M20250105_indexes where import Database.SQLite.Simple (Query) import Database.SQLite.Simple.QQ (sql) diff --git a/src/Simplex/Chat/Migrations/chat_lint.sql b/src/Simplex/Chat/Store/SQLite/Migrations/chat_lint.sql similarity index 100% rename from src/Simplex/Chat/Migrations/chat_lint.sql rename to src/Simplex/Chat/Store/SQLite/Migrations/chat_lint.sql diff --git a/src/Simplex/Chat/Migrations/chat_schema.sql b/src/Simplex/Chat/Store/SQLite/Migrations/chat_schema.sql similarity index 100% rename from src/Simplex/Chat/Migrations/chat_schema.sql rename to src/Simplex/Chat/Store/SQLite/Migrations/chat_schema.sql diff --git a/src/Simplex/Chat/Store/Shared.hs b/src/Simplex/Chat/Store/Shared.hs index a2b8fbdf6b..5b56b67704 100644 --- a/src/Simplex/Chat/Store/Shared.hs +++ b/src/Simplex/Chat/Store/Shared.hs @@ -1,3 +1,4 @@ +{-# LANGUAGE CPP #-} {-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE LambdaCase #-} @@ -27,9 +28,6 @@ import Data.Maybe (fromMaybe, isJust, listToMaybe) import Data.Text (Text) import qualified Data.Text as T import Data.Time.Clock (UTCTime (..), getCurrentTime) -import Database.SQLite.Simple (NamedParam (..), Only (..), Query, SQLError, (:.) (..)) -import qualified Database.SQLite.Simple as SQL -import Database.SQLite.Simple.QQ (sql) import Simplex.Chat.Messages import Simplex.Chat.Protocol import Simplex.Chat.Remote.Types @@ -39,7 +37,8 @@ import Simplex.Chat.Types.Shared import Simplex.Chat.Types.UITheme import Simplex.Messaging.Agent.Protocol (ConnId, UserId) 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 Simplex.Messaging.Crypto.Ratchet (PQEncryption (..), PQSupport (..)) import qualified Simplex.Messaging.Crypto.Ratchet as CR @@ -48,6 +47,15 @@ import Simplex.Messaging.Protocol (SubscriptionMode (..)) import Simplex.Messaging.Util (allFinally) import Simplex.Messaging.Version import UnliftIO.STM +#if defined(dbPostgres) +import Database.PostgreSQL.Simple (Only (..), Query, SqlError, (:.) (..)) +import Database.PostgreSQL.Simple.Errors (constraintViolation) +import Database.PostgreSQL.Simple.SqlQQ (sql) +#else +import Database.SQLite.Simple (Only (..), Query, SQLError, (:.) (..)) +import qualified Database.SQLite.Simple as SQL +import Database.SQLite.Simple.QQ (sql) +#endif data ChatLockEntity = CLInvitation ByteString @@ -137,14 +145,32 @@ data StoreError $(J.deriveJSON (sumTypeJSON $ dropPrefix "SE") ''StoreError) insertedRowId :: DB.Connection -> IO Int64 -insertedRowId db = fromOnly . head <$> DB.query_ db "SELECT last_insert_rowid()" +insertedRowId db = fromOnly . head <$> DB.query_ db q + where +#if defined(dbPostgres) + q = "SELECT lastval()" +#else + q = "SELECT last_insert_rowid()" +#endif checkConstraint :: StoreError -> ExceptT StoreError IO a -> ExceptT StoreError IO a checkConstraint err action = ExceptT $ runExceptT action `E.catch` (pure . Left . handleSQLError err) +#if defined(dbPostgres) +type SQLError = SqlError +#endif + +constraintError :: SQLError -> Bool +#if defined(dbPostgres) +constraintError = isJust . constraintViolation +#else +constraintError e = SQL.sqlError e == SQL.ErrorConstraint +#endif +{-# INLINE constraintError #-} + handleSQLError :: StoreError -> SQLError -> StoreError handleSQLError err e - | SQL.sqlError e == SQL.ErrorConstraint = err + | constraintError e = err | otherwise = SEInternalError $ show e storeFinally :: ExceptT StoreError IO a -> ExceptT StoreError IO b -> ExceptT StoreError IO a @@ -168,12 +194,12 @@ toFileInfo (fileId, fileStatus, filePath) = CIFileInfo {fileId, fileStatus, file type EntityIdsRow = (Maybe Int64, Maybe Int64, Maybe Int64, Maybe Int64, Maybe Int64) -type ConnectionRow = (Int64, ConnId, Int, Maybe Int64, Maybe Int64, Bool, Maybe GroupLinkId, Maybe Int64, ConnStatus, ConnType, Bool, LocalAlias) :. EntityIdsRow :. (UTCTime, Maybe Text, Maybe UTCTime, PQSupport, PQEncryption, Maybe PQEncryption, Maybe PQEncryption, Int, Int, Maybe VersionChat, VersionChat, VersionChat) +type ConnectionRow = (Int64, ConnId, Int, Maybe Int64, Maybe Int64, BoolInt, Maybe GroupLinkId, Maybe Int64, ConnStatus, ConnType, BoolInt, LocalAlias) :. EntityIdsRow :. (UTCTime, Maybe Text, Maybe UTCTime, PQSupport, PQEncryption, Maybe PQEncryption, Maybe PQEncryption, Int, Int, Maybe VersionChat, VersionChat, VersionChat) -type MaybeConnectionRow = (Maybe Int64, Maybe ConnId, Maybe Int, Maybe Int64, Maybe Int64, Maybe Bool, Maybe GroupLinkId, Maybe Int64, Maybe ConnStatus, Maybe ConnType, Maybe Bool, Maybe LocalAlias) :. EntityIdsRow :. (Maybe UTCTime, Maybe Text, Maybe UTCTime, Maybe PQSupport, Maybe PQEncryption, Maybe PQEncryption, Maybe PQEncryption, Maybe Int, Maybe Int, Maybe VersionChat, Maybe VersionChat, Maybe VersionChat) +type MaybeConnectionRow = (Maybe Int64, Maybe ConnId, Maybe Int, Maybe Int64, Maybe Int64, Maybe BoolInt, Maybe GroupLinkId, Maybe Int64, Maybe ConnStatus, Maybe ConnType, Maybe BoolInt, Maybe LocalAlias) :. EntityIdsRow :. (Maybe UTCTime, Maybe Text, Maybe UTCTime, Maybe PQSupport, Maybe PQEncryption, Maybe PQEncryption, Maybe PQEncryption, Maybe Int, Maybe Int, Maybe VersionChat, Maybe VersionChat, Maybe VersionChat) toConnection :: VersionRangeChat -> ConnectionRow -> Connection -toConnection vr ((connId, acId, connLevel, viaContact, viaUserContactLink, viaGroupLink, groupLinkId, customUserProfileId, connStatus, connType, contactConnInitiated, localAlias) :. (contactId, groupMemberId, sndFileId, rcvFileId, userContactLinkId) :. (createdAt, code_, verifiedAt_, pqSupport, pqEncryption, pqSndEnabled, pqRcvEnabled, authErrCounter, quotaErrCounter, chatV, minVer, maxVer)) = +toConnection vr ((connId, acId, connLevel, viaContact, viaUserContactLink, BI viaGroupLink, groupLinkId, customUserProfileId, connStatus, connType, BI contactConnInitiated, localAlias) :. (contactId, groupMemberId, sndFileId, rcvFileId, userContactLinkId) :. (createdAt, code_, verifiedAt_, pqSupport, pqEncryption, pqSndEnabled, pqRcvEnabled, authErrCounter, quotaErrCounter, chatV, minVer, maxVer)) = Connection { connId, agentConnId = AgentConnId acId, @@ -227,9 +253,9 @@ createConnection_ db userId connType entityId acId connStatus connChatVersion pe conn_chat_version, peer_chat_min_version, peer_chat_max_version, to_subscribe, pq_support, pq_encryption ) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) |] - ( (userId, acId, connLevel, viaContact, viaUserContactLink, viaGroupLink, customUserProfileId, connStatus, connType) + ( (userId, acId, connLevel, viaContact, viaUserContactLink, BI viaGroupLink, customUserProfileId, connStatus, connType) :. (ent ConnContact, ent ConnMember, ent ConnSndFile, ent ConnRcvFile, ent ConnUserContact, currentTs, currentTs) - :. (connChatVersion, minV, maxV, subMode == SMOnlyCreate, pqSup, pqSup) + :. (connChatVersion, minV, maxV, BI (subMode == SMOnlyCreate), pqSup, pqSup) ) connId <- insertedRowId db pure @@ -269,7 +295,7 @@ createIncognitoProfile_ db userId createdAt Profile {displayName, fullName, imag INSERT INTO contact_profiles (display_name, full_name, image, user_id, incognito, created_at, updated_at) VALUES (?,?,?,?,?,?,?) |] - (displayName, fullName, image, userId, Just True, createdAt, createdAt) + (displayName, fullName, image, userId, Just (BI True), createdAt, createdAt) insertedRowId db updateConnSupportPQ :: DB.Connection -> Int64 -> PQSupport -> PQEncryption -> IO () @@ -366,37 +392,37 @@ createContact_ db userId Profile {displayName, fullName, image, contactLink, pre DB.execute db "INSERT INTO contacts (contact_profile_id, local_display_name, user_id, via_group, created_at, updated_at, chat_ts, contact_used) VALUES (?,?,?,?,?,?,?,?)" - (profileId, ldn, userId, viaGroup, currentTs, currentTs, currentTs, contactUsed) + (profileId, ldn, userId, viaGroup, currentTs, currentTs, currentTs, BI contactUsed) contactId <- insertedRowId db pure $ Right (ldn, contactId, profileId) deleteUnusedIncognitoProfileById_ :: DB.Connection -> User -> ProfileId -> IO () deleteUnusedIncognitoProfileById_ db User {userId} profileId = - DB.executeNamed + DB.execute db [sql| DELETE FROM contact_profiles - WHERE user_id = :user_id AND contact_profile_id = :profile_id AND incognito = 1 + WHERE user_id = ? AND contact_profile_id = ? AND incognito = 1 AND 1 NOT IN ( 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 ( SELECT 1 FROM group_members - WHERE user_id = :user_id AND member_profile_id = :profile_id LIMIT 1 + WHERE user_id = ? AND member_profile_id = ? LIMIT 1 ) |] - [":user_id" := userId, ":profile_id" := profileId] + (userId, profileId, userId, profileId, userId, profileId) -type ContactRow' = (ProfileId, ContactName, Maybe Int64, ContactName, Text, Maybe ImageData, Maybe ConnReqContact, LocalAlias, Bool, ContactStatus) :. (Maybe MsgFilter, Maybe Bool, Bool, Maybe Preferences, Preferences, UTCTime, UTCTime, Maybe UTCTime) :. (Maybe GroupMemberId, Bool, Maybe UIThemeEntityOverrides, Bool, Maybe CustomData) +type ContactRow' = (ProfileId, ContactName, Maybe Int64, ContactName, Text, Maybe ImageData, Maybe ConnReqContact, LocalAlias, BoolInt, ContactStatus) :. (Maybe MsgFilter, Maybe BoolInt, BoolInt, Maybe Preferences, Preferences, UTCTime, UTCTime, Maybe UTCTime) :. (Maybe GroupMemberId, BoolInt, Maybe UIThemeEntityOverrides, BoolInt, Maybe CustomData) type ContactRow = Only ContactId :. ContactRow' toContact :: VersionRangeChat -> User -> [ChatTagId] -> ContactRow :. MaybeConnectionRow -> Contact -toContact vr user chatTags ((Only contactId :. (profileId, localDisplayName, viaGroup, displayName, fullName, image, contactLink, localAlias, contactUsed, contactStatus) :. (enableNtfs_, sendRcpts, favorite, preferences, userPreferences, createdAt, updatedAt, chatTs) :. (contactGroupMemberId, contactGrpInvSent, uiThemes, chatDeleted, customData)) :. connRow) = +toContact vr user chatTags ((Only contactId :. (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)) :. connRow) = let profile = LocalProfile {profileId, displayName, fullName, image, contactLink, preferences, localAlias} activeConn = toMaybeConnection vr connRow - chatSettings = ChatSettings {enableNtfs = fromMaybe MFAll enableNtfs_, sendRcpts, favorite} + chatSettings = ChatSettings {enableNtfs = fromMaybe MFAll enableNtfs_, sendRcpts = unBI <$> sendRcpts, favorite} incognito = maybe False connIncognito activeConn mergedPreferences = contactUserPreferences user userPreferences preferences incognito in Contact {contactId, localDisplayName, profile, activeConn, viaGroup, contactUsed, contactStatus, chatSettings, userPreferences, mergedPreferences, createdAt, updatedAt, chatTs, contactGroupMemberId, contactGrpInvSent, chatTags, uiThemes, chatDeleted, customData} @@ -434,8 +460,8 @@ userQuery = JOIN contact_profiles ucp ON ucp.contact_profile_id = uct.contact_profile_id |] -toUser :: (UserId, UserId, ContactId, ProfileId, Bool, Int64, ContactName, Text, Maybe ImageData, Maybe ConnReqContact, Maybe Preferences) :. (Bool, Bool, Bool, Maybe B64UrlByteString, Maybe B64UrlByteString, Maybe UTCTime, Maybe UIThemeEntityOverrides) -> User -toUser ((userId, auId, userContactId, profileId, activeUser, activeOrder, displayName, fullName, image, contactLink, userPreferences) :. (showNtfs, sendRcptsContacts, sendRcptsSmallGroups, viewPwdHash_, viewPwdSalt_, userMemberProfileUpdatedAt, uiThemes)) = +toUser :: (UserId, UserId, ContactId, ProfileId, BoolInt, Int64, ContactName, Text, Maybe ImageData, Maybe ConnReqContact, Maybe Preferences) :. (BoolInt, BoolInt, BoolInt, Maybe B64UrlByteString, Maybe B64UrlByteString, Maybe UTCTime, Maybe UIThemeEntityOverrides) -> User +toUser ((userId, auId, userContactId, profileId, BI activeUser, activeOrder, displayName, fullName, image, contactLink, userPreferences) :. (BI showNtfs, BI sendRcptsContacts, BI sendRcptsSmallGroups, viewPwdHash_, viewPwdSalt_, userMemberProfileUpdatedAt, uiThemes)) = User {userId, agentUserId = AgentUserId auId, userContactId, localDisplayName = displayName, profile, activeUser, activeOrder, fullPreferences, showNtfs, sendRcptsContacts, sendRcptsSmallGroups, viewPwdHash, userMemberProfileUpdatedAt, uiThemes} where profile = LocalProfile {profileId, displayName, fullName, image, contactLink, preferences = userPreferences, localAlias = ""} @@ -462,15 +488,15 @@ withLocalDisplayName db userId displayName action = getLdnSuffix >>= (`tryCreate getLdnSuffix :: IO Int getLdnSuffix = maybe 0 ((+ 1) . fromOnly) . listToMaybe - <$> DB.queryNamed + <$> DB.query db [sql| SELECT ldn_suffix FROM display_names - WHERE user_id = :user_id AND ldn_base = :display_name + WHERE user_id = ? AND ldn_base = ? ORDER BY ldn_suffix DESC LIMIT 1 |] - [":user_id" := userId, ":display_name" := displayName] + (userId, displayName) tryCreateName :: Int -> Int -> IO (Either StoreError a) tryCreateName _ 0 = pure $ Left SEDuplicateName tryCreateName ldnSuffix attempts = do @@ -479,7 +505,7 @@ withLocalDisplayName db userId displayName action = getLdnSuffix >>= (`tryCreate E.try (insertName ldn currentTs) >>= \case Right () -> action ldn Left e - | SQL.sqlError e == SQL.ErrorConstraint -> tryCreateName (ldnSuffix + 1) (attempts - 1) + | constraintError e -> tryCreateName (ldnSuffix + 1) (attempts - 1) | otherwise -> E.throwIO e where insertName ldn ts = @@ -511,7 +537,7 @@ createWithRandomBytes' size gVar create = tryCreate 3 liftIO (E.try $ create id') >>= \case Right x -> liftEither x Left e - | SQL.sqlError e == SQL.ErrorConstraint -> tryCreate (n - 1) + | constraintError e -> tryCreate (n - 1) | otherwise -> throwError . SEInternalError $ show e encodedRandomBytes :: TVar ChaChaDRG -> Int -> IO ByteString @@ -549,21 +575,21 @@ safeDeleteLDN db User {userId} localDisplayName = do type BusinessChatInfoRow = (Maybe BusinessChatType, Maybe MemberId, Maybe MemberId) -type GroupInfoRow = (Int64, GroupName, GroupName, Text, Maybe Text, Maybe ImageData, Maybe ProfileId, Maybe MsgFilter, Maybe Bool, Bool, Maybe GroupPreferences) :. (UTCTime, UTCTime, Maybe UTCTime, Maybe UTCTime) :. BusinessChatInfoRow :. (Maybe UIThemeEntityOverrides, Maybe CustomData) :. GroupMemberRow +type GroupInfoRow = (Int64, GroupName, GroupName, Text, Maybe Text, Maybe ImageData, Maybe ProfileId, Maybe MsgFilter, Maybe BoolInt, BoolInt, Maybe GroupPreferences) :. (UTCTime, UTCTime, Maybe UTCTime, Maybe UTCTime) :. BusinessChatInfoRow :. (Maybe UIThemeEntityOverrides, Maybe CustomData) :. GroupMemberRow -type GroupMemberRow = ((Int64, Int64, MemberId, VersionChat, VersionChat, GroupMemberRole, GroupMemberCategory, GroupMemberStatus, Bool, Maybe MemberRestrictionStatus) :. (Maybe Int64, Maybe GroupMemberId, ContactName, Maybe ContactId, ProfileId, ProfileId, ContactName, Text, Maybe ImageData, Maybe ConnReqContact, LocalAlias, Maybe Preferences)) +type GroupMemberRow = ((Int64, Int64, MemberId, VersionChat, VersionChat, GroupMemberRole, GroupMemberCategory, GroupMemberStatus, BoolInt, Maybe MemberRestrictionStatus) :. (Maybe Int64, Maybe GroupMemberId, ContactName, Maybe ContactId, ProfileId, ProfileId, ContactName, Text, Maybe ImageData, Maybe ConnReqContact, LocalAlias, Maybe Preferences)) toGroupInfo :: VersionRangeChat -> Int64 -> [ChatTagId] -> GroupInfoRow -> GroupInfo -toGroupInfo vr userContactId chatTags ((groupId, localDisplayName, displayName, fullName, description, image, hostConnCustomUserProfileId, enableNtfs_, sendRcpts, favorite, groupPreferences) :. (createdAt, updatedAt, chatTs, userMemberProfileSentAt) :. businessRow :. (uiThemes, customData) :. userMemberRow) = +toGroupInfo vr userContactId chatTags ((groupId, localDisplayName, displayName, fullName, description, image, hostConnCustomUserProfileId, enableNtfs_, sendRcpts, BI favorite, groupPreferences) :. (createdAt, updatedAt, chatTs, userMemberProfileSentAt) :. businessRow :. (uiThemes, customData) :. userMemberRow) = let membership = (toGroupMember userContactId userMemberRow) {memberChatVRange = vr} - chatSettings = ChatSettings {enableNtfs = fromMaybe MFAll enableNtfs_, sendRcpts, favorite} + chatSettings = ChatSettings {enableNtfs = fromMaybe MFAll enableNtfs_, sendRcpts = unBI <$> sendRcpts, favorite} fullGroupPreferences = mergeGroupPreferences groupPreferences groupProfile = GroupProfile {displayName, fullName, description, image, groupPreferences} businessChat = toBusinessChatInfo businessRow in GroupInfo {groupId, localDisplayName, groupProfile, businessChat, fullGroupPreferences, membership, hostConnCustomUserProfileId, chatSettings, createdAt, updatedAt, chatTs, userMemberProfileSentAt, chatTags, uiThemes, customData} toGroupMember :: Int64 -> GroupMemberRow -> GroupMember -toGroupMember userContactId ((groupMemberId, groupId, memberId, minVer, maxVer, memberRole, memberCategory, memberStatus, showMessages, memberRestriction_) :. (invitedById, invitedByGroupMemberId, localDisplayName, memberContactId, memberContactProfileId, profileId, displayName, fullName, image, contactLink, localAlias, preferences)) = +toGroupMember userContactId ((groupMemberId, groupId, memberId, minVer, maxVer, memberRole, memberCategory, memberStatus, BI showMessages, memberRestriction_) :. (invitedById, invitedByGroupMemberId, localDisplayName, memberContactId, memberContactProfileId, profileId, displayName, fullName, image, contactLink, localAlias, preferences)) = let memberProfile = LocalProfile {profileId, displayName, fullName, image, contactLink, preferences, localAlias} memberSettings = GroupMemberSettings {showMessages} blockedByAdmin = maybe False mrsBlocked memberRestriction_ diff --git a/src/Simplex/Chat/Terminal.hs b/src/Simplex/Chat/Terminal.hs index 2ff9e60699..829bdc3d31 100644 --- a/src/Simplex/Chat/Terminal.hs +++ b/src/Simplex/Chat/Terminal.hs @@ -1,3 +1,4 @@ +{-# LANGUAGE CPP #-} {-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE NamedFieldPuns #-} @@ -6,14 +7,8 @@ module Simplex.Chat.Terminal where -import Control.Exception (handle, throwIO) import Control.Monad -import qualified Data.ByteArray as BA import qualified Data.List.NonEmpty as L -import qualified Data.Text as T -import Data.Text.Encoding (encodeUtf8) -import Database.SQLite.Simple (SQLError (..)) -import qualified Database.SQLite.Simple as DB import Simplex.Chat (defaultChatConfig, operatorSimpleXChat) import Simplex.Chat.Controller import Simplex.Chat.Core @@ -21,12 +16,21 @@ import Simplex.Chat.Help (chatWelcome) import Simplex.Chat.Library.Commands (_defaultNtfServers) import Simplex.Chat.Operators import Simplex.Chat.Options +import Simplex.Chat.Options.DB import Simplex.Chat.Terminal.Input import Simplex.Chat.Terminal.Output import Simplex.FileTransfer.Client.Presets (defaultXFTPServers) import Simplex.Messaging.Client (NetworkConfig (..), SMPProxyFallback (..), SMPProxyMode (..), defaultNetworkConfig) import Simplex.Messaging.Util (raceAny_) +#if !defined(dbPostgres) +import Control.Exception (handle, throwIO) +import qualified Data.ByteArray as BA +import qualified Data.Text as T +import Data.Text.Encoding (encodeUtf8) +import Database.SQLite.Simple (SQLError (..)) +import qualified Database.SQLite.Simple as DB import System.IO (hFlush, hSetEcho, stdin, stdout) +#endif terminalChatConfig :: ChatConfig terminalChatConfig = @@ -61,7 +65,14 @@ terminalChatConfig = simplexChatTerminal :: WithTerminal t => ChatConfig -> ChatOpts -> t -> IO () simplexChatTerminal cfg options t = run options where - run opts@ChatOpts {coreOptions = coreOptions@CoreChatOpts {dbKey}} = +#if defined(dbPostgres) + run opts = + simplexChatCore cfg opts $ \u cc -> do + ct <- newChatTerminal t opts + when (firstTime cc) . printToTerminal ct $ chatWelcome u + runChatTerminal ct cc opts +#else + run opts@ChatOpts {coreOptions = coreOptions@CoreChatOpts {dbOptions}} = handle checkDBKeyError . simplexChatCore cfg opts $ \u cc -> do ct <- newChatTerminal t opts when (firstTime cc) . printToTerminal ct $ chatWelcome u @@ -70,7 +81,7 @@ simplexChatTerminal cfg options t = run options checkDBKeyError :: SQLError -> IO () checkDBKeyError e = case sqlError e of DB.ErrorNotADatabase -> do - putStrLn $ "Database file is invalid or " <> if BA.null dbKey then "encrypted." else "you passed an incorrect encryption key." + putStrLn $ "Database file is invalid or " <> if BA.null (dbKey dbOptions) then "encrypted." else "you passed an incorrect encryption key." run =<< getKeyOpts _ -> throwIO e getKeyOpts :: IO ChatOpts @@ -81,7 +92,8 @@ simplexChatTerminal cfg options t = run options key <- getLine hSetEcho stdin True putStrLn "" - pure opts {coreOptions = coreOptions {dbKey = BA.convert $ encodeUtf8 $ T.pack key}} + pure opts {coreOptions = coreOptions {dbOptions = dbOptions {dbKey = BA.convert $ encodeUtf8 $ T.pack key}}} +#endif runChatTerminal :: ChatTerminal -> ChatController -> ChatOpts -> IO () runChatTerminal ct cc opts = raceAny_ [runTerminalInput ct cc, runTerminalOutput ct cc opts, runInputLoop ct cc] diff --git a/src/Simplex/Chat/Terminal/Input.hs b/src/Simplex/Chat/Terminal/Input.hs index 073c604009..bf48d1d4f5 100644 --- a/src/Simplex/Chat/Terminal/Input.hs +++ b/src/Simplex/Chat/Terminal/Input.hs @@ -1,3 +1,4 @@ +{-# LANGUAGE CPP #-} {-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE GADTs #-} @@ -25,9 +26,6 @@ import Data.Maybe (isJust, isNothing) import Data.Text (Text) import qualified Data.Text as T import Data.Text.Encoding (encodeUtf8) -import Database.SQLite.Simple (Only (..)) -import qualified Database.SQLite.Simple as SQL -import Database.SQLite.Simple.QQ (sql) import GHC.Weak (deRefWeak) import Simplex.Chat.Controller import Simplex.Chat.Library.Commands @@ -36,12 +34,19 @@ import Simplex.Chat.Messages.CIContent import Simplex.Chat.Styled import Simplex.Chat.Terminal.Output import Simplex.Chat.Types (User (..)) -import Simplex.Messaging.Agent.Store.SQLite.Common (DBStore, withTransaction) -import qualified Simplex.Messaging.Agent.Store.SQLite.DB as DB +import Simplex.Messaging.Agent.Store.Common (DBStore, withTransaction) +import qualified Simplex.Messaging.Agent.Store.DB as DB import Simplex.Messaging.Util (catchAll_, safeDecodeUtf8, whenM) import System.Exit (exitSuccess) import System.Terminal hiding (insertChars) import UnliftIO.STM +#if defined(dbPostgres) +import Database.PostgreSQL.Simple (Only (..), Query, ToRow) +import Database.PostgreSQL.Simple.SqlQQ (sql) +#else +import Database.SQLite.Simple (Only (..), Query, ToRow) +import Database.SQLite.Simple.QQ (sql) +#endif getKey :: MonadTerminal m => m (Key, Modifiers) getKey = @@ -321,7 +326,7 @@ updateTermState user_ st chatPrefix live tw (key, ms) ts@TerminalState {inputStr getNameSfxs table pfx = getNameSfxs_ pfx (userId, pfx <> "%") $ "SELECT local_display_name FROM " <> table <> " WHERE user_id = ? AND local_display_name LIKE ?" - getNameSfxs_ :: SQL.ToRow p => Text -> p -> SQL.Query -> IO [String] + getNameSfxs_ :: ToRow p => Text -> p -> Query -> IO [String] getNameSfxs_ pfx ps q = withTransaction st (\db -> hasPfx pfx . map fromOnly <$> DB.query db q ps) `catchAll_` pure [] commands = diff --git a/src/Simplex/Chat/Terminal/Main.hs b/src/Simplex/Chat/Terminal/Main.hs index b0eb4dac88..aa9adb059f 100644 --- a/src/Simplex/Chat/Terminal/Main.hs +++ b/src/Simplex/Chat/Terminal/Main.hs @@ -13,6 +13,7 @@ import Network.Socket import Simplex.Chat.Controller (ChatConfig (..), ChatController (..), ChatResponse (..), PresetServers (..), SimpleNetCfg (..), currentRemoteHost, versionNumber, versionString) import Simplex.Chat.Core import Simplex.Chat.Options +import Simplex.Chat.Options.DB import Simplex.Chat.Terminal import Simplex.Chat.View (serializeChatResponse, smpProxyModeStr) import Simplex.Messaging.Client (NetworkConfig (..), SocksMode (..)) @@ -56,11 +57,11 @@ simplexChatCLI' cfg opts@ChatOpts {chatCmd, chatCmdLog, chatCmdDelay, chatServer putStrLn $ serializeChatResponse (rh, Just user) ts tz rh r welcome :: ChatConfig -> ChatOpts -> IO () -welcome ChatConfig {presetServers = PresetServers {netCfg}} ChatOpts {coreOptions = CoreChatOpts {dbFilePrefix, simpleNetCfg = SimpleNetCfg {socksProxy, socksMode, smpProxyMode_, smpProxyFallback_}}} = +welcome ChatConfig {presetServers = PresetServers {netCfg}} ChatOpts {coreOptions = CoreChatOpts {dbOptions, simpleNetCfg = SimpleNetCfg {socksProxy, socksMode, smpProxyMode_, smpProxyFallback_}}} = mapM_ putStrLn [ versionString versionNumber, - "db: " <> dbFilePrefix <> "_chat.db, " <> dbFilePrefix <> "_agent.db", + "db: " <> dbString dbOptions, maybe "direct network connection - use `/network` command or `-x` CLI option to connect via SOCKS5 at :9050" ((\sp -> "using SOCKS5 proxy " <> sp <> if socksMode == SMOnion then " for onion servers ONLY." else " for ALL servers.") . show) diff --git a/src/Simplex/Chat/Types.hs b/src/Simplex/Chat/Types.hs index dbabc7c262..8e9fbf55f4 100644 --- a/src/Simplex/Chat/Types.hs +++ b/src/Simplex/Chat/Types.hs @@ -1,17 +1,21 @@ {-# LANGUAGE AllowAmbiguousTypes #-} +{-# LANGUAGE CPP #-} {-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE DeriveAnyClass #-} +{-# LANGUAGE DerivingStrategies #-} {-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GADTs #-} +{-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE PatternSynonyms #-} {-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE StrictData #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeFamilyDependencies #-} @@ -29,6 +33,7 @@ import qualified Data.Aeson.Encoding as JE import qualified Data.Aeson.TH as JQ import qualified Data.Attoparsec.ByteString.Char8 as A import Data.ByteString.Char8 (ByteString, pack, unpack) +import qualified Data.ByteString.Lazy as LB import Data.Int (Int64) import Data.Maybe (isJust) import Data.Text (Text) @@ -37,11 +42,6 @@ import Data.Text.Encoding (encodeUtf8) import Data.Time.Clock (UTCTime) import Data.Typeable (Typeable) import Data.Word (Word16) -import Database.SQLite.Simple (ResultError (..), SQLData (..)) -import Database.SQLite.Simple.FromField (FromField (..), returnError) -import Database.SQLite.Simple.Internal (Field (..)) -import Database.SQLite.Simple.Ok -import Database.SQLite.Simple.ToField (ToField (..)) import Simplex.Chat.Types.Preferences import Simplex.Chat.Types.Shared import Simplex.Chat.Types.UITheme @@ -49,13 +49,23 @@ import Simplex.Chat.Types.Util import Simplex.FileTransfer.Description (FileDigest) import Simplex.FileTransfer.Types (RcvFileId, SndFileId) import Simplex.Messaging.Agent.Protocol (ACorrId, AEventTag (..), AEvtTag (..), ConnId, ConnectionMode (..), ConnectionRequestUri, InvitationId, SAEntity (..), UserId) +import Simplex.Messaging.Agent.Store.DB (Binary (..)) import Simplex.Messaging.Crypto.File (CryptoFileArgs (..)) import Simplex.Messaging.Crypto.Ratchet (PQEncryption (..), PQSupport, pattern PQEncOff) import Simplex.Messaging.Encoding.String -import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, enumJSON, fromTextField_, sumTypeJSON) +import Simplex.Messaging.Parsers (blobFieldDecoder, defaultJSON, dropPrefix, enumJSON, fromTextField_, sumTypeJSON) import Simplex.Messaging.Util (safeDecodeUtf8, (<$?>)) import Simplex.Messaging.Version import Simplex.Messaging.Version.Internal +#if defined(dbPostgres) +import Database.PostgreSQL.Simple (ResultError (..)) +import Database.PostgreSQL.Simple.FromField (FromField(..), FieldParser, returnError) +import Database.PostgreSQL.Simple.ToField (ToField (..)) +#else +import Database.SQLite.Simple (ResultError (..)) +import Database.SQLite.Simple.FromField (FromField (..), FieldParser, returnError) +import Database.SQLite.Simple.ToField (ToField (..)) +#endif class IsContact a where contactId' :: a -> ContactId @@ -98,7 +108,7 @@ instance ToJSON AgentUserId where toJSON = strToJSON toEncoding = strToJEncoding -instance FromField AgentUserId where fromField f = AgentUserId <$> fromField f +deriving newtype instance FromField AgentUserId instance ToField AgentUserId where toField (AgentUserId uId) = toField uId @@ -131,10 +141,9 @@ data NewUser = NewUser newtype B64UrlByteString = B64UrlByteString ByteString deriving (Eq, Show) + deriving newtype (FromField) -instance FromField B64UrlByteString where fromField f = B64UrlByteString <$> fromField f - -instance ToField B64UrlByteString where toField (B64UrlByteString m) = toField m +instance ToField B64UrlByteString where toField (B64UrlByteString m) = toField $ Binary m instance StrEncoding B64UrlByteString where strEncode (B64UrlByteString m) = strEncode m @@ -195,9 +204,9 @@ instance ToJSON CustomData where instance FromJSON CustomData where parseJSON = J.withObject "CustomData" (pure . CustomData) -instance ToField CustomData where toField (CustomData v) = toField $ J.encode v +instance ToField CustomData where toField (CustomData v) = toField . Binary . LB.toStrict $ J.encode v -instance FromField CustomData where fromField = fromBlobField_ J.eitherDecodeStrict +instance FromField CustomData where fromField = blobFieldDecoder J.eitherDecodeStrict contactConn :: Contact -> Maybe Connection contactConn Contact {activeConn} = activeConn @@ -316,10 +325,9 @@ data UserContactRequest = UserContactRequest newtype XContactId = XContactId ByteString deriving (Eq, Show) + deriving newtype (FromField) -instance FromField XContactId where fromField f = XContactId <$> fromField f - -instance ToField XContactId where toField (XContactId m) = toField m +instance ToField XContactId where toField (XContactId m) = toField $ Binary m instance StrEncoding XContactId where strEncode (XContactId m) = strEncode m @@ -335,10 +343,9 @@ instance ToJSON XContactId where newtype ConnReqUriHash = ConnReqUriHash {unConnReqUriHash :: ByteString} deriving (Eq, Show) + deriving newtype (FromField) -instance FromField ConnReqUriHash where fromField f = ConnReqUriHash <$> fromField f - -instance ToField ConnReqUriHash where toField (ConnReqUriHash m) = toField m +instance ToField ConnReqUriHash where toField (ConnReqUriHash m) = toField $ Binary m instance StrEncoding ConnReqUriHash where strEncode (ConnReqUriHash m) = strEncode m @@ -457,13 +464,16 @@ msgFilterIntP = \case 2 -> Just MFMentions _ -> Just MFAll -fromIntField_ :: Typeable a => (Int64 -> Maybe a) -> Field -> Ok a -fromIntField_ fromInt = \case - f@(Field (SQLInteger i) _) -> - case fromInt i of - Just x -> Ok x - _ -> returnError ConversionFailed f ("invalid integer: " <> show i) - f -> returnError ConversionFailed f "expecting SQLInteger column type" +fromIntField_ :: Typeable a => (Int64 -> Maybe a) -> FieldParser a +#if defined(dbPostgres) +fromIntField_ fromInt f val = fromField f val >>= parseInt +#else +fromIntField_ fromInt f = fromField f >>= parseInt +#endif + where + parseInt i = case fromInt i of + Just x -> pure x + _ -> returnError ConversionFailed f $ "invalid integer: " <> show i featureAllowed :: SChatFeature f -> (PrefEnabled -> Bool) -> Contact -> Bool featureAllowed feature forWhom Contact {mergedPreferences} = @@ -593,16 +603,15 @@ instance ToJSON ImageData where instance ToField ImageData where toField (ImageData t) = toField t -instance FromField ImageData where fromField = fmap ImageData . fromField +deriving newtype instance FromField ImageData data CReqClientData = CRDataGroup {groupLinkId :: GroupLinkId} newtype GroupLinkId = GroupLinkId {unGroupLinkId :: ByteString} -- used to identify invitation via group link deriving (Eq, Show) + deriving newtype (FromField) -instance FromField GroupLinkId where fromField f = GroupLinkId <$> fromField f - -instance ToField GroupLinkId where toField (GroupLinkId g) = toField g +instance ToField GroupLinkId where toField (GroupLinkId g) = toField $ Binary g instance StrEncoding GroupLinkId where strEncode (GroupLinkId g) = strEncode g @@ -679,7 +688,7 @@ data MemberRestrictionStatus | MRSUnknown Text deriving (Eq, Show) -instance FromField MemberRestrictionStatus where fromField = fromBlobField_ strDecode +instance FromField MemberRestrictionStatus where fromField = blobFieldDecoder strDecode instance ToField MemberRestrictionStatus where toField = toField . strEncode @@ -808,10 +817,9 @@ data NewGroupMember = NewGroupMember newtype MemberId = MemberId {unMemberId :: ByteString} deriving (Eq, Show) + deriving newtype (FromField) -instance FromField MemberId where fromField f = MemberId <$> fromField f - -instance ToField MemberId where toField (MemberId m) = toField m +instance ToField MemberId where toField (MemberId m) = toField $ Binary m instance StrEncoding MemberId where strEncode (MemberId m) = strEncode m @@ -1162,6 +1170,9 @@ liveRcvFileTransferPath ft = fp <$> liveRcvFileTransferInfo ft newtype AgentConnId = AgentConnId ConnId deriving (Eq, Ord, Show) + deriving newtype (FromField) + +instance ToField AgentConnId where toField (AgentConnId m) = toField $ Binary m instance StrEncoding AgentConnId where strEncode (AgentConnId connId) = strEncode connId @@ -1175,12 +1186,11 @@ instance ToJSON AgentConnId where toJSON = strToJSON toEncoding = strToJEncoding -instance FromField AgentConnId where fromField f = AgentConnId <$> fromField f - -instance ToField AgentConnId where toField (AgentConnId m) = toField m - newtype AgentSndFileId = AgentSndFileId SndFileId deriving (Eq, Show) + deriving newtype (FromField) + +instance ToField AgentSndFileId where toField (AgentSndFileId m) = toField $ Binary m instance StrEncoding AgentSndFileId where strEncode (AgentSndFileId connId) = strEncode connId @@ -1194,12 +1204,11 @@ instance ToJSON AgentSndFileId where toJSON = strToJSON toEncoding = strToJEncoding -instance FromField AgentSndFileId where fromField f = AgentSndFileId <$> fromField f - -instance ToField AgentSndFileId where toField (AgentSndFileId m) = toField m - newtype AgentRcvFileId = AgentRcvFileId RcvFileId deriving (Eq, Show) + deriving newtype (FromField) + +instance ToField AgentRcvFileId where toField (AgentRcvFileId m) = toField $ Binary m instance StrEncoding AgentRcvFileId where strEncode (AgentRcvFileId connId) = strEncode connId @@ -1213,10 +1222,6 @@ instance ToJSON AgentRcvFileId where toJSON = strToJSON toEncoding = strToJEncoding -instance FromField AgentRcvFileId where fromField f = AgentRcvFileId <$> fromField f - -instance ToField AgentRcvFileId where toField (AgentRcvFileId m) = toField m - newtype AgentInvId = AgentInvId InvitationId deriving (Eq, Show) @@ -1232,7 +1237,7 @@ instance ToJSON AgentInvId where toJSON = strToJSON toEncoding = strToJEncoding -instance FromField AgentInvId where fromField f = AgentInvId <$> fromField f +deriving newtype instance FromField AgentInvId instance ToField AgentInvId where toField (AgentInvId m) = toField m diff --git a/src/Simplex/Chat/Types/Preferences.hs b/src/Simplex/Chat/Types/Preferences.hs index 8465caeee0..bc5eadac3a 100644 --- a/src/Simplex/Chat/Types/Preferences.hs +++ b/src/Simplex/Chat/Types/Preferences.hs @@ -1,3 +1,4 @@ +{-# LANGUAGE CPP #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DuplicateRecordFields #-} @@ -29,14 +30,18 @@ import qualified Data.ByteString.Char8 as B import Data.Maybe (fromMaybe, isJust) import Data.Text (Text) import qualified Data.Text as T -import Database.SQLite.Simple.FromField (FromField (..)) -import Database.SQLite.Simple.ToField (ToField (..)) import GHC.Records.Compat import Simplex.Chat.Types.Shared -import Simplex.Chat.Types.Util import Simplex.Messaging.Encoding.String -import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, enumJSON, fromTextField_, sumTypeJSON) +import Simplex.Messaging.Parsers (blobFieldDecoder, defaultJSON, dropPrefix, enumJSON, fromTextField_, sumTypeJSON) import Simplex.Messaging.Util (decodeJSON, encodeJSON, 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 ChatFeature = CFTimedMessages @@ -678,7 +683,7 @@ data FeatureAllowed | FANo -- do not allow deriving (Eq, Show) -instance FromField FeatureAllowed where fromField = fromBlobField_ strDecode +instance FromField FeatureAllowed where fromField = blobFieldDecoder strDecode instance ToField FeatureAllowed where toField = toField . strEncode @@ -704,7 +709,7 @@ instance ToJSON FeatureAllowed where data GroupFeatureEnabled = FEOn | FEOff deriving (Eq, Show) -instance FromField GroupFeatureEnabled where fromField = fromBlobField_ strDecode +instance FromField GroupFeatureEnabled where fromField = blobFieldDecoder strDecode instance ToField GroupFeatureEnabled where toField = toField . strEncode diff --git a/src/Simplex/Chat/Types/Shared.hs b/src/Simplex/Chat/Types/Shared.hs index 4601fe4e4a..b70ae81974 100644 --- a/src/Simplex/Chat/Types/Shared.hs +++ b/src/Simplex/Chat/Types/Shared.hs @@ -1,3 +1,4 @@ +{-# LANGUAGE CPP #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} @@ -6,11 +7,16 @@ module Simplex.Chat.Types.Shared where import Data.Aeson (FromJSON (..), ToJSON (..)) import qualified Data.Attoparsec.ByteString.Char8 as A import qualified Data.ByteString.Char8 as B +import Simplex.Messaging.Encoding.String +import Simplex.Messaging.Parsers (blobFieldDecoder) +import Simplex.Messaging.Util ((<$?>)) +#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 (..)) -import Simplex.Chat.Types.Util -import Simplex.Messaging.Encoding.String -import Simplex.Messaging.Util ((<$?>)) +#endif data GroupMemberRole = GRObserver -- connects to all group members and receives all messages, can't send messages @@ -21,7 +27,7 @@ data GroupMemberRole | GROwner -- + delete and change group information, add/remove/change roles for Owners deriving (Eq, Show, Ord) -instance FromField GroupMemberRole where fromField = fromBlobField_ strDecode +instance FromField GroupMemberRole where fromField = blobFieldDecoder strDecode instance ToField GroupMemberRole where toField = toField . strEncode diff --git a/src/Simplex/Chat/Types/UITheme.hs b/src/Simplex/Chat/Types/UITheme.hs index cc5290aa69..460076649e 100644 --- a/src/Simplex/Chat/Types/UITheme.hs +++ b/src/Simplex/Chat/Types/UITheme.hs @@ -1,3 +1,4 @@ +{-# LANGUAGE CPP #-} {-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} @@ -13,12 +14,17 @@ import qualified Data.Aeson.TH as JQ import Data.Char (toLower) import Data.Maybe (fromMaybe) import Data.Text (Text) -import Database.SQLite.Simple.FromField (FromField (..)) -import Database.SQLite.Simple.ToField (ToField (..)) import Simplex.Chat.Types.Util import Simplex.Messaging.Encoding.String import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, enumJSON, fromTextField_) 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 UITheme = UITheme { themeId :: Text, diff --git a/src/Simplex/Chat/Types/Util.hs b/src/Simplex/Chat/Types/Util.hs index 47edf8eaf8..afea178e41 100644 --- a/src/Simplex/Chat/Types/Util.hs +++ b/src/Simplex/Chat/Types/Util.hs @@ -1,24 +1,8 @@ -{-# LANGUAGE LambdaCase #-} - module Simplex.Chat.Types.Util where import qualified Data.Aeson as J import qualified Data.Aeson.Types as JT -import Data.ByteString (ByteString) -import Data.Typeable -import Database.SQLite.Simple (ResultError (..), SQLData (..)) -import Database.SQLite.Simple.FromField (FieldParser, returnError) -import Database.SQLite.Simple.Internal (Field (..)) -import Database.SQLite.Simple.Ok (Ok (Ok)) import Simplex.Messaging.Encoding.String textParseJSON :: TextEncoding a => String -> J.Value -> JT.Parser a textParseJSON name = J.withText name $ maybe (fail $ "bad " <> name) pure . textDecode - -fromBlobField_ :: Typeable k => (ByteString -> Either String k) -> FieldParser k -fromBlobField_ p = \case - f@(Field (SQLBlob b) _) -> - case p b of - Right k -> Ok k - Left e -> returnError ConversionFailed f ("could not parse field: " ++ e) - f -> returnError ConversionFailed f "expecting SQLBlob column type" diff --git a/src/Simplex/Chat/View.hs b/src/Simplex/Chat/View.hs index d363c95461..2c736d9269 100644 --- a/src/Simplex/Chat/View.hs +++ b/src/Simplex/Chat/View.hs @@ -1,3 +1,4 @@ +{-# LANGUAGE CPP #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE GADTs #-} @@ -58,7 +59,6 @@ import qualified Simplex.FileTransfer.Transport as XFTP import Simplex.Messaging.Agent.Client (ProtocolTestFailure (..), ProtocolTestStep (..), SubscriptionsInfo (..)) import Simplex.Messaging.Agent.Env.SQLite (NetworkConfig (..), ServerRoles (..)) import Simplex.Messaging.Agent.Protocol -import Simplex.Messaging.Agent.Store.SQLite.DB (SlowQueryStats (..)) import Simplex.Messaging.Client (SMPProxyFallback, SMPProxyMode (..), SocksMode (..)) import qualified Simplex.Messaging.Crypto as C import Simplex.Messaging.Crypto.File (CryptoFile (..), CryptoFileArgs (..)) @@ -73,6 +73,9 @@ import Simplex.Messaging.Util (safeDecodeUtf8, tshow) import Simplex.Messaging.Version hiding (version) import Simplex.RemoteControl.Types (RCCtrlAddress (..), RCErrorType (..)) import System.Console.ANSI.Types +#if !defined(dbPostgres) +import Simplex.Messaging.Agent.Store.SQLite.DB (SlowQueryStats (..)) +#endif type CurrentTime = UTCTime @@ -390,6 +393,9 @@ responseToView hu@(currentRH, user_) ChatConfig {logLevel, showReactions, showRe CRRemoteCtrlStopped {rcStopReason} -> viewRemoteCtrlStopped rcStopReason CRContactPQEnabled u c (CR.PQEncryption pqOn) -> ttyUser u [ttyContact' c <> ": " <> (if pqOn then "quantum resistant" else "standard") <> " end-to-end encryption enabled"] CRSQLResult rows -> map plain rows +#if !defined(dbPostgres) + CRArchiveExported archiveErrs -> if null archiveErrs then ["ok"] else ["archive export errors: " <> plain (show archiveErrs)] + CRArchiveImported archiveErrs -> if null archiveErrs then ["ok"] else ["archive import errors: " <> plain (show archiveErrs)] CRSlowSQLQueries {chatQueries, agentQueries} -> let viewQuery SlowSQLQuery {query, queryStats = SlowQueryStats {count, timeMax, timeAvg}} = ("count: " <> sShow count) @@ -397,6 +403,7 @@ responseToView hu@(currentRH, user_) ChatConfig {logLevel, showReactions, showRe <> (" :: avg: " <> sShow timeAvg <> " ms") <> (" :: " <> plain (T.unwords $ T.lines query)) in ("Chat queries" : map viewQuery chatQueries) <> [""] <> ("Agent queries" : map viewQuery agentQueries) +#endif CRDebugLocks {chatLockName, chatEntityLocks, agentLocks} -> [ maybe "no chat lock" (("chat lock: " <>) . plain) chatLockName, "chat entity locks: " <> viewJSON chatEntityLocks, @@ -441,8 +448,6 @@ responseToView hu@(currentRH, user_) ChatConfig {logLevel, showReactions, showRe CRChatCmdError u e -> ttyUserPrefix' u $ viewChatError True logLevel testView e CRChatError u e -> ttyUser' u $ viewChatError False logLevel testView e CRChatErrors u errs -> ttyUser' u $ concatMap (viewChatError False logLevel testView) errs - CRArchiveExported archiveErrs -> if null archiveErrs then ["ok"] else ["archive export errors: " <> plain (show archiveErrs)] - CRArchiveImported archiveErrs -> if null archiveErrs then ["ok"] else ["archive import errors: " <> plain (show archiveErrs)] CRAppSettings as -> ["app settings: " <> viewJSON as] CRTimedAction _ _ -> [] CRCustomChatResponse u r -> ttyUser' u $ map plain $ T.lines r @@ -2216,8 +2221,7 @@ viewChatError isCmd logLevel testView = \case CMD PROHIBITED cxt -> [withConnEntity <> plain ("error: command is prohibited, " <> cxt)] SMP _ SMP.AUTH -> [ withConnEntity - <> "error: connection authorization failed - this could happen if connection was deleted,\ - \ secured with different credentials, or due to a bug - please re-create the connection" + <> "error: connection authorization failed - this could happen if connection was deleted, secured with different credentials, or due to a bug - please re-create the connection" ] BROKER _ NETWORK | not isCmd -> [] BROKER _ TIMEOUT | not isCmd -> [] diff --git a/tests/Bots/BroadcastTests.hs b/tests/Bots/BroadcastTests.hs index 95c80d6345..7d10f6a34a 100644 --- a/tests/Bots/BroadcastTests.hs +++ b/tests/Bots/BroadcastTests.hs @@ -1,3 +1,4 @@ +{-# LANGUAGE CPP #-} {-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE OverloadedStrings #-} @@ -13,9 +14,12 @@ import Control.Exception (bracket) import Simplex.Chat.Bot.KnownContacts import Simplex.Chat.Core import Simplex.Chat.Options (CoreChatOpts (..)) +import Simplex.Chat.Options.DB import Simplex.Chat.Types (Profile (..)) -import System.FilePath (()) import Test.Hspec hiding (it) +#if !defined(dbPostgres) +import System.FilePath (()) +#endif broadcastBotTests :: SpecWith FilePath broadcastBotTests = do @@ -33,7 +37,17 @@ broadcastBotProfile = Profile {displayName = "broadcast_bot", fullName = "Broadc mkBotOpts :: FilePath -> [KnownContact] -> BroadcastBotOpts mkBotOpts tmp publishers = BroadcastBotOpts - { coreOptions = testCoreOpts {dbFilePrefix = tmp botDbPrefix}, + { coreOptions = + testCoreOpts + { dbOptions = + (dbOptions testCoreOpts) +#if defined(dbPostgres) + {dbSchemaPrefix = "client_" <> botDbPrefix} +#else + {dbFilePrefix = tmp botDbPrefix} +#endif + + }, publishers, welcomeMessage = defaultWelcomeMessage publishers, prohibitedMessage = defaultWelcomeMessage publishers diff --git a/tests/Bots/DirectoryTests.hs b/tests/Bots/DirectoryTests.hs index 9775dddd5f..0aa508994a 100644 --- a/tests/Bots/DirectoryTests.hs +++ b/tests/Bots/DirectoryTests.hs @@ -1,3 +1,4 @@ +{-# LANGUAGE CPP #-} {-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE OverloadedStrings #-} @@ -20,6 +21,7 @@ import Simplex.Chat.Bot.KnownContacts import Simplex.Chat.Controller (ChatConfig (..)) import Simplex.Chat.Core import Simplex.Chat.Options (CoreChatOpts (..)) +import Simplex.Chat.Options.DB import Simplex.Chat.Types (Profile (..)) import Simplex.Chat.Types.Shared (GroupMemberRole (..)) import System.FilePath (()) @@ -67,7 +69,17 @@ directoryProfile = Profile {displayName = "SimpleX-Directory", fullName = "", im mkDirectoryOpts :: FilePath -> [KnownContact] -> DirectoryOpts mkDirectoryOpts tmp superUsers = DirectoryOpts - { coreOptions = testCoreOpts {dbFilePrefix = tmp serviceDbPrefix}, + { coreOptions = + testCoreOpts + { dbOptions = + (dbOptions testCoreOpts) +#if defined(dbPostgres) + {dbSchemaPrefix = "client_" <> serviceDbPrefix} +#else + {dbFilePrefix = tmp serviceDbPrefix} +#endif + + }, adminUsers = [], superUsers, directoryLog = Just $ tmp "directory_service.log", diff --git a/tests/ChatClient.hs b/tests/ChatClient.hs index 42f139f05d..1c107d5d3f 100644 --- a/tests/ChatClient.hs +++ b/tests/ChatClient.hs @@ -1,3 +1,4 @@ +{-# LANGUAGE CPP #-} {-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE NamedFieldPuns #-} @@ -18,7 +19,6 @@ import Control.Exception (bracket, bracket_) import Control.Monad import Control.Monad.Except import Control.Monad.Reader -import Data.ByteArray (ScrubbedBytes) import Data.Functor (($>)) import Data.List (dropWhileEnd, find) import Data.Maybe (isNothing) @@ -29,6 +29,7 @@ import Simplex.Chat.Controller (ChatCommand (..), ChatConfig (..), ChatControlle import Simplex.Chat.Core import Simplex.Chat.Library.Commands import Simplex.Chat.Options +import Simplex.Chat.Options.DB import Simplex.Chat.Protocol (currentChatVersion, pqEncryptionCompressionVersion) import Simplex.Chat.Store import Simplex.Chat.Store.Profiles @@ -43,9 +44,9 @@ import Simplex.Messaging.Agent (disposeAgentClient) import Simplex.Messaging.Agent.Env.SQLite import Simplex.Messaging.Agent.Protocol (currentSMPAgentVersion, duplexHandshakeSMPAgentVersion, pqdrSMPAgentVersion, supportedSMPAgentVRange) import Simplex.Messaging.Agent.RetryInterval -import Simplex.Messaging.Agent.Store.SQLite (closeDBStore) -import Simplex.Messaging.Agent.Store.Shared (MigrationConfirmation (..)) -import qualified Simplex.Messaging.Agent.Store.SQLite.DB as DB +import Simplex.Messaging.Agent.Store (closeStore) +import Simplex.Messaging.Agent.Store.Shared (MigrationConfirmation (..), MigrationError) +import qualified Simplex.Messaging.Agent.Store.DB as DB import Simplex.Messaging.Client (ProtocolClientConfig (..)) import Simplex.Messaging.Client.Agent (defaultSMPClientAgentConfig) import Simplex.Messaging.Crypto.Ratchet (supportedE2EEncryptVRange) @@ -59,14 +60,31 @@ import Simplex.Messaging.Transport.Server (ServerCredentials (..), defaultTransp import Simplex.Messaging.Version import Simplex.Messaging.Version.Internal import System.Directory (createDirectoryIfMissing, removeDirectoryRecursive) -import System.FilePath (()) import qualified System.Terminal as C import System.Terminal.Internal (VirtualTerminal (..), VirtualTerminalSettings (..), withVirtualTerminal) import System.Timeout (timeout) import Test.Hspec (Expectation, HasCallStack, shouldReturn) +#if defined(dbPostgres) +import Database.PostgreSQL.Simple (ConnectInfo (..), defaultConnectInfo) +#else +import Data.ByteArray (ScrubbedBytes) +import System.FilePath (()) +#endif -testDBPrefix :: FilePath -testDBPrefix = "tests/tmp/test" +#if defined(dbPostgres) +testDBName :: String +testDBName = "test_chat_db" + +testDBUser :: String +testDBUser = "test_chat_user" + +testDBConnectInfo :: ConnectInfo +testDBConnectInfo = + defaultConnectInfo { + connectUser = testDBUser, + connectDatabase = testDBName + } +#endif serverPort :: ServiceName serverPort = "7001" @@ -93,9 +111,20 @@ testOpts = testCoreOpts :: CoreChatOpts testCoreOpts = CoreChatOpts - { dbFilePrefix = "./simplex_v1", - dbKey = "", - -- dbKey = "this is a pass-phrase to encrypt the database", + { + dbOptions = ChatDbOpts +#if defined(dbPostgres) + { dbName = testDBName, + dbUser = testDBUser, + -- dbSchemaPrefix is not used in tests (except bot tests where it's redefined), + -- instead different schema prefix is passed per client so that single test database is used + dbSchemaPrefix = "" +#else + { dbFilePrefix = "./simplex_v1", -- dbFilePrefix is not used in tests (except bot tests where it's redefined) + dbKey = "", -- dbKey = "this is a pass-phrase to encrypt the database", + vacuumOnMigration = True +#endif + }, smpServers = ["smp://LcJUMfVhwD8yxjAiSaDzzGF3-kLG4Uh0Fl_ZIjrRwjI=:server_password@localhost:7001"], xftpServers = ["xftp://LcJUMfVhwD8yxjAiSaDzzGF3-kLG4Uh0Fl_ZIjrRwjI=:server_password@localhost:7002"], simpleNetCfg = defaultSimpleNetCfg, @@ -106,12 +135,13 @@ testCoreOpts = logFile = Nothing, tbqSize = 16, highlyAvailable = False, - yesToUpMigrations = False, - vacuumOnMigration = True + yesToUpMigrations = False } +#if !defined(dbPostgres) getTestOpts :: Bool -> ScrubbedBytes -> ChatOpts -getTestOpts maintenance dbKey = testOpts {maintenance, coreOptions = testCoreOpts {dbKey}} +getTestOpts maintenance dbKey = testOpts {maintenance, coreOptions = testCoreOpts {dbOptions = (dbOptions testCoreOpts) {dbKey}}} +#endif termSettings :: VirtualTerminalSettings termSettings = @@ -248,18 +278,33 @@ groupLinkViaContactVRange :: VersionRangeChat groupLinkViaContactVRange = mkVersionRange (VersionChat 1) (VersionChat 2) createTestChat :: FilePath -> ChatConfig -> ChatOpts -> String -> Profile -> IO TestCC -createTestChat tmp cfg opts@ChatOpts {coreOptions = CoreChatOpts {dbKey, vacuumOnMigration}} dbPrefix profile = do - Right db@ChatDatabase {chatStore, agentStore} <- createChatDatabase (tmp dbPrefix) dbKey False MCError vacuumOnMigration - withTransaction agentStore (`DB.execute_` "INSERT INTO users (user_id) VALUES (1);") +createTestChat tmp cfg opts@ChatOpts {coreOptions} dbPrefix profile = do + Right db@ChatDatabase {chatStore, agentStore} <- createDatabase tmp coreOptions dbPrefix + insertUser agentStore Right user <- withTransaction chatStore $ \db' -> runExceptT $ createUserRecord db' (AgentUserId 1) profile True startTestChat_ db cfg opts user startTestChat :: FilePath -> ChatConfig -> ChatOpts -> String -> IO TestCC -startTestChat tmp cfg opts@ChatOpts {coreOptions = CoreChatOpts {dbKey, vacuumOnMigration}} dbPrefix = do - Right db@ChatDatabase {chatStore} <- createChatDatabase (tmp dbPrefix) dbKey False MCError vacuumOnMigration +startTestChat tmp cfg opts@ChatOpts {coreOptions} dbPrefix = do + Right db@ChatDatabase {chatStore} <- createDatabase tmp coreOptions dbPrefix Just user <- find activeUser <$> withTransaction chatStore getUsers startTestChat_ db cfg opts user +createDatabase :: FilePath -> CoreChatOpts -> String -> IO (Either MigrationError ChatDatabase) +#if defined(dbPostgres) +createDatabase _tmp CoreChatOpts {dbOptions} dbPrefix = do + createChatDatabase dbOptions {dbSchemaPrefix = "client_" <> dbPrefix} MCError + +insertUser :: DBStore -> IO () +insertUser st = withTransaction st (`DB.execute_` "INSERT INTO users DEFAULT VALUES") +#else +createDatabase tmp CoreChatOpts {dbOptions} dbPrefix = do + createChatDatabase dbOptions {dbFilePrefix = tmp dbPrefix} MCError + +insertUser :: DBStore -> IO () +insertUser st = withTransaction st (`DB.execute_` "INSERT INTO users (user_id) VALUES (1)") +#endif + startTestChat_ :: ChatDatabase -> ChatConfig -> ChatOpts -> User -> IO TestCC startTestChat_ db cfg opts user = do t <- withVirtualTerminal termSettings pure @@ -278,7 +323,7 @@ stopTestChat TestCC {chatController = cc@ChatController {smpAgent, chatStore}, c uninterruptibleCancel termAsync uninterruptibleCancel chatAsync liftIO $ disposeAgentClient smpAgent - closeDBStore chatStore + closeStore chatStore threadDelay 200000 withNewTestChat :: HasCallStack => FilePath -> String -> Profile -> (HasCallStack => TestCC -> IO a) -> IO a diff --git a/tests/ChatTests/ChatList.hs b/tests/ChatTests/ChatList.hs index de12caf648..dd88aac54e 100644 --- a/tests/ChatTests/ChatList.hs +++ b/tests/ChatTests/ChatList.hs @@ -193,9 +193,9 @@ testPaginationAllChatTypes = _ts6 <- iso8601Show <$> getCurrentTime - -- * (notes) + -- \* (notes) createCCNoteFolder alice - alice /* "psst" + alice >* "psst" ts7 <- iso8601Show <$> getCurrentTime diff --git a/tests/ChatTests/Direct.hs b/tests/ChatTests/Direct.hs index 960b4bd96e..917fc36395 100644 --- a/tests/ChatTests/Direct.hs +++ b/tests/ChatTests/Direct.hs @@ -1,3 +1,4 @@ +{-# LANGUAGE CPP #-} {-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE OverloadedStrings #-} @@ -21,7 +22,6 @@ import qualified Data.ByteString.Char8 as B import qualified Data.ByteString.Lazy.Char8 as LB import Data.List (intercalate) import qualified Data.Text as T -import Database.SQLite.Simple (Only (..)) import Simplex.Chat.AppSettings (defaultAppSettings) import qualified Simplex.Chat.AppSettings as AS import Simplex.Chat.Call @@ -29,19 +29,24 @@ import Simplex.Chat.Controller (ChatConfig (..), PresetServers (..)) import Simplex.Chat.Messages (ChatItemId) import Simplex.Chat.Options import Simplex.Chat.Protocol (supportedChatVRange) -import Simplex.Chat.Store (agentStoreFile, chatStoreFile) import Simplex.Chat.Types (VersionRangeChat, authErrDisableCount, sameVerificationCode, verificationCode, pattern VersionChat) import Simplex.Messaging.Agent.Env.SQLite import Simplex.Messaging.Agent.RetryInterval -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 Simplex.Messaging.Server.Env.STM hiding (subscriptions) import Simplex.Messaging.Transport import Simplex.Messaging.Util (safeDecodeUtf8) import Simplex.Messaging.Version import System.Directory (copyFile, doesDirectoryExist, doesFileExist) -import System.FilePath (()) import Test.Hspec hiding (it) +#if defined(dbPostgres) +import Database.PostgreSQL.Simple (Only (..)) +#else +import Database.SQLite.Simple (Only (..)) +import Simplex.Chat.Store (agentStoreFile, chatStoreFile) +import System.FilePath (()) +#endif chatDirectTests :: SpecWith FilePath chatDirectTests = do @@ -106,10 +111,12 @@ chatDirectTests = do xit'' "curr/v5" $ testFullAsyncSlow testCfg testCfgSlow describe "webrtc calls api" $ do it "negotiate call" testNegotiateCall +#if !defined(dbPostgres) describe "maintenance mode" $ do it "start/stop/export/import chat" testMaintenanceMode it "export/import chat with files" testMaintenanceModeWithFiles it "encrypt/decrypt database" testDatabaseEncryption +#endif describe "coordination between app and NSE" $ do it "should not subscribe in NSE and subscribe in the app" testSubscribeAppNSE describe "mute/unmute messages" $ do @@ -142,10 +149,13 @@ chatDirectTests = do sameVerificationCode "123 456 789" "12345 6789" `shouldBe` True it "mark contact verified" testMarkContactVerified it "mark group member verified" testMarkGroupMemberVerified +#if !defined(dbPostgres) + -- TODO [postgres] restore from outdated db backup (same as in agent) describe "message errors" $ do it "show message decryption error" testMsgDecryptError it "should report ratchet de-synchronization, synchronize ratchets" testSyncRatchet it "synchronize ratchets, reset connection code" testSyncRatchetCodeReset +#endif describe "message reactions" $ do it "set message reactions" testSetMessageReactions describe "delivery receipts" $ do @@ -1363,6 +1373,7 @@ testNegotiateCall = bob #$> ("/_get chat @2 count=100", chat, chatFeatures <> [(0, "incoming call: accepted")]) alice <## "bob accepted your WebRTC video call (e2e encrypted)" repeatM_ 3 $ getTermLine alice + threadDelay 100000 alice #$> ("/_get chat @2 count=100", chat, chatFeatures <> [(1, "outgoing call: accepted")]) -- alice confirms call by sending WebRTC answer alice ##> ("/_call answer @2 " <> serialize testWebRTCSession) @@ -1480,6 +1491,7 @@ testMaintenanceModeWithFiles tmp = withXFTPServer $ do -- works after full restart withTestChat tmp "alice" $ \alice -> testChatWorking alice bob +#if !defined(dbPostgres) testDatabaseEncryption :: HasCallStack => FilePath -> IO () testDatabaseEncryption tmp = do withNewTestChat tmp "bob" bobProfile $ \bob -> do @@ -1527,6 +1539,7 @@ testDatabaseEncryption tmp = do alice <## "ok" withTestChat tmp "alice" $ \alice -> do testChatWorking alice bob +#endif testSubscribeAppNSE :: HasCallStack => FilePath -> IO () testSubscribeAppNSE tmp = @@ -2730,6 +2743,7 @@ testMarkGroupMemberVerified = | verified = "connection verified" | otherwise = "connection not verified, use /code command to see security code" +#if !defined(dbPostgres) testMsgDecryptError :: HasCallStack => FilePath -> IO () testMsgDecryptError tmp = withNewTestChat tmp "alice" aliceProfile $ \alice -> do @@ -2867,6 +2881,7 @@ testSyncRatchetCodeReset tmp = connVerified | verified = "connection verified" | otherwise = "connection not verified, use /code command to see security code" +#endif testSetMessageReactions :: HasCallStack => FilePath -> IO () testSetMessageReactions = diff --git a/tests/ChatTests/Forward.hs b/tests/ChatTests/Forward.hs index f347e1a396..44c211d2e0 100644 --- a/tests/ChatTests/Forward.hs +++ b/tests/ChatTests/Forward.hs @@ -224,7 +224,7 @@ testForwardNotesToContact = createCCNoteFolder alice connectUsers alice cath - alice /* "hi" + alice >* "hi" alice `send` "@cath <- * hi" alice <# "@cath hi" @@ -237,7 +237,7 @@ testForwardNotesToGroup = createCCNoteFolder alice createGroup2 "team" alice cath - alice /* "hi" + alice >* "hi" alice `send` "#team <- * hi" alice <# "#team hi" @@ -248,7 +248,7 @@ testForwardNotesToNotes tmp = withNewTestChat tmp "alice" aliceProfile $ \alice -> do createCCNoteFolder alice - alice /* "hi" + alice >* "hi" alice `send` "* <- * hi" alice <# "* hi" diff --git a/tests/ChatTests/Groups.hs b/tests/ChatTests/Groups.hs index f14a041f67..5cb89d9417 100644 --- a/tests/ChatTests/Groups.hs +++ b/tests/ChatTests/Groups.hs @@ -1,3 +1,4 @@ +{-# LANGUAGE CPP #-} {-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE NumericUnderscores #-} {-# LANGUAGE OverloadedStrings #-} @@ -16,22 +17,26 @@ import Control.Monad (forM_, void, when) import qualified Data.ByteString.Char8 as B import Data.List (intercalate, isInfixOf) import qualified Data.Text as T -import Database.SQLite.Simple (Only (..)) import Simplex.Chat.Controller (ChatConfig (..)) import Simplex.Chat.Messages (ChatItemId) import Simplex.Chat.Options import Simplex.Chat.Protocol (supportedChatVRange) -import Simplex.Chat.Store (agentStoreFile, chatStoreFile) import Simplex.Chat.Types (VersionRangeChat) import Simplex.Chat.Types.Shared (GroupMemberRole (..)) import Simplex.Messaging.Agent.Env.SQLite import Simplex.Messaging.Agent.RetryInterval -import qualified Simplex.Messaging.Agent.Store.SQLite.DB as DB +import qualified Simplex.Messaging.Agent.Store.DB as DB import Simplex.Messaging.Server.Env.STM hiding (subscriptions) import Simplex.Messaging.Transport +import Test.Hspec hiding (it) +#if defined(dbPostgres) +import Database.PostgreSQL.Simple (Only (..)) +#else +import Database.SQLite.Simple (Only (..)) +import Simplex.Chat.Store (agentStoreFile, chatStoreFile) import System.Directory (copyFile) import System.FilePath (()) -import Test.Hspec hiding (it) +#endif chatGroupTests :: SpecWith FilePath chatGroupTests = do @@ -104,10 +109,13 @@ chatGroupTests = do it "group link without contact - known group" testPlanGroupLinkNoContactKnown it "group link without contact - connecting" testPlanGroupLinkNoContactConnecting it "group link without contact - connecting (slow handshake)" testPlanGroupLinkNoContactConnectingSlow +#if !defined(dbPostgres) + -- TODO [postgres] restore from outdated db backup (same as in agent) describe "group message errors" $ do it "show message decryption error" testGroupMsgDecryptError it "should report ratchet de-synchronization, synchronize ratchets" testGroupSyncRatchet it "synchronize ratchets, reset connection code" testGroupSyncRatchetCodeReset +#endif describe "group message reactions" $ do it "set group message reactions" testSetGroupMessageReactions describe "group delivery receipts" $ do @@ -3549,6 +3557,7 @@ testPlanGroupLinkNoContactConnectingSlow tmp = do bob ##> ("/c " <> gLink) bob <## "group link: connecting to group #team" +#if !defined(dbPostgres) testGroupMsgDecryptError :: HasCallStack => FilePath -> IO () testGroupMsgDecryptError tmp = withNewTestChat tmp "alice" aliceProfile $ \alice -> do @@ -3692,6 +3701,7 @@ testGroupSyncRatchetCodeReset tmp = connVerified | verified = "connection verified" | otherwise = "connection not verified, use /code command to see security code" +#endif testSetGroupMessageReactions :: HasCallStack => FilePath -> IO () testSetGroupMessageReactions = diff --git a/tests/ChatTests/Local.hs b/tests/ChatTests/Local.hs index c17b893be1..594e6e47b0 100644 --- a/tests/ChatTests/Local.hs +++ b/tests/ChatTests/Local.hs @@ -33,7 +33,7 @@ testNotes tmp = withNewTestChat tmp "alice" aliceProfile $ \alice -> do alice ##> "/contacts" -- not a contact - alice /* "keep in mind" + alice >* "keep in mind" alice ##> "/tail" alice <# "* keep in mind" alice ##> "/chats" @@ -50,7 +50,7 @@ testNotes tmp = withNewTestChat tmp "alice" aliceProfile $ \alice -> do alice ##> "/tail" alice ##> "/chats" - alice /* "ahoy!" + alice >* "ahoy!" alice ##> "/_update item *1 2 text Greetings." alice ##> "/tail *" alice <# "* Greetings." @@ -59,7 +59,7 @@ testUserNotes :: FilePath -> IO () testUserNotes tmp = withNewTestChat tmp "alice" aliceProfile $ \alice -> do createCCNoteFolder alice - alice /* "keep in mind" + alice >* "keep in mind" alice ##> "/tail" alice <# "* keep in mind" @@ -78,9 +78,9 @@ testPreviewsPagination tmp = withNewTestChat tmp "alice" aliceProfile $ \alice - createCCNoteFolder alice tsS <- iso8601Show <$> getCurrentTime - alice /* "first" + alice >* "first" tsM <- iso8601Show <$> getCurrentTime - alice /* "last" + alice >* "last" tsE <- iso8601Show <$> getCurrentTime -- there's only one folder that got updated after tsM and before tsE @@ -95,10 +95,10 @@ testChatPagination :: FilePath -> IO () testChatPagination tmp = withNewTestChat tmp "alice" aliceProfile $ \alice -> do createCCNoteFolder alice - alice /* "hello world" - alice /* "memento mori" - alice /* "knock-knock" - alice /* "who's there?" + alice >* "hello world" + alice >* "memento mori" + alice >* "knock-knock" + alice >* "who's there?" alice #$> ("/_get chat *1 count=100", chat, [(1, "hello world"), (1, "memento mori"), (1, "knock-knock"), (1, "who's there?")]) alice #$> ("/_get chat *1 count=1", chat, [(1, "who's there?")]) @@ -184,7 +184,7 @@ testOtherFiles = ] bob <## "completed receiving file 1 (test.jpg) from alice" - bob /* "test" + bob >* "test" bob ##> "/tail *" bob <# "* test" bob ##> "/clear *" diff --git a/tests/ChatTests/Profiles.hs b/tests/ChatTests/Profiles.hs index 0730d32866..699565af23 100644 --- a/tests/ChatTests/Profiles.hs +++ b/tests/ChatTests/Profiles.hs @@ -1,3 +1,4 @@ +{-# LANGUAGE CPP #-} {-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE PostfixOperators #-} @@ -1121,16 +1122,20 @@ testPlanAddressContactViaAddress = bob ##> ("/c " <> cLink) connecting alice bob - bob ##> "/_delete @2 notify=off" + bob ##> "/delete @alice" bob <## "alice: contact is deleted" - alice ##> "/_delete @2 notify=off" + alice ##> "/delete @bob" alice <## "bob: contact is deleted" void $ withCCUser bob $ \user -> withCCTransaction bob $ \db -> runExceptT $ createContact db user profile bob @@@ [("@alice", "")] -- GUI api +#if defined(dbPostgres) + bob ##> "/_connect contact 1 4" +#else bob ##> "/_connect contact 1 2" +#endif connecting alice bob where connecting alice bob = do @@ -1264,9 +1269,10 @@ testAcceptContactRequestIncognito = testChat3 aliceProfile bobProfile cathProfil \alice bob cath -> do alice ##> "/ad" cLink <- getContactLink alice True + -- GUI /_accept api bob ##> ("/c " <> cLink) alice <#? bob - alice ##> "/accept incognito bob" + alice ##> "/_accept incognito=on 1" alice <## "bob (Bob): accepting contact request, you can send messages to contact" aliceIncognitoBob <- getTermLine alice concurrentlyN_ @@ -1291,10 +1297,10 @@ testAcceptContactRequestIncognito = testChat3 aliceProfile bobProfile cathProfil alice ##> "/contacts" (alice ("/c " <> cLink) alice <#? cath - alice ##> "/_accept incognito=on 1" + alice ##> "/accept incognito cath" alice <## "cath (Catherine): accepting contact request, you can send messages to contact" aliceIncognitoCath <- getTermLine alice concurrentlyN_ diff --git a/tests/ChatTests/Utils.hs b/tests/ChatTests/Utils.hs index 90d744766f..24178125ee 100644 --- a/tests/ChatTests/Utils.hs +++ b/tests/ChatTests/Utils.hs @@ -1,3 +1,4 @@ +{-# LANGUAGE CPP #-} {-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE NamedFieldPuns #-} @@ -21,7 +22,6 @@ import Data.List (isPrefixOf, isSuffixOf) import Data.Maybe (fromMaybe) import Data.String import qualified Data.Text as T -import Database.SQLite.Simple (Only (..)) import Simplex.Chat.Controller (ChatConfig (..), ChatController (..)) import Simplex.Chat.Messages.CIContent (e2eInfoNoPQText, e2eInfoPQText) import Simplex.Chat.Protocol @@ -32,9 +32,8 @@ import Simplex.Chat.Types import Simplex.Chat.Types.Preferences import Simplex.Chat.Types.Shared import Simplex.FileTransfer.Client.Main (xftpClientCLI) -import Simplex.Messaging.Agent.Store.AgentStore (maybeFirstRow) -import qualified Simplex.Messaging.Agent.Store.SQLite.DB as DB -import Simplex.Messaging.Agent.Store.SQLite.Common (withTransaction) +import Simplex.Messaging.Agent.Store.AgentStore (maybeFirstRow, withTransaction) +import qualified Simplex.Messaging.Agent.Store.DB as DB import qualified Simplex.Messaging.Crypto as C import Simplex.Messaging.Crypto.Ratchet (PQEncryption (..), PQSupport, pattern PQEncOff, pattern PQEncOn, pattern PQSupportOff) import Simplex.Messaging.Encoding.String @@ -46,6 +45,11 @@ import System.Info (os) import Test.Hspec hiding (it) import qualified Test.Hspec as Hspec import UnliftIO (timeout) +#if defined(dbPostgres) +import Database.PostgreSQL.Simple (Only (..)) +#else +import Database.SQLite.Simple (Only (..)) +#endif defaultPrefs :: Maybe Preferences defaultPrefs = Just $ toChatPrefs defaultChatPrefs @@ -363,8 +367,8 @@ cc <##.. ls = do unless prefix $ print ("expected to start from one of: " <> show ls, ", got: " <> l) prefix `shouldBe` True -(/*) :: HasCallStack => TestCC -> String -> IO () -cc /* note = do +(>*) :: HasCallStack => TestCC -> String -> IO () +cc >* note = do cc `send` ("/* " <> note) (dropTime <$> getTermLine cc) `shouldReturn` ("* " <> note) diff --git a/tests/JSONFixtures.hs b/tests/JSONFixtures.hs new file mode 100644 index 0000000000..32e90cf754 --- /dev/null +++ b/tests/JSONFixtures.hs @@ -0,0 +1,62 @@ +{-# LANGUAGE OverloadedStrings #-} + +module JSONFixtures where + +import qualified Data.ByteString.Lazy.Char8 as LB + +noActiveUserSwift :: LB.ByteString +noActiveUserSwift = "{\"resp\":{\"_owsf\":true,\"chatCmdError\":{\"chatError\":{\"_owsf\":true,\"error\":{\"errorType\":{\"_owsf\":true,\"noActiveUser\":{}}}}}}}" + +noActiveUserTagged :: LB.ByteString +noActiveUserTagged = "{\"resp\":{\"type\":\"chatCmdError\",\"chatError\":{\"type\":\"error\",\"errorType\":{\"type\":\"noActiveUser\"}}}}" + +activeUserExistsSwift :: LB.ByteString +activeUserExistsSwift = "{\"resp\":{\"_owsf\":true,\"chatCmdError\":{\"user_\":{\"userId\":1,\"agentUserId\":\"1\",\"userContactId\":1,\"localDisplayName\":\"alice\",\"profile\":{\"profileId\":1,\"displayName\":\"alice\",\"fullName\":\"Alice\",\"localAlias\":\"\"},\"fullPreferences\":{\"timedMessages\":{\"allow\":\"yes\"},\"fullDelete\":{\"allow\":\"no\"},\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"},\"calls\":{\"allow\":\"yes\"}},\"activeUser\":true,\"activeOrder\":1,\"showNtfs\":true,\"sendRcptsContacts\":true,\"sendRcptsSmallGroups\":true},\"chatError\":{\"_owsf\":true,\"error\":{\"errorType\":{\"_owsf\":true,\"userExists\":{\"contactName\":\"alice\"}}}}}}}" + +activeUserExistsTagged :: LB.ByteString +activeUserExistsTagged = "{\"resp\":{\"type\":\"chatCmdError\",\"user_\":{\"userId\":1,\"agentUserId\":\"1\",\"userContactId\":1,\"localDisplayName\":\"alice\",\"profile\":{\"profileId\":1,\"displayName\":\"alice\",\"fullName\":\"Alice\",\"localAlias\":\"\"},\"fullPreferences\":{\"timedMessages\":{\"allow\":\"yes\"},\"fullDelete\":{\"allow\":\"no\"},\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"},\"calls\":{\"allow\":\"yes\"}},\"activeUser\":true,\"activeOrder\":1,\"showNtfs\":true,\"sendRcptsContacts\":true,\"sendRcptsSmallGroups\":true},\"chatError\":{\"type\":\"error\",\"errorType\":{\"type\":\"userExists\",\"contactName\":\"alice\"}}}}" + +activeUserSwift :: LB.ByteString +activeUserSwift = "{\"resp\":{\"_owsf\":true,\"activeUser\":{\"user\":{\"userId\":1,\"agentUserId\":\"1\",\"userContactId\":1,\"localDisplayName\":\"alice\",\"profile\":{\"profileId\":1,\"displayName\":\"alice\",\"fullName\":\"Alice\",\"localAlias\":\"\"},\"fullPreferences\":{\"timedMessages\":{\"allow\":\"yes\"},\"fullDelete\":{\"allow\":\"no\"},\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"},\"calls\":{\"allow\":\"yes\"}},\"activeUser\":true,\"activeOrder\":1,\"showNtfs\":true,\"sendRcptsContacts\":true,\"sendRcptsSmallGroups\":true}}}}" + +activeUserTagged :: LB.ByteString +activeUserTagged = "{\"resp\":{\"type\":\"activeUser\",\"user\":{\"userId\":1,\"agentUserId\":\"1\",\"userContactId\":1,\"localDisplayName\":\"alice\",\"profile\":{\"profileId\":1,\"displayName\":\"alice\",\"fullName\":\"Alice\",\"localAlias\":\"\"},\"fullPreferences\":{\"timedMessages\":{\"allow\":\"yes\"},\"fullDelete\":{\"allow\":\"no\"},\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"},\"calls\":{\"allow\":\"yes\"}},\"activeUser\":true,\"activeOrder\":1,\"showNtfs\":true,\"sendRcptsContacts\":true,\"sendRcptsSmallGroups\":true}}}" + +chatStartedSwift :: LB.ByteString +chatStartedSwift = "{\"resp\":{\"_owsf\":true,\"chatStarted\":{}}}" + +chatStartedTagged :: LB.ByteString +chatStartedTagged = "{\"resp\":{\"type\":\"chatStarted\"}}" + +networkStatusesSwift :: LB.ByteString +networkStatusesSwift = "{\"resp\":{\"_owsf\":true,\"networkStatuses\":{\"user_\":" <> userJSON <> ",\"networkStatuses\":[]}}}" + +networkStatusesTagged :: LB.ByteString +networkStatusesTagged = "{\"resp\":{\"type\":\"networkStatuses\",\"user_\":" <> userJSON <> ",\"networkStatuses\":[]}}" + +userJSON :: LB.ByteString +userJSON = "{\"userId\":1,\"agentUserId\":\"1\",\"userContactId\":1,\"localDisplayName\":\"alice\",\"profile\":{\"profileId\":1,\"displayName\":\"alice\",\"fullName\":\"Alice\",\"localAlias\":\"\"},\"fullPreferences\":{\"timedMessages\":{\"allow\":\"yes\"},\"fullDelete\":{\"allow\":\"no\"},\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"},\"calls\":{\"allow\":\"yes\"}},\"activeUser\":true,\"activeOrder\":1,\"showNtfs\":true,\"sendRcptsContacts\":true,\"sendRcptsSmallGroups\":true}" + +memberSubSummarySwift :: LB.ByteString +memberSubSummarySwift = "{\"resp\":{\"_owsf\":true,\"memberSubSummary\":{\"user\":" <> userJSON <> ",\"memberSubscriptions\":[]}}}" + +memberSubSummaryTagged :: LB.ByteString +memberSubSummaryTagged = "{\"resp\":{\"type\":\"memberSubSummary\",\"user\":" <> userJSON <> ",\"memberSubscriptions\":[]}}" + +userContactSubSummarySwift :: LB.ByteString +userContactSubSummarySwift = "{\"resp\":{\"_owsf\":true,\"userContactSubSummary\":{\"user\":" <> userJSON <> ",\"userContactSubscriptions\":[]}}}" + +userContactSubSummaryTagged :: LB.ByteString +userContactSubSummaryTagged = "{\"resp\":{\"type\":\"userContactSubSummary\",\"user\":" <> userJSON <> ",\"userContactSubscriptions\":[]}}" + +pendingSubSummarySwift :: LB.ByteString +pendingSubSummarySwift = "{\"resp\":{\"_owsf\":true,\"pendingSubSummary\":{\"user\":" <> userJSON <> ",\"pendingSubscriptions\":[]}}}" + +pendingSubSummaryTagged :: LB.ByteString +pendingSubSummaryTagged = "{\"resp\":{\"type\":\"pendingSubSummary\",\"user\":" <> userJSON <> ",\"pendingSubscriptions\":[]}}" + +parsedMarkdownSwift :: LB.ByteString +parsedMarkdownSwift = "{\"formattedText\":[{\"format\":{\"_owsf\":true,\"bold\":{}},\"text\":\"hello\"}]}" + +parsedMarkdownTagged :: LB.ByteString +parsedMarkdownTagged = "{\"formattedText\":[{\"format\":{\"type\":\"bold\"},\"text\":\"hello\"}]}" diff --git a/tests/JSONTests.hs b/tests/JSONTests.hs index a17a69fae8..400db87b36 100644 --- a/tests/JSONTests.hs +++ b/tests/JSONTests.hs @@ -12,7 +12,7 @@ import Data.ByteString.Builder (toLazyByteString) import qualified Data.ByteString.Lazy.Char8 as LB import GHC.Generics (Generic) import Generic.Random (genericArbitraryU) -import MobileTests +import JSONFixtures import Simplex.Chat.Remote.Protocol (owsf2tagged) import Simplex.Messaging.Parsers import Test.Hspec diff --git a/tests/MobileTests.hs b/tests/MobileTests.hs index 905f927f37..730b0d8649 100644 --- a/tests/MobileTests.hs +++ b/tests/MobileTests.hs @@ -26,6 +26,7 @@ import Foreign.Ptr import Foreign.StablePtr import Foreign.Storable (peek) import GHC.IO.Encoding (setLocaleEncoding, setFileSystemEncoding, setForeignEncoding) +import JSONFixtures import Simplex.Chat.Controller (ChatController (..)) import Simplex.Chat.Mobile import Simplex.Chat.Mobile.File @@ -79,12 +80,6 @@ noActiveUser = noActiveUserTagged #endif -noActiveUserSwift :: LB.ByteString -noActiveUserSwift = "{\"resp\":{\"_owsf\":true,\"chatCmdError\":{\"chatError\":{\"_owsf\":true,\"error\":{\"errorType\":{\"_owsf\":true,\"noActiveUser\":{}}}}}}}" - -noActiveUserTagged :: LB.ByteString -noActiveUserTagged = "{\"resp\":{\"type\":\"chatCmdError\",\"chatError\":{\"type\":\"error\",\"errorType\":{\"type\":\"noActiveUser\"}}}}" - activeUserExists :: LB.ByteString activeUserExists = #if defined(darwin_HOST_OS) && defined(swiftJSON) @@ -93,12 +88,6 @@ activeUserExists = activeUserExistsTagged #endif -activeUserExistsSwift :: LB.ByteString -activeUserExistsSwift = "{\"resp\":{\"_owsf\":true,\"chatCmdError\":{\"user_\":{\"userId\":1,\"agentUserId\":\"1\",\"userContactId\":1,\"localDisplayName\":\"alice\",\"profile\":{\"profileId\":1,\"displayName\":\"alice\",\"fullName\":\"Alice\",\"localAlias\":\"\"},\"fullPreferences\":{\"timedMessages\":{\"allow\":\"yes\"},\"fullDelete\":{\"allow\":\"no\"},\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"},\"calls\":{\"allow\":\"yes\"}},\"activeUser\":true,\"activeOrder\":1,\"showNtfs\":true,\"sendRcptsContacts\":true,\"sendRcptsSmallGroups\":true},\"chatError\":{\"_owsf\":true,\"error\":{\"errorType\":{\"_owsf\":true,\"userExists\":{\"contactName\":\"alice\"}}}}}}}" - -activeUserExistsTagged :: LB.ByteString -activeUserExistsTagged = "{\"resp\":{\"type\":\"chatCmdError\",\"user_\":{\"userId\":1,\"agentUserId\":\"1\",\"userContactId\":1,\"localDisplayName\":\"alice\",\"profile\":{\"profileId\":1,\"displayName\":\"alice\",\"fullName\":\"Alice\",\"localAlias\":\"\"},\"fullPreferences\":{\"timedMessages\":{\"allow\":\"yes\"},\"fullDelete\":{\"allow\":\"no\"},\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"},\"calls\":{\"allow\":\"yes\"}},\"activeUser\":true,\"activeOrder\":1,\"showNtfs\":true,\"sendRcptsContacts\":true,\"sendRcptsSmallGroups\":true},\"chatError\":{\"type\":\"error\",\"errorType\":{\"type\":\"userExists\",\"contactName\":\"alice\"}}}}" - activeUser :: LB.ByteString activeUser = #if defined(darwin_HOST_OS) && defined(swiftJSON) @@ -107,12 +96,6 @@ activeUser = activeUserTagged #endif -activeUserSwift :: LB.ByteString -activeUserSwift = "{\"resp\":{\"_owsf\":true,\"activeUser\":{\"user\":{\"userId\":1,\"agentUserId\":\"1\",\"userContactId\":1,\"localDisplayName\":\"alice\",\"profile\":{\"profileId\":1,\"displayName\":\"alice\",\"fullName\":\"Alice\",\"localAlias\":\"\"},\"fullPreferences\":{\"timedMessages\":{\"allow\":\"yes\"},\"fullDelete\":{\"allow\":\"no\"},\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"},\"calls\":{\"allow\":\"yes\"}},\"activeUser\":true,\"activeOrder\":1,\"showNtfs\":true,\"sendRcptsContacts\":true,\"sendRcptsSmallGroups\":true}}}}" - -activeUserTagged :: LB.ByteString -activeUserTagged = "{\"resp\":{\"type\":\"activeUser\",\"user\":{\"userId\":1,\"agentUserId\":\"1\",\"userContactId\":1,\"localDisplayName\":\"alice\",\"profile\":{\"profileId\":1,\"displayName\":\"alice\",\"fullName\":\"Alice\",\"localAlias\":\"\"},\"fullPreferences\":{\"timedMessages\":{\"allow\":\"yes\"},\"fullDelete\":{\"allow\":\"no\"},\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"},\"calls\":{\"allow\":\"yes\"}},\"activeUser\":true,\"activeOrder\":1,\"showNtfs\":true,\"sendRcptsContacts\":true,\"sendRcptsSmallGroups\":true}}}" - chatStarted :: LB.ByteString chatStarted = #if defined(darwin_HOST_OS) && defined(swiftJSON) @@ -121,12 +104,6 @@ chatStarted = chatStartedTagged #endif -chatStartedSwift :: LB.ByteString -chatStartedSwift = "{\"resp\":{\"_owsf\":true,\"chatStarted\":{}}}" - -chatStartedTagged :: LB.ByteString -chatStartedTagged = "{\"resp\":{\"type\":\"chatStarted\"}}" - networkStatuses :: LB.ByteString networkStatuses = #if defined(darwin_HOST_OS) && defined(swiftJSON) @@ -135,12 +112,6 @@ networkStatuses = networkStatusesTagged #endif -networkStatusesSwift :: LB.ByteString -networkStatusesSwift = "{\"resp\":{\"_owsf\":true,\"networkStatuses\":{\"user_\":" <> userJSON <> ",\"networkStatuses\":[]}}}" - -networkStatusesTagged :: LB.ByteString -networkStatusesTagged = "{\"resp\":{\"type\":\"networkStatuses\",\"user_\":" <> userJSON <> ",\"networkStatuses\":[]}}" - memberSubSummary :: LB.ByteString memberSubSummary = #if defined(darwin_HOST_OS) && defined(swiftJSON) @@ -149,12 +120,6 @@ memberSubSummary = memberSubSummaryTagged #endif -memberSubSummarySwift :: LB.ByteString -memberSubSummarySwift = "{\"resp\":{\"_owsf\":true,\"memberSubSummary\":{\"user\":" <> userJSON <> ",\"memberSubscriptions\":[]}}}" - -memberSubSummaryTagged :: LB.ByteString -memberSubSummaryTagged = "{\"resp\":{\"type\":\"memberSubSummary\",\"user\":" <> userJSON <> ",\"memberSubscriptions\":[]}}" - userContactSubSummary :: LB.ByteString userContactSubSummary = #if defined(darwin_HOST_OS) && defined(swiftJSON) @@ -163,12 +128,6 @@ userContactSubSummary = userContactSubSummaryTagged #endif -userContactSubSummarySwift :: LB.ByteString -userContactSubSummarySwift = "{\"resp\":{\"_owsf\":true,\"userContactSubSummary\":{\"user\":" <> userJSON <> ",\"userContactSubscriptions\":[]}}}" - -userContactSubSummaryTagged :: LB.ByteString -userContactSubSummaryTagged = "{\"resp\":{\"type\":\"userContactSubSummary\",\"user\":" <> userJSON <> ",\"userContactSubscriptions\":[]}}" - pendingSubSummary :: LB.ByteString pendingSubSummary = #if defined(darwin_HOST_OS) && defined(swiftJSON) @@ -177,15 +136,6 @@ pendingSubSummary = pendingSubSummaryTagged #endif -pendingSubSummarySwift :: LB.ByteString -pendingSubSummarySwift = "{\"resp\":{\"_owsf\":true,\"pendingSubSummary\":{\"user\":" <> userJSON <> ",\"pendingSubscriptions\":[]}}}" - -pendingSubSummaryTagged :: LB.ByteString -pendingSubSummaryTagged = "{\"resp\":{\"type\":\"pendingSubSummary\",\"user\":" <> userJSON <> ",\"pendingSubscriptions\":[]}}" - -userJSON :: LB.ByteString -userJSON = "{\"userId\":1,\"agentUserId\":\"1\",\"userContactId\":1,\"localDisplayName\":\"alice\",\"profile\":{\"profileId\":1,\"displayName\":\"alice\",\"fullName\":\"Alice\",\"localAlias\":\"\"},\"fullPreferences\":{\"timedMessages\":{\"allow\":\"yes\"},\"fullDelete\":{\"allow\":\"no\"},\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"},\"calls\":{\"allow\":\"yes\"}},\"activeUser\":true,\"activeOrder\":1,\"showNtfs\":true,\"sendRcptsContacts\":true,\"sendRcptsSmallGroups\":true}" - parsedMarkdown :: LB.ByteString parsedMarkdown = #if defined(darwin_HOST_OS) && defined(swiftJSON) @@ -194,12 +144,6 @@ parsedMarkdown = parsedMarkdownTagged #endif -parsedMarkdownSwift :: LB.ByteString -parsedMarkdownSwift = "{\"formattedText\":[{\"format\":{\"_owsf\":true,\"bold\":{}},\"text\":\"hello\"}]}" - -parsedMarkdownTagged :: LB.ByteString -parsedMarkdownTagged = "{\"formattedText\":[{\"format\":{\"type\":\"bold\"},\"text\":\"hello\"}]}" - testChatApiNoUser :: FilePath -> IO () testChatApiNoUser tmp = do let dbPrefix = tmp "1" diff --git a/tests/RemoteTests.hs b/tests/RemoteTests.hs index e51a938252..dd4032e274 100644 --- a/tests/RemoteTests.hs +++ b/tests/RemoteTests.hs @@ -12,10 +12,10 @@ import qualified Data.Aeson as J import qualified Data.ByteString as B import qualified Data.ByteString.Lazy.Char8 as LB import qualified Data.Map.Strict as M -import Simplex.Chat.Archive (archiveFilesFolder) import Simplex.Chat.Controller (versionNumber) import qualified Simplex.Chat.Controller as Controller import Simplex.Chat.Mobile.File +import Simplex.Chat.Remote (remoteFilesFolder) import Simplex.Chat.Remote.Types import Simplex.Messaging.Crypto.File (CryptoFileArgs (..)) import Simplex.Messaging.Encoding.String (strEncode) @@ -214,7 +214,7 @@ remoteStoreFileTest = rhs <- readTVarIO (Controller.remoteHostSessions $ chatController desktop) desktopHostStore <- case M.lookup (RHId 1) rhs of - Just (_, RHSessionConnected {storePath}) -> pure $ desktopHostFiles storePath archiveFilesFolder + Just (_, RHSessionConnected {storePath}) -> pure $ desktopHostFiles storePath remoteFilesFolder _ -> fail "Host session 1 should be started" desktop ##> "/store remote file 1 tests/fixtures/test.pdf" desktop <## "file test.pdf stored on remote host 1" @@ -338,7 +338,7 @@ remoteCLIFileTest = testChat3 aliceProfile aliceDesktopProfile bobProfile $ \mob rhs <- readTVarIO (Controller.remoteHostSessions $ chatController desktop) desktopHostStore <- case M.lookup (RHId 1) rhs of - Just (_, RHSessionConnected {storePath}) -> pure $ desktopHostFiles storePath archiveFilesFolder + Just (_, RHSessionConnected {storePath}) -> pure $ desktopHostFiles storePath remoteFilesFolder _ -> fail "Host session 1 should be started" mobileName <- userName mobile diff --git a/tests/SchemaDump.hs b/tests/SchemaDump.hs index a2794ca0f7..1f7f6af8a3 100644 --- a/tests/SchemaDump.hs +++ b/tests/SchemaDump.hs @@ -10,8 +10,8 @@ import Data.List (dropWhileEnd) import Data.Maybe (fromJust, isJust) import Simplex.Chat.Store (createChatStore) import qualified Simplex.Chat.Store as Store -import Simplex.Messaging.Agent.Store.SQLite (closeDBStore, createDBStore) import Simplex.Messaging.Agent.Store.Shared (Migration (..), MigrationConfirmation (..), MigrationsToRun (..), toDownMigration) +import Simplex.Messaging.Agent.Store.SQLite (closeDBStore, createDBStore) import qualified Simplex.Messaging.Agent.Store.SQLite.Migrations as Migrations import Simplex.Messaging.Util (ifM, whenM) import System.Directory (doesFileExist, removeFile) @@ -22,7 +22,7 @@ testDB :: FilePath testDB = "tests/tmp/test_chat.db" appSchema :: FilePath -appSchema = "src/Simplex/Chat/Migrations/chat_schema.sql" +appSchema = "src/Simplex/Chat/Store/SQLite/Migrations/chat_schema.sql" -- Some indexes found by `.lint fkey-indexes` are not added to schema, explanation: -- @@ -38,7 +38,7 @@ appSchema = "src/Simplex/Chat/Migrations/chat_schema.sql" -- EXPLAIN QUERY PLAN DELETE FROM group_members; -- (uses idx_connections_group_member) appLint :: FilePath -appLint = "src/Simplex/Chat/Migrations/chat_lint.sql" +appLint = "src/Simplex/Chat/Store/SQLite/Migrations/chat_lint.sql" testSchema :: FilePath testSchema = "tests/tmp/test_agent_schema.sql" diff --git a/tests/Test.hs b/tests/Test.hs index 079c583a6e..042f699d3f 100644 --- a/tests/Test.hs +++ b/tests/Test.hs @@ -1,3 +1,5 @@ +{-# LANGUAGE CPP #-} + import Bots.BroadcastTests import Bots.DirectoryTests import ChatClient @@ -8,38 +10,56 @@ import Data.Time.Clock.System import JSONTests import MarkdownTests import MessageBatching -import MobileTests import ProtocolTests import OperatorTests import RandomServers import RemoteTests -import SchemaDump import Test.Hspec hiding (it) import UnliftIO.Temporary (withTempDirectory) import ValidNames import ViewTests +#if defined(dbPostgres) +import Simplex.Messaging.Agent.Store.Postgres.Util (createDBAndUserIfNotExists, dropAllSchemasExceptSystem, dropDatabaseAndUser) +#else +import MobileTests +import SchemaDump import WebRTCTests +#endif main :: IO () main = do setLogLevel LogError - withGlobalLogging logCfg . hspec $ do - describe "Schema dump" schemaDumpTest - describe "SimpleX chat markdown" markdownTests - describe "JSON Tests" jsonTests - describe "SimpleX chat view" viewTests - describe "SimpleX chat protocol" protocolTests - around tmpBracket $ describe "WebRTC encryption" webRTCTests - describe "Valid names" validNameTests - describe "Message batching" batchingTests - describe "Operators" operatorTests - describe "Random servers" randomServersTests - around testBracket $ do - describe "Mobile API Tests" mobileTests - describe "SimpleX chat client" chatTests - xdescribe'' "SimpleX Broadcast bot" broadcastBotTests - xdescribe'' "SimpleX Directory service bot" directoryServiceTests - describe "Remote session" remoteTests + withGlobalLogging logCfg . hspec +#if defined(dbPostgres) + . beforeAll_ (dropDatabaseAndUser testDBConnectInfo >> createDBAndUserIfNotExists testDBConnectInfo) + . afterAll_ (dropDatabaseAndUser testDBConnectInfo) +#endif + $ do +-- TODO [postgres] schema dump for postgres +#if !defined(dbPostgres) + describe "Schema dump" schemaDumpTest + around tmpBracket $ describe "WebRTC encryption" webRTCTests +#endif + describe "SimpleX chat markdown" markdownTests + describe "JSON Tests" jsonTests + describe "SimpleX chat view" viewTests + describe "SimpleX chat protocol" protocolTests + describe "Valid names" validNameTests + describe "Message batching" batchingTests + describe "Operators" operatorTests + describe "Random servers" randomServersTests + around testBracket +#if defined(dbPostgres) + . after_ (dropAllSchemasExceptSystem testDBConnectInfo) +#endif + $ do +#if !defined(dbPostgres) + describe "Mobile API Tests" mobileTests +#endif + describe "SimpleX chat client" chatTests + xdescribe'' "SimpleX Broadcast bot" broadcastBotTests + xdescribe'' "SimpleX Directory service bot" directoryServiceTests + describe "Remote session" remoteTests where testBracket test = withSmpServer $ tmpBracket test tmpBracket test = do