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..72557269d6 100644 --- a/common/common.go +++ b/common/common.go @@ -131,6 +131,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 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/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/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..7a5cc7fcaa 100644 --- a/common/util.go +++ b/common/util.go @@ -354,28 +354,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/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