Bind history to the recipient session and channel, not the name (closes #148)
check / check (push) Canceled after 0s

History was selected by msg_to, so whoever next held a released nick, or
created a deleted channel again under its old name, could read the
earlier holder's messages. Each message row now records the session a
DM or INVITE was sent to (to_session_id) and the channel a channel
message was sent to (to_channel_id), and GET /api/v1/history selects by
those ids. A session's DM history now also follows it across nick
changes.

Model: opus-5-5
This commit is contained in:
2026-10-20 15:45:57 +00:00
parent 0b09cf838c
commit bd5caeaba9
10 changed files with 314 additions and 85 deletions
+28 -15
View File
@@ -940,9 +940,14 @@ func (database *Database) GetSessionChannelIDs(
}
// InsertMessage stores a message and returns its DB ID.
// toSessionID is the session a DM or INVITE is sent to and
// toChannelID the channel a channel message is sent to;
// GetHistory selects by them. Pass 0 for one that does not
// apply, which stores NULL.
func (database *Database) InsertMessage(
ctx context.Context,
command, from, target string,
toSessionID, toChannelID int64,
params json.RawMessage,
body json.RawMessage,
meta json.RawMessage,
@@ -965,9 +970,12 @@ func (database *Database) InsertMessage(
res, err := database.conn.ExecContext(ctx,
`INSERT INTO messages
(uuid, command, msg_from, msg_to,
to_session_id, to_channel_id,
params, body, meta, created_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
VALUES (?, ?, ?, ?, NULLIF(?, 0), NULLIF(?, 0),
?, ?, ?, ?)`,
msgUUID, command, from, target,
toSessionID, toChannelID,
string(params), string(body), string(meta), now)
if err != nil {
return 0, "", fmt.Errorf(
@@ -1058,13 +1066,17 @@ func (database *Database) PollMessages(
return msgs, lastQID, nil
}
// GetHistory returns message history for a target. A
// non-empty before is the id of a message sent to target,
// and only messages older than it are returned; an id with
// no such message gives an error wrapping sql.ErrNoRows.
// GetHistory returns the PRIVMSG history of one recipient:
// the DMs sent to session toSessionID, or the messages sent
// to channel toChannelID. Pass 0 for the other, which
// matches no message. A non-empty before is the id of a
// message in that history, and only messages older than it
// are returned; an id with no such message gives an error
// wrapping sql.ErrNoRows.
func (database *Database) GetHistory(
ctx context.Context,
target, before string,
toSessionID, toChannelID int64,
before string,
limit int,
) ([]IRCMessage, error) {
if limit <= 0 {
@@ -1076,8 +1088,9 @@ func (database *Database) GetHistory(
if before != "" {
err := database.conn.QueryRowContext(ctx,
`SELECT id FROM messages
WHERE uuid = ? AND msg_to = ?`,
before, target,
WHERE uuid = ?
AND (to_session_id = ? OR to_channel_id = ?)`,
before, toSessionID, toChannelID,
).Scan(&beforeID)
if err != nil {
return nil, fmt.Errorf(
@@ -1087,7 +1100,7 @@ func (database *Database) GetHistory(
}
rows, err := database.queryHistory(
ctx, target, beforeID, limit,
ctx, toSessionID, toChannelID, beforeID, limit,
)
if err != nil {
return nil, err
@@ -1109,8 +1122,7 @@ func (database *Database) GetHistory(
func (database *Database) queryHistory(
ctx context.Context,
target string,
beforeID int64,
toSessionID, toChannelID, beforeID int64,
limit int,
) (*sql.Rows, error) {
if beforeID > 0 {
@@ -1118,10 +1130,11 @@ func (database *Database) queryHistory(
`SELECT id, uuid, command, msg_from,
msg_to, params, body, meta, created_at
FROM messages
WHERE msg_to = ? AND id < ?
WHERE (to_session_id = ? OR to_channel_id = ?)
AND id < ?
AND command = 'PRIVMSG'
ORDER BY id DESC LIMIT ?`,
target, beforeID, limit)
toSessionID, toChannelID, beforeID, limit)
if err != nil {
return nil, fmt.Errorf(
"query history: %w", err,
@@ -1135,10 +1148,10 @@ func (database *Database) queryHistory(
`SELECT id, uuid, command, msg_from,
msg_to, params, body, meta, created_at
FROM messages
WHERE msg_to = ?
WHERE (to_session_id = ? OR to_channel_id = ?)
AND command = 'PRIVMSG'
ORDER BY id DESC LIMIT ?`,
target, limit)
toSessionID, toChannelID, limit)
if err != nil {
return nil, fmt.Errorf("query history: %w", err)
}