mirror of
https://github.com/signalapp/Signal-Desktop.git
synced 2025-12-20 02:08:57 +00:00
Improve handling of group story replies
Co-authored-by: trevor-signal <131492920+trevor-signal@users.noreply.github.com>
This commit is contained in:
@@ -4119,6 +4119,23 @@ export class ConversationModel {
|
||||
expireTimer = this.get('expireTimer');
|
||||
}
|
||||
|
||||
if (storyId && isGroup(this.attributes)) {
|
||||
const story = await getMessageById(storyId);
|
||||
strictAssert(story, 'story being replied to must exist');
|
||||
strictAssert(
|
||||
story.expireTimer != null && story.expireTimer > 0,
|
||||
'story missing expireTimer'
|
||||
);
|
||||
strictAssert(
|
||||
story.expirationStartTimestamp != null &&
|
||||
story.expirationStartTimestamp > 0,
|
||||
'story missing expirationStartTimestamp'
|
||||
);
|
||||
|
||||
expireTimer = story.expireTimer;
|
||||
expirationStartTimestamp = story.expirationStartTimestamp;
|
||||
}
|
||||
|
||||
const recipientMaybeConversations = map(
|
||||
this.getRecipients({
|
||||
isStoryReply: storyId !== undefined,
|
||||
|
||||
32
ts/sql/migrations/1580-expired-group-replies.std.ts
Normal file
32
ts/sql/migrations/1580-expired-group-replies.std.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
// Copyright 2025 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import type { LoggerType } from '../../types/Logging.std.js';
|
||||
import type { WritableDB } from '../Interface.std.js';
|
||||
import { sql } from '../util.std.js';
|
||||
|
||||
export default function updateToSchemaVersion1580(
|
||||
db: WritableDB,
|
||||
logger: LoggerType
|
||||
): void {
|
||||
const [query] = sql`
|
||||
DELETE FROM messages
|
||||
WHERE id IN (
|
||||
SELECT messages.id from messages
|
||||
INNER JOIN conversations ON messages.conversationId = conversations.id
|
||||
WHERE
|
||||
conversations.type = 'group'
|
||||
AND messages.storyId IS NOT NULL
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM messages AS messages_exists
|
||||
WHERE messages.storyId = messages_exists.id AND messages_exists.isErased IS NOT 1
|
||||
)
|
||||
)
|
||||
`;
|
||||
const result = db.prepare(query).run();
|
||||
if (result.changes > 0) {
|
||||
logger.warn(
|
||||
`Deleted ${result.changes} group story replies without matching stories`
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -134,6 +134,7 @@ import updateToSchemaVersion1550 from './1550-has-link-preview.std.js';
|
||||
import updateToSchemaVersion1560 from './1560-pinned-messages.std.js';
|
||||
import updateToSchemaVersion1561 from './1561-cleanup-polls.std.js';
|
||||
import updateToSchemaVersion1570 from './1570-pinned-messages-updates.std.js';
|
||||
import updateToSchemaVersion1580 from './1580-expired-group-replies.std.js';
|
||||
|
||||
import { DataWriter } from '../Server.node.js';
|
||||
|
||||
@@ -1627,6 +1628,7 @@ export const SCHEMA_VERSIONS: ReadonlyArray<SchemaUpdateType> = [
|
||||
// 1561, 1551, and 1541 all refer to the same migration
|
||||
{ version: 1561, update: updateToSchemaVersion1561 },
|
||||
{ version: 1570, update: updateToSchemaVersion1570 },
|
||||
{ version: 1580, update: updateToSchemaVersion1580 },
|
||||
];
|
||||
|
||||
export class DBVersionFromFutureError extends Error {
|
||||
|
||||
108
ts/test-node/sql/migration_1580_test.node.ts
Normal file
108
ts/test-node/sql/migration_1580_test.node.ts
Normal file
@@ -0,0 +1,108 @@
|
||||
// Copyright 2025 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
import { assert } from 'chai';
|
||||
|
||||
import { type WritableDB } from '../../sql/Interface.std.js';
|
||||
import {
|
||||
createDB,
|
||||
getTableData,
|
||||
insertData,
|
||||
updateToVersion,
|
||||
} from './helpers.node.js';
|
||||
|
||||
describe('SQL/updateToSchemaVersion1580', () => {
|
||||
let db: WritableDB;
|
||||
|
||||
beforeEach(() => {
|
||||
db = createDB();
|
||||
updateToVersion(db, 1570);
|
||||
});
|
||||
afterEach(() => {
|
||||
db.close();
|
||||
});
|
||||
|
||||
it('deletes any expired group story replies', () => {
|
||||
insertData(db, 'conversations', [
|
||||
{
|
||||
id: 'groupConvoId',
|
||||
type: 'group',
|
||||
},
|
||||
{
|
||||
id: 'directConvoId',
|
||||
type: 'private',
|
||||
},
|
||||
]);
|
||||
|
||||
insertData(db, 'messages', [
|
||||
{
|
||||
id: 'story-that-exists',
|
||||
type: 'story',
|
||||
conversationId: 'groupConvoId',
|
||||
timestamp: Date.now(),
|
||||
},
|
||||
{
|
||||
id: 'doe-story',
|
||||
type: 'story',
|
||||
conversationId: 'groupConvoId',
|
||||
timestamp: Date.now(),
|
||||
isErased: 1,
|
||||
},
|
||||
{
|
||||
id: 'group-reply-to-existing-story',
|
||||
conversationId: 'groupConvoId',
|
||||
timestamp: Date.now(),
|
||||
storyId: 'story-that-exists',
|
||||
},
|
||||
{
|
||||
id: 'group-reply-to-non-existing-story',
|
||||
conversationId: 'groupConvoId',
|
||||
timestamp: Date.now(),
|
||||
storyId: 'story-that-does-not-exist',
|
||||
},
|
||||
{
|
||||
id: 'group-reply-to-doe-story',
|
||||
conversationId: 'groupConvoId',
|
||||
timestamp: Date.now(),
|
||||
storyId: 'doe-story',
|
||||
},
|
||||
{
|
||||
id: 'direct-reply-to-existing-story',
|
||||
conversationId: 'directConvoId',
|
||||
timestamp: Date.now(),
|
||||
storyId: 'storyThatExists',
|
||||
},
|
||||
{
|
||||
id: 'direct-reply-to-non-existing-story',
|
||||
conversationId: 'directConvoId',
|
||||
timestamp: Date.now(),
|
||||
storyId: 'storyThatDoesNotExist',
|
||||
},
|
||||
{
|
||||
id: 'normal-group-message',
|
||||
conversationId: 'groupConvoId',
|
||||
timestamp: Date.now(),
|
||||
},
|
||||
{
|
||||
id: 'normal-direct-message',
|
||||
conversationId: 'directConvoId',
|
||||
timestamp: Date.now(),
|
||||
},
|
||||
]);
|
||||
|
||||
updateToVersion(db, 1580);
|
||||
assert.deepStrictEqual(
|
||||
getTableData(db, 'messages').map(row => row.id),
|
||||
[
|
||||
'story-that-exists',
|
||||
'doe-story',
|
||||
'group-reply-to-existing-story',
|
||||
// 'group-reply-to-non-existing-story', <-- DELETED!
|
||||
// 'group-reply-to-doe-story', <-- DELETED!
|
||||
'direct-reply-to-existing-story',
|
||||
'direct-reply-to-non-existing-story',
|
||||
'normal-group-message',
|
||||
'normal-direct-message',
|
||||
]
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -180,17 +180,14 @@ async function cleanupStoryReplies(
|
||||
}
|
||||
|
||||
if (isGroupConversation) {
|
||||
// Cleanup all group replies
|
||||
await Promise.all(
|
||||
replies.map(reply => {
|
||||
const replyMessageModel = window.MessageCache.register(
|
||||
new MessageModel(reply)
|
||||
);
|
||||
return eraseMessageContents(replyMessageModel);
|
||||
})
|
||||
// Delete all group replies
|
||||
await DataWriter.removeMessages(
|
||||
replies.map(reply => reply.id),
|
||||
{ cleanupMessages }
|
||||
);
|
||||
} else {
|
||||
// Refresh the storyReplyContext data for 1:1 conversations
|
||||
// Clean out the storyReplyContext data for 1:1 conversations; these remain in the
|
||||
// 1:1 timeline with a "story not found" message
|
||||
await Promise.all(
|
||||
replies.map(async reply => {
|
||||
const model = window.MessageCache.register(new MessageModel(reply));
|
||||
|
||||
Reference in New Issue
Block a user