1 Commits
Author SHA1 Message Date
sneak 39955494a4 Run JOIN, PART and KICK as one transaction each (closes #153)
check / check (push) Waiting to run
JoinChannel made the channel, counted its members, ran the join
checks and inserted the member as separate statements, so two
requests could each decide on a count the other was about to change:
+l was exceeded, two first joiners both became operator, and a JOIN
racing the last PART failed its foreign key and answered 500.

db.CreateOrJoinChannel now does all of that, and uses up the invite,
in one transaction; the join checks moved into it, and the service
turns its refusals into the same IRC errors as before. PART and KICK
remove the member and delete an empty channel in one transaction
(db.PartChannelAndDeleteIfEmpty). The db functions this leaves with
no caller are removed.

Model: opus-5-5
2026-10-20 20:17:46 +00:00
2 changed files with 2 additions and 30 deletions
+2 -19
View File
@@ -558,8 +558,8 @@ func (database *Database) CreateOrJoinChannel(
// ErrInviteOnlyChannel when the channel is +i and the
// session has no invite, ErrBadChannelKey when suppliedKey
// is not the +k key, or ErrChannelIsFull when the channel
// has its +l number of members. It returns nil when none
// applies.
// has at least its +l number of members. It returns nil
// when none applies.
func checkJoinRestrictions(
ctx context.Context,
transaction *sql.Tx,
@@ -2304,23 +2304,6 @@ func (database *Database) HasChannelInvite(
return count > 0, nil
}
// ClearChannelInvite removes a session's invite to a
// channel (called after successful JOIN).
func (database *Database) ClearChannelInvite(
ctx context.Context,
channelID, sessionID int64,
) error {
_, err := database.conn.ExecContext(ctx,
`DELETE FROM channel_invites
WHERE channel_id = ? AND session_id = ?`,
channelID, sessionID)
if err != nil {
return fmt.Errorf("clear invite: %w", err)
}
return nil
}
// --- Tier 2: Secret (+s) ---
// IsChannelSecret checks if a channel has +s mode.
-11
View File
@@ -1342,17 +1342,6 @@ func TestChannelInviteCRUD(t *testing.T) {
if !has {
t.Fatal("should have invite")
}
// Clear invite.
err = database.ClearChannelInvite(ctx, chID, sid)
if err != nil {
t.Fatal(err)
}
has, _ = database.HasChannelInvite(ctx, chID, sid)
if has {
t.Fatal("invite should be cleared")
}
}
func TestChannelSecret(t *testing.T) {