feat: use notifiers on schedule (#362)

* fix potential memory leak with time.After

* add new background service to manage scheduled notifications

* update docs

* remove old js reference

* closes #278

* tidy
This commit is contained in:
Hayden 2023-03-21 11:32:48 -08:00 committed by GitHub
parent 975e636fb6
commit 840d220d4f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 201 additions and 52 deletions

View file

@ -5,9 +5,10 @@ import (
)
type AllServices struct {
User *UserService
Group *GroupService
Items *ItemService
User *UserService
Group *GroupService
Items *ItemService
BackgroundService *BackgroundService
}
type OptionsFunc func(*options)
@ -42,5 +43,6 @@ func New(repos *repo.AllRepos, opts ...OptionsFunc) *AllServices {
repo: repos,
autoIncrementAssetID: options.autoIncrementAssetID,
},
BackgroundService: &BackgroundService{repos},
}
}

View file

@ -0,0 +1,81 @@
package services
import (
"context"
"strings"
"time"
"github.com/containrrr/shoutrrr"
"github.com/hay-kot/homebox/backend/internal/data/repo"
"github.com/hay-kot/homebox/backend/internal/data/types"
"github.com/rs/zerolog/log"
)
type BackgroundService struct {
repos *repo.AllRepos
}
func (svc *BackgroundService) SendNotifiersToday(ctx context.Context) error {
// Get All Groups
groups, err := svc.repos.Groups.GetAllGroups(ctx)
if err != nil {
return err
}
today := types.DateFromTime(time.Now())
for i := range groups {
group := groups[i]
entries, err := svc.repos.MaintEntry.GetScheduled(ctx, group.ID, today)
if err != nil {
return err
}
if len(entries) == 0 {
log.Debug().
Str("group_name", group.Name).
Str("group_id", group.ID.String()).
Msg("No scheduled maintenance for today")
continue
}
notifiers, err := svc.repos.Notifiers.GetByGroup(ctx, group.ID)
if err != nil {
return err
}
urls := make([]string, len(notifiers))
for i := range notifiers {
urls[i] = notifiers[i].URL
}
bldr := strings.Builder{}
bldr.WriteString("Homebox Maintenance for (")
bldr.WriteString(today.String())
bldr.WriteString("):\n")
for i := range entries {
entry := entries[i]
bldr.WriteString(" - ")
bldr.WriteString(entry.Name)
bldr.WriteString("\n")
}
var sendErrs []error
for i := range urls {
err := shoutrrr.Send(urls[i], bldr.String())
if err != nil {
sendErrs = append(sendErrs, err)
}
}
if len(sendErrs) > 0 {
return sendErrs[0]
}
}
return nil
}

View file

@ -33,6 +33,8 @@ func (MaintenanceEntry) Fields() []ent.Field {
Optional(),
field.Float("cost").
Default(0.0),
field.Bool("reminders_enabled").
Default(false),
}
}

View file

@ -16,7 +16,36 @@ import (
)
type GroupRepository struct {
db *ent.Client
db *ent.Client
groupMapper MapFunc[*ent.Group, Group]
invitationMapper MapFunc[*ent.GroupInvitationToken, GroupInvitation]
}
func NewGroupRepository(db *ent.Client) *GroupRepository {
gmap := func(g *ent.Group) Group {
return Group{
ID: g.ID,
Name: g.Name,
CreatedAt: g.CreatedAt,
UpdatedAt: g.UpdatedAt,
Currency: strings.ToUpper(g.Currency.String()),
}
}
imap := func(i *ent.GroupInvitationToken) GroupInvitation {
return GroupInvitation{
ID: i.ID,
ExpiresAt: i.ExpiresAt,
Uses: i.Uses,
Group: gmap(i.Edges.Group),
}
}
return &GroupRepository{
db: db,
groupMapper: gmap,
invitationMapper: imap,
}
}
type (
@ -76,27 +105,8 @@ type (
}
)
var mapToGroupErr = mapTErrFunc(mapToGroup)
func mapToGroup(g *ent.Group) Group {
return Group{
ID: g.ID,
Name: g.Name,
CreatedAt: g.CreatedAt,
UpdatedAt: g.UpdatedAt,
Currency: strings.ToUpper(g.Currency.String()),
}
}
var mapToGroupInvitationErr = mapTErrFunc(mapToGroupInvitation)
func mapToGroupInvitation(g *ent.GroupInvitationToken) GroupInvitation {
return GroupInvitation{
ID: g.ID,
ExpiresAt: g.ExpiresAt,
Uses: g.Uses,
Group: mapToGroup(g.Edges.Group),
}
func (r *GroupRepository) GetAllGroups(ctx context.Context) ([]Group, error) {
return r.groupMapper.MapEachErr(r.db.Group.Query().All(ctx))
}
func (r *GroupRepository) StatsLocationsByPurchasePrice(ctx context.Context, GID uuid.UUID) ([]TotalsByOrganizer, error) {
@ -249,7 +259,7 @@ func (r *GroupRepository) StatsGroup(ctx context.Context, GID uuid.UUID) (GroupS
}
func (r *GroupRepository) GroupCreate(ctx context.Context, name string) (Group, error) {
return mapToGroupErr(r.db.Group.Create().
return r.groupMapper.MapErr(r.db.Group.Create().
SetName(name).
Save(ctx))
}
@ -262,15 +272,15 @@ func (r *GroupRepository) GroupUpdate(ctx context.Context, ID uuid.UUID, data Gr
SetCurrency(currency).
Save(ctx)
return mapToGroupErr(entity, err)
return r.groupMapper.MapErr(entity, err)
}
func (r *GroupRepository) GroupByID(ctx context.Context, id uuid.UUID) (Group, error) {
return mapToGroupErr(r.db.Group.Get(ctx, id))
return r.groupMapper.MapErr(r.db.Group.Get(ctx, id))
}
func (r *GroupRepository) InvitationGet(ctx context.Context, token []byte) (GroupInvitation, error) {
return mapToGroupInvitationErr(r.db.GroupInvitationToken.Query().
return r.invitationMapper.MapErr(r.db.GroupInvitationToken.Query().
Where(groupinvitationtoken.Token(token)).
WithGroup().
Only(ctx))

View file

@ -84,6 +84,27 @@ func mapMaintenanceEntry(entry *ent.MaintenanceEntry) MaintenanceEntry {
}
}
func (r *MaintenanceEntryRepository) GetScheduled(ctx context.Context, GID uuid.UUID, dt types.Date) ([]MaintenanceEntry, error) {
entries, err := r.db.MaintenanceEntry.Query().
Where(
maintenanceentry.HasItemWith(
item.HasGroupWith(group.ID(GID)),
),
maintenanceentry.ScheduledDate(dt.Time()),
maintenanceentry.Or(
maintenanceentry.DateIsNil(),
maintenanceentry.DateEQ(time.Time{}),
),
).
All(ctx)
if err != nil {
return nil, err
}
return mapEachMaintenanceEntry(entries), nil
}
func (r *MaintenanceEntryRepository) Create(ctx context.Context, itemID uuid.UUID, input MaintenanceEntryCreate) (MaintenanceEntry, error) {
item, err := r.db.MaintenanceEntry.Create().
SetItemID(itemID).

View file

@ -73,6 +73,16 @@ func (r *NotifierRepository) GetByGroup(ctx context.Context, groupID uuid.UUID)
Where(notifier.GroupID(groupID)).
Order(ent.Asc(notifier.FieldName)).
All(ctx)
return r.mapper.MapEachErr(notifier, err)
}
func (r *NotifierRepository) GetActiveByGroup(ctx context.Context, groupID uuid.UUID) ([]NotifierOut, error) {
notifier, err := r.db.Notifier.Query().
Where(notifier.GroupID(groupID), notifier.IsActive(true)).
Order(ent.Asc(notifier.FieldName)).
All(ctx)
return r.mapper.MapEachErr(notifier, err)
}

View file

@ -20,7 +20,7 @@ func New(db *ent.Client, root string) *AllRepos {
return &AllRepos{
Users: &UserRepository{db},
AuthTokens: &TokenRepository{db},
Groups: &GroupRepository{db},
Groups: NewGroupRepository(db),
Locations: &LocationRepository{db},
Labels: &LabelRepository{db},
Items: &ItemsRepository{db},