Skip to content

Commit

Permalink
Formalize our one-to-many relationship between Slack and Matrix events
Browse files Browse the repository at this point in the history
We may produce multiple Matrix events per Slack events, if the Slack events contains text with attachments.
This should have been forbidden with a DB constraint, but for some reason it happened regardless.

This updates the constraint to match reality, differentiating Matrix events by the type of incoming Slack event.
For backwards compatibility reasons getEventBySlackId() returns the "main" Matrix event.
  • Loading branch information
tadzik committed Apr 5, 2024
1 parent 6e4907c commit 0fe144d
Show file tree
Hide file tree
Showing 4 changed files with 26 additions and 14 deletions.
5 changes: 3 additions & 2 deletions src/BridgedRoom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -878,7 +878,7 @@ export class BridgedRoom {
formatted_body: `<a href="${link}">${file.name}</a>`,
msgtype: "m.text",
};
await ghost.sendMessage(this.matrixRoomId, messageContent, channelId, slackEventId);
await ghost.sendMessage(this.matrixRoomId, messageContent, channelId, slackEventId, { type: "attachment" });
return;
}

Expand Down Expand Up @@ -917,7 +917,7 @@ export class BridgedRoom {
formatted_body: htmlCode,
msgtype: "m.text",
};
await ghost.sendMessage(this.matrixRoomId, messageContent, channelId, slackEventId);
await ghost.sendMessage(this.matrixRoomId, messageContent, channelId, slackEventId, { type: "attachment" });
return;
}

Expand Down Expand Up @@ -954,6 +954,7 @@ export class BridgedRoom {
slackFileToMatrixMessage(file, fileContentUri, thumbnailContentUri),
channelId,
slackEventId,
{ type: "attachment" },
);
}

Expand Down
11 changes: 9 additions & 2 deletions src/SlackGhost.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import * as Slackdown from "Slackdown";
import { ISlackUser } from "./BaseSlackHandler";
import { WebClient } from "@slack/web-api";
import { BotsInfoResponse, UsersInfoResponse } from "./SlackResponses";
import { UserEntry, Datastore } from "./datastore/Models";
import { UserEntry, Datastore, EventEntryExtra } from "./datastore/Models";
import axios from "axios";

const log = new Logger("SlackGhost");
Expand Down Expand Up @@ -368,7 +368,13 @@ export class SlackGhost {
await this.sendMessage(roomId, content, slackRoomID, slackEventTS);
}

public async sendMessage(roomId: string, msg: Record<string, unknown>, slackRoomId: string, slackEventTs: string): Promise<{event_id: string}> {
public async sendMessage(
roomId: string,
msg: Record<string, unknown>,
slackRoomId: string,
slackEventTs: string,
eventExtras?: EventEntryExtra,
): Promise<{event_id: string}> {
if (!this._intent) {
throw Error('No intent associated with ghost');
}
Expand All @@ -383,6 +389,7 @@ export class SlackGhost {
matrixEvent.event_id,
slackRoomId,
slackEventTs,
eventExtras,
);

return {
Expand Down
1 change: 1 addition & 0 deletions src/datastore/Models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export interface EventEntry {
}

export interface EventEntryExtra {
type?: 'attachment';
slackThreadMessages?: string[];
}

Expand Down
23 changes: 13 additions & 10 deletions src/datastore/postgres/PgDatastore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ export interface SchemaRunUserMessage {
type SchemaRunFn = (db: IDatabase<unknown>) => Promise<void|{userMessages: SchemaRunUserMessage[]}>;

export class PgDatastore implements Datastore, ClientEncryptionStore, ProvisioningStore {
public static readonly LATEST_SCHEMA = 16;
public static readonly LATEST_SCHEMA = 17;
public readonly postgresDb: IDatabase<any>;

constructor(connectionString: string) {
Expand Down Expand Up @@ -180,15 +180,18 @@ export class PgDatastore implements Datastore, ClientEncryptionStore, Provisioni

public async getEventBySlackId(slackChannel: string, slackTs: string): Promise<EventEntry|null> {
log.debug(`getEventBySlackId: ${slackChannel} ${slackTs}`);
return this.postgresDb.oneOrNone(
"SELECT * FROM events WHERE slackChannel = ${slackChannel} AND slackTs = ${slackTs} LIMIT 1",
{ slackChannel, slackTs }, e => e && {
roomId: e.roomid,
eventId: e.eventid,
slackChannelId: slackChannel,
slackTs,
_extras: JSON.parse(e.extras),
});
const events = await this.postgresDb.manyOrNone(
"SELECT * FROM events WHERE slackChannel = ${slackChannel} AND slackTs = ${slackTs}",
{ slackChannel, slackTs }
).then(entries => entries.map(e => ({
roomId: e.roomid,
eventId: e.eventid,
slackChannelId: slackChannel,
slackTs,
_extras: JSON.parse(e.extras) as EventEntryExtra,
})));

return events.find(e => e._extras.type !== 'attachment') ?? null;
}

public async deleteEventByMatrixId(roomId: string, eventId: string): Promise<null> {
Expand Down

0 comments on commit 0fe144d

Please sign in to comment.