snapshot of https://github.com/cortezaproject/corteza.git (2024.9.x, 2026-09-18; blobs > 131072B omitted)
Some checks failed
SonarQube / SonarQube (push) Failing after 5m28s
E2e test on main branch push / server and client test (map[name:admin port:8080]) (push) Failing after 3m33s
Server and lib tests / Library test (js) (push) Failing after 1m36s
Server and lib tests / Library test (vue) (push) Successful in 2m24s
Server and lib tests / Web client test (admin) (push) Successful in 2m52s
Server and lib tests / Web client test (compose) (push) Successful in 3m23s
Server and lib tests / Web client test (discovery) (push) Successful in 2m49s
Server and lib tests / Web client test (one) (push) Successful in 2m52s
Server and lib tests / Web client test (privacy) (push) Successful in 2m44s
Server and lib tests / Web client test (reporter) (push) Successful in 2m56s
Server and lib tests / Web client test (workflow) (push) Successful in 3m0s
Server and lib tests / Server tests (push) Failing after 4m55s
E2e test on main branch push / notify-failure (push) Failing after 9s
Server and lib tests / Send matrix testing notification (push) Successful in 2s
Mark stale issues and pull requests / stale (push) Successful in 40s
Some checks failed
SonarQube / SonarQube (push) Failing after 5m28s
E2e test on main branch push / server and client test (map[name:admin port:8080]) (push) Failing after 3m33s
Server and lib tests / Library test (js) (push) Failing after 1m36s
Server and lib tests / Library test (vue) (push) Successful in 2m24s
Server and lib tests / Web client test (admin) (push) Successful in 2m52s
Server and lib tests / Web client test (compose) (push) Successful in 3m23s
Server and lib tests / Web client test (discovery) (push) Successful in 2m49s
Server and lib tests / Web client test (one) (push) Successful in 2m52s
Server and lib tests / Web client test (privacy) (push) Successful in 2m44s
Server and lib tests / Web client test (reporter) (push) Successful in 2m56s
Server and lib tests / Web client test (workflow) (push) Successful in 3m0s
Server and lib tests / Server tests (push) Failing after 4m55s
E2e test on main branch push / notify-failure (push) Failing after 9s
Server and lib tests / Send matrix testing notification (push) Successful in 2s
Mark stale issues and pull requests / stale (push) Successful in 40s
This commit is contained in:
419
server/system/service/reminder.go
Normal file
419
server/system/service/reminder.go
Normal file
@@ -0,0 +1,419 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/cortezaproject/corteza/server/pkg/actionlog"
|
||||
intAuth "github.com/cortezaproject/corteza/server/pkg/auth"
|
||||
"github.com/cortezaproject/corteza/server/pkg/eventbus"
|
||||
"github.com/cortezaproject/corteza/server/store"
|
||||
"github.com/cortezaproject/corteza/server/system/types"
|
||||
"github.com/getsentry/sentry-go"
|
||||
"go.uber.org/zap"
|
||||
"github.com/cortezaproject/corteza/server/system/service/event"
|
||||
)
|
||||
|
||||
type (
|
||||
reminderSender interface {
|
||||
Send(kind string, payload interface{}, userIDs ...uint64) error
|
||||
}
|
||||
|
||||
reminder struct {
|
||||
ac reminderAccessController
|
||||
|
||||
log *zap.Logger
|
||||
actionlog actionlog.Recorder
|
||||
store store.Reminders
|
||||
reminderSender reminderSender
|
||||
eventbus eventDispatcher
|
||||
}
|
||||
|
||||
reminderAccessController interface {
|
||||
CanAssignReminder(ctx context.Context) bool
|
||||
}
|
||||
|
||||
ReminderService interface {
|
||||
Find(context.Context, types.ReminderFilter) (types.ReminderSet, types.ReminderFilter, error)
|
||||
FindByID(context.Context, uint64) (*types.Reminder, error)
|
||||
|
||||
Create(context.Context, *types.Reminder) (*types.Reminder, error)
|
||||
|
||||
Update(context.Context, *types.Reminder) (*types.Reminder, error)
|
||||
|
||||
Dismiss(context.Context, uint64) error
|
||||
Undismiss(context.Context, uint64) error
|
||||
Snooze(context.Context, uint64, *time.Time) error
|
||||
|
||||
Delete(context.Context, uint64) error
|
||||
|
||||
Watch(ctx context.Context)
|
||||
}
|
||||
)
|
||||
|
||||
func Reminder(ctx context.Context, log *zap.Logger, rs reminderSender) ReminderService {
|
||||
return &reminder{
|
||||
ac: DefaultAccessControl,
|
||||
log: log,
|
||||
store: DefaultStore,
|
||||
reminderSender: rs,
|
||||
eventbus: eventbus.Service(),
|
||||
}
|
||||
}
|
||||
|
||||
func (svc reminder) Find(ctx context.Context, filter types.ReminderFilter) (rr types.ReminderSet, f types.ReminderFilter, err error) {
|
||||
var (
|
||||
raProps = &reminderActionProps{filter: &filter}
|
||||
)
|
||||
|
||||
// Users that are allowed to assign reminders to others can also
|
||||
// search through reminders of other users
|
||||
if !svc.ac.CanAssignReminder(ctx) {
|
||||
filter.Check = func(r *types.Reminder) (bool, error) {
|
||||
return !svc.checkAssignTo(ctx, r), nil
|
||||
}
|
||||
}
|
||||
|
||||
err = func() (err error) {
|
||||
rr, f, err = store.SearchReminders(ctx, svc.store, filter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}()
|
||||
|
||||
return rr, f, svc.recordAction(ctx, raProps, ReminderActionSearch, err)
|
||||
}
|
||||
|
||||
func (svc reminder) FindByID(ctx context.Context, ID uint64) (r *types.Reminder, err error) {
|
||||
var (
|
||||
raProps = &reminderActionProps{reminder: &types.Reminder{ID: ID}}
|
||||
)
|
||||
|
||||
err = func() (err error) {
|
||||
if ID == 0 {
|
||||
return ReminderErrInvalidID()
|
||||
}
|
||||
|
||||
r, err = store.LookupReminderByID(ctx, svc.store, ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !svc.canAccess(ctx, r) {
|
||||
return ReminderErrNotAllowedToRead()
|
||||
}
|
||||
|
||||
raProps.setReminder(r)
|
||||
|
||||
return nil
|
||||
}()
|
||||
|
||||
return r, svc.recordAction(ctx, raProps, ReminderActionLookup, err)
|
||||
}
|
||||
|
||||
func (svc reminder) FindByIDs(ctx context.Context, IDs ...uint64) (rr types.ReminderSet, err error) {
|
||||
if len(IDs) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
rr, _, err = svc.Find(ctx, types.ReminderFilter{ReminderID: IDs, AssignedTo: svc.currentUser(ctx)})
|
||||
|
||||
return rr, nil
|
||||
}
|
||||
|
||||
func (svc reminder) checkAssignee(ctx context.Context, rm *types.Reminder) (err error) {
|
||||
// Check if user is assigning to someone else
|
||||
if svc.checkAssignTo(ctx, rm) {
|
||||
if !svc.ac.CanAssignReminder(ctx) {
|
||||
return ReminderErrNotAllowedToAssign()
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// checkAssignTo compares current user with reminder.AssignedTo and return bool
|
||||
func (svc reminder) checkAssignTo(ctx context.Context, rm *types.Reminder) (valid bool) {
|
||||
return rm.AssignedTo != svc.currentUser(ctx)
|
||||
}
|
||||
|
||||
// canAccess returns true when reminder is assigned to the current user or when
|
||||
// the user is allowed to assign reminders to others
|
||||
func (svc reminder) canAccess(ctx context.Context, rm *types.Reminder) bool {
|
||||
return !svc.checkAssignTo(ctx, rm) || svc.ac.CanAssignReminder(ctx)
|
||||
}
|
||||
|
||||
func (svc reminder) currentUser(ctx context.Context) uint64 {
|
||||
return intAuth.GetIdentityFromContext(ctx).Identity()
|
||||
}
|
||||
|
||||
func (svc reminder) Create(ctx context.Context, new *types.Reminder) (r *types.Reminder, err error) {
|
||||
var (
|
||||
raProps = &reminderActionProps{new: new}
|
||||
)
|
||||
|
||||
err = func() (err error) {
|
||||
if err := svc.checkAssignee(ctx, new); err != nil {
|
||||
return err
|
||||
}
|
||||
r = new
|
||||
r.ID = nextID()
|
||||
r.CreatedAt = *now()
|
||||
if err = svc.eventbus.WaitFor(ctx, event.ReminderBeforeCreate(new, r)); err != nil {
|
||||
return
|
||||
}
|
||||
if err = store.CreateReminder(ctx, svc.store, r); err != nil {
|
||||
return err
|
||||
}
|
||||
svc.eventbus.Dispatch(ctx,event.ReminderAfterCreate(new,r))
|
||||
return nil
|
||||
}()
|
||||
|
||||
return r, svc.recordAction(ctx, raProps, ReminderActionUpdate, err)
|
||||
|
||||
}
|
||||
|
||||
func (svc reminder) Update(ctx context.Context, upd *types.Reminder) (r *types.Reminder, err error) {
|
||||
var (
|
||||
raProps = &reminderActionProps{updated: upd}
|
||||
)
|
||||
|
||||
err = func() (err error) {
|
||||
if upd.ID == 0 {
|
||||
return ReminderErrInvalidID()
|
||||
}
|
||||
|
||||
if r, err = store.LookupReminderByID(ctx, svc.store, upd.ID); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := svc.checkAssignee(ctx, upd); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Assign changed values
|
||||
if upd.AssignedTo != r.AssignedTo {
|
||||
r.AssignedTo = upd.AssignedTo
|
||||
r.AssignedBy = svc.currentUser(ctx)
|
||||
r.AssignedAt = time.Now()
|
||||
}
|
||||
|
||||
a := r.RemindAt
|
||||
if a == nil {
|
||||
a = &time.Time{}
|
||||
}
|
||||
|
||||
b := upd.RemindAt
|
||||
if b == nil {
|
||||
b = &time.Time{}
|
||||
}
|
||||
if !a.Equal(*b) {
|
||||
r.SnoozeCount = 0
|
||||
}
|
||||
|
||||
r.RemindAt = upd.RemindAt
|
||||
r.Payload = upd.Payload
|
||||
r.Resource = upd.Resource
|
||||
r.UpdatedAt = now()
|
||||
if err = svc.eventbus.WaitFor(ctx,event.ReminderBeforeUpdate(upd,r)); err!=nil {
|
||||
return
|
||||
}
|
||||
if err = store.UpdateReminder(ctx, svc.store, r); err != nil {
|
||||
return err
|
||||
}
|
||||
svc.eventbus.Dispatch(ctx,event.ReminderAfterUpdate(upd,r))
|
||||
return nil
|
||||
}()
|
||||
|
||||
return r, svc.recordAction(ctx, raProps, ReminderActionUpdate, err)
|
||||
}
|
||||
|
||||
func (svc reminder) Dismiss(ctx context.Context, ID uint64) (err error) {
|
||||
var (
|
||||
r *types.Reminder
|
||||
|
||||
raProps = &reminderActionProps{reminder: &types.Reminder{ID: ID}}
|
||||
)
|
||||
|
||||
err = func() (err error) {
|
||||
if ID == 0 {
|
||||
return ReminderErrInvalidID()
|
||||
}
|
||||
|
||||
if r, err = store.LookupReminderByID(ctx, svc.store, ID); err != nil {
|
||||
return ReminderErrNotFound()
|
||||
}
|
||||
|
||||
if !svc.canAccess(ctx, r) {
|
||||
return ReminderErrNotAllowedToDismiss()
|
||||
}
|
||||
|
||||
raProps.setReminder(r)
|
||||
|
||||
// Assign changed values
|
||||
n := time.Now()
|
||||
r.DismissedAt = &n
|
||||
r.DismissedBy = svc.currentUser(ctx)
|
||||
if err=svc.eventbus.WaitFor(ctx,event.ReminderBeforeDismiss(nil,r)); err!=nil{
|
||||
return
|
||||
}
|
||||
if err = store.UpdateReminder(ctx, svc.store, r); err != nil {
|
||||
return err
|
||||
}
|
||||
svc.eventbus.Dispatch(ctx,event.ReminderAfterDismiss(nil,r))
|
||||
return nil
|
||||
}()
|
||||
|
||||
return svc.recordAction(ctx, raProps, ReminderActionDismiss, err)
|
||||
}
|
||||
|
||||
func (svc reminder) Undismiss(ctx context.Context, ID uint64) (err error) {
|
||||
var (
|
||||
r *types.Reminder
|
||||
|
||||
raProps = &reminderActionProps{reminder: &types.Reminder{ID: ID}}
|
||||
)
|
||||
|
||||
err = func() (err error) {
|
||||
if ID == 0 {
|
||||
return ReminderErrInvalidID()
|
||||
}
|
||||
|
||||
if r, err = store.LookupReminderByID(ctx, svc.store, ID); err != nil {
|
||||
return ReminderErrNotFound()
|
||||
}
|
||||
|
||||
if !svc.canAccess(ctx, r) {
|
||||
return ReminderErrNotAllowedToUndismiss()
|
||||
}
|
||||
|
||||
raProps.setReminder(r)
|
||||
|
||||
// Assign changed values
|
||||
r.DismissedAt = nil
|
||||
r.DismissedBy = 0
|
||||
//pending eventbus integration
|
||||
if err = store.UpdateReminder(ctx, svc.store, r); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}()
|
||||
|
||||
return svc.recordAction(ctx, raProps, ReminderActionDismiss, err)
|
||||
}
|
||||
|
||||
func (svc reminder) Snooze(ctx context.Context, ID uint64, remindAt *time.Time) (err error) {
|
||||
var (
|
||||
r *types.Reminder
|
||||
|
||||
raProps = &reminderActionProps{reminder: &types.Reminder{ID: ID, RemindAt: remindAt}}
|
||||
)
|
||||
|
||||
err = func() (err error) {
|
||||
if ID == 0 {
|
||||
return ReminderErrInvalidID()
|
||||
}
|
||||
|
||||
if r, err = store.LookupReminderByID(ctx, svc.store, ID); err != nil {
|
||||
return ReminderErrNotFound()
|
||||
}
|
||||
|
||||
if !svc.canAccess(ctx, r) {
|
||||
return ReminderErrNotAllowedToSnooze()
|
||||
}
|
||||
|
||||
raProps.setReminder(r)
|
||||
|
||||
// Assign changed values
|
||||
r.SnoozeCount++
|
||||
r.RemindAt = remindAt
|
||||
if err=svc.eventbus.WaitFor(ctx,event.ReminderBeforeSnooze(nil,r));err!=nil {
|
||||
return
|
||||
}
|
||||
if err = store.UpdateReminder(ctx, svc.store, r); err != nil {
|
||||
return err
|
||||
}
|
||||
svc.eventbus.Dispatch(ctx,event.ReminderAfterSnooze(nil,r))
|
||||
|
||||
return nil
|
||||
}()
|
||||
|
||||
return svc.recordAction(ctx, raProps, ReminderActionSnooze, err)
|
||||
}
|
||||
|
||||
func (svc reminder) Delete(ctx context.Context, ID uint64) (err error) {
|
||||
var (
|
||||
r *types.Reminder
|
||||
|
||||
raProps = &reminderActionProps{reminder: &types.Reminder{ID: ID}}
|
||||
)
|
||||
|
||||
err = func() (err error) {
|
||||
if ID == 0 {
|
||||
return ReminderErrInvalidID()
|
||||
}
|
||||
|
||||
if r, err = svc.FindByID(ctx, ID); err != nil {
|
||||
return ReminderErrNotFound()
|
||||
}
|
||||
|
||||
r.DeletedAt = now()
|
||||
|
||||
raProps.setReminder(r)
|
||||
if err=svc.eventbus.WaitFor(ctx,event.ReminderBeforeDelete(nil,r));err != nil {
|
||||
return
|
||||
}
|
||||
if err=store.UpdateReminder(ctx, svc.store, r);err!=nil{
|
||||
return err
|
||||
}
|
||||
svc.eventbus.Dispatch(ctx,event.ReminderAfterDelete(nil,r))
|
||||
return nil
|
||||
}()
|
||||
|
||||
return svc.recordAction(ctx, raProps, ReminderActionDelete, err)
|
||||
}
|
||||
|
||||
func (svc reminder) Watch(ctx context.Context) {
|
||||
if svc.reminderSender != nil {
|
||||
var (
|
||||
interval = time.Minute
|
||||
rTicker = time.NewTicker(interval)
|
||||
)
|
||||
|
||||
go func() {
|
||||
defer sentry.Recover()
|
||||
defer rTicker.Stop()
|
||||
defer svc.log.Info("stopped")
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-rTicker.C:
|
||||
// Get scheduled reminders of users
|
||||
rr, _, err := svc.Find(ctx, types.ReminderFilter{
|
||||
ExcludeDismissed: true,
|
||||
ScheduledOnly: true,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
svc.log.Error("failed to get reminders of users", zap.Error(err))
|
||||
}
|
||||
|
||||
// Send scheduled reminders to users
|
||||
_ = rr.Walk(func(r *types.Reminder) error {
|
||||
if r.RemindAt != nil && r.DismissedAt == nil && now().Add(interval).After(*r.RemindAt) {
|
||||
if err := svc.reminderSender.Send("reminder", r, r.AssignedTo); err != nil {
|
||||
svc.log.Error("failed to send reminder to user", zap.Error(err))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user