dcbot/internal/adapter/group_bun_repository.go

312 lines
8.1 KiB
Go

package adapter
import (
"context"
"database/sql"
"errors"
"fmt"
"gitea.dwysokinski.me/twhelp/dcbot/internal/adapter/internal/bunmodel"
"gitea.dwysokinski.me/twhelp/dcbot/internal/domain"
"github.com/google/uuid"
"github.com/jackc/pgerrcode"
"github.com/uptrace/bun"
"github.com/uptrace/bun/driver/pgdriver"
)
type GroupBunRepository struct {
db *bun.DB
}
func NewGroupBunRepository(db *bun.DB) *GroupBunRepository {
return &GroupBunRepository{db: db}
}
func (g *GroupBunRepository) Create(ctx context.Context, params domain.CreateGroupParams) (domain.GroupWithMonitors, error) {
group := bunmodel.Group{
ServerID: params.ServerID(),
VersionCode: params.VersionCode(),
ChannelGains: params.ChannelGains(),
ChannelLosses: params.ChannelLosses(),
ServerKey: params.ServerKey(),
Barbarians: params.Barbarians(),
Internals: params.Internals(),
LanguageTag: params.LanguageTag(),
}
if _, err := g.db.NewInsert().
Model(&group).
Returning("*").
Exec(ctx); err != nil {
return domain.GroupWithMonitors{}, fmt.Errorf("something went wrong while inserting group into the db: %w", err)
}
return group.ToDomain(), nil
}
func (g *GroupBunRepository) Update(ctx context.Context, id string, params domain.UpdateGroupParams) (domain.GroupWithMonitors, error) {
if params.IsZero() {
return domain.GroupWithMonitors{}, domain.ErrNothingToUpdate
}
if _, err := uuid.Parse(id); err != nil {
return domain.GroupWithMonitors{}, domain.GroupNotFoundError{ID: id}
}
var group bunmodel.Group
res, err := g.db.NewUpdate().
Model(&group).
Returning("NULL").
Where("id = ?", id).
Apply(updateGroupsParamsApplier{params}.apply).
Exec(ctx)
if err != nil && !errors.Is(err, sql.ErrNoRows) {
return domain.GroupWithMonitors{}, fmt.Errorf("couldn't update group (id=%s): %w", id, err)
}
if affected, _ := res.RowsAffected(); affected == 0 {
return domain.GroupWithMonitors{}, domain.GroupNotFoundError{ID: id}
}
return g.Get(ctx, id)
}
func (g *GroupBunRepository) AddMonitor(ctx context.Context, id string, tribeID int64) (domain.GroupWithMonitors, error) {
parsedID, err := uuid.Parse(id)
if err != nil {
return domain.GroupWithMonitors{}, domain.GroupDoesNotExistError{ID: id}
}
monitor := bunmodel.Monitor{
GroupID: parsedID,
TribeID: tribeID,
}
if _, err = g.db.NewInsert().
Model(&monitor).
Returning("NULL").
Exec(ctx); err != nil {
return domain.GroupWithMonitors{}, fmt.Errorf(
"something went wrong while inserting monitor into the db: %w",
mapAddMonitorError(err, id, tribeID),
)
}
return g.Get(ctx, id)
}
func (g *GroupBunRepository) DeleteMonitors(ctx context.Context, id string, monitorIDs ...string) (domain.GroupWithMonitors, error) {
if _, err := uuid.Parse(id); err != nil {
return domain.GroupWithMonitors{}, domain.GroupNotFoundError{ID: id}
}
for _, monitorID := range monitorIDs {
if _, err := uuid.Parse(monitorID); err != nil {
return domain.GroupWithMonitors{}, domain.MonitorNotFoundError{ID: monitorID}
}
}
_, err := g.db.NewDelete().
Model(&bunmodel.Monitor{}).
Returning("NULL").
Where("id IN (?)", bun.In(monitorIDs)).
Where("group_id = ?", id).
Exec(ctx)
if err != nil && !errors.Is(err, sql.ErrNoRows) {
return domain.GroupWithMonitors{}, fmt.Errorf("couldn't delete monitors: %w", err)
}
return g.Get(ctx, id)
}
func (g *GroupBunRepository) List(ctx context.Context, params domain.ListGroupsParams) ([]domain.GroupWithMonitors, error) {
var groups []bunmodel.Group
if err := g.db.NewSelect().
Model(&groups).
Order("created_at ASC").
Relation("Monitors", func(q *bun.SelectQuery) *bun.SelectQuery {
return q.Order("created_at ASC")
}).
Apply(listGroupsParamsApplier{params}.apply).
Scan(ctx); err != nil && !errors.Is(err, sql.ErrNoRows) {
return nil, fmt.Errorf("couldn't select groups from the db: %w", err)
}
result := make([]domain.GroupWithMonitors, 0, len(groups))
for _, group := range groups {
result = append(result, group.ToDomain())
}
return result, nil
}
func (g *GroupBunRepository) Get(ctx context.Context, id string) (domain.GroupWithMonitors, error) {
group, err := g.get(ctx, id, true)
if err != nil {
return domain.GroupWithMonitors{}, err
}
return group.ToDomain(), nil
}
func (g *GroupBunRepository) get(ctx context.Context, id string, withMonitors bool) (bunmodel.Group, error) {
if _, err := uuid.Parse(id); err != nil {
return bunmodel.Group{}, domain.GroupNotFoundError{ID: id}
}
var group bunmodel.Group
q := g.db.NewSelect().
Model(&group).
Where("id = ?", id)
if withMonitors {
q = q.Relation("Monitors", func(q *bun.SelectQuery) *bun.SelectQuery {
return q.Order("created_at ASC")
})
}
if err := q.Scan(ctx); err != nil {
if errors.Is(err, sql.ErrNoRows) {
return bunmodel.Group{}, domain.GroupNotFoundError{ID: id}
}
return bunmodel.Group{}, fmt.Errorf("couldn't select group (id=%s) from the db: %w", id, err)
}
return group, nil
}
func (g *GroupBunRepository) Delete(ctx context.Context, id string) error {
if _, err := uuid.Parse(id); err != nil {
return domain.GroupNotFoundError{ID: id}
}
res, err := g.db.NewDelete().
Model(&bunmodel.Group{}).
Returning("NULL").
Where("id = ?", id).
Exec(ctx)
if err != nil && !errors.Is(err, sql.ErrNoRows) {
return fmt.Errorf("couldn't delete group (id=%s): %w", id, err)
}
if affected, _ := res.RowsAffected(); affected == 0 {
return domain.GroupNotFoundError{ID: id}
}
return nil
}
func (g *GroupBunRepository) DeleteMany(ctx context.Context, ids ...string) error {
if len(ids) == 0 {
return nil
}
_, err := g.db.NewDelete().
Model(&bunmodel.Group{}).
Returning("NULL").
Where("id IN (?)", bun.In(ids)).
Exec(ctx)
if err != nil && !errors.Is(err, sql.ErrNoRows) {
return fmt.Errorf("couldn't delete groups: %w", err)
}
return nil
}
type updateGroupsParamsApplier struct {
params domain.UpdateGroupParams
}
func (u updateGroupsParamsApplier) apply(q *bun.UpdateQuery) *bun.UpdateQuery {
if u.params.ChannelGains.Valid {
if u.params.ChannelGains.String != "" {
q = q.Set("channel_gains = ?", u.params.ChannelGains.String)
} else {
q = q.Set("channel_gains = NULL")
}
}
if u.params.ChannelLosses.Valid {
if u.params.ChannelLosses.String != "" {
q = q.Set("channel_losses = ?", u.params.ChannelLosses.String)
} else {
q = q.Set("channel_losses = NULL")
}
}
if u.params.LanguageTag.Valid {
if u.params.LanguageTag.String != "" {
q = q.Set("language_tag = ?", u.params.LanguageTag.String)
} else {
q = q.Set("language_tag = NULL")
}
}
if u.params.Barbarians.Valid {
q = q.Set("barbarians = ?", u.params.Barbarians.Bool)
}
if u.params.Internals.Valid {
q = q.Set("internals = ?", u.params.Internals.Bool)
}
return q
}
type listGroupsParamsApplier struct {
params domain.ListGroupsParams
}
func (l listGroupsParamsApplier) apply(q *bun.SelectQuery) *bun.SelectQuery {
if len(l.params.ServerIDs) > 0 {
q = q.Where("server_id IN (?)", bun.In(l.params.ServerIDs))
}
if l.params.EnabledNotifications.Valid {
if l.params.EnabledNotifications.Bool {
q = q.WhereGroup(" AND ", func(query *bun.SelectQuery) *bun.SelectQuery {
return q.WhereOr("channel_gains IS NOT NULL").
WhereOr("channel_losses IS NOT NULL")
})
} else {
q = q.Where("channel_gains IS NULL").Where("channel_losses IS NULL")
}
}
if l.params.VersionCode.Valid {
q = q.Where("version_code = ?", l.params.VersionCode.String)
}
if len(l.params.ServerKeys) > 0 {
q = q.Where("server_key IN (?)", bun.In(l.params.ServerKeys))
}
if !l.params.CreatedAtLTE.IsZero() {
q = q.Where("created_at <= ?", l.params.CreatedAtLTE)
}
return q
}
func mapAddMonitorError(err error, groupID string, tribeID int64) error {
var pgError pgdriver.Error
if !errors.As(err, &pgError) {
return err
}
code := pgError.Field('C')
constraint := pgError.Field('n')
switch {
case code == pgerrcode.ForeignKeyViolation && constraint == "monitors_group_id_fkey":
return domain.GroupDoesNotExistError{
ID: groupID,
}
case code == pgerrcode.UniqueViolation && constraint == "monitors_group_id_tribe_id_key":
return domain.MonitorAlreadyExistsError{
TribeID: tribeID,
GroupID: groupID,
}
default:
return err
}
}