Apply namespace changes to all layers of charts

This commit is contained in:
Denis Arh
2019-04-29 18:51:18 +02:00
parent 8ef7b77a77
commit a41e4bfc19
9 changed files with 324 additions and 122 deletions
+75 -22
View File
@@ -5,6 +5,7 @@ import (
"time"
"github.com/titpetric/factory"
"gopkg.in/Masterminds/squirrel.v1"
"github.com/crusttech/crust/compose/types"
)
@@ -13,11 +14,11 @@ type (
ChartRepository interface {
With(ctx context.Context, db *factory.DB) ChartRepository
FindByID(id uint64) (*types.Chart, error)
Find() (types.ChartSet, error)
FindByID(namespaceID, attachmentID uint64) (*types.Chart, error)
Find(filter types.ChartFilter) (set types.ChartSet, f types.ChartFilter, err error)
Create(mod *types.Chart) (*types.Chart, error)
Update(mod *types.Chart) (*types.Chart, error)
DeleteByID(id uint64) error
DeleteByID(namespaceID, attachmentID uint64) error
}
chart struct {
@@ -25,48 +26,100 @@ type (
}
)
const sqlChartColumns = "id, name, config, " +
"created_at, updated_at, deleted_at"
const sqlChartSelect = "SELECT " + sqlChartColumns + " FROM compose_chart"
const (
ErrChartNotFound = repositoryError("ChartNotFound")
)
func Chart(ctx context.Context, db *factory.DB) ChartRepository {
return (&chart{}).With(ctx, db)
}
func (r *chart) With(ctx context.Context, db *factory.DB) ChartRepository {
func (r chart) With(ctx context.Context, db *factory.DB) ChartRepository {
return &chart{
repository: r.repository.With(ctx, db),
}
}
func (r *chart) FindByID(id uint64) (*types.Chart, error) {
mod := &types.Chart{}
if err := r.db().Get(mod, sqlChartSelect+" WHERE id = ?", id); err != nil {
return nil, err
func (r chart) table() string {
return "compose_chart"
}
func (r chart) columns() []string {
return []string{
"id", "rel_namespace", "name", "config",
"created_at", "updated_at", "deleted_at",
}
return mod, nil
}
func (r *chart) Find() (types.ChartSet, error) {
mod := types.ChartSet{}
return mod, r.db().Select(&mod, sqlChartSelect+" ORDER BY id ASC")
func (r chart) query() squirrel.SelectBuilder {
return squirrel.
Select().
From(r.table()).
Where("deleted_at IS NULL")
}
func (r *chart) Create(mod *types.Chart) (*types.Chart, error) {
func (r chart) FindByID(namespaceID, chartID uint64) (*types.Chart, error) {
var (
query = r.query().
Columns(r.columns()...).
Where("id = ?", chartID)
c = &types.Chart{}
)
if namespaceID > 0 {
query = query.Where("rel_namespace = ?", namespaceID)
}
return c, isFound(r.fetchOne(c, query), c.ID > 0, ErrChartNotFound)
}
func (r chart) Find(filter types.ChartFilter) (set types.ChartSet, f types.ChartFilter, err error) {
f = filter
f.PerPage = normalizePerPage(f.PerPage, 5, 100, 50)
query := r.query()
if filter.NamespaceID > 0 {
query = query.Where("a.rel_namespace = ?", filter.NamespaceID)
}
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 chart) Create(mod *types.Chart) (*types.Chart, error) {
mod.ID = factory.Sonyflake.NextID()
mod.CreatedAt = time.Now()
return mod, r.db().Insert("compose_chart", mod)
return mod, r.db().Insert(r.table(), mod)
}
func (r *chart) Update(mod *types.Chart) (*types.Chart, error) {
func (r chart) Update(mod *types.Chart) (*types.Chart, error) {
now := time.Now()
mod.UpdatedAt = &now
return mod, r.db().Replace("compose_chart", mod)
return mod, r.db().Replace(r.table(), mod)
}
func (r *chart) DeleteByID(id uint64) error {
_, err := r.db().Exec("DELETE FROM compose_chart WHERE id = ?", id)
func (r chart) 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
}
+45 -45
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"
@@ -23,12 +22,12 @@ type (
ChartService interface {
With(ctx context.Context) ChartService
FindByID(chartID uint64) (*types.Chart, error)
Find() (types.ChartSet, error)
FindByID(namespaceID, chartID uint64) (*types.Chart, error)
Find(filter types.ChartFilter) (set types.ChartSet, f types.ChartFilter, err error)
Create(chart *types.Chart) (*types.Chart, error)
Update(chart *types.Chart) (*types.Chart, error)
DeleteByID(chartID uint64) error
DeleteByID(namespaceID, chartID uint64) error
}
)
@@ -50,73 +49,74 @@ func (svc *chart) With(ctx context.Context) ChartService {
}
}
func (svc *chart) FindByID(chartID uint64) (c *types.Chart, err error) {
if c, err = svc.chartRepo.FindByID(chartID); err != nil {
func (svc *chart) FindByID(namespaceID, chartID uint64) (c *types.Chart, err error) {
if namespaceID == 0 {
return nil, ErrNamespaceRequired
}
if c, err = svc.chartRepo.FindByID(namespaceID, chartID); err != nil {
return
} else if !svc.prmSvc.CanReadChart(c) {
return nil, errors.New("not allowed to access this chart")
return nil, ErrNoReadPermissions.withStack()
}
return
}
func (svc *chart) Find() (cc types.ChartSet, err error) {
if cc, err = svc.chartRepo.Find(); err != nil {
return nil, err
} else {
return cc.Filter(func(m *types.Chart) (bool, error) {
return svc.prmSvc.CanReadChart(m), nil
})
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
}
set, _ = set.Filter(func(m *types.Chart) (bool, error) {
return svc.prmSvc.CanReadChart(m), nil
})
return
}
func (svc *chart) Create(mod *types.Chart) (c *types.Chart, err error) {
if !svc.prmSvc.CanCreateChart(crmNamespace()) {
return nil, errors.New("not allowed to create this chart")
return nil, ErrNoCreatePermissions.withStack()
}
return c, svc.db.Transaction(func() error {
c, err = svc.chartRepo.Create(mod)
return err
})
return svc.chartRepo.Create(mod)
}
func (svc *chart) Update(mod *types.Chart) (c *types.Chart, err error) {
validate := func() error {
if mod.ID == 0 {
return errors.New("Error updating chart: invalid ID")
} else if c, err = svc.chartRepo.FindByID(mod.ID); err != nil {
return errors.Wrap(err, "Error while loading chart for update")
} else {
if !svc.prmSvc.CanUpdateChart(c) {
return errors.New("not allowed to update this chart")
}
mod.CreatedAt = c.CreatedAt
}
return nil
if mod.ID == 0 {
return nil, ErrInvalidID.withStack()
}
if err = validate(); err != nil {
return nil, err
if c, err = svc.chartRepo.FindByID(mod.NamespaceID, mod.ID); err != nil {
return
}
if isStale(mod.UpdatedAt, c.UpdatedAt, c.CreatedAt) {
return nil, ErrStaleData.withStack()
}
if !svc.prmSvc.CanUpdateChart(c) {
return nil, ErrNoUpdatePermissions.withStack()
}
c.Config = mod.Config
c.Name = mod.Name
return c, svc.db.Transaction(func() error {
c, err = svc.chartRepo.Update(c)
return err
})
return svc.chartRepo.Update(c)
}
func (svc *chart) DeleteByID(ID uint64) error {
if c, err := svc.chartRepo.FindByID(ID); err != nil {
return errors.Wrap(err, "could not delete chart")
} else if !svc.prmSvc.CanDeleteChart(c) {
return errors.New("not allowed to delete this chart")
func (svc *chart) DeleteByID(namespaceID, chartID uint64) error {
if namespaceID == 0 {
return ErrNamespaceRequired.withStack()
}
return svc.chartRepo.DeleteByID(ID)
if c, err := svc.chartRepo.FindByID(namespaceID, chartID); err != nil {
return err
} else if !svc.prmSvc.CanDeleteChart(c) {
return ErrNoDeletePermissions.withStack()
}
return svc.chartRepo.DeleteByID(namespaceID, chartID)
}
+91 -27
View File
@@ -14,45 +14,109 @@ import (
var _ = errors.Wrap
type Chart struct {
module service.ModuleService
chart service.ChartService
}
type (
chartPayload struct {
*types.Chart
CanUpdateChart bool `json:"canUpdateChart"`
CanDeleteChart bool `json:"canDeleteChart"`
}
chartSetPayload struct {
Filter types.ChartFilter `json:"filter"`
Set []*chartPayload `json:"set"`
}
Chart struct {
chart service.ChartService
permissions service.PermissionsService
}
)
func (Chart) New() *Chart {
return &Chart{
module: service.DefaultModule,
chart: service.DefaultChart,
chart: service.DefaultChart,
permissions: service.DefaultPermissions,
}
}
func (ctrl *Chart) List(ctx context.Context, r *request.ChartList) (interface{}, error) {
return ctrl.chart.With(ctx).Find()
}
func (ctrl *Chart) Create(ctx context.Context, r *request.ChartCreate) (interface{}, error) {
chart := &types.Chart{
Name: r.Name,
Config: r.Config,
func (ctrl Chart) List(ctx context.Context, r *request.ChartList) (interface{}, error) {
f := types.ChartFilter{
Query: r.Query,
PerPage: r.PerPage,
Page: r.Page,
}
return ctrl.chart.With(ctx).Create(chart)
set, filter, err := ctrl.chart.With(ctx).Find(f)
return ctrl.makeFilterPayload(ctx, set, filter, err)
}
func (ctrl *Chart) Read(ctx context.Context, r *request.ChartRead) (interface{}, error) {
return ctrl.chart.With(ctx).FindByID(r.ChartID)
}
func (ctrl *Chart) Update(ctx context.Context, r *request.ChartUpdate) (interface{}, error) {
chart := &types.Chart{
ID: r.ChartID,
Name: r.Name,
Config: r.Config,
func (ctrl Chart) Create(ctx context.Context, r *request.ChartCreate) (interface{}, error) {
var err error
ns := &types.Chart{
NamespaceID: r.NamespaceID,
Name: r.Name,
Config: r.Config,
}
return ctrl.chart.With(ctx).Update(chart)
ns, err = ctrl.chart.With(ctx).Create(ns)
return ctrl.makePayload(ctx, ns, err)
}
func (ctrl *Chart) Delete(ctx context.Context, r *request.ChartDelete) (interface{}, error) {
return resputil.OK(), ctrl.chart.With(ctx).DeleteByID(r.ChartID)
func (ctrl Chart) Read(ctx context.Context, r *request.ChartRead) (interface{}, error) {
return ctrl.chart.With(ctx).FindByID(r.NamespaceID, r.ChartID)
}
func (ctrl Chart) Update(ctx context.Context, r *request.ChartUpdate) (interface{}, error) {
var (
ns = &types.Chart{}
err error
)
ns.ID = r.ChartID
ns.Name = r.Name
ns.Config = r.Config
ns.NamespaceID = r.NamespaceID
ns.UpdatedAt = r.UpdatedAt
ns, err = ctrl.chart.With(ctx).Update(ns)
return ctrl.makePayload(ctx, ns, err)
}
func (ctrl Chart) Delete(ctx context.Context, r *request.ChartDelete) (interface{}, error) {
_, err := ctrl.chart.With(ctx).FindByID(r.NamespaceID, r.ChartID)
if err != nil {
return nil, err
}
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 {
return nil, err
}
perm := ctrl.permissions.With(ctx)
return &chartPayload{
Chart: ns,
CanUpdateChart: perm.CanUpdateChart(ns),
CanDeleteChart: perm.CanDeleteChart(ns),
}, nil
}
func (ctrl Chart) makeFilterPayload(ctx context.Context, nn types.ChartSet, f types.ChartFilter, err error) (*chartSetPayload, error) {
if err != nil {
return nil, err
}
nsp := &chartSetPayload{Filter: f, Set: make([]*chartPayload, len(nn))}
for i := range nn {
nsp.Set[i], _ = ctrl.makePayload(ctx, nn[i], nil)
}
return nsp, nil
}
+23
View File
@@ -27,6 +27,7 @@ import (
"github.com/pkg/errors"
sqlxTypes "github.com/jmoiron/sqlx/types"
"time"
)
var _ = chi.URLParam
@@ -34,6 +35,9 @@ var _ = multipart.FileHeader{}
// Chart list request parameters
type ChartList struct {
Query string
Page uint
PerPage uint
NamespaceID uint64 `json:",string"`
}
@@ -68,6 +72,18 @@ func (cReq *ChartList) Fill(r *http.Request) (err error) {
post[name] = string(param[0])
}
if val, ok := get["query"]; ok {
cReq.Query = val
}
if val, ok := get["page"]; ok {
cReq.Page = parseUint(val)
}
if val, ok := get["perPage"]; ok {
cReq.PerPage = parseUint(val)
}
cReq.NamespaceID = parseUInt64(chi.URLParam(r, "namespaceID"))
return err
@@ -181,6 +197,7 @@ type ChartUpdate struct {
NamespaceID uint64 `json:",string"`
Config sqlxTypes.JSONText
Name string
UpdatedAt *time.Time
}
func NewChartUpdate() *ChartUpdate {
@@ -226,6 +243,12 @@ func (cReq *ChartUpdate) Fill(r *http.Request) (err error) {
cReq.Name = val
}
if val, ok := post["updatedAt"]; ok {
if cReq.UpdatedAt, err = parseISODatePtrWithErr(val); err != nil {
return err
}
}
return err
}
+6 -5
View File
@@ -22,11 +22,12 @@ type (
}
ChartFilter struct {
Query string `json:"query"`
Page uint `json:"page"`
PerPage uint `json:"perPage"`
Sort string `json:"sort"`
Count uint `json:"count"`
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"`
}
)