mirror of
https://github.com/hay-kot/homebox.git
synced 2025-08-05 09:10:26 +00:00
add new background service to manage scheduled notifications
This commit is contained in:
parent
40f4bd069e
commit
f1089833e0
8 changed files with 170 additions and 30 deletions
|
@ -2,6 +2,7 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
@ -168,6 +169,19 @@ func run(cfg *config.Config) error {
|
||||||
Msg("failed to purge expired invitations")
|
Msg("failed to purge expired invitations")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
go app.startBgTask(time.Duration(1)*time.Hour, func() {
|
||||||
|
now := time.Now()
|
||||||
|
|
||||||
|
if now.Hour() == 8 {
|
||||||
|
fmt.Println("run notifiers")
|
||||||
|
err := app.services.BackgroundService.SendNotifiersToday(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
log.Error().
|
||||||
|
Err(err).
|
||||||
|
Msg("failed to send notifiers")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
// TODO: Remove through external API that does setup
|
// TODO: Remove through external API that does setup
|
||||||
if cfg.Demo {
|
if cfg.Demo {
|
||||||
|
|
|
@ -5,9 +5,10 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type AllServices struct {
|
type AllServices struct {
|
||||||
User *UserService
|
User *UserService
|
||||||
Group *GroupService
|
Group *GroupService
|
||||||
Items *ItemService
|
Items *ItemService
|
||||||
|
BackgroundService *BackgroundService
|
||||||
}
|
}
|
||||||
|
|
||||||
type OptionsFunc func(*options)
|
type OptionsFunc func(*options)
|
||||||
|
@ -42,5 +43,6 @@ func New(repos *repo.AllRepos, opts ...OptionsFunc) *AllServices {
|
||||||
repo: repos,
|
repo: repos,
|
||||||
autoIncrementAssetID: options.autoIncrementAssetID,
|
autoIncrementAssetID: options.autoIncrementAssetID,
|
||||||
},
|
},
|
||||||
|
BackgroundService: &BackgroundService{repos},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
81
backend/internal/core/services/service_background.go
Normal file
81
backend/internal/core/services/service_background.go
Normal 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
|
||||||
|
}
|
|
@ -33,6 +33,8 @@ func (MaintenanceEntry) Fields() []ent.Field {
|
||||||
Optional(),
|
Optional(),
|
||||||
field.Float("cost").
|
field.Float("cost").
|
||||||
Default(0.0),
|
Default(0.0),
|
||||||
|
field.Bool("reminders_enabled").
|
||||||
|
Default(false),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,36 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type GroupRepository struct {
|
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 (
|
type (
|
||||||
|
@ -76,27 +105,8 @@ type (
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
var mapToGroupErr = mapTErrFunc(mapToGroup)
|
func (r *GroupRepository) GetAllGroups(ctx context.Context) ([]Group, error) {
|
||||||
|
return r.groupMapper.MapEachErr(r.db.Group.Query().All(ctx))
|
||||||
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) StatsLocationsByPurchasePrice(ctx context.Context, GID uuid.UUID) ([]TotalsByOrganizer, error) {
|
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) {
|
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).
|
SetName(name).
|
||||||
Save(ctx))
|
Save(ctx))
|
||||||
}
|
}
|
||||||
|
@ -262,15 +272,15 @@ func (r *GroupRepository) GroupUpdate(ctx context.Context, ID uuid.UUID, data Gr
|
||||||
SetCurrency(currency).
|
SetCurrency(currency).
|
||||||
Save(ctx)
|
Save(ctx)
|
||||||
|
|
||||||
return mapToGroupErr(entity, err)
|
return r.groupMapper.MapErr(entity, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *GroupRepository) GroupByID(ctx context.Context, id uuid.UUID) (Group, error) {
|
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) {
|
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)).
|
Where(groupinvitationtoken.Token(token)).
|
||||||
WithGroup().
|
WithGroup().
|
||||||
Only(ctx))
|
Only(ctx))
|
||||||
|
|
|
@ -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) {
|
func (r *MaintenanceEntryRepository) Create(ctx context.Context, itemID uuid.UUID, input MaintenanceEntryCreate) (MaintenanceEntry, error) {
|
||||||
item, err := r.db.MaintenanceEntry.Create().
|
item, err := r.db.MaintenanceEntry.Create().
|
||||||
SetItemID(itemID).
|
SetItemID(itemID).
|
||||||
|
|
|
@ -73,6 +73,16 @@ func (r *NotifierRepository) GetByGroup(ctx context.Context, groupID uuid.UUID)
|
||||||
Where(notifier.GroupID(groupID)).
|
Where(notifier.GroupID(groupID)).
|
||||||
Order(ent.Asc(notifier.FieldName)).
|
Order(ent.Asc(notifier.FieldName)).
|
||||||
All(ctx)
|
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)
|
return r.mapper.MapEachErr(notifier, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,7 @@ func New(db *ent.Client, root string) *AllRepos {
|
||||||
return &AllRepos{
|
return &AllRepos{
|
||||||
Users: &UserRepository{db},
|
Users: &UserRepository{db},
|
||||||
AuthTokens: &TokenRepository{db},
|
AuthTokens: &TokenRepository{db},
|
||||||
Groups: &GroupRepository{db},
|
Groups: NewGroupRepository(db),
|
||||||
Locations: &LocationRepository{db},
|
Locations: &LocationRepository{db},
|
||||||
Labels: &LabelRepository{db},
|
Labels: &LabelRepository{db},
|
||||||
Items: &ItemsRepository{db},
|
Items: &ItemsRepository{db},
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue