From 96062770b76cbef8476eedca0f2e1888e57db436 Mon Sep 17 00:00:00 2001 From: Ashish <51633862+ashishjh-bst@users.noreply.github.com> Date: Thu, 20 Jun 2024 01:47:47 +0530 Subject: [PATCH] This is prime branch for all GORM TO SQLBOILER migrations. (#1655) * all: migrate executed command logging away from gorm to sqlboiler (#1654) * common: add schemas for executed command logs * common: configure and run sqlboiler for executed command log model * all: migrate executed command logging to sqlboiler * reminders: migrate away from gorm to sqlboiler (#1652) * reminders: add database schemas * reminders: set up and run sqlboiler * reminders: migrate to sqlboiler While at it, lightly refactor and simplify the code as applicable. The most substantial (non-database) refactor is renaming and moving strReminders to DisplayReminders. To aid in the refactor, also add discordgo.ParseID as a shortcut for strconv.ParseInt(...). This operation is often needed to convert string IDs in database to int64's and having a shorter name helps. * reminders: auto-delete old entries with null guild_id as necessary * soundboard: remove stray references to gorm (#1651) The soundboard module used to use gorm, but was migrated to sqlboiler some years ago in commit 628ea9a. There are still two (erroneous) references to gorm remaining which were not caught since this section of code ignores errors. This commit changes these to use sqlboiler as well. * schemamigration: skip executing ALTER COLUMN SET NOT NULL queries when possible (#1650) * schemamigration: skip executing ALTER COLUMN SET NOT NULL queries when possible * Use quantifier * not + in regex for consistency It really should be +, but the current code works and hey -- when in Rome, do as the Romans. * youtube: migrate away from gorm to sqlboiler (#1660) * youtube: add database schemas * youtube: set up and run sqlboiler * youtube: migrate to sqlboiler * notifications: migrate away from gorm to sqlboiler (#1659) * notifications: decouple from configstore * notifications: add database schemas * notifications: set up and run sqlboiler * notifications: migrate to sqlboiler * moderation: migrate away from gorm to sqlboiler (#1658) * moderation: decouple from configstore * moderation: add database schemas * moderation: set up and run sqlboiler * moderation: migrate to sqlboiler * web: support null.Int64 in forms and validator We currently use gorilla/schema to parse HTML form values into structs -- this allows us to specify, e,g., `` and have `payload.SomeField` set correctly in Go handlers. This approach mostly works nicely, with the major exception of null.Int64 (formerly sql.NullInt64 with gorm) fields, which show up in the moderation plugin, e.g., Config.DefaultMuteDuration. Particularly, since gorilla/schema has no native support for null.Int64, we have instead instructed gorilla/schema to directly set the Int64 value using and then manually set DefaultMuteDuration.Valid=true in the web handler, without which the changes are not saved to database. This approach is, for obvious reasons, rather brittle. In this commit we therefore register a custom gorilla/schema decoder for null.Int64, obviating the need for both pointing to the Int64 field and manually setting Valid=true. While at it, we also fix the validator so that it checks null.Int64 fields properly. * web: support validating types.Int64Array gorm uses pq.Int64Array but sqlboiler uses types.Int64Array, so we need also support the latter now. * common/configstore: remove package (#1668) * all: remove last traces of gorm (#1670) * common: connect to postgres via database/sql ...instead of through gorm. * common: remove gorm log hook * common: remove gorm SmallModel * deps: run go mod tidy --------- Co-authored-by: Joe L <56809242+jo3-l@users.noreply.github.com> --- automod/automod_web.go | 4 +- commands/commands.go | 4 - commands/yagcommmand.go | 10 +- common/common.go | 13 +- common/configstore/README.md | 5 - common/configstore/db.go | 157 -- common/configstore/sql.go | 70 - common/executedcommandschema.go | 39 + common/loghooks.go | 7 - common/models/boil_table_names.go | 6 +- common/models/boil_view_names.go | 7 + common/models/executed_commands.go | 974 +++++++++++++ common/run/run.go | 4 - common/schemamigration.go | 35 +- common/sqlboiler.toml | 18 +- common/util.go | 28 - go.mod | 2 - go.sum | 19 - lib/discordgo/discord.go | 23 +- moderation/assets/moderation.html | 6 +- moderation/commands.go | 122 +- moderation/config.go | 205 +++ moderation/models.go | 154 -- moderation/models/boil_queries.go | 38 + moderation/models/boil_table_names.go | 14 + moderation/models/boil_types.go | 52 + moderation/models/boil_view_names.go | 7 + moderation/models/moderation_configs.go | 1293 +++++++++++++++++ moderation/models/moderation_warnings.go | 889 ++++++++++++ moderation/models/muted_users.go | 864 +++++++++++ moderation/models/psql_upsert.go | 99 ++ moderation/moderation.go | 35 +- moderation/modlog.go | 7 +- moderation/plugin_bot.go | 142 +- moderation/plugin_web.go | 23 +- moderation/punishments.go | 71 +- moderation/schema.go | 143 ++ moderation/sqlboiler.toml | 27 + moderation/tmplextensions.go | 88 +- notifications/config.go | 120 ++ notifications/models/boil_queries.go | 38 + notifications/models/boil_table_names.go | 10 + notifications/models/boil_types.go | 52 + notifications/models/boil_view_names.go | 7 + .../models/general_notification_configs.go | 992 +++++++++++++ notifications/models/psql_upsert.go | 99 ++ notifications/notifications.go | 145 +- notifications/plugin_bot.go | 90 +- notifications/plugin_web.go | 8 +- notifications/schema.go | 78 + notifications/sqlboiler.toml | 22 + reminders/models/boil_queries.go | 38 + reminders/models/boil_table_names.go | 10 + reminders/models/boil_types.go | 52 + reminders/models/boil_view_names.go | 7 + reminders/models/psql_upsert.go | 99 ++ reminders/models/reminders.go | 998 +++++++++++++ reminders/plugin_bot.go | 183 +-- reminders/reminders.go | 126 +- reminders/schema.go | 57 + reminders/sqlboiler.toml | 15 + soundboard/transcoder.go | 9 +- stdcommands/topcommands/topcommands.go | 29 +- web/handlers_general.go | 9 +- web/middleware.go | 7 + web/validation.go | 16 +- youtube/bot.go | 32 +- youtube/feed.go | 52 +- youtube/models/boil_queries.go | 38 + youtube/models/boil_table_names.go | 12 + youtube/models/boil_types.go | 52 + youtube/models/boil_view_names.go | 7 + youtube/models/psql_upsert.go | 99 ++ youtube/models/youtube_announcements.go | 831 +++++++++++ .../models/youtube_channel_subscriptions.go | 928 ++++++++++++ youtube/schema.go | 76 + youtube/sqlboiler.toml | 15 + youtube/web.go | 120 +- youtube/youtube.go | 61 +- 79 files changed, 10187 insertions(+), 1156 deletions(-) delete mode 100644 common/configstore/README.md delete mode 100644 common/configstore/db.go delete mode 100644 common/configstore/sql.go create mode 100644 common/executedcommandschema.go create mode 100644 common/models/boil_view_names.go create mode 100644 common/models/executed_commands.go create mode 100644 moderation/config.go delete mode 100644 moderation/models.go create mode 100644 moderation/models/boil_queries.go create mode 100644 moderation/models/boil_table_names.go create mode 100644 moderation/models/boil_types.go create mode 100644 moderation/models/boil_view_names.go create mode 100644 moderation/models/moderation_configs.go create mode 100644 moderation/models/moderation_warnings.go create mode 100644 moderation/models/muted_users.go create mode 100644 moderation/models/psql_upsert.go create mode 100644 moderation/schema.go create mode 100644 moderation/sqlboiler.toml create mode 100644 notifications/config.go create mode 100644 notifications/models/boil_queries.go create mode 100644 notifications/models/boil_table_names.go create mode 100644 notifications/models/boil_types.go create mode 100644 notifications/models/boil_view_names.go create mode 100644 notifications/models/general_notification_configs.go create mode 100644 notifications/models/psql_upsert.go create mode 100644 notifications/schema.go create mode 100644 notifications/sqlboiler.toml create mode 100644 reminders/models/boil_queries.go create mode 100644 reminders/models/boil_table_names.go create mode 100644 reminders/models/boil_types.go create mode 100644 reminders/models/boil_view_names.go create mode 100644 reminders/models/psql_upsert.go create mode 100644 reminders/models/reminders.go create mode 100644 reminders/schema.go create mode 100644 reminders/sqlboiler.toml create mode 100644 youtube/models/boil_queries.go create mode 100644 youtube/models/boil_table_names.go create mode 100644 youtube/models/boil_types.go create mode 100644 youtube/models/boil_view_names.go create mode 100644 youtube/models/psql_upsert.go create mode 100644 youtube/models/youtube_announcements.go create mode 100644 youtube/models/youtube_channel_subscriptions.go create mode 100644 youtube/schema.go create mode 100644 youtube/sqlboiler.toml 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 }