From 2677c16ff887494223df3dae7c6f9c4f80a858f2 Mon Sep 17 00:00:00 2001 From: ze0s <43699394+zze0s@users.noreply.github.com> Date: Wed, 14 Jun 2023 21:06:28 +0200 Subject: [PATCH] feat(irc): add bouncer/znc support (#951) * feat(irc): add initial bouncer support * feat(irc): add bouncer fields to irc update form * fix: make fields optional * feat(db): add migrations --- internal/database/irc.go | 65 +++++++++++++++------------ internal/database/postgres_migrate.go | 7 +++ internal/database/sqlite_migrate.go | 7 +++ internal/domain/irc.go | 4 ++ internal/irc/handler.go | 4 ++ internal/irc/service.go | 2 + web/src/forms/settings/IrcForms.tsx | 13 ++++++ web/src/types/Irc.d.ts | 6 +++ 8 files changed, 79 insertions(+), 29 deletions(-) diff --git a/internal/database/irc.go b/internal/database/irc.go index bf10e1a..050f0fa 100644 --- a/internal/database/irc.go +++ b/internal/database/irc.go @@ -30,7 +30,7 @@ func NewIrcRepo(log logger.Logger, db *DB) domain.IrcRepo { func (r *IrcRepo) GetNetworkByID(ctx context.Context, id int64) (*domain.IrcNetwork, error) { queryBuilder := r.db.squirrel. - Select("id", "enabled", "name", "server", "port", "tls", "pass", "nick", "auth_mechanism", "auth_account", "auth_password", "invite_command"). + Select("id", "enabled", "name", "server", "port", "tls", "pass", "nick", "auth_mechanism", "auth_account", "auth_password", "invite_command", "bouncer_addr", "use_bouncer"). From("irc_network"). Where(sq.Eq{"id": id}) @@ -42,12 +42,12 @@ func (r *IrcRepo) GetNetworkByID(ctx context.Context, id int64) (*domain.IrcNetw var n domain.IrcNetwork - var pass, nick, inviteCmd sql.NullString + var pass, nick, inviteCmd, bouncerAddr sql.NullString var account, password sql.NullString var tls sql.NullBool row := r.db.handler.QueryRowContext(ctx, query, args...) - if err := row.Scan(&n.ID, &n.Enabled, &n.Name, &n.Server, &n.Port, &tls, &pass, &nick, &n.Auth.Mechanism, &account, &password, &inviteCmd); err != nil { + if err := row.Scan(&n.ID, &n.Enabled, &n.Name, &n.Server, &n.Port, &tls, &pass, &nick, &n.Auth.Mechanism, &account, &password, &inviteCmd, &bouncerAddr, &n.UseBouncer); err != nil { return nil, errors.Wrap(err, "error scanning row") } @@ -57,6 +57,7 @@ func (r *IrcRepo) GetNetworkByID(ctx context.Context, id int64) (*domain.IrcNetw n.InviteCommand = inviteCmd.String n.Auth.Account = account.String n.Auth.Password = password.String + n.BouncerAddr = bouncerAddr.String return &n, nil } @@ -106,7 +107,7 @@ func (r *IrcRepo) DeleteNetwork(ctx context.Context, id int64) error { func (r *IrcRepo) FindActiveNetworks(ctx context.Context) ([]domain.IrcNetwork, error) { queryBuilder := r.db.squirrel. - Select("id", "enabled", "name", "server", "port", "tls", "pass", "nick", "auth_mechanism", "auth_account", "auth_password", "invite_command"). + Select("id", "enabled", "name", "server", "port", "tls", "pass", "nick", "auth_mechanism", "auth_account", "auth_password", "invite_command", "bouncer_addr", "use_bouncer"). From("irc_network"). Where(sq.Eq{"enabled": true}) @@ -126,11 +127,11 @@ func (r *IrcRepo) FindActiveNetworks(ctx context.Context) ([]domain.IrcNetwork, for rows.Next() { var net domain.IrcNetwork - var pass, nick, inviteCmd sql.NullString + var pass, nick, inviteCmd, bouncerAddr sql.NullString var account, password sql.NullString var tls sql.NullBool - if err := rows.Scan(&net.ID, &net.Enabled, &net.Name, &net.Server, &net.Port, &tls, &pass, &nick, &net.Auth.Mechanism, &account, &password, &inviteCmd); err != nil { + if err := rows.Scan(&net.ID, &net.Enabled, &net.Name, &net.Server, &net.Port, &tls, &pass, &nick, &net.Auth.Mechanism, &account, &password, &inviteCmd, &bouncerAddr, &net.UseBouncer); err != nil { return nil, errors.Wrap(err, "error scanning row") } @@ -138,6 +139,7 @@ func (r *IrcRepo) FindActiveNetworks(ctx context.Context) ([]domain.IrcNetwork, net.Pass = pass.String net.Nick = nick.String net.InviteCommand = inviteCmd.String + net.BouncerAddr = bouncerAddr.String net.Auth.Account = account.String net.Auth.Password = password.String @@ -153,7 +155,7 @@ func (r *IrcRepo) FindActiveNetworks(ctx context.Context) ([]domain.IrcNetwork, func (r *IrcRepo) ListNetworks(ctx context.Context) ([]domain.IrcNetwork, error) { queryBuilder := r.db.squirrel. - Select("id", "enabled", "name", "server", "port", "tls", "pass", "nick", "auth_mechanism", "auth_account", "auth_password", "invite_command"). + Select("id", "enabled", "name", "server", "port", "tls", "pass", "nick", "auth_mechanism", "auth_account", "auth_password", "invite_command", "bouncer_addr", "use_bouncer"). From("irc_network"). OrderBy("name ASC") @@ -173,11 +175,11 @@ func (r *IrcRepo) ListNetworks(ctx context.Context) ([]domain.IrcNetwork, error) for rows.Next() { var net domain.IrcNetwork - var pass, nick, inviteCmd sql.NullString + var pass, nick, inviteCmd, bouncerAddr sql.NullString var account, password sql.NullString var tls sql.NullBool - if err := rows.Scan(&net.ID, &net.Enabled, &net.Name, &net.Server, &net.Port, &tls, &pass, &nick, &net.Auth.Mechanism, &account, &password, &inviteCmd); err != nil { + if err := rows.Scan(&net.ID, &net.Enabled, &net.Name, &net.Server, &net.Port, &tls, &pass, &nick, &net.Auth.Mechanism, &account, &password, &inviteCmd, &bouncerAddr, &net.UseBouncer); err != nil { return nil, errors.Wrap(err, "error scanning row") } @@ -185,6 +187,7 @@ func (r *IrcRepo) ListNetworks(ctx context.Context) ([]domain.IrcNetwork, error) net.Pass = pass.String net.Nick = nick.String net.InviteCommand = inviteCmd.String + net.BouncerAddr = bouncerAddr.String net.Auth.Account = account.String net.Auth.Password = password.String @@ -237,7 +240,7 @@ func (r *IrcRepo) ListChannels(networkID int64) ([]domain.IrcChannel, error) { func (r *IrcRepo) CheckExistingNetwork(ctx context.Context, network *domain.IrcNetwork) (*domain.IrcNetwork, error) { queryBuilder := r.db.squirrel. - Select("id", "enabled", "name", "server", "port", "tls", "pass", "nick", "auth_mechanism", "auth_account", "auth_password", "invite_command"). + Select("id", "enabled", "name", "server", "port", "tls", "pass", "nick", "auth_mechanism", "auth_account", "auth_password", "invite_command", "bouncer_addr", "use_bouncer"). From("irc_network"). Where(sq.Eq{"server": network.Server}). Where(sq.Eq{"auth_account": network.Auth.Account}) @@ -252,22 +255,24 @@ func (r *IrcRepo) CheckExistingNetwork(ctx context.Context, network *domain.IrcN var net domain.IrcNetwork - var pass, nick, inviteCmd sql.NullString + var pass, nick, inviteCmd, bouncerAddr sql.NullString var account, password sql.NullString var tls sql.NullBool - err = row.Scan(&net.ID, &net.Enabled, &net.Name, &net.Server, &net.Port, &tls, &pass, &nick, &net.Auth.Mechanism, &account, &password, &inviteCmd) - if err == sql.ErrNoRows { - // no result is not an error in our case - return nil, nil - } else if err != nil { - return nil, errors.Wrap(err, "error scanning row") + if err = row.Scan(&net.ID, &net.Enabled, &net.Name, &net.Server, &net.Port, &tls, &pass, &nick, &net.Auth.Mechanism, &account, &password, &inviteCmd, &bouncerAddr, &net.UseBouncer); err != nil { + if err == sql.ErrNoRows { + // no result is not an error in our case + return nil, nil + } else { + return nil, errors.Wrap(err, "error scanning row") + } } net.TLS = tls.Bool net.Pass = pass.String net.Nick = nick.String net.InviteCommand = inviteCmd.String + net.BouncerAddr = bouncerAddr.String net.Auth.Account = account.String net.Auth.Password = password.String @@ -279,6 +284,7 @@ func (r *IrcRepo) StoreNetwork(network *domain.IrcNetwork) error { pass := toNullString(network.Pass) nick := toNullString(network.Nick) inviteCmd := toNullString(network.InviteCommand) + bouncerAddr := toNullString(network.BouncerAddr) account := toNullString(network.Auth.Account) password := toNullString(network.Auth.Password) @@ -300,6 +306,8 @@ func (r *IrcRepo) StoreNetwork(network *domain.IrcNetwork) error { "auth_account", "auth_password", "invite_command", + "bouncer_addr", + "use_bouncer", ). Values( network.Enabled, @@ -313,12 +321,13 @@ func (r *IrcRepo) StoreNetwork(network *domain.IrcNetwork) error { account, password, inviteCmd, + bouncerAddr, + network.UseBouncer, ). Suffix("RETURNING id"). RunWith(r.db.handler) - err = queryBuilder.QueryRow().Scan(&retID) - if err != nil { + if err = queryBuilder.QueryRow().Scan(&retID); err != nil { return errors.Wrap(err, "error executing query") } @@ -332,6 +341,7 @@ func (r *IrcRepo) UpdateNetwork(ctx context.Context, network *domain.IrcNetwork) pass := toNullString(network.Pass) nick := toNullString(network.Nick) inviteCmd := toNullString(network.InviteCommand) + bouncerAddr := toNullString(network.BouncerAddr) account := toNullString(network.Auth.Account) password := toNullString(network.Auth.Password) @@ -351,6 +361,8 @@ func (r *IrcRepo) UpdateNetwork(ctx context.Context, network *domain.IrcNetwork) Set("auth_account", account). Set("auth_password", password). Set("invite_command", inviteCmd). + Set("bouncer_addr", bouncerAddr). + Set("use_bouncer", network.UseBouncer). Set("updated_at", time.Now().Format(time.RFC3339)). Where(sq.Eq{"id": network.ID}) @@ -360,8 +372,7 @@ func (r *IrcRepo) UpdateNetwork(ctx context.Context, network *domain.IrcNetwork) } // update record - _, err = r.db.handler.ExecContext(ctx, query, args...) - if err != nil { + if _, err = r.db.handler.ExecContext(ctx, query, args...); err != nil { return errors.Wrap(err, "error executing query") } @@ -387,8 +398,7 @@ func (r *IrcRepo) StoreNetworkChannels(ctx context.Context, networkID int64, cha return errors.Wrap(err, "error building query") } - _, err = tx.ExecContext(ctx, query, args...) - if err != nil { + if _, err = tx.ExecContext(ctx, query, args...); err != nil { return errors.Wrap(err, "error executing query") } @@ -418,8 +428,7 @@ func (r *IrcRepo) StoreNetworkChannels(ctx context.Context, networkID int64, cha // returning var retID int64 - err = channelQueryBuilder.QueryRowContext(ctx).Scan(&retID) - if err != nil { + if err = channelQueryBuilder.QueryRowContext(ctx).Scan(&retID); err != nil { return errors.Wrap(err, "error executing query storeNetworkChannels") } @@ -466,8 +475,7 @@ func (r *IrcRepo) StoreChannel(ctx context.Context, networkID int64, channel *do return errors.Wrap(err, "error building query") } - _, err = r.db.handler.Exec(query, args...) - if err != nil { + if _, err = r.db.handler.ExecContext(ctx, query, args...); err != nil { return errors.Wrap(err, "error executing query") } } else { @@ -493,8 +501,7 @@ func (r *IrcRepo) StoreChannel(ctx context.Context, networkID int64, channel *do // returning var retID int64 - err = queryBuilder.QueryRow().Scan(&retID) - if err != nil { + if err = queryBuilder.QueryRowContext(ctx).Scan(&retID); err != nil { return errors.Wrap(err, "error executing query") } diff --git a/internal/database/postgres_migrate.go b/internal/database/postgres_migrate.go index be225f8..f78bc5b 100644 --- a/internal/database/postgres_migrate.go +++ b/internal/database/postgres_migrate.go @@ -45,6 +45,8 @@ CREATE TABLE irc_network auth_account TEXT, auth_password TEXT, invite_command TEXT, + use_bouncer BOOLEAN, + bouncer_addr TEXT, connected BOOLEAN, connected_since TIMESTAMP, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, @@ -699,4 +701,9 @@ ADD COLUMN topic text;`, ALTER TABLE release_action_status ADD CONSTRAINT release_action_status_action_id_fk FOREIGN KEY (action_id) REFERENCES action;`, + `ALTER TABLE irc_network +ADD COLUMN use_bouncer BOOLEAN DEFAULT FALSE; + +ALTER TABLE irc_network +ADD COLUMN bouncer_addr TEXT;`, } diff --git a/internal/database/sqlite_migrate.go b/internal/database/sqlite_migrate.go index 4dc79ea..1ac7608 100644 --- a/internal/database/sqlite_migrate.go +++ b/internal/database/sqlite_migrate.go @@ -45,6 +45,8 @@ CREATE TABLE irc_network auth_account TEXT, auth_password TEXT, invite_command TEXT, + use_bouncer BOOLEAN, + bouncer_addr TEXT, connected BOOLEAN, connected_since TIMESTAMP, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, @@ -1141,4 +1143,9 @@ create index release_action_status_release_id_index create index release_action_status_status_index on release_action_status (status);`, + `ALTER TABLE irc_network +ADD COLUMN use_bouncer BOOLEAN DEFAULT FALSE; + +ALTER TABLE irc_network +ADD COLUMN bouncer_addr TEXT;`, } diff --git a/internal/domain/irc.go b/internal/domain/irc.go index 5c6a83d..d204f42 100644 --- a/internal/domain/irc.go +++ b/internal/domain/irc.go @@ -43,6 +43,8 @@ type IrcNetwork struct { Nick string `json:"nick"` Auth IRCAuth `json:"auth,omitempty"` InviteCommand string `json:"invite_command"` + UseBouncer bool `json:"use_bouncer"` + BouncerAddr string `json:"bouncer_addr"` Channels []IrcChannel `json:"channels"` Connected bool `json:"connected"` ConnectedSince *time.Time `json:"connected_since"` @@ -59,6 +61,8 @@ type IrcNetworkWithHealth struct { Nick string `json:"nick"` Auth IRCAuth `json:"auth,omitempty"` InviteCommand string `json:"invite_command"` + UseBouncer bool `json:"use_bouncer"` + BouncerAddr string `json:"bouncer_addr"` CurrentNick string `json:"current_nick"` PreferredNick string `json:"preferred_nick"` Channels []ChannelWithHealth `json:"channels"` diff --git a/internal/irc/handler.go b/internal/irc/handler.go index 941b21f..f0fa6d4 100644 --- a/internal/irc/handler.go +++ b/internal/irc/handler.go @@ -156,6 +156,10 @@ func (h *Handler) Run() error { addr := fmt.Sprintf("%v:%d", h.network.Server, h.network.Port) + if h.network.UseBouncer && h.network.BouncerAddr != "" { + addr = h.network.BouncerAddr + } + subLogger := zstdlog.NewStdLoggerWithLevel(h.log.With().Logger(), zerolog.TraceLevel) h.client = &ircevent.Connection{ diff --git a/internal/irc/service.go b/internal/irc/service.go index 12c0b5b..5124a71 100644 --- a/internal/irc/service.go +++ b/internal/irc/service.go @@ -416,6 +416,8 @@ func (s *service) GetNetworksWithHealth(ctx context.Context) ([]domain.IrcNetwor Nick: n.Nick, Auth: n.Auth, InviteCommand: n.InviteCommand, + BouncerAddr: n.BouncerAddr, + UseBouncer: n.UseBouncer, Connected: false, Channels: []domain.ChannelWithHealth{}, ConnectionErrors: []string{}, diff --git a/web/src/forms/settings/IrcForms.tsx b/web/src/forms/settings/IrcForms.tsx index b5ad879..d1cdfbd 100644 --- a/web/src/forms/settings/IrcForms.tsx +++ b/web/src/forms/settings/IrcForms.tsx @@ -235,6 +235,8 @@ interface IrcNetworkUpdateFormValues { nick: string; auth?: IrcAuth; invite_command: string; + use_bouncer: boolean; + bouncer_addr: string; channels: Array; } @@ -288,6 +290,8 @@ export function IrcNetworkUpdateForm({ pass: network.pass, auth: network.auth, invite_command: network.invite_command, + use_bouncer: network.use_bouncer, + bouncer_addr: network.bouncer_addr, channels: network.channels }; @@ -340,6 +344,15 @@ export function IrcNetworkUpdateForm({ required={true} /> + + {values.use_bouncer && ( + + )} +
Identification diff --git a/web/src/types/Irc.d.ts b/web/src/types/Irc.d.ts index b614ef0..118de2d 100644 --- a/web/src/types/Irc.d.ts +++ b/web/src/types/Irc.d.ts @@ -14,6 +14,8 @@ interface IrcNetwork { pass: string; auth: IrcAuth; // optional invite_command: string; + use_bouncer: boolean; + bouncer_addr: string; channels: IrcChannel[]; connected: boolean; connected_since: string; @@ -29,6 +31,8 @@ interface IrcNetworkCreate { nick: string; auth: IrcAuth; // optional invite_command: string; + use_bouncer?: boolean; + bouncer_addr?: string; channels: IrcChannel[]; connected: boolean; } @@ -58,6 +62,8 @@ interface IrcNetworkWithHealth { nick: string; auth: IrcAuth; // optional invite_command: string; + use_bouncer: boolean; + bouncer_addr: string; channels: IrcChannelWithHealth[]; connected: boolean; connected_since: string;