From 854c3c9047dd73b96a2c499ec97e6783c9df0fa1 Mon Sep 17 00:00:00 2001 From: Denis Arh Date: Tue, 26 May 2020 14:52:41 +0200 Subject: [PATCH] Implement actionlog for reminders --- system/service/reminder.go | 202 +++++--- system/service/reminder_actions.gen.go | 682 +++++++++++++++++++++++++ system/service/reminder_actions.yaml | 63 +++ tests/system/reminder_test.go | 4 +- 4 files changed, 891 insertions(+), 60 deletions(-) create mode 100644 system/service/reminder_actions.gen.go create mode 100644 system/service/reminder_actions.yaml diff --git a/system/service/reminder.go b/system/service/reminder.go index ae1bd515b..8b981192d 100644 --- a/system/service/reminder.go +++ b/system/service/reminder.go @@ -4,6 +4,7 @@ import ( "context" "time" + "github.com/cortezaproject/corteza-server/pkg/actionlog" "github.com/cortezaproject/corteza-server/system/repository" "github.com/titpetric/factory" @@ -17,7 +18,8 @@ type ( db *factory.DB ac reminderAccessController - reminder repository.ReminderRepository + actionlog actionlog.Recorder + reminder repository.ReminderRepository } reminderAccessController interface { @@ -48,42 +50,61 @@ func Reminder(ctx context.Context) ReminderService { } } -func (svc reminder) Find(ctx context.Context, f types.ReminderFilter) (types.ReminderSet, types.ReminderFilter, error) { - rr, f, err := svc.reminder.Find(f) - if err != nil { - return nil, f, err - } +func (svc reminder) Find(ctx context.Context, filter types.ReminderFilter) (rr types.ReminderSet, f types.ReminderFilter, err error) { + var ( + raProps = &reminderActionProps{filter: &filter} + ) - return rr, f, nil + err = svc.db.With(ctx).Transaction(func() (err error) { + rr, f, err = svc.reminder.Find(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) (*types.Reminder, error) { - rm, err := svc.reminder.FindByID(ID) - if err != nil { - return nil, err - } +func (svc reminder) FindByID(ctx context.Context, ID uint64) (r *types.Reminder, err error) { + var ( + raProps = &reminderActionProps{reminder: &types.Reminder{ID: ID}} + ) - return rm, nil + err = svc.db.With(ctx).Transaction(func() (err error) { + if ID == 0 { + return ReminderErrInvalidID() + } + + r, err = svc.reminder.FindByID(ID) + if err != nil { + return err + } + + raProps.setReminder(r) + + return nil + }) + + return r, svc.recordAction(ctx, raProps, ReminderActionLookup, err) } -func (svc reminder) FindByIDs(ctx context.Context, IDs ...uint64) (types.ReminderSet, error) { +func (svc reminder) FindByIDs(ctx context.Context, IDs ...uint64) (rr types.ReminderSet, err error) { if len(IDs) == 0 { return nil, nil } - rr, _, err := svc.reminder.Find(types.ReminderFilter{ReminderID: IDs}) - if err != nil { - return nil, err - } + rr, _, err = svc.Find(ctx, types.ReminderFilter{ReminderID: IDs}) return rr, nil } -func (svc reminder) checkAssignee(ctx context.Context, rm *types.Reminder) error { +func (svc reminder) checkAssignee(ctx context.Context, rm *types.Reminder) (err error) { // Check if user is assigning to someone else if rm.AssignedTo != svc.currentUser(ctx) { if !svc.ac.CanAssignReminder(ctx) { - return ErrNoReminderAssignPermissions + return ReminderErrNotAllowedToAssign() } } @@ -94,86 +115,151 @@ func (svc reminder) currentUser(ctx context.Context) uint64 { return intAuth.GetIdentityFromContext(ctx).Identity() } -func (svc reminder) Create(ctx context.Context, rm *types.Reminder) (*types.Reminder, error) { - if err := svc.checkAssignee(ctx, rm); err != nil { - return nil, err - } +func (svc reminder) Create(ctx context.Context, new *types.Reminder) (r *types.Reminder, err error) { + var ( + raProps = &reminderActionProps{new: new} + ) + + err = svc.db.With(ctx).Transaction(func() (err error) { + if err := svc.checkAssignee(ctx, new); err != nil { + return err + } + + if r, err = svc.reminder.Create(new); err != nil { + return err + } + + return nil + }) + + return r, svc.recordAction(ctx, raProps, ReminderActionUpdate, err) - return svc.reminder.Create(rm) } -func (svc reminder) Update(ctx context.Context, rm *types.Reminder) (t *types.Reminder, err error) { - return rm, svc.db.With(ctx).Transaction(func() (err error) { - if t, err = svc.reminder.FindByID(rm.ID); err != nil { +func (svc reminder) Update(ctx context.Context, upd *types.Reminder) (r *types.Reminder, err error) { + var ( + raProps = &reminderActionProps{updated: upd} + ) + + err = svc.db.With(ctx).Transaction(func() (err error) { + if upd.ID == 0 { + return ReminderErrInvalidID() + } + + if r, err = svc.reminder.FindByID(upd.ID); err != nil { return } - if err := svc.checkAssignee(ctx, rm); err != nil { + if err := svc.checkAssignee(ctx, upd); err != nil { return err } // Assign changed values - if rm.AssignedTo != t.AssignedTo { - t.AssignedTo = rm.AssignedTo - t.AssignedBy = svc.currentUser(ctx) - t.AssignedAt = time.Now() + if upd.AssignedTo != r.AssignedTo { + r.AssignedTo = upd.AssignedTo + r.AssignedBy = svc.currentUser(ctx) + r.AssignedAt = time.Now() } - t.Payload = rm.Payload - t.RemindAt = rm.RemindAt - t.Resource = rm.Resource - if t, err = svc.reminder.Update(t); err != nil { + r.Payload = upd.Payload + r.RemindAt = upd.RemindAt + r.Resource = upd.Resource + + if r, err = svc.reminder.Update(r); err != nil { return err } return nil }) + + return r, svc.recordAction(ctx, raProps, ReminderActionUpdate, err) } func (svc reminder) Dismiss(ctx context.Context, ID uint64) (err error) { - return svc.db.With(ctx).Transaction(func() (err error) { - var t *types.Reminder - if t, err = svc.reminder.FindByID(ID); err != nil { - return err + var ( + r *types.Reminder + + raProps = &reminderActionProps{reminder: &types.Reminder{ID: ID}} + ) + + err = svc.db.With(ctx).Transaction(func() (err error) { + if ID == 0 { + return ReminderErrInvalidID() } + if r, err = svc.reminder.FindByID(ID); err != nil { + return ReminderErrNonexistent() + } + + raProps.setReminder(r) + // Assign changed values n := time.Now() - t.DismissedAt = &n - t.DismissedBy = svc.currentUser(ctx) + r.DismissedAt = &n + r.DismissedBy = svc.currentUser(ctx) - if t, err = svc.reminder.Update(t); err != nil { + if r, err = svc.reminder.Update(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) { - return svc.db.With(ctx).Transaction(func() (err error) { - var t *types.Reminder - if t, err = svc.reminder.FindByID(ID); err != nil { - return err + var ( + r *types.Reminder + + raProps = &reminderActionProps{reminder: &types.Reminder{ID: ID, RemindAt: remindAt}} + ) + + err = svc.db.With(ctx).Transaction(func() (err error) { + if ID == 0 { + return ReminderErrInvalidID() } - // Assign changed values - t.SnoozeCount++ - t.RemindAt = remindAt + if r, err = svc.reminder.FindByID(ID); err != nil { + return ReminderErrNonexistent() + } - if t, err = svc.reminder.Update(t); err != nil { + raProps.setReminder(r) + + // Assign changed values + r.SnoozeCount++ + r.RemindAt = remindAt + + if r, err = svc.reminder.Update(r); err != nil { return err } return nil }) + + return svc.recordAction(ctx, raProps, ReminderActionSnooze, err) } -func (svc reminder) Delete(ctx context.Context, ID uint64) error { - _, err := svc.FindByID(ctx, ID) - if err != nil { - return err - } +func (svc reminder) Delete(ctx context.Context, ID uint64) (err error) { + var ( + r *types.Reminder - return svc.reminder.Delete(ID) + raProps = &reminderActionProps{reminder: &types.Reminder{ID: ID}} + ) + + err = svc.db.With(ctx).Transaction(func() (err error) { + if ID == 0 { + return ReminderErrInvalidID() + } + + if r, err = svc.FindByID(ctx, ID); err != nil { + return ReminderErrNonexistent() + } + + raProps.setReminder(r) + + return svc.reminder.Delete(ID) + }) + + return svc.recordAction(ctx, raProps, ReminderActionDelete, err) } diff --git a/system/service/reminder_actions.gen.go b/system/service/reminder_actions.gen.go new file mode 100644 index 000000000..029d7018f --- /dev/null +++ b/system/service/reminder_actions.gen.go @@ -0,0 +1,682 @@ +package service + +// This file is auto-generated from system/service/reminder_actions.yaml +// + +import ( + "context" + "errors" + "fmt" + "strings" + "time" + + "github.com/cortezaproject/corteza-server/pkg/actionlog" + "github.com/cortezaproject/corteza-server/system/types" +) + +type ( + reminderActionProps struct { + reminder *types.Reminder + new *types.Reminder + updated *types.Reminder + filter *types.ReminderFilter + } + + reminderAction struct { + timestamp time.Time + resource string + action string + log string + severity actionlog.Severity + + // prefix for error when action fails + errorMessage string + + props *reminderActionProps + } + + reminderError struct { + timestamp time.Time + error string + resource string + action string + message string + log string + severity actionlog.Severity + + wrap error + + props *reminderActionProps + } +) + +// ********************************************************************************************************************* +// ********************************************************************************************************************* +// Props methods +// setReminder updates reminderActionProps's reminder +// +// Allows method chaining +// +// This function is auto-generated. +// +func (p *reminderActionProps) setReminder(reminder *types.Reminder) *reminderActionProps { + p.reminder = reminder + return p +} + +// setNew updates reminderActionProps's new +// +// Allows method chaining +// +// This function is auto-generated. +// +func (p *reminderActionProps) setNew(new *types.Reminder) *reminderActionProps { + p.new = new + return p +} + +// setUpdated updates reminderActionProps's updated +// +// Allows method chaining +// +// This function is auto-generated. +// +func (p *reminderActionProps) setUpdated(updated *types.Reminder) *reminderActionProps { + p.updated = updated + return p +} + +// setFilter updates reminderActionProps's filter +// +// Allows method chaining +// +// This function is auto-generated. +// +func (p *reminderActionProps) setFilter(filter *types.ReminderFilter) *reminderActionProps { + p.filter = filter + return p +} + +// serialize converts reminderActionProps to actionlog.Meta +// +// This function is auto-generated. +// +func (p reminderActionProps) serialize() actionlog.Meta { + var ( + m = make(actionlog.Meta) + str = func(i interface{}) string { return fmt.Sprintf("%v", i) } + ) + + // avoiding declared but not used + _ = str + + if p.reminder != nil { + m["reminder.resource"] = str(p.reminder.Resource) + m["reminder.ID"] = str(p.reminder.ID) + m["reminder.assignedTo"] = str(p.reminder.AssignedTo) + m["reminder.assignedBy"] = str(p.reminder.AssignedBy) + m["reminder.remindAt"] = str(p.reminder.RemindAt) + } + if p.new != nil { + m["new.resource"] = str(p.new.Resource) + m["new.ID"] = str(p.new.ID) + m["new.assignedTo"] = str(p.new.AssignedTo) + m["new.assignedBy"] = str(p.new.AssignedBy) + m["new.remindAt"] = str(p.new.RemindAt) + } + if p.updated != nil { + m["updated.resource"] = str(p.updated.Resource) + m["updated.ID"] = str(p.updated.ID) + m["updated.assignedTo"] = str(p.updated.AssignedTo) + m["updated.assignedBy"] = str(p.updated.AssignedBy) + m["updated.remindAt"] = str(p.updated.RemindAt) + } + if p.filter != nil { + m["filter.resource"] = str(p.filter.Resource) + m["filter.reminderID"] = str(p.filter.ReminderID) + m["filter.assignedTo"] = str(p.filter.AssignedTo) + m["filter.scheduledFrom"] = str(p.filter.ScheduledFrom) + m["filter.scheduledUntil"] = str(p.filter.ScheduledUntil) + m["filter.excludeDismissed"] = str(p.filter.ExcludeDismissed) + m["filter.scheduledOnly"] = str(p.filter.ScheduledOnly) + m["filter.sort"] = str(p.filter.Sort) + } + + return m +} + +// tr translates string and replaces meta value placeholder with values +// +// This function is auto-generated. +// +func (p reminderActionProps) tr(in string, err error) string { + var pairs = []string{"{err}"} + + if err != nil { + for { + // Unwrap errors + ue := errors.Unwrap(err) + if ue == nil { + break + } + + err = ue + } + + pairs = append(pairs, err.Error()) + } else { + pairs = append(pairs, "nil") + } + + if p.reminder != nil { + pairs = append(pairs, "{reminder}", fmt.Sprintf("%v", p.reminder.Resource)) + pairs = append(pairs, "{reminder.resource}", fmt.Sprintf("%v", p.reminder.Resource)) + pairs = append(pairs, "{reminder.ID}", fmt.Sprintf("%v", p.reminder.ID)) + pairs = append(pairs, "{reminder.assignedTo}", fmt.Sprintf("%v", p.reminder.AssignedTo)) + pairs = append(pairs, "{reminder.assignedBy}", fmt.Sprintf("%v", p.reminder.AssignedBy)) + pairs = append(pairs, "{reminder.remindAt}", fmt.Sprintf("%v", p.reminder.RemindAt)) + } + + if p.new != nil { + pairs = append(pairs, "{new}", fmt.Sprintf("%v", p.new.Resource)) + pairs = append(pairs, "{new.resource}", fmt.Sprintf("%v", p.new.Resource)) + pairs = append(pairs, "{new.ID}", fmt.Sprintf("%v", p.new.ID)) + pairs = append(pairs, "{new.assignedTo}", fmt.Sprintf("%v", p.new.AssignedTo)) + pairs = append(pairs, "{new.assignedBy}", fmt.Sprintf("%v", p.new.AssignedBy)) + pairs = append(pairs, "{new.remindAt}", fmt.Sprintf("%v", p.new.RemindAt)) + } + + if p.updated != nil { + pairs = append(pairs, "{updated}", fmt.Sprintf("%v", p.updated.Resource)) + pairs = append(pairs, "{updated.resource}", fmt.Sprintf("%v", p.updated.Resource)) + pairs = append(pairs, "{updated.ID}", fmt.Sprintf("%v", p.updated.ID)) + pairs = append(pairs, "{updated.assignedTo}", fmt.Sprintf("%v", p.updated.AssignedTo)) + pairs = append(pairs, "{updated.assignedBy}", fmt.Sprintf("%v", p.updated.AssignedBy)) + pairs = append(pairs, "{updated.remindAt}", fmt.Sprintf("%v", p.updated.RemindAt)) + } + + if p.filter != nil { + pairs = append(pairs, "{filter}", fmt.Sprintf("%v", p.filter.Resource)) + pairs = append(pairs, "{filter.resource}", fmt.Sprintf("%v", p.filter.Resource)) + pairs = append(pairs, "{filter.reminderID}", fmt.Sprintf("%v", p.filter.ReminderID)) + pairs = append(pairs, "{filter.assignedTo}", fmt.Sprintf("%v", p.filter.AssignedTo)) + pairs = append(pairs, "{filter.scheduledFrom}", fmt.Sprintf("%v", p.filter.ScheduledFrom)) + pairs = append(pairs, "{filter.scheduledUntil}", fmt.Sprintf("%v", p.filter.ScheduledUntil)) + pairs = append(pairs, "{filter.excludeDismissed}", fmt.Sprintf("%v", p.filter.ExcludeDismissed)) + pairs = append(pairs, "{filter.scheduledOnly}", fmt.Sprintf("%v", p.filter.ScheduledOnly)) + pairs = append(pairs, "{filter.sort}", fmt.Sprintf("%v", p.filter.Sort)) + } + return strings.NewReplacer(pairs...).Replace(in) +} + +// ********************************************************************************************************************* +// ********************************************************************************************************************* +// Action methods + +// String returns loggable description as string +// +// This function is auto-generated. +// +func (a *reminderAction) String() string { + var props = &reminderActionProps{} + + if a.props != nil { + props = a.props + } + + return props.tr(a.log, nil) +} + +func (e *reminderAction) LoggableAction() *actionlog.Action { + return &actionlog.Action{ + Timestamp: e.timestamp, + Resource: e.resource, + Action: e.action, + Severity: e.severity, + Description: e.String(), + Meta: e.props.serialize(), + } +} + +// ********************************************************************************************************************* +// ********************************************************************************************************************* +// Error methods + +// String returns loggable description as string +// +// It falls back to message if log is not set +// +// This function is auto-generated. +// +func (e *reminderError) String() string { + var props = &reminderActionProps{} + + if e.props != nil { + props = e.props + } + + if e.wrap != nil && !strings.Contains(e.log, "{err}") { + // Suffix error log with {err} to ensure + // we log the cause for this error + e.log += ": {err}" + } + + return props.tr(e.log, e.wrap) +} + +// Error satisfies +// +// This function is auto-generated. +// +func (e *reminderError) Error() string { + var props = &reminderActionProps{} + + if e.props != nil { + props = e.props + } + + return props.tr(e.message, e.wrap) +} + +// Is fn for error equality check +// +// This function is auto-generated. +// +func (e *reminderError) Is(Resource error) bool { + t, ok := Resource.(*reminderError) + if !ok { + return false + } + + return t.resource == e.resource && t.error == e.error +} + +// Wrap wraps reminderError around another error +// +// This function is auto-generated. +// +func (e *reminderError) Wrap(err error) *reminderError { + e.wrap = err + return e +} + +// Unwrap returns wrapped error +// +// This function is auto-generated. +// +func (e *reminderError) Unwrap() error { + return e.wrap +} + +func (e *reminderError) LoggableAction() *actionlog.Action { + return &actionlog.Action{ + Timestamp: e.timestamp, + Resource: e.resource, + Action: e.action, + Severity: e.severity, + Description: e.String(), + Error: e.Error(), + Meta: e.props.serialize(), + } +} + +// ********************************************************************************************************************* +// ********************************************************************************************************************* +// Action constructors + +// ReminderActionSearch returns "system:reminder.search" error +// +// This function is auto-generated. +// +func ReminderActionSearch(props ...*reminderActionProps) *reminderAction { + a := &reminderAction{ + timestamp: time.Now(), + resource: "system:reminder", + action: "search", + log: "searched for reminders", + severity: actionlog.Info, + } + + if len(props) > 0 { + a.props = props[0] + } + + return a +} + +// ReminderActionLookup returns "system:reminder.lookup" error +// +// This function is auto-generated. +// +func ReminderActionLookup(props ...*reminderActionProps) *reminderAction { + a := &reminderAction{ + timestamp: time.Now(), + resource: "system:reminder", + action: "lookup", + log: "looked-up for a {reminder}", + severity: actionlog.Info, + } + + if len(props) > 0 { + a.props = props[0] + } + + return a +} + +// ReminderActionCreate returns "system:reminder.create" error +// +// This function is auto-generated. +// +func ReminderActionCreate(props ...*reminderActionProps) *reminderAction { + a := &reminderAction{ + timestamp: time.Now(), + resource: "system:reminder", + action: "create", + log: "created {reminder}", + severity: actionlog.Info, + } + + if len(props) > 0 { + a.props = props[0] + } + + return a +} + +// ReminderActionUpdate returns "system:reminder.update" error +// +// This function is auto-generated. +// +func ReminderActionUpdate(props ...*reminderActionProps) *reminderAction { + a := &reminderAction{ + timestamp: time.Now(), + resource: "system:reminder", + action: "update", + log: "updated {reminder}", + severity: actionlog.Info, + } + + if len(props) > 0 { + a.props = props[0] + } + + return a +} + +// ReminderActionDelete returns "system:reminder.delete" error +// +// This function is auto-generated. +// +func ReminderActionDelete(props ...*reminderActionProps) *reminderAction { + a := &reminderAction{ + timestamp: time.Now(), + resource: "system:reminder", + action: "delete", + log: "deleted {reminder}", + severity: actionlog.Info, + } + + if len(props) > 0 { + a.props = props[0] + } + + return a +} + +// ReminderActionDismiss returns "system:reminder.dismiss" error +// +// This function is auto-generated. +// +func ReminderActionDismiss(props ...*reminderActionProps) *reminderAction { + a := &reminderAction{ + timestamp: time.Now(), + resource: "system:reminder", + action: "dismiss", + log: "deleted {reminder}", + severity: actionlog.Info, + } + + if len(props) > 0 { + a.props = props[0] + } + + return a +} + +// ReminderActionSnooze returns "system:reminder.snooze" error +// +// This function is auto-generated. +// +func ReminderActionSnooze(props ...*reminderActionProps) *reminderAction { + a := &reminderAction{ + timestamp: time.Now(), + resource: "system:reminder", + action: "snooze", + log: "deleted {reminder}", + severity: actionlog.Info, + } + + if len(props) > 0 { + a.props = props[0] + } + + return a +} + +// ********************************************************************************************************************* +// ********************************************************************************************************************* +// Error constructors + +// ReminderErrGeneric returns "system:reminder.generic" audit event as actionlog.Error +// +// +// This function is auto-generated. +// +func ReminderErrGeneric(props ...*reminderActionProps) *reminderError { + var e = &reminderError{ + timestamp: time.Now(), + resource: "system:reminder", + error: "generic", + action: "error", + message: "failed to complete request due to internal error", + log: "{err}", + severity: actionlog.Error, + props: func() *reminderActionProps { + if len(props) > 0 { + return props[0] + } + return nil + }(), + } + + if len(props) > 0 { + e.props = props[0] + } + + return e + +} + +// ReminderErrNonexistent returns "system:reminder.nonexistent" audit event as actionlog.Warning +// +// +// This function is auto-generated. +// +func ReminderErrNonexistent(props ...*reminderActionProps) *reminderError { + var e = &reminderError{ + timestamp: time.Now(), + resource: "system:reminder", + error: "nonexistent", + action: "error", + message: "reminder does not exist", + log: "reminder does not exist", + severity: actionlog.Warning, + props: func() *reminderActionProps { + if len(props) > 0 { + return props[0] + } + return nil + }(), + } + + if len(props) > 0 { + e.props = props[0] + } + + return e + +} + +// ReminderErrInvalidID returns "system:reminder.invalidID" audit event as actionlog.Warning +// +// +// This function is auto-generated. +// +func ReminderErrInvalidID(props ...*reminderActionProps) *reminderError { + var e = &reminderError{ + timestamp: time.Now(), + resource: "system:reminder", + error: "invalidID", + action: "error", + message: "invalid ID", + log: "invalid ID", + severity: actionlog.Warning, + props: func() *reminderActionProps { + if len(props) > 0 { + return props[0] + } + return nil + }(), + } + + if len(props) > 0 { + e.props = props[0] + } + + return e + +} + +// ReminderErrNotAllowedToAssign returns "system:reminder.notAllowedToAssign" audit event as actionlog.Error +// +// +// This function is auto-generated. +// +func ReminderErrNotAllowedToAssign(props ...*reminderActionProps) *reminderError { + var e = &reminderError{ + timestamp: time.Now(), + resource: "system:reminder", + error: "notAllowedToAssign", + action: "error", + message: "not allowed to assign reminders to other users", + log: "not allowed to assign reminders to other users", + severity: actionlog.Error, + props: func() *reminderActionProps { + if len(props) > 0 { + return props[0] + } + return nil + }(), + } + + if len(props) > 0 { + e.props = props[0] + } + + return e + +} + +// ********************************************************************************************************************* +// ********************************************************************************************************************* + +// recordAction is a service helper function wraps function that can return error +// +// context is used to enrich audit log entry with current user info, request ID, IP address... +// props are collected action/error properties +// action (optional) fn will be used to construct reminderAction struct from given props (and error) +// err is any error that occurred while action was happening +// +// Action has success and fail (error) state: +// - when recorded without an error (4th param), action is recorded as successful. +// - when an additional error is given (4th param), action is used to wrap +// the additional error +// +// This function is auto-generated. +// +func (svc reminder) recordAction(ctx context.Context, props *reminderActionProps, action func(...*reminderActionProps) *reminderAction, err error) error { + var ( + ok bool + + // Return error + retError *reminderError + + // Recorder error + recError *reminderError + ) + + if err != nil { + if retError, ok = err.(*reminderError); !ok { + // got non-reminder error, wrap it with ReminderErrGeneric + retError = ReminderErrGeneric(props).Wrap(err) + + // copy action to returning and recording error + retError.action = action().action + + // we'll use ReminderErrGeneric for recording too + // because it can hold more info + recError = retError + } else if retError != nil { + // copy action to returning and recording error + retError.action = action().action + // start with copy of return error for recording + // this will be updated with tha root cause as we try and + // unwrap the error + recError = retError + + // find the original recError for this error + // for the purpose of logging + var unwrappedError error = retError + for { + if unwrappedError = errors.Unwrap(unwrappedError); unwrappedError == nil { + // nothing wrapped + break + } + + // update recError ONLY of wrapped error is of type reminderError + if unwrappedSinkError, ok := unwrappedError.(*reminderError); ok { + recError = unwrappedSinkError + } + } + + if retError.props == nil { + // set props on returning error if empty + retError.props = props + } + + if recError.props == nil { + // set props on recording error if empty + recError.props = props + } + } + } + + if svc.actionlog != nil { + if retError != nil { + // failed action, log error + svc.actionlog.Record(ctx, recError) + } else if action != nil { + // successful + svc.actionlog.Record(ctx, action(props)) + } + } + + if err == nil { + // retError not an interface and that WILL (!!) cause issues + // with nil check (== nil) when it is not explicitly returned + return nil + } + + return retError +} diff --git a/system/service/reminder_actions.yaml b/system/service/reminder_actions.yaml new file mode 100644 index 000000000..5dce35f0f --- /dev/null +++ b/system/service/reminder_actions.yaml @@ -0,0 +1,63 @@ +# List of loggable service actions + +resource: system:reminder +service: reminder + +# Default sensitivity for actions +defaultActionSeverity: info + +# default severity for errors +defaultErrorSeverity: error + +import: + - github.com/cortezaproject/corteza-server/system/types + +props: + - name: reminder + type: "*types.Reminder" + fields: [ resource, ID, assignedTo, assignedBy, remindAt ] + - name: new + type: "*types.Reminder" + fields: [ resource, ID, assignedTo, assignedBy, remindAt ] + - name: updated + type: "*types.Reminder" + fields: [ resource, ID, assignedTo, assignedBy, remindAt ] + - name: filter + type: "*types.ReminderFilter" + fields: [ resource, reminderID, assignedTo, scheduledFrom, scheduledUntil, excludeDismissed, scheduledOnly, sort ] + +actions: + - action: search + log: "searched for reminders" + severity: info + + - action: lookup + log: "looked-up for a {reminder}" + severity: info + + - action: create + log: "created {reminder}" + + - action: update + log: "updated {reminder}" + + - action: delete + log: "deleted {reminder}" + + - action: dismiss + log: "deleted {reminder}" + + - action: snooze + log: "deleted {reminder}" + +errors: + - error: nonexistent + message: "reminder does not exist" + severity: warning + + - error: invalidID + message: "invalid ID" + severity: warning + + - error: notAllowedToAssign + message: "not allowed to assign reminders to other users" diff --git a/tests/system/reminder_test.go b/tests/system/reminder_test.go index a88eb74d7..2271d8c9d 100644 --- a/tests/system/reminder_test.go +++ b/tests/system/reminder_test.go @@ -212,7 +212,7 @@ func TestReminderCreateSetAssigneeForbidden(t *testing.T) { JSON(`{"resource":"test_rf_as_namespace:*","assignedTo":"222","payload":{}}`). Expect(t). Status(http.StatusOK). - Assert(helpers.AssertError("system.service.NoReminderAssignPermissions")). + Assert(helpers.AssertError("not allowed to assign reminders to other users")). End() } @@ -278,7 +278,7 @@ func TestReminderUpdateSetAssigneeForbidden(t *testing.T) { JSON(`{"resource":"test_updf_as_namespace:*","assignedTo":"111","payload":{}}`). Expect(t). Status(http.StatusOK). - Assert(helpers.AssertError("system.service.NoReminderAssignPermissions")). + Assert(helpers.AssertError("not allowed to assign reminders to other users")). End() }