758 lines
19 KiB
Go
758 lines
19 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"reflect"
|
|
|
|
"github.com/cortezaproject/corteza/server/compose/service/event"
|
|
"github.com/cortezaproject/corteza/server/compose/types"
|
|
"github.com/cortezaproject/corteza/server/pkg/actionlog"
|
|
"github.com/cortezaproject/corteza/server/pkg/errors"
|
|
"github.com/cortezaproject/corteza/server/pkg/eventbus"
|
|
"github.com/cortezaproject/corteza/server/pkg/handle"
|
|
"github.com/cortezaproject/corteza/server/pkg/label"
|
|
"github.com/cortezaproject/corteza/server/pkg/locale"
|
|
"github.com/cortezaproject/corteza/server/store"
|
|
systemTypes "github.com/cortezaproject/corteza/server/system/types"
|
|
)
|
|
|
|
type (
|
|
page struct {
|
|
actionlog actionlog.Recorder
|
|
ac pageAccessController
|
|
eventbus eventDispatcher
|
|
store store.Storer
|
|
locale ResourceTranslationsManagerService
|
|
|
|
pageSettings *pageSettings
|
|
}
|
|
|
|
pageSettings struct {
|
|
hideNew bool
|
|
hideEdit bool
|
|
hideSubmit bool
|
|
hideDelete bool
|
|
hideClone bool
|
|
hideBack bool
|
|
}
|
|
|
|
pageAccessController interface {
|
|
CanManageResourceTranslations(ctx context.Context) bool
|
|
CanSearchPagesOnNamespace(context.Context, *types.Namespace) bool
|
|
CanReadNamespace(context.Context, *types.Namespace) bool
|
|
CanCreatePageOnNamespace(context.Context, *types.Namespace) bool
|
|
CanReadPage(context.Context, *types.Page) bool
|
|
CanUpdatePage(context.Context, *types.Page) bool
|
|
CanDeletePage(context.Context, *types.Page) bool
|
|
}
|
|
|
|
pageUpdateHandler func(ctx context.Context, ns *types.Namespace, c *types.Page) (pageChanges, error)
|
|
pageChanges uint8
|
|
)
|
|
|
|
const (
|
|
pageUnchanged pageChanges = 0
|
|
pageChanged pageChanges = 1
|
|
pageLabelsChanged pageChanges = 2
|
|
)
|
|
|
|
func Page() *page {
|
|
return &page{
|
|
actionlog: DefaultActionlog,
|
|
ac: DefaultAccessControl,
|
|
eventbus: eventbus.Service(),
|
|
store: DefaultStore,
|
|
locale: DefaultResourceTranslation,
|
|
pageSettings: &pageSettings{},
|
|
}
|
|
}
|
|
|
|
func (svc page) FindByID(ctx context.Context, namespaceID, pageID uint64) (p *types.Page, err error) {
|
|
return svc.lookup(ctx, namespaceID, func(aProps *pageActionProps) (*types.Page, error) {
|
|
if pageID == 0 {
|
|
return nil, PageErrInvalidID()
|
|
}
|
|
|
|
aProps.page.ID = pageID
|
|
return store.LookupComposePageByID(ctx, svc.store, pageID)
|
|
})
|
|
}
|
|
|
|
func (svc page) FindByHandle(ctx context.Context, namespaceID uint64, h string) (c *types.Page, err error) {
|
|
return svc.lookup(ctx, namespaceID, func(aProps *pageActionProps) (*types.Page, error) {
|
|
if !handle.IsValid(h) {
|
|
return nil, PageErrInvalidHandle()
|
|
}
|
|
|
|
aProps.page.Handle = h
|
|
return store.LookupComposePageByNamespaceIDHandle(ctx, svc.store, namespaceID, h)
|
|
})
|
|
}
|
|
|
|
func (svc page) FindByPageID(ctx context.Context, namespaceID, pageID uint64) (p *types.Page, err error) {
|
|
return svc.lookup(ctx, namespaceID, func(aProps *pageActionProps) (*types.Page, error) {
|
|
if pageID == 0 {
|
|
return nil, PageErrInvalidID()
|
|
}
|
|
|
|
aProps.page.ID = pageID
|
|
return store.LookupComposePageByID(ctx, svc.store, pageID)
|
|
})
|
|
}
|
|
|
|
func checkPage(ctx context.Context, ac pageAccessController) func(res *types.Page) (bool, error) {
|
|
return func(res *types.Page) (bool, error) {
|
|
if !ac.CanReadPage(ctx, res) {
|
|
return false, nil
|
|
}
|
|
|
|
return true, nil
|
|
}
|
|
}
|
|
|
|
// search fn() orchestrates pages search, namespace preload and check
|
|
func (svc page) search(ctx context.Context, filter types.PageFilter) (set types.PageSet, f types.PageFilter, err error) {
|
|
var (
|
|
aProps = &pageActionProps{filter: &filter}
|
|
ns *types.Namespace
|
|
)
|
|
|
|
// For each fetched item, store backend will check if it is valid or not
|
|
filter.Check = checkPage(ctx, svc.ac)
|
|
|
|
err = func() error {
|
|
ns, err = loadNamespace(ctx, svc.store, filter.NamespaceID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
aProps.setNamespace(ns)
|
|
if !svc.ac.CanSearchPagesOnNamespace(ctx, ns) {
|
|
return PageErrNotAllowedToSearch()
|
|
}
|
|
|
|
if len(filter.Labels) > 0 {
|
|
filter.LabeledIDs, err = label.Search(
|
|
ctx,
|
|
svc.store,
|
|
types.Page{}.LabelResourceKind(),
|
|
filter.Labels,
|
|
)
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// labels specified but no labeled resources found
|
|
if len(filter.LabeledIDs) == 0 {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
if set, f, err = store.SearchComposePages(ctx, svc.store, filter); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err = label.Load(ctx, svc.store, toLabeledPages(set)...); err != nil {
|
|
return err
|
|
}
|
|
|
|
// i18n
|
|
tag := locale.GetAcceptLanguageFromContext(ctx)
|
|
set.Walk(func(p *types.Page) error {
|
|
p.DecodeTranslations(svc.locale.Locale().ResourceTranslations(tag, p.ResourceTranslation()))
|
|
return nil
|
|
})
|
|
|
|
return nil
|
|
}()
|
|
|
|
return set, f, svc.recordAction(ctx, aProps, PageActionSearch, err)
|
|
}
|
|
|
|
func (svc page) FindBySelfID(ctx context.Context, namespaceID, parentID uint64) (pp types.PageSet, f types.PageFilter, err error) {
|
|
return svc.search(ctx, types.PageFilter{
|
|
NamespaceID: namespaceID,
|
|
ParentID: parentID,
|
|
|
|
// This will enable parentID=0 query
|
|
Root: true,
|
|
|
|
Check: checkPage(ctx, svc.ac),
|
|
})
|
|
}
|
|
|
|
func (svc page) Find(ctx context.Context, filter types.PageFilter) (set types.PageSet, f types.PageFilter, err error) {
|
|
return svc.search(ctx, filter)
|
|
}
|
|
|
|
func (svc page) Tree(ctx context.Context, namespaceID uint64) (tree types.PageSet, err error) {
|
|
var (
|
|
pages types.PageSet
|
|
filter = types.PageFilter{
|
|
NamespaceID: namespaceID,
|
|
Check: checkPage(ctx, svc.ac),
|
|
}
|
|
)
|
|
|
|
if err = filter.Sort.Set("weight ASC"); err != nil {
|
|
return
|
|
}
|
|
|
|
if pages, _, err = svc.search(ctx, filter); err != nil {
|
|
return
|
|
}
|
|
|
|
// safe to ignore errors
|
|
_ = pages.Walk(func(p *types.Page) error {
|
|
if p.SelfID == 0 {
|
|
tree = append(tree, p)
|
|
} else if c := pages.FindByID(p.SelfID); c != nil {
|
|
if c.Children == nil {
|
|
c.Children = types.PageSet{}
|
|
}
|
|
|
|
c.Children = append(c.Children, p)
|
|
} else {
|
|
// Move orphans to root
|
|
p.SelfID = 0
|
|
tree = append(tree, p)
|
|
}
|
|
|
|
return nil
|
|
})
|
|
|
|
return tree, nil
|
|
}
|
|
|
|
// Reorder pages
|
|
func (svc page) Reorder(ctx context.Context, namespaceID, parentID uint64, pageIDs []uint64) (err error) {
|
|
var (
|
|
aProps = &pageActionProps{page: &types.Page{ID: parentID}}
|
|
ns *types.Namespace
|
|
p *types.Page
|
|
)
|
|
|
|
err = store.Tx(ctx, svc.store, func(ctx context.Context, s store.Storer) error {
|
|
if ns, err = loadNamespace(ctx, s, namespaceID); err != nil {
|
|
return err
|
|
}
|
|
|
|
if parentID == 0 {
|
|
// Reordering on root mode -- check if user can create pages.
|
|
if !svc.ac.CanCreatePageOnNamespace(ctx, ns) {
|
|
return PageErrNotAllowedToUpdate()
|
|
}
|
|
} else {
|
|
// Validate permissions on parent page
|
|
if p, err = store.LookupComposePageByID(ctx, s, parentID); errors.IsNotFound(err) {
|
|
return PageErrNotFound()
|
|
} else if err != nil {
|
|
return err
|
|
}
|
|
|
|
aProps.setPage(p)
|
|
|
|
if !svc.ac.CanUpdatePage(ctx, p) {
|
|
return PageErrNotAllowedToUpdate()
|
|
}
|
|
}
|
|
|
|
return store.ReorderComposePages(ctx, s, namespaceID, parentID, pageIDs)
|
|
})
|
|
|
|
return svc.recordAction(ctx, aProps, PageActionReorder, err)
|
|
|
|
}
|
|
|
|
func (svc page) Create(ctx context.Context, new *types.Page) (*types.Page, error) {
|
|
var (
|
|
ns *types.Namespace
|
|
aProps = &pageActionProps{page: new}
|
|
)
|
|
|
|
new.ID = 0
|
|
|
|
err := store.Tx(ctx, svc.store, func(ctx context.Context, s store.Storer) (err error) {
|
|
if !handle.IsValid(new.Handle) {
|
|
return PageErrInvalidID()
|
|
}
|
|
|
|
if ns, err = loadNamespace(ctx, s, new.NamespaceID); err != nil {
|
|
return err
|
|
}
|
|
|
|
if !svc.ac.CanCreatePageOnNamespace(ctx, ns) {
|
|
return PageErrNotAllowedToCreate()
|
|
}
|
|
|
|
aProps.setNamespace(ns)
|
|
|
|
if err = svc.eventbus.WaitFor(ctx, event.PageBeforeCreate(new, nil, ns, nil)); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err = svc.uniqueCheck(ctx, new); err != nil {
|
|
return err
|
|
}
|
|
|
|
new.ID = nextID()
|
|
new.CreatedAt = *now()
|
|
new.UpdatedAt = nil
|
|
new.DeletedAt = nil
|
|
|
|
// Ensure page-block IDs
|
|
for i := range new.Blocks {
|
|
new.Blocks[i].BlockID = uint64(i) + 1
|
|
}
|
|
|
|
aProps.setChanged(new)
|
|
|
|
if err = store.CreateComposePage(ctx, s, new); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err = updateTranslations(ctx, svc.ac, svc.locale, new.EncodeTranslations()...); err != nil {
|
|
return
|
|
}
|
|
|
|
if err = label.Create(ctx, s, new); err != nil {
|
|
return
|
|
}
|
|
|
|
_ = svc.eventbus.WaitFor(ctx, event.PageAfterCreate(new, nil, ns, nil))
|
|
return err
|
|
})
|
|
|
|
return new, svc.recordAction(ctx, aProps, PageActionCreate, err)
|
|
}
|
|
|
|
func (svc page) Update(ctx context.Context, upd *types.Page) (c *types.Page, err error) {
|
|
err = store.Tx(ctx, svc.store, func(ctx context.Context, s store.Storer) (err error) {
|
|
ns, res, err := loadPageCombo(ctx, s, upd.NamespaceID, upd.ID)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
c, err = svc.updater(ctx, svc.store, ns, res, PageActionUpdate, svc.handleUpdate(ctx, upd))
|
|
return
|
|
})
|
|
|
|
return
|
|
}
|
|
|
|
func (svc page) DeleteByID(ctx context.Context, namespaceID, pageID uint64, strategy types.PageChildrenDeleteStrategy) error {
|
|
var (
|
|
validChildren, pp types.PageSet
|
|
|
|
ns *types.Namespace
|
|
res *types.Page
|
|
|
|
skipUndeleted = func(p *types.Page) (bool, error) {
|
|
return p.DeletedAt == nil, nil
|
|
}
|
|
)
|
|
|
|
return store.Tx(ctx, svc.store, func(ctx context.Context, s store.Storer) (err error) {
|
|
if strategy == types.PageChildrenOnDeleteForce {
|
|
// simply delete the page and ignore the subpages
|
|
ns, res, err = loadPageCombo(ctx, s, namespaceID, pageID)
|
|
if err != nil {
|
|
return
|
|
}
|
|
} else {
|
|
// Load all pages in the namespace and
|
|
// try to figure out the family tree
|
|
pp, _, err = store.SearchComposePages(ctx, s, types.PageFilter{
|
|
NamespaceID: namespaceID,
|
|
})
|
|
|
|
if res = pp.FindByID(pageID); res == nil {
|
|
return PageErrNotFound()
|
|
}
|
|
|
|
validChildren, _ = pp.FindByParent(res.ID).Filter(skipUndeleted)
|
|
|
|
switch strategy {
|
|
case types.PageChildrenOnDeleteAbort:
|
|
// Abort if there are any valid (undeleted) children
|
|
if len(validChildren) > 0 {
|
|
return PageErrDeleteAbortedForPageWithSubpages()
|
|
}
|
|
|
|
case types.PageChildrenOnDeleteRebase:
|
|
// update all our children to point to our parent
|
|
err = validChildren.Walk(func(child *types.Page) (err error) {
|
|
updChild := child.Clone()
|
|
updChild.SelfID = res.SelfID
|
|
_, err = svc.updater(ctx, s, ns, child, PageActionUpdate, svc.handleUpdate(ctx, updChild))
|
|
return err
|
|
})
|
|
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
case types.PageChildrenOnDeleteCascade:
|
|
// update all our children to point to our parent
|
|
err = pp.RecursiveWalk(res, func(child *types.Page, _ *types.Page) (err error) {
|
|
if child.DeletedAt != nil {
|
|
// skip the ones that are already deleted
|
|
return nil
|
|
}
|
|
|
|
_, err = svc.updater(ctx, s, ns, child, PageActionDelete, svc.handleDelete)
|
|
return err
|
|
})
|
|
|
|
if err != nil {
|
|
return
|
|
}
|
|
default:
|
|
return PageErrUnknownDeleteStrategy()
|
|
}
|
|
|
|
if ns, err = loadNamespace(ctx, s, namespaceID); err != nil {
|
|
return
|
|
}
|
|
}
|
|
|
|
_, err = svc.updater(ctx, svc.store, ns, res, PageActionDelete, svc.handleDelete)
|
|
return
|
|
})
|
|
}
|
|
|
|
func (svc page) UndeleteByID(ctx context.Context, namespaceID, pageID uint64) error {
|
|
return store.Tx(ctx, svc.store, func(ctx context.Context, s store.Storer) (err error) {
|
|
ns, res, err := loadPageCombo(ctx, s, namespaceID, pageID)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
_, err = svc.updater(ctx, svc.store, ns, res, PageActionUpdate, svc.handleUndelete)
|
|
return
|
|
})
|
|
}
|
|
|
|
func (svc page) UpdateIcon(ctx context.Context, namespaceID, pageID uint64, icon *types.PageConfigIcon) (out *types.PageConfigIcon, err error) {
|
|
err = store.Tx(ctx, svc.store, func(ctx context.Context, s store.Storer) (err error) {
|
|
ns, p, err := loadPageCombo(ctx, s, namespaceID, pageID)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
p.Config.NavItem.Icon = icon
|
|
p, err = svc.updater(ctx, svc.store, ns, p, PageActionUpdate, svc.handleUpdate(ctx, p))
|
|
out = p.Config.NavItem.Icon
|
|
|
|
return
|
|
})
|
|
|
|
return
|
|
}
|
|
|
|
func (svc page) updater(ctx context.Context, s store.Storer, ns *types.Namespace, res *types.Page, action func(...*pageActionProps) *pageAction, fn pageUpdateHandler) (*types.Page, error) {
|
|
var (
|
|
changes pageChanges
|
|
old *types.Page
|
|
aProps = &pageActionProps{page: res}
|
|
err error
|
|
)
|
|
|
|
err = store.Tx(ctx, s, func(ctx context.Context, s store.Storer) (err error) {
|
|
if err = label.Load(ctx, svc.store, res); err != nil {
|
|
return err
|
|
}
|
|
|
|
old = res.Clone()
|
|
|
|
aProps.setNamespace(ns)
|
|
aProps.setChanged(res)
|
|
|
|
if res.DeletedAt == nil {
|
|
err = svc.eventbus.WaitFor(ctx, event.PageBeforeUpdate(old, res, ns, nil))
|
|
} else {
|
|
err = svc.eventbus.WaitFor(ctx, event.PageBeforeDelete(old, res, ns, nil))
|
|
}
|
|
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
if changes, err = fn(ctx, ns, res); err != nil {
|
|
return err
|
|
}
|
|
|
|
if changes&pageChanged > 0 {
|
|
if err = store.UpdateComposePage(ctx, s, res); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if err = updateTranslations(ctx, svc.ac, svc.locale, res.EncodeTranslations()...); err != nil {
|
|
return
|
|
}
|
|
|
|
if changes&pageLabelsChanged > 0 {
|
|
if err = label.Update(ctx, s, res); err != nil {
|
|
return
|
|
}
|
|
}
|
|
|
|
if res.DeletedAt == nil {
|
|
err = svc.eventbus.WaitFor(ctx, event.PageAfterUpdate(res, res, ns, nil))
|
|
} else {
|
|
err = svc.eventbus.WaitFor(ctx, event.PageAfterDelete(nil, res, ns, nil))
|
|
}
|
|
|
|
return err
|
|
})
|
|
|
|
return res, svc.recordAction(ctx, aProps, action, err)
|
|
}
|
|
|
|
// lookup fn() orchestrates page lookup, namespace preload and check
|
|
func (svc page) lookup(ctx context.Context, namespaceID uint64, lookup func(*pageActionProps) (*types.Page, error)) (p *types.Page, err error) {
|
|
var aProps = &pageActionProps{page: &types.Page{NamespaceID: namespaceID}}
|
|
|
|
err = func() error {
|
|
if ns, err := loadNamespace(ctx, svc.store, namespaceID); err != nil {
|
|
return err
|
|
} else {
|
|
aProps.setNamespace(ns)
|
|
}
|
|
|
|
if p, err = lookup(aProps); errors.IsNotFound(err) {
|
|
return PageErrNotFound()
|
|
} else if err != nil {
|
|
return err
|
|
}
|
|
|
|
p.DecodeTranslations(svc.locale.Locale().ResourceTranslations(locale.GetAcceptLanguageFromContext(ctx), p.ResourceTranslation()))
|
|
|
|
aProps.setPage(p)
|
|
|
|
if !svc.ac.CanReadPage(ctx, p) {
|
|
return PageErrNotAllowedToRead()
|
|
}
|
|
|
|
if err = label.Load(ctx, svc.store, p); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}()
|
|
|
|
return p, svc.recordAction(ctx, aProps, PageActionLookup, err)
|
|
}
|
|
|
|
func (svc page) uniqueCheck(ctx context.Context, p *types.Page) (err error) {
|
|
if p.Handle != "" {
|
|
if e, _ := store.LookupComposePageByNamespaceIDHandle(ctx, svc.store, p.NamespaceID, p.Handle); e != nil && e.ID != p.ID {
|
|
return PageErrHandleNotUnique()
|
|
}
|
|
}
|
|
|
|
if p.ModuleID > 0 {
|
|
if e, _ := store.LookupComposePageByNamespaceIDModuleID(ctx, svc.store, p.NamespaceID, p.ModuleID); e != nil && e.ID != p.ID {
|
|
return PageErrModuleNotFound()
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (svc page) handleUpdate(ctx context.Context, upd *types.Page) pageUpdateHandler {
|
|
return func(ctx context.Context, ns *types.Namespace, res *types.Page) (changes pageChanges, err error) {
|
|
if isStale(upd.UpdatedAt, res.UpdatedAt, res.CreatedAt) {
|
|
return pageUnchanged, PageErrStaleData()
|
|
}
|
|
|
|
if upd.Handle != res.Handle && !handle.IsValid(upd.Handle) {
|
|
return pageUnchanged, PageErrInvalidHandle()
|
|
}
|
|
|
|
if err := svc.uniqueCheck(ctx, upd); err != nil {
|
|
return pageUnchanged, err
|
|
}
|
|
|
|
if !svc.ac.CanUpdatePage(ctx, res) {
|
|
return pageUnchanged, PageErrNotAllowedToUpdate()
|
|
}
|
|
|
|
// Get max blockID for later use
|
|
blockID := uint64(0)
|
|
for _, b := range res.Blocks {
|
|
if b.BlockID > blockID {
|
|
blockID = b.BlockID
|
|
}
|
|
}
|
|
|
|
if res.ID != upd.ID {
|
|
res.ID = upd.ID
|
|
changes |= pageChanged
|
|
}
|
|
|
|
if res.SelfID != upd.SelfID {
|
|
res.SelfID = upd.SelfID
|
|
changes |= pageChanged
|
|
}
|
|
|
|
if !reflect.DeepEqual(res.Config, upd.Config) {
|
|
res.Config = upd.Config
|
|
changes |= pageChanged
|
|
}
|
|
|
|
if !reflect.DeepEqual(res.Blocks, upd.Blocks) {
|
|
res.Blocks = upd.Blocks
|
|
changes |= pageChanged
|
|
}
|
|
|
|
// Assure blockIDs
|
|
for i, b := range res.Blocks {
|
|
if b.BlockID == 0 {
|
|
blockID++
|
|
b.BlockID = blockID
|
|
res.Blocks[i] = b
|
|
|
|
changes |= pageChanged
|
|
}
|
|
}
|
|
|
|
if res.Meta.AllowPersonalLayouts != upd.Meta.AllowPersonalLayouts {
|
|
res.Meta.AllowPersonalLayouts = upd.Meta.AllowPersonalLayouts
|
|
changes |= pageChanged
|
|
}
|
|
|
|
if res.Title != upd.Title {
|
|
res.Title = upd.Title
|
|
changes |= pageChanged
|
|
}
|
|
|
|
if res.Handle != upd.Handle {
|
|
res.Handle = upd.Handle
|
|
changes |= pageChanged
|
|
}
|
|
|
|
if res.Description != upd.Description {
|
|
res.Description = upd.Description
|
|
changes |= pageChanged
|
|
}
|
|
|
|
if res.Visible != upd.Visible {
|
|
res.Visible = upd.Visible
|
|
changes |= pageChanged
|
|
}
|
|
|
|
if res.Weight != upd.Weight {
|
|
res.Weight = upd.Weight
|
|
changes |= pageChanged
|
|
}
|
|
|
|
if upd.Labels != nil {
|
|
if label.Changed(res.Labels, upd.Labels) {
|
|
changes |= pageLabelsChanged
|
|
res.Labels = upd.Labels
|
|
}
|
|
}
|
|
|
|
if changes&pageChanged > 0 {
|
|
res.UpdatedAt = now()
|
|
}
|
|
|
|
return
|
|
}
|
|
}
|
|
|
|
func (svc page) handleDelete(ctx context.Context, ns *types.Namespace, m *types.Page) (pageChanges, error) {
|
|
if !svc.ac.CanDeletePage(ctx, m) {
|
|
return pageUnchanged, PageErrNotAllowedToDelete()
|
|
}
|
|
|
|
if m.DeletedAt != nil {
|
|
// page already deleted
|
|
return pageUnchanged, nil
|
|
}
|
|
|
|
m.DeletedAt = now()
|
|
return pageChanged, nil
|
|
}
|
|
|
|
func (svc page) handleUndelete(ctx context.Context, ns *types.Namespace, m *types.Page) (pageChanges, error) {
|
|
if !svc.ac.CanDeletePage(ctx, m) {
|
|
return pageUnchanged, PageErrNotAllowedToUndelete()
|
|
}
|
|
|
|
if m.DeletedAt == nil {
|
|
// page not deleted
|
|
return pageUnchanged, nil
|
|
}
|
|
|
|
m.DeletedAt = nil
|
|
return pageChanged, nil
|
|
}
|
|
|
|
func (svc *page) UpdateConfig(ss *systemTypes.AppSettings) {
|
|
a := ss.Compose.UI.RecordToolbar
|
|
|
|
svc.pageSettings = &pageSettings{
|
|
hideNew: a.HideNew,
|
|
hideEdit: a.HideEdit,
|
|
hideSubmit: a.HideSubmit,
|
|
hideDelete: a.HideDelete,
|
|
hideClone: a.HideClone,
|
|
hideBack: a.HideBack,
|
|
}
|
|
}
|
|
|
|
func loadPageCombo(ctx context.Context, s interface {
|
|
store.ComposePages
|
|
store.ComposeNamespaces
|
|
}, namespaceID, pageID uint64) (ns *types.Namespace, c *types.Page, err error) {
|
|
ns, err = loadNamespace(ctx, s, namespaceID)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
c, err = loadPage(ctx, s, namespaceID, pageID)
|
|
return
|
|
}
|
|
|
|
func loadPage(ctx context.Context, s store.ComposePages, namespaceID, pageID uint64) (res *types.Page, err error) {
|
|
if pageID == 0 || namespaceID == 0 {
|
|
return nil, PageErrInvalidID()
|
|
}
|
|
|
|
if res, err = store.LookupComposePageByID(ctx, s, pageID); errors.IsNotFound(err) {
|
|
err = PageErrNotFound()
|
|
}
|
|
|
|
if err == nil && namespaceID != res.NamespaceID {
|
|
// Make sure chart belongs to the right namespace
|
|
return nil, PageErrNotFound()
|
|
}
|
|
|
|
if err == nil && namespaceID != res.NamespaceID {
|
|
// Make sure page belongs to the right namespace
|
|
return nil, PageErrNotFound()
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// toLabeledPages converts to []label.LabeledResource
|
|
//
|
|
// This function is auto-generated.
|
|
func toLabeledPages(set []*types.Page) []label.LabeledResource {
|
|
if len(set) == 0 {
|
|
return nil
|
|
}
|
|
|
|
ll := make([]label.LabeledResource, len(set))
|
|
for i := range set {
|
|
ll[i] = set[i]
|
|
}
|
|
|
|
return ll
|
|
}
|