core, python: fix refused renames, extend the client API (#7379)

* core: do not commit refused name changes

* feat(python): add error base and missing commands

* feat(python): let callers drive startup themselves

* style(python): satisfy the linter, skip generated types

* feat(python): expose the message of a command error

* core: update query plans

* core: fix the batch limit parse error on GHC 8.10

* feat(python): reject profile images no client can render
This commit is contained in:
sh
2026-08-17 09:02:14 +01:00
committed by GitHub
parent caac5a61a9
commit 55d18efe24
19 changed files with 718 additions and 82 deletions
+3 -2
View File
@@ -965,9 +965,10 @@ parseChatMessages msg = checkBatchLimit $ case B.head msg of
checkBatchLimit ms
| ms `lengthLE` maxBatchElementCount = ms
| otherwise = [Left "too many messages in batch"]
-- defined prefix: GHC 8.10 does not parse a bang operand in an infix definition
lengthLE :: [a] -> Int -> Bool
[] `lengthLE` !n = n >= 0
(_ : xs) `lengthLE` !n = n > 0 && xs `lengthLE` (n - 1)
lengthLE [] !n = n >= 0
lengthLE (_ : xs) !n = n > 0 && lengthLE xs (n - 1)
parseUncompressed c s = case c of
'[' -> case J.eitherDecodeStrict' s of
Right v -> map (fmap plainMsg . parseItem) v
+7 -3
View File
@@ -135,7 +135,6 @@ import Database.SQLite.Simple.QQ (sql)
createUserRecordAt :: DB.Connection -> AgentUserId -> Bool -> Bool -> Profile -> Bool -> UTCTime -> ExceptT StoreError IO User
createUserRecordAt db (AgentUserId auId) userChatRelay clientService Profile {displayName, fullName, shortDescr, description, image, peerType, preferences = userPreferences} activeUser currentTs =
checkConstraint SEDuplicateName . liftIO $ do
when activeUser $ DB.execute_ db "UPDATE users SET active_user = 0"
let showNtfs = True
sendRcptsContacts = True
sendRcptsSmallGroups = True
@@ -148,6 +147,9 @@ createUserRecordAt db (AgentUserId auId) userChatRelay clientService Profile {di
:. (BI showNtfs, BI sendRcptsContacts, BI sendRcptsSmallGroups, BI autoAcceptMemberContacts, BI clientService, currentTs, currentTs)
)
userId <- insertedRowId db
-- After the insert: the name is unique in users, so a duplicate fails
-- above, and deactivating first would commit a database with no active user.
when activeUser $ DB.execute db "UPDATE users SET active_user = 0 WHERE user_id != ?" (Only userId)
DB.execute
db
"INSERT INTO display_names (local_display_name, ldn_base, user_id, created_at, updated_at) VALUES (?,?,?,?,?)"
@@ -338,12 +340,14 @@ updateUserProfile db user p'
| otherwise =
checkConstraint SEDuplicateName . liftIO $ do
currentTs <- getCurrentTime
DB.execute db "UPDATE users SET local_display_name = ?, updated_at = ? WHERE user_id = ?" (newName, currentTs, userId)
userMemberProfileUpdatedAt' <- updateUserMemberProfileUpdatedAt_ currentTs
-- Insert first: checkConstraint returns the violation as a value, so the
-- transaction commits, keeping whatever ran before the failing insert.
DB.execute
db
"INSERT INTO display_names (local_display_name, ldn_base, user_id, created_at, updated_at) VALUES (?,?,?,?,?)"
(newName, newName, userId, currentTs, currentTs)
DB.execute db "UPDATE users SET local_display_name = ?, updated_at = ? WHERE user_id = ?" (newName, currentTs, userId)
userMemberProfileUpdatedAt' <- updateUserMemberProfileUpdatedAt_ currentTs
updateUserProfileFields_' db userId profileId p' currentTs
updateContactLDN_ db user userContactId localDisplayName newName currentTs
pure user {localDisplayName = newName, profile = (toLocalProfile profileId p' localAlias currentTs (Just False) Nothing) {localBadge}, fullPreferences, userMemberProfileUpdatedAt = userMemberProfileUpdatedAt'}
@@ -7931,6 +7931,10 @@ Query: UPDATE users SET active_user = 0
Plan:
SCAN users
Query: UPDATE users SET active_user = 0 WHERE user_id != ?
Plan:
SCAN users
Query: UPDATE users SET active_user = 1, active_order = ? WHERE user_id = ?
Plan:
SEARCH users USING INTEGER PRIMARY KEY (rowid=?)