Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add migration for unread call history messages and fix json.seenStatus
Co-authored-by: ayumi-signal <143036029+ayumi-signal@users.noreply.github.com>
- Loading branch information
1 parent
578d6f4
commit dc95fd9
Showing
4 changed files
with
268 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
60 changes: 60 additions & 0 deletions
60
ts/sql/migrations/1000-mark-unread-call-history-messages-as-unseen.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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!'); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
}); | ||
}); |