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');
|
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(
|
const recipientMaybeConversations = map(
|
||||||
this.getRecipients({
|
this.getRecipients({
|
||||||
isStoryReply: storyId !== undefined,
|
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 updateToSchemaVersion1560 from './1560-pinned-messages.std.js';
|
||||||
import updateToSchemaVersion1561 from './1561-cleanup-polls.std.js';
|
import updateToSchemaVersion1561 from './1561-cleanup-polls.std.js';
|
||||||
import updateToSchemaVersion1570 from './1570-pinned-messages-updates.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';
|
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
|
// 1561, 1551, and 1541 all refer to the same migration
|
||||||
{ version: 1561, update: updateToSchemaVersion1561 },
|
{ version: 1561, update: updateToSchemaVersion1561 },
|
||||||
{ version: 1570, update: updateToSchemaVersion1570 },
|
{ version: 1570, update: updateToSchemaVersion1570 },
|
||||||
|
{ version: 1580, update: updateToSchemaVersion1580 },
|
||||||
];
|
];
|
||||||
|
|
||||||
export class DBVersionFromFutureError extends Error {
|
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) {
|
if (isGroupConversation) {
|
||||||
// Cleanup all group replies
|
// Delete all group replies
|
||||||
await Promise.all(
|
await DataWriter.removeMessages(
|
||||||
replies.map(reply => {
|
replies.map(reply => reply.id),
|
||||||
const replyMessageModel = window.MessageCache.register(
|
{ cleanupMessages }
|
||||||
new MessageModel(reply)
|
|
||||||
);
|
|
||||||
return eraseMessageContents(replyMessageModel);
|
|
||||||
})
|
|
||||||
);
|
);
|
||||||
} else {
|
} 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(
|
await Promise.all(
|
||||||
replies.map(async reply => {
|
replies.map(async reply => {
|
||||||
const model = window.MessageCache.register(new MessageModel(reply));
|
const model = window.MessageCache.register(new MessageModel(reply));
|
||||||
|
|||||||
Reference in New Issue
Block a user