3
0

Ported compose attachments

This commit is contained in:
Denis Arh
2020-08-27 22:43:19 +02:00
parent 361a9007bf
commit 0c085d7b24
11 changed files with 965 additions and 367 deletions

View File

@@ -1,177 +1,177 @@
package repository
import (
"context"
"time"
"github.com/Masterminds/squirrel"
"github.com/pkg/errors"
"github.com/titpetric/factory"
"github.com/cortezaproject/corteza-server/compose/types"
"github.com/cortezaproject/corteza-server/pkg/rh"
)
type (
AttachmentRepository interface {
With(ctx context.Context, db *factory.DB) AttachmentRepository
Find(filter types.AttachmentFilter) (types.AttachmentSet, types.AttachmentFilter, error)
FindByID(namespaceID, attachmentID uint64) (*types.Attachment, error)
Create(mod *types.Attachment) (*types.Attachment, error)
DeleteByID(namespaceID, attachmentID uint64) error
}
attachment struct {
*repository
}
)
const (
ErrAttachmentNotFound = repositoryError("AttachmentNotFound")
)
func Attachment(ctx context.Context, db *factory.DB) AttachmentRepository {
return (&attachment{}).With(ctx, db)
}
func (r attachment) With(ctx context.Context, db *factory.DB) AttachmentRepository {
return &attachment{
repository: r.repository.With(ctx, db),
}
}
func (r attachment) table() string {
return "compose_attachment"
}
func (r attachment) columns() []string {
return []string{
"a.id",
"a.rel_namespace",
"a.rel_owner",
"a.kind",
"a.url",
"a.preview_url",
"a.name",
"a.meta",
"a.created_at",
"a.updated_at",
"a.deleted_at",
}
}
func (r attachment) query() squirrel.SelectBuilder {
return squirrel.
Select(r.columns()...).
From(r.table() + " AS a").
Where("a.deleted_at IS NULL")
}
func (r attachment) FindByID(namespaceID, attachmentID uint64) (*types.Attachment, error) {
return r.findOneBy(namespaceID, "id", attachmentID)
}
func (r attachment) findOneBy(namespaceID uint64, field string, value interface{}) (*types.Attachment, error) {
var (
p = &types.Attachment{}
q = r.query().
Where(squirrel.Eq{field: value, "rel_namespace": namespaceID})
err = rh.FetchOne(r.db(), q, p)
)
if err != nil {
return nil, err
} else if p.ID == 0 {
return nil, ErrAttachmentNotFound
}
return p, nil
}
func (r attachment) Find(filter types.AttachmentFilter) (set types.AttachmentSet, f types.AttachmentFilter, err error) {
f = filter
if f.Sort == "" {
f.Sort = "id ASC"
}
query := r.query().
Where(squirrel.Eq{"a.kind": f.Kind})
if filter.NamespaceID > 0 {
query = query.Where("a.rel_namespace = ?", filter.NamespaceID)
}
switch f.Kind {
case types.PageAttachment:
// @todo implement filtering by page
if f.PageID > 0 {
err = errors.New("filtering by pageID not implemented")
return
}
case types.RecordAttachment:
query = query.
Join("compose_record_value AS v ON (v.ref = a.id)")
if f.ModuleID > 0 {
query = query.
Join("compose_record AS r ON (r.id = v.record_id)").
Where(squirrel.Eq{"r.module_id": f.ModuleID})
}
if f.RecordID > 0 {
query = query.Where(squirrel.Eq{"v.record_id": f.RecordID})
}
if f.FieldName != "" {
query = query.Where(squirrel.Eq{"v.name": f.FieldName})
}
default:
err = errors.New("unsupported kind value")
return
}
if f.Filter != "" {
err = errors.New("filtering by filter not implemented")
return
}
var orderBy []string
if orderBy, err = rh.ParseOrder(f.Sort, r.columns()...); err != nil {
return
} else {
query = query.OrderBy(orderBy...)
}
if f.Count, err = rh.Count(r.db(), query); err != nil || f.Count == 0 {
return
}
return set, f, rh.FetchPaged(r.db(), query, f.PageFilter, &set)
}
func (r attachment) Create(mod *types.Attachment) (*types.Attachment, error) {
if mod.ID == 0 {
mod.ID = factory.Sonyflake.NextID()
}
mod.CreatedAt = time.Now()
return mod, r.db().Insert(r.table(), mod)
}
func (r attachment) 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
}
//import (
// "context"
// "time"
//
// "github.com/Masterminds/squirrel"
// "github.com/pkg/errors"
// "github.com/titpetric/factory"
//
// "github.com/cortezaproject/corteza-server/compose/types"
// "github.com/cortezaproject/corteza-server/pkg/rh"
//)
//
//type (
// AttachmentRepository interface {
// With(ctx context.Context, db *factory.DB) AttachmentRepository
//
// Find(filter types.AttachmentFilter) (types.AttachmentSet, types.AttachmentFilter, error)
// FindByID(namespaceID, attachmentID uint64) (*types.Attachment, error)
// Create(mod *types.Attachment) (*types.Attachment, error)
// DeleteByID(namespaceID, attachmentID uint64) error
// }
//
// attachment struct {
// *repository
// }
//)
//
//const (
// ErrAttachmentNotFound = repositoryError("AttachmentNotFound")
//)
//
//func Attachment(ctx context.Context, db *factory.DB) AttachmentRepository {
// return (&attachment{}).With(ctx, db)
//}
//
//func (r attachment) With(ctx context.Context, db *factory.DB) AttachmentRepository {
// return &attachment{
// repository: r.repository.With(ctx, db),
// }
//}
//
//func (r attachment) table() string {
// return "compose_attachment"
//}
//
//func (r attachment) columns() []string {
// return []string{
// "a.id",
// "a.rel_namespace",
// "a.rel_owner",
// "a.kind",
// "a.url",
// "a.preview_url",
// "a.name",
// "a.meta",
// "a.created_at",
// "a.updated_at",
// "a.deleted_at",
// }
//}
//
//func (r attachment) query() squirrel.SelectBuilder {
// return squirrel.
// Select(r.columns()...).
// From(r.table() + " AS a").
// Where("a.deleted_at IS NULL")
//
//}
//
//func (r attachment) FindByID(namespaceID, attachmentID uint64) (*types.Attachment, error) {
// return r.findOneBy(namespaceID, "id", attachmentID)
//}
//
//func (r attachment) findOneBy(namespaceID uint64, field string, value interface{}) (*types.Attachment, error) {
// var (
// p = &types.Attachment{}
//
// q = r.query().
// Where(squirrel.Eq{field: value, "rel_namespace": namespaceID})
//
// err = rh.FetchOne(r.db(), q, p)
// )
//
// if err != nil {
// return nil, err
// } else if p.ID == 0 {
// return nil, ErrAttachmentNotFound
// }
//
// return p, nil
//}
//
//func (r attachment) Find(filter types.AttachmentFilter) (set types.AttachmentSet, f types.AttachmentFilter, err error) {
// f = filter
//
// if f.Sort == "" {
// f.Sort = "id ASC"
// }
//
// query := r.query().
// Where(squirrel.Eq{"a.kind": f.Kind})
//
// if filter.NamespaceID > 0 {
// query = query.Where("a.rel_namespace = ?", filter.NamespaceID)
// }
//
// switch f.Kind {
// case types.PageAttachment:
// // @todo implement filtering by page
// if f.PageID > 0 {
// err = errors.New("filtering by pageID not implemented")
// return
// }
//
// case types.RecordAttachment:
// query = query.
// Join("compose_record_value AS v ON (v.ref = a.id)")
//
// if f.ModuleID > 0 {
// query = query.
// Join("compose_record AS r ON (r.id = v.record_id)").
// Where(squirrel.Eq{"r.module_id": f.ModuleID})
// }
//
// if f.RecordID > 0 {
// query = query.Where(squirrel.Eq{"v.record_id": f.RecordID})
// }
//
// if f.FieldName != "" {
// query = query.Where(squirrel.Eq{"v.name": f.FieldName})
// }
//
// default:
// err = errors.New("unsupported kind value")
// return
// }
//
// if f.Filter != "" {
// err = errors.New("filtering by filter not implemented")
// return
// }
//
// var orderBy []string
// if orderBy, err = rh.ParseOrder(f.Sort, r.columns()...); err != nil {
// return
// } else {
// query = query.OrderBy(orderBy...)
// }
//
// if f.Count, err = rh.Count(r.db(), query); err != nil || f.Count == 0 {
// return
// }
//
// return set, f, rh.FetchPaged(r.db(), query, f.PageFilter, &set)
//}
//
//func (r attachment) Create(mod *types.Attachment) (*types.Attachment, error) {
// if mod.ID == 0 {
// mod.ID = factory.Sonyflake.NextID()
// }
//
// mod.CreatedAt = time.Now()
//
// return mod, r.db().Insert(r.table(), mod)
//}
//
//func (r attachment) 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
//}

View File

@@ -3,23 +3,21 @@ package service
import (
"bytes"
"context"
"github.com/cortezaproject/corteza-server/compose/types"
"github.com/cortezaproject/corteza-server/pkg/actionlog"
"github.com/cortezaproject/corteza-server/pkg/auth"
"github.com/cortezaproject/corteza-server/pkg/id"
files "github.com/cortezaproject/corteza-server/pkg/store"
"github.com/cortezaproject/corteza-server/store"
"github.com/disintegration/imaging"
"github.com/edwvee/exiffix"
"github.com/pkg/errors"
"image"
"image/gif"
"io"
"net/http"
"path"
"strings"
"github.com/disintegration/imaging"
"github.com/edwvee/exiffix"
"github.com/pkg/errors"
"github.com/titpetric/factory"
"github.com/cortezaproject/corteza-server/compose/repository"
"github.com/cortezaproject/corteza-server/compose/types"
"github.com/cortezaproject/corteza-server/pkg/actionlog"
"github.com/cortezaproject/corteza-server/pkg/auth"
"github.com/cortezaproject/corteza-server/pkg/store"
)
const (
@@ -29,21 +27,11 @@ const (
type (
attachment struct {
db *factory.DB
ctx context.Context
ctx context.Context
actionlog actionlog.Recorder
store store.Store
ac attachmentAccessController
pageRepo repository.PageRepository
moduleRepo repository.ModuleRepository
recordRepo repository.RecordRepository
namespaceRepo repository.NamespaceRepository
attachmentRepo repository.AttachmentRepository
files files.Store
ac attachmentAccessController
store store.Storable
}
attachmentAccessController interface {
@@ -69,186 +57,183 @@ type (
}
)
func Attachment(store store.Store) AttachmentService {
func Attachment(store files.Store) AttachmentService {
return (&attachment{
store: store,
files: store,
ac: DefaultAccessControl,
}).With(context.Background())
}
func (svc attachment) With(ctx context.Context) AttachmentService {
db := repository.DB(ctx)
return &attachment{
db: db,
ctx: ctx,
ctx: ctx,
actionlog: DefaultActionlog,
ac: svc.ac,
store: svc.store,
attachmentRepo: repository.Attachment(ctx, db),
recordRepo: repository.Record(ctx, db),
pageRepo: repository.Page(ctx, db),
moduleRepo: repository.Module(ctx, db),
namespaceRepo: repository.Namespace(ctx, db),
ac: svc.ac,
files: svc.files,
}
}
func (svc attachment) FindByID(namespaceID, attachmentID uint64) (att *types.Attachment, err error) {
var (
aProps = &attachmentActionProps{}
)
err = svc.db.Transaction(func() error {
if attachmentID == 0 {
return AttachmentErrInvalidID()
}
if att, err = svc.attachmentRepo.FindByID(namespaceID, attachmentID); err != nil {
return err
}
aProps.setAttachment(att)
return nil
})
return att, svc.recordAction(svc.ctx, aProps, AttachmentActionLookup, err)
}
func (svc attachment) DeleteByID(namespaceID, attachmentID uint64) (err error) {
var (
aProps = &attachmentActionProps{attachment: &types.Attachment{ID: attachmentID}}
)
err = svc.db.Transaction(func() (err error) {
if attachmentID == 0 {
return AttachmentErrInvalidID()
}
if aProps.attachment, err = svc.attachmentRepo.FindByID(namespaceID, attachmentID); err != nil {
return err
}
return svc.attachmentRepo.DeleteByID(namespaceID, attachmentID)
})
return svc.recordAction(svc.ctx, aProps, AttachmentActionDelete, err)
}
func (svc attachment) Find(filter types.AttachmentFilter) (set types.AttachmentSet, f types.AttachmentFilter, err error) {
var (
aProps = &attachmentActionProps{filter: &filter}
)
err = svc.db.Transaction(func() error {
err = func() error {
if filter.NamespaceID == 0 {
return AttachmentErrInvalidNamespaceID()
}
if filter.PageID > 0 {
if aProps.page, err = svc.findPageByID(filter.NamespaceID, filter.PageID); err != nil {
aProps.namespace, aProps.page, err = loadPage(svc.ctx, svc.store, filter.NamespaceID, filter.PageID)
if err != nil {
return err
} else if svc.ac.CanReadPage(svc.ctx, aProps.page) {
return AttachmentErrNotAllowedToReadPage()
}
}
if filter.ModuleID > 0 {
var m *types.Module
if m, err = svc.findModuleByID(filter.NamespaceID, filter.ModuleID); err != nil {
if filter.RecordID > 0 {
aProps.namespace, aProps.module, aProps.record, err = loadRecordCombo(svc.ctx, svc.store, filter.NamespaceID, filter.ModuleID, filter.RecordID)
if err != nil {
return err
} else if svc.ac.CanReadRecord(svc.ctx, aProps.module) {
return AttachmentErrNotAllowedToReadRecord()
}
aProps.setModule(m)
if filter.RecordID > 0 {
if aProps.record, err = svc.findRecordByID(m, filter.RecordID); err != nil {
return err
}
} else if filter.ModuleID > 0 {
aProps.namespace, aProps.module, err = loadModuleWithNamespace(svc.ctx, svc.store, filter.NamespaceID, filter.ModuleID)
if err != nil {
return err
} else if svc.ac.CanReadRecord(svc.ctx, aProps.module) {
return AttachmentErrNotAllowedToReadRecord()
}
} else if filter.RecordID > 0 {
return AttachmentErrInvalidModuleID()
}
set, f, err = svc.attachmentRepo.Find(filter)
set, f, err = store.SearchComposeAttachments(svc.ctx, svc.store, f)
return err
})
}()
return set, f, svc.recordAction(svc.ctx, aProps, AttachmentActionSearch, err)
}
func (svc attachment) findNamespaceByID(namespaceID uint64) (ns *types.Namespace, err error) {
if namespaceID == 0 {
return nil, AttachmentErrInvalidNamespaceID()
}
func (svc attachment) FindByID(namespaceID, attachmentID uint64) (att *types.Attachment, err error) {
var (
aProps = &attachmentActionProps{}
)
ns, err = svc.namespaceRepo.FindByID(namespaceID)
if repository.ErrNamespaceNotFound.Eq(err) {
return nil, AttachmentErrNamespaceNotFound()
}
err = func() error {
if attachmentID == 0 {
return AttachmentErrInvalidID()
}
if !svc.ac.CanReadNamespace(svc.ctx, ns) {
return nil, AttachmentErrNotAllowedToReadNamespace()
}
if att, err = store.LookupComposeAttachmentByID(svc.ctx, svc.store, attachmentID); err != nil {
return err
}
return ns, nil
aProps.setAttachment(att)
return nil
}()
return att, svc.recordAction(svc.ctx, aProps, AttachmentActionLookup, err)
}
func (svc attachment) findPageByID(namespaceID, pageID uint64) (p *types.Page, err error) {
if pageID == 0 {
return nil, AttachmentErrInvalidPageID()
}
func (svc attachment) DeleteByID(namespaceID, attachmentID uint64) (err error) {
var (
att *types.Attachment
aProps = &attachmentActionProps{attachment: &types.Attachment{ID: attachmentID}}
)
p, err = svc.pageRepo.FindByID(namespaceID, pageID)
if repository.ErrPageNotFound.Eq(err) {
return nil, AttachmentErrPageNotFound()
}
err = store.Tx(svc.ctx, svc.store, func(ctx context.Context, s store.Storable) (err error) {
if attachmentID == 0 {
return AttachmentErrInvalidID()
}
if !svc.ac.CanReadPage(svc.ctx, p) {
return nil, AttachmentErrNotAllowedToReadPage()
}
if att, err = store.LookupComposeAttachmentByID(ctx, s, attachmentID); err != nil {
return err
}
return p, nil
aProps.setAttachment(att)
att.DeletedAt = nowPtr()
return store.UpdateComposeAttachment(ctx, s, att)
})
return svc.recordAction(svc.ctx, aProps, AttachmentActionDelete, err)
}
func (svc attachment) findModuleByID(namespaceID, moduleID uint64) (m *types.Module, err error) {
if moduleID == 0 {
return nil, AttachmentErrInvalidModuleID()
}
m, err = svc.moduleRepo.FindByID(namespaceID, moduleID)
if repository.ErrModuleNotFound.Eq(err) {
return nil, AttachmentErrModuleNotFound()
}
if !svc.ac.CanReadModule(svc.ctx, m) {
return nil, AttachmentErrNotAllowedToReadModule()
}
return m, nil
}
func (svc attachment) findRecordByID(m *types.Module, recordID uint64) (r *types.Record, err error) {
if recordID == 0 {
return nil, AttachmentErrInvalidRecordID()
}
r, err = svc.recordRepo.FindByID(m.NamespaceID, recordID)
if repository.ErrRecordNotFound.Eq(err) {
return nil, AttachmentErrRecordNotFound()
}
if !svc.ac.CanReadRecord(svc.ctx, m) {
return nil, AttachmentErrNotAllowedToReadRecord()
}
return r, nil
}
//func (svc attachment) findNamespaceByID(namespaceID uint64) (ns *types.Namespace, err error) {
// if namespaceID == 0 {
// return nil, AttachmentErrInvalidNamespaceID()
// }
//
// ns, err = svc.namespaceRepo.FindByID(namespaceID)
// if repository.ErrNamespaceNotFound.Eq(err) {
// return nil, AttachmentErrNamespaceNotFound()
// }
//
// if !svc.ac.CanReadNamespace(svc.ctx, ns) {
// return nil, AttachmentErrNotAllowedToReadNamespace()
// }
//
// return ns, nil
//}
//
//func (svc attachment) findPageByID(namespaceID, pageID uint64) (p *types.Page, err error) {
// if pageID == 0 {
// return nil, AttachmentErrInvalidPageID()
// }
//
// p, err = svc.pageRepo.FindByID(namespaceID, pageID)
// if repository.ErrPageNotFound.Eq(err) {
// return nil, AttachmentErrPageNotFound()
// }
//
// if !svc.ac.CanReadPage(svc.ctx, p) {
// return nil, AttachmentErrNotAllowedToReadPage()
// }
//
// return p, nil
//}
//
//func (svc attachment) findModuleByID(namespaceID, moduleID uint64) (m *types.Module, err error) {
// if moduleID == 0 {
// return nil, AttachmentErrInvalidModuleID()
// }
//
// m, err = svc.moduleRepo.FindByID(namespaceID, moduleID)
// if repository.ErrModuleNotFound.Eq(err) {
// return nil, AttachmentErrModuleNotFound()
// }
//
// if !svc.ac.CanReadModule(svc.ctx, m) {
// return nil, AttachmentErrNotAllowedToReadModule()
// }
//
// return m, nil
//}
//
//func (svc attachment) findRecordByID(m *types.Module, recordID uint64) (r *types.Record, err error) {
// if recordID == 0 {
// return nil, AttachmentErrInvalidRecordID()
// }
//
// r, err = svc.recordRepo.FindByID(m.NamespaceID, recordID)
// if repository.ErrRecordNotFound.Eq(err) {
// return nil, AttachmentErrRecordNotFound()
// }
//
// if !svc.ac.CanReadRecord(svc.ctx, m) {
// return nil, AttachmentErrNotAllowedToReadRecord()
// }
//
// return r, nil
//}
func (svc attachment) OpenOriginal(att *types.Attachment) (io.ReadSeeker, error) {
if len(att.Url) == 0 {
return nil, nil
}
return svc.store.Open(att.Url)
return svc.files.Open(att.Url)
}
func (svc attachment) OpenPreview(att *types.Attachment) (io.ReadSeeker, error) {
@@ -256,7 +241,7 @@ func (svc attachment) OpenPreview(att *types.Attachment) (io.ReadSeeker, error)
return nil, nil
}
return svc.store.Open(att.PreviewUrl)
return svc.files.Open(att.PreviewUrl)
}
func (svc attachment) CreatePageAttachment(namespaceID uint64, name string, size int64, fh io.ReadSeeker, pageID uint64) (att *types.Attachment, err error) {
@@ -270,19 +255,13 @@ func (svc attachment) CreatePageAttachment(namespaceID uint64, name string, size
}
)
err = svc.db.Transaction(func() error {
ns, err = svc.findNamespaceByID(namespaceID)
err = func() error {
ns, p, err = loadPage(svc.ctx, svc.store, namespaceID, pageID)
if err != nil {
return err
}
aProps.setNamespace(ns)
p, err = svc.findPageByID(namespaceID, pageID)
if err != nil {
return err
}
aProps.setPage(p)
if !svc.ac.CanUpdatePage(svc.ctx, p) {
@@ -296,7 +275,7 @@ func (svc attachment) CreatePageAttachment(namespaceID uint64, name string, size
}
return svc.create(name, size, fh, att)
})
}()
return att, svc.recordAction(svc.ctx, aProps, AttachmentActionCreate, err)
@@ -314,19 +293,13 @@ func (svc attachment) CreateRecordAttachment(namespaceID uint64, name string, si
}
)
err = svc.db.Transaction(func() (err error) {
ns, err = svc.findNamespaceByID(namespaceID)
err = store.Tx(svc.ctx, svc.store, func(ctx context.Context, s store.Storable) (err error) {
ns, m, err = loadModuleWithNamespace(ctx, s, namespaceID, moduleID)
if err != nil {
return err
}
aProps.setNamespace(ns)
m, err = svc.findModuleByID(namespaceID, moduleID)
if err != nil {
return err
}
aProps.setModule(m)
if recordID > 0 {
@@ -335,14 +308,14 @@ func (svc attachment) CreateRecordAttachment(namespaceID uint64, name string, si
// To allow upload (attachment creation) user must have permissions to
// alter that record
r, err = svc.findRecordByID(m, recordID)
r, err = store.LookupComposeRecordByID(ctx, s, m, recordID)
if err != nil {
return err
}
aProps.setRecord(r)
if !svc.ac.CanUpdateRecord(svc.ctx, m) {
if !svc.ac.CanUpdateRecord(ctx, m) {
return AttachmentErrNotAllowedToUpdateRecord()
}
} else {
@@ -350,7 +323,7 @@ func (svc attachment) CreateRecordAttachment(namespaceID uint64, name string, si
//
// To allow upload (attachment creation) user must have permissions to
// create records
if !svc.ac.CanCreateRecord(svc.ctx, m) {
if !svc.ac.CanCreateRecord(ctx, m) {
return AttachmentErrNotAllowedToCreateRecords()
}
}
@@ -365,7 +338,6 @@ func (svc attachment) CreateRecordAttachment(namespaceID uint64, name string, si
})
return att, svc.recordAction(svc.ctx, aProps, AttachmentActionCreate, err)
}
func (svc attachment) create(name string, size int64, fh io.ReadSeeker, att *types.Attachment) (err error) {
@@ -374,13 +346,13 @@ func (svc attachment) create(name string, size int64, fh io.ReadSeeker, att *typ
)
// preset attachment ID because we need ref for storage
att.ID = factory.Sonyflake.NextID()
att.ID = id.Next()
if att.OwnerID == 0 {
att.OwnerID = auth.GetIdentityFromContext(svc.ctx).Identity()
}
if svc.store == nil {
if svc.files == nil {
return errors.New("can not create attachment: store handler not set")
}
@@ -395,10 +367,10 @@ func (svc attachment) create(name string, size int64, fh io.ReadSeeker, att *typ
return AttachmentErrFailedToExtractMimeType(aProps).Wrap(err)
}
att.Url = svc.store.Original(att.ID, att.Meta.Original.Extension)
att.Url = svc.files.Original(att.ID, att.Meta.Original.Extension)
aProps.setUrl(att.Url)
if err = svc.store.Save(att.Url, fh); err != nil {
if err = svc.files.Save(att.Url, fh); err != nil {
return AttachmentErrFailedToStoreFile(aProps).Wrap(err)
}
@@ -408,7 +380,7 @@ func (svc attachment) create(name string, size int64, fh io.ReadSeeker, att *typ
return AttachmentErrFailedToProcessImage(aProps).Wrap(err)
}
if att, err = svc.attachmentRepo.Create(att); err != nil {
if err = store.CreateComposeAttachment(svc.ctx, svc.store, att); err != nil {
return
}
@@ -525,9 +497,9 @@ func (svc attachment) processImage(original io.ReadSeeker, att *types.Attachment
meta.Extension = f2e[previewFormat]
// Can and how we make a preview of this attachment?
att.PreviewUrl = svc.store.Preview(att.ID, meta.Extension)
att.PreviewUrl = svc.files.Preview(att.ID, meta.Extension)
return svc.store.Save(att.PreviewUrl, buf)
return svc.files.Save(att.PreviewUrl, buf)
}
var _ AttachmentService = &attachment{}

View File

@@ -622,7 +622,7 @@ func (svc record) update(upd *types.Record) (rec *types.Record, err error) {
return nil, RecordErrInvalidID()
}
ns, m, old, err = svc.loadRecordCombo(svc.ctx, svc.store, upd.NamespaceID, upd.ModuleID, upd.ID)
ns, m, old, err = loadRecordCombo(svc.ctx, svc.store, upd.NamespaceID, upd.ModuleID, upd.ID)
if err != nil {
return
}
@@ -848,7 +848,7 @@ func (svc record) delete(namespaceID, moduleID, recordID uint64) (del *types.Rec
return nil, RecordErrInvalidID()
}
ns, m, del, err = svc.loadRecordCombo(svc.ctx, svc.store, namespaceID, moduleID, recordID)
ns, m, del, err = loadRecordCombo(svc.ctx, svc.store, namespaceID, moduleID, recordID)
if err != nil {
return nil, err
}
@@ -969,7 +969,7 @@ func (svc record) Organize(namespaceID, moduleID, recordID uint64, posField, pos
)
err = func() error {
ns, m, r, err = svc.loadRecordCombo(svc.ctx, svc.store, namespaceID, moduleID, recordID)
ns, m, r, err = loadRecordCombo(svc.ctx, svc.store, namespaceID, moduleID, recordID)
if err != nil {
return err
}
@@ -1368,7 +1368,7 @@ func (svc record) readableFields(m *types.Module) []string {
}
// loadRecordCombo Loads namespace, module and record
func (svc record) loadRecordCombo(ctx context.Context, s store.Storable, namespaceID, moduleID, recordID uint64) (ns *types.Namespace, m *types.Module, r *types.Record, err error) {
func loadRecordCombo(ctx context.Context, s store.Storable, namespaceID, moduleID, recordID uint64) (ns *types.Namespace, m *types.Module, r *types.Record, err error) {
if ns, m, err = loadModuleWithNamespace(ctx, s, namespaceID, moduleID); err != nil {
return
}

View File

@@ -6,8 +6,6 @@ import (
"time"
"github.com/pkg/errors"
"github.com/cortezaproject/corteza-server/pkg/rh"
)
type (
@@ -39,9 +37,6 @@ type (
Sort string `json:"sort"`
// Standard helpers for paging and sorting
rh.PageFilter
// Check fn is called by store backend for each resource found function can
// modify the resource and return false if store should not return it
//

View File

@@ -0,0 +1,83 @@
package store
// This file is auto-generated.
//
// Template: pkg/codegen/assets/store_base.gen.go.tpl
// Definitions: store/compose_attachments.yaml
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
import (
"context"
"github.com/cortezaproject/corteza-server/compose/types"
)
type (
ComposeAttachments interface {
SearchComposeAttachments(ctx context.Context, f types.AttachmentFilter) (types.AttachmentSet, types.AttachmentFilter, error)
LookupComposeAttachmentByID(ctx context.Context, id uint64) (*types.Attachment, error)
CreateComposeAttachment(ctx context.Context, rr ...*types.Attachment) error
UpdateComposeAttachment(ctx context.Context, rr ...*types.Attachment) error
PartialComposeAttachmentUpdate(ctx context.Context, onlyColumns []string, rr ...*types.Attachment) error
UpsertComposeAttachment(ctx context.Context, rr ...*types.Attachment) error
DeleteComposeAttachment(ctx context.Context, rr ...*types.Attachment) error
DeleteComposeAttachmentByID(ctx context.Context, ID uint64) error
TruncateComposeAttachments(ctx context.Context) error
}
)
var _ *types.Attachment
var _ context.Context
// SearchComposeAttachments returns all matching ComposeAttachments from store
func SearchComposeAttachments(ctx context.Context, s ComposeAttachments, f types.AttachmentFilter) (types.AttachmentSet, types.AttachmentFilter, error) {
return s.SearchComposeAttachments(ctx, f)
}
// LookupComposeAttachmentByID searches for attachment by its ID
//
// It returns attachment even if deleted
func LookupComposeAttachmentByID(ctx context.Context, s ComposeAttachments, id uint64) (*types.Attachment, error) {
return s.LookupComposeAttachmentByID(ctx, id)
}
// CreateComposeAttachment creates one or more ComposeAttachments in store
func CreateComposeAttachment(ctx context.Context, s ComposeAttachments, rr ...*types.Attachment) error {
return s.CreateComposeAttachment(ctx, rr...)
}
// UpdateComposeAttachment updates one or more (existing) ComposeAttachments in store
func UpdateComposeAttachment(ctx context.Context, s ComposeAttachments, rr ...*types.Attachment) error {
return s.UpdateComposeAttachment(ctx, rr...)
}
// PartialComposeAttachmentUpdate updates one or more existing ComposeAttachments in store
func PartialComposeAttachmentUpdate(ctx context.Context, s ComposeAttachments, onlyColumns []string, rr ...*types.Attachment) error {
return s.PartialComposeAttachmentUpdate(ctx, onlyColumns, rr...)
}
// UpsertComposeAttachment creates new or updates existing one or more ComposeAttachments in store
func UpsertComposeAttachment(ctx context.Context, s ComposeAttachments, rr ...*types.Attachment) error {
return s.UpsertComposeAttachment(ctx, rr...)
}
// DeleteComposeAttachment Deletes one or more ComposeAttachments from store
func DeleteComposeAttachment(ctx context.Context, s ComposeAttachments, rr ...*types.Attachment) error {
return s.DeleteComposeAttachment(ctx, rr...)
}
// DeleteComposeAttachmentByID Deletes ComposeAttachment from store
func DeleteComposeAttachmentByID(ctx context.Context, s ComposeAttachments, ID uint64) error {
return s.DeleteComposeAttachmentByID(ctx, ID)
}
// TruncateComposeAttachments Deletes all ComposeAttachments from store
func TruncateComposeAttachments(ctx context.Context, s ComposeAttachments) error {
return s.TruncateComposeAttachments(ctx)
}

View File

@@ -0,0 +1,34 @@
import:
- github.com/cortezaproject/corteza-server/compose/types
types:
type: types.Attachment
fields:
- { field: ID }
- { field: NamespaceID }
- { field: Kind }
- { field: Url }
- { field: PreviewUrl }
- { field: Name }
- { field: Meta }
- { field: OwnerID }
- { field: CreatedAt }
- { field: UpdatedAt }
- { field: DeletedAt }
lookups:
- fields: [ ID ]
description: |-
searches for attachment by its ID
It returns attachment even if deleted
search:
enableSorting: false
enablePaging: false
rdbms:
alias: att
table: compose_attachment
customFilterConverter: true

View File

@@ -7,6 +7,7 @@ package store
// - store/actionlog.yaml
// - store/applications.yaml
// - store/attachments.yaml
// - store/compose_attachments.yaml
// - store/compose_charts.yaml
// - store/compose_module_fields.yaml
// - store/compose_modules.yaml
@@ -42,6 +43,7 @@ type (
Actionlogs
Applications
Attachments
ComposeAttachments
ComposeCharts
ComposeModuleFields
ComposeModules

View File

@@ -0,0 +1,370 @@
package rdbms
// This file is an auto-generated file
//
// Template: pkg/codegen/assets/store_rdbms.gen.go.tpl
// Definitions: store/compose_attachments.yaml
//
// Changes to this file may cause incorrect behavior
// and will be lost if the code is regenerated.
import (
"context"
"database/sql"
"errors"
"fmt"
"github.com/Masterminds/squirrel"
"github.com/cortezaproject/corteza-server/compose/types"
"github.com/cortezaproject/corteza-server/store"
)
var _ = errors.Is
const (
TriggerBeforeComposeAttachmentCreate triggerKey = "composeAttachmentBeforeCreate"
TriggerBeforeComposeAttachmentUpdate triggerKey = "composeAttachmentBeforeUpdate"
TriggerBeforeComposeAttachmentUpsert triggerKey = "composeAttachmentBeforeUpsert"
TriggerBeforeComposeAttachmentDelete triggerKey = "composeAttachmentBeforeDelete"
)
// SearchComposeAttachments returns all matching rows
//
// This function calls convertComposeAttachmentFilter with the given
// types.AttachmentFilter and expects to receive a working squirrel.SelectBuilder
func (s Store) SearchComposeAttachments(ctx context.Context, f types.AttachmentFilter) (types.AttachmentSet, types.AttachmentFilter, error) {
var scap uint
q, err := s.convertComposeAttachmentFilter(f)
if err != nil {
return nil, f, err
}
if scap == 0 {
scap = DefaultSliceCapacity
}
var (
set = make([]*types.Attachment, 0, scap)
// Paging is disabled in definition yaml file
// {search: {enablePaging:false}} and this allows
// a much simpler row fetching logic
fetch = func() error {
var (
res *types.Attachment
rows, err = s.Query(ctx, q)
)
if err != nil {
return err
}
for rows.Next() {
if rows.Err() == nil {
res, err = s.internalComposeAttachmentRowScanner(rows)
}
if err != nil {
if cerr := rows.Close(); cerr != nil {
err = fmt.Errorf("could not close rows (%v) after scan error: %w", cerr, err)
}
return err
}
// If check function is set, call it and act accordingly
if f.Check != nil {
if chk, err := f.Check(res); err != nil {
if cerr := rows.Close(); cerr != nil {
err = fmt.Errorf("could not close rows (%v) after check error: %w", cerr, err)
}
return err
} else if !chk {
// did not pass the check
// go with the next row
continue
}
}
set = append(set, res)
}
return rows.Close()
}
)
return set, f, s.config.ErrorHandler(fetch())
}
// LookupComposeAttachmentByID searches for attachment by its ID
//
// It returns attachment even if deleted
func (s Store) LookupComposeAttachmentByID(ctx context.Context, id uint64) (*types.Attachment, error) {
return s.execLookupComposeAttachment(ctx, squirrel.Eq{
s.preprocessColumn("att.id", ""): s.preprocessValue(id, ""),
})
}
// CreateComposeAttachment creates one or more rows in compose_attachment table
func (s Store) CreateComposeAttachment(ctx context.Context, rr ...*types.Attachment) (err error) {
for _, res := range rr {
err = s.checkComposeAttachmentConstraints(ctx, res)
if err != nil {
return err
}
// err = s.composeAttachmentHook(ctx, TriggerBeforeComposeAttachmentCreate, res)
// if err != nil {
// return err
// }
err = s.execCreateComposeAttachments(ctx, s.internalComposeAttachmentEncoder(res))
if err != nil {
return err
}
}
return
}
// UpdateComposeAttachment updates one or more existing rows in compose_attachment
func (s Store) UpdateComposeAttachment(ctx context.Context, rr ...*types.Attachment) error {
return s.config.ErrorHandler(s.PartialComposeAttachmentUpdate(ctx, nil, rr...))
}
// PartialComposeAttachmentUpdate updates one or more existing rows in compose_attachment
func (s Store) PartialComposeAttachmentUpdate(ctx context.Context, onlyColumns []string, rr ...*types.Attachment) (err error) {
for _, res := range rr {
err = s.checkComposeAttachmentConstraints(ctx, res)
if err != nil {
return err
}
// err = s.composeAttachmentHook(ctx, TriggerBeforeComposeAttachmentUpdate, res)
// if err != nil {
// return err
// }
err = s.execUpdateComposeAttachments(
ctx,
squirrel.Eq{
s.preprocessColumn("att.id", ""): s.preprocessValue(res.ID, ""),
},
s.internalComposeAttachmentEncoder(res).Skip("id").Only(onlyColumns...))
if err != nil {
return s.config.ErrorHandler(err)
}
}
return
}
// UpsertComposeAttachment updates one or more existing rows in compose_attachment
func (s Store) UpsertComposeAttachment(ctx context.Context, rr ...*types.Attachment) (err error) {
for _, res := range rr {
err = s.checkComposeAttachmentConstraints(ctx, res)
if err != nil {
return err
}
// err = s.composeAttachmentHook(ctx, TriggerBeforeComposeAttachmentUpsert, res)
// if err != nil {
// return err
// }
err = s.config.ErrorHandler(s.execUpsertComposeAttachments(ctx, s.internalComposeAttachmentEncoder(res)))
if err != nil {
return err
}
}
return nil
}
// DeleteComposeAttachment Deletes one or more rows from compose_attachment table
func (s Store) DeleteComposeAttachment(ctx context.Context, rr ...*types.Attachment) (err error) {
for _, res := range rr {
// err = s.composeAttachmentHook(ctx, TriggerBeforeComposeAttachmentDelete, res)
// if err != nil {
// return err
// }
err = s.execDeleteComposeAttachments(ctx, squirrel.Eq{
s.preprocessColumn("att.id", ""): s.preprocessValue(res.ID, ""),
})
if err != nil {
return s.config.ErrorHandler(err)
}
}
return nil
}
// DeleteComposeAttachmentByID Deletes row from the compose_attachment table
func (s Store) DeleteComposeAttachmentByID(ctx context.Context, ID uint64) error {
return s.execDeleteComposeAttachments(ctx, squirrel.Eq{
s.preprocessColumn("att.id", ""): s.preprocessValue(ID, ""),
})
}
// TruncateComposeAttachments Deletes all rows from the compose_attachment table
func (s Store) TruncateComposeAttachments(ctx context.Context) error {
return s.config.ErrorHandler(s.Truncate(ctx, s.composeAttachmentTable()))
}
// execLookupComposeAttachment prepares ComposeAttachment query and executes it,
// returning types.Attachment (or error)
func (s Store) execLookupComposeAttachment(ctx context.Context, cnd squirrel.Sqlizer) (res *types.Attachment, err error) {
var (
row rowScanner
)
row, err = s.QueryRow(ctx, s.composeAttachmentsSelectBuilder().Where(cnd))
if err != nil {
return
}
res, err = s.internalComposeAttachmentRowScanner(row)
if err != nil {
return
}
return res, nil
}
// execCreateComposeAttachments updates all matched (by cnd) rows in compose_attachment with given data
func (s Store) execCreateComposeAttachments(ctx context.Context, payload store.Payload) error {
return s.config.ErrorHandler(s.Exec(ctx, s.InsertBuilder(s.composeAttachmentTable()).SetMap(payload)))
}
// execUpdateComposeAttachments updates all matched (by cnd) rows in compose_attachment with given data
func (s Store) execUpdateComposeAttachments(ctx context.Context, cnd squirrel.Sqlizer, set store.Payload) error {
return s.config.ErrorHandler(s.Exec(ctx, s.UpdateBuilder(s.composeAttachmentTable("att")).Where(cnd).SetMap(set)))
}
// execUpsertComposeAttachments inserts new or updates matching (by-primary-key) rows in compose_attachment with given data
func (s Store) execUpsertComposeAttachments(ctx context.Context, set store.Payload) error {
upsert, err := s.config.UpsertBuilder(
s.config,
s.composeAttachmentTable(),
set,
"id",
)
if err != nil {
return err
}
return s.config.ErrorHandler(s.Exec(ctx, upsert))
}
// execDeleteComposeAttachments Deletes all matched (by cnd) rows in compose_attachment with given data
func (s Store) execDeleteComposeAttachments(ctx context.Context, cnd squirrel.Sqlizer) error {
return s.config.ErrorHandler(s.Exec(ctx, s.DeleteBuilder(s.composeAttachmentTable("att")).Where(cnd)))
}
func (s Store) internalComposeAttachmentRowScanner(row rowScanner) (res *types.Attachment, err error) {
res = &types.Attachment{}
if _, has := s.config.RowScanners["composeAttachment"]; has {
scanner := s.config.RowScanners["composeAttachment"].(func(_ rowScanner, _ *types.Attachment) error)
err = scanner(row, res)
} else {
err = row.Scan(
&res.ID,
&res.NamespaceID,
&res.Kind,
&res.Url,
&res.PreviewUrl,
&res.Name,
&res.Meta,
&res.OwnerID,
&res.CreatedAt,
&res.UpdatedAt,
&res.DeletedAt,
)
}
if err == sql.ErrNoRows {
return nil, store.ErrNotFound
}
if err != nil {
return nil, fmt.Errorf("could not scan db row for ComposeAttachment: %w", err)
} else {
return res, nil
}
}
// QueryComposeAttachments returns squirrel.SelectBuilder with set table and all columns
func (s Store) composeAttachmentsSelectBuilder() squirrel.SelectBuilder {
return s.SelectBuilder(s.composeAttachmentTable("att"), s.composeAttachmentColumns("att")...)
}
// composeAttachmentTable name of the db table
func (Store) composeAttachmentTable(aa ...string) string {
var alias string
if len(aa) > 0 {
alias = " AS " + aa[0]
}
return "compose_attachment" + alias
}
// ComposeAttachmentColumns returns all defined table columns
//
// With optional string arg, all columns are returned aliased
func (Store) composeAttachmentColumns(aa ...string) []string {
var alias string
if len(aa) > 0 {
alias = aa[0] + "."
}
return []string{
alias + "id",
alias + "rel_namespace",
alias + "kind",
alias + "url",
alias + "preview_url",
alias + "name",
alias + "meta",
alias + "rel_owner",
alias + "created_at",
alias + "updated_at",
alias + "deleted_at",
}
}
// {true true false false true}
// internalComposeAttachmentEncoder encodes fields from types.Attachment to store.Payload (map)
//
// Encoding is done by using generic approach or by calling encodeComposeAttachment
// func when rdbms.customEncoder=true
func (s Store) internalComposeAttachmentEncoder(res *types.Attachment) store.Payload {
return store.Payload{
"id": res.ID,
"rel_namespace": res.NamespaceID,
"kind": res.Kind,
"url": res.Url,
"preview_url": res.PreviewUrl,
"name": res.Name,
"meta": res.Meta,
"rel_owner": res.OwnerID,
"created_at": res.CreatedAt,
"updated_at": res.UpdatedAt,
"deleted_at": res.DeletedAt,
}
}
func (s *Store) checkComposeAttachmentConstraints(ctx context.Context, res *types.Attachment) error {
return nil
}
// func (s *Store) composeAttachmentHook(ctx context.Context, key triggerKey, res *types.Attachment) error {
// if fn, has := s.config.TriggerHandlers[key]; has {
// return fn.(func (ctx context.Context, s *Store, res *types.Attachment) error)(ctx, s, res)
// }
//
// return nil
// }

View File

@@ -0,0 +1,57 @@
package rdbms
import (
"fmt"
"github.com/Masterminds/squirrel"
"github.com/cortezaproject/corteza-server/compose/types"
)
func (s Store) convertComposeAttachmentFilter(f types.AttachmentFilter) (query squirrel.SelectBuilder, err error) {
query = s.attachmentsSelectBuilder()
if f.Kind != "" {
query = query.Where(squirrel.Eq{"att.kind": f.Kind})
}
if f.NamespaceID > 0 {
query = query.Where("a.rel_namespace = ?", f.NamespaceID)
}
switch f.Kind {
case types.PageAttachment:
// @todo implement filtering by page
if f.PageID > 0 {
err = fmt.Errorf("filtering by pageID not implemented")
return
}
case types.RecordAttachment:
query = query.
Join("compose_record_value AS v ON (v.ref = a.id)")
if f.ModuleID > 0 {
query = query.
Join("compose_record AS r ON (r.id = v.record_id)").
Where(squirrel.Eq{"r.module_id": f.ModuleID})
}
if f.RecordID > 0 {
query = query.Where(squirrel.Eq{"v.record_id": f.RecordID})
}
if f.FieldName != "" {
query = query.Where(squirrel.Eq{"v.name": f.FieldName})
}
default:
err = fmt.Errorf("unsupported kind value")
return
}
if f.Filter != "" {
err = fmt.Errorf("filtering by filter not implemented")
return
}
return
}

View File

@@ -0,0 +1,79 @@
package tests
import (
"context"
"github.com/cortezaproject/corteza-server/compose/types"
"github.com/cortezaproject/corteza-server/pkg/id"
"github.com/cortezaproject/corteza-server/store"
_ "github.com/joho/godotenv/autoload"
"github.com/stretchr/testify/require"
"strings"
"testing"
)
func testComposeAttachments(t *testing.T, s store.ComposeAttachments) {
var (
ctx = context.Background()
makeNew = func(nn ...string) *types.Attachment {
// minimum data set for new attachment
name := strings.Join(nn, "")
return &types.Attachment{
ID: id.Next(),
CreatedAt: *now(),
Name: "handle_" + name,
}
}
truncAndCreate = func(t *testing.T) (*require.Assertions, *types.Attachment) {
req := require.New(t)
req.NoError(s.TruncateComposeAttachments(ctx))
res := makeNew()
req.NoError(s.CreateComposeAttachment(ctx, res))
return req, res
}
)
t.Run("create", func(t *testing.T) {
req := require.New(t)
attachment := &types.Attachment{
ID: id.Next(),
CreatedAt: *now(),
}
req.NoError(s.CreateComposeAttachment(ctx, attachment))
})
t.Run("lookup by ID", func(t *testing.T) {
req, att := truncAndCreate(t)
fetched, err := s.LookupComposeAttachmentByID(ctx, att.ID)
req.NoError(err)
req.Equal(att.ID, fetched.ID)
req.NotNil(fetched.CreatedAt)
req.Nil(fetched.UpdatedAt)
req.Nil(fetched.DeletedAt)
})
t.Run("update", func(t *testing.T) {
req, att := truncAndCreate(t)
att.Url = "url"
req.NoError(s.UpdateComposeAttachment(ctx, att))
fetched, err := s.LookupComposeAttachmentByID(ctx, att.ID)
req.NoError(err)
req.Equal(att.ID, fetched.ID)
req.Equal("url", fetched.Url)
})
t.Run("search", func(t *testing.T) {
t.Skip("not implemented")
})
t.Run("search by *", func(t *testing.T) {
t.Skip("not implemented")
})
t.Run("ordered search", func(t *testing.T) {
t.Skip("not implemented")
})
}

View File

@@ -7,6 +7,7 @@ package tests
// - store/actionlog.yaml
// - store/applications.yaml
// - store/attachments.yaml
// - store/compose_attachments.yaml
// - store/compose_charts.yaml
// - store/compose_module_fields.yaml
// - store/compose_modules.yaml
@@ -46,6 +47,11 @@ func testAllGenerated(t *testing.T, s store.Storable) {
testAttachment(t, s)
})
// Run generated tests for ComposeAttachments
t.Run("ComposeAttachments", func(t *testing.T) {
testComposeAttachments(t, s)
})
// Run generated tests for ComposeCharts
t.Run("ComposeCharts", func(t *testing.T) {
testComposeCharts(t, s)