3
0
2019-11-08 16:24:02 +01:00

275 lines
6.5 KiB
Go

package repository
import (
"context"
"fmt"
"strings"
"github.com/Masterminds/squirrel"
"github.com/jmoiron/sqlx"
"github.com/pkg/errors"
"github.com/titpetric/factory"
"github.com/cortezaproject/corteza-server/compose/types"
"github.com/cortezaproject/corteza-server/pkg/rh"
)
type (
ModuleRepository interface {
With(ctx context.Context, db *factory.DB) ModuleRepository
FindByID(namespaceID, moduleID uint64) (*types.Module, error)
FindByName(namespaceID uint64, name string) (*types.Module, error)
FindByHandle(namespaceID uint64, handle string) (*types.Module, error)
Find(filter types.ModuleFilter) (set types.ModuleSet, f types.ModuleFilter, err error)
FindFields(moduleIDs ...uint64) (ff types.ModuleFieldSet, err error)
Create(mod *types.Module) (*types.Module, error)
Update(mod *types.Module) (*types.Module, error)
UpdateFields(moduleID uint64, ff types.ModuleFieldSet, hasRecords bool) (err error)
DeleteByID(namespaceID, moduleID uint64) error
}
module struct {
*repository
}
)
const (
ErrModuleNotFound = repositoryError("ModuleNotFound")
ErrModuleNameNotUnique = repositoryError("ModuleNameNotUnique")
ErrModuleHandleNotUnique = repositoryError("ModuleHandleNotUnique")
)
func Module(ctx context.Context, db *factory.DB) ModuleRepository {
return (&module{}).With(ctx, db)
}
func (r module) With(ctx context.Context, db *factory.DB) ModuleRepository {
return &module{
repository: r.repository.With(ctx, db),
}
}
func (r module) table() string {
return "compose_module"
}
func (r module) tableFields() string {
return "compose_module_field"
}
func (r module) columns() []string {
return []string{
"id",
"rel_namespace",
"handle",
"name",
"json",
"created_at",
"updated_at",
"deleted_at",
}
}
func (r module) query() squirrel.SelectBuilder {
return squirrel.
Select(r.columns()...).
From(r.table()).
Where("deleted_at IS NULL")
}
func (r module) FindByID(namespaceID, moduleID uint64) (*types.Module, error) {
return r.findOneBy(namespaceID, "id", moduleID)
}
func (r module) FindByHandle(namespaceID uint64, handle string) (*types.Module, error) {
return r.findOneBy(namespaceID, "LOWER(handle)", strings.ToLower(strings.TrimSpace(handle)))
}
func (r module) FindByName(namespaceID uint64, name string) (*types.Module, error) {
return r.findOneBy(namespaceID, "LOWER(name)", strings.ToLower(strings.TrimSpace(name)))
}
func (r module) findOneBy(namespaceID uint64, field string, value interface{}) (*types.Module, error) {
var (
m = &types.Module{}
q = r.query().
Where(squirrel.Eq{field: value, "rel_namespace": namespaceID})
err = rh.FetchOne(r.db(), q, m)
)
if err != nil {
return nil, err
} else if m.ID == 0 {
return nil, ErrModuleNotFound
}
return m, nil
}
func (r module) Find(filter types.ModuleFilter) (set types.ModuleSet, f types.ModuleFilter, err error) {
f = filter
if f.Sort == "" {
f.Sort = "id ASC"
}
query := r.query()
if filter.NamespaceID > 0 {
query = query.Where("rel_namespace = ?", filter.NamespaceID)
}
if f.Query != "" {
q := "%" + strings.ToLower(f.Query) + "%"
query = query.Where(squirrel.Or{
squirrel.Like{"LOWER(name)": q},
squirrel.Like{"LOWER(handle)": q},
})
}
if f.Name != "" {
query = query.Where(squirrel.Eq{"LOWER(name)": strings.ToLower(f.Name)})
}
if f.Handle != "" {
query = query.Where(squirrel.Eq{"LOWER(handle)": strings.ToLower(f.Handle)})
}
if f.IsReadable != nil {
query = query.Where(f.IsReadable)
}
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.Page, f.PerPage, &set)
}
func (r module) Create(mod *types.Module) (*types.Module, error) {
var err error
mod.ID = factory.Sonyflake.NextID()
rh.SetCurrentTimeRounded(&mod.CreatedAt)
mod.UpdatedAt = nil
if err = r.db().Insert(r.table(), mod); err != nil {
return nil, err
}
return mod, nil
}
func (r module) Update(mod *types.Module) (*types.Module, error) {
rh.SetCurrentTimeRounded(&mod.UpdatedAt)
return mod, r.db().Update(r.table(), mod, "id")
}
func (r module) UpdateFields(moduleID uint64, ff types.ModuleFieldSet, hasRecords bool) error {
if existing, err := r.FindFields(moduleID); err != nil {
return err
} else {
// Remove fields that do not exist anymore
err = existing.Walk(func(e *types.ModuleField) error {
if ff.FindByID(e.ID) == nil {
return r.deleteFieldByID(moduleID, e.ID)
}
return nil
})
if err != nil {
return err
}
for idx, f := range ff {
if e := existing.FindByID(f.ID); e != nil {
f.CreatedAt = e.CreatedAt
// We do not have any other code in place that would handle changes of field name and kind, so we need
// to reset any changes made to the field.
// @todo remove when we are able to handle field rename & type change
if hasRecords {
f.Name = e.Name
f.Kind = e.Kind
} else {
rh.SetCurrentTimeRounded(&f.UpdatedAt)
}
} else {
f.ID = 0
}
if f.ID == 0 {
f.ID = factory.Sonyflake.NextID()
rh.SetCurrentTimeRounded(&f.CreatedAt)
}
f.ModuleID = moduleID
f.Place = idx
f.DeletedAt = nil
if err := r.db().Replace(r.tableFields(), f); err != nil {
return errors.Wrap(err, "Error updating module fields")
}
}
}
return nil
}
func (r module) deleteFieldByID(moduleID, fieldID uint64) error {
_, err := r.db().Exec(
fmt.Sprintf("DELETE FROM %s WHERE rel_module = ? AND id = ?", r.tableFields()),
moduleID,
fieldID,
)
return err
}
func (r module) DeleteByID(namespaceID, moduleID uint64) error {
_, err := r.db().Exec(
fmt.Sprintf("UPDATE %s SET deleted_at = NOW() WHERE rel_namespace = ? AND id = ?", r.table()),
namespaceID,
moduleID,
)
return err
}
func (r module) FindFields(moduleIDs ...uint64) (ff types.ModuleFieldSet, err error) {
if len(moduleIDs) == 0 {
return
}
query := `SELECT id, rel_module, place,
kind, name, label, options,
is_private, is_required, is_visible, is_multi, default_value,
created_at, updated_at, deleted_at
FROM %s
WHERE rel_module IN (?)
AND deleted_at IS NULL
ORDER BY rel_module, place`
query = fmt.Sprintf(query, r.tableFields())
if sql, args, err := sqlx.In(query, moduleIDs); err != nil {
return nil, err
} else {
return ff, r.db().Select(&ff, sql, args...)
}
}