Add migration for unread call history messages and fix json.seenStatus
This commit is contained in:
parent
407b6b0f97
commit
6fd117bde7
4 changed files with 268 additions and 4 deletions
|
@ -3471,9 +3471,16 @@ async function getCallHistoryUnreadCount(): Promise<number> {
|
|||
|
||||
async function markCallHistoryRead(callId: string): Promise<void> {
|
||||
const db = await getWritableInstance();
|
||||
|
||||
const jsonPatch = JSON.stringify({
|
||||
seenStatus: SeenStatus.Seen,
|
||||
});
|
||||
|
||||
const [query, params] = sql`
|
||||
UPDATE messages
|
||||
SET seenStatus = ${SEEN_STATUS_UNSEEN}
|
||||
SET
|
||||
seenStatus = ${SEEN_STATUS_UNSEEN}
|
||||
json = json_patch(json, ${jsonPatch})
|
||||
WHERE type IS 'call-history'
|
||||
AND callId IS ${callId}
|
||||
`;
|
||||
|
@ -3497,9 +3504,15 @@ async function markAllCallHistoryRead(): Promise<ReadonlyArray<string>> {
|
|||
|
||||
const conversationIds = db.prepare(selectQuery).pluck().all(selectParams);
|
||||
|
||||
const jsonPatch = JSON.stringify({
|
||||
seenStatus: SeenStatus.Seen,
|
||||
});
|
||||
|
||||
const [updateQuery, updateParams] = sql`
|
||||
UPDATE messages
|
||||
SET seenStatus = ${SEEN_STATUS_SEEN}
|
||||
SET
|
||||
seenStatus = ${SEEN_STATUS_SEEN},
|
||||
json = json_patch(json, ${jsonPatch})
|
||||
${where};
|
||||
`;
|
||||
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
// Copyright 2024 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import type { Database } from '@signalapp/better-sqlite3';
|
||||
|
||||
import type { LoggerType } from '../../types/Logging';
|
||||
import { ReadStatus } from '../../messages/MessageReadStatus';
|
||||
import { SeenStatus } from '../../MessageSeenStatus';
|
||||
import { strictAssert } from '../../util/assert';
|
||||
import { sql, sqlConstant } from '../util';
|
||||
|
||||
export const version = 1000;
|
||||
|
||||
const READ_STATUS_UNREAD = sqlConstant(ReadStatus.Unread);
|
||||
const READ_STATUS_READ = sqlConstant(ReadStatus.Read);
|
||||
const SEEN_STATUS_UNSEEN = sqlConstant(SeenStatus.Unseen);
|
||||
|
||||
export function updateToSchemaVersion1000(
|
||||
currentVersion: number,
|
||||
db: Database,
|
||||
logger: LoggerType
|
||||
): void {
|
||||
if (currentVersion >= 1000) {
|
||||
return;
|
||||
}
|
||||
|
||||
db.transaction(() => {
|
||||
const [selectQuery] = sql`
|
||||
SELECT id
|
||||
FROM messages
|
||||
WHERE messages.type = 'call-history'
|
||||
AND messages.readStatus IS ${READ_STATUS_UNREAD}
|
||||
`;
|
||||
|
||||
const rows = db.prepare(selectQuery).all();
|
||||
|
||||
for (const row of rows) {
|
||||
const { id } = row;
|
||||
strictAssert(id != null, 'message id must exist');
|
||||
|
||||
const [updateQuery, updateParams] = sql`
|
||||
UPDATE messages
|
||||
SET
|
||||
json = JSON_PATCH(json, ${JSON.stringify({
|
||||
readStatus: ReadStatus.Read,
|
||||
seenStatus: SeenStatus.Unseen,
|
||||
})}),
|
||||
readStatus = ${READ_STATUS_READ},
|
||||
seenStatus = ${SEEN_STATUS_UNSEEN}
|
||||
WHERE id = ${id}
|
||||
`;
|
||||
|
||||
db.prepare(updateQuery).run(updateParams);
|
||||
}
|
||||
})();
|
||||
|
||||
db.pragma('user_version = 1000');
|
||||
|
||||
logger.info('updateToSchemaVersion1000: success!');
|
||||
}
|
|
@ -74,10 +74,11 @@ import { updateToSchemaVersion950 } from './950-fts5-secure-delete';
|
|||
import { updateToSchemaVersion960 } from './960-untag-pni';
|
||||
import { updateToSchemaVersion970 } from './970-fts5-optimize';
|
||||
import { updateToSchemaVersion980 } from './980-reaction-timestamp';
|
||||
import { updateToSchemaVersion990 } from './990-phone-number-sharing';
|
||||
import {
|
||||
version as MAX_VERSION,
|
||||
updateToSchemaVersion990,
|
||||
} from './990-phone-number-sharing';
|
||||
updateToSchemaVersion1000,
|
||||
} from './1000-mark-unread-call-history-messages-as-unseen';
|
||||
|
||||
function updateToSchemaVersion1(
|
||||
currentVersion: number,
|
||||
|
@ -2019,6 +2020,7 @@ export const SCHEMA_VERSIONS = [
|
|||
updateToSchemaVersion970,
|
||||
updateToSchemaVersion980,
|
||||
updateToSchemaVersion990,
|
||||
updateToSchemaVersion1000,
|
||||
];
|
||||
|
||||
export class DBVersionFromFutureError extends Error {
|
||||
|
|
189
ts/test-node/sql/migration_1000_test.ts
Normal file
189
ts/test-node/sql/migration_1000_test.ts
Normal file
|
@ -0,0 +1,189 @@
|
|||
// Copyright 2023 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { assert } from 'chai';
|
||||
import type { Database } from '@signalapp/better-sqlite3';
|
||||
import SQL from '@signalapp/better-sqlite3';
|
||||
import { v4 as generateGuid } from 'uuid';
|
||||
|
||||
import { jsonToObject, sql } from '../../sql/util';
|
||||
import { updateToVersion } from './helpers';
|
||||
import type { MessageType } from '../../sql/Interface';
|
||||
import { ReadStatus } from '../../messages/MessageReadStatus';
|
||||
import { SeenStatus } from '../../MessageSeenStatus';
|
||||
|
||||
describe('SQL/updateToSchemaVersion1000', () => {
|
||||
let db: Database;
|
||||
|
||||
beforeEach(() => {
|
||||
db = new SQL(':memory:');
|
||||
updateToVersion(db, 990);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
db.close();
|
||||
});
|
||||
|
||||
function createCallHistoryMessage(options: {
|
||||
messageId: string;
|
||||
conversationId: string;
|
||||
callId: string;
|
||||
readStatus: ReadStatus;
|
||||
seenStatus: SeenStatus;
|
||||
}): MessageType {
|
||||
const message: MessageType = {
|
||||
id: options.messageId,
|
||||
type: 'call-history',
|
||||
conversationId: options.conversationId,
|
||||
received_at: Date.now(),
|
||||
sent_at: Date.now(),
|
||||
received_at_ms: Date.now(),
|
||||
timestamp: Date.now(),
|
||||
readStatus: options.readStatus,
|
||||
seenStatus: options.seenStatus,
|
||||
callId: options.callId,
|
||||
};
|
||||
|
||||
const json = JSON.stringify(message);
|
||||
|
||||
const [query, params] = sql`
|
||||
INSERT INTO messages
|
||||
(id, conversationId, type, readStatus, seenStatus, json)
|
||||
VALUES
|
||||
(
|
||||
${message.id},
|
||||
${message.conversationId},
|
||||
${message.type},
|
||||
${message.readStatus},
|
||||
${message.seenStatus},
|
||||
${json}
|
||||
)
|
||||
`;
|
||||
|
||||
db.prepare(query).run(params);
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
function createConversation(
|
||||
type: 'private' | 'group',
|
||||
discoveredUnregisteredAt?: number
|
||||
) {
|
||||
const id = generateGuid();
|
||||
const groupId = type === 'group' ? generateGuid() : null;
|
||||
|
||||
const json = JSON.stringify({
|
||||
type,
|
||||
id,
|
||||
groupId,
|
||||
discoveredUnregisteredAt,
|
||||
});
|
||||
|
||||
const [query, params] = sql`
|
||||
INSERT INTO conversations
|
||||
(id, type, groupId, json)
|
||||
VALUES
|
||||
(${id}, ${type}, ${groupId}, ${json});
|
||||
`;
|
||||
|
||||
db.prepare(query).run(params);
|
||||
|
||||
return { id, groupId };
|
||||
}
|
||||
|
||||
function getMessages() {
|
||||
const [query] = sql`
|
||||
SELECT json, readStatus, seenStatus FROM messages;
|
||||
`;
|
||||
return db
|
||||
.prepare(query)
|
||||
.all()
|
||||
.map(row => {
|
||||
return {
|
||||
message: jsonToObject<MessageType>(row.json),
|
||||
readStatus: row.readStatus,
|
||||
seenStatus: row.seenStatus,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
it('marks unread call history messages read and unseen', () => {
|
||||
const conversation1 = createConversation('private');
|
||||
const conversation2 = createConversation('group');
|
||||
|
||||
const callId1 = '1';
|
||||
const callId2 = '2';
|
||||
|
||||
createCallHistoryMessage({
|
||||
messageId: generateGuid(),
|
||||
conversationId: conversation1.id,
|
||||
callId: callId1,
|
||||
readStatus: ReadStatus.Unread,
|
||||
seenStatus: SeenStatus.Unseen,
|
||||
});
|
||||
|
||||
createCallHistoryMessage({
|
||||
messageId: generateGuid(),
|
||||
conversationId: conversation2.id,
|
||||
callId: callId2,
|
||||
readStatus: ReadStatus.Unread,
|
||||
seenStatus: SeenStatus.Unseen,
|
||||
});
|
||||
|
||||
updateToVersion(db, 1000);
|
||||
|
||||
const messages = getMessages();
|
||||
|
||||
assert.strictEqual(messages.length, 2);
|
||||
|
||||
assert.strictEqual(messages[0].message.readStatus, ReadStatus.Read);
|
||||
assert.strictEqual(messages[0].message.seenStatus, SeenStatus.Unseen);
|
||||
assert.strictEqual(messages[0].readStatus, ReadStatus.Read);
|
||||
assert.strictEqual(messages[0].seenStatus, SeenStatus.Unseen);
|
||||
|
||||
assert.strictEqual(messages[1].message.readStatus, ReadStatus.Read);
|
||||
assert.strictEqual(messages[1].message.seenStatus, SeenStatus.Unseen);
|
||||
assert.strictEqual(messages[1].readStatus, ReadStatus.Read);
|
||||
assert.strictEqual(messages[1].seenStatus, SeenStatus.Unseen);
|
||||
});
|
||||
|
||||
it('does not mark read call history messages as unseen', () => {
|
||||
const conversation1 = createConversation('private');
|
||||
const conversation2 = createConversation('group');
|
||||
|
||||
const callId1 = '1';
|
||||
const callId2 = '2';
|
||||
|
||||
createCallHistoryMessage({
|
||||
messageId: generateGuid(),
|
||||
conversationId: conversation1.id,
|
||||
callId: callId1,
|
||||
readStatus: ReadStatus.Read,
|
||||
seenStatus: SeenStatus.Seen,
|
||||
});
|
||||
|
||||
createCallHistoryMessage({
|
||||
messageId: generateGuid(),
|
||||
conversationId: conversation2.id,
|
||||
callId: callId2,
|
||||
readStatus: ReadStatus.Read,
|
||||
seenStatus: SeenStatus.Seen,
|
||||
});
|
||||
|
||||
updateToVersion(db, 1000);
|
||||
|
||||
const messages = getMessages();
|
||||
|
||||
assert.strictEqual(messages.length, 2);
|
||||
|
||||
assert.strictEqual(messages[0].message.readStatus, ReadStatus.Read);
|
||||
assert.strictEqual(messages[0].message.seenStatus, SeenStatus.Seen);
|
||||
assert.strictEqual(messages[0].readStatus, ReadStatus.Read);
|
||||
assert.strictEqual(messages[0].seenStatus, SeenStatus.Seen);
|
||||
|
||||
assert.strictEqual(messages[1].message.readStatus, ReadStatus.Read);
|
||||
assert.strictEqual(messages[1].message.seenStatus, SeenStatus.Seen);
|
||||
assert.strictEqual(messages[1].readStatus, ReadStatus.Read);
|
||||
assert.strictEqual(messages[1].seenStatus, SeenStatus.Seen);
|
||||
});
|
||||
});
|
Loading…
Reference in a new issue