diff --git a/api/compose/spec.json b/api/compose/spec.json index add300e05..c34e4037d 100644 --- a/api/compose/spec.json +++ b/api/compose/spec.json @@ -926,6 +926,13 @@ } ] }, + "struct": [ + { + "imports": [ + "time" + ] + } + ], "apis": [ { "name": "list", @@ -939,6 +946,24 @@ "name": "moduleID", "required": false, "title": "Filter triggers by module" + }, + { + "type": "string", + "name": "query", + "required": false, + "title": "Search query" + }, + { + "name": "page", + "type": "uint", + "required": false, + "title": "Page number (0 based)" + }, + { + "name": "perPage", + "type": "uint", + "required": false, + "title": "Returned items per page (default 50)" } ] } @@ -979,6 +1004,12 @@ "name": "source", "required": false, "title": "Trigger source code" + }, + { + "type": "*time.Time", + "name": "updatedAt", + "required": false, + "title": "Last update (or creation) date" } ] } diff --git a/api/compose/spec/trigger.json b/api/compose/spec/trigger.json index fafe5c3a4..dad412d7a 100644 --- a/api/compose/spec/trigger.json +++ b/api/compose/spec/trigger.json @@ -2,7 +2,13 @@ "Title": "Triggers", "Description": "Compose Triggers", "Interface": "Trigger", - "Struct": null, + "Struct": [ + { + "imports": [ + "time" + ] + } + ], "Parameters": { "path": [ { @@ -29,6 +35,24 @@ "required": false, "title": "Filter triggers by module", "type": "uint64" + }, + { + "name": "query", + "required": false, + "title": "Search query", + "type": "string" + }, + { + "name": "page", + "required": false, + "title": "Page number (0 based)", + "type": "uint" + }, + { + "name": "perPage", + "required": false, + "title": "Returned items per page (default 50)", + "type": "uint" } ] } @@ -69,6 +93,12 @@ "required": false, "title": "Trigger source code", "type": "string" + }, + { + "name": "updatedAt", + "required": false, + "title": "Last update (or creation) date", + "type": "*time.Time" } ] } diff --git a/compose/internal/repository/chart.go b/compose/internal/repository/chart.go index 70d432dce..00dfcf63a 100644 --- a/compose/internal/repository/chart.go +++ b/compose/internal/repository/chart.go @@ -56,7 +56,6 @@ func (r chart) query() squirrel.SelectBuilder { Select(). From(r.table()). Where("deleted_at IS NULL") - } func (r chart) FindByID(namespaceID, chartID uint64) (*types.Chart, error) { diff --git a/compose/internal/repository/trigger.go b/compose/internal/repository/trigger.go index 27a1d4525..cf3588776 100644 --- a/compose/internal/repository/trigger.go +++ b/compose/internal/repository/trigger.go @@ -14,11 +14,11 @@ type ( TriggerRepository interface { With(ctx context.Context, db *factory.DB) TriggerRepository - FindByID(id uint64) (*types.Trigger, error) - Find(filter types.TriggerFilter) (types.TriggerSet, error) + FindByID(namespaceID, attachmentID uint64) (*types.Trigger, error) + Find(filter types.TriggerFilter) (set types.TriggerSet, f types.TriggerFilter, err error) Create(mod *types.Trigger) (*types.Trigger, error) Update(mod *types.Trigger) (*types.Trigger, error) - DeleteByID(id uint64) error + DeleteByID(namespaceID, attachmentID uint64) error } trigger struct { @@ -26,66 +26,100 @@ type ( } ) +const ( + ErrTriggerNotFound = repositoryError("TriggerNotFound") +) + func Trigger(ctx context.Context, db *factory.DB) TriggerRepository { return (&trigger{}).With(ctx, db) } -func (r *trigger) With(ctx context.Context, db *factory.DB) TriggerRepository { +func (r trigger) With(ctx context.Context, db *factory.DB) TriggerRepository { return &trigger{ repository: r.repository.With(ctx, db), } } -func (r *trigger) FindByID(id uint64) (*types.Trigger, error) { - mod := &types.Trigger{} - query := r.query().Where("id = ?", id) +func (r trigger) table() string { + return "compose_trigger" +} - if sql, args, err := query.ToSql(); err != nil { - return nil, err - } else { - return mod, r.db().Get(mod, sql, args...) +func (r trigger) columns() []string { + return []string{ + "id", "rel_namespace", "name", + "actions", "enabled", "source", "rel_module", + "created_at", "updated_at", "deleted_at", } } -func (r *trigger) Find(filter types.TriggerFilter) (mod types.TriggerSet, err error) { +func (r trigger) query() squirrel.SelectBuilder { + return squirrel. + Select(). + From(r.table()). + Where("deleted_at IS NULL") +} + +func (r trigger) FindByID(namespaceID, triggerID uint64) (*types.Trigger, error) { + var ( + query = r.query(). + Columns(r.columns()...). + Where("id = ?", triggerID) + + c = &types.Trigger{} + ) + + if namespaceID > 0 { + query = query.Where("rel_namespace = ?", namespaceID) + } + + return c, isFound(r.fetchOne(c, query), c.ID > 0, ErrTriggerNotFound) +} + +func (r trigger) Find(filter types.TriggerFilter) (set types.TriggerSet, f types.TriggerFilter, err error) { + f = filter + f.PerPage = normalizePerPage(f.PerPage, 5, 100, 50) + query := r.query() - if filter.ModuleID > 0 { - query = query.Where("rel_module = ?", filter.ModuleID) + if filter.NamespaceID > 0 { + query = query.Where("a.rel_namespace = ?", filter.NamespaceID) } - if sql, args, err := query.ToSql(); err != nil { - return nil, err - } else { - return mod, r.db().Select(&mod, sql, args...) + if f.Query != "" { + q := "%" + f.Query + "%" + query = query.Where("name like ?", q) } + + if f.Count, err = r.count(query); err != nil || f.Count == 0 { + return + } + + query = query. + Columns(r.columns()...). + OrderBy("id ASC") + + return set, f, r.fetchPaged(&set, query, f.Page, f.PerPage) } -func (r trigger) query() (query squirrel.SelectBuilder) { - query = squirrel.Select(). - Columns( - "id", "name", "actions", "enabled", "source", "rel_module", - "created_at", "updated_at", "deleted_at"). - From("compose_trigger"). - OrderBy("id DESC") - - return -} - -func (r *trigger) Create(mod *types.Trigger) (*types.Trigger, error) { +func (r trigger) Create(mod *types.Trigger) (*types.Trigger, error) { mod.ID = factory.Sonyflake.NextID() mod.CreatedAt = time.Now() - return mod, r.db().Insert("compose_trigger", mod) + return mod, r.db().Insert(r.table(), mod) } -func (r *trigger) Update(mod *types.Trigger) (*types.Trigger, error) { +func (r trigger) Update(mod *types.Trigger) (*types.Trigger, error) { now := time.Now() mod.UpdatedAt = &now - return mod, r.db().Replace("compose_trigger", mod) + return mod, r.db().Replace(r.table(), mod) } -func (r *trigger) DeleteByID(id uint64) error { - _, err := r.db().Exec("DELETE FROM compose_trigger WHERE id = ?", id) +func (r trigger) DeleteByID(namespaceID, attachmentID uint64) error { + _, err := r.db().Exec( + "UPDATE "+r.table()+" SET deleted_at = NOW() WHERE rel_namespace = ? AND id = ?", + namespaceID, + attachmentID, + ) + return err } diff --git a/compose/internal/service/chart.go b/compose/internal/service/chart.go index 60ab1c29a..9c812cbb3 100644 --- a/compose/internal/service/chart.go +++ b/compose/internal/service/chart.go @@ -37,7 +37,7 @@ func Chart() ChartService { }).With(context.Background()) } -func (svc *chart) With(ctx context.Context) ChartService { +func (svc chart) With(ctx context.Context) ChartService { db := repository.DB(ctx) return &chart{ db: db, @@ -49,7 +49,7 @@ func (svc *chart) With(ctx context.Context) ChartService { } } -func (svc *chart) FindByID(namespaceID, chartID uint64) (c *types.Chart, err error) { +func (svc chart) FindByID(namespaceID, chartID uint64) (c *types.Chart, err error) { if namespaceID == 0 { return nil, ErrNamespaceRequired } @@ -63,7 +63,7 @@ func (svc *chart) FindByID(namespaceID, chartID uint64) (c *types.Chart, err err return } -func (svc *chart) Find(filter types.ChartFilter) (set types.ChartSet, f types.ChartFilter, err error) { +func (svc chart) Find(filter types.ChartFilter) (set types.ChartSet, f types.ChartFilter, err error) { set, f, err = svc.chartRepo.Find(filter) if err != nil { return @@ -76,7 +76,7 @@ func (svc *chart) Find(filter types.ChartFilter) (set types.ChartSet, f types.Ch return } -func (svc *chart) Create(mod *types.Chart) (c *types.Chart, err error) { +func (svc chart) Create(mod *types.Chart) (c *types.Chart, err error) { if !svc.prmSvc.CanCreateChart(crmNamespace()) { return nil, ErrNoCreatePermissions.withStack() } @@ -84,7 +84,7 @@ func (svc *chart) Create(mod *types.Chart) (c *types.Chart, err error) { return svc.chartRepo.Create(mod) } -func (svc *chart) Update(mod *types.Chart) (c *types.Chart, err error) { +func (svc chart) Update(mod *types.Chart) (c *types.Chart, err error) { if mod.ID == 0 { return nil, ErrInvalidID.withStack() } @@ -107,7 +107,7 @@ func (svc *chart) Update(mod *types.Chart) (c *types.Chart, err error) { return svc.chartRepo.Update(c) } -func (svc *chart) DeleteByID(namespaceID, chartID uint64) error { +func (svc chart) DeleteByID(namespaceID, chartID uint64) error { if namespaceID == 0 { return ErrNamespaceRequired.withStack() } diff --git a/compose/internal/service/trigger.go b/compose/internal/service/trigger.go index ac5e51a8a..fba68b15f 100644 --- a/compose/internal/service/trigger.go +++ b/compose/internal/service/trigger.go @@ -3,7 +3,6 @@ package service import ( "context" - "github.com/pkg/errors" "github.com/titpetric/factory" "github.com/crusttech/crust/compose/internal/repository" @@ -18,27 +17,27 @@ type ( prmSvc PermissionsService triggerRepo repository.TriggerRepository - moduleRepo repository.ModuleRepository } TriggerService interface { With(ctx context.Context) TriggerService - FindByID(triggerID uint64) (*types.Trigger, error) - Find(filter types.TriggerFilter) (set types.TriggerSet, err error) + FindByID(namespaceID, triggerID uint64) (*types.Trigger, error) + Find(filter types.TriggerFilter) (set types.TriggerSet, f types.TriggerFilter, err error) Create(trigger *types.Trigger) (*types.Trigger, error) Update(trigger *types.Trigger) (*types.Trigger, error) - DeleteByID(triggerID uint64) error + DeleteByID(namespaceID, triggerID uint64) error } ) func Trigger() TriggerService { return (&trigger{ - prmSvc: DefaultPermissions}).With(context.Background()) + prmSvc: DefaultPermissions, + }).With(context.Background()) } -func (svc *trigger) With(ctx context.Context) TriggerService { +func (svc trigger) With(ctx context.Context) TriggerService { db := repository.DB(ctx) return &trigger{ db: db, @@ -47,74 +46,80 @@ func (svc *trigger) With(ctx context.Context) TriggerService { prmSvc: svc.prmSvc.With(ctx), triggerRepo: repository.Trigger(ctx, db), - moduleRepo: repository.Module(ctx, db), } } -func (svc *trigger) FindByID(id uint64) (t *types.Trigger, err error) { - if t, err = svc.triggerRepo.FindByID(id); err != nil { +func (svc trigger) FindByID(namespaceID, triggerID uint64) (c *types.Trigger, err error) { + if namespaceID == 0 { + return nil, ErrNamespaceRequired + } + + if c, err = svc.triggerRepo.FindByID(namespaceID, triggerID); err != nil { return - } else if !svc.prmSvc.CanReadTrigger(t) { - return nil, errors.New("not allowed to access this trigger") + } else if !svc.prmSvc.CanReadTrigger(c) { + return nil, ErrNoReadPermissions.withStack() } return } -func (svc *trigger) Find(filter types.TriggerFilter) (tt types.TriggerSet, err error) { - if tt, err = svc.triggerRepo.Find(filter); err != nil { - return nil, err - } else { - return tt.Filter(func(m *types.Trigger) (bool, error) { - return svc.prmSvc.CanReadTrigger(m), nil - }) +func (svc trigger) Find(filter types.TriggerFilter) (set types.TriggerSet, f types.TriggerFilter, err error) { + set, f, err = svc.triggerRepo.Find(filter) + if err != nil { + return } + + set, _ = set.Filter(func(m *types.Trigger) (bool, error) { + return svc.prmSvc.CanReadTrigger(m), nil + }) + + return } -func (svc *trigger) Create(trigger *types.Trigger) (p *types.Trigger, err error) { +func (svc trigger) Create(mod *types.Trigger) (c *types.Trigger, err error) { if !svc.prmSvc.CanCreateTrigger(crmNamespace()) { - return nil, errors.New("not allowed to create this trigger") + return nil, ErrNoCreatePermissions.withStack() } - return p, svc.db.Transaction(func() (err error) { - p, err = svc.triggerRepo.Create(trigger) + return svc.triggerRepo.Create(mod) +} + +func (svc trigger) Update(mod *types.Trigger) (c *types.Trigger, err error) { + if mod.ID == 0 { + return nil, ErrInvalidID.withStack() + } + + if c, err = svc.triggerRepo.FindByID(mod.NamespaceID, mod.ID); err != nil { return - }) -} - -func (svc *trigger) Update(trigger *types.Trigger) (t *types.Trigger, err error) { - validate := func() error { - if trigger.ID == 0 { - return errors.New("Error updating trigger: invalid ID") - } else if t, err = svc.triggerRepo.FindByID(trigger.ID); err != nil { - return errors.Wrap(err, "Error while loading trigger for update") - } else { - if !svc.prmSvc.CanUpdateModule(t) { - return errors.New("not allowed to update this trigger") - } - - trigger.CreatedAt = t.CreatedAt - } - - return nil } - if err := validate(); err != nil { - return nil, err + if isStale(mod.UpdatedAt, c.UpdatedAt, c.CreatedAt) { + return nil, ErrStaleData.withStack() } - return t, svc.db.Transaction(func() (err error) { - t, err = svc.triggerRepo.Update(trigger) - return - }) -} - -func (svc *trigger) DeleteByID(ID uint64) error { - if t, err := svc.triggerRepo.FindByID(ID); err != nil { - return errors.Wrap(err, "could not delete trigger") - } else if !svc.prmSvc.CanDeleteTrigger(t) { - return errors.New("not allowed to delete this trigger") + if !svc.prmSvc.CanUpdateTrigger(c) { + return nil, ErrNoUpdatePermissions.withStack() } - return svc.triggerRepo.DeleteByID(ID) + c.Name = mod.Name + c.ModuleID = mod.ModuleID + c.Source = mod.Source + c.Actions = mod.Actions + c.Enabled = mod.Enabled + + return svc.triggerRepo.Update(c) +} + +func (svc trigger) DeleteByID(namespaceID, triggerID uint64) error { + if namespaceID == 0 { + return ErrNamespaceRequired.withStack() + } + + if c, err := svc.triggerRepo.FindByID(namespaceID, triggerID); err != nil { + return err + } else if !svc.prmSvc.CanDeleteTrigger(c) { + return ErrNoDeletePermissions.withStack() + } + + return svc.triggerRepo.DeleteByID(namespaceID, triggerID) } diff --git a/compose/rest/chart.go b/compose/rest/chart.go index 0dde7f717..65457b9e6 100644 --- a/compose/rest/chart.go +++ b/compose/rest/chart.go @@ -92,18 +92,18 @@ func (ctrl Chart) Delete(ctx context.Context, r *request.ChartDelete) (interface return resputil.OK(), ctrl.chart.With(ctx).DeleteByID(r.NamespaceID, r.ChartID) } -func (ctrl Chart) makePayload(ctx context.Context, ns *types.Chart, err error) (*chartPayload, error) { - if err != nil || ns == nil { +func (ctrl Chart) makePayload(ctx context.Context, t *types.Chart, err error) (*chartPayload, error) { + if err != nil || t == nil { return nil, err } perm := ctrl.permissions.With(ctx) return &chartPayload{ - Chart: ns, + Chart: t, - CanUpdateChart: perm.CanUpdateChart(ns), - CanDeleteChart: perm.CanDeleteChart(ns), + CanUpdateChart: perm.CanUpdateChart(t), + CanDeleteChart: perm.CanDeleteChart(t), }, nil } diff --git a/compose/rest/request/trigger.go b/compose/rest/request/trigger.go index 9042deb69..49fc43252 100644 --- a/compose/rest/request/trigger.go +++ b/compose/rest/request/trigger.go @@ -25,6 +25,8 @@ import ( "github.com/go-chi/chi" "github.com/pkg/errors" + + "time" ) var _ = chi.URLParam @@ -33,6 +35,9 @@ var _ = multipart.FileHeader{} // Trigger list request parameters type TriggerList struct { ModuleID uint64 `json:",string"` + Query string + Page uint + PerPage uint NamespaceID uint64 `json:",string"` } @@ -71,6 +76,18 @@ func (tReq *TriggerList) Fill(r *http.Request) (err error) { tReq.ModuleID = parseUInt64(val) } + if val, ok := get["query"]; ok { + + tReq.Query = val + } + if val, ok := get["page"]; ok { + + tReq.Page = parseUint(val) + } + if val, ok := get["perPage"]; ok { + + tReq.PerPage = parseUint(val) + } tReq.NamespaceID = parseUInt64(chi.URLParam(r, "namespaceID")) return err @@ -85,6 +102,7 @@ type TriggerCreate struct { Actions []string Enabled bool Source string + UpdatedAt *time.Time NamespaceID uint64 `json:",string"` } @@ -135,6 +153,12 @@ func (tReq *TriggerCreate) Fill(r *http.Request) (err error) { tReq.Source = val } + if val, ok := post["updatedAt"]; ok { + + if tReq.UpdatedAt, err = parseISODatePtrWithErr(val); err != nil { + return err + } + } tReq.NamespaceID = parseUInt64(chi.URLParam(r, "namespaceID")) return err diff --git a/compose/rest/trigger.go b/compose/rest/trigger.go index cee170569..7921ba2a5 100644 --- a/compose/rest/trigger.go +++ b/compose/rest/trigger.go @@ -8,67 +8,114 @@ import ( "github.com/crusttech/crust/compose/internal/service" "github.com/crusttech/crust/compose/rest/request" "github.com/crusttech/crust/compose/types" - - "github.com/pkg/errors" ) -var _ = errors.Wrap +type ( + triggerPayload struct { + *types.Trigger -type Trigger struct { - trigger service.TriggerService -} + CanUpdateTrigger bool `json:"canUpdateTrigger"` + CanDeleteTrigger bool `json:"canDeleteTrigger"` + } + + triggerSetPayload struct { + Filter types.TriggerFilter `json:"filter"` + Set []*triggerPayload `json:"set"` + } + + Trigger struct { + trigger service.TriggerService + permissions service.PermissionsService + } +) func (Trigger) New() *Trigger { return &Trigger{ - trigger: service.DefaultTrigger, + trigger: service.DefaultTrigger, + permissions: service.DefaultPermissions, } } -func (ctrl *Trigger) List(ctx context.Context, r *request.TriggerList) (interface{}, error) { - filter := types.TriggerFilter{} - - if r.ModuleID > 0 { - filter.ModuleID = r.ModuleID +func (ctrl Trigger) List(ctx context.Context, r *request.TriggerList) (interface{}, error) { + f := types.TriggerFilter{ + Query: r.Query, + PerPage: r.PerPage, + Page: r.Page, } - return ctrl.trigger.With(ctx).Find(filter) + set, filter, err := ctrl.trigger.With(ctx).Find(f) + return ctrl.makeFilterPayload(ctx, set, filter, err) } -func (ctrl *Trigger) Create(ctx context.Context, r *request.TriggerCreate) (interface{}, error) { - trigger := &types.Trigger{ - Name: r.Name, - Actions: r.Actions, - Enabled: r.Enabled, - Source: r.Source, +func (ctrl Trigger) Create(ctx context.Context, r *request.TriggerCreate) (interface{}, error) { + var err error + ns := &types.Trigger{ + NamespaceID: r.NamespaceID, + ModuleID: r.ModuleID, + Name: r.Name, + Actions: r.Actions, + Enabled: r.Enabled, + Source: r.Source, } - if r.ModuleID > 0 { - trigger.ModuleID = r.ModuleID - } - - return ctrl.trigger.With(ctx).Create(trigger) + ns, err = ctrl.trigger.With(ctx).Create(ns) + return ctrl.makePayload(ctx, ns, err) } -func (ctrl *Trigger) Read(ctx context.Context, r *request.TriggerRead) (interface{}, error) { - return ctrl.trigger.With(ctx).FindByID(r.TriggerID) +func (ctrl Trigger) Read(ctx context.Context, r *request.TriggerRead) (interface{}, error) { + return ctrl.trigger.With(ctx).FindByID(r.NamespaceID, r.TriggerID) } -func (ctrl *Trigger) Update(ctx context.Context, r *request.TriggerUpdate) (interface{}, error) { - svc := ctrl.trigger.With(ctx) +func (ctrl Trigger) Update(ctx context.Context, r *request.TriggerUpdate) (interface{}, error) { + var ( + mod = &types.Trigger{} + err error + ) - if trigger, err := svc.FindByID(r.TriggerID); err != nil { + mod.ModuleID = r.ModuleID + mod.Name = r.Name + mod.Actions = r.Actions + mod.Enabled = r.Enabled + mod.Source = r.Source + + mod, err = ctrl.trigger.With(ctx).Update(mod) + return ctrl.makePayload(ctx, mod, err) +} + +func (ctrl Trigger) Delete(ctx context.Context, r *request.TriggerDelete) (interface{}, error) { + _, err := ctrl.trigger.With(ctx).FindByID(r.NamespaceID, r.TriggerID) + if err != nil { return nil, err - } else { - trigger.Name = r.Name - trigger.Actions = r.Actions - trigger.Enabled = r.Enabled - trigger.Source = r.Source - trigger.ModuleID = r.ModuleID - - return svc.Update(trigger) } + + return resputil.OK(), ctrl.trigger.With(ctx).DeleteByID(r.NamespaceID, r.TriggerID) } -func (ctrl *Trigger) Delete(ctx context.Context, r *request.TriggerDelete) (interface{}, error) { - return resputil.OK(), ctrl.trigger.With(ctx).DeleteByID(r.TriggerID) +func (ctrl Trigger) makePayload(ctx context.Context, t *types.Trigger, err error) (*triggerPayload, error) { + if err != nil || t == nil { + return nil, err + } + + perm := ctrl.permissions.With(ctx) + + return &triggerPayload{ + Trigger: t, + + CanUpdateTrigger: perm.CanUpdateTrigger(t), + CanDeleteTrigger: perm.CanDeleteTrigger(t), + }, nil +} + +func (ctrl Trigger) makeFilterPayload(ctx context.Context, nn types.TriggerSet, f types.TriggerFilter, err error) (*triggerSetPayload, error) { + if err != nil { + return nil, err + } + + nsp := &triggerSetPayload{Filter: f, Set: make([]*triggerPayload, len(nn))} + + for i := range nn { + nsp.Set[i], _ = ctrl.makePayload(ctx, nn[i], nil) + } + + return nsp, nil } diff --git a/compose/types/trigger.go b/compose/types/trigger.go index 59a389be3..358077741 100644 --- a/compose/types/trigger.go +++ b/compose/types/trigger.go @@ -26,6 +26,12 @@ type ( } TriggerFilter struct { + NamespaceID uint64 `json:"namespaceID,string"` + Query string `json:"query"` + Page uint `json:"page"` + PerPage uint `json:"perPage"` + // Sort string `json:"sort"` + Count uint `json:"count"` ModuleID uint64 `json:"moduleID,string"` } ) diff --git a/docs/compose/README.md b/docs/compose/README.md index f9e06862e..96e5814ee 100644 --- a/docs/compose/README.md +++ b/docs/compose/README.md @@ -774,6 +774,9 @@ Compose Triggers | Parameter | Type | Method | Description | Default | Required? | | --------- | ---- | ------ | ----------- | ------- | --------- | | moduleID | uint64 | GET | Filter triggers by module | N/A | NO | +| query | string | GET | Search query | N/A | NO | +| page | uint | GET | Page number (0 based) | N/A | NO | +| perPage | uint | GET | Returned items per page (default 50) | N/A | NO | | namespaceID | uint64 | PATH | Namespace ID | N/A | YES | ## Create trigger @@ -793,6 +796,7 @@ Compose Triggers | actions | []string | POST | Actions that trigger this trigger | N/A | NO | | enabled | bool | POST | Enabled | N/A | NO | | source | string | POST | Trigger source code | N/A | NO | +| updatedAt | *time.Time | POST | Last update (or creation) date | N/A | NO | | namespaceID | uint64 | PATH | Namespace ID | N/A | YES | ## Get trigger details