Skip to content

Commit

Permalink
feat: use notifiers on schedule (hay-kot#362)
Browse files Browse the repository at this point in the history
* fix potential memory leak with time.After

* add new background service to manage scheduled notifications

* update docs

* remove old js reference

* closes hay-kot#278

* tidy
  • Loading branch information
hay-kot authored and OrellBuehler committed Jun 24, 2023
1 parent 54f57f8 commit 552cb1e
Show file tree
Hide file tree
Showing 16 changed files with 201 additions and 52 deletions.
11 changes: 7 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,13 @@
[Configuration & Docker Compose](https://hay-kot.github.io/homebox/quick-start)

```bash
docker run --name=homebox \
--restart=always \
--publish=3100:7745 \
ghcr.io/hay-kot/homebox:latest
docker run -d \
--name homebox \
--restart unless-stopped \
--publish 3100:7745 \
--env TZ=Europe/Bucharest \
--volume /path/to/data/folder/:/data \
ghcr.io/hay-kot/homebox:latest
```

## Credits
Expand Down
5 changes: 4 additions & 1 deletion backend/app/api/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,11 @@ func new(conf *config.Config) *app {
}

func (a *app) startBgTask(t time.Duration, fn func()) {
timer := time.NewTimer(t)

for {
timer.Reset(t)
a.server.Background(fn)
time.Sleep(t)
<-timer.C
}
}
14 changes: 14 additions & 0 deletions backend/app/api/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package main

import (
"context"
"fmt"
"net/http"
"os"
"path/filepath"
Expand Down Expand Up @@ -168,6 +169,19 @@ func run(cfg *config.Config) error {
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
if cfg.Demo {
Expand Down
1 change: 0 additions & 1 deletion backend/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ require (
github.com/rs/zerolog v1.29.0
github.com/stretchr/testify v1.8.2
github.com/swaggo/http-swagger v1.3.4
github.com/swaggo/http-swagger/v2 v2.0.0
github.com/swaggo/swag v1.8.11
github.com/yeqown/go-qrcode/v2 v2.2.1
github.com/yeqown/go-qrcode/writer/standard v1.2.1
Expand Down
7 changes: 0 additions & 7 deletions backend/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -183,8 +183,6 @@ cloud.google.com/go/webrisk v1.5.0/go.mod h1:iPG6fr52Tv7sGk0H6qUFzmL3HHZev1htXuW
cloud.google.com/go/workflows v1.6.0/go.mod h1:6t9F5h/unJz41YqfBmqSASJSXccBLtD1Vwf+KmJENM0=
cloud.google.com/go/workflows v1.7.0/go.mod h1:JhSrZuVZWuiDfKEFxU0/F1PQjmpnpcoISEXH2bcHC3M=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
entgo.io/ent v0.11.8 h1:M/M0QL1CYCUSdqGRXUrXhFYSDRJPsOOrr+RLEej/gyQ=
entgo.io/ent v0.11.8/go.mod h1:ericBi6Q8l3wBH1wEIDfKxw7rcQEuRPyBfbIzjtxJ18=
entgo.io/ent v0.11.10 h1:iqn32ybY5HRW3xSAyMNdNKpZhKgMf1Zunsej9yPKUI8=
entgo.io/ent v0.11.10/go.mod h1:mzTZ0trE+jCQw/fnzijbm5Mck/l8Gbg7gC/+L1COyzM=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
Expand Down Expand Up @@ -620,9 +618,6 @@ github.com/swaggo/files v1.0.0 h1:1gGXVIeUFCS/dta17rnP0iOpr6CXFwKD7EO5ID233e4=
github.com/swaggo/files v1.0.0/go.mod h1:N59U6URJLyU1PQgFqPM7wXLMhJx7QAolnvfQkqO13kc=
github.com/swaggo/http-swagger v1.3.4 h1:q7t/XLx0n15H1Q9/tk3Y9L4n210XzJF5WtnDX64a5ww=
github.com/swaggo/http-swagger v1.3.4/go.mod h1:9dAh0unqMBAlbp1uE2Uc2mQTxNMU/ha4UbucIg1MFkQ=
github.com/swaggo/http-swagger/v2 v2.0.0/go.mod h1:XYhrQVIKz13CxuKD4p4kvpaRB4jJ1/MlfQXVOE+CX8Y=
github.com/swaggo/swag v1.8.10 h1:eExW4bFa52WOjqRzRD58bgWsWfdFJso50lpbeTcmTfo=
github.com/swaggo/swag v1.8.10/go.mod h1:ezQVUUhly8dludpVk+/PuwJWvLLanB13ygV5Pr9enSk=
github.com/swaggo/swag v1.8.11 h1:Fp1dNNtDvbCf+8kvehZbHQnlF6AxHGjmw6H/xAMrZfY=
github.com/swaggo/swag v1.8.11/go.mod h1:2GXgpNI9iy5OdsYWu8zXfRAGnOAPxYxTWTyM0XOTYZQ=
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
Expand Down Expand Up @@ -991,8 +986,6 @@ golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA=
golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.6.1-0.20230222164832-25d2519c8696 h1:8985/C5IvACpd9DDXckSnjSBLKDgbxXiyODgi94zOPM=
golang.org/x/tools v0.6.1-0.20230222164832-25d2519c8696/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
Expand Down
8 changes: 5 additions & 3 deletions backend/internal/core/services/all.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -42,5 +43,6 @@ func New(repos *repo.AllRepos, opts ...OptionsFunc) *AllServices {
repo: repos,
autoIncrementAssetID: options.autoIncrementAssetID,
},
BackgroundService: &BackgroundService{repos},
}
}
81 changes: 81 additions & 0 deletions backend/internal/core/services/service_background.go
Original file line number Diff line number Diff line change
@@ -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
}
2 changes: 2 additions & 0 deletions backend/internal/data/ent/schema/maintenance_entry.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ func (MaintenanceEntry) Fields() []ent.Field {
Optional(),
field.Float("cost").
Default(0.0),
field.Bool("reminders_enabled").
Default(false),
}
}

Expand Down
62 changes: 36 additions & 26 deletions backend/internal/data/repo/repo_group.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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))
}
Expand All @@ -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))
Expand Down
21 changes: 21 additions & 0 deletions backend/internal/data/repo/repo_maintenance_entry.go
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down
10 changes: 10 additions & 0 deletions backend/internal/data/repo/repo_notifier.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

Expand Down
2 changes: 1 addition & 1 deletion backend/internal/data/repo/repos_all.go
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand Down
2 changes: 1 addition & 1 deletion docs/docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ Homebox is currently in early-active development and is currently in **beta** st
- Item Identifications (Serial, Model, etc)
- Categorized Attachments (Images, Manuals, General)
- Arbitrary/Custom Fields
- Csv Import for quickly creating and managing items
- CSV Import/Export for quickly creating and managing items
- Custom Reporting
- Bill of Materials Export
- QR Code Label Generator
Expand Down
11 changes: 7 additions & 4 deletions docs/docs/quick-start.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,13 @@
Great for testing out the application, but not recommended for stable use. Checkout the docker-compose for the recommended deployment.

```sh
docker run --name=homebox \
--restart=always \
--publish=3100:7745 \
ghcr.io/hay-kot/homebox:latest
docker run -d \
--name homebox \
--restart unless-stopped \
--publish 3100:7745 \
--env TZ=Europe/Bucharest \
--volume /path/to/data/folder/:/data \
ghcr.io/hay-kot/homebox:latest
```

## Docker-Compose
Expand Down

0 comments on commit 552cb1e

Please sign in to comment.