diff --git a/automod/automod_web.go b/automod/automod_web.go index 47ed9a5559..ffe2c0c59d 100644 --- a/automod/automod_web.go +++ b/automod/automod_web.go @@ -506,8 +506,8 @@ func (p *Plugin) handlePostAutomodUpdateRule(w http.ResponseWriter, r *http.Requ } } if anyMute { - conf, err := moderation.GetConfig(g.ID) - if err != nil || conf.MuteRole == "" { + conf, err := moderation.GetCachedConfigOrDefault(g.ID) + if err != nil || conf.MuteRole == 0 { tx.Rollback() tmpl.AddAlerts(web.ErrorAlert("No mute role set, please configure one.")) return tmpl, nil diff --git a/commands/commands.go b/commands/commands.go index 6f91c89dd2..6d15c91dbd 100644 --- a/commands/commands.go +++ b/commands/commands.go @@ -49,10 +49,6 @@ func (p *Plugin) PluginInfo() *common.PluginInfo { func RegisterPlugin() { plugin := &Plugin{} common.RegisterPlugin(plugin) - err := common.GORM.AutoMigrate(&common.LoggedExecutedCommand{}).Error - if err != nil { - logger.WithError(err).Fatal("Failed migrating logged commands database") - } common.InitSchemas("commands", DBSchemas...) } diff --git a/commands/yagcommmand.go b/commands/yagcommmand.go index 0c4831dc57..4128ae281c 100644 --- a/commands/yagcommmand.go +++ b/commands/yagcommmand.go @@ -20,7 +20,10 @@ import ( "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" "github.com/sirupsen/logrus" + "github.com/volatiletech/sqlboiler/v4/boil" "github.com/volatiletech/sqlboiler/v4/queries/qm" + + commonmodels "github.com/botlabs-gg/yagpdb/v2/common/models" ) type ContextKey int @@ -233,7 +236,7 @@ func (yc *YAGCommand) Run(data *dcmd.Data) (interface{}, error) { } // Set up log entry for later use - logEntry := &common.LoggedExecutedCommand{ + logEntry := &commonmodels.ExecutedCommand{ UserID: discordgo.StrID(data.Author.ID), ChannelID: discordgo.StrID(data.ChannelID), @@ -243,8 +246,7 @@ func (yc *YAGCommand) Run(data *dcmd.Data) (interface{}, error) { } if data.GuildData != nil { - logEntry.GuildID = discordgo.StrID(data.GuildData.GS.ID) - + logEntry.GuildID.SetValid(discordgo.StrID(data.GuildData.GS.ID)) } metricsExcecutedCommands.With(prometheus.Labels{"name": "(other)", "trigger_type": triggerType}).Inc() @@ -284,7 +286,7 @@ func (yc *YAGCommand) Run(data *dcmd.Data) (interface{}, error) { } // Create command log entry - err := common.GORM.Create(logEntry).Error + err := logEntry.InsertG(data.Context(), boil.Infer()) if err != nil { logger.WithError(err).Error("Failed creating command execution log") } diff --git a/common/common.go b/common/common.go index 60fb491e30..c29d7cc0da 100644 --- a/common/common.go +++ b/common/common.go @@ -17,8 +17,6 @@ import ( "github.com/botlabs-gg/yagpdb/v2/common/cacheset" "github.com/botlabs-gg/yagpdb/v2/lib/discordgo" - "github.com/jinzhu/gorm" - _ "github.com/jinzhu/gorm/dialects/postgres" "github.com/jmoiron/sqlx" "github.com/mediocregopher/radix/v3" "github.com/prometheus/client_golang/prometheus" @@ -30,7 +28,6 @@ import ( var ( VERSION = "unknown" - GORM *gorm.DB PQ *sql.DB SQLX *sqlx.DB @@ -131,6 +128,10 @@ func Init() error { logger.Info("Initializing core schema") InitSchemas("core_configs", CoreServerConfDBSchema, localIDsSchema) + + logger.Info("Initializing executed command log schema") + InitSchemas("executed_commands", ExecutedCommandDBSchemas...) + initQueuedSchemas() return err @@ -274,9 +275,8 @@ func connectDB(host, user, pass, dbName string, maxConns int) error { passwordPart = " password='" + pass + "'" } - db, err := gorm.Open("postgres", fmt.Sprintf("host=%s user=%s dbname=%s sslmode=disable%s", host, user, dbName, passwordPart)) - GORM = db - PQ = db.DB() + db, err := sql.Open("postgres", fmt.Sprintf("host=%s user=%s dbname=%s sslmode=disable%s", host, user, dbName, passwordPart)) + PQ = db SQLX = sqlx.NewDb(PQ, "postgres") boil.SetDB(PQ) if err == nil { @@ -284,7 +284,6 @@ func connectDB(host, user, pass, dbName string, maxConns int) error { PQ.SetMaxIdleConns(maxConns) logger.Infof("Set max PG connections to %d", maxConns) } - GORM.SetLogger(&GORMLogger{}) return err } diff --git a/common/configstore/README.md b/common/configstore/README.md deleted file mode 100644 index a63ba90dfd..0000000000 --- a/common/configstore/README.md +++ /dev/null @@ -1,5 +0,0 @@ -#common/configstore - -Provides 2 backend storages (redis/postgres) and the ability to have custom ones. - -Wraps everything around a cache. diff --git a/common/configstore/db.go b/common/configstore/db.go deleted file mode 100644 index 157e08e5f0..0000000000 --- a/common/configstore/db.go +++ /dev/null @@ -1,157 +0,0 @@ -package configstore - -import ( - "errors" - "reflect" - "strconv" - "time" - - "github.com/botlabs-gg/yagpdb/v2/common" - "github.com/botlabs-gg/yagpdb/v2/common/pubsub" - "github.com/jinzhu/gorm" - "github.com/karlseguin/ccache" - "golang.org/x/net/context" -) - -var ( - ErrNotFound = errors.New("Config not found") - ErrInvalidConfig = errors.New("Invalid config") - - SQL = &Postgres{} - Cached = NewCached() - storages = make(map[reflect.Type]Storage) - - logger = common.GetFixedPrefixLogger("configstore") -) - -func RegisterConfig(stor Storage, conf GuildConfig) { - storages[reflect.TypeOf(conf)] = stor -} - -func StrID(id int64) string { - return strconv.FormatInt(id, 10) -} - -func KeyGuildConfig(guildID int64, configName string) string { - return "guild_config:" + configName + ":" + StrID(guildID) -} - -type GuildConfigModel struct { - GuildID int64 `gorm:"primary_key"` - CreatedAt time.Time - UpdatedAt time.Time -} - -func (gm *GuildConfigModel) GetUpdatedAt() time.Time { - return gm.UpdatedAt -} - -func (gm *GuildConfigModel) GetGuildID() int64 { - return gm.GuildID -} - -type GuildConfig interface { - GetGuildID() int64 - GetUpdatedAt() time.Time - GetName() string -} - -type PostFetchHandler interface { - // Called after retrieving from underlying storage, before being put in cache - // use this for any post processing etc.. - PostFetch() -} - -type Storage interface { - // GetGuildConfig returns a GuildConfig item from db - GetGuildConfig(ctx context.Context, guildID int64, dest GuildConfig) (err error) - - // SetGuildConfig saves the GuildConfig struct - SetGuildConfig(ctx context.Context, conf GuildConfig) error - - // SetIfLatest saves it only if the passedLatest time is the latest version - // SetIfLatest(ctx context.Context, conf GuildConfig) (updated bool, err error) -} - -type CachedStorage struct { - cache *ccache.Cache -} - -func NewCached() *CachedStorage { - return &CachedStorage{ - cache: ccache.New(ccache.Configure().MaxSize(25000)), - } -} - -func (c *CachedStorage) InvalidateCache(guildID int64, config string) { - c.cache.Delete(KeyGuildConfig(guildID, config)) -} - -func (c *CachedStorage) GetGuildConfig(ctx context.Context, guildID int64, dest GuildConfig) error { - cached := true - item, err := c.cache.Fetch(KeyGuildConfig(guildID, dest.GetName()), time.Minute*10, func() (interface{}, error) { - underlying, ok := storages[reflect.TypeOf(dest)] - if !ok { - return nil, ErrInvalidConfig - } - - cached = false - err := underlying.GetGuildConfig(ctx, guildID, dest) - if err == gorm.ErrRecordNotFound { - err = ErrNotFound - } - - // Call the postfetchhandler - if err == nil { - if pfh, ok := dest.(PostFetchHandler); ok { - pfh.PostFetch() - } - } - - return dest, err - }) - - // If it was loaded from cache, we need to load it into "dest" ourselves - if err == nil && cached { - reflect.Indirect(reflect.ValueOf(dest)).Set(reflect.Indirect(reflect.ValueOf(item.Value()))) - } - - return err -} - -func InitDatabases() { - pubsub.AddHandler("invalidate_guild_config_cache", HandleInvalidateCacheEvt, "") -} - -func HandleInvalidateCacheEvt(event *pubsub.Event) { - conf, ok := event.Data.(*string) - if !ok { - logger.Error("Invalid invalidate guild config cache event") - return - } - - tg, _ := strconv.ParseInt(event.TargetGuild, 10, 64) - Cached.InvalidateCache(tg, *conf) -} - -// InvalidateGuildCache is a helper that both instantly invalides the local application cache -// As well as sending the pusub event -func InvalidateGuildCache(guildID interface{}, conf GuildConfig) { - var gID int64 - switch t := guildID.(type) { - case int64: - gID = t - case string: - gID, _ = strconv.ParseInt(t, 10, 64) - case GuildConfig: - gID = t.GetGuildID() - default: - panic("Invalid guildID passed to InvalidateGuildCache") - } - - Cached.InvalidateCache(gID, conf.GetName()) - err := pubsub.Publish("invalidate_guild_config_cache", gID, conf.GetName()) - if err != nil { - logger.WithError(err).Error("FAILED INVALIDATING CACHE") - } -} diff --git a/common/configstore/sql.go b/common/configstore/sql.go deleted file mode 100644 index 359c9e87b3..0000000000 --- a/common/configstore/sql.go +++ /dev/null @@ -1,70 +0,0 @@ -package configstore - -import ( - "math/rand" - "strings" - "time" - - "github.com/botlabs-gg/yagpdb/v2/common" - "github.com/jinzhu/gorm" - "golang.org/x/net/context" -) - -const MaxRetries = 1000 - -type Postgres struct{} - -// conf is requried to be a pointer value -func (p *Postgres) GetGuildConfig(ctx context.Context, guildID int64, conf GuildConfig) error { - - currentRetries := 0 - for { - err := common.GORM.Where("guild_id = ?", guildID).First(conf).Error - if err == nil { - if currentRetries > 1 { - logger.Info("Suceeded after ", currentRetries, " retries") - } - return nil - } - - if err == gorm.ErrRecordNotFound { - return ErrNotFound - } - - if strings.Contains(err.Error(), "sorry, too many clients already") { - time.Sleep(time.Millisecond * 10 * time.Duration(rand.Intn(10))) - currentRetries++ - if currentRetries > MaxRetries { - return err - } - continue - } - - return err - } - - return nil -} - -// conf is requried to be a pointer value -func (p *Postgres) SetGuildConfig(ctx context.Context, conf GuildConfig) error { - err := common.GORM.Save(conf).Error - if err != nil { - return err - } - - InvalidateGuildCache(conf, conf) - return nil -} - -func (p *Postgres) SetIfLatest(ctx context.Context, conf GuildConfig) (updated bool, err error) { - result := common.GORM.Where("updated_at = ?", conf.GetUpdatedAt()).Save(conf) - updated = result.RowsAffected > 0 - err = result.Error - - if err == nil { - InvalidateGuildCache(conf, conf) - } - - return -} diff --git a/common/executedcommandschema.go b/common/executedcommandschema.go new file mode 100644 index 0000000000..662585ff38 --- /dev/null +++ b/common/executedcommandschema.go @@ -0,0 +1,39 @@ +package common + +var ExecutedCommandDBSchemas = []string{` +CREATE TABLE IF NOT EXISTS executed_commands ( + id SERIAL PRIMARY KEY, + created_at TIMESTAMP WITH TIME ZONE NOT NULL, + updated_at TIMESTAMP WITH TIME ZONE NOT NULL, + + user_id TEXT NOT NULL, -- text not bigint for legacy compatibility + channel_id TEXT NOT NULL, + guild_id TEXT, + + command TEXT NOT NULL, + raw_command TEXT NOT NULL, + error TEXT, + + time_stamp TIMESTAMP WITH TIME ZONE NOT NULL, + response_time BIGINT NOT NULL +); +`, ` +-- Preexisting tables created prior to sqlboiler are missing non-null constraints, +-- so add them retraoctively. + +ALTER TABLE executed_commands ALTER COLUMN created_at SET NOT NULL; +`, ` +ALTER TABLE executed_commands ALTER COLUMN updated_at SET NOT NULL; +`, ` +ALTER TABLE executed_commands ALTER COLUMN user_id SET NOT NULL; +`, ` +ALTER TABLE executed_commands ALTER COLUMN channel_id SET NOT NULL; +`, ` +ALTER TABLE executed_commands ALTER COLUMN command SET NOT NULL; +`, ` +ALTER TABLE executed_commands ALTER COLUMN raw_command SET NOT NULL; +`, ` +ALTER TABLE executed_commands ALTER COLUMN time_stamp SET NOT NULL; +`, ` +ALTER TABLE executed_commands ALTER COLUMN response_time SET NOT NULL; +`} diff --git a/common/loghooks.go b/common/loghooks.go index 55325cf3f6..a0ea7aef3d 100644 --- a/common/loghooks.go +++ b/common/loghooks.go @@ -137,13 +137,6 @@ func DiscordGatewayLogger(shardID int, connectionID int, msgL int, msgf string, } } -type GORMLogger struct { -} - -func (g *GORMLogger) Print(v ...interface{}) { - logrus.WithField("stck", "...").Error(v...) -} - type LoggingTransport struct { Inner http.RoundTripper } diff --git a/common/models/boil_table_names.go b/common/models/boil_table_names.go index 34981d1d8c..cf967ed513 100644 --- a/common/models/boil_table_names.go +++ b/common/models/boil_table_names.go @@ -4,7 +4,9 @@ package models var TableNames = struct { - CoreConfigs string + CoreConfigs string + ExecutedCommands string }{ - CoreConfigs: "core_configs", + CoreConfigs: "core_configs", + ExecutedCommands: "executed_commands", } diff --git a/common/models/boil_view_names.go b/common/models/boil_view_names.go new file mode 100644 index 0000000000..01504d82bf --- /dev/null +++ b/common/models/boil_view_names.go @@ -0,0 +1,7 @@ +// Code generated by SQLBoiler 4.16.2 (https://github.com/volatiletech/sqlboiler). DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package models + +var ViewNames = struct { +}{} diff --git a/common/models/executed_commands.go b/common/models/executed_commands.go new file mode 100644 index 0000000000..e3716e7190 --- /dev/null +++ b/common/models/executed_commands.go @@ -0,0 +1,974 @@ +// Code generated by SQLBoiler 4.16.2 (https://github.com/volatiletech/sqlboiler). DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package models + +import ( + "context" + "database/sql" + "fmt" + "reflect" + "strconv" + "strings" + "sync" + "time" + + "github.com/friendsofgo/errors" + "github.com/volatiletech/null/v8" + "github.com/volatiletech/sqlboiler/v4/boil" + "github.com/volatiletech/sqlboiler/v4/queries" + "github.com/volatiletech/sqlboiler/v4/queries/qm" + "github.com/volatiletech/sqlboiler/v4/queries/qmhelper" + "github.com/volatiletech/strmangle" +) + +// ExecutedCommand is an object representing the database table. +type ExecutedCommand struct { + ID int `boil:"id" json:"id" toml:"id" yaml:"id"` + CreatedAt time.Time `boil:"created_at" json:"created_at" toml:"created_at" yaml:"created_at"` + UpdatedAt time.Time `boil:"updated_at" json:"updated_at" toml:"updated_at" yaml:"updated_at"` + UserID string `boil:"user_id" json:"user_id" toml:"user_id" yaml:"user_id"` + ChannelID string `boil:"channel_id" json:"channel_id" toml:"channel_id" yaml:"channel_id"` + GuildID null.String `boil:"guild_id" json:"guild_id,omitempty" toml:"guild_id" yaml:"guild_id,omitempty"` + Command string `boil:"command" json:"command" toml:"command" yaml:"command"` + RawCommand string `boil:"raw_command" json:"raw_command" toml:"raw_command" yaml:"raw_command"` + Error null.String `boil:"error" json:"error,omitempty" toml:"error" yaml:"error,omitempty"` + TimeStamp time.Time `boil:"time_stamp" json:"time_stamp" toml:"time_stamp" yaml:"time_stamp"` + ResponseTime int64 `boil:"response_time" json:"response_time" toml:"response_time" yaml:"response_time"` + + R *executedCommandR `boil:"-" json:"-" toml:"-" yaml:"-"` + L executedCommandL `boil:"-" json:"-" toml:"-" yaml:"-"` +} + +var ExecutedCommandColumns = struct { + ID string + CreatedAt string + UpdatedAt string + UserID string + ChannelID string + GuildID string + Command string + RawCommand string + Error string + TimeStamp string + ResponseTime string +}{ + ID: "id", + CreatedAt: "created_at", + UpdatedAt: "updated_at", + UserID: "user_id", + ChannelID: "channel_id", + GuildID: "guild_id", + Command: "command", + RawCommand: "raw_command", + Error: "error", + TimeStamp: "time_stamp", + ResponseTime: "response_time", +} + +var ExecutedCommandTableColumns = struct { + ID string + CreatedAt string + UpdatedAt string + UserID string + ChannelID string + GuildID string + Command string + RawCommand string + Error string + TimeStamp string + ResponseTime string +}{ + ID: "executed_commands.id", + CreatedAt: "executed_commands.created_at", + UpdatedAt: "executed_commands.updated_at", + UserID: "executed_commands.user_id", + ChannelID: "executed_commands.channel_id", + GuildID: "executed_commands.guild_id", + Command: "executed_commands.command", + RawCommand: "executed_commands.raw_command", + Error: "executed_commands.error", + TimeStamp: "executed_commands.time_stamp", + ResponseTime: "executed_commands.response_time", +} + +// Generated where + +type whereHelperint struct{ field string } + +func (w whereHelperint) EQ(x int) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.EQ, x) } +func (w whereHelperint) NEQ(x int) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.NEQ, x) } +func (w whereHelperint) LT(x int) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.LT, x) } +func (w whereHelperint) LTE(x int) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.LTE, x) } +func (w whereHelperint) GT(x int) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.GT, x) } +func (w whereHelperint) GTE(x int) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.GTE, x) } +func (w whereHelperint) IN(slice []int) qm.QueryMod { + values := make([]interface{}, 0, len(slice)) + for _, value := range slice { + values = append(values, value) + } + return qm.WhereIn(fmt.Sprintf("%s IN ?", w.field), values...) +} +func (w whereHelperint) NIN(slice []int) qm.QueryMod { + values := make([]interface{}, 0, len(slice)) + for _, value := range slice { + values = append(values, value) + } + return qm.WhereNotIn(fmt.Sprintf("%s NOT IN ?", w.field), values...) +} + +type whereHelpertime_Time struct{ field string } + +func (w whereHelpertime_Time) EQ(x time.Time) qm.QueryMod { + return qmhelper.Where(w.field, qmhelper.EQ, x) +} +func (w whereHelpertime_Time) NEQ(x time.Time) qm.QueryMod { + return qmhelper.Where(w.field, qmhelper.NEQ, x) +} +func (w whereHelpertime_Time) LT(x time.Time) qm.QueryMod { + return qmhelper.Where(w.field, qmhelper.LT, x) +} +func (w whereHelpertime_Time) LTE(x time.Time) qm.QueryMod { + return qmhelper.Where(w.field, qmhelper.LTE, x) +} +func (w whereHelpertime_Time) GT(x time.Time) qm.QueryMod { + return qmhelper.Where(w.field, qmhelper.GT, x) +} +func (w whereHelpertime_Time) GTE(x time.Time) qm.QueryMod { + return qmhelper.Where(w.field, qmhelper.GTE, x) +} + +type whereHelperstring struct{ field string } + +func (w whereHelperstring) EQ(x string) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.EQ, x) } +func (w whereHelperstring) NEQ(x string) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.NEQ, x) } +func (w whereHelperstring) LT(x string) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.LT, x) } +func (w whereHelperstring) LTE(x string) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.LTE, x) } +func (w whereHelperstring) GT(x string) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.GT, x) } +func (w whereHelperstring) GTE(x string) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.GTE, x) } +func (w whereHelperstring) LIKE(x string) qm.QueryMod { return qm.Where(w.field+" LIKE ?", x) } +func (w whereHelperstring) NLIKE(x string) qm.QueryMod { return qm.Where(w.field+" NOT LIKE ?", x) } +func (w whereHelperstring) ILIKE(x string) qm.QueryMod { return qm.Where(w.field+" ILIKE ?", x) } +func (w whereHelperstring) NILIKE(x string) qm.QueryMod { return qm.Where(w.field+" NOT ILIKE ?", x) } +func (w whereHelperstring) IN(slice []string) qm.QueryMod { + values := make([]interface{}, 0, len(slice)) + for _, value := range slice { + values = append(values, value) + } + return qm.WhereIn(fmt.Sprintf("%s IN ?", w.field), values...) +} +func (w whereHelperstring) NIN(slice []string) qm.QueryMod { + values := make([]interface{}, 0, len(slice)) + for _, value := range slice { + values = append(values, value) + } + return qm.WhereNotIn(fmt.Sprintf("%s NOT IN ?", w.field), values...) +} + +type whereHelpernull_String struct{ field string } + +func (w whereHelpernull_String) EQ(x null.String) qm.QueryMod { + return qmhelper.WhereNullEQ(w.field, false, x) +} +func (w whereHelpernull_String) NEQ(x null.String) qm.QueryMod { + return qmhelper.WhereNullEQ(w.field, true, x) +} +func (w whereHelpernull_String) LT(x null.String) qm.QueryMod { + return qmhelper.Where(w.field, qmhelper.LT, x) +} +func (w whereHelpernull_String) LTE(x null.String) qm.QueryMod { + return qmhelper.Where(w.field, qmhelper.LTE, x) +} +func (w whereHelpernull_String) GT(x null.String) qm.QueryMod { + return qmhelper.Where(w.field, qmhelper.GT, x) +} +func (w whereHelpernull_String) GTE(x null.String) qm.QueryMod { + return qmhelper.Where(w.field, qmhelper.GTE, x) +} +func (w whereHelpernull_String) LIKE(x null.String) qm.QueryMod { + return qm.Where(w.field+" LIKE ?", x) +} +func (w whereHelpernull_String) NLIKE(x null.String) qm.QueryMod { + return qm.Where(w.field+" NOT LIKE ?", x) +} +func (w whereHelpernull_String) ILIKE(x null.String) qm.QueryMod { + return qm.Where(w.field+" ILIKE ?", x) +} +func (w whereHelpernull_String) NILIKE(x null.String) qm.QueryMod { + return qm.Where(w.field+" NOT ILIKE ?", x) +} +func (w whereHelpernull_String) IN(slice []string) qm.QueryMod { + values := make([]interface{}, 0, len(slice)) + for _, value := range slice { + values = append(values, value) + } + return qm.WhereIn(fmt.Sprintf("%s IN ?", w.field), values...) +} +func (w whereHelpernull_String) NIN(slice []string) qm.QueryMod { + values := make([]interface{}, 0, len(slice)) + for _, value := range slice { + values = append(values, value) + } + return qm.WhereNotIn(fmt.Sprintf("%s NOT IN ?", w.field), values...) +} + +func (w whereHelpernull_String) IsNull() qm.QueryMod { return qmhelper.WhereIsNull(w.field) } +func (w whereHelpernull_String) IsNotNull() qm.QueryMod { return qmhelper.WhereIsNotNull(w.field) } + +var ExecutedCommandWhere = struct { + ID whereHelperint + CreatedAt whereHelpertime_Time + UpdatedAt whereHelpertime_Time + UserID whereHelperstring + ChannelID whereHelperstring + GuildID whereHelpernull_String + Command whereHelperstring + RawCommand whereHelperstring + Error whereHelpernull_String + TimeStamp whereHelpertime_Time + ResponseTime whereHelperint64 +}{ + ID: whereHelperint{field: "\"executed_commands\".\"id\""}, + CreatedAt: whereHelpertime_Time{field: "\"executed_commands\".\"created_at\""}, + UpdatedAt: whereHelpertime_Time{field: "\"executed_commands\".\"updated_at\""}, + UserID: whereHelperstring{field: "\"executed_commands\".\"user_id\""}, + ChannelID: whereHelperstring{field: "\"executed_commands\".\"channel_id\""}, + GuildID: whereHelpernull_String{field: "\"executed_commands\".\"guild_id\""}, + Command: whereHelperstring{field: "\"executed_commands\".\"command\""}, + RawCommand: whereHelperstring{field: "\"executed_commands\".\"raw_command\""}, + Error: whereHelpernull_String{field: "\"executed_commands\".\"error\""}, + TimeStamp: whereHelpertime_Time{field: "\"executed_commands\".\"time_stamp\""}, + ResponseTime: whereHelperint64{field: "\"executed_commands\".\"response_time\""}, +} + +// ExecutedCommandRels is where relationship names are stored. +var ExecutedCommandRels = struct { +}{} + +// executedCommandR is where relationships are stored. +type executedCommandR struct { +} + +// NewStruct creates a new relationship struct +func (*executedCommandR) NewStruct() *executedCommandR { + return &executedCommandR{} +} + +// executedCommandL is where Load methods for each relationship are stored. +type executedCommandL struct{} + +var ( + executedCommandAllColumns = []string{"id", "created_at", "updated_at", "user_id", "channel_id", "guild_id", "command", "raw_command", "error", "time_stamp", "response_time"} + executedCommandColumnsWithoutDefault = []string{"created_at", "updated_at", "user_id", "channel_id", "command", "raw_command", "time_stamp", "response_time"} + executedCommandColumnsWithDefault = []string{"id", "guild_id", "error"} + executedCommandPrimaryKeyColumns = []string{"id"} + executedCommandGeneratedColumns = []string{} +) + +type ( + // ExecutedCommandSlice is an alias for a slice of pointers to ExecutedCommand. + // This should almost always be used instead of []ExecutedCommand. + ExecutedCommandSlice []*ExecutedCommand + + executedCommandQuery struct { + *queries.Query + } +) + +// Cache for insert, update and upsert +var ( + executedCommandType = reflect.TypeOf(&ExecutedCommand{}) + executedCommandMapping = queries.MakeStructMapping(executedCommandType) + executedCommandPrimaryKeyMapping, _ = queries.BindMapping(executedCommandType, executedCommandMapping, executedCommandPrimaryKeyColumns) + executedCommandInsertCacheMut sync.RWMutex + executedCommandInsertCache = make(map[string]insertCache) + executedCommandUpdateCacheMut sync.RWMutex + executedCommandUpdateCache = make(map[string]updateCache) + executedCommandUpsertCacheMut sync.RWMutex + executedCommandUpsertCache = make(map[string]insertCache) +) + +var ( + // Force time package dependency for automated UpdatedAt/CreatedAt. + _ = time.Second + // Force qmhelper dependency for where clause generation (which doesn't + // always happen) + _ = qmhelper.Where +) + +// OneG returns a single executedCommand record from the query using the global executor. +func (q executedCommandQuery) OneG(ctx context.Context) (*ExecutedCommand, error) { + return q.One(ctx, boil.GetContextDB()) +} + +// One returns a single executedCommand record from the query. +func (q executedCommandQuery) One(ctx context.Context, exec boil.ContextExecutor) (*ExecutedCommand, error) { + o := &ExecutedCommand{} + + queries.SetLimit(q.Query, 1) + + err := q.Bind(ctx, exec, o) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + return nil, sql.ErrNoRows + } + return nil, errors.Wrap(err, "models: failed to execute a one query for executed_commands") + } + + return o, nil +} + +// AllG returns all ExecutedCommand records from the query using the global executor. +func (q executedCommandQuery) AllG(ctx context.Context) (ExecutedCommandSlice, error) { + return q.All(ctx, boil.GetContextDB()) +} + +// All returns all ExecutedCommand records from the query. +func (q executedCommandQuery) All(ctx context.Context, exec boil.ContextExecutor) (ExecutedCommandSlice, error) { + var o []*ExecutedCommand + + err := q.Bind(ctx, exec, &o) + if err != nil { + return nil, errors.Wrap(err, "models: failed to assign all query results to ExecutedCommand slice") + } + + return o, nil +} + +// CountG returns the count of all ExecutedCommand records in the query using the global executor +func (q executedCommandQuery) CountG(ctx context.Context) (int64, error) { + return q.Count(ctx, boil.GetContextDB()) +} + +// Count returns the count of all ExecutedCommand records in the query. +func (q executedCommandQuery) Count(ctx context.Context, exec boil.ContextExecutor) (int64, error) { + var count int64 + + queries.SetSelect(q.Query, nil) + queries.SetCount(q.Query) + + err := q.Query.QueryRowContext(ctx, exec).Scan(&count) + if err != nil { + return 0, errors.Wrap(err, "models: failed to count executed_commands rows") + } + + return count, nil +} + +// ExistsG checks if the row exists in the table using the global executor. +func (q executedCommandQuery) ExistsG(ctx context.Context) (bool, error) { + return q.Exists(ctx, boil.GetContextDB()) +} + +// Exists checks if the row exists in the table. +func (q executedCommandQuery) Exists(ctx context.Context, exec boil.ContextExecutor) (bool, error) { + var count int64 + + queries.SetSelect(q.Query, nil) + queries.SetCount(q.Query) + queries.SetLimit(q.Query, 1) + + err := q.Query.QueryRowContext(ctx, exec).Scan(&count) + if err != nil { + return false, errors.Wrap(err, "models: failed to check if executed_commands exists") + } + + return count > 0, nil +} + +// ExecutedCommands retrieves all the records using an executor. +func ExecutedCommands(mods ...qm.QueryMod) executedCommandQuery { + mods = append(mods, qm.From("\"executed_commands\"")) + q := NewQuery(mods...) + if len(queries.GetSelect(q)) == 0 { + queries.SetSelect(q, []string{"\"executed_commands\".*"}) + } + + return executedCommandQuery{q} +} + +// FindExecutedCommandG retrieves a single record by ID. +func FindExecutedCommandG(ctx context.Context, iD int, selectCols ...string) (*ExecutedCommand, error) { + return FindExecutedCommand(ctx, boil.GetContextDB(), iD, selectCols...) +} + +// FindExecutedCommand retrieves a single record by ID with an executor. +// If selectCols is empty Find will return all columns. +func FindExecutedCommand(ctx context.Context, exec boil.ContextExecutor, iD int, selectCols ...string) (*ExecutedCommand, error) { + executedCommandObj := &ExecutedCommand{} + + sel := "*" + if len(selectCols) > 0 { + sel = strings.Join(strmangle.IdentQuoteSlice(dialect.LQ, dialect.RQ, selectCols), ",") + } + query := fmt.Sprintf( + "select %s from \"executed_commands\" where \"id\"=$1", sel, + ) + + q := queries.Raw(query, iD) + + err := q.Bind(ctx, exec, executedCommandObj) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + return nil, sql.ErrNoRows + } + return nil, errors.Wrap(err, "models: unable to select from executed_commands") + } + + return executedCommandObj, nil +} + +// InsertG a single record. See Insert for whitelist behavior description. +func (o *ExecutedCommand) InsertG(ctx context.Context, columns boil.Columns) error { + return o.Insert(ctx, boil.GetContextDB(), columns) +} + +// Insert a single record using an executor. +// See boil.Columns.InsertColumnSet documentation to understand column list inference for inserts. +func (o *ExecutedCommand) Insert(ctx context.Context, exec boil.ContextExecutor, columns boil.Columns) error { + if o == nil { + return errors.New("models: no executed_commands provided for insertion") + } + + var err error + if !boil.TimestampsAreSkipped(ctx) { + currTime := time.Now().In(boil.GetLocation()) + + if o.CreatedAt.IsZero() { + o.CreatedAt = currTime + } + if o.UpdatedAt.IsZero() { + o.UpdatedAt = currTime + } + } + + nzDefaults := queries.NonZeroDefaultSet(executedCommandColumnsWithDefault, o) + + key := makeCacheKey(columns, nzDefaults) + executedCommandInsertCacheMut.RLock() + cache, cached := executedCommandInsertCache[key] + executedCommandInsertCacheMut.RUnlock() + + if !cached { + wl, returnColumns := columns.InsertColumnSet( + executedCommandAllColumns, + executedCommandColumnsWithDefault, + executedCommandColumnsWithoutDefault, + nzDefaults, + ) + + cache.valueMapping, err = queries.BindMapping(executedCommandType, executedCommandMapping, wl) + if err != nil { + return err + } + cache.retMapping, err = queries.BindMapping(executedCommandType, executedCommandMapping, returnColumns) + if err != nil { + return err + } + if len(wl) != 0 { + cache.query = fmt.Sprintf("INSERT INTO \"executed_commands\" (\"%s\") %%sVALUES (%s)%%s", strings.Join(wl, "\",\""), strmangle.Placeholders(dialect.UseIndexPlaceholders, len(wl), 1, 1)) + } else { + cache.query = "INSERT INTO \"executed_commands\" %sDEFAULT VALUES%s" + } + + var queryOutput, queryReturning string + + if len(cache.retMapping) != 0 { + queryReturning = fmt.Sprintf(" RETURNING \"%s\"", strings.Join(returnColumns, "\",\"")) + } + + cache.query = fmt.Sprintf(cache.query, queryOutput, queryReturning) + } + + value := reflect.Indirect(reflect.ValueOf(o)) + vals := queries.ValuesFromMapping(value, cache.valueMapping) + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, cache.query) + fmt.Fprintln(writer, vals) + } + + if len(cache.retMapping) != 0 { + err = exec.QueryRowContext(ctx, cache.query, vals...).Scan(queries.PtrsFromMapping(value, cache.retMapping)...) + } else { + _, err = exec.ExecContext(ctx, cache.query, vals...) + } + + if err != nil { + return errors.Wrap(err, "models: unable to insert into executed_commands") + } + + if !cached { + executedCommandInsertCacheMut.Lock() + executedCommandInsertCache[key] = cache + executedCommandInsertCacheMut.Unlock() + } + + return nil +} + +// UpdateG a single ExecutedCommand record using the global executor. +// See Update for more documentation. +func (o *ExecutedCommand) UpdateG(ctx context.Context, columns boil.Columns) (int64, error) { + return o.Update(ctx, boil.GetContextDB(), columns) +} + +// Update uses an executor to update the ExecutedCommand. +// See boil.Columns.UpdateColumnSet documentation to understand column list inference for updates. +// Update does not automatically update the record in case of default values. Use .Reload() to refresh the records. +func (o *ExecutedCommand) Update(ctx context.Context, exec boil.ContextExecutor, columns boil.Columns) (int64, error) { + if !boil.TimestampsAreSkipped(ctx) { + currTime := time.Now().In(boil.GetLocation()) + + o.UpdatedAt = currTime + } + + var err error + key := makeCacheKey(columns, nil) + executedCommandUpdateCacheMut.RLock() + cache, cached := executedCommandUpdateCache[key] + executedCommandUpdateCacheMut.RUnlock() + + if !cached { + wl := columns.UpdateColumnSet( + executedCommandAllColumns, + executedCommandPrimaryKeyColumns, + ) + + if !columns.IsWhitelist() { + wl = strmangle.SetComplement(wl, []string{"created_at"}) + } + if len(wl) == 0 { + return 0, errors.New("models: unable to update executed_commands, could not build whitelist") + } + + cache.query = fmt.Sprintf("UPDATE \"executed_commands\" SET %s WHERE %s", + strmangle.SetParamNames("\"", "\"", 1, wl), + strmangle.WhereClause("\"", "\"", len(wl)+1, executedCommandPrimaryKeyColumns), + ) + cache.valueMapping, err = queries.BindMapping(executedCommandType, executedCommandMapping, append(wl, executedCommandPrimaryKeyColumns...)) + if err != nil { + return 0, err + } + } + + values := queries.ValuesFromMapping(reflect.Indirect(reflect.ValueOf(o)), cache.valueMapping) + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, cache.query) + fmt.Fprintln(writer, values) + } + var result sql.Result + result, err = exec.ExecContext(ctx, cache.query, values...) + if err != nil { + return 0, errors.Wrap(err, "models: unable to update executed_commands row") + } + + rowsAff, err := result.RowsAffected() + if err != nil { + return 0, errors.Wrap(err, "models: failed to get rows affected by update for executed_commands") + } + + if !cached { + executedCommandUpdateCacheMut.Lock() + executedCommandUpdateCache[key] = cache + executedCommandUpdateCacheMut.Unlock() + } + + return rowsAff, nil +} + +// UpdateAllG updates all rows with the specified column values. +func (q executedCommandQuery) UpdateAllG(ctx context.Context, cols M) (int64, error) { + return q.UpdateAll(ctx, boil.GetContextDB(), cols) +} + +// UpdateAll updates all rows with the specified column values. +func (q executedCommandQuery) UpdateAll(ctx context.Context, exec boil.ContextExecutor, cols M) (int64, error) { + queries.SetUpdate(q.Query, cols) + + result, err := q.Query.ExecContext(ctx, exec) + if err != nil { + return 0, errors.Wrap(err, "models: unable to update all for executed_commands") + } + + rowsAff, err := result.RowsAffected() + if err != nil { + return 0, errors.Wrap(err, "models: unable to retrieve rows affected for executed_commands") + } + + return rowsAff, nil +} + +// UpdateAllG updates all rows with the specified column values. +func (o ExecutedCommandSlice) UpdateAllG(ctx context.Context, cols M) (int64, error) { + return o.UpdateAll(ctx, boil.GetContextDB(), cols) +} + +// UpdateAll updates all rows with the specified column values, using an executor. +func (o ExecutedCommandSlice) UpdateAll(ctx context.Context, exec boil.ContextExecutor, cols M) (int64, error) { + ln := int64(len(o)) + if ln == 0 { + return 0, nil + } + + if len(cols) == 0 { + return 0, errors.New("models: update all requires at least one column argument") + } + + colNames := make([]string, len(cols)) + args := make([]interface{}, len(cols)) + + i := 0 + for name, value := range cols { + colNames[i] = name + args[i] = value + i++ + } + + // Append all of the primary key values for each column + for _, obj := range o { + pkeyArgs := queries.ValuesFromMapping(reflect.Indirect(reflect.ValueOf(obj)), executedCommandPrimaryKeyMapping) + args = append(args, pkeyArgs...) + } + + sql := fmt.Sprintf("UPDATE \"executed_commands\" SET %s WHERE %s", + strmangle.SetParamNames("\"", "\"", 1, colNames), + strmangle.WhereClauseRepeated(string(dialect.LQ), string(dialect.RQ), len(colNames)+1, executedCommandPrimaryKeyColumns, len(o))) + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, sql) + fmt.Fprintln(writer, args...) + } + result, err := exec.ExecContext(ctx, sql, args...) + if err != nil { + return 0, errors.Wrap(err, "models: unable to update all in executedCommand slice") + } + + rowsAff, err := result.RowsAffected() + if err != nil { + return 0, errors.Wrap(err, "models: unable to retrieve rows affected all in update all executedCommand") + } + return rowsAff, nil +} + +// UpsertG attempts an insert, and does an update or ignore on conflict. +func (o *ExecutedCommand) UpsertG(ctx context.Context, updateOnConflict bool, conflictColumns []string, updateColumns, insertColumns boil.Columns, opts ...UpsertOptionFunc) error { + return o.Upsert(ctx, boil.GetContextDB(), updateOnConflict, conflictColumns, updateColumns, insertColumns, opts...) +} + +// Upsert attempts an insert using an executor, and does an update or ignore on conflict. +// See boil.Columns documentation for how to properly use updateColumns and insertColumns. +func (o *ExecutedCommand) Upsert(ctx context.Context, exec boil.ContextExecutor, updateOnConflict bool, conflictColumns []string, updateColumns, insertColumns boil.Columns, opts ...UpsertOptionFunc) error { + if o == nil { + return errors.New("models: no executed_commands provided for upsert") + } + if !boil.TimestampsAreSkipped(ctx) { + currTime := time.Now().In(boil.GetLocation()) + + if o.CreatedAt.IsZero() { + o.CreatedAt = currTime + } + o.UpdatedAt = currTime + } + + nzDefaults := queries.NonZeroDefaultSet(executedCommandColumnsWithDefault, o) + + // Build cache key in-line uglily - mysql vs psql problems + buf := strmangle.GetBuffer() + if updateOnConflict { + buf.WriteByte('t') + } else { + buf.WriteByte('f') + } + buf.WriteByte('.') + for _, c := range conflictColumns { + buf.WriteString(c) + } + buf.WriteByte('.') + buf.WriteString(strconv.Itoa(updateColumns.Kind)) + for _, c := range updateColumns.Cols { + buf.WriteString(c) + } + buf.WriteByte('.') + buf.WriteString(strconv.Itoa(insertColumns.Kind)) + for _, c := range insertColumns.Cols { + buf.WriteString(c) + } + buf.WriteByte('.') + for _, c := range nzDefaults { + buf.WriteString(c) + } + key := buf.String() + strmangle.PutBuffer(buf) + + executedCommandUpsertCacheMut.RLock() + cache, cached := executedCommandUpsertCache[key] + executedCommandUpsertCacheMut.RUnlock() + + var err error + + if !cached { + insert, _ := insertColumns.InsertColumnSet( + executedCommandAllColumns, + executedCommandColumnsWithDefault, + executedCommandColumnsWithoutDefault, + nzDefaults, + ) + + update := updateColumns.UpdateColumnSet( + executedCommandAllColumns, + executedCommandPrimaryKeyColumns, + ) + + if updateOnConflict && len(update) == 0 { + return errors.New("models: unable to upsert executed_commands, could not build update column list") + } + + ret := strmangle.SetComplement(executedCommandAllColumns, strmangle.SetIntersect(insert, update)) + + conflict := conflictColumns + if len(conflict) == 0 && updateOnConflict && len(update) != 0 { + if len(executedCommandPrimaryKeyColumns) == 0 { + return errors.New("models: unable to upsert executed_commands, could not build conflict column list") + } + + conflict = make([]string, len(executedCommandPrimaryKeyColumns)) + copy(conflict, executedCommandPrimaryKeyColumns) + } + cache.query = buildUpsertQueryPostgres(dialect, "\"executed_commands\"", updateOnConflict, ret, update, conflict, insert, opts...) + + cache.valueMapping, err = queries.BindMapping(executedCommandType, executedCommandMapping, insert) + if err != nil { + return err + } + if len(ret) != 0 { + cache.retMapping, err = queries.BindMapping(executedCommandType, executedCommandMapping, ret) + if err != nil { + return err + } + } + } + + value := reflect.Indirect(reflect.ValueOf(o)) + vals := queries.ValuesFromMapping(value, cache.valueMapping) + var returns []interface{} + if len(cache.retMapping) != 0 { + returns = queries.PtrsFromMapping(value, cache.retMapping) + } + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, cache.query) + fmt.Fprintln(writer, vals) + } + if len(cache.retMapping) != 0 { + err = exec.QueryRowContext(ctx, cache.query, vals...).Scan(returns...) + if errors.Is(err, sql.ErrNoRows) { + err = nil // Postgres doesn't return anything when there's no update + } + } else { + _, err = exec.ExecContext(ctx, cache.query, vals...) + } + if err != nil { + return errors.Wrap(err, "models: unable to upsert executed_commands") + } + + if !cached { + executedCommandUpsertCacheMut.Lock() + executedCommandUpsertCache[key] = cache + executedCommandUpsertCacheMut.Unlock() + } + + return nil +} + +// DeleteG deletes a single ExecutedCommand record. +// DeleteG will match against the primary key column to find the record to delete. +func (o *ExecutedCommand) DeleteG(ctx context.Context) (int64, error) { + return o.Delete(ctx, boil.GetContextDB()) +} + +// Delete deletes a single ExecutedCommand record with an executor. +// Delete will match against the primary key column to find the record to delete. +func (o *ExecutedCommand) Delete(ctx context.Context, exec boil.ContextExecutor) (int64, error) { + if o == nil { + return 0, errors.New("models: no ExecutedCommand provided for delete") + } + + args := queries.ValuesFromMapping(reflect.Indirect(reflect.ValueOf(o)), executedCommandPrimaryKeyMapping) + sql := "DELETE FROM \"executed_commands\" WHERE \"id\"=$1" + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, sql) + fmt.Fprintln(writer, args...) + } + result, err := exec.ExecContext(ctx, sql, args...) + if err != nil { + return 0, errors.Wrap(err, "models: unable to delete from executed_commands") + } + + rowsAff, err := result.RowsAffected() + if err != nil { + return 0, errors.Wrap(err, "models: failed to get rows affected by delete for executed_commands") + } + + return rowsAff, nil +} + +func (q executedCommandQuery) DeleteAllG(ctx context.Context) (int64, error) { + return q.DeleteAll(ctx, boil.GetContextDB()) +} + +// DeleteAll deletes all matching rows. +func (q executedCommandQuery) DeleteAll(ctx context.Context, exec boil.ContextExecutor) (int64, error) { + if q.Query == nil { + return 0, errors.New("models: no executedCommandQuery provided for delete all") + } + + queries.SetDelete(q.Query) + + result, err := q.Query.ExecContext(ctx, exec) + if err != nil { + return 0, errors.Wrap(err, "models: unable to delete all from executed_commands") + } + + rowsAff, err := result.RowsAffected() + if err != nil { + return 0, errors.Wrap(err, "models: failed to get rows affected by deleteall for executed_commands") + } + + return rowsAff, nil +} + +// DeleteAllG deletes all rows in the slice. +func (o ExecutedCommandSlice) DeleteAllG(ctx context.Context) (int64, error) { + return o.DeleteAll(ctx, boil.GetContextDB()) +} + +// DeleteAll deletes all rows in the slice, using an executor. +func (o ExecutedCommandSlice) DeleteAll(ctx context.Context, exec boil.ContextExecutor) (int64, error) { + if len(o) == 0 { + return 0, nil + } + + var args []interface{} + for _, obj := range o { + pkeyArgs := queries.ValuesFromMapping(reflect.Indirect(reflect.ValueOf(obj)), executedCommandPrimaryKeyMapping) + args = append(args, pkeyArgs...) + } + + sql := "DELETE FROM \"executed_commands\" WHERE " + + strmangle.WhereClauseRepeated(string(dialect.LQ), string(dialect.RQ), 1, executedCommandPrimaryKeyColumns, len(o)) + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, sql) + fmt.Fprintln(writer, args) + } + result, err := exec.ExecContext(ctx, sql, args...) + if err != nil { + return 0, errors.Wrap(err, "models: unable to delete all from executedCommand slice") + } + + rowsAff, err := result.RowsAffected() + if err != nil { + return 0, errors.Wrap(err, "models: failed to get rows affected by deleteall for executed_commands") + } + + return rowsAff, nil +} + +// ReloadG refetches the object from the database using the primary keys. +func (o *ExecutedCommand) ReloadG(ctx context.Context) error { + if o == nil { + return errors.New("models: no ExecutedCommand provided for reload") + } + + return o.Reload(ctx, boil.GetContextDB()) +} + +// Reload refetches the object from the database +// using the primary keys with an executor. +func (o *ExecutedCommand) Reload(ctx context.Context, exec boil.ContextExecutor) error { + ret, err := FindExecutedCommand(ctx, exec, o.ID) + if err != nil { + return err + } + + *o = *ret + return nil +} + +// ReloadAllG refetches every row with matching primary key column values +// and overwrites the original object slice with the newly updated slice. +func (o *ExecutedCommandSlice) ReloadAllG(ctx context.Context) error { + if o == nil { + return errors.New("models: empty ExecutedCommandSlice provided for reload all") + } + + return o.ReloadAll(ctx, boil.GetContextDB()) +} + +// ReloadAll refetches every row with matching primary key column values +// and overwrites the original object slice with the newly updated slice. +func (o *ExecutedCommandSlice) ReloadAll(ctx context.Context, exec boil.ContextExecutor) error { + if o == nil || len(*o) == 0 { + return nil + } + + slice := ExecutedCommandSlice{} + var args []interface{} + for _, obj := range *o { + pkeyArgs := queries.ValuesFromMapping(reflect.Indirect(reflect.ValueOf(obj)), executedCommandPrimaryKeyMapping) + args = append(args, pkeyArgs...) + } + + sql := "SELECT \"executed_commands\".* FROM \"executed_commands\" WHERE " + + strmangle.WhereClauseRepeated(string(dialect.LQ), string(dialect.RQ), 1, executedCommandPrimaryKeyColumns, len(*o)) + + q := queries.Raw(sql, args...) + + err := q.Bind(ctx, exec, &slice) + if err != nil { + return errors.Wrap(err, "models: unable to reload all in ExecutedCommandSlice") + } + + *o = slice + + return nil +} + +// ExecutedCommandExistsG checks if the ExecutedCommand row exists. +func ExecutedCommandExistsG(ctx context.Context, iD int) (bool, error) { + return ExecutedCommandExists(ctx, boil.GetContextDB(), iD) +} + +// ExecutedCommandExists checks if the ExecutedCommand row exists. +func ExecutedCommandExists(ctx context.Context, exec boil.ContextExecutor, iD int) (bool, error) { + var exists bool + sql := "select exists(select 1 from \"executed_commands\" where \"id\"=$1 limit 1)" + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, sql) + fmt.Fprintln(writer, iD) + } + row := exec.QueryRowContext(ctx, sql, iD) + + err := row.Scan(&exists) + if err != nil { + return false, errors.Wrap(err, "models: unable to check if executed_commands exists") + } + + return exists, nil +} + +// Exists checks if the ExecutedCommand row exists. +func (o *ExecutedCommand) Exists(ctx context.Context, exec boil.ContextExecutor) (bool, error) { + return ExecutedCommandExists(ctx, exec, o.ID) +} diff --git a/common/run/run.go b/common/run/run.go index a531586b9f..f338d64b11 100644 --- a/common/run/run.go +++ b/common/run/run.go @@ -17,7 +17,6 @@ import ( "github.com/botlabs-gg/yagpdb/v2/common" "github.com/botlabs-gg/yagpdb/v2/common/backgroundworkers" "github.com/botlabs-gg/yagpdb/v2/common/config" - "github.com/botlabs-gg/yagpdb/v2/common/configstore" "github.com/botlabs-gg/yagpdb/v2/common/mqueue" "github.com/botlabs-gg/yagpdb/v2/common/pubsub" "github.com/botlabs-gg/yagpdb/v2/common/sentryhook" @@ -114,9 +113,6 @@ func Init() { log.WithError(err).Fatal("Failed intializing") } - log.Info("Initiliazing generic config store") - configstore.InitDatabases() - log.Info("Starting plugins") } diff --git a/common/schemamigration.go b/common/schemamigration.go index a02197b41f..1d3f780708 100644 --- a/common/schemamigration.go +++ b/common/schemamigration.go @@ -8,9 +8,10 @@ import ( ) var ( - createTableRegex = regexp.MustCompile(`(?i)create table if not exists ([0-9a-z_]*) *\(`) - alterTableAddColumnRegex = regexp.MustCompile(`(?i)alter table ([0-9a-z_]*) add column if not exists ([0-9a-z_]*)`) - addIndexRegex = regexp.MustCompile(`(?i)create (unique )?index if not exists ([0-9a-z_]*) on ([0-9a-z_]*)`) + createTableRegex = regexp.MustCompile(`(?i)create table if not exists ([0-9a-z_]*) *\(`) + alterTableAddColumnRegex = regexp.MustCompile(`(?i)alter table ([0-9a-z_]*) add column if not exists "?([0-9a-z_]*)"?`) + addIndexRegex = regexp.MustCompile(`(?i)create (unique )?index if not exists ([0-9a-z_]*) on ([0-9a-z_]*)`) + addNotNullConstraintRegex = regexp.MustCompile(`(?i)alter table ([0-9a-z_]*) alter column "?([0-9a-z_]*)"? set not null`) ) type DBSchema struct { @@ -35,7 +36,7 @@ func initSchema(schema string, name string) { return } - skip, err := checkSkipSchemaInit(schema, name) + skip, err := checkSkipSchemaInit(schema) if err != nil { logger.WithError(err).Error("Failed checking if we should skip schema: ", schema) } @@ -45,20 +46,15 @@ func initSchema(schema string, name string) { } logger.Info("Schema initialization: ", name, ": not skipped") - // if strings.HasPrefix("create table if not exists", trimmedLower) { - - // }else if strings.HasPrefix("alter table", prefix) _, err = PQ.Exec(schema) if err != nil { UnlockRedisKey("schema_init") logger.WithError(err).Fatal("failed initializing postgres db schema for ", name) } - - return } -func checkSkipSchemaInit(schema string, name string) (exists bool, err error) { +func checkSkipSchemaInit(schema string) (exists bool, err error) { trimmed := strings.TrimSpace(schema) if matches := createTableRegex.FindAllStringSubmatch(trimmed, -1); len(matches) > 0 { @@ -73,6 +69,10 @@ func checkSkipSchemaInit(schema string, name string) (exists bool, err error) { return checkColumnExists(matches[0][1], matches[0][2]) } + if matches := addNotNullConstraintRegex.FindAllStringSubmatch(trimmed, -1); len(matches) > 0 { + return checkNotNullConstraintExists(matches[0][1], matches[0][2]) + } + return false, nil } @@ -127,6 +127,19 @@ WHERE table_name=$1 and column_name=$2 return b, err } +func checkNotNullConstraintExists(table, column string) (bool, error) { + const query = ` +SELECT is_nullable +FROM information_schema.columns +WHERE table_name=$1 AND column_name=$2; +` + var isNullable string + if err := PQ.QueryRow(query, table, column).Scan(&isNullable); err != nil { + return false, err + } + return isNullable == "NO", nil +} + func InitSchemas(name string, schemas ...string) { if err := BlockingLockRedisKey("schema_init", time.Minute*10, 60*60); err != nil { panic(err) @@ -138,6 +151,4 @@ func InitSchemas(name string, schemas ...string) { actualName := fmt.Sprintf("%s[%d]", name, i) initSchema(v, actualName) } - - return } diff --git a/common/sqlboiler.toml b/common/sqlboiler.toml index c12f6f86dd..2261aa962b 100644 --- a/common/sqlboiler.toml +++ b/common/sqlboiler.toml @@ -1,11 +1,11 @@ -add-global-variants="true" -no-hooks="true" -no-tests="true" +add-global-variants = true +no-hooks = true +no-tests = true [psql] -dbname="yagpdb" -host="localhost" -user="postgres" -pass="123" -sslmode="disable" -whitelist=["core_configs"] \ No newline at end of file +dbname = "yagpdb" +host = "localhost" +user = "postgres" +pass = "pass" +sslmode = "disable" +whitelist = ["core_configs", "executed_commands"] diff --git a/common/util.go b/common/util.go index b34bbafbad..08c71aa1a5 100644 --- a/common/util.go +++ b/common/util.go @@ -235,12 +235,6 @@ func CutStringShort(s string, l int) string { return mainBuf.String() + latestBuf.String() } -type SmallModel struct { - ID uint `gorm:"primary_key"` - CreatedAt time.Time - UpdatedAt time.Time -} - func MustParseInt(s string) int64 { i, err := strconv.ParseInt(s, 10, 64) if err != nil { @@ -354,28 +348,6 @@ func IsDiscordErr(err error, codes ...int) bool { return false } -type LoggedExecutedCommand struct { - SmallModel - - UserID string - ChannelID string - GuildID string - - // Name of command that was triggered - Command string - // Raw command with arguments passed - RawCommand string - // If command returned any error this will be no-empty - Error string - - TimeStamp time.Time - ResponseTime int64 -} - -func (l LoggedExecutedCommand) TableName() string { - return "executed_commands" -} - // for backward compatibility with previous implementations of HumanizePermissions var legacyPermNames = map[int64]string{ discordgo.PermissionManageGuild: "ManageServer", diff --git a/go.mod b/go.mod index 79f918ba21..fdfbc765d9 100644 --- a/go.mod +++ b/go.mod @@ -26,7 +26,6 @@ require ( github.com/gorilla/websocket v1.5.0 github.com/hashicorp/go-cleanhttp v0.5.2 github.com/jedib0t/go-pretty v4.3.0+incompatible - github.com/jinzhu/gorm v1.9.16 github.com/jmoiron/sqlx v1.3.5 github.com/json-iterator/go v1.1.12 github.com/karlseguin/ccache v2.0.3+incompatible @@ -130,7 +129,6 @@ require ( github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/huandu/xstrings v1.4.0 // indirect github.com/imdario/mergo v0.3.16 // indirect - github.com/jinzhu/inflection v1.0.0 // indirect github.com/karlseguin/expect v1.0.1 // indirect github.com/kr/text v0.2.0 // indirect github.com/leodido/go-urn v1.2.4 // indirect diff --git a/go.sum b/go.sum index 58045a7a13..ce6d4aecf4 100644 --- a/go.sum +++ b/go.sum @@ -93,13 +93,11 @@ github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBa github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I= github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= -github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apmckinlay/gsuneido v0.0.0-20190404155041-0b6cd442a18f/go.mod h1:JU2DOj5Fc6rol0yaT79Csr47QR0vONGwJtBNGRD7jmc= @@ -160,8 +158,6 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd h1:83Wprp6ROGeiHFAP8WJdI2RoxALQYgdllERc3N5N2DM= -github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= github.com/didip/tollbooth v4.0.2+incompatible h1:fVSa33JzSz0hoh2NxpwZtksAzAgd7zjmGO20HCZtF4M= github.com/didip/tollbooth v4.0.2+incompatible/go.mod h1:A9b0665CE6l1KmzpDws2++elm/CsuWBMa5Jv4WY0PEY= github.com/dnaeon/go-vcr v1.1.0/go.mod h1:M7tiix8f0r6mKKJ3Yq/kqU1OYf3MnfmBWVbPx/yU9ko= @@ -184,8 +180,6 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7 github.com/ericlagergren/decimal v0.0.0-20211103172832-aca2edc11f73/go.mod h1:5sruVSMrZCk0U4hwRaGD0D8wIMFVsBWQqG74jQDFg4k= github.com/ericlagergren/decimal v0.0.0-20221120152707-495c53812d05 h1:S92OBrGuLLZsyM5ybUzgc/mPjIYk2AZqufieooe98uw= github.com/ericlagergren/decimal v0.0.0-20221120152707-495c53812d05/go.mod h1:M9R1FoZ3y//hwwnJtO51ypFGwm8ZfpxPT/ZLtO1mcgQ= -github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y= -github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= @@ -237,7 +231,6 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/validator/v10 v10.14.1 h1:9c50NUPC30zyuKprjL3vNZ0m5oG+jU0zvx4AqHGnv4k= github.com/go-playground/validator/v10 v10.14.1/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= -github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= @@ -260,7 +253,6 @@ github.com/golang-jwt/jwt v3.2.1+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzq github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= -github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA= github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= @@ -425,12 +417,6 @@ github.com/jarcoal/httpmock v1.0.4/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT github.com/jedib0t/go-pretty v4.3.0+incompatible h1:CGs8AVhEKg/n9YbUenWmNStRW2PHJzaeDodcfvRAbIo= github.com/jedib0t/go-pretty v4.3.0+incompatible/go.mod h1:XemHduiw8R651AF9Pt4FwCTKeG3oo7hrHJAoznj9nag= github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU= -github.com/jinzhu/gorm v1.9.16 h1:+IyIjPEABKRpsu/F8OvDPy9fyQlgsg2luMV2ZIH5i5o= -github.com/jinzhu/gorm v1.9.16/go.mod h1:G3LB3wezTOWM2ITLzPxEXgSkOXAntiLHS7UdBefADcs= -github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= -github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= -github.com/jinzhu/now v1.0.1 h1:HjfetcXq097iXP0uoPCdnM4Efp5/9MsM0/M+XOTeR3M= -github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g= github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= @@ -480,7 +466,6 @@ github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+ github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.10.6/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= @@ -509,7 +494,6 @@ github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APP github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus= github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mattn/go-sqlite3 v1.14.14 h1:qZgc/Rwetq+MtyE18WhzjokPD93dNqLGNT3QJuLvBGw= github.com/mattn/go-sqlite3 v1.14.14/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= @@ -810,12 +794,10 @@ golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnf golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -871,7 +853,6 @@ golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= diff --git a/lib/discordgo/discord.go b/lib/discordgo/discord.go index 7352512464..78b3b910e6 100644 --- a/lib/discordgo/discord.go +++ b/lib/discordgo/discord.go @@ -34,15 +34,16 @@ var ErrMFA = errors.New("account has 2FA enabled") // tasks if given enough information to do so. Currently you can pass zero // arguments and it will return an empty Discord session. // There are 3 ways to call New: -// With a single auth token - All requests will use the token blindly, -// no verification of the token will be done and requests may fail. -// IF THE TOKEN IS FOR A BOT, IT MUST BE PREFIXED WITH `BOT ` -// eg: `"Bot "` -// With an email and password - Discord will sign in with the provided -// credentials. -// With an email, password and auth token - Discord will verify the auth -// token, if it is invalid it will sign in with the provided -// credentials. This is the Discord recommended way to sign in. +// +// With a single auth token - All requests will use the token blindly, +// no verification of the token will be done and requests may fail. +// IF THE TOKEN IS FOR A BOT, IT MUST BE PREFIXED WITH `BOT ` +// eg: `"Bot "` +// With an email and password - Discord will sign in with the provided +// credentials. +// With an email, password and auth token - Discord will verify the auth +// token, if it is invalid it will sign in with the provided +// credentials. This is the Discord recommended way to sign in. // // NOTE: While email/pass authentication is supported by DiscordGo it is // HIGHLY DISCOURAGED by Discord. Please only use email/pass to obtain a token @@ -153,3 +154,7 @@ func CheckRetry(_ context.Context, resp *http.Response, err error) (bool, error) func StrID(id int64) string { return strconv.FormatInt(id, 10) } + +func ParseID(s string) (int64, error) { + return strconv.ParseInt(s, 10, 64) +} diff --git a/moderation/assets/moderation.html b/moderation/assets/moderation.html index 254ed506d0..e1b10bbb56 100644 --- a/moderation/assets/moderation.html +++ b/moderation/assets/moderation.html @@ -218,7 +218,7 @@

Delete ALL server warnings?

-

@@ -300,7 +300,7 @@

Delete ALL server warnings?

-

@@ -423,7 +423,7 @@

Delete ALL server warnings?

-

diff --git a/moderation/commands.go b/moderation/commands.go index 60f5230b11..b5457d19f9 100644 --- a/moderation/commands.go +++ b/moderation/commands.go @@ -1,6 +1,8 @@ package moderation import ( + "context" + "database/sql" "fmt" "regexp" "strconv" @@ -19,12 +21,13 @@ import ( "github.com/botlabs-gg/yagpdb/v2/lib/discordgo" "github.com/botlabs-gg/yagpdb/v2/lib/dstate" "github.com/botlabs-gg/yagpdb/v2/logs" + "github.com/botlabs-gg/yagpdb/v2/moderation/models" "github.com/botlabs-gg/yagpdb/v2/web" - "github.com/jinzhu/gorm" + "github.com/volatiletech/sqlboiler/v4/queries/qm" ) func MBaseCmd(cmdData *dcmd.Data, targetID int64) (config *Config, targetUser *discordgo.User, err error) { - config, err = GetConfig(cmdData.GuildData.GS.ID) + config, err = GetCachedConfigOrDefault(cmdData.GuildData.GS.ID) if err != nil { return nil, nil, errors.WithMessage(err, "GetConfig") } @@ -337,7 +340,7 @@ var ModerationCommands = []*commands.YAGCommand{ return nil, err } - if config.MuteRole == "" { + if config.MuteRole == 0 { return fmt.Sprintf("No mute role selected. Select one at <%s/moderation>", web.ManageServerURL(parsed.GuildData)), nil } @@ -396,7 +399,7 @@ var ModerationCommands = []*commands.YAGCommand{ return nil, err } - if config.MuteRole == "" { + if config.MuteRole == 0 { return "No mute role set up, assign a mute role in the control panel", nil } @@ -562,7 +565,7 @@ var ModerationCommands = []*commands.YAGCommand{ logLink := CreateLogs(parsed.GuildData.GS.ID, parsed.GuildData.CS.ID, parsed.Author) - channelID := config.IntReportChannel() + channelID := config.ReportChannel if channelID == 0 { return "No report channel set up", nil } @@ -776,11 +779,11 @@ var ModerationCommands = []*commands.YAGCommand{ return nil, err } - if config.ActionChannel == "" { + if config.ActionChannel == 0 { return "No mod log channel set up", nil } - msg, err := common.BotSession.ChannelMessage(config.IntActionChannel(), parsed.Args[0].Int64()) + msg, err := common.BotSession.ChannelMessage(config.ActionChannel, parsed.Args[0].Int64()) if err != nil { return nil, err } @@ -795,7 +798,7 @@ var ModerationCommands = []*commands.YAGCommand{ embed := msg.Embeds[0] updateEmbedReason(parsed.Author, parsed.Args[1].Str(), embed) - _, err = common.BotSession.ChannelMessageEditEmbed(config.IntActionChannel(), msg.ID, embed) + _, err = common.BotSession.ChannelMessageEditEmbed(config.ActionChannel, msg.ID, embed) if err != nil { return nil, err } @@ -874,19 +877,23 @@ var ModerationCommands = []*commands.YAGCommand{ } if parsed.Switches["id"].Value != nil { - var warn []*WarningModel - err = common.GORM.Where("guild_id = ? AND id = ?", parsed.GuildData.GS.ID, parsed.Switches["id"].Int()).First(&warn).Error - if err != nil && err != gorm.ErrRecordNotFound { + id := parsed.Switches["id"].Int() + warning, err := models.ModerationWarnings( + models.ModerationWarningWhere.ID.EQ(id), + // don't display warnings from other servers, even if ID is correct + models.ModerationWarningWhere.GuildID.EQ(parsed.GuildData.GS.ID), + ).OneG(parsed.Context()) + if err != nil { + if err == sql.ErrNoRows { + return fmt.Sprintf("Could not find warning with ID `%d`", id), nil + } return nil, err } - if len(warn) == 0 { - return fmt.Sprintf("Warning with given id : `%d` does not exist.", parsed.Switches["id"].Int()), nil - } return &discordgo.MessageEmbed{ - Title: fmt.Sprintf("Warning#%d - User : %s", warn[0].ID, warn[0].UserID), - Description: fmt.Sprintf(" - **Reason** : %s", warn[0].CreatedAt.Unix(), warn[0].Message), - Footer: &discordgo.MessageEmbedFooter{Text: fmt.Sprintf("By: %s (%13s)", warn[0].AuthorUsernameDiscrim, warn[0].AuthorID)}, + Title: fmt.Sprintf("Warning#%d - User : %s", warning.ID, warning.UserID), + Description: fmt.Sprintf(" - **Reason** : %s", warning.CreatedAt.Unix(), warning.Message), + Footer: &discordgo.MessageEmbedFooter{Text: fmt.Sprintf("By: %s (%13s)", warning.AuthorUsernameDiscrim, warning.AuthorID)}, }, nil } page := parsed.Args[1].Int() @@ -924,11 +931,18 @@ var ModerationCommands = []*commands.YAGCommand{ return nil, err } - rows := common.GORM.Model(WarningModel{}).Where("guild_id = ? AND id = ?", parsed.GuildData.GS.ID, parsed.Args[0].Int()).Update( - "message", fmt.Sprintf("%s (updated by %s (%d))", parsed.Args[1].Str(), parsed.Author.String(), parsed.Author.ID)).RowsAffected - - if rows < 1 { - return "Failed updating, most likely couldn't find the warning", nil + warningID := parsed.Args[0].Int() + updatedMessage := fmt.Sprintf("%s (updated by %s (%d))", parsed.Args[1].Str(), parsed.Author.String(), parsed.Author.ID) + numUpdated, err := models.ModerationWarnings( + models.ModerationWarningWhere.ID.EQ(warningID), + // don't edit warnings from other servers, even if ID is correct + models.ModerationWarningWhere.GuildID.EQ(parsed.GuildData.GS.ID), + ).UpdateAllG(parsed.Context(), models.M{"message": updatedMessage}) + if err != nil { + return "Failed editing warning", err + } + if numUpdated == 0 { + return fmt.Sprintf("Could not find warning with ID `%d`", warningID), nil } return "👌", nil @@ -959,9 +973,17 @@ var ModerationCommands = []*commands.YAGCommand{ return nil, err } - rows := common.GORM.Where("guild_id = ? AND id = ?", parsed.GuildData.GS.ID, parsed.Args[0].Int()).Delete(WarningModel{}).RowsAffected - if rows < 1 { - return "Failed deleting, most likely couldn't find the warning", nil + warningID := parsed.Args[0].Int() + numDeleted, err := models.ModerationWarnings( + models.ModerationWarningWhere.ID.EQ(warningID), + // don't delete warnings from other servers, even if ID is correct + models.ModerationWarningWhere.GuildID.EQ(parsed.GuildData.GS.ID), + ).DeleteAllG(parsed.Context()) + if err != nil { + return "Failed deleting warning", err + } + if numDeleted == 0 { + return fmt.Sprintf("Could not find warning with ID `%d`", warningID), nil } return "👌", nil @@ -993,15 +1015,21 @@ var ModerationCommands = []*commands.YAGCommand{ return nil, err } - rows := common.GORM.Where("guild_id = ? AND user_id = ?", parsed.GuildData.GS.ID, target.ID).Delete(WarningModel{}).RowsAffected + numDeleted, err := models.ModerationWarnings( + models.ModerationWarningWhere.GuildID.EQ(parsed.GuildData.GS.ID), + models.ModerationWarningWhere.UserID.EQ(discordgo.StrID(target.ID)), + ).DeleteAllG(parsed.Context()) + if err != nil { + return "Failed deleting warnings", err + } reason := parsed.Args[1].Str() err = CreateModlogEmbed(config, parsed.Author, MAClearWarnings, target, reason, "") if err != nil { - return "failed sending modlog", err + return "Failed sending modlog", err } - return fmt.Sprintf("Deleted %d warnings.", rows), nil + return fmt.Sprintf("Deleted %d warnings.", numDeleted), nil }, }, { @@ -1061,8 +1089,11 @@ var ModerationCommands = []*commands.YAGCommand{ out += fmt.Sprintf("#%02d: %4d - %d\n", v.Rank, v.WarnCount, v.UserID) } } - var count int - common.GORM.Table("moderation_warnings").Where("guild_id = ?", parsed.GuildData.GS.ID).Count(&count) + + count, err := models.ModerationWarnings(models.ModerationWarningWhere.GuildID.EQ(parsed.GuildData.GS.ID)).CountG(context.Background()) + if err != nil { + return nil, err + } out += "```\n" + fmt.Sprintf("Total Server Warnings: `%d`", count) @@ -1139,7 +1170,7 @@ var ModerationCommands = []*commands.YAGCommand{ action := MAGiveRole action.Prefix = "Gave the role " + role.Name + " to " - if config.GiveRoleCmdModlog && config.IntActionChannel() != 0 { + if config.GiveRoleCmdModlog && config.ActionChannel != 0 { if dur > 0 { action.Footer = "Duration: " + common.HumanizeDuration(common.DurationPrecisionMinutes, dur) } @@ -1200,7 +1231,7 @@ var ModerationCommands = []*commands.YAGCommand{ action := MARemoveRole action.Prefix = "Removed the role " + role.Name + " from " - if config.GiveRoleCmdModlog && config.IntActionChannel() != 0 { + if config.GiveRoleCmdModlog && config.ActionChannel != 0 { CreateModlogEmbed(config, parsed.Author, action, target, "", "") } @@ -1343,7 +1374,6 @@ func FindRole(gs *dstate.GuildSet, roleS string) *discordgo.Role { } func PaginateWarnings(parsed *dcmd.Data) func(p *paginatedmessages.PaginatedMessage, page int) (*discordgo.MessageEmbed, error) { - return func(p *paginatedmessages.PaginatedMessage, page int) (*discordgo.MessageEmbed, error) { var err error @@ -1351,14 +1381,24 @@ func PaginateWarnings(parsed *dcmd.Data) func(p *paginatedmessages.PaginatedMess userID := parsed.Args[0].Int64() limit := 6 - var result []*WarningModel - var count int - err = common.GORM.Table("moderation_warnings").Where("user_id = ? AND guild_id = ?", userID, parsed.GuildData.GS.ID).Count(&count).Error - if err != nil && err != gorm.ErrRecordNotFound { + userIDStr := discordgo.StrID(userID) + count, err := models.ModerationWarnings( + models.ModerationWarningWhere.UserID.EQ(userIDStr), + models.ModerationWarningWhere.GuildID.EQ(parsed.GuildData.GS.ID), + ).CountG(parsed.Context()) + if err != nil { return nil, err } - err = common.GORM.Where("user_id = ? AND guild_id = ?", userID, parsed.GuildData.GS.ID).Order("id desc").Offset(skip).Limit(limit).Find(&result).Error - if err != nil && err != gorm.ErrRecordNotFound { + + result, err := models.ModerationWarnings( + models.ModerationWarningWhere.UserID.EQ(userIDStr), + models.ModerationWarningWhere.GuildID.EQ(parsed.GuildData.GS.ID), + + qm.OrderBy("id desc"), + qm.Offset(skip), + qm.Limit(limit), + ).AllG(parsed.Context()) + if err != nil { return nil, err } @@ -1383,8 +1423,8 @@ func PaginateWarnings(parsed *dcmd.Data) func(p *paginatedmessages.PaginatedMess } entry_formatted += "\n" purgedWarnLogs := logs.ConfEnableMessageLogPurge.GetBool() && entry.CreatedAt.Before(time.Now().AddDate(0, 0, -30)) - if entry.LogsLink != "" && !purgedWarnLogs { - entry_formatted += fmt.Sprintf("> logs: [`link`](%s)\n", entry.LogsLink) + if entry.LogsLink.String != "" && !purgedWarnLogs { + entry_formatted += fmt.Sprintf("> logs: [`link`](%s)\n", entry.LogsLink.String) } if len([]rune(currentField.Value+entry_formatted)) > 1023 { currentField = &discordgo.MessageEmbedField{ diff --git a/moderation/config.go b/moderation/config.go new file mode 100644 index 0000000000..70cdb25ccf --- /dev/null +++ b/moderation/config.go @@ -0,0 +1,205 @@ +package moderation + +import ( + "time" + + "github.com/botlabs-gg/yagpdb/v2/lib/discordgo" + "github.com/botlabs-gg/yagpdb/v2/moderation/models" + "github.com/volatiletech/null/v8" + "github.com/volatiletech/sqlboiler/v4/types" +) + +// For legacy reasons, many columns in the database schema are marked as +// nullable when they should really be non-nullable, meaning working with +// models.ModerationConfig directly is much more annoying than it should be. We +// therefore wrap it in a Config (which has proper types) and convert to/from +// only when strictly required. + +type Config struct { + GuildID int64 + CreatedAt time.Time + UpdatedAt time.Time + + // Kick + KickEnabled bool + KickCmdRoles types.Int64Array `valid:"role,true"` + DeleteMessagesOnKick bool + KickReasonOptional bool + KickMessage string `valid:"template,5000"` + + // Ban + BanEnabled bool + BanCmdRoles types.Int64Array `valid:"role,true"` + BanReasonOptional bool + BanMessage string `valid:"template,5000"` + DefaultBanDeleteDays null.Int64 `valid:"0,7"` + + // Timeout + TimeoutEnabled bool + TimeoutCmdRoles types.Int64Array `valid:"role,true"` + TimeoutReasonOptional bool + TimeoutRemoveReasonOptional bool + TimeoutMessage string `valid:"template,5000"` + DefaultTimeoutDuration null.Int64 `valid:"1,40320"` + + // Mute/unmute + MuteEnabled bool + MuteCmdRoles types.Int64Array `valid:"role,true"` + MuteRole int64 `valid:"role,true"` + MuteDisallowReactionAdd bool + MuteReasonOptional bool + UnmuteReasonOptional bool + MuteManageRole bool + MuteRemoveRoles types.Int64Array `valid:"role,true"` + MuteIgnoreChannels types.Int64Array `valid:"channel,true"` + MuteMessage string `valid:"template,5000"` + UnmuteMessage string `valid:"template,5000"` + DefaultMuteDuration null.Int64 `valid:"0,"` + + // Warn + WarnCommandsEnabled bool + WarnCmdRoles types.Int64Array `valid:"role,true"` + WarnIncludeChannelLogs bool + WarnSendToModlog bool + WarnMessage string `valid:"template,5000"` + + // Misc + CleanEnabled bool + ReportEnabled bool + ActionChannel int64 `valid:"channel,true"` + ReportChannel int64 `valid:"channel,true"` + ErrorChannel int64 `valid:"channel,true"` + LogUnbans bool + LogBans bool + LogKicks bool + LogTimeouts bool + + GiveRoleCmdEnabled bool + GiveRoleCmdModlog bool + GiveRoleCmdRoles types.Int64Array `valid:"role,true"` +} + +func (c *Config) ToModel() *models.ModerationConfig { + return &models.ModerationConfig{ + GuildID: c.GuildID, + CreatedAt: c.CreatedAt, + UpdatedAt: c.UpdatedAt, + + KickEnabled: null.BoolFrom(c.KickEnabled), + KickCmdRoles: c.KickCmdRoles, + DeleteMessagesOnKick: null.BoolFrom(c.DeleteMessagesOnKick), + KickReasonOptional: null.BoolFrom(c.KickReasonOptional), + KickMessage: null.StringFrom(c.KickMessage), + + BanEnabled: null.BoolFrom(c.BanEnabled), + BanCmdRoles: c.BanCmdRoles, + BanReasonOptional: null.BoolFrom(c.BanReasonOptional), + BanMessage: null.StringFrom(c.BanMessage), + DefaultBanDeleteDays: c.DefaultBanDeleteDays, + + TimeoutEnabled: null.BoolFrom(c.TimeoutEnabled), + TimeoutCmdRoles: c.TimeoutCmdRoles, + TimeoutReasonOptional: null.BoolFrom(c.TimeoutReasonOptional), + TimeoutRemoveReasonOptional: null.BoolFrom(c.TimeoutRemoveReasonOptional), + TimeoutMessage: null.StringFrom(c.TimeoutMessage), + DefaultTimeoutDuration: c.DefaultTimeoutDuration, + + MuteEnabled: null.BoolFrom(c.MuteEnabled), + MuteCmdRoles: c.MuteCmdRoles, + MuteRole: null.StringFrom(discordgo.StrID(c.MuteRole)), + MuteDisallowReactionAdd: null.BoolFrom(c.MuteDisallowReactionAdd), + MuteReasonOptional: null.BoolFrom(c.MuteReasonOptional), + UnmuteReasonOptional: null.BoolFrom(c.UnmuteReasonOptional), + MuteManageRole: null.BoolFrom(c.MuteManageRole), + MuteRemoveRoles: c.MuteRemoveRoles, + MuteIgnoreChannels: c.MuteIgnoreChannels, + MuteMessage: null.StringFrom(c.MuteMessage), + UnmuteMessage: null.StringFrom(c.UnmuteMessage), + DefaultMuteDuration: c.DefaultMuteDuration, + + WarnCommandsEnabled: null.BoolFrom(c.WarnCommandsEnabled), + WarnCmdRoles: c.WarnCmdRoles, + WarnIncludeChannelLogs: null.BoolFrom(c.WarnIncludeChannelLogs), + WarnSendToModlog: null.BoolFrom(c.WarnSendToModlog), + WarnMessage: null.StringFrom(c.WarnMessage), + + CleanEnabled: null.BoolFrom(c.CleanEnabled), + ReportEnabled: null.BoolFrom(c.ReportEnabled), + ActionChannel: null.StringFrom(discordgo.StrID(c.ActionChannel)), + ReportChannel: null.StringFrom(discordgo.StrID(c.ReportChannel)), + ErrorChannel: null.StringFrom(discordgo.StrID(c.ErrorChannel)), + LogUnbans: null.BoolFrom(c.LogUnbans), + LogBans: null.BoolFrom(c.LogBans), + LogKicks: null.BoolFrom(c.LogKicks), + LogTimeouts: null.BoolFrom(c.LogTimeouts), + + GiveRoleCmdEnabled: null.BoolFrom(c.GiveRoleCmdEnabled), + GiveRoleCmdModlog: null.BoolFrom(c.GiveRoleCmdModlog), + GiveRoleCmdRoles: c.GiveRoleCmdRoles, + } +} + +func configFromModel(model *models.ModerationConfig) *Config { + muteRole, _ := discordgo.ParseID(model.MuteRole.String) + actionChannel, _ := discordgo.ParseID(model.ActionChannel.String) + reportChannel, _ := discordgo.ParseID(model.ReportChannel.String) + errorChannel, _ := discordgo.ParseID(model.ErrorChannel.String) + + return &Config{ + GuildID: model.GuildID, + CreatedAt: model.CreatedAt, + UpdatedAt: model.UpdatedAt, + + KickEnabled: model.KickEnabled.Bool, + KickCmdRoles: model.KickCmdRoles, + DeleteMessagesOnKick: model.DeleteMessagesOnKick.Bool, + KickReasonOptional: model.KickReasonOptional.Bool, + KickMessage: model.KickMessage.String, + + BanEnabled: model.BanEnabled.Bool, + BanCmdRoles: model.BanCmdRoles, + BanReasonOptional: model.BanReasonOptional.Bool, + BanMessage: model.BanMessage.String, + DefaultBanDeleteDays: model.DefaultBanDeleteDays, + + TimeoutEnabled: model.TimeoutEnabled.Bool, + TimeoutCmdRoles: model.TimeoutCmdRoles, + TimeoutReasonOptional: model.TimeoutReasonOptional.Bool, + TimeoutRemoveReasonOptional: model.TimeoutRemoveReasonOptional.Bool, + TimeoutMessage: model.TimeoutMessage.String, + DefaultTimeoutDuration: model.DefaultTimeoutDuration, + + MuteEnabled: model.MuteEnabled.Bool, + MuteCmdRoles: model.MuteCmdRoles, + MuteRole: muteRole, + MuteDisallowReactionAdd: model.MuteDisallowReactionAdd.Bool, + MuteReasonOptional: model.MuteReasonOptional.Bool, + UnmuteReasonOptional: model.UnmuteReasonOptional.Bool, + MuteManageRole: model.MuteManageRole.Bool, + MuteRemoveRoles: model.MuteRemoveRoles, + MuteIgnoreChannels: model.MuteIgnoreChannels, + MuteMessage: model.MuteMessage.String, + UnmuteMessage: model.UnmuteMessage.String, + DefaultMuteDuration: model.DefaultMuteDuration, + + WarnCommandsEnabled: model.WarnCommandsEnabled.Bool, + WarnCmdRoles: model.WarnCmdRoles, + WarnIncludeChannelLogs: model.WarnIncludeChannelLogs.Bool, + WarnSendToModlog: model.WarnSendToModlog.Bool, + WarnMessage: model.WarnMessage.String, + + CleanEnabled: model.CleanEnabled.Bool, + ReportEnabled: model.ReportEnabled.Bool, + ActionChannel: actionChannel, + ReportChannel: reportChannel, + ErrorChannel: errorChannel, + LogUnbans: model.LogUnbans.Bool, + LogBans: model.LogBans.Bool, + LogKicks: model.LogKicks.Bool, + LogTimeouts: model.LogTimeouts.Bool, + + GiveRoleCmdEnabled: model.GiveRoleCmdEnabled.Bool, + GiveRoleCmdModlog: model.GiveRoleCmdModlog.Bool, + GiveRoleCmdRoles: model.GiveRoleCmdRoles, + } +} diff --git a/moderation/models.go b/moderation/models.go deleted file mode 100644 index 3356f46cb7..0000000000 --- a/moderation/models.go +++ /dev/null @@ -1,154 +0,0 @@ -package moderation - -import ( - "context" - "database/sql" - "strconv" - "time" - - "github.com/botlabs-gg/yagpdb/v2/common" - "github.com/botlabs-gg/yagpdb/v2/common/configstore" - "github.com/botlabs-gg/yagpdb/v2/common/featureflags" - "github.com/botlabs-gg/yagpdb/v2/common/pubsub" - "github.com/lib/pq" -) - -type Config struct { - configstore.GuildConfigModel - - // Kick command - KickEnabled bool - KickCmdRoles pq.Int64Array `gorm:"type:bigint[]" valid:"role,true"` - DeleteMessagesOnKick bool - KickReasonOptional bool - KickMessage string `valid:"template,5000"` - - // Ban - BanEnabled bool - BanCmdRoles pq.Int64Array `gorm:"type:bigint[]" valid:"role,true"` - BanReasonOptional bool - BanMessage string `valid:"template,5000"` - DefaultBanDeleteDays sql.NullInt64 `gorm:"default:1" valid:"0,7"` - - // Timeout - TimeoutEnabled bool - TimeoutCmdRoles pq.Int64Array `gorm:"type:bigint[]" valid:"role,true"` - TimeoutReasonOptional bool - TimeoutRemoveReasonOptional bool - TimeoutMessage string `valid:"template,5000"` - DefaultTimeoutDuration sql.NullInt64 `gorm:"default:10" valid:"1,40320"` - - // Mute/unmute - MuteEnabled bool - MuteCmdRoles pq.Int64Array `gorm:"type:bigint[]" valid:"role,true"` - MuteRole string `valid:"role,true"` - MuteDisallowReactionAdd bool - MuteReasonOptional bool - UnmuteReasonOptional bool - MuteManageRole bool - MuteRemoveRoles pq.Int64Array `gorm:"type:bigint[]" valid:"role,true"` - MuteIgnoreChannels pq.Int64Array `gorm:"type:bigint[]" valid:"channel,true"` - MuteMessage string `valid:"template,5000"` - UnmuteMessage string `valid:"template,5000"` - DefaultMuteDuration sql.NullInt64 `gorm:"default:10" valid:"0,"` - - // Warn - WarnCommandsEnabled bool - WarnCmdRoles pq.Int64Array `gorm:"type:bigint[]" valid:"role,true"` - WarnIncludeChannelLogs bool - WarnSendToModlog bool - WarnMessage string `valid:"template,5000"` - - // Misc - CleanEnabled bool - ReportEnabled bool - ActionChannel string `valid:"channel,true"` - ReportChannel string `valid:"channel,true"` - ErrorChannel string `valid:"channel,true"` - LogUnbans bool - LogBans bool - LogKicks bool `gorm:"default:true"` - LogTimeouts bool - - GiveRoleCmdEnabled bool - GiveRoleCmdModlog bool - GiveRoleCmdRoles pq.Int64Array `gorm:"type:bigint[]" valid:"role,true"` -} - -func (c *Config) IntMuteRole() (r int64) { - r, _ = strconv.ParseInt(c.MuteRole, 10, 64) - return -} - -func (c *Config) IntActionChannel() (r int64) { - r, _ = strconv.ParseInt(c.ActionChannel, 10, 64) - return -} - -func (c *Config) IntReportChannel() (r int64) { - r, _ = strconv.ParseInt(c.ReportChannel, 10, 64) - return -} - -func (c *Config) IntErrorChannel() (r int64) { - r, _ = strconv.ParseInt(c.ErrorChannel, 10, 64) - return -} - -func (c *Config) GetName() string { - return "moderation" -} - -func (c *Config) TableName() string { - return "moderation_configs" -} - -func (c *Config) Save(guildID int64) error { - c.GuildID = guildID - err := configstore.SQL.SetGuildConfig(context.Background(), c) - if err != nil { - return err - } - - if err = featureflags.UpdatePluginFeatureFlags(guildID, &Plugin{}); err != nil { - return err - } - - pubsub.Publish("mod_refresh_mute_override", guildID, nil) - return err -} - -type WarningModel struct { - common.SmallModel - GuildID int64 `gorm:"index"` - UserID string - AuthorID string - - // Username and discrim for author incase he/she leaves - AuthorUsernameDiscrim string - - Message string - LogsLink string -} - -func (w *WarningModel) TableName() string { - return "moderation_warnings" -} - -type MuteModel struct { - common.SmallModel - - ExpiresAt time.Time - - GuildID int64 `gorm:"index"` - UserID int64 - - AuthorID int64 - Reason string - - RemovedRoles pq.Int64Array `gorm:"type:bigint[]"` -} - -func (m *MuteModel) TableName() string { - return "muted_users" -} diff --git a/moderation/models/boil_queries.go b/moderation/models/boil_queries.go new file mode 100644 index 0000000000..20c2563fdb --- /dev/null +++ b/moderation/models/boil_queries.go @@ -0,0 +1,38 @@ +// Code generated by SQLBoiler 4.16.2 (https://github.com/volatiletech/sqlboiler). DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package models + +import ( + "regexp" + + "github.com/volatiletech/sqlboiler/v4/drivers" + "github.com/volatiletech/sqlboiler/v4/queries" + "github.com/volatiletech/sqlboiler/v4/queries/qm" +) + +var dialect = drivers.Dialect{ + LQ: 0x22, + RQ: 0x22, + + UseIndexPlaceholders: true, + UseLastInsertID: false, + UseSchema: false, + UseDefaultKeyword: true, + UseAutoColumns: false, + UseTopClause: false, + UseOutputClause: false, + UseCaseWhenExistsClause: false, +} + +// This is a dummy variable to prevent unused regexp import error +var _ = ®exp.Regexp{} + +// NewQuery initializes a new Query using the passed in QueryMods +func NewQuery(mods ...qm.QueryMod) *queries.Query { + q := &queries.Query{} + queries.SetDialect(q, &dialect) + qm.Apply(q, mods...) + + return q +} diff --git a/moderation/models/boil_table_names.go b/moderation/models/boil_table_names.go new file mode 100644 index 0000000000..cf4e74f5e2 --- /dev/null +++ b/moderation/models/boil_table_names.go @@ -0,0 +1,14 @@ +// Code generated by SQLBoiler 4.16.2 (https://github.com/volatiletech/sqlboiler). DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package models + +var TableNames = struct { + ModerationConfigs string + ModerationWarnings string + MutedUsers string +}{ + ModerationConfigs: "moderation_configs", + ModerationWarnings: "moderation_warnings", + MutedUsers: "muted_users", +} diff --git a/moderation/models/boil_types.go b/moderation/models/boil_types.go new file mode 100644 index 0000000000..02a6fdfdc5 --- /dev/null +++ b/moderation/models/boil_types.go @@ -0,0 +1,52 @@ +// Code generated by SQLBoiler 4.16.2 (https://github.com/volatiletech/sqlboiler). DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package models + +import ( + "strconv" + + "github.com/friendsofgo/errors" + "github.com/volatiletech/sqlboiler/v4/boil" + "github.com/volatiletech/strmangle" +) + +// M type is for providing columns and column values to UpdateAll. +type M map[string]interface{} + +// ErrSyncFail occurs during insert when the record could not be retrieved in +// order to populate default value information. This usually happens when LastInsertId +// fails or there was a primary key configuration that was not resolvable. +var ErrSyncFail = errors.New("models: failed to synchronize data after insert") + +type insertCache struct { + query string + retQuery string + valueMapping []uint64 + retMapping []uint64 +} + +type updateCache struct { + query string + valueMapping []uint64 +} + +func makeCacheKey(cols boil.Columns, nzDefaults []string) string { + buf := strmangle.GetBuffer() + + buf.WriteString(strconv.Itoa(cols.Kind)) + for _, w := range cols.Cols { + buf.WriteString(w) + } + + if len(nzDefaults) != 0 { + buf.WriteByte('.') + } + for _, nz := range nzDefaults { + buf.WriteString(nz) + } + + str := buf.String() + strmangle.PutBuffer(buf) + return str +} diff --git a/moderation/models/boil_view_names.go b/moderation/models/boil_view_names.go new file mode 100644 index 0000000000..01504d82bf --- /dev/null +++ b/moderation/models/boil_view_names.go @@ -0,0 +1,7 @@ +// Code generated by SQLBoiler 4.16.2 (https://github.com/volatiletech/sqlboiler). DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package models + +var ViewNames = struct { +}{} diff --git a/moderation/models/moderation_configs.go b/moderation/models/moderation_configs.go new file mode 100644 index 0000000000..84032cff8f --- /dev/null +++ b/moderation/models/moderation_configs.go @@ -0,0 +1,1293 @@ +// Code generated by SQLBoiler 4.16.2 (https://github.com/volatiletech/sqlboiler). DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package models + +import ( + "context" + "database/sql" + "fmt" + "reflect" + "strconv" + "strings" + "sync" + "time" + + "github.com/friendsofgo/errors" + "github.com/volatiletech/null/v8" + "github.com/volatiletech/sqlboiler/v4/boil" + "github.com/volatiletech/sqlboiler/v4/queries" + "github.com/volatiletech/sqlboiler/v4/queries/qm" + "github.com/volatiletech/sqlboiler/v4/queries/qmhelper" + "github.com/volatiletech/sqlboiler/v4/types" + "github.com/volatiletech/strmangle" +) + +// ModerationConfig is an object representing the database table. +type ModerationConfig struct { + GuildID int64 `boil:"guild_id" json:"guild_id" toml:"guild_id" yaml:"guild_id"` + CreatedAt time.Time `boil:"created_at" json:"created_at" toml:"created_at" yaml:"created_at"` + UpdatedAt time.Time `boil:"updated_at" json:"updated_at" toml:"updated_at" yaml:"updated_at"` + KickEnabled null.Bool `boil:"kick_enabled" json:"kick_enabled,omitempty" toml:"kick_enabled" yaml:"kick_enabled,omitempty"` + KickCmdRoles types.Int64Array `boil:"kick_cmd_roles" json:"kick_cmd_roles,omitempty" toml:"kick_cmd_roles" yaml:"kick_cmd_roles,omitempty"` + DeleteMessagesOnKick null.Bool `boil:"delete_messages_on_kick" json:"delete_messages_on_kick,omitempty" toml:"delete_messages_on_kick" yaml:"delete_messages_on_kick,omitempty"` + KickReasonOptional null.Bool `boil:"kick_reason_optional" json:"kick_reason_optional,omitempty" toml:"kick_reason_optional" yaml:"kick_reason_optional,omitempty"` + KickMessage null.String `boil:"kick_message" json:"kick_message,omitempty" toml:"kick_message" yaml:"kick_message,omitempty"` + BanEnabled null.Bool `boil:"ban_enabled" json:"ban_enabled,omitempty" toml:"ban_enabled" yaml:"ban_enabled,omitempty"` + BanCmdRoles types.Int64Array `boil:"ban_cmd_roles" json:"ban_cmd_roles,omitempty" toml:"ban_cmd_roles" yaml:"ban_cmd_roles,omitempty"` + BanReasonOptional null.Bool `boil:"ban_reason_optional" json:"ban_reason_optional,omitempty" toml:"ban_reason_optional" yaml:"ban_reason_optional,omitempty"` + BanMessage null.String `boil:"ban_message" json:"ban_message,omitempty" toml:"ban_message" yaml:"ban_message,omitempty"` + DefaultBanDeleteDays null.Int64 `boil:"default_ban_delete_days" json:"default_ban_delete_days,omitempty" toml:"default_ban_delete_days" yaml:"default_ban_delete_days,omitempty"` + TimeoutEnabled null.Bool `boil:"timeout_enabled" json:"timeout_enabled,omitempty" toml:"timeout_enabled" yaml:"timeout_enabled,omitempty"` + TimeoutCmdRoles types.Int64Array `boil:"timeout_cmd_roles" json:"timeout_cmd_roles,omitempty" toml:"timeout_cmd_roles" yaml:"timeout_cmd_roles,omitempty"` + TimeoutReasonOptional null.Bool `boil:"timeout_reason_optional" json:"timeout_reason_optional,omitempty" toml:"timeout_reason_optional" yaml:"timeout_reason_optional,omitempty"` + TimeoutRemoveReasonOptional null.Bool `boil:"timeout_remove_reason_optional" json:"timeout_remove_reason_optional,omitempty" toml:"timeout_remove_reason_optional" yaml:"timeout_remove_reason_optional,omitempty"` + TimeoutMessage null.String `boil:"timeout_message" json:"timeout_message,omitempty" toml:"timeout_message" yaml:"timeout_message,omitempty"` + DefaultTimeoutDuration null.Int64 `boil:"default_timeout_duration" json:"default_timeout_duration,omitempty" toml:"default_timeout_duration" yaml:"default_timeout_duration,omitempty"` + MuteEnabled null.Bool `boil:"mute_enabled" json:"mute_enabled,omitempty" toml:"mute_enabled" yaml:"mute_enabled,omitempty"` + MuteCmdRoles types.Int64Array `boil:"mute_cmd_roles" json:"mute_cmd_roles,omitempty" toml:"mute_cmd_roles" yaml:"mute_cmd_roles,omitempty"` + MuteRole null.String `boil:"mute_role" json:"mute_role,omitempty" toml:"mute_role" yaml:"mute_role,omitempty"` + MuteDisallowReactionAdd null.Bool `boil:"mute_disallow_reaction_add" json:"mute_disallow_reaction_add,omitempty" toml:"mute_disallow_reaction_add" yaml:"mute_disallow_reaction_add,omitempty"` + MuteReasonOptional null.Bool `boil:"mute_reason_optional" json:"mute_reason_optional,omitempty" toml:"mute_reason_optional" yaml:"mute_reason_optional,omitempty"` + UnmuteReasonOptional null.Bool `boil:"unmute_reason_optional" json:"unmute_reason_optional,omitempty" toml:"unmute_reason_optional" yaml:"unmute_reason_optional,omitempty"` + MuteManageRole null.Bool `boil:"mute_manage_role" json:"mute_manage_role,omitempty" toml:"mute_manage_role" yaml:"mute_manage_role,omitempty"` + MuteRemoveRoles types.Int64Array `boil:"mute_remove_roles" json:"mute_remove_roles,omitempty" toml:"mute_remove_roles" yaml:"mute_remove_roles,omitempty"` + MuteIgnoreChannels types.Int64Array `boil:"mute_ignore_channels" json:"mute_ignore_channels,omitempty" toml:"mute_ignore_channels" yaml:"mute_ignore_channels,omitempty"` + MuteMessage null.String `boil:"mute_message" json:"mute_message,omitempty" toml:"mute_message" yaml:"mute_message,omitempty"` + UnmuteMessage null.String `boil:"unmute_message" json:"unmute_message,omitempty" toml:"unmute_message" yaml:"unmute_message,omitempty"` + DefaultMuteDuration null.Int64 `boil:"default_mute_duration" json:"default_mute_duration,omitempty" toml:"default_mute_duration" yaml:"default_mute_duration,omitempty"` + WarnCommandsEnabled null.Bool `boil:"warn_commands_enabled" json:"warn_commands_enabled,omitempty" toml:"warn_commands_enabled" yaml:"warn_commands_enabled,omitempty"` + WarnCmdRoles types.Int64Array `boil:"warn_cmd_roles" json:"warn_cmd_roles,omitempty" toml:"warn_cmd_roles" yaml:"warn_cmd_roles,omitempty"` + WarnIncludeChannelLogs null.Bool `boil:"warn_include_channel_logs" json:"warn_include_channel_logs,omitempty" toml:"warn_include_channel_logs" yaml:"warn_include_channel_logs,omitempty"` + WarnSendToModlog null.Bool `boil:"warn_send_to_modlog" json:"warn_send_to_modlog,omitempty" toml:"warn_send_to_modlog" yaml:"warn_send_to_modlog,omitempty"` + WarnMessage null.String `boil:"warn_message" json:"warn_message,omitempty" toml:"warn_message" yaml:"warn_message,omitempty"` + CleanEnabled null.Bool `boil:"clean_enabled" json:"clean_enabled,omitempty" toml:"clean_enabled" yaml:"clean_enabled,omitempty"` + ReportEnabled null.Bool `boil:"report_enabled" json:"report_enabled,omitempty" toml:"report_enabled" yaml:"report_enabled,omitempty"` + ActionChannel null.String `boil:"action_channel" json:"action_channel,omitempty" toml:"action_channel" yaml:"action_channel,omitempty"` + ReportChannel null.String `boil:"report_channel" json:"report_channel,omitempty" toml:"report_channel" yaml:"report_channel,omitempty"` + ErrorChannel null.String `boil:"error_channel" json:"error_channel,omitempty" toml:"error_channel" yaml:"error_channel,omitempty"` + LogUnbans null.Bool `boil:"log_unbans" json:"log_unbans,omitempty" toml:"log_unbans" yaml:"log_unbans,omitempty"` + LogBans null.Bool `boil:"log_bans" json:"log_bans,omitempty" toml:"log_bans" yaml:"log_bans,omitempty"` + LogKicks null.Bool `boil:"log_kicks" json:"log_kicks,omitempty" toml:"log_kicks" yaml:"log_kicks,omitempty"` + LogTimeouts null.Bool `boil:"log_timeouts" json:"log_timeouts,omitempty" toml:"log_timeouts" yaml:"log_timeouts,omitempty"` + GiveRoleCmdEnabled null.Bool `boil:"give_role_cmd_enabled" json:"give_role_cmd_enabled,omitempty" toml:"give_role_cmd_enabled" yaml:"give_role_cmd_enabled,omitempty"` + GiveRoleCmdModlog null.Bool `boil:"give_role_cmd_modlog" json:"give_role_cmd_modlog,omitempty" toml:"give_role_cmd_modlog" yaml:"give_role_cmd_modlog,omitempty"` + GiveRoleCmdRoles types.Int64Array `boil:"give_role_cmd_roles" json:"give_role_cmd_roles,omitempty" toml:"give_role_cmd_roles" yaml:"give_role_cmd_roles,omitempty"` + + R *moderationConfigR `boil:"-" json:"-" toml:"-" yaml:"-"` + L moderationConfigL `boil:"-" json:"-" toml:"-" yaml:"-"` +} + +var ModerationConfigColumns = struct { + GuildID string + CreatedAt string + UpdatedAt string + KickEnabled string + KickCmdRoles string + DeleteMessagesOnKick string + KickReasonOptional string + KickMessage string + BanEnabled string + BanCmdRoles string + BanReasonOptional string + BanMessage string + DefaultBanDeleteDays string + TimeoutEnabled string + TimeoutCmdRoles string + TimeoutReasonOptional string + TimeoutRemoveReasonOptional string + TimeoutMessage string + DefaultTimeoutDuration string + MuteEnabled string + MuteCmdRoles string + MuteRole string + MuteDisallowReactionAdd string + MuteReasonOptional string + UnmuteReasonOptional string + MuteManageRole string + MuteRemoveRoles string + MuteIgnoreChannels string + MuteMessage string + UnmuteMessage string + DefaultMuteDuration string + WarnCommandsEnabled string + WarnCmdRoles string + WarnIncludeChannelLogs string + WarnSendToModlog string + WarnMessage string + CleanEnabled string + ReportEnabled string + ActionChannel string + ReportChannel string + ErrorChannel string + LogUnbans string + LogBans string + LogKicks string + LogTimeouts string + GiveRoleCmdEnabled string + GiveRoleCmdModlog string + GiveRoleCmdRoles string +}{ + GuildID: "guild_id", + CreatedAt: "created_at", + UpdatedAt: "updated_at", + KickEnabled: "kick_enabled", + KickCmdRoles: "kick_cmd_roles", + DeleteMessagesOnKick: "delete_messages_on_kick", + KickReasonOptional: "kick_reason_optional", + KickMessage: "kick_message", + BanEnabled: "ban_enabled", + BanCmdRoles: "ban_cmd_roles", + BanReasonOptional: "ban_reason_optional", + BanMessage: "ban_message", + DefaultBanDeleteDays: "default_ban_delete_days", + TimeoutEnabled: "timeout_enabled", + TimeoutCmdRoles: "timeout_cmd_roles", + TimeoutReasonOptional: "timeout_reason_optional", + TimeoutRemoveReasonOptional: "timeout_remove_reason_optional", + TimeoutMessage: "timeout_message", + DefaultTimeoutDuration: "default_timeout_duration", + MuteEnabled: "mute_enabled", + MuteCmdRoles: "mute_cmd_roles", + MuteRole: "mute_role", + MuteDisallowReactionAdd: "mute_disallow_reaction_add", + MuteReasonOptional: "mute_reason_optional", + UnmuteReasonOptional: "unmute_reason_optional", + MuteManageRole: "mute_manage_role", + MuteRemoveRoles: "mute_remove_roles", + MuteIgnoreChannels: "mute_ignore_channels", + MuteMessage: "mute_message", + UnmuteMessage: "unmute_message", + DefaultMuteDuration: "default_mute_duration", + WarnCommandsEnabled: "warn_commands_enabled", + WarnCmdRoles: "warn_cmd_roles", + WarnIncludeChannelLogs: "warn_include_channel_logs", + WarnSendToModlog: "warn_send_to_modlog", + WarnMessage: "warn_message", + CleanEnabled: "clean_enabled", + ReportEnabled: "report_enabled", + ActionChannel: "action_channel", + ReportChannel: "report_channel", + ErrorChannel: "error_channel", + LogUnbans: "log_unbans", + LogBans: "log_bans", + LogKicks: "log_kicks", + LogTimeouts: "log_timeouts", + GiveRoleCmdEnabled: "give_role_cmd_enabled", + GiveRoleCmdModlog: "give_role_cmd_modlog", + GiveRoleCmdRoles: "give_role_cmd_roles", +} + +var ModerationConfigTableColumns = struct { + GuildID string + CreatedAt string + UpdatedAt string + KickEnabled string + KickCmdRoles string + DeleteMessagesOnKick string + KickReasonOptional string + KickMessage string + BanEnabled string + BanCmdRoles string + BanReasonOptional string + BanMessage string + DefaultBanDeleteDays string + TimeoutEnabled string + TimeoutCmdRoles string + TimeoutReasonOptional string + TimeoutRemoveReasonOptional string + TimeoutMessage string + DefaultTimeoutDuration string + MuteEnabled string + MuteCmdRoles string + MuteRole string + MuteDisallowReactionAdd string + MuteReasonOptional string + UnmuteReasonOptional string + MuteManageRole string + MuteRemoveRoles string + MuteIgnoreChannels string + MuteMessage string + UnmuteMessage string + DefaultMuteDuration string + WarnCommandsEnabled string + WarnCmdRoles string + WarnIncludeChannelLogs string + WarnSendToModlog string + WarnMessage string + CleanEnabled string + ReportEnabled string + ActionChannel string + ReportChannel string + ErrorChannel string + LogUnbans string + LogBans string + LogKicks string + LogTimeouts string + GiveRoleCmdEnabled string + GiveRoleCmdModlog string + GiveRoleCmdRoles string +}{ + GuildID: "moderation_configs.guild_id", + CreatedAt: "moderation_configs.created_at", + UpdatedAt: "moderation_configs.updated_at", + KickEnabled: "moderation_configs.kick_enabled", + KickCmdRoles: "moderation_configs.kick_cmd_roles", + DeleteMessagesOnKick: "moderation_configs.delete_messages_on_kick", + KickReasonOptional: "moderation_configs.kick_reason_optional", + KickMessage: "moderation_configs.kick_message", + BanEnabled: "moderation_configs.ban_enabled", + BanCmdRoles: "moderation_configs.ban_cmd_roles", + BanReasonOptional: "moderation_configs.ban_reason_optional", + BanMessage: "moderation_configs.ban_message", + DefaultBanDeleteDays: "moderation_configs.default_ban_delete_days", + TimeoutEnabled: "moderation_configs.timeout_enabled", + TimeoutCmdRoles: "moderation_configs.timeout_cmd_roles", + TimeoutReasonOptional: "moderation_configs.timeout_reason_optional", + TimeoutRemoveReasonOptional: "moderation_configs.timeout_remove_reason_optional", + TimeoutMessage: "moderation_configs.timeout_message", + DefaultTimeoutDuration: "moderation_configs.default_timeout_duration", + MuteEnabled: "moderation_configs.mute_enabled", + MuteCmdRoles: "moderation_configs.mute_cmd_roles", + MuteRole: "moderation_configs.mute_role", + MuteDisallowReactionAdd: "moderation_configs.mute_disallow_reaction_add", + MuteReasonOptional: "moderation_configs.mute_reason_optional", + UnmuteReasonOptional: "moderation_configs.unmute_reason_optional", + MuteManageRole: "moderation_configs.mute_manage_role", + MuteRemoveRoles: "moderation_configs.mute_remove_roles", + MuteIgnoreChannels: "moderation_configs.mute_ignore_channels", + MuteMessage: "moderation_configs.mute_message", + UnmuteMessage: "moderation_configs.unmute_message", + DefaultMuteDuration: "moderation_configs.default_mute_duration", + WarnCommandsEnabled: "moderation_configs.warn_commands_enabled", + WarnCmdRoles: "moderation_configs.warn_cmd_roles", + WarnIncludeChannelLogs: "moderation_configs.warn_include_channel_logs", + WarnSendToModlog: "moderation_configs.warn_send_to_modlog", + WarnMessage: "moderation_configs.warn_message", + CleanEnabled: "moderation_configs.clean_enabled", + ReportEnabled: "moderation_configs.report_enabled", + ActionChannel: "moderation_configs.action_channel", + ReportChannel: "moderation_configs.report_channel", + ErrorChannel: "moderation_configs.error_channel", + LogUnbans: "moderation_configs.log_unbans", + LogBans: "moderation_configs.log_bans", + LogKicks: "moderation_configs.log_kicks", + LogTimeouts: "moderation_configs.log_timeouts", + GiveRoleCmdEnabled: "moderation_configs.give_role_cmd_enabled", + GiveRoleCmdModlog: "moderation_configs.give_role_cmd_modlog", + GiveRoleCmdRoles: "moderation_configs.give_role_cmd_roles", +} + +// Generated where + +type whereHelperint64 struct{ field string } + +func (w whereHelperint64) EQ(x int64) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.EQ, x) } +func (w whereHelperint64) NEQ(x int64) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.NEQ, x) } +func (w whereHelperint64) LT(x int64) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.LT, x) } +func (w whereHelperint64) LTE(x int64) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.LTE, x) } +func (w whereHelperint64) GT(x int64) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.GT, x) } +func (w whereHelperint64) GTE(x int64) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.GTE, x) } +func (w whereHelperint64) IN(slice []int64) qm.QueryMod { + values := make([]interface{}, 0, len(slice)) + for _, value := range slice { + values = append(values, value) + } + return qm.WhereIn(fmt.Sprintf("%s IN ?", w.field), values...) +} +func (w whereHelperint64) NIN(slice []int64) qm.QueryMod { + values := make([]interface{}, 0, len(slice)) + for _, value := range slice { + values = append(values, value) + } + return qm.WhereNotIn(fmt.Sprintf("%s NOT IN ?", w.field), values...) +} + +type whereHelpertime_Time struct{ field string } + +func (w whereHelpertime_Time) EQ(x time.Time) qm.QueryMod { + return qmhelper.Where(w.field, qmhelper.EQ, x) +} +func (w whereHelpertime_Time) NEQ(x time.Time) qm.QueryMod { + return qmhelper.Where(w.field, qmhelper.NEQ, x) +} +func (w whereHelpertime_Time) LT(x time.Time) qm.QueryMod { + return qmhelper.Where(w.field, qmhelper.LT, x) +} +func (w whereHelpertime_Time) LTE(x time.Time) qm.QueryMod { + return qmhelper.Where(w.field, qmhelper.LTE, x) +} +func (w whereHelpertime_Time) GT(x time.Time) qm.QueryMod { + return qmhelper.Where(w.field, qmhelper.GT, x) +} +func (w whereHelpertime_Time) GTE(x time.Time) qm.QueryMod { + return qmhelper.Where(w.field, qmhelper.GTE, x) +} + +type whereHelpernull_Bool struct{ field string } + +func (w whereHelpernull_Bool) EQ(x null.Bool) qm.QueryMod { + return qmhelper.WhereNullEQ(w.field, false, x) +} +func (w whereHelpernull_Bool) NEQ(x null.Bool) qm.QueryMod { + return qmhelper.WhereNullEQ(w.field, true, x) +} +func (w whereHelpernull_Bool) LT(x null.Bool) qm.QueryMod { + return qmhelper.Where(w.field, qmhelper.LT, x) +} +func (w whereHelpernull_Bool) LTE(x null.Bool) qm.QueryMod { + return qmhelper.Where(w.field, qmhelper.LTE, x) +} +func (w whereHelpernull_Bool) GT(x null.Bool) qm.QueryMod { + return qmhelper.Where(w.field, qmhelper.GT, x) +} +func (w whereHelpernull_Bool) GTE(x null.Bool) qm.QueryMod { + return qmhelper.Where(w.field, qmhelper.GTE, x) +} + +func (w whereHelpernull_Bool) IsNull() qm.QueryMod { return qmhelper.WhereIsNull(w.field) } +func (w whereHelpernull_Bool) IsNotNull() qm.QueryMod { return qmhelper.WhereIsNotNull(w.field) } + +type whereHelpertypes_Int64Array struct{ field string } + +func (w whereHelpertypes_Int64Array) EQ(x types.Int64Array) qm.QueryMod { + return qmhelper.WhereNullEQ(w.field, false, x) +} +func (w whereHelpertypes_Int64Array) NEQ(x types.Int64Array) qm.QueryMod { + return qmhelper.WhereNullEQ(w.field, true, x) +} +func (w whereHelpertypes_Int64Array) LT(x types.Int64Array) qm.QueryMod { + return qmhelper.Where(w.field, qmhelper.LT, x) +} +func (w whereHelpertypes_Int64Array) LTE(x types.Int64Array) qm.QueryMod { + return qmhelper.Where(w.field, qmhelper.LTE, x) +} +func (w whereHelpertypes_Int64Array) GT(x types.Int64Array) qm.QueryMod { + return qmhelper.Where(w.field, qmhelper.GT, x) +} +func (w whereHelpertypes_Int64Array) GTE(x types.Int64Array) qm.QueryMod { + return qmhelper.Where(w.field, qmhelper.GTE, x) +} + +func (w whereHelpertypes_Int64Array) IsNull() qm.QueryMod { return qmhelper.WhereIsNull(w.field) } +func (w whereHelpertypes_Int64Array) IsNotNull() qm.QueryMod { return qmhelper.WhereIsNotNull(w.field) } + +type whereHelpernull_String struct{ field string } + +func (w whereHelpernull_String) EQ(x null.String) qm.QueryMod { + return qmhelper.WhereNullEQ(w.field, false, x) +} +func (w whereHelpernull_String) NEQ(x null.String) qm.QueryMod { + return qmhelper.WhereNullEQ(w.field, true, x) +} +func (w whereHelpernull_String) LT(x null.String) qm.QueryMod { + return qmhelper.Where(w.field, qmhelper.LT, x) +} +func (w whereHelpernull_String) LTE(x null.String) qm.QueryMod { + return qmhelper.Where(w.field, qmhelper.LTE, x) +} +func (w whereHelpernull_String) GT(x null.String) qm.QueryMod { + return qmhelper.Where(w.field, qmhelper.GT, x) +} +func (w whereHelpernull_String) GTE(x null.String) qm.QueryMod { + return qmhelper.Where(w.field, qmhelper.GTE, x) +} +func (w whereHelpernull_String) LIKE(x null.String) qm.QueryMod { + return qm.Where(w.field+" LIKE ?", x) +} +func (w whereHelpernull_String) NLIKE(x null.String) qm.QueryMod { + return qm.Where(w.field+" NOT LIKE ?", x) +} +func (w whereHelpernull_String) ILIKE(x null.String) qm.QueryMod { + return qm.Where(w.field+" ILIKE ?", x) +} +func (w whereHelpernull_String) NILIKE(x null.String) qm.QueryMod { + return qm.Where(w.field+" NOT ILIKE ?", x) +} +func (w whereHelpernull_String) IN(slice []string) qm.QueryMod { + values := make([]interface{}, 0, len(slice)) + for _, value := range slice { + values = append(values, value) + } + return qm.WhereIn(fmt.Sprintf("%s IN ?", w.field), values...) +} +func (w whereHelpernull_String) NIN(slice []string) qm.QueryMod { + values := make([]interface{}, 0, len(slice)) + for _, value := range slice { + values = append(values, value) + } + return qm.WhereNotIn(fmt.Sprintf("%s NOT IN ?", w.field), values...) +} + +func (w whereHelpernull_String) IsNull() qm.QueryMod { return qmhelper.WhereIsNull(w.field) } +func (w whereHelpernull_String) IsNotNull() qm.QueryMod { return qmhelper.WhereIsNotNull(w.field) } + +type whereHelpernull_Int64 struct{ field string } + +func (w whereHelpernull_Int64) EQ(x null.Int64) qm.QueryMod { + return qmhelper.WhereNullEQ(w.field, false, x) +} +func (w whereHelpernull_Int64) NEQ(x null.Int64) qm.QueryMod { + return qmhelper.WhereNullEQ(w.field, true, x) +} +func (w whereHelpernull_Int64) LT(x null.Int64) qm.QueryMod { + return qmhelper.Where(w.field, qmhelper.LT, x) +} +func (w whereHelpernull_Int64) LTE(x null.Int64) qm.QueryMod { + return qmhelper.Where(w.field, qmhelper.LTE, x) +} +func (w whereHelpernull_Int64) GT(x null.Int64) qm.QueryMod { + return qmhelper.Where(w.field, qmhelper.GT, x) +} +func (w whereHelpernull_Int64) GTE(x null.Int64) qm.QueryMod { + return qmhelper.Where(w.field, qmhelper.GTE, x) +} +func (w whereHelpernull_Int64) IN(slice []int64) qm.QueryMod { + values := make([]interface{}, 0, len(slice)) + for _, value := range slice { + values = append(values, value) + } + return qm.WhereIn(fmt.Sprintf("%s IN ?", w.field), values...) +} +func (w whereHelpernull_Int64) NIN(slice []int64) qm.QueryMod { + values := make([]interface{}, 0, len(slice)) + for _, value := range slice { + values = append(values, value) + } + return qm.WhereNotIn(fmt.Sprintf("%s NOT IN ?", w.field), values...) +} + +func (w whereHelpernull_Int64) IsNull() qm.QueryMod { return qmhelper.WhereIsNull(w.field) } +func (w whereHelpernull_Int64) IsNotNull() qm.QueryMod { return qmhelper.WhereIsNotNull(w.field) } + +var ModerationConfigWhere = struct { + GuildID whereHelperint64 + CreatedAt whereHelpertime_Time + UpdatedAt whereHelpertime_Time + KickEnabled whereHelpernull_Bool + KickCmdRoles whereHelpertypes_Int64Array + DeleteMessagesOnKick whereHelpernull_Bool + KickReasonOptional whereHelpernull_Bool + KickMessage whereHelpernull_String + BanEnabled whereHelpernull_Bool + BanCmdRoles whereHelpertypes_Int64Array + BanReasonOptional whereHelpernull_Bool + BanMessage whereHelpernull_String + DefaultBanDeleteDays whereHelpernull_Int64 + TimeoutEnabled whereHelpernull_Bool + TimeoutCmdRoles whereHelpertypes_Int64Array + TimeoutReasonOptional whereHelpernull_Bool + TimeoutRemoveReasonOptional whereHelpernull_Bool + TimeoutMessage whereHelpernull_String + DefaultTimeoutDuration whereHelpernull_Int64 + MuteEnabled whereHelpernull_Bool + MuteCmdRoles whereHelpertypes_Int64Array + MuteRole whereHelpernull_String + MuteDisallowReactionAdd whereHelpernull_Bool + MuteReasonOptional whereHelpernull_Bool + UnmuteReasonOptional whereHelpernull_Bool + MuteManageRole whereHelpernull_Bool + MuteRemoveRoles whereHelpertypes_Int64Array + MuteIgnoreChannels whereHelpertypes_Int64Array + MuteMessage whereHelpernull_String + UnmuteMessage whereHelpernull_String + DefaultMuteDuration whereHelpernull_Int64 + WarnCommandsEnabled whereHelpernull_Bool + WarnCmdRoles whereHelpertypes_Int64Array + WarnIncludeChannelLogs whereHelpernull_Bool + WarnSendToModlog whereHelpernull_Bool + WarnMessage whereHelpernull_String + CleanEnabled whereHelpernull_Bool + ReportEnabled whereHelpernull_Bool + ActionChannel whereHelpernull_String + ReportChannel whereHelpernull_String + ErrorChannel whereHelpernull_String + LogUnbans whereHelpernull_Bool + LogBans whereHelpernull_Bool + LogKicks whereHelpernull_Bool + LogTimeouts whereHelpernull_Bool + GiveRoleCmdEnabled whereHelpernull_Bool + GiveRoleCmdModlog whereHelpernull_Bool + GiveRoleCmdRoles whereHelpertypes_Int64Array +}{ + GuildID: whereHelperint64{field: "\"moderation_configs\".\"guild_id\""}, + CreatedAt: whereHelpertime_Time{field: "\"moderation_configs\".\"created_at\""}, + UpdatedAt: whereHelpertime_Time{field: "\"moderation_configs\".\"updated_at\""}, + KickEnabled: whereHelpernull_Bool{field: "\"moderation_configs\".\"kick_enabled\""}, + KickCmdRoles: whereHelpertypes_Int64Array{field: "\"moderation_configs\".\"kick_cmd_roles\""}, + DeleteMessagesOnKick: whereHelpernull_Bool{field: "\"moderation_configs\".\"delete_messages_on_kick\""}, + KickReasonOptional: whereHelpernull_Bool{field: "\"moderation_configs\".\"kick_reason_optional\""}, + KickMessage: whereHelpernull_String{field: "\"moderation_configs\".\"kick_message\""}, + BanEnabled: whereHelpernull_Bool{field: "\"moderation_configs\".\"ban_enabled\""}, + BanCmdRoles: whereHelpertypes_Int64Array{field: "\"moderation_configs\".\"ban_cmd_roles\""}, + BanReasonOptional: whereHelpernull_Bool{field: "\"moderation_configs\".\"ban_reason_optional\""}, + BanMessage: whereHelpernull_String{field: "\"moderation_configs\".\"ban_message\""}, + DefaultBanDeleteDays: whereHelpernull_Int64{field: "\"moderation_configs\".\"default_ban_delete_days\""}, + TimeoutEnabled: whereHelpernull_Bool{field: "\"moderation_configs\".\"timeout_enabled\""}, + TimeoutCmdRoles: whereHelpertypes_Int64Array{field: "\"moderation_configs\".\"timeout_cmd_roles\""}, + TimeoutReasonOptional: whereHelpernull_Bool{field: "\"moderation_configs\".\"timeout_reason_optional\""}, + TimeoutRemoveReasonOptional: whereHelpernull_Bool{field: "\"moderation_configs\".\"timeout_remove_reason_optional\""}, + TimeoutMessage: whereHelpernull_String{field: "\"moderation_configs\".\"timeout_message\""}, + DefaultTimeoutDuration: whereHelpernull_Int64{field: "\"moderation_configs\".\"default_timeout_duration\""}, + MuteEnabled: whereHelpernull_Bool{field: "\"moderation_configs\".\"mute_enabled\""}, + MuteCmdRoles: whereHelpertypes_Int64Array{field: "\"moderation_configs\".\"mute_cmd_roles\""}, + MuteRole: whereHelpernull_String{field: "\"moderation_configs\".\"mute_role\""}, + MuteDisallowReactionAdd: whereHelpernull_Bool{field: "\"moderation_configs\".\"mute_disallow_reaction_add\""}, + MuteReasonOptional: whereHelpernull_Bool{field: "\"moderation_configs\".\"mute_reason_optional\""}, + UnmuteReasonOptional: whereHelpernull_Bool{field: "\"moderation_configs\".\"unmute_reason_optional\""}, + MuteManageRole: whereHelpernull_Bool{field: "\"moderation_configs\".\"mute_manage_role\""}, + MuteRemoveRoles: whereHelpertypes_Int64Array{field: "\"moderation_configs\".\"mute_remove_roles\""}, + MuteIgnoreChannels: whereHelpertypes_Int64Array{field: "\"moderation_configs\".\"mute_ignore_channels\""}, + MuteMessage: whereHelpernull_String{field: "\"moderation_configs\".\"mute_message\""}, + UnmuteMessage: whereHelpernull_String{field: "\"moderation_configs\".\"unmute_message\""}, + DefaultMuteDuration: whereHelpernull_Int64{field: "\"moderation_configs\".\"default_mute_duration\""}, + WarnCommandsEnabled: whereHelpernull_Bool{field: "\"moderation_configs\".\"warn_commands_enabled\""}, + WarnCmdRoles: whereHelpertypes_Int64Array{field: "\"moderation_configs\".\"warn_cmd_roles\""}, + WarnIncludeChannelLogs: whereHelpernull_Bool{field: "\"moderation_configs\".\"warn_include_channel_logs\""}, + WarnSendToModlog: whereHelpernull_Bool{field: "\"moderation_configs\".\"warn_send_to_modlog\""}, + WarnMessage: whereHelpernull_String{field: "\"moderation_configs\".\"warn_message\""}, + CleanEnabled: whereHelpernull_Bool{field: "\"moderation_configs\".\"clean_enabled\""}, + ReportEnabled: whereHelpernull_Bool{field: "\"moderation_configs\".\"report_enabled\""}, + ActionChannel: whereHelpernull_String{field: "\"moderation_configs\".\"action_channel\""}, + ReportChannel: whereHelpernull_String{field: "\"moderation_configs\".\"report_channel\""}, + ErrorChannel: whereHelpernull_String{field: "\"moderation_configs\".\"error_channel\""}, + LogUnbans: whereHelpernull_Bool{field: "\"moderation_configs\".\"log_unbans\""}, + LogBans: whereHelpernull_Bool{field: "\"moderation_configs\".\"log_bans\""}, + LogKicks: whereHelpernull_Bool{field: "\"moderation_configs\".\"log_kicks\""}, + LogTimeouts: whereHelpernull_Bool{field: "\"moderation_configs\".\"log_timeouts\""}, + GiveRoleCmdEnabled: whereHelpernull_Bool{field: "\"moderation_configs\".\"give_role_cmd_enabled\""}, + GiveRoleCmdModlog: whereHelpernull_Bool{field: "\"moderation_configs\".\"give_role_cmd_modlog\""}, + GiveRoleCmdRoles: whereHelpertypes_Int64Array{field: "\"moderation_configs\".\"give_role_cmd_roles\""}, +} + +// ModerationConfigRels is where relationship names are stored. +var ModerationConfigRels = struct { +}{} + +// moderationConfigR is where relationships are stored. +type moderationConfigR struct { +} + +// NewStruct creates a new relationship struct +func (*moderationConfigR) NewStruct() *moderationConfigR { + return &moderationConfigR{} +} + +// moderationConfigL is where Load methods for each relationship are stored. +type moderationConfigL struct{} + +var ( + moderationConfigAllColumns = []string{"guild_id", "created_at", "updated_at", "kick_enabled", "kick_cmd_roles", "delete_messages_on_kick", "kick_reason_optional", "kick_message", "ban_enabled", "ban_cmd_roles", "ban_reason_optional", "ban_message", "default_ban_delete_days", "timeout_enabled", "timeout_cmd_roles", "timeout_reason_optional", "timeout_remove_reason_optional", "timeout_message", "default_timeout_duration", "mute_enabled", "mute_cmd_roles", "mute_role", "mute_disallow_reaction_add", "mute_reason_optional", "unmute_reason_optional", "mute_manage_role", "mute_remove_roles", "mute_ignore_channels", "mute_message", "unmute_message", "default_mute_duration", "warn_commands_enabled", "warn_cmd_roles", "warn_include_channel_logs", "warn_send_to_modlog", "warn_message", "clean_enabled", "report_enabled", "action_channel", "report_channel", "error_channel", "log_unbans", "log_bans", "log_kicks", "log_timeouts", "give_role_cmd_enabled", "give_role_cmd_modlog", "give_role_cmd_roles"} + moderationConfigColumnsWithoutDefault = []string{"guild_id", "created_at", "updated_at"} + moderationConfigColumnsWithDefault = []string{"kick_enabled", "kick_cmd_roles", "delete_messages_on_kick", "kick_reason_optional", "kick_message", "ban_enabled", "ban_cmd_roles", "ban_reason_optional", "ban_message", "default_ban_delete_days", "timeout_enabled", "timeout_cmd_roles", "timeout_reason_optional", "timeout_remove_reason_optional", "timeout_message", "default_timeout_duration", "mute_enabled", "mute_cmd_roles", "mute_role", "mute_disallow_reaction_add", "mute_reason_optional", "unmute_reason_optional", "mute_manage_role", "mute_remove_roles", "mute_ignore_channels", "mute_message", "unmute_message", "default_mute_duration", "warn_commands_enabled", "warn_cmd_roles", "warn_include_channel_logs", "warn_send_to_modlog", "warn_message", "clean_enabled", "report_enabled", "action_channel", "report_channel", "error_channel", "log_unbans", "log_bans", "log_kicks", "log_timeouts", "give_role_cmd_enabled", "give_role_cmd_modlog", "give_role_cmd_roles"} + moderationConfigPrimaryKeyColumns = []string{"guild_id"} + moderationConfigGeneratedColumns = []string{} +) + +type ( + // ModerationConfigSlice is an alias for a slice of pointers to ModerationConfig. + // This should almost always be used instead of []ModerationConfig. + ModerationConfigSlice []*ModerationConfig + + moderationConfigQuery struct { + *queries.Query + } +) + +// Cache for insert, update and upsert +var ( + moderationConfigType = reflect.TypeOf(&ModerationConfig{}) + moderationConfigMapping = queries.MakeStructMapping(moderationConfigType) + moderationConfigPrimaryKeyMapping, _ = queries.BindMapping(moderationConfigType, moderationConfigMapping, moderationConfigPrimaryKeyColumns) + moderationConfigInsertCacheMut sync.RWMutex + moderationConfigInsertCache = make(map[string]insertCache) + moderationConfigUpdateCacheMut sync.RWMutex + moderationConfigUpdateCache = make(map[string]updateCache) + moderationConfigUpsertCacheMut sync.RWMutex + moderationConfigUpsertCache = make(map[string]insertCache) +) + +var ( + // Force time package dependency for automated UpdatedAt/CreatedAt. + _ = time.Second + // Force qmhelper dependency for where clause generation (which doesn't + // always happen) + _ = qmhelper.Where +) + +// OneG returns a single moderationConfig record from the query using the global executor. +func (q moderationConfigQuery) OneG(ctx context.Context) (*ModerationConfig, error) { + return q.One(ctx, boil.GetContextDB()) +} + +// One returns a single moderationConfig record from the query. +func (q moderationConfigQuery) One(ctx context.Context, exec boil.ContextExecutor) (*ModerationConfig, error) { + o := &ModerationConfig{} + + queries.SetLimit(q.Query, 1) + + err := q.Bind(ctx, exec, o) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + return nil, sql.ErrNoRows + } + return nil, errors.Wrap(err, "models: failed to execute a one query for moderation_configs") + } + + return o, nil +} + +// AllG returns all ModerationConfig records from the query using the global executor. +func (q moderationConfigQuery) AllG(ctx context.Context) (ModerationConfigSlice, error) { + return q.All(ctx, boil.GetContextDB()) +} + +// All returns all ModerationConfig records from the query. +func (q moderationConfigQuery) All(ctx context.Context, exec boil.ContextExecutor) (ModerationConfigSlice, error) { + var o []*ModerationConfig + + err := q.Bind(ctx, exec, &o) + if err != nil { + return nil, errors.Wrap(err, "models: failed to assign all query results to ModerationConfig slice") + } + + return o, nil +} + +// CountG returns the count of all ModerationConfig records in the query using the global executor +func (q moderationConfigQuery) CountG(ctx context.Context) (int64, error) { + return q.Count(ctx, boil.GetContextDB()) +} + +// Count returns the count of all ModerationConfig records in the query. +func (q moderationConfigQuery) Count(ctx context.Context, exec boil.ContextExecutor) (int64, error) { + var count int64 + + queries.SetSelect(q.Query, nil) + queries.SetCount(q.Query) + + err := q.Query.QueryRowContext(ctx, exec).Scan(&count) + if err != nil { + return 0, errors.Wrap(err, "models: failed to count moderation_configs rows") + } + + return count, nil +} + +// ExistsG checks if the row exists in the table using the global executor. +func (q moderationConfigQuery) ExistsG(ctx context.Context) (bool, error) { + return q.Exists(ctx, boil.GetContextDB()) +} + +// Exists checks if the row exists in the table. +func (q moderationConfigQuery) Exists(ctx context.Context, exec boil.ContextExecutor) (bool, error) { + var count int64 + + queries.SetSelect(q.Query, nil) + queries.SetCount(q.Query) + queries.SetLimit(q.Query, 1) + + err := q.Query.QueryRowContext(ctx, exec).Scan(&count) + if err != nil { + return false, errors.Wrap(err, "models: failed to check if moderation_configs exists") + } + + return count > 0, nil +} + +// ModerationConfigs retrieves all the records using an executor. +func ModerationConfigs(mods ...qm.QueryMod) moderationConfigQuery { + mods = append(mods, qm.From("\"moderation_configs\"")) + q := NewQuery(mods...) + if len(queries.GetSelect(q)) == 0 { + queries.SetSelect(q, []string{"\"moderation_configs\".*"}) + } + + return moderationConfigQuery{q} +} + +// FindModerationConfigG retrieves a single record by ID. +func FindModerationConfigG(ctx context.Context, guildID int64, selectCols ...string) (*ModerationConfig, error) { + return FindModerationConfig(ctx, boil.GetContextDB(), guildID, selectCols...) +} + +// FindModerationConfig retrieves a single record by ID with an executor. +// If selectCols is empty Find will return all columns. +func FindModerationConfig(ctx context.Context, exec boil.ContextExecutor, guildID int64, selectCols ...string) (*ModerationConfig, error) { + moderationConfigObj := &ModerationConfig{} + + sel := "*" + if len(selectCols) > 0 { + sel = strings.Join(strmangle.IdentQuoteSlice(dialect.LQ, dialect.RQ, selectCols), ",") + } + query := fmt.Sprintf( + "select %s from \"moderation_configs\" where \"guild_id\"=$1", sel, + ) + + q := queries.Raw(query, guildID) + + err := q.Bind(ctx, exec, moderationConfigObj) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + return nil, sql.ErrNoRows + } + return nil, errors.Wrap(err, "models: unable to select from moderation_configs") + } + + return moderationConfigObj, nil +} + +// InsertG a single record. See Insert for whitelist behavior description. +func (o *ModerationConfig) InsertG(ctx context.Context, columns boil.Columns) error { + return o.Insert(ctx, boil.GetContextDB(), columns) +} + +// Insert a single record using an executor. +// See boil.Columns.InsertColumnSet documentation to understand column list inference for inserts. +func (o *ModerationConfig) Insert(ctx context.Context, exec boil.ContextExecutor, columns boil.Columns) error { + if o == nil { + return errors.New("models: no moderation_configs provided for insertion") + } + + var err error + if !boil.TimestampsAreSkipped(ctx) { + currTime := time.Now().In(boil.GetLocation()) + + if o.CreatedAt.IsZero() { + o.CreatedAt = currTime + } + if o.UpdatedAt.IsZero() { + o.UpdatedAt = currTime + } + } + + nzDefaults := queries.NonZeroDefaultSet(moderationConfigColumnsWithDefault, o) + + key := makeCacheKey(columns, nzDefaults) + moderationConfigInsertCacheMut.RLock() + cache, cached := moderationConfigInsertCache[key] + moderationConfigInsertCacheMut.RUnlock() + + if !cached { + wl, returnColumns := columns.InsertColumnSet( + moderationConfigAllColumns, + moderationConfigColumnsWithDefault, + moderationConfigColumnsWithoutDefault, + nzDefaults, + ) + + cache.valueMapping, err = queries.BindMapping(moderationConfigType, moderationConfigMapping, wl) + if err != nil { + return err + } + cache.retMapping, err = queries.BindMapping(moderationConfigType, moderationConfigMapping, returnColumns) + if err != nil { + return err + } + if len(wl) != 0 { + cache.query = fmt.Sprintf("INSERT INTO \"moderation_configs\" (\"%s\") %%sVALUES (%s)%%s", strings.Join(wl, "\",\""), strmangle.Placeholders(dialect.UseIndexPlaceholders, len(wl), 1, 1)) + } else { + cache.query = "INSERT INTO \"moderation_configs\" %sDEFAULT VALUES%s" + } + + var queryOutput, queryReturning string + + if len(cache.retMapping) != 0 { + queryReturning = fmt.Sprintf(" RETURNING \"%s\"", strings.Join(returnColumns, "\",\"")) + } + + cache.query = fmt.Sprintf(cache.query, queryOutput, queryReturning) + } + + value := reflect.Indirect(reflect.ValueOf(o)) + vals := queries.ValuesFromMapping(value, cache.valueMapping) + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, cache.query) + fmt.Fprintln(writer, vals) + } + + if len(cache.retMapping) != 0 { + err = exec.QueryRowContext(ctx, cache.query, vals...).Scan(queries.PtrsFromMapping(value, cache.retMapping)...) + } else { + _, err = exec.ExecContext(ctx, cache.query, vals...) + } + + if err != nil { + return errors.Wrap(err, "models: unable to insert into moderation_configs") + } + + if !cached { + moderationConfigInsertCacheMut.Lock() + moderationConfigInsertCache[key] = cache + moderationConfigInsertCacheMut.Unlock() + } + + return nil +} + +// UpdateG a single ModerationConfig record using the global executor. +// See Update for more documentation. +func (o *ModerationConfig) UpdateG(ctx context.Context, columns boil.Columns) (int64, error) { + return o.Update(ctx, boil.GetContextDB(), columns) +} + +// Update uses an executor to update the ModerationConfig. +// See boil.Columns.UpdateColumnSet documentation to understand column list inference for updates. +// Update does not automatically update the record in case of default values. Use .Reload() to refresh the records. +func (o *ModerationConfig) Update(ctx context.Context, exec boil.ContextExecutor, columns boil.Columns) (int64, error) { + if !boil.TimestampsAreSkipped(ctx) { + currTime := time.Now().In(boil.GetLocation()) + + o.UpdatedAt = currTime + } + + var err error + key := makeCacheKey(columns, nil) + moderationConfigUpdateCacheMut.RLock() + cache, cached := moderationConfigUpdateCache[key] + moderationConfigUpdateCacheMut.RUnlock() + + if !cached { + wl := columns.UpdateColumnSet( + moderationConfigAllColumns, + moderationConfigPrimaryKeyColumns, + ) + + if !columns.IsWhitelist() { + wl = strmangle.SetComplement(wl, []string{"created_at"}) + } + if len(wl) == 0 { + return 0, errors.New("models: unable to update moderation_configs, could not build whitelist") + } + + cache.query = fmt.Sprintf("UPDATE \"moderation_configs\" SET %s WHERE %s", + strmangle.SetParamNames("\"", "\"", 1, wl), + strmangle.WhereClause("\"", "\"", len(wl)+1, moderationConfigPrimaryKeyColumns), + ) + cache.valueMapping, err = queries.BindMapping(moderationConfigType, moderationConfigMapping, append(wl, moderationConfigPrimaryKeyColumns...)) + if err != nil { + return 0, err + } + } + + values := queries.ValuesFromMapping(reflect.Indirect(reflect.ValueOf(o)), cache.valueMapping) + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, cache.query) + fmt.Fprintln(writer, values) + } + var result sql.Result + result, err = exec.ExecContext(ctx, cache.query, values...) + if err != nil { + return 0, errors.Wrap(err, "models: unable to update moderation_configs row") + } + + rowsAff, err := result.RowsAffected() + if err != nil { + return 0, errors.Wrap(err, "models: failed to get rows affected by update for moderation_configs") + } + + if !cached { + moderationConfigUpdateCacheMut.Lock() + moderationConfigUpdateCache[key] = cache + moderationConfigUpdateCacheMut.Unlock() + } + + return rowsAff, nil +} + +// UpdateAllG updates all rows with the specified column values. +func (q moderationConfigQuery) UpdateAllG(ctx context.Context, cols M) (int64, error) { + return q.UpdateAll(ctx, boil.GetContextDB(), cols) +} + +// UpdateAll updates all rows with the specified column values. +func (q moderationConfigQuery) UpdateAll(ctx context.Context, exec boil.ContextExecutor, cols M) (int64, error) { + queries.SetUpdate(q.Query, cols) + + result, err := q.Query.ExecContext(ctx, exec) + if err != nil { + return 0, errors.Wrap(err, "models: unable to update all for moderation_configs") + } + + rowsAff, err := result.RowsAffected() + if err != nil { + return 0, errors.Wrap(err, "models: unable to retrieve rows affected for moderation_configs") + } + + return rowsAff, nil +} + +// UpdateAllG updates all rows with the specified column values. +func (o ModerationConfigSlice) UpdateAllG(ctx context.Context, cols M) (int64, error) { + return o.UpdateAll(ctx, boil.GetContextDB(), cols) +} + +// UpdateAll updates all rows with the specified column values, using an executor. +func (o ModerationConfigSlice) UpdateAll(ctx context.Context, exec boil.ContextExecutor, cols M) (int64, error) { + ln := int64(len(o)) + if ln == 0 { + return 0, nil + } + + if len(cols) == 0 { + return 0, errors.New("models: update all requires at least one column argument") + } + + colNames := make([]string, len(cols)) + args := make([]interface{}, len(cols)) + + i := 0 + for name, value := range cols { + colNames[i] = name + args[i] = value + i++ + } + + // Append all of the primary key values for each column + for _, obj := range o { + pkeyArgs := queries.ValuesFromMapping(reflect.Indirect(reflect.ValueOf(obj)), moderationConfigPrimaryKeyMapping) + args = append(args, pkeyArgs...) + } + + sql := fmt.Sprintf("UPDATE \"moderation_configs\" SET %s WHERE %s", + strmangle.SetParamNames("\"", "\"", 1, colNames), + strmangle.WhereClauseRepeated(string(dialect.LQ), string(dialect.RQ), len(colNames)+1, moderationConfigPrimaryKeyColumns, len(o))) + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, sql) + fmt.Fprintln(writer, args...) + } + result, err := exec.ExecContext(ctx, sql, args...) + if err != nil { + return 0, errors.Wrap(err, "models: unable to update all in moderationConfig slice") + } + + rowsAff, err := result.RowsAffected() + if err != nil { + return 0, errors.Wrap(err, "models: unable to retrieve rows affected all in update all moderationConfig") + } + return rowsAff, nil +} + +// UpsertG attempts an insert, and does an update or ignore on conflict. +func (o *ModerationConfig) UpsertG(ctx context.Context, updateOnConflict bool, conflictColumns []string, updateColumns, insertColumns boil.Columns, opts ...UpsertOptionFunc) error { + return o.Upsert(ctx, boil.GetContextDB(), updateOnConflict, conflictColumns, updateColumns, insertColumns, opts...) +} + +// Upsert attempts an insert using an executor, and does an update or ignore on conflict. +// See boil.Columns documentation for how to properly use updateColumns and insertColumns. +func (o *ModerationConfig) Upsert(ctx context.Context, exec boil.ContextExecutor, updateOnConflict bool, conflictColumns []string, updateColumns, insertColumns boil.Columns, opts ...UpsertOptionFunc) error { + if o == nil { + return errors.New("models: no moderation_configs provided for upsert") + } + if !boil.TimestampsAreSkipped(ctx) { + currTime := time.Now().In(boil.GetLocation()) + + if o.CreatedAt.IsZero() { + o.CreatedAt = currTime + } + o.UpdatedAt = currTime + } + + nzDefaults := queries.NonZeroDefaultSet(moderationConfigColumnsWithDefault, o) + + // Build cache key in-line uglily - mysql vs psql problems + buf := strmangle.GetBuffer() + if updateOnConflict { + buf.WriteByte('t') + } else { + buf.WriteByte('f') + } + buf.WriteByte('.') + for _, c := range conflictColumns { + buf.WriteString(c) + } + buf.WriteByte('.') + buf.WriteString(strconv.Itoa(updateColumns.Kind)) + for _, c := range updateColumns.Cols { + buf.WriteString(c) + } + buf.WriteByte('.') + buf.WriteString(strconv.Itoa(insertColumns.Kind)) + for _, c := range insertColumns.Cols { + buf.WriteString(c) + } + buf.WriteByte('.') + for _, c := range nzDefaults { + buf.WriteString(c) + } + key := buf.String() + strmangle.PutBuffer(buf) + + moderationConfigUpsertCacheMut.RLock() + cache, cached := moderationConfigUpsertCache[key] + moderationConfigUpsertCacheMut.RUnlock() + + var err error + + if !cached { + insert, _ := insertColumns.InsertColumnSet( + moderationConfigAllColumns, + moderationConfigColumnsWithDefault, + moderationConfigColumnsWithoutDefault, + nzDefaults, + ) + + update := updateColumns.UpdateColumnSet( + moderationConfigAllColumns, + moderationConfigPrimaryKeyColumns, + ) + + if updateOnConflict && len(update) == 0 { + return errors.New("models: unable to upsert moderation_configs, could not build update column list") + } + + ret := strmangle.SetComplement(moderationConfigAllColumns, strmangle.SetIntersect(insert, update)) + + conflict := conflictColumns + if len(conflict) == 0 && updateOnConflict && len(update) != 0 { + if len(moderationConfigPrimaryKeyColumns) == 0 { + return errors.New("models: unable to upsert moderation_configs, could not build conflict column list") + } + + conflict = make([]string, len(moderationConfigPrimaryKeyColumns)) + copy(conflict, moderationConfigPrimaryKeyColumns) + } + cache.query = buildUpsertQueryPostgres(dialect, "\"moderation_configs\"", updateOnConflict, ret, update, conflict, insert, opts...) + + cache.valueMapping, err = queries.BindMapping(moderationConfigType, moderationConfigMapping, insert) + if err != nil { + return err + } + if len(ret) != 0 { + cache.retMapping, err = queries.BindMapping(moderationConfigType, moderationConfigMapping, ret) + if err != nil { + return err + } + } + } + + value := reflect.Indirect(reflect.ValueOf(o)) + vals := queries.ValuesFromMapping(value, cache.valueMapping) + var returns []interface{} + if len(cache.retMapping) != 0 { + returns = queries.PtrsFromMapping(value, cache.retMapping) + } + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, cache.query) + fmt.Fprintln(writer, vals) + } + if len(cache.retMapping) != 0 { + err = exec.QueryRowContext(ctx, cache.query, vals...).Scan(returns...) + if errors.Is(err, sql.ErrNoRows) { + err = nil // Postgres doesn't return anything when there's no update + } + } else { + _, err = exec.ExecContext(ctx, cache.query, vals...) + } + if err != nil { + return errors.Wrap(err, "models: unable to upsert moderation_configs") + } + + if !cached { + moderationConfigUpsertCacheMut.Lock() + moderationConfigUpsertCache[key] = cache + moderationConfigUpsertCacheMut.Unlock() + } + + return nil +} + +// DeleteG deletes a single ModerationConfig record. +// DeleteG will match against the primary key column to find the record to delete. +func (o *ModerationConfig) DeleteG(ctx context.Context) (int64, error) { + return o.Delete(ctx, boil.GetContextDB()) +} + +// Delete deletes a single ModerationConfig record with an executor. +// Delete will match against the primary key column to find the record to delete. +func (o *ModerationConfig) Delete(ctx context.Context, exec boil.ContextExecutor) (int64, error) { + if o == nil { + return 0, errors.New("models: no ModerationConfig provided for delete") + } + + args := queries.ValuesFromMapping(reflect.Indirect(reflect.ValueOf(o)), moderationConfigPrimaryKeyMapping) + sql := "DELETE FROM \"moderation_configs\" WHERE \"guild_id\"=$1" + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, sql) + fmt.Fprintln(writer, args...) + } + result, err := exec.ExecContext(ctx, sql, args...) + if err != nil { + return 0, errors.Wrap(err, "models: unable to delete from moderation_configs") + } + + rowsAff, err := result.RowsAffected() + if err != nil { + return 0, errors.Wrap(err, "models: failed to get rows affected by delete for moderation_configs") + } + + return rowsAff, nil +} + +func (q moderationConfigQuery) DeleteAllG(ctx context.Context) (int64, error) { + return q.DeleteAll(ctx, boil.GetContextDB()) +} + +// DeleteAll deletes all matching rows. +func (q moderationConfigQuery) DeleteAll(ctx context.Context, exec boil.ContextExecutor) (int64, error) { + if q.Query == nil { + return 0, errors.New("models: no moderationConfigQuery provided for delete all") + } + + queries.SetDelete(q.Query) + + result, err := q.Query.ExecContext(ctx, exec) + if err != nil { + return 0, errors.Wrap(err, "models: unable to delete all from moderation_configs") + } + + rowsAff, err := result.RowsAffected() + if err != nil { + return 0, errors.Wrap(err, "models: failed to get rows affected by deleteall for moderation_configs") + } + + return rowsAff, nil +} + +// DeleteAllG deletes all rows in the slice. +func (o ModerationConfigSlice) DeleteAllG(ctx context.Context) (int64, error) { + return o.DeleteAll(ctx, boil.GetContextDB()) +} + +// DeleteAll deletes all rows in the slice, using an executor. +func (o ModerationConfigSlice) DeleteAll(ctx context.Context, exec boil.ContextExecutor) (int64, error) { + if len(o) == 0 { + return 0, nil + } + + var args []interface{} + for _, obj := range o { + pkeyArgs := queries.ValuesFromMapping(reflect.Indirect(reflect.ValueOf(obj)), moderationConfigPrimaryKeyMapping) + args = append(args, pkeyArgs...) + } + + sql := "DELETE FROM \"moderation_configs\" WHERE " + + strmangle.WhereClauseRepeated(string(dialect.LQ), string(dialect.RQ), 1, moderationConfigPrimaryKeyColumns, len(o)) + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, sql) + fmt.Fprintln(writer, args) + } + result, err := exec.ExecContext(ctx, sql, args...) + if err != nil { + return 0, errors.Wrap(err, "models: unable to delete all from moderationConfig slice") + } + + rowsAff, err := result.RowsAffected() + if err != nil { + return 0, errors.Wrap(err, "models: failed to get rows affected by deleteall for moderation_configs") + } + + return rowsAff, nil +} + +// ReloadG refetches the object from the database using the primary keys. +func (o *ModerationConfig) ReloadG(ctx context.Context) error { + if o == nil { + return errors.New("models: no ModerationConfig provided for reload") + } + + return o.Reload(ctx, boil.GetContextDB()) +} + +// Reload refetches the object from the database +// using the primary keys with an executor. +func (o *ModerationConfig) Reload(ctx context.Context, exec boil.ContextExecutor) error { + ret, err := FindModerationConfig(ctx, exec, o.GuildID) + if err != nil { + return err + } + + *o = *ret + return nil +} + +// ReloadAllG refetches every row with matching primary key column values +// and overwrites the original object slice with the newly updated slice. +func (o *ModerationConfigSlice) ReloadAllG(ctx context.Context) error { + if o == nil { + return errors.New("models: empty ModerationConfigSlice provided for reload all") + } + + return o.ReloadAll(ctx, boil.GetContextDB()) +} + +// ReloadAll refetches every row with matching primary key column values +// and overwrites the original object slice with the newly updated slice. +func (o *ModerationConfigSlice) ReloadAll(ctx context.Context, exec boil.ContextExecutor) error { + if o == nil || len(*o) == 0 { + return nil + } + + slice := ModerationConfigSlice{} + var args []interface{} + for _, obj := range *o { + pkeyArgs := queries.ValuesFromMapping(reflect.Indirect(reflect.ValueOf(obj)), moderationConfigPrimaryKeyMapping) + args = append(args, pkeyArgs...) + } + + sql := "SELECT \"moderation_configs\".* FROM \"moderation_configs\" WHERE " + + strmangle.WhereClauseRepeated(string(dialect.LQ), string(dialect.RQ), 1, moderationConfigPrimaryKeyColumns, len(*o)) + + q := queries.Raw(sql, args...) + + err := q.Bind(ctx, exec, &slice) + if err != nil { + return errors.Wrap(err, "models: unable to reload all in ModerationConfigSlice") + } + + *o = slice + + return nil +} + +// ModerationConfigExistsG checks if the ModerationConfig row exists. +func ModerationConfigExistsG(ctx context.Context, guildID int64) (bool, error) { + return ModerationConfigExists(ctx, boil.GetContextDB(), guildID) +} + +// ModerationConfigExists checks if the ModerationConfig row exists. +func ModerationConfigExists(ctx context.Context, exec boil.ContextExecutor, guildID int64) (bool, error) { + var exists bool + sql := "select exists(select 1 from \"moderation_configs\" where \"guild_id\"=$1 limit 1)" + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, sql) + fmt.Fprintln(writer, guildID) + } + row := exec.QueryRowContext(ctx, sql, guildID) + + err := row.Scan(&exists) + if err != nil { + return false, errors.Wrap(err, "models: unable to check if moderation_configs exists") + } + + return exists, nil +} + +// Exists checks if the ModerationConfig row exists. +func (o *ModerationConfig) Exists(ctx context.Context, exec boil.ContextExecutor) (bool, error) { + return ModerationConfigExists(ctx, exec, o.GuildID) +} diff --git a/moderation/models/moderation_warnings.go b/moderation/models/moderation_warnings.go new file mode 100644 index 0000000000..bb433062cc --- /dev/null +++ b/moderation/models/moderation_warnings.go @@ -0,0 +1,889 @@ +// Code generated by SQLBoiler 4.16.2 (https://github.com/volatiletech/sqlboiler). DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package models + +import ( + "context" + "database/sql" + "fmt" + "reflect" + "strconv" + "strings" + "sync" + "time" + + "github.com/friendsofgo/errors" + "github.com/volatiletech/null/v8" + "github.com/volatiletech/sqlboiler/v4/boil" + "github.com/volatiletech/sqlboiler/v4/queries" + "github.com/volatiletech/sqlboiler/v4/queries/qm" + "github.com/volatiletech/sqlboiler/v4/queries/qmhelper" + "github.com/volatiletech/strmangle" +) + +// ModerationWarning is an object representing the database table. +type ModerationWarning struct { + ID int `boil:"id" json:"id" toml:"id" yaml:"id"` + CreatedAt time.Time `boil:"created_at" json:"created_at" toml:"created_at" yaml:"created_at"` + UpdatedAt time.Time `boil:"updated_at" json:"updated_at" toml:"updated_at" yaml:"updated_at"` + GuildID int64 `boil:"guild_id" json:"guild_id" toml:"guild_id" yaml:"guild_id"` + UserID string `boil:"user_id" json:"user_id" toml:"user_id" yaml:"user_id"` + AuthorID string `boil:"author_id" json:"author_id" toml:"author_id" yaml:"author_id"` + AuthorUsernameDiscrim string `boil:"author_username_discrim" json:"author_username_discrim" toml:"author_username_discrim" yaml:"author_username_discrim"` + Message string `boil:"message" json:"message" toml:"message" yaml:"message"` + LogsLink null.String `boil:"logs_link" json:"logs_link,omitempty" toml:"logs_link" yaml:"logs_link,omitempty"` + + R *moderationWarningR `boil:"-" json:"-" toml:"-" yaml:"-"` + L moderationWarningL `boil:"-" json:"-" toml:"-" yaml:"-"` +} + +var ModerationWarningColumns = struct { + ID string + CreatedAt string + UpdatedAt string + GuildID string + UserID string + AuthorID string + AuthorUsernameDiscrim string + Message string + LogsLink string +}{ + ID: "id", + CreatedAt: "created_at", + UpdatedAt: "updated_at", + GuildID: "guild_id", + UserID: "user_id", + AuthorID: "author_id", + AuthorUsernameDiscrim: "author_username_discrim", + Message: "message", + LogsLink: "logs_link", +} + +var ModerationWarningTableColumns = struct { + ID string + CreatedAt string + UpdatedAt string + GuildID string + UserID string + AuthorID string + AuthorUsernameDiscrim string + Message string + LogsLink string +}{ + ID: "moderation_warnings.id", + CreatedAt: "moderation_warnings.created_at", + UpdatedAt: "moderation_warnings.updated_at", + GuildID: "moderation_warnings.guild_id", + UserID: "moderation_warnings.user_id", + AuthorID: "moderation_warnings.author_id", + AuthorUsernameDiscrim: "moderation_warnings.author_username_discrim", + Message: "moderation_warnings.message", + LogsLink: "moderation_warnings.logs_link", +} + +// Generated where + +type whereHelperint struct{ field string } + +func (w whereHelperint) EQ(x int) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.EQ, x) } +func (w whereHelperint) NEQ(x int) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.NEQ, x) } +func (w whereHelperint) LT(x int) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.LT, x) } +func (w whereHelperint) LTE(x int) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.LTE, x) } +func (w whereHelperint) GT(x int) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.GT, x) } +func (w whereHelperint) GTE(x int) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.GTE, x) } +func (w whereHelperint) IN(slice []int) qm.QueryMod { + values := make([]interface{}, 0, len(slice)) + for _, value := range slice { + values = append(values, value) + } + return qm.WhereIn(fmt.Sprintf("%s IN ?", w.field), values...) +} +func (w whereHelperint) NIN(slice []int) qm.QueryMod { + values := make([]interface{}, 0, len(slice)) + for _, value := range slice { + values = append(values, value) + } + return qm.WhereNotIn(fmt.Sprintf("%s NOT IN ?", w.field), values...) +} + +type whereHelperstring struct{ field string } + +func (w whereHelperstring) EQ(x string) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.EQ, x) } +func (w whereHelperstring) NEQ(x string) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.NEQ, x) } +func (w whereHelperstring) LT(x string) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.LT, x) } +func (w whereHelperstring) LTE(x string) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.LTE, x) } +func (w whereHelperstring) GT(x string) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.GT, x) } +func (w whereHelperstring) GTE(x string) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.GTE, x) } +func (w whereHelperstring) LIKE(x string) qm.QueryMod { return qm.Where(w.field+" LIKE ?", x) } +func (w whereHelperstring) NLIKE(x string) qm.QueryMod { return qm.Where(w.field+" NOT LIKE ?", x) } +func (w whereHelperstring) ILIKE(x string) qm.QueryMod { return qm.Where(w.field+" ILIKE ?", x) } +func (w whereHelperstring) NILIKE(x string) qm.QueryMod { return qm.Where(w.field+" NOT ILIKE ?", x) } +func (w whereHelperstring) IN(slice []string) qm.QueryMod { + values := make([]interface{}, 0, len(slice)) + for _, value := range slice { + values = append(values, value) + } + return qm.WhereIn(fmt.Sprintf("%s IN ?", w.field), values...) +} +func (w whereHelperstring) NIN(slice []string) qm.QueryMod { + values := make([]interface{}, 0, len(slice)) + for _, value := range slice { + values = append(values, value) + } + return qm.WhereNotIn(fmt.Sprintf("%s NOT IN ?", w.field), values...) +} + +var ModerationWarningWhere = struct { + ID whereHelperint + CreatedAt whereHelpertime_Time + UpdatedAt whereHelpertime_Time + GuildID whereHelperint64 + UserID whereHelperstring + AuthorID whereHelperstring + AuthorUsernameDiscrim whereHelperstring + Message whereHelperstring + LogsLink whereHelpernull_String +}{ + ID: whereHelperint{field: "\"moderation_warnings\".\"id\""}, + CreatedAt: whereHelpertime_Time{field: "\"moderation_warnings\".\"created_at\""}, + UpdatedAt: whereHelpertime_Time{field: "\"moderation_warnings\".\"updated_at\""}, + GuildID: whereHelperint64{field: "\"moderation_warnings\".\"guild_id\""}, + UserID: whereHelperstring{field: "\"moderation_warnings\".\"user_id\""}, + AuthorID: whereHelperstring{field: "\"moderation_warnings\".\"author_id\""}, + AuthorUsernameDiscrim: whereHelperstring{field: "\"moderation_warnings\".\"author_username_discrim\""}, + Message: whereHelperstring{field: "\"moderation_warnings\".\"message\""}, + LogsLink: whereHelpernull_String{field: "\"moderation_warnings\".\"logs_link\""}, +} + +// ModerationWarningRels is where relationship names are stored. +var ModerationWarningRels = struct { +}{} + +// moderationWarningR is where relationships are stored. +type moderationWarningR struct { +} + +// NewStruct creates a new relationship struct +func (*moderationWarningR) NewStruct() *moderationWarningR { + return &moderationWarningR{} +} + +// moderationWarningL is where Load methods for each relationship are stored. +type moderationWarningL struct{} + +var ( + moderationWarningAllColumns = []string{"id", "created_at", "updated_at", "guild_id", "user_id", "author_id", "author_username_discrim", "message", "logs_link"} + moderationWarningColumnsWithoutDefault = []string{"created_at", "updated_at", "guild_id", "user_id", "author_id", "author_username_discrim", "message"} + moderationWarningColumnsWithDefault = []string{"id", "logs_link"} + moderationWarningPrimaryKeyColumns = []string{"id"} + moderationWarningGeneratedColumns = []string{} +) + +type ( + // ModerationWarningSlice is an alias for a slice of pointers to ModerationWarning. + // This should almost always be used instead of []ModerationWarning. + ModerationWarningSlice []*ModerationWarning + + moderationWarningQuery struct { + *queries.Query + } +) + +// Cache for insert, update and upsert +var ( + moderationWarningType = reflect.TypeOf(&ModerationWarning{}) + moderationWarningMapping = queries.MakeStructMapping(moderationWarningType) + moderationWarningPrimaryKeyMapping, _ = queries.BindMapping(moderationWarningType, moderationWarningMapping, moderationWarningPrimaryKeyColumns) + moderationWarningInsertCacheMut sync.RWMutex + moderationWarningInsertCache = make(map[string]insertCache) + moderationWarningUpdateCacheMut sync.RWMutex + moderationWarningUpdateCache = make(map[string]updateCache) + moderationWarningUpsertCacheMut sync.RWMutex + moderationWarningUpsertCache = make(map[string]insertCache) +) + +var ( + // Force time package dependency for automated UpdatedAt/CreatedAt. + _ = time.Second + // Force qmhelper dependency for where clause generation (which doesn't + // always happen) + _ = qmhelper.Where +) + +// OneG returns a single moderationWarning record from the query using the global executor. +func (q moderationWarningQuery) OneG(ctx context.Context) (*ModerationWarning, error) { + return q.One(ctx, boil.GetContextDB()) +} + +// One returns a single moderationWarning record from the query. +func (q moderationWarningQuery) One(ctx context.Context, exec boil.ContextExecutor) (*ModerationWarning, error) { + o := &ModerationWarning{} + + queries.SetLimit(q.Query, 1) + + err := q.Bind(ctx, exec, o) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + return nil, sql.ErrNoRows + } + return nil, errors.Wrap(err, "models: failed to execute a one query for moderation_warnings") + } + + return o, nil +} + +// AllG returns all ModerationWarning records from the query using the global executor. +func (q moderationWarningQuery) AllG(ctx context.Context) (ModerationWarningSlice, error) { + return q.All(ctx, boil.GetContextDB()) +} + +// All returns all ModerationWarning records from the query. +func (q moderationWarningQuery) All(ctx context.Context, exec boil.ContextExecutor) (ModerationWarningSlice, error) { + var o []*ModerationWarning + + err := q.Bind(ctx, exec, &o) + if err != nil { + return nil, errors.Wrap(err, "models: failed to assign all query results to ModerationWarning slice") + } + + return o, nil +} + +// CountG returns the count of all ModerationWarning records in the query using the global executor +func (q moderationWarningQuery) CountG(ctx context.Context) (int64, error) { + return q.Count(ctx, boil.GetContextDB()) +} + +// Count returns the count of all ModerationWarning records in the query. +func (q moderationWarningQuery) Count(ctx context.Context, exec boil.ContextExecutor) (int64, error) { + var count int64 + + queries.SetSelect(q.Query, nil) + queries.SetCount(q.Query) + + err := q.Query.QueryRowContext(ctx, exec).Scan(&count) + if err != nil { + return 0, errors.Wrap(err, "models: failed to count moderation_warnings rows") + } + + return count, nil +} + +// ExistsG checks if the row exists in the table using the global executor. +func (q moderationWarningQuery) ExistsG(ctx context.Context) (bool, error) { + return q.Exists(ctx, boil.GetContextDB()) +} + +// Exists checks if the row exists in the table. +func (q moderationWarningQuery) Exists(ctx context.Context, exec boil.ContextExecutor) (bool, error) { + var count int64 + + queries.SetSelect(q.Query, nil) + queries.SetCount(q.Query) + queries.SetLimit(q.Query, 1) + + err := q.Query.QueryRowContext(ctx, exec).Scan(&count) + if err != nil { + return false, errors.Wrap(err, "models: failed to check if moderation_warnings exists") + } + + return count > 0, nil +} + +// ModerationWarnings retrieves all the records using an executor. +func ModerationWarnings(mods ...qm.QueryMod) moderationWarningQuery { + mods = append(mods, qm.From("\"moderation_warnings\"")) + q := NewQuery(mods...) + if len(queries.GetSelect(q)) == 0 { + queries.SetSelect(q, []string{"\"moderation_warnings\".*"}) + } + + return moderationWarningQuery{q} +} + +// FindModerationWarningG retrieves a single record by ID. +func FindModerationWarningG(ctx context.Context, iD int, selectCols ...string) (*ModerationWarning, error) { + return FindModerationWarning(ctx, boil.GetContextDB(), iD, selectCols...) +} + +// FindModerationWarning retrieves a single record by ID with an executor. +// If selectCols is empty Find will return all columns. +func FindModerationWarning(ctx context.Context, exec boil.ContextExecutor, iD int, selectCols ...string) (*ModerationWarning, error) { + moderationWarningObj := &ModerationWarning{} + + sel := "*" + if len(selectCols) > 0 { + sel = strings.Join(strmangle.IdentQuoteSlice(dialect.LQ, dialect.RQ, selectCols), ",") + } + query := fmt.Sprintf( + "select %s from \"moderation_warnings\" where \"id\"=$1", sel, + ) + + q := queries.Raw(query, iD) + + err := q.Bind(ctx, exec, moderationWarningObj) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + return nil, sql.ErrNoRows + } + return nil, errors.Wrap(err, "models: unable to select from moderation_warnings") + } + + return moderationWarningObj, nil +} + +// InsertG a single record. See Insert for whitelist behavior description. +func (o *ModerationWarning) InsertG(ctx context.Context, columns boil.Columns) error { + return o.Insert(ctx, boil.GetContextDB(), columns) +} + +// Insert a single record using an executor. +// See boil.Columns.InsertColumnSet documentation to understand column list inference for inserts. +func (o *ModerationWarning) Insert(ctx context.Context, exec boil.ContextExecutor, columns boil.Columns) error { + if o == nil { + return errors.New("models: no moderation_warnings provided for insertion") + } + + var err error + if !boil.TimestampsAreSkipped(ctx) { + currTime := time.Now().In(boil.GetLocation()) + + if o.CreatedAt.IsZero() { + o.CreatedAt = currTime + } + if o.UpdatedAt.IsZero() { + o.UpdatedAt = currTime + } + } + + nzDefaults := queries.NonZeroDefaultSet(moderationWarningColumnsWithDefault, o) + + key := makeCacheKey(columns, nzDefaults) + moderationWarningInsertCacheMut.RLock() + cache, cached := moderationWarningInsertCache[key] + moderationWarningInsertCacheMut.RUnlock() + + if !cached { + wl, returnColumns := columns.InsertColumnSet( + moderationWarningAllColumns, + moderationWarningColumnsWithDefault, + moderationWarningColumnsWithoutDefault, + nzDefaults, + ) + + cache.valueMapping, err = queries.BindMapping(moderationWarningType, moderationWarningMapping, wl) + if err != nil { + return err + } + cache.retMapping, err = queries.BindMapping(moderationWarningType, moderationWarningMapping, returnColumns) + if err != nil { + return err + } + if len(wl) != 0 { + cache.query = fmt.Sprintf("INSERT INTO \"moderation_warnings\" (\"%s\") %%sVALUES (%s)%%s", strings.Join(wl, "\",\""), strmangle.Placeholders(dialect.UseIndexPlaceholders, len(wl), 1, 1)) + } else { + cache.query = "INSERT INTO \"moderation_warnings\" %sDEFAULT VALUES%s" + } + + var queryOutput, queryReturning string + + if len(cache.retMapping) != 0 { + queryReturning = fmt.Sprintf(" RETURNING \"%s\"", strings.Join(returnColumns, "\",\"")) + } + + cache.query = fmt.Sprintf(cache.query, queryOutput, queryReturning) + } + + value := reflect.Indirect(reflect.ValueOf(o)) + vals := queries.ValuesFromMapping(value, cache.valueMapping) + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, cache.query) + fmt.Fprintln(writer, vals) + } + + if len(cache.retMapping) != 0 { + err = exec.QueryRowContext(ctx, cache.query, vals...).Scan(queries.PtrsFromMapping(value, cache.retMapping)...) + } else { + _, err = exec.ExecContext(ctx, cache.query, vals...) + } + + if err != nil { + return errors.Wrap(err, "models: unable to insert into moderation_warnings") + } + + if !cached { + moderationWarningInsertCacheMut.Lock() + moderationWarningInsertCache[key] = cache + moderationWarningInsertCacheMut.Unlock() + } + + return nil +} + +// UpdateG a single ModerationWarning record using the global executor. +// See Update for more documentation. +func (o *ModerationWarning) UpdateG(ctx context.Context, columns boil.Columns) (int64, error) { + return o.Update(ctx, boil.GetContextDB(), columns) +} + +// Update uses an executor to update the ModerationWarning. +// See boil.Columns.UpdateColumnSet documentation to understand column list inference for updates. +// Update does not automatically update the record in case of default values. Use .Reload() to refresh the records. +func (o *ModerationWarning) Update(ctx context.Context, exec boil.ContextExecutor, columns boil.Columns) (int64, error) { + if !boil.TimestampsAreSkipped(ctx) { + currTime := time.Now().In(boil.GetLocation()) + + o.UpdatedAt = currTime + } + + var err error + key := makeCacheKey(columns, nil) + moderationWarningUpdateCacheMut.RLock() + cache, cached := moderationWarningUpdateCache[key] + moderationWarningUpdateCacheMut.RUnlock() + + if !cached { + wl := columns.UpdateColumnSet( + moderationWarningAllColumns, + moderationWarningPrimaryKeyColumns, + ) + + if !columns.IsWhitelist() { + wl = strmangle.SetComplement(wl, []string{"created_at"}) + } + if len(wl) == 0 { + return 0, errors.New("models: unable to update moderation_warnings, could not build whitelist") + } + + cache.query = fmt.Sprintf("UPDATE \"moderation_warnings\" SET %s WHERE %s", + strmangle.SetParamNames("\"", "\"", 1, wl), + strmangle.WhereClause("\"", "\"", len(wl)+1, moderationWarningPrimaryKeyColumns), + ) + cache.valueMapping, err = queries.BindMapping(moderationWarningType, moderationWarningMapping, append(wl, moderationWarningPrimaryKeyColumns...)) + if err != nil { + return 0, err + } + } + + values := queries.ValuesFromMapping(reflect.Indirect(reflect.ValueOf(o)), cache.valueMapping) + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, cache.query) + fmt.Fprintln(writer, values) + } + var result sql.Result + result, err = exec.ExecContext(ctx, cache.query, values...) + if err != nil { + return 0, errors.Wrap(err, "models: unable to update moderation_warnings row") + } + + rowsAff, err := result.RowsAffected() + if err != nil { + return 0, errors.Wrap(err, "models: failed to get rows affected by update for moderation_warnings") + } + + if !cached { + moderationWarningUpdateCacheMut.Lock() + moderationWarningUpdateCache[key] = cache + moderationWarningUpdateCacheMut.Unlock() + } + + return rowsAff, nil +} + +// UpdateAllG updates all rows with the specified column values. +func (q moderationWarningQuery) UpdateAllG(ctx context.Context, cols M) (int64, error) { + return q.UpdateAll(ctx, boil.GetContextDB(), cols) +} + +// UpdateAll updates all rows with the specified column values. +func (q moderationWarningQuery) UpdateAll(ctx context.Context, exec boil.ContextExecutor, cols M) (int64, error) { + queries.SetUpdate(q.Query, cols) + + result, err := q.Query.ExecContext(ctx, exec) + if err != nil { + return 0, errors.Wrap(err, "models: unable to update all for moderation_warnings") + } + + rowsAff, err := result.RowsAffected() + if err != nil { + return 0, errors.Wrap(err, "models: unable to retrieve rows affected for moderation_warnings") + } + + return rowsAff, nil +} + +// UpdateAllG updates all rows with the specified column values. +func (o ModerationWarningSlice) UpdateAllG(ctx context.Context, cols M) (int64, error) { + return o.UpdateAll(ctx, boil.GetContextDB(), cols) +} + +// UpdateAll updates all rows with the specified column values, using an executor. +func (o ModerationWarningSlice) UpdateAll(ctx context.Context, exec boil.ContextExecutor, cols M) (int64, error) { + ln := int64(len(o)) + if ln == 0 { + return 0, nil + } + + if len(cols) == 0 { + return 0, errors.New("models: update all requires at least one column argument") + } + + colNames := make([]string, len(cols)) + args := make([]interface{}, len(cols)) + + i := 0 + for name, value := range cols { + colNames[i] = name + args[i] = value + i++ + } + + // Append all of the primary key values for each column + for _, obj := range o { + pkeyArgs := queries.ValuesFromMapping(reflect.Indirect(reflect.ValueOf(obj)), moderationWarningPrimaryKeyMapping) + args = append(args, pkeyArgs...) + } + + sql := fmt.Sprintf("UPDATE \"moderation_warnings\" SET %s WHERE %s", + strmangle.SetParamNames("\"", "\"", 1, colNames), + strmangle.WhereClauseRepeated(string(dialect.LQ), string(dialect.RQ), len(colNames)+1, moderationWarningPrimaryKeyColumns, len(o))) + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, sql) + fmt.Fprintln(writer, args...) + } + result, err := exec.ExecContext(ctx, sql, args...) + if err != nil { + return 0, errors.Wrap(err, "models: unable to update all in moderationWarning slice") + } + + rowsAff, err := result.RowsAffected() + if err != nil { + return 0, errors.Wrap(err, "models: unable to retrieve rows affected all in update all moderationWarning") + } + return rowsAff, nil +} + +// UpsertG attempts an insert, and does an update or ignore on conflict. +func (o *ModerationWarning) UpsertG(ctx context.Context, updateOnConflict bool, conflictColumns []string, updateColumns, insertColumns boil.Columns, opts ...UpsertOptionFunc) error { + return o.Upsert(ctx, boil.GetContextDB(), updateOnConflict, conflictColumns, updateColumns, insertColumns, opts...) +} + +// Upsert attempts an insert using an executor, and does an update or ignore on conflict. +// See boil.Columns documentation for how to properly use updateColumns and insertColumns. +func (o *ModerationWarning) Upsert(ctx context.Context, exec boil.ContextExecutor, updateOnConflict bool, conflictColumns []string, updateColumns, insertColumns boil.Columns, opts ...UpsertOptionFunc) error { + if o == nil { + return errors.New("models: no moderation_warnings provided for upsert") + } + if !boil.TimestampsAreSkipped(ctx) { + currTime := time.Now().In(boil.GetLocation()) + + if o.CreatedAt.IsZero() { + o.CreatedAt = currTime + } + o.UpdatedAt = currTime + } + + nzDefaults := queries.NonZeroDefaultSet(moderationWarningColumnsWithDefault, o) + + // Build cache key in-line uglily - mysql vs psql problems + buf := strmangle.GetBuffer() + if updateOnConflict { + buf.WriteByte('t') + } else { + buf.WriteByte('f') + } + buf.WriteByte('.') + for _, c := range conflictColumns { + buf.WriteString(c) + } + buf.WriteByte('.') + buf.WriteString(strconv.Itoa(updateColumns.Kind)) + for _, c := range updateColumns.Cols { + buf.WriteString(c) + } + buf.WriteByte('.') + buf.WriteString(strconv.Itoa(insertColumns.Kind)) + for _, c := range insertColumns.Cols { + buf.WriteString(c) + } + buf.WriteByte('.') + for _, c := range nzDefaults { + buf.WriteString(c) + } + key := buf.String() + strmangle.PutBuffer(buf) + + moderationWarningUpsertCacheMut.RLock() + cache, cached := moderationWarningUpsertCache[key] + moderationWarningUpsertCacheMut.RUnlock() + + var err error + + if !cached { + insert, _ := insertColumns.InsertColumnSet( + moderationWarningAllColumns, + moderationWarningColumnsWithDefault, + moderationWarningColumnsWithoutDefault, + nzDefaults, + ) + + update := updateColumns.UpdateColumnSet( + moderationWarningAllColumns, + moderationWarningPrimaryKeyColumns, + ) + + if updateOnConflict && len(update) == 0 { + return errors.New("models: unable to upsert moderation_warnings, could not build update column list") + } + + ret := strmangle.SetComplement(moderationWarningAllColumns, strmangle.SetIntersect(insert, update)) + + conflict := conflictColumns + if len(conflict) == 0 && updateOnConflict && len(update) != 0 { + if len(moderationWarningPrimaryKeyColumns) == 0 { + return errors.New("models: unable to upsert moderation_warnings, could not build conflict column list") + } + + conflict = make([]string, len(moderationWarningPrimaryKeyColumns)) + copy(conflict, moderationWarningPrimaryKeyColumns) + } + cache.query = buildUpsertQueryPostgres(dialect, "\"moderation_warnings\"", updateOnConflict, ret, update, conflict, insert, opts...) + + cache.valueMapping, err = queries.BindMapping(moderationWarningType, moderationWarningMapping, insert) + if err != nil { + return err + } + if len(ret) != 0 { + cache.retMapping, err = queries.BindMapping(moderationWarningType, moderationWarningMapping, ret) + if err != nil { + return err + } + } + } + + value := reflect.Indirect(reflect.ValueOf(o)) + vals := queries.ValuesFromMapping(value, cache.valueMapping) + var returns []interface{} + if len(cache.retMapping) != 0 { + returns = queries.PtrsFromMapping(value, cache.retMapping) + } + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, cache.query) + fmt.Fprintln(writer, vals) + } + if len(cache.retMapping) != 0 { + err = exec.QueryRowContext(ctx, cache.query, vals...).Scan(returns...) + if errors.Is(err, sql.ErrNoRows) { + err = nil // Postgres doesn't return anything when there's no update + } + } else { + _, err = exec.ExecContext(ctx, cache.query, vals...) + } + if err != nil { + return errors.Wrap(err, "models: unable to upsert moderation_warnings") + } + + if !cached { + moderationWarningUpsertCacheMut.Lock() + moderationWarningUpsertCache[key] = cache + moderationWarningUpsertCacheMut.Unlock() + } + + return nil +} + +// DeleteG deletes a single ModerationWarning record. +// DeleteG will match against the primary key column to find the record to delete. +func (o *ModerationWarning) DeleteG(ctx context.Context) (int64, error) { + return o.Delete(ctx, boil.GetContextDB()) +} + +// Delete deletes a single ModerationWarning record with an executor. +// Delete will match against the primary key column to find the record to delete. +func (o *ModerationWarning) Delete(ctx context.Context, exec boil.ContextExecutor) (int64, error) { + if o == nil { + return 0, errors.New("models: no ModerationWarning provided for delete") + } + + args := queries.ValuesFromMapping(reflect.Indirect(reflect.ValueOf(o)), moderationWarningPrimaryKeyMapping) + sql := "DELETE FROM \"moderation_warnings\" WHERE \"id\"=$1" + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, sql) + fmt.Fprintln(writer, args...) + } + result, err := exec.ExecContext(ctx, sql, args...) + if err != nil { + return 0, errors.Wrap(err, "models: unable to delete from moderation_warnings") + } + + rowsAff, err := result.RowsAffected() + if err != nil { + return 0, errors.Wrap(err, "models: failed to get rows affected by delete for moderation_warnings") + } + + return rowsAff, nil +} + +func (q moderationWarningQuery) DeleteAllG(ctx context.Context) (int64, error) { + return q.DeleteAll(ctx, boil.GetContextDB()) +} + +// DeleteAll deletes all matching rows. +func (q moderationWarningQuery) DeleteAll(ctx context.Context, exec boil.ContextExecutor) (int64, error) { + if q.Query == nil { + return 0, errors.New("models: no moderationWarningQuery provided for delete all") + } + + queries.SetDelete(q.Query) + + result, err := q.Query.ExecContext(ctx, exec) + if err != nil { + return 0, errors.Wrap(err, "models: unable to delete all from moderation_warnings") + } + + rowsAff, err := result.RowsAffected() + if err != nil { + return 0, errors.Wrap(err, "models: failed to get rows affected by deleteall for moderation_warnings") + } + + return rowsAff, nil +} + +// DeleteAllG deletes all rows in the slice. +func (o ModerationWarningSlice) DeleteAllG(ctx context.Context) (int64, error) { + return o.DeleteAll(ctx, boil.GetContextDB()) +} + +// DeleteAll deletes all rows in the slice, using an executor. +func (o ModerationWarningSlice) DeleteAll(ctx context.Context, exec boil.ContextExecutor) (int64, error) { + if len(o) == 0 { + return 0, nil + } + + var args []interface{} + for _, obj := range o { + pkeyArgs := queries.ValuesFromMapping(reflect.Indirect(reflect.ValueOf(obj)), moderationWarningPrimaryKeyMapping) + args = append(args, pkeyArgs...) + } + + sql := "DELETE FROM \"moderation_warnings\" WHERE " + + strmangle.WhereClauseRepeated(string(dialect.LQ), string(dialect.RQ), 1, moderationWarningPrimaryKeyColumns, len(o)) + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, sql) + fmt.Fprintln(writer, args) + } + result, err := exec.ExecContext(ctx, sql, args...) + if err != nil { + return 0, errors.Wrap(err, "models: unable to delete all from moderationWarning slice") + } + + rowsAff, err := result.RowsAffected() + if err != nil { + return 0, errors.Wrap(err, "models: failed to get rows affected by deleteall for moderation_warnings") + } + + return rowsAff, nil +} + +// ReloadG refetches the object from the database using the primary keys. +func (o *ModerationWarning) ReloadG(ctx context.Context) error { + if o == nil { + return errors.New("models: no ModerationWarning provided for reload") + } + + return o.Reload(ctx, boil.GetContextDB()) +} + +// Reload refetches the object from the database +// using the primary keys with an executor. +func (o *ModerationWarning) Reload(ctx context.Context, exec boil.ContextExecutor) error { + ret, err := FindModerationWarning(ctx, exec, o.ID) + if err != nil { + return err + } + + *o = *ret + return nil +} + +// ReloadAllG refetches every row with matching primary key column values +// and overwrites the original object slice with the newly updated slice. +func (o *ModerationWarningSlice) ReloadAllG(ctx context.Context) error { + if o == nil { + return errors.New("models: empty ModerationWarningSlice provided for reload all") + } + + return o.ReloadAll(ctx, boil.GetContextDB()) +} + +// ReloadAll refetches every row with matching primary key column values +// and overwrites the original object slice with the newly updated slice. +func (o *ModerationWarningSlice) ReloadAll(ctx context.Context, exec boil.ContextExecutor) error { + if o == nil || len(*o) == 0 { + return nil + } + + slice := ModerationWarningSlice{} + var args []interface{} + for _, obj := range *o { + pkeyArgs := queries.ValuesFromMapping(reflect.Indirect(reflect.ValueOf(obj)), moderationWarningPrimaryKeyMapping) + args = append(args, pkeyArgs...) + } + + sql := "SELECT \"moderation_warnings\".* FROM \"moderation_warnings\" WHERE " + + strmangle.WhereClauseRepeated(string(dialect.LQ), string(dialect.RQ), 1, moderationWarningPrimaryKeyColumns, len(*o)) + + q := queries.Raw(sql, args...) + + err := q.Bind(ctx, exec, &slice) + if err != nil { + return errors.Wrap(err, "models: unable to reload all in ModerationWarningSlice") + } + + *o = slice + + return nil +} + +// ModerationWarningExistsG checks if the ModerationWarning row exists. +func ModerationWarningExistsG(ctx context.Context, iD int) (bool, error) { + return ModerationWarningExists(ctx, boil.GetContextDB(), iD) +} + +// ModerationWarningExists checks if the ModerationWarning row exists. +func ModerationWarningExists(ctx context.Context, exec boil.ContextExecutor, iD int) (bool, error) { + var exists bool + sql := "select exists(select 1 from \"moderation_warnings\" where \"id\"=$1 limit 1)" + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, sql) + fmt.Fprintln(writer, iD) + } + row := exec.QueryRowContext(ctx, sql, iD) + + err := row.Scan(&exists) + if err != nil { + return false, errors.Wrap(err, "models: unable to check if moderation_warnings exists") + } + + return exists, nil +} + +// Exists checks if the ModerationWarning row exists. +func (o *ModerationWarning) Exists(ctx context.Context, exec boil.ContextExecutor) (bool, error) { + return ModerationWarningExists(ctx, exec, o.ID) +} diff --git a/moderation/models/muted_users.go b/moderation/models/muted_users.go new file mode 100644 index 0000000000..f69f7bf1b2 --- /dev/null +++ b/moderation/models/muted_users.go @@ -0,0 +1,864 @@ +// Code generated by SQLBoiler 4.16.2 (https://github.com/volatiletech/sqlboiler). DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package models + +import ( + "context" + "database/sql" + "fmt" + "reflect" + "strconv" + "strings" + "sync" + "time" + + "github.com/friendsofgo/errors" + "github.com/volatiletech/null/v8" + "github.com/volatiletech/sqlboiler/v4/boil" + "github.com/volatiletech/sqlboiler/v4/queries" + "github.com/volatiletech/sqlboiler/v4/queries/qm" + "github.com/volatiletech/sqlboiler/v4/queries/qmhelper" + "github.com/volatiletech/sqlboiler/v4/types" + "github.com/volatiletech/strmangle" +) + +// MutedUser is an object representing the database table. +type MutedUser struct { + ID int `boil:"id" json:"id" toml:"id" yaml:"id"` + CreatedAt time.Time `boil:"created_at" json:"created_at" toml:"created_at" yaml:"created_at"` + UpdatedAt time.Time `boil:"updated_at" json:"updated_at" toml:"updated_at" yaml:"updated_at"` + ExpiresAt null.Time `boil:"expires_at" json:"expires_at,omitempty" toml:"expires_at" yaml:"expires_at,omitempty"` + GuildID int64 `boil:"guild_id" json:"guild_id" toml:"guild_id" yaml:"guild_id"` + UserID int64 `boil:"user_id" json:"user_id" toml:"user_id" yaml:"user_id"` + AuthorID int64 `boil:"author_id" json:"author_id" toml:"author_id" yaml:"author_id"` + Reason string `boil:"reason" json:"reason" toml:"reason" yaml:"reason"` + RemovedRoles types.Int64Array `boil:"removed_roles" json:"removed_roles,omitempty" toml:"removed_roles" yaml:"removed_roles,omitempty"` + + R *mutedUserR `boil:"-" json:"-" toml:"-" yaml:"-"` + L mutedUserL `boil:"-" json:"-" toml:"-" yaml:"-"` +} + +var MutedUserColumns = struct { + ID string + CreatedAt string + UpdatedAt string + ExpiresAt string + GuildID string + UserID string + AuthorID string + Reason string + RemovedRoles string +}{ + ID: "id", + CreatedAt: "created_at", + UpdatedAt: "updated_at", + ExpiresAt: "expires_at", + GuildID: "guild_id", + UserID: "user_id", + AuthorID: "author_id", + Reason: "reason", + RemovedRoles: "removed_roles", +} + +var MutedUserTableColumns = struct { + ID string + CreatedAt string + UpdatedAt string + ExpiresAt string + GuildID string + UserID string + AuthorID string + Reason string + RemovedRoles string +}{ + ID: "muted_users.id", + CreatedAt: "muted_users.created_at", + UpdatedAt: "muted_users.updated_at", + ExpiresAt: "muted_users.expires_at", + GuildID: "muted_users.guild_id", + UserID: "muted_users.user_id", + AuthorID: "muted_users.author_id", + Reason: "muted_users.reason", + RemovedRoles: "muted_users.removed_roles", +} + +// Generated where + +type whereHelpernull_Time struct{ field string } + +func (w whereHelpernull_Time) EQ(x null.Time) qm.QueryMod { + return qmhelper.WhereNullEQ(w.field, false, x) +} +func (w whereHelpernull_Time) NEQ(x null.Time) qm.QueryMod { + return qmhelper.WhereNullEQ(w.field, true, x) +} +func (w whereHelpernull_Time) LT(x null.Time) qm.QueryMod { + return qmhelper.Where(w.field, qmhelper.LT, x) +} +func (w whereHelpernull_Time) LTE(x null.Time) qm.QueryMod { + return qmhelper.Where(w.field, qmhelper.LTE, x) +} +func (w whereHelpernull_Time) GT(x null.Time) qm.QueryMod { + return qmhelper.Where(w.field, qmhelper.GT, x) +} +func (w whereHelpernull_Time) GTE(x null.Time) qm.QueryMod { + return qmhelper.Where(w.field, qmhelper.GTE, x) +} + +func (w whereHelpernull_Time) IsNull() qm.QueryMod { return qmhelper.WhereIsNull(w.field) } +func (w whereHelpernull_Time) IsNotNull() qm.QueryMod { return qmhelper.WhereIsNotNull(w.field) } + +var MutedUserWhere = struct { + ID whereHelperint + CreatedAt whereHelpertime_Time + UpdatedAt whereHelpertime_Time + ExpiresAt whereHelpernull_Time + GuildID whereHelperint64 + UserID whereHelperint64 + AuthorID whereHelperint64 + Reason whereHelperstring + RemovedRoles whereHelpertypes_Int64Array +}{ + ID: whereHelperint{field: "\"muted_users\".\"id\""}, + CreatedAt: whereHelpertime_Time{field: "\"muted_users\".\"created_at\""}, + UpdatedAt: whereHelpertime_Time{field: "\"muted_users\".\"updated_at\""}, + ExpiresAt: whereHelpernull_Time{field: "\"muted_users\".\"expires_at\""}, + GuildID: whereHelperint64{field: "\"muted_users\".\"guild_id\""}, + UserID: whereHelperint64{field: "\"muted_users\".\"user_id\""}, + AuthorID: whereHelperint64{field: "\"muted_users\".\"author_id\""}, + Reason: whereHelperstring{field: "\"muted_users\".\"reason\""}, + RemovedRoles: whereHelpertypes_Int64Array{field: "\"muted_users\".\"removed_roles\""}, +} + +// MutedUserRels is where relationship names are stored. +var MutedUserRels = struct { +}{} + +// mutedUserR is where relationships are stored. +type mutedUserR struct { +} + +// NewStruct creates a new relationship struct +func (*mutedUserR) NewStruct() *mutedUserR { + return &mutedUserR{} +} + +// mutedUserL is where Load methods for each relationship are stored. +type mutedUserL struct{} + +var ( + mutedUserAllColumns = []string{"id", "created_at", "updated_at", "expires_at", "guild_id", "user_id", "author_id", "reason", "removed_roles"} + mutedUserColumnsWithoutDefault = []string{"created_at", "updated_at", "guild_id", "user_id", "author_id", "reason"} + mutedUserColumnsWithDefault = []string{"id", "expires_at", "removed_roles"} + mutedUserPrimaryKeyColumns = []string{"id"} + mutedUserGeneratedColumns = []string{} +) + +type ( + // MutedUserSlice is an alias for a slice of pointers to MutedUser. + // This should almost always be used instead of []MutedUser. + MutedUserSlice []*MutedUser + + mutedUserQuery struct { + *queries.Query + } +) + +// Cache for insert, update and upsert +var ( + mutedUserType = reflect.TypeOf(&MutedUser{}) + mutedUserMapping = queries.MakeStructMapping(mutedUserType) + mutedUserPrimaryKeyMapping, _ = queries.BindMapping(mutedUserType, mutedUserMapping, mutedUserPrimaryKeyColumns) + mutedUserInsertCacheMut sync.RWMutex + mutedUserInsertCache = make(map[string]insertCache) + mutedUserUpdateCacheMut sync.RWMutex + mutedUserUpdateCache = make(map[string]updateCache) + mutedUserUpsertCacheMut sync.RWMutex + mutedUserUpsertCache = make(map[string]insertCache) +) + +var ( + // Force time package dependency for automated UpdatedAt/CreatedAt. + _ = time.Second + // Force qmhelper dependency for where clause generation (which doesn't + // always happen) + _ = qmhelper.Where +) + +// OneG returns a single mutedUser record from the query using the global executor. +func (q mutedUserQuery) OneG(ctx context.Context) (*MutedUser, error) { + return q.One(ctx, boil.GetContextDB()) +} + +// One returns a single mutedUser record from the query. +func (q mutedUserQuery) One(ctx context.Context, exec boil.ContextExecutor) (*MutedUser, error) { + o := &MutedUser{} + + queries.SetLimit(q.Query, 1) + + err := q.Bind(ctx, exec, o) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + return nil, sql.ErrNoRows + } + return nil, errors.Wrap(err, "models: failed to execute a one query for muted_users") + } + + return o, nil +} + +// AllG returns all MutedUser records from the query using the global executor. +func (q mutedUserQuery) AllG(ctx context.Context) (MutedUserSlice, error) { + return q.All(ctx, boil.GetContextDB()) +} + +// All returns all MutedUser records from the query. +func (q mutedUserQuery) All(ctx context.Context, exec boil.ContextExecutor) (MutedUserSlice, error) { + var o []*MutedUser + + err := q.Bind(ctx, exec, &o) + if err != nil { + return nil, errors.Wrap(err, "models: failed to assign all query results to MutedUser slice") + } + + return o, nil +} + +// CountG returns the count of all MutedUser records in the query using the global executor +func (q mutedUserQuery) CountG(ctx context.Context) (int64, error) { + return q.Count(ctx, boil.GetContextDB()) +} + +// Count returns the count of all MutedUser records in the query. +func (q mutedUserQuery) Count(ctx context.Context, exec boil.ContextExecutor) (int64, error) { + var count int64 + + queries.SetSelect(q.Query, nil) + queries.SetCount(q.Query) + + err := q.Query.QueryRowContext(ctx, exec).Scan(&count) + if err != nil { + return 0, errors.Wrap(err, "models: failed to count muted_users rows") + } + + return count, nil +} + +// ExistsG checks if the row exists in the table using the global executor. +func (q mutedUserQuery) ExistsG(ctx context.Context) (bool, error) { + return q.Exists(ctx, boil.GetContextDB()) +} + +// Exists checks if the row exists in the table. +func (q mutedUserQuery) Exists(ctx context.Context, exec boil.ContextExecutor) (bool, error) { + var count int64 + + queries.SetSelect(q.Query, nil) + queries.SetCount(q.Query) + queries.SetLimit(q.Query, 1) + + err := q.Query.QueryRowContext(ctx, exec).Scan(&count) + if err != nil { + return false, errors.Wrap(err, "models: failed to check if muted_users exists") + } + + return count > 0, nil +} + +// MutedUsers retrieves all the records using an executor. +func MutedUsers(mods ...qm.QueryMod) mutedUserQuery { + mods = append(mods, qm.From("\"muted_users\"")) + q := NewQuery(mods...) + if len(queries.GetSelect(q)) == 0 { + queries.SetSelect(q, []string{"\"muted_users\".*"}) + } + + return mutedUserQuery{q} +} + +// FindMutedUserG retrieves a single record by ID. +func FindMutedUserG(ctx context.Context, iD int, selectCols ...string) (*MutedUser, error) { + return FindMutedUser(ctx, boil.GetContextDB(), iD, selectCols...) +} + +// FindMutedUser retrieves a single record by ID with an executor. +// If selectCols is empty Find will return all columns. +func FindMutedUser(ctx context.Context, exec boil.ContextExecutor, iD int, selectCols ...string) (*MutedUser, error) { + mutedUserObj := &MutedUser{} + + sel := "*" + if len(selectCols) > 0 { + sel = strings.Join(strmangle.IdentQuoteSlice(dialect.LQ, dialect.RQ, selectCols), ",") + } + query := fmt.Sprintf( + "select %s from \"muted_users\" where \"id\"=$1", sel, + ) + + q := queries.Raw(query, iD) + + err := q.Bind(ctx, exec, mutedUserObj) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + return nil, sql.ErrNoRows + } + return nil, errors.Wrap(err, "models: unable to select from muted_users") + } + + return mutedUserObj, nil +} + +// InsertG a single record. See Insert for whitelist behavior description. +func (o *MutedUser) InsertG(ctx context.Context, columns boil.Columns) error { + return o.Insert(ctx, boil.GetContextDB(), columns) +} + +// Insert a single record using an executor. +// See boil.Columns.InsertColumnSet documentation to understand column list inference for inserts. +func (o *MutedUser) Insert(ctx context.Context, exec boil.ContextExecutor, columns boil.Columns) error { + if o == nil { + return errors.New("models: no muted_users provided for insertion") + } + + var err error + if !boil.TimestampsAreSkipped(ctx) { + currTime := time.Now().In(boil.GetLocation()) + + if o.CreatedAt.IsZero() { + o.CreatedAt = currTime + } + if o.UpdatedAt.IsZero() { + o.UpdatedAt = currTime + } + } + + nzDefaults := queries.NonZeroDefaultSet(mutedUserColumnsWithDefault, o) + + key := makeCacheKey(columns, nzDefaults) + mutedUserInsertCacheMut.RLock() + cache, cached := mutedUserInsertCache[key] + mutedUserInsertCacheMut.RUnlock() + + if !cached { + wl, returnColumns := columns.InsertColumnSet( + mutedUserAllColumns, + mutedUserColumnsWithDefault, + mutedUserColumnsWithoutDefault, + nzDefaults, + ) + + cache.valueMapping, err = queries.BindMapping(mutedUserType, mutedUserMapping, wl) + if err != nil { + return err + } + cache.retMapping, err = queries.BindMapping(mutedUserType, mutedUserMapping, returnColumns) + if err != nil { + return err + } + if len(wl) != 0 { + cache.query = fmt.Sprintf("INSERT INTO \"muted_users\" (\"%s\") %%sVALUES (%s)%%s", strings.Join(wl, "\",\""), strmangle.Placeholders(dialect.UseIndexPlaceholders, len(wl), 1, 1)) + } else { + cache.query = "INSERT INTO \"muted_users\" %sDEFAULT VALUES%s" + } + + var queryOutput, queryReturning string + + if len(cache.retMapping) != 0 { + queryReturning = fmt.Sprintf(" RETURNING \"%s\"", strings.Join(returnColumns, "\",\"")) + } + + cache.query = fmt.Sprintf(cache.query, queryOutput, queryReturning) + } + + value := reflect.Indirect(reflect.ValueOf(o)) + vals := queries.ValuesFromMapping(value, cache.valueMapping) + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, cache.query) + fmt.Fprintln(writer, vals) + } + + if len(cache.retMapping) != 0 { + err = exec.QueryRowContext(ctx, cache.query, vals...).Scan(queries.PtrsFromMapping(value, cache.retMapping)...) + } else { + _, err = exec.ExecContext(ctx, cache.query, vals...) + } + + if err != nil { + return errors.Wrap(err, "models: unable to insert into muted_users") + } + + if !cached { + mutedUserInsertCacheMut.Lock() + mutedUserInsertCache[key] = cache + mutedUserInsertCacheMut.Unlock() + } + + return nil +} + +// UpdateG a single MutedUser record using the global executor. +// See Update for more documentation. +func (o *MutedUser) UpdateG(ctx context.Context, columns boil.Columns) (int64, error) { + return o.Update(ctx, boil.GetContextDB(), columns) +} + +// Update uses an executor to update the MutedUser. +// See boil.Columns.UpdateColumnSet documentation to understand column list inference for updates. +// Update does not automatically update the record in case of default values. Use .Reload() to refresh the records. +func (o *MutedUser) Update(ctx context.Context, exec boil.ContextExecutor, columns boil.Columns) (int64, error) { + if !boil.TimestampsAreSkipped(ctx) { + currTime := time.Now().In(boil.GetLocation()) + + o.UpdatedAt = currTime + } + + var err error + key := makeCacheKey(columns, nil) + mutedUserUpdateCacheMut.RLock() + cache, cached := mutedUserUpdateCache[key] + mutedUserUpdateCacheMut.RUnlock() + + if !cached { + wl := columns.UpdateColumnSet( + mutedUserAllColumns, + mutedUserPrimaryKeyColumns, + ) + + if !columns.IsWhitelist() { + wl = strmangle.SetComplement(wl, []string{"created_at"}) + } + if len(wl) == 0 { + return 0, errors.New("models: unable to update muted_users, could not build whitelist") + } + + cache.query = fmt.Sprintf("UPDATE \"muted_users\" SET %s WHERE %s", + strmangle.SetParamNames("\"", "\"", 1, wl), + strmangle.WhereClause("\"", "\"", len(wl)+1, mutedUserPrimaryKeyColumns), + ) + cache.valueMapping, err = queries.BindMapping(mutedUserType, mutedUserMapping, append(wl, mutedUserPrimaryKeyColumns...)) + if err != nil { + return 0, err + } + } + + values := queries.ValuesFromMapping(reflect.Indirect(reflect.ValueOf(o)), cache.valueMapping) + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, cache.query) + fmt.Fprintln(writer, values) + } + var result sql.Result + result, err = exec.ExecContext(ctx, cache.query, values...) + if err != nil { + return 0, errors.Wrap(err, "models: unable to update muted_users row") + } + + rowsAff, err := result.RowsAffected() + if err != nil { + return 0, errors.Wrap(err, "models: failed to get rows affected by update for muted_users") + } + + if !cached { + mutedUserUpdateCacheMut.Lock() + mutedUserUpdateCache[key] = cache + mutedUserUpdateCacheMut.Unlock() + } + + return rowsAff, nil +} + +// UpdateAllG updates all rows with the specified column values. +func (q mutedUserQuery) UpdateAllG(ctx context.Context, cols M) (int64, error) { + return q.UpdateAll(ctx, boil.GetContextDB(), cols) +} + +// UpdateAll updates all rows with the specified column values. +func (q mutedUserQuery) UpdateAll(ctx context.Context, exec boil.ContextExecutor, cols M) (int64, error) { + queries.SetUpdate(q.Query, cols) + + result, err := q.Query.ExecContext(ctx, exec) + if err != nil { + return 0, errors.Wrap(err, "models: unable to update all for muted_users") + } + + rowsAff, err := result.RowsAffected() + if err != nil { + return 0, errors.Wrap(err, "models: unable to retrieve rows affected for muted_users") + } + + return rowsAff, nil +} + +// UpdateAllG updates all rows with the specified column values. +func (o MutedUserSlice) UpdateAllG(ctx context.Context, cols M) (int64, error) { + return o.UpdateAll(ctx, boil.GetContextDB(), cols) +} + +// UpdateAll updates all rows with the specified column values, using an executor. +func (o MutedUserSlice) UpdateAll(ctx context.Context, exec boil.ContextExecutor, cols M) (int64, error) { + ln := int64(len(o)) + if ln == 0 { + return 0, nil + } + + if len(cols) == 0 { + return 0, errors.New("models: update all requires at least one column argument") + } + + colNames := make([]string, len(cols)) + args := make([]interface{}, len(cols)) + + i := 0 + for name, value := range cols { + colNames[i] = name + args[i] = value + i++ + } + + // Append all of the primary key values for each column + for _, obj := range o { + pkeyArgs := queries.ValuesFromMapping(reflect.Indirect(reflect.ValueOf(obj)), mutedUserPrimaryKeyMapping) + args = append(args, pkeyArgs...) + } + + sql := fmt.Sprintf("UPDATE \"muted_users\" SET %s WHERE %s", + strmangle.SetParamNames("\"", "\"", 1, colNames), + strmangle.WhereClauseRepeated(string(dialect.LQ), string(dialect.RQ), len(colNames)+1, mutedUserPrimaryKeyColumns, len(o))) + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, sql) + fmt.Fprintln(writer, args...) + } + result, err := exec.ExecContext(ctx, sql, args...) + if err != nil { + return 0, errors.Wrap(err, "models: unable to update all in mutedUser slice") + } + + rowsAff, err := result.RowsAffected() + if err != nil { + return 0, errors.Wrap(err, "models: unable to retrieve rows affected all in update all mutedUser") + } + return rowsAff, nil +} + +// UpsertG attempts an insert, and does an update or ignore on conflict. +func (o *MutedUser) UpsertG(ctx context.Context, updateOnConflict bool, conflictColumns []string, updateColumns, insertColumns boil.Columns, opts ...UpsertOptionFunc) error { + return o.Upsert(ctx, boil.GetContextDB(), updateOnConflict, conflictColumns, updateColumns, insertColumns, opts...) +} + +// Upsert attempts an insert using an executor, and does an update or ignore on conflict. +// See boil.Columns documentation for how to properly use updateColumns and insertColumns. +func (o *MutedUser) Upsert(ctx context.Context, exec boil.ContextExecutor, updateOnConflict bool, conflictColumns []string, updateColumns, insertColumns boil.Columns, opts ...UpsertOptionFunc) error { + if o == nil { + return errors.New("models: no muted_users provided for upsert") + } + if !boil.TimestampsAreSkipped(ctx) { + currTime := time.Now().In(boil.GetLocation()) + + if o.CreatedAt.IsZero() { + o.CreatedAt = currTime + } + o.UpdatedAt = currTime + } + + nzDefaults := queries.NonZeroDefaultSet(mutedUserColumnsWithDefault, o) + + // Build cache key in-line uglily - mysql vs psql problems + buf := strmangle.GetBuffer() + if updateOnConflict { + buf.WriteByte('t') + } else { + buf.WriteByte('f') + } + buf.WriteByte('.') + for _, c := range conflictColumns { + buf.WriteString(c) + } + buf.WriteByte('.') + buf.WriteString(strconv.Itoa(updateColumns.Kind)) + for _, c := range updateColumns.Cols { + buf.WriteString(c) + } + buf.WriteByte('.') + buf.WriteString(strconv.Itoa(insertColumns.Kind)) + for _, c := range insertColumns.Cols { + buf.WriteString(c) + } + buf.WriteByte('.') + for _, c := range nzDefaults { + buf.WriteString(c) + } + key := buf.String() + strmangle.PutBuffer(buf) + + mutedUserUpsertCacheMut.RLock() + cache, cached := mutedUserUpsertCache[key] + mutedUserUpsertCacheMut.RUnlock() + + var err error + + if !cached { + insert, _ := insertColumns.InsertColumnSet( + mutedUserAllColumns, + mutedUserColumnsWithDefault, + mutedUserColumnsWithoutDefault, + nzDefaults, + ) + + update := updateColumns.UpdateColumnSet( + mutedUserAllColumns, + mutedUserPrimaryKeyColumns, + ) + + if updateOnConflict && len(update) == 0 { + return errors.New("models: unable to upsert muted_users, could not build update column list") + } + + ret := strmangle.SetComplement(mutedUserAllColumns, strmangle.SetIntersect(insert, update)) + + conflict := conflictColumns + if len(conflict) == 0 && updateOnConflict && len(update) != 0 { + if len(mutedUserPrimaryKeyColumns) == 0 { + return errors.New("models: unable to upsert muted_users, could not build conflict column list") + } + + conflict = make([]string, len(mutedUserPrimaryKeyColumns)) + copy(conflict, mutedUserPrimaryKeyColumns) + } + cache.query = buildUpsertQueryPostgres(dialect, "\"muted_users\"", updateOnConflict, ret, update, conflict, insert, opts...) + + cache.valueMapping, err = queries.BindMapping(mutedUserType, mutedUserMapping, insert) + if err != nil { + return err + } + if len(ret) != 0 { + cache.retMapping, err = queries.BindMapping(mutedUserType, mutedUserMapping, ret) + if err != nil { + return err + } + } + } + + value := reflect.Indirect(reflect.ValueOf(o)) + vals := queries.ValuesFromMapping(value, cache.valueMapping) + var returns []interface{} + if len(cache.retMapping) != 0 { + returns = queries.PtrsFromMapping(value, cache.retMapping) + } + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, cache.query) + fmt.Fprintln(writer, vals) + } + if len(cache.retMapping) != 0 { + err = exec.QueryRowContext(ctx, cache.query, vals...).Scan(returns...) + if errors.Is(err, sql.ErrNoRows) { + err = nil // Postgres doesn't return anything when there's no update + } + } else { + _, err = exec.ExecContext(ctx, cache.query, vals...) + } + if err != nil { + return errors.Wrap(err, "models: unable to upsert muted_users") + } + + if !cached { + mutedUserUpsertCacheMut.Lock() + mutedUserUpsertCache[key] = cache + mutedUserUpsertCacheMut.Unlock() + } + + return nil +} + +// DeleteG deletes a single MutedUser record. +// DeleteG will match against the primary key column to find the record to delete. +func (o *MutedUser) DeleteG(ctx context.Context) (int64, error) { + return o.Delete(ctx, boil.GetContextDB()) +} + +// Delete deletes a single MutedUser record with an executor. +// Delete will match against the primary key column to find the record to delete. +func (o *MutedUser) Delete(ctx context.Context, exec boil.ContextExecutor) (int64, error) { + if o == nil { + return 0, errors.New("models: no MutedUser provided for delete") + } + + args := queries.ValuesFromMapping(reflect.Indirect(reflect.ValueOf(o)), mutedUserPrimaryKeyMapping) + sql := "DELETE FROM \"muted_users\" WHERE \"id\"=$1" + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, sql) + fmt.Fprintln(writer, args...) + } + result, err := exec.ExecContext(ctx, sql, args...) + if err != nil { + return 0, errors.Wrap(err, "models: unable to delete from muted_users") + } + + rowsAff, err := result.RowsAffected() + if err != nil { + return 0, errors.Wrap(err, "models: failed to get rows affected by delete for muted_users") + } + + return rowsAff, nil +} + +func (q mutedUserQuery) DeleteAllG(ctx context.Context) (int64, error) { + return q.DeleteAll(ctx, boil.GetContextDB()) +} + +// DeleteAll deletes all matching rows. +func (q mutedUserQuery) DeleteAll(ctx context.Context, exec boil.ContextExecutor) (int64, error) { + if q.Query == nil { + return 0, errors.New("models: no mutedUserQuery provided for delete all") + } + + queries.SetDelete(q.Query) + + result, err := q.Query.ExecContext(ctx, exec) + if err != nil { + return 0, errors.Wrap(err, "models: unable to delete all from muted_users") + } + + rowsAff, err := result.RowsAffected() + if err != nil { + return 0, errors.Wrap(err, "models: failed to get rows affected by deleteall for muted_users") + } + + return rowsAff, nil +} + +// DeleteAllG deletes all rows in the slice. +func (o MutedUserSlice) DeleteAllG(ctx context.Context) (int64, error) { + return o.DeleteAll(ctx, boil.GetContextDB()) +} + +// DeleteAll deletes all rows in the slice, using an executor. +func (o MutedUserSlice) DeleteAll(ctx context.Context, exec boil.ContextExecutor) (int64, error) { + if len(o) == 0 { + return 0, nil + } + + var args []interface{} + for _, obj := range o { + pkeyArgs := queries.ValuesFromMapping(reflect.Indirect(reflect.ValueOf(obj)), mutedUserPrimaryKeyMapping) + args = append(args, pkeyArgs...) + } + + sql := "DELETE FROM \"muted_users\" WHERE " + + strmangle.WhereClauseRepeated(string(dialect.LQ), string(dialect.RQ), 1, mutedUserPrimaryKeyColumns, len(o)) + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, sql) + fmt.Fprintln(writer, args) + } + result, err := exec.ExecContext(ctx, sql, args...) + if err != nil { + return 0, errors.Wrap(err, "models: unable to delete all from mutedUser slice") + } + + rowsAff, err := result.RowsAffected() + if err != nil { + return 0, errors.Wrap(err, "models: failed to get rows affected by deleteall for muted_users") + } + + return rowsAff, nil +} + +// ReloadG refetches the object from the database using the primary keys. +func (o *MutedUser) ReloadG(ctx context.Context) error { + if o == nil { + return errors.New("models: no MutedUser provided for reload") + } + + return o.Reload(ctx, boil.GetContextDB()) +} + +// Reload refetches the object from the database +// using the primary keys with an executor. +func (o *MutedUser) Reload(ctx context.Context, exec boil.ContextExecutor) error { + ret, err := FindMutedUser(ctx, exec, o.ID) + if err != nil { + return err + } + + *o = *ret + return nil +} + +// ReloadAllG refetches every row with matching primary key column values +// and overwrites the original object slice with the newly updated slice. +func (o *MutedUserSlice) ReloadAllG(ctx context.Context) error { + if o == nil { + return errors.New("models: empty MutedUserSlice provided for reload all") + } + + return o.ReloadAll(ctx, boil.GetContextDB()) +} + +// ReloadAll refetches every row with matching primary key column values +// and overwrites the original object slice with the newly updated slice. +func (o *MutedUserSlice) ReloadAll(ctx context.Context, exec boil.ContextExecutor) error { + if o == nil || len(*o) == 0 { + return nil + } + + slice := MutedUserSlice{} + var args []interface{} + for _, obj := range *o { + pkeyArgs := queries.ValuesFromMapping(reflect.Indirect(reflect.ValueOf(obj)), mutedUserPrimaryKeyMapping) + args = append(args, pkeyArgs...) + } + + sql := "SELECT \"muted_users\".* FROM \"muted_users\" WHERE " + + strmangle.WhereClauseRepeated(string(dialect.LQ), string(dialect.RQ), 1, mutedUserPrimaryKeyColumns, len(*o)) + + q := queries.Raw(sql, args...) + + err := q.Bind(ctx, exec, &slice) + if err != nil { + return errors.Wrap(err, "models: unable to reload all in MutedUserSlice") + } + + *o = slice + + return nil +} + +// MutedUserExistsG checks if the MutedUser row exists. +func MutedUserExistsG(ctx context.Context, iD int) (bool, error) { + return MutedUserExists(ctx, boil.GetContextDB(), iD) +} + +// MutedUserExists checks if the MutedUser row exists. +func MutedUserExists(ctx context.Context, exec boil.ContextExecutor, iD int) (bool, error) { + var exists bool + sql := "select exists(select 1 from \"muted_users\" where \"id\"=$1 limit 1)" + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, sql) + fmt.Fprintln(writer, iD) + } + row := exec.QueryRowContext(ctx, sql, iD) + + err := row.Scan(&exists) + if err != nil { + return false, errors.Wrap(err, "models: unable to check if muted_users exists") + } + + return exists, nil +} + +// Exists checks if the MutedUser row exists. +func (o *MutedUser) Exists(ctx context.Context, exec boil.ContextExecutor) (bool, error) { + return MutedUserExists(ctx, exec, o.ID) +} diff --git a/moderation/models/psql_upsert.go b/moderation/models/psql_upsert.go new file mode 100644 index 0000000000..07602da9c5 --- /dev/null +++ b/moderation/models/psql_upsert.go @@ -0,0 +1,99 @@ +// Code generated by SQLBoiler 4.16.2 (https://github.com/volatiletech/sqlboiler). DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package models + +import ( + "fmt" + "strings" + + "github.com/volatiletech/sqlboiler/v4/drivers" + "github.com/volatiletech/strmangle" +) + +type UpsertOptions struct { + conflictTarget string + updateSet string +} + +type UpsertOptionFunc func(o *UpsertOptions) + +func UpsertConflictTarget(conflictTarget string) UpsertOptionFunc { + return func(o *UpsertOptions) { + o.conflictTarget = conflictTarget + } +} + +func UpsertUpdateSet(updateSet string) UpsertOptionFunc { + return func(o *UpsertOptions) { + o.updateSet = updateSet + } +} + +// buildUpsertQueryPostgres builds a SQL statement string using the upsertData provided. +func buildUpsertQueryPostgres(dia drivers.Dialect, tableName string, updateOnConflict bool, ret, update, conflict, whitelist []string, opts ...UpsertOptionFunc) string { + conflict = strmangle.IdentQuoteSlice(dia.LQ, dia.RQ, conflict) + whitelist = strmangle.IdentQuoteSlice(dia.LQ, dia.RQ, whitelist) + ret = strmangle.IdentQuoteSlice(dia.LQ, dia.RQ, ret) + + upsertOpts := &UpsertOptions{} + for _, o := range opts { + o(upsertOpts) + } + + buf := strmangle.GetBuffer() + defer strmangle.PutBuffer(buf) + + columns := "DEFAULT VALUES" + if len(whitelist) != 0 { + columns = fmt.Sprintf("(%s) VALUES (%s)", + strings.Join(whitelist, ", "), + strmangle.Placeholders(dia.UseIndexPlaceholders, len(whitelist), 1, 1)) + } + + fmt.Fprintf( + buf, + "INSERT INTO %s %s ON CONFLICT ", + tableName, + columns, + ) + + if upsertOpts.conflictTarget != "" { + buf.WriteString(upsertOpts.conflictTarget) + } else if len(conflict) != 0 { + buf.WriteByte('(') + buf.WriteString(strings.Join(conflict, ", ")) + buf.WriteByte(')') + } + buf.WriteByte(' ') + + if !updateOnConflict || len(update) == 0 { + buf.WriteString("DO NOTHING") + } else { + buf.WriteString("DO UPDATE SET ") + + if upsertOpts.updateSet != "" { + buf.WriteString(upsertOpts.updateSet) + } else { + for i, v := range update { + if len(v) == 0 { + continue + } + if i != 0 { + buf.WriteByte(',') + } + quoted := strmangle.IdentQuote(dia.LQ, dia.RQ, v) + buf.WriteString(quoted) + buf.WriteString(" = EXCLUDED.") + buf.WriteString(quoted) + } + } + } + + if len(ret) != 0 { + buf.WriteString(" RETURNING ") + buf.WriteString(strings.Join(ret, ", ")) + } + + return buf.String() +} diff --git a/moderation/moderation.go b/moderation/moderation.go index d2b6a21134..f30186db99 100644 --- a/moderation/moderation.go +++ b/moderation/moderation.go @@ -3,12 +3,12 @@ package moderation import ( "emperror.dev/errors" "github.com/botlabs-gg/yagpdb/v2/common" - "github.com/botlabs-gg/yagpdb/v2/common/configstore" "github.com/botlabs-gg/yagpdb/v2/common/featureflags" "github.com/botlabs-gg/yagpdb/v2/lib/discordgo" - "golang.org/x/net/context" ) +//go:generate sqlboiler --no-hooks psql + const ( ActionMuted = "Muted" ActionUnMuted = "Unmuted" @@ -48,32 +48,9 @@ func RedisKeyLockedMute(guildID, userID int64) string { func RegisterPlugin() { plugin := &Plugin{} - common.RegisterPlugin(plugin) - configstore.RegisterConfig(configstore.SQL, &Config{}) - common.GORM.AutoMigrate(&Config{}, &WarningModel{}, &MuteModel{}) -} - -func getConfigIfNotSet(guildID int64, config *Config) (*Config, error) { - if config == nil { - var err error - config, err = GetConfig(guildID) - if err != nil { - return nil, err - } - } - - return config, nil -} - -func GetConfig(guildID int64) (*Config, error) { - var config Config - err := configstore.Cached.GetGuildConfig(context.Background(), guildID, &config) - if err == configstore.ErrNotFound { - err = nil - } - return &config, err + common.InitSchemas("moderation", DBSchemas...) } var _ featureflags.PluginWithFeatureFlags = (*Plugin)(nil) @@ -84,17 +61,17 @@ const ( ) func (p *Plugin) UpdateFeatureFlags(guildID int64) ([]string, error) { - config, err := GetConfig(guildID) + config, err := GetCachedConfigOrDefault(guildID) if err != nil { return nil, errors.WithStackIf(err) } var flags []string - if config.MuteRole != "" && config.MuteManageRole { + if config.MuteRole != 0 && config.MuteManageRole { flags = append(flags, featureFlagMuteRoleManaged) } - if config.MuteRole != "" { + if config.MuteRole != 0 { flags = append(flags, featureFlagMuteEnabled) } diff --git a/moderation/modlog.go b/moderation/modlog.go index c79996a556..6cbd96d1cb 100644 --- a/moderation/modlog.go +++ b/moderation/modlog.go @@ -41,8 +41,7 @@ var ( ) func CreateModlogEmbed(config *Config, author *discordgo.User, action ModlogAction, target *discordgo.User, reason, logLink string) error { - channelID := config.IntActionChannel() - config.GetGuildID() + channelID := config.ActionChannel if channelID == 0 { return nil } @@ -88,8 +87,8 @@ func CreateModlogEmbed(config *Config, author *discordgo.User, action ModlogActi if err != nil { if common.IsDiscordErr(err, discordgo.ErrCodeMissingAccess, discordgo.ErrCodeMissingPermissions, discordgo.ErrCodeUnknownChannel) { // disable the modlog - config.ActionChannel = "" - config.Save(config.GetGuildID()) + config.ActionChannel = 0 + SaveConfig(config) return nil } return err diff --git a/moderation/plugin_bot.go b/moderation/plugin_bot.go index 09c860ea46..090efcb29b 100644 --- a/moderation/plugin_bot.go +++ b/moderation/plugin_bot.go @@ -1,6 +1,8 @@ package moderation import ( + "context" + "database/sql" "math/rand" "strconv" "strings" @@ -18,8 +20,10 @@ import ( "github.com/botlabs-gg/yagpdb/v2/lib/discordgo" "github.com/botlabs-gg/yagpdb/v2/lib/dshardorchestrator" "github.com/botlabs-gg/yagpdb/v2/lib/dstate" - "github.com/jinzhu/gorm" + "github.com/botlabs-gg/yagpdb/v2/moderation/models" + "github.com/karlseguin/ccache" "github.com/mediocregopher/radix/v3" + "github.com/volatiletech/sqlboiler/v4/boil" ) var ( @@ -43,8 +47,6 @@ func (p *Plugin) AddCommands() { } func (p *Plugin) BotInit() { - // scheduledevents.RegisterEventHandler("unmute", handleUnMuteLegacy) - // scheduledevents.RegisterEventHandler("mod_unban", handleUnbanLegacy) scheduledevents2.RegisterHandler("moderation_unmute", ScheduledUnmuteData{}, handleScheduledUnmute) scheduledevents2.RegisterHandler("moderation_unban", ScheduledUnbanData{}, handleScheduledUnban) scheduledevents2.RegisterLegacyMigrater("unmute", handleMigrateScheduledUnmute) @@ -59,10 +61,92 @@ func (p *Plugin) BotInit() { eventsystem.AddHandlerAsyncLastLegacy(p, bot.ConcurrentEventHandler(HandleGuildCreate), eventsystem.EventGuildCreate) eventsystem.AddHandlerAsyncLast(p, HandleChannelCreateUpdate, eventsystem.EventChannelCreate, eventsystem.EventChannelUpdate) + pubsub.AddHandler("invalidate_moderation_config_cache", handleInvalidateConfigCache, nil) pubsub.AddHandler("mod_refresh_mute_override", HandleRefreshMuteOverrides, nil) pubsub.AddHandler("mod_refresh_mute_override_create_role", HandleRefreshMuteOverridesCreateRole, nil) } +func SaveConfig(config *Config) error { + err := config.ToModel().UpsertG(context.Background(), true, []string{"guild_id"}, boil.Infer(), boil.Infer()) + if err != nil { + return err + } + pubsub.Publish("invalidate_moderation_config_cache", config.GuildID, nil) + + if err := featureflags.UpdatePluginFeatureFlags(config.GuildID, &Plugin{}); err != nil { + return err + } + pubsub.Publish("mod_refresh_mute_override", config.GuildID, nil) + return nil +} + +func GetConfigIfNotSet(guildID int64, config *Config) (*Config, error) { + if config == nil { + var err error + config, err = GetCachedConfigOrDefault(guildID) + if err != nil { + return nil, err + } + } + + return config, nil +} + +var configCache = ccache.New(ccache.Configure().MaxSize(15000)) + +func GetCachedConfigOrDefault(guildID int64) (*Config, error) { + const cacheDuration = 10 * time.Minute + + item, err := configCache.Fetch(cacheKey(guildID), cacheDuration, func() (interface{}, error) { + return GetConfig(guildID) + }) + if err != nil { + if err == sql.ErrNoRows { + return &Config{GuildID: guildID}, nil + } + return nil, err + } + return item.Value().(*Config), nil +} + +func handleInvalidateConfigCache(evt *pubsub.Event) { + configCache.Delete(cacheKey(evt.TargetGuildInt)) +} + +func cacheKey(guildID int64) string { + return discordgo.StrID(guildID) +} + +func GetConfig(guildID int64) (*Config, error) { + const maxRetries = 1000 + + currentRetries := 0 + for { + conf, err := models.FindModerationConfigG(context.Background(), guildID) + if err == nil { + if currentRetries > 1 { + logger.Info("Fetched config after ", currentRetries, " retries") + } + return configFromModel(conf), nil + } + + if err == sql.ErrNoRows { + return nil, err + } + + if strings.Contains(err.Error(), "sorry, too many clients already") { + time.Sleep(time.Millisecond * 10 * time.Duration(rand.Intn(10))) + currentRetries++ + if currentRetries > maxRetries { + return nil, err + } + continue + } + + return nil, err + } +} + type ScheduledUnmuteData struct { UserID int64 `json:"user_id"` } @@ -118,7 +202,7 @@ func RefreshMuteOverrides(guildID int64, createRole bool) { return // nothing to do } - config, err := GetConfig(guildID) + config, err := GetCachedConfigOrDefault(guildID) if err != nil { return } @@ -131,7 +215,7 @@ func RefreshMuteOverrides(guildID int64, createRole bool) { return } - if config.MuteRole == "" || config.MuteRole == "0" { + if config.MuteRole == 0 { if createRole { _, err := createMuteRole(config, guildID) if err != nil { @@ -149,7 +233,7 @@ func RefreshMuteOverrides(guildID int64, createRole bool) { return // Still starting up and haven't received the guild yet } - if guild.GetRole(config.IntMuteRole()) == nil { + if guild.GetRole(config.MuteRole) == nil { return } @@ -175,8 +259,8 @@ func createMuteRole(config *Config, guildID int64) (int64, error) { return 0, err } - config.MuteRole = strconv.FormatInt(r.ID, 10) - err = config.Save(guildID) + config.MuteRole = r.ID + err = SaveConfig(config) if err != nil { // failed saving config, attempt to delete the role common.BotSession.GuildRoleDelete(guildID, r.ID) @@ -202,12 +286,12 @@ func HandleChannelCreateUpdate(evt *eventsystem.EventData) (retry bool, err erro return false, nil } - config, err := GetConfig(channel.GuildID) + config, err := GetCachedConfigOrDefault(channel.GuildID) if err != nil { return true, errors.WithStackIf(err) } - if config.MuteRole == "" || !config.MuteManageRole { + if config.MuteRole == 0 || !config.MuteManageRole { return false, nil } @@ -230,7 +314,7 @@ func RefreshMuteOverrideForChannel(config *Config, channel dstate.ChannelState) // Check for existing override for _, v := range channel.PermissionOverwrites { - if v.Type == discordgo.PermissionOverwriteTypeRole && v.ID == config.IntMuteRole() { + if v.Type == discordgo.PermissionOverwriteTypeRole && v.ID == config.MuteRole { override = &v break } @@ -263,7 +347,7 @@ func RefreshMuteOverrideForChannel(config *Config, channel dstate.ChannelState) } if changed { - common.BotSession.ChannelPermissionSet(channel.ID, config.IntMuteRole(), discordgo.PermissionOverwriteTypeRole, allows, denies) + common.BotSession.ChannelPermissionSet(channel.ID, config.MuteRole, discordgo.PermissionOverwriteTypeRole, allows, denies) } } @@ -274,13 +358,13 @@ func HandleGuildMemberTimeoutChange(evt *eventsystem.EventData) (retry bool, err return false, nil } - config, err := GetConfig(data.GuildID) + config, err := GetCachedConfigOrDefault(data.GuildID) if err != nil { return true, errors.WithStackIf(err) } // no modlog channel setup - if config.IntActionChannel() == 0 { + if config.ActionChannel == 0 { return false, nil } // If we poll the audit log too fast then there sometimes wont be a audit log entry @@ -358,13 +442,13 @@ func HandleGuildBanAddRemove(evt *eventsystem.EventData) { return } - config, err := GetConfig(guildID) + config, err := GetCachedConfigOrDefault(guildID) if err != nil { logger.WithError(err).WithField("guild", guildID).Error("Failed retrieving config") return } - if config.IntActionChannel() == 0 { + if config.ActionChannel == 0 { return } @@ -407,12 +491,12 @@ func HandleGuildBanAddRemove(evt *eventsystem.EventData) { func HandleGuildMemberRemove(evt *eventsystem.EventData) (retry bool, err error) { data := evt.GuildMemberRemove() - config, err := GetConfig(data.GuildID) + config, err := GetCachedConfigOrDefault(data.GuildID) if err != nil { return true, errors.WithStackIf(err) } - if config.IntActionChannel() == 0 { + if config.ActionChannel == 0 { return false, nil } @@ -468,10 +552,12 @@ func LockMemberMuteMW(next eventsystem.HandlerFunc) eventsystem.HandlerFunc { guildID := evt.GS.ID - var currentMute MuteModel - err = common.GORM.Where(MuteModel{UserID: userID, GuildID: guildID}).First(¤tMute).Error + currentMute, err := models.MutedUsers( + models.MutedUserWhere.UserID.EQ(userID), + models.MutedUserWhere.GuildID.EQ(guildID), + ).OneG(evt.Context()) if err != nil { - if err == gorm.ErrRecordNotFound { + if err == sql.ErrNoRows { return false, nil } @@ -479,7 +565,7 @@ func LockMemberMuteMW(next eventsystem.HandlerFunc) eventsystem.HandlerFunc { } // Don't bother doing anything if this mute is almost up - if !currentMute.ExpiresAt.IsZero() && currentMute.ExpiresAt.Sub(time.Now()) < 5*time.Second { + if !currentMute.ExpiresAt.Time.IsZero() && time.Until(currentMute.ExpiresAt.Time) < 5*time.Second { return false, nil } @@ -490,16 +576,16 @@ func LockMemberMuteMW(next eventsystem.HandlerFunc) eventsystem.HandlerFunc { func HandleMemberJoin(evt *eventsystem.EventData) (retry bool, err error) { c := evt.GuildMemberAdd() - config, err := GetConfig(c.GuildID) + config, err := GetCachedConfigOrDefault(c.GuildID) if err != nil { return true, errors.WithStackIf(err) } - if config.MuteRole == "" { + if config.MuteRole == 0 { return false, nil } - err = common.BotSession.GuildMemberRoleAdd(c.GuildID, c.User.ID, config.IntMuteRole()) + err = common.BotSession.GuildMemberRoleAdd(c.GuildID, c.User.ID, config.MuteRole) if err != nil { return bot.CheckDiscordErrRetry(err), errors.WithStackIf(err) } @@ -515,12 +601,12 @@ func HandleGuildMemberUpdate(evt *eventsystem.EventData) (retry bool, err error) return false, nil } - config, err := GetConfig(c.GuildID) + config, err := GetCachedConfigOrDefault(c.GuildID) if err != nil { return true, errors.WithStackIf(err) } - if config.MuteRole == "" { + if config.MuteRole == 0 { return false, nil } @@ -530,7 +616,7 @@ func HandleGuildMemberUpdate(evt *eventsystem.EventData) (retry bool, err error) guild := evt.GS - role := guild.GetRole(config.IntMuteRole()) + role := guild.GetRole(config.MuteRole) if role == nil { return false, nil // Probably deleted the mute role, do nothing then } diff --git a/moderation/plugin_web.go b/moderation/plugin_web.go index dbf6ca3ca2..45e8132979 100644 --- a/moderation/plugin_web.go +++ b/moderation/plugin_web.go @@ -9,6 +9,7 @@ import ( "github.com/botlabs-gg/yagpdb/v2/common" "github.com/botlabs-gg/yagpdb/v2/common/cplogs" "github.com/botlabs-gg/yagpdb/v2/lib/discordgo" + "github.com/botlabs-gg/yagpdb/v2/moderation/models" "github.com/botlabs-gg/yagpdb/v2/web" "goji.io" "goji.io/pat" @@ -57,7 +58,7 @@ func HandleModeration(w http.ResponseWriter, r *http.Request) (web.TemplateData, templateData["DefaultTimeoutDuration"] = int(DefaultTimeoutDuration.Minutes()) if _, ok := templateData["ModConfig"]; !ok { - config, err := GetConfig(activeGuild.ID) + config, err := GetCachedConfigOrDefault(activeGuild.ID) if err != nil { return templateData, err } @@ -67,19 +68,17 @@ func HandleModeration(w http.ResponseWriter, r *http.Request) (web.TemplateData, return templateData, nil } -// HandlePostModeration update the settings +// HandlePostModeration updates the settings func HandlePostModeration(w http.ResponseWriter, r *http.Request) (web.TemplateData, error) { ctx := r.Context() activeGuild, templateData := web.GetBaseCPContextData(ctx) templateData["VisibleURL"] = "/manage/" + discordgo.StrID(activeGuild.ID) + "/moderation/" newConfig := ctx.Value(common.ContextKeyParsedForm).(*Config) - newConfig.DefaultMuteDuration.Valid = true - newConfig.DefaultTimeoutDuration.Valid = true - newConfig.DefaultBanDeleteDays.Valid = true templateData["ModConfig"] = newConfig - err := newConfig.Save(activeGuild.ID) + newConfig.GuildID = activeGuild.ID + err := SaveConfig(newConfig) templateData["DefaultDMMessage"] = DefaultDMMessage @@ -96,12 +95,12 @@ func HandleClearServerWarnings(w http.ResponseWriter, r *http.Request) (web.Temp activeGuild, templateData := web.GetBaseCPContextData(ctx) templateData["VisibleURL"] = "/manage/" + discordgo.StrID(activeGuild.ID) + "/moderation/" - rows := common.GORM.Where("guild_id = ?", activeGuild.ID).Delete(WarningModel{}).RowsAffected - templateData.AddAlerts(web.SucessAlert("Deleted ", rows, " warnings!")) + numDeleted, _ := models.ModerationWarnings(models.ModerationWarningWhere.GuildID.EQ(activeGuild.ID)).DeleteAllG(r.Context()) + templateData.AddAlerts(web.SucessAlert("Deleted ", numDeleted, " warnings!")) templateData["DefaultDMMessage"] = DefaultDMMessage - if rows > 0 { - go cplogs.RetryAddEntry(web.NewLogEntryFromContext(r.Context(), panelLogKeyClearWarnings, &cplogs.Param{Type: cplogs.ParamTypeInt, Value: rows})) + if numDeleted > 0 { + go cplogs.RetryAddEntry(web.NewLogEntryFromContext(r.Context(), panelLogKeyClearWarnings, &cplogs.Param{Type: cplogs.ParamTypeInt, Value: numDeleted})) } return templateData, nil @@ -115,7 +114,7 @@ func (p *Plugin) LoadServerHomeWidget(w http.ResponseWriter, r *http.Request) (w templateData["WidgetTitle"] = "Moderation" templateData["SettingsPath"] = "/moderation" - config, err := GetConfig(activeGuild.ID) + config, err := GetCachedConfigOrDefault(activeGuild.ID) if err != nil { return templateData, err } @@ -131,7 +130,7 @@ func (p *Plugin) LoadServerHomeWidget(w http.ResponseWriter, r *http.Request) (w
  • Warning commands: %s
  • ` - if config.ReportEnabled || config.CleanEnabled || config.GiveRoleCmdEnabled || config.ActionChannel != "" || + if config.ReportEnabled || config.CleanEnabled || config.GiveRoleCmdEnabled || config.ActionChannel != 0 || config.MuteEnabled || config.KickEnabled || config.BanEnabled || config.WarnCommandsEnabled || config.TimeoutEnabled { templateData["WidgetEnabled"] = true } else { diff --git a/moderation/punishments.go b/moderation/punishments.go index 11da72fdda..544278661c 100644 --- a/moderation/punishments.go +++ b/moderation/punishments.go @@ -2,6 +2,7 @@ package moderation import ( "context" + "database/sql" "fmt" "strconv" "strings" @@ -16,8 +17,9 @@ import ( "github.com/botlabs-gg/yagpdb/v2/lib/discordgo" "github.com/botlabs-gg/yagpdb/v2/lib/dstate" "github.com/botlabs-gg/yagpdb/v2/logs" - "github.com/jinzhu/gorm" + "github.com/botlabs-gg/yagpdb/v2/moderation/models" "github.com/mediocregopher/radix/v3" + "github.com/volatiletech/sqlboiler/v4/boil" "github.com/volatiletech/sqlboiler/v4/queries/qm" ) @@ -53,7 +55,7 @@ func getMemberWithFallback(gs *dstate.GuildSet, user *discordgo.User) (ms *dstat // Kick or bans someone, uploading a hasebin log, and sending the report message in the action channel func punish(config *Config, p Punishment, guildID int64, channel *dstate.ChannelState, message *discordgo.Message, author *discordgo.User, reason string, user *discordgo.User, duration time.Duration, variadicBanDeleteDays ...int) error { - config, err := getConfigIfNotSet(guildID, config) + config, err := GetConfigIfNotSet(guildID, config) if err != nil { return common.ErrWithCaller(err) } @@ -196,8 +198,8 @@ func sendPunishDM(config *Config, dmMsg string, action ModlogAction, gs *dstate. logger.WithError(err).WithField("guild", gs.ID).Warn("Failed executing punishment DM") executed = "Failed executing template." - if config.ErrorChannel != "" { - _, _, _ = bot.SendMessage(gs.ID, config.IntErrorChannel(), fmt.Sprintf("Failed executing punishment DM (Action: `%s`).\nError: `%v`", ActionMap[action.Prefix], err)) + if config.ErrorChannel != 0 { + _, _, _ = bot.SendMessage(gs.ID, config.ErrorChannel, fmt.Sprintf("Failed executing punishment DM (Action: `%s`).\nError: `%v`", ActionMap[action.Prefix], err)) } } @@ -210,7 +212,7 @@ func sendPunishDM(config *Config, dmMsg string, action ModlogAction, gs *dstate. } func KickUser(config *Config, guildID int64, channel *dstate.ChannelState, message *discordgo.Message, author *discordgo.User, reason string, user *discordgo.User, del int) error { - config, err := getConfigIfNotSet(guildID, config) + config, err := GetConfigIfNotSet(guildID, config) if err != nil { return common.ErrWithCaller(err) } @@ -226,10 +228,7 @@ func KickUser(config *Config, guildID int64, channel *dstate.ChannelState, messa del = 100 } - if channel != nil { - _, err = DeleteMessages(guildID, channel.ID, user.ID, del, del) - } - + _, err = DeleteMessages(guildID, channel.ID, user.ID, del, del) return err } @@ -307,7 +306,7 @@ func BanUser(config *Config, guildID int64, channel *dstate.ChannelState, messag } func UnbanUser(config *Config, guildID int64, author *discordgo.User, reason string, user *discordgo.User) (bool, error) { - config, err := getConfigIfNotSet(guildID, config) + config, err := GetConfigIfNotSet(guildID, config) if err != nil { return false, common.ErrWithCaller(err) } @@ -318,7 +317,7 @@ func UnbanUser(config *Config, guildID int64, author *discordgo.User, reason str common.LogIgnoreError(err, "[moderation] failed clearing unban events", nil) //We need details for user only if unban is to be logged in modlog. Thus we can save a potential api call by directly attempting an unban in other cases. - if config.LogUnbans && config.IntActionChannel() != 0 { + if config.LogUnbans && config.ActionChannel != 0 { // check if they're already banned guildBan, err := common.BotSession.GuildBan(guildID, user.ID) if err != nil { @@ -362,7 +361,7 @@ func TimeoutUser(config *Config, guildID int64, channel *dstate.ChannelState, me } func RemoveTimeout(config *Config, guildID int64, author *discordgo.User, reason string, user *discordgo.User) error { - config, err := getConfigIfNotSet(guildID, config) + config, err := GetConfigIfNotSet(guildID, config) if err != nil { return common.ErrWithCaller(err) } @@ -403,12 +402,12 @@ const ( // Unmut or mute a user, ignore duration if unmuting // TODO: i don't think we need to track mutes in its own database anymore now with the new scheduled event system func MuteUnmuteUser(config *Config, mute bool, guildID int64, channel *dstate.ChannelState, message *discordgo.Message, author *discordgo.User, reason string, member *dstate.MemberState, duration int) error { - config, err := getConfigIfNotSet(guildID, config) + config, err := GetConfigIfNotSet(guildID, config) if err != nil { return common.ErrWithCaller(err) } - if config.MuteRole == "" { + if config.MuteRole == 0 { return ErrNoMuteRole } @@ -422,16 +421,18 @@ func MuteUnmuteUser(config *Config, mute bool, guildID int64, channel *dstate.Ch defer UnlockMute(member.User.ID) // Look for existing mute - currentMute := MuteModel{} - err = common.GORM.Where(&MuteModel{UserID: member.User.ID, GuildID: guildID}).First(¤tMute).Error - alreadyMuted := err != gorm.ErrRecordNotFound - if err != nil && err != gorm.ErrRecordNotFound { + currentMute, err := models.MutedUsers( + models.MutedUserWhere.UserID.EQ(member.User.ID), + models.MutedUserWhere.GuildID.EQ(guildID), + ).OneG(context.Background()) + alreadyMuted := err != sql.ErrNoRows + if err != nil && err != sql.ErrNoRows { return common.ErrWithCaller(err) } // Insert/update the mute entry in the database if !alreadyMuted { - currentMute = MuteModel{ + currentMute = &models.MutedUser{ UserID: member.User.ID, GuildID: guildID, } @@ -443,7 +444,9 @@ func MuteUnmuteUser(config *Config, mute bool, guildID int64, channel *dstate.Ch currentMute.Reason = reason if duration > 0 { - currentMute.ExpiresAt = time.Now().Add(time.Minute * time.Duration(duration)) + currentMute.ExpiresAt.SetValid(time.Now().Add(time.Minute * time.Duration(duration))) + } else { + currentMute.ExpiresAt.Valid = false // duration <= 0 means no expiry } // no matter what, if were unmuting or muting, we wanna make sure we dont have duplicated unmute events @@ -475,7 +478,7 @@ func MuteUnmuteUser(config *Config, mute bool, guildID int64, channel *dstate.Ch currentMute.RemovedRoles = removedRoles } - err = common.GORM.Save(¤tMute).Error + err = currentMute.UpsertG(context.Background(), true, []string{"id"}, boil.Infer(), boil.Infer()) if err != nil { return errors.WithMessage(err, "failed inserting/updating mute") } @@ -496,7 +499,7 @@ func MuteUnmuteUser(config *Config, mute bool, guildID int64, channel *dstate.Ch } if alreadyMuted { - common.GORM.Delete(¤tMute) + currentMute.DeleteG(context.Background()) common.RedisPool.Do(radix.Cmd(nil, "DEL", RedisKeyMutedUser(guildID, member.User.ID))) } } @@ -532,11 +535,11 @@ func MuteUnmuteUser(config *Config, mute bool, guildID int64, channel *dstate.Ch func AddMemberMuteRole(config *Config, id int64, currentRoles []int64) (removedRoles []int64, err error) { removedRoles = make([]int64, 0, len(config.MuteRemoveRoles)) newMemberRoles := make([]string, 0, len(currentRoles)) - newMemberRoles = append(newMemberRoles, config.MuteRole) + newMemberRoles = append(newMemberRoles, discordgo.StrID(config.MuteRole)) hadMuteRole := false for _, r := range currentRoles { - if config.IntMuteRole() == r { + if r == config.MuteRole { hadMuteRole = true continue } @@ -557,13 +560,13 @@ func AddMemberMuteRole(config *Config, id int64, currentRoles []int64) (removedR return } -func RemoveMemberMuteRole(config *Config, id int64, currentRoles []int64, mute MuteModel) (err error) { +func RemoveMemberMuteRole(config *Config, id int64, currentRoles []int64, mute *models.MutedUser) (err error) { newMemberRoles := decideUnmuteRoles(config, currentRoles, mute) err = common.BotSession.GuildMemberEdit(config.GuildID, id, newMemberRoles) return } -func decideUnmuteRoles(config *Config, currentRoles []int64, mute MuteModel) []string { +func decideUnmuteRoles(config *Config, currentRoles []int64, mute *models.MutedUser) []string { newMemberRoles := make([]string, 0) gs := bot.State.GetGuild(config.GuildID) @@ -571,7 +574,7 @@ func decideUnmuteRoles(config *Config, currentRoles []int64, mute MuteModel) []s if err != nil || botState == nil { // We couldn't find the bot on state, so keep old behaviour for _, r := range currentRoles { - if r != config.IntMuteRole() { + if r != config.MuteRole { newMemberRoles = append(newMemberRoles, strconv.FormatInt(r, 10)) } } @@ -588,7 +591,7 @@ func decideUnmuteRoles(config *Config, currentRoles []int64, mute MuteModel) []s yagHighest := bot.MemberHighestRole(gs, botState) for _, v := range currentRoles { - if v != config.IntMuteRole() { + if v != config.MuteRole { newMemberRoles = append(newMemberRoles, strconv.FormatInt(v, 10)) } } @@ -604,7 +607,7 @@ func decideUnmuteRoles(config *Config, currentRoles []int64, mute MuteModel) []s } func WarnUser(config *Config, guildID int64, channel *dstate.ChannelState, msg *discordgo.Message, author *discordgo.User, target *discordgo.User, message string) error { - warning := &WarningModel{ + warning := &models.ModerationWarning{ GuildID: guildID, UserID: discordgo.StrID(target.ID), AuthorID: discordgo.StrID(author.ID), @@ -618,17 +621,17 @@ func WarnUser(config *Config, guildID int64, channel *dstate.ChannelState, msg * channelID = channel.ID } - config, err := getConfigIfNotSet(guildID, config) + config, err := GetConfigIfNotSet(guildID, config) if err != nil { return common.ErrWithCaller(err) } if config.WarnIncludeChannelLogs && channelID != 0 { - warning.LogsLink = CreateLogs(guildID, channelID, author) + warning.LogsLink.SetValid(CreateLogs(guildID, channelID, author)) } // Create the entry in the database - err = common.GORM.Create(warning).Error + err = warning.InsertG(context.Background(), boil.Infer()) if err != nil { return common.ErrWithCaller(err) } @@ -641,8 +644,8 @@ func WarnUser(config *Config, guildID int64, channel *dstate.ChannelState, msg * // go bot.SendDM(target.ID, fmt.Sprintf("**%s**: You have been warned for: %s", bot.GuildName(guildID), message)) - if config.WarnSendToModlog && config.ActionChannel != "" { - err = CreateModlogEmbed(config, author, MAWarned, target, message, warning.LogsLink) + if config.WarnSendToModlog && config.ActionChannel != 0 { + err = CreateModlogEmbed(config, author, MAWarned, target, message, warning.LogsLink.String) if err != nil { return common.ErrWithCaller(err) } diff --git a/moderation/schema.go b/moderation/schema.go new file mode 100644 index 0000000000..c2c9e41269 --- /dev/null +++ b/moderation/schema.go @@ -0,0 +1,143 @@ +package moderation + +var DBSchemas = []string{` +CREATE TABLE IF NOT EXISTS moderation_configs ( + guild_id BIGINT PRIMARY KEY, + created_at TIMESTAMP WITH TIME ZONE NOT NULL, + updated_at TIMESTAMP WITH TIME ZONE NOT NULL, + + -- Many of the following columns should be non-nullable, but were originally + -- managed by gorm (which does not add NOT NULL constraints by default) so are + -- missing them. Unfortunately, it is unfeasible to retroactively fill missing + -- values with defaults and add the constraints as there are simply too many + -- rows in production. + + -- For similar legacy reasons, many fields that should have type BIGINT are TEXT. + + kick_enabled BOOLEAN, + kick_cmd_roles BIGINT[], + delete_messages_on_kick BOOLEAN, + kick_reason_optional BOOLEAN, + kick_message TEXT, + + ban_enabled BOOLEAN, + ban_cmd_roles BIGINT[], + ban_reason_optional BOOLEAN, + ban_message TEXT, + default_ban_delete_days BIGINT DEFAULT 1, + + timeout_enabled BOOLEAN, + timeout_cmd_roles BIGINT[], + timeout_reason_optional BOOLEAN, + timeout_remove_reason_optional BOOLEAN, + timeout_message TEXT, + default_timeout_duration BIGINT DEFAULT 10, + + mute_enabled BOOLEAN, + mute_cmd_roles BIGINT[], + mute_role TEXT, + mute_disallow_reaction_add BOOLEAN, + mute_reason_optional BOOLEAN, + unmute_reason_optional BOOLEAN, + mute_manage_role BOOLEAN, + mute_remove_roles BIGINT[], + mute_ignore_channels BIGINT[], + mute_message TEXT, + unmute_message TEXT, + default_mute_duration BIGINT DEFAULT 10, + + warn_commands_enabled BOOLEAN, + warn_cmd_roles BIGINT[], + warn_include_channel_logs BOOLEAN, + warn_send_to_modlog BOOLEAN, + warn_message TEXT, + + clean_enabled BOOLEAN, + report_enabled BOOLEAN, + action_channel TEXT, + report_channel TEXT, + error_channel TEXT, + log_unbans BOOLEAN, + log_bans BOOLEAN, + log_kicks BOOLEAN DEFAULT TRUE, + log_timeouts BOOLEAN, + + give_role_cmd_enabled BOOLEAN, + give_role_cmd_modlog BOOLEAN, + give_role_cmd_roles BIGINT[] +); +`, ` +-- Tables created with gorm have missing NOT NULL constraints for created_at and +-- updated_at columns; since these columns are never null in existing rows, we can +-- retraoctively add the constraints without needing to update any data. + +ALTER TABLE moderation_configs ALTER COLUMN created_at SET NOT NULL; +`, ` +ALTER TABLE moderation_configs ALTER COLUMN updated_at SET NOT NULL; +`, ` + +CREATE TABLE IF NOT EXISTS moderation_warnings ( + id SERIAL PRIMARY KEY, + created_at TIMESTAMP WITH TIME ZONE NOT NULL, + updated_at TIMESTAMP WITH TIME ZONE NOT NULL, + + guild_id BIGINT NOT NULL, + user_id TEXT NOT NULL, -- text instead of bigint for legacy compatibility + author_id TEXT NOT NULL, + + author_username_discrim TEXT NOT NULL, + + message TEXT NOT NULL, + logs_link TEXT +); +`, ` +CREATE INDEX IF NOT EXISTS idx_moderation_warnings_guild_id ON moderation_warnings(guild_id); +`, ` +-- Similar to moderation_warnings.{created_at,updated_at}, there are a number of +-- fields that are never null in existing data but do not have the proper NOT NULL +-- constraints if they were created with gorm. Add them in. + +ALTER TABLE moderation_warnings ALTER COLUMN created_at SET NOT NULL; +`, ` +ALTER TABLE moderation_warnings ALTER COLUMN updated_at SET NOT NULL; +`, ` +ALTER TABLE moderation_warnings ALTER COLUMN guild_id SET NOT NULL; +`, ` +ALTER TABLE moderation_warnings ALTER COLUMN user_id SET NOT NULL; +`, ` +ALTER TABLE moderation_warnings ALTER COLUMN author_id SET NOT NULL; +`, ` +ALTER TABLE moderation_warnings ALTER COLUMN author_username_discrim SET NOT NULL; +`, ` +ALTER TABLE moderation_warnings ALTER COLUMN message SET NOT NULL; +`, ` + +CREATE TABLE IF NOT EXISTS muted_users ( + id SERIAL PRIMARY KEY, + created_at TIMESTAMP WITH TIME ZONE NOT NULL, + updated_at TIMESTAMP WITH TIME ZONE NOT NULL, + + expires_at TIMESTAMP WITH TIME ZONE, + + guild_id BIGINT NOT NULL, + user_id BIGINT NOT NULL, + + author_id BIGINT NOT NULL, + reason TEXT NOT NULL, + + removed_roles BIGINT[] +); +`, ` + +ALTER TABLE muted_users ALTER COLUMN created_at SET NOT NULL; +`, ` +ALTER TABLE muted_users ALTER COLUMN updated_at SET NOT NULL; +`, ` +ALTER TABLE muted_users ALTER COLUMN guild_id SET NOT NULL; +`, ` +ALTER TABLE muted_users ALTER COLUMN user_id SET NOT NULL; +`, ` +ALTER TABLE muted_users ALTER COLUMN author_id SET NOT NULL; +`, ` +ALTER TABLE muted_users ALTER COLUMN reason SET NOT NULL; +`} diff --git a/moderation/sqlboiler.toml b/moderation/sqlboiler.toml new file mode 100644 index 0000000000..2136666922 --- /dev/null +++ b/moderation/sqlboiler.toml @@ -0,0 +1,27 @@ +add-global-variants = true +no-hooks = true +no-tests = true + +[psql] +dbname = "yagpdb" +host = "localhost" +user = "postgres" +pass = "pass" +sslmode = "disable" +whitelist = ["moderation_configs", "moderation_warnings", "muted_users"] + +[auto-columns] +created = "created_at" +updated = "updated_at" + +# sqlboiler column name inference capitalizes CMD, so, for instance, +# kick_cmd_roles becomes KickCMDRoles; manually override the names +[aliases.tables.moderation_configs.columns] +kick_cmd_roles = "KickCmdRoles" +ban_cmd_roles = "BanCmdRoles" +timeout_cmd_roles = "TimeoutCmdRoles" +mute_cmd_roles = "MuteCmdRoles" +warn_cmd_roles = "WarnCmdRoles" +give_role_cmd_enabled = "GiveRoleCmdEnabled" +give_role_cmd_modlog = "GiveRoleCmdModlog" +give_role_cmd_roles = "GiveRoleCmdRoles" diff --git a/moderation/tmplextensions.go b/moderation/tmplextensions.go index fb8aa65798..df2c51aac9 100644 --- a/moderation/tmplextensions.go +++ b/moderation/tmplextensions.go @@ -1,13 +1,16 @@ package moderation import ( + "context" + "database/sql" "fmt" "time" - "github.com/botlabs-gg/yagpdb/v2/common" "github.com/botlabs-gg/yagpdb/v2/common/templates" + "github.com/botlabs-gg/yagpdb/v2/lib/discordgo" "github.com/botlabs-gg/yagpdb/v2/logs" - "github.com/jinzhu/gorm" + "github.com/botlabs-gg/yagpdb/v2/moderation/models" + "github.com/volatiletech/sqlboiler/v4/queries/qm" ) func init() { @@ -16,33 +19,90 @@ func init() { }) } +// Needed to maintain backward compatibility with previous implementation of +// getWarnings using gorm, in which LogsLink was marked as a string instead of a +// null.String. +type TemplatesWarning struct { + ID int + CreatedAt time.Time + UpdatedAt time.Time + + GuildID int64 + UserID int64 + + AuthorID string + AuthorUsernameDiscrim string + + Message string + LogsLink string +} + +func templatesWarningFromModel(model *models.ModerationWarning) *TemplatesWarning { + var logsLink string + if model.LogsLink.Valid { + logsLink = model.LogsLink.String + } + + userID, _ := discordgo.ParseID(model.UserID) + return &TemplatesWarning{ + ID: model.ID, + CreatedAt: model.CreatedAt, + UpdatedAt: model.UpdatedAt, + + GuildID: model.GuildID, + UserID: userID, + + AuthorID: model.AuthorID, + AuthorUsernameDiscrim: model.AuthorUsernameDiscrim, + + Message: model.Message, + LogsLink: logsLink, + } +} + // getWarnings returns a slice of all warnings the target user has. func tmplGetWarnings(ctx *templates.Context) interface{} { - return func(target interface{}) ([]*WarningModel, error) { + return func(target interface{}) ([]*TemplatesWarning, error) { if ctx.IncreaseCheckCallCounterPremium("cc_moderation", 5, 10) { return nil, templates.ErrTooManyCalls } - gID := ctx.GS.ID - var warns []*WarningModel targetID := templates.TargetUserID(target) if targetID == 0 { return nil, fmt.Errorf("could not convert %T to a user ID", target) } - err := common.GORM.Where("user_id = ? AND guild_id = ?", targetID, gID).Order("id desc").Find(&warns).Error - if err != nil && err != gorm.ErrRecordNotFound { + warns, err := models.ModerationWarnings( + models.ModerationWarningWhere.UserID.EQ(discordgo.StrID(targetID)), + models.ModerationWarningWhere.GuildID.EQ(ctx.GS.ID), + + qm.OrderBy("id DESC"), + ).AllG(context.Background()) + if err != nil && err != sql.ErrNoRows { return nil, err } - // Avoid listing expired logs. - for _, entry := range warns { - purgedWarnLogs := logs.ConfEnableMessageLogPurge.GetBool() && entry.CreatedAt.Before(time.Now().AddDate(0, 0, -30)) - if entry.LogsLink != "" && purgedWarnLogs { - entry.LogsLink = "" - } + out := make([]*TemplatesWarning, len(warns)) + for i, w := range warns { + out[i] = templatesWarningFromModel(w) } + return stripExpiredLogLinks(out), nil + } +} + +// stripExpiredLogLinks clears the LogLink field for warnings whose logs have +// expired in-place and returns the input slice. +func stripExpiredLogLinks(warns []*TemplatesWarning) []*TemplatesWarning { + if !logs.ConfEnableMessageLogPurge.GetBool() { + return warns + } - return warns, nil + const logStorageDuration = 30 * 24 * time.Hour // TODO: does this constant already exist elsewhere? + + for _, entry := range warns { + if entry.LogsLink != "" && time.Since(entry.CreatedAt) > logStorageDuration { + entry.LogsLink = "" + } } + return warns } diff --git a/notifications/config.go b/notifications/config.go new file mode 100644 index 0000000000..bdcbed95b3 --- /dev/null +++ b/notifications/config.go @@ -0,0 +1,120 @@ +package notifications + +import ( + "fmt" + "strings" + "time" + + "github.com/botlabs-gg/yagpdb/v2/lib/discordgo" + "github.com/botlabs-gg/yagpdb/v2/notifications/models" + "github.com/botlabs-gg/yagpdb/v2/web" + "github.com/volatiletech/null/v8" +) + +// For legacy reasons, many columns in the database schema are marked as +// nullable when they should really be non-nullable, meaning working with +// models.GeneralNotificationConfig directly is much more annoying than it +// should be. We therefore wrap it in a Config (which has proper types) and +// convert to/from only when strictly required. + +type Config struct { + GuildID int64 + CreatedAt time.Time + UpdatedAt time.Time + + JoinServerEnabled bool `json:"join_server_enabled" schema:"join_server_enabled"` + JoinServerChannel int64 `json:"join_server_channel" schema:"join_server_channel" valid:"channel,true"` + + JoinServerMsgs []string `json:"join_server_msgs" schema:"join_server_msgs" valid:"template,5000"` + JoinDMEnabled bool `json:"join_dm_enabled" schema:"join_dm_enabled"` + JoinDMMsg string `json:"join_dm_msg" schema:"join_dm_msg" valid:"template,5000"` + + LeaveEnabled bool `json:"leave_enabled" schema:"leave_enabled"` + LeaveChannel int64 `json:"leave_channel" schema:"leave_channel" valid:"channel,true"` + LeaveMsgs []string `json:"leave_msgs" schema:"leave_msgs" valid:"template,5000"` + + TopicEnabled bool `json:"topic_enabled" schema:"topic_enabled"` + TopicChannel int64 `json:"topic_channel" schema:"topic_channel" valid:"channel,true"` + + CensorInvites bool `schema:"censor_invites"` +} + +var _ web.CustomValidator = (*Config)(nil) + +const MaxResponses = 10 + +func (c *Config) Validate(tmpl web.TemplateData, _ int64) bool { + if len(c.JoinServerMsgs) > MaxResponses { + tmpl.AddAlerts(web.ErrorAlert(fmt.Sprintf("Too many join server messages, max %d", MaxResponses))) + return false + } + if len(c.LeaveMsgs) > MaxResponses { + tmpl.AddAlerts(web.ErrorAlert(fmt.Sprintf("Too many leave server messages, max %d", MaxResponses))) + return false + } + return true +} + +// For legacy reasons, the JoinServerMsgs and LeaveMsgs columns are not stored +// as real TEXT[] columns in database but rather single TEXT columns separated +// by U+001E (INFORMATION SEPARATOR TWO.) + +const RecordSeparator = "\x1e" + +func readLegacyMultiResponseColumn(s string) []string { + return strings.Split(s, RecordSeparator) +} +func writeLegacyMultiResponseColumn(responses []string) string { + return strings.Join(responses, RecordSeparator) +} + +func (c *Config) ToModel() *models.GeneralNotificationConfig { + return &models.GeneralNotificationConfig{ + GuildID: c.GuildID, + CreatedAt: c.CreatedAt, + UpdatedAt: c.UpdatedAt, + + JoinServerEnabled: null.BoolFrom(c.JoinServerEnabled), + JoinServerChannel: null.StringFrom(discordgo.StrID(c.JoinServerChannel)), + + JoinServerMsgs: null.StringFrom(writeLegacyMultiResponseColumn(c.JoinServerMsgs)), + JoinDMEnabled: null.BoolFrom(c.JoinDMEnabled), + JoinDMMsg: null.StringFrom(c.JoinDMMsg), + + LeaveEnabled: null.BoolFrom(c.LeaveEnabled), + LeaveChannel: null.StringFrom(discordgo.StrID(c.LeaveChannel)), + LeaveMsgs: null.StringFrom(writeLegacyMultiResponseColumn(c.LeaveMsgs)), + + TopicEnabled: null.BoolFrom(c.TopicEnabled), + TopicChannel: null.StringFrom(discordgo.StrID(c.TopicChannel)), + + CensorInvites: null.BoolFrom(c.CensorInvites), + } +} + +func configFromModel(model *models.GeneralNotificationConfig) *Config { + joinServerChannel, _ := discordgo.ParseID(model.JoinServerChannel.String) + leaveChannel, _ := discordgo.ParseID(model.LeaveChannel.String) + topicChannel, _ := discordgo.ParseID(model.TopicChannel.String) + return &Config{ + GuildID: model.GuildID, + CreatedAt: model.CreatedAt, + UpdatedAt: model.UpdatedAt, + + JoinServerEnabled: model.JoinServerEnabled.Bool, + JoinServerChannel: joinServerChannel, + + JoinServerMsgs: readLegacyMultiResponseColumn(model.JoinServerMsgs.String), + JoinDMEnabled: model.JoinDMEnabled.Bool, + JoinDMMsg: model.JoinDMMsg.String, + + LeaveEnabled: model.LeaveEnabled.Bool, + LeaveChannel: leaveChannel, + LeaveMsgs: readLegacyMultiResponseColumn(model.LeaveMsgs.String), + + TopicEnabled: model.TopicEnabled.Bool, + TopicChannel: topicChannel, + + CensorInvites: model.CensorInvites.Bool, + } +} diff --git a/notifications/models/boil_queries.go b/notifications/models/boil_queries.go new file mode 100644 index 0000000000..20c2563fdb --- /dev/null +++ b/notifications/models/boil_queries.go @@ -0,0 +1,38 @@ +// Code generated by SQLBoiler 4.16.2 (https://github.com/volatiletech/sqlboiler). DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package models + +import ( + "regexp" + + "github.com/volatiletech/sqlboiler/v4/drivers" + "github.com/volatiletech/sqlboiler/v4/queries" + "github.com/volatiletech/sqlboiler/v4/queries/qm" +) + +var dialect = drivers.Dialect{ + LQ: 0x22, + RQ: 0x22, + + UseIndexPlaceholders: true, + UseLastInsertID: false, + UseSchema: false, + UseDefaultKeyword: true, + UseAutoColumns: false, + UseTopClause: false, + UseOutputClause: false, + UseCaseWhenExistsClause: false, +} + +// This is a dummy variable to prevent unused regexp import error +var _ = ®exp.Regexp{} + +// NewQuery initializes a new Query using the passed in QueryMods +func NewQuery(mods ...qm.QueryMod) *queries.Query { + q := &queries.Query{} + queries.SetDialect(q, &dialect) + qm.Apply(q, mods...) + + return q +} diff --git a/notifications/models/boil_table_names.go b/notifications/models/boil_table_names.go new file mode 100644 index 0000000000..f9897e9812 --- /dev/null +++ b/notifications/models/boil_table_names.go @@ -0,0 +1,10 @@ +// Code generated by SQLBoiler 4.16.2 (https://github.com/volatiletech/sqlboiler). DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package models + +var TableNames = struct { + GeneralNotificationConfigs string +}{ + GeneralNotificationConfigs: "general_notification_configs", +} diff --git a/notifications/models/boil_types.go b/notifications/models/boil_types.go new file mode 100644 index 0000000000..02a6fdfdc5 --- /dev/null +++ b/notifications/models/boil_types.go @@ -0,0 +1,52 @@ +// Code generated by SQLBoiler 4.16.2 (https://github.com/volatiletech/sqlboiler). DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package models + +import ( + "strconv" + + "github.com/friendsofgo/errors" + "github.com/volatiletech/sqlboiler/v4/boil" + "github.com/volatiletech/strmangle" +) + +// M type is for providing columns and column values to UpdateAll. +type M map[string]interface{} + +// ErrSyncFail occurs during insert when the record could not be retrieved in +// order to populate default value information. This usually happens when LastInsertId +// fails or there was a primary key configuration that was not resolvable. +var ErrSyncFail = errors.New("models: failed to synchronize data after insert") + +type insertCache struct { + query string + retQuery string + valueMapping []uint64 + retMapping []uint64 +} + +type updateCache struct { + query string + valueMapping []uint64 +} + +func makeCacheKey(cols boil.Columns, nzDefaults []string) string { + buf := strmangle.GetBuffer() + + buf.WriteString(strconv.Itoa(cols.Kind)) + for _, w := range cols.Cols { + buf.WriteString(w) + } + + if len(nzDefaults) != 0 { + buf.WriteByte('.') + } + for _, nz := range nzDefaults { + buf.WriteString(nz) + } + + str := buf.String() + strmangle.PutBuffer(buf) + return str +} diff --git a/notifications/models/boil_view_names.go b/notifications/models/boil_view_names.go new file mode 100644 index 0000000000..01504d82bf --- /dev/null +++ b/notifications/models/boil_view_names.go @@ -0,0 +1,7 @@ +// Code generated by SQLBoiler 4.16.2 (https://github.com/volatiletech/sqlboiler). DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package models + +var ViewNames = struct { +}{} diff --git a/notifications/models/general_notification_configs.go b/notifications/models/general_notification_configs.go new file mode 100644 index 0000000000..15ab99d5cd --- /dev/null +++ b/notifications/models/general_notification_configs.go @@ -0,0 +1,992 @@ +// Code generated by SQLBoiler 4.16.2 (https://github.com/volatiletech/sqlboiler). DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package models + +import ( + "context" + "database/sql" + "fmt" + "reflect" + "strconv" + "strings" + "sync" + "time" + + "github.com/friendsofgo/errors" + "github.com/volatiletech/null/v8" + "github.com/volatiletech/sqlboiler/v4/boil" + "github.com/volatiletech/sqlboiler/v4/queries" + "github.com/volatiletech/sqlboiler/v4/queries/qm" + "github.com/volatiletech/sqlboiler/v4/queries/qmhelper" + "github.com/volatiletech/strmangle" +) + +// GeneralNotificationConfig is an object representing the database table. +type GeneralNotificationConfig struct { + GuildID int64 `boil:"guild_id" json:"guild_id" toml:"guild_id" yaml:"guild_id"` + CreatedAt time.Time `boil:"created_at" json:"created_at" toml:"created_at" yaml:"created_at"` + UpdatedAt time.Time `boil:"updated_at" json:"updated_at" toml:"updated_at" yaml:"updated_at"` + JoinServerEnabled null.Bool `boil:"join_server_enabled" json:"join_server_enabled,omitempty" toml:"join_server_enabled" yaml:"join_server_enabled,omitempty"` + JoinServerChannel null.String `boil:"join_server_channel" json:"join_server_channel,omitempty" toml:"join_server_channel" yaml:"join_server_channel,omitempty"` + JoinServerMsgs null.String `boil:"join_server_msgs" json:"join_server_msgs,omitempty" toml:"join_server_msgs" yaml:"join_server_msgs,omitempty"` + JoinDMEnabled null.Bool `boil:"join_dm_enabled" json:"join_dm_enabled,omitempty" toml:"join_dm_enabled" yaml:"join_dm_enabled,omitempty"` + JoinDMMsg null.String `boil:"join_dm_msg" json:"join_dm_msg,omitempty" toml:"join_dm_msg" yaml:"join_dm_msg,omitempty"` + LeaveEnabled null.Bool `boil:"leave_enabled" json:"leave_enabled,omitempty" toml:"leave_enabled" yaml:"leave_enabled,omitempty"` + LeaveChannel null.String `boil:"leave_channel" json:"leave_channel,omitempty" toml:"leave_channel" yaml:"leave_channel,omitempty"` + LeaveMsgs null.String `boil:"leave_msgs" json:"leave_msgs,omitempty" toml:"leave_msgs" yaml:"leave_msgs,omitempty"` + TopicEnabled null.Bool `boil:"topic_enabled" json:"topic_enabled,omitempty" toml:"topic_enabled" yaml:"topic_enabled,omitempty"` + TopicChannel null.String `boil:"topic_channel" json:"topic_channel,omitempty" toml:"topic_channel" yaml:"topic_channel,omitempty"` + CensorInvites null.Bool `boil:"censor_invites" json:"censor_invites,omitempty" toml:"censor_invites" yaml:"censor_invites,omitempty"` + + R *generalNotificationConfigR `boil:"-" json:"-" toml:"-" yaml:"-"` + L generalNotificationConfigL `boil:"-" json:"-" toml:"-" yaml:"-"` +} + +var GeneralNotificationConfigColumns = struct { + GuildID string + CreatedAt string + UpdatedAt string + JoinServerEnabled string + JoinServerChannel string + JoinServerMsgs string + JoinDMEnabled string + JoinDMMsg string + LeaveEnabled string + LeaveChannel string + LeaveMsgs string + TopicEnabled string + TopicChannel string + CensorInvites string +}{ + GuildID: "guild_id", + CreatedAt: "created_at", + UpdatedAt: "updated_at", + JoinServerEnabled: "join_server_enabled", + JoinServerChannel: "join_server_channel", + JoinServerMsgs: "join_server_msgs", + JoinDMEnabled: "join_dm_enabled", + JoinDMMsg: "join_dm_msg", + LeaveEnabled: "leave_enabled", + LeaveChannel: "leave_channel", + LeaveMsgs: "leave_msgs", + TopicEnabled: "topic_enabled", + TopicChannel: "topic_channel", + CensorInvites: "censor_invites", +} + +var GeneralNotificationConfigTableColumns = struct { + GuildID string + CreatedAt string + UpdatedAt string + JoinServerEnabled string + JoinServerChannel string + JoinServerMsgs string + JoinDMEnabled string + JoinDMMsg string + LeaveEnabled string + LeaveChannel string + LeaveMsgs string + TopicEnabled string + TopicChannel string + CensorInvites string +}{ + GuildID: "general_notification_configs.guild_id", + CreatedAt: "general_notification_configs.created_at", + UpdatedAt: "general_notification_configs.updated_at", + JoinServerEnabled: "general_notification_configs.join_server_enabled", + JoinServerChannel: "general_notification_configs.join_server_channel", + JoinServerMsgs: "general_notification_configs.join_server_msgs", + JoinDMEnabled: "general_notification_configs.join_dm_enabled", + JoinDMMsg: "general_notification_configs.join_dm_msg", + LeaveEnabled: "general_notification_configs.leave_enabled", + LeaveChannel: "general_notification_configs.leave_channel", + LeaveMsgs: "general_notification_configs.leave_msgs", + TopicEnabled: "general_notification_configs.topic_enabled", + TopicChannel: "general_notification_configs.topic_channel", + CensorInvites: "general_notification_configs.censor_invites", +} + +// Generated where + +type whereHelperint64 struct{ field string } + +func (w whereHelperint64) EQ(x int64) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.EQ, x) } +func (w whereHelperint64) NEQ(x int64) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.NEQ, x) } +func (w whereHelperint64) LT(x int64) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.LT, x) } +func (w whereHelperint64) LTE(x int64) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.LTE, x) } +func (w whereHelperint64) GT(x int64) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.GT, x) } +func (w whereHelperint64) GTE(x int64) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.GTE, x) } +func (w whereHelperint64) IN(slice []int64) qm.QueryMod { + values := make([]interface{}, 0, len(slice)) + for _, value := range slice { + values = append(values, value) + } + return qm.WhereIn(fmt.Sprintf("%s IN ?", w.field), values...) +} +func (w whereHelperint64) NIN(slice []int64) qm.QueryMod { + values := make([]interface{}, 0, len(slice)) + for _, value := range slice { + values = append(values, value) + } + return qm.WhereNotIn(fmt.Sprintf("%s NOT IN ?", w.field), values...) +} + +type whereHelpertime_Time struct{ field string } + +func (w whereHelpertime_Time) EQ(x time.Time) qm.QueryMod { + return qmhelper.Where(w.field, qmhelper.EQ, x) +} +func (w whereHelpertime_Time) NEQ(x time.Time) qm.QueryMod { + return qmhelper.Where(w.field, qmhelper.NEQ, x) +} +func (w whereHelpertime_Time) LT(x time.Time) qm.QueryMod { + return qmhelper.Where(w.field, qmhelper.LT, x) +} +func (w whereHelpertime_Time) LTE(x time.Time) qm.QueryMod { + return qmhelper.Where(w.field, qmhelper.LTE, x) +} +func (w whereHelpertime_Time) GT(x time.Time) qm.QueryMod { + return qmhelper.Where(w.field, qmhelper.GT, x) +} +func (w whereHelpertime_Time) GTE(x time.Time) qm.QueryMod { + return qmhelper.Where(w.field, qmhelper.GTE, x) +} + +type whereHelpernull_Bool struct{ field string } + +func (w whereHelpernull_Bool) EQ(x null.Bool) qm.QueryMod { + return qmhelper.WhereNullEQ(w.field, false, x) +} +func (w whereHelpernull_Bool) NEQ(x null.Bool) qm.QueryMod { + return qmhelper.WhereNullEQ(w.field, true, x) +} +func (w whereHelpernull_Bool) LT(x null.Bool) qm.QueryMod { + return qmhelper.Where(w.field, qmhelper.LT, x) +} +func (w whereHelpernull_Bool) LTE(x null.Bool) qm.QueryMod { + return qmhelper.Where(w.field, qmhelper.LTE, x) +} +func (w whereHelpernull_Bool) GT(x null.Bool) qm.QueryMod { + return qmhelper.Where(w.field, qmhelper.GT, x) +} +func (w whereHelpernull_Bool) GTE(x null.Bool) qm.QueryMod { + return qmhelper.Where(w.field, qmhelper.GTE, x) +} + +func (w whereHelpernull_Bool) IsNull() qm.QueryMod { return qmhelper.WhereIsNull(w.field) } +func (w whereHelpernull_Bool) IsNotNull() qm.QueryMod { return qmhelper.WhereIsNotNull(w.field) } + +type whereHelpernull_String struct{ field string } + +func (w whereHelpernull_String) EQ(x null.String) qm.QueryMod { + return qmhelper.WhereNullEQ(w.field, false, x) +} +func (w whereHelpernull_String) NEQ(x null.String) qm.QueryMod { + return qmhelper.WhereNullEQ(w.field, true, x) +} +func (w whereHelpernull_String) LT(x null.String) qm.QueryMod { + return qmhelper.Where(w.field, qmhelper.LT, x) +} +func (w whereHelpernull_String) LTE(x null.String) qm.QueryMod { + return qmhelper.Where(w.field, qmhelper.LTE, x) +} +func (w whereHelpernull_String) GT(x null.String) qm.QueryMod { + return qmhelper.Where(w.field, qmhelper.GT, x) +} +func (w whereHelpernull_String) GTE(x null.String) qm.QueryMod { + return qmhelper.Where(w.field, qmhelper.GTE, x) +} +func (w whereHelpernull_String) LIKE(x null.String) qm.QueryMod { + return qm.Where(w.field+" LIKE ?", x) +} +func (w whereHelpernull_String) NLIKE(x null.String) qm.QueryMod { + return qm.Where(w.field+" NOT LIKE ?", x) +} +func (w whereHelpernull_String) ILIKE(x null.String) qm.QueryMod { + return qm.Where(w.field+" ILIKE ?", x) +} +func (w whereHelpernull_String) NILIKE(x null.String) qm.QueryMod { + return qm.Where(w.field+" NOT ILIKE ?", x) +} +func (w whereHelpernull_String) IN(slice []string) qm.QueryMod { + values := make([]interface{}, 0, len(slice)) + for _, value := range slice { + values = append(values, value) + } + return qm.WhereIn(fmt.Sprintf("%s IN ?", w.field), values...) +} +func (w whereHelpernull_String) NIN(slice []string) qm.QueryMod { + values := make([]interface{}, 0, len(slice)) + for _, value := range slice { + values = append(values, value) + } + return qm.WhereNotIn(fmt.Sprintf("%s NOT IN ?", w.field), values...) +} + +func (w whereHelpernull_String) IsNull() qm.QueryMod { return qmhelper.WhereIsNull(w.field) } +func (w whereHelpernull_String) IsNotNull() qm.QueryMod { return qmhelper.WhereIsNotNull(w.field) } + +var GeneralNotificationConfigWhere = struct { + GuildID whereHelperint64 + CreatedAt whereHelpertime_Time + UpdatedAt whereHelpertime_Time + JoinServerEnabled whereHelpernull_Bool + JoinServerChannel whereHelpernull_String + JoinServerMsgs whereHelpernull_String + JoinDMEnabled whereHelpernull_Bool + JoinDMMsg whereHelpernull_String + LeaveEnabled whereHelpernull_Bool + LeaveChannel whereHelpernull_String + LeaveMsgs whereHelpernull_String + TopicEnabled whereHelpernull_Bool + TopicChannel whereHelpernull_String + CensorInvites whereHelpernull_Bool +}{ + GuildID: whereHelperint64{field: "\"general_notification_configs\".\"guild_id\""}, + CreatedAt: whereHelpertime_Time{field: "\"general_notification_configs\".\"created_at\""}, + UpdatedAt: whereHelpertime_Time{field: "\"general_notification_configs\".\"updated_at\""}, + JoinServerEnabled: whereHelpernull_Bool{field: "\"general_notification_configs\".\"join_server_enabled\""}, + JoinServerChannel: whereHelpernull_String{field: "\"general_notification_configs\".\"join_server_channel\""}, + JoinServerMsgs: whereHelpernull_String{field: "\"general_notification_configs\".\"join_server_msgs\""}, + JoinDMEnabled: whereHelpernull_Bool{field: "\"general_notification_configs\".\"join_dm_enabled\""}, + JoinDMMsg: whereHelpernull_String{field: "\"general_notification_configs\".\"join_dm_msg\""}, + LeaveEnabled: whereHelpernull_Bool{field: "\"general_notification_configs\".\"leave_enabled\""}, + LeaveChannel: whereHelpernull_String{field: "\"general_notification_configs\".\"leave_channel\""}, + LeaveMsgs: whereHelpernull_String{field: "\"general_notification_configs\".\"leave_msgs\""}, + TopicEnabled: whereHelpernull_Bool{field: "\"general_notification_configs\".\"topic_enabled\""}, + TopicChannel: whereHelpernull_String{field: "\"general_notification_configs\".\"topic_channel\""}, + CensorInvites: whereHelpernull_Bool{field: "\"general_notification_configs\".\"censor_invites\""}, +} + +// GeneralNotificationConfigRels is where relationship names are stored. +var GeneralNotificationConfigRels = struct { +}{} + +// generalNotificationConfigR is where relationships are stored. +type generalNotificationConfigR struct { +} + +// NewStruct creates a new relationship struct +func (*generalNotificationConfigR) NewStruct() *generalNotificationConfigR { + return &generalNotificationConfigR{} +} + +// generalNotificationConfigL is where Load methods for each relationship are stored. +type generalNotificationConfigL struct{} + +var ( + generalNotificationConfigAllColumns = []string{"guild_id", "created_at", "updated_at", "join_server_enabled", "join_server_channel", "join_server_msgs", "join_dm_enabled", "join_dm_msg", "leave_enabled", "leave_channel", "leave_msgs", "topic_enabled", "topic_channel", "censor_invites"} + generalNotificationConfigColumnsWithoutDefault = []string{"guild_id", "created_at", "updated_at"} + generalNotificationConfigColumnsWithDefault = []string{"join_server_enabled", "join_server_channel", "join_server_msgs", "join_dm_enabled", "join_dm_msg", "leave_enabled", "leave_channel", "leave_msgs", "topic_enabled", "topic_channel", "censor_invites"} + generalNotificationConfigPrimaryKeyColumns = []string{"guild_id"} + generalNotificationConfigGeneratedColumns = []string{} +) + +type ( + // GeneralNotificationConfigSlice is an alias for a slice of pointers to GeneralNotificationConfig. + // This should almost always be used instead of []GeneralNotificationConfig. + GeneralNotificationConfigSlice []*GeneralNotificationConfig + + generalNotificationConfigQuery struct { + *queries.Query + } +) + +// Cache for insert, update and upsert +var ( + generalNotificationConfigType = reflect.TypeOf(&GeneralNotificationConfig{}) + generalNotificationConfigMapping = queries.MakeStructMapping(generalNotificationConfigType) + generalNotificationConfigPrimaryKeyMapping, _ = queries.BindMapping(generalNotificationConfigType, generalNotificationConfigMapping, generalNotificationConfigPrimaryKeyColumns) + generalNotificationConfigInsertCacheMut sync.RWMutex + generalNotificationConfigInsertCache = make(map[string]insertCache) + generalNotificationConfigUpdateCacheMut sync.RWMutex + generalNotificationConfigUpdateCache = make(map[string]updateCache) + generalNotificationConfigUpsertCacheMut sync.RWMutex + generalNotificationConfigUpsertCache = make(map[string]insertCache) +) + +var ( + // Force time package dependency for automated UpdatedAt/CreatedAt. + _ = time.Second + // Force qmhelper dependency for where clause generation (which doesn't + // always happen) + _ = qmhelper.Where +) + +// OneG returns a single generalNotificationConfig record from the query using the global executor. +func (q generalNotificationConfigQuery) OneG(ctx context.Context) (*GeneralNotificationConfig, error) { + return q.One(ctx, boil.GetContextDB()) +} + +// One returns a single generalNotificationConfig record from the query. +func (q generalNotificationConfigQuery) One(ctx context.Context, exec boil.ContextExecutor) (*GeneralNotificationConfig, error) { + o := &GeneralNotificationConfig{} + + queries.SetLimit(q.Query, 1) + + err := q.Bind(ctx, exec, o) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + return nil, sql.ErrNoRows + } + return nil, errors.Wrap(err, "models: failed to execute a one query for general_notification_configs") + } + + return o, nil +} + +// AllG returns all GeneralNotificationConfig records from the query using the global executor. +func (q generalNotificationConfigQuery) AllG(ctx context.Context) (GeneralNotificationConfigSlice, error) { + return q.All(ctx, boil.GetContextDB()) +} + +// All returns all GeneralNotificationConfig records from the query. +func (q generalNotificationConfigQuery) All(ctx context.Context, exec boil.ContextExecutor) (GeneralNotificationConfigSlice, error) { + var o []*GeneralNotificationConfig + + err := q.Bind(ctx, exec, &o) + if err != nil { + return nil, errors.Wrap(err, "models: failed to assign all query results to GeneralNotificationConfig slice") + } + + return o, nil +} + +// CountG returns the count of all GeneralNotificationConfig records in the query using the global executor +func (q generalNotificationConfigQuery) CountG(ctx context.Context) (int64, error) { + return q.Count(ctx, boil.GetContextDB()) +} + +// Count returns the count of all GeneralNotificationConfig records in the query. +func (q generalNotificationConfigQuery) Count(ctx context.Context, exec boil.ContextExecutor) (int64, error) { + var count int64 + + queries.SetSelect(q.Query, nil) + queries.SetCount(q.Query) + + err := q.Query.QueryRowContext(ctx, exec).Scan(&count) + if err != nil { + return 0, errors.Wrap(err, "models: failed to count general_notification_configs rows") + } + + return count, nil +} + +// ExistsG checks if the row exists in the table using the global executor. +func (q generalNotificationConfigQuery) ExistsG(ctx context.Context) (bool, error) { + return q.Exists(ctx, boil.GetContextDB()) +} + +// Exists checks if the row exists in the table. +func (q generalNotificationConfigQuery) Exists(ctx context.Context, exec boil.ContextExecutor) (bool, error) { + var count int64 + + queries.SetSelect(q.Query, nil) + queries.SetCount(q.Query) + queries.SetLimit(q.Query, 1) + + err := q.Query.QueryRowContext(ctx, exec).Scan(&count) + if err != nil { + return false, errors.Wrap(err, "models: failed to check if general_notification_configs exists") + } + + return count > 0, nil +} + +// GeneralNotificationConfigs retrieves all the records using an executor. +func GeneralNotificationConfigs(mods ...qm.QueryMod) generalNotificationConfigQuery { + mods = append(mods, qm.From("\"general_notification_configs\"")) + q := NewQuery(mods...) + if len(queries.GetSelect(q)) == 0 { + queries.SetSelect(q, []string{"\"general_notification_configs\".*"}) + } + + return generalNotificationConfigQuery{q} +} + +// FindGeneralNotificationConfigG retrieves a single record by ID. +func FindGeneralNotificationConfigG(ctx context.Context, guildID int64, selectCols ...string) (*GeneralNotificationConfig, error) { + return FindGeneralNotificationConfig(ctx, boil.GetContextDB(), guildID, selectCols...) +} + +// FindGeneralNotificationConfig retrieves a single record by ID with an executor. +// If selectCols is empty Find will return all columns. +func FindGeneralNotificationConfig(ctx context.Context, exec boil.ContextExecutor, guildID int64, selectCols ...string) (*GeneralNotificationConfig, error) { + generalNotificationConfigObj := &GeneralNotificationConfig{} + + sel := "*" + if len(selectCols) > 0 { + sel = strings.Join(strmangle.IdentQuoteSlice(dialect.LQ, dialect.RQ, selectCols), ",") + } + query := fmt.Sprintf( + "select %s from \"general_notification_configs\" where \"guild_id\"=$1", sel, + ) + + q := queries.Raw(query, guildID) + + err := q.Bind(ctx, exec, generalNotificationConfigObj) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + return nil, sql.ErrNoRows + } + return nil, errors.Wrap(err, "models: unable to select from general_notification_configs") + } + + return generalNotificationConfigObj, nil +} + +// InsertG a single record. See Insert for whitelist behavior description. +func (o *GeneralNotificationConfig) InsertG(ctx context.Context, columns boil.Columns) error { + return o.Insert(ctx, boil.GetContextDB(), columns) +} + +// Insert a single record using an executor. +// See boil.Columns.InsertColumnSet documentation to understand column list inference for inserts. +func (o *GeneralNotificationConfig) Insert(ctx context.Context, exec boil.ContextExecutor, columns boil.Columns) error { + if o == nil { + return errors.New("models: no general_notification_configs provided for insertion") + } + + var err error + if !boil.TimestampsAreSkipped(ctx) { + currTime := time.Now().In(boil.GetLocation()) + + if o.CreatedAt.IsZero() { + o.CreatedAt = currTime + } + if o.UpdatedAt.IsZero() { + o.UpdatedAt = currTime + } + } + + nzDefaults := queries.NonZeroDefaultSet(generalNotificationConfigColumnsWithDefault, o) + + key := makeCacheKey(columns, nzDefaults) + generalNotificationConfigInsertCacheMut.RLock() + cache, cached := generalNotificationConfigInsertCache[key] + generalNotificationConfigInsertCacheMut.RUnlock() + + if !cached { + wl, returnColumns := columns.InsertColumnSet( + generalNotificationConfigAllColumns, + generalNotificationConfigColumnsWithDefault, + generalNotificationConfigColumnsWithoutDefault, + nzDefaults, + ) + + cache.valueMapping, err = queries.BindMapping(generalNotificationConfigType, generalNotificationConfigMapping, wl) + if err != nil { + return err + } + cache.retMapping, err = queries.BindMapping(generalNotificationConfigType, generalNotificationConfigMapping, returnColumns) + if err != nil { + return err + } + if len(wl) != 0 { + cache.query = fmt.Sprintf("INSERT INTO \"general_notification_configs\" (\"%s\") %%sVALUES (%s)%%s", strings.Join(wl, "\",\""), strmangle.Placeholders(dialect.UseIndexPlaceholders, len(wl), 1, 1)) + } else { + cache.query = "INSERT INTO \"general_notification_configs\" %sDEFAULT VALUES%s" + } + + var queryOutput, queryReturning string + + if len(cache.retMapping) != 0 { + queryReturning = fmt.Sprintf(" RETURNING \"%s\"", strings.Join(returnColumns, "\",\"")) + } + + cache.query = fmt.Sprintf(cache.query, queryOutput, queryReturning) + } + + value := reflect.Indirect(reflect.ValueOf(o)) + vals := queries.ValuesFromMapping(value, cache.valueMapping) + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, cache.query) + fmt.Fprintln(writer, vals) + } + + if len(cache.retMapping) != 0 { + err = exec.QueryRowContext(ctx, cache.query, vals...).Scan(queries.PtrsFromMapping(value, cache.retMapping)...) + } else { + _, err = exec.ExecContext(ctx, cache.query, vals...) + } + + if err != nil { + return errors.Wrap(err, "models: unable to insert into general_notification_configs") + } + + if !cached { + generalNotificationConfigInsertCacheMut.Lock() + generalNotificationConfigInsertCache[key] = cache + generalNotificationConfigInsertCacheMut.Unlock() + } + + return nil +} + +// UpdateG a single GeneralNotificationConfig record using the global executor. +// See Update for more documentation. +func (o *GeneralNotificationConfig) UpdateG(ctx context.Context, columns boil.Columns) (int64, error) { + return o.Update(ctx, boil.GetContextDB(), columns) +} + +// Update uses an executor to update the GeneralNotificationConfig. +// See boil.Columns.UpdateColumnSet documentation to understand column list inference for updates. +// Update does not automatically update the record in case of default values. Use .Reload() to refresh the records. +func (o *GeneralNotificationConfig) Update(ctx context.Context, exec boil.ContextExecutor, columns boil.Columns) (int64, error) { + if !boil.TimestampsAreSkipped(ctx) { + currTime := time.Now().In(boil.GetLocation()) + + o.UpdatedAt = currTime + } + + var err error + key := makeCacheKey(columns, nil) + generalNotificationConfigUpdateCacheMut.RLock() + cache, cached := generalNotificationConfigUpdateCache[key] + generalNotificationConfigUpdateCacheMut.RUnlock() + + if !cached { + wl := columns.UpdateColumnSet( + generalNotificationConfigAllColumns, + generalNotificationConfigPrimaryKeyColumns, + ) + + if !columns.IsWhitelist() { + wl = strmangle.SetComplement(wl, []string{"created_at"}) + } + if len(wl) == 0 { + return 0, errors.New("models: unable to update general_notification_configs, could not build whitelist") + } + + cache.query = fmt.Sprintf("UPDATE \"general_notification_configs\" SET %s WHERE %s", + strmangle.SetParamNames("\"", "\"", 1, wl), + strmangle.WhereClause("\"", "\"", len(wl)+1, generalNotificationConfigPrimaryKeyColumns), + ) + cache.valueMapping, err = queries.BindMapping(generalNotificationConfigType, generalNotificationConfigMapping, append(wl, generalNotificationConfigPrimaryKeyColumns...)) + if err != nil { + return 0, err + } + } + + values := queries.ValuesFromMapping(reflect.Indirect(reflect.ValueOf(o)), cache.valueMapping) + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, cache.query) + fmt.Fprintln(writer, values) + } + var result sql.Result + result, err = exec.ExecContext(ctx, cache.query, values...) + if err != nil { + return 0, errors.Wrap(err, "models: unable to update general_notification_configs row") + } + + rowsAff, err := result.RowsAffected() + if err != nil { + return 0, errors.Wrap(err, "models: failed to get rows affected by update for general_notification_configs") + } + + if !cached { + generalNotificationConfigUpdateCacheMut.Lock() + generalNotificationConfigUpdateCache[key] = cache + generalNotificationConfigUpdateCacheMut.Unlock() + } + + return rowsAff, nil +} + +// UpdateAllG updates all rows with the specified column values. +func (q generalNotificationConfigQuery) UpdateAllG(ctx context.Context, cols M) (int64, error) { + return q.UpdateAll(ctx, boil.GetContextDB(), cols) +} + +// UpdateAll updates all rows with the specified column values. +func (q generalNotificationConfigQuery) UpdateAll(ctx context.Context, exec boil.ContextExecutor, cols M) (int64, error) { + queries.SetUpdate(q.Query, cols) + + result, err := q.Query.ExecContext(ctx, exec) + if err != nil { + return 0, errors.Wrap(err, "models: unable to update all for general_notification_configs") + } + + rowsAff, err := result.RowsAffected() + if err != nil { + return 0, errors.Wrap(err, "models: unable to retrieve rows affected for general_notification_configs") + } + + return rowsAff, nil +} + +// UpdateAllG updates all rows with the specified column values. +func (o GeneralNotificationConfigSlice) UpdateAllG(ctx context.Context, cols M) (int64, error) { + return o.UpdateAll(ctx, boil.GetContextDB(), cols) +} + +// UpdateAll updates all rows with the specified column values, using an executor. +func (o GeneralNotificationConfigSlice) UpdateAll(ctx context.Context, exec boil.ContextExecutor, cols M) (int64, error) { + ln := int64(len(o)) + if ln == 0 { + return 0, nil + } + + if len(cols) == 0 { + return 0, errors.New("models: update all requires at least one column argument") + } + + colNames := make([]string, len(cols)) + args := make([]interface{}, len(cols)) + + i := 0 + for name, value := range cols { + colNames[i] = name + args[i] = value + i++ + } + + // Append all of the primary key values for each column + for _, obj := range o { + pkeyArgs := queries.ValuesFromMapping(reflect.Indirect(reflect.ValueOf(obj)), generalNotificationConfigPrimaryKeyMapping) + args = append(args, pkeyArgs...) + } + + sql := fmt.Sprintf("UPDATE \"general_notification_configs\" SET %s WHERE %s", + strmangle.SetParamNames("\"", "\"", 1, colNames), + strmangle.WhereClauseRepeated(string(dialect.LQ), string(dialect.RQ), len(colNames)+1, generalNotificationConfigPrimaryKeyColumns, len(o))) + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, sql) + fmt.Fprintln(writer, args...) + } + result, err := exec.ExecContext(ctx, sql, args...) + if err != nil { + return 0, errors.Wrap(err, "models: unable to update all in generalNotificationConfig slice") + } + + rowsAff, err := result.RowsAffected() + if err != nil { + return 0, errors.Wrap(err, "models: unable to retrieve rows affected all in update all generalNotificationConfig") + } + return rowsAff, nil +} + +// UpsertG attempts an insert, and does an update or ignore on conflict. +func (o *GeneralNotificationConfig) UpsertG(ctx context.Context, updateOnConflict bool, conflictColumns []string, updateColumns, insertColumns boil.Columns, opts ...UpsertOptionFunc) error { + return o.Upsert(ctx, boil.GetContextDB(), updateOnConflict, conflictColumns, updateColumns, insertColumns, opts...) +} + +// Upsert attempts an insert using an executor, and does an update or ignore on conflict. +// See boil.Columns documentation for how to properly use updateColumns and insertColumns. +func (o *GeneralNotificationConfig) Upsert(ctx context.Context, exec boil.ContextExecutor, updateOnConflict bool, conflictColumns []string, updateColumns, insertColumns boil.Columns, opts ...UpsertOptionFunc) error { + if o == nil { + return errors.New("models: no general_notification_configs provided for upsert") + } + if !boil.TimestampsAreSkipped(ctx) { + currTime := time.Now().In(boil.GetLocation()) + + if o.CreatedAt.IsZero() { + o.CreatedAt = currTime + } + o.UpdatedAt = currTime + } + + nzDefaults := queries.NonZeroDefaultSet(generalNotificationConfigColumnsWithDefault, o) + + // Build cache key in-line uglily - mysql vs psql problems + buf := strmangle.GetBuffer() + if updateOnConflict { + buf.WriteByte('t') + } else { + buf.WriteByte('f') + } + buf.WriteByte('.') + for _, c := range conflictColumns { + buf.WriteString(c) + } + buf.WriteByte('.') + buf.WriteString(strconv.Itoa(updateColumns.Kind)) + for _, c := range updateColumns.Cols { + buf.WriteString(c) + } + buf.WriteByte('.') + buf.WriteString(strconv.Itoa(insertColumns.Kind)) + for _, c := range insertColumns.Cols { + buf.WriteString(c) + } + buf.WriteByte('.') + for _, c := range nzDefaults { + buf.WriteString(c) + } + key := buf.String() + strmangle.PutBuffer(buf) + + generalNotificationConfigUpsertCacheMut.RLock() + cache, cached := generalNotificationConfigUpsertCache[key] + generalNotificationConfigUpsertCacheMut.RUnlock() + + var err error + + if !cached { + insert, _ := insertColumns.InsertColumnSet( + generalNotificationConfigAllColumns, + generalNotificationConfigColumnsWithDefault, + generalNotificationConfigColumnsWithoutDefault, + nzDefaults, + ) + + update := updateColumns.UpdateColumnSet( + generalNotificationConfigAllColumns, + generalNotificationConfigPrimaryKeyColumns, + ) + + if updateOnConflict && len(update) == 0 { + return errors.New("models: unable to upsert general_notification_configs, could not build update column list") + } + + ret := strmangle.SetComplement(generalNotificationConfigAllColumns, strmangle.SetIntersect(insert, update)) + + conflict := conflictColumns + if len(conflict) == 0 && updateOnConflict && len(update) != 0 { + if len(generalNotificationConfigPrimaryKeyColumns) == 0 { + return errors.New("models: unable to upsert general_notification_configs, could not build conflict column list") + } + + conflict = make([]string, len(generalNotificationConfigPrimaryKeyColumns)) + copy(conflict, generalNotificationConfigPrimaryKeyColumns) + } + cache.query = buildUpsertQueryPostgres(dialect, "\"general_notification_configs\"", updateOnConflict, ret, update, conflict, insert, opts...) + + cache.valueMapping, err = queries.BindMapping(generalNotificationConfigType, generalNotificationConfigMapping, insert) + if err != nil { + return err + } + if len(ret) != 0 { + cache.retMapping, err = queries.BindMapping(generalNotificationConfigType, generalNotificationConfigMapping, ret) + if err != nil { + return err + } + } + } + + value := reflect.Indirect(reflect.ValueOf(o)) + vals := queries.ValuesFromMapping(value, cache.valueMapping) + var returns []interface{} + if len(cache.retMapping) != 0 { + returns = queries.PtrsFromMapping(value, cache.retMapping) + } + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, cache.query) + fmt.Fprintln(writer, vals) + } + if len(cache.retMapping) != 0 { + err = exec.QueryRowContext(ctx, cache.query, vals...).Scan(returns...) + if errors.Is(err, sql.ErrNoRows) { + err = nil // Postgres doesn't return anything when there's no update + } + } else { + _, err = exec.ExecContext(ctx, cache.query, vals...) + } + if err != nil { + return errors.Wrap(err, "models: unable to upsert general_notification_configs") + } + + if !cached { + generalNotificationConfigUpsertCacheMut.Lock() + generalNotificationConfigUpsertCache[key] = cache + generalNotificationConfigUpsertCacheMut.Unlock() + } + + return nil +} + +// DeleteG deletes a single GeneralNotificationConfig record. +// DeleteG will match against the primary key column to find the record to delete. +func (o *GeneralNotificationConfig) DeleteG(ctx context.Context) (int64, error) { + return o.Delete(ctx, boil.GetContextDB()) +} + +// Delete deletes a single GeneralNotificationConfig record with an executor. +// Delete will match against the primary key column to find the record to delete. +func (o *GeneralNotificationConfig) Delete(ctx context.Context, exec boil.ContextExecutor) (int64, error) { + if o == nil { + return 0, errors.New("models: no GeneralNotificationConfig provided for delete") + } + + args := queries.ValuesFromMapping(reflect.Indirect(reflect.ValueOf(o)), generalNotificationConfigPrimaryKeyMapping) + sql := "DELETE FROM \"general_notification_configs\" WHERE \"guild_id\"=$1" + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, sql) + fmt.Fprintln(writer, args...) + } + result, err := exec.ExecContext(ctx, sql, args...) + if err != nil { + return 0, errors.Wrap(err, "models: unable to delete from general_notification_configs") + } + + rowsAff, err := result.RowsAffected() + if err != nil { + return 0, errors.Wrap(err, "models: failed to get rows affected by delete for general_notification_configs") + } + + return rowsAff, nil +} + +func (q generalNotificationConfigQuery) DeleteAllG(ctx context.Context) (int64, error) { + return q.DeleteAll(ctx, boil.GetContextDB()) +} + +// DeleteAll deletes all matching rows. +func (q generalNotificationConfigQuery) DeleteAll(ctx context.Context, exec boil.ContextExecutor) (int64, error) { + if q.Query == nil { + return 0, errors.New("models: no generalNotificationConfigQuery provided for delete all") + } + + queries.SetDelete(q.Query) + + result, err := q.Query.ExecContext(ctx, exec) + if err != nil { + return 0, errors.Wrap(err, "models: unable to delete all from general_notification_configs") + } + + rowsAff, err := result.RowsAffected() + if err != nil { + return 0, errors.Wrap(err, "models: failed to get rows affected by deleteall for general_notification_configs") + } + + return rowsAff, nil +} + +// DeleteAllG deletes all rows in the slice. +func (o GeneralNotificationConfigSlice) DeleteAllG(ctx context.Context) (int64, error) { + return o.DeleteAll(ctx, boil.GetContextDB()) +} + +// DeleteAll deletes all rows in the slice, using an executor. +func (o GeneralNotificationConfigSlice) DeleteAll(ctx context.Context, exec boil.ContextExecutor) (int64, error) { + if len(o) == 0 { + return 0, nil + } + + var args []interface{} + for _, obj := range o { + pkeyArgs := queries.ValuesFromMapping(reflect.Indirect(reflect.ValueOf(obj)), generalNotificationConfigPrimaryKeyMapping) + args = append(args, pkeyArgs...) + } + + sql := "DELETE FROM \"general_notification_configs\" WHERE " + + strmangle.WhereClauseRepeated(string(dialect.LQ), string(dialect.RQ), 1, generalNotificationConfigPrimaryKeyColumns, len(o)) + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, sql) + fmt.Fprintln(writer, args) + } + result, err := exec.ExecContext(ctx, sql, args...) + if err != nil { + return 0, errors.Wrap(err, "models: unable to delete all from generalNotificationConfig slice") + } + + rowsAff, err := result.RowsAffected() + if err != nil { + return 0, errors.Wrap(err, "models: failed to get rows affected by deleteall for general_notification_configs") + } + + return rowsAff, nil +} + +// ReloadG refetches the object from the database using the primary keys. +func (o *GeneralNotificationConfig) ReloadG(ctx context.Context) error { + if o == nil { + return errors.New("models: no GeneralNotificationConfig provided for reload") + } + + return o.Reload(ctx, boil.GetContextDB()) +} + +// Reload refetches the object from the database +// using the primary keys with an executor. +func (o *GeneralNotificationConfig) Reload(ctx context.Context, exec boil.ContextExecutor) error { + ret, err := FindGeneralNotificationConfig(ctx, exec, o.GuildID) + if err != nil { + return err + } + + *o = *ret + return nil +} + +// ReloadAllG refetches every row with matching primary key column values +// and overwrites the original object slice with the newly updated slice. +func (o *GeneralNotificationConfigSlice) ReloadAllG(ctx context.Context) error { + if o == nil { + return errors.New("models: empty GeneralNotificationConfigSlice provided for reload all") + } + + return o.ReloadAll(ctx, boil.GetContextDB()) +} + +// ReloadAll refetches every row with matching primary key column values +// and overwrites the original object slice with the newly updated slice. +func (o *GeneralNotificationConfigSlice) ReloadAll(ctx context.Context, exec boil.ContextExecutor) error { + if o == nil || len(*o) == 0 { + return nil + } + + slice := GeneralNotificationConfigSlice{} + var args []interface{} + for _, obj := range *o { + pkeyArgs := queries.ValuesFromMapping(reflect.Indirect(reflect.ValueOf(obj)), generalNotificationConfigPrimaryKeyMapping) + args = append(args, pkeyArgs...) + } + + sql := "SELECT \"general_notification_configs\".* FROM \"general_notification_configs\" WHERE " + + strmangle.WhereClauseRepeated(string(dialect.LQ), string(dialect.RQ), 1, generalNotificationConfigPrimaryKeyColumns, len(*o)) + + q := queries.Raw(sql, args...) + + err := q.Bind(ctx, exec, &slice) + if err != nil { + return errors.Wrap(err, "models: unable to reload all in GeneralNotificationConfigSlice") + } + + *o = slice + + return nil +} + +// GeneralNotificationConfigExistsG checks if the GeneralNotificationConfig row exists. +func GeneralNotificationConfigExistsG(ctx context.Context, guildID int64) (bool, error) { + return GeneralNotificationConfigExists(ctx, boil.GetContextDB(), guildID) +} + +// GeneralNotificationConfigExists checks if the GeneralNotificationConfig row exists. +func GeneralNotificationConfigExists(ctx context.Context, exec boil.ContextExecutor, guildID int64) (bool, error) { + var exists bool + sql := "select exists(select 1 from \"general_notification_configs\" where \"guild_id\"=$1 limit 1)" + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, sql) + fmt.Fprintln(writer, guildID) + } + row := exec.QueryRowContext(ctx, sql, guildID) + + err := row.Scan(&exists) + if err != nil { + return false, errors.Wrap(err, "models: unable to check if general_notification_configs exists") + } + + return exists, nil +} + +// Exists checks if the GeneralNotificationConfig row exists. +func (o *GeneralNotificationConfig) Exists(ctx context.Context, exec boil.ContextExecutor) (bool, error) { + return GeneralNotificationConfigExists(ctx, exec, o.GuildID) +} diff --git a/notifications/models/psql_upsert.go b/notifications/models/psql_upsert.go new file mode 100644 index 0000000000..07602da9c5 --- /dev/null +++ b/notifications/models/psql_upsert.go @@ -0,0 +1,99 @@ +// Code generated by SQLBoiler 4.16.2 (https://github.com/volatiletech/sqlboiler). DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package models + +import ( + "fmt" + "strings" + + "github.com/volatiletech/sqlboiler/v4/drivers" + "github.com/volatiletech/strmangle" +) + +type UpsertOptions struct { + conflictTarget string + updateSet string +} + +type UpsertOptionFunc func(o *UpsertOptions) + +func UpsertConflictTarget(conflictTarget string) UpsertOptionFunc { + return func(o *UpsertOptions) { + o.conflictTarget = conflictTarget + } +} + +func UpsertUpdateSet(updateSet string) UpsertOptionFunc { + return func(o *UpsertOptions) { + o.updateSet = updateSet + } +} + +// buildUpsertQueryPostgres builds a SQL statement string using the upsertData provided. +func buildUpsertQueryPostgres(dia drivers.Dialect, tableName string, updateOnConflict bool, ret, update, conflict, whitelist []string, opts ...UpsertOptionFunc) string { + conflict = strmangle.IdentQuoteSlice(dia.LQ, dia.RQ, conflict) + whitelist = strmangle.IdentQuoteSlice(dia.LQ, dia.RQ, whitelist) + ret = strmangle.IdentQuoteSlice(dia.LQ, dia.RQ, ret) + + upsertOpts := &UpsertOptions{} + for _, o := range opts { + o(upsertOpts) + } + + buf := strmangle.GetBuffer() + defer strmangle.PutBuffer(buf) + + columns := "DEFAULT VALUES" + if len(whitelist) != 0 { + columns = fmt.Sprintf("(%s) VALUES (%s)", + strings.Join(whitelist, ", "), + strmangle.Placeholders(dia.UseIndexPlaceholders, len(whitelist), 1, 1)) + } + + fmt.Fprintf( + buf, + "INSERT INTO %s %s ON CONFLICT ", + tableName, + columns, + ) + + if upsertOpts.conflictTarget != "" { + buf.WriteString(upsertOpts.conflictTarget) + } else if len(conflict) != 0 { + buf.WriteByte('(') + buf.WriteString(strings.Join(conflict, ", ")) + buf.WriteByte(')') + } + buf.WriteByte(' ') + + if !updateOnConflict || len(update) == 0 { + buf.WriteString("DO NOTHING") + } else { + buf.WriteString("DO UPDATE SET ") + + if upsertOpts.updateSet != "" { + buf.WriteString(upsertOpts.updateSet) + } else { + for i, v := range update { + if len(v) == 0 { + continue + } + if i != 0 { + buf.WriteByte(',') + } + quoted := strmangle.IdentQuote(dia.LQ, dia.RQ, v) + buf.WriteString(quoted) + buf.WriteString(" = EXCLUDED.") + buf.WriteString(quoted) + } + } + } + + if len(ret) != 0 { + buf.WriteString(" RETURNING ") + buf.WriteString(strings.Join(ret, ", ")) + } + + return buf.String() +} diff --git a/notifications/notifications.go b/notifications/notifications.go index 097427849f..963197c63b 100644 --- a/notifications/notifications.go +++ b/notifications/notifications.go @@ -1,19 +1,10 @@ package notifications import ( - "strconv" - "strings" - - "emperror.dev/errors" "github.com/botlabs-gg/yagpdb/v2/common" - "github.com/botlabs-gg/yagpdb/v2/common/configstore" - "golang.org/x/net/context" ) -const ( - RecordSeparator = "\x1e" - MaxUserMessages = 10 -) +//go:generate sqlboiler --no-hooks psql var logger = common.GetPluginLogger(&Plugin{}) @@ -23,9 +14,7 @@ func RegisterPlugin() { plugin := &Plugin{} common.RegisterPlugin(plugin) - common.GORM.AutoMigrate(&Config{}) - configstore.RegisterConfig(configstore.SQL, &Config{}) - + common.InitSchemas("notifications", DBSchemas...) } func (p *Plugin) PluginInfo() *common.PluginInfo { @@ -35,133 +24,3 @@ func (p *Plugin) PluginInfo() *common.PluginInfo { Category: common.PluginCategoryFeeds, } } - -type Config struct { - configstore.GuildConfigModel - JoinServerEnabled bool `json:"join_server_enabled" schema:"join_server_enabled"` - JoinServerChannel string `json:"join_server_channel" schema:"join_server_channel" valid:"channel,true"` - - // Implementation note: gorilla/schema currently requires manual index - // setting in forms to parse sub-objects. GORM has_many is also complicated - // by manual handling of associations and loss of IDs through the web form - // (without which Replace() is currently n^2). - // For strings, we greatly simplify things by flattening for storage. - - // TODO: Remove the legacy single-message variant when ready to migrate the - // database. - JoinServerMsg string `json:"join_server_msg" valid:"template,5000"` - JoinServerMsgs []string `json:"join_server_msgs" schema:"join_server_msgs" gorm:"-" valid:"template,5000"` - // Do Not Use! For persistence only. - JoinServerMsgs_ string `json:"-"` - - JoinDMEnabled bool `json:"join_dm_enabled" schema:"join_dm_enabled"` - JoinDMMsg string `json:"join_dm_msg" schema:"join_dm_msg" valid:"template,5000"` - - LeaveEnabled bool `json:"leave_enabled" schema:"leave_enabled"` - LeaveChannel string `json:"leave_channel" schema:"leave_channel" valid:"channel,true"` - LeaveMsg string `json:"leave_msg" schema:"leave_msg" valid:"template,5000"` - LeaveMsgs []string `json:"leave_msgs" schema:"leave_msgs" gorm:"-" valid:"template,5000"` - // Do Not Use! For persistence only. - LeaveMsgs_ string `json:"-"` - - TopicEnabled bool `json:"topic_enabled" schema:"topic_enabled"` - TopicChannel string `json:"topic_channel" schema:"topic_channel" valid:"channel,true"` - - CensorInvites bool `schema:"censor_invites"` -} - -func (c *Config) JoinServerChannelInt() (i int64) { - i, _ = strconv.ParseInt(c.JoinServerChannel, 10, 64) - return -} - -func (c *Config) LeaveChannelInt() (i int64) { - i, _ = strconv.ParseInt(c.LeaveChannel, 10, 64) - return -} - -func (c *Config) TopicChannelInt() (i int64) { - i, _ = strconv.ParseInt(c.TopicChannel, 10, 64) - return -} - -func (c *Config) GetName() string { - return "general_notifications" -} - -func (c *Config) TableName() string { - return "general_notification_configs" -} - -// GORM BeforeSave hook -func (c *Config) BeforeSave() (err error) { - filterAndJoin := func(a []string) string { - joined := "" - msgsJoined := 0 - for _, s := range a { - if s == "" { - continue - } - if msgsJoined >= MaxUserMessages { - break - } - msgsJoined++ - - if len(joined) > 0 { - joined += RecordSeparator - } - - joined += s - } - - return joined - } - - c.JoinServerMsgs_ = filterAndJoin(c.JoinServerMsgs) - c.LeaveMsgs_ = filterAndJoin(c.LeaveMsgs) - - return nil -} - -// GORM AfterFind hook -func (c *Config) AfterFind() (err error) { - if c.JoinServerMsg != "" { - c.JoinServerMsgs = append(c.JoinServerMsgs, c.JoinServerMsg) - c.JoinServerMsg = "" - } - if c.JoinServerMsgs_ != "" { - c.JoinServerMsgs = append(c.JoinServerMsgs, strings.Split(c.JoinServerMsgs_, RecordSeparator)...) - } - - if c.LeaveMsg != "" { - c.LeaveMsgs = append(c.LeaveMsgs, c.LeaveMsg) - c.LeaveMsg = "" - } - if c.LeaveMsgs_ != "" { - c.LeaveMsgs = append(c.LeaveMsgs, strings.Split(c.LeaveMsgs_, RecordSeparator)...) - } - - return nil -} - -var DefaultConfig = &Config{} - -func GetConfig(guildID int64) (*Config, error) { - var conf Config - err := configstore.Cached.GetGuildConfig(context.Background(), guildID, &conf) - if err == nil { - return &conf, nil - } - - if err == configstore.ErrNotFound { - // if err != configstore.ErrNotFound { - // log.WithError(err).Error("Failed retrieving config") - // } - return &Config{ - JoinServerMsgs: []string{"<@{{.User.ID}}> Joined!"}, - LeaveMsgs: []string{"**{{.User.Username}}** Left... :'("}, - }, nil - } - - return nil, errors.WithStackIf(err) -} diff --git a/notifications/plugin_bot.go b/notifications/plugin_bot.go index 80422d57fc..62f0f36395 100644 --- a/notifications/plugin_bot.go +++ b/notifications/plugin_bot.go @@ -1,18 +1,25 @@ package notifications import ( + "context" + "database/sql" "fmt" "math/rand" "strings" + "time" "emperror.dev/errors" "github.com/botlabs-gg/yagpdb/v2/analytics" "github.com/botlabs-gg/yagpdb/v2/bot" "github.com/botlabs-gg/yagpdb/v2/bot/eventsystem" "github.com/botlabs-gg/yagpdb/v2/common" + "github.com/botlabs-gg/yagpdb/v2/common/pubsub" "github.com/botlabs-gg/yagpdb/v2/common/templates" "github.com/botlabs-gg/yagpdb/v2/lib/discordgo" "github.com/botlabs-gg/yagpdb/v2/lib/dstate" + "github.com/botlabs-gg/yagpdb/v2/notifications/models" + "github.com/karlseguin/ccache" + "github.com/volatiletech/sqlboiler/v4/boil" ) var _ bot.BotInitHandler = (*Plugin)(nil) @@ -21,12 +28,81 @@ func (p *Plugin) BotInit() { eventsystem.AddHandlerAsyncLast(p, HandleGuildMemberAdd, eventsystem.EventGuildMemberAdd) eventsystem.AddHandlerAsyncLast(p, HandleGuildMemberRemove, eventsystem.EventGuildMemberRemove) eventsystem.AddHandlerFirst(p, HandleChannelUpdate, eventsystem.EventChannelUpdate) + + pubsub.AddHandler("invalidate_notifications_config_cache", handleInvalidateConfigCache, nil) +} + +var configCache = ccache.New(ccache.Configure().MaxSize(15000)) + +func SaveConfig(config *Config) error { + err := config.ToModel().UpsertG(context.Background(), true, []string{"guild_id"}, boil.Infer(), boil.Infer()) + if err != nil { + return err + } + pubsub.Publish("invalidate_notifications_config_cache", config.GuildID, nil) + return nil +} + +func GetCachedConfigOrDefault(guildID int64) (*Config, error) { + const cacheDuration = 10 * time.Minute + + item, err := configCache.Fetch(cacheKey(guildID), cacheDuration, func() (interface{}, error) { + return GetConfig(guildID) + }) + if err != nil { + if err == sql.ErrNoRows { + return &Config{ + JoinServerMsgs: []string{"<@{{.User.ID}}> Joined!"}, + LeaveMsgs: []string{"**{{.User.Username}}** Left... :'("}, + }, nil + } + return nil, err + } + return item.Value().(*Config), nil +} + +func handleInvalidateConfigCache(evt *pubsub.Event) { + configCache.Delete(cacheKey(evt.TargetGuildInt)) +} + +func cacheKey(guildID int64) string { + return discordgo.StrID(guildID) +} + +func GetConfig(guildID int64) (*Config, error) { + const maxRetries = 1000 + + currentRetries := 0 + for { + conf, err := models.FindGeneralNotificationConfigG(context.Background(), guildID) + if err == nil { + if currentRetries > 1 { + logger.Info("Fetched config after ", currentRetries, " retries") + } + return configFromModel(conf), nil + } + + if err == sql.ErrNoRows { + return nil, err + } + + if strings.Contains(err.Error(), "sorry, too many clients already") { + time.Sleep(time.Millisecond * 10 * time.Duration(rand.Intn(10))) + currentRetries++ + if currentRetries > maxRetries { + return nil, err + } + continue + } + + return nil, err + } } func HandleGuildMemberAdd(evtData *eventsystem.EventData) (retry bool, err error) { evt := evtData.GuildMemberAdd() - config, err := GetConfig(evt.GuildID) + config, err := GetCachedConfigOrDefault(evt.GuildID) if err != nil { return true, errors.WithStackIf(err) } @@ -69,7 +145,7 @@ func HandleGuildMemberAdd(evtData *eventsystem.EventData) (retry bool, err error } if config.JoinServerEnabled && len(config.JoinServerMsgs) > 0 { - channel := gs.GetChannel(config.JoinServerChannelInt()) + channel := gs.GetChannel(config.JoinServerChannel) if channel == nil { return } @@ -88,7 +164,7 @@ func HandleGuildMemberAdd(evtData *eventsystem.EventData) (retry bool, err error func HandleGuildMemberRemove(evt *eventsystem.EventData) (retry bool, err error) { memberRemove := evt.GuildMemberRemove() - config, err := GetConfig(memberRemove.GuildID) + config, err := GetCachedConfigOrDefault(memberRemove.GuildID) if err != nil { return true, errors.WithStackIf(err) } @@ -102,7 +178,7 @@ func HandleGuildMemberRemove(evt *eventsystem.EventData) (retry bool, err error) return } - channel := gs.GetChannel(config.LeaveChannelInt()) + channel := gs.GetChannel(config.LeaveChannel) if channel == nil { return } @@ -230,7 +306,7 @@ func HandleChannelUpdate(evt *eventsystem.EventData) (retry bool, err error) { return } - config, err := GetConfig(cu.GuildID) + config, err := GetCachedConfigOrDefault(cu.GuildID) if err != nil { return true, errors.WithStackIf(err) } @@ -240,8 +316,8 @@ func HandleChannelUpdate(evt *eventsystem.EventData) (retry bool, err error) { } topicChannel := cu.Channel.ID - if config.TopicChannelInt() != 0 { - c := gs.GetChannel(config.TopicChannelInt()) + if config.TopicChannel != 0 { + c := gs.GetChannel(config.TopicChannel) if c != nil { topicChannel = c.ID } diff --git a/notifications/plugin_web.go b/notifications/plugin_web.go index 44a747e304..07b8ee9cce 100644 --- a/notifications/plugin_web.go +++ b/notifications/plugin_web.go @@ -7,7 +7,6 @@ import ( "net/http" "github.com/botlabs-gg/yagpdb/v2/common" - "github.com/botlabs-gg/yagpdb/v2/common/configstore" "github.com/botlabs-gg/yagpdb/v2/common/cplogs" "github.com/botlabs-gg/yagpdb/v2/lib/discordgo" "github.com/botlabs-gg/yagpdb/v2/web" @@ -45,7 +44,7 @@ func HandleNotificationsGet(w http.ResponseWriter, r *http.Request) interface{} if ok { templateData["NotifyConfig"] = formConfig } else { - conf, err := GetConfig(activeGuild.ID) + conf, err := GetCachedConfigOrDefault(activeGuild.ID) if err != nil { web.CtxLogger(r.Context()).WithError(err).Error("failed retrieving config") } @@ -64,8 +63,7 @@ func HandleNotificationsPost(w http.ResponseWriter, r *http.Request) (web.Templa newConfig := ctx.Value(common.ContextKeyParsedForm).(*Config) newConfig.GuildID = activeGuild.ID - - err := configstore.SQL.SetGuildConfig(ctx, newConfig) + err := SaveConfig(newConfig) if err != nil { return templateData, nil } @@ -83,7 +81,7 @@ func (p *Plugin) LoadServerHomeWidget(w http.ResponseWriter, r *http.Request) (w templateData["WidgetTitle"] = "General notifications" templateData["SettingsPath"] = "/notifications/general" - config, err := GetConfig(ag.ID) + config, err := GetCachedConfigOrDefault(ag.ID) if err != nil { return templateData, err } diff --git a/notifications/schema.go b/notifications/schema.go new file mode 100644 index 0000000000..686a05f85d --- /dev/null +++ b/notifications/schema.go @@ -0,0 +1,78 @@ +package notifications + +var DBSchemas = []string{` +CREATE TABLE IF NOT EXISTS general_notification_configs ( + guild_id BIGINT PRIMARY KEY, + created_at TIMESTAMP WITH TIME ZONE NOT NULL, + updated_at TIMESTAMP WITH TIME ZONE NOT NULL, + + -- Many of the following columns should be non-nullable, but were originally + -- managed by gorm (which does not add NOT NULL constraints by default) so are + -- missing them. Unfortunately, it is unfeasible to retroactively fill missing + -- values with defaults and add the constraints as there are simply too many + -- rows in production. + + -- For similar legacy reasons, many fields that should have type BIGINT are TEXT. + + join_server_enabled BOOLEAN, + join_server_channel TEXT, + -- This column should be a TEXT[]. But for legacy reasons, it is instead a single + -- TEXT column containing all template responses joined together and delimited by + -- the character U+001E (INFORMATION SEPARATOR TWO.) + join_server_msgs TEXT, + join_dm_enabled BOOLEAN, + join_dm_msg TEXT, + + leave_enabled BOOLEAN, + leave_channel TEXT, + -- Same deal as join_server_msgs. + leave_msgs TEXT, + + topic_enabled BOOLEAN, + topic_channel TEXT, + + censor_invites BOOLEAN +); +`, ` + +-- Tables created with gorm have missing NOT NULL constraints for created_at and +-- updated_at columns; since these columns are never null in existing rows, we can +-- retraoctively add the constraints without needing to update any data. + +ALTER TABLE general_notification_configs ALTER COLUMN created_at SET NOT NULL; +`, ` +ALTER TABLE general_notification_configs ALTER COLUMN updated_at SET NOT NULL; +`, ` + +-- Now the more complicated migration. For legacy reasons, the general_notification_configs +-- table previously contained two pairs of columns for join and leave message: +-- * join_server_msg, join_server_msgs_ +-- * leave_msg, leave_msgs_ +-- all of type TEXT. (The variants with _ were added when multiple-response support was +-- implemented and contain the individual responses separated by U+001E as described +-- previously.) + +-- Ideally, we only have one column for each message. We achieve this state with the following +-- multi-step migration: +-- 1. Update old records with join_server_msg != '' or leave_msg != '' to use the plural +-- join_server_msgs_ and leave_msgs_ columns respectively. +-- 2. Drop the join_server_msg and leave_msg columns. +-- 3. Rename join_server_msgs_ to join_server_msgs and leave_msgs_ to leave_msgs. + +-- Here goes. +DO $$ +BEGIN + +-- only run if general_notifcation_configs.join_server_msg (indicative of legacy table) exists +IF EXISTS(SELECT 1 FROM information_schema.columns WHERE table_name='general_notification_configs' AND column_name='join_server_msg') THEN + UPDATE general_notification_configs SET join_server_msgs_ = join_server_msg WHERE join_server_msg != ''; + UPDATE general_notification_configs SET leave_msgs_ = leave_msg WHERE leave_msg != ''; + + ALTER TABLE general_notification_configs DROP COLUMN join_server_msg; + ALTER TABLE general_notification_configs DROP COLUMN leave_msg; + + ALTER TABLE general_notification_configs RENAME COLUMN join_server_msgs_ to join_server_msgs; + ALTER TABLE general_notification_configs RENAME COLUMN leave_msgs_ to leave_msgs; +END IF; +END $$; +`} diff --git a/notifications/sqlboiler.toml b/notifications/sqlboiler.toml new file mode 100644 index 0000000000..47d4b92185 --- /dev/null +++ b/notifications/sqlboiler.toml @@ -0,0 +1,22 @@ +add-global-variants = true +no-hooks = true +no-tests = true + +[psql] +dbname = "yagpdb" +host = "localhost" +user = "postgres" +pass = "pass" +sslmode = "disable" +whitelist = ["general_notification_configs"] + +[auto-columns] +created = "created_at" +updated = "updated_at" + +# sqlboiler column name inference capitalizes MSG and MSGS so, for instance, +# leave_msgs becomes LeaveMSGS; manually override the names +[aliases.tables.general_notification_configs.columns] +join_server_msgs = "JoinServerMsgs" +join_dm_msg = "JoinDMMsg" +leave_msgs = "LeaveMsgs" diff --git a/reminders/models/boil_queries.go b/reminders/models/boil_queries.go new file mode 100644 index 0000000000..20c2563fdb --- /dev/null +++ b/reminders/models/boil_queries.go @@ -0,0 +1,38 @@ +// Code generated by SQLBoiler 4.16.2 (https://github.com/volatiletech/sqlboiler). DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package models + +import ( + "regexp" + + "github.com/volatiletech/sqlboiler/v4/drivers" + "github.com/volatiletech/sqlboiler/v4/queries" + "github.com/volatiletech/sqlboiler/v4/queries/qm" +) + +var dialect = drivers.Dialect{ + LQ: 0x22, + RQ: 0x22, + + UseIndexPlaceholders: true, + UseLastInsertID: false, + UseSchema: false, + UseDefaultKeyword: true, + UseAutoColumns: false, + UseTopClause: false, + UseOutputClause: false, + UseCaseWhenExistsClause: false, +} + +// This is a dummy variable to prevent unused regexp import error +var _ = ®exp.Regexp{} + +// NewQuery initializes a new Query using the passed in QueryMods +func NewQuery(mods ...qm.QueryMod) *queries.Query { + q := &queries.Query{} + queries.SetDialect(q, &dialect) + qm.Apply(q, mods...) + + return q +} diff --git a/reminders/models/boil_table_names.go b/reminders/models/boil_table_names.go new file mode 100644 index 0000000000..879f7cf0bd --- /dev/null +++ b/reminders/models/boil_table_names.go @@ -0,0 +1,10 @@ +// Code generated by SQLBoiler 4.16.2 (https://github.com/volatiletech/sqlboiler). DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package models + +var TableNames = struct { + Reminders string +}{ + Reminders: "reminders", +} diff --git a/reminders/models/boil_types.go b/reminders/models/boil_types.go new file mode 100644 index 0000000000..02a6fdfdc5 --- /dev/null +++ b/reminders/models/boil_types.go @@ -0,0 +1,52 @@ +// Code generated by SQLBoiler 4.16.2 (https://github.com/volatiletech/sqlboiler). DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package models + +import ( + "strconv" + + "github.com/friendsofgo/errors" + "github.com/volatiletech/sqlboiler/v4/boil" + "github.com/volatiletech/strmangle" +) + +// M type is for providing columns and column values to UpdateAll. +type M map[string]interface{} + +// ErrSyncFail occurs during insert when the record could not be retrieved in +// order to populate default value information. This usually happens when LastInsertId +// fails or there was a primary key configuration that was not resolvable. +var ErrSyncFail = errors.New("models: failed to synchronize data after insert") + +type insertCache struct { + query string + retQuery string + valueMapping []uint64 + retMapping []uint64 +} + +type updateCache struct { + query string + valueMapping []uint64 +} + +func makeCacheKey(cols boil.Columns, nzDefaults []string) string { + buf := strmangle.GetBuffer() + + buf.WriteString(strconv.Itoa(cols.Kind)) + for _, w := range cols.Cols { + buf.WriteString(w) + } + + if len(nzDefaults) != 0 { + buf.WriteByte('.') + } + for _, nz := range nzDefaults { + buf.WriteString(nz) + } + + str := buf.String() + strmangle.PutBuffer(buf) + return str +} diff --git a/reminders/models/boil_view_names.go b/reminders/models/boil_view_names.go new file mode 100644 index 0000000000..01504d82bf --- /dev/null +++ b/reminders/models/boil_view_names.go @@ -0,0 +1,7 @@ +// Code generated by SQLBoiler 4.16.2 (https://github.com/volatiletech/sqlboiler). DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package models + +var ViewNames = struct { +}{} diff --git a/reminders/models/psql_upsert.go b/reminders/models/psql_upsert.go new file mode 100644 index 0000000000..07602da9c5 --- /dev/null +++ b/reminders/models/psql_upsert.go @@ -0,0 +1,99 @@ +// Code generated by SQLBoiler 4.16.2 (https://github.com/volatiletech/sqlboiler). DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package models + +import ( + "fmt" + "strings" + + "github.com/volatiletech/sqlboiler/v4/drivers" + "github.com/volatiletech/strmangle" +) + +type UpsertOptions struct { + conflictTarget string + updateSet string +} + +type UpsertOptionFunc func(o *UpsertOptions) + +func UpsertConflictTarget(conflictTarget string) UpsertOptionFunc { + return func(o *UpsertOptions) { + o.conflictTarget = conflictTarget + } +} + +func UpsertUpdateSet(updateSet string) UpsertOptionFunc { + return func(o *UpsertOptions) { + o.updateSet = updateSet + } +} + +// buildUpsertQueryPostgres builds a SQL statement string using the upsertData provided. +func buildUpsertQueryPostgres(dia drivers.Dialect, tableName string, updateOnConflict bool, ret, update, conflict, whitelist []string, opts ...UpsertOptionFunc) string { + conflict = strmangle.IdentQuoteSlice(dia.LQ, dia.RQ, conflict) + whitelist = strmangle.IdentQuoteSlice(dia.LQ, dia.RQ, whitelist) + ret = strmangle.IdentQuoteSlice(dia.LQ, dia.RQ, ret) + + upsertOpts := &UpsertOptions{} + for _, o := range opts { + o(upsertOpts) + } + + buf := strmangle.GetBuffer() + defer strmangle.PutBuffer(buf) + + columns := "DEFAULT VALUES" + if len(whitelist) != 0 { + columns = fmt.Sprintf("(%s) VALUES (%s)", + strings.Join(whitelist, ", "), + strmangle.Placeholders(dia.UseIndexPlaceholders, len(whitelist), 1, 1)) + } + + fmt.Fprintf( + buf, + "INSERT INTO %s %s ON CONFLICT ", + tableName, + columns, + ) + + if upsertOpts.conflictTarget != "" { + buf.WriteString(upsertOpts.conflictTarget) + } else if len(conflict) != 0 { + buf.WriteByte('(') + buf.WriteString(strings.Join(conflict, ", ")) + buf.WriteByte(')') + } + buf.WriteByte(' ') + + if !updateOnConflict || len(update) == 0 { + buf.WriteString("DO NOTHING") + } else { + buf.WriteString("DO UPDATE SET ") + + if upsertOpts.updateSet != "" { + buf.WriteString(upsertOpts.updateSet) + } else { + for i, v := range update { + if len(v) == 0 { + continue + } + if i != 0 { + buf.WriteByte(',') + } + quoted := strmangle.IdentQuote(dia.LQ, dia.RQ, v) + buf.WriteString(quoted) + buf.WriteString(" = EXCLUDED.") + buf.WriteString(quoted) + } + } + } + + if len(ret) != 0 { + buf.WriteString(" RETURNING ") + buf.WriteString(strings.Join(ret, ", ")) + } + + return buf.String() +} diff --git a/reminders/models/reminders.go b/reminders/models/reminders.go new file mode 100644 index 0000000000..dd68b9f533 --- /dev/null +++ b/reminders/models/reminders.go @@ -0,0 +1,998 @@ +// Code generated by SQLBoiler 4.16.2 (https://github.com/volatiletech/sqlboiler). DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package models + +import ( + "context" + "database/sql" + "fmt" + "reflect" + "strconv" + "strings" + "sync" + "time" + + "github.com/friendsofgo/errors" + "github.com/volatiletech/null/v8" + "github.com/volatiletech/sqlboiler/v4/boil" + "github.com/volatiletech/sqlboiler/v4/queries" + "github.com/volatiletech/sqlboiler/v4/queries/qm" + "github.com/volatiletech/sqlboiler/v4/queries/qmhelper" + "github.com/volatiletech/strmangle" +) + +// Reminder is an object representing the database table. +type Reminder struct { + ID int `boil:"id" json:"id" toml:"id" yaml:"id"` + CreatedAt time.Time `boil:"created_at" json:"created_at" toml:"created_at" yaml:"created_at"` + UpdatedAt time.Time `boil:"updated_at" json:"updated_at" toml:"updated_at" yaml:"updated_at"` + DeletedAt null.Time `boil:"deleted_at" json:"deleted_at,omitempty" toml:"deleted_at" yaml:"deleted_at,omitempty"` + UserID string `boil:"user_id" json:"user_id" toml:"user_id" yaml:"user_id"` + ChannelID string `boil:"channel_id" json:"channel_id" toml:"channel_id" yaml:"channel_id"` + GuildID int64 `boil:"guild_id" json:"guild_id" toml:"guild_id" yaml:"guild_id"` + Message string `boil:"message" json:"message" toml:"message" yaml:"message"` + When int64 `boil:"when" json:"when" toml:"when" yaml:"when"` + + R *reminderR `boil:"-" json:"-" toml:"-" yaml:"-"` + L reminderL `boil:"-" json:"-" toml:"-" yaml:"-"` +} + +var ReminderColumns = struct { + ID string + CreatedAt string + UpdatedAt string + DeletedAt string + UserID string + ChannelID string + GuildID string + Message string + When string +}{ + ID: "id", + CreatedAt: "created_at", + UpdatedAt: "updated_at", + DeletedAt: "deleted_at", + UserID: "user_id", + ChannelID: "channel_id", + GuildID: "guild_id", + Message: "message", + When: "when", +} + +var ReminderTableColumns = struct { + ID string + CreatedAt string + UpdatedAt string + DeletedAt string + UserID string + ChannelID string + GuildID string + Message string + When string +}{ + ID: "reminders.id", + CreatedAt: "reminders.created_at", + UpdatedAt: "reminders.updated_at", + DeletedAt: "reminders.deleted_at", + UserID: "reminders.user_id", + ChannelID: "reminders.channel_id", + GuildID: "reminders.guild_id", + Message: "reminders.message", + When: "reminders.when", +} + +// Generated where + +type whereHelperint struct{ field string } + +func (w whereHelperint) EQ(x int) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.EQ, x) } +func (w whereHelperint) NEQ(x int) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.NEQ, x) } +func (w whereHelperint) LT(x int) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.LT, x) } +func (w whereHelperint) LTE(x int) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.LTE, x) } +func (w whereHelperint) GT(x int) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.GT, x) } +func (w whereHelperint) GTE(x int) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.GTE, x) } +func (w whereHelperint) IN(slice []int) qm.QueryMod { + values := make([]interface{}, 0, len(slice)) + for _, value := range slice { + values = append(values, value) + } + return qm.WhereIn(fmt.Sprintf("%s IN ?", w.field), values...) +} +func (w whereHelperint) NIN(slice []int) qm.QueryMod { + values := make([]interface{}, 0, len(slice)) + for _, value := range slice { + values = append(values, value) + } + return qm.WhereNotIn(fmt.Sprintf("%s NOT IN ?", w.field), values...) +} + +type whereHelpertime_Time struct{ field string } + +func (w whereHelpertime_Time) EQ(x time.Time) qm.QueryMod { + return qmhelper.Where(w.field, qmhelper.EQ, x) +} +func (w whereHelpertime_Time) NEQ(x time.Time) qm.QueryMod { + return qmhelper.Where(w.field, qmhelper.NEQ, x) +} +func (w whereHelpertime_Time) LT(x time.Time) qm.QueryMod { + return qmhelper.Where(w.field, qmhelper.LT, x) +} +func (w whereHelpertime_Time) LTE(x time.Time) qm.QueryMod { + return qmhelper.Where(w.field, qmhelper.LTE, x) +} +func (w whereHelpertime_Time) GT(x time.Time) qm.QueryMod { + return qmhelper.Where(w.field, qmhelper.GT, x) +} +func (w whereHelpertime_Time) GTE(x time.Time) qm.QueryMod { + return qmhelper.Where(w.field, qmhelper.GTE, x) +} + +type whereHelpernull_Time struct{ field string } + +func (w whereHelpernull_Time) EQ(x null.Time) qm.QueryMod { + return qmhelper.WhereNullEQ(w.field, false, x) +} +func (w whereHelpernull_Time) NEQ(x null.Time) qm.QueryMod { + return qmhelper.WhereNullEQ(w.field, true, x) +} +func (w whereHelpernull_Time) LT(x null.Time) qm.QueryMod { + return qmhelper.Where(w.field, qmhelper.LT, x) +} +func (w whereHelpernull_Time) LTE(x null.Time) qm.QueryMod { + return qmhelper.Where(w.field, qmhelper.LTE, x) +} +func (w whereHelpernull_Time) GT(x null.Time) qm.QueryMod { + return qmhelper.Where(w.field, qmhelper.GT, x) +} +func (w whereHelpernull_Time) GTE(x null.Time) qm.QueryMod { + return qmhelper.Where(w.field, qmhelper.GTE, x) +} + +func (w whereHelpernull_Time) IsNull() qm.QueryMod { return qmhelper.WhereIsNull(w.field) } +func (w whereHelpernull_Time) IsNotNull() qm.QueryMod { return qmhelper.WhereIsNotNull(w.field) } + +type whereHelperstring struct{ field string } + +func (w whereHelperstring) EQ(x string) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.EQ, x) } +func (w whereHelperstring) NEQ(x string) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.NEQ, x) } +func (w whereHelperstring) LT(x string) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.LT, x) } +func (w whereHelperstring) LTE(x string) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.LTE, x) } +func (w whereHelperstring) GT(x string) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.GT, x) } +func (w whereHelperstring) GTE(x string) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.GTE, x) } +func (w whereHelperstring) LIKE(x string) qm.QueryMod { return qm.Where(w.field+" LIKE ?", x) } +func (w whereHelperstring) NLIKE(x string) qm.QueryMod { return qm.Where(w.field+" NOT LIKE ?", x) } +func (w whereHelperstring) ILIKE(x string) qm.QueryMod { return qm.Where(w.field+" ILIKE ?", x) } +func (w whereHelperstring) NILIKE(x string) qm.QueryMod { return qm.Where(w.field+" NOT ILIKE ?", x) } +func (w whereHelperstring) IN(slice []string) qm.QueryMod { + values := make([]interface{}, 0, len(slice)) + for _, value := range slice { + values = append(values, value) + } + return qm.WhereIn(fmt.Sprintf("%s IN ?", w.field), values...) +} +func (w whereHelperstring) NIN(slice []string) qm.QueryMod { + values := make([]interface{}, 0, len(slice)) + for _, value := range slice { + values = append(values, value) + } + return qm.WhereNotIn(fmt.Sprintf("%s NOT IN ?", w.field), values...) +} + +type whereHelperint64 struct{ field string } + +func (w whereHelperint64) EQ(x int64) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.EQ, x) } +func (w whereHelperint64) NEQ(x int64) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.NEQ, x) } +func (w whereHelperint64) LT(x int64) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.LT, x) } +func (w whereHelperint64) LTE(x int64) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.LTE, x) } +func (w whereHelperint64) GT(x int64) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.GT, x) } +func (w whereHelperint64) GTE(x int64) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.GTE, x) } +func (w whereHelperint64) IN(slice []int64) qm.QueryMod { + values := make([]interface{}, 0, len(slice)) + for _, value := range slice { + values = append(values, value) + } + return qm.WhereIn(fmt.Sprintf("%s IN ?", w.field), values...) +} +func (w whereHelperint64) NIN(slice []int64) qm.QueryMod { + values := make([]interface{}, 0, len(slice)) + for _, value := range slice { + values = append(values, value) + } + return qm.WhereNotIn(fmt.Sprintf("%s NOT IN ?", w.field), values...) +} + +var ReminderWhere = struct { + ID whereHelperint + CreatedAt whereHelpertime_Time + UpdatedAt whereHelpertime_Time + DeletedAt whereHelpernull_Time + UserID whereHelperstring + ChannelID whereHelperstring + GuildID whereHelperint64 + Message whereHelperstring + When whereHelperint64 +}{ + ID: whereHelperint{field: "\"reminders\".\"id\""}, + CreatedAt: whereHelpertime_Time{field: "\"reminders\".\"created_at\""}, + UpdatedAt: whereHelpertime_Time{field: "\"reminders\".\"updated_at\""}, + DeletedAt: whereHelpernull_Time{field: "\"reminders\".\"deleted_at\""}, + UserID: whereHelperstring{field: "\"reminders\".\"user_id\""}, + ChannelID: whereHelperstring{field: "\"reminders\".\"channel_id\""}, + GuildID: whereHelperint64{field: "\"reminders\".\"guild_id\""}, + Message: whereHelperstring{field: "\"reminders\".\"message\""}, + When: whereHelperint64{field: "\"reminders\".\"when\""}, +} + +// ReminderRels is where relationship names are stored. +var ReminderRels = struct { +}{} + +// reminderR is where relationships are stored. +type reminderR struct { +} + +// NewStruct creates a new relationship struct +func (*reminderR) NewStruct() *reminderR { + return &reminderR{} +} + +// reminderL is where Load methods for each relationship are stored. +type reminderL struct{} + +var ( + reminderAllColumns = []string{"id", "created_at", "updated_at", "deleted_at", "user_id", "channel_id", "guild_id", "message", "when"} + reminderColumnsWithoutDefault = []string{"created_at", "updated_at", "user_id", "channel_id", "guild_id", "message", "when"} + reminderColumnsWithDefault = []string{"id", "deleted_at"} + reminderPrimaryKeyColumns = []string{"id"} + reminderGeneratedColumns = []string{} +) + +type ( + // ReminderSlice is an alias for a slice of pointers to Reminder. + // This should almost always be used instead of []Reminder. + ReminderSlice []*Reminder + + reminderQuery struct { + *queries.Query + } +) + +// Cache for insert, update and upsert +var ( + reminderType = reflect.TypeOf(&Reminder{}) + reminderMapping = queries.MakeStructMapping(reminderType) + reminderPrimaryKeyMapping, _ = queries.BindMapping(reminderType, reminderMapping, reminderPrimaryKeyColumns) + reminderInsertCacheMut sync.RWMutex + reminderInsertCache = make(map[string]insertCache) + reminderUpdateCacheMut sync.RWMutex + reminderUpdateCache = make(map[string]updateCache) + reminderUpsertCacheMut sync.RWMutex + reminderUpsertCache = make(map[string]insertCache) +) + +var ( + // Force time package dependency for automated UpdatedAt/CreatedAt. + _ = time.Second + // Force qmhelper dependency for where clause generation (which doesn't + // always happen) + _ = qmhelper.Where +) + +// OneG returns a single reminder record from the query using the global executor. +func (q reminderQuery) OneG(ctx context.Context) (*Reminder, error) { + return q.One(ctx, boil.GetContextDB()) +} + +// One returns a single reminder record from the query. +func (q reminderQuery) One(ctx context.Context, exec boil.ContextExecutor) (*Reminder, error) { + o := &Reminder{} + + queries.SetLimit(q.Query, 1) + + err := q.Bind(ctx, exec, o) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + return nil, sql.ErrNoRows + } + return nil, errors.Wrap(err, "models: failed to execute a one query for reminders") + } + + return o, nil +} + +// AllG returns all Reminder records from the query using the global executor. +func (q reminderQuery) AllG(ctx context.Context) (ReminderSlice, error) { + return q.All(ctx, boil.GetContextDB()) +} + +// All returns all Reminder records from the query. +func (q reminderQuery) All(ctx context.Context, exec boil.ContextExecutor) (ReminderSlice, error) { + var o []*Reminder + + err := q.Bind(ctx, exec, &o) + if err != nil { + return nil, errors.Wrap(err, "models: failed to assign all query results to Reminder slice") + } + + return o, nil +} + +// CountG returns the count of all Reminder records in the query using the global executor +func (q reminderQuery) CountG(ctx context.Context) (int64, error) { + return q.Count(ctx, boil.GetContextDB()) +} + +// Count returns the count of all Reminder records in the query. +func (q reminderQuery) Count(ctx context.Context, exec boil.ContextExecutor) (int64, error) { + var count int64 + + queries.SetSelect(q.Query, nil) + queries.SetCount(q.Query) + + err := q.Query.QueryRowContext(ctx, exec).Scan(&count) + if err != nil { + return 0, errors.Wrap(err, "models: failed to count reminders rows") + } + + return count, nil +} + +// ExistsG checks if the row exists in the table using the global executor. +func (q reminderQuery) ExistsG(ctx context.Context) (bool, error) { + return q.Exists(ctx, boil.GetContextDB()) +} + +// Exists checks if the row exists in the table. +func (q reminderQuery) Exists(ctx context.Context, exec boil.ContextExecutor) (bool, error) { + var count int64 + + queries.SetSelect(q.Query, nil) + queries.SetCount(q.Query) + queries.SetLimit(q.Query, 1) + + err := q.Query.QueryRowContext(ctx, exec).Scan(&count) + if err != nil { + return false, errors.Wrap(err, "models: failed to check if reminders exists") + } + + return count > 0, nil +} + +// Reminders retrieves all the records using an executor. +func Reminders(mods ...qm.QueryMod) reminderQuery { + mods = append(mods, qm.From("\"reminders\""), qmhelper.WhereIsNull("\"reminders\".\"deleted_at\"")) + q := NewQuery(mods...) + if len(queries.GetSelect(q)) == 0 { + queries.SetSelect(q, []string{"\"reminders\".*"}) + } + + return reminderQuery{q} +} + +// FindReminderG retrieves a single record by ID. +func FindReminderG(ctx context.Context, iD int, selectCols ...string) (*Reminder, error) { + return FindReminder(ctx, boil.GetContextDB(), iD, selectCols...) +} + +// FindReminder retrieves a single record by ID with an executor. +// If selectCols is empty Find will return all columns. +func FindReminder(ctx context.Context, exec boil.ContextExecutor, iD int, selectCols ...string) (*Reminder, error) { + reminderObj := &Reminder{} + + sel := "*" + if len(selectCols) > 0 { + sel = strings.Join(strmangle.IdentQuoteSlice(dialect.LQ, dialect.RQ, selectCols), ",") + } + query := fmt.Sprintf( + "select %s from \"reminders\" where \"id\"=$1 and \"deleted_at\" is null", sel, + ) + + q := queries.Raw(query, iD) + + err := q.Bind(ctx, exec, reminderObj) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + return nil, sql.ErrNoRows + } + return nil, errors.Wrap(err, "models: unable to select from reminders") + } + + return reminderObj, nil +} + +// InsertG a single record. See Insert for whitelist behavior description. +func (o *Reminder) InsertG(ctx context.Context, columns boil.Columns) error { + return o.Insert(ctx, boil.GetContextDB(), columns) +} + +// Insert a single record using an executor. +// See boil.Columns.InsertColumnSet documentation to understand column list inference for inserts. +func (o *Reminder) Insert(ctx context.Context, exec boil.ContextExecutor, columns boil.Columns) error { + if o == nil { + return errors.New("models: no reminders provided for insertion") + } + + var err error + if !boil.TimestampsAreSkipped(ctx) { + currTime := time.Now().In(boil.GetLocation()) + + if o.CreatedAt.IsZero() { + o.CreatedAt = currTime + } + if o.UpdatedAt.IsZero() { + o.UpdatedAt = currTime + } + } + + nzDefaults := queries.NonZeroDefaultSet(reminderColumnsWithDefault, o) + + key := makeCacheKey(columns, nzDefaults) + reminderInsertCacheMut.RLock() + cache, cached := reminderInsertCache[key] + reminderInsertCacheMut.RUnlock() + + if !cached { + wl, returnColumns := columns.InsertColumnSet( + reminderAllColumns, + reminderColumnsWithDefault, + reminderColumnsWithoutDefault, + nzDefaults, + ) + + cache.valueMapping, err = queries.BindMapping(reminderType, reminderMapping, wl) + if err != nil { + return err + } + cache.retMapping, err = queries.BindMapping(reminderType, reminderMapping, returnColumns) + if err != nil { + return err + } + if len(wl) != 0 { + cache.query = fmt.Sprintf("INSERT INTO \"reminders\" (\"%s\") %%sVALUES (%s)%%s", strings.Join(wl, "\",\""), strmangle.Placeholders(dialect.UseIndexPlaceholders, len(wl), 1, 1)) + } else { + cache.query = "INSERT INTO \"reminders\" %sDEFAULT VALUES%s" + } + + var queryOutput, queryReturning string + + if len(cache.retMapping) != 0 { + queryReturning = fmt.Sprintf(" RETURNING \"%s\"", strings.Join(returnColumns, "\",\"")) + } + + cache.query = fmt.Sprintf(cache.query, queryOutput, queryReturning) + } + + value := reflect.Indirect(reflect.ValueOf(o)) + vals := queries.ValuesFromMapping(value, cache.valueMapping) + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, cache.query) + fmt.Fprintln(writer, vals) + } + + if len(cache.retMapping) != 0 { + err = exec.QueryRowContext(ctx, cache.query, vals...).Scan(queries.PtrsFromMapping(value, cache.retMapping)...) + } else { + _, err = exec.ExecContext(ctx, cache.query, vals...) + } + + if err != nil { + return errors.Wrap(err, "models: unable to insert into reminders") + } + + if !cached { + reminderInsertCacheMut.Lock() + reminderInsertCache[key] = cache + reminderInsertCacheMut.Unlock() + } + + return nil +} + +// UpdateG a single Reminder record using the global executor. +// See Update for more documentation. +func (o *Reminder) UpdateG(ctx context.Context, columns boil.Columns) (int64, error) { + return o.Update(ctx, boil.GetContextDB(), columns) +} + +// Update uses an executor to update the Reminder. +// See boil.Columns.UpdateColumnSet documentation to understand column list inference for updates. +// Update does not automatically update the record in case of default values. Use .Reload() to refresh the records. +func (o *Reminder) Update(ctx context.Context, exec boil.ContextExecutor, columns boil.Columns) (int64, error) { + if !boil.TimestampsAreSkipped(ctx) { + currTime := time.Now().In(boil.GetLocation()) + + o.UpdatedAt = currTime + } + + var err error + key := makeCacheKey(columns, nil) + reminderUpdateCacheMut.RLock() + cache, cached := reminderUpdateCache[key] + reminderUpdateCacheMut.RUnlock() + + if !cached { + wl := columns.UpdateColumnSet( + reminderAllColumns, + reminderPrimaryKeyColumns, + ) + + if !columns.IsWhitelist() { + wl = strmangle.SetComplement(wl, []string{"created_at"}) + } + if len(wl) == 0 { + return 0, errors.New("models: unable to update reminders, could not build whitelist") + } + + cache.query = fmt.Sprintf("UPDATE \"reminders\" SET %s WHERE %s", + strmangle.SetParamNames("\"", "\"", 1, wl), + strmangle.WhereClause("\"", "\"", len(wl)+1, reminderPrimaryKeyColumns), + ) + cache.valueMapping, err = queries.BindMapping(reminderType, reminderMapping, append(wl, reminderPrimaryKeyColumns...)) + if err != nil { + return 0, err + } + } + + values := queries.ValuesFromMapping(reflect.Indirect(reflect.ValueOf(o)), cache.valueMapping) + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, cache.query) + fmt.Fprintln(writer, values) + } + var result sql.Result + result, err = exec.ExecContext(ctx, cache.query, values...) + if err != nil { + return 0, errors.Wrap(err, "models: unable to update reminders row") + } + + rowsAff, err := result.RowsAffected() + if err != nil { + return 0, errors.Wrap(err, "models: failed to get rows affected by update for reminders") + } + + if !cached { + reminderUpdateCacheMut.Lock() + reminderUpdateCache[key] = cache + reminderUpdateCacheMut.Unlock() + } + + return rowsAff, nil +} + +// UpdateAllG updates all rows with the specified column values. +func (q reminderQuery) UpdateAllG(ctx context.Context, cols M) (int64, error) { + return q.UpdateAll(ctx, boil.GetContextDB(), cols) +} + +// UpdateAll updates all rows with the specified column values. +func (q reminderQuery) UpdateAll(ctx context.Context, exec boil.ContextExecutor, cols M) (int64, error) { + queries.SetUpdate(q.Query, cols) + + result, err := q.Query.ExecContext(ctx, exec) + if err != nil { + return 0, errors.Wrap(err, "models: unable to update all for reminders") + } + + rowsAff, err := result.RowsAffected() + if err != nil { + return 0, errors.Wrap(err, "models: unable to retrieve rows affected for reminders") + } + + return rowsAff, nil +} + +// UpdateAllG updates all rows with the specified column values. +func (o ReminderSlice) UpdateAllG(ctx context.Context, cols M) (int64, error) { + return o.UpdateAll(ctx, boil.GetContextDB(), cols) +} + +// UpdateAll updates all rows with the specified column values, using an executor. +func (o ReminderSlice) UpdateAll(ctx context.Context, exec boil.ContextExecutor, cols M) (int64, error) { + ln := int64(len(o)) + if ln == 0 { + return 0, nil + } + + if len(cols) == 0 { + return 0, errors.New("models: update all requires at least one column argument") + } + + colNames := make([]string, len(cols)) + args := make([]interface{}, len(cols)) + + i := 0 + for name, value := range cols { + colNames[i] = name + args[i] = value + i++ + } + + // Append all of the primary key values for each column + for _, obj := range o { + pkeyArgs := queries.ValuesFromMapping(reflect.Indirect(reflect.ValueOf(obj)), reminderPrimaryKeyMapping) + args = append(args, pkeyArgs...) + } + + sql := fmt.Sprintf("UPDATE \"reminders\" SET %s WHERE %s", + strmangle.SetParamNames("\"", "\"", 1, colNames), + strmangle.WhereClauseRepeated(string(dialect.LQ), string(dialect.RQ), len(colNames)+1, reminderPrimaryKeyColumns, len(o))) + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, sql) + fmt.Fprintln(writer, args...) + } + result, err := exec.ExecContext(ctx, sql, args...) + if err != nil { + return 0, errors.Wrap(err, "models: unable to update all in reminder slice") + } + + rowsAff, err := result.RowsAffected() + if err != nil { + return 0, errors.Wrap(err, "models: unable to retrieve rows affected all in update all reminder") + } + return rowsAff, nil +} + +// UpsertG attempts an insert, and does an update or ignore on conflict. +func (o *Reminder) UpsertG(ctx context.Context, updateOnConflict bool, conflictColumns []string, updateColumns, insertColumns boil.Columns, opts ...UpsertOptionFunc) error { + return o.Upsert(ctx, boil.GetContextDB(), updateOnConflict, conflictColumns, updateColumns, insertColumns, opts...) +} + +// Upsert attempts an insert using an executor, and does an update or ignore on conflict. +// See boil.Columns documentation for how to properly use updateColumns and insertColumns. +func (o *Reminder) Upsert(ctx context.Context, exec boil.ContextExecutor, updateOnConflict bool, conflictColumns []string, updateColumns, insertColumns boil.Columns, opts ...UpsertOptionFunc) error { + if o == nil { + return errors.New("models: no reminders provided for upsert") + } + if !boil.TimestampsAreSkipped(ctx) { + currTime := time.Now().In(boil.GetLocation()) + + if o.CreatedAt.IsZero() { + o.CreatedAt = currTime + } + o.UpdatedAt = currTime + } + + nzDefaults := queries.NonZeroDefaultSet(reminderColumnsWithDefault, o) + + // Build cache key in-line uglily - mysql vs psql problems + buf := strmangle.GetBuffer() + if updateOnConflict { + buf.WriteByte('t') + } else { + buf.WriteByte('f') + } + buf.WriteByte('.') + for _, c := range conflictColumns { + buf.WriteString(c) + } + buf.WriteByte('.') + buf.WriteString(strconv.Itoa(updateColumns.Kind)) + for _, c := range updateColumns.Cols { + buf.WriteString(c) + } + buf.WriteByte('.') + buf.WriteString(strconv.Itoa(insertColumns.Kind)) + for _, c := range insertColumns.Cols { + buf.WriteString(c) + } + buf.WriteByte('.') + for _, c := range nzDefaults { + buf.WriteString(c) + } + key := buf.String() + strmangle.PutBuffer(buf) + + reminderUpsertCacheMut.RLock() + cache, cached := reminderUpsertCache[key] + reminderUpsertCacheMut.RUnlock() + + var err error + + if !cached { + insert, _ := insertColumns.InsertColumnSet( + reminderAllColumns, + reminderColumnsWithDefault, + reminderColumnsWithoutDefault, + nzDefaults, + ) + + update := updateColumns.UpdateColumnSet( + reminderAllColumns, + reminderPrimaryKeyColumns, + ) + + if updateOnConflict && len(update) == 0 { + return errors.New("models: unable to upsert reminders, could not build update column list") + } + + ret := strmangle.SetComplement(reminderAllColumns, strmangle.SetIntersect(insert, update)) + + conflict := conflictColumns + if len(conflict) == 0 && updateOnConflict && len(update) != 0 { + if len(reminderPrimaryKeyColumns) == 0 { + return errors.New("models: unable to upsert reminders, could not build conflict column list") + } + + conflict = make([]string, len(reminderPrimaryKeyColumns)) + copy(conflict, reminderPrimaryKeyColumns) + } + cache.query = buildUpsertQueryPostgres(dialect, "\"reminders\"", updateOnConflict, ret, update, conflict, insert, opts...) + + cache.valueMapping, err = queries.BindMapping(reminderType, reminderMapping, insert) + if err != nil { + return err + } + if len(ret) != 0 { + cache.retMapping, err = queries.BindMapping(reminderType, reminderMapping, ret) + if err != nil { + return err + } + } + } + + value := reflect.Indirect(reflect.ValueOf(o)) + vals := queries.ValuesFromMapping(value, cache.valueMapping) + var returns []interface{} + if len(cache.retMapping) != 0 { + returns = queries.PtrsFromMapping(value, cache.retMapping) + } + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, cache.query) + fmt.Fprintln(writer, vals) + } + if len(cache.retMapping) != 0 { + err = exec.QueryRowContext(ctx, cache.query, vals...).Scan(returns...) + if errors.Is(err, sql.ErrNoRows) { + err = nil // Postgres doesn't return anything when there's no update + } + } else { + _, err = exec.ExecContext(ctx, cache.query, vals...) + } + if err != nil { + return errors.Wrap(err, "models: unable to upsert reminders") + } + + if !cached { + reminderUpsertCacheMut.Lock() + reminderUpsertCache[key] = cache + reminderUpsertCacheMut.Unlock() + } + + return nil +} + +// DeleteG deletes a single Reminder record. +// DeleteG will match against the primary key column to find the record to delete. +func (o *Reminder) DeleteG(ctx context.Context, hardDelete bool) (int64, error) { + return o.Delete(ctx, boil.GetContextDB(), hardDelete) +} + +// Delete deletes a single Reminder record with an executor. +// Delete will match against the primary key column to find the record to delete. +func (o *Reminder) Delete(ctx context.Context, exec boil.ContextExecutor, hardDelete bool) (int64, error) { + if o == nil { + return 0, errors.New("models: no Reminder provided for delete") + } + + var ( + sql string + args []interface{} + ) + if hardDelete { + args = queries.ValuesFromMapping(reflect.Indirect(reflect.ValueOf(o)), reminderPrimaryKeyMapping) + sql = "DELETE FROM \"reminders\" WHERE \"id\"=$1" + } else { + currTime := time.Now().In(boil.GetLocation()) + o.DeletedAt = null.TimeFrom(currTime) + wl := []string{"deleted_at"} + sql = fmt.Sprintf("UPDATE \"reminders\" SET %s WHERE \"id\"=$2", + strmangle.SetParamNames("\"", "\"", 1, wl), + ) + valueMapping, err := queries.BindMapping(reminderType, reminderMapping, append(wl, reminderPrimaryKeyColumns...)) + if err != nil { + return 0, err + } + args = queries.ValuesFromMapping(reflect.Indirect(reflect.ValueOf(o)), valueMapping) + } + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, sql) + fmt.Fprintln(writer, args...) + } + result, err := exec.ExecContext(ctx, sql, args...) + if err != nil { + return 0, errors.Wrap(err, "models: unable to delete from reminders") + } + + rowsAff, err := result.RowsAffected() + if err != nil { + return 0, errors.Wrap(err, "models: failed to get rows affected by delete for reminders") + } + + return rowsAff, nil +} + +func (q reminderQuery) DeleteAllG(ctx context.Context, hardDelete bool) (int64, error) { + return q.DeleteAll(ctx, boil.GetContextDB(), hardDelete) +} + +// DeleteAll deletes all matching rows. +func (q reminderQuery) DeleteAll(ctx context.Context, exec boil.ContextExecutor, hardDelete bool) (int64, error) { + if q.Query == nil { + return 0, errors.New("models: no reminderQuery provided for delete all") + } + + if hardDelete { + queries.SetDelete(q.Query) + } else { + currTime := time.Now().In(boil.GetLocation()) + queries.SetUpdate(q.Query, M{"deleted_at": currTime}) + } + + result, err := q.Query.ExecContext(ctx, exec) + if err != nil { + return 0, errors.Wrap(err, "models: unable to delete all from reminders") + } + + rowsAff, err := result.RowsAffected() + if err != nil { + return 0, errors.Wrap(err, "models: failed to get rows affected by deleteall for reminders") + } + + return rowsAff, nil +} + +// DeleteAllG deletes all rows in the slice. +func (o ReminderSlice) DeleteAllG(ctx context.Context, hardDelete bool) (int64, error) { + return o.DeleteAll(ctx, boil.GetContextDB(), hardDelete) +} + +// DeleteAll deletes all rows in the slice, using an executor. +func (o ReminderSlice) DeleteAll(ctx context.Context, exec boil.ContextExecutor, hardDelete bool) (int64, error) { + if len(o) == 0 { + return 0, nil + } + + var ( + sql string + args []interface{} + ) + if hardDelete { + for _, obj := range o { + pkeyArgs := queries.ValuesFromMapping(reflect.Indirect(reflect.ValueOf(obj)), reminderPrimaryKeyMapping) + args = append(args, pkeyArgs...) + } + sql = "DELETE FROM \"reminders\" WHERE " + + strmangle.WhereClauseRepeated(string(dialect.LQ), string(dialect.RQ), 1, reminderPrimaryKeyColumns, len(o)) + } else { + currTime := time.Now().In(boil.GetLocation()) + for _, obj := range o { + pkeyArgs := queries.ValuesFromMapping(reflect.Indirect(reflect.ValueOf(obj)), reminderPrimaryKeyMapping) + args = append(args, pkeyArgs...) + obj.DeletedAt = null.TimeFrom(currTime) + } + wl := []string{"deleted_at"} + sql = fmt.Sprintf("UPDATE \"reminders\" SET %s WHERE "+ + strmangle.WhereClauseRepeated(string(dialect.LQ), string(dialect.RQ), 2, reminderPrimaryKeyColumns, len(o)), + strmangle.SetParamNames("\"", "\"", 1, wl), + ) + args = append([]interface{}{currTime}, args...) + } + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, sql) + fmt.Fprintln(writer, args) + } + result, err := exec.ExecContext(ctx, sql, args...) + if err != nil { + return 0, errors.Wrap(err, "models: unable to delete all from reminder slice") + } + + rowsAff, err := result.RowsAffected() + if err != nil { + return 0, errors.Wrap(err, "models: failed to get rows affected by deleteall for reminders") + } + + return rowsAff, nil +} + +// ReloadG refetches the object from the database using the primary keys. +func (o *Reminder) ReloadG(ctx context.Context) error { + if o == nil { + return errors.New("models: no Reminder provided for reload") + } + + return o.Reload(ctx, boil.GetContextDB()) +} + +// Reload refetches the object from the database +// using the primary keys with an executor. +func (o *Reminder) Reload(ctx context.Context, exec boil.ContextExecutor) error { + ret, err := FindReminder(ctx, exec, o.ID) + if err != nil { + return err + } + + *o = *ret + return nil +} + +// ReloadAllG refetches every row with matching primary key column values +// and overwrites the original object slice with the newly updated slice. +func (o *ReminderSlice) ReloadAllG(ctx context.Context) error { + if o == nil { + return errors.New("models: empty ReminderSlice provided for reload all") + } + + return o.ReloadAll(ctx, boil.GetContextDB()) +} + +// ReloadAll refetches every row with matching primary key column values +// and overwrites the original object slice with the newly updated slice. +func (o *ReminderSlice) ReloadAll(ctx context.Context, exec boil.ContextExecutor) error { + if o == nil || len(*o) == 0 { + return nil + } + + slice := ReminderSlice{} + var args []interface{} + for _, obj := range *o { + pkeyArgs := queries.ValuesFromMapping(reflect.Indirect(reflect.ValueOf(obj)), reminderPrimaryKeyMapping) + args = append(args, pkeyArgs...) + } + + sql := "SELECT \"reminders\".* FROM \"reminders\" WHERE " + + strmangle.WhereClauseRepeated(string(dialect.LQ), string(dialect.RQ), 1, reminderPrimaryKeyColumns, len(*o)) + + "and \"deleted_at\" is null" + + q := queries.Raw(sql, args...) + + err := q.Bind(ctx, exec, &slice) + if err != nil { + return errors.Wrap(err, "models: unable to reload all in ReminderSlice") + } + + *o = slice + + return nil +} + +// ReminderExistsG checks if the Reminder row exists. +func ReminderExistsG(ctx context.Context, iD int) (bool, error) { + return ReminderExists(ctx, boil.GetContextDB(), iD) +} + +// ReminderExists checks if the Reminder row exists. +func ReminderExists(ctx context.Context, exec boil.ContextExecutor, iD int) (bool, error) { + var exists bool + sql := "select exists(select 1 from \"reminders\" where \"id\"=$1 and \"deleted_at\" is null limit 1)" + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, sql) + fmt.Fprintln(writer, iD) + } + row := exec.QueryRowContext(ctx, sql, iD) + + err := row.Scan(&exists) + if err != nil { + return false, errors.Wrap(err, "models: unable to check if reminders exists") + } + + return exists, nil +} + +// Exists checks if the Reminder row exists. +func (o *Reminder) Exists(ctx context.Context, exec boil.ContextExecutor) (bool, error) { + return ReminderExists(ctx, exec, o.ID) +} diff --git a/reminders/plugin_bot.go b/reminders/plugin_bot.go index 83af5579b0..354237b70a 100644 --- a/reminders/plugin_bot.go +++ b/reminders/plugin_bot.go @@ -1,12 +1,12 @@ package reminders import ( + "context" + "database/sql" "errors" "fmt" - "strconv" "strings" "time" - "unicode/utf8" "github.com/botlabs-gg/yagpdb/v2/bot" "github.com/botlabs-gg/yagpdb/v2/commands" @@ -16,7 +16,8 @@ import ( "github.com/botlabs-gg/yagpdb/v2/lib/dcmd" "github.com/botlabs-gg/yagpdb/v2/lib/discordgo" "github.com/botlabs-gg/yagpdb/v2/lib/dstate" - "github.com/jinzhu/gorm" + "github.com/botlabs-gg/yagpdb/v2/reminders/models" + "github.com/volatiletech/sqlboiler/v4/queries/qm" ) var logger = common.GetPluginLogger(&Plugin{}) @@ -29,11 +30,17 @@ func (p *Plugin) AddCommands() { } func (p *Plugin) BotInit() { - // scheduledevents.RegisterEventHandler("reminders_check_user", checkUserEvtHandlerLegacy) scheduledevents2.RegisterHandler("reminders_check_user", int64(0), checkUserScheduledEvent) scheduledevents2.RegisterLegacyMigrater("reminders_check_user", migrateLegacyScheduledEvents) } +const ( + MaxReminders = 25 + + MaxReminderOffset = time.Hour * 24 * 366 + MaxReminderOffsetExceededMsg = "Can be max 1 year from now..." +) + // Reminder management commands var cmds = []*commands.YAGCommand{ { @@ -52,23 +59,19 @@ var cmds = []*commands.YAGCommand{ SlashCommandEnabled: true, DefaultEnabled: true, RunFunc: func(parsed *dcmd.Data) (interface{}, error) { - currentReminders, _ := GetUserReminders(parsed.Author.ID) - if len(currentReminders) >= 25 { - return "You can have a maximum of 25 active reminders, list all your reminders with the `reminders` command in DM, doing it in a server will only show reminders set in the server", nil + uid := discordgo.StrID(parsed.Author.ID) + count, _ := models.Reminders(models.ReminderWhere.UserID.EQ(uid)).CountG(parsed.Context()) + if count >= MaxReminders { + return fmt.Sprintf("You can have a maximum of %d active reminders; list all your reminders with the `reminders` command in DM, doing it in a server will only show reminders set in the server", MaxReminders), nil } if parsed.Author.Bot { - return nil, errors.New("cannot create reminder for Bots, you're most likely trying to use `execAdmin` to create a reminder, use `exec` instead") + return nil, errors.New("cannot create reminder for bots; you're likely trying to use `execAdmin` to create a reminder (use `exec` instead)") } - fromNow := parsed.Args[0].Value.(time.Duration) - - durString := common.HumanizeDuration(common.DurationPrecisionSeconds, fromNow) - when := time.Now().Add(fromNow) - tUnix := fmt.Sprint(when.Unix()) - - if when.After(time.Now().Add(time.Hour * 24 * 366)) { - return "Can be max 365 days from now...", nil + offsetFromNow := parsed.Args[0].Value.(time.Duration) + if offsetFromNow > MaxReminderOffset { + return MaxReminderOffsetExceededMsg, nil } id := parsed.ChannelID @@ -77,7 +80,7 @@ var cmds = []*commands.YAGCommand{ hasPerms, err := bot.AdminOrPermMS(parsed.GuildData.GS.ID, id, parsed.GuildData.MS, discordgo.PermissionSendMessages|discordgo.PermissionViewChannel) if err != nil { - return "Failed checking permissions, please try again or join the support server.", err + return "Failed checking permissions; please try again or join the support server.", err } if !hasPerms { @@ -85,12 +88,14 @@ var cmds = []*commands.YAGCommand{ } } + when := time.Now().Add(offsetFromNow) _, err := NewReminder(parsed.Author.ID, parsed.GuildData.GS.ID, id, parsed.Args[1].Str(), when) if err != nil { return nil, err } - return "Set a reminder in " + durString + " from now ()\nView reminders with the `reminders` command", nil + durString := common.HumanizeDuration(common.DurationPrecisionSeconds, offsetFromNow) + return fmt.Sprintf("Set a reminder in %s from now ()\nView reminders with the `reminders` command", durString, when.Unix()), nil }, }, { @@ -102,28 +107,29 @@ var cmds = []*commands.YAGCommand{ IsResponseEphemeral: true, RunInDM: true, RunFunc: func(parsed *dcmd.Data) (interface{}, error) { + uid := discordgo.StrID(parsed.Author.ID) + qms := []qm.QueryMod{models.ReminderWhere.UserID.EQ(uid)} + + // if command used in server, only show reminders in that server + var inServerSuffix string + if inServer := parsed.GuildData != nil; inServer { + inServerSuffix = " in this server" - var currentReminders []*Reminder - var err error - //command was used in DM - inServerString := "" - if parsed.GuildData == nil { - currentReminders, err = GetUserReminders(parsed.Author.ID) - } else { - inServerString = " in this server" - currentReminders, err = GetGuildUserReminder(parsed.Author.ID, parsed.GuildData.GS.ID) + guildID := parsed.GuildData.GS.ID + qms = append(qms, models.ReminderWhere.GuildID.EQ(guildID)) } + currentReminders, err := models.Reminders(qms...).AllG(parsed.Context()) if err != nil { return nil, err } if len(currentReminders) == 0 { - return fmt.Sprintf("You have no reminders%s. Create reminders with the `remindme` command.", inServerString), nil + return fmt.Sprintf("You have no reminders%s. Create reminders with the `remindme` command", inServerSuffix), nil } - out := fmt.Sprintf("Your reminders%s:\n", inServerString) - out += stringReminders(currentReminders, false) + out := fmt.Sprintf("Your reminders%s:\n", inServerSuffix) + out += DisplayReminders(currentReminders, ModeDisplayUserReminders) out += "\nRemove a reminder with `delreminder/rmreminder (id)` where id is the first number for each reminder above.\nTo clear all reminders, use `delreminder` with the `-a` switch." return out, nil }, @@ -132,20 +138,14 @@ var cmds = []*commands.YAGCommand{ CmdCategory: commands.CategoryTool, Name: "CReminders", Aliases: []string{"channelreminders"}, - Description: "Lists reminders in channel, only users with 'manage channel' permissions can use this.", + Description: "Lists reminders in channel", + RequireDiscordPerms: []int64{discordgo.PermissionManageChannels}, SlashCommandEnabled: true, DefaultEnabled: true, IsResponseEphemeral: true, RunFunc: func(parsed *dcmd.Data) (interface{}, error) { - ok, err := bot.AdminOrPermMS(parsed.GuildData.GS.ID, parsed.ChannelID, parsed.GuildData.MS, discordgo.PermissionManageChannels) - if err != nil { - return nil, err - } - if !ok { - return "You do not have access to this command (requires manage channel permission)", nil - } - - currentReminders, err := GetChannelReminders(parsed.ChannelID) + cid := discordgo.StrID(parsed.ChannelID) + currentReminders, err := models.Reminders(models.ReminderWhere.ChannelID.EQ(cid)).AllG(parsed.Context()) if err != nil { return nil, err } @@ -155,7 +155,7 @@ var cmds = []*commands.YAGCommand{ } out := "Reminders in this channel:\n" - out += stringReminders(currentReminders, true) + out += DisplayReminders(currentReminders, ModeDisplayChannelReminders) out += "\nRemove a reminder with `delreminder/rmreminder (id)` where id is the first number for each reminder above" return out, nil }, @@ -177,17 +177,13 @@ var cmds = []*commands.YAGCommand{ DefaultEnabled: true, IsResponseEphemeral: true, RunFunc: func(parsed *dcmd.Data) (interface{}, error) { - var reminder Reminder - - clearAll := parsed.Switch("a").Value != nil && parsed.Switch("a").Value.(bool) - if clearAll { - db := common.GORM.Where("user_id = ?", parsed.Author.ID).Delete(&reminder) - err := db.Error + if clearAll := parsed.Switch("a").Bool(); clearAll { + uid := discordgo.StrID(parsed.Author.ID) + count, err := models.Reminders(models.ReminderWhere.UserID.EQ(uid)).DeleteAllG(parsed.Context(), false /* hardDelete */) if err != nil { return "Error clearing reminders", err } - count := db.RowsAffected if count == 0 { return "No reminders to clear", nil } @@ -198,20 +194,22 @@ var cmds = []*commands.YAGCommand{ return "No reminder ID provided", nil } - err := common.GORM.Where(parsed.Args[0].Int()).First(&reminder).Error + reminder, err := models.FindReminderG(parsed.Context(), parsed.Args[0].Int()) if err != nil { - if err == gorm.ErrRecordNotFound { - return "No reminder by that id found", nil + if err == sql.ErrNoRows { + return "No reminder by that ID found", nil } return "Error retrieving reminder", err } - // Check perms + // check perms if reminder.UserID != discordgo.StrID(parsed.Author.ID) { if reminder.GuildID != parsed.GuildData.GS.ID { return "You can only delete reminders that are not your own in the guild the reminder was originally created", nil } - ok, err := bot.AdminOrPermMS(reminder.GuildID, reminder.ChannelIDInt(), parsed.GuildData.MS, discordgo.PermissionManageChannels) + + cid, _ := discordgo.ParseID(reminder.ChannelID) + ok, err := bot.AdminOrPermMS(reminder.GuildID, cid, parsed.GuildData.MS, discordgo.PermissionManageChannels) if err != nil { return nil, err } @@ -220,70 +218,33 @@ var cmds = []*commands.YAGCommand{ } } - // Do the actual deletion - err = common.GORM.Delete(reminder).Error - if err != nil { - return nil, err - } - - // Check if we should remove the scheduled event - currentReminders, err := GetUserReminders(reminder.UserIDInt()) + // just deleting from database is enough; we need not delete the + // scheduled event since the handler will check database + _, err = reminder.DeleteG(parsed.Context(), false /* hardDelete */) if err != nil { return nil, err } - delMsg := fmt.Sprintf("Deleted reminder **#%d**: '%s'", reminder.ID, limitString(reminder.Message)) - - // If there is another reminder with the same timestamp, do not remove the scheduled event - for _, v := range currentReminders { - if v.When == reminder.When { - return delMsg, nil - } - } - - return delMsg, nil + return fmt.Sprintf("Deleted reminder **#%d**: '%s'", reminder.ID, CutReminderShort(reminder.Message)), nil }, }, } -func stringReminders(reminders []*Reminder, displayUsernames bool) string { - out := "" - for _, v := range reminders { - parsedCID, _ := strconv.ParseInt(v.ChannelID, 10, 64) - - t := time.Unix(v.When, 0) - tUnix := t.Unix() - timeFromNow := common.HumanizeTime(common.DurationPrecisionMinutes, t) - if !displayUsernames { - channel := "<#" + discordgo.StrID(parsedCID) + ">" - out += fmt.Sprintf("**%d**: %s: '%s' - %s from now ()\n", v.ID, channel, limitString(v.Message), timeFromNow, tUnix) - } else { - member, _ := bot.GetMember(v.GuildID, v.UserIDInt()) - username := "Unknown user" - if member != nil { - username = member.User.Username - } - out += fmt.Sprintf("**%d**: %s: '%s' - %s from now ()\n", v.ID, username, limitString(v.Message), timeFromNow, tUnix) - } - } - return out -} - func checkUserScheduledEvent(evt *seventsmodels.ScheduledEvent, data interface{}) (retry bool, err error) { - // !important! the evt.GuildID can be 1 in cases where it was migrated from the legacy scheduled event system + // IMPORTANT: evt.GuildID can be 1 in cases where it was migrated from the + // legacy scheduled event system. - userID := *data.(*int64) - - reminders, err := GetUserReminders(userID) + userID := discordgo.StrID(*data.(*int64)) + reminders, err := models.Reminders(models.ReminderWhere.UserID.EQ(userID)).AllG(context.Background()) if err != nil { return true, err } - now := time.Now() - nowUnix := now.Unix() - for _, v := range reminders { - if v.When <= nowUnix { - err := v.Trigger() + // TODO: can we move this filtering step into the database query? + nowUnix := time.Now().Unix() + for _, r := range reminders { + if r.When <= nowUnix { + err := TriggerReminder(r) if err != nil { // possibly try again return scheduledevents2.CheckDiscordErrRetry(err), err @@ -295,22 +256,12 @@ func checkUserScheduledEvent(evt *seventsmodels.ScheduledEvent, data interface{} } func migrateLegacyScheduledEvents(t time.Time, data string) error { - split := strings.Split(data, ":") - if len(split) < 2 { + _, userID, ok := strings.Cut(data, ":") + if !ok { logger.Error("invalid check user scheduled event: ", data) return nil } - parsed, _ := strconv.ParseInt(split[1], 10, 64) - + parsed, _ := discordgo.ParseID(userID) return scheduledevents2.ScheduleEvent("reminders_check_user", 1, t, parsed) } - -func limitString(s string) string { - if utf8.RuneCountInString(s) < 50 { - return s - } - - runes := []rune(s) - return string(runes[:47]) + "..." -} diff --git a/reminders/reminders.go b/reminders/reminders.go index 1b8bcbcd2b..425554ba84 100644 --- a/reminders/reminders.go +++ b/reminders/reminders.go @@ -1,27 +1,30 @@ package reminders import ( - "strconv" + "context" + "fmt" + "strings" "time" + "github.com/botlabs-gg/yagpdb/v2/bot" "github.com/botlabs-gg/yagpdb/v2/common" "github.com/botlabs-gg/yagpdb/v2/common/mqueue" "github.com/botlabs-gg/yagpdb/v2/common/scheduledevents2" "github.com/botlabs-gg/yagpdb/v2/lib/discordgo" - "github.com/jinzhu/gorm" + "github.com/botlabs-gg/yagpdb/v2/reminders/models" "github.com/sirupsen/logrus" + "github.com/volatiletech/sqlboiler/v4/boil" ) +//go:generate sqlboiler --no-hooks --add-soft-deletes psql + type Plugin struct{} func RegisterPlugin() { - err := common.GORM.AutoMigrate(&Reminder{}).Error - if err != nil { - panic(err) - } - p := &Plugin{} common.RegisterPlugin(p) + + common.InitSchemas("reminders", DBSchemas...) } func (p *Plugin) PluginInfo() *common.PluginInfo { @@ -32,31 +35,8 @@ func (p *Plugin) PluginInfo() *common.PluginInfo { } } -type Reminder struct { - gorm.Model - UserID string - ChannelID string - GuildID int64 - Message string - When int64 -} - -func (r *Reminder) UserIDInt() (i int64) { - i, _ = strconv.ParseInt(r.UserID, 10, 64) - return -} - -func (r *Reminder) ChannelIDInt() (i int64) { - i, _ = strconv.ParseInt(r.ChannelID, 10, 64) - return -} - -func (r *Reminder) Trigger() error { - // remove the actual reminder - rows := common.GORM.Delete(r).RowsAffected - if rows < 1 { - logger.Info("Tried to execute multiple reminders at once") - } +func TriggerReminder(r *models.Reminder) error { + r.DeleteG(context.Background(), false /* hardDelete */) logger.WithFields(logrus.Fields{"channel": r.ChannelID, "user": r.UserID, "message": r.Message, "id": r.ID}).Info("Triggered reminder") embed := &discordgo.MessageEmbed{ @@ -64,62 +44,76 @@ func (r *Reminder) Trigger() error { Description: common.ReplaceServerInvites(r.Message, r.GuildID, "(removed-invite)"), } - mqueue.QueueMessage(&mqueue.QueuedElement{ + channelID, _ := discordgo.ParseID(r.ChannelID) + userID, _ := discordgo.ParseID(r.UserID) + return mqueue.QueueMessage(&mqueue.QueuedElement{ Source: "reminder", SourceItemID: "", GuildID: r.GuildID, - ChannelID: r.ChannelIDInt(), + ChannelID: channelID, MessageEmbed: embed, MessageStr: "**Reminder** for <@" + r.UserID + ">", AllowedMentions: discordgo.AllowedMentions{ - Users: []int64{r.UserIDInt()}, + Users: []int64{userID}, }, Priority: 10, // above all feeds }) - return nil -} - -func GetGuildUserReminder(userID, guildID int64) (results []*Reminder, err error) { - err = common.GORM.Where(&Reminder{UserID: discordgo.StrID(userID), GuildID: guildID}).Find(&results).Error - if err == gorm.ErrRecordNotFound { - err = nil - } - return } -func GetUserReminders(userID int64) (results []*Reminder, err error) { - err = common.GORM.Where(&Reminder{UserID: discordgo.StrID(userID)}).Find(&results).Error - if err == gorm.ErrRecordNotFound { - err = nil - } - return -} - -func GetChannelReminders(channel int64) (results []*Reminder, err error) { - err = common.GORM.Where(&Reminder{ChannelID: discordgo.StrID(channel)}).Find(&results).Error - if err == gorm.ErrRecordNotFound { - err = nil - } - return -} - -func NewReminder(userID int64, guildID int64, channelID int64, message string, when time.Time) (*Reminder, error) { - whenUnix := when.Unix() - reminder := &Reminder{ +func NewReminder(userID int64, guildID int64, channelID int64, message string, when time.Time) (*models.Reminder, error) { + reminder := &models.Reminder{ UserID: discordgo.StrID(userID), ChannelID: discordgo.StrID(channelID), Message: message, - When: whenUnix, + When: when.Unix(), GuildID: guildID, } - err := common.GORM.Create(reminder).Error + err := reminder.InsertG(context.Background(), boil.Infer()) if err != nil { return nil, err } err = scheduledevents2.ScheduleEvent("reminders_check_user", guildID, when, userID) - // err = scheduledevents.ScheduleEvent("reminders_check_user:"+strconv.FormatInt(whenUnix, 10), discordgo.StrID(userID), when) return reminder, err } + +type DisplayRemindersMode int + +const ( + ModeDisplayChannelReminders DisplayRemindersMode = iota + ModeDisplayUserReminders +) + +func DisplayReminders(reminders models.ReminderSlice, mode DisplayRemindersMode) string { + var out strings.Builder + for _, r := range reminders { + t := time.Unix(r.When, 0) + timeFromNow := common.HumanizeTime(common.DurationPrecisionMinutes, t) + + switch mode { + case ModeDisplayChannelReminders: + // don't show the channel; do show the user + uid, _ := discordgo.ParseID(r.UserID) + member, _ := bot.GetMember(r.GuildID, uid) + username := "Unknown user" + if member != nil { + username = member.User.Username + } + + fmt.Fprintf(&out, "**%d**: %s: '%s' - %s from now ()\n", r.ID, username, CutReminderShort(r.Message), timeFromNow, t.Unix()) + + case ModeDisplayUserReminders: + // do show the channel; don't show the user + channel := "<#" + r.ChannelID + ">" + fmt.Fprintf(&out, "**%d**: %s: '%s' - %s from now ()\n", r.ID, channel, CutReminderShort(r.Message), timeFromNow, t.Unix()) + } + } + + return out.String() +} + +func CutReminderShort(msg string) string { + return common.CutStringShort(msg, 50) +} diff --git a/reminders/schema.go b/reminders/schema.go new file mode 100644 index 0000000000..a6990b2bde --- /dev/null +++ b/reminders/schema.go @@ -0,0 +1,57 @@ +package reminders + +var DBSchemas = []string{` +CREATE TABLE IF NOT EXISTS reminders ( + id SERIAL PRIMARY KEY, + created_at TIMESTAMP WITH TIME ZONE NOT NULL, + updated_at TIMESTAMP WITH TIME ZONE NOT NULL, + deleted_at TIMESTAMP WITH TIME ZONE, + + -- text instead of bigint for legacy compatibility + user_id TEXT NOT NULL, + channel_id TEXT NOT NULL, + guild_id BIGINT NOT NULL, + message TEXT NOT NULL, + "when" BIGINT NOT NULL +); +`, ` +CREATE INDEX IF NOT EXISTS idx_reminders_deleted_at ON reminders(deleted_at); +`, ` +-- Previous versions of the reputation module used gorm instead of sqlboiler, +-- which does not add NOT NULL constraints by default. Therefore, ensure the +-- NOT NULL constraints are present in existing tables as well. + +-- The first few columns below have always been set since the reminders plugin was +-- added, so barring the presence of invalid entries, we can safely add NOT NULL +-- constraints without error. + +ALTER TABLE reminders ALTER COLUMN created_at SET NOT NULL; +`, ` +ALTER TABLE reminders ALTER COLUMN updated_at SET NOT NULL; +`, ` +ALTER TABLE reminders ALTER COLUMN user_id SET NOT NULL; +`, ` +ALTER TABLE reminders ALTER COLUMN channel_id SET NOT NULL; +`, ` +ALTER TABLE reminders ALTER COLUMN message SET NOT NULL; +`, ` +ALTER TABLE reminders ALTER COLUMN "when" SET NOT NULL; +`, ` +DO $$ +BEGIN + +-- The guild_id column is more annoying to deal with. When the reminders plugin +-- was first created, the reminders table did not have a guild_id column -- it +-- was added later, in October 2018 (9f5ef28). So reminders before then could +-- plausibly have guild_id = NULL, meaning directly adding the NOT NULL +-- constraint would fail. But since the maximum offset of a reminder is 1 year, +-- all such reminders have now expired and so we can just delete them before +-- adding the constraint. + +-- Only run if we haven't added the NOT NULL constraint yet. +IF EXISTS(SELECT 1 FROM information_schema.columns WHERE table_name='reminders' AND column_name='guild_id' AND is_nullable='YES') THEN + DELETE FROM reminders WHERE guild_id IS NULL; + ALTER TABLE reminders ALTER COLUMN guild_id SET NOT NULL; +END IF; +END $$; +`} diff --git a/reminders/sqlboiler.toml b/reminders/sqlboiler.toml new file mode 100644 index 0000000000..cc4cee83ad --- /dev/null +++ b/reminders/sqlboiler.toml @@ -0,0 +1,15 @@ +add-global-variants = true +no-hooks = true +no-tests = true + +[psql] +dbname = "yagpdb" +host = "localhost" +user = "postgres" +pass = "pass" +sslmode = "disable" +whitelist = ["reminders"] + +[auto-columns] +created = "created_at" +updated = "updated_at" diff --git a/soundboard/transcoder.go b/soundboard/transcoder.go index cc100581df..810d605d6e 100644 --- a/soundboard/transcoder.go +++ b/soundboard/transcoder.go @@ -16,6 +16,7 @@ import ( "github.com/botlabs-gg/yagpdb/v2/common/backgroundworkers" "github.com/botlabs-gg/yagpdb/v2/lib/dca" "github.com/botlabs-gg/yagpdb/v2/soundboard/models" + "github.com/volatiletech/sqlboiler/v4/boil" "goji.io/pat" ) @@ -120,10 +121,14 @@ func handleQueueItem(item string) error { err = transcodeSound(sound) if err != nil { logger.WithError(err).WithField("sound", sound.ID).Error("Failed transcoding sound") - common.GORM.Model(&sound).Update("Status", TranscodingStatusFailedOther) + + sound.Status = int(TranscodingStatusFailedOther) + sound.UpdateG(context.Background(), boil.Whitelist("status")) + os.Remove(SoundFilePath(sound.ID, TranscodingStatusReady)) } else { - common.GORM.Model(&sound).Update("Status", TranscodingStatusReady) + sound.Status = int(TranscodingStatusReady) + sound.UpdateG(context.Background(), boil.Whitelist("status")) } err = os.Remove(SoundFilePath(sound.ID, TranscodingStatusQueued)) diff --git a/stdcommands/topcommands/topcommands.go b/stdcommands/topcommands/topcommands.go index 657f1d5aa0..75141b45b5 100644 --- a/stdcommands/topcommands/topcommands.go +++ b/stdcommands/topcommands/topcommands.go @@ -26,17 +26,31 @@ func cmdFuncTopCommands(data *dcmd.Data) (interface{}, error) { hours := data.Args[0].Int() within := time.Now().Add(time.Duration(-hours) * time.Hour) - var results []*TopCommandsResult - err := common.GORM.Table(common.LoggedExecutedCommand{}.TableName()).Select("command, COUNT(id)").Where("created_at > ?", within).Group("command").Order("count(id) desc").Scan(&results).Error + const q = ` +SELECT command, COUNT(id) +FROM executed_commands +WHERE created_at > $1 +GROUP BY command +ORDER BY COUNT(id) DESC; +` + rows, err := common.PQ.Query(q, within) if err != nil { return nil, err } + defer rows.Close() out := fmt.Sprintf("```\nCommand stats from now to %d hour(s) ago\n# Total - Command\n", hours) total := 0 - for k, result := range results { - out += fmt.Sprintf("#%02d: %5d - %s\n", k+1, result.Count, result.Command) - total += result.Count + for k := 1; rows.Next(); k++ { + var command string + var count int + err = rows.Scan(&command, &count) + if err != nil { + return nil, err + } + + out += fmt.Sprintf("#%02d: %5d - %s\n", k, count, command) + total += count } cpm := float64(total) / float64(hours) / 60 @@ -46,8 +60,3 @@ func cmdFuncTopCommands(data *dcmd.Data) (interface{}, error) { return out, nil } - -type TopCommandsResult struct { - Command string - Count int -} diff --git a/web/handlers_general.go b/web/handlers_general.go index 0cfb63cc92..8209a65a91 100644 --- a/web/handlers_general.go +++ b/web/handlers_general.go @@ -386,17 +386,12 @@ var commandsRanToday = new(int64) func pollCommandsRan() { t := time.NewTicker(time.Minute) for { - var result struct { - Count int64 - } - within := time.Now().Add(-24 * time.Hour) - - err := common.GORM.Table(common.LoggedExecutedCommand{}.TableName()).Select("COUNT(*)").Where("created_at > ?", within).Scan(&result).Error + count, err := models.ExecutedCommands(models.ExecutedCommandWhere.CreatedAt.GT(within)).CountG(context.Background()) if err != nil { logger.WithError(err).Error("failed counting commands ran today") } else { - atomic.StoreInt64(commandsRanToday, result.Count) + atomic.StoreInt64(commandsRanToday, count) } <-t.C diff --git a/web/middleware.go b/web/middleware.go index 013524ba19..faca58e5a2 100644 --- a/web/middleware.go +++ b/web/middleware.go @@ -24,6 +24,7 @@ import ( "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" "github.com/sirupsen/logrus" + "github.com/volatiletech/null/v8" "goji.io/pat" ) @@ -552,6 +553,12 @@ func FormParserMW(inner http.Handler, dst interface{}) http.Handler { // Decode the form into the destination struct decoded := reflect.New(typ).Interface() decoder := schema.NewDecoder() + decoder.RegisterConverter(null.Int64{}, func(value string) reflect.Value { + if v, err := strconv.ParseInt(value, 10, 64); err == nil { + return reflect.ValueOf(null.Int64From(v)) + } + return reflect.Value{} + }) decoder.IgnoreUnknownKeys(true) err = decoder.Decode(decoded, r.Form) diff --git a/web/validation.go b/web/validation.go index 832243bb95..d60d869763 100644 --- a/web/validation.go +++ b/web/validation.go @@ -25,7 +25,6 @@ package web // // if the struct also implements CustomValidator then that will also be ran import ( - "database/sql" "errors" "fmt" "reflect" @@ -40,6 +39,8 @@ import ( "github.com/botlabs-gg/yagpdb/v2/lib/discordgo" "github.com/botlabs-gg/yagpdb/v2/lib/dstate" "github.com/lib/pq" + "github.com/volatiletech/null/v8" + "github.com/volatiletech/sqlboiler/v4/types" ) type CustomValidator interface { @@ -122,12 +123,11 @@ func ValidateForm(guild *dstate.GuildSet, tmpl TemplateData, form interface{}) b if err == nil && !keep { vField.SetInt(0) } - case sql.NullInt64: + case null.Int64: var keep bool - var newNullInt sql.NullInt64 keep, err = ValidateIntField(cv.Int64, validationTag, guild, false) if err == nil && !keep { - vField.Set(reflect.ValueOf(newNullInt)) + vField.Set(reflect.ValueOf(null.Int64{})) } case float64: min, max, onlyMin := readMinMax(validationTag) @@ -171,6 +171,14 @@ func ValidateForm(guild *dstate.GuildSet, tmpl TemplateData, form interface{}) b } vField.Set(reflect.ValueOf(pq.Int64Array(newSlice))) + case types.Int64Array: + newSlice, e := ValidateIntSliceField(cv, validationTag, guild) + if e != nil { + err = e + break + } + + vField.Set(reflect.ValueOf(types.Int64Array(newSlice))) default: // Recurse if it's another struct switch tField.Type.Kind() { diff --git a/youtube/bot.go b/youtube/bot.go index 08c0a224e9..c8ab0a27a8 100644 --- a/youtube/bot.go +++ b/youtube/bot.go @@ -1,39 +1,39 @@ package youtube import ( + "context" "fmt" "github.com/botlabs-gg/yagpdb/v2/common" + "github.com/botlabs-gg/yagpdb/v2/lib/discordgo" + "github.com/botlabs-gg/yagpdb/v2/youtube/models" "github.com/mediocregopher/radix/v3" + "github.com/volatiletech/sqlboiler/v4/queries/qm" ) func (p *Plugin) Status() (string, string) { var unique int common.RedisPool.Do(radix.Cmd(&unique, "ZCARD", RedisKeyWebSubChannels)) - var numChannels int - common.GORM.Model(&ChannelSubscription{}).Count(&numChannels) + total, _ := models.YoutubeChannelSubscriptions().CountG(context.Background()) - return "Unique/Total", fmt.Sprintf("%d/%d", unique, numChannels) + return "Unique/Total", fmt.Sprintf("%d/%d", unique, total) } func (p *Plugin) OnRemovedPremiumGuild(guildID int64) error { - logger.WithField("guild_id", guildID).Infof("Removed Excess Youtube Feeds") - feeds := make([]ChannelSubscription, 0) - err := common.GORM.Model(&ChannelSubscription{}).Where(`guild_id = ? and enabled = ?`, guildID, common.BoolToPointer(true)).Offset(GuildMaxFeeds).Order( - "id desc", - ).Find(&feeds).Error + numDisabled, err := models.YoutubeChannelSubscriptions( + models.YoutubeChannelSubscriptionWhere.GuildID.EQ(discordgo.StrID(guildID)), + models.YoutubeChannelSubscriptionWhere.Enabled.EQ(true), + + qm.Offset(GuildMaxFeeds), + qm.OrderBy("id DESC"), + ).UpdateAllG(context.Background(), models.M{"enabled": false}) + if err != nil { - logger.WithError(err).Errorf("failed getting feed ids for guild_id %d", guildID) + logger.WithError(err).WithField("guild", guildID).Error("failed disabling excess feeds") return err } - if len(feeds) > 0 { - err = common.GORM.Model(&feeds).Update(ChannelSubscription{Enabled: common.BoolToPointer(false)}).Error - if err != nil { - logger.WithError(err).Errorf("failed getting feed ids for guild_id %d", guildID) - return err - } - } + logger.WithField("guild", guildID).Infof("disabled %d excess feeds", numDisabled) return nil } diff --git a/youtube/feed.go b/youtube/feed.go index 7ca7dc37ca..78974f4125 100644 --- a/youtube/feed.go +++ b/youtube/feed.go @@ -2,6 +2,7 @@ package youtube import ( "context" + "database/sql" "errors" "fmt" "math" @@ -19,9 +20,10 @@ import ( "github.com/botlabs-gg/yagpdb/v2/feeds" "github.com/botlabs-gg/yagpdb/v2/lib/discordgo" "github.com/botlabs-gg/yagpdb/v2/web/discorddata" - "github.com/jinzhu/gorm" + "github.com/botlabs-gg/yagpdb/v2/youtube/models" "github.com/mediocregopher/radix/v3" "github.com/prometheus/client_golang/prometheus" + "github.com/volatiletech/sqlboiler/v4/boil" "google.golang.org/api/option" "google.golang.org/api/youtube/v3" ) @@ -185,11 +187,10 @@ func (p *Plugin) syncWebSubs() { })) } -func (p *Plugin) sendNewVidMessage(sub *ChannelSubscription, video *youtube.Video) { +func (p *Plugin) sendNewVidMessage(sub *models.YoutubeChannelSubscription, video *youtube.Video) { parsedChannel, _ := strconv.ParseInt(sub.ChannelID, 10, 64) parsedGuild, _ := strconv.ParseInt(sub.GuildID, 10, 64) videoUrl := "https://www.youtube.com/watch?v=" + video.Id - var announcement YoutubeAnnouncements var content string switch video.Snippet.LiveBroadcastContent { @@ -204,11 +205,11 @@ func (p *Plugin) sendNewVidMessage(sub *ChannelSubscription, video *youtube.Vide } parseMentions := []discordgo.AllowedMentionType{} - err := common.GORM.Model(&YoutubeAnnouncements{}).Where("guild_id = ?", parsedGuild).First(&announcement).Error + announcement, err := models.FindYoutubeAnnouncementG(context.Background(), parsedGuild) hasCustomAnnouncement := true if err != nil { hasCustomAnnouncement = false - if errors.Is(err, gorm.ErrRecordNotFound) { + if err == sql.ErrNoRows { logger.WithError(err).Debugf("Custom announcement doesn't exist for guild_id %d", parsedGuild) } else { logger.WithError(err).Errorf("Failed fetching custom announcement for guild_id %d", parsedGuild) @@ -217,7 +218,7 @@ func (p *Plugin) sendNewVidMessage(sub *ChannelSubscription, video *youtube.Vide var publishAnnouncement bool - if hasCustomAnnouncement && *announcement.Enabled && len(announcement.Message) > 0 { + if hasCustomAnnouncement && announcement.Enabled && len(announcement.Message) > 0 { guildState, err := discorddata.GetFullGuild(parsedGuild) if err != nil { logger.WithError(err).Errorf("Failed to get guild state for guild_id %d", parsedGuild) @@ -301,9 +302,10 @@ var ( ErrIDNotFound = errors.New("ID not found") ) -func SubsForChannel(channel string) (result []*ChannelSubscription, err error) { - err = common.GORM.Where("youtube_channel_id = ?", channel).Find(&result).Error - return +func SubsForChannel(channel string) (models.YoutubeChannelSubscriptionSlice, error) { + return models.YoutubeChannelSubscriptions( + models.YoutubeChannelSubscriptionWhere.YoutubeChannelID.EQ(channel), + ).AllG(context.Background()) } var ( @@ -447,19 +449,19 @@ func (p *Plugin) parseYtVideoID(parse string) (id ytChannelID, err error) { } } -func (p *Plugin) AddFeed(guildID, discordChannelID int64, ytChannel *youtube.Channel, mentionEveryone bool, publishLivestream bool, publishShorts bool, mentionRoles []int64) (*ChannelSubscription, error) { +func (p *Plugin) AddFeed(guildID, discordChannelID int64, ytChannel *youtube.Channel, mentionEveryone bool, publishLivestream bool, publishShorts bool, mentionRoles []int64) (*models.YoutubeChannelSubscription, error) { if mentionEveryone && len(mentionRoles) > 0 { mentionRoles = make([]int64, 0) } - sub := &ChannelSubscription{ + sub := &models.YoutubeChannelSubscription{ GuildID: discordgo.StrID(guildID), ChannelID: discordgo.StrID(discordChannelID), MentionEveryone: mentionEveryone, MentionRoles: mentionRoles, - PublishLivestream: &publishLivestream, - PublishShorts: &publishShorts, - Enabled: common.BoolToPointer(true), + PublishLivestream: publishLivestream, + PublishShorts: publishShorts, + Enabled: true, } sub.YoutubeChannelName = ytChannel.Snippet.Title @@ -471,7 +473,7 @@ func (p *Plugin) AddFeed(guildID, discordChannelID int64, ytChannel *youtube.Cha } defer common.UnlockRedisKey(RedisChannelsLockKey) - err = common.GORM.Create(sub).Error + err = sub.InsertG(context.Background(), boil.Whitelist("guild_id", "channel_id", "mention_everyone", "mention_roles", "publish_livestream", "publish_shorts", "enabled")) if err != nil { return nil, err } @@ -488,8 +490,9 @@ func (p *Plugin) MaybeRemoveChannelWatch(channel string) { } defer common.UnlockRedisKey(RedisChannelsLockKey) - var count int - err = common.GORM.Model(&ChannelSubscription{}).Where("youtube_channel_id = ?", channel).Count(&count).Error + count, err := models.YoutubeChannelSubscriptions( + models.YoutubeChannelSubscriptionWhere.YoutubeChannelID.EQ(channel), + ).CountG(context.Background()) if err != nil || count > 0 { if err != nil { logger.WithError(err).WithField("yt_channel", channel).Error("Failed getting sub count") @@ -650,7 +653,7 @@ func (p *Plugin) isShortsRedirect(videoId string) bool { return resp.StatusCode == 200 } -func (p *Plugin) postVideo(subs []*ChannelSubscription, publishedAt time.Time, video *youtube.Video, channelID string) error { +func (p *Plugin) postVideo(subs models.YoutubeChannelSubscriptionSlice, publishedAt time.Time, video *youtube.Video, channelID string) error { // add video to list of published videos err := common.RedisPool.Do(radix.FlatCmd(nil, "ZADD", RedisKeyPublishedVideoList, publishedAt.Unix(), video.Id)) if err != nil { @@ -666,13 +669,13 @@ func (p *Plugin) postVideo(subs []*ChannelSubscription, publishedAt time.Time, v isShorts := false for _, sub := range subs { - if *sub.Enabled { - if (isLivestream || isUpcoming) && !*sub.PublishLivestream { + if sub.Enabled { + if (isLivestream || isUpcoming) && !sub.PublishLivestream { continue } //no need to check for shorts for a livestream - if !(isLivestream || isUpcoming) && !*sub.PublishShorts { + if !(isLivestream || isUpcoming) && !sub.PublishShorts { //check if a video is a short only when seeing the first shorts disabled subscription //and cache in "isShorts" to reduce requests to youtube to check for shorts. if !isShortsCheckDone { @@ -691,9 +694,10 @@ func (p *Plugin) postVideo(subs []*ChannelSubscription, publishedAt time.Time, v return nil } -func (p *Plugin) getRemoveSubs(channelID string) ([]*ChannelSubscription, error) { - var subs []*ChannelSubscription - err := common.GORM.Where("youtube_channel_id = ?", channelID).Find(&subs).Error +func (p *Plugin) getRemoveSubs(channelID string) (models.YoutubeChannelSubscriptionSlice, error) { + subs, err := models.YoutubeChannelSubscriptions( + models.YoutubeChannelSubscriptionWhere.YoutubeChannelID.EQ(channelID), + ).AllG(context.Background()) if err != nil { return subs, err } diff --git a/youtube/models/boil_queries.go b/youtube/models/boil_queries.go new file mode 100644 index 0000000000..20c2563fdb --- /dev/null +++ b/youtube/models/boil_queries.go @@ -0,0 +1,38 @@ +// Code generated by SQLBoiler 4.16.2 (https://github.com/volatiletech/sqlboiler). DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package models + +import ( + "regexp" + + "github.com/volatiletech/sqlboiler/v4/drivers" + "github.com/volatiletech/sqlboiler/v4/queries" + "github.com/volatiletech/sqlboiler/v4/queries/qm" +) + +var dialect = drivers.Dialect{ + LQ: 0x22, + RQ: 0x22, + + UseIndexPlaceholders: true, + UseLastInsertID: false, + UseSchema: false, + UseDefaultKeyword: true, + UseAutoColumns: false, + UseTopClause: false, + UseOutputClause: false, + UseCaseWhenExistsClause: false, +} + +// This is a dummy variable to prevent unused regexp import error +var _ = ®exp.Regexp{} + +// NewQuery initializes a new Query using the passed in QueryMods +func NewQuery(mods ...qm.QueryMod) *queries.Query { + q := &queries.Query{} + queries.SetDialect(q, &dialect) + qm.Apply(q, mods...) + + return q +} diff --git a/youtube/models/boil_table_names.go b/youtube/models/boil_table_names.go new file mode 100644 index 0000000000..27d63986a8 --- /dev/null +++ b/youtube/models/boil_table_names.go @@ -0,0 +1,12 @@ +// Code generated by SQLBoiler 4.16.2 (https://github.com/volatiletech/sqlboiler). DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package models + +var TableNames = struct { + YoutubeAnnouncements string + YoutubeChannelSubscriptions string +}{ + YoutubeAnnouncements: "youtube_announcements", + YoutubeChannelSubscriptions: "youtube_channel_subscriptions", +} diff --git a/youtube/models/boil_types.go b/youtube/models/boil_types.go new file mode 100644 index 0000000000..02a6fdfdc5 --- /dev/null +++ b/youtube/models/boil_types.go @@ -0,0 +1,52 @@ +// Code generated by SQLBoiler 4.16.2 (https://github.com/volatiletech/sqlboiler). DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package models + +import ( + "strconv" + + "github.com/friendsofgo/errors" + "github.com/volatiletech/sqlboiler/v4/boil" + "github.com/volatiletech/strmangle" +) + +// M type is for providing columns and column values to UpdateAll. +type M map[string]interface{} + +// ErrSyncFail occurs during insert when the record could not be retrieved in +// order to populate default value information. This usually happens when LastInsertId +// fails or there was a primary key configuration that was not resolvable. +var ErrSyncFail = errors.New("models: failed to synchronize data after insert") + +type insertCache struct { + query string + retQuery string + valueMapping []uint64 + retMapping []uint64 +} + +type updateCache struct { + query string + valueMapping []uint64 +} + +func makeCacheKey(cols boil.Columns, nzDefaults []string) string { + buf := strmangle.GetBuffer() + + buf.WriteString(strconv.Itoa(cols.Kind)) + for _, w := range cols.Cols { + buf.WriteString(w) + } + + if len(nzDefaults) != 0 { + buf.WriteByte('.') + } + for _, nz := range nzDefaults { + buf.WriteString(nz) + } + + str := buf.String() + strmangle.PutBuffer(buf) + return str +} diff --git a/youtube/models/boil_view_names.go b/youtube/models/boil_view_names.go new file mode 100644 index 0000000000..01504d82bf --- /dev/null +++ b/youtube/models/boil_view_names.go @@ -0,0 +1,7 @@ +// Code generated by SQLBoiler 4.16.2 (https://github.com/volatiletech/sqlboiler). DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package models + +var ViewNames = struct { +}{} diff --git a/youtube/models/psql_upsert.go b/youtube/models/psql_upsert.go new file mode 100644 index 0000000000..07602da9c5 --- /dev/null +++ b/youtube/models/psql_upsert.go @@ -0,0 +1,99 @@ +// Code generated by SQLBoiler 4.16.2 (https://github.com/volatiletech/sqlboiler). DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package models + +import ( + "fmt" + "strings" + + "github.com/volatiletech/sqlboiler/v4/drivers" + "github.com/volatiletech/strmangle" +) + +type UpsertOptions struct { + conflictTarget string + updateSet string +} + +type UpsertOptionFunc func(o *UpsertOptions) + +func UpsertConflictTarget(conflictTarget string) UpsertOptionFunc { + return func(o *UpsertOptions) { + o.conflictTarget = conflictTarget + } +} + +func UpsertUpdateSet(updateSet string) UpsertOptionFunc { + return func(o *UpsertOptions) { + o.updateSet = updateSet + } +} + +// buildUpsertQueryPostgres builds a SQL statement string using the upsertData provided. +func buildUpsertQueryPostgres(dia drivers.Dialect, tableName string, updateOnConflict bool, ret, update, conflict, whitelist []string, opts ...UpsertOptionFunc) string { + conflict = strmangle.IdentQuoteSlice(dia.LQ, dia.RQ, conflict) + whitelist = strmangle.IdentQuoteSlice(dia.LQ, dia.RQ, whitelist) + ret = strmangle.IdentQuoteSlice(dia.LQ, dia.RQ, ret) + + upsertOpts := &UpsertOptions{} + for _, o := range opts { + o(upsertOpts) + } + + buf := strmangle.GetBuffer() + defer strmangle.PutBuffer(buf) + + columns := "DEFAULT VALUES" + if len(whitelist) != 0 { + columns = fmt.Sprintf("(%s) VALUES (%s)", + strings.Join(whitelist, ", "), + strmangle.Placeholders(dia.UseIndexPlaceholders, len(whitelist), 1, 1)) + } + + fmt.Fprintf( + buf, + "INSERT INTO %s %s ON CONFLICT ", + tableName, + columns, + ) + + if upsertOpts.conflictTarget != "" { + buf.WriteString(upsertOpts.conflictTarget) + } else if len(conflict) != 0 { + buf.WriteByte('(') + buf.WriteString(strings.Join(conflict, ", ")) + buf.WriteByte(')') + } + buf.WriteByte(' ') + + if !updateOnConflict || len(update) == 0 { + buf.WriteString("DO NOTHING") + } else { + buf.WriteString("DO UPDATE SET ") + + if upsertOpts.updateSet != "" { + buf.WriteString(upsertOpts.updateSet) + } else { + for i, v := range update { + if len(v) == 0 { + continue + } + if i != 0 { + buf.WriteByte(',') + } + quoted := strmangle.IdentQuote(dia.LQ, dia.RQ, v) + buf.WriteString(quoted) + buf.WriteString(" = EXCLUDED.") + buf.WriteString(quoted) + } + } + } + + if len(ret) != 0 { + buf.WriteString(" RETURNING ") + buf.WriteString(strings.Join(ret, ", ")) + } + + return buf.String() +} diff --git a/youtube/models/youtube_announcements.go b/youtube/models/youtube_announcements.go new file mode 100644 index 0000000000..9429a96304 --- /dev/null +++ b/youtube/models/youtube_announcements.go @@ -0,0 +1,831 @@ +// Code generated by SQLBoiler 4.16.2 (https://github.com/volatiletech/sqlboiler). DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package models + +import ( + "context" + "database/sql" + "fmt" + "reflect" + "strconv" + "strings" + "sync" + "time" + + "github.com/friendsofgo/errors" + "github.com/volatiletech/sqlboiler/v4/boil" + "github.com/volatiletech/sqlboiler/v4/queries" + "github.com/volatiletech/sqlboiler/v4/queries/qm" + "github.com/volatiletech/sqlboiler/v4/queries/qmhelper" + "github.com/volatiletech/strmangle" +) + +// YoutubeAnnouncement is an object representing the database table. +type YoutubeAnnouncement struct { + GuildID int64 `boil:"guild_id" json:"guild_id" toml:"guild_id" yaml:"guild_id"` + Message string `boil:"message" json:"message" toml:"message" yaml:"message"` + Enabled bool `boil:"enabled" json:"enabled" toml:"enabled" yaml:"enabled"` + + R *youtubeAnnouncementR `boil:"-" json:"-" toml:"-" yaml:"-"` + L youtubeAnnouncementL `boil:"-" json:"-" toml:"-" yaml:"-"` +} + +var YoutubeAnnouncementColumns = struct { + GuildID string + Message string + Enabled string +}{ + GuildID: "guild_id", + Message: "message", + Enabled: "enabled", +} + +var YoutubeAnnouncementTableColumns = struct { + GuildID string + Message string + Enabled string +}{ + GuildID: "youtube_announcements.guild_id", + Message: "youtube_announcements.message", + Enabled: "youtube_announcements.enabled", +} + +// Generated where + +type whereHelperint64 struct{ field string } + +func (w whereHelperint64) EQ(x int64) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.EQ, x) } +func (w whereHelperint64) NEQ(x int64) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.NEQ, x) } +func (w whereHelperint64) LT(x int64) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.LT, x) } +func (w whereHelperint64) LTE(x int64) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.LTE, x) } +func (w whereHelperint64) GT(x int64) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.GT, x) } +func (w whereHelperint64) GTE(x int64) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.GTE, x) } +func (w whereHelperint64) IN(slice []int64) qm.QueryMod { + values := make([]interface{}, 0, len(slice)) + for _, value := range slice { + values = append(values, value) + } + return qm.WhereIn(fmt.Sprintf("%s IN ?", w.field), values...) +} +func (w whereHelperint64) NIN(slice []int64) qm.QueryMod { + values := make([]interface{}, 0, len(slice)) + for _, value := range slice { + values = append(values, value) + } + return qm.WhereNotIn(fmt.Sprintf("%s NOT IN ?", w.field), values...) +} + +type whereHelperstring struct{ field string } + +func (w whereHelperstring) EQ(x string) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.EQ, x) } +func (w whereHelperstring) NEQ(x string) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.NEQ, x) } +func (w whereHelperstring) LT(x string) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.LT, x) } +func (w whereHelperstring) LTE(x string) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.LTE, x) } +func (w whereHelperstring) GT(x string) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.GT, x) } +func (w whereHelperstring) GTE(x string) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.GTE, x) } +func (w whereHelperstring) LIKE(x string) qm.QueryMod { return qm.Where(w.field+" LIKE ?", x) } +func (w whereHelperstring) NLIKE(x string) qm.QueryMod { return qm.Where(w.field+" NOT LIKE ?", x) } +func (w whereHelperstring) ILIKE(x string) qm.QueryMod { return qm.Where(w.field+" ILIKE ?", x) } +func (w whereHelperstring) NILIKE(x string) qm.QueryMod { return qm.Where(w.field+" NOT ILIKE ?", x) } +func (w whereHelperstring) IN(slice []string) qm.QueryMod { + values := make([]interface{}, 0, len(slice)) + for _, value := range slice { + values = append(values, value) + } + return qm.WhereIn(fmt.Sprintf("%s IN ?", w.field), values...) +} +func (w whereHelperstring) NIN(slice []string) qm.QueryMod { + values := make([]interface{}, 0, len(slice)) + for _, value := range slice { + values = append(values, value) + } + return qm.WhereNotIn(fmt.Sprintf("%s NOT IN ?", w.field), values...) +} + +type whereHelperbool struct{ field string } + +func (w whereHelperbool) EQ(x bool) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.EQ, x) } +func (w whereHelperbool) NEQ(x bool) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.NEQ, x) } +func (w whereHelperbool) LT(x bool) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.LT, x) } +func (w whereHelperbool) LTE(x bool) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.LTE, x) } +func (w whereHelperbool) GT(x bool) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.GT, x) } +func (w whereHelperbool) GTE(x bool) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.GTE, x) } + +var YoutubeAnnouncementWhere = struct { + GuildID whereHelperint64 + Message whereHelperstring + Enabled whereHelperbool +}{ + GuildID: whereHelperint64{field: "\"youtube_announcements\".\"guild_id\""}, + Message: whereHelperstring{field: "\"youtube_announcements\".\"message\""}, + Enabled: whereHelperbool{field: "\"youtube_announcements\".\"enabled\""}, +} + +// YoutubeAnnouncementRels is where relationship names are stored. +var YoutubeAnnouncementRels = struct { +}{} + +// youtubeAnnouncementR is where relationships are stored. +type youtubeAnnouncementR struct { +} + +// NewStruct creates a new relationship struct +func (*youtubeAnnouncementR) NewStruct() *youtubeAnnouncementR { + return &youtubeAnnouncementR{} +} + +// youtubeAnnouncementL is where Load methods for each relationship are stored. +type youtubeAnnouncementL struct{} + +var ( + youtubeAnnouncementAllColumns = []string{"guild_id", "message", "enabled"} + youtubeAnnouncementColumnsWithoutDefault = []string{"guild_id", "message"} + youtubeAnnouncementColumnsWithDefault = []string{"enabled"} + youtubeAnnouncementPrimaryKeyColumns = []string{"guild_id"} + youtubeAnnouncementGeneratedColumns = []string{} +) + +type ( + // YoutubeAnnouncementSlice is an alias for a slice of pointers to YoutubeAnnouncement. + // This should almost always be used instead of []YoutubeAnnouncement. + YoutubeAnnouncementSlice []*YoutubeAnnouncement + + youtubeAnnouncementQuery struct { + *queries.Query + } +) + +// Cache for insert, update and upsert +var ( + youtubeAnnouncementType = reflect.TypeOf(&YoutubeAnnouncement{}) + youtubeAnnouncementMapping = queries.MakeStructMapping(youtubeAnnouncementType) + youtubeAnnouncementPrimaryKeyMapping, _ = queries.BindMapping(youtubeAnnouncementType, youtubeAnnouncementMapping, youtubeAnnouncementPrimaryKeyColumns) + youtubeAnnouncementInsertCacheMut sync.RWMutex + youtubeAnnouncementInsertCache = make(map[string]insertCache) + youtubeAnnouncementUpdateCacheMut sync.RWMutex + youtubeAnnouncementUpdateCache = make(map[string]updateCache) + youtubeAnnouncementUpsertCacheMut sync.RWMutex + youtubeAnnouncementUpsertCache = make(map[string]insertCache) +) + +var ( + // Force time package dependency for automated UpdatedAt/CreatedAt. + _ = time.Second + // Force qmhelper dependency for where clause generation (which doesn't + // always happen) + _ = qmhelper.Where +) + +// OneG returns a single youtubeAnnouncement record from the query using the global executor. +func (q youtubeAnnouncementQuery) OneG(ctx context.Context) (*YoutubeAnnouncement, error) { + return q.One(ctx, boil.GetContextDB()) +} + +// One returns a single youtubeAnnouncement record from the query. +func (q youtubeAnnouncementQuery) One(ctx context.Context, exec boil.ContextExecutor) (*YoutubeAnnouncement, error) { + o := &YoutubeAnnouncement{} + + queries.SetLimit(q.Query, 1) + + err := q.Bind(ctx, exec, o) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + return nil, sql.ErrNoRows + } + return nil, errors.Wrap(err, "models: failed to execute a one query for youtube_announcements") + } + + return o, nil +} + +// AllG returns all YoutubeAnnouncement records from the query using the global executor. +func (q youtubeAnnouncementQuery) AllG(ctx context.Context) (YoutubeAnnouncementSlice, error) { + return q.All(ctx, boil.GetContextDB()) +} + +// All returns all YoutubeAnnouncement records from the query. +func (q youtubeAnnouncementQuery) All(ctx context.Context, exec boil.ContextExecutor) (YoutubeAnnouncementSlice, error) { + var o []*YoutubeAnnouncement + + err := q.Bind(ctx, exec, &o) + if err != nil { + return nil, errors.Wrap(err, "models: failed to assign all query results to YoutubeAnnouncement slice") + } + + return o, nil +} + +// CountG returns the count of all YoutubeAnnouncement records in the query using the global executor +func (q youtubeAnnouncementQuery) CountG(ctx context.Context) (int64, error) { + return q.Count(ctx, boil.GetContextDB()) +} + +// Count returns the count of all YoutubeAnnouncement records in the query. +func (q youtubeAnnouncementQuery) Count(ctx context.Context, exec boil.ContextExecutor) (int64, error) { + var count int64 + + queries.SetSelect(q.Query, nil) + queries.SetCount(q.Query) + + err := q.Query.QueryRowContext(ctx, exec).Scan(&count) + if err != nil { + return 0, errors.Wrap(err, "models: failed to count youtube_announcements rows") + } + + return count, nil +} + +// ExistsG checks if the row exists in the table using the global executor. +func (q youtubeAnnouncementQuery) ExistsG(ctx context.Context) (bool, error) { + return q.Exists(ctx, boil.GetContextDB()) +} + +// Exists checks if the row exists in the table. +func (q youtubeAnnouncementQuery) Exists(ctx context.Context, exec boil.ContextExecutor) (bool, error) { + var count int64 + + queries.SetSelect(q.Query, nil) + queries.SetCount(q.Query) + queries.SetLimit(q.Query, 1) + + err := q.Query.QueryRowContext(ctx, exec).Scan(&count) + if err != nil { + return false, errors.Wrap(err, "models: failed to check if youtube_announcements exists") + } + + return count > 0, nil +} + +// YoutubeAnnouncements retrieves all the records using an executor. +func YoutubeAnnouncements(mods ...qm.QueryMod) youtubeAnnouncementQuery { + mods = append(mods, qm.From("\"youtube_announcements\"")) + q := NewQuery(mods...) + if len(queries.GetSelect(q)) == 0 { + queries.SetSelect(q, []string{"\"youtube_announcements\".*"}) + } + + return youtubeAnnouncementQuery{q} +} + +// FindYoutubeAnnouncementG retrieves a single record by ID. +func FindYoutubeAnnouncementG(ctx context.Context, guildID int64, selectCols ...string) (*YoutubeAnnouncement, error) { + return FindYoutubeAnnouncement(ctx, boil.GetContextDB(), guildID, selectCols...) +} + +// FindYoutubeAnnouncement retrieves a single record by ID with an executor. +// If selectCols is empty Find will return all columns. +func FindYoutubeAnnouncement(ctx context.Context, exec boil.ContextExecutor, guildID int64, selectCols ...string) (*YoutubeAnnouncement, error) { + youtubeAnnouncementObj := &YoutubeAnnouncement{} + + sel := "*" + if len(selectCols) > 0 { + sel = strings.Join(strmangle.IdentQuoteSlice(dialect.LQ, dialect.RQ, selectCols), ",") + } + query := fmt.Sprintf( + "select %s from \"youtube_announcements\" where \"guild_id\"=$1", sel, + ) + + q := queries.Raw(query, guildID) + + err := q.Bind(ctx, exec, youtubeAnnouncementObj) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + return nil, sql.ErrNoRows + } + return nil, errors.Wrap(err, "models: unable to select from youtube_announcements") + } + + return youtubeAnnouncementObj, nil +} + +// InsertG a single record. See Insert for whitelist behavior description. +func (o *YoutubeAnnouncement) InsertG(ctx context.Context, columns boil.Columns) error { + return o.Insert(ctx, boil.GetContextDB(), columns) +} + +// Insert a single record using an executor. +// See boil.Columns.InsertColumnSet documentation to understand column list inference for inserts. +func (o *YoutubeAnnouncement) Insert(ctx context.Context, exec boil.ContextExecutor, columns boil.Columns) error { + if o == nil { + return errors.New("models: no youtube_announcements provided for insertion") + } + + var err error + + nzDefaults := queries.NonZeroDefaultSet(youtubeAnnouncementColumnsWithDefault, o) + + key := makeCacheKey(columns, nzDefaults) + youtubeAnnouncementInsertCacheMut.RLock() + cache, cached := youtubeAnnouncementInsertCache[key] + youtubeAnnouncementInsertCacheMut.RUnlock() + + if !cached { + wl, returnColumns := columns.InsertColumnSet( + youtubeAnnouncementAllColumns, + youtubeAnnouncementColumnsWithDefault, + youtubeAnnouncementColumnsWithoutDefault, + nzDefaults, + ) + + cache.valueMapping, err = queries.BindMapping(youtubeAnnouncementType, youtubeAnnouncementMapping, wl) + if err != nil { + return err + } + cache.retMapping, err = queries.BindMapping(youtubeAnnouncementType, youtubeAnnouncementMapping, returnColumns) + if err != nil { + return err + } + if len(wl) != 0 { + cache.query = fmt.Sprintf("INSERT INTO \"youtube_announcements\" (\"%s\") %%sVALUES (%s)%%s", strings.Join(wl, "\",\""), strmangle.Placeholders(dialect.UseIndexPlaceholders, len(wl), 1, 1)) + } else { + cache.query = "INSERT INTO \"youtube_announcements\" %sDEFAULT VALUES%s" + } + + var queryOutput, queryReturning string + + if len(cache.retMapping) != 0 { + queryReturning = fmt.Sprintf(" RETURNING \"%s\"", strings.Join(returnColumns, "\",\"")) + } + + cache.query = fmt.Sprintf(cache.query, queryOutput, queryReturning) + } + + value := reflect.Indirect(reflect.ValueOf(o)) + vals := queries.ValuesFromMapping(value, cache.valueMapping) + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, cache.query) + fmt.Fprintln(writer, vals) + } + + if len(cache.retMapping) != 0 { + err = exec.QueryRowContext(ctx, cache.query, vals...).Scan(queries.PtrsFromMapping(value, cache.retMapping)...) + } else { + _, err = exec.ExecContext(ctx, cache.query, vals...) + } + + if err != nil { + return errors.Wrap(err, "models: unable to insert into youtube_announcements") + } + + if !cached { + youtubeAnnouncementInsertCacheMut.Lock() + youtubeAnnouncementInsertCache[key] = cache + youtubeAnnouncementInsertCacheMut.Unlock() + } + + return nil +} + +// UpdateG a single YoutubeAnnouncement record using the global executor. +// See Update for more documentation. +func (o *YoutubeAnnouncement) UpdateG(ctx context.Context, columns boil.Columns) (int64, error) { + return o.Update(ctx, boil.GetContextDB(), columns) +} + +// Update uses an executor to update the YoutubeAnnouncement. +// See boil.Columns.UpdateColumnSet documentation to understand column list inference for updates. +// Update does not automatically update the record in case of default values. Use .Reload() to refresh the records. +func (o *YoutubeAnnouncement) Update(ctx context.Context, exec boil.ContextExecutor, columns boil.Columns) (int64, error) { + var err error + key := makeCacheKey(columns, nil) + youtubeAnnouncementUpdateCacheMut.RLock() + cache, cached := youtubeAnnouncementUpdateCache[key] + youtubeAnnouncementUpdateCacheMut.RUnlock() + + if !cached { + wl := columns.UpdateColumnSet( + youtubeAnnouncementAllColumns, + youtubeAnnouncementPrimaryKeyColumns, + ) + + if !columns.IsWhitelist() { + wl = strmangle.SetComplement(wl, []string{"created_at"}) + } + if len(wl) == 0 { + return 0, errors.New("models: unable to update youtube_announcements, could not build whitelist") + } + + cache.query = fmt.Sprintf("UPDATE \"youtube_announcements\" SET %s WHERE %s", + strmangle.SetParamNames("\"", "\"", 1, wl), + strmangle.WhereClause("\"", "\"", len(wl)+1, youtubeAnnouncementPrimaryKeyColumns), + ) + cache.valueMapping, err = queries.BindMapping(youtubeAnnouncementType, youtubeAnnouncementMapping, append(wl, youtubeAnnouncementPrimaryKeyColumns...)) + if err != nil { + return 0, err + } + } + + values := queries.ValuesFromMapping(reflect.Indirect(reflect.ValueOf(o)), cache.valueMapping) + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, cache.query) + fmt.Fprintln(writer, values) + } + var result sql.Result + result, err = exec.ExecContext(ctx, cache.query, values...) + if err != nil { + return 0, errors.Wrap(err, "models: unable to update youtube_announcements row") + } + + rowsAff, err := result.RowsAffected() + if err != nil { + return 0, errors.Wrap(err, "models: failed to get rows affected by update for youtube_announcements") + } + + if !cached { + youtubeAnnouncementUpdateCacheMut.Lock() + youtubeAnnouncementUpdateCache[key] = cache + youtubeAnnouncementUpdateCacheMut.Unlock() + } + + return rowsAff, nil +} + +// UpdateAllG updates all rows with the specified column values. +func (q youtubeAnnouncementQuery) UpdateAllG(ctx context.Context, cols M) (int64, error) { + return q.UpdateAll(ctx, boil.GetContextDB(), cols) +} + +// UpdateAll updates all rows with the specified column values. +func (q youtubeAnnouncementQuery) UpdateAll(ctx context.Context, exec boil.ContextExecutor, cols M) (int64, error) { + queries.SetUpdate(q.Query, cols) + + result, err := q.Query.ExecContext(ctx, exec) + if err != nil { + return 0, errors.Wrap(err, "models: unable to update all for youtube_announcements") + } + + rowsAff, err := result.RowsAffected() + if err != nil { + return 0, errors.Wrap(err, "models: unable to retrieve rows affected for youtube_announcements") + } + + return rowsAff, nil +} + +// UpdateAllG updates all rows with the specified column values. +func (o YoutubeAnnouncementSlice) UpdateAllG(ctx context.Context, cols M) (int64, error) { + return o.UpdateAll(ctx, boil.GetContextDB(), cols) +} + +// UpdateAll updates all rows with the specified column values, using an executor. +func (o YoutubeAnnouncementSlice) UpdateAll(ctx context.Context, exec boil.ContextExecutor, cols M) (int64, error) { + ln := int64(len(o)) + if ln == 0 { + return 0, nil + } + + if len(cols) == 0 { + return 0, errors.New("models: update all requires at least one column argument") + } + + colNames := make([]string, len(cols)) + args := make([]interface{}, len(cols)) + + i := 0 + for name, value := range cols { + colNames[i] = name + args[i] = value + i++ + } + + // Append all of the primary key values for each column + for _, obj := range o { + pkeyArgs := queries.ValuesFromMapping(reflect.Indirect(reflect.ValueOf(obj)), youtubeAnnouncementPrimaryKeyMapping) + args = append(args, pkeyArgs...) + } + + sql := fmt.Sprintf("UPDATE \"youtube_announcements\" SET %s WHERE %s", + strmangle.SetParamNames("\"", "\"", 1, colNames), + strmangle.WhereClauseRepeated(string(dialect.LQ), string(dialect.RQ), len(colNames)+1, youtubeAnnouncementPrimaryKeyColumns, len(o))) + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, sql) + fmt.Fprintln(writer, args...) + } + result, err := exec.ExecContext(ctx, sql, args...) + if err != nil { + return 0, errors.Wrap(err, "models: unable to update all in youtubeAnnouncement slice") + } + + rowsAff, err := result.RowsAffected() + if err != nil { + return 0, errors.Wrap(err, "models: unable to retrieve rows affected all in update all youtubeAnnouncement") + } + return rowsAff, nil +} + +// UpsertG attempts an insert, and does an update or ignore on conflict. +func (o *YoutubeAnnouncement) UpsertG(ctx context.Context, updateOnConflict bool, conflictColumns []string, updateColumns, insertColumns boil.Columns, opts ...UpsertOptionFunc) error { + return o.Upsert(ctx, boil.GetContextDB(), updateOnConflict, conflictColumns, updateColumns, insertColumns, opts...) +} + +// Upsert attempts an insert using an executor, and does an update or ignore on conflict. +// See boil.Columns documentation for how to properly use updateColumns and insertColumns. +func (o *YoutubeAnnouncement) Upsert(ctx context.Context, exec boil.ContextExecutor, updateOnConflict bool, conflictColumns []string, updateColumns, insertColumns boil.Columns, opts ...UpsertOptionFunc) error { + if o == nil { + return errors.New("models: no youtube_announcements provided for upsert") + } + + nzDefaults := queries.NonZeroDefaultSet(youtubeAnnouncementColumnsWithDefault, o) + + // Build cache key in-line uglily - mysql vs psql problems + buf := strmangle.GetBuffer() + if updateOnConflict { + buf.WriteByte('t') + } else { + buf.WriteByte('f') + } + buf.WriteByte('.') + for _, c := range conflictColumns { + buf.WriteString(c) + } + buf.WriteByte('.') + buf.WriteString(strconv.Itoa(updateColumns.Kind)) + for _, c := range updateColumns.Cols { + buf.WriteString(c) + } + buf.WriteByte('.') + buf.WriteString(strconv.Itoa(insertColumns.Kind)) + for _, c := range insertColumns.Cols { + buf.WriteString(c) + } + buf.WriteByte('.') + for _, c := range nzDefaults { + buf.WriteString(c) + } + key := buf.String() + strmangle.PutBuffer(buf) + + youtubeAnnouncementUpsertCacheMut.RLock() + cache, cached := youtubeAnnouncementUpsertCache[key] + youtubeAnnouncementUpsertCacheMut.RUnlock() + + var err error + + if !cached { + insert, _ := insertColumns.InsertColumnSet( + youtubeAnnouncementAllColumns, + youtubeAnnouncementColumnsWithDefault, + youtubeAnnouncementColumnsWithoutDefault, + nzDefaults, + ) + + update := updateColumns.UpdateColumnSet( + youtubeAnnouncementAllColumns, + youtubeAnnouncementPrimaryKeyColumns, + ) + + if updateOnConflict && len(update) == 0 { + return errors.New("models: unable to upsert youtube_announcements, could not build update column list") + } + + ret := strmangle.SetComplement(youtubeAnnouncementAllColumns, strmangle.SetIntersect(insert, update)) + + conflict := conflictColumns + if len(conflict) == 0 && updateOnConflict && len(update) != 0 { + if len(youtubeAnnouncementPrimaryKeyColumns) == 0 { + return errors.New("models: unable to upsert youtube_announcements, could not build conflict column list") + } + + conflict = make([]string, len(youtubeAnnouncementPrimaryKeyColumns)) + copy(conflict, youtubeAnnouncementPrimaryKeyColumns) + } + cache.query = buildUpsertQueryPostgres(dialect, "\"youtube_announcements\"", updateOnConflict, ret, update, conflict, insert, opts...) + + cache.valueMapping, err = queries.BindMapping(youtubeAnnouncementType, youtubeAnnouncementMapping, insert) + if err != nil { + return err + } + if len(ret) != 0 { + cache.retMapping, err = queries.BindMapping(youtubeAnnouncementType, youtubeAnnouncementMapping, ret) + if err != nil { + return err + } + } + } + + value := reflect.Indirect(reflect.ValueOf(o)) + vals := queries.ValuesFromMapping(value, cache.valueMapping) + var returns []interface{} + if len(cache.retMapping) != 0 { + returns = queries.PtrsFromMapping(value, cache.retMapping) + } + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, cache.query) + fmt.Fprintln(writer, vals) + } + if len(cache.retMapping) != 0 { + err = exec.QueryRowContext(ctx, cache.query, vals...).Scan(returns...) + if errors.Is(err, sql.ErrNoRows) { + err = nil // Postgres doesn't return anything when there's no update + } + } else { + _, err = exec.ExecContext(ctx, cache.query, vals...) + } + if err != nil { + return errors.Wrap(err, "models: unable to upsert youtube_announcements") + } + + if !cached { + youtubeAnnouncementUpsertCacheMut.Lock() + youtubeAnnouncementUpsertCache[key] = cache + youtubeAnnouncementUpsertCacheMut.Unlock() + } + + return nil +} + +// DeleteG deletes a single YoutubeAnnouncement record. +// DeleteG will match against the primary key column to find the record to delete. +func (o *YoutubeAnnouncement) DeleteG(ctx context.Context) (int64, error) { + return o.Delete(ctx, boil.GetContextDB()) +} + +// Delete deletes a single YoutubeAnnouncement record with an executor. +// Delete will match against the primary key column to find the record to delete. +func (o *YoutubeAnnouncement) Delete(ctx context.Context, exec boil.ContextExecutor) (int64, error) { + if o == nil { + return 0, errors.New("models: no YoutubeAnnouncement provided for delete") + } + + args := queries.ValuesFromMapping(reflect.Indirect(reflect.ValueOf(o)), youtubeAnnouncementPrimaryKeyMapping) + sql := "DELETE FROM \"youtube_announcements\" WHERE \"guild_id\"=$1" + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, sql) + fmt.Fprintln(writer, args...) + } + result, err := exec.ExecContext(ctx, sql, args...) + if err != nil { + return 0, errors.Wrap(err, "models: unable to delete from youtube_announcements") + } + + rowsAff, err := result.RowsAffected() + if err != nil { + return 0, errors.Wrap(err, "models: failed to get rows affected by delete for youtube_announcements") + } + + return rowsAff, nil +} + +func (q youtubeAnnouncementQuery) DeleteAllG(ctx context.Context) (int64, error) { + return q.DeleteAll(ctx, boil.GetContextDB()) +} + +// DeleteAll deletes all matching rows. +func (q youtubeAnnouncementQuery) DeleteAll(ctx context.Context, exec boil.ContextExecutor) (int64, error) { + if q.Query == nil { + return 0, errors.New("models: no youtubeAnnouncementQuery provided for delete all") + } + + queries.SetDelete(q.Query) + + result, err := q.Query.ExecContext(ctx, exec) + if err != nil { + return 0, errors.Wrap(err, "models: unable to delete all from youtube_announcements") + } + + rowsAff, err := result.RowsAffected() + if err != nil { + return 0, errors.Wrap(err, "models: failed to get rows affected by deleteall for youtube_announcements") + } + + return rowsAff, nil +} + +// DeleteAllG deletes all rows in the slice. +func (o YoutubeAnnouncementSlice) DeleteAllG(ctx context.Context) (int64, error) { + return o.DeleteAll(ctx, boil.GetContextDB()) +} + +// DeleteAll deletes all rows in the slice, using an executor. +func (o YoutubeAnnouncementSlice) DeleteAll(ctx context.Context, exec boil.ContextExecutor) (int64, error) { + if len(o) == 0 { + return 0, nil + } + + var args []interface{} + for _, obj := range o { + pkeyArgs := queries.ValuesFromMapping(reflect.Indirect(reflect.ValueOf(obj)), youtubeAnnouncementPrimaryKeyMapping) + args = append(args, pkeyArgs...) + } + + sql := "DELETE FROM \"youtube_announcements\" WHERE " + + strmangle.WhereClauseRepeated(string(dialect.LQ), string(dialect.RQ), 1, youtubeAnnouncementPrimaryKeyColumns, len(o)) + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, sql) + fmt.Fprintln(writer, args) + } + result, err := exec.ExecContext(ctx, sql, args...) + if err != nil { + return 0, errors.Wrap(err, "models: unable to delete all from youtubeAnnouncement slice") + } + + rowsAff, err := result.RowsAffected() + if err != nil { + return 0, errors.Wrap(err, "models: failed to get rows affected by deleteall for youtube_announcements") + } + + return rowsAff, nil +} + +// ReloadG refetches the object from the database using the primary keys. +func (o *YoutubeAnnouncement) ReloadG(ctx context.Context) error { + if o == nil { + return errors.New("models: no YoutubeAnnouncement provided for reload") + } + + return o.Reload(ctx, boil.GetContextDB()) +} + +// Reload refetches the object from the database +// using the primary keys with an executor. +func (o *YoutubeAnnouncement) Reload(ctx context.Context, exec boil.ContextExecutor) error { + ret, err := FindYoutubeAnnouncement(ctx, exec, o.GuildID) + if err != nil { + return err + } + + *o = *ret + return nil +} + +// ReloadAllG refetches every row with matching primary key column values +// and overwrites the original object slice with the newly updated slice. +func (o *YoutubeAnnouncementSlice) ReloadAllG(ctx context.Context) error { + if o == nil { + return errors.New("models: empty YoutubeAnnouncementSlice provided for reload all") + } + + return o.ReloadAll(ctx, boil.GetContextDB()) +} + +// ReloadAll refetches every row with matching primary key column values +// and overwrites the original object slice with the newly updated slice. +func (o *YoutubeAnnouncementSlice) ReloadAll(ctx context.Context, exec boil.ContextExecutor) error { + if o == nil || len(*o) == 0 { + return nil + } + + slice := YoutubeAnnouncementSlice{} + var args []interface{} + for _, obj := range *o { + pkeyArgs := queries.ValuesFromMapping(reflect.Indirect(reflect.ValueOf(obj)), youtubeAnnouncementPrimaryKeyMapping) + args = append(args, pkeyArgs...) + } + + sql := "SELECT \"youtube_announcements\".* FROM \"youtube_announcements\" WHERE " + + strmangle.WhereClauseRepeated(string(dialect.LQ), string(dialect.RQ), 1, youtubeAnnouncementPrimaryKeyColumns, len(*o)) + + q := queries.Raw(sql, args...) + + err := q.Bind(ctx, exec, &slice) + if err != nil { + return errors.Wrap(err, "models: unable to reload all in YoutubeAnnouncementSlice") + } + + *o = slice + + return nil +} + +// YoutubeAnnouncementExistsG checks if the YoutubeAnnouncement row exists. +func YoutubeAnnouncementExistsG(ctx context.Context, guildID int64) (bool, error) { + return YoutubeAnnouncementExists(ctx, boil.GetContextDB(), guildID) +} + +// YoutubeAnnouncementExists checks if the YoutubeAnnouncement row exists. +func YoutubeAnnouncementExists(ctx context.Context, exec boil.ContextExecutor, guildID int64) (bool, error) { + var exists bool + sql := "select exists(select 1 from \"youtube_announcements\" where \"guild_id\"=$1 limit 1)" + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, sql) + fmt.Fprintln(writer, guildID) + } + row := exec.QueryRowContext(ctx, sql, guildID) + + err := row.Scan(&exists) + if err != nil { + return false, errors.Wrap(err, "models: unable to check if youtube_announcements exists") + } + + return exists, nil +} + +// Exists checks if the YoutubeAnnouncement row exists. +func (o *YoutubeAnnouncement) Exists(ctx context.Context, exec boil.ContextExecutor) (bool, error) { + return YoutubeAnnouncementExists(ctx, exec, o.GuildID) +} diff --git a/youtube/models/youtube_channel_subscriptions.go b/youtube/models/youtube_channel_subscriptions.go new file mode 100644 index 0000000000..a8daf14031 --- /dev/null +++ b/youtube/models/youtube_channel_subscriptions.go @@ -0,0 +1,928 @@ +// Code generated by SQLBoiler 4.16.2 (https://github.com/volatiletech/sqlboiler). DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package models + +import ( + "context" + "database/sql" + "fmt" + "reflect" + "strconv" + "strings" + "sync" + "time" + + "github.com/friendsofgo/errors" + "github.com/volatiletech/sqlboiler/v4/boil" + "github.com/volatiletech/sqlboiler/v4/queries" + "github.com/volatiletech/sqlboiler/v4/queries/qm" + "github.com/volatiletech/sqlboiler/v4/queries/qmhelper" + "github.com/volatiletech/sqlboiler/v4/types" + "github.com/volatiletech/strmangle" +) + +// YoutubeChannelSubscription is an object representing the database table. +type YoutubeChannelSubscription struct { + ID int `boil:"id" json:"id" toml:"id" yaml:"id"` + CreatedAt time.Time `boil:"created_at" json:"created_at" toml:"created_at" yaml:"created_at"` + UpdatedAt time.Time `boil:"updated_at" json:"updated_at" toml:"updated_at" yaml:"updated_at"` + GuildID string `boil:"guild_id" json:"guild_id" toml:"guild_id" yaml:"guild_id"` + ChannelID string `boil:"channel_id" json:"channel_id" toml:"channel_id" yaml:"channel_id"` + YoutubeChannelID string `boil:"youtube_channel_id" json:"youtube_channel_id" toml:"youtube_channel_id" yaml:"youtube_channel_id"` + YoutubeChannelName string `boil:"youtube_channel_name" json:"youtube_channel_name" toml:"youtube_channel_name" yaml:"youtube_channel_name"` + MentionEveryone bool `boil:"mention_everyone" json:"mention_everyone" toml:"mention_everyone" yaml:"mention_everyone"` + MentionRoles types.Int64Array `boil:"mention_roles" json:"mention_roles,omitempty" toml:"mention_roles" yaml:"mention_roles,omitempty"` + PublishLivestream bool `boil:"publish_livestream" json:"publish_livestream" toml:"publish_livestream" yaml:"publish_livestream"` + PublishShorts bool `boil:"publish_shorts" json:"publish_shorts" toml:"publish_shorts" yaml:"publish_shorts"` + Enabled bool `boil:"enabled" json:"enabled" toml:"enabled" yaml:"enabled"` + + R *youtubeChannelSubscriptionR `boil:"-" json:"-" toml:"-" yaml:"-"` + L youtubeChannelSubscriptionL `boil:"-" json:"-" toml:"-" yaml:"-"` +} + +var YoutubeChannelSubscriptionColumns = struct { + ID string + CreatedAt string + UpdatedAt string + GuildID string + ChannelID string + YoutubeChannelID string + YoutubeChannelName string + MentionEveryone string + MentionRoles string + PublishLivestream string + PublishShorts string + Enabled string +}{ + ID: "id", + CreatedAt: "created_at", + UpdatedAt: "updated_at", + GuildID: "guild_id", + ChannelID: "channel_id", + YoutubeChannelID: "youtube_channel_id", + YoutubeChannelName: "youtube_channel_name", + MentionEveryone: "mention_everyone", + MentionRoles: "mention_roles", + PublishLivestream: "publish_livestream", + PublishShorts: "publish_shorts", + Enabled: "enabled", +} + +var YoutubeChannelSubscriptionTableColumns = struct { + ID string + CreatedAt string + UpdatedAt string + GuildID string + ChannelID string + YoutubeChannelID string + YoutubeChannelName string + MentionEveryone string + MentionRoles string + PublishLivestream string + PublishShorts string + Enabled string +}{ + ID: "youtube_channel_subscriptions.id", + CreatedAt: "youtube_channel_subscriptions.created_at", + UpdatedAt: "youtube_channel_subscriptions.updated_at", + GuildID: "youtube_channel_subscriptions.guild_id", + ChannelID: "youtube_channel_subscriptions.channel_id", + YoutubeChannelID: "youtube_channel_subscriptions.youtube_channel_id", + YoutubeChannelName: "youtube_channel_subscriptions.youtube_channel_name", + MentionEveryone: "youtube_channel_subscriptions.mention_everyone", + MentionRoles: "youtube_channel_subscriptions.mention_roles", + PublishLivestream: "youtube_channel_subscriptions.publish_livestream", + PublishShorts: "youtube_channel_subscriptions.publish_shorts", + Enabled: "youtube_channel_subscriptions.enabled", +} + +// Generated where + +type whereHelperint struct{ field string } + +func (w whereHelperint) EQ(x int) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.EQ, x) } +func (w whereHelperint) NEQ(x int) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.NEQ, x) } +func (w whereHelperint) LT(x int) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.LT, x) } +func (w whereHelperint) LTE(x int) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.LTE, x) } +func (w whereHelperint) GT(x int) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.GT, x) } +func (w whereHelperint) GTE(x int) qm.QueryMod { return qmhelper.Where(w.field, qmhelper.GTE, x) } +func (w whereHelperint) IN(slice []int) qm.QueryMod { + values := make([]interface{}, 0, len(slice)) + for _, value := range slice { + values = append(values, value) + } + return qm.WhereIn(fmt.Sprintf("%s IN ?", w.field), values...) +} +func (w whereHelperint) NIN(slice []int) qm.QueryMod { + values := make([]interface{}, 0, len(slice)) + for _, value := range slice { + values = append(values, value) + } + return qm.WhereNotIn(fmt.Sprintf("%s NOT IN ?", w.field), values...) +} + +type whereHelpertime_Time struct{ field string } + +func (w whereHelpertime_Time) EQ(x time.Time) qm.QueryMod { + return qmhelper.Where(w.field, qmhelper.EQ, x) +} +func (w whereHelpertime_Time) NEQ(x time.Time) qm.QueryMod { + return qmhelper.Where(w.field, qmhelper.NEQ, x) +} +func (w whereHelpertime_Time) LT(x time.Time) qm.QueryMod { + return qmhelper.Where(w.field, qmhelper.LT, x) +} +func (w whereHelpertime_Time) LTE(x time.Time) qm.QueryMod { + return qmhelper.Where(w.field, qmhelper.LTE, x) +} +func (w whereHelpertime_Time) GT(x time.Time) qm.QueryMod { + return qmhelper.Where(w.field, qmhelper.GT, x) +} +func (w whereHelpertime_Time) GTE(x time.Time) qm.QueryMod { + return qmhelper.Where(w.field, qmhelper.GTE, x) +} + +type whereHelpertypes_Int64Array struct{ field string } + +func (w whereHelpertypes_Int64Array) EQ(x types.Int64Array) qm.QueryMod { + return qmhelper.WhereNullEQ(w.field, false, x) +} +func (w whereHelpertypes_Int64Array) NEQ(x types.Int64Array) qm.QueryMod { + return qmhelper.WhereNullEQ(w.field, true, x) +} +func (w whereHelpertypes_Int64Array) LT(x types.Int64Array) qm.QueryMod { + return qmhelper.Where(w.field, qmhelper.LT, x) +} +func (w whereHelpertypes_Int64Array) LTE(x types.Int64Array) qm.QueryMod { + return qmhelper.Where(w.field, qmhelper.LTE, x) +} +func (w whereHelpertypes_Int64Array) GT(x types.Int64Array) qm.QueryMod { + return qmhelper.Where(w.field, qmhelper.GT, x) +} +func (w whereHelpertypes_Int64Array) GTE(x types.Int64Array) qm.QueryMod { + return qmhelper.Where(w.field, qmhelper.GTE, x) +} + +func (w whereHelpertypes_Int64Array) IsNull() qm.QueryMod { return qmhelper.WhereIsNull(w.field) } +func (w whereHelpertypes_Int64Array) IsNotNull() qm.QueryMod { return qmhelper.WhereIsNotNull(w.field) } + +var YoutubeChannelSubscriptionWhere = struct { + ID whereHelperint + CreatedAt whereHelpertime_Time + UpdatedAt whereHelpertime_Time + GuildID whereHelperstring + ChannelID whereHelperstring + YoutubeChannelID whereHelperstring + YoutubeChannelName whereHelperstring + MentionEveryone whereHelperbool + MentionRoles whereHelpertypes_Int64Array + PublishLivestream whereHelperbool + PublishShorts whereHelperbool + Enabled whereHelperbool +}{ + ID: whereHelperint{field: "\"youtube_channel_subscriptions\".\"id\""}, + CreatedAt: whereHelpertime_Time{field: "\"youtube_channel_subscriptions\".\"created_at\""}, + UpdatedAt: whereHelpertime_Time{field: "\"youtube_channel_subscriptions\".\"updated_at\""}, + GuildID: whereHelperstring{field: "\"youtube_channel_subscriptions\".\"guild_id\""}, + ChannelID: whereHelperstring{field: "\"youtube_channel_subscriptions\".\"channel_id\""}, + YoutubeChannelID: whereHelperstring{field: "\"youtube_channel_subscriptions\".\"youtube_channel_id\""}, + YoutubeChannelName: whereHelperstring{field: "\"youtube_channel_subscriptions\".\"youtube_channel_name\""}, + MentionEveryone: whereHelperbool{field: "\"youtube_channel_subscriptions\".\"mention_everyone\""}, + MentionRoles: whereHelpertypes_Int64Array{field: "\"youtube_channel_subscriptions\".\"mention_roles\""}, + PublishLivestream: whereHelperbool{field: "\"youtube_channel_subscriptions\".\"publish_livestream\""}, + PublishShorts: whereHelperbool{field: "\"youtube_channel_subscriptions\".\"publish_shorts\""}, + Enabled: whereHelperbool{field: "\"youtube_channel_subscriptions\".\"enabled\""}, +} + +// YoutubeChannelSubscriptionRels is where relationship names are stored. +var YoutubeChannelSubscriptionRels = struct { +}{} + +// youtubeChannelSubscriptionR is where relationships are stored. +type youtubeChannelSubscriptionR struct { +} + +// NewStruct creates a new relationship struct +func (*youtubeChannelSubscriptionR) NewStruct() *youtubeChannelSubscriptionR { + return &youtubeChannelSubscriptionR{} +} + +// youtubeChannelSubscriptionL is where Load methods for each relationship are stored. +type youtubeChannelSubscriptionL struct{} + +var ( + youtubeChannelSubscriptionAllColumns = []string{"id", "created_at", "updated_at", "guild_id", "channel_id", "youtube_channel_id", "youtube_channel_name", "mention_everyone", "mention_roles", "publish_livestream", "publish_shorts", "enabled"} + youtubeChannelSubscriptionColumnsWithoutDefault = []string{"created_at", "updated_at", "guild_id", "channel_id", "youtube_channel_id", "youtube_channel_name", "mention_everyone"} + youtubeChannelSubscriptionColumnsWithDefault = []string{"id", "mention_roles", "publish_livestream", "publish_shorts", "enabled"} + youtubeChannelSubscriptionPrimaryKeyColumns = []string{"id"} + youtubeChannelSubscriptionGeneratedColumns = []string{} +) + +type ( + // YoutubeChannelSubscriptionSlice is an alias for a slice of pointers to YoutubeChannelSubscription. + // This should almost always be used instead of []YoutubeChannelSubscription. + YoutubeChannelSubscriptionSlice []*YoutubeChannelSubscription + + youtubeChannelSubscriptionQuery struct { + *queries.Query + } +) + +// Cache for insert, update and upsert +var ( + youtubeChannelSubscriptionType = reflect.TypeOf(&YoutubeChannelSubscription{}) + youtubeChannelSubscriptionMapping = queries.MakeStructMapping(youtubeChannelSubscriptionType) + youtubeChannelSubscriptionPrimaryKeyMapping, _ = queries.BindMapping(youtubeChannelSubscriptionType, youtubeChannelSubscriptionMapping, youtubeChannelSubscriptionPrimaryKeyColumns) + youtubeChannelSubscriptionInsertCacheMut sync.RWMutex + youtubeChannelSubscriptionInsertCache = make(map[string]insertCache) + youtubeChannelSubscriptionUpdateCacheMut sync.RWMutex + youtubeChannelSubscriptionUpdateCache = make(map[string]updateCache) + youtubeChannelSubscriptionUpsertCacheMut sync.RWMutex + youtubeChannelSubscriptionUpsertCache = make(map[string]insertCache) +) + +var ( + // Force time package dependency for automated UpdatedAt/CreatedAt. + _ = time.Second + // Force qmhelper dependency for where clause generation (which doesn't + // always happen) + _ = qmhelper.Where +) + +// OneG returns a single youtubeChannelSubscription record from the query using the global executor. +func (q youtubeChannelSubscriptionQuery) OneG(ctx context.Context) (*YoutubeChannelSubscription, error) { + return q.One(ctx, boil.GetContextDB()) +} + +// One returns a single youtubeChannelSubscription record from the query. +func (q youtubeChannelSubscriptionQuery) One(ctx context.Context, exec boil.ContextExecutor) (*YoutubeChannelSubscription, error) { + o := &YoutubeChannelSubscription{} + + queries.SetLimit(q.Query, 1) + + err := q.Bind(ctx, exec, o) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + return nil, sql.ErrNoRows + } + return nil, errors.Wrap(err, "models: failed to execute a one query for youtube_channel_subscriptions") + } + + return o, nil +} + +// AllG returns all YoutubeChannelSubscription records from the query using the global executor. +func (q youtubeChannelSubscriptionQuery) AllG(ctx context.Context) (YoutubeChannelSubscriptionSlice, error) { + return q.All(ctx, boil.GetContextDB()) +} + +// All returns all YoutubeChannelSubscription records from the query. +func (q youtubeChannelSubscriptionQuery) All(ctx context.Context, exec boil.ContextExecutor) (YoutubeChannelSubscriptionSlice, error) { + var o []*YoutubeChannelSubscription + + err := q.Bind(ctx, exec, &o) + if err != nil { + return nil, errors.Wrap(err, "models: failed to assign all query results to YoutubeChannelSubscription slice") + } + + return o, nil +} + +// CountG returns the count of all YoutubeChannelSubscription records in the query using the global executor +func (q youtubeChannelSubscriptionQuery) CountG(ctx context.Context) (int64, error) { + return q.Count(ctx, boil.GetContextDB()) +} + +// Count returns the count of all YoutubeChannelSubscription records in the query. +func (q youtubeChannelSubscriptionQuery) Count(ctx context.Context, exec boil.ContextExecutor) (int64, error) { + var count int64 + + queries.SetSelect(q.Query, nil) + queries.SetCount(q.Query) + + err := q.Query.QueryRowContext(ctx, exec).Scan(&count) + if err != nil { + return 0, errors.Wrap(err, "models: failed to count youtube_channel_subscriptions rows") + } + + return count, nil +} + +// ExistsG checks if the row exists in the table using the global executor. +func (q youtubeChannelSubscriptionQuery) ExistsG(ctx context.Context) (bool, error) { + return q.Exists(ctx, boil.GetContextDB()) +} + +// Exists checks if the row exists in the table. +func (q youtubeChannelSubscriptionQuery) Exists(ctx context.Context, exec boil.ContextExecutor) (bool, error) { + var count int64 + + queries.SetSelect(q.Query, nil) + queries.SetCount(q.Query) + queries.SetLimit(q.Query, 1) + + err := q.Query.QueryRowContext(ctx, exec).Scan(&count) + if err != nil { + return false, errors.Wrap(err, "models: failed to check if youtube_channel_subscriptions exists") + } + + return count > 0, nil +} + +// YoutubeChannelSubscriptions retrieves all the records using an executor. +func YoutubeChannelSubscriptions(mods ...qm.QueryMod) youtubeChannelSubscriptionQuery { + mods = append(mods, qm.From("\"youtube_channel_subscriptions\"")) + q := NewQuery(mods...) + if len(queries.GetSelect(q)) == 0 { + queries.SetSelect(q, []string{"\"youtube_channel_subscriptions\".*"}) + } + + return youtubeChannelSubscriptionQuery{q} +} + +// FindYoutubeChannelSubscriptionG retrieves a single record by ID. +func FindYoutubeChannelSubscriptionG(ctx context.Context, iD int, selectCols ...string) (*YoutubeChannelSubscription, error) { + return FindYoutubeChannelSubscription(ctx, boil.GetContextDB(), iD, selectCols...) +} + +// FindYoutubeChannelSubscription retrieves a single record by ID with an executor. +// If selectCols is empty Find will return all columns. +func FindYoutubeChannelSubscription(ctx context.Context, exec boil.ContextExecutor, iD int, selectCols ...string) (*YoutubeChannelSubscription, error) { + youtubeChannelSubscriptionObj := &YoutubeChannelSubscription{} + + sel := "*" + if len(selectCols) > 0 { + sel = strings.Join(strmangle.IdentQuoteSlice(dialect.LQ, dialect.RQ, selectCols), ",") + } + query := fmt.Sprintf( + "select %s from \"youtube_channel_subscriptions\" where \"id\"=$1", sel, + ) + + q := queries.Raw(query, iD) + + err := q.Bind(ctx, exec, youtubeChannelSubscriptionObj) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + return nil, sql.ErrNoRows + } + return nil, errors.Wrap(err, "models: unable to select from youtube_channel_subscriptions") + } + + return youtubeChannelSubscriptionObj, nil +} + +// InsertG a single record. See Insert for whitelist behavior description. +func (o *YoutubeChannelSubscription) InsertG(ctx context.Context, columns boil.Columns) error { + return o.Insert(ctx, boil.GetContextDB(), columns) +} + +// Insert a single record using an executor. +// See boil.Columns.InsertColumnSet documentation to understand column list inference for inserts. +func (o *YoutubeChannelSubscription) Insert(ctx context.Context, exec boil.ContextExecutor, columns boil.Columns) error { + if o == nil { + return errors.New("models: no youtube_channel_subscriptions provided for insertion") + } + + var err error + if !boil.TimestampsAreSkipped(ctx) { + currTime := time.Now().In(boil.GetLocation()) + + if o.CreatedAt.IsZero() { + o.CreatedAt = currTime + } + if o.UpdatedAt.IsZero() { + o.UpdatedAt = currTime + } + } + + nzDefaults := queries.NonZeroDefaultSet(youtubeChannelSubscriptionColumnsWithDefault, o) + + key := makeCacheKey(columns, nzDefaults) + youtubeChannelSubscriptionInsertCacheMut.RLock() + cache, cached := youtubeChannelSubscriptionInsertCache[key] + youtubeChannelSubscriptionInsertCacheMut.RUnlock() + + if !cached { + wl, returnColumns := columns.InsertColumnSet( + youtubeChannelSubscriptionAllColumns, + youtubeChannelSubscriptionColumnsWithDefault, + youtubeChannelSubscriptionColumnsWithoutDefault, + nzDefaults, + ) + + cache.valueMapping, err = queries.BindMapping(youtubeChannelSubscriptionType, youtubeChannelSubscriptionMapping, wl) + if err != nil { + return err + } + cache.retMapping, err = queries.BindMapping(youtubeChannelSubscriptionType, youtubeChannelSubscriptionMapping, returnColumns) + if err != nil { + return err + } + if len(wl) != 0 { + cache.query = fmt.Sprintf("INSERT INTO \"youtube_channel_subscriptions\" (\"%s\") %%sVALUES (%s)%%s", strings.Join(wl, "\",\""), strmangle.Placeholders(dialect.UseIndexPlaceholders, len(wl), 1, 1)) + } else { + cache.query = "INSERT INTO \"youtube_channel_subscriptions\" %sDEFAULT VALUES%s" + } + + var queryOutput, queryReturning string + + if len(cache.retMapping) != 0 { + queryReturning = fmt.Sprintf(" RETURNING \"%s\"", strings.Join(returnColumns, "\",\"")) + } + + cache.query = fmt.Sprintf(cache.query, queryOutput, queryReturning) + } + + value := reflect.Indirect(reflect.ValueOf(o)) + vals := queries.ValuesFromMapping(value, cache.valueMapping) + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, cache.query) + fmt.Fprintln(writer, vals) + } + + if len(cache.retMapping) != 0 { + err = exec.QueryRowContext(ctx, cache.query, vals...).Scan(queries.PtrsFromMapping(value, cache.retMapping)...) + } else { + _, err = exec.ExecContext(ctx, cache.query, vals...) + } + + if err != nil { + return errors.Wrap(err, "models: unable to insert into youtube_channel_subscriptions") + } + + if !cached { + youtubeChannelSubscriptionInsertCacheMut.Lock() + youtubeChannelSubscriptionInsertCache[key] = cache + youtubeChannelSubscriptionInsertCacheMut.Unlock() + } + + return nil +} + +// UpdateG a single YoutubeChannelSubscription record using the global executor. +// See Update for more documentation. +func (o *YoutubeChannelSubscription) UpdateG(ctx context.Context, columns boil.Columns) (int64, error) { + return o.Update(ctx, boil.GetContextDB(), columns) +} + +// Update uses an executor to update the YoutubeChannelSubscription. +// See boil.Columns.UpdateColumnSet documentation to understand column list inference for updates. +// Update does not automatically update the record in case of default values. Use .Reload() to refresh the records. +func (o *YoutubeChannelSubscription) Update(ctx context.Context, exec boil.ContextExecutor, columns boil.Columns) (int64, error) { + if !boil.TimestampsAreSkipped(ctx) { + currTime := time.Now().In(boil.GetLocation()) + + o.UpdatedAt = currTime + } + + var err error + key := makeCacheKey(columns, nil) + youtubeChannelSubscriptionUpdateCacheMut.RLock() + cache, cached := youtubeChannelSubscriptionUpdateCache[key] + youtubeChannelSubscriptionUpdateCacheMut.RUnlock() + + if !cached { + wl := columns.UpdateColumnSet( + youtubeChannelSubscriptionAllColumns, + youtubeChannelSubscriptionPrimaryKeyColumns, + ) + + if !columns.IsWhitelist() { + wl = strmangle.SetComplement(wl, []string{"created_at"}) + } + if len(wl) == 0 { + return 0, errors.New("models: unable to update youtube_channel_subscriptions, could not build whitelist") + } + + cache.query = fmt.Sprintf("UPDATE \"youtube_channel_subscriptions\" SET %s WHERE %s", + strmangle.SetParamNames("\"", "\"", 1, wl), + strmangle.WhereClause("\"", "\"", len(wl)+1, youtubeChannelSubscriptionPrimaryKeyColumns), + ) + cache.valueMapping, err = queries.BindMapping(youtubeChannelSubscriptionType, youtubeChannelSubscriptionMapping, append(wl, youtubeChannelSubscriptionPrimaryKeyColumns...)) + if err != nil { + return 0, err + } + } + + values := queries.ValuesFromMapping(reflect.Indirect(reflect.ValueOf(o)), cache.valueMapping) + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, cache.query) + fmt.Fprintln(writer, values) + } + var result sql.Result + result, err = exec.ExecContext(ctx, cache.query, values...) + if err != nil { + return 0, errors.Wrap(err, "models: unable to update youtube_channel_subscriptions row") + } + + rowsAff, err := result.RowsAffected() + if err != nil { + return 0, errors.Wrap(err, "models: failed to get rows affected by update for youtube_channel_subscriptions") + } + + if !cached { + youtubeChannelSubscriptionUpdateCacheMut.Lock() + youtubeChannelSubscriptionUpdateCache[key] = cache + youtubeChannelSubscriptionUpdateCacheMut.Unlock() + } + + return rowsAff, nil +} + +// UpdateAllG updates all rows with the specified column values. +func (q youtubeChannelSubscriptionQuery) UpdateAllG(ctx context.Context, cols M) (int64, error) { + return q.UpdateAll(ctx, boil.GetContextDB(), cols) +} + +// UpdateAll updates all rows with the specified column values. +func (q youtubeChannelSubscriptionQuery) UpdateAll(ctx context.Context, exec boil.ContextExecutor, cols M) (int64, error) { + queries.SetUpdate(q.Query, cols) + + result, err := q.Query.ExecContext(ctx, exec) + if err != nil { + return 0, errors.Wrap(err, "models: unable to update all for youtube_channel_subscriptions") + } + + rowsAff, err := result.RowsAffected() + if err != nil { + return 0, errors.Wrap(err, "models: unable to retrieve rows affected for youtube_channel_subscriptions") + } + + return rowsAff, nil +} + +// UpdateAllG updates all rows with the specified column values. +func (o YoutubeChannelSubscriptionSlice) UpdateAllG(ctx context.Context, cols M) (int64, error) { + return o.UpdateAll(ctx, boil.GetContextDB(), cols) +} + +// UpdateAll updates all rows with the specified column values, using an executor. +func (o YoutubeChannelSubscriptionSlice) UpdateAll(ctx context.Context, exec boil.ContextExecutor, cols M) (int64, error) { + ln := int64(len(o)) + if ln == 0 { + return 0, nil + } + + if len(cols) == 0 { + return 0, errors.New("models: update all requires at least one column argument") + } + + colNames := make([]string, len(cols)) + args := make([]interface{}, len(cols)) + + i := 0 + for name, value := range cols { + colNames[i] = name + args[i] = value + i++ + } + + // Append all of the primary key values for each column + for _, obj := range o { + pkeyArgs := queries.ValuesFromMapping(reflect.Indirect(reflect.ValueOf(obj)), youtubeChannelSubscriptionPrimaryKeyMapping) + args = append(args, pkeyArgs...) + } + + sql := fmt.Sprintf("UPDATE \"youtube_channel_subscriptions\" SET %s WHERE %s", + strmangle.SetParamNames("\"", "\"", 1, colNames), + strmangle.WhereClauseRepeated(string(dialect.LQ), string(dialect.RQ), len(colNames)+1, youtubeChannelSubscriptionPrimaryKeyColumns, len(o))) + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, sql) + fmt.Fprintln(writer, args...) + } + result, err := exec.ExecContext(ctx, sql, args...) + if err != nil { + return 0, errors.Wrap(err, "models: unable to update all in youtubeChannelSubscription slice") + } + + rowsAff, err := result.RowsAffected() + if err != nil { + return 0, errors.Wrap(err, "models: unable to retrieve rows affected all in update all youtubeChannelSubscription") + } + return rowsAff, nil +} + +// UpsertG attempts an insert, and does an update or ignore on conflict. +func (o *YoutubeChannelSubscription) UpsertG(ctx context.Context, updateOnConflict bool, conflictColumns []string, updateColumns, insertColumns boil.Columns, opts ...UpsertOptionFunc) error { + return o.Upsert(ctx, boil.GetContextDB(), updateOnConflict, conflictColumns, updateColumns, insertColumns, opts...) +} + +// Upsert attempts an insert using an executor, and does an update or ignore on conflict. +// See boil.Columns documentation for how to properly use updateColumns and insertColumns. +func (o *YoutubeChannelSubscription) Upsert(ctx context.Context, exec boil.ContextExecutor, updateOnConflict bool, conflictColumns []string, updateColumns, insertColumns boil.Columns, opts ...UpsertOptionFunc) error { + if o == nil { + return errors.New("models: no youtube_channel_subscriptions provided for upsert") + } + if !boil.TimestampsAreSkipped(ctx) { + currTime := time.Now().In(boil.GetLocation()) + + if o.CreatedAt.IsZero() { + o.CreatedAt = currTime + } + o.UpdatedAt = currTime + } + + nzDefaults := queries.NonZeroDefaultSet(youtubeChannelSubscriptionColumnsWithDefault, o) + + // Build cache key in-line uglily - mysql vs psql problems + buf := strmangle.GetBuffer() + if updateOnConflict { + buf.WriteByte('t') + } else { + buf.WriteByte('f') + } + buf.WriteByte('.') + for _, c := range conflictColumns { + buf.WriteString(c) + } + buf.WriteByte('.') + buf.WriteString(strconv.Itoa(updateColumns.Kind)) + for _, c := range updateColumns.Cols { + buf.WriteString(c) + } + buf.WriteByte('.') + buf.WriteString(strconv.Itoa(insertColumns.Kind)) + for _, c := range insertColumns.Cols { + buf.WriteString(c) + } + buf.WriteByte('.') + for _, c := range nzDefaults { + buf.WriteString(c) + } + key := buf.String() + strmangle.PutBuffer(buf) + + youtubeChannelSubscriptionUpsertCacheMut.RLock() + cache, cached := youtubeChannelSubscriptionUpsertCache[key] + youtubeChannelSubscriptionUpsertCacheMut.RUnlock() + + var err error + + if !cached { + insert, _ := insertColumns.InsertColumnSet( + youtubeChannelSubscriptionAllColumns, + youtubeChannelSubscriptionColumnsWithDefault, + youtubeChannelSubscriptionColumnsWithoutDefault, + nzDefaults, + ) + + update := updateColumns.UpdateColumnSet( + youtubeChannelSubscriptionAllColumns, + youtubeChannelSubscriptionPrimaryKeyColumns, + ) + + if updateOnConflict && len(update) == 0 { + return errors.New("models: unable to upsert youtube_channel_subscriptions, could not build update column list") + } + + ret := strmangle.SetComplement(youtubeChannelSubscriptionAllColumns, strmangle.SetIntersect(insert, update)) + + conflict := conflictColumns + if len(conflict) == 0 && updateOnConflict && len(update) != 0 { + if len(youtubeChannelSubscriptionPrimaryKeyColumns) == 0 { + return errors.New("models: unable to upsert youtube_channel_subscriptions, could not build conflict column list") + } + + conflict = make([]string, len(youtubeChannelSubscriptionPrimaryKeyColumns)) + copy(conflict, youtubeChannelSubscriptionPrimaryKeyColumns) + } + cache.query = buildUpsertQueryPostgres(dialect, "\"youtube_channel_subscriptions\"", updateOnConflict, ret, update, conflict, insert, opts...) + + cache.valueMapping, err = queries.BindMapping(youtubeChannelSubscriptionType, youtubeChannelSubscriptionMapping, insert) + if err != nil { + return err + } + if len(ret) != 0 { + cache.retMapping, err = queries.BindMapping(youtubeChannelSubscriptionType, youtubeChannelSubscriptionMapping, ret) + if err != nil { + return err + } + } + } + + value := reflect.Indirect(reflect.ValueOf(o)) + vals := queries.ValuesFromMapping(value, cache.valueMapping) + var returns []interface{} + if len(cache.retMapping) != 0 { + returns = queries.PtrsFromMapping(value, cache.retMapping) + } + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, cache.query) + fmt.Fprintln(writer, vals) + } + if len(cache.retMapping) != 0 { + err = exec.QueryRowContext(ctx, cache.query, vals...).Scan(returns...) + if errors.Is(err, sql.ErrNoRows) { + err = nil // Postgres doesn't return anything when there's no update + } + } else { + _, err = exec.ExecContext(ctx, cache.query, vals...) + } + if err != nil { + return errors.Wrap(err, "models: unable to upsert youtube_channel_subscriptions") + } + + if !cached { + youtubeChannelSubscriptionUpsertCacheMut.Lock() + youtubeChannelSubscriptionUpsertCache[key] = cache + youtubeChannelSubscriptionUpsertCacheMut.Unlock() + } + + return nil +} + +// DeleteG deletes a single YoutubeChannelSubscription record. +// DeleteG will match against the primary key column to find the record to delete. +func (o *YoutubeChannelSubscription) DeleteG(ctx context.Context) (int64, error) { + return o.Delete(ctx, boil.GetContextDB()) +} + +// Delete deletes a single YoutubeChannelSubscription record with an executor. +// Delete will match against the primary key column to find the record to delete. +func (o *YoutubeChannelSubscription) Delete(ctx context.Context, exec boil.ContextExecutor) (int64, error) { + if o == nil { + return 0, errors.New("models: no YoutubeChannelSubscription provided for delete") + } + + args := queries.ValuesFromMapping(reflect.Indirect(reflect.ValueOf(o)), youtubeChannelSubscriptionPrimaryKeyMapping) + sql := "DELETE FROM \"youtube_channel_subscriptions\" WHERE \"id\"=$1" + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, sql) + fmt.Fprintln(writer, args...) + } + result, err := exec.ExecContext(ctx, sql, args...) + if err != nil { + return 0, errors.Wrap(err, "models: unable to delete from youtube_channel_subscriptions") + } + + rowsAff, err := result.RowsAffected() + if err != nil { + return 0, errors.Wrap(err, "models: failed to get rows affected by delete for youtube_channel_subscriptions") + } + + return rowsAff, nil +} + +func (q youtubeChannelSubscriptionQuery) DeleteAllG(ctx context.Context) (int64, error) { + return q.DeleteAll(ctx, boil.GetContextDB()) +} + +// DeleteAll deletes all matching rows. +func (q youtubeChannelSubscriptionQuery) DeleteAll(ctx context.Context, exec boil.ContextExecutor) (int64, error) { + if q.Query == nil { + return 0, errors.New("models: no youtubeChannelSubscriptionQuery provided for delete all") + } + + queries.SetDelete(q.Query) + + result, err := q.Query.ExecContext(ctx, exec) + if err != nil { + return 0, errors.Wrap(err, "models: unable to delete all from youtube_channel_subscriptions") + } + + rowsAff, err := result.RowsAffected() + if err != nil { + return 0, errors.Wrap(err, "models: failed to get rows affected by deleteall for youtube_channel_subscriptions") + } + + return rowsAff, nil +} + +// DeleteAllG deletes all rows in the slice. +func (o YoutubeChannelSubscriptionSlice) DeleteAllG(ctx context.Context) (int64, error) { + return o.DeleteAll(ctx, boil.GetContextDB()) +} + +// DeleteAll deletes all rows in the slice, using an executor. +func (o YoutubeChannelSubscriptionSlice) DeleteAll(ctx context.Context, exec boil.ContextExecutor) (int64, error) { + if len(o) == 0 { + return 0, nil + } + + var args []interface{} + for _, obj := range o { + pkeyArgs := queries.ValuesFromMapping(reflect.Indirect(reflect.ValueOf(obj)), youtubeChannelSubscriptionPrimaryKeyMapping) + args = append(args, pkeyArgs...) + } + + sql := "DELETE FROM \"youtube_channel_subscriptions\" WHERE " + + strmangle.WhereClauseRepeated(string(dialect.LQ), string(dialect.RQ), 1, youtubeChannelSubscriptionPrimaryKeyColumns, len(o)) + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, sql) + fmt.Fprintln(writer, args) + } + result, err := exec.ExecContext(ctx, sql, args...) + if err != nil { + return 0, errors.Wrap(err, "models: unable to delete all from youtubeChannelSubscription slice") + } + + rowsAff, err := result.RowsAffected() + if err != nil { + return 0, errors.Wrap(err, "models: failed to get rows affected by deleteall for youtube_channel_subscriptions") + } + + return rowsAff, nil +} + +// ReloadG refetches the object from the database using the primary keys. +func (o *YoutubeChannelSubscription) ReloadG(ctx context.Context) error { + if o == nil { + return errors.New("models: no YoutubeChannelSubscription provided for reload") + } + + return o.Reload(ctx, boil.GetContextDB()) +} + +// Reload refetches the object from the database +// using the primary keys with an executor. +func (o *YoutubeChannelSubscription) Reload(ctx context.Context, exec boil.ContextExecutor) error { + ret, err := FindYoutubeChannelSubscription(ctx, exec, o.ID) + if err != nil { + return err + } + + *o = *ret + return nil +} + +// ReloadAllG refetches every row with matching primary key column values +// and overwrites the original object slice with the newly updated slice. +func (o *YoutubeChannelSubscriptionSlice) ReloadAllG(ctx context.Context) error { + if o == nil { + return errors.New("models: empty YoutubeChannelSubscriptionSlice provided for reload all") + } + + return o.ReloadAll(ctx, boil.GetContextDB()) +} + +// ReloadAll refetches every row with matching primary key column values +// and overwrites the original object slice with the newly updated slice. +func (o *YoutubeChannelSubscriptionSlice) ReloadAll(ctx context.Context, exec boil.ContextExecutor) error { + if o == nil || len(*o) == 0 { + return nil + } + + slice := YoutubeChannelSubscriptionSlice{} + var args []interface{} + for _, obj := range *o { + pkeyArgs := queries.ValuesFromMapping(reflect.Indirect(reflect.ValueOf(obj)), youtubeChannelSubscriptionPrimaryKeyMapping) + args = append(args, pkeyArgs...) + } + + sql := "SELECT \"youtube_channel_subscriptions\".* FROM \"youtube_channel_subscriptions\" WHERE " + + strmangle.WhereClauseRepeated(string(dialect.LQ), string(dialect.RQ), 1, youtubeChannelSubscriptionPrimaryKeyColumns, len(*o)) + + q := queries.Raw(sql, args...) + + err := q.Bind(ctx, exec, &slice) + if err != nil { + return errors.Wrap(err, "models: unable to reload all in YoutubeChannelSubscriptionSlice") + } + + *o = slice + + return nil +} + +// YoutubeChannelSubscriptionExistsG checks if the YoutubeChannelSubscription row exists. +func YoutubeChannelSubscriptionExistsG(ctx context.Context, iD int) (bool, error) { + return YoutubeChannelSubscriptionExists(ctx, boil.GetContextDB(), iD) +} + +// YoutubeChannelSubscriptionExists checks if the YoutubeChannelSubscription row exists. +func YoutubeChannelSubscriptionExists(ctx context.Context, exec boil.ContextExecutor, iD int) (bool, error) { + var exists bool + sql := "select exists(select 1 from \"youtube_channel_subscriptions\" where \"id\"=$1 limit 1)" + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, sql) + fmt.Fprintln(writer, iD) + } + row := exec.QueryRowContext(ctx, sql, iD) + + err := row.Scan(&exists) + if err != nil { + return false, errors.Wrap(err, "models: unable to check if youtube_channel_subscriptions exists") + } + + return exists, nil +} + +// Exists checks if the YoutubeChannelSubscription row exists. +func (o *YoutubeChannelSubscription) Exists(ctx context.Context, exec boil.ContextExecutor) (bool, error) { + return YoutubeChannelSubscriptionExists(ctx, exec, o.ID) +} diff --git a/youtube/schema.go b/youtube/schema.go new file mode 100644 index 0000000000..92f887d48c --- /dev/null +++ b/youtube/schema.go @@ -0,0 +1,76 @@ +package youtube + +var DBSchemas = []string{` +CREATE TABLE IF NOT EXISTS youtube_channel_subscriptions ( + id SERIAL PRIMARY KEY, + created_at TIMESTAMP WITH TIME ZONE NOT NULL, + updated_at TIMESTAMP WITH TIME ZONE NOT NULL, + + guild_id TEXT NOT NULL, + channel_id TEXT NOT NULL, + youtube_channel_id TEXT NOT NULL, + youtube_channel_name TEXT NOT NULL, + + mention_everyone BOOLEAN NOT NULL, + mention_roles BIGINT[], + publish_livestream BOOLEAN NOT NULL DEFAULT TRUE, + publish_shorts BOOLEAN NOT NULL DEFAULT TRUE, + enabled BOOLEAN NOT NULL DEFAULT TRUE +); +`, ` + +-- Old tables managed with gorm are missing NOT NULL constraints on some columns +-- that are never null in existing records; add them as needed. + +ALTER TABLE youtube_channel_subscriptions ALTER COLUMN created_at SET NOT NULL; +`, ` +ALTER TABLE youtube_channel_subscriptions ALTER COLUMN updated_at SET NOT NULL; +`, ` +ALTER TABLE youtube_channel_subscriptions ALTER COLUMN guild_id SET NOT NULL; +`, ` +ALTER TABLE youtube_channel_subscriptions ALTER COLUMN channel_id SET NOT NULL; +`, ` +ALTER TABLE youtube_channel_subscriptions ALTER COLUMN youtube_channel_id SET NOT NULL; +`, ` +ALTER TABLE youtube_channel_subscriptions ALTER COLUMN youtube_channel_name SET NOT NULL; +`, ` +ALTER TABLE youtube_channel_subscriptions ALTER COLUMN mention_everyone SET NOT NULL; + +-- Can't add a NOT NULL constraint to mention_roles because too many records in +-- production have it set to null. +`, ` + +-- The migration for the publish_livestream, publish_shorts, and enabled columns is +-- more involved. These columns were added later and so it is possible that they are +-- null in some older records. Therefore, we first replace these missing values with +-- defaults before adding the NOT NULL constraint. + +DO $$ +BEGIN + +-- only run if we haven't added the NOT NULL constraint yet +IF EXISTS(SELECT 1 FROM information_schema.columns WHERE table_name='youtube_channel_subscriptions' AND column_name='publish_livestream' AND is_nullable='yes') THEN + UPDATE youtube_channel_subscriptions SET publish_livestream = TRUE WHERE publish_livestream IS NULL; + UPDATE youtube_channel_subscriptions SET publish_shorts = TRUE WHERE publish_shorts IS NULL; + UPDATE youtube_channel_subscriptions SET enabled = TRUE WHERE enabled IS NULL; + + ALTER TABLE youtube_channel_subscriptions ALTER COLUMN publish_livestream SET NOT NULL; + ALTER TABLE youtube_channel_subscriptions ALTER COLUMN publish_shorts SET NOT NULL; + ALTER TABLE youtube_channel_subscriptions ALTER COLUMN enabled SET NOT NULL; +END IF; +END $$; +`, ` + +CREATE TABLE IF NOT EXISTS youtube_announcements ( + guild_id BIGINT PRIMARY KEY, + message TEXT NOT NULL, + enabled BOOLEAN NOT NULL DEFAULT FALSE +); +`, ` + +ALTER TABLE youtube_announcements ALTER COLUMN guild_id SET NOT NULL; +`, ` +ALTER TABLE youtube_announcements ALTER COLUMN message SET NOT NULL; +`, ` +ALTER TABLE youtube_announcements ALTER COLUMN enabled SET NOT NULL; +`} diff --git a/youtube/sqlboiler.toml b/youtube/sqlboiler.toml new file mode 100644 index 0000000000..5de144e45f --- /dev/null +++ b/youtube/sqlboiler.toml @@ -0,0 +1,15 @@ +add-global-variants = true +no-hooks = true +no-tests = true + +[psql] +dbname = "yagpdb" +host = "localhost" +user = "postgres" +pass = "pass" +sslmode = "disable" +whitelist = ["youtube_channel_subscriptions", "youtube_announcements"] + +[auto-columns] +created = "created_at" +updated = "updated_at" diff --git a/youtube/web.go b/youtube/web.go index b4af046886..a89589e19c 100644 --- a/youtube/web.go +++ b/youtube/web.go @@ -2,6 +2,7 @@ package youtube import ( "context" + "database/sql" _ "embed" "encoding/xml" "errors" @@ -18,8 +19,10 @@ import ( "github.com/botlabs-gg/yagpdb/v2/common/cplogs" "github.com/botlabs-gg/yagpdb/v2/lib/discordgo" "github.com/botlabs-gg/yagpdb/v2/web" - "github.com/jinzhu/gorm" + "github.com/botlabs-gg/yagpdb/v2/youtube/models" "github.com/mediocregopher/radix/v3" + "github.com/volatiletech/sqlboiler/v4/boil" + "github.com/volatiletech/sqlboiler/v4/queries/qm" "goji.io" "goji.io/pat" ) @@ -94,45 +97,49 @@ func (p *Plugin) InitWeb() { func (p *Plugin) HandleYoutube(w http.ResponseWriter, r *http.Request) (web.TemplateData, error) { ctx := r.Context() - ag, templateData := web.GetBaseCPContextData(ctx) + activeGuild, templateData := web.GetBaseCPContextData(ctx) - var subs []*ChannelSubscription - err := common.GORM.Where("guild_id = ?", ag.ID).Order("id desc").Find(&subs).Error - if err != nil && err != gorm.ErrRecordNotFound { + subs, err := models.YoutubeChannelSubscriptions( + models.YoutubeChannelSubscriptionWhere.GuildID.EQ(discordgo.StrID(activeGuild.ID)), + qm.OrderBy("id DESC"), + ).AllG(ctx) + if err != nil && err != sql.ErrNoRows { return templateData, err } - var announcement YoutubeAnnouncements - err = common.GORM.Model(&YoutubeAnnouncements{}).Where("guild_id = ?", ag.ID).First(&announcement).Error + announcement, err := models.FindYoutubeAnnouncementG(ctx, activeGuild.ID) if err != nil { - announcement.Message = "{{.ChannelName}} published a new video! {{.URL}}" - announcement.Enabled = common.BoolToPointer(false) + announcement = &models.YoutubeAnnouncement{ + GuildID: activeGuild.ID, + Message: "{{.ChannelName}} published a new video! {{.URL}}", + Enabled: false, + } } templateData["Announcement"] = announcement templateData["Subs"] = subs - templateData["VisibleURL"] = "/manage/" + discordgo.StrID(ag.ID) + "/youtube" - + templateData["VisibleURL"] = "/manage/" + discordgo.StrID(activeGuild.ID) + "/youtube" return templateData, nil } func (p *Plugin) HandleYoutubeAnnouncement(w http.ResponseWriter, r *http.Request) (templateData web.TemplateData, err error) { ctx := r.Context() - guild, templateData := web.GetBaseCPContextData(ctx) - data := ctx.Value(common.ContextKeyParsedForm).(*YoutubeAnnouncementForm) - - var announcement YoutubeAnnouncements - announcement.Message = data.Message - announcement.Enabled = &data.Enabled - announcement.GuildID = guild.ID + activeGuild, templateData := web.GetBaseCPContextData(ctx) + form := ctx.Value(common.ContextKeyParsedForm).(*YoutubeAnnouncementForm) - err = common.GORM.Model(&YoutubeAnnouncements{}).Where("guild_id = ?", guild.ID).Save(&announcement).Error + announcement := &models.YoutubeAnnouncement{ + GuildID: activeGuild.ID, + Message: form.Message, + Enabled: form.Enabled, + } + err = announcement.UpsertG(ctx, true, []string{"guild_id"}, + boil.Whitelist("message", "enabled"), /* updateColumns */ + boil.Whitelist("guild_id", "message", "enabled") /* insertColumns */) if err != nil { return templateData, err } go cplogs.RetryAddEntry(web.NewLogEntryFromContext(r.Context(), panelLogKeyAnnouncement, &cplogs.Param{})) - return templateData, nil } @@ -140,10 +147,10 @@ func (p *Plugin) HandleNew(w http.ResponseWriter, r *http.Request) (web.Template ctx := r.Context() activeGuild, templateData := web.GetBaseCPContextData(ctx) - // limit it to max 25 feeds - var count int - common.GORM.Model(&ChannelSubscription{}).Where("guild_id = ?", activeGuild.ID).Count(&count) - if count >= MaxFeedsForContext(ctx) { + count, _ := models.YoutubeChannelSubscriptions( + models.YoutubeChannelSubscriptionWhere.GuildID.EQ(discordgo.StrID(activeGuild.ID)), + ).CountG(ctx) + if int(count) >= MaxFeedsForContext(ctx) { return templateData.AddAlerts(web.ErrorAlert(fmt.Sprintf("Max %d youtube feeds allowed (%d for premium servers)", GuildMaxFeeds, GuildMaxFeedsPremium))), nil } @@ -183,7 +190,6 @@ func (p *Plugin) HandleNew(w http.ResponseWriter, r *http.Request) (web.Template } go cplogs.RetryAddEntry(web.NewLogEntryFromContext(r.Context(), panelLogKeyAddedFeed, &cplogs.Param{Type: cplogs.ParamTypeString, Value: sub.YoutubeChannelName})) - return templateData, nil } @@ -198,11 +204,13 @@ func BaseEditHandler(inner web.ControllerHandlerFunc) web.ControllerHandlerFunc ctx := r.Context() activeGuild, templateData := web.GetBaseCPContextData(ctx) - id := pat.Param(r, "item") + id, err := strconv.Atoi(pat.Param(r, "item")) + if err != nil { + return templateData.AddAlerts(web.ErrorAlert("Invalid feed ID")), err + } // Get the actual watch item from the config - var sub ChannelSubscription - err := common.GORM.Model(&ChannelSubscription{}).Where("id = ?", id).First(&sub).Error + sub, err := models.FindYoutubeChannelSubscriptionG(ctx, id) if err != nil { return templateData.AddAlerts(web.ErrorAlert("Failed retrieving that feed item")), err } @@ -212,50 +220,51 @@ func BaseEditHandler(inner web.ControllerHandlerFunc) web.ControllerHandlerFunc } ctx = context.WithValue(ctx, ContextKeySub, &sub) - return inner(w, r.WithContext(ctx)) } } func (p *Plugin) HandleEdit(w http.ResponseWriter, r *http.Request) (templateData web.TemplateData, err error) { ctx := r.Context() - _, templateData = web.GetBaseCPContextData(ctx) - - sub := ctx.Value(ContextKeySub).(*ChannelSubscription) - data := ctx.Value(common.ContextKeyParsedForm).(*YoutubeFeedForm) + activeGuild, templateData := web.GetBaseCPContextData(ctx) - sub.MentionEveryone = data.MentionEveryone - sub.MentionRoles = data.MentionRoles - sub.PublishLivestream = &data.PublishLivestream - sub.PublishShorts = &data.PublishShorts - sub.ChannelID = discordgo.StrID(data.DiscordChannel) - sub.Enabled = &data.Enabled - count := 0 - common.GORM.Model(&ChannelSubscription{}).Where("guild_id = ? and enabled = ?", sub.GuildID, common.BoolToPointer(true)).Count(&count) - if count >= MaxFeedsForContext(ctx) { - var currFeed ChannelSubscription - err := common.GORM.Model(&ChannelSubscription{}).Where("id = ?", sub.ID).First(&currFeed) + updatedSub := ctx.Value(ContextKeySub).(*models.YoutubeChannelSubscription) + form := ctx.Value(common.ContextKeyParsedForm).(*YoutubeFeedForm) + + updatedSub.MentionEveryone = form.MentionEveryone + updatedSub.MentionRoles = form.MentionRoles + updatedSub.PublishLivestream = form.PublishLivestream + updatedSub.PublishShorts = form.PublishShorts + updatedSub.ChannelID = discordgo.StrID(form.DiscordChannel) + updatedSub.Enabled = form.Enabled + + numEnabled, _ := models.YoutubeChannelSubscriptions( + models.YoutubeChannelSubscriptionWhere.GuildID.EQ(discordgo.StrID(activeGuild.ID)), + models.YoutubeChannelSubscriptionWhere.Enabled.EQ(true), + ).CountG(ctx) + if int(numEnabled) >= MaxFeedsForContext(ctx) { + curSub, err := models.FindYoutubeChannelSubscriptionG(ctx, updatedSub.ID) if err != nil { - logger.WithError(err.Error).Errorf("Failed getting feed %d", sub.ID) + logger.WithError(err).Errorf("Failed getting feed %d", updatedSub.ID) } - if !*currFeed.Enabled && *sub.Enabled { + if !curSub.Enabled && updatedSub.Enabled { return templateData.AddAlerts(web.ErrorAlert(fmt.Sprintf("Max %d enabled youtube feeds allowed (%d for premium servers)", GuildMaxFeeds, GuildMaxFeedsPremium))), nil } } - err = common.GORM.Save(sub).Error + _, err = updatedSub.UpdateG(ctx, boil.Infer()) if err == nil { - go cplogs.RetryAddEntry(web.NewLogEntryFromContext(r.Context(), panelLogKeyUpdatedFeed, &cplogs.Param{Type: cplogs.ParamTypeString, Value: sub.YoutubeChannelName})) + go cplogs.RetryAddEntry(web.NewLogEntryFromContext(r.Context(), panelLogKeyUpdatedFeed, &cplogs.Param{Type: cplogs.ParamTypeString, Value: updatedSub.YoutubeChannelName})) } - return + return templateData, err } func (p *Plugin) HandleRemove(w http.ResponseWriter, r *http.Request) (templateData web.TemplateData, err error) { ctx := r.Context() _, templateData = web.GetBaseCPContextData(ctx) - sub := ctx.Value(ContextKeySub).(*ChannelSubscription) - err = common.GORM.Delete(sub).Error + sub := ctx.Value(ContextKeySub).(*models.YoutubeChannelSubscription) + _, err = sub.DeleteG(ctx) if err != nil { return } @@ -349,13 +358,14 @@ func (p *Plugin) ValidateSubscription(w http.ResponseWriter, r *http.Request, qu var _ web.PluginWithServerHomeWidget = (*Plugin)(nil) func (p *Plugin) LoadServerHomeWidget(w http.ResponseWriter, r *http.Request) (web.TemplateData, error) { - ag, templateData := web.GetBaseCPContextData(r.Context()) + activeGuild, templateData := web.GetBaseCPContextData(r.Context()) templateData["WidgetTitle"] = "Youtube feeds" templateData["SettingsPath"] = "/youtube" - var numFeeds int64 - result := common.GORM.Model(&ChannelSubscription{}).Where("guild_id = ?", ag.ID).Count(&numFeeds) + numFeeds, err := models.YoutubeChannelSubscriptions( + models.YoutubeChannelSubscriptionWhere.GuildID.EQ(discordgo.StrID(activeGuild.ID)), + ).CountG(r.Context()) if numFeeds > 0 { templateData["WidgetEnabled"] = true } else { @@ -365,5 +375,5 @@ func (p *Plugin) LoadServerHomeWidget(w http.ResponseWriter, r *http.Request) (w const format = `

    Active Youtube feeds: %d

    ` templateData["WidgetBody"] = template.HTML(fmt.Sprintf(format, numFeeds)) - return templateData, result.Error + return templateData, err } diff --git a/youtube/youtube.go b/youtube/youtube.go index 3f7cd8afbb..c591cd9180 100644 --- a/youtube/youtube.go +++ b/youtube/youtube.go @@ -11,11 +11,14 @@ import ( "github.com/botlabs-gg/yagpdb/v2/common" "github.com/botlabs-gg/yagpdb/v2/common/config" "github.com/botlabs-gg/yagpdb/v2/common/mqueue" + "github.com/botlabs-gg/yagpdb/v2/lib/discordgo" "github.com/botlabs-gg/yagpdb/v2/premium" - "github.com/lib/pq" + "github.com/botlabs-gg/yagpdb/v2/youtube/models" "google.golang.org/api/youtube/v3" ) +//go:generate sqlboiler --no-hooks psql + const ( RedisChannelsLockKey = "youtube_subbed_channel_lock" RedisKeyPublishedVideoList = "youtube_published_videos" @@ -49,8 +52,6 @@ func (p *Plugin) PluginInfo() *common.PluginInfo { func RegisterPlugin() { p := &Plugin{} - common.GORM.AutoMigrate(ChannelSubscription{}, YoutubeAnnouncements{}) - mqueue.RegisterSource("youtube", p) err := p.SetupClient() @@ -59,29 +60,8 @@ func RegisterPlugin() { return } common.RegisterPlugin(p) -} - -type ChannelSubscription struct { - common.SmallModel - GuildID string - ChannelID string - YoutubeChannelID string - YoutubeChannelName string - MentionEveryone bool - MentionRoles pq.Int64Array `gorm:"type:bigint[]" valid:"role,true"` - PublishLivestream *bool `sql:"DEFAULT:true"` - PublishShorts *bool `sql:"DEFAULT:true"` - Enabled *bool `sql:"DEFAULT:true"` -} - -func (c *ChannelSubscription) TableName() string { - return "youtube_channel_subscriptions" -} -type YoutubeAnnouncements struct { - GuildID int64 `gorm:"primary_key" sql:"AUTO_INCREMENT:false"` - Message string - Enabled *bool `sql:"DEFAULT:false"` + common.InitSchemas("youtube", DBSchemas...) } var _ mqueue.PluginWithSourceDisabler = (*Plugin)(nil) @@ -92,36 +72,38 @@ func (p *Plugin) DisableFeed(elem *mqueue.QueuedElement, err error) { } func (p *Plugin) DisableChannelFeeds(channelID int64) error { - err := common.GORM.Model(&ChannelSubscription{}).Where("channel_id = ?", channelID).Updates(ChannelSubscription{Enabled: common.BoolToPointer(false)}).Error + numDisabled, err := models.YoutubeChannelSubscriptions( + models.YoutubeChannelSubscriptionWhere.ChannelID.EQ(discordgo.StrID(channelID)), + ).UpdateAllG(context.Background(), models.M{"enabled": false}) if err != nil { - logger.WithError(err).Errorf("failed removing non-existant channel for channel_id %d", channelID) + logger.WithError(err).WithField("channel", channelID).Error("failed removing feeds in nonexistent channel") return err - } else { - logger.WithField("channel", channelID).Info("Disabled youtube feed to non-existant channel") } + + logger.WithField("channel", channelID).Infof("disabled %d feeds in nonexistent channel", numDisabled) return nil } func (p *Plugin) DisableGuildFeeds(guildID int64) error { - err := common.GORM.Model(&ChannelSubscription{}).Where("guild_id = ?", guildID).Updates(ChannelSubscription{Enabled: common.BoolToPointer(false)}).Error + numDisabled, err := models.YoutubeChannelSubscriptions( + models.YoutubeChannelSubscriptionWhere.GuildID.EQ(discordgo.StrID(guildID)), + ).UpdateAllG(context.Background(), models.M{"enabled": false}) if err != nil { - logger.WithError(err).Errorf("failed removing non-existant guild for guild_id %d", guildID) + logger.WithError(err).WithField("guild", guildID).Error("failed removing feeds in nonexistent guild") return err - } else { - logger.WithField("guild", guildID).Info("Disabled youtube feed to non-existant guild") } + + logger.WithField("guild", guildID).Infof("disabled %d feeds in nonexistent guild", numDisabled) return nil } func (p *Plugin) WebSubSubscribe(ytChannelID string) error { - values := url.Values{ "hub.callback": {"https://" + common.ConfHost.GetString() + "/yt_new_upload/" + confWebsubVerifytoken.GetString()}, "hub.topic": {"https://www.youtube.com/xml/feeds/videos.xml?channel_id=" + ytChannelID}, "hub.verify": {"sync"}, "hub.mode": {"subscribe"}, "hub.verify_token": {confWebsubVerifytoken.GetString()}, - // "hub.lease_seconds": {"60"}, } resp, err := http.PostForm(GoogleWebsubHub, values) @@ -132,16 +114,14 @@ func (p *Plugin) WebSubSubscribe(ytChannelID string) error { if resp.StatusCode < 200 || resp.StatusCode > 299 { body, _ := io.ReadAll(resp.Body) - return fmt.Errorf("go bad status code: %d (%s) %s", resp.StatusCode, resp.Status, string(body)) + return fmt.Errorf("bad status code: %d (%s) %s", resp.StatusCode, resp.Status, string(body)) } logger.Info("Websub: Subscribed to channel ", ytChannelID) - return nil } func (p *Plugin) WebSubUnsubscribe(ytChannelID string) error { - values := url.Values{ "hub.callback": {"https://" + common.ConfHost.GetString() + "/yt_new_upload/" + confWebsubVerifytoken.GetString()}, "hub.topic": {"https://www.youtube.com/xml/feeds/videos.xml?channel_id=" + ytChannelID}, @@ -156,11 +136,10 @@ func (p *Plugin) WebSubUnsubscribe(ytChannelID string) error { } if resp.StatusCode < 200 || resp.StatusCode > 299 { - return fmt.Errorf("go bad status code: %d (%s)", resp.StatusCode, resp.Status) + return fmt.Errorf("bad status code: %d (%s)", resp.StatusCode, resp.Status) } - logger.Info("Websub: UnSubscribed to channel ", ytChannelID) - + logger.Info("Websub: Unsubscribed from channel ", ytChannelID) return nil }