mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-03-31 03:16:05 +00:00
core: use strict tables (#6535)
* core: use strict tables * fix field types * change encodings to match schema types; migrate sqlite tables to strict mode * stabilize postgres client tests, remove slow handshake tests * update simplexmq * fix test * change call_state type to text * fix directory service queries * update local_alias for existing schemas * change types before strict
This commit is contained in:
@@ -665,7 +665,7 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName
|
||||
where
|
||||
rStatus = groupRolesStatus contactRole serviceRole
|
||||
groupRef = groupReference g
|
||||
ctRole = "*" <> strEncodeTxt contactRole <> "*"
|
||||
ctRole = "*" <> textEncode contactRole <> "*"
|
||||
suCtRole = "(user role is set to " <> ctRole <> ")."
|
||||
|
||||
deServiceRoleChanged :: GroupInfo -> GroupMemberRole -> IO ()
|
||||
@@ -691,7 +691,7 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName
|
||||
_ -> pure ()
|
||||
where
|
||||
groupRef = groupReference g
|
||||
srvRole = "*" <> strEncodeTxt serviceRole <> "*"
|
||||
srvRole = "*" <> textEncode serviceRole <> "*"
|
||||
suSrvRole = "(" <> serviceName <> " role is changed to " <> srvRole <> ")."
|
||||
whenContactIsOwner gr action =
|
||||
getOwnerGroupMember groupId gr
|
||||
@@ -801,7 +801,7 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName
|
||||
let anotherRole = case acceptMemberRole of GRObserver -> GRMember; _ -> GRObserver
|
||||
sendReply $
|
||||
initialRole n acceptMemberRole
|
||||
<> ("Send /'role " <> tshow gId <> " " <> strEncodeTxt anotherRole <> "' to change it.\n\n")
|
||||
<> ("Send /'role " <> tshow gId <> " " <> textEncode anotherRole <> "' to change it.\n\n")
|
||||
<> onlyViaLink gLink
|
||||
Left _ -> sendReply $ "Error: failed reading the initial member role for the group " <> n
|
||||
Just mRole -> do
|
||||
@@ -809,7 +809,7 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName
|
||||
Just gLink -> sendReply $ initialRole n mRole <> "\n" <> onlyViaLink gLink
|
||||
Nothing -> sendReply $ "Error: the initial member role for the group " <> n <> " was NOT upgated."
|
||||
where
|
||||
initialRole n mRole = "The initial member role for the group " <> n <> " is set to *" <> strEncodeTxt mRole <> "*\n"
|
||||
initialRole n mRole = "The initial member role for the group " <> n <> " is set to *" <> textEncode mRole <> "*\n"
|
||||
onlyViaLink gLink = "*Please note*: it applies only to members joining via this link: " <> groupLinkText gLink
|
||||
DCGroupFilter gId gName_ acceptance_ ->
|
||||
(if isAdmin then withGroupAndReg_ sendReply else withUserGroupReg_) gId gName_ $ \g _gr -> do
|
||||
@@ -852,7 +852,7 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName
|
||||
T.unlines $
|
||||
[ "The link to join the group " <> groupRef <> ":",
|
||||
groupLinkText gLink,
|
||||
"New member role: " <> strEncodeTxt acceptMemberRole
|
||||
"New member role: " <> textEncode acceptMemberRole
|
||||
]
|
||||
<> ["The link is being upgraded..." | shouldBeUpgraded]
|
||||
when shouldBeUpgraded $ do
|
||||
|
||||
@@ -124,6 +124,7 @@ library
|
||||
Simplex.Chat.Store.Postgres.Migrations.M20251017_chat_tags_cascade
|
||||
Simplex.Chat.Store.Postgres.Migrations.M20251117_member_relations_vector
|
||||
Simplex.Chat.Store.Postgres.Migrations.M20251128_migrate_member_relations
|
||||
Simplex.Chat.Store.Postgres.Migrations.M20251230_strict_tables
|
||||
else
|
||||
exposed-modules:
|
||||
Simplex.Chat.Archive
|
||||
@@ -271,6 +272,7 @@ library
|
||||
Simplex.Chat.Store.SQLite.Migrations.M20251017_chat_tags_cascade
|
||||
Simplex.Chat.Store.SQLite.Migrations.M20251117_member_relations_vector
|
||||
Simplex.Chat.Store.SQLite.Migrations.M20251128_migrate_member_relations
|
||||
Simplex.Chat.Store.SQLite.Migrations.M20251230_strict_tables
|
||||
other-modules:
|
||||
Paths_simplex_chat
|
||||
hs-source-dirs:
|
||||
|
||||
@@ -76,6 +76,7 @@ import Simplex.Messaging.Agent.Lock
|
||||
import Simplex.Messaging.Agent.Protocol
|
||||
import Simplex.Messaging.Agent.Store.Common (DBStore, withTransaction, withTransactionPriority)
|
||||
import Simplex.Messaging.Agent.Store.Shared (MigrationConfirmation, UpMigration)
|
||||
import Simplex.Messaging.Agent.Store.DB (SQLError)
|
||||
import qualified Simplex.Messaging.Agent.Store.DB as DB
|
||||
import Simplex.Messaging.Client (HostMode (..), SMPProxyFallback (..), SMPProxyMode (..), SMPWebPortServers (..), SocksMode (..))
|
||||
import qualified Simplex.Messaging.Crypto as C
|
||||
@@ -96,13 +97,7 @@ import Simplex.RemoteControl.Types
|
||||
import System.IO (Handle)
|
||||
import System.Mem.Weak (Weak)
|
||||
import UnliftIO.STM
|
||||
|
||||
#if defined(dbPostgres)
|
||||
import qualified Database.PostgreSQL.Simple as PSQL
|
||||
|
||||
type SQLError = PSQL.SqlError
|
||||
#else
|
||||
import Database.SQLite.Simple (SQLError)
|
||||
#if !defined(dbPostgres)
|
||||
import qualified Database.SQLite.Simple as SQL
|
||||
import Simplex.Messaging.Agent.Store.SQLite.DB (SlowQueryStats (..))
|
||||
#endif
|
||||
|
||||
@@ -22,7 +22,7 @@ import qualified Data.Attoparsec.ByteString.Char8 as A
|
||||
import qualified Data.ByteString.Lazy as LB
|
||||
import Data.Int (Int64)
|
||||
import Data.Text (Text)
|
||||
import Data.Text.Encoding (decodeLatin1, encodeUtf8)
|
||||
import Data.Text.Encoding (encodeUtf8)
|
||||
import Data.Type.Equality
|
||||
import Data.Word (Word32)
|
||||
import Simplex.Chat.Messages.CIContent.Events
|
||||
@@ -323,7 +323,7 @@ e2eInfoPQText =
|
||||
|
||||
ciGroupInvitationToText :: CIGroupInvitation -> GroupMemberRole -> Text
|
||||
ciGroupInvitationToText CIGroupInvitation {groupProfile = GroupProfile {displayName, fullName}} role =
|
||||
"invitation to join group " <> displayName <> optionalFullName displayName fullName Nothing <> " as " <> (decodeLatin1 . strEncode $ role)
|
||||
"invitation to join group " <> displayName <> optionalFullName displayName fullName Nothing <> " as " <> textEncode role
|
||||
|
||||
rcvDirectEventToText :: RcvDirectEvent -> Text
|
||||
rcvDirectEventToText = \case
|
||||
@@ -338,9 +338,9 @@ rcvGroupEventToText = \case
|
||||
RGEMemberAccepted _ p -> "accepted " <> profileToText p
|
||||
RGEUserAccepted -> "accepted you"
|
||||
RGEMemberLeft -> "left"
|
||||
RGEMemberRole _ p r -> "changed role of " <> profileToText p <> " to " <> safeDecodeUtf8 (strEncode r)
|
||||
RGEMemberRole _ p r -> "changed role of " <> profileToText p <> " to " <> textEncode r
|
||||
RGEMemberBlocked _ p blocked -> (if blocked then "blocked" else "unblocked") <> " " <> profileToText p
|
||||
RGEUserRole r -> "changed your role to " <> safeDecodeUtf8 (strEncode r)
|
||||
RGEUserRole r -> "changed your role to " <> textEncode r
|
||||
RGEMemberDeleted _ p -> "removed " <> profileToText p
|
||||
RGEUserDeleted -> "removed you"
|
||||
RGEGroupDeleted -> "deleted group"
|
||||
@@ -352,9 +352,9 @@ rcvGroupEventToText = \case
|
||||
|
||||
sndGroupEventToText :: SndGroupEvent -> Text
|
||||
sndGroupEventToText = \case
|
||||
SGEMemberRole _ p r -> "changed role of " <> profileToText p <> " to " <> safeDecodeUtf8 (strEncode r)
|
||||
SGEMemberRole _ p r -> "changed role of " <> profileToText p <> " to " <> textEncode r
|
||||
SGEMemberBlocked _ p blocked -> (if blocked then "blocked" else "unblocked") <> " " <> profileToText p
|
||||
SGEUserRole r -> "changed role for yourself to " <> safeDecodeUtf8 (strEncode r)
|
||||
SGEUserRole r -> "changed role for yourself to " <> textEncode r
|
||||
SGEMemberDeleted _ p -> "removed " <> profileToText p
|
||||
SGEUserLeft -> "left"
|
||||
SGEGroupUpdated _ -> "group profile updated"
|
||||
|
||||
@@ -5,11 +5,12 @@ module Simplex.Chat.Store.AppSettings where
|
||||
|
||||
import Control.Monad (join)
|
||||
import Control.Monad.IO.Class (liftIO)
|
||||
import qualified Data.Aeson as J
|
||||
import Data.Maybe (fromMaybe)
|
||||
import Simplex.Chat.AppSettings (AppSettings (..), combineAppSettings, defaultAppSettings, defaultParseAppSettings)
|
||||
import Simplex.Messaging.Agent.Store.AgentStore (maybeFirstRow)
|
||||
import qualified Simplex.Messaging.Agent.Store.DB as DB
|
||||
import Simplex.Messaging.Util (decodeJSON, encodeJSON)
|
||||
|
||||
#if defined(dbPostgres)
|
||||
import Database.PostgreSQL.Simple (Only (..))
|
||||
#else
|
||||
@@ -19,9 +20,9 @@ import Database.SQLite.Simple (Only (..))
|
||||
saveAppSettings :: DB.Connection -> AppSettings -> IO ()
|
||||
saveAppSettings db appSettings = do
|
||||
DB.execute_ db "DELETE FROM app_settings"
|
||||
DB.execute db "INSERT INTO app_settings (app_settings) VALUES (?)" (Only $ J.encode appSettings)
|
||||
DB.execute db "INSERT INTO app_settings (app_settings) VALUES (?)" (Only $ encodeJSON appSettings)
|
||||
|
||||
getAppSettings :: DB.Connection -> Maybe AppSettings -> IO AppSettings
|
||||
getAppSettings db platformDefaults = do
|
||||
stored_ <- join <$> liftIO (maybeFirstRow (J.decodeStrict . fromOnly) $ DB.query_ db "SELECT app_settings FROM app_settings")
|
||||
stored_ <- join <$> liftIO (maybeFirstRow (decodeJSON . fromOnly) $ DB.query_ db "SELECT app_settings FROM app_settings")
|
||||
pure $ combineAppSettings (fromMaybe defaultAppSettings platformDefaults) (fromMaybe defaultParseAppSettings stored_)
|
||||
|
||||
@@ -23,6 +23,7 @@ import Simplex.Chat.Store.Postgres.Migrations.M20251007_connections_sync
|
||||
import Simplex.Chat.Store.Postgres.Migrations.M20251017_chat_tags_cascade
|
||||
import Simplex.Chat.Store.Postgres.Migrations.M20251117_member_relations_vector
|
||||
import Simplex.Chat.Store.Postgres.Migrations.M20251128_migrate_member_relations
|
||||
import Simplex.Chat.Store.Postgres.Migrations.M20251230_strict_tables
|
||||
import Simplex.Messaging.Agent.Store.Shared (Migration (..))
|
||||
|
||||
schemaMigrations :: [(String, Text, Maybe Text)]
|
||||
@@ -45,7 +46,8 @@ schemaMigrations =
|
||||
("20251007_connections_sync", m20251007_connections_sync, Just down_m20251007_connections_sync),
|
||||
("20251017_chat_tags_cascade", m20251017_chat_tags_cascade, Just down_m20251017_chat_tags_cascade),
|
||||
("20251117_member_relations_vector", m20251117_member_relations_vector, Just down_m20251117_member_relations_vector),
|
||||
("20251128_migrate_member_relations", m20251128_migrate_member_relations, Just down_m20251128_migrate_member_relations)
|
||||
("20251128_migrate_member_relations", m20251128_migrate_member_relations, Just down_m20251128_migrate_member_relations),
|
||||
("20251230_strict_tables", m20251230_strict_tables, Just down_m20251230_strict_tables)
|
||||
]
|
||||
|
||||
-- | The list of migrations in ascending order by date
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
{-# LANGUAGE OverloadedStrings #-}
|
||||
{-# LANGUAGE QuasiQuotes #-}
|
||||
|
||||
module Simplex.Chat.Store.Postgres.Migrations.M20251230_strict_tables where
|
||||
|
||||
import Data.Text (Text)
|
||||
import Text.RawString.QQ (r)
|
||||
import Simplex.Messaging.Agent.Store.Postgres.Migrations.M20251230_strict_tables (isValidText)
|
||||
|
||||
m20251230_strict_tables :: Text
|
||||
m20251230_strict_tables =
|
||||
isValidText
|
||||
<> [r|
|
||||
DELETE FROM calls
|
||||
WHERE NOT simplex_is_valid_text(call_state);
|
||||
|
||||
ALTER TABLE calls ALTER COLUMN call_state TYPE TEXT USING call_state::TEXT;
|
||||
|
||||
DROP FUNCTION simplex_is_valid_text(BYTEA);
|
||||
|]
|
||||
|
||||
down_m20251230_strict_tables :: Text
|
||||
down_m20251230_strict_tables =
|
||||
[r|
|
||||
ALTER TABLE calls ALTER COLUMN call_state TYPE BYTEA USING call_state::BYTEA;
|
||||
|]
|
||||
@@ -173,7 +173,7 @@ CREATE TABLE test_chat_schema.calls (
|
||||
contact_id bigint NOT NULL,
|
||||
shared_call_id bytea NOT NULL,
|
||||
chat_item_id bigint NOT NULL,
|
||||
call_state bytea NOT NULL,
|
||||
call_state text NOT NULL,
|
||||
call_ts timestamp with time zone NOT NULL,
|
||||
user_id bigint NOT NULL,
|
||||
created_at timestamp with time zone DEFAULT now() NOT NULL,
|
||||
|
||||
@@ -146,6 +146,7 @@ import Simplex.Chat.Store.SQLite.Migrations.M20251007_connections_sync
|
||||
import Simplex.Chat.Store.SQLite.Migrations.M20251017_chat_tags_cascade
|
||||
import Simplex.Chat.Store.SQLite.Migrations.M20251117_member_relations_vector
|
||||
import Simplex.Chat.Store.SQLite.Migrations.M20251128_migrate_member_relations
|
||||
import Simplex.Chat.Store.SQLite.Migrations.M20251230_strict_tables
|
||||
import Simplex.Messaging.Agent.Store.Shared (Migration (..))
|
||||
|
||||
schemaMigrations :: [(String, Query, Maybe Query)]
|
||||
@@ -291,7 +292,8 @@ schemaMigrations =
|
||||
("20251007_connections_sync", m20251007_connections_sync, Just down_m20251007_connections_sync),
|
||||
("20251017_chat_tags_cascade", m20251017_chat_tags_cascade, Just down_m20251017_chat_tags_cascade),
|
||||
("20251117_member_relations_vector", m20251117_member_relations_vector, Just down_m20251117_member_relations_vector),
|
||||
("20251128_migrate_member_relations", m20251128_migrate_member_relations, Just down_m20251128_migrate_member_relations)
|
||||
("20251128_migrate_member_relations", m20251128_migrate_member_relations, Just down_m20251128_migrate_member_relations),
|
||||
("20251230_strict_tables", m20251230_strict_tables, Just down_m20251230_strict_tables)
|
||||
]
|
||||
|
||||
-- | The list of migrations in ascending order by date
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
{-# LANGUAGE QuasiQuotes #-}
|
||||
|
||||
module Simplex.Chat.Store.SQLite.Migrations.M20251230_strict_tables where
|
||||
|
||||
import Database.SQLite.Simple (Query)
|
||||
import Database.SQLite.Simple.QQ (sql)
|
||||
|
||||
m20251230_strict_tables :: Query
|
||||
m20251230_strict_tables =
|
||||
[sql|
|
||||
UPDATE group_members
|
||||
SET member_role = CAST(member_role as TEXT),
|
||||
member_restriction = CAST(member_restriction as TEXT);
|
||||
|
||||
UPDATE user_contact_links SET group_link_member_role = CAST(group_link_member_role as TEXT);
|
||||
|
||||
UPDATE app_settings SET app_settings = CAST(app_settings as TEXT);
|
||||
|
||||
PRAGMA writable_schema=1;
|
||||
|
||||
UPDATE sqlite_master
|
||||
SET sql = replace(sql, 'call_state BLOB NOT NULL', 'call_state TEXT NOT NULL')
|
||||
WHERE type = 'table' AND name = 'calls';
|
||||
|
||||
UPDATE sqlite_master
|
||||
SET sql = replace(sql, 'local_alias DEFAULT', 'local_alias TEXT DEFAULT')
|
||||
WHERE type = 'table' AND name = 'connections';
|
||||
|
||||
UPDATE sqlite_master
|
||||
SET sql = CASE
|
||||
WHEN LOWER(SUBSTR(sql, -15)) = ') without rowid' THEN sql || ', STRICT'
|
||||
WHEN SUBSTR(sql, -1) = ')' THEN sql || ' STRICT'
|
||||
ELSE sql
|
||||
END
|
||||
WHERE type = 'table' AND name != 'sqlite_sequence';
|
||||
|
||||
PRAGMA writable_schema=0;
|
||||
|]
|
||||
|
||||
down_m20251230_strict_tables :: Query
|
||||
down_m20251230_strict_tables =
|
||||
[sql|
|
||||
PRAGMA writable_schema=1;
|
||||
|
||||
UPDATE sqlite_master
|
||||
SET sql = CASE
|
||||
WHEN LOWER(SUBSTR(sql, -8)) = ', strict' THEN SUBSTR(sql, 1, LENGTH(sql) - 8)
|
||||
WHEN LOWER(SUBSTR(sql, -7)) = ' strict' THEN SUBSTR(sql, 1, LENGTH(sql) - 7)
|
||||
ELSE sql
|
||||
END
|
||||
WHERE type = 'table' AND name != 'sqlite_sequence';
|
||||
|
||||
UPDATE sqlite_master
|
||||
SET sql = replace(sql, 'call_state TEXT NOT NULL', 'call_state BLOB NOT NULL')
|
||||
WHERE type = 'table' AND name = 'calls';
|
||||
|
||||
UPDATE sqlite_master
|
||||
SET sql = replace(sql, 'local_alias TEXT DEFAULT', 'local_alias DEFAULT')
|
||||
WHERE type = 'table' AND name = 'connections';
|
||||
|
||||
PRAGMA writable_schema=0;
|
||||
|
||||
UPDATE group_members
|
||||
SET member_role = CAST(member_role as BLOB),
|
||||
member_restriction = CAST(member_restriction as BLOB);
|
||||
|
||||
UPDATE user_contact_links SET group_link_member_role = CAST(group_link_member_role as BLOB);
|
||||
|
||||
UPDATE app_settings SET app_settings = CAST(app_settings as BLOB);
|
||||
|]
|
||||
@@ -1255,10 +1255,6 @@ Query: UPDATE snd_file_chunk_replicas SET replica_status = ?, updated_at = ? WHE
|
||||
Plan:
|
||||
SEARCH snd_file_chunk_replicas USING INTEGER PRIMARY KEY (rowid=?)
|
||||
|
||||
Query: UPDATE snd_files SET deleted = 1, updated_at = ? WHERE snd_file_id = ?
|
||||
Plan:
|
||||
SEARCH snd_files USING INTEGER PRIMARY KEY (rowid=?)
|
||||
|
||||
Query: UPDATE snd_files SET prefix_path = NULL, status = ?, updated_at = ? WHERE snd_file_id = ?
|
||||
Plan:
|
||||
SEARCH snd_files USING INTEGER PRIMARY KEY (rowid=?)
|
||||
|
||||
@@ -2,7 +2,7 @@ CREATE TABLE migrations(
|
||||
name TEXT NOT NULL PRIMARY KEY,
|
||||
ts TEXT NOT NULL,
|
||||
down TEXT
|
||||
);
|
||||
) STRICT;
|
||||
CREATE TABLE contact_profiles(
|
||||
-- remote user profile
|
||||
contact_profile_id INTEGER PRIMARY KEY,
|
||||
@@ -20,7 +20,7 @@ CREATE TABLE contact_profiles(
|
||||
contact_link BLOB,
|
||||
short_descr TEXT,
|
||||
chat_peer_type TEXT
|
||||
);
|
||||
) STRICT;
|
||||
CREATE TABLE users(
|
||||
user_id INTEGER PRIMARY KEY,
|
||||
contact_id INTEGER NOT NULL UNIQUE REFERENCES contacts ON DELETE RESTRICT
|
||||
@@ -44,7 +44,7 @@ CREATE TABLE users(
|
||||
ON DELETE RESTRICT
|
||||
ON UPDATE CASCADE
|
||||
DEFERRABLE INITIALLY DEFERRED
|
||||
);
|
||||
) STRICT;
|
||||
CREATE TABLE display_names(
|
||||
user_id INTEGER NOT NULL REFERENCES users ON DELETE CASCADE,
|
||||
local_display_name TEXT NOT NULL,
|
||||
@@ -54,7 +54,7 @@ CREATE TABLE display_names(
|
||||
updated_at TEXT CHECK(updated_at NOT NULL),
|
||||
PRIMARY KEY(user_id, local_display_name) ON CONFLICT FAIL,
|
||||
UNIQUE(user_id, ldn_base, ldn_suffix) ON CONFLICT FAIL
|
||||
) WITHOUT ROWID;
|
||||
) WITHOUT ROWID, STRICT;
|
||||
CREATE TABLE contacts(
|
||||
contact_id INTEGER PRIMARY KEY,
|
||||
contact_profile_id INTEGER REFERENCES contact_profiles ON DELETE SET NULL,
|
||||
@@ -96,7 +96,7 @@ CREATE TABLE contacts(
|
||||
ON UPDATE CASCADE,
|
||||
UNIQUE(user_id, local_display_name),
|
||||
UNIQUE(user_id, contact_profile_id)
|
||||
);
|
||||
) STRICT;
|
||||
CREATE TABLE known_servers(
|
||||
server_id INTEGER PRIMARY KEY,
|
||||
host TEXT NOT NULL,
|
||||
@@ -106,7 +106,7 @@ CREATE TABLE known_servers(
|
||||
created_at TEXT CHECK(created_at NOT NULL),
|
||||
updated_at TEXT CHECK(updated_at NOT NULL),
|
||||
UNIQUE(user_id, host, port)
|
||||
) WITHOUT ROWID;
|
||||
) WITHOUT ROWID, STRICT;
|
||||
CREATE TABLE group_profiles(
|
||||
-- shared group profiles
|
||||
group_profile_id INTEGER PRIMARY KEY,
|
||||
@@ -122,7 +122,7 @@ CREATE TABLE group_profiles(
|
||||
description TEXT NULL,
|
||||
member_admission TEXT,
|
||||
short_descr TEXT
|
||||
);
|
||||
) STRICT;
|
||||
CREATE TABLE groups(
|
||||
group_id INTEGER PRIMARY KEY, -- local group ID
|
||||
user_id INTEGER NOT NULL REFERENCES users ON DELETE CASCADE,
|
||||
@@ -163,7 +163,7 @@ CREATE TABLE groups(
|
||||
ON UPDATE CASCADE,
|
||||
UNIQUE(user_id, local_display_name),
|
||||
UNIQUE(user_id, group_profile_id)
|
||||
);
|
||||
) STRICT;
|
||||
CREATE TABLE group_members(
|
||||
-- group members, excluding the local user
|
||||
group_member_id INTEGER PRIMARY KEY,
|
||||
@@ -203,7 +203,7 @@ CREATE TABLE group_members(
|
||||
ON DELETE CASCADE
|
||||
ON UPDATE CASCADE,
|
||||
UNIQUE(group_id, member_id)
|
||||
);
|
||||
) STRICT;
|
||||
CREATE TABLE group_member_intros(
|
||||
group_member_intro_id INTEGER PRIMARY KEY,
|
||||
re_group_member_id INTEGER NOT NULL REFERENCES group_members(group_member_id) ON DELETE CASCADE,
|
||||
@@ -215,7 +215,7 @@ CREATE TABLE group_member_intros(
|
||||
updated_at TEXT CHECK(updated_at NOT NULL),
|
||||
intro_chat_protocol_version INTEGER NOT NULL DEFAULT 3, -- see GroupMemberIntroStatus
|
||||
UNIQUE(re_group_member_id, to_group_member_id)
|
||||
);
|
||||
) STRICT;
|
||||
CREATE TABLE files(
|
||||
file_id INTEGER PRIMARY KEY,
|
||||
contact_id INTEGER REFERENCES contacts ON DELETE CASCADE,
|
||||
@@ -240,7 +240,7 @@ CREATE TABLE files(
|
||||
file_crypto_nonce BLOB,
|
||||
note_folder_id INTEGER DEFAULT NULL REFERENCES note_folders ON DELETE CASCADE,
|
||||
redirect_file_id INTEGER REFERENCES files ON DELETE CASCADE
|
||||
);
|
||||
) STRICT;
|
||||
CREATE TABLE snd_files(
|
||||
file_id INTEGER NOT NULL REFERENCES files ON DELETE CASCADE,
|
||||
connection_id INTEGER NOT NULL REFERENCES connections ON DELETE CASCADE,
|
||||
@@ -253,7 +253,7 @@ CREATE TABLE snd_files(
|
||||
file_descr_id INTEGER NULL
|
||||
REFERENCES xftp_file_descriptions ON DELETE SET NULL,
|
||||
PRIMARY KEY(file_id, connection_id)
|
||||
) WITHOUT ROWID;
|
||||
) WITHOUT ROWID, STRICT;
|
||||
CREATE TABLE rcv_files(
|
||||
file_id INTEGER PRIMARY KEY REFERENCES files ON DELETE CASCADE,
|
||||
file_status TEXT NOT NULL, -- new, accepted, connected, completed
|
||||
@@ -270,7 +270,7 @@ CREATE TABLE rcv_files(
|
||||
agent_rcv_file_deleted INTEGER DEFAULT 0 CHECK(agent_rcv_file_deleted NOT NULL),
|
||||
to_receive INTEGER,
|
||||
user_approved_relays INTEGER NOT NULL DEFAULT 0
|
||||
);
|
||||
) STRICT;
|
||||
CREATE TABLE rcv_file_chunks(
|
||||
file_id INTEGER NOT NULL REFERENCES rcv_files ON DELETE CASCADE,
|
||||
chunk_number INTEGER NOT NULL,
|
||||
@@ -279,7 +279,7 @@ CREATE TABLE rcv_file_chunks(
|
||||
created_at TEXT CHECK(created_at NOT NULL),
|
||||
updated_at TEXT CHECK(updated_at NOT NULL), -- 0(received), 1(appended to file)
|
||||
PRIMARY KEY(file_id, chunk_number)
|
||||
) WITHOUT ROWID;
|
||||
) WITHOUT ROWID, STRICT;
|
||||
CREATE TABLE connections(
|
||||
-- all SMP agent connections
|
||||
connection_id INTEGER PRIMARY KEY,
|
||||
@@ -302,7 +302,7 @@ CREATE TABLE connections(
|
||||
REFERENCES user_contact_links(user_contact_link_id) ON DELETE SET NULL,
|
||||
custom_user_profile_id INTEGER REFERENCES contact_profiles ON DELETE SET NULL,
|
||||
conn_req_inv BLOB,
|
||||
local_alias DEFAULT '' CHECK(local_alias NOT NULL),
|
||||
local_alias TEXT DEFAULT '' CHECK(local_alias NOT NULL),
|
||||
via_group_link INTEGER DEFAULT 0 CHECK(via_group_link NOT NULL),
|
||||
group_link_id BLOB,
|
||||
security_code TEXT NULL,
|
||||
@@ -325,7 +325,7 @@ CREATE TABLE connections(
|
||||
REFERENCES snd_files(file_id, connection_id)
|
||||
ON DELETE CASCADE
|
||||
DEFERRABLE INITIALLY DEFERRED
|
||||
);
|
||||
) STRICT;
|
||||
CREATE TABLE user_contact_links(
|
||||
user_contact_link_id INTEGER PRIMARY KEY,
|
||||
conn_req_contact BLOB NOT NULL,
|
||||
@@ -344,7 +344,7 @@ CREATE TABLE user_contact_links(
|
||||
short_link_data_set INTEGER NOT NULL DEFAULT 0,
|
||||
short_link_large_data_set INTEGER NOT NULL DEFAULT 0,
|
||||
UNIQUE(user_id, local_display_name)
|
||||
);
|
||||
) STRICT;
|
||||
CREATE TABLE contact_requests(
|
||||
contact_request_id INTEGER PRIMARY KEY,
|
||||
user_contact_link_id INTEGER REFERENCES user_contact_links
|
||||
@@ -372,7 +372,7 @@ CREATE TABLE contact_requests(
|
||||
DEFERRABLE INITIALLY DEFERRED,
|
||||
UNIQUE(user_id, local_display_name),
|
||||
UNIQUE(user_id, contact_profile_id)
|
||||
);
|
||||
) STRICT;
|
||||
CREATE TABLE messages(
|
||||
message_id INTEGER PRIMARY KEY,
|
||||
msg_sent INTEGER NOT NULL, -- 0 for received, 1 for sent
|
||||
@@ -388,14 +388,14 @@ CREATE TABLE messages(
|
||||
author_group_member_id INTEGER REFERENCES group_members ON DELETE SET NULL,
|
||||
forwarded_by_group_member_id INTEGER REFERENCES group_members ON DELETE SET NULL,
|
||||
broker_ts TEXT
|
||||
);
|
||||
) STRICT;
|
||||
CREATE TABLE pending_group_messages(
|
||||
pending_group_message_id INTEGER PRIMARY KEY,
|
||||
group_member_id INTEGER NOT NULL REFERENCES group_members ON DELETE CASCADE,
|
||||
message_id INTEGER NOT NULL REFERENCES messages ON DELETE CASCADE,
|
||||
created_at TEXT NOT NULL DEFAULT(datetime('now')),
|
||||
updated_at TEXT NOT NULL DEFAULT(datetime('now'))
|
||||
);
|
||||
) STRICT;
|
||||
CREATE TABLE chat_items(
|
||||
chat_item_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER NOT NULL REFERENCES users ON DELETE CASCADE,
|
||||
@@ -441,7 +441,7 @@ CREATE TABLE chat_items(
|
||||
group_scope_tag TEXT,
|
||||
group_scope_group_member_id INTEGER REFERENCES group_members(group_member_id) ON DELETE CASCADE,
|
||||
show_group_as_sender INTEGER NOT NULL DEFAULT 0
|
||||
);
|
||||
) STRICT;
|
||||
CREATE TABLE sqlite_sequence(name,seq);
|
||||
CREATE TABLE chat_item_messages(
|
||||
chat_item_id INTEGER NOT NULL REFERENCES chat_items ON DELETE CASCADE,
|
||||
@@ -449,21 +449,21 @@ CREATE TABLE chat_item_messages(
|
||||
created_at TEXT NOT NULL DEFAULT(datetime('now')),
|
||||
updated_at TEXT NOT NULL DEFAULT(datetime('now')),
|
||||
UNIQUE(chat_item_id, message_id)
|
||||
);
|
||||
) STRICT;
|
||||
CREATE TABLE calls(
|
||||
-- stores call invitations state for communicating state between NSE and app when call notification comes
|
||||
call_id INTEGER PRIMARY KEY,
|
||||
contact_id INTEGER NOT NULL REFERENCES contacts ON DELETE CASCADE,
|
||||
shared_call_id BLOB NOT NULL,
|
||||
chat_item_id INTEGER NOT NULL REFERENCES chat_items ON DELETE CASCADE,
|
||||
call_state BLOB NOT NULL,
|
||||
call_state TEXT NOT NULL,
|
||||
call_ts TEXT NOT NULL,
|
||||
user_id INTEGER NOT NULL REFERENCES users ON DELETE CASCADE,
|
||||
created_at TEXT NOT NULL DEFAULT(datetime('now')),
|
||||
updated_at TEXT NOT NULL DEFAULT(datetime('now'))
|
||||
,
|
||||
call_uuid TEXT NOT NULL DEFAULT ""
|
||||
);
|
||||
) STRICT;
|
||||
CREATE TABLE commands(
|
||||
command_id INTEGER PRIMARY KEY AUTOINCREMENT, -- used as ACorrId
|
||||
connection_id INTEGER REFERENCES connections ON DELETE CASCADE,
|
||||
@@ -472,14 +472,14 @@ CREATE TABLE commands(
|
||||
user_id INTEGER NOT NULL REFERENCES users ON DELETE CASCADE,
|
||||
created_at TEXT NOT NULL DEFAULT(datetime('now')),
|
||||
updated_at TEXT NOT NULL DEFAULT(datetime('now'))
|
||||
);
|
||||
) STRICT;
|
||||
CREATE TABLE settings(
|
||||
settings_id INTEGER PRIMARY KEY,
|
||||
chat_item_ttl INTEGER,
|
||||
user_id INTEGER NOT NULL REFERENCES users ON DELETE CASCADE,
|
||||
created_at TEXT NOT NULL DEFAULT(datetime('now')),
|
||||
updated_at TEXT NOT NULL DEFAULT(datetime('now'))
|
||||
);
|
||||
) STRICT;
|
||||
CREATE TABLE IF NOT EXISTS "protocol_servers"(
|
||||
smp_server_id INTEGER PRIMARY KEY,
|
||||
host TEXT NOT NULL,
|
||||
@@ -494,7 +494,7 @@ CREATE TABLE IF NOT EXISTS "protocol_servers"(
|
||||
updated_at TEXT NOT NULL DEFAULT(datetime('now')),
|
||||
protocol TEXT NOT NULL DEFAULT 'smp',
|
||||
UNIQUE(user_id, host, port)
|
||||
);
|
||||
) STRICT;
|
||||
CREATE TABLE xftp_file_descriptions(
|
||||
file_descr_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER NOT NULL REFERENCES users ON DELETE CASCADE,
|
||||
@@ -503,7 +503,7 @@ CREATE TABLE xftp_file_descriptions(
|
||||
file_descr_complete INTEGER NOT NULL DEFAULT(0),
|
||||
created_at TEXT NOT NULL DEFAULT(datetime('now')),
|
||||
updated_at TEXT NOT NULL DEFAULT(datetime('now'))
|
||||
);
|
||||
) STRICT;
|
||||
CREATE TABLE extra_xftp_file_descriptions(
|
||||
extra_file_descr_id INTEGER PRIMARY KEY,
|
||||
file_id INTEGER NOT NULL REFERENCES files ON DELETE CASCADE,
|
||||
@@ -511,7 +511,7 @@ CREATE TABLE extra_xftp_file_descriptions(
|
||||
file_descr_text TEXT NOT NULL,
|
||||
created_at TEXT NOT NULL DEFAULT(datetime('now')),
|
||||
updated_at TEXT NOT NULL DEFAULT(datetime('now'))
|
||||
);
|
||||
) STRICT;
|
||||
CREATE TABLE chat_item_versions(
|
||||
-- contains versions only for edited chat items, including current version
|
||||
chat_item_version_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
@@ -520,7 +520,7 @@ CREATE TABLE chat_item_versions(
|
||||
item_version_ts TEXT NOT NULL,
|
||||
created_at TEXT NOT NULL DEFAULT(datetime('now')),
|
||||
updated_at TEXT NOT NULL DEFAULT(datetime('now'))
|
||||
);
|
||||
) STRICT;
|
||||
CREATE TABLE chat_item_reactions(
|
||||
chat_item_reaction_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
item_member_id BLOB, -- member that created item, NULL for items in direct chats
|
||||
@@ -534,7 +534,7 @@ CREATE TABLE chat_item_reactions(
|
||||
reaction_ts TEXT NOT NULL, -- broker_ts of creating message for received, created_at for sent
|
||||
created_at TEXT NOT NULL DEFAULT(datetime('now')),
|
||||
updated_at TEXT NOT NULL DEFAULT(datetime('now'))
|
||||
);
|
||||
) STRICT;
|
||||
CREATE TABLE chat_item_moderations(
|
||||
chat_item_moderation_id INTEGER PRIMARY KEY,
|
||||
group_id INTEGER NOT NULL REFERENCES groups ON DELETE CASCADE,
|
||||
@@ -545,7 +545,7 @@ CREATE TABLE chat_item_moderations(
|
||||
moderated_at TEXT NOT NULL, -- broker_ts of creating message
|
||||
created_at TEXT NOT NULL DEFAULT(datetime('now')),
|
||||
updated_at TEXT NOT NULL DEFAULT(datetime('now'))
|
||||
);
|
||||
) STRICT;
|
||||
CREATE TABLE group_snd_item_statuses(
|
||||
group_snd_item_status_id INTEGER PRIMARY KEY,
|
||||
chat_item_id INTEGER NOT NULL REFERENCES chat_items ON DELETE CASCADE,
|
||||
@@ -555,7 +555,7 @@ CREATE TABLE group_snd_item_statuses(
|
||||
updated_at TEXT NOT NULL DEFAULT(datetime('now'))
|
||||
,
|
||||
via_proxy INTEGER
|
||||
);
|
||||
) STRICT;
|
||||
CREATE TABLE IF NOT EXISTS "sent_probes"(
|
||||
sent_probe_id INTEGER PRIMARY KEY,
|
||||
contact_id INTEGER REFERENCES contacts ON DELETE CASCADE,
|
||||
@@ -565,7 +565,7 @@ CREATE TABLE IF NOT EXISTS "sent_probes"(
|
||||
created_at TEXT CHECK(created_at NOT NULL),
|
||||
updated_at TEXT CHECK(updated_at NOT NULL),
|
||||
UNIQUE(user_id, probe)
|
||||
);
|
||||
) STRICT;
|
||||
CREATE TABLE IF NOT EXISTS "sent_probe_hashes"(
|
||||
sent_probe_hash_id INTEGER PRIMARY KEY,
|
||||
sent_probe_id INTEGER NOT NULL REFERENCES "sent_probes" ON DELETE CASCADE,
|
||||
@@ -574,7 +574,7 @@ CREATE TABLE IF NOT EXISTS "sent_probe_hashes"(
|
||||
user_id INTEGER NOT NULL REFERENCES users ON DELETE CASCADE,
|
||||
created_at TEXT CHECK(created_at NOT NULL),
|
||||
updated_at TEXT CHECK(updated_at NOT NULL)
|
||||
);
|
||||
) STRICT;
|
||||
CREATE TABLE IF NOT EXISTS "received_probes"(
|
||||
received_probe_id INTEGER PRIMARY KEY,
|
||||
contact_id INTEGER REFERENCES contacts ON DELETE CASCADE,
|
||||
@@ -584,7 +584,7 @@ CREATE TABLE IF NOT EXISTS "received_probes"(
|
||||
user_id INTEGER NOT NULL REFERENCES users ON DELETE CASCADE,
|
||||
created_at TEXT CHECK(created_at NOT NULL),
|
||||
updated_at TEXT CHECK(updated_at NOT NULL)
|
||||
);
|
||||
) STRICT;
|
||||
CREATE TABLE remote_hosts(
|
||||
-- e.g., mobiles known to a desktop app
|
||||
remote_host_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
@@ -599,7 +599,7 @@ CREATE TABLE remote_hosts(
|
||||
bind_addr TEXT,
|
||||
bind_iface TEXT,
|
||||
bind_port INTEGER
|
||||
);
|
||||
) STRICT;
|
||||
CREATE TABLE remote_controllers(
|
||||
-- e.g., desktops known to a mobile app
|
||||
remote_ctrl_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
@@ -610,7 +610,7 @@ CREATE TABLE remote_controllers(
|
||||
id_pub BLOB NOT NULL, -- remote controller long-term/identity key to verify signatures
|
||||
dh_priv_key BLOB NOT NULL, -- last session DH key
|
||||
prev_dh_priv_key BLOB -- previous session DH key
|
||||
);
|
||||
) STRICT;
|
||||
CREATE TABLE IF NOT EXISTS "msg_deliveries"(
|
||||
msg_delivery_id INTEGER PRIMARY KEY,
|
||||
message_id INTEGER NOT NULL REFERENCES messages ON DELETE CASCADE, -- non UNIQUE for group messages and for batched messages
|
||||
@@ -621,7 +621,7 @@ CREATE TABLE IF NOT EXISTS "msg_deliveries"(
|
||||
created_at TEXT CHECK(created_at NOT NULL),
|
||||
updated_at TEXT CHECK(updated_at NOT NULL),
|
||||
delivery_status TEXT -- MsgDeliveryStatus
|
||||
);
|
||||
) STRICT;
|
||||
CREATE TABLE note_folders(
|
||||
note_folder_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER NOT NULL REFERENCES users ON DELETE CASCADE,
|
||||
@@ -630,8 +630,8 @@ CREATE TABLE note_folders(
|
||||
chat_ts TEXT NOT NULL DEFAULT(datetime('now')),
|
||||
favorite INTEGER NOT NULL DEFAULT 0,
|
||||
unread_chat INTEGER NOT NULL DEFAULT 0
|
||||
);
|
||||
CREATE TABLE app_settings(app_settings TEXT NOT NULL);
|
||||
) STRICT;
|
||||
CREATE TABLE app_settings(app_settings TEXT NOT NULL) STRICT;
|
||||
CREATE TABLE server_operators(
|
||||
server_operator_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
server_operator_tag TEXT,
|
||||
@@ -645,14 +645,14 @@ CREATE TABLE server_operators(
|
||||
xftp_role_proxy INTEGER NOT NULL DEFAULT 1,
|
||||
created_at TEXT NOT NULL DEFAULT(datetime('now')),
|
||||
updated_at TEXT NOT NULL DEFAULT(datetime('now'))
|
||||
);
|
||||
) STRICT;
|
||||
CREATE TABLE usage_conditions(
|
||||
usage_conditions_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
conditions_commit TEXT NOT NULL UNIQUE,
|
||||
notified_at TEXT,
|
||||
created_at TEXT NOT NULL DEFAULT(datetime('now')),
|
||||
updated_at TEXT NOT NULL DEFAULT(datetime('now'))
|
||||
);
|
||||
) STRICT;
|
||||
CREATE TABLE operator_usage_conditions(
|
||||
operator_usage_conditions_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
server_operator_id INTEGER REFERENCES server_operators(server_operator_id) ON DELETE SET NULL ON UPDATE CASCADE,
|
||||
@@ -662,26 +662,26 @@ CREATE TABLE operator_usage_conditions(
|
||||
created_at TEXT NOT NULL DEFAULT(datetime('now'))
|
||||
,
|
||||
auto_accepted INTEGER DEFAULT 0
|
||||
);
|
||||
) STRICT;
|
||||
CREATE TABLE chat_tags(
|
||||
chat_tag_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER REFERENCES users ON DELETE CASCADE,
|
||||
chat_tag_text TEXT NOT NULL,
|
||||
chat_tag_emoji TEXT,
|
||||
tag_order INTEGER NOT NULL
|
||||
);
|
||||
) STRICT;
|
||||
CREATE TABLE chat_tags_chats(
|
||||
contact_id INTEGER REFERENCES contacts ON DELETE CASCADE,
|
||||
group_id INTEGER REFERENCES groups ON DELETE CASCADE,
|
||||
chat_tag_id INTEGER NOT NULL REFERENCES chat_tags ON DELETE CASCADE
|
||||
);
|
||||
) STRICT;
|
||||
CREATE TABLE chat_item_mentions(
|
||||
chat_item_mention_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
group_id INTEGER NOT NULL REFERENCES groups ON DELETE CASCADE,
|
||||
member_id BLOB NOT NULL,
|
||||
chat_item_id INTEGER NOT NULL REFERENCES chat_items ON DELETE CASCADE,
|
||||
display_name TEXT NOT NULL
|
||||
);
|
||||
) STRICT;
|
||||
CREATE TABLE delivery_tasks(
|
||||
delivery_task_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
group_id INTEGER NOT NULL REFERENCES groups ON DELETE CASCADE,
|
||||
@@ -697,7 +697,7 @@ CREATE TABLE delivery_tasks(
|
||||
failed INTEGER DEFAULT 0,
|
||||
created_at TEXT NOT NULL DEFAULT(datetime('now')),
|
||||
updated_at TEXT NOT NULL DEFAULT(datetime('now'))
|
||||
);
|
||||
) STRICT;
|
||||
CREATE TABLE delivery_jobs(
|
||||
delivery_job_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
group_id INTEGER NOT NULL REFERENCES groups ON DELETE CASCADE,
|
||||
@@ -713,16 +713,16 @@ CREATE TABLE delivery_jobs(
|
||||
failed INTEGER DEFAULT 0,
|
||||
created_at TEXT NOT NULL DEFAULT(datetime('now')),
|
||||
updated_at TEXT NOT NULL DEFAULT(datetime('now'))
|
||||
);
|
||||
) STRICT;
|
||||
CREATE TABLE group_member_status_predicates(
|
||||
member_status TEXT NOT NULL PRIMARY KEY,
|
||||
current_member INTEGER NOT NULL DEFAULT 0
|
||||
);
|
||||
) STRICT;
|
||||
CREATE TABLE connections_sync(
|
||||
connections_sync_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
should_sync INTEGER NOT NULL DEFAULT 0,
|
||||
last_sync_ts TEXT
|
||||
);
|
||||
) STRICT;
|
||||
CREATE INDEX contact_profiles_index ON contact_profiles(
|
||||
display_name,
|
||||
full_name
|
||||
|
||||
@@ -58,7 +58,7 @@ 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, sumTypeJSON)
|
||||
import Simplex.Messaging.Util (decodeJSON, encodeJSON, safeDecodeUtf8, (<$?>))
|
||||
import Simplex.Messaging.Util (decodeJSON, encodeJSON, safeDecodeUtf8)
|
||||
import Simplex.Messaging.Version
|
||||
import Simplex.Messaging.Version.Internal
|
||||
#if defined(dbPostgres)
|
||||
@@ -871,27 +871,26 @@ data MemberRestrictionStatus
|
||||
| MRSUnknown Text
|
||||
deriving (Eq, Show)
|
||||
|
||||
instance FromField MemberRestrictionStatus where fromField = blobFieldDecoder strDecode
|
||||
instance FromField MemberRestrictionStatus where fromField = fromTextField_ textDecode
|
||||
|
||||
instance ToField MemberRestrictionStatus where toField = toField . strEncode
|
||||
instance ToField MemberRestrictionStatus where toField = toField . textEncode
|
||||
|
||||
instance StrEncoding MemberRestrictionStatus where
|
||||
strEncode = \case
|
||||
instance TextEncoding MemberRestrictionStatus where
|
||||
textEncode = \case
|
||||
MRSBlocked -> "blocked"
|
||||
MRSUnrestricted -> "unrestricted"
|
||||
MRSUnknown tag -> encodeUtf8 tag
|
||||
strDecode s = Right $ case s of
|
||||
MRSUnknown tag -> tag
|
||||
textDecode s = Just $ case s of
|
||||
"blocked" -> MRSBlocked
|
||||
"unrestricted" -> MRSUnrestricted
|
||||
tag -> MRSUnknown $ safeDecodeUtf8 tag
|
||||
strP = strDecode <$?> A.takeByteString
|
||||
tag -> MRSUnknown tag
|
||||
|
||||
instance FromJSON MemberRestrictionStatus where
|
||||
parseJSON = strParseJSON "MemberRestrictionStatus"
|
||||
parseJSON = textParseJSON "MemberRestrictionStatus"
|
||||
|
||||
instance ToJSON MemberRestrictionStatus where
|
||||
toJSON = strToJSON
|
||||
toEncoding = strToJEncoding
|
||||
toJSON = textToJSON
|
||||
toEncoding = textToEncoding
|
||||
|
||||
mrsBlocked :: MemberRestrictionStatus -> Bool
|
||||
mrsBlocked = \case
|
||||
|
||||
@@ -782,7 +782,7 @@ groupPrefStateText :: HasField "enable" p GroupFeatureEnabled => GroupFeature ->
|
||||
groupPrefStateText feature pref param role =
|
||||
let enabled = getField @"enable" pref
|
||||
paramText = if enabled == FEOn then groupParamText_ feature param else ""
|
||||
roleText = maybe "" (\r -> " for " <> safeDecodeUtf8 (strEncode r) <> "s") role
|
||||
roleText = maybe "" (\r -> " for " <> textEncode r <> "s") role
|
||||
in groupFeatureNameText feature <> ": " <> safeDecodeUtf8 (strEncode enabled) <> paramText <> roleText
|
||||
|
||||
groupParamText_ :: GroupFeature -> Maybe Int -> Text
|
||||
|
||||
@@ -7,7 +7,7 @@ import Data.Aeson (FromJSON (..), ToJSON (..))
|
||||
import qualified Data.Attoparsec.ByteString.Char8 as A
|
||||
import qualified Data.ByteString.Char8 as B
|
||||
import Simplex.Chat.Options.DB (FromField (..), ToField (..))
|
||||
import Simplex.Messaging.Agent.Store.DB (blobFieldDecoder)
|
||||
import Simplex.Messaging.Agent.Store.DB (fromTextField_)
|
||||
import Simplex.Messaging.Encoding.String
|
||||
import Simplex.Messaging.Util ((<$?>))
|
||||
|
||||
@@ -20,34 +20,33 @@ data GroupMemberRole
|
||||
| GROwner -- + delete and change group information, add/remove/change roles for Owners
|
||||
deriving (Eq, Show, Ord)
|
||||
|
||||
instance FromField GroupMemberRole where fromField = blobFieldDecoder strDecode
|
||||
instance FromField GroupMemberRole where fromField = fromTextField_ textDecode
|
||||
|
||||
instance ToField GroupMemberRole where toField = toField . strEncode
|
||||
instance ToField GroupMemberRole where toField = toField . textEncode
|
||||
|
||||
instance StrEncoding GroupMemberRole where
|
||||
strEncode = \case
|
||||
instance TextEncoding GroupMemberRole where
|
||||
textEncode = \case
|
||||
GROwner -> "owner"
|
||||
GRAdmin -> "admin"
|
||||
GRModerator -> "moderator"
|
||||
GRMember -> "member"
|
||||
GRAuthor -> "author"
|
||||
GRObserver -> "observer"
|
||||
strDecode = \case
|
||||
"owner" -> Right GROwner
|
||||
"admin" -> Right GRAdmin
|
||||
"moderator" -> Right GRModerator
|
||||
"member" -> Right GRMember
|
||||
"author" -> Right GRAuthor
|
||||
"observer" -> Right GRObserver
|
||||
r -> Left $ "bad GroupMemberRole " <> B.unpack r
|
||||
strP = strDecode <$?> A.takeByteString
|
||||
textDecode = \case
|
||||
"owner" -> Just GROwner
|
||||
"admin" -> Just GRAdmin
|
||||
"moderator" -> Just GRModerator
|
||||
"member" -> Just GRMember
|
||||
"author" -> Just GRAuthor
|
||||
"observer" -> Just GRObserver
|
||||
r -> Nothing
|
||||
|
||||
instance FromJSON GroupMemberRole where
|
||||
parseJSON = strParseJSON "GroupMemberRole"
|
||||
parseJSON = textParseJSON "GroupMemberRole"
|
||||
|
||||
instance ToJSON GroupMemberRole where
|
||||
toJSON = strToJSON
|
||||
toEncoding = strToJEncoding
|
||||
toJSON = textToJSON
|
||||
toEncoding = textToEncoding
|
||||
|
||||
data GroupAcceptance = GAAccepted | GAPendingApproval | GAPendingReview deriving (Eq, Show)
|
||||
|
||||
|
||||
@@ -1231,7 +1231,7 @@ viewConnectedToGroupMember g@GroupInfo {groupId} m@GroupMember {groupMemberId, m
|
||||
|
||||
viewReceivedGroupInvitation :: GroupInfo -> Contact -> GroupMemberRole -> [StyledString]
|
||||
viewReceivedGroupInvitation g c role =
|
||||
ttyFullGroup g <> ": " <> ttyContact' c <> " invites you to join the group as " <> plain (strEncode role)
|
||||
ttyFullGroup g <> ": " <> ttyContact' c <> " invites you to join the group as " <> showRole role
|
||||
: case incognitoMembershipProfile g of
|
||||
Just mp -> ["use " <> highlight ("/j " <> viewGroupName g) <> " to join incognito as " <> incognitoProfile' (fromLocalProfile mp)]
|
||||
Nothing -> ["use " <> highlight ("/j " <> viewGroupName g) <> " to accept"]
|
||||
@@ -1270,15 +1270,15 @@ viewMembersBlockedForAllUser g members blocked = case members of
|
||||
mems' -> [ttyGroup' g <> ": you " <> (if blocked then "blocked" else "unblocked") <> " " <> sShow (length mems') <> " members"]
|
||||
|
||||
showRole :: GroupMemberRole -> StyledString
|
||||
showRole = plain . strEncode
|
||||
showRole = plain . textEncode
|
||||
|
||||
viewGroupMembers :: Group -> [StyledString]
|
||||
viewGroupMembers (Group GroupInfo {membership} members) = map groupMember . filter (not . removedOrLeft) $ membership : members
|
||||
where
|
||||
removedOrLeft m = let s = memberStatus m in s == GSMemRejected || s == GSMemRemoved || s == GSMemLeft
|
||||
groupMember m = memIncognito m <> ttyFullMember m <> ": " <> plain (intercalate ", " $ [role m] <> category m <> status m <> muted m)
|
||||
role :: GroupMember -> String
|
||||
role GroupMember {memberRole} = B.unpack $ strEncode memberRole
|
||||
groupMember m = memIncognito m <> ttyFullMember m <> ": " <> plain (T.intercalate ", " $ [role m] <> category m <> status m <> muted m)
|
||||
role :: GroupMember -> Text
|
||||
role GroupMember {memberRole} = textEncode memberRole
|
||||
category m = case memberCategory m of
|
||||
GCUserMember -> ["you"]
|
||||
GCInviteeMember -> ["invited"]
|
||||
@@ -2455,8 +2455,8 @@ viewChatError isCmd logLevel testView = \case
|
||||
CEGroupUserRole g role ->
|
||||
(: []) . (ttyGroup' g <>) $ case role of
|
||||
GRAuthor -> ": you don't have permission to send messages"
|
||||
_ -> ": you have insufficient permissions for this action, the required role is " <> plain (strEncode role)
|
||||
CEGroupMemberInitialRole g role -> [ttyGroup' g <> ": initial role for group member cannot be " <> plain (strEncode role) <> ", use member or observer"]
|
||||
_ -> ": you have insufficient permissions for this action, the required role is " <> showRole role
|
||||
CEGroupMemberInitialRole g role -> [ttyGroup' g <> ": initial role for group member cannot be " <> showRole role <> ", use member or observer"]
|
||||
CEContactIncognitoCantInvite -> ["you're using your main profile for this group - prohibited to invite contacts to whom you are connected incognito"]
|
||||
CEGroupIncognitoCantInvite -> ["you are using an incognito profile for this group - prohibited to invite contacts"]
|
||||
CEGroupContactRole c -> ["contact " <> ttyContact c <> " has insufficient permissions for this group action"]
|
||||
|
||||
@@ -8389,7 +8389,7 @@ testChannelsRelayDeliver =
|
||||
createChannel5 :: TestCC -> TestCC -> TestCC -> TestCC -> TestCC -> GroupMemberRole -> IO ()
|
||||
createChannel5 alice bob cath dan eve mRole = do
|
||||
createGroup2 "team" alice bob
|
||||
bob ##> ("/create link #team " <> B.unpack (strEncode mRole))
|
||||
bob ##> ("/create link #team " <> T.unpack (textEncode mRole))
|
||||
gLink <- getGroupLink bob "team" mRole True
|
||||
cath ##> ("/c " <> gLink)
|
||||
cath <## "connection request sent!"
|
||||
|
||||
@@ -593,7 +593,7 @@ getGroupLink_ cc gName mRole created = do
|
||||
cc <## ""
|
||||
link <- getTermLine cc
|
||||
cc <## ""
|
||||
cc <## ("Anybody can connect to you and join group as " <> B.unpack (strEncode mRole) <> " with: /c <group_link_above>")
|
||||
cc <## ("Anybody can connect to you and join group as " <> T.unpack (textEncode mRole) <> " with: /c <group_link_above>")
|
||||
cc <## ("to show it again: /show link #" <> gName)
|
||||
cc <## ("to delete it: /delete link #" <> gName <> " (joined members will remain connected to you)")
|
||||
pure link
|
||||
@@ -809,12 +809,12 @@ fullAddMember :: HasCallStack => String -> String -> TestCC -> TestCC -> GroupMe
|
||||
fullAddMember gName fullName inviting invitee role = do
|
||||
name1 <- userName inviting
|
||||
memName <- userName invitee
|
||||
inviting ##> ("/a " <> gName <> " " <> memName <> " " <> B.unpack (strEncode role))
|
||||
inviting ##> ("/a " <> gName <> " " <> memName <> " " <> T.unpack (textEncode role))
|
||||
let fullName' = if null fullName || fullName == gName then "" else " (" <> fullName <> ")"
|
||||
concurrentlyN_
|
||||
[ inviting <## ("invitation to join the group #" <> gName <> " sent to " <> memName),
|
||||
do
|
||||
invitee <## ("#" <> gName <> fullName' <> ": " <> name1 <> " invites you to join the group as " <> B.unpack (strEncode role))
|
||||
invitee <## ("#" <> gName <> fullName' <> ": " <> name1 <> " invites you to join the group as " <> T.unpack (textEncode role))
|
||||
invitee <## ("use /j " <> gName <> " to accept")
|
||||
]
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ import Data.Maybe (fromJust, isJust)
|
||||
import Data.Text (Text)
|
||||
import qualified Data.Text as T
|
||||
import qualified Data.Text.IO as T
|
||||
import Database.SQLite.Simple (Query (..))
|
||||
import Database.SQLite.Simple (Only (..), Query (..))
|
||||
import Simplex.Chat.Options.SQLite (chatDBFunctions)
|
||||
import Simplex.Chat.Store (createChatStore)
|
||||
import qualified Simplex.Chat.Store as Store
|
||||
@@ -59,6 +59,7 @@ schemaDumpTest = do
|
||||
it "verify and overwrite schema dump" testVerifySchemaDump
|
||||
it "verify .lint fkey-indexes" testVerifyLintFKeyIndexes
|
||||
it "verify schema down migrations" testSchemaMigrations
|
||||
it "verify strict tables" testVerifyStrict
|
||||
|
||||
testVerifySchemaDump :: IO ()
|
||||
testVerifySchemaDump = withTmpFiles $ do
|
||||
@@ -101,6 +102,12 @@ testSchemaMigrations = withTmpFiles $ do
|
||||
schema''' <- getSchema testDB testSchema
|
||||
schema''' `shouldBe` schema'
|
||||
|
||||
testVerifyStrict :: IO ()
|
||||
testVerifyStrict = do
|
||||
Right st <- createDBStore (DBOpts testDB chatDBFunctions "" False True TQOff) Store.migrations (MigrationConfig MCConsole Nothing)
|
||||
withConnection st (`DB.query_` "SELECT name FROM sqlite_master WHERE type = 'table' AND name != 'sqlite_sequence' AND sql NOT LIKE '% STRICT'")
|
||||
`shouldReturn` ([] :: [Only Text])
|
||||
|
||||
skipComparisonForUpMigrations :: [String]
|
||||
skipComparisonForUpMigrations =
|
||||
[ -- schema doesn't change
|
||||
|
||||
Reference in New Issue
Block a user