Apply namespace changes to all layers of triggers

This commit is contained in:
Denis Arh
2019-04-29 18:51:18 +02:00
parent 40999e623e
commit 0c2286be83
11 changed files with 322 additions and 142 deletions
+31
View File
@@ -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"
}
]
}
+31 -1
View File
@@ -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"
}
]
}
-1
View File
@@ -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) {
+69 -35
View File
@@ -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
}
+6 -6
View File
@@ -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()
}
+60 -55
View File
@@ -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)
}
+5 -5
View File
@@ -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
}
+24
View File
@@ -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
+86 -39
View File
@@ -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
}
+6
View File
@@ -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"`
}
)
+4
View File
@@ -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