diff --git a/system/service/attachment.go b/system/service/attachment.go index 59b2f916e..235469ee0 100644 --- a/system/service/attachment.go +++ b/system/service/attachment.go @@ -3,7 +3,6 @@ package service import ( "bytes" "context" - "github.com/cortezaproject/corteza-server/pkg/settings" "image" "image/gif" "io" @@ -11,15 +10,15 @@ import ( "path" "strings" + "github.com/cortezaproject/corteza-server/pkg/actionlog" + "github.com/cortezaproject/corteza-server/pkg/settings" + "github.com/disintegration/imaging" "github.com/edwvee/exiffix" "github.com/pkg/errors" "github.com/titpetric/factory" - "go.uber.org/zap" - "go.uber.org/zap/zapcore" intAuth "github.com/cortezaproject/corteza-server/pkg/auth" - "github.com/cortezaproject/corteza-server/pkg/logger" "github.com/cortezaproject/corteza-server/pkg/store" "github.com/cortezaproject/corteza-server/system/repository" "github.com/cortezaproject/corteza-server/system/types" @@ -32,9 +31,10 @@ const ( type ( attachment struct { - db *factory.DB - ctx context.Context - logger *zap.Logger + db *factory.DB + ctx context.Context + + actionlog actionlog.Recorder store store.Store @@ -52,20 +52,21 @@ type ( AttachmentService interface { With(ctx context.Context) AttachmentService - FindByID(attachmentID uint64) (*types.Attachment, error) + FindByID(ID uint64) (*types.Attachment, error) Find(filter types.AttachmentFilter) (types.AttachmentSet, types.AttachmentFilter, error) CreateSettingsAttachment(name string, size int64, fh io.ReadSeeker, labels map[string]string) (*types.Attachment, error) OpenOriginal(att *types.Attachment) (io.ReadSeeker, error) OpenPreview(att *types.Attachment) (io.ReadSeeker, error) - DeleteByID(attachmentID uint64) error + DeleteByID(ID uint64) error } ) func Attachment(store store.Store) AttachmentService { return (&attachment{ - logger: DefaultLogger.Named("attachment"), - store: store, - ac: DefaultAccessControl, + store: store, + + actionlog: DefaultActionlog, + ac: DefaultAccessControl, settingsSvc: DefaultSettings, }).With(context.Background()) @@ -74,11 +75,11 @@ func Attachment(store store.Store) AttachmentService { func (svc attachment) With(ctx context.Context) AttachmentService { db := repository.DB(ctx) return &attachment{ - db: db, - ctx: ctx, - logger: svc.logger, + db: db, + ctx: ctx, - ac: svc.ac, + actionlog: svc.actionlog, + ac: svc.ac, settingsSvc: svc.settingsSvc, @@ -88,21 +89,59 @@ func (svc attachment) With(ctx context.Context) AttachmentService { } } -// log() returns zap's logger with requestID from current context and fields. -func (svc attachment) log(fields ...zapcore.Field) *zap.Logger { - return logger.AddRequestID(svc.ctx, svc.logger).With(fields...) +func (svc attachment) FindByID(ID uint64) (att *types.Attachment, err error) { + var ( + aaProps = &attachmentActionProps{} + ) + + err = svc.db.Transaction(func() (err error) { + if ID == 0 { + return AttachmentErrInvalidID() + } + + if att, err = svc.attachment.FindByID(ID); err != nil { + return err + } + + aaProps.setAttachment(att) + return nil + }) + + return att, svc.recordAction(svc.ctx, aaProps, AttachmentActionLookup, err) } -func (svc attachment) FindByID(attachmentID uint64) (*types.Attachment, error) { - return svc.attachment.FindByID(attachmentID) +func (svc attachment) DeleteByID(ID uint64) (err error) { + var ( + aaProps = &attachmentActionProps{attachment: &types.Attachment{ID: ID}} + ) + + err = svc.db.Transaction(func() (err error) { + if ID == 0 { + return AttachmentErrInvalidID() + } + + if aaProps.attachment, err = svc.attachment.FindByID(ID); err != nil { + return err + } + + return svc.attachment.DeleteByID(ID) + }) + + return svc.recordAction(svc.ctx, aaProps, AttachmentActionDelete, err) } -func (svc attachment) DeleteByID(attachmentID uint64) error { - return svc.attachment.DeleteByID(attachmentID) -} +func (svc attachment) Find(filter types.AttachmentFilter) (aa types.AttachmentSet, f types.AttachmentFilter, err error) { + var ( + aaProps = &attachmentActionProps{filter: &filter} + ) + + err = svc.db.Transaction(func() (err error) { + aa, f, err = svc.attachment.Find(filter) + return err + }) + + return aa, f, svc.recordAction(svc.ctx, aaProps, AttachmentActionSearch, err) -func (svc attachment) Find(filter types.AttachmentFilter) (types.AttachmentSet, types.AttachmentFilter, error) { - return svc.attachment.Find(filter) } func (svc attachment) OpenOriginal(att *types.Attachment) (io.ReadSeeker, error) { @@ -123,71 +162,76 @@ func (svc attachment) OpenPreview(att *types.Attachment) (io.ReadSeeker, error) func (svc attachment) CreateSettingsAttachment(name string, size int64, fh io.ReadSeeker, labels map[string]string) (att *types.Attachment, err error) { var ( - currentUserID uint64 = intAuth.GetIdentityFromContext(svc.ctx).Identity() + aaProps = &attachmentActionProps{} + currentUserID = intAuth.GetIdentityFromContext(svc.ctx).Identity() ) - if !svc.ac.CanManageSettings(svc.ctx) { - return nil, ErrNoUpdatePermissions.withStack() - } + err = svc.db.Transaction(func() (err error) { + if !svc.ac.CanManageSettings(svc.ctx) { + return AttachmentErrNotAllowedToCreate() + } - att = &types.Attachment{ - ID: factory.Sonyflake.NextID(), - OwnerID: currentUserID, - Name: strings.TrimSpace(name), - Kind: types.AttachmentKindSettings, - } + att = &types.Attachment{ + ID: factory.Sonyflake.NextID(), + OwnerID: currentUserID, + Name: strings.TrimSpace(name), + Kind: types.AttachmentKindSettings, + } - if labels != nil { - att.Meta.Labels = labels - } + aaProps.setAttachment(att) - if err = svc.create(name, size, fh, att); err != nil { - return nil, err - } + if labels != nil { + att.Meta.Labels = labels + } - return att, err + if err = svc.create(name, size, fh, att); err != nil { + return err + } + + return err + }) + + return att, svc.recordAction(svc.ctx, aaProps, AttachmentActionCreate, err) } func (svc attachment) create(name string, size int64, fh io.ReadSeeker, att *types.Attachment) (err error) { + var ( + aaProps = &attachmentActionProps{} + ) + if svc.store == nil { - return errors.New("Can not create attachment: store handler not set") + return errors.New("can not create attachment: store handler not set") } - log := svc.log( - zap.String("name", att.Name), - zap.Int64("size", att.Meta.Original.Size), - ) + aaProps.setName(name) + aaProps.setSize(size) // Extract extension but make sure path.Ext is not confused by any leading/trailing dots att.Meta.Original.Extension = strings.Trim(path.Ext(strings.Trim(name, ".")), ".") att.Meta.Original.Size = size if att.Meta.Original.Mimetype, err = svc.extractMimetype(fh); err != nil { - log.Error("could not extract mime-type", zap.Error(err)) - return + return AttachmentErrFailedToExtractMimeType(aaProps).Wrap(err) } att.Url = svc.store.Original(att.ID, att.Meta.Original.Extension) - log = log.With(zap.String("url", att.Url)) + aaProps.setUrl(att.Url) if err = svc.store.Save(att.Url, fh); err != nil { - log.Error("could not store file", zap.Error(err)) - return + return AttachmentErrFailedToStoreFile(aaProps).Wrap(err) } // Process image: extract width, height, make preview err = svc.processImage(fh, att) if err != nil { - log.Error("could not process image", zap.Error(err)) + return AttachmentErrFailedToProcessImage(aaProps).Wrap(err) } - return svc.db.Transaction(func() (err error) { - if att, err = svc.attachment.Create(att); err != nil { - return - } + if att, err = svc.attachment.Create(att); err != nil { + return + } - return nil - }) + return nil } func (svc attachment) extractMimetype(file io.ReadSeeker) (mimetype string, err error) { @@ -235,7 +279,7 @@ func (svc attachment) processImage(original io.ReadSeeker, att *types.Attachment } if format, err = imaging.FormatFromExtension(att.Meta.Original.Extension); err != nil { - return errors.Wrapf(err, "Could not get format from extension '%s'", att.Meta.Original.Extension) + return errors.Wrapf(err, "could not get format from extension '%s'", att.Meta.Original.Extension) } previewFormat = format @@ -256,7 +300,7 @@ func (svc attachment) processImage(original io.ReadSeeker, att *types.Attachment // Use first image for the preview preview = cfg.Image[0] } else { - return errors.Wrapf(err, "Could not decode gif config") + return errors.Wrapf(err, "could not decode gif config") } } else { @@ -271,7 +315,7 @@ func (svc attachment) processImage(original io.ReadSeeker, att *types.Attachment // other cases are handled here if preview == nil { if preview, err = imaging.Decode(original); err != nil { - return errors.Wrapf(err, "Could not decode original image") + return errors.Wrapf(err, "could not decode original image") } } @@ -304,5 +348,3 @@ func (svc attachment) processImage(original io.ReadSeeker, att *types.Attachment return svc.store.Save(att.PreviewUrl, buf) } - -var _ AttachmentService = &attachment{} diff --git a/system/service/attachment_actions.gen.go b/system/service/attachment_actions.gen.go new file mode 100644 index 000000000..1bc91f28a --- /dev/null +++ b/system/service/attachment_actions.gen.go @@ -0,0 +1,736 @@ +package service + +// This file is auto-generated from system/service/attachment_actions.yaml +// + +import ( + "context" + "errors" + "fmt" + "strings" + "time" + + "github.com/cortezaproject/corteza-server/pkg/actionlog" + "github.com/cortezaproject/corteza-server/system/types" +) + +type ( + attachmentActionProps struct { + size int64 + name string + mimetype string + url string + attachment *types.Attachment + filter *types.AttachmentFilter + } + + attachmentAction struct { + timestamp time.Time + resource string + action string + log string + severity actionlog.Severity + + // prefix for error when action fails + errorMessage string + + props *attachmentActionProps + } + + attachmentError struct { + timestamp time.Time + error string + resource string + action string + message string + log string + severity actionlog.Severity + + wrap error + + props *attachmentActionProps + } +) + +// ********************************************************************************************************************* +// ********************************************************************************************************************* +// Props methods +// setSize updates attachmentActionProps's size +// +// Allows method chaining +// +// This function is auto-generated. +// +func (p *attachmentActionProps) setSize(size int64) *attachmentActionProps { + p.size = size + return p +} + +// setName updates attachmentActionProps's name +// +// Allows method chaining +// +// This function is auto-generated. +// +func (p *attachmentActionProps) setName(name string) *attachmentActionProps { + p.name = name + return p +} + +// setMimetype updates attachmentActionProps's mimetype +// +// Allows method chaining +// +// This function is auto-generated. +// +func (p *attachmentActionProps) setMimetype(mimetype string) *attachmentActionProps { + p.mimetype = mimetype + return p +} + +// setUrl updates attachmentActionProps's url +// +// Allows method chaining +// +// This function is auto-generated. +// +func (p *attachmentActionProps) setUrl(url string) *attachmentActionProps { + p.url = url + return p +} + +// setAttachment updates attachmentActionProps's attachment +// +// Allows method chaining +// +// This function is auto-generated. +// +func (p *attachmentActionProps) setAttachment(attachment *types.Attachment) *attachmentActionProps { + p.attachment = attachment + return p +} + +// setFilter updates attachmentActionProps's filter +// +// Allows method chaining +// +// This function is auto-generated. +// +func (p *attachmentActionProps) setFilter(filter *types.AttachmentFilter) *attachmentActionProps { + p.filter = filter + return p +} + +// serialize converts attachmentActionProps to actionlog.Meta +// +// This function is auto-generated. +// +func (p attachmentActionProps) serialize() actionlog.Meta { + var ( + m = make(actionlog.Meta) + str = func(i interface{}) string { return fmt.Sprintf("%v", i) } + ) + + // avoiding declared but not used + _ = str + + m["size"] = str(p.size) + m["name"] = str(p.name) + m["mimetype"] = str(p.mimetype) + m["url"] = str(p.url) + if p.attachment != nil { + m["attachment.name"] = str(p.attachment.Name) + m["attachment.kind"] = str(p.attachment.Kind) + m["attachment.url"] = str(p.attachment.Url) + m["attachment.previewUrl"] = str(p.attachment.PreviewUrl) + m["attachment.meta"] = str(p.attachment.Meta) + m["attachment.ownerID"] = str(p.attachment.OwnerID) + m["attachment.ID"] = str(p.attachment.ID) + } + if p.filter != nil { + m["filter.filter"] = str(p.filter.Filter) + m["filter.kind"] = str(p.filter.Kind) + 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 attachmentActionProps) 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") + } + pairs = append(pairs, "{size}", fmt.Sprintf("%v", p.size)) + pairs = append(pairs, "{name}", fmt.Sprintf("%v", p.name)) + pairs = append(pairs, "{mimetype}", fmt.Sprintf("%v", p.mimetype)) + pairs = append(pairs, "{url}", fmt.Sprintf("%v", p.url)) + + if p.attachment != nil { + pairs = append(pairs, "{attachment}", fmt.Sprintf("%v", p.attachment.Name)) + pairs = append(pairs, "{attachment.name}", fmt.Sprintf("%v", p.attachment.Name)) + pairs = append(pairs, "{attachment.kind}", fmt.Sprintf("%v", p.attachment.Kind)) + pairs = append(pairs, "{attachment.url}", fmt.Sprintf("%v", p.attachment.Url)) + pairs = append(pairs, "{attachment.previewUrl}", fmt.Sprintf("%v", p.attachment.PreviewUrl)) + pairs = append(pairs, "{attachment.meta}", fmt.Sprintf("%v", p.attachment.Meta)) + pairs = append(pairs, "{attachment.ownerID}", fmt.Sprintf("%v", p.attachment.OwnerID)) + pairs = append(pairs, "{attachment.ID}", fmt.Sprintf("%v", p.attachment.ID)) + } + + if p.filter != nil { + pairs = append(pairs, "{filter}", fmt.Sprintf("%v", p.filter.Filter)) + pairs = append(pairs, "{filter.filter}", fmt.Sprintf("%v", p.filter.Filter)) + pairs = append(pairs, "{filter.kind}", fmt.Sprintf("%v", p.filter.Kind)) + 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 *attachmentAction) String() string { + var props = &attachmentActionProps{} + + if a.props != nil { + props = a.props + } + + return props.tr(a.log, nil) +} + +func (e *attachmentAction) 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 *attachmentError) String() string { + var props = &attachmentActionProps{} + + 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 *attachmentError) Error() string { + var props = &attachmentActionProps{} + + 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 *attachmentError) Is(Resource error) bool { + t, ok := Resource.(*attachmentError) + if !ok { + return false + } + + return t.resource == e.resource && t.error == e.error +} + +// Wrap wraps attachmentError around another error +// +// This function is auto-generated. +// +func (e *attachmentError) Wrap(err error) *attachmentError { + e.wrap = err + return e +} + +// Unwrap returns wrapped error +// +// This function is auto-generated. +// +func (e *attachmentError) Unwrap() error { + return e.wrap +} + +func (e *attachmentError) 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 + +// AttachmentActionSearch returns "system:attachment.search" error +// +// This function is auto-generated. +// +func AttachmentActionSearch(props ...*attachmentActionProps) *attachmentAction { + a := &attachmentAction{ + timestamp: time.Now(), + resource: "system:attachment", + action: "search", + log: "searched for attachments", + severity: actionlog.Info, + } + + if len(props) > 0 { + a.props = props[0] + } + + return a +} + +// AttachmentActionLookup returns "system:attachment.lookup" error +// +// This function is auto-generated. +// +func AttachmentActionLookup(props ...*attachmentActionProps) *attachmentAction { + a := &attachmentAction{ + timestamp: time.Now(), + resource: "system:attachment", + action: "lookup", + log: "looked-up for a {attachment}", + severity: actionlog.Info, + } + + if len(props) > 0 { + a.props = props[0] + } + + return a +} + +// AttachmentActionCreate returns "system:attachment.create" error +// +// This function is auto-generated. +// +func AttachmentActionCreate(props ...*attachmentActionProps) *attachmentAction { + a := &attachmentAction{ + timestamp: time.Now(), + resource: "system:attachment", + action: "create", + log: "created {attachment}", + severity: actionlog.Notice, + } + + if len(props) > 0 { + a.props = props[0] + } + + return a +} + +// AttachmentActionDelete returns "system:attachment.delete" error +// +// This function is auto-generated. +// +func AttachmentActionDelete(props ...*attachmentActionProps) *attachmentAction { + a := &attachmentAction{ + timestamp: time.Now(), + resource: "system:attachment", + action: "delete", + log: "deleted {attachment}", + severity: actionlog.Notice, + } + + if len(props) > 0 { + a.props = props[0] + } + + return a +} + +// ********************************************************************************************************************* +// ********************************************************************************************************************* +// Error constructors + +// AttachmentErrGeneric returns "system:attachment.generic" audit event as actionlog.Error +// +// +// This function is auto-generated. +// +func AttachmentErrGeneric(props ...*attachmentActionProps) *attachmentError { + var e = &attachmentError{ + timestamp: time.Now(), + resource: "system:attachment", + error: "generic", + action: "error", + message: "failed to complete request due to internal error", + log: "{err}", + severity: actionlog.Error, + props: func() *attachmentActionProps { + if len(props) > 0 { + return props[0] + } + return nil + }(), + } + + if len(props) > 0 { + e.props = props[0] + } + + return e + +} + +// AttachmentErrNonexistent returns "system:attachment.nonexistent" audit event as actionlog.Warning +// +// +// This function is auto-generated. +// +func AttachmentErrNonexistent(props ...*attachmentActionProps) *attachmentError { + var e = &attachmentError{ + timestamp: time.Now(), + resource: "system:attachment", + error: "nonexistent", + action: "error", + message: "attachment does not exist", + log: "attachment does not exist", + severity: actionlog.Warning, + props: func() *attachmentActionProps { + if len(props) > 0 { + return props[0] + } + return nil + }(), + } + + if len(props) > 0 { + e.props = props[0] + } + + return e + +} + +// AttachmentErrInvalidID returns "system:attachment.invalidID" audit event as actionlog.Warning +// +// +// This function is auto-generated. +// +func AttachmentErrInvalidID(props ...*attachmentActionProps) *attachmentError { + var e = &attachmentError{ + timestamp: time.Now(), + resource: "system:attachment", + error: "invalidID", + action: "error", + message: "invalid ID", + log: "invalid ID", + severity: actionlog.Warning, + props: func() *attachmentActionProps { + if len(props) > 0 { + return props[0] + } + return nil + }(), + } + + if len(props) > 0 { + e.props = props[0] + } + + return e + +} + +// AttachmentErrNotAllowedToListAttachments returns "system:attachment.notAllowedToListAttachments" audit event as actionlog.Alert +// +// +// This function is auto-generated. +// +func AttachmentErrNotAllowedToListAttachments(props ...*attachmentActionProps) *attachmentError { + var e = &attachmentError{ + timestamp: time.Now(), + resource: "system:attachment", + error: "notAllowedToListAttachments", + action: "error", + message: "not allowed to list attachments", + log: "failed to list attachment; insufficient permissions", + severity: actionlog.Alert, + props: func() *attachmentActionProps { + if len(props) > 0 { + return props[0] + } + return nil + }(), + } + + if len(props) > 0 { + e.props = props[0] + } + + return e + +} + +// AttachmentErrNotAllowedToCreate returns "system:attachment.notAllowedToCreate" audit event as actionlog.Alert +// +// +// This function is auto-generated. +// +func AttachmentErrNotAllowedToCreate(props ...*attachmentActionProps) *attachmentError { + var e = &attachmentError{ + timestamp: time.Now(), + resource: "system:attachment", + error: "notAllowedToCreate", + action: "error", + message: "not allowed to create attachment", + log: "failed to create attachment; insufficient permissions", + severity: actionlog.Alert, + props: func() *attachmentActionProps { + if len(props) > 0 { + return props[0] + } + return nil + }(), + } + + if len(props) > 0 { + e.props = props[0] + } + + return e + +} + +// AttachmentErrFailedToExtractMimeType returns "system:attachment.failedToExtractMimeType" audit event as actionlog.Alert +// +// +// This function is auto-generated. +// +func AttachmentErrFailedToExtractMimeType(props ...*attachmentActionProps) *attachmentError { + var e = &attachmentError{ + timestamp: time.Now(), + resource: "system:attachment", + error: "failedToExtractMimeType", + action: "error", + message: "could not extract mime type", + log: "could not extract mime type", + severity: actionlog.Alert, + props: func() *attachmentActionProps { + if len(props) > 0 { + return props[0] + } + return nil + }(), + } + + if len(props) > 0 { + e.props = props[0] + } + + return e + +} + +// AttachmentErrFailedToStoreFile returns "system:attachment.failedToStoreFile" audit event as actionlog.Alert +// +// +// This function is auto-generated. +// +func AttachmentErrFailedToStoreFile(props ...*attachmentActionProps) *attachmentError { + var e = &attachmentError{ + timestamp: time.Now(), + resource: "system:attachment", + error: "failedToStoreFile", + action: "error", + message: "could not extract store file", + log: "could not extract store file", + severity: actionlog.Alert, + props: func() *attachmentActionProps { + if len(props) > 0 { + return props[0] + } + return nil + }(), + } + + if len(props) > 0 { + e.props = props[0] + } + + return e + +} + +// AttachmentErrFailedToProcessImage returns "system:attachment.failedToProcessImage" audit event as actionlog.Alert +// +// +// This function is auto-generated. +// +func AttachmentErrFailedToProcessImage(props ...*attachmentActionProps) *attachmentError { + var e = &attachmentError{ + timestamp: time.Now(), + resource: "system:attachment", + error: "failedToProcessImage", + action: "error", + message: "could not process image", + log: "could not process image", + severity: actionlog.Alert, + props: func() *attachmentActionProps { + 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 attachmentAction 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 attachment) recordAction(ctx context.Context, props *attachmentActionProps, action func(...*attachmentActionProps) *attachmentAction, err error) error { + var ( + ok bool + + // Return error + retError *attachmentError + + // Recorder error + recError *attachmentError + ) + + if err != nil { + if retError, ok = err.(*attachmentError); !ok { + // got non-attachment error, wrap it with AttachmentErrGeneric + retError = AttachmentErrGeneric(props).Wrap(err) + + // copy action to returning and recording error + retError.action = action().action + + // we'll use AttachmentErrGeneric 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 attachmentError + if unwrappedSinkError, ok := unwrappedError.(*attachmentError); 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/attachment_actions.yaml b/system/service/attachment_actions.yaml new file mode 100644 index 000000000..58f0a1b89 --- /dev/null +++ b/system/service/attachment_actions.yaml @@ -0,0 +1,67 @@ +# List of loggable service actions + +resource: system:attachment +service: attachment + +# Default sensitivity for actions +defaultActionSeverity: notice + +# default severity for errors +defaultErrorSeverity: alert + +import: + - github.com/cortezaproject/corteza-server/system/types + +props: + - name: size + type: int64 + - name: name + - name: mimetype + - name: url + - name: attachment + type: "*types.Attachment" + fields: [ name, kind, url, previewUrl, meta, ownerID, ID ] + - name: filter + type: "*types.AttachmentFilter" + fields: [ filter, kind, sort ] + +actions: + - action: search + log: "searched for attachments" + severity: info + + - action: lookup + log: "looked-up for a {attachment}" + severity: info + + - action: create + log: "created {attachment}" + + - action: delete + log: "deleted {attachment}" + +errors: + - error: nonexistent + message: "attachment does not exist" + severity: warning + + - error: invalidID + message: "invalid ID" + severity: warning + + - error: notAllowedToListAttachments + message: "not allowed to list attachments" + log: "failed to list attachment; insufficient permissions" + + - error: notAllowedToCreate + message: "not allowed to create attachment" + log: "failed to create attachment; insufficient permissions" + + - error: failedToExtractMimeType + message: "could not extract mime type" + + - error: failedToStoreFile + message: "could not extract store file" + + - error: failedToProcessImage + message: "could not process image" diff --git a/system/service/error.go b/system/service/error.go index d7fbd5fba..f06862499 100644 --- a/system/service/error.go +++ b/system/service/error.go @@ -9,7 +9,6 @@ type ( ) const ( - ErrNoUpdatePermissions serviceError = "NoUpdatePermissions" ErrNoReminderAssignPermissions serviceError = "NoReminderAssignPermissions" )