Support delete for call links
Co-authored-by: Fedor Indutny <79877362+indutny-signal@users.noreply.github.com>
This commit is contained in:
parent
11fed7e7f8
commit
9a9f9495f1
67 changed files with 853 additions and 345 deletions
|
@ -562,6 +562,7 @@ type ReadableInterface = {
|
|||
callLinkExists(roomId: string): boolean;
|
||||
getAllCallLinks: () => ReadonlyArray<CallLinkType>;
|
||||
getCallLinkByRoomId: (roomId: string) => CallLinkType | undefined;
|
||||
getAllMarkedDeletedCallLinks(): ReadonlyArray<CallLinkType>;
|
||||
getMessagesBetween: (
|
||||
conversationId: string,
|
||||
options: GetMessagesBetweenOptions
|
||||
|
@ -794,6 +795,10 @@ type WritableInterface = {
|
|||
roomId: string,
|
||||
callLinkState: CallLinkStateType
|
||||
): CallLinkType;
|
||||
beginDeleteAllCallLinks(): void;
|
||||
beginDeleteCallLink(roomId: string): void;
|
||||
finalizeDeleteCallLink(roomId: string): void;
|
||||
deleteCallLinkFromSync(roomId: string): void;
|
||||
migrateConversationMessages: (obsoleteId: string, currentId: string) => void;
|
||||
saveEditedMessage: (
|
||||
mainMessage: ReadonlyDeep<MessageType>,
|
||||
|
|
|
@ -168,6 +168,7 @@ import {
|
|||
GroupCallStatus,
|
||||
CallType,
|
||||
CallStatusValue,
|
||||
CallMode,
|
||||
} from '../types/CallDisposition';
|
||||
import {
|
||||
callLinkExists,
|
||||
|
@ -176,13 +177,17 @@ import {
|
|||
insertCallLink,
|
||||
updateCallLinkAdminKeyByRoomId,
|
||||
updateCallLinkState,
|
||||
beginDeleteAllCallLinks,
|
||||
getAllMarkedDeletedCallLinks,
|
||||
finalizeDeleteCallLink,
|
||||
beginDeleteCallLink,
|
||||
deleteCallLinkFromSync,
|
||||
} from './server/callLinks';
|
||||
import {
|
||||
replaceAllEndorsementsForGroup,
|
||||
deleteAllEndorsementsForGroup,
|
||||
getGroupSendCombinedEndorsementExpiration,
|
||||
} from './server/groupEndorsements';
|
||||
import { CallMode } from '../types/Calling';
|
||||
import {
|
||||
attachmentDownloadJobSchema,
|
||||
type AttachmentDownloadJobType,
|
||||
|
@ -296,6 +301,7 @@ export const DataReader: ServerReadableInterface = {
|
|||
callLinkExists,
|
||||
getAllCallLinks,
|
||||
getCallLinkByRoomId,
|
||||
getAllMarkedDeletedCallLinks,
|
||||
getMessagesBetween,
|
||||
getNearbyMessageFromDeletedSet,
|
||||
getMostRecentAddressableMessages,
|
||||
|
@ -430,6 +436,10 @@ export const DataWriter: ServerWritableInterface = {
|
|||
insertCallLink,
|
||||
updateCallLinkAdminKeyByRoomId,
|
||||
updateCallLinkState,
|
||||
beginDeleteAllCallLinks,
|
||||
beginDeleteCallLink,
|
||||
finalizeDeleteCallLink,
|
||||
deleteCallLinkFromSync,
|
||||
migrateConversationMessages,
|
||||
saveEditedMessage,
|
||||
saveEditedMessages,
|
||||
|
@ -3458,7 +3468,7 @@ function clearCallHistory(
|
|||
return db.transaction(() => {
|
||||
const timestamp = getMessageTimestampForCallLogEventTarget(db, target);
|
||||
|
||||
const [selectCallIdsQuery, selectCallIdsParams] = sql`
|
||||
const [selectCallsQuery, selectCallsParams] = sql`
|
||||
SELECT callsHistory.callId
|
||||
FROM callsHistory
|
||||
WHERE
|
||||
|
@ -3471,18 +3481,30 @@ function clearCallHistory(
|
|||
);
|
||||
`;
|
||||
|
||||
const callIds = db
|
||||
.prepare(selectCallIdsQuery)
|
||||
const deletedCallIds: ReadonlyArray<string> = db
|
||||
.prepare(selectCallsQuery)
|
||||
.pluck()
|
||||
.all(selectCallIdsParams);
|
||||
.all(selectCallsParams);
|
||||
|
||||
let deletedMessageIds: ReadonlyArray<string> = [];
|
||||
|
||||
batchMultiVarQuery(db, callIds, (ids: ReadonlyArray<string>): void => {
|
||||
batchMultiVarQuery(db, deletedCallIds, (ids): void => {
|
||||
const idsFragment = sqlJoin(ids);
|
||||
|
||||
const [clearCallsHistoryQuery, clearCallsHistoryParams] = sql`
|
||||
UPDATE callsHistory
|
||||
SET
|
||||
status = ${DirectCallStatus.Deleted},
|
||||
timestamp = ${Date.now()}
|
||||
WHERE callsHistory.callId IN (${idsFragment});
|
||||
`;
|
||||
|
||||
db.prepare(clearCallsHistoryQuery).run(clearCallsHistoryParams);
|
||||
|
||||
const [deleteMessagesQuery, deleteMessagesParams] = sql`
|
||||
DELETE FROM messages
|
||||
WHERE messages.type IS 'call-history'
|
||||
AND messages.callId IN (${sqlJoin(ids)})
|
||||
AND messages.callId IN (${idsFragment})
|
||||
RETURNING id;
|
||||
`;
|
||||
|
||||
|
@ -3494,21 +3516,6 @@ function clearCallHistory(
|
|||
deletedMessageIds = deletedMessageIds.concat(batchDeletedMessageIds);
|
||||
});
|
||||
|
||||
const [clearCallsHistoryQuery, clearCallsHistoryParams] = sql`
|
||||
UPDATE callsHistory
|
||||
SET
|
||||
status = ${DirectCallStatus.Deleted},
|
||||
timestamp = ${Date.now()}
|
||||
WHERE callsHistory.timestamp <= ${timestamp};
|
||||
`;
|
||||
|
||||
try {
|
||||
db.prepare(clearCallsHistoryQuery).run(clearCallsHistoryParams);
|
||||
} catch (error) {
|
||||
logger.error(error, error.message);
|
||||
throw error;
|
||||
}
|
||||
|
||||
return deletedMessageIds;
|
||||
})();
|
||||
}
|
||||
|
@ -3519,9 +3526,8 @@ function markCallHistoryDeleted(db: WritableDB, callId: string): void {
|
|||
SET
|
||||
status = ${DirectCallStatus.Deleted},
|
||||
timestamp = ${Date.now()}
|
||||
WHERE callId = ${callId};
|
||||
WHERE callId = ${callId}
|
||||
`;
|
||||
|
||||
db.prepare(query).run(params);
|
||||
}
|
||||
|
||||
|
@ -4829,7 +4835,7 @@ function getNextAttachmentBackupJobs(
|
|||
active = 0
|
||||
AND
|
||||
(retryAfter is NULL OR retryAfter <= ${timestamp})
|
||||
ORDER BY
|
||||
ORDER BY
|
||||
-- type is "standard" or "thumbnail"; we prefer "standard" jobs
|
||||
type ASC, receivedAt DESC
|
||||
LIMIT ${limit}
|
||||
|
|
31
ts/sql/migrations/1140-call-links-deleted-column.ts
Normal file
31
ts/sql/migrations/1140-call-links-deleted-column.ts
Normal file
|
@ -0,0 +1,31 @@
|
|||
// 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';
|
||||
|
||||
export const version = 1140;
|
||||
|
||||
export function updateToSchemaVersion1140(
|
||||
currentVersion: number,
|
||||
db: Database,
|
||||
logger: LoggerType
|
||||
): void {
|
||||
if (currentVersion >= 1140) {
|
||||
return;
|
||||
}
|
||||
|
||||
db.transaction(() => {
|
||||
db.exec(`
|
||||
DROP INDEX IF EXISTS callLinks_deleted;
|
||||
|
||||
ALTER TABLE callLinks
|
||||
ADD COLUMN deleted INTEGER NOT NULL DEFAULT 0;
|
||||
|
||||
CREATE INDEX callLinks_deleted
|
||||
ON callLinks (deleted, roomId);
|
||||
`);
|
||||
})();
|
||||
|
||||
db.pragma('user_version = 1140');
|
||||
logger.info('updateToSchemaVersion1140: success!');
|
||||
}
|
|
@ -17,8 +17,8 @@ import {
|
|||
CallType,
|
||||
GroupCallStatus,
|
||||
callHistoryDetailsSchema,
|
||||
CallMode,
|
||||
} from '../../types/CallDisposition';
|
||||
import { CallMode } from '../../types/Calling';
|
||||
import type { WritableDB, MessageType, ConversationType } from '../Interface';
|
||||
import { strictAssert } from '../../util/assert';
|
||||
import { missingCaseError } from '../../util/missingCaseError';
|
||||
|
|
|
@ -89,10 +89,11 @@ import { updateToSchemaVersion1090 } from './1090-message-delete-indexes';
|
|||
import { updateToSchemaVersion1100 } from './1100-optimize-mark-call-history-read-in-conversation';
|
||||
import { updateToSchemaVersion1110 } from './1110-sticker-local-key';
|
||||
import { updateToSchemaVersion1120 } from './1120-messages-foreign-keys-indexes';
|
||||
import { updateToSchemaVersion1130 } from './1130-isStory-index';
|
||||
import {
|
||||
updateToSchemaVersion1130,
|
||||
updateToSchemaVersion1140,
|
||||
version as MAX_VERSION,
|
||||
} from './1130-isStory-index';
|
||||
} from './1140-call-links-deleted-column';
|
||||
|
||||
function updateToSchemaVersion1(
|
||||
currentVersion: number,
|
||||
|
@ -2050,6 +2051,7 @@ export const SCHEMA_VERSIONS = [
|
|||
updateToSchemaVersion1110,
|
||||
updateToSchemaVersion1120,
|
||||
updateToSchemaVersion1130,
|
||||
updateToSchemaVersion1140,
|
||||
];
|
||||
|
||||
export class DBVersionFromFutureError extends Error {
|
||||
|
|
|
@ -7,15 +7,16 @@ import {
|
|||
callLinkRestrictionsSchema,
|
||||
callLinkRecordSchema,
|
||||
} from '../../types/CallLink';
|
||||
import { toAdminKeyBytes } from '../../util/callLinks';
|
||||
import {
|
||||
callLinkToRecord,
|
||||
callLinkFromRecord,
|
||||
toAdminKeyBytes,
|
||||
} from '../../util/callLinks';
|
||||
} from '../../util/callLinksRingrtc';
|
||||
import type { ReadableDB, WritableDB } from '../Interface';
|
||||
import { prepare } from '../Server';
|
||||
import { sql } from '../util';
|
||||
import { strictAssert } from '../../util/assert';
|
||||
import { CallStatusValue } from '../../types/CallDisposition';
|
||||
|
||||
export function callLinkExists(db: ReadableDB, roomId: string): boolean {
|
||||
const [query, params] = sql`
|
||||
|
@ -133,3 +134,106 @@ function assertRoomIdMatchesRootKey(roomId: string, rootKey: string): void {
|
|||
'passed roomId must match roomId derived from root key'
|
||||
);
|
||||
}
|
||||
|
||||
function deleteCallHistoryByRoomId(db: WritableDB, roomId: string) {
|
||||
const [
|
||||
markCallHistoryDeleteByPeerIdQuery,
|
||||
markCallHistoryDeleteByPeerIdParams,
|
||||
] = sql`
|
||||
UPDATE callsHistory
|
||||
SET
|
||||
status = ${CallStatusValue.Deleted},
|
||||
timestamp = ${Date.now()}
|
||||
WHERE peerId = ${roomId}
|
||||
`;
|
||||
|
||||
db.prepare(markCallHistoryDeleteByPeerIdQuery).run(
|
||||
markCallHistoryDeleteByPeerIdParams
|
||||
);
|
||||
}
|
||||
|
||||
// This should only be called from a sync message to avoid accidentally deleting
|
||||
// on the client but not the server
|
||||
export function deleteCallLinkFromSync(db: WritableDB, roomId: string): void {
|
||||
db.transaction(() => {
|
||||
const [query, params] = sql`
|
||||
DELETE FROM callLinks
|
||||
WHERE roomId = ${roomId};
|
||||
`;
|
||||
|
||||
db.prepare(query).run(params);
|
||||
|
||||
deleteCallHistoryByRoomId(db, roomId);
|
||||
})();
|
||||
}
|
||||
|
||||
export function beginDeleteCallLink(db: WritableDB, roomId: string): void {
|
||||
db.transaction(() => {
|
||||
// If adminKey is null, then we should delete the call link
|
||||
const [deleteNonAdminCallLinksQuery, deleteNonAdminCallLinksParams] = sql`
|
||||
DELETE FROM callLinks
|
||||
WHERE adminKey IS NULL
|
||||
AND roomId = ${roomId};
|
||||
`;
|
||||
|
||||
const result = db
|
||||
.prepare(deleteNonAdminCallLinksQuery)
|
||||
.run(deleteNonAdminCallLinksParams);
|
||||
|
||||
// Skip this query if the call is already deleted
|
||||
if (result.changes === 0) {
|
||||
// If the admin key is not null, we should mark it for deletion
|
||||
const [markAdminCallLinksDeletedQuery, markAdminCallLinksDeletedParams] =
|
||||
sql`
|
||||
UPDATE callLinks
|
||||
SET deleted = 1
|
||||
WHERE adminKey IS NOT NULL
|
||||
AND roomId = ${roomId};
|
||||
`;
|
||||
|
||||
db.prepare(markAdminCallLinksDeletedQuery).run(
|
||||
markAdminCallLinksDeletedParams
|
||||
);
|
||||
}
|
||||
|
||||
deleteCallHistoryByRoomId(db, roomId);
|
||||
})();
|
||||
}
|
||||
|
||||
export function beginDeleteAllCallLinks(db: WritableDB): void {
|
||||
db.transaction(() => {
|
||||
const [markAdminCallLinksDeletedQuery] = sql`
|
||||
UPDATE callLinks
|
||||
SET deleted = 1
|
||||
WHERE adminKey IS NOT NULL;
|
||||
`;
|
||||
|
||||
db.prepare(markAdminCallLinksDeletedQuery).run();
|
||||
|
||||
const [deleteNonAdminCallLinksQuery] = sql`
|
||||
DELETE FROM callLinks
|
||||
WHERE adminKey IS NULL;
|
||||
`;
|
||||
|
||||
db.prepare(deleteNonAdminCallLinksQuery).run();
|
||||
})();
|
||||
}
|
||||
|
||||
export function getAllMarkedDeletedCallLinks(
|
||||
db: ReadableDB
|
||||
): ReadonlyArray<CallLinkType> {
|
||||
const [query] = sql`
|
||||
SELECT * FROM callLinks WHERE deleted = 1;
|
||||
`;
|
||||
return db
|
||||
.prepare(query)
|
||||
.all()
|
||||
.map(item => callLinkFromRecord(callLinkRecordSchema.parse(item)));
|
||||
}
|
||||
|
||||
export function finalizeDeleteCallLink(db: WritableDB, roomId: string): void {
|
||||
const [query, params] = sql`
|
||||
DELETE FROM callLinks WHERE roomId = ${roomId} AND deleted = 1;
|
||||
`;
|
||||
db.prepare(query).run(params);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue