Skip to content

Commit

Permalink
feat(backend): commands expiration
Browse files Browse the repository at this point in the history
  • Loading branch information
danluki committed Oct 25, 2024
1 parent 21bcad2 commit ced1656
Show file tree
Hide file tree
Showing 7 changed files with 212 additions and 28 deletions.
23 changes: 23 additions & 0 deletions apps/api-gql/internal/gql/resolvers/commands.resolver.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions apps/api-gql/schema/commands.graphqls
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ type Command {
requiredWatchTime: Int!
requiredMessages: Int!
requiredUsedChannelPoints: Int!
expiredIn: Int!
expired: Boolean!
expiresAt: Time
group: CommandGroup
}
type CommandResponse {
Expand Down Expand Up @@ -80,6 +83,7 @@ input CommandsCreateOpts {
requiredWatchTime: Int!
requiredMessages: Int!
requiredUsedChannelPoints: Int!
expiredIn: Int!
groupId: String @validate(constraint: "max=500,omitempty")
}

Expand All @@ -103,6 +107,8 @@ input CommandsUpdateOpts {
requiredWatchTime: Int
requiredMessages: Int
requiredUsedChannelPoints: Int
expiredIn: Int
expired: Boolean
groupId: String @validate(constraint: "max=500,omitempty")
}

Expand Down
31 changes: 31 additions & 0 deletions apps/parser/internal/commands/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,37 @@ func (c *Commands) ProcessChatMessage(ctx context.Context, data twitch.TwitchCha
return nil, nil
}

if cmd.Cmd.Expired {
return nil, nil
}

if cmd.Cmd.ExpiresAt.Valid && cmd.Cmd.ExpiredIn > 0 {
if cmd.Cmd.ExpiresAt.Time.Before(time.Now().UTC()) {
err = c.services.Gorm.
WithContext(ctx).
Where(`"id" = ?`, cmd.Cmd.ID).
Model(&model.ChannelsCommands{}).
Updates(map[string]interface{}{
"expired": true,
}).Error
if err != nil {
c.services.Logger.Sugar().Error(err)
return nil, err
}

err = c.services.CommandsCache.Invalidate(
ctx,
cmd.Cmd.ChannelID,
)
if err != nil {
c.services.Logger.Sugar().Error(err)
return nil, err
}

return nil, nil
}
}

if cmd.Cmd.OnlineOnly {
stream := &model.ChannelsStreams{}
err = c.services.Gorm.
Expand Down
1 change: 1 addition & 0 deletions apps/scheduler/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ var App = fx.Module(
timers.NewCommandsAndRoles,
timers.NewBannedChannels,
timers.NewWatched,
timers.NewExpiredCommands,
func(l logger.Logger) {
l.Info("Started")
},
Expand Down
108 changes: 108 additions & 0 deletions apps/scheduler/internal/timers/expired_commands.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package timers

import (
"context"
"log/slog"
"time"

"github.com/redis/go-redis/v9"
"github.com/samber/lo"
config "github.com/satont/twir/libs/config"
model "github.com/satont/twir/libs/gomodels"
"github.com/satont/twir/libs/logger"
commandscache "github.com/twirapp/twir/libs/cache/commands"
generic_cacher "github.com/twirapp/twir/libs/cache/generic-cacher"
"go.uber.org/fx"
"gorm.io/gorm"
)

type ExpiredCommandsOpts struct {
fx.In
Lc fx.Lifecycle

Logger logger.Logger
Config config.Config

Gorm *gorm.DB
RedisClient *redis.Client
}

type expiredCommands struct {
config config.Config
logger logger.Logger
db *gorm.DB
commandsCache *generic_cacher.GenericCacher[[]model.ChannelsCommands]
}

func NewExpiredCommands(opts ExpiredCommandsOpts) *expiredCommands {
timeTick := lo.If(opts.Config.AppEnv != "production", 15*time.Second).Else(5 * time.Minute)
ticker := time.NewTicker(timeTick)

ctx, cancel := context.WithCancel(context.Background())

s := &expiredCommands{
config: opts.Config,
logger: opts.Logger,
db: opts.Gorm,
commandsCache: commandscache.New(opts.Gorm, opts.RedisClient),
}

opts.Lc.Append(
fx.Hook{
OnStart: func(_ context.Context) error {
go func() {
for {
select {
case <-ctx.Done():
ticker.Stop()
return
case <-ticker.C:
s.checkForExpiredCommands(ctx)
}
}
}()

return nil
},
OnStop: func(_ context.Context) error {
cancel()
return nil
},
},
)

return s
}

func (s *expiredCommands) checkForExpiredCommands(ctx context.Context) {
var commands []model.ChannelsCommands
if err := s.db.WithContext(ctx).Preload("Channel").Where(
`"expires_at" < ? AND "expired" = ?`,
time.Now().UTC(),
false,
).Find(&commands).Error; err != nil {
s.logger.Error("failed to get commands", slog.Any("err", err))
return
}

for _, c := range commands {
s.logger.Info("Command expired", slog.Any("command", c))
c.Expired = true

err := s.db.WithContext(ctx).Updates(
&c,
).Error
if err != nil {
s.logger.Error("failed to update command", slog.Any("err", err))
}

err = s.commandsCache.Invalidate(
ctx,
c.Channel.ID)
if err != nil {
s.logger.Error("failed to invalidate commands cache", slog.Any("err", err))
return
}

}
}
58 changes: 30 additions & 28 deletions libs/gomodels/channels_commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,34 +17,36 @@ var (
)

type ChannelsCommands struct {
ID string `gorm:"primaryKey;column:id;type:TEXT;default:uuid_generate_v4()" json:"id"`
Name string `gorm:"column:name;type:TEXT;" json:"name"`
Cooldown null.Int `gorm:"column:cooldown;type:INT4;default:0;" json:"cooldown" swaggertype:"integer"`
CooldownType string `gorm:"column:cooldownType;type:VARCHAR;default:GLOBAL;" json:"cooldownType"`
Enabled bool `gorm:"column:enabled;type:BOOL;" json:"enabled"`
Aliases pq.StringArray `gorm:"column:aliases;type:text[];default:[];" json:"aliases"`
Description null.String `gorm:"column:description;type:TEXT;" json:"description" swaggertype:"string"`
Visible bool `gorm:"column:visible;type:BOOL;" json:"visible"`
ChannelID string `gorm:"column:channelId;type:TEXT;" json:"channelId"`
Default bool `gorm:"column:default;type:BOOL;default:false;" json:"default"`
DefaultName null.String `gorm:"column:defaultName;type:TEXT;" json:"defaultName" swaggertype:"string"`
Module string `gorm:"column:module;type:VARCHAR;default:CUSTOM;" json:"module"`
IsReply bool `gorm:"column:is_reply;type:BOOL;" json:"isReply"`
KeepResponsesOrder bool `gorm:"column:keepResponsesOrder;type:BOOL;" json:"keepResponsesOrder"`
DeniedUsersIDS pq.StringArray `gorm:"column:deniedUsersIds;type:text[];default:[];" json:"deniedUsersIds"`
AllowedUsersIDS pq.StringArray `gorm:"column:allowedUsersIds;type:text[];default:[];" json:"allowedUsersIds"`
RolesIDS pq.StringArray `gorm:"column:rolesIds;type:text[];default:[];" json:"rolesIds"`
OnlineOnly bool `gorm:"column:online_only;type:BOOL;" json:"onlineOnly"`
CooldownRolesIDs pq.StringArray `gorm:"column:cooldown_roles_ids;type:text[];default:[];" json:"cooldownRolesIds"`
EnabledCategories pq.StringArray `gorm:"column:enabled_categories;type:text[];default:[];" json:"enabledCategories"`
RequiredWatchTime int `gorm:"column:requiredWatchTime;type:INT4;default:0;" json:"requiredWatchTime"`
RequiredMessages int `gorm:"column:requiredMessages;type:INT4;default:0;" json:"requiredMessages"`
RequiredUsedChannelPoints int `gorm:"column:requiredUsedChannelPoints;type:INT4;default:0;" json:"requiredUsedChannelPoints"`

Channel *Channels `gorm:"foreignKey:ChannelID" json:"-"`
Responses []*ChannelsCommandsResponses `gorm:"foreignKey:CommandID" json:"responses"`
GroupID null.String `gorm:"column:groupId;type:UUID" json:"groupId"`
Group *ChannelCommandGroup `gorm:"foreignKey:GroupID" json:"group"`
ID string `gorm:"primaryKey;column:id;type:TEXT;default:uuid_generate_v4()" json:"id"`
Name string `gorm:"column:name;type:TEXT;" json:"name"`
Cooldown null.Int `gorm:"column:cooldown;type:INT4;default:0;" json:"cooldown" swaggertype:"integer"`
CooldownType string `gorm:"column:cooldownType;type:VARCHAR;default:GLOBAL;" json:"cooldownType"`
Enabled bool `gorm:"column:enabled;type:BOOL;" json:"enabled"`
Aliases pq.StringArray `gorm:"column:aliases;type:text[];default:[];" json:"aliases"`
Description null.String `gorm:"column:description;type:TEXT;" json:"description" swaggertype:"string"`
Visible bool `gorm:"column:visible;type:BOOL;" json:"visible"`
ChannelID string `gorm:"column:channelId;type:TEXT;" json:"channelId"`
Default bool `gorm:"column:default;type:BOOL;default:false;" json:"default"`
DefaultName null.String `gorm:"column:defaultName;type:TEXT;" json:"defaultName" swaggertype:"string"`
Module string `gorm:"column:module;type:VARCHAR;default:CUSTOM;" json:"module"`
IsReply bool `gorm:"column:is_reply;type:BOOL;" json:"isReply"`
KeepResponsesOrder bool `gorm:"column:keepResponsesOrder;type:BOOL;" json:"keepResponsesOrder"`
DeniedUsersIDS pq.StringArray `gorm:"column:deniedUsersIds;type:text[];default:[];" json:"deniedUsersIds"`
AllowedUsersIDS pq.StringArray `gorm:"column:allowedUsersIds;type:text[];default:[];" json:"allowedUsersIds"`
RolesIDS pq.StringArray `gorm:"column:rolesIds;type:text[];default:[];" json:"rolesIds"`
OnlineOnly bool `gorm:"column:online_only;type:BOOL;" json:"onlineOnly"`
CooldownRolesIDs pq.StringArray `gorm:"column:cooldown_roles_ids;type:text[];default:[];" json:"cooldownRolesIds"`
EnabledCategories pq.StringArray `gorm:"column:enabled_categories;type:text[];default:[];" json:"enabledCategories"`
RequiredWatchTime int `gorm:"column:requiredWatchTime;type:INT4;default:0;" json:"requiredWatchTime"`
RequiredMessages int `gorm:"column:requiredMessages;type:INT4;default:0;" json:"requiredMessages"`
RequiredUsedChannelPoints int `gorm:"column:requiredUsedChannelPoints;type:INT4;default:0;" json:"requiredUsedChannelPoints"`
ExpiresAt null.Time `gorm:"column:expires_at;type:TIMESTAMPTZ;" json:"expires_at"`
Expired bool `gorm:"column:expired;type:BOOL;default:false;" json:"expired"`
ExpiredIn int `gorm:"column:expired_in;type:INT4;default:0;" json:"expired_in"`
Channel *Channels `gorm:"foreignKey:ChannelID" json:"-"`
Responses []*ChannelsCommandsResponses `gorm:"foreignKey:CommandID" json:"responses"`
GroupID null.String `gorm:"column:groupId;type:UUID" json:"groupId"`
Group *ChannelCommandGroup `gorm:"foreignKey:GroupID" json:"group"`
}

func (c *ChannelsCommands) TableName() string {
Expand Down
13 changes: 13 additions & 0 deletions libs/migrations/migrations/20241025040229_commands_expiration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
-- +goose Up
-- +goose StatementBegin
SELECT 'up SQL query';

ALTER TABLE channels_commands ADD COLUMN expires_at timestamptz;
ALTER TABLE channels_commands ADD COLUMN expired boolean default false;
ALTER TABLE channels_commands ADD COLUMN expired_in integer DEFAULT 0;
-- +goose StatementEnd

-- +goose Down
-- +goose StatementBegin
SELECT 'down SQL query';
-- +goose StatementEnd

0 comments on commit ced1656

Please sign in to comment.