diff --git a/Makefile b/Makefile index 99dc6110d..d3fd6b278 100644 --- a/Makefile +++ b/Makefile @@ -141,7 +141,10 @@ watch.test.%: $(NODEMON) # codegen: $(PROTOGEN) codegen: $(CODEGEN) - ./build/codegen + $(CODEGEN) + +watch.codegen: $(CODEGEN) + $(CODEGEN) -w -v ######################################################################################################################## # Quality Assurance diff --git a/app/app.go b/app/app.go index a50bef1ad..ac1361d69 100644 --- a/app/app.go +++ b/app/app.go @@ -2,6 +2,7 @@ package app import ( "context" + "github.com/cortezaproject/corteza-server/store" "github.com/go-chi/chi" "github.com/spf13/cobra" "go.uber.org/zap" @@ -31,7 +32,7 @@ type ( // // Value will be type-casted when assigned to sys/msg/cmp services // with warnings when incompatible - Store interface{} + Store store.Storable // CLI Commands Command *cobra.Command diff --git a/compose/repository/chart.go b/compose/repository/chart.go index e2f4aa150..b3e64f062 100644 --- a/compose/repository/chart.go +++ b/compose/repository/chart.go @@ -1,163 +1,151 @@ package repository -import ( - "context" - "fmt" - "strings" - - "github.com/Masterminds/squirrel" - "github.com/titpetric/factory" - - "github.com/cortezaproject/corteza-server/compose/types" - "github.com/cortezaproject/corteza-server/pkg/rh" -) - -type ( - ChartRepository interface { - With(ctx context.Context, db *factory.DB) ChartRepository - - FindByID(namespaceID, chartID uint64) (*types.Chart, error) - FindByHandle(namespaceID uint64, handle string) (c *types.Chart, err 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(namespaceID, chartID uint64) error - } - - chart struct { - *repository - } -) - -const ( - ErrChartNotFound = repositoryError("ChartNotFound") - ErrChartHandleNotUnique = repositoryError("ChartHandleNotUnique") -) - -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 { - return &chart{ - repository: r.repository.With(ctx, db), - } -} - -func (r chart) table() string { - return "compose_chart" -} - -func (r chart) columns() []string { - return []string{ - "id", - "rel_namespace", - "handle", - "name", - "config", - "created_at", - "updated_at", - "deleted_at", - } -} - -func (r chart) query() squirrel.SelectBuilder { - return squirrel. - Select(r.columns()...). - From(r.table()). - Where("deleted_at IS NULL") -} - -func (r chart) FindByID(namespaceID, chartID uint64) (*types.Chart, error) { - return r.findOneBy(namespaceID, "id", chartID) -} - -func (r chart) FindByHandle(namespaceID uint64, handle string) (*types.Chart, error) { - return r.findOneBy(namespaceID, "LOWER(handle)", strings.ToLower(strings.TrimSpace(handle))) -} - -func (r chart) findOneBy(namespaceID uint64, field string, value interface{}) (*types.Chart, error) { - var ( - c = &types.Chart{} - - q = r.query(). - Where(squirrel.Eq{field: value, "rel_namespace": namespaceID}) - - err = rh.FetchOne(r.db(), q, c) - ) - - if err != nil { - return nil, err - } else if c.ID == 0 { - return nil, ErrChartNotFound - } - - return c, nil -} - -func (r chart) Find(filter types.ChartFilter) (set types.ChartSet, f types.ChartFilter, err error) { - //f = filter - // - //if f.Sort == "" { - // f.Sort = "id ASC" - //} - // - //query := r.query() - // - //if filter.NamespaceID > 0 { - // query = query.Where(squirrel.Eq{"rel_namespace": filter.NamespaceID}) - //} - // - //if f.Query != "" { - // q := "%" + strings.ToLower(f.Query) + "%" - // query = query.Where(squirrel.Or{ - // squirrel.Like{"LOWER(name)": q}, - // }) - //} - // - //if f.Handle != "" { - // query = query.Where("LOWER(handle) = LOWER(?)", 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.PageFilter, &set) - return nil, f, fmt.Errorf("deprecated") -} - -func (r chart) Create(mod *types.Chart) (*types.Chart, error) { - mod.ID = factory.Sonyflake.NextID() - rh.SetCurrentTimeRounded(&mod.CreatedAt) - mod.UpdatedAt = nil - - return mod, r.db().Insert(r.table(), mod) -} - -func (r chart) Update(mod *types.Chart) (*types.Chart, error) { - rh.SetCurrentTimeRounded(&mod.UpdatedAt) - - return mod, r.db().Update(r.table(), mod, "id") -} - -func (r chart) DeleteByID(namespaceID, chartID uint64) error { - _, err := r.db().Exec( - "UPDATE "+r.table()+" SET deleted_at = NOW() WHERE rel_namespace = ? AND id = ?", - namespaceID, - chartID, - ) - - return err -} +//type ( +// ChartRepository interface { +// With(ctx context.Context, db *factory.DB) ChartRepository +// +// FindByID(namespaceID, chartID uint64) (*types.Chart, error) +// FindByHandle(namespaceID uint64, handle string) (c *types.Chart, err 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(namespaceID, chartID uint64) error +// } +// +// chart struct { +// *repository +// } +//) +// +//const ( +// ErrChartNotFound = repositoryError("ChartNotFound") +// ErrChartHandleNotUnique = repositoryError("ChartHandleNotUnique") +//) +// +//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 { +// return &chart{ +// repository: r.repository.With(ctx, db), +// } +//} +// +//func (r chart) table() string { +// return "compose_chart" +//} +// +//func (r chart) columns() []string { +// return []string{ +// "id", +// "rel_namespace", +// "handle", +// "name", +// "config", +// "created_at", +// "updated_at", +// "deleted_at", +// } +//} +// +//func (r chart) query() squirrel.SelectBuilder { +// return squirrel. +// Select(r.columns()...). +// From(r.table()). +// Where("deleted_at IS NULL") +//} +// +//func (r chart) FindByID(namespaceID, chartID uint64) (*types.Chart, error) { +// return r.findOneBy(namespaceID, "id", chartID) +//} +// +//func (r chart) FindByHandle(namespaceID uint64, handle string) (*types.Chart, error) { +// return r.findOneBy(namespaceID, "LOWER(handle)", strings.ToLower(strings.TrimSpace(handle))) +//} +// +//func (r chart) findOneBy(namespaceID uint64, field string, value interface{}) (*types.Chart, error) { +// var ( +// c = &types.Chart{} +// +// q = r.query(). +// Where(squirrel.Eq{field: value, "rel_namespace": namespaceID}) +// +// err = rh.FetchOne(r.db(), q, c) +// ) +// +// if err != nil { +// return nil, err +// } else if c.ID == 0 { +// return nil, ErrChartNotFound +// } +// +// return c, nil +//} +// +//func (r chart) Find(filter types.ChartFilter) (set types.ChartSet, f types.ChartFilter, err error) { +// //f = filter +// // +// //if f.Sort == "" { +// // f.Sort = "id ASC" +// //} +// // +// //query := r.query() +// // +// //if filter.NamespaceID > 0 { +// // query = query.Where(squirrel.Eq{"rel_namespace": filter.NamespaceID}) +// //} +// // +// //if f.Query != "" { +// // q := "%" + strings.ToLower(f.Query) + "%" +// // query = query.Where(squirrel.Or{ +// // squirrel.Like{"LOWER(name)": q}, +// // }) +// //} +// // +// //if f.Handle != "" { +// // query = query.Where("LOWER(handle) = LOWER(?)", 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.PageFilter, &set) +// return nil, f, fmt.Errorf("deprecated") +//} +// +//func (r chart) Create(mod *types.Chart) (*types.Chart, error) { +// mod.ID = factory.Sonyflake.NextID() +// rh.SetCurrentTimeRounded(&mod.CreatedAt) +// mod.UpdatedAt = nil +// +// return mod, r.db().Insert(r.table(), mod) +//} +// +//func (r chart) Update(mod *types.Chart) (*types.Chart, error) { +// rh.SetCurrentTimeRounded(&mod.UpdatedAt) +// +// return mod, r.db().Update(r.table(), mod, "id") +//} +// +//func (r chart) DeleteByID(namespaceID, chartID uint64) error { +// _, err := r.db().Exec( +// "UPDATE "+r.table()+" SET deleted_at = NOW() WHERE rel_namespace = ? AND id = ?", +// namespaceID, +// chartID, +// ) +// +// return err +//} diff --git a/compose/repository/module.go b/compose/repository/module.go index a8157b23a..1ed5d182d 100644 --- a/compose/repository/module.go +++ b/compose/repository/module.go @@ -35,9 +35,9 @@ type ( ) const ( - ErrModuleNotFound = repositoryError("ModuleNotFound") - ErrModuleNameNotUnique = repositoryError("ModuleNameNotUnique") - ErrModuleHandleNotUnique = repositoryError("ModuleHandleNotUnique") + ErrModuleNotFound = repositoryError("ModuleNotFound") + //ErrModuleNameNotUnique = repositoryError("ModuleNameNotUnique") + //ErrModuleHandleNotUnique = repositoryError("ModuleHandleNotUnique") ) func Module(ctx context.Context, db *factory.DB) ModuleRepository { @@ -257,13 +257,13 @@ func (r module) FindFields(moduleIDs ...uint64) (ff types.ModuleFieldSet, err er } 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` + 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()) diff --git a/compose/repository/record.go b/compose/repository/record.go index 67648eac1..13c2d9844 100644 --- a/compose/repository/record.go +++ b/compose/repository/record.go @@ -2,17 +2,9 @@ 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/ql" - "github.com/cortezaproject/corteza-server/pkg/rh" + "github.com/titpetric/factory" + "strings" ) type ( @@ -41,323 +33,326 @@ type ( } ) +// const ( ErrRecordNotFound = repositoryError("RecordNotFound") ) -func Record(ctx context.Context, db *factory.DB) RecordRepository { - return (&record{}).With(ctx, db) -} - -func (r record) With(ctx context.Context, db *factory.DB) RecordRepository { - return &record{ - repository: r.repository.With(ctx, db), - } -} - -func (r record) table() string { - return "compose_record" -} - -func (r record) columns() []string { - return []string{ - "r.id", - "r.module_id", - "r.rel_namespace", - "r.owned_by", - "r.created_at", - "r.created_by", - "r.updated_at", - "r.updated_by", - "r.deleted_at", - "r.deleted_by", - } -} - -func (r record) query() squirrel.SelectBuilder { - return squirrel. - Select(r.columns()...). - From(r.table() + " AS r") -} - -// @todo: update to accepted DeletedAt column semantics from Messaging - -func (r record) FindByID(namespaceID, recordID uint64) (*types.Record, error) { - return r.findOneBy(namespaceID, "id", recordID) -} - -func (r record) findOneBy(namespaceID uint64, field string, value interface{}) (*types.Record, error) { - var ( - rec = &types.Record{} - - q = r.query(). - Where("r.deleted_at IS NULL"). - Where(squirrel.Eq{field: value, "rel_namespace": namespaceID}) - - err = rh.FetchOne(r.db(), q, rec) - ) - - if err != nil { - return nil, err - } else if rec.ID == 0 { - return nil, ErrRecordNotFound - } - - return rec, nil -} - -func (r record) Report(module *types.Module, metrics, dimensions, filter string) (results interface{}, err error) { - crb := NewRecordReportBuilder(module) - - var result = make([]map[string]interface{}, 0) - - if query, args, err := crb.Build(metrics, dimensions, filter); err != nil { - return nil, errors.Wrap(err, "can not generate report query") - } else if rows, err := r.db().Query(query, args...); err != nil { - return nil, errors.Wrapf(err, "can not execute report query (%s)", query) - } else { - for rows.Next() { - result = append(result, crb.Cast(rows)) - } - - return result, nil - } -} - -func (r record) Find(module *types.Module, filter types.RecordFilter) (set types.RecordSet, f types.RecordFilter, err error) { - var query squirrel.SelectBuilder - f = filter - - query, err = r.buildQuery(module, filter) - if err != nil { - return - } - - 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) -} - -// Export ignores paging and does not return filter // -// @todo optimize and include value loading -func (r record) Export(module *types.Module, filter types.RecordFilter) (set types.RecordSet, err error) { - filter.PageFilter = rh.PageFilter{} - - query, err := r.buildQuery(module, filter) - if err != nil { - return - } - - return set, rh.FetchAll(r.db(), query, &set) -} - -func (r record) buildQuery(module *types.Module, f types.RecordFilter) (query squirrel.SelectBuilder, err error) { - var ( - joinedFields = []string{} - alreadyJoined = func(f string) bool { - for _, a := range joinedFields { - if a == f { - return true - } - } - - joinedFields = append(joinedFields, f) - return false - } - - identResolver = func(i ql.Ident) (ql.Ident, error) { - var is bool - if i.Value, is = isRealRecordCol(i.Value); is { - i.Value += " " - return i, nil - } - - if !module.Fields.HasName(i.Value) { - return i, errors.Errorf("unknown field %q", i.Value) - } - - if !alreadyJoined(i.Value) { - query = query.LeftJoin(fmt.Sprintf( - "compose_record_value AS rv_%s ON (rv_%s.record_id = r.id AND rv_%s.name = ? AND rv_%s.deleted_at IS NULL)", - i.Value, i.Value, i.Value, i.Value, - ), i.Value) - } - - field := module.Fields.FindByName(i.Value) - - switch true { - case field.IsBoolean(): - i.Value = fmt.Sprintf("(rv_%s.value NOT IN ('', '0', 'false', 'f', 'FALSE', 'F', false))", i.Value) - case field.IsNumeric(): - i.Value = fmt.Sprintf("CAST(rv_%s.value AS SIGNED)", i.Value) - case field.IsDateTime(): - i.Value = fmt.Sprintf("CAST(rv_%s.value AS DATETIME)", i.Value) - case field.IsRef(): - i.Value = fmt.Sprintf("rv_%s.ref ", i.Value) - default: - i.Value = fmt.Sprintf("rv_%s.value ", i.Value) - } - - return i, nil - } - ) - - // Create query for fetching and counting records. - query = r.query(). - Where("r.module_id = ?", module.ID). - Where("r.rel_namespace = ?", module.NamespaceID) - - // Inc/exclude deleted records according to filter settings - query = rh.FilterNullByState(query, "r.deleted_at", f.Deleted) - - // Parse filters. - if f.Query != "" { - var ( - // Filter parser - fp = ql.NewParser() - - // Filter node - fn ql.ASTNode - ) - - // Resolve all identifiers found in the query - // into their table/column counterparts - fp.OnIdent = identResolver - - if fn, err = fp.ParseExpression(f.Query); err != nil { - return - } else if filterSql, filterArgs, err := fn.ToSql(); err != nil { - return query, err - } else { - query = query.Where("("+filterSql+")", filterArgs...) - } - } - - if f.Sort != "" { - var ( - // Sort parser - sp = ql.NewParser() - - // Sort columns - sc ql.Columns - ) - - // Resolve all identifiers found in sort - // into their table/column counterparts - sp.OnIdent = identResolver - - if sc, err = sp.ParseColumns(f.Sort); err != nil { - return - } - - query = query.OrderBy(sc.Strings()...) - } - - return -} - -func (r record) Create(record *types.Record) (*types.Record, error) { - record.ID = factory.Sonyflake.NextID() - - if err := r.db().Insert("compose_record", record); err != nil { - return nil, errors.Wrap(err, "could not update record") - } - - return record, nil -} - -func (r record) Update(record *types.Record) (*types.Record, error) { - if err := r.db().Update("compose_record", record, "id"); err != nil { - return nil, errors.Wrap(err, "could not update record") - } - - return record, nil -} - -func (r record) Delete(record *types.Record) error { - _, err := r.db().Exec( - "UPDATE compose_record SET deleted_at = ?, deleted_by = ? WHERE rel_namespace = ? AND id = ?", - record.DeletedAt, - record.DeletedBy, - record.NamespaceID, - record.ID, - ) - - return err -} - -func (r record) DeleteValues(record *types.Record) error { - _, err := r.db().Exec( - "UPDATE compose_record_value SET deleted_at = ? WHERE record_id = ?", - record.DeletedAt, - record.ID) - - return err -} - -func (r record) UpdateValues(recordID uint64, rvs types.RecordValueSet) (err error) { - // Remove all records and prepare to be updated - // @todo be more selective and delete only removed and update/insert changed/new values - if _, err = r.db().Exec("DELETE FROM compose_record_value WHERE record_id = ?", recordID); err != nil { - return errors.Wrap(err, "could not remove record values") - } - - err = rvs.Walk(func(value *types.RecordValue) error { - if value.DeletedAt != nil { - return nil - } - - value.RecordID = recordID - return r.db().Insert("compose_record_value", value) - }) - - return errors.Wrap(err, "could not insert record values") - -} - -func (r record) PartialUpdateValues(rvs ...*types.RecordValue) (err error) { - err = types.RecordValueSet(rvs).Walk(func(value *types.RecordValue) error { - return r.db().Replace("compose_record_value", value) - }) - - return errors.Wrap(err, "could not replace record values") -} - -func (r record) RefValueLookup(moduleID uint64, field string, ref uint64) (recordID uint64, err error) { - var sql = "SELECT record_id" + - " FROM compose_record AS r INNER JOIN compose_record_value AS v " + - " WHERE r.module_id = ? " + - " AND v.name = ? " + - " AND v.ref = ? " + - " AND r.deleted_at IS NULL " + - " AND v.deleted_at IS NULL " + - " LIMIT 1" - - return recordID, r.db().Get(&recordID, sql, moduleID, field, ref) -} - -func (r record) LoadValues(fieldNames []string, IDs []uint64) (rvs types.RecordValueSet, err error) { - if len(fieldNames) == 0 || len(IDs) == 0 { - return - } - - var sql = "SELECT record_id, name, value, ref, place, deleted_at " + - " FROM compose_record_value " + - " WHERE record_id IN (?) " + - " AND name IN (?) " + - " AND deleted_at IS NULL " + - " ORDER BY record_id, place" - - if sql, args, err := sqlx.In(sql, IDs, fieldNames); err != nil { - return nil, err - } else { - return rvs, r.db().Select(&rvs, sql, args...) - } +func Record(ctx context.Context, db *factory.DB) RecordRepository { + return nil //.With(ctx, db) } +// +//func (r record) With(ctx context.Context, db *factory.DB) RecordRepository { +// return &record{ +// repository: r.repository.With(ctx, db), +// } +//} +// +//func (r record) table() string { +// return "compose_record" +//} +// +//func (r record) columns() []string { +// return []string{ +// "r.id", +// "r.module_id", +// "r.rel_namespace", +// "r.owned_by", +// "r.created_at", +// "r.created_by", +// "r.updated_at", +// "r.updated_by", +// "r.deleted_at", +// "r.deleted_by", +// } +//} +// +//func (r record) query() squirrel.SelectBuilder { +// return squirrel. +// Select(r.columns()...). +// From(r.table() + " AS r") +//} +// +//// @todo: update to accepted DeletedAt column semantics from Messaging +// +//func (r record) FindByID(namespaceID, recordID uint64) (*types.Record, error) { +// return r.findOneBy(namespaceID, "id", recordID) +//} +// +//func (r record) findOneBy(namespaceID uint64, field string, value interface{}) (*types.Record, error) { +// var ( +// rec = &types.Record{} +// +// q = r.query(). +// Where("r.deleted_at IS NULL"). +// Where(squirrel.Eq{field: value, "rel_namespace": namespaceID}) +// +// err = rh.FetchOne(r.db(), q, rec) +// ) +// +// if err != nil { +// return nil, err +// } else if rec.ID == 0 { +// return nil, ErrRecordNotFound +// } +// +// return rec, nil +//} +// +//func (r record) Report(module *types.Module, metrics, dimensions, filter string) (results interface{}, err error) { +// crb := NewRecordReportBuilder(module) +// +// var result = make([]map[string]interface{}, 0) +// +// if query, args, err := crb.Build(metrics, dimensions, filter); err != nil { +// return nil, errors.Wrap(err, "can not generate report query") +// } else if rows, err := r.db().Query(query, args...); err != nil { +// return nil, errors.Wrapf(err, "can not execute report query (%s)", query) +// } else { +// for rows.Next() { +// result = append(result, crb.Cast(rows)) +// } +// +// return result, nil +// } +//} +// +//func (r record) Find(module *types.Module, filter types.RecordFilter) (set types.RecordSet, f types.RecordFilter, err error) { +// var query squirrel.SelectBuilder +// f = filter +// +// query, err = r.buildQuery(module, filter) +// if err != nil { +// return +// } +// +// 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) +//} +// +//// Export ignores paging and does not return filter +//// +//// @todo optimize and include value loading +//func (r record) Export(module *types.Module, filter types.RecordFilter) (set types.RecordSet, err error) { +// filter.PageFilter = rh.PageFilter{} +// +// query, err := r.buildQuery(module, filter) +// if err != nil { +// return +// } +// +// return set, rh.FetchAll(r.db(), query, &set) +//} +// +//func (r record) buildQuery(module *types.Module, f types.RecordFilter) (query squirrel.SelectBuilder, err error) { +// var ( +// joinedFields = []string{} +// alreadyJoined = func(f string) bool { +// for _, a := range joinedFields { +// if a == f { +// return true +// } +// } +// +// joinedFields = append(joinedFields, f) +// return false +// } +// +// identResolver = func(i ql.Ident) (ql.Ident, error) { +// var is bool +// if i.Value, is = isRealRecordCol(i.Value); is { +// i.Value += " " +// return i, nil +// } +// +// if !module.Fields.HasName(i.Value) { +// return i, errors.Errorf("unknown field %q", i.Value) +// } +// +// if !alreadyJoined(i.Value) { +// query = query.LeftJoin(fmt.Sprintf( +// "compose_record_value AS rv_%s ON (rv_%s.record_id = r.id AND rv_%s.name = ? AND rv_%s.deleted_at IS NULL)", +// i.Value, i.Value, i.Value, i.Value, +// ), i.Value) +// } +// +// field := module.Fields.FindByName(i.Value) +// +// switch true { +// case field.IsBoolean(): +// i.Value = fmt.Sprintf("(rv_%s.value NOT IN ('', '0', 'false', 'f', 'FALSE', 'F', false))", i.Value) +// case field.IsNumeric(): +// i.Value = fmt.Sprintf("CAST(rv_%s.value AS SIGNED)", i.Value) +// case field.IsDateTime(): +// i.Value = fmt.Sprintf("CAST(rv_%s.value AS DATETIME)", i.Value) +// case field.IsRef(): +// i.Value = fmt.Sprintf("rv_%s.ref ", i.Value) +// default: +// i.Value = fmt.Sprintf("rv_%s.value ", i.Value) +// } +// +// return i, nil +// } +// ) +// +// // Create query for fetching and counting records. +// query = r.query(). +// Where("r.module_id = ?", module.ID). +// Where("r.rel_namespace = ?", module.NamespaceID) +// +// // Inc/exclude deleted records according to filter settings +// query = rh.FilterNullByState(query, "r.deleted_at", f.Deleted) +// +// // Parse filters. +// if f.Query != "" { +// var ( +// // Filter parser +// fp = ql.NewParser() +// +// // Filter node +// fn ql.ASTNode +// ) +// +// // Resolve all identifiers found in the query +// // into their table/column counterparts +// fp.OnIdent = identResolver +// +// if fn, err = fp.ParseExpression(f.Query); err != nil { +// return +// } else if filterSql, filterArgs, err := fn.ToSql(); err != nil { +// return query, err +// } else { +// query = query.Where("("+filterSql+")", filterArgs...) +// } +// } +// +// if f.Sort != "" { +// var ( +// // Sort parser +// sp = ql.NewParser() +// +// // Sort columns +// sc ql.Columns +// ) +// +// // Resolve all identifiers found in sort +// // into their table/column counterparts +// sp.OnIdent = identResolver +// +// if sc, err = sp.ParseColumns(f.Sort); err != nil { +// return +// } +// +// query = query.OrderBy(sc.Strings()...) +// } +// +// return +//} +// +//func (r record) Create(record *types.Record) (*types.Record, error) { +// record.ID = factory.Sonyflake.NextID() +// +// if err := r.db().Insert("compose_record", record); err != nil { +// return nil, errors.Wrap(err, "could not update record") +// } +// +// return record, nil +//} +// +//func (r record) Update(record *types.Record) (*types.Record, error) { +// if err := r.db().Update("compose_record", record, "id"); err != nil { +// return nil, errors.Wrap(err, "could not update record") +// } +// +// return record, nil +//} +// +//func (r record) Delete(record *types.Record) error { +// _, err := r.db().Exec( +// "UPDATE compose_record SET deleted_at = ?, deleted_by = ? WHERE rel_namespace = ? AND id = ?", +// record.DeletedAt, +// record.DeletedBy, +// record.NamespaceID, +// record.ID, +// ) +// +// return err +//} +// +//func (r record) DeleteValues(record *types.Record) error { +// _, err := r.db().Exec( +// "UPDATE compose_record_value SET deleted_at = ? WHERE record_id = ?", +// record.DeletedAt, +// record.ID) +// +// return err +//} +// +//func (r record) UpdateValues(recordID uint64, rvs types.RecordValueSet) (err error) { +// // Remove all records and prepare to be updated +// // @todo be more selective and delete only removed and update/insert changed/new values +// if _, err = r.db().Exec("DELETE FROM compose_record_value WHERE record_id = ?", recordID); err != nil { +// return errors.Wrap(err, "could not remove record values") +// } +// +// err = rvs.Walk(func(value *types.RecordValue) error { +// if value.DeletedAt != nil { +// return nil +// } +// +// value.RecordID = recordID +// return r.db().Insert("compose_record_value", value) +// }) +// +// return errors.Wrap(err, "could not insert record values") +// +//} +// +//func (r record) PartialUpdateValues(rvs ...*types.RecordValue) (err error) { +// err = types.RecordValueSet(rvs).Walk(func(value *types.RecordValue) error { +// return r.db().Replace("compose_record_value", value) +// }) +// +// return errors.Wrap(err, "could not replace record values") +//} +// +//func (r record) RefValueLookup(moduleID uint64, field string, ref uint64) (recordID uint64, err error) { +// var sql = "SELECT record_id" + +// " FROM compose_record AS r INNER JOIN compose_record_value AS v " + +// " WHERE r.module_id = ? " + +// " AND v.name = ? " + +// " AND v.ref = ? " + +// " AND r.deleted_at IS NULL " + +// " AND v.deleted_at IS NULL " + +// " LIMIT 1" +// +// return recordID, r.db().Get(&recordID, sql, moduleID, field, ref) +//} +// +//func (r record) LoadValues(fieldNames []string, IDs []uint64) (rvs types.RecordValueSet, err error) { +// if len(fieldNames) == 0 || len(IDs) == 0 { +// return +// } +// +// var sql = "SELECT record_id, name, value, ref, place, deleted_at " + +// " FROM compose_record_value " + +// " WHERE record_id IN (?) " + +// " AND name IN (?) " + +// " AND deleted_at IS NULL " + +// " ORDER BY record_id, place" +// +// if sql, args, err := sqlx.In(sql, IDs, fieldNames); err != nil { +// return nil, err +// } else { +// return rvs, r.db().Select(&rvs, sql, args...) +// } +//} +// // Checks if field name is "real column", reformats it and returns func isRealRecordCol(name string) (string, bool) { switch name { diff --git a/compose/rest/chart.go b/compose/rest/chart.go index 2bfb951d1..558e387f7 100644 --- a/compose/rest/chart.go +++ b/compose/rest/chart.go @@ -2,8 +2,7 @@ package rest import ( "context" - "github.com/cortezaproject/corteza-server/store" - + "github.com/cortezaproject/corteza-server/pkg/filter" "github.com/titpetric/factory/resputil" "github.com/cortezaproject/corteza-server/compose/rest/request" @@ -59,11 +58,11 @@ func (ctrl Chart) List(ctx context.Context, r *request.ChartList) (interface{}, } ) - if f.Paging, err = store.NewPaging(r.Limit, r.PageCursor); err != nil { + if f.Paging, err = filter.NewPaging(r.Limit, r.PageCursor); err != nil { return nil, err } - if f.Sorting, err = store.NewSorting(r.Sort); err != nil { + if f.Sorting, err = filter.NewSorting(r.Sort); err != nil { return nil, err } diff --git a/compose/rest/module.go b/compose/rest/module.go index d78cabfa6..4eacfb542 100644 --- a/compose/rest/module.go +++ b/compose/rest/module.go @@ -2,8 +2,7 @@ package rest import ( "context" - "github.com/cortezaproject/corteza-server/store" - + "github.com/cortezaproject/corteza-server/pkg/filter" "github.com/titpetric/factory/resputil" "github.com/cortezaproject/corteza-server/compose/rest/request" @@ -84,11 +83,11 @@ func (ctrl *Module) List(ctx context.Context, r *request.ModuleList) (interface{ } ) - if f.Paging, err = store.NewPaging(r.Limit, r.PageCursor); err != nil { + if f.Paging, err = filter.NewPaging(r.Limit, r.PageCursor); err != nil { return nil, err } - if f.Sorting, err = store.NewSorting(r.Sort); err != nil { + if f.Sorting, err = filter.NewSorting(r.Sort); err != nil { return nil, err } diff --git a/compose/rest/namespace.go b/compose/rest/namespace.go index 5087f93f0..78e8b2c46 100644 --- a/compose/rest/namespace.go +++ b/compose/rest/namespace.go @@ -2,8 +2,7 @@ package rest import ( "context" - "github.com/cortezaproject/corteza-server/store" - + "github.com/cortezaproject/corteza-server/pkg/filter" "github.com/titpetric/factory/resputil" "github.com/cortezaproject/corteza-server/compose/rest/request" @@ -65,11 +64,11 @@ func (ctrl Namespace) List(ctx context.Context, r *request.NamespaceList) (inter } ) - if f.Paging, err = store.NewPaging(r.Limit, r.PageCursor); err != nil { + if f.Paging, err = filter.NewPaging(r.Limit, r.PageCursor); err != nil { return nil, err } - if f.Sorting, err = store.NewSorting(r.Sort); err != nil { + if f.Sorting, err = filter.NewSorting(r.Sort); err != nil { return nil, err } diff --git a/compose/rest/page.go b/compose/rest/page.go index 153eaf3de..fc32c7f3d 100644 --- a/compose/rest/page.go +++ b/compose/rest/page.go @@ -2,8 +2,7 @@ package rest import ( "context" - "github.com/cortezaproject/corteza-server/store" - + "github.com/cortezaproject/corteza-server/pkg/filter" "github.com/titpetric/factory/resputil" "github.com/cortezaproject/corteza-server/compose/rest/request" @@ -66,11 +65,11 @@ func (ctrl *Page) List(ctx context.Context, r *request.PageList) (interface{}, e } ) - if f.Paging, err = store.NewPaging(r.Limit, r.PageCursor); err != nil { + if f.Paging, err = filter.NewPaging(r.Limit, r.PageCursor); err != nil { return nil, err } - if f.Sorting, err = store.NewSorting(r.Sort); err != nil { + if f.Sorting, err = filter.NewSorting(r.Sort); err != nil { return nil, err } diff --git a/compose/rest/record.go b/compose/rest/record.go index 79586c365..92de7d1f1 100644 --- a/compose/rest/record.go +++ b/compose/rest/record.go @@ -85,14 +85,16 @@ func (ctrl *Record) List(ctx context.Context, r *request.RecordList) (interface{ rf = types.RecordFilter{ NamespaceID: r.NamespaceID, ModuleID: r.ModuleID, - Sort: r.Sort, - - Deleted: rh.FilterState(r.Deleted), - - PageFilter: rh.Paging(r), + Deleted: rh.FilterState(r.Deleted), } ) + if err = rf.Sort.Set(r.Sort); err != nil { + return nil, err + } + + panic("refactor page filter") + if m, err = ctrl.module.With(ctx).FindByID(r.NamespaceID, r.ModuleID); err != nil { return nil, err } @@ -121,7 +123,7 @@ func (ctrl *Record) Read(ctx context.Context, r *request.RecordRead) (interface{ return nil, err } - record, err := ctrl.record.With(ctx).FindByID(r.NamespaceID, r.RecordID) + record, err := ctrl.record.With(ctx).FindByID(r.NamespaceID, r.ModuleID, r.RecordID) // Temp workaround until we do proper by-module filtering for record findByID if record != nil && record.ModuleID != r.ModuleID { @@ -498,7 +500,7 @@ func (ctrl *Record) TriggerScript(ctx context.Context, r *request.RecordTriggerS namespace *types.Namespace ) - if oldRecord, err = ctrl.record.With(ctx).FindByID(r.NamespaceID, r.RecordID); err != nil { + if oldRecord, err = ctrl.record.With(ctx).FindByID(r.NamespaceID, r.ModuleID, r.RecordID); err != nil { return } diff --git a/compose/service/access_control.go b/compose/service/access_control.go index 79143c336..1dfdf07d9 100644 --- a/compose/service/access_control.go +++ b/compose/service/access_control.go @@ -156,7 +156,6 @@ func (svc accessControl) CanDeleteChart(ctx context.Context, r *types.Chart) boo } func (svc accessControl) CanCreatePage(ctx context.Context, r *types.Namespace) bool { - // @todo move to func args when namespaces are implemented return svc.can(ctx, r, "page.create") } diff --git a/compose/service/chart.go b/compose/service/chart.go index 59c3bd00a..1c818720e 100644 --- a/compose/service/chart.go +++ b/compose/service/chart.go @@ -2,27 +2,21 @@ package service import ( "context" - - "github.com/titpetric/factory" - - "github.com/cortezaproject/corteza-server/compose/repository" + "errors" "github.com/cortezaproject/corteza-server/compose/types" "github.com/cortezaproject/corteza-server/pkg/actionlog" "github.com/cortezaproject/corteza-server/pkg/handle" + "github.com/cortezaproject/corteza-server/pkg/id" "github.com/cortezaproject/corteza-server/pkg/permissions" + "github.com/cortezaproject/corteza-server/store" ) type ( chart struct { - db *factory.DB - ctx context.Context - + ctx context.Context actionlog actionlog.Recorder - - ac chartAccessController - - chartRepo repository.ChartRepository - nsRepo repository.NamespaceRepository + ac chartAccessController + store store.Storable } chartAccessController interface { @@ -46,84 +40,27 @@ type ( Update(chart *types.Chart) (*types.Chart, error) DeleteByID(namespaceID, chartID uint64) error } + + chartUpdateHandler func(ctx context.Context, ns *types.Namespace, c *types.Chart) (bool, error) ) func Chart() ChartService { return (&chart{ - ac: DefaultAccessControl, + ctx: context.Background(), + ac: DefaultAccessControl, }).With(context.Background()) } func (svc chart) With(ctx context.Context) ChartService { - db := repository.DB(ctx) return &chart{ - db: db, - ctx: ctx, - + ctx: ctx, actionlog: DefaultActionlog, - - ac: svc.ac, - - chartRepo: repository.Chart(ctx, db), - nsRepo: repository.Namespace(ctx, db), + ac: svc.ac, + store: DefaultNgStore, } } -// lookup fn() orchestrates chart lookup, namespace preload and check -func (svc chart) lookup(namespaceID uint64, lookup func(*chartActionProps) (*types.Chart, error)) (m *types.Chart, err error) { - var aProps = &chartActionProps{chart: &types.Chart{NamespaceID: namespaceID}} - - err = svc.db.Transaction(func() error { - if ns, err := svc.loadNamespace(namespaceID); err != nil { - return err - } else { - aProps.setNamespace(ns) - } - - if m, err = lookup(aProps); err != nil { - if repository.ErrChartNotFound.Eq(err) { - return ChartErrNotFound() - } - - return err - } - - aProps.setChart(m) - - if !svc.ac.CanReadChart(svc.ctx, m) { - return ChartErrNotAllowedToRead() - } - - return nil - }) - - return m, svc.recordAction(svc.ctx, aProps, ChartActionLookup, err) -} - -func (svc chart) FindByID(namespaceID, chartID uint64) (p *types.Chart, err error) { - return svc.lookup(namespaceID, func(aProps *chartActionProps) (*types.Chart, error) { - if chartID == 0 { - return nil, ChartErrInvalidID() - } - - aProps.chart.ID = chartID - return svc.chartRepo.FindByID(namespaceID, chartID) - }) -} - -func (svc chart) FindByHandle(namespaceID uint64, h string) (c *types.Chart, err error) { - return svc.lookup(namespaceID, func(aProps *chartActionProps) (*types.Chart, error) { - if !handle.IsValid(h) { - return nil, ChartErrInvalidHandle() - } - - aProps.chart.Handle = h - return svc.chartRepo.FindByHandle(namespaceID, h) - }) -} - -// search fn() orchestrates charts search, namespace preload and check -func (svc chart) search(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) { var ( aProps = &chartActionProps{filter: &filter} ) @@ -138,13 +75,13 @@ func (svc chart) search(filter types.ChartFilter) (set types.ChartSet, f types.C } err = func() error { - if ns, err := svc.loadNamespace(f.NamespaceID); err != nil { + if ns, err := loadNamespace(svc.ctx, svc.store, f.NamespaceID); err != nil { return err } else { aProps.setNamespace(ns) } - if set, f, err = svc.chartRepo.Find(filter); err != nil { + if set, f, err = store.SearchComposeCharts(svc.ctx, svc.store, filter); err != nil { return err } @@ -154,23 +91,41 @@ func (svc chart) search(filter types.ChartFilter) (set types.ChartSet, f types.C return set, f, svc.recordAction(svc.ctx, aProps, ChartActionSearch, err) } -func (svc chart) Find(filter types.ChartFilter) (set types.ChartSet, f types.ChartFilter, err error) { - return svc.search(filter) +func (svc chart) FindByID(namespaceID, chartID uint64) (c *types.Chart, err error) { + return svc.lookup(namespaceID, func(aProps *chartActionProps) (*types.Chart, error) { + if chartID == 0 { + return nil, ChartErrInvalidID() + } + + aProps.chart.ID = chartID + return svc.store.LookupComposeChartByID(svc.ctx, chartID) + }) } -func (svc chart) Create(new *types.Chart) (c *types.Chart, err error) { +func (svc chart) FindByHandle(namespaceID uint64, h string) (c *types.Chart, err error) { + return svc.lookup(namespaceID, func(aProps *chartActionProps) (*types.Chart, error) { + if !handle.IsValid(h) { + return nil, ChartErrInvalidHandle() + } + + aProps.chart.Handle = h + return svc.store.LookupComposeChartByNamespaceIDHandle(svc.ctx, namespaceID, h) + }) +} + +func (svc chart) Create(new *types.Chart) (*types.Chart, error) { var ( + err error ns *types.Namespace aProps = &chartActionProps{changed: new} ) - err = svc.db.Transaction(func() error { - + err = func() error { if !handle.IsValid(new.Handle) { return ChartErrInvalidHandle() } - if ns, err = svc.loadNamespace(new.NamespaceID); err != nil { + if ns, err = loadNamespace(svc.ctx, svc.store, new.NamespaceID); err != nil { return err } @@ -180,110 +135,95 @@ func (svc chart) Create(new *types.Chart) (c *types.Chart, err error) { return ChartErrNotAllowedToCreate() } - if err = svc.UniqueCheck(new); err != nil { + new.ID = id.Next() + new.CreatedAt = *nowPtr() + new.UpdatedAt = nil + new.DeletedAt = nil + + if err = svc.uniqueCheck(new); err != nil { return err } - c, err = svc.chartRepo.Create(new) - return err - }) - return c, svc.recordAction(svc.ctx, aProps, ChartActionCreate, err) + return svc.store.CreateComposeChart(svc.ctx, new) + }() + + return new, svc.recordAction(svc.ctx, aProps, ChartActionCreate, err) } func (svc chart) Update(upd *types.Chart) (c *types.Chart, err error) { - var ( - ns *types.Namespace - aProps = &chartActionProps{changed: upd} - ) - - err = svc.db.Transaction(func() error { - if !handle.IsValid(upd.Handle) { - return ChartErrInvalidHandle() - } - - if upd.ID == 0 { - return ChartErrInvalidID() - } - - if ns, err = svc.loadNamespace(upd.NamespaceID); err != nil { - return err - } - - aProps.setNamespace(ns) - - if c, err = svc.chartRepo.FindByID(upd.NamespaceID, upd.ID); err != nil { - if repository.ErrModuleNotFound.Eq(err) { - return ModuleErrNotFound() - } - - return err - } - - if isStale(upd.UpdatedAt, c.UpdatedAt, c.CreatedAt) { - return ChartErrStaleData() - } - - if err = svc.UniqueCheck(upd); err != nil { - return err - } - - if !svc.ac.CanUpdateChart(svc.ctx, c) { - return ChartErrNotAllowedToUpdate() - } - - c.Config = upd.Config - c.Name = upd.Name - c.Handle = upd.Handle - - c, err = svc.chartRepo.Update(c) - return err - }) - - return c, svc.recordAction(svc.ctx, aProps, ChartActionUpdate, err) + return svc.updater(upd.NamespaceID, upd.ID, ChartActionUpdate, svc.handleUpdate(upd)) } -func (svc chart) DeleteByID(namespaceID, chartID uint64) (err error) { - var ( - ns *types.Namespace - c *types.Chart - aProps = &chartActionProps{chart: &types.Chart{ID: chartID, NamespaceID: namespaceID}} - ) +func (svc chart) DeleteByID(namespaceID, chartID uint64) error { + return trim1st(svc.updater(namespaceID, chartID, ChartActionDelete, svc.handleDelete)) +} - err = svc.db.Transaction(func() (err error) { - if chartID == 0 { - return ChartErrInvalidID() +func (svc chart) UndeleteByID(namespaceID, chartID uint64) error { + return trim1st(svc.updater(namespaceID, chartID, ChartActionUndelete, svc.handleUndelete)) +} + +// lookup fn() orchestrates chart lookup, namespace preload and check +func (svc chart) lookup(namespaceID uint64, lookup func(*chartActionProps) (*types.Chart, error)) (c *types.Chart, err error) { + var aProps = &chartActionProps{chart: &types.Chart{NamespaceID: namespaceID}} + + err = func() error { + if ns, err := loadNamespace(svc.ctx, svc.store, namespaceID); err != nil { + return err + } else { + aProps.setNamespace(ns) } - if ns, err = svc.loadNamespace(namespaceID); err != nil { + if c, err = lookup(aProps); errors.Is(err, store.ErrNotFound) { + return ChartErrNotFound() + } else if err != nil { return err } + aProps.setChart(c) + + if !svc.ac.CanReadChart(svc.ctx, c) { + return ChartErrNotAllowedToRead() + } + + return nil + }() + + return c, svc.recordAction(svc.ctx, aProps, ChartActionLookup, err) +} + +func (svc chart) updater(namespaceID, chartID uint64, action func(...*chartActionProps) *chartAction, fn chartUpdateHandler) (*types.Chart, error) { + var ( + changed bool + ns *types.Namespace + c *types.Chart + aProps = &chartActionProps{chart: &types.Chart{ID: chartID, NamespaceID: namespaceID}} + err error + ) + + err = store.Tx(svc.ctx, svc.store, func(ctx context.Context, s store.Storable) (err error) { + ns, c, err = loadChart(svc.ctx, s, namespaceID, chartID) + if err != nil { + return + } + aProps.setNamespace(ns) - - if c, err = svc.chartRepo.FindByID(namespaceID, chartID); err != nil { - if repository.ErrChartNotFound.Eq(err) { - return ChartErrNotFound() - } - - return err - } - aProps.setChanged(c) - if !svc.ac.CanDeleteChart(svc.ctx, c) { - return ChartErrNotAllowedToDelete() + if changed, err = fn(svc.ctx, ns, c); err != nil { + return err + } else if !changed { + return } - return svc.chartRepo.DeleteByID(namespaceID, chartID) + return svc.store.UpdateComposeChart(svc.ctx, c) }) - return svc.recordAction(svc.ctx, aProps, ChartActionDelete, err) - + return c, svc.recordAction(svc.ctx, aProps, action, err) } -func (svc chart) UniqueCheck(c *types.Chart) (err error) { +func (svc chart) uniqueCheck(c *types.Chart) (err error) { if c.Handle != "" { - if e, _ := svc.chartRepo.FindByHandle(c.NamespaceID, c.Handle); e != nil && e.ID != c.ID { + if e, _ := svc.store.LookupComposeChartByNamespaceIDHandle(svc.ctx, c.NamespaceID, c.Handle); e != nil && e.ID != c.ID { return ChartErrHandleNotUnique() } } @@ -291,17 +231,78 @@ func (svc chart) UniqueCheck(c *types.Chart) (err error) { return nil } -func (svc chart) loadNamespace(namespaceID uint64) (ns *types.Namespace, err error) { - if namespaceID == 0 { - return nil, ChartErrInvalidNamespaceID() +func (svc chart) handleUpdate(upd *types.Chart) chartUpdateHandler { + return func(ctx context.Context, ns *types.Namespace, c *types.Chart) (bool, error) { + if isStale(upd.UpdatedAt, c.UpdatedAt, c.CreatedAt) { + return false, ChartErrStaleData() + } + + if upd.Handle != c.Handle && !handle.IsValid(upd.Handle) { + return false, ChartErrInvalidHandle() + } + + if err := svc.uniqueCheck(upd); err != nil { + return false, err + } + + if !svc.ac.CanUpdateChart(svc.ctx, c) { + return false, ChartErrNotAllowedToUpdate() + } + + c.Name = upd.Name + c.Handle = upd.Handle + c.Config = upd.Config + c.UpdatedAt = nowPtr() + return true, nil + } +} + +func (svc chart) handleDelete(ctx context.Context, ns *types.Namespace, c *types.Chart) (bool, error) { + if !svc.ac.CanDeleteChart(ctx, c) { + return false, ChartErrNotAllowedToUndelete() } - if ns, err = svc.nsRepo.FindByID(namespaceID); err != nil { - return + if c.DeletedAt != nil { + // chart already deleted + return false, nil } - if !svc.ac.CanReadNamespace(svc.ctx, ns) { - return nil, ChartErrNotAllowedToReadNamespace() + c.DeletedAt = nowPtr() + return true, nil +} + +func (svc chart) handleUndelete(ctx context.Context, ns *types.Namespace, c *types.Chart) (bool, error) { + if !svc.ac.CanDeleteChart(ctx, c) { + return false, ChartErrNotAllowedToUndelete() + } + + if c.DeletedAt == nil { + // chart not deleted + return false, nil + } + + c.DeletedAt = nil + return true, nil +} + +func loadChart(ctx context.Context, s store.Storable, namespaceID, chartID uint64) (ns *types.Namespace, c *types.Chart, err error) { + if chartID == 0 { + return nil, nil, ChartErrInvalidID() + } + + if ns, err = loadNamespace(ctx, s, namespaceID); err == nil { + if c, err = store.LookupComposeChartByID(ctx, s, chartID); errors.Is(err, store.ErrNotFound) { + err = ChartErrNotFound() + } + } + + if err != nil { + return nil, nil, err + } + + if namespaceID != c.NamespaceID { + // Make sure chart belongs to the right namespace + return nil, nil, ChartErrNotFound() } return diff --git a/compose/service/chart_actions.gen.go b/compose/service/chart_actions.gen.go index 5a861047b..26988a91a 100644 --- a/compose/service/chart_actions.gen.go +++ b/compose/service/chart_actions.gen.go @@ -134,7 +134,7 @@ func (p chartActionProps) serialize() actionlog.Meta { m.Set("filter.handle", p.filter.Handle, true) m.Set("filter.namespaceID", p.filter.NamespaceID, true) m.Set("filter.sort", p.filter.Sort, true) - m.Set("filter.Limit", p.filter.Limit, true) + m.Set("filter.limit", p.filter.Limit, true) } if p.namespace != nil { m.Set("namespace.name", p.namespace.Name, true) @@ -235,7 +235,7 @@ func (p chartActionProps) tr(in string, err error) string { pairs = append(pairs, "{filter.handle}", fns(p.filter.Handle)) pairs = append(pairs, "{filter.namespaceID}", fns(p.filter.NamespaceID)) pairs = append(pairs, "{filter.sort}", fns(p.filter.Sort)) - pairs = append(pairs, "{filter.Limit}", fns(p.filter.Limit)) + pairs = append(pairs, "{filter.limit}", fns(p.filter.Limit)) } if p.namespace != nil { diff --git a/compose/service/chart_actions.yaml b/compose/service/chart_actions.yaml index a1bb5dd77..d706e2714 100644 --- a/compose/service/chart_actions.yaml +++ b/compose/service/chart_actions.yaml @@ -21,7 +21,7 @@ props: fields: [ name, handle, ID, namespaceID, config ] - name: filter type: "*types.ChartFilter" - fields: [ query, handle, namespaceID, sort, Limit ] + fields: [ query, handle, namespaceID, sort, limit ] - name: namespace type: "*types.Namespace" fields: [ name, slug, ID ] diff --git a/compose/service/chart_test.go b/compose/service/chart_test.go new file mode 100644 index 000000000..1244ee3a5 --- /dev/null +++ b/compose/service/chart_test.go @@ -0,0 +1,105 @@ +package service + +import ( + "context" + "errors" + "github.com/cortezaproject/corteza-server/compose/types" + "github.com/cortezaproject/corteza-server/pkg/id" + "github.com/cortezaproject/corteza-server/pkg/permissions" + "github.com/cortezaproject/corteza-server/store" + "github.com/cortezaproject/corteza-server/store/sqlite" + "github.com/stretchr/testify/require" + "go.uber.org/zap" + "testing" +) + +func TestCharts(t *testing.T) { + var ( + ctx = context.Background() + s, err = sqlite.NewInMemory(ctx) + + namespaceID = id.Next() + ns *types.Namespace + ) + + if err != nil { + t.Fatalf("failed to init sqlite in-memory db: %v", err) + } + + func(s interface{}) { + err = s.(interface { + Upgrade(context.Context, *zap.Logger) error + }).Upgrade(ctx, zap.NewNop()) + if err != nil { + t.Fatalf("failed to upgrade store: %v", err) + } + }(s) + + if err = s.TruncateComposeNamespaces(ctx); err != nil { + t.Fatalf("failed to truncate compose namespaces: %v", err) + } + + if err = s.TruncateComposeCharts(ctx); err != nil { + t.Fatalf("failed to truncate compose charts: %v", err) + } + + ns = &types.Namespace{Name: "testing", ID: namespaceID, CreatedAt: *nowPtr()} + if err = store.CreateComposeNamespace(ctx, s, ns); err != nil { + t.Fatalf("failed to seed namespaces: %v", err) + } + + t.Run("crud", func(t *testing.T) { + req := require.New(t) + svc := chart{ + store: s, + ctx: context.Background(), + ac: AccessControl(&permissions.ServiceAllowAll{}), + } + res, err := svc.Create(&types.Chart{Name: "My first chart", NamespaceID: namespaceID}) + req.NoError(err) + req.NotNil(res) + + res, err = svc.FindByID(namespaceID, res.ID) + req.NoError(err) + req.NotNil(res) + + res, err = svc.FindByHandle(namespaceID, res.Handle) + req.NoError(err) + req.NotNil(res) + + res.Name = "Changed" + res, err = svc.Update(res) + req.NoError(err) + req.NotNil(res) + req.NotNil(res.UpdatedAt) + req.Equal(res.Name, "Changed") + + res, err = svc.FindByID(namespaceID, res.ID) + req.NoError(err) + req.NotNil(res) + req.Equal(res.Name, "Changed") + + err = svc.DeleteByID(namespaceID, res.ID) + req.NoError(err) + req.NotNil(res) + + // this works because we're allowed to do everything + res, err = svc.FindByID(namespaceID, res.ID) + req.NoError(err) + req.NotNil(res) + req.NotNil(res.DeletedAt) + + }) +} + +func unwrapChartInternal(err error) error { + g := ChartErrGeneric() + for { + if errors.Is(err, g) { + err = errors.Unwrap(err) + continue + } + + return err + } +} diff --git a/compose/service/module.go b/compose/service/module.go index a595523e9..42904d4e9 100644 --- a/compose/service/module.go +++ b/compose/service/module.go @@ -2,33 +2,27 @@ package service import ( "context" - "strconv" - - "github.com/titpetric/factory" - - "github.com/cortezaproject/corteza-server/compose/repository" + "errors" + "fmt" "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/eventbus" "github.com/cortezaproject/corteza-server/pkg/handle" + "github.com/cortezaproject/corteza-server/pkg/id" "github.com/cortezaproject/corteza-server/pkg/permissions" + "github.com/cortezaproject/corteza-server/store" + "sort" + "strconv" ) type ( module struct { - db *factory.DB - ctx context.Context - + ctx context.Context actionlog actionlog.Recorder - - ac moduleAccessController - eventbus eventDispatcher - - moduleRepo repository.ModuleRepository - recordRepo repository.RecordRepository - pageRepo repository.PageRepository - nsRepo repository.NamespaceRepository + ac moduleAccessController + eventbus eventDispatcher + store store.Storable } moduleAccessController interface { @@ -54,66 +48,57 @@ type ( Update(module *types.Module) (*types.Module, error) DeleteByID(namespaceID, moduleID uint64) error } + + moduleUpdateHandler func(ctx context.Context, ns *types.Namespace, c *types.Module) (bool, bool, error) ) func Module() ModuleService { return (&module{ + ctx: context.Background(), ac: DefaultAccessControl, eventbus: eventbus.Service(), }).With(context.Background()) } func (svc module) With(ctx context.Context) ModuleService { - db := repository.DB(ctx) return &module{ - db: db, - ctx: ctx, - + ctx: ctx, actionlog: DefaultActionlog, - - ac: svc.ac, - eventbus: svc.eventbus, - - moduleRepo: repository.Module(ctx, db), - recordRepo: repository.Record(ctx, db), - pageRepo: repository.Page(ctx, db), - nsRepo: repository.Namespace(ctx, db), + ac: svc.ac, + eventbus: svc.eventbus, + store: DefaultNgStore, } } -// lookup fn() orchestrates module lookup, namespace preload and check, module reading... -func (svc module) lookup(namespaceID uint64, lookup func(*moduleActionProps) (*types.Module, error)) (m *types.Module, err error) { - var aProps = &moduleActionProps{module: &types.Module{NamespaceID: namespaceID}} +func (svc module) Find(filter types.ModuleFilter) (set types.ModuleSet, f types.ModuleFilter, err error) { + var ( + aProps = &moduleActionProps{filter: &filter} + ) - err = svc.db.Transaction(func() error { - if ns, err := svc.loadNamespace(namespaceID); err != nil { + // For each fetched item, store backend will check if it is valid or not + filter.Check = func(res *types.Module) (bool, error) { + if !svc.ac.CanReadModule(svc.ctx, res) { + return false, nil + } + + return true, nil + } + + err = func() error { + if ns, err := loadNamespace(svc.ctx, svc.store, f.NamespaceID); err != nil { return err } else { aProps.setNamespace(ns) } - if m, err = lookup(aProps); err != nil { - if repository.ErrModuleNotFound.Eq(err) { - return ModuleErrNotFound() - } - + if set, f, err = store.SearchComposeModules(svc.ctx, svc.store, filter); err != nil { return err } - aProps.setModule(m) + return loadModuleFields(svc.ctx, svc.store, set...) + }() - if !svc.ac.CanReadModule(svc.ctx, m) { - return ModuleErrNotAllowedToRead() - } - - if m.Fields, err = svc.moduleRepo.FindFields(m.ID); err != nil { - return err - } - - return nil - }) - - return m, svc.recordAction(svc.ctx, aProps, ModuleActionLookup, err) + return set, f, svc.recordAction(svc.ctx, aProps, ModuleActionSearch, err) } // FindByID tries to find module by ID @@ -124,7 +109,7 @@ func (svc module) FindByID(namespaceID, moduleID uint64) (m *types.Module, err e } aProps.module.ID = moduleID - return svc.moduleRepo.FindByID(namespaceID, moduleID) + return store.LookupComposeModuleByID(svc.ctx, svc.store, moduleID) }) } @@ -132,7 +117,7 @@ func (svc module) FindByID(namespaceID, moduleID uint64) (m *types.Module, err e func (svc module) FindByName(namespaceID uint64, name string) (m *types.Module, err error) { return svc.lookup(namespaceID, func(aProps *moduleActionProps) (*types.Module, error) { aProps.module.Name = name - return svc.moduleRepo.FindByName(namespaceID, name) + return store.LookupComposeModuleByNamespaceIDName(svc.ctx, svc.store, namespaceID, name) }) } @@ -144,7 +129,7 @@ func (svc module) FindByHandle(namespaceID uint64, h string) (m *types.Module, e } aProps.module.Handle = h - return svc.moduleRepo.FindByHandle(namespaceID, h) + return store.LookupComposeModuleByNamespaceIDHandle(svc.ctx, svc.store, namespaceID, h) }) } @@ -174,214 +159,182 @@ func (svc module) FindByAny(namespaceID uint64, identifier interface{}) (m *type return m, nil } -func (svc module) Find(filter types.ModuleFilter) (set types.ModuleSet, f types.ModuleFilter, err error) { - var ( - aProps = &moduleActionProps{filter: &filter} - ) - - // For each fetched item, store backend will check if it is valid or not - filter.Check = func(res *types.Module) (bool, error) { - if !svc.ac.CanReadModule(svc.ctx, res) { - return false, nil - } - - return true, nil - } - - err = func() error { - set, f, err = svc.moduleRepo.Find(filter) - if err != nil { - return err - } - - // Preload all fields and update all modules - var ff types.ModuleFieldSet - if ff, err = svc.moduleRepo.FindFields(set.IDs()...); err != nil { - return err - } - - return set.Walk(func(m *types.Module) error { - m.Fields = ff.FilterByModule(m.ID) - return nil - }) - }() - - return set, f, svc.recordAction(svc.ctx, aProps, ModuleActionSearch, err) -} - func (svc module) Create(new *types.Module) (m *types.Module, err error) { var ( ns *types.Namespace aProps = &moduleActionProps{changed: new} ) - err = svc.db.Transaction(func() error { + err = store.Tx(svc.ctx, svc.store, func(ctx context.Context, s store.Storable) error { if !handle.IsValid(new.Handle) { return ModuleErrInvalidHandle() } - if ns, err = svc.loadNamespace(new.NamespaceID); err != nil { + if ns, err = loadNamespace(ctx, s, new.NamespaceID); err != nil { return err } - if !svc.ac.CanCreateModule(svc.ctx, ns) { + if !svc.ac.CanCreateModule(ctx, ns) { return ModuleErrNotAllowedToCreate() } aProps.setNamespace(ns) // Calling before-create scripts - if err = svc.eventbus.WaitFor(svc.ctx, event.ModuleBeforeCreate(new, nil, ns)); err != nil { + if err = svc.eventbus.WaitFor(ctx, event.ModuleBeforeCreate(new, nil, ns)); err != nil { return err } - if err = svc.UniqueCheck(new); err != nil { + if err = svc.uniqueCheck(new); err != nil { return err } - if m, err = svc.moduleRepo.Create(new); err != nil { - return err - } + new.ID = id.Next() + new.CreatedAt = *nowPtr() + new.UpdatedAt = nil + new.DeletedAt = nil + + m.Fields.Walk(func(f *types.ModuleField) error { + f.ModuleID = new.ID + f.CreatedAt = *nowPtr() + f.UpdatedAt = nil + f.DeletedAt = nil + return nil + }) aProps.setModule(m) - if err = svc.moduleRepo.UpdateFields(m.ID, m.Fields, false); err != nil { + if err = store.CreateComposeModule(ctx, s, new); err != nil { return err } - _ = svc.eventbus.WaitFor(svc.ctx, event.ModuleAfterCreate(m, nil, ns)) + if err = store.CreateComposeModuleField(ctx, s, m.Fields...); err != nil { + return err + } + + _ = svc.eventbus.WaitFor(ctx, event.ModuleAfterCreate(m, nil, ns)) return nil }) - return m, svc.recordAction(svc.ctx, aProps, ModuleActionCreate, err) + return new, svc.recordAction(svc.ctx, aProps, ModuleActionCreate, err) } -func (svc module) Update(upd *types.Module) (m *types.Module, err error) { - var ( - ns *types.Namespace - aProps = &moduleActionProps{changed: upd} - ) - - err = svc.db.Transaction(func() error { - - if upd.ID == 0 { - return ModuleErrInvalidID() - } - - if !handle.IsValid(upd.Handle) { - return ModuleErrInvalidHandle() - } - - if ns, err = svc.loadNamespace(upd.NamespaceID); err != nil { - return err - } - - aProps.setNamespace(ns) - - if m, err = svc.moduleRepo.FindByID(upd.NamespaceID, upd.ID); err != nil { - return err - } - - aProps.setModule(m) - - if isStale(upd.UpdatedAt, m.UpdatedAt, m.CreatedAt) { - return ModuleErrStaleData() - } - - if !svc.ac.CanUpdateModule(svc.ctx, m) { - return ModuleErrNotAllowedToUpdate() - } - - if err = svc.eventbus.WaitFor(svc.ctx, event.ModuleBeforeUpdate(upd, m, ns)); err != nil { - return err - } - - if err = svc.UniqueCheck(upd); err != nil { - return err - } - - m.Name = upd.Name - m.Handle = upd.Handle - m.Meta = upd.Meta - m.Fields = upd.Fields - - if m, err = svc.moduleRepo.Update(m); err != nil { - return err - } - - // select 1 record to see how fields can be updated - var rf = types.RecordFilter{} - rf.Limit = 1 - if _, rf, err = svc.recordRepo.Find(m, rf); err != nil { - return err - } - - if err = svc.moduleRepo.UpdateFields(m.ID, m.Fields, rf.Count > 0); err != nil { - return err - } - - _ = svc.eventbus.WaitFor(svc.ctx, event.ModuleAfterUpdate(upd, m, ns)) - return nil - }) - - return m, svc.recordAction(svc.ctx, aProps, ModuleActionUpdate, err) +func (svc module) Update(upd *types.Module) (c *types.Module, err error) { + return svc.updater(upd.NamespaceID, upd.ID, ModuleActionUpdate, svc.handleUpdate(upd)) } -func (svc module) DeleteByID(namespaceID, moduleID uint64) (err error) { +func (svc module) DeleteByID(namespaceID, moduleID uint64) error { + return trim1st(svc.updater(namespaceID, moduleID, ModuleActionDelete, svc.handleDelete)) +} + +func (svc module) UndeleteByID(namespaceID, moduleID uint64) error { + return trim1st(svc.updater(namespaceID, moduleID, ModuleActionUndelete, svc.handleUndelete)) +} + +func (svc module) updater(namespaceID, moduleID uint64, action func(...*moduleActionProps) *moduleAction, fn moduleUpdateHandler) (*types.Module, error) { var ( - m *types.Module + moduleChanged, fieldsChanged bool + ns *types.Namespace + m, old *types.Module aProps = &moduleActionProps{module: &types.Module{ID: moduleID, NamespaceID: namespaceID}} + err error ) - err = svc.db.Transaction(func() (err error) { - if moduleID == 0 { - return ModuleErrInvalidID() + err = store.Tx(svc.ctx, svc.store, func(ctx context.Context, s store.Storable) (err error) { + ns, m, err = loadModuleWithNamespace(svc.ctx, s, namespaceID, moduleID) + if err != nil { + return } - if ns, err = svc.loadNamespace(namespaceID); err != nil { - return err - } + old = m.Clone() aProps.setNamespace(ns) - - if m, err = svc.moduleRepo.FindByID(namespaceID, moduleID); err != nil { - if repository.ErrModuleNotFound.Eq(err) { - return ModuleErrNotFound() - } - - return err - } else if !svc.ac.CanDeleteModule(svc.ctx, m) { - return ModuleErrNotAllowedToDelete() - } - aProps.setChanged(m) - if err = svc.eventbus.WaitFor(svc.ctx, event.ModuleBeforeDelete(nil, m, ns)); err != nil { + if m.DeletedAt == nil { + err = svc.eventbus.WaitFor(svc.ctx, event.ModuleBeforeUpdate(m, old, ns)) + } else { + err = svc.eventbus.WaitFor(svc.ctx, event.ModuleBeforeDelete(m, old, ns)) + } + + if err != nil { + return + } + + if moduleChanged, fieldsChanged, err = fn(svc.ctx, ns, m); err != nil { return err } - if err = svc.moduleRepo.DeleteByID(namespaceID, moduleID); err != nil { - return err + if moduleChanged { + if err = svc.store.UpdateComposeModule(svc.ctx, m); err != nil { + return err + } + } + + if fieldsChanged { + var ( + hasRecords bool + // @todo + //store.SearchComposeRecords() + ) + + if err = updateModuleFields(ctx, s, m, m.Fields, hasRecords); err != nil { + + } + } + + if m.DeletedAt == nil { + err = svc.eventbus.WaitFor(svc.ctx, event.ModuleAfterUpdate(m, old, ns)) + } else { + err = svc.eventbus.WaitFor(svc.ctx, event.ModuleAfterDelete(nil, old, ns)) } - _ = svc.eventbus.WaitFor(svc.ctx, event.ModuleAfterDelete(nil, m, ns)) return err }) - return svc.recordAction(svc.ctx, aProps, ModuleActionDelete, err) - + return m, svc.recordAction(svc.ctx, aProps, action, err) } -func (svc module) UniqueCheck(m *types.Module) (err error) { +// lookup fn() orchestrates module lookup, namespace preload and check, module reading... +func (svc module) lookup(namespaceID uint64, lookup func(*moduleActionProps) (*types.Module, error)) (m *types.Module, err error) { + var aProps = &moduleActionProps{module: &types.Module{NamespaceID: namespaceID}} + + err = func() error { + if ns, err := loadNamespace(svc.ctx, svc.store, namespaceID); err != nil { + return err + } else { + aProps.setNamespace(ns) + } + + if m, err = lookup(aProps); errors.Is(err, store.ErrNotFound) { + return ModuleErrNotFound() + } else if err != nil { + return err + } + + aProps.setModule(m) + + if !svc.ac.CanReadModule(svc.ctx, m) { + return ModuleErrNotAllowedToRead() + } + + return loadModuleFields(svc.ctx, svc.store, m) + + }() + + return m, svc.recordAction(svc.ctx, aProps, ModuleActionLookup, err) +} + +func (svc module) uniqueCheck(m *types.Module) (err error) { if m.Handle != "" { - if e, _ := svc.moduleRepo.FindByHandle(m.NamespaceID, m.Handle); e != nil && e.ID > 0 && e.ID != m.ID { + if e, _ := store.LookupComposeModuleByNamespaceIDHandle(svc.ctx, svc.store, m.NamespaceID, m.Handle); e != nil && e.ID > 0 && e.ID != m.ID { return ModuleErrHandleNotUnique() } } if m.Name != "" { - if e, _ := svc.moduleRepo.FindByName(m.NamespaceID, m.Name); e != nil && e.ID > 0 && e.ID != m.ID { + if e, _ := store.LookupComposeModuleByNamespaceIDName(svc.ctx, svc.store, m.NamespaceID, m.Name); e != nil && e.ID > 0 && e.ID != m.ID { return ModuleErrNameNotUnique() } } @@ -389,29 +342,190 @@ func (svc module) UniqueCheck(m *types.Module) (err error) { return nil } -// Namespace loader -// -func (svc module) loadNamespace(namespaceID uint64) (ns *types.Namespace, err error) { - var ( - moProps = &moduleActionProps{namespace: &types.Namespace{ID: namespaceID}} - ) - - if namespaceID == 0 { - return nil, ModuleErrInvalidID(moProps) - } - - if ns, err = svc.nsRepo.FindByID(namespaceID); err != nil { - if repository.ErrNamespaceNotFound.Eq(err) { - return nil, ModuleErrNamespaceNotFound() +func (svc module) handleUpdate(upd *types.Module) moduleUpdateHandler { + return func(ctx context.Context, ns *types.Namespace, m *types.Module) (bool, bool, error) { + if isStale(upd.UpdatedAt, m.UpdatedAt, m.CreatedAt) { + return false, false, ModuleErrStaleData() } - return nil, err + if upd.Handle != m.Handle && !handle.IsValid(upd.Handle) { + return false, false, ModuleErrInvalidHandle() + } + + if err := svc.uniqueCheck(upd); err != nil { + return false, false, err + } + + if !svc.ac.CanUpdateModule(svc.ctx, m) { + return false, false, ModuleErrNotAllowedToUpdate() + } + + m.Name = upd.Name + m.Handle = upd.Handle + m.Meta = upd.Meta + m.Fields = upd.Fields + m.UpdatedAt = nowPtr() + + // @todo + // select 1 record to see how fields can be updated + //var rf = types.RecordFilter{} + //rf.Limit = 1 + //if _, rf, err = svc.recordRepo.Find(m, rf); err != nil { + // return err + //} + // + //if err = svc.moduleRepo.UpdateFields(m.ID, m.Fields, rf.Count > 0); err != nil { + // return err + //} + + return true, false, nil + } +} + +func (svc module) handleDelete(ctx context.Context, ns *types.Namespace, m *types.Module) (bool, bool, error) { + if !svc.ac.CanDeleteModule(ctx, m) { + return false, false, ModuleErrNotAllowedToUndelete() } - moProps.setNamespace(ns) + if m.DeletedAt != nil { + // module already deleted + return false, false, nil + } - if !svc.ac.CanReadNamespace(svc.ctx, ns) { - return nil, ModuleErrNotAllowedToReadNamespace(moProps) + m.DeletedAt = nowPtr() + return true, false, nil +} + +func (svc module) handleUndelete(ctx context.Context, ns *types.Namespace, m *types.Module) (bool, bool, error) { + if !svc.ac.CanDeleteModule(ctx, m) { + return false, false, ModuleErrNotAllowedToUndelete() + } + + if m.DeletedAt == nil { + // module not deleted + return false, false, nil + } + + m.DeletedAt = nil + return true, false, nil +} + +// updates module fields +// expecting to receive all module fields, as it deletes the rest +// also, sort order of the fields is also important as this fn stores and updates field's place as send +func updateModuleFields(ctx context.Context, s store.Storable, m *types.Module, ff types.ModuleFieldSet, hasRecords bool) error { + for _, f := range ff { + // Set module ID to all new fields + if f.ModuleID == 0 { + f.ModuleID = m.ID + } + + // Make sure all updating fields belong here + if f.ModuleID != m.ID { + return fmt.Errorf("module id of field %q does not match the module", f.Name) + } + } + + eff, _, err := store.SearchComposeModuleFields(ctx, s, types.ModuleFieldFilter{ModuleID: []uint64{m.ID}}) + if err != nil { + return err + } + + for _, ef := range eff { + f := ff.FindByID(ef.ID) + if f != nil || f.DeletedAt == nil { + continue + } + + ef.DeletedAt = nowPtr() + err = store.PartialComposeModuleFieldUpdate(ctx, s, []string{"deleted_at"}, ef) + if err != nil { + return err + } + } + + for idx, f := range ff { + f.Place = idx + f.DeletedAt = nil + + if e := eff.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 + } + + f.UpdatedAt = nowPtr() + + err = store.UpdateComposeModuleField(ctx, s, f) + } else { + f.ID = id.Next() + f.CreatedAt = *nowPtr() + err = store.CreateComposeModuleField(ctx, s, f) + } + + if err != nil { + return err + } + } + + return nil +} + +func loadModuleFields(ctx context.Context, s store.Storable, mm ...*types.Module) (err error) { + var ( + ff types.ModuleFieldSet + mff = types.ModuleFieldFilter{ModuleID: types.ModuleSet(mm).IDs()} + ) + + if ff, _, err = store.SearchComposeModuleFields(ctx, s, mff); err != nil { + return + } + + for _, m := range mm { + m.Fields = ff.FilterByModule(m.ID) + sort.Sort(m.Fields) + } + + return +} + +func loadModuleWithNamespace(ctx context.Context, s store.Storable, namespaceID, moduleID uint64) (ns *types.Namespace, m *types.Module, err error) { + if moduleID == 0 { + return nil, nil, ModuleErrInvalidID() + } + + if ns, err = loadNamespace(ctx, s, namespaceID); err == nil { + m, err = loadModule(ctx, s, moduleID) + } + + if err != nil { + return nil, nil, err + } + + if namespaceID != m.NamespaceID { + // Make sure chart belongs to the right namespace + return nil, nil, ModuleErrNotFound() + } + + return +} + +func loadModule(ctx context.Context, s store.Storable, moduleID uint64) (m *types.Module, err error) { + if moduleID == 0 { + return nil, ModuleErrInvalidID() + } + + if m, err = store.LookupComposeModuleByID(ctx, s, moduleID); errors.Is(err, store.ErrNotFound) { + err = ModuleErrNotFound() + } + + if err != nil { + return nil, err } return diff --git a/compose/service/module_actions.gen.go b/compose/service/module_actions.gen.go index 695f857d3..4710ff328 100644 --- a/compose/service/module_actions.gen.go +++ b/compose/service/module_actions.gen.go @@ -137,7 +137,7 @@ func (p moduleActionProps) serialize() actionlog.Meta { m.Set("filter.name", p.filter.Name, true) m.Set("filter.namespaceID", p.filter.NamespaceID, true) m.Set("filter.sort", p.filter.Sort, true) - m.Set("filter.Limit", p.filter.Limit, true) + m.Set("filter.limit", p.filter.Limit, true) } if p.namespace != nil { m.Set("namespace.name", p.namespace.Name, true) @@ -244,7 +244,7 @@ func (p moduleActionProps) tr(in string, err error) string { pairs = append(pairs, "{filter.name}", fns(p.filter.Name)) pairs = append(pairs, "{filter.namespaceID}", fns(p.filter.NamespaceID)) pairs = append(pairs, "{filter.sort}", fns(p.filter.Sort)) - pairs = append(pairs, "{filter.Limit}", fns(p.filter.Limit)) + pairs = append(pairs, "{filter.limit}", fns(p.filter.Limit)) } if p.namespace != nil { diff --git a/compose/service/module_actions.yaml b/compose/service/module_actions.yaml index ffccd44d7..3b4e6a6af 100644 --- a/compose/service/module_actions.yaml +++ b/compose/service/module_actions.yaml @@ -21,7 +21,7 @@ props: fields: [ name, handle, ID, namespaceID, meta, fields ] - name: filter type: "*types.ModuleFilter" - fields: [ query, name, handle, name, namespaceID, sort, Limit ] + fields: [ query, name, handle, name, namespaceID, sort, limit ] - name: namespace type: "*types.Namespace" fields: [ name, slug, ID ] @@ -56,7 +56,6 @@ errors: message: "namespace does not exist" severity: warning - - error: invalidID message: "invalid ID" severity: warning diff --git a/compose/service/namespace.go b/compose/service/namespace.go index 3d5592772..2535b6653 100644 --- a/compose/service/namespace.go +++ b/compose/service/namespace.go @@ -2,30 +2,24 @@ package service import ( "context" - "strconv" - - "github.com/titpetric/factory" - - "github.com/cortezaproject/corteza-server/compose/repository" + "errors" "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/eventbus" "github.com/cortezaproject/corteza-server/pkg/handle" "github.com/cortezaproject/corteza-server/pkg/permissions" + "github.com/cortezaproject/corteza-server/store" + "strconv" ) type ( namespace struct { - db *factory.DB - ctx context.Context - + ctx context.Context actionlog actionlog.Recorder - - ac namespaceAccessController - eventbus eventDispatcher - - namespaceRepo repository.NamespaceRepository + ac namespaceAccessController + eventbus eventDispatcher + store store.Storable } namespaceAccessController interface { @@ -51,6 +45,8 @@ type ( Update(namespace *types.Namespace) (*types.Namespace, error) DeleteByID(namespaceID uint64) error } + + namespaceUpdateHandler func(ctx context.Context, ns *types.Namespace) (bool, error) ) func Namespace() NamespaceService { @@ -61,43 +57,39 @@ func Namespace() NamespaceService { } func (svc namespace) With(ctx context.Context) NamespaceService { - db := repository.DB(ctx) return &namespace{ - db: db, - ctx: ctx, - + ctx: ctx, actionlog: DefaultActionlog, - - ac: svc.ac, - eventbus: svc.eventbus, - - namespaceRepo: repository.Namespace(ctx, db), + ac: svc.ac, + eventbus: svc.eventbus, + store: DefaultNgStore, } } -// lookup fn() orchestrates namespace lookup, and check -func (svc namespace) lookup(lookup func(*namespaceActionProps) (*types.Namespace, error)) (m *types.Namespace, err error) { - var aProps = &namespaceActionProps{namespace: &types.Namespace{}} +// search fn() orchestrates pages search, namespace preload and check +func (svc namespace) Find(filter types.NamespaceFilter) (set types.NamespaceSet, f types.NamespaceFilter, err error) { + var ( + aProps = &namespaceActionProps{filter: &filter} + ) - err = svc.db.Transaction(func() error { - if m, err = lookup(aProps); err != nil { - if repository.ErrNamespaceNotFound.Eq(err) { - return NamespaceErrNotFound() - } + // For each fetched item, store backend will check if it is valid or not + filter.Check = func(res *types.Namespace) (bool, error) { + if !svc.ac.CanReadNamespace(svc.ctx, res) { + return false, nil + } + return true, nil + } + + err = func() error { + if set, f, err = store.SearchComposeNamespaces(svc.ctx, svc.store, filter); err != nil { return err } - aProps.setNamespace(m) - - if !svc.ac.CanReadNamespace(svc.ctx, m) { - return NamespaceErrNotAllowedToRead() - } - return nil - }) + }() - return m, svc.recordAction(svc.ctx, aProps, NamespaceActionLookup, err) + return set, f, svc.recordAction(svc.ctx, aProps, NamespaceActionSearch, err) } func (svc namespace) FindByID(ID uint64) (ns *types.Namespace, err error) { @@ -107,7 +99,7 @@ func (svc namespace) FindByID(ID uint64) (ns *types.Namespace, err error) { } aProps.namespace.ID = ID - return svc.namespaceRepo.FindByID(ID) + return store.LookupComposeNamespaceByID(svc.ctx, svc.store, ID) }) } @@ -123,7 +115,7 @@ func (svc namespace) FindBySlug(slug string) (ns *types.Namespace, err error) { } aProps.namespace.Slug = slug - return svc.namespaceRepo.FindBySlug(slug) + return store.LookupComposeNamespaceBySlug(svc.ctx, svc.store, slug) }) } @@ -151,43 +143,13 @@ func (svc namespace) FindByAny(identifier interface{}) (r *types.Namespace, err return } -// search fn() orchestrates pages search, namespace preload and check -func (svc namespace) search(filter types.NamespaceFilter) (set types.NamespaceSet, f types.NamespaceFilter, err error) { - var ( - aProps = &namespaceActionProps{filter: &filter} - ) - - // For each fetched item, store backend will check if it is valid or not - filter.Check = func(res *types.Namespace) (bool, error) { - if !svc.ac.CanReadNamespace(svc.ctx, res) { - return false, nil - } - - return true, nil - } - - err = svc.db.Transaction(func() error { - if set, f, err = svc.namespaceRepo.Find(filter); err != nil { - return err - } - - return nil - }) - - return set, f, svc.recordAction(svc.ctx, aProps, NamespaceActionSearch, err) -} - -func (svc namespace) Find(filter types.NamespaceFilter) (types.NamespaceSet, types.NamespaceFilter, error) { - return svc.search(filter) -} - // Create adds namespace and presets access rules for role everyone func (svc namespace) Create(new *types.Namespace) (ns *types.Namespace, err error) { var ( aProps = &namespaceActionProps{changed: new} ) - err = svc.db.Transaction(func() (err error) { + err = store.Tx(svc.ctx, svc.store, func(ctx context.Context, s store.Storable) error { if !handle.IsValid(new.Slug) { return NamespaceErrInvalidHandle() } @@ -197,123 +159,184 @@ func (svc namespace) Create(new *types.Namespace) (ns *types.Namespace, err erro } if err = svc.eventbus.WaitFor(svc.ctx, event.NamespaceBeforeCreate(new, nil)); err != nil { - return + return err } - if err = svc.UniqueCheck(new); err != nil { - return + if err = svc.uniqueCheck(new); err != nil { + return err } - if ns, err = svc.namespaceRepo.Create(new); err != nil { + if err = store.CreateComposeNamespace(svc.ctx, svc.store, new); err != nil { return err } _ = svc.eventbus.WaitFor(svc.ctx, event.NamespaceAfterCreate(ns, nil)) - return + return nil }) return ns, svc.recordAction(svc.ctx, aProps, NamespaceActionCreate, err) } -func (svc namespace) Update(upd *types.Namespace) (ns *types.Namespace, err error) { +func (svc namespace) Update(upd *types.Namespace) (c *types.Namespace, err error) { + return svc.updater(upd.ID, NamespaceActionUpdate, svc.handleUpdate(upd)) +} + +func (svc namespace) DeleteByID(namespaceID uint64) error { + return trim1st(svc.updater(namespaceID, NamespaceActionDelete, svc.handleDelete)) +} + +func (svc namespace) UndeleteByID(namespaceID uint64) error { + return trim1st(svc.updater(namespaceID, NamespaceActionUndelete, svc.handleUndelete)) +} + +func (svc namespace) updater(namespaceID uint64, action func(...*namespaceActionProps) *namespaceAction, fn namespaceUpdateHandler) (*types.Namespace, error) { var ( - aProps = &namespaceActionProps{changed: upd} + changed bool + ns, old *types.Namespace + aProps = &namespaceActionProps{namespace: &types.Namespace{ID: namespaceID}} + err error ) - err = svc.db.Transaction(func() (err error) { - if upd.ID == 0 { - return NamespaceErrInvalidID() + err = store.Tx(svc.ctx, svc.store, func(ctx context.Context, s store.Storable) (err error) { + ns, err = loadNamespace(svc.ctx, s, namespaceID) + if err != nil { + return } - if !handle.IsValid(upd.Slug) { - return NamespaceErrInvalidHandle() + old = ns.Clone() + + aProps.setNamespace(ns) + aProps.setChanged(ns) + + if ns.DeletedAt == nil { + err = svc.eventbus.WaitFor(svc.ctx, event.NamespaceBeforeUpdate(ns, old)) + } else { + err = svc.eventbus.WaitFor(svc.ctx, event.NamespaceBeforeDelete(ns, old)) } - if ns, err = svc.FindByID(upd.ID); err != nil { + if err != nil { + return + } + + if changed, err = fn(svc.ctx, ns); err != nil { + return err + } + + if changed { + if err = svc.store.UpdateComposeNamespace(svc.ctx, ns); err != nil { + return err + } + } + + if ns.DeletedAt == nil { + err = svc.eventbus.WaitFor(svc.ctx, event.NamespaceAfterUpdate(ns, old)) + } else { + err = svc.eventbus.WaitFor(svc.ctx, event.NamespaceAfterDelete(nil, old)) + } + + return err + }) + + return ns, svc.recordAction(svc.ctx, aProps, action, err) +} + +// lookup fn() orchestrates namespace lookup, and check +func (svc namespace) lookup(lookup func(*namespaceActionProps) (*types.Namespace, error)) (ns *types.Namespace, err error) { + var aProps = &namespaceActionProps{namespace: &types.Namespace{}} + + err = func() error { + if ns, err = lookup(aProps); errors.Is(err, store.ErrNotFound) { + return NamespaceErrNotFound() + } else if err != nil { return err } aProps.setNamespace(ns) - if isStale(upd.UpdatedAt, ns.UpdatedAt, ns.CreatedAt) { - return NamespaceErrStaleData() + if !svc.ac.CanReadNamespace(svc.ctx, ns) { + return NamespaceErrNotAllowedToRead() } - if !svc.ac.CanUpdateNamespace(svc.ctx, ns) { - return NamespaceErrNotAllowedToUpdate() - } + return nil + }() - if err = svc.eventbus.WaitFor(svc.ctx, event.NamespaceBeforeUpdate(upd, ns)); err != nil { - return - } - - if err = svc.UniqueCheck(upd); err != nil { - return - } - - // Copy changes - ns.Name = upd.Name - ns.Slug = upd.Slug - ns.Meta = upd.Meta - ns.Enabled = upd.Enabled - - if ns, err = svc.namespaceRepo.Update(ns); err != nil { - return err - } - - _ = svc.eventbus.WaitFor(svc.ctx, event.NamespaceAfterUpdate(upd, ns)) - return - }) - - return ns, svc.recordAction(svc.ctx, aProps, NamespaceActionUpdate, err) + return ns, svc.recordAction(svc.ctx, aProps, NamespaceActionLookup, err) } -func (svc namespace) DeleteByID(namespaceID uint64) (err error) { - var ( - del *types.Namespace - aProps = &namespaceActionProps{namespace: &types.Namespace{ID: namespaceID}} - ) - - err = svc.db.Transaction(func() (err error) { - if namespaceID == 0 { - return NamespaceErrInvalidID() - } - - if del, err = svc.namespaceRepo.FindByID(namespaceID); err != nil { - if repository.ErrNamespaceNotFound.Eq(err) { - return NamespaceErrNotFound() - } - - return - } - - aProps.setChanged(del) - - if !svc.ac.CanDeleteNamespace(svc.ctx, del) { - return NamespaceErrNotAllowedToDelete() - } - - if err = svc.eventbus.WaitFor(svc.ctx, event.NamespaceBeforeDelete(nil, del)); err != nil { - return - } - - if err = svc.namespaceRepo.DeleteByID(namespaceID); err != nil { - return - } - - _ = svc.eventbus.WaitFor(svc.ctx, event.NamespaceAfterDelete(nil, del)) - return - }) - - return svc.recordAction(svc.ctx, aProps, NamespaceActionDelete, err) -} - -func (svc namespace) UniqueCheck(ns *types.Namespace) (err error) { +func (svc namespace) uniqueCheck(ns *types.Namespace) (err error) { if ns.Slug != "" { - if e, _ := svc.namespaceRepo.FindBySlug(ns.Slug); e != nil && e.ID != ns.ID { + if e, _ := store.LookupComposeNamespaceBySlug(svc.ctx, svc.store, ns.Slug); e != nil && e.ID != ns.ID { return NamespaceErrHandleNotUnique() } } return nil } + +func (svc namespace) handleUpdate(upd *types.Namespace) namespaceUpdateHandler { + return func(ctx context.Context, ns *types.Namespace) (bool, error) { + if isStale(upd.UpdatedAt, ns.UpdatedAt, ns.CreatedAt) { + return false, NamespaceErrStaleData() + } + + if upd.Slug != ns.Slug && !handle.IsValid(upd.Slug) { + return false, NamespaceErrInvalidHandle() + } + + if err := svc.uniqueCheck(upd); err != nil { + return false, err + } + + if !svc.ac.CanUpdateNamespace(svc.ctx, ns) { + return false, NamespaceErrNotAllowedToUpdate() + } + + ns.Name = upd.Name + ns.Slug = upd.Slug + ns.Meta = upd.Meta + ns.Enabled = upd.Enabled + ns.UpdatedAt = nowPtr() + + return true, nil + } +} + +func (svc namespace) handleDelete(ctx context.Context, ns *types.Namespace) (bool, error) { + if !svc.ac.CanDeleteNamespace(ctx, ns) { + return false, NamespaceErrNotAllowedToUndelete() + } + + if ns.DeletedAt != nil { + // namespace already deleted + return false, nil + } + + ns.DeletedAt = nowPtr() + return true, nil +} + +func (svc namespace) handleUndelete(ctx context.Context, ns *types.Namespace) (bool, error) { + if !svc.ac.CanDeleteNamespace(ctx, ns) { + return false, NamespaceErrNotAllowedToUndelete() + } + + if ns.DeletedAt == nil { + // namespace not deleted + return false, nil + } + + ns.DeletedAt = nil + return true, nil +} + +func loadNamespace(ctx context.Context, s store.Storable, namespaceID uint64) (ns *types.Namespace, err error) { + if namespaceID == 0 { + return nil, ChartErrInvalidNamespaceID() + } + + if ns, err = store.LookupComposeNamespaceByID(ctx, s, namespaceID); errors.Is(err, store.ErrNotFound) { + return nil, NamespaceErrNotFound() + } + + return +} diff --git a/compose/service/namespace_actions.gen.go b/compose/service/namespace_actions.gen.go index 3fa2a2d81..4d2a20b05 100644 --- a/compose/service/namespace_actions.gen.go +++ b/compose/service/namespace_actions.gen.go @@ -121,7 +121,7 @@ func (p namespaceActionProps) serialize() actionlog.Meta { m.Set("filter.query", p.filter.Query, true) m.Set("filter.slug", p.filter.Slug, true) m.Set("filter.sort", p.filter.Sort, true) - m.Set("filter.Limit", p.filter.Limit, true) + m.Set("filter.limit", p.filter.Limit, true) } return m @@ -215,7 +215,7 @@ func (p namespaceActionProps) tr(in string, err error) string { pairs = append(pairs, "{filter.query}", fns(p.filter.Query)) pairs = append(pairs, "{filter.slug}", fns(p.filter.Slug)) pairs = append(pairs, "{filter.sort}", fns(p.filter.Sort)) - pairs = append(pairs, "{filter.Limit}", fns(p.filter.Limit)) + pairs = append(pairs, "{filter.limit}", fns(p.filter.Limit)) } return strings.NewReplacer(pairs...).Replace(in) } diff --git a/compose/service/namespace_actions.yaml b/compose/service/namespace_actions.yaml index f2280f7fa..002fa46c1 100644 --- a/compose/service/namespace_actions.yaml +++ b/compose/service/namespace_actions.yaml @@ -21,7 +21,7 @@ props: fields: [ name, slug, ID, meta, enabled ] - name: filter type: "*types.NamespaceFilter" - fields: [ query, slug, sort, Limit ] + fields: [ query, slug, sort, limit ] actions: - action: search diff --git a/compose/service/page.go b/compose/service/page.go index 100a14902..8311e54a7 100644 --- a/compose/service/page.go +++ b/compose/service/page.go @@ -2,31 +2,23 @@ package service import ( "context" - - "github.com/titpetric/factory" - - "github.com/cortezaproject/corteza-server/compose/repository" + "errors" "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/eventbus" "github.com/cortezaproject/corteza-server/pkg/handle" "github.com/cortezaproject/corteza-server/pkg/permissions" + "github.com/cortezaproject/corteza-server/store" ) type ( page struct { - db *factory.DB - ctx context.Context - + ctx context.Context actionlog actionlog.Recorder - - ac pageAccessController - eventbus eventDispatcher - - pageRepo repository.PageRepository - moduleRepo repository.ModuleRepository - nsRepo repository.NamespaceRepository + ac pageAccessController + eventbus eventDispatcher + store store.Storable } pageAccessController interface { @@ -44,7 +36,7 @@ type ( FindByID(namespaceID, pageID uint64) (*types.Page, error) FindByHandle(namespaceID uint64, handle string) (*types.Page, error) - FindByModuleID(namespaceID, moduleID uint64) (*types.Page, error) + FindByPageID(namespaceID, pageID uint64) (*types.Page, error) FindBySelfID(namespaceID, selfID uint64) (pages types.PageSet, f types.PageFilter, err error) Find(filter types.PageFilter) (set types.PageSet, f types.PageFilter, err error) Tree(namespaceID uint64) (pages types.PageSet, err error) @@ -55,6 +47,8 @@ type ( Reorder(namespaceID, selfID uint64, pageIDs []uint64) error } + + pageUpdateHandler func(ctx context.Context, ns *types.Namespace, c *types.Page) (bool, error) ) func Page() PageService { @@ -65,53 +59,15 @@ func Page() PageService { } func (svc page) With(ctx context.Context) PageService { - db := repository.DB(ctx) return &page{ - db: db, - ctx: ctx, - + ctx: ctx, actionlog: DefaultActionlog, - - ac: svc.ac, - eventbus: svc.eventbus, - - pageRepo: repository.Page(ctx, db), - moduleRepo: repository.Module(ctx, db), - nsRepo: repository.Namespace(ctx, db), + ac: svc.ac, + eventbus: svc.eventbus, + store: DefaultNgStore, } } -// lookup fn() orchestrates page lookup, namespace preload and check -func (svc page) lookup(namespaceID uint64, lookup func(*pageActionProps) (*types.Page, error)) (p *types.Page, err error) { - var aProps = &pageActionProps{page: &types.Page{NamespaceID: namespaceID}} - - err = svc.db.Transaction(func() error { - if ns, err := svc.loadNamespace(namespaceID); err != nil { - return err - } else { - aProps.setNamespace(ns) - } - - if p, err = lookup(aProps); err != nil { - if repository.ErrPageNotFound.Eq(err) { - return PageErrNotFound() - } - - return err - } - - aProps.setPage(p) - - if !svc.ac.CanReadPage(svc.ctx, p) { - return PageErrNotAllowedToRead() - } - - return nil - }) - - return p, svc.recordAction(svc.ctx, aProps, PageActionLookup, err) -} - func (svc page) FindByID(namespaceID, pageID uint64) (p *types.Page, err error) { return svc.lookup(namespaceID, func(aProps *pageActionProps) (*types.Page, error) { if pageID == 0 { @@ -119,7 +75,7 @@ func (svc page) FindByID(namespaceID, pageID uint64) (p *types.Page, err error) } aProps.page.ID = pageID - return svc.pageRepo.FindByID(namespaceID, pageID) + return store.LookupComposePageByID(svc.ctx, svc.store, pageID) }) } @@ -130,18 +86,18 @@ func (svc page) FindByHandle(namespaceID uint64, h string) (c *types.Page, err e } aProps.page.Handle = h - return svc.pageRepo.FindByHandle(namespaceID, h) + return store.LookupComposePageByNamespaceIDHandle(svc.ctx, svc.store, namespaceID, h) }) } -func (svc page) FindByModuleID(namespaceID, moduleID uint64) (p *types.Page, err error) { +func (svc page) FindByPageID(namespaceID, pageID uint64) (p *types.Page, err error) { return svc.lookup(namespaceID, func(aProps *pageActionProps) (*types.Page, error) { - if moduleID == 0 { + if pageID == 0 { return nil, PageErrInvalidID() } - aProps.page.ModuleID = moduleID - return svc.pageRepo.FindByModuleID(namespaceID, moduleID) + aProps.page.ID = pageID + return store.LookupComposePageByID(svc.ctx, svc.store, pageID) }) } @@ -164,19 +120,19 @@ func (svc page) search(filter types.PageFilter) (set types.PageSet, f types.Page // For each fetched item, store backend will check if it is valid or not filter.Check = checkPage(svc.ctx, svc.ac) - err = svc.db.Transaction(func() error { - if ns, err := svc.loadNamespace(f.NamespaceID); err != nil { + err = func() error { + if ns, err := loadNamespace(svc.ctx, svc.store, f.NamespaceID); err != nil { return err } else { aProps.setNamespace(ns) } - if set, f, err = svc.pageRepo.Find(filter); err != nil { + if set, f, err = store.SearchComposePages(svc.ctx, svc.store, filter); err != nil { return err } return nil - }) + }() return set, f, svc.recordAction(svc.ctx, aProps, PageActionSearch, err) } @@ -245,8 +201,8 @@ func (svc page) Reorder(namespaceID, parentID uint64, pageIDs []uint64) (err err p *types.Page ) - err = svc.db.Transaction(func() (err error) { - if ns, err = svc.loadNamespace(namespaceID); err != nil { + err = store.Tx(svc.ctx, svc.store, func(ctx context.Context, s store.Storable) error { + if ns, err = loadNamespace(ctx, s, namespaceID); err != nil { return err } @@ -257,12 +213,10 @@ func (svc page) Reorder(namespaceID, parentID uint64, pageIDs []uint64) (err err } } else { // Validate permissions on parent page - if p, err = svc.pageRepo.FindByID(ns.ID, parentID); err != nil { - if repository.ErrPageNotFound.Eq(err) { - return PageErrNotFound() - } - - return + if p, err = store.LookupComposePageByID(ctx, s, parentID); errors.Is(err, store.ErrNotFound) { + return PageErrNotFound() + } else if err != nil { + return err } aProps.setPage(p) @@ -272,7 +226,7 @@ func (svc page) Reorder(namespaceID, parentID uint64, pageIDs []uint64) (err err } } - return svc.pageRepo.Reorder(namespaceID, parentID, pageIDs) + return store.ReorderComposePages(ctx, s, namespaceID, parentID, pageIDs) }) return svc.recordAction(svc.ctx, aProps, PageActionReorder, err) @@ -287,26 +241,30 @@ func (svc page) Create(new *types.Page) (p *types.Page, err error) { new.ID = 0 - err = svc.db.Transaction(func() error { + err = store.Tx(svc.ctx, svc.store, func(ctx context.Context, s store.Storable) error { if !handle.IsValid(new.Handle) { return PageErrInvalidID() } - if ns, err = svc.loadNamespace(new.NamespaceID); err != nil { + if ns, err = loadNamespace(ctx, s, new.NamespaceID); err != nil { return err - } else if !svc.ac.CanCreatePage(svc.ctx, ns) { + } + + if !svc.ac.CanCreatePage(svc.ctx, ns) { return PageErrNotAllowedToCreate() } + aProps.setNamespace(ns) + if err = svc.eventbus.WaitFor(svc.ctx, event.PageBeforeCreate(new, nil, ns)); err != nil { return err } - if err = svc.UniqueCheck(new); err != nil { + if err = svc.uniqueCheck(new); err != nil { return err } - if p, err = svc.pageRepo.Create(new); err != nil { + if err = store.CreateComposePage(ctx, s, new); err != nil { return err } @@ -317,124 +275,109 @@ func (svc page) Create(new *types.Page) (p *types.Page, err error) { return p, svc.recordAction(svc.ctx, aProps, PageActionCreate, err) } -func (svc page) Update(upd *types.Page) (p *types.Page, err error) { - var ( - ns *types.Namespace - aProps = &pageActionProps{changed: upd} - ) - - err = svc.db.Transaction(func() error { - if upd.ID == 0 { - return PageErrInvalidID() - } - - if !handle.IsValid(upd.Handle) { - return PageErrInvalidHandle() - } - - if ns, err = svc.loadNamespace(upd.NamespaceID); err != nil { - return err - } - - if p, err = svc.pageRepo.FindByID(upd.NamespaceID, upd.ID); err != nil { - if repository.ErrPageNotFound.Eq(err) { - return PageErrNotFound() - } - - return err - } - - if isStale(upd.UpdatedAt, p.UpdatedAt, p.CreatedAt) { - return PageErrStaleData() - } - - if !svc.ac.CanUpdatePage(svc.ctx, p) { - return PageErrNotAllowedToUpdate() - } - - if err = svc.eventbus.WaitFor(svc.ctx, event.PageBeforeUpdate(upd, p, ns)); err != nil { - return err - } - - if err = svc.UniqueCheck(upd); err != nil { - return err - } - - p.ModuleID = upd.ModuleID - p.SelfID = upd.SelfID - p.Blocks = upd.Blocks - p.Title = upd.Title - p.Handle = upd.Handle - p.Description = upd.Description - p.Visible = upd.Visible - p.Weight = upd.Weight - - if p, err = svc.pageRepo.Update(p); err != nil { - return err - } - - _ = svc.eventbus.WaitFor(svc.ctx, event.PageAfterUpdate(upd, p, ns)) - return err - }) - - return p, svc.recordAction(svc.ctx, aProps, PageActionUpdate, err) +func (svc page) Update(upd *types.Page) (c *types.Page, err error) { + return svc.updater(upd.NamespaceID, upd.ID, PageActionUpdate, svc.handleUpdate(upd)) } -func (svc page) DeleteByID(namespaceID, pageID uint64) (err error) { +func (svc page) DeleteByID(namespaceID, pageID uint64) error { + return trim1st(svc.updater(namespaceID, pageID, PageActionDelete, svc.handleDelete)) +} + +func (svc page) UndeleteByID(namespaceID, pageID uint64) error { + return trim1st(svc.updater(namespaceID, pageID, PageActionUndelete, svc.handleUndelete)) +} + +func (svc page) updater(namespaceID, pageID uint64, action func(...*pageActionProps) *pageAction, fn pageUpdateHandler) (*types.Page, error) { var ( - del *types.Page + changed bool + ns *types.Namespace + p, old *types.Page aProps = &pageActionProps{page: &types.Page{ID: pageID, NamespaceID: namespaceID}} + err error ) - err = svc.db.Transaction(func() error { - - if pageID == 0 { - return PageErrInvalidID() + err = store.Tx(svc.ctx, svc.store, func(ctx context.Context, s store.Storable) (err error) { + ns, p, err = loadPage(svc.ctx, s, namespaceID, pageID) + if err != nil { + return } - if ns, err = svc.loadNamespace(namespaceID); err != nil { + old = p.Clone() + + aProps.setNamespace(ns) + aProps.setChanged(p) + + if p.DeletedAt == nil { + err = svc.eventbus.WaitFor(svc.ctx, event.PageBeforeUpdate(p, old, ns)) + } else { + err = svc.eventbus.WaitFor(svc.ctx, event.PageBeforeDelete(p, old, ns)) + } + + if err != nil { + return + } + + if changed, err = fn(svc.ctx, ns, p); err != nil { return err } - if del, err = svc.pageRepo.FindByID(namespaceID, pageID); err != nil { - if repository.ErrPageNotFound.Eq(err) { - return PageErrNotFound() + if changed { + if err = svc.store.UpdateComposePage(svc.ctx, p); err != nil { + return err } - - return err } - aProps.setChanged(del) - - if !svc.ac.CanDeletePage(svc.ctx, del) { - return PageErrNotAllowedToDelete() + if p.DeletedAt == nil { + err = svc.eventbus.WaitFor(svc.ctx, event.PageAfterUpdate(p, old, ns)) + } else { + err = svc.eventbus.WaitFor(svc.ctx, event.PageAfterDelete(nil, old, ns)) } - if err = svc.eventbus.WaitFor(svc.ctx, event.PageBeforeDelete(nil, del, ns)); err != nil { - return err - } - - if err = svc.pageRepo.DeleteByID(namespaceID, pageID); err != nil { - return err - } - - _ = svc.eventbus.WaitFor(svc.ctx, event.PageAfterDelete(nil, del, ns)) return err }) - return svc.recordAction(svc.ctx, aProps, PageActionDelete, err) + return p, svc.recordAction(svc.ctx, aProps, action, err) } -func (svc page) UniqueCheck(p *types.Page) (err error) { +// lookup fn() orchestrates page lookup, namespace preload and check +func (svc page) lookup(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(svc.ctx, svc.store, namespaceID); err != nil { + return err + } else { + aProps.setNamespace(ns) + } + + if p, err = lookup(aProps); errors.Is(err, store.ErrNotFound) { + return PageErrNotFound() + } else if err != nil { + return err + } + + aProps.setPage(p) + + if !svc.ac.CanReadPage(svc.ctx, p) { + return PageErrNotAllowedToRead() + } + + return nil + }() + + return p, svc.recordAction(svc.ctx, aProps, PageActionLookup, err) +} + +func (svc page) uniqueCheck(p *types.Page) (err error) { if p.Handle != "" { - if e, _ := svc.pageRepo.FindByHandle(p.NamespaceID, p.Handle); e != nil && e.ID != p.ID { + if e, _ := store.LookupComposePageByNamespaceIDHandle(svc.ctx, svc.store, p.NamespaceID, p.Handle); e != nil && e.ID != p.ID { return PageErrHandleNotUnique() } } if p.ModuleID > 0 { - if e, _ := svc.pageRepo.FindByModuleID(p.NamespaceID, p.ModuleID); e != nil && e.ID != p.ID { + if e, _ := store.LookupComposePageByNamespaceIDModuleID(svc.ctx, svc.store, p.NamespaceID, p.ModuleID); e != nil && e.ID != p.ID { return PageErrModuleNotFound() } } @@ -442,17 +385,84 @@ func (svc page) UniqueCheck(p *types.Page) (err error) { return nil } -func (svc page) loadNamespace(namespaceID uint64) (ns *types.Namespace, err error) { - if namespaceID == 0 { - return nil, PageErrInvalidNamespaceID() +func (svc page) handleUpdate(upd *types.Page) pageUpdateHandler { + return func(ctx context.Context, ns *types.Namespace, p *types.Page) (bool, error) { + if isStale(upd.UpdatedAt, p.UpdatedAt, p.CreatedAt) { + return false, PageErrStaleData() + } + + if upd.Handle != p.Handle && !handle.IsValid(upd.Handle) { + return false, PageErrInvalidHandle() + } + + if err := svc.uniqueCheck(upd); err != nil { + return false, err + } + + if !svc.ac.CanUpdatePage(svc.ctx, p) { + return false, PageErrNotAllowedToUpdate() + } + + p.ID = upd.ID + p.SelfID = upd.SelfID + p.Blocks = upd.Blocks + p.Title = upd.Title + p.Handle = upd.Handle + p.Description = upd.Description + p.Visible = upd.Visible + p.Weight = upd.Weight + p.UpdatedAt = nowPtr() + + return true, nil + } +} + +func (svc page) handleDelete(ctx context.Context, ns *types.Namespace, m *types.Page) (bool, error) { + if !svc.ac.CanDeletePage(ctx, m) { + return false, PageErrNotAllowedToUndelete() } - if ns, err = svc.nsRepo.FindByID(namespaceID); err != nil { - return + if m.DeletedAt != nil { + // page already deleted + return false, nil } - if !svc.ac.CanReadNamespace(svc.ctx, ns) { - return nil, PageErrNotAllowedToReadNamespace() + m.DeletedAt = nowPtr() + return true, nil +} + +func (svc page) handleUndelete(ctx context.Context, ns *types.Namespace, m *types.Page) (bool, error) { + if !svc.ac.CanDeletePage(ctx, m) { + return false, PageErrNotAllowedToUndelete() + } + + if m.DeletedAt == nil { + // page not deleted + return false, nil + } + + m.DeletedAt = nil + return true, nil +} + +func loadPage(ctx context.Context, s store.Storable, namespaceID, pageID uint64) (ns *types.Namespace, m *types.Page, err error) { + if pageID == 0 { + return nil, nil, PageErrInvalidID() + } + + if ns, err = loadNamespace(ctx, s, namespaceID); err == nil { + if m, err = store.LookupComposePageByID(ctx, s, pageID); errors.Is(err, store.ErrNotFound) { + err = PageErrNotFound() + } + } + + if err != nil { + return nil, nil, err + } + + if namespaceID != m.NamespaceID { + // Make sure chart belongs to the right namespace + return nil, nil, PageErrNotFound() } return diff --git a/compose/service/page_actions.gen.go b/compose/service/page_actions.gen.go index 2f31f390b..10cea6320 100644 --- a/compose/service/page_actions.gen.go +++ b/compose/service/page_actions.gen.go @@ -141,7 +141,7 @@ func (p pageActionProps) serialize() actionlog.Meta { m.Set("filter.namespaceID", p.filter.NamespaceID, true) m.Set("filter.parentID", p.filter.ParentID, true) m.Set("filter.sort", p.filter.Sort, true) - m.Set("filter.Limit", p.filter.Limit, true) + m.Set("filter.limit", p.filter.Limit, true) } if p.namespace != nil { m.Set("namespace.name", p.namespace.Name, true) @@ -256,7 +256,7 @@ func (p pageActionProps) tr(in string, err error) string { pairs = append(pairs, "{filter.namespaceID}", fns(p.filter.NamespaceID)) pairs = append(pairs, "{filter.parentID}", fns(p.filter.ParentID)) pairs = append(pairs, "{filter.sort}", fns(p.filter.Sort)) - pairs = append(pairs, "{filter.Limit}", fns(p.filter.Limit)) + pairs = append(pairs, "{filter.limit}", fns(p.filter.Limit)) } if p.namespace != nil { diff --git a/compose/service/page_actions.yaml b/compose/service/page_actions.yaml index ac570ee23..b4d7b847e 100644 --- a/compose/service/page_actions.yaml +++ b/compose/service/page_actions.yaml @@ -21,7 +21,7 @@ props: fields: [ title, handle, ID, namespaceID, description, moduleID, blocks, visible, weight ] - name: filter type: "*types.PageFilter" - fields: [ query, handle, root, namespaceID, parentID, sort, Limit ] + fields: [ query, handle, root, namespaceID, parentID, sort, limit ] - name: namespace type: "*types.Namespace" fields: [ name, slug, ID ] diff --git a/compose/service/record.go b/compose/service/record.go index 4174f7d11..d0491215e 100644 --- a/compose/service/record.go +++ b/compose/service/record.go @@ -2,21 +2,20 @@ package service import ( "context" + "errors" "fmt" - "regexp" - "strconv" - "time" - - "github.com/titpetric/factory" - "github.com/cortezaproject/corteza-server/compose/decoder" - "github.com/cortezaproject/corteza-server/compose/repository" "github.com/cortezaproject/corteza-server/compose/service/event" "github.com/cortezaproject/corteza-server/compose/service/values" "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/eventbus" + "github.com/cortezaproject/corteza-server/pkg/id" + "github.com/cortezaproject/corteza-server/store" + "regexp" + "strconv" + "time" ) const ( @@ -26,7 +25,6 @@ const ( type ( record struct { - db *factory.DB ctx context.Context actionlog actionlog.Recorder @@ -34,9 +32,7 @@ type ( ac recordAccessController eventbus eventDispatcher - recordRepo repository.RecordRepository - moduleRepo repository.ModuleRepository - nsRepo repository.NamespaceRepository + store store.Storable formatter recordValuesFormatter sanitizer recordValuesSanitizer @@ -74,7 +70,7 @@ type ( RecordService interface { With(ctx context.Context) RecordService - FindByID(namespaceID, recordID uint64) (*types.Record, error) + FindByID(namespaceID, moduleID, recordID uint64) (*types.Record, error) Report(namespaceID, moduleID uint64, metrics, dimensions, filter string) (interface{}, error) Find(filter types.RecordFilter) (set types.RecordSet, f types.RecordFilter, err error) @@ -136,8 +132,6 @@ func Record() RecordService { } func (svc record) With(ctx context.Context) RecordService { - db := repository.DB(ctx) - // Initialize validator and setup all checkers it needs validator := values.Validator() @@ -146,7 +140,7 @@ func (svc record) With(ctx context.Context) RecordService { return 0, nil } - return repository.Record(ctx, db).RefValueLookup(m.ID, f.Name, v.Ref) + return store.ComposeRecordValueRefLookup(ctx, svc.store, m, f.Name, v.Ref) }) validator.RecordRefChecker(func(v *types.RecordValue, f *types.ModuleField, m *types.Module) (bool, error) { @@ -154,13 +148,13 @@ func (svc record) With(ctx context.Context) RecordService { return false, nil } - r, err := repository.Record(ctx, db).FindByID(m.NamespaceID, v.Ref) + r, err := store.LookupComposeRecordByID(ctx, svc.store, m, v.Ref) return r != nil, err }) validator.UserRefChecker(func(v *types.RecordValue, f *types.ModuleField, m *types.Module) (bool, error) { - // @todo cross service check - return true, nil + r, err := svc.store.LookupUserByID(ctx, v.Ref) + return r != nil, err }) validator.FileRefChecker(func(v *types.RecordValue, f *types.ModuleField, m *types.Module) (bool, error) { @@ -168,12 +162,13 @@ func (svc record) With(ctx context.Context) RecordService { return false, nil } - r, err := repository.Attachment(ctx, db).FindByID(m.NamespaceID, v.Ref) - return r != nil, err + // @todo refactor this + panic("refactor this") + //r, err := repository.Attachment(ctx, nil).FindByID(m.NamespaceID, v.Ref) + //return r != nil, err }) return &record{ - db: db, ctx: ctx, actionlog: DefaultActionlog, @@ -181,9 +176,7 @@ func (svc record) With(ctx context.Context) RecordService { ac: svc.ac, eventbus: svc.eventbus, - recordRepo: repository.Record(ctx, db), - moduleRepo: repository.Module(ctx, db), - nsRepo: repository.Namespace(ctx, db), + store: svc.store, formatter: values.Formatter(), sanitizer: values.Sanitizer(), @@ -198,7 +191,7 @@ func (svc *record) EventEmitting(enable bool) { } // lookup fn() orchestrates record lookup, namespace preload and check -func (svc record) lookup(namespaceID uint64, lookup func(*recordActionProps) (*types.Record, error)) (r *types.Record, err error) { +func (svc record) lookup(namespaceID, moduleID uint64, lookup func(*types.Module, *recordActionProps) (*types.Record, error)) (r *types.Record, err error) { var ( ns *types.Namespace m *types.Module @@ -206,28 +199,21 @@ func (svc record) lookup(namespaceID uint64, lookup func(*recordActionProps) (*t ) err = func() error { - if ns, err = svc.loadNamespace(namespaceID); err != nil { + if ns, m, err = loadModuleWithNamespace(svc.ctx, svc.store, namespaceID, moduleID); err != nil { return err } aProps.setNamespace(ns) + aProps.setModule(m) - if r, err = lookup(aProps); err != nil { - if repository.ErrRecordNotFound.Eq(err) { - return RecordErrNotFound() - } - + if r, err = lookup(m, aProps); errors.Is(err, store.ErrNotFound) { + return RecordErrNotFound() + } else if err != nil { return err } aProps.setRecord(r) - if m, err = svc.loadModule(namespaceID, r.ModuleID); err != nil { - return err - } - - aProps.setModule(m) - if !svc.ac.CanReadRecord(svc.ctx, m) { return RecordErrNotAllowedToRead() } @@ -242,64 +228,64 @@ func (svc record) lookup(namespaceID uint64, lookup func(*recordActionProps) (*t return r, svc.recordAction(svc.ctx, aProps, RecordActionLookup, err) } -func (svc record) FindByID(namespaceID, recordID uint64) (r *types.Record, err error) { - return svc.lookup(namespaceID, func(props *recordActionProps) (*types.Record, error) { +func (svc record) FindByID(namespaceID, moduleID, recordID uint64) (r *types.Record, err error) { + return svc.lookup(namespaceID, moduleID, func(m *types.Module, props *recordActionProps) (*types.Record, error) { props.record.ID = recordID - return svc.recordRepo.FindByID(namespaceID, recordID) + return store.LookupComposeRecordByID(svc.ctx, svc.store, m, recordID) }) } -func (svc record) loadModule(namespaceID, moduleID uint64) (m *types.Module, err error) { - return m, func() error { - if namespaceID == 0 { - return RecordErrInvalidNamespaceID() - } - - if moduleID == 0 { - return RecordErrInvalidModuleID() - } - - if m, err = svc.moduleRepo.FindByID(namespaceID, moduleID); err != nil { - if repository.ErrModuleNotFound.Eq(err) { - return RecordErrModuleNotFoundModule() - } - - return err - } - - if !svc.ac.CanReadModule(svc.ctx, m) { - return RecordErrNotAllowedToReadModule() - } - - if m.Fields, err = svc.moduleRepo.FindFields(m.ID); err != nil { - return err - } - - return nil - }() -} - -func (svc record) loadNamespace(namespaceID uint64) (ns *types.Namespace, err error) { - return ns, func() error { - if namespaceID == 0 { - return RecordErrInvalidNamespaceID() - } - - if ns, err = svc.nsRepo.FindByID(namespaceID); err != nil { - if repository.ErrNamespaceNotFound.Eq(err) { - return RecordErrNamespaceNotFound() - } - - return err - } - - if !svc.ac.CanReadNamespace(svc.ctx, ns) { - return RecordErrNotAllowedToReadNamespace() - } - - return err - }() -} +//func (svc record) loadModuleWithNamespace(namespaceID, moduleID uint64) (m *types.Module, err error) { +// return m, func() error { +// if namespaceID == 0 { +// return RecordErrInvalidNamespaceID() +// } +// +// if moduleID == 0 { +// return RecordErrInvalidModuleID() +// } +// +// if m, err = svc.moduleRepo.FindByID(namespaceID, moduleID); err != nil { +// if repository.ErrModuleNotFound.Eq(err) { +// return RecordErrModuleNotFoundModule() +// } +// +// return err +// } +// +// if !svc.ac.CanReadModule(svc.ctx, m) { +// return RecordErrNotAllowedToReadModule() +// } +// +// if m.Fields, err = svc.moduleRepo.FindFields(m.ID); err != nil { +// return err +// } +// +// return nil +// }() +//} +// +//func (svc record) loadNamespace(namespaceID uint64) (ns *types.Namespace, err error) { +// return ns, func() error { +// if namespaceID == 0 { +// return RecordErrInvalidNamespaceID() +// } +// +// if ns, err = svc.nsRepo.FindByID(namespaceID); err != nil { +// if repository.ErrNamespaceNotFound.Eq(err) { +// return RecordErrNamespaceNotFound() +// } +// +// return err +// } +// +// if !svc.ac.CanReadNamespace(svc.ctx, ns) { +// return RecordErrNotAllowedToReadNamespace() +// } +// +// return err +// }() +//} // Report generates report for a given module using metrics, dimensions and filter func (svc record) Report(namespaceID, moduleID uint64, metrics, dimensions, filter string) (out interface{}, err error) { @@ -310,19 +296,15 @@ func (svc record) Report(namespaceID, moduleID uint64, metrics, dimensions, filt ) err = func() error { - if ns, err = svc.loadNamespace(namespaceID); err != nil { + if ns, m, err = loadModuleWithNamespace(svc.ctx, svc.store, namespaceID, moduleID); err != nil { return err } aProps.setNamespace(ns) - - if m, err = svc.loadModule(namespaceID, moduleID); err != nil { - return err - } - aProps.setModule(m) - out, err = svc.recordRepo.Report(m, metrics, dimensions, filter) + panic("refactor") + //out, err = store.ComposeRecordReport(m, svc.store, metrics, dimensions, filter) return err }() @@ -336,11 +318,11 @@ func (svc record) Find(filter types.RecordFilter) (set types.RecordSet, f types. ) err = func() error { - if m, err = svc.loadModule(filter.NamespaceID, filter.ModuleID); err != nil { + if m, err = loadModule(svc.ctx, svc.store, filter.ModuleID); err != nil { return err } - set, f, err = svc.recordRepo.Find(m, filter) + set, f, err = store.SearchComposeRecords(svc.ctx, svc.store, m, filter) if err != nil { return err } @@ -408,18 +390,21 @@ func (svc record) Import(ses *RecordImportSession, ssvc ImportSessionService) (e // Export returns all records // // @todo better value handling -func (svc record) Export(filter types.RecordFilter, enc Encoder) (err error) { +func (svc record) Export(f types.RecordFilter, enc Encoder) (err error) { var ( - aProps = &recordActionProps{filter: &filter} + aProps = &recordActionProps{filter: &f} + + m *types.Module + set types.RecordSet ) - err = func() error { - m, err := svc.loadModule(filter.NamespaceID, filter.ModuleID) + err = func() (err error) { + m, err = loadModule(svc.ctx, svc.store, f.ModuleID) if err != nil { return err } - set, err := svc.recordRepo.Export(m, filter) + set, _, err = store.SearchComposeRecords(svc.ctx, svc.store, m, f) if err != nil { return err } @@ -560,7 +545,7 @@ func (svc record) create(new *types.Record) (rec *types.Record, err error) { m *types.Module ) - ns, m, _, err = svc.loadCombo(new.NamespaceID, new.ModuleID, 0) + ns, m, err = loadModuleWithNamespace(svc.ctx, svc.store, new.NamespaceID, new.ModuleID) if err != nil { return } @@ -602,12 +587,8 @@ func (svc record) create(new *types.Record) (rec *types.Record, err error) { return nil, RecordErrValueInput().Wrap(rve) } - err = svc.db.Transaction(func() error { - if new, err = svc.recordRepo.Create(new); err != nil { - return err - } - - return svc.recordRepo.UpdateValues(new.ID, new.Values) + err = store.Tx(svc.ctx, svc.store, func(ctx context.Context, s store.Storable) error { + return store.CreateComposeRecord(ctx, s, m, new) }) if err != nil { @@ -641,7 +622,7 @@ func (svc record) update(upd *types.Record) (rec *types.Record, err error) { return nil, RecordErrInvalidID() } - ns, m, old, err = svc.loadCombo(upd.NamespaceID, upd.ModuleID, upd.ID) + ns, m, old, err = svc.loadRecordCombo(svc.ctx, svc.store, upd.NamespaceID, upd.ModuleID, upd.ID) if err != nil { return } @@ -706,12 +687,8 @@ func (svc record) update(upd *types.Record) (rec *types.Record, err error) { return nil, RecordErrValueInput().Wrap(rve) } - err = svc.db.Transaction(func() error { - if upd, err = svc.recordRepo.Update(upd); err != nil { - return nil - } - - return svc.recordRepo.UpdateValues(upd.ID, upd.Values) + err = store.Tx(svc.ctx, svc.store, func(ctx context.Context, s store.Storable) error { + return store.UpdateComposeRecord(ctx, s, m, upd) }) @@ -766,6 +743,7 @@ func (svc record) procCreate(invokerID uint64, m *types.Module, new *types.Recor // Reset values to new record // to make sure nobody slips in something we do not want + new.ID = id.Next() new.CreatedBy = invokerID new.CreatedAt = *nowPtr() new.UpdatedAt = nil @@ -870,7 +848,7 @@ func (svc record) delete(namespaceID, moduleID, recordID uint64) (del *types.Rec return nil, RecordErrInvalidID() } - ns, m, del, err = svc.loadCombo(namespaceID, moduleID, recordID) + ns, m, del, err = svc.loadRecordCombo(svc.ctx, svc.store, namespaceID, moduleID, recordID) if err != nil { return nil, err } @@ -894,12 +872,8 @@ func (svc record) delete(namespaceID, moduleID, recordID uint64) (del *types.Rec del.DeletedAt = nowPtr() del.DeletedBy = invokerID - err = svc.db.Transaction(func() error { - if err = svc.recordRepo.Delete(del); err != nil { - return err - } - - return svc.recordRepo.DeleteValues(del) + err = store.Tx(svc.ctx, svc.store, func(ctx context.Context, s store.Storable) error { + return store.UpdateComposeRecord(ctx, s, m, del) }) if err != nil { @@ -939,7 +913,7 @@ func (svc record) DeleteByID(namespaceID, moduleID uint64, recordIDs ...uint64) return RecordErrInvalidModuleID() } - ns, m, _, err = svc.loadCombo(namespaceID, moduleID, 0) + ns, m, err = loadModuleWithNamespace(svc.ctx, svc.store, namespaceID, moduleID) if err != nil { return err } @@ -995,7 +969,7 @@ func (svc record) Organize(namespaceID, moduleID, recordID uint64, posField, pos ) err = func() error { - ns, m, r, err = svc.loadCombo(namespaceID, moduleID, recordID) + ns, m, r, err = svc.loadRecordCombo(svc.ctx, svc.store, namespaceID, moduleID, recordID) if err != nil { return err } @@ -1067,16 +1041,17 @@ func (svc record) Organize(namespaceID, moduleID, recordID uint64, posField, pos }) } - return svc.db.Transaction(func() error { + return store.Tx(svc.ctx, svc.store, func(ctx context.Context, s store.Storable) error { if len(recordValues) > 0 { svc.recordInfoUpdate(r) - if _, err = svc.recordRepo.Update(r); err != nil { + if err = store.UpdateComposeRecord(ctx, s, m, r); err != nil { return err } - if err = svc.recordRepo.PartialUpdateValues(recordValues...); err != nil { - return err - } + panic("refactor") + //if err = svc.recordRepo.PartialUpdateValues(recordValues...); err != nil { + // return err + //} } if reorderingRecords { @@ -1098,11 +1073,13 @@ func (svc record) Organize(namespaceID, moduleID, recordID uint64, posField, pos // We are interested only in records that have value of a sorting field greater than // the place we're moving our record to. // and sort the set with sorting field - set, _, err = svc.recordRepo.Find(m, types.RecordFilter{ - Query: fmt.Sprintf("%s(%s >= %d)", filter, posField, recordOrderPlace), - Sort: posField, - }) + filter := types.RecordFilter{} + filter.Query = fmt.Sprintf("%s(%s >= %d)", filter, posField, recordOrderPlace) + if err = filter.Sort.Set(posField); err != nil { + return err + } + set, _, err = store.SearchComposeRecords(ctx, s, m, filter) if err != nil { return err } @@ -1112,11 +1089,12 @@ func (svc record) Organize(namespaceID, moduleID, recordID uint64, posField, pos recordOrderPlace++ // Update each and every set - return svc.recordRepo.PartialUpdateValues(&types.RecordValue{ - RecordID: r.ID, - Name: posField, - Value: strconv.FormatUint(recordOrderPlace, 10), - }) + panic("refactor") + //return svc.recordRepo.PartialUpdateValues(&types.RecordValue{ + // RecordID: r.ID, + // Name: posField, + // Value: strconv.FormatUint(recordOrderPlace, 10), + //}) }) } @@ -1178,7 +1156,8 @@ func (svc record) Iterator(f types.RecordFilter, fn eventbus.HandlerFn, action s ) err = func() error { - if ns, m, _, err = svc.loadCombo(f.NamespaceID, f.ModuleID, 0); err != nil { + ns, m, err = loadModuleWithNamespace(svc.ctx, svc.store, f.NamespaceID, f.ModuleID) + if err != nil { return err } @@ -1204,7 +1183,7 @@ func (svc record) Iterator(f types.RecordFilter, fn eventbus.HandlerFn, action s } // @todo might be good to split set into smaller chunks - set, f, err = svc.recordRepo.Find(m, f) + set, f, err = store.SearchComposeRecords(svc.ctx, svc.store, m, f) if err != nil { return err } @@ -1230,8 +1209,6 @@ func (svc record) Iterator(f types.RecordFilter, fn eventbus.HandlerFn, action s case "clone": recordableAction = RecordActionIteratorClone - var cln *types.Record - // Assign defaults (only on missing values) rec.Values = svc.setDefaultValues(m, rec.Values) @@ -1240,14 +1217,8 @@ func (svc record) Iterator(f types.RecordFilter, fn eventbus.HandlerFn, action s return RecordErrValueInput().Wrap(rve) } - return svc.db.Transaction(func() error { - if cln, err = svc.recordRepo.Create(rec); err != nil { - return err - } else if err = svc.recordRepo.UpdateValues(cln.ID, cln.Values); err != nil { - return err - } - - return nil + return store.Tx(svc.ctx, svc.store, func(ctx context.Context, s store.Storable) error { + return store.CreateComposeRecord(ctx, s, m, rec) }) case "update": recordableAction = RecordActionIteratorUpdate @@ -1257,26 +1228,16 @@ func (svc record) Iterator(f types.RecordFilter, fn eventbus.HandlerFn, action s return RecordErrValueInput().Wrap(rve) } - return svc.db.Transaction(func() error { - if rec, err = svc.recordRepo.Update(rec); err != nil { - return err - } else if err = svc.recordRepo.UpdateValues(rec.ID, rec.Values); err != nil { - return err - } - - return nil + return store.Tx(svc.ctx, svc.store, func(ctx context.Context, s store.Storable) error { + return store.UpdateComposeRecord(ctx, s, m, rec) }) case "delete": recordableAction = RecordActionIteratorDelete - return svc.db.Transaction(func() error { - if err = svc.recordRepo.Delete(rec); err != nil { - return err - } else if err = svc.recordRepo.DeleteValues(rec); err != nil { - return err - } - - return nil + return store.Tx(svc.ctx, svc.store, func(ctx context.Context, s store.Storable) error { + rec.DeletedAt = nowPtr() + rec.DeletedBy = invokerID + return store.UpdateComposeRecord(ctx, s, m, rec) }) } @@ -1298,36 +1259,6 @@ func (svc record) Iterator(f types.RecordFilter, fn eventbus.HandlerFn, action s } -// loadCombo Loads everything we need for record manipulation -// -// Loads namespace, module, record and set of triggers. -func (svc record) loadCombo(namespaceID, moduleID, recordID uint64) (ns *types.Namespace, m *types.Module, r *types.Record, err error) { - if namespaceID == 0 { - return nil, nil, nil, RecordErrInvalidNamespaceID() - } - if ns, err = svc.loadNamespace(namespaceID); err != nil { - return - } - - if recordID > 0 { - if r, err = svc.recordRepo.FindByID(namespaceID, recordID); err != nil { - return - } - - if r.ModuleID != moduleID && moduleID > 0 { - return nil, nil, nil, RecordErrInvalidModuleID() - } - } - - if moduleID > 0 { - if m, err = svc.loadModule(ns.ID, moduleID); err != nil { - return - } - } - - return -} - func (svc record) setDefaultValues(m *types.Module, vv types.RecordValueSet) (out types.RecordValueSet) { out = vv @@ -1410,14 +1341,15 @@ func (svc record) generalValueSetValidation(m *types.Module, vv types.RecordValu } func (svc record) preloadValues(m *types.Module, rr ...*types.Record) error { - if rvs, err := svc.recordRepo.LoadValues(svc.readableFields(m), types.RecordSet(rr).IDs()); err != nil { - return err - } else { - return types.RecordSet(rr).Walk(func(r *types.Record) error { - r.Values = svc.formatter.Run(m, rvs.FilterByRecordID(r.ID)) - return nil - }) - } + panic("refactor") + //if rvs, err := svc.recordRepo.LoadValues(svc.readableFields(m), types.RecordSet(rr).IDs()); err != nil { + // return err + //} else { + // return types.RecordSet(rr).Walk(func(r *types.Record) error { + // r.Values = svc.formatter.Run(m, rvs.FilterByRecordID(r.ID)) + // return nil + // }) + //} } // readableFields creates a slice of module fields that current user has permission to read @@ -1434,3 +1366,20 @@ func (svc record) readableFields(m *types.Module) []string { return ff } + +// 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) { + if ns, m, err = loadModuleWithNamespace(ctx, s, namespaceID, moduleID); err != nil { + return + } + + if r, err = store.LookupComposeRecordByID(ctx, s, m, recordID); err != nil { + return + } + + if r.ModuleID != moduleID { + return nil, nil, nil, RecordErrInvalidModuleID() + } + + return +} diff --git a/compose/service/record_actions.gen.go b/compose/service/record_actions.gen.go index 8307dadf5..c2a307e74 100644 --- a/compose/service/record_actions.gen.go +++ b/compose/service/record_actions.gen.go @@ -195,9 +195,6 @@ func (p recordActionProps) serialize() actionlog.Meta { m.Set("filter.deleted", p.filter.Deleted, true) m.Set("filter.sort", p.filter.Sort, true) m.Set("filter.limit", p.filter.Limit, true) - m.Set("filter.offset", p.filter.Offset, true) - m.Set("filter.page", p.filter.Page, true) - m.Set("filter.perPage", p.filter.PerPage, true) } if p.namespace != nil { m.Set("namespace.name", p.namespace.Name, true) @@ -303,9 +300,6 @@ func (p recordActionProps) tr(in string, err error) string { p.filter.Deleted, p.filter.Sort, p.filter.Limit, - p.filter.Offset, - p.filter.Page, - p.filter.PerPage, ), ) pairs = append(pairs, "{filter.query}", fns(p.filter.Query)) @@ -314,9 +308,6 @@ func (p recordActionProps) tr(in string, err error) string { pairs = append(pairs, "{filter.deleted}", fns(p.filter.Deleted)) pairs = append(pairs, "{filter.sort}", fns(p.filter.Sort)) pairs = append(pairs, "{filter.limit}", fns(p.filter.Limit)) - pairs = append(pairs, "{filter.offset}", fns(p.filter.Offset)) - pairs = append(pairs, "{filter.page}", fns(p.filter.Page)) - pairs = append(pairs, "{filter.perPage}", fns(p.filter.PerPage)) } if p.namespace != nil { diff --git a/compose/service/record_actions.yaml b/compose/service/record_actions.yaml index 16d1bf164..2a76ec6b7 100644 --- a/compose/service/record_actions.yaml +++ b/compose/service/record_actions.yaml @@ -21,7 +21,7 @@ props: fields: [ ID, moduleID, namespaceID, ownedBy ] - name: filter type: "*types.RecordFilter" - fields: [ query, namespaceID, moduleID, deleted, sort, limit, offset, page, perPage ] + fields: [ query, namespaceID, moduleID, deleted, sort, limit ] - name: namespace type: "*types.Namespace" fields: [ name, slug, ID ] diff --git a/compose/service/service.go b/compose/service/service.go index 31f54356c..54e40506d 100644 --- a/compose/service/service.go +++ b/compose/service/service.go @@ -3,20 +3,20 @@ package service import ( "context" "errors" - "github.com/cortezaproject/corteza-server/pkg/healthcheck" - "go.uber.org/zap" - "time" - "github.com/cortezaproject/corteza-server/compose/types" "github.com/cortezaproject/corteza-server/pkg/actionlog" "github.com/cortezaproject/corteza-server/pkg/corredor" "github.com/cortezaproject/corteza-server/pkg/eventbus" + "github.com/cortezaproject/corteza-server/pkg/healthcheck" "github.com/cortezaproject/corteza-server/pkg/options" "github.com/cortezaproject/corteza-server/pkg/permissions" "github.com/cortezaproject/corteza-server/pkg/store" "github.com/cortezaproject/corteza-server/pkg/store/minio" "github.com/cortezaproject/corteza-server/pkg/store/plain" + ngStore "github.com/cortezaproject/corteza-server/store" systemService "github.com/cortezaproject/corteza-server/system/service" + "go.uber.org/zap" + "time" ) type ( @@ -34,15 +34,6 @@ type ( WaitFor(ctx context.Context, ev eventbus.Event) (err error) Dispatch(ctx context.Context, ev eventbus.Event) } - - // storeInterface wraps generated interfaces to enable extensions - storeInterface interface { - // Include generated interfaces - storeGeneratedInterfaces - - // And all additional required functions - // ... - } ) var ( @@ -51,7 +42,7 @@ var ( // DefaultNgStore is an interface to storage backend(s) // ng (next-gen) is a temporary prefix // so that we can differentiate between it and the file-only store - DefaultNgStore storeInterface + DefaultNgStore ngStore.Storable DefaultLogger *zap.Logger @@ -82,16 +73,12 @@ var ( ) // Initializes compose-only services -func Initialize(ctx context.Context, log *zap.Logger, s interface{}, c Config) (err error) { +func Initialize(ctx context.Context, log *zap.Logger, s ngStore.Storable, c Config) (err error) { var ( hcd = healthcheck.Defaults() - - cmpStore storeInterface ) - // we're doing conversion to avoid having - // store interface exposed or generated inside app package - cmpStore = s.(storeInterface) + DefaultNgStore = s DefaultLogger = log.Named("service") @@ -112,7 +99,7 @@ func Initialize(ctx context.Context, log *zap.Logger, s interface{}, c Config) ( if DefaultPermissions == nil { // Do not override permissions service stored under DefaultPermissions // to allow integration tests to inject own permission service - DefaultPermissions = permissions.Service(ctx, DefaultLogger, cmpStore) + DefaultPermissions = permissions.Service(ctx, DefaultLogger, s) } DefaultAccessControl = AccessControl(DefaultPermissions) @@ -186,12 +173,12 @@ func RegisterIteratorProviders() { corredor.Service().RegisterIteratorProvider( "compose:record", func(ctx context.Context, f map[string]string, h eventbus.HandlerFn, action string) error { - rf := types.RecordFilter{ - Query: f["query"], - Sort: f["sort"], - } + rf := types.RecordFilter{Query: f["query"]} + rf.Sort.Set(f["sort"]) - rf.ParsePagination(f) + panic("refactor") + //rf.Paging.Limit = + //rf.ParsePagination(f) if nsLookup, has := f["namespace"]; !has { return errors.New("namespace for record iteration filter not defined") @@ -232,3 +219,8 @@ func nowPtr() *time.Time { now := time.Now() return &now } + +// trim1st removes 1st param and returns only error +func trim1st(_ interface{}, err error) error { + return err +} diff --git a/compose/service/store_interface.gen.go b/compose/service/store_interface.gen.go deleted file mode 100644 index 1b9c9ad45..000000000 --- a/compose/service/store_interface.gen.go +++ /dev/null @@ -1,30 +0,0 @@ -package service - -// This file is auto-generated. -// -// Template: pkg/store_interfaces_joined.gen.go.tpl -// Definitions: -// - store/actionlog.yaml -// - store/compose_charts.yaml -// - store/compose_module_fields.yaml -// - store/compose_modules.yaml -// - store/compose_namespaces.yaml -// - store/compose_pages.yaml -// - store/rbac_rules.yaml -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// - -type ( - // Interface combines interfaces of all supported store interfaces - storeGeneratedInterfaces interface { - actionlogsStore - composeChartsStore - composeModuleFieldsStore - composeModulesStore - composeNamespacesStore - composePagesStore - rbacRulesStore - } -) diff --git a/compose/service/store_interface_actionlog.gen.go b/compose/service/store_interface_actionlog.gen.go deleted file mode 100644 index 1b67c3e64..000000000 --- a/compose/service/store_interface_actionlog.gen.go +++ /dev/null @@ -1,27 +0,0 @@ -package service - -// This file is auto-generated. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -// Definitions file that controls how this file is generated: -// - store/actionlog.yaml - -import ( - "context" - "github.com/cortezaproject/corteza-server/pkg/actionlog" -) - -type ( - actionlogsStore interface { - SearchActionlogs(ctx context.Context, f actionlog.Filter) (actionlog.ActionSet, actionlog.Filter, error) - CreateActionlog(ctx context.Context, rr ...*actionlog.Action) error - UpdateActionlog(ctx context.Context, rr ...*actionlog.Action) error - PartialUpdateActionlog(ctx context.Context, onlyColumns []string, rr ...*actionlog.Action) error - RemoveActionlog(ctx context.Context, rr ...*actionlog.Action) error - RemoveActionlogByID(ctx context.Context, ID uint64) error - - TruncateActionlogs(ctx context.Context) error - } -) diff --git a/compose/service/store_interface_compose_attachments.gen.go b/compose/service/store_interface_compose_attachments.gen.go deleted file mode 100644 index 57ad6571c..000000000 --- a/compose/service/store_interface_compose_attachments.gen.go +++ /dev/null @@ -1,28 +0,0 @@ -package service - -// This file is auto-generated. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -// Definitions file that controls how this file is generated: -// - store/compose_attachments.yaml - -import ( - "context" - "github.com/cortezaproject/corteza-server/compose/types" -) - -type ( - composeAttachmentsStore 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 - PartialUpdateComposeAttachment(ctx context.Context, onlyColumns []string, rr ...*types.Attachment) error - RemoveComposeAttachment(ctx context.Context, rr ...*types.Attachment) error - RemoveComposeAttachmentByID(ctx context.Context, ID uint64) error - - TruncateComposeAttachments(ctx context.Context) error - } -) diff --git a/compose/service/store_interface_compose_charts.gen.go b/compose/service/store_interface_compose_charts.gen.go deleted file mode 100644 index 6d711c03a..000000000 --- a/compose/service/store_interface_compose_charts.gen.go +++ /dev/null @@ -1,29 +0,0 @@ -package service - -// This file is auto-generated. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -// Definitions file that controls how this file is generated: -// - store/compose_charts.yaml - -import ( - "context" - "github.com/cortezaproject/corteza-server/compose/types" -) - -type ( - composeChartsStore interface { - SearchComposeCharts(ctx context.Context, f types.ChartFilter) (types.ChartSet, types.ChartFilter, error) - LookupComposeChartByID(ctx context.Context, id uint64) (*types.Chart, error) - LookupComposeChartByHandle(ctx context.Context, handle string) (*types.Chart, error) - CreateComposeChart(ctx context.Context, rr ...*types.Chart) error - UpdateComposeChart(ctx context.Context, rr ...*types.Chart) error - PartialUpdateComposeChart(ctx context.Context, onlyColumns []string, rr ...*types.Chart) error - RemoveComposeChart(ctx context.Context, rr ...*types.Chart) error - RemoveComposeChartByID(ctx context.Context, ID uint64) error - - TruncateComposeCharts(ctx context.Context) error - } -) diff --git a/compose/service/store_interface_compose_module_fields.gen.go b/compose/service/store_interface_compose_module_fields.gen.go deleted file mode 100644 index c2893bf6e..000000000 --- a/compose/service/store_interface_compose_module_fields.gen.go +++ /dev/null @@ -1,26 +0,0 @@ -package service - -// This file is auto-generated. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -// Definitions file that controls how this file is generated: -// - store/compose_module_fields.yaml - -import ( - "context" - "github.com/cortezaproject/corteza-server/compose/types" -) - -type ( - composeModuleFieldsStore interface { - CreateComposeModuleField(ctx context.Context, rr ...*types.ModuleField) error - UpdateComposeModuleField(ctx context.Context, rr ...*types.ModuleField) error - PartialUpdateComposeModuleField(ctx context.Context, onlyColumns []string, rr ...*types.ModuleField) error - RemoveComposeModuleField(ctx context.Context, rr ...*types.ModuleField) error - RemoveComposeModuleFieldByID(ctx context.Context, ID uint64) error - - TruncateComposeModuleFields(ctx context.Context) error - } -) diff --git a/compose/service/store_interface_compose_modules.gen.go b/compose/service/store_interface_compose_modules.gen.go deleted file mode 100644 index 89f83f6e2..000000000 --- a/compose/service/store_interface_compose_modules.gen.go +++ /dev/null @@ -1,29 +0,0 @@ -package service - -// This file is auto-generated. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -// Definitions file that controls how this file is generated: -// - store/compose_modules.yaml - -import ( - "context" - "github.com/cortezaproject/corteza-server/compose/types" -) - -type ( - composeModulesStore interface { - SearchComposeModules(ctx context.Context, f types.ModuleFilter) (types.ModuleSet, types.ModuleFilter, error) - LookupComposeModuleByHandle(ctx context.Context, handle string) (*types.Module, error) - LookupComposeModuleByID(ctx context.Context, id uint64) (*types.Module, error) - CreateComposeModule(ctx context.Context, rr ...*types.Module) error - UpdateComposeModule(ctx context.Context, rr ...*types.Module) error - PartialUpdateComposeModule(ctx context.Context, onlyColumns []string, rr ...*types.Module) error - RemoveComposeModule(ctx context.Context, rr ...*types.Module) error - RemoveComposeModuleByID(ctx context.Context, ID uint64) error - - TruncateComposeModules(ctx context.Context) error - } -) diff --git a/compose/service/store_interface_compose_namespaces.gen.go b/compose/service/store_interface_compose_namespaces.gen.go deleted file mode 100644 index 7bb68236e..000000000 --- a/compose/service/store_interface_compose_namespaces.gen.go +++ /dev/null @@ -1,29 +0,0 @@ -package service - -// This file is auto-generated. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -// Definitions file that controls how this file is generated: -// - store/compose_namespaces.yaml - -import ( - "context" - "github.com/cortezaproject/corteza-server/compose/types" -) - -type ( - composeNamespacesStore interface { - SearchComposeNamespaces(ctx context.Context, f types.NamespaceFilter) (types.NamespaceSet, types.NamespaceFilter, error) - LookupComposeNamespaceBySlug(ctx context.Context, slug string) (*types.Namespace, error) - LookupComposeNamespaceByID(ctx context.Context, id uint64) (*types.Namespace, error) - CreateComposeNamespace(ctx context.Context, rr ...*types.Namespace) error - UpdateComposeNamespace(ctx context.Context, rr ...*types.Namespace) error - PartialUpdateComposeNamespace(ctx context.Context, onlyColumns []string, rr ...*types.Namespace) error - RemoveComposeNamespace(ctx context.Context, rr ...*types.Namespace) error - RemoveComposeNamespaceByID(ctx context.Context, ID uint64) error - - TruncateComposeNamespaces(ctx context.Context) error - } -) diff --git a/compose/service/store_interface_compose_pages.gen.go b/compose/service/store_interface_compose_pages.gen.go deleted file mode 100644 index 8632ea81c..000000000 --- a/compose/service/store_interface_compose_pages.gen.go +++ /dev/null @@ -1,29 +0,0 @@ -package service - -// This file is auto-generated. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -// Definitions file that controls how this file is generated: -// - store/compose_pages.yaml - -import ( - "context" - "github.com/cortezaproject/corteza-server/compose/types" -) - -type ( - composePagesStore interface { - SearchComposePages(ctx context.Context, f types.PageFilter) (types.PageSet, types.PageFilter, error) - LookupComposePageByHandle(ctx context.Context, handle string) (*types.Page, error) - LookupComposePageByID(ctx context.Context, id uint64) (*types.Page, error) - CreateComposePage(ctx context.Context, rr ...*types.Page) error - UpdateComposePage(ctx context.Context, rr ...*types.Page) error - PartialUpdateComposePage(ctx context.Context, onlyColumns []string, rr ...*types.Page) error - RemoveComposePage(ctx context.Context, rr ...*types.Page) error - RemoveComposePageByID(ctx context.Context, ID uint64) error - - TruncateComposePages(ctx context.Context) error - } -) diff --git a/compose/service/store_interface_rbac_rules.gen.go b/compose/service/store_interface_rbac_rules.gen.go deleted file mode 100644 index c244d22ab..000000000 --- a/compose/service/store_interface_rbac_rules.gen.go +++ /dev/null @@ -1,27 +0,0 @@ -package service - -// This file is auto-generated. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -// Definitions file that controls how this file is generated: -// - store/rbac_rules.yaml - -import ( - "context" - "github.com/cortezaproject/corteza-server/pkg/permissions" -) - -type ( - rbacRulesStore interface { - SearchRbacRules(ctx context.Context, f permissions.RuleFilter) (permissions.RuleSet, permissions.RuleFilter, error) - CreateRbacRule(ctx context.Context, rr ...*permissions.Rule) error - UpdateRbacRule(ctx context.Context, rr ...*permissions.Rule) error - PartialUpdateRbacRule(ctx context.Context, onlyColumns []string, rr ...*permissions.Rule) error - RemoveRbacRule(ctx context.Context, rr ...*permissions.Rule) error - RemoveRbacRuleByRoleIDResourceOperation(ctx context.Context, roleID uint64, resource string, operation string) error - - TruncateRbacRules(ctx context.Context) error - } -) diff --git a/compose/types/chart.go b/compose/types/chart.go index 0879dc34f..7213fca68 100644 --- a/compose/types/chart.go +++ b/compose/types/chart.go @@ -3,8 +3,8 @@ package types import ( "database/sql/driver" "encoding/json" + "github.com/cortezaproject/corteza-server/pkg/filter" "github.com/cortezaproject/corteza-server/pkg/rh" - "github.com/cortezaproject/corteza-server/store" "time" "github.com/pkg/errors" @@ -56,8 +56,8 @@ type ( Check func(*Chart) (bool, error) `json:"-"` // Standard helpers for paging and sorting - store.Sorting - store.Paging + filter.Sorting + filter.Paging } ) diff --git a/compose/types/module.go b/compose/types/module.go index 31cd730af..1c6acf1e4 100644 --- a/compose/types/module.go +++ b/compose/types/module.go @@ -1,7 +1,7 @@ package types import ( - "github.com/cortezaproject/corteza-server/store" + "github.com/cortezaproject/corteza-server/pkg/filter" "time" "github.com/jmoiron/sqlx/types" @@ -40,8 +40,8 @@ type ( Check func(*Module) (bool, error) `json:"-"` // Standard helpers for paging and sorting - store.Sorting - store.Paging + filter.Sorting + filter.Paging } ) @@ -54,6 +54,12 @@ func (m Module) DynamicRoles(userID uint64) []uint64 { return nil } +func (m Module) Clone() *Module { + c := &m + c.Fields = m.Fields.Clone() + return c +} + // FindByHandle finds module by it's handle func (set ModuleSet) FindByHandle(handle string) *Module { for i := range set { diff --git a/compose/types/module_field.go b/compose/types/module_field.go index 7d3e9903f..f4d311bfc 100644 --- a/compose/types/module_field.go +++ b/compose/types/module_field.go @@ -3,6 +3,7 @@ package types import ( "database/sql/driver" "encoding/json" + "github.com/cortezaproject/corteza-server/pkg/rh" "sort" "time" @@ -32,6 +33,11 @@ type ( UpdatedAt *time.Time `json:"updatedAt,omitempty"` DeletedAt *time.Time `json:"deletedAt,omitempty"` } + + ModuleFieldFilter struct { + ModuleID []uint64 + Deleted rh.FilterState + } ) var ( @@ -47,6 +53,19 @@ func (m ModuleField) DynamicRoles(userID uint64) []uint64 { return nil } +func (m ModuleField) Clone() *ModuleField { + return &m +} + +func (set ModuleFieldSet) Clone() (out ModuleFieldSet) { + out = make([]*ModuleField, len(set)) + for i := range set { + out[i] = set[i].Clone() + } + + return out +} + func (set *ModuleFieldSet) Scan(src interface{}) error { if data, ok := src.([]byte); ok { return json.Unmarshal(data, set) diff --git a/compose/types/namespace.go b/compose/types/namespace.go index e5b357f85..1e7239dc6 100644 --- a/compose/types/namespace.go +++ b/compose/types/namespace.go @@ -3,7 +3,7 @@ package types import ( "database/sql/driver" "encoding/json" - "github.com/cortezaproject/corteza-server/store" + "github.com/cortezaproject/corteza-server/pkg/filter" "time" "github.com/pkg/errors" @@ -39,8 +39,8 @@ type ( Check func(*Namespace) (bool, error) `json:"-"` // Standard helpers for paging and sorting - store.Sorting - store.Paging + filter.Sorting + filter.Paging } NamespaceMeta struct { @@ -58,6 +58,11 @@ func (n Namespace) DynamicRoles(userID uint64) []uint64 { return nil } +func (n Namespace) Clone() *Namespace { + c := &n + return c +} + // FindByHandle finds namespace by it's handle/slug func (set NamespaceSet) FindByHandle(handle string) *Namespace { for i := range set { diff --git a/compose/types/page.go b/compose/types/page.go index 8d38980ee..68e9b2878 100644 --- a/compose/types/page.go +++ b/compose/types/page.go @@ -3,7 +3,7 @@ package types import ( "database/sql/driver" "encoding/json" - "github.com/cortezaproject/corteza-server/store" + "github.com/cortezaproject/corteza-server/pkg/filter" "time" "github.com/pkg/errors" @@ -69,8 +69,8 @@ type ( Check func(*Page) (bool, error) `json:"-"` // Standard helpers for paging and sorting - store.Sorting - store.Paging + filter.Sorting + filter.Paging } ) @@ -83,6 +83,11 @@ func (p Page) DynamicRoles(userID uint64) []uint64 { return nil } +func (m Page) Clone() *Page { + c := &m + return c +} + // FindByHandle finds page by it's handle func (set PageSet) FindByHandle(handle string) *Page { for i := range set { diff --git a/compose/types/record.go b/compose/types/record.go index adc8d4572..1d2764bdf 100644 --- a/compose/types/record.go +++ b/compose/types/record.go @@ -3,6 +3,7 @@ package types import ( "encoding/json" "fmt" + "github.com/cortezaproject/corteza-server/pkg/filter" "strconv" "time" @@ -35,7 +36,7 @@ type ( ID uint64 `json:"recordID,string"` ModuleID uint64 `json:"moduleID,string"` - Values RecordValueSet `json:"values,omitempty" db:"-"` + Values RecordValueSet `json:"values,omitempty"` NamespaceID uint64 `json:"namespaceID,string"` @@ -52,10 +53,11 @@ type ( ModuleID uint64 `json:"moduleID,string"` NamespaceID uint64 `json:"namespaceID,string"` Query string `json:"query"` - Sort string `json:"sort"` - // Standard helpers for paging and sorting - rh.PageFilter + // Preloaded set of additional modules that are used for record filtering + // Modules ModuleSet + + Deleted rh.FilterState `json:"deleted"` // 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 @@ -63,7 +65,9 @@ type ( // Store then loads additional resources to satisfy the paging parameters Check func(*Record) (bool, error) `json:"-"` - Deleted rh.FilterState `json:"deleted"` + // Standard helpers for paging and sorting + filter.Sorting + filter.Paging } ) diff --git a/compose/types/record_value.go b/compose/types/record_value.go index 4d330c326..14ecbe510 100644 --- a/compose/types/record_value.go +++ b/compose/types/record_value.go @@ -19,8 +19,12 @@ type ( Place uint `json:"-"` DeletedAt *time.Time `json:"deletedAt,omitempty"` - Updated bool `db:"-" json:"-"` - OldValue string `db:"-" json:"-"` + Updated bool `json:"-"` + OldValue string `json:"-"` + } + + RecordValueFilter struct { + RecordID []uint64 } ) @@ -119,6 +123,12 @@ func (set RecordValueSet) Has(name string, place uint) bool { return false } +func (set RecordValueSet) SetRecordID(recordID uint64) { + for i := range set { + set[i].RecordID = recordID + } +} + func (set RecordValueSet) SetUpdatedFlag(updated bool) { for i := range set { set[i].Updated = updated diff --git a/go.mod b/go.mod index 55e1e1d3c..7b269f938 100644 --- a/go.mod +++ b/go.mod @@ -4,22 +4,22 @@ go 1.12 require ( cloud.google.com/go v0.44.3 // indirect - github.com/0xAX/notificator v0.0.0-20191016112426-3962a5ea8da1 // indirect github.com/360EntSecGroup-Skylar/excelize/v2 v2.0.2 github.com/766b/chi-prometheus v0.0.0-20180509160047-46ac2b31aa30 github.com/99designs/basicauth-go v0.0.0-20160802081356-2a93ba0f464d - github.com/ExpansiveWorlds/instrumentedsql v0.0.0-20171218214018-45abb4b1947d // indirect + github.com/Masterminds/goutils v1.1.0 // indirect + github.com/Masterminds/semver v1.5.0 // indirect + github.com/Masterminds/sprig v2.22.0+incompatible github.com/Masterminds/squirrel v1.1.1-0.20191017225151-12f2162c8d8d github.com/PaesslerAG/gval v1.0.1 github.com/PaesslerAG/jsonpath v0.1.1 // indirect github.com/SentimensRG/ctx v0.0.0-20180729130232-0bfd988c655d - github.com/codegangsta/envy v0.0.0-20141216192214-4b78388c8ce4 // indirect - github.com/codegangsta/gin v0.0.0-20171026143024-cafe2ce98974 // indirect github.com/crusttech/go-oidc v0.0.0-20180918092017-982855dad3e1 github.com/davecgh/go-spew v1.1.1 github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/disintegration/imaging v1.6.0 github.com/edwvee/exiffix v0.0.0-20180602190213-b57537c92a6b + github.com/fsnotify/fsnotify v1.4.9 github.com/gabriel-vasile/mimetype v0.3.17 github.com/getsentry/sentry-go v0.1.1 github.com/go-chi/chi v3.3.4+incompatible @@ -29,27 +29,29 @@ require ( github.com/golang/mock v1.3.1 github.com/golang/protobuf v1.3.3 github.com/gomodule/redigo v2.0.0+incompatible + github.com/google/uuid v1.1.1 // indirect github.com/gorhill/cronexpr v0.0.0-20180427100037-88b0669f7d75 github.com/gorilla/mux v1.7.1 // indirect github.com/gorilla/sessions v1.1.3 github.com/gorilla/websocket v1.4.0 github.com/goware/statik v0.2.0 + github.com/huandu/xstrings v1.3.2 // indirect + github.com/imdario/mergo v0.3.11 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/jmoiron/sqlx v1.2.0 github.com/joho/godotenv v1.3.0 github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 github.com/lib/pq v1.1.0 github.com/markbates/goth v1.50.0 - github.com/mattn/go-shellwords v1.0.10 // indirect github.com/mattn/go-sqlite3 v1.14.0 github.com/minio/minio-go/v6 v6.0.39 + github.com/mitchellh/copystructure v1.0.0 // indirect github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect github.com/pkg/errors v0.8.1 github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect github.com/prometheus/client_golang v0.9.3 github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd // indirect github.com/schollz/progressbar/v2 v2.15.0 - github.com/simukti/sqldb-logger v0.0.0-20200602044015-843152fd150e // indirect github.com/smartystreets/goconvey v1.6.4 // indirect github.com/sony/sonyflake v0.0.0-20181109022403-6d5bd6181009 github.com/spf13/afero v1.2.2 @@ -69,6 +71,6 @@ require ( gopkg.in/ini.v1 v1.51.0 // indirect gopkg.in/mail.v2 v2.3.1 gopkg.in/square/go-jose.v2 v2.3.1 // indirect - gopkg.in/urfave/cli.v1 v1.20.0 // indirect - gopkg.in/yaml.v2 v2.2.8 + gopkg.in/yaml.v2 v2.3.0 + gopkg.in/yaml.v3 v3.0.0-20200601152816-913338de1bd2 // indirect ) diff --git a/go.sum b/go.sum index 621e42e0d..6eb03c931 100644 --- a/go.sum +++ b/go.sum @@ -6,8 +6,6 @@ cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6A cloud.google.com/go v0.44.3 h1:0sMegbmn/8uTwpNkB0q9cLEpZ2W5a6kl+wtBQgPWBJQ= cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -github.com/0xAX/notificator v0.0.0-20191016112426-3962a5ea8da1 h1:j9HaafapDbPbGRDku6e/HRs6KBMcKHiWcm1/9Sbxnl4= -github.com/0xAX/notificator v0.0.0-20191016112426-3962a5ea8da1/go.mod h1:NtXa9WwQsukMHZpjNakTTz0LArxvGYdPA9CjIcUSZ6s= github.com/360EntSecGroup-Skylar/excelize/v2 v2.0.2 h1:StMrA6UQ5Cm6206DxXGuV/NMqSIOIDoMXMYt8JPe1lE= github.com/360EntSecGroup-Skylar/excelize/v2 v2.0.2/go.mod h1:EfRHD2k+Kd7ijnqlwOrH1IifwgWB9yYJ0pdXtBZmlpU= github.com/766b/chi-prometheus v0.0.0-20180509160047-46ac2b31aa30 h1:bNHbCMKiQxpRNe4Pk2W09N1aXXc4ICOawQFKIDEicqc= @@ -17,8 +15,12 @@ github.com/99designs/basicauth-go v0.0.0-20160802081356-2a93ba0f464d/go.mod h1:3 github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/ExpansiveWorlds/instrumentedsql v0.0.0-20171218214018-45abb4b1947d h1:r+whow+VHd9kAd4UTtQ/rtvcvmwkdryKUcGofpYOp+8= -github.com/ExpansiveWorlds/instrumentedsql v0.0.0-20171218214018-45abb4b1947d/go.mod h1:Lm6NFlzU3HvZIo5l8GykZn6MH8/wq/A/X/d+7P/hgZU= +github.com/Masterminds/goutils v1.1.0 h1:zukEsf/1JZwCMgHiK3GZftabmxiCw4apj3a28RPBiVg= +github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= +github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= +github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= +github.com/Masterminds/sprig v2.22.0+incompatible h1:z4yfnGrZ7netVz+0EDJ0Wi+5VZCSYp4Z0m2dk6cEM60= +github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= github.com/Masterminds/squirrel v1.1.1-0.20191017225151-12f2162c8d8d h1:5ocAvSd8xUzrwz0ZTZrSWFLhYTwdnXBxlIJeVIkO5y8= github.com/Masterminds/squirrel v1.1.1-0.20191017225151-12f2162c8d8d/go.mod h1:yaPeOnPG5ZRwL9oKdTsO/prlkPbXWZlRVMQ/gGlzIuA= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= @@ -42,11 +44,6 @@ github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/codegangsta/envy v0.0.0-20141216192214-4b78388c8ce4 h1:ihrIKrLQzm6Q6NJHBMemvaIGTFxgxQUEkn2AjN0Aulw= -github.com/codegangsta/envy v0.0.0-20141216192214-4b78388c8ce4/go.mod h1:X7wHz0C25Lga6CnJ4WAQNbUQ9P/8eWSNv8qIO71YkSM= -github.com/codegangsta/gin v0.0.0-20171026143024-cafe2ce98974 h1:ysuVNDVE4LIky6I+6JlgAKG+wBNKMpVv3m3neVpvFVw= -github.com/codegangsta/gin v0.0.0-20171026143024-cafe2ce98974/go.mod h1:UBYuwaH3dMw91EZ7tGVaFF6GDj5j46S7zqB9lZPIe58= -github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/crusttech/go-oidc v0.0.0-20180918092017-982855dad3e1 h1:V2GKd4ImRY9lFUu3TclHNmqgJCADdA7muym9JuVEPlY= github.com/crusttech/go-oidc v0.0.0-20180918092017-982855dad3e1/go.mod h1:2LVBu240CBZgYkGOSRx733tkh9QUNcH+fIqmeAsdrds= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -65,8 +62,8 @@ github.com/edwvee/exiffix v0.0.0-20180602190213-b57537c92a6b/go.mod h1:KoE3Ti1qb github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/francoispqt/gojay v0.0.0-20181220093123-f2cc13a668ca/go.mod h1:H8Wgri1Asi1VevY3ySdpIK5+KCpqzToVswNq8g2xZj4= -github.com/francoispqt/onelog v0.0.0-20190306043706-8c2bb31b10a4/go.mod h1:v1Il1fkBpjiYPpEJcGxqgrPUPcHuTC7eHh9zBV3CLBE= +github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/gabriel-vasile/mimetype v0.3.17 h1:NGWgggJJqTofUcTV1E7hkk2zVjZ54EfJa1z5O3z6By4= github.com/gabriel-vasile/mimetype v0.3.17/go.mod h1:kMJbg3SlWZCsj4R73F1WDzbT9AyGCOVmUtIxxwO5pmI= github.com/getsentry/sentry-go v0.1.1 h1:5jcHHCJ+EUK+X8aoDbJUzZU1kG4ZFk2xX15BKv29v10= @@ -112,6 +109,8 @@ github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXi github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= @@ -135,6 +134,10 @@ github.com/goware/statik v0.2.0 h1:2dKnJIawSr/qbd4TdSgRtNc6mdVZrTOR56aSiL47460= github.com/goware/statik v0.2.0/go.mod h1:Fktf+coYRC3SB2RfBB++LAG6ojA/VzuDp0Jfd064ICs= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw= +github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA= +github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jarcoal/httpmock v0.0.0-20180424175123-9c70cfe4a1da/go.mod h1:ks+b9deReOc7jgqp+e7LuFiCBH6Rm5hL32cLcEAArb4= @@ -148,7 +151,6 @@ github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfV github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= @@ -165,8 +167,6 @@ github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/markbates/going v1.0.0/go.mod h1:I6mnB4BPnEeqo85ynXIx1ZFLLbtiLHNXVgWeFO9OGOA= github.com/markbates/goth v1.50.0 h1:KCAErbDdHh11gQAJs/GV73LCv4NwA7Z6wNZAU32ggMc= github.com/markbates/goth v1.50.0/go.mod h1:zZmAw0Es0Dpm7TT/4AdN14QrkiWLMrrU9Xei1o+/mdA= -github.com/mattn/go-shellwords v1.0.10 h1:Y7Xqm8piKOO3v10Thp7Z36h4FYFjt5xB//6XvOrs2Gw= -github.com/mattn/go-shellwords v1.0.10/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= github.com/mattn/go-sqlite3 v1.9.0 h1:pDRiWfl+++eC2FEFRy6jXmQlvp4Yh3z1MJKg4UeYM/4= github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v1.14.0 h1:mLyGNKR8+Vv9CAU7PphKa2hkEqxxhn8i32J6FPj1/QA= @@ -179,8 +179,12 @@ github.com/minio/sha256-simd v0.1.1 h1:5QHSlgo3nt5yKOJrC7W8w7X+NFl8cMPZm96iu8kKU github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM= github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ= github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw= +github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ= +github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY= +github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= github.com/mrjones/oauth v0.0.0-20180629183705-f4e24b6d100c/go.mod h1:skjdDftzkFALcuGzYSklqYd8gvat6F1gZJ4YPVbkZpM= @@ -213,18 +217,12 @@ github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084 h1:sofwID9zm4tzr github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= -github.com/rs/zerolog v1.19.0 h1:hYz4ZVdUgjXTBUmrkrw55j1nHx68LfOKIQk5IYtyScg= -github.com/rs/zerolog v1.19.0/go.mod h1:IzD0RJ65iWH0w97OQQebJEvTZYvsCUm9WVLWBQrJRjo= github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd h1:CmH9+J6ZSsIjUK3dcGsnCnO41eRBOnY12zwkn5qVwgc= github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk= github.com/schollz/progressbar/v2 v2.15.0 h1:dVzHQ8fHRmtPjD3K10jT3Qgn/+H+92jhPrhmxIJfDz8= github.com/schollz/progressbar/v2 v2.15.0/go.mod h1:UdPq3prGkfQ7MOzZKlDRpYKcFqEMczbD7YmbPgpzKMI= -github.com/simukti/sqldb-logger v0.0.0-20200602044015-843152fd150e h1:Zdf7DuOyh+sxySqbEHxIrH246IcCCokKAQNql+Hsy3w= -github.com/simukti/sqldb-logger v0.0.0-20200602044015-843152fd150e/go.mod h1:S2r2jtBph6XWCYytnRMerRfmp6gwiWBsmEWTPbCJWCU= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a h1:pa8hGb/2YqsZKovtsgrwcDH1RZhVbTKCjLp47XpqCDs= @@ -249,33 +247,22 @@ github.com/steinfletcher/apitest-jsonpath v1.3.0 h1:mq6ILNSPtjher/QMp1q93BUgASvj github.com/steinfletcher/apitest-jsonpath v1.3.0/go.mod h1:3wP64zeeW8CNUN1g01ZaH0fnVco4s4a6z7KcLTTR6e0= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.0 h1:jlIyCplCJFULU/01vCkhKuTyc3OorI3bJFuw6obfgho= github.com/stretchr/testify v1.6.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/titpetric/factory v0.0.0-20190806200833-ae4b02b9e034 h1:H2WfXOKTtF37QV4McGIRH37zNa00NgZjRJ/5wwxqSv8= -github.com/titpetric/factory v0.0.0-20190806200833-ae4b02b9e034/go.mod h1:VFd2XRrQZoX9cOxpeZezKpOlXDwU/dbRejKLjwP+xY8= github.com/titpetric/factory v0.0.0-20190828134837-8466c9bef13f h1:sTgdEUlmmNSU3QKe8n20jR26zYEKw6ciSuj919XobV4= github.com/titpetric/factory v0.0.0-20190828134837-8466c9bef13f/go.mod h1:VFd2XRrQZoX9cOxpeZezKpOlXDwU/dbRejKLjwP+xY8= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -go.uber.org/atomic v1.5.0 h1:OI5t8sDa1Or+q8AeE+yKeB/SDYioSHAgcVljj9JIETY= -go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.6.0 h1:Ezj3JGmsOnG1MoRWQkPBsKLe9DwWD9QeXzTRzzldNVk= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= -go.uber.org/multierr v1.3.0 h1:sFPn2GLc3poCkfrpIXGhBD2X0CMIo4Q/zSULXrj/+uc= -go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A= go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= -go.uber.org/zap v1.13.0 h1:nR6NoDBgAf67s68NhaXbsojM+2gxp3S1hWkHDl27pVU= -go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= go.uber.org/zap v1.15.0 h1:ZZCA22JRF2gQE5FoNmhmrf7jeJJ2uhqDUNRYKm8dvmM= go.uber.org/zap v1.15.0/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -339,6 +326,7 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0 h1:HyfiK1WMnHj5FXFXatD+Qs1A/xC2Run6RzeW1SyHxpc= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -360,7 +348,6 @@ golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBn golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190828213141-aed303cbaa74/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5 h1:hKsoRgsbwY1NafxrwTs+k64bikrLBkAgPir1TNCj3Zs= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -386,8 +373,6 @@ google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98 google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -google.golang.org/grpc v1.22.1 h1:/7cs52RnTJmD43s3uxzlq2U7nqVTd/37viQwMrMNlOM= -google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.29.1 h1:EC2SB8S04d2r73uptxphDSUG+kTKVgjRPF+N3xpxRB4= @@ -410,13 +395,11 @@ gopkg.in/mail.v2 v2.3.1 h1:WYFn/oANrAGP2C0dcV6/pbkPzv8yGzqTjPmTeO7qoXk= gopkg.in/mail.v2 v2.3.1/go.mod h1:htwXN1Qh09vZJ1NVKxQqHPBaCBbzKhp5GzuJEA4VJWw= gopkg.in/square/go-jose.v2 v2.3.1 h1:SK5KegNXmKmqE342YYN2qPHEnUYeoMiXXl1poUlI+o4= gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= -gopkg.in/urfave/cli.v1 v1.20.0 h1:NdAVW6RYxDif9DhDHaAortIu956m2c0v+09AZBPTbE0= -gopkg.in/urfave/cli.v1 v1.20.0/go.mod h1:vuBzUtMdQeixQj8LVd+/98pzhxNGQoyuPBlsXHOQNO0= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200601152816-913338de1bd2 h1:VEmvx0P+GVTgkNu2EdTN988YCZPcD3lo9AoczZpucwc= gopkg.in/yaml.v3 v3.0.0-20200601152816-913338de1bd2/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/messaging/service/service.go b/messaging/service/service.go index ef9facac9..8fdda4847 100644 --- a/messaging/service/service.go +++ b/messaging/service/service.go @@ -2,17 +2,16 @@ package service import ( "context" - "github.com/cortezaproject/corteza-server/pkg/healthcheck" - "time" - - "go.uber.org/zap" - "github.com/cortezaproject/corteza-server/pkg/actionlog" + "github.com/cortezaproject/corteza-server/pkg/healthcheck" "github.com/cortezaproject/corteza-server/pkg/options" "github.com/cortezaproject/corteza-server/pkg/permissions" "github.com/cortezaproject/corteza-server/pkg/store" "github.com/cortezaproject/corteza-server/pkg/store/minio" "github.com/cortezaproject/corteza-server/pkg/store/plain" + ngStore "github.com/cortezaproject/corteza-server/store" + "go.uber.org/zap" + "time" ) type ( @@ -29,15 +28,6 @@ type ( ActionLog options.ActionLogOpt Storage options.StorageOpt } - - // storeInterface wraps generated interfaces to enable extensions - storeInterface interface { - // Include generated interfaces - storeGeneratedInterfaces - - // And all additional required functions - // ... - } ) var ( @@ -46,7 +36,7 @@ var ( // DefaultNgStore is an interface to storage backend(s) // ng (next-gen) is a temporary prefix // so that we can differentiate between it and the file-only store - DefaultNgStore storeInterface + DefaultNgStore ngStore.Storable DefaultPermissions permissionServicer @@ -63,16 +53,14 @@ var ( DefaultCommand CommandService ) -func Initialize(ctx context.Context, log *zap.Logger, s interface{}, c Config) (err error) { +func Initialize(ctx context.Context, log *zap.Logger, s ngStore.Storable, c Config) (err error) { var ( hcd = healthcheck.Defaults() - - msgStore storeInterface ) // we're doing conversion to avoid having // store interface exposed or generated inside app package - msgStore = s.(storeInterface) + DefaultNgStore = s DefaultLogger = log.Named("service") @@ -93,7 +81,7 @@ func Initialize(ctx context.Context, log *zap.Logger, s interface{}, c Config) ( if DefaultPermissions == nil { // Do not override permissions service stored under DefaultPermissions // to allow integration tests to inject own permission service - DefaultPermissions = permissions.Service(ctx, DefaultLogger, msgStore) + DefaultPermissions = permissions.Service(ctx, DefaultLogger, s) } DefaultAccessControl = AccessControl(DefaultPermissions) diff --git a/messaging/service/store_interface.gen.go b/messaging/service/store_interface.gen.go deleted file mode 100644 index 70caa7d67..000000000 --- a/messaging/service/store_interface.gen.go +++ /dev/null @@ -1,20 +0,0 @@ -package service - -// This file is auto-generated. -// -// Template: pkg/store_interfaces_joined.gen.go.tpl -// Definitions: -// - store/actionlog.yaml -// - store/rbac_rules.yaml -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// - -type ( - // Interface combines interfaces of all supported store interfaces - storeGeneratedInterfaces interface { - actionlogsStore - rbacRulesStore - } -) diff --git a/messaging/service/store_interface_actionlog.gen.go b/messaging/service/store_interface_actionlog.gen.go deleted file mode 100644 index 1b67c3e64..000000000 --- a/messaging/service/store_interface_actionlog.gen.go +++ /dev/null @@ -1,27 +0,0 @@ -package service - -// This file is auto-generated. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -// Definitions file that controls how this file is generated: -// - store/actionlog.yaml - -import ( - "context" - "github.com/cortezaproject/corteza-server/pkg/actionlog" -) - -type ( - actionlogsStore interface { - SearchActionlogs(ctx context.Context, f actionlog.Filter) (actionlog.ActionSet, actionlog.Filter, error) - CreateActionlog(ctx context.Context, rr ...*actionlog.Action) error - UpdateActionlog(ctx context.Context, rr ...*actionlog.Action) error - PartialUpdateActionlog(ctx context.Context, onlyColumns []string, rr ...*actionlog.Action) error - RemoveActionlog(ctx context.Context, rr ...*actionlog.Action) error - RemoveActionlogByID(ctx context.Context, ID uint64) error - - TruncateActionlogs(ctx context.Context) error - } -) diff --git a/messaging/service/store_interface_rbac_rules.gen.go b/messaging/service/store_interface_rbac_rules.gen.go deleted file mode 100644 index c244d22ab..000000000 --- a/messaging/service/store_interface_rbac_rules.gen.go +++ /dev/null @@ -1,27 +0,0 @@ -package service - -// This file is auto-generated. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -// Definitions file that controls how this file is generated: -// - store/rbac_rules.yaml - -import ( - "context" - "github.com/cortezaproject/corteza-server/pkg/permissions" -) - -type ( - rbacRulesStore interface { - SearchRbacRules(ctx context.Context, f permissions.RuleFilter) (permissions.RuleSet, permissions.RuleFilter, error) - CreateRbacRule(ctx context.Context, rr ...*permissions.Rule) error - UpdateRbacRule(ctx context.Context, rr ...*permissions.Rule) error - PartialUpdateRbacRule(ctx context.Context, onlyColumns []string, rr ...*permissions.Rule) error - RemoveRbacRule(ctx context.Context, rr ...*permissions.Rule) error - RemoveRbacRuleByRoleIDResourceOperation(ctx context.Context, roleID uint64, resource string, operation string) error - - TruncateRbacRules(ctx context.Context) error - } -) diff --git a/pkg/actionlog/types.go b/pkg/actionlog/types.go index 1fd573d73..2e4ae1ee4 100644 --- a/pkg/actionlog/types.go +++ b/pkg/actionlog/types.go @@ -1,7 +1,7 @@ package actionlog import ( - "github.com/cortezaproject/corteza-server/store" + "github.com/cortezaproject/corteza-server/pkg/filter" "time" ) @@ -63,8 +63,8 @@ type ( // Query string `json:"query"` // Standard helpers for paging and sorting - store.Sorting - store.Paging + filter.Sorting + filter.Paging } loggableMetaValue interface { diff --git a/pkg/codegen/actions.go b/pkg/codegen/actions.go index f35d3bdcb..242a35086 100644 --- a/pkg/codegen/actions.go +++ b/pkg/codegen/actions.go @@ -7,7 +7,6 @@ import ( "io" "os" "path" - "path/filepath" "regexp" "strings" "text/template" @@ -94,20 +93,13 @@ type ( ) // Processes multiple action definitions -func procActions() ([]*actionsDef, error) { +func procActions(mm ...string) (dd []*actionsDef, err error) { var ( f io.ReadCloser d *actionsDef - - dd = make([]*actionsDef, 0) ) - // /service/_actions.yaml - mm, err := filepath.Glob(filepath.Join("*", "service", "*_actions.yaml")) - if err != nil { - return nil, fmt.Errorf("glob failed: %w", err) - } - + dd = make([]*actionsDef, 0) for _, m := range mm { err = func() error { if f, err = os.Open(m); err != nil { @@ -284,7 +276,7 @@ func severityConstName(s string) string { } } -func genActions(tpl *template.Template, dd []*actionsDef) (err error) { +func genActions(tpl *template.Template, dd ...*actionsDef) (err error) { var ( // Will only be generated if file does not exist previously tplActionsGen = tpl.Lookup("actions.gen.go.tpl") diff --git a/pkg/codegen/assets/rest_handler.go.tpl b/pkg/codegen/assets/rest_handler.go.tpl index 2a1764974..111055bc5 100644 --- a/pkg/codegen/assets/rest_handler.go.tpl +++ b/pkg/codegen/assets/rest_handler.go.tpl @@ -20,40 +20,40 @@ import ( type ( // Internal API interface - {{ pubIdent $.Endpoint.Entrypoint }}API interface { + {{ export $.Endpoint.Entrypoint }}API interface { {{- range $a := $.Endpoint.Apis }} - {{ pubIdent $a.Name }}(context.Context, *request.{{ pubIdent $.Endpoint.Entrypoint $a.Name }}) (interface{}, error) + {{ export $a.Name }}(context.Context, *request.{{ export $.Endpoint.Entrypoint $a.Name }}) (interface{}, error) {{- end }} } // HTTP API interface - {{ pubIdent .Endpoint.Entrypoint }} struct { + {{ export .Endpoint.Entrypoint }} struct { {{- range $a := .Endpoint.Apis }} - {{ pubIdent $a.Name }} func(http.ResponseWriter, *http.Request) + {{ export $a.Name }} func(http.ResponseWriter, *http.Request) {{- end }} } ) -func {{ pubIdent "New" $.Endpoint.Entrypoint }}(h {{ pubIdent $.Endpoint.Entrypoint }}API) *{{ pubIdent $.Endpoint.Entrypoint }} { - return &{{ pubIdent $.Endpoint.Entrypoint }}{ +func {{ export "New" $.Endpoint.Entrypoint }}(h {{ export $.Endpoint.Entrypoint }}API) *{{ export $.Endpoint.Entrypoint }} { + return &{{ export $.Endpoint.Entrypoint }}{ {{- range $a := .Endpoint.Apis }} - {{ pubIdent $a.Name }}: func(w http.ResponseWriter, r *http.Request) { + {{ export $a.Name }}: func(w http.ResponseWriter, r *http.Request) { defer r.Body.Close() - params := request.New{{ pubIdent $.Endpoint.Entrypoint $a.Name }}() + params := request.New{{ export $.Endpoint.Entrypoint $a.Name }}() if err := params.Fill(r); err != nil { - logger.LogParamError("{{ pubIdent $.Endpoint.Entrypoint }}.{{ pubIdent $a.Name }}", r, err) + logger.LogParamError("{{ export $.Endpoint.Entrypoint }}.{{ export $a.Name }}", r, err) resputil.JSON(w, err) return } - value, err := h.{{ pubIdent $a.Name }}(r.Context(), params) + value, err := h.{{ export $a.Name }}(r.Context(), params) if err != nil { - logger.LogControllerError("{{ pubIdent $.Endpoint.Entrypoint }}.{{ pubIdent $a.Name }}", r, err, params.Auditable()) + logger.LogControllerError("{{ export $.Endpoint.Entrypoint }}.{{ export $a.Name }}", r, err, params.Auditable()) resputil.JSON(w, err) return } - logger.LogControllerCall("{{ pubIdent $.Endpoint.Entrypoint }}.{{ pubIdent $a.Name }}", r, params.Auditable()) + logger.LogControllerCall("{{ export $.Endpoint.Entrypoint }}.{{ export $a.Name }}", r, params.Auditable()) if !serveHTTP(value, w, r) { resputil.JSON(w, value) } @@ -62,12 +62,12 @@ func {{ pubIdent "New" $.Endpoint.Entrypoint }}(h {{ pubIdent $.Endpoint.Entrypo } } -func (h {{ pubIdent $.Endpoint.Entrypoint }}) MountRoutes(r chi.Router, middlewares ...func(http.Handler) http.Handler) { +func (h {{ export $.Endpoint.Entrypoint }}) MountRoutes(r chi.Router, middlewares ...func(http.Handler) http.Handler) { r.Group(func(r chi.Router) { r.Use(middlewares...) {{- range $a := .Endpoint.Apis }} - r.{{ pubIdent ( toLower $a.Method ) }}("{{ $.Endpoint.Path }}{{ $a.Path }}", h.{{ pubIdent $a.Name }}) + r.{{ export ( toLower $a.Method ) }}("{{ $.Endpoint.Path }}{{ $a.Path }}", h.{{ export $a.Name }}) {{- end }} }) } diff --git a/pkg/codegen/assets/rest_request.go.tpl b/pkg/codegen/assets/rest_request.go.tpl index b8d388be8..1195edd22 100644 --- a/pkg/codegen/assets/rest_request.go.tpl +++ b/pkg/codegen/assets/rest_request.go.tpl @@ -33,43 +33,43 @@ var ( type ( // Internal API interface {{- range $a := $.Endpoint.Apis }} - {{ pubIdent $.Endpoint.Entrypoint $a.Name }} struct { + {{ export $.Endpoint.Entrypoint $a.Name }} struct { {{- range $p := $a.Params.All }} - // {{ pubIdent $p.Name }} {{ $p.Origin }} parameter + // {{ export $p.Name }} {{ $p.Origin }} parameter // // {{ $p.Title }} - {{ pubIdent $p.Name }} {{ $p.Type }} {{ $p.FieldTag }} + {{ export $p.Name }} {{ $p.Type }} {{ $p.FieldTag }} {{ end }} } {{ end }} ) {{- range $a := $.Endpoint.Apis }} -// {{ pubIdent "New" $.Endpoint.Entrypoint $a.Name }} request -func {{ pubIdent "New" $.Endpoint.Entrypoint $a.Name }}() *{{ pubIdent $.Endpoint.Entrypoint $a.Name }} { - return &{{ pubIdent $.Endpoint.Entrypoint $a.Name }}{} +// {{ export "New" $.Endpoint.Entrypoint $a.Name }} request +func {{ export "New" $.Endpoint.Entrypoint $a.Name }}() *{{ export $.Endpoint.Entrypoint $a.Name }} { + return &{{ export $.Endpoint.Entrypoint $a.Name }}{} } // Auditable returns all auditable/loggable parameters -func (r {{ pubIdent $.Endpoint.Entrypoint $a.Name }}) Auditable() map[string]interface{} { +func (r {{ export $.Endpoint.Entrypoint $a.Name }}) Auditable() map[string]interface{} { return map[string]interface{}{ {{- range $p := $a.Params.All }} - "{{ $p.Name }}": r.{{ pubIdent $p.Name }}, + "{{ $p.Name }}": r.{{ export $p.Name }}, {{- end }} } } {{- range $p := $a.Params.All }} // Auditable returns all auditable/loggable parameters -func (r {{ pubIdent $.Endpoint.Entrypoint $a.Name }}) Get{{ pubIdent $p.Name }}() {{ $p.Type }} { - return r.{{ pubIdent $p.Name }} +func (r {{ export $.Endpoint.Entrypoint $a.Name }}) Get{{ export $p.Name }}() {{ $p.Type }} { + return r.{{ export $p.Name }} } {{- end }} // Fill processes request and fills internal variables -func (r *{{ pubIdent $.Endpoint.Entrypoint $a.Name }}) Fill(req *http.Request) (err error) { +func (r *{{ export $.Endpoint.Entrypoint $a.Name }}) Fill(req *http.Request) (err error) { if strings.ToLower(req.Header.Get("content-type")) == "application/json" { err = json.NewDecoder(req.Body).Decode(r) @@ -89,7 +89,7 @@ func (r *{{ pubIdent $.Endpoint.Entrypoint $a.Name }}) Fill(req *http.Request) ( {{ range $p := $a.Params.Get }} {{- if not $p.IsSlice }} if val, ok := tmp["{{ $p.Name }}"]; ok && len(val) > 0 { - r.{{ pubIdent $p.Name }}, err = {{ $p.Parser "val[0]" }} + r.{{ export $p.Name }}, err = {{ $p.Parser "val[0]" }} if err != nil { return err } @@ -97,12 +97,12 @@ func (r *{{ pubIdent $.Endpoint.Entrypoint $a.Name }}) Fill(req *http.Request) ( {{- end }} {{- if $p.IsSlice }} if val, ok := tmp["{{ $p.Name }}[]"]; ok { - r.{{ pubIdent $p.Name }}, err = {{ $p.Parser "val" }} + r.{{ export $p.Name }}, err = {{ $p.Parser "val" }} if err != nil { return err } } else if val, ok := tmp["{{ $p.Name }}"]; ok { - r.{{ pubIdent $p.Name }}, err = {{ $p.Parser "val" }} + r.{{ export $p.Name }}, err = {{ $p.Parser "val" }} if err != nil { return err } @@ -121,13 +121,13 @@ func (r *{{ pubIdent $.Endpoint.Entrypoint $a.Name }}) Fill(req *http.Request) ( // POST params {{ range $p := $a.Params.Post }} {{ if $p.IsUpload }} - if _, r.{{ pubIdent $p.Name }}, err = req.FormFile("{{ $p.Name }}"); err != nil { + if _, r.{{ export $p.Name }}, err = req.FormFile("{{ $p.Name }}"); err != nil { return fmt.Errorf("error processing uploaded file: %w", err) } {{ else }} {{- if not $p.IsSlice }} if val, ok := req.Form["{{ $p.Name }}"]; ok && len(val) > 0 { - r.{{ pubIdent $p.Name }}, err = {{ $p.Parser "val[0]" }} + r.{{ export $p.Name }}, err = {{ $p.Parser "val[0]" }} if err != nil { return err } @@ -135,7 +135,7 @@ func (r *{{ pubIdent $.Endpoint.Entrypoint $a.Name }}) Fill(req *http.Request) ( {{- end }} {{- if $p.IsSlice }} //if val, ok := req.Form["{{ $p.Name }}[]"]; ok && len(val) > 0 { - // r.{{ pubIdent $p.Name }}, err = {{ $p.Parser "val" }} + // r.{{ export $p.Name }}, err = {{ $p.Parser "val" }} // if err != nil { // return err // } @@ -153,7 +153,7 @@ func (r *{{ pubIdent $.Endpoint.Entrypoint $a.Name }}) Fill(req *http.Request) ( // path params {{ range $p := $a.Params.Path }} val = chi.URLParam(req, "{{ $p.Name }}") - r.{{ pubIdent $p.Name }}, err = {{ $p.Parser "val" }} + r.{{ export $p.Name }}, err = {{ $p.Parser "val" }} if err != nil { return err } diff --git a/pkg/codegen/assets/store_base.gen.go.tpl b/pkg/codegen/assets/store_base.gen.go.tpl new file mode 100644 index 000000000..91e020fe3 --- /dev/null +++ b/pkg/codegen/assets/store_base.gen.go.tpl @@ -0,0 +1,127 @@ +package store + +// This file is auto-generated. +// +// Template: pkg/codegen/assets/store_base.gen.go.tpl +// Definitions: {{ .Source }} +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. + +import ( + "context" +{{- range .Import }} + {{ normalizeImport . }} +{{- end }} +) + +type ( + {{- $Types := .Types }} + {{- $Fields := .Fields }} + + {{ export .Types.Plural }} interface { + +{{ if .Publish }} + {{- if .Search.Enable }} + Search{{ export $Types.Plural }}(ctx context.Context{{ template "extraArgsDef" . }}, f {{ $Types.GoFilterType }}) ({{ $Types.GoSetType }}, {{ $Types.GoFilterType }}, error) + {{- end }} +{{- range .Lookups }} + Lookup{{ export $Types.Singular }}By{{ export .Suffix }}(ctx context.Context{{ template "extraArgsDef" $ }}{{- range $field := .Fields }}, {{ cc2underscore $field }} {{ ($field | $Fields.Find).Type }}{{- end }}) (*{{ $Types.GoType }}, error) +{{- end }} + {{ if .Create.Enable }} + Create{{ export $Types.Singular }}(ctx context.Context{{ template "extraArgsDef" . }}, rr ... *{{ $Types.GoType }}) error + {{- end }} + {{ if .Update.Enable }} + Update{{ export $Types.Singular }}(ctx context.Context{{ template "extraArgsDef" . }}, rr ... *{{ $Types.GoType }}) error + Partial{{ export $Types.Singular }}Update(ctx context.Context{{ template "extraArgsDef" . }}, onlyColumns []string, rr ... *{{ $Types.GoType }}) error + {{- end }} + {{ if .Upsert.Enable }} + Upsert{{ export $Types.Singular }}(ctx context.Context{{ template "extraArgsDef" . }}, rr ... *{{ $Types.GoType }}) error + {{- end }} + {{ if .Delete.Enable }} + Delete{{ export $Types.Singular }}(ctx context.Context{{ template "extraArgsDef" . }}, rr ... *{{ $Types.GoType }}) error + Delete{{ export $Types.Singular }}By{{ template "primaryKeySuffix" $Fields }}(ctx context.Context{{ template "extraArgsDef" . }} {{ template "primaryKeyArgsDef" $Fields }}) error + {{- end }} + + Truncate{{ export $Types.Plural }}(ctx context.Context{{ template "extraArgsDef" . }}) error +{{ end }} + +{{- if .Functions}} + // Additional custom functions + {{- range .Functions }} + + // {{ .Name }} (custom function) + {{ .Name }}(ctx context.Context{{ template "extraArgsDef" . }}) ({{ join ", " .Return }}) + {{- end }} +{{- end -}} + } +) + +{{/* convering scenario with non-exported main functions and no additional functions defined + where we get "imported and not used error" */}} +var _ *{{ $Types.GoType }} +var _ context.Context + +{{ if .Publish }} +{{- if .Search.Enable }} +// Search{{ export $.Types.Plural }} returns all matching {{ $.Types.Plural }} from store +func Search{{ export $Types.Plural }}(ctx context.Context, s {{ export $Types.Plural }}{{ template "extraArgsDef" . }}, f {{ $Types.GoFilterType }}) ({{ $Types.GoSetType }}, {{ $Types.GoFilterType }}, error) { + return s.Search{{ export $Types.Plural }}(ctx{{ template "extraArgsCall" . }}, f) +} +{{- end -}} + +{{ range .Lookups }} + +// Lookup{{ export $.Types.Singular }}By{{ export .Suffix }} {{ comment .Description true -}} +func Lookup{{ export $Types.Singular }}By{{ export .Suffix }}(ctx context.Context, s {{ export $Types.Plural }}{{ template "extraArgsDef" $ }}{{- range $field := .Fields }}, {{ cc2underscore $field }} {{ ($field | $Fields.Find).Type }}{{- end }}) (*{{ $Types.GoType }}, error) { + return s.Lookup{{ export $Types.Singular }}By{{ export .Suffix }}(ctx{{ template "extraArgsCall" $ }}{{- range $field := .Fields }}, {{ cc2underscore $field }}{{- end }}) +} +{{- end }} + +// Create{{ export $.Types.Singular }} creates one or more {{ $.Types.Plural }} in store +func Create{{ export $Types.Singular }}(ctx context.Context, s {{ export $Types.Plural }}{{ template "extraArgsDef" . }}, rr ... *{{ $Types.GoType }}) error { + return s.Create{{ export $Types.Singular }}(ctx{{ template "extraArgsCall" . }}, rr... ) +} + +{{ if .Update.Enable }} +// Update{{ export $.Types.Singular }} updates one or more (existing) {{ $.Types.Plural }} in store +func Update{{ export $Types.Singular }}(ctx context.Context, s {{ export $Types.Plural }}{{ template "extraArgsDef" . }}, rr ... *{{ $Types.GoType }}) error { + return s.Update{{ export $Types.Singular }}(ctx{{ template "extraArgsCall" . }}, rr... ) +} + +// Partial{{ export $.Types.Singular }}Update updates one or more existing {{ $.Types.Plural }} in store +func Partial{{ export $Types.Singular }}Update(ctx context.Context, s {{ export $Types.Plural }}{{ template "extraArgsDef" . }}, onlyColumns []string, rr ... *{{ $Types.GoType }}) error { + return s.Partial{{ export $Types.Singular }}Update(ctx{{ template "extraArgsCall" . }}, onlyColumns, rr...) +} +{{ end }} + +{{ if .Upsert.Enable }} +// Upsert{{ export $.Types.Singular }} creates new or updates existing one or more {{ $.Types.Plural }} in store +func Upsert{{ export $Types.Singular }}(ctx context.Context, s {{ export $Types.Plural }}{{ template "extraArgsDef" . }}, rr ... *{{ $Types.GoType }}) error { + return s.Upsert{{ export $Types.Singular }}(ctx{{ template "extraArgsCall" . }}, rr... ) +} +{{ end }} + +{{ if .Delete.Enable }} +// Delete{{ export $.Types.Singular }} Deletes one or more {{ $.Types.Plural }} from store +func Delete{{ export $Types.Singular }}(ctx context.Context, s {{ export $Types.Plural }}{{ template "extraArgsDef" . }}, rr ... *{{ $Types.GoType }}) error { + return s.Delete{{ export $Types.Singular }}(ctx{{ template "extraArgsCall" . }}, rr...) +} + +// Delete{{ export $.Types.Singular }}By{{ template "primaryKeySuffix" $.Fields }} Deletes {{ $.Types.Singular }} from store +func Delete{{ export $Types.Singular }}By{{ template "primaryKeySuffix" $Fields }}(ctx context.Context, s {{ export $Types.Plural }}{{ template "extraArgsDef" . }} {{ template "primaryKeyArgsDef" $Fields }}) error { + return s.Delete{{ export $Types.Singular }}By{{ template "primaryKeySuffix" $Fields }}(ctx{{ template "extraArgsCall" . }}{{ template "primaryKeyArgsCall" $Fields }}) +} +{{ end }} + +// Truncate{{ export $.Types.Plural }} Deletes all {{ $.Types.Plural }} from store +func Truncate{{ export $Types.Plural }}(ctx context.Context, s {{ export $Types.Plural }}{{ template "extraArgsDef" . }}) error { + return s.Truncate{{ export $Types.Plural }}(ctx{{ template "extraArgsCall" . }}) +} +{{ end }} + +{{ range .Functions }} +func {{ .Name }}(ctx context.Context, s {{ export $Types.Plural }}{{ template "extraArgsDef" . }}) ({{ join ", " .Return }}) { + return s.{{ .Name }}(ctx{{ template "extraArgsCall" . }}) +} +{{ end }} diff --git a/pkg/codegen/assets/store_bulk.gen.go.tpl b/pkg/codegen/assets/store_bulk.gen.go.tpl deleted file mode 100644 index 529cef6f2..000000000 --- a/pkg/codegen/assets/store_bulk.gen.go.tpl +++ /dev/null @@ -1,74 +0,0 @@ -package bulk - -// This file is auto-generated. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// Definitions file that controls how this file is generated: -// {{ .Source }} - -import ( - "context" -{{- range $import := $.Import }} - {{ normalizeImport $import }} -{{- end }} -) - -type ( - {{ unpubIdent $.Types.Singular }}Create struct { - Done chan struct{} - res *{{ $.Types.GoType }} - err error - } - - {{ unpubIdent $.Types.Singular }}Update struct { - Done chan struct{} - res *{{ $.Types.GoType }} - err error - } - - {{ unpubIdent $.Types.Singular }}Remove struct { - Done chan struct{} - res *{{ $.Types.GoType }} - err error - } -) - -// Create{{ pubIdent $.Types.Singular }} creates a new {{ pubIdent $.Types.Singular }} -// create job that can be pushed to store's transaction handler -func Create{{ pubIdent $.Types.Singular }}(res *{{ $.Types.GoType }}) *{{ unpubIdent $.Types.Singular }}Create { - return &{{ unpubIdent $.Types.Singular }}Create{res: res} -} - -// Do Executes {{ unpubIdent $.Types.Singular }}Create job -func (j *{{ unpubIdent $.Types.Singular }}Create) Do(ctx context.Context, s storeInterface) error { - j.err = s.Create{{ pubIdent $.Types.Singular }}(ctx, j.res) - j.Done <- struct{}{} - return j.err -} - -// Update{{ pubIdent $.Types.Singular }} creates a new {{ pubIdent $.Types.Singular }} -// update job that can be pushed to store's transaction handler -func Update{{ pubIdent $.Types.Singular }}(res *{{ $.Types.GoType }}) *{{ unpubIdent $.Types.Singular }}Update { - return &{{ unpubIdent $.Types.Singular }}Update{res: res} -} - -// Do Executes {{ unpubIdent $.Types.Singular }}Update job -func (j *{{ unpubIdent $.Types.Singular }}Update) Do(ctx context.Context, s storeInterface) error { - j.err = s.Update{{ pubIdent $.Types.Singular }}(ctx, j.res) - j.Done <- struct{}{} - return j.err -} - -// Remove{{ pubIdent $.Types.Singular }} creates a new {{ pubIdent $.Types.Singular }} -// remove job that can be pushed to store's transaction handler -func Remove{{ pubIdent $.Types.Singular }}(res *{{ $.Types.GoType }}) *{{ unpubIdent $.Types.Singular }}Remove { - return &{{ unpubIdent $.Types.Singular }}Remove{res: res} -} - -// Do Executes {{ unpubIdent $.Types.Singular }}Remove job -func (j *{{ unpubIdent $.Types.Singular }}Remove) Do(ctx context.Context, s storeInterface) error { - j.err = s.Remove{{ pubIdent $.Types.Singular }}(ctx, j.res) - j.Done <- struct{}{} - return j.err -} diff --git a/pkg/codegen/assets/store_interfaces.gen.go.tpl b/pkg/codegen/assets/store_interfaces.gen.go.tpl deleted file mode 100644 index 0edb592b3..000000000 --- a/pkg/codegen/assets/store_interfaces.gen.go.tpl +++ /dev/null @@ -1,37 +0,0 @@ -package {{ .Package }} - -// This file is auto-generated. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -// Definitions file that controls how this file is generated: -// - {{ .Source }} - -import ( - "context" -{{- range .Import }} - {{ normalizeImport . }} -{{- end }} -) - -type ( - {{- $Types := .Types }} - {{- $Fields := .Fields }} - - {{ unpubIdent .Types.Plural }}Store interface { - {{- if not .Search.Disable }} - Search{{ pubIdent $Types.Plural }}(ctx context.Context, f {{ $Types.GoFilterType }}) ({{ $Types.GoSetType }}, {{ $Types.GoFilterType }}, error) - {{- end }} - {{- range .Lookups }} - Lookup{{ pubIdent $Types.Singular }}By{{ pubIdent .Suffix }}(ctx context.Context{{- range $field := .Fields }}, {{ cc2underscore $field }} {{ ($field | $Fields.Find).Type }}{{- end }}) (*{{ $Types.GoType }}, error) - {{- end }} - Create{{ pubIdent $Types.Singular }}(ctx context.Context, rr ... *{{ $Types.GoType }}) error - Update{{ pubIdent $Types.Singular }}(ctx context.Context, rr ... *{{ $Types.GoType }}) error - PartialUpdate{{ pubIdent $Types.Singular }}(ctx context.Context, onlyColumns []string, rr ... *{{ $Types.GoType }}) error - Remove{{ pubIdent $Types.Singular }}(ctx context.Context, rr ... *{{ $Types.GoType }}) error - Remove{{ pubIdent $Types.Singular }}By{{ template "primaryKeySuffix" $Fields }}(ctx context.Context {{ template "primaryKeyArgs" $Fields }}) error - - Truncate{{ pubIdent $Types.Plural }}(ctx context.Context) error - } -) diff --git a/pkg/codegen/assets/store_interfaces_joined.gen.go.tpl b/pkg/codegen/assets/store_interfaces_joined.gen.go.tpl index 7bb6cb085..dc81a5d0e 100644 --- a/pkg/codegen/assets/store_interfaces_joined.gen.go.tpl +++ b/pkg/codegen/assets/store_interfaces_joined.gen.go.tpl @@ -1,8 +1,8 @@ -package {{ .Package }} +package store // This file is auto-generated. // -// Template: pkg/store_interfaces_joined.gen.go.tpl +// Template: pkg/codegen/assets/store_interfaces_joined.gen.go.tpl // Definitions: {{- range .Definitions }} // - {{ .Source }} @@ -12,11 +12,21 @@ package {{ .Package }} // the code is regenerated. // +import ( + "context" +) + type ( - // Interface combines interfaces of all supported store interfaces - storeGeneratedInterfaces interface { + Transactionable interface { + Tx(context.Context, func(context.Context, Storable) error) error + } + + // Sortable interface combines interfaces of all supported store interfaces + Storable interface { + Transactionable + {{ range .Definitions -}} - {{ unpubIdent .Types.Plural }}Store + {{ export .Types.Plural }} {{ end }} } ) diff --git a/pkg/codegen/assets/store_partials.go.tpl b/pkg/codegen/assets/store_partials.go.tpl index e9e1be647..00619e529 100644 --- a/pkg/codegen/assets/store_partials.go.tpl +++ b/pkg/codegen/assets/store_partials.go.tpl @@ -1,4 +1,4 @@ -{{- define "primaryKeyArgs" -}} +{{- define "primaryKeyArgsDef" -}} {{- range $field := . -}} {{- if $field.IsPrimaryKey -}} , {{ $field.Arg }} {{ camelCase $field.Type }} @@ -6,12 +6,44 @@ {{- end -}} {{- end -}} +{{- define "primaryKeyArgsCall" -}} + {{- range $field := . -}} + {{- if $field.IsPrimaryKey -}} + , {{ $field.Arg }} + {{- end -}} + {{- end -}} +{{- end -}} + {{- define "primaryKeySuffix" -}} {{- range $field := . }}{{ if $field.IsPrimaryKey }}{{ $field.Field }}{{ end }}{{ end -}} {{- end -}} -{{- define "partialUpdateArgs" -}} - {{- range .Args -}} - , {{ .Arg }} {{ .Type }} +{{ define "extraArgsDefFirst" }} + {{- range .Arguments -}} + _{{ .Name }} {{ .Type }}, {{- end -}} -{{- end -}} +{{ end }} + +{{ define "extraArgsDef" }} + {{- range .Arguments -}} + , _{{ .Name }} {{ .Type }} + {{- end -}} +{{ end }} + +{{ define "extraArgsDefTypesOnly" }} + {{- range .Arguments -}} + , {{ .Type }} + {{- end -}} +{{ end }} + +{{ define "extraArgsCallFirst" }} + {{- range .Arguments -}} + _{{ .Name }}, + {{- end -}} +{{ end }} + +{{ define "extraArgsCall" }} + {{- range .Arguments -}} + , _{{ .Name }} + {{- end -}} +{{ end }} diff --git a/pkg/codegen/assets/store_rdbms.gen.go.tpl b/pkg/codegen/assets/store_rdbms.gen.go.tpl index f179223a6..a6a083d8b 100644 --- a/pkg/codegen/assets/store_rdbms.gen.go.tpl +++ b/pkg/codegen/assets/store_rdbms.gen.go.tpl @@ -10,11 +10,13 @@ package rdbms import ( "context" + "errors" "database/sql" "fmt" "github.com/Masterminds/squirrel" "github.com/cortezaproject/corteza-server/store" -{{- if not $.Search.DisablePaging }} +{{- if $.Search.EnablePaging }} + "github.com/cortezaproject/corteza-server/pkg/filter" "strings" {{- end }} {{- range $import := $.Import }} @@ -22,24 +24,41 @@ import ( {{- end }} ) -{{ if not $.Search.Disable }} -// Search{{ pubIdent $.Types.Plural }} returns all matching rows +var _ = errors.Is + +const ( + {{- if .Create.Enable }} + TriggerBefore{{ export $.Types.Singular }}Create triggerKey = "{{ unexport $.Types.Singular }}BeforeCreate" + {{- end }} + {{- if .Update.Enable }} + TriggerBefore{{ export $.Types.Singular }}Update triggerKey = "{{ unexport $.Types.Singular }}BeforeUpdate" + {{- end }} + {{- if .Upsert.Enable }} + TriggerBefore{{ export $.Types.Singular }}Upsert triggerKey = "{{ unexport $.Types.Singular }}BeforeUpsert" + {{- end }} + {{- if .Delete.Enable }} + TriggerBefore{{ export $.Types.Singular }}Delete triggerKey = "{{ unexport $.Types.Singular }}BeforeDelete" + {{- end }} +) + +{{ if $.Search.Enable }} +// {{ toggleExport .Search.Export "Search" $.Types.Plural }} returns all matching rows // -// This function calls convert{{ pubIdent $.Types.Singular }}Filter with the given +// This function calls convert{{ export $.Types.Singular }}Filter with the given // {{ $.Types.GoFilterType }} and expects to receive a working squirrel.SelectBuilder -func (s Store) Search{{ pubIdent $.Types.Plural }}(ctx context.Context, f {{ $.Types.GoFilterType }}) ({{ $.Types.GoSetType }}, {{ $.Types.GoFilterType }}, error) { +func (s Store) {{ toggleExport .Search.Export "Search" $.Types.Plural }}(ctx context.Context{{ template "extraArgsDef" . }}, f {{ $.Types.GoFilterType }}) ({{ $.Types.GoSetType }}, {{ $.Types.GoFilterType }}, error) { var scap uint {{- if .RDBMS.CustomFilterConverter }} - q, err := s.convert{{ pubIdent $.Types.Singular }}Filter(f) + q, err := s.convert{{ export $.Types.Singular }}Filter({{ template "extraArgsCallFirst" . }}f) if err != nil { return nil, f, err } {{- else }} - q := s.Query{{ pubIdent $.Types.Plural }}() + q := s.{{ unexport $.Types.Plural }}SelectBuilder() {{- end }} -{{ if not $.Search.DisablePaging }} +{{ if $.Search.EnablePaging }} scap = f.Limit // Cleanup anything we've accidentally received... @@ -50,22 +69,8 @@ func (s Store) Search{{ pubIdent $.Types.Plural }}(ctx context.Context, f {{ $.T reverseCursor := f.PageCursor != nil && f.PageCursor.Reverse {{ end }} -{{ if $.Search.DisableSorting }} - {{ if not $.Search.DisablePaging }} - // Sorting is disabled in definition yaml file - // {search: {disableSorting:true}} - // - // We still need to sort the results by primary key for paging purposes - sort := store.SortExprSet{ - {{ range $.Fields }} - {{- if or .IsPrimaryKey -}} - &store.SortExpr{Column: {{ printf "%q" .Column }}, {{ if .SortDescending }}Descending: true, {{ end }}}, - {{- end }} - {{- end }} - } - {{ end }} -{{ else }} - if err = f.Sort.Validate(s.sortable{{ pubIdent $.Types.Singular }}Columns()...); err != nil { +{{ if $.Search.EnableSorting }} + if err := f.Sort.Validate(s.sortable{{ export $.Types.Singular }}Columns()...); err != nil { return nil, f, fmt.Errorf("could not validate sort: %v", err) } @@ -88,6 +93,18 @@ func (s Store) Search{{ pubIdent $.Types.Plural }}(ctx context.Context, f {{ $.T q = q.OrderBy(sqlSort...) } +{{ else if $.Search.EnablePaging }} + // Sorting is disabled in definition yaml file + // {search: {enablePaging:false}} + // + // We still need to sort the results by primary key for paging purposes + sort := filter.SortExprSet{ + {{ range $.Fields }} + {{- if or .IsPrimaryKey -}} + &filter.SortExpr{Column: {{ printf "%q" .Column }}, {{ if .SortDescending }}Descending: true, {{ end }}}, + {{- end }} + {{- end }} + } {{ end }} if scap == 0 { @@ -98,9 +115,9 @@ func (s Store) Search{{ pubIdent $.Types.Plural }}(ctx context.Context, f {{ $.T var ( set = make([]*{{ $.Types.GoType }}, 0, scap) - {{- if $.Search.DisablePaging }} + {{- if not $.Search.EnablePaging }} // Paging is disabled in definition yaml file - // {search: {disablePaging:true}} and this allows + // {search: {enablePaging:false}} and this allows // a much simpler row fetching logic fetch = func() error { var ( @@ -113,21 +130,42 @@ func (s Store) Search{{ pubIdent $.Types.Plural }}(ctx context.Context, f {{ $.T } for rows.Next() { - if res, err = s.internal{{ pubIdent $.Types.Singular }}RowScanner(rows, rows.Err()); err != nil { + if rows.Err() == nil { + res, err = s.internal{{ export $.Types.Singular }}RowScanner({{ template "extraArgsCallFirst" . }}rows) + } + + if err != nil { if cerr := rows.Close(); cerr != nil { - return fmt.Errorf("could not close rows (%v) after scan error: %w", cerr, err) + 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 $.Search.EnableFilterCheckFn }} + 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 + } + } + {{ end -}} + set = append(set, res) } return rows.Close() } {{ else }} - // fetches rows and scans them into {{ pubIdent $.Types.GoType }} resource this is then passed to Check function on filter + // fetches rows and scans them into {{ $.Types.GoType }} resource this is then passed to Check function on filter // to help determine if fetched resource fits or not // // Note that limit is passed explicitly and is not necessarily equal to filter's limit. We want @@ -136,7 +174,7 @@ func (s Store) Search{{ pubIdent $.Types.Plural }}(ctx context.Context, f {{ $.T // The value for cursor is used and set directly from/to the filter! // // It returns total number of fetched pages and modifies PageCursor value for paging - fetchPage = func(cursor *store.PagingCursor, limit uint) (fetched uint, err error) { + fetchPage = func(cursor *filter.PagingCursor, limit uint) (fetched uint, err error) { var ( res *{{ $.Types.GoType }} @@ -167,7 +205,12 @@ func (s Store) Search{{ pubIdent $.Types.Plural }}(ctx context.Context, f {{ $.T for rows.Next() { fetched++ - if res, err = s.internal{{ pubIdent $.Types.Singular }}RowScanner(rows, rows.Err()); err != nil { + + if rows.Err() == nil { + res, err = s.internal{{ export $.Types.Singular }}RowScanner({{ template "extraArgsCallFirst" . }}rows) + } + + if err != nil { if cerr := rows.Close(); cerr != nil { err = fmt.Errorf("could not close rows (%v) after scan error: %w", cerr, err) } @@ -176,7 +219,7 @@ func (s Store) Search{{ pubIdent $.Types.Plural }}(ctx context.Context, f {{ $.T } // If check function is set, call it and act accordingly - {{ if not $.Search.DisableFilterCheckFn }} + {{ if $.Search.EnableFilterCheckFn }} if f.Check != nil { var chk bool if chk, err = f.Check(res); err != nil { @@ -265,13 +308,13 @@ func (s Store) Search{{ pubIdent $.Types.Plural }}(ctx context.Context, f {{ $.T if f.Limit > 0 && len(set) > 0 { if f.PageCursor != nil && (!f.PageCursor.Reverse || lastSetFull) { - f.PrevPage = s.collect{{ pubIdent $.Types.Singular }}CursorValues(set[0], sort.Columns()...) + f.PrevPage = s.collect{{ export $.Types.Singular }}CursorValues(set[0], sort.Columns()...) f.PrevPage.Reverse = true } // Less items fetched then requested by page-limit // not very likely there's another page - f.NextPage = s.collect{{ pubIdent $.Types.Singular }}CursorValues(set[len(set)-1], sort.Columns()...) + f.NextPage = s.collect{{ export $.Types.Singular }}CursorValues(set[len(set)-1], sort.Columns()...) } f.PageCursor = nil @@ -285,49 +328,69 @@ func (s Store) Search{{ pubIdent $.Types.Plural }}(ctx context.Context, f {{ $.T {{ end }} {{- range $lookup := $.Lookups }} -// Lookup{{ pubIdent $.Types.Singular }}By{{ pubIdent $lookup.Suffix }} {{ comment $lookup.Description true -}} -func (s Store) Lookup{{ pubIdent $.Types.Singular }}By{{ pubIdent $lookup.Suffix }}(ctx context.Context{{- range $field := $lookup.Fields }}, {{ cc2underscore $field }} {{ ($field | $.Fields.Find).Type }}{{- end }}) (*{{ $.Types.GoType }}, error) { - return s.{{ $.Types.Singular }}Lookup(ctx, squirrel.Eq{ +// {{ toggleExport $lookup.Export "Lookup" $.Types.Singular "By" $lookup.Suffix }} {{ comment $lookup.Description true -}} +func (s Store) {{ toggleExport $lookup.Export "Lookup" $.Types.Singular "By" $lookup.Suffix }}(ctx context.Context{{ template "extraArgsDef" $ }}{{- range $field := $lookup.Fields }}, {{ cc2underscore $field }} {{ ($field | $.Fields.Find).Type }}{{- end }}) (*{{ $.Types.GoType }}, error) { + return s.execLookup{{ $.Types.Singular }}(ctx{{ template "extraArgsCall" $ }}, squirrel.Eq{ {{- range $field := $lookup.Fields }} - "{{ ($field | $.Fields.Find).AliasedColumn }}": {{ cc2underscore $field }}, + s.preprocessColumn({{ printf "%q" ($field | $.Fields.Find).AliasedColumn }}, {{ printf "%q" ($field | $.Fields.Find).LookupFilterPreprocess }}): s.preprocessValue({{ cc2underscore $field }}, {{ printf "%q" ($field | $.Fields.Find).LookupFilterPreprocess }}), {{- end }} - {{- range $field, $value := $lookup.Filter }} + + {{ range $field, $value := $lookup.Filter }} "{{ ($field | $.Fields.Find).AliasedColumn }}": {{ $value }}, {{- end }} }) } {{ end }} -// Create{{ pubIdent $.Types.Singular }} creates one or more rows in {{ $.RDBMS.Table }} table -func (s Store) Create{{ pubIdent $.Types.Singular }}(ctx context.Context, rr ... *{{ $.Types.GoType }}) (err error) { +{{ if .Create.Enable }} +// {{ toggleExport .Create.Export "Create" $.Types.Singular }} creates one or more rows in {{ $.RDBMS.Table }} table +func (s Store) {{ toggleExport .Create.Export "Create" $.Types.Singular }}(ctx context.Context{{ template "extraArgsDef" . }}, rr ... *{{ $.Types.GoType }}) (err error) { for _, res := range rr { - err = ExecuteSqlizer(ctx, s.DB(), s.Insert(s.{{ $.Types.Singular }}Table()).SetMap(s.internal{{ pubIdent $.Types.Singular }}Encoder(res))) + err = s.check{{ export $.Types.Singular }}Constraints(ctx {{ template "extraArgsCall" $ }}, res) if err != nil { - return s.config.ErrorHandler(err) + return err + } + + // err = s.{{ unexport $.Types.Singular }}Hook(ctx, TriggerBefore{{ export $.Types.Singular }}Create{{ template "extraArgsCall" . }}, res) + // if err != nil { + // return err + // } + + err = s.execCreate{{ export $.Types.Plural }}(ctx, s.internal{{ export $.Types.Singular }}Encoder(res)) + if err != nil { + return err } } return } +{{ end }} -// Update{{ pubIdent $.Types.Singular }} updates one or more existing rows in {{ $.RDBMS.Table }} -func (s Store) Update{{ pubIdent $.Types.Singular }}(ctx context.Context, rr ... *{{ $.Types.GoType }}) error { - return s.config.ErrorHandler(s.PartialUpdate{{ pubIdent $.Types.Singular }}(ctx, nil, rr...)) +{{ if .Update.Enable }} +// {{ toggleExport .Update.Export "Update" $.Types.Singular }} updates one or more existing rows in {{ $.RDBMS.Table }} +func (s Store) {{ toggleExport .Update.Export "Update" $.Types.Singular }}(ctx context.Context{{ template "extraArgsDef" . }}, rr ... *{{ $.Types.GoType }}) error { + return s.config.ErrorHandler(s.{{ toggleExport .Update.Export "Partial" $.Types.Singular "Update" }}(ctx{{ template "extraArgsCall" . }}, nil, rr...)) } -// PartialUpdate{{ pubIdent $.Types.Singular }} updates one or more existing rows in {{ $.RDBMS.Table }} -// -// It wraps the update into transaction and can perform partial update by providing list of updatable columns -func (s Store) PartialUpdate{{ pubIdent $.Types.Singular }}(ctx context.Context, onlyColumns []string, rr ... *{{ $.Types.GoType }}) (err error) { +// {{ toggleExport .Update.Export "Partial" $.Types.Singular "Update" }} updates one or more existing rows in {{ $.RDBMS.Table }} +func (s Store) {{ toggleExport .Update.Export "Partial" $.Types.Singular "Update" }}(ctx context.Context{{ template "extraArgsDef" . }}, onlyColumns []string, rr ... *{{ $.Types.GoType }}) (err error) { for _, res := range rr { - err = s.ExecUpdate{{ pubIdent $.Types.Plural }}( + err = s.check{{ export $.Types.Singular }}Constraints(ctx {{ template "extraArgsCall" $ }}, res) + if err != nil { + return err + } + + // err = s.{{ unexport $.Types.Singular }}Hook(ctx, TriggerBefore{{ export $.Types.Singular }}Update{{ template "extraArgsCall" . }}, res) + // if err != nil { + // return err + // } + + err = s.execUpdate{{ export $.Types.Plural }}( ctx, - {{ template "filterByPrimaryKeys" $.Fields }}, - s.internal{{ pubIdent $.Types.Singular }}Encoder(res).Skip( - {{- range $field := $.Fields -}} - {{- if $field.IsPrimaryKey -}} - {{ printf "%q" $field.Column }}, - {{- end -}} + {{ template "filterByPrimaryKeys" $.Fields.PrimaryKeyFields }}, + s.internal{{ export $.Types.Singular }}Encoder(res).Skip( + {{- range $field := $.Fields.PrimaryKeyFields -}} + {{ printf "%q" $field.Column }}, {{- end -}} ).Only(onlyColumns...)) if err != nil { @@ -337,11 +400,42 @@ func (s Store) PartialUpdate{{ pubIdent $.Types.Singular }}(ctx context.Context, return } +{{ end }} -// Remove{{ pubIdent $.Types.Singular }} removes one or more rows from {{ $.RDBMS.Table }} table -func (s Store) Remove{{ pubIdent $.Types.Singular }}(ctx context.Context, rr ... *{{ $.Types.GoType }}) (err error) { +{{ if .Upsert.Enable }} +// {{ toggleExport .Delete.Export "Upsert" $.Types.Singular }} updates one or more existing rows in {{ $.RDBMS.Table }} +func (s Store) {{ toggleExport .Delete.Export "Upsert" $.Types.Singular }}(ctx context.Context{{ template "extraArgsDef" . }}, rr ... *{{ $.Types.GoType }}) (err error) { for _, res := range rr { - err = ExecuteSqlizer(ctx, s.DB(), s.Delete(s.{{ $.Types.Singular }}Table({{ printf "%q" .RDBMS.Alias }})).Where({{ template "filterByPrimaryKeys" $.Fields }},)) + err = s.check{{ export $.Types.Singular }}Constraints(ctx {{ template "extraArgsCall" $ }}, res) + if err != nil { + return err + } + + // err = s.{{ unexport $.Types.Singular }}Hook(ctx, TriggerBefore{{ export $.Types.Singular }}Upsert{{ template "extraArgsCall" . }}, res) + // if err != nil { + // return err + // } + + err = s.config.ErrorHandler(s.execUpsert{{ export $.Types.Plural }}(ctx, s.internal{{ export $.Types.Singular }}Encoder(res))) + if err != nil { + return err + } + } + + return nil +} +{{ end }} + +{{ if .Delete.Enable }} +// {{ toggleExport .Delete.Export "Delete" $.Types.Singular }} Deletes one or more rows from {{ $.RDBMS.Table }} table +func (s Store) {{ toggleExport .Delete.Export "Delete" $.Types.Singular }}(ctx context.Context{{ template "extraArgsDef" . }}, rr ... *{{ $.Types.GoType }}) (err error) { + for _, res := range rr { + // err = s.{{ unexport $.Types.Singular }}Hook(ctx, TriggerBefore{{ export $.Types.Singular }}Delete{{ template "extraArgsCall" . }}, res) + // if err != nil { + // return err + // } + + err = s.execDelete{{ export $.Types.Plural }}(ctx,{{ template "filterByPrimaryKeys" $.Fields.PrimaryKeyFields }}) if err != nil { return s.config.ErrorHandler(err) } @@ -350,42 +444,90 @@ func (s Store) Remove{{ pubIdent $.Types.Singular }}(ctx context.Context, rr ... return nil } +// {{ toggleExport .Delete.Export "Delete" $.Types.Singular "By" }}{{ template "primaryKeySuffix" $.Fields }} Deletes row from the {{ $.RDBMS.Table }} table +func (s Store) {{ toggleExport .Delete.Export "Delete" $.Types.Singular "By" }}{{ template "primaryKeySuffix" $.Fields }}(ctx context.Context{{ template "extraArgsDef" . }}{{ template "primaryKeyArgsDef" $.Fields }}) error { + return s.execDelete{{ export $.Types.Plural }}(ctx, {{ template "filterByPrimaryKeysWithArgs" $.Fields.PrimaryKeyFields }}) +} +{{ end }} -// Remove{{ pubIdent $.Types.Singular }}By{{ template "primaryKeySuffix" $.Fields }} removes row from the {{ $.RDBMS.Table }} table -func (s Store) Remove{{ pubIdent $.Types.Singular }}By{{ template "primaryKeySuffix" $.Fields }}(ctx context.Context {{ template "primaryKeyArgs" $.Fields }}) error { - return s.config.ErrorHandler(ExecuteSqlizer(ctx, s.DB(), s.Delete(s.{{ $.Types.Singular }}Table({{ printf "%q" .RDBMS.Alias }})).Where({{ template "filterByPrimaryKeysWithArgs" $.Fields }},))) +// {{ toggleExport .Truncate.Export "Truncate" $.Types.Plural }} Deletes all rows from the {{ $.RDBMS.Table }} table +func (s Store) {{ toggleExport .Truncate.Export "Truncate" $.Types.Plural }}(ctx context.Context{{ template "extraArgsDef" . }}) error { + return s.config.ErrorHandler(s.Truncate(ctx, s.{{ unexport $.Types.Singular }}Table())) } - -// Truncate{{ pubIdent $.Types.Plural }} removes all rows from the {{ $.RDBMS.Table }} table -func (s Store) Truncate{{ pubIdent $.Types.Plural }}(ctx context.Context) error { - return s.config.ErrorHandler(Truncate(ctx, s.DB(), s.{{ $.Types.Singular }}Table())) -} - - -// ExecUpdate{{ pubIdent $.Types.Plural }} updates all matched (by cnd) rows in {{ $.RDBMS.Table }} with given data -func (s Store) ExecUpdate{{ pubIdent $.Types.Plural }}(ctx context.Context, cnd squirrel.Sqlizer, set store.Payload) error { - return s.config.ErrorHandler(ExecuteSqlizer(ctx, s.DB(), s.Update(s.{{ $.Types.Singular }}Table({{ printf "%q" .RDBMS.Alias }})).Where(cnd).SetMap(set))) -} - -// {{ $.Types.Singular }}Lookup prepares {{ $.Types.Singular }} query and executes it, +// execLookup{{ $.Types.Singular }} prepares {{ $.Types.Singular }} query and executes it, // returning {{ $.Types.GoType }} (or error) -func (s Store) {{ $.Types.Singular }}Lookup(ctx context.Context, cnd squirrel.Sqlizer) (*{{ $.Types.GoType }}, error) { - return s.internal{{ $.Types.Singular }}RowScanner(s.QueryRow(ctx, s.Query{{ pubIdent $.Types.Plural }}().Where(cnd))) -} +func (s Store) execLookup{{ $.Types.Singular }}(ctx context.Context{{ template "extraArgsDef" . }}, cnd squirrel.Sqlizer) (res *{{ $.Types.GoType }}, err error) { + var ( + row rowScanner + ) -func (s Store) internal{{ $.Types.Singular }}RowScanner(row rowScanner, err error) (*{{ $.Types.GoType }}, error) { + row, err = s.QueryRow(ctx, s.{{ unexport $.Types.Plural }}SelectBuilder().Where(cnd)) if err != nil { - return nil, err + return } - var res = &{{ $.Types.GoType }}{} - if _, has := s.config.RowScanners[{{ printf "%q" (unpubIdent $.Types.Singular) }}]; has { - scanner := s.config.RowScanners[{{ printf "%q" (unpubIdent $.Types.Singular) }}].(func(rowScanner, *{{ $.Types.GoType }}) error) - err = scanner(row, res) + res, err = s.internal{{ $.Types.Singular }}RowScanner({{ template "extraArgsCallFirst" . }}row) + if err != nil { + return + } + + + return res, nil +} + +{{ if .Create.Enable }} +// execCreate{{ export $.Types.Plural }} updates all matched (by cnd) rows in {{ $.RDBMS.Table }} with given data +func (s Store) execCreate{{ export $.Types.Plural }}(ctx context.Context, payload store.Payload) error { + return s.config.ErrorHandler(s.Exec(ctx, s.InsertBuilder(s.{{ unexport $.Types.Singular }}Table()).SetMap(payload))) +} +{{ end }} + +{{ if .Update.Enable }} +// execUpdate{{ export $.Types.Plural }} updates all matched (by cnd) rows in {{ $.RDBMS.Table }} with given data +func (s Store) execUpdate{{ export $.Types.Plural }}(ctx context.Context, cnd squirrel.Sqlizer, set store.Payload) error { + return s.config.ErrorHandler(s.Exec(ctx, s.UpdateBuilder(s.{{ unexport $.Types.Singular }}Table({{ printf "%q" .RDBMS.Alias }})).Where(cnd).SetMap(set))) +} +{{ end }} + +{{ if .Upsert.Enable }} +// execUpsert{{ export $.Types.Plural }} inserts new or updates matching (by-primary-key) rows in {{ $.RDBMS.Table }} with given data +func (s Store) execUpsert{{ export $.Types.Plural }}(ctx context.Context, set store.Payload) error { + upsert, err := s.config.UpsertBuilder( + s.config, + s.{{ unexport $.Types.Singular }}Table(), + set, +{{ range $.Fields }} + {{- if or .IsPrimaryKey -}} + {{ printf "%q" .Column }}, + {{ end }} +{{- end }} + ) + + if err != nil { + return err + } + + return s.config.ErrorHandler(s.Exec(ctx, upsert)) +} +{{ end }} + +{{ if .Delete.Enable }} +// execDelete{{ export $.Types.Plural }} Deletes all matched (by cnd) rows in {{ $.RDBMS.Table }} with given data +func (s Store) execDelete{{ export $.Types.Plural }}(ctx context.Context, cnd squirrel.Sqlizer) error { + return s.config.ErrorHandler(s.Exec(ctx, s.DeleteBuilder(s.{{ unexport $.Types.Singular }}Table({{ printf "%q" .RDBMS.Alias }})).Where(cnd))) +} +{{ end }} + +func (s Store) internal{{ $.Types.Singular }}RowScanner({{ template "extraArgsDefFirst" . }}row rowScanner) (res *{{ $.Types.GoType }}, err error) { + res = &{{ $.Types.GoType }}{} + + if _, has := s.config.RowScanners[{{ printf "%q" (unexport $.Types.Singular) }}]; has { + scanner := s.config.RowScanners[{{ printf "%q" (unexport $.Types.Singular) }}].(func({{ template "extraArgsDefFirst" . }}_ rowScanner, _ *{{ $.Types.GoType }}) error) + err = scanner({{ template "extraArgsCallFirst" . }}row, res) } else { {{- if .RDBMS.CustomRowScanner }} - err = s.scan{{ $.Types.Singular }}Row(row, res) + err = s.scan{{ $.Types.Singular }}Row({{ template "extraArgsCallFirst" . }}row, res) {{- else }} err = row.Scan( {{- range $.Fields }} @@ -406,13 +548,13 @@ func (s Store) internal{{ $.Types.Singular }}RowScanner(row rowScanner, err erro } } -// Query{{ pubIdent $.Types.Plural }} returns squirrel.SelectBuilder with set table and all columns -func (s Store) Query{{ pubIdent $.Types.Plural }}() squirrel.SelectBuilder { - return s.Select(s.{{ $.Types.Singular }}Table({{ printf "%q" .RDBMS.Alias }}), s.{{ $.Types.Singular }}Columns({{ printf "%q" $.RDBMS.Alias }})...) +// Query{{ export $.Types.Plural }} returns squirrel.SelectBuilder with set table and all columns +func (s Store) {{ unexport $.Types.Plural }}SelectBuilder() squirrel.SelectBuilder { + return s.SelectBuilder(s.{{ unexport $.Types.Singular }}Table({{ printf "%q" .RDBMS.Alias }}), s.{{ unexport $.Types.Singular }}Columns({{ printf "%q" $.RDBMS.Alias }})...) } -// {{ $.Types.Singular }}Table name of the db table -func (Store) {{ $.Types.Singular }}Table(aa ... string) string { +// {{ unexport $.Types.Singular }}Table name of the db table +func (Store) {{ unexport $.Types.Singular }}Table(aa ... string) string { var alias string if len(aa) > 0 { alias = " AS " + aa[0] @@ -424,7 +566,7 @@ func (Store) {{ $.Types.Singular }}Table(aa ... string) string { // {{ $.Types.Singular }}Columns returns all defined table columns // // With optional string arg, all columns are returned aliased -func (Store) {{ $.Types.Singular }}Columns(aa ... string) []string { +func (Store) {{ unexport $.Types.Singular }}Columns(aa ... string) []string { var alias string if len(aa) > 0 { alias = aa[0] + "." @@ -439,7 +581,7 @@ func (Store) {{ $.Types.Singular }}Columns(aa ... string) []string { // {{ printf "%v" .Search }} -{{ if not $.Search.DisableSorting }} +{{ if $.Search.EnableSorting }} // sortable{{ $.Types.Singular }}Columns returns all {{ $.Types.Singular }} columns flagged as sortable // // With optional string arg, all columns are returned aliased @@ -454,13 +596,13 @@ func (Store) sortable{{ $.Types.Singular }}Columns() []string { } {{ end }} -// internal{{ pubIdent $.Types.Singular }}Encoder encodes fields from {{ $.Types.GoType }} to store.Payload (map) +// internal{{ export $.Types.Singular }}Encoder encodes fields from {{ $.Types.GoType }} to store.Payload (map) // -// Encoding is done by using generic approach or by calling encode{{ pubIdent $.Types.Singular }} +// Encoding is done by using generic approach or by calling encode{{ export $.Types.Singular }} // func when rdbms.customEncoder=true -func (s Store) internal{{ pubIdent $.Types.Singular }}Encoder(res *{{ $.Types.GoType }}) store.Payload { +func (s Store) internal{{ export $.Types.Singular }}Encoder(res *{{ $.Types.GoType }}) store.Payload { {{- if .RDBMS.CustomEncoder }} - return s.encode{{ pubIdent $.Types.Singular }}(res) + return s.encode{{ export $.Types.Singular }}(res) {{- else }} return store.Payload{ {{- range $.Fields }} @@ -470,10 +612,10 @@ func (s Store) internal{{ pubIdent $.Types.Singular }}Encoder(res *{{ $.Types.Go {{- end }} } -{{ if not $.Search.DisablePaging }} -func (s Store) collect{{ pubIdent $.Types.Singular }}CursorValues(res *{{ $.Types.GoType }}, cc ...string) *store.PagingCursor { +{{ if $.Search.EnablePaging }} +func (s Store) collect{{ export $.Types.Singular }}CursorValues(res *{{ $.Types.GoType }}, cc ...string) *filter.PagingCursor { var ( - cursor = &store.PagingCursor{} + cursor = &filter.PagingCursor{} hasUnique bool @@ -507,26 +649,46 @@ func (s Store) collect{{ pubIdent $.Types.Singular }}CursorValues(res *{{ $.Type } {{ end }} +func (s *Store) check{{ export $.Types.Singular }}Constraints(ctx context.Context{{ template "extraArgsDef" $ }}, res *{{ $.Types.GoType }}) error { +{{- range $lookup := $.Lookups }} + {{ if $lookup.UniqueConstraintCheck }} + { + ex, err := s.{{ toggleExport $lookup.Export "Lookup" $.Types.Singular "By" $lookup.Suffix }}(ctx{{ template "extraArgsCall" $ }}{{- range $field := $lookup.Fields }}, res.{{ $field }} {{- end }}) + if err == nil && ex != nil && ex.ID != res.ID { + return store.ErrNotUnique + } else if !errors.Is(err, store.ErrNotFound) { + return err + } + } + {{ end }} +{{ end }} + + return nil +} + +// func (s *Store) {{ unexport $.Types.Singular }}Hook(ctx context.Context, key triggerKey{{ template "extraArgsDef" . }}, res *{{ $.Types.GoType }}) error { +// if fn, has := s.config.TriggerHandlers[key]; has { +// return fn.(func (ctx context.Context, s *Store{{ template "extraArgsDef" . }}, res *{{ $.Types.GoType }}) error)(ctx, s{{ template "extraArgsCall" . }}, res) +// } +// +// return nil +// } + {{/* ************************************************************ */}} {{- define "filterByPrimaryKeys" -}} squirrel.Eq{ - {{- range $field := . -}} - {{- if $field.IsPrimaryKey -}} - s.preprocessColumn({{ printf "%q" $field.AliasedColumn }}, {{ printf "%q" $field.LookupFilterPreprocess }}): s.preprocessValue(res.{{ $field.Field }}, {{ printf "%q" $field.LookupFilterPreprocess }}), - {{ end }} - {{- end -}} + {{ range $field := . -}} + s.preprocessColumn({{ printf "%q" $field.AliasedColumn }}, {{ printf "%q" $field.LookupFilterPreprocess }}): s.preprocessValue(res.{{ $field.Field }}, {{ printf "%q" $field.LookupFilterPreprocess }}), + {{- end }} } {{- end -}} {{- define "filterByPrimaryKeysWithArgs" -}} squirrel.Eq{ - {{- range $field := . }} - {{- if $field.IsPrimaryKey -}} - s.preprocessColumn({{ printf "%q" $field.AliasedColumn }}, {{ printf "%q" $field.LookupFilterPreprocess }}): s.preprocessValue({{ $field.Arg }}, {{ printf "%q" $field.LookupFilterPreprocess }}), - {{ end }} - {{ end -}} + {{ range $field := . -}} + s.preprocessColumn({{ printf "%q" $field.AliasedColumn }}, {{ printf "%q" $field.LookupFilterPreprocess }}): s.preprocessValue({{ $field.Arg }}, {{ printf "%q" $field.LookupFilterPreprocess }}), + {{ end }} } {{- end -}} - diff --git a/pkg/codegen/assets/store_test_all.gen.go.tpl b/pkg/codegen/assets/store_test_all.gen.go.tpl index b535c2c01..288f89092 100644 --- a/pkg/codegen/assets/store_test_all.gen.go.tpl +++ b/pkg/codegen/assets/store_test_all.gen.go.tpl @@ -2,23 +2,29 @@ package tests // This file is auto-generated. // +// Template: pkg/codegen/assets/store_test_all.gen.go +// Definitions: +{{ range . }} +{{- if .Exported -}} +// - {{ .Source }} +{{ end -}}{{- end }} +// // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // import ( - "context" - "github.com/stretchr/testify/require" + "github.com/cortezaproject/corteza-server/store" "testing" ) -func testAllGenerated(t *testing.T, all interface{}) { -{{ range . }} - // Run generated tests for {{ .Types.Base }} - t.Run({{ printf "%q" .Types.Base }}, func(t *testing.T) { - var s = all.({{ unpubIdent .Types.Plural }}Store) - require.New(t).NoError(s.Truncate{{ pubIdent .Types.Plural }}(context.Background())) - test{{ pubIdent .Types.Base }}(t, s) - }) -{{ end }} +func testAllGenerated(t *testing.T, s store.Storable) { +{{- range . }} + {{- if .Exported }} + // Run generated tests for {{ .Types.Base }} + t.Run({{ printf "%q" .Types.Base }}, func(t *testing.T) { + test{{ export .Types.Base }}(t, s) + }) + {{ end -}} +{{ end -}} } diff --git a/pkg/codegen/codegen.go b/pkg/codegen/codegen.go index 51609ca9d..2b8401940 100644 --- a/pkg/codegen/codegen.go +++ b/pkg/codegen/codegen.go @@ -1,83 +1,214 @@ package codegen import ( + "flag" + "fmt" + "github.com/Masterminds/sprig" "github.com/cortezaproject/corteza-server/pkg/cli" + "github.com/fsnotify/fsnotify" + "os" + "path/filepath" "strings" "text/template" ) -type ( - definitions struct { - App string - - Rest []*restDef - Actions []*actionsDef - Events []*eventsDef - Types []*typesDef - Store []*storeDef - } -) - func Proc() { var ( err error - def = &definitions{} + watchChanges bool + beVerbose bool - tpls = template.New("").Funcs(map[string]interface{}{ - "camelCase": camelCase, - "pubIdent": pubIdent, - "unpubIdent": unpubIdent, - "toLower": strings.ToLower, - "cc2underscore": cc2underscore, - "normalizeImport": normalizeImport, - "comment": func(text string, skip1st bool) string { - ll := strings.Split(text, "\n") - s := 0 - out := "" - if skip1st { - s = 1 - out = ll[0] + "\n" - } + fileList []string + watcher *fsnotify.Watcher - for ; s < len(ll); s++ { - out += "// " + ll[s] + "\n" - } + templatesPath = filepath.Join("pkg", "codegen", "assets", "*.tpl") + templatesSrc []string - return out - }, - }) + actionSrcPath = filepath.Join("*", "service", "*_actions.yaml") + actionSrc []string + actionDefs []*actionsDef + + eventSrcPath = filepath.Join("*", "service", "event", "events.yaml") + eventSrc []string + eventDefs []*eventsDef + + typeSrcPath = filepath.Join("*", "*", "types.yaml") + typeSrc []string + typeDefs []*typesDef + + restSrcPath = filepath.Join("*", "rest.yaml") + restSrc []string + restDefs []*restDef + + storeSrcPath = filepath.Join("store", "*.yaml") + storeSrc []string + storeDefs []*storeDef + + tpls *template.Template + tplBase = template.New(""). + Funcs(map[string]interface{}{ + "camelCase": camelCase, + "export": export, + "unexport": unexport, + "toggleExport": toggleExport, + "toLower": strings.ToLower, + "cc2underscore": cc2underscore, + "normalizeImport": normalizeImport, + "comment": func(text string, skip1st bool) string { + ll := strings.Split(text, "\n") + s := 0 + out := "" + if skip1st { + s = 1 + out = ll[0] + "\n" + } + + for ; s < len(ll); s++ { + out += "// " + ll[s] + "\n" + } + + return out + }, + }). + Funcs(sprig.TxtFuncMap()) + + output = func(format string, aa ...interface{}) { + if beVerbose { + fmt.Fprintf(os.Stdout, format, aa...) + } + } + + outputErr = func(err error, format string, aa ...interface{}) bool { + if err != nil { + fmt.Fprintf(os.Stdout, format, aa...) + fmt.Fprintf(os.Stdout, "%v\n", err) + return true + } + + return false + } ) - tpls = template.Must(tpls.ParseGlob("pkg/codegen/assets/*.tpl")) + flag.BoolVar(&watchChanges, "w", false, "regenerate code on template or definition change") + flag.BoolVar(&beVerbose, "v", false, "output loaded definitions, templates and outputs") + flag.Parse() - if def.Actions, err = procActions(); err != nil { - cli.HandleError(err) - } else { - cli.HandleError(genActions(tpls, def.Actions)) - } + defer func() { + if watcher != nil { + watcher.Close() + } + }() - if def.Events, err = procEvents(); err != nil { - cli.HandleError(err) - } else { - cli.HandleError(genEvents(tpls, def.Events)) - } + for { + fileList = make([]string, 0, 100) - if def.Types, err = procTypes(); err != nil { - cli.HandleError(err) - } else { - cli.HandleError(genTypes(tpls, def.Types)) - } + templatesSrc = glob(templatesPath) + output("loaded %d templates from %s\n", len(templatesSrc), templatesPath) - if def.Rest, err = procRest(); err != nil { - cli.HandleError(err) - } else { - cli.HandleError(genRest(tpls, def.Rest)) - } + actionSrc = glob(actionSrcPath) + output("loaded %d action definitions from %s\n", len(actionSrc), actionSrcPath) - if def.Store, err = procStore(); err != nil { - cli.HandleError(err) - } else { - cli.HandleError(genStore(tpls, def.Store)) + eventSrc = glob(eventSrcPath) + output("loaded %d event definitions from %s\n", len(eventSrc), eventSrcPath) + + typeSrc = glob(typeSrcPath) + output("loaded %d type definitions from %s\n", len(typeSrc), typeSrcPath) + + restSrc = glob(restSrcPath) + output("loaded %d rest definitions from %s\n", len(restSrc), restSrcPath) + + storeSrc = glob(storeSrcPath) + output("loaded %d store definitions from %s\n", len(storeSrc), storeSrcPath) + + if watchChanges { + if watcher != nil { + watcher.Close() + } + + watcher, err = fsnotify.NewWatcher() + cli.HandleError(err) + + fileList = append(fileList, templatesSrc...) + fileList = append(fileList, actionSrc...) + fileList = append(fileList, eventSrc...) + fileList = append(fileList, typeSrc...) + fileList = append(fileList, restSrc...) + fileList = append(fileList, storeSrc...) + + for _, d := range fileList { + cli.HandleError(watcher.Add(d)) + } + } + + func() { + tpls, err = tplBase.ParseFiles(templatesSrc...) + if outputErr(err, "could not parse templates:\n") { + return + } + + if actionDefs, err = procActions(actionSrc...); err == nil { + err = genActions(tpls, actionDefs...) + } + + if outputErr(err, "failed to process actions:\n") { + return + } + + if eventDefs, err = procEvents(eventSrc...); err == nil { + err = genEvents(tpls, eventDefs...) + } + + if outputErr(err, "failed to process events:\n") { + return + } + + if typeDefs, err = procTypes(typeSrc...); err == nil { + err = genTypes(tpls, typeDefs...) + } + + if outputErr(err, "failed to process types:\n") { + return + } + + if restDefs, err = procRest(restSrc...); err == nil { + err = genRest(tpls, restDefs...) + } + + if outputErr(err, "failed to process rest:\n") { + return + } + + if storeDefs, err = procStore(storeSrc...); err == nil { + err = genStore(tpls, storeDefs...) + } + + if outputErr(err, "failed to process store:\n") { + return + } + + }() + + if !watchChanges { + break + } + + // @todo fix this (without causing too many "too-many-files" issues :) + output("waiting for changes (if you add a new file, restart codegen manually)\n") + + select { + case <-watcher.Events: + case err = <-watcher.Errors: + cli.HandleError(err) + } } } + +func glob(path string) []string { + src, err := filepath.Glob(path) + if err != nil { + cli.HandleError(fmt.Errorf("failed to glob %q: %w", path, err)) + } + + return src +} diff --git a/pkg/codegen/events.go b/pkg/codegen/events.go index e45df7ea5..dc51441cf 100644 --- a/pkg/codegen/events.go +++ b/pkg/codegen/events.go @@ -6,7 +6,6 @@ import ( "gopkg.in/yaml.v2" "os" "path" - "path/filepath" "strings" "text/template" ) @@ -59,22 +58,14 @@ type ( } ) -func procEvents() ([]*eventsDef, error) { +func procEvents(mm ...string) (dd []*eventsDef, err error) { // /service/event/events.yaml const ( importTypePathTpl = "github.com/cortezaproject/corteza-server/%s/types" importAuthPath = "github.com/cortezaproject/corteza-server/pkg/auth" ) - var ( - dd = make([]*eventsDef, 0) - ) - - mm, err := filepath.Glob(filepath.Join("*", "service", "event", "events.yaml")) - if err != nil { - return nil, fmt.Errorf("glob failed: %w", err) - } - + dd = make([]*eventsDef, 0) for _, m := range mm { f, err := os.Open(m) if err != nil { @@ -156,7 +147,7 @@ func procEvents() ([]*eventsDef, error) { return dd, nil } -func genEvents(tpl *template.Template, dd []*eventsDef) (err error) { +func genEvents(tpl *template.Template, dd ...*eventsDef) (err error) { var ( // Will only be generated if file does not exist previously tplEvents = tpl.Lookup("events.go.tpl") diff --git a/pkg/codegen/rest.go b/pkg/codegen/rest.go index 3f8bf8432..5750975e1 100644 --- a/pkg/codegen/rest.go +++ b/pkg/codegen/rest.go @@ -5,7 +5,6 @@ import ( "gopkg.in/yaml.v2" "os" "path" - "path/filepath" "strings" "text/template" ) @@ -59,17 +58,8 @@ type ( } ) -func procRest() ([]*restDef, error) { - // /rest.yaml - - var ( - dd = make([]*restDef, 0) - ) - - mm, err := filepath.Glob(filepath.Join("*", "rest.yaml")) - if err != nil { - return nil, fmt.Errorf("glob failed: %w", err) - } +func procRest(mm ...string) (dd []*restDef, err error) { + dd = make([]*restDef, 0) for _, m := range mm { err = func() error { @@ -109,7 +99,7 @@ func procRest() ([]*restDef, error) { return dd, nil } -func genRest(tpl *template.Template, dd []*restDef) (err error) { +func genRest(tpl *template.Template, dd ...*restDef) (err error) { var ( // Will only be generated if file does not exist previously tplHandler = tpl.Lookup("rest_handler.go.tpl") @@ -212,7 +202,7 @@ func (d *restEndpointParamDef) Parser(arg string) string { case "sqlxTypes.JSONText": return fmt.Sprintf("payload.ParseJSONTextWithErr(%s)", arg) case "int", "uint", "uint64", "int64", "float", "float64", "bool": - return fmt.Sprintf("payload.Parse%s(%s), nil", pubIdent(d.Type), arg) + return fmt.Sprintf("payload.Parse%s(%s), nil", export(d.Type), arg) case "string", "[]string": return fmt.Sprintf("%s, nil", arg) default: diff --git a/pkg/codegen/store.go b/pkg/codegen/store.go index c62b3dce7..294025697 100644 --- a/pkg/codegen/store.go +++ b/pkg/codegen/store.go @@ -6,7 +6,7 @@ import ( "gopkg.in/yaml.v2" "os" "path" - "path/filepath" + "regexp" "strings" "text/template" ) @@ -21,9 +21,6 @@ type ( Import []string `yaml:"import"` - // List of all locations where we should export the store interface to - Interface []string `yaml:"interface"` - // Tries to autogenerate type by changing it to singular and prefixing it with *types. Types storeTypeDef `yaml:"types"` @@ -32,12 +29,21 @@ type ( // For now, this set does not variate between different implementation // To support that, a (sub)set will need to be defined under each implementation (rdbms, mysql, mongo...) // - Fields storeTypeFieldSetDef `yaml:"fields"` - Lookups []*storeTypeLookups `yaml:"lookups"` - PartialUpdates []*storeTypePartialUpdate `yaml:"partialUpdates"` - RDBMS *storeTypeRdbmsDef `yaml:"rdbms"` + Fields storeTypeFieldSetDef `yaml:"fields"` + RDBMS storeTypeRdbmsDef `yaml:"rdbms"` + Functions []*storeTypeFunctionsDef `yaml:"functions"` + Arguments []*storeTypeExtraArgumentDef `yaml:"arguments"` - Search storeTypeSearchDef `yaml:"search"` + Search storeTypeSearchDef `yaml:"search"` + Lookups []*storeTypeLookups `yaml:"lookups"` + Create storeTypeCreateDef `yaml:"create"` + Update storeTypeUpdateDef `yaml:"update"` + Upsert storeTypeUpsertDef `yaml:"upsert"` + Delete storeTypeDeleteDef `yaml:"Delete"` + Truncate storeTypeTruncateDef `yaml:"truncate"` + + // Make interfaces and store functions + Publish bool `yaml:"publish"` } storeTypeDef struct { @@ -80,6 +86,17 @@ type ( CustomEncoder bool `yaml:"customEncoder"` } + storeTypeFunctionsDef struct { + Name string `yaml:"name"` + Arguments []storeTypeExtraArgumentDef `yaml:"arguments"` + Return []string `yaml:"return"` + } + + storeTypeExtraArgumentDef struct { + Name string + Type string + } + storeTypeFieldSetDef []*storeTypeFieldDef storeTypeFieldDef struct { @@ -98,7 +115,7 @@ type ( // If field name ends with ID (ID), it converts that to rel_ Column string `yaml:"column"` - // If field is flagged as PK it is used in update & remove conditions + // If field is flagged as PK it is used in update & Delete conditions // Note: if no other field is set as primary and field with ID name // exists, that field is auto-set as primary. IsPrimaryKey bool `yaml:"isPrimaryKey"` @@ -128,36 +145,99 @@ type ( storeTypeLookups struct { // LookupBy // When not explicitly defined, it names of all fields - Suffix string `yaml:"suffix"` - Description string `yaml:"description"` - Fields []string `yaml:"fields"` - Filter map[string]string `yaml:"filter"` - fields storeTypeFieldSetDef - } - - storeTypePartialUpdate struct { - Name string `yaml:"name"` - Description string `yaml:"description"` - Set map[string]string `yaml:"set"` - XX_Args []string `yaml:"args"` - fields storeTypeFieldSetDef + Export bool `yaml:"export"` + Suffix string `yaml:"suffix"` + Description string `yaml:"description"` + UniqueConstraintCheck bool `yaml:"uniqueConstraintCheck"` + Fields []string `yaml:"fields"` + Filter map[string]string `yaml:"filter"` + fields storeTypeFieldSetDef } storeTypeSearchDef struct { - Disable bool `yaml:"disable"` - DisablePaging bool `yaml:"disablePaging"` - DisableSorting bool `yaml:"disableSorting"` - DisableFilterCheckFn bool `yaml:"disableFilterCheckFunction"` + Enable bool `yaml:"enable"` + Export bool `yaml:"export"` + EnablePaging bool `yaml:"enablePaging"` + EnableSorting bool `yaml:"enableSorting"` + EnableFilterCheckFn bool `yaml:"enableFilterCheckFunction"` + } + + storeTypeCreateDef struct { + Enable bool `yaml:"enable"` + Export bool `yaml:"export"` + } + + storeTypeUpdateDef struct { + Enable bool `yaml:"enable"` + Export bool `yaml:"export"` + } + + storeTypeUpsertDef struct { + Enable bool `yaml:"enable"` + Export bool `yaml:"export"` + } + + storeTypeDeleteDef struct { + Enable bool `yaml:"enable"` + Export bool `yaml:"export"` + } + + storeTypeTruncateDef struct { + Export bool `yaml:"export"` } ) var ( - outputDir string = "store" + outputDir string = "store" + spaceSplit = regexp.MustCompile(`\s+`) ) -func procStore() ([]*storeDef, error) { +func procStore(mm ...string) ([]*storeDef, error) { procDef := func(m string) (*storeDef, error) { - def := &storeDef{Source: m} + // initialize & set default + def := &storeDef{ + Source: m, + + RDBMS: storeTypeRdbmsDef{ + CustomRowScanner: false, + CustomFilterConverter: false, + CustomEncoder: false, + }, + + Search: storeTypeSearchDef{ + Enable: true, + Export: true, + EnablePaging: true, + EnableSorting: true, + EnableFilterCheckFn: true, + }, + + Create: storeTypeCreateDef{ + Enable: true, + Export: true, + }, + + Update: storeTypeUpdateDef{ + Enable: true, + Export: true, + }, + + Upsert: storeTypeUpsertDef{ + Enable: true, + Export: true, + }, + + Delete: storeTypeDeleteDef{ + Enable: true, + Export: true, + }, + + Truncate: storeTypeTruncateDef{ + Export: true, + }, + + Publish: true, + } f, err := os.Open(m) if err != nil { return nil, fmt.Errorf("%s read failed: %w", m, err) @@ -172,18 +252,35 @@ func procStore() ([]*storeDef, error) { def.Filename = path.Base(m) def.Filename = def.Filename[:len(def.Filename)-5] - if def.Search.Disable { + if !def.Search.Enable { // No use for any of that if search is disabled... - def.Search.DisablePaging = true - def.Search.DisableSorting = true - def.Search.DisableFilterCheckFn = true + def.Search.EnablePaging = false + def.Search.EnableSorting = false + def.Search.EnableFilterCheckFn = false } - // Always generate interface in store/tests and store/bulk - def.Interface = append(def.Interface, "store/tests", "store/bulk") + if !def.Create.Enable { + // No use for any of that if operation is disabled... + def.Create.Export = false + } + + if !def.Update.Enable { + // No use for any of that if operation is disabled... + def.Update.Export = false + } + + if !def.Upsert.Enable { + // No use for any of that if operation is disabled... + def.Upsert.Export = false + } + + if !def.Delete.Enable { + // No use for any of that if operation is disabled... + def.Delete.Export = false + } if def.Types.Base == "" { - def.Types.Base = pubIdent(strings.Split(def.Filename, "_")...) + def.Types.Base = export(strings.Split(def.Filename, "_")...) } if def.Types.Singular == "" { @@ -198,7 +295,7 @@ func procStore() ([]*storeDef, error) { } if def.Types.GoType == "" { - def.Types.GoType = def.Types.Package + "." + pubIdent(def.Types.Singular) + def.Types.GoType = def.Types.Package + "." + export(def.Types.Singular) } if def.Types.GoSetType == "" { @@ -213,75 +310,40 @@ func procStore() ([]*storeDef, error) { def.RDBMS.Alias = def.Types.Base[0:1] } - var hasPrimaryKey = false - for _, f := range def.Fields { - if f.IsPrimaryKey { - hasPrimaryKey = true - break - } - } - - for _, f := range def.Fields { - if !hasPrimaryKey && f.Field == "ID" { - f.IsPrimaryKey = true - f.IsSortable = true + var hasPrimaryKey = def.Fields.HasPrimaryKey() + for _, field := range def.Fields { + if !hasPrimaryKey && field.Field == "ID" { + field.IsPrimaryKey = true + field.IsSortable = true } // copy alias from global spec so we can // generate aliased columsn - f.alias = def.RDBMS.Alias + field.alias = def.RDBMS.Alias - if f.Column == "" { + if field.Column == "" { switch { - case f.Field != "ID" && strings.HasSuffix(f.Field, "ID"): - f.Column = "rel_" + cc2underscore(f.Field[:len(f.Field)-2]) + case field.Field != "ID" && strings.HasSuffix(field.Field, "ID"): + field.Column = "rel_" + cc2underscore(field.Field[:len(field.Field)-2]) default: - f.Column = cc2underscore(f.Field) + field.Column = cc2underscore(field.Field) } } switch { - case f.Type != "": + case field.Type != "": // type set - case strings.HasSuffix(f.Field, "ID") || strings.HasSuffix(f.Field, "By"): - f.Type = "uint64" - case f.Field == "CreatedAt": - f.Type = "time.Time" - case strings.HasSuffix(f.Field, "At"): - f.Type = "uint64" + case strings.HasSuffix(field.Field, "ID") || strings.HasSuffix(field.Field, "By"): + field.Type = "uint64" + case field.Field == "CreatedAt": + field.Type = "time.Time" + case strings.HasSuffix(field.Field, "At"): + field.Type = "uint64" default: - f.Type = "string" + field.Type = "string" } } - if len(def.PartialUpdates) > 0 && def.Fields.Find("ID") == nil { - return nil, fmt.Errorf("partial updates without ID field are not supported") - } - - // Checking if filters exist in the fields - for i, p := range def.PartialUpdates { - // Check and normalize set - for f, v := range p.Set { - if def.Fields.Find(f) == nil { - return nil, fmt.Errorf("undefined field %q used in partialUpdate #%d set", f, i) - } - - if v == "" { - // Set empty strings to nil - p.Set[f] = "nil" - } - } - - for _, a := range p.XX_Args { - if def.Fields.Find(a) == nil { - return nil, fmt.Errorf("undefined field %q used in partialUpdate #%d arguments", a, i) - } - } - - p.fields = def.Fields - - } - for i, l := range def.Lookups { if len(l.Fields) == 0 { return nil, fmt.Errorf("define at least one lookup field in lookup #%d", i) @@ -318,12 +380,7 @@ func procStore() ([]*storeDef, error) { return def, nil } - mm, err := filepath.Glob(filepath.Join(outputDir, "*.yaml")) - if err != nil { - return nil, fmt.Errorf("failed to glob: %w", err) - } - - dd := []*storeDef{} + dd := make([]*storeDef, 0, len(mm)) for _, m := range mm { def, err := procDef(m) if err != nil { @@ -339,11 +396,11 @@ func procStore() ([]*storeDef, error) { // genStore generates all store related code, functions, interfaces... // // Templates can be found under assets/store*.tpl -func genStore(tpl *template.Template, dd []*storeDef) (err error) { +func genStore(tpl *template.Template, dd ...*storeDef) (err error) { var ( // general interfaces tplInterfacesJoined = tpl.Lookup("store_interfaces_joined.gen.go.tpl") - tplInterfaces = tpl.Lookup("store_interfaces.gen.go.tpl") + tplBase = tpl.Lookup("store_base.gen.go.tpl") // general tests tplTestAll = tpl.Lookup("store_test_all.gen.go.tpl") @@ -357,11 +414,7 @@ func genStore(tpl *template.Template, dd []*storeDef) (err error) { // @todo mongodb // @todo elasticsearch - // bulk specific - tplBulk = tpl.Lookup("store_bulk.gen.go.tpl") - - dst string - joinedInterface = make(map[string][]*storeDef) + dst string ) // Output all test setup into a single file @@ -377,40 +430,15 @@ func genStore(tpl *template.Template, dd []*storeDef) (err error) { return } - dst = path.Join(outputDir, "bulk", d.Filename+".gen.go") - if err = goTemplate(dst, tplBulk, d); err != nil { - return - } - - // Collect and map all interface output locations - // and their corresponding definitions - for _, dst = range d.Interface { - if err = genStoreInterfaces(tplInterfaces, path.Join(dst, "store_interface_"+d.Filename+".gen.go"), path.Base(dst), d); err != nil { - return - } - - if joinedInterface[dst] == nil { - joinedInterface[dst] = make([]*storeDef, 0, len(dd)) - } - - joinedInterface[dst] = append(joinedInterface[dst], d) - } - } - - // Add joined interfaces for each interface destination - for dst, dd := range joinedInterface { - if err = genStoreInterfacesJoined(tplInterfacesJoined, path.Join(dst, "store_interface.gen.go"), path.Base(dst), dd); err != nil { + dst = path.Join(outputDir, d.Filename+".gen.go") + if err = goTemplate(dst, tplBase, d); err != nil { return } } - //for _, d := range dd { - // d.Package = "tests" - // dst = path.Join("store/tests", "store_interface_"+d.Filename+".gen.go") - // if err = goTemplate(dst, tplInterfaces, d); err != nil { - // return - // } - //} + if err = genStoreInterfacesJoined(tplInterfacesJoined, path.Join("store", "interfaces.gen.go"), path.Base(dst), dd); err != nil { + return + } return nil } @@ -443,8 +471,17 @@ func collectStoreDefImports(basePkg string, dd ...*storeDef) []string { return ii } -func (s storeTypeFieldSetDef) Find(name string) *storeTypeFieldDef { - for _, f := range s { +// Exported returns true if at least one of the functions is exported +func (d storeDef) Exported() bool { + return d.Search.Export || + d.Create.Export || + d.Update.Export || + d.Upsert.Export || + d.Delete.Export +} + +func (ff storeTypeFieldSetDef) Find(name string) *storeTypeFieldDef { + for _, f := range ff { if f.Field == name { return f } @@ -453,6 +490,27 @@ func (s storeTypeFieldSetDef) Find(name string) *storeTypeFieldDef { return nil } +func (ff storeTypeFieldSetDef) HasPrimaryKey() bool { + for _, f := range ff { + if f.IsPrimaryKey { + return true + } + } + + return false +} + +func (ff storeTypeFieldSetDef) PrimaryKeyFields() storeTypeFieldSetDef { + pkSet := storeTypeFieldSetDef{} + for _, f := range ff { + if f.IsPrimaryKey { + pkSet = append(pkSet, f) + } + } + + return pkSet +} + func (f storeTypeFieldDef) Arg() string { if f.Field == "ID" { return f.Field @@ -465,11 +523,10 @@ func (f storeTypeFieldDef) AliasedColumn() string { return fmt.Sprintf("%s.%s", f.alias, f.Column) } -func (p storeTypePartialUpdate) Args() []*storeTypeFieldDef { - ff := make([]*storeTypeFieldDef, len(p.XX_Args)) - for a := range p.XX_Args { - ff[a] = p.fields.Find(p.XX_Args[a]) - } - - return ff +// UnmarshalYAML makes sure that export flag is set to true when not explicity disabled +func (d *storeTypeLookups) UnmarshalYAML(unmarshal func(interface{}) error) error { + type dAux storeTypeLookups + var aux = (*dAux)(d) + aux.Export = true + return unmarshal(aux) } diff --git a/pkg/codegen/templating.go b/pkg/codegen/templating.go index 5a77a22a9..a1cc7208a 100644 --- a/pkg/codegen/templating.go +++ b/pkg/codegen/templating.go @@ -84,14 +84,14 @@ func camelCase(pp ...string) (out string) { // input, cammelcasing it and removing ident unfriendly characters var nonIdentChars = regexp.MustCompile(`[\s\\/]+`) -func pubIdent(pp ...string) (out string) { +func export(pp ...string) (out string) { for _, p := range pp { if len(p) > 1 { p = strings.ToUpper(p[:1]) + p[1:] } if ss := nonIdentChars.Split(p, -1); len(ss) > 1 { - p = pubIdent(ss...) + p = export(ss...) } out = out + p @@ -100,11 +100,19 @@ func pubIdent(pp ...string) (out string) { return out } -func unpubIdent(pp ...string) (out string) { - out = pubIdent(pp...) +func unexport(pp ...string) (out string) { + out = export(pp...) return strings.ToLower(out[:1]) + out[1:] } +func toggleExport(e bool, pp ...string) (out string) { + if e { + return export(pp...) + } + + return unexport(pp...) +} + // convets to underscore func cc2underscore(cc string) string { var matchFirstCap = regexp.MustCompile("(.)([A-Z][a-z]+)") diff --git a/pkg/codegen/types.go b/pkg/codegen/types.go index 380c2e4fd..9099ec7f2 100644 --- a/pkg/codegen/types.go +++ b/pkg/codegen/types.go @@ -5,7 +5,6 @@ import ( "gopkg.in/yaml.v2" "os" "path" - "path/filepath" "text/template" ) @@ -28,15 +27,8 @@ type ( } ) -func procTypes() ([]*typesDef, error) { - var ( - dd = make([]*typesDef, 0) - ) - - mm, err := filepath.Glob(filepath.Join("*", "*", "types.yaml")) - if err != nil { - return nil, fmt.Errorf("glob failed: %w", err) - } +func procTypes(mm ...string) (dd []*typesDef, err error) { + dd = make([]*typesDef, 0) for _, m := range mm { var ( @@ -69,7 +61,7 @@ func procTypes() ([]*typesDef, error) { // Generates all type set files & accompanying tests // // generates 2 files per type definition -func genTypes(tpl *template.Template, dd []*typesDef) (err error) { +func genTypes(tpl *template.Template, dd ...*typesDef) (err error) { var ( typeGen = tpl.Lookup("type_set.gen.go.tpl") typeGenTest = tpl.Lookup("type_set.gen_test.go.tpl") diff --git a/store/pagination.go b/pkg/filter/pagination.go similarity index 99% rename from store/pagination.go rename to pkg/filter/pagination.go index a4479d3e8..209e83f86 100644 --- a/store/pagination.go +++ b/pkg/filter/pagination.go @@ -1,4 +1,4 @@ -package store +package filter import ( "encoding/base64" diff --git a/store/sorting.go b/pkg/filter/sorting.go similarity index 99% rename from store/sorting.go rename to pkg/filter/sorting.go index 426a2bea0..b3678b6ed 100644 --- a/store/sorting.go +++ b/pkg/filter/sorting.go @@ -1,4 +1,4 @@ -package store +package filter import ( "fmt" diff --git a/store/sorting_test.go b/pkg/filter/sorting_test.go similarity index 92% rename from store/sorting_test.go rename to pkg/filter/sorting_test.go index 2daae2a5d..2272193f7 100644 --- a/store/sorting_test.go +++ b/pkg/filter/sorting_test.go @@ -1,4 +1,4 @@ -package store +package filter import ( "encoding/json" @@ -59,7 +59,7 @@ func Test_parseSort(t *testing.T) { func TestSortUmarshaling(t *testing.T) { type tmp struct { - Sort + Sorting Other bool } @@ -71,7 +71,7 @@ func TestSortUmarshaling(t *testing.T) { { "one simple column", `{"sort": "name DESC", "other": true}`, - &tmp{Sort: Sort{Sort: SortExprSet{&SortExpr{Column: "name", Descending: true}}}, Other: true}, + &tmp{Sorting: Sorting{Sort: SortExprSet{&SortExpr{Column: "name", Descending: true}}}, Other: true}, }, } for _, tt := range tests { diff --git a/pkg/ngimporter/import_users.go b/pkg/ngimporter/import_users.go index ddc4bda98..ff0764492 100644 --- a/pkg/ngimporter/import_users.go +++ b/pkg/ngimporter/import_users.go @@ -8,17 +8,17 @@ import ( "strings" "time" - "github.com/cortezaproject/corteza-server/compose/repository" + //"github.com/cortezaproject/corteza-server/compose/repository" cct "github.com/cortezaproject/corteza-server/compose/types" "github.com/cortezaproject/corteza-server/pkg/ngimporter/types" - sysRepo "github.com/cortezaproject/corteza-server/system/repository" + //sysRepo "github.com/cortezaproject/corteza-server/system/repository" sysTypes "github.com/cortezaproject/corteza-server/system/types" ) // imports system users based on the provided source func importUsers(ctx context.Context, is *types.ImportSource, ns *cct.Namespace) (map[string]uint64, *types.ImportSource, error) { - db := repository.DB(ctx) - repoUser := sysRepo.User(ctx, db) + //db := repository.DB(ctx) + //repoUser := sysRepo.User(ctx, db) // this provides a map between importSourceID -> CortezaID mapping := make(map[string]uint64) @@ -112,20 +112,20 @@ func importUsers(ctx context.Context, is *types.ImportSource, ns *cct.Namespace) } } - // this allows us to reuse existing users - uu, err := repoUser.FindByEmail(u.Email) - if err == nil { - u.ID = uu.ID - u, err = repoUser.Update(u) - if err != nil { - return nil, nil, err - } - } else { - u, err = repoUser.Create(u) - if err != nil { - return nil, nil, err - } - } + //// this allows us to reuse existing users + //uu, err := repoUser.FindByEmail(u.Email) + //if err == nil { + // u.ID = uu.ID + // u, err = repoUser.Update(u) + // if err != nil { + // return nil, nil, err + // } + //} else { + // u, err = repoUser.Create(u) + // if err != nil { + // return nil, nil, err + // } + //} mapping[record[0]] = u.ID } diff --git a/pkg/permissions/repository.go b/pkg/permissions/repository.go deleted file mode 100644 index 1f99a60d8..000000000 --- a/pkg/permissions/repository.go +++ /dev/null @@ -1,95 +0,0 @@ -package permissions - -import ( - "context" - - "github.com/Masterminds/squirrel" - "github.com/pkg/errors" - "github.com/titpetric/factory" -) - -type ( - // repository servs as a db storage layer for permission rules - repository struct { - dbh *factory.DB - - // sql table reference - dbTable string - } -) - -func Repository(db *factory.DB, table string) *repository { - return &repository{ - dbTable: table, - dbh: db, - } -} - -func (r *repository) db() *factory.DB { - return r.dbh -} - -func (r repository) columns() []string { - return []string{ - "rel_role", - "resource", - "operation", - "access", - } -} - -func (r *repository) With(ctx context.Context) *repository { - return &repository{ - dbTable: r.dbTable, - dbh: r.db().With(ctx), - } -} - -func (r *repository) Load() (RuleSet, error) { - rr := make([]*Rule, 0) - - lookup := squirrel. - Select(r.columns()...). - From(r.dbTable) - - if query, args, err := lookup.ToSql(); err != nil { - return nil, errors.Wrap(err, "could not build lookup query for permission rules") - } else if err = r.dbh.Select(&rr, query, args...); err != nil { - return nil, errors.Wrap(err, "could not get permission rules") - } - - return rr, nil -} - -func (r *repository) Purge() error { - return r.db().Delete(r.dbTable, nil) -} - -func (r *repository) Store(deleteSet, updateSet RuleSet) (err error) { - if len(deleteSet) == 0 && len(updateSet) == 0 { - return - } - - return r.dbh.Transaction(func() error { - if len(deleteSet) > 0 { - err = deleteSet.Walk(func(rule *Rule) error { - return r.dbh.Delete(r.dbTable, rule, "rel_role", "resource", "operation") - }) - - if err != nil { - return err - } - } - - if len(updateSet) > 0 { - err = updateSet.Walk(func(rule *Rule) error { - return r.dbh.Replace(r.dbTable, rule) - }) - if err != nil { - return err - } - } - - return nil - }) -} diff --git a/pkg/permissions/service.go b/pkg/permissions/service.go index bbbaa9e0a..b6c8a1555 100644 --- a/pkg/permissions/service.go +++ b/pkg/permissions/service.go @@ -2,13 +2,11 @@ package permissions import ( "context" - "sync" - "time" - + "github.com/cortezaproject/corteza-server/pkg/sentry" "github.com/pkg/errors" "go.uber.org/zap" - - "github.com/cortezaproject/corteza-server/pkg/sentry" + "sync" + "time" ) type ( @@ -21,7 +19,7 @@ type ( rules RuleSet - repository rbacRulesStore + store rbacRulesStore } // RuleFilter is a dummy struct to satisfy store codegen @@ -34,15 +32,15 @@ const ( // Service initializes service{} struct // -// service{} struct preloads, checks, grants and flushes privileges to and from repository +// service{} struct preloads, checks, grants and flushes privileges to and from store // It acts as a caching layer func Service(ctx context.Context, logger *zap.Logger, s rbacRulesStore) (svc *service) { svc = &service{ l: &sync.Mutex{}, f: make(chan bool), - logger: logger.Named("permissions"), - repository: s, + logger: logger.Named("permissions"), + store: s, } svc.Reload(ctx) @@ -160,7 +158,7 @@ func (svc *service) Reload(ctx context.Context) { svc.l.Lock() defer svc.l.Unlock() - rr, _, err := svc.repository.SearchRbacRules(ctx, RuleFilter{}) + rr, _, err := svc.store.SearchRbacRules(ctx, RuleFilter{}) svc.logger.Debug( "reloading rules", zap.Error(err), @@ -173,7 +171,7 @@ func (svc *service) Reload(ctx context.Context) { } } -// ResourceFilter is repository helper that we use to filter resources directly in the database +// ResourceFilter is store helper that we use to filter resources directly in the database // // See ResourceFilter struct documentation for details // @@ -193,12 +191,12 @@ func (svc *service) ResourceFilter(roles []uint64, r Resource, op Operation, fal func (svc service) flush(ctx context.Context) (err error) { d, u := svc.rules.dirty() - err = svc.repository.RemoveRbacRule(ctx, d...) + err = svc.store.DeleteRbacRule(ctx, d...) if err != nil { return } - err = svc.repository.UpdateRbacRule(ctx, u...) + err = svc.store.UpsertRbacRule(ctx, u...) if err != nil { return } diff --git a/pkg/permissions/service_alt.go b/pkg/permissions/service_alt.go index f317f86c8..6a88f28a3 100644 --- a/pkg/permissions/service_alt.go +++ b/pkg/permissions/service_alt.go @@ -54,7 +54,7 @@ func (ServiceDenyAll) FindRulesByRoleID(uint64) (rr RuleSet) { } func (svc *TestService) ClearGrants() { - _ = svc.repository.TruncateRbacRules(context.Background()) + _ = svc.store.TruncateRbacRules(context.Background()) svc.rules = RuleSet{} } @@ -79,8 +79,8 @@ func NewTestService(ctx context.Context, logger *zap.Logger, s rbacRulesStore) ( l: &sync.Mutex{}, f: make(chan bool), - logger: logger.Named("permissions"), - repository: s, + logger: logger.Named("permissions"), + store: s, }, } diff --git a/pkg/permissions/store_interface.go b/pkg/permissions/store_interface.go new file mode 100644 index 000000000..a1da35009 --- /dev/null +++ b/pkg/permissions/store_interface.go @@ -0,0 +1,15 @@ +package permissions + +import ( + "context" +) + +type ( + // All + rbacRulesStore interface { + SearchRbacRules(ctx context.Context, f RuleFilter) (RuleSet, RuleFilter, error) + UpsertRbacRule(ctx context.Context, rr ...*Rule) error + DeleteRbacRule(ctx context.Context, rr ...*Rule) error + TruncateRbacRules(ctx context.Context) error + } +) diff --git a/pkg/permissions/store_interfaces.gen.go b/pkg/permissions/store_interfaces.gen.go deleted file mode 100644 index abfc6b935..000000000 --- a/pkg/permissions/store_interfaces.gen.go +++ /dev/null @@ -1,18 +0,0 @@ -package permissions - -import ( - "context" -) - -type ( - rbacRulesStore interface { - SearchRbacRules(ctx context.Context, f RuleFilter) (RuleSet, RuleFilter, error) - CreateRbacRule(ctx context.Context, rr ...*Rule) error - UpdateRbacRule(ctx context.Context, rr ...*Rule) error - PartialUpdateRbacRule(ctx context.Context, onlyColumns []string, rr ...*Rule) error - RemoveRbacRule(ctx context.Context, rr ...*Rule) error - RemoveRbacRuleByRoleIDResourceOperation(ctx context.Context, roleID uint64, resource string, operation string) error - - TruncateRbacRules(ctx context.Context) error - } -) diff --git a/provision/system/provision.go b/provision/system/provision.go index b4a14551e..53714f193 100644 --- a/provision/system/provision.go +++ b/provision/system/provision.go @@ -6,6 +6,7 @@ import ( impAux "github.com/cortezaproject/corteza-server/pkg/importer" "github.com/cortezaproject/corteza-server/pkg/permissions" "github.com/cortezaproject/corteza-server/pkg/settings" + "github.com/cortezaproject/corteza-server/store" "github.com/cortezaproject/corteza-server/system/importer" "github.com/cortezaproject/corteza-server/system/service" "github.com/cortezaproject/corteza-server/system/types" @@ -22,9 +23,8 @@ type ( } ) -func Provision(ctx context.Context, log *zap.Logger, s interface{}) (err error) { +func Provision(ctx context.Context, log *zap.Logger, s store.Storable) (err error) { var ( - store = s.(storeGeneratedInterfaces) provisioned bool readers []io.Reader ) @@ -55,7 +55,7 @@ func Provision(ctx context.Context, log *zap.Logger, s interface{}) (err error) } } - if err = makeDefaultApplications(ctx, log, store); err != nil { + if err = makeDefaultApplications(ctx, log, s); err != nil { return } if err = authSettingsAutoDiscovery(ctx, log, service.DefaultSettings); err != nil { @@ -80,10 +80,10 @@ func notProvisioned(ctx context.Context) (bool, error) { } // Updates default application directly in the store -func makeDefaultApplications(ctx context.Context, log *zap.Logger, store storeGeneratedInterfaces) error { +func makeDefaultApplications(ctx context.Context, log *zap.Logger, s store.Storable) error { var ( now = time.Now() - aa, _, err = store.SearchApplications(ctx, types.ApplicationFilter{}) + aa, _, err = s.SearchApplications(ctx, types.ApplicationFilter{}) ) if err != nil { return err @@ -121,7 +121,7 @@ func makeDefaultApplications(ctx context.Context, log *zap.Logger, store storeGe aa[a].UpdatedAt = &now - if err = store.UpdateApplication(ctx, aa[a]); err != nil { + if err = s.UpdateApplication(ctx, aa[a]); err != nil { return err } } @@ -167,7 +167,7 @@ func makeDefaultApplications(ctx context.Context, log *zap.Logger, store storeGe defApp.ID = id.Next() defApp.CreatedAt = time.Now() - err = store.CreateApplication(ctx, defApp) + err = s.CreateApplication(ctx, defApp) log.Info( "creating default application", zap.String("name", defApp.Name), diff --git a/provision/system/store_interface.gen.go b/provision/system/store_interface.gen.go deleted file mode 100644 index 1f96629bc..000000000 --- a/provision/system/store_interface.gen.go +++ /dev/null @@ -1,18 +0,0 @@ -package system - -// This file is auto-generated. -// -// Template: pkg/store_interfaces_joined.gen.go.tpl -// Definitions: -// - store/applications.yaml -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// - -type ( - // Interface combines interfaces of all supported store interfaces - storeGeneratedInterfaces interface { - applicationsStore - } -) diff --git a/provision/system/store_interface_applications.gen.go b/provision/system/store_interface_applications.gen.go deleted file mode 100644 index 760a976c4..000000000 --- a/provision/system/store_interface_applications.gen.go +++ /dev/null @@ -1,28 +0,0 @@ -package system - -// This file is auto-generated. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -// Definitions file that controls how this file is generated: -// - store/applications.yaml - -import ( - "context" - "github.com/cortezaproject/corteza-server/system/types" -) - -type ( - applicationsStore interface { - SearchApplications(ctx context.Context, f types.ApplicationFilter) (types.ApplicationSet, types.ApplicationFilter, error) - LookupApplicationByID(ctx context.Context, id uint64) (*types.Application, error) - CreateApplication(ctx context.Context, rr ...*types.Application) error - UpdateApplication(ctx context.Context, rr ...*types.Application) error - PartialUpdateApplication(ctx context.Context, onlyColumns []string, rr ...*types.Application) error - RemoveApplication(ctx context.Context, rr ...*types.Application) error - RemoveApplicationByID(ctx context.Context, ID uint64) error - - TruncateApplications(ctx context.Context) error - } -) diff --git a/store/actionlog.gen.go b/store/actionlog.gen.go new file mode 100644 index 000000000..dcc5ad2d3 --- /dev/null +++ b/store/actionlog.gen.go @@ -0,0 +1,42 @@ +package store + +// This file is auto-generated. +// +// Template: pkg/codegen/assets/store_base.gen.go.tpl +// Definitions: store/actionlog.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/pkg/actionlog" +) + +type ( + Actionlogs interface { + SearchActionlogs(ctx context.Context, f actionlog.Filter) (actionlog.ActionSet, actionlog.Filter, error) + + CreateActionlog(ctx context.Context, rr ...*actionlog.Action) error + + TruncateActionlogs(ctx context.Context) error + } +) + +var _ *actionlog.Action +var _ context.Context + +// SearchActionlogs returns all matching Actionlogs from store +func SearchActionlogs(ctx context.Context, s Actionlogs, f actionlog.Filter) (actionlog.ActionSet, actionlog.Filter, error) { + return s.SearchActionlogs(ctx, f) +} + +// CreateActionlog creates one or more Actionlogs in store +func CreateActionlog(ctx context.Context, s Actionlogs, rr ...*actionlog.Action) error { + return s.CreateActionlog(ctx, rr...) +} + +// TruncateActionlogs Deletes all Actionlogs from store +func TruncateActionlogs(ctx context.Context, s Actionlogs) error { + return s.TruncateActionlogs(ctx) +} diff --git a/store/actionlog.yaml b/store/actionlog.yaml index 200e19e02..5f91f082c 100644 --- a/store/actionlog.yaml +++ b/store/actionlog.yaml @@ -1,10 +1,6 @@ import: - github.com/cortezaproject/corteza-server/pkg/actionlog -interface: - - system/service - - compose/service - - messaging/service types: package: actionlog @@ -33,5 +29,14 @@ rdbms: customEncoder: true search: - disableSorting: true - disableFilterCheckFunction: true + enableSorting: false + enableFilterCheckFunction: false + +update: + enable: false + +upsert: + enable: false + +Delete: + enable: false diff --git a/store/applications.gen.go b/store/applications.gen.go new file mode 100644 index 000000000..2268dce5e --- /dev/null +++ b/store/applications.gen.go @@ -0,0 +1,92 @@ +package store + +// This file is auto-generated. +// +// Template: pkg/codegen/assets/store_base.gen.go.tpl +// Definitions: store/applications.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/system/types" +) + +type ( + Applications interface { + SearchApplications(ctx context.Context, f types.ApplicationFilter) (types.ApplicationSet, types.ApplicationFilter, error) + LookupApplicationByID(ctx context.Context, id uint64) (*types.Application, error) + + CreateApplication(ctx context.Context, rr ...*types.Application) error + + UpdateApplication(ctx context.Context, rr ...*types.Application) error + PartialApplicationUpdate(ctx context.Context, onlyColumns []string, rr ...*types.Application) error + + UpsertApplication(ctx context.Context, rr ...*types.Application) error + + DeleteApplication(ctx context.Context, rr ...*types.Application) error + DeleteApplicationByID(ctx context.Context, ID uint64) error + + TruncateApplications(ctx context.Context) error + + // Additional custom functions + + // ApplicationMetrics (custom function) + ApplicationMetrics(ctx context.Context) (*types.ApplicationMetrics, error) + } +) + +var _ *types.Application +var _ context.Context + +// SearchApplications returns all matching Applications from store +func SearchApplications(ctx context.Context, s Applications, f types.ApplicationFilter) (types.ApplicationSet, types.ApplicationFilter, error) { + return s.SearchApplications(ctx, f) +} + +// LookupApplicationByID searches for application by ID +// +// It returns application even if deleted +func LookupApplicationByID(ctx context.Context, s Applications, id uint64) (*types.Application, error) { + return s.LookupApplicationByID(ctx, id) +} + +// CreateApplication creates one or more Applications in store +func CreateApplication(ctx context.Context, s Applications, rr ...*types.Application) error { + return s.CreateApplication(ctx, rr...) +} + +// UpdateApplication updates one or more (existing) Applications in store +func UpdateApplication(ctx context.Context, s Applications, rr ...*types.Application) error { + return s.UpdateApplication(ctx, rr...) +} + +// PartialApplicationUpdate updates one or more existing Applications in store +func PartialApplicationUpdate(ctx context.Context, s Applications, onlyColumns []string, rr ...*types.Application) error { + return s.PartialApplicationUpdate(ctx, onlyColumns, rr...) +} + +// UpsertApplication creates new or updates existing one or more Applications in store +func UpsertApplication(ctx context.Context, s Applications, rr ...*types.Application) error { + return s.UpsertApplication(ctx, rr...) +} + +// DeleteApplication Deletes one or more Applications from store +func DeleteApplication(ctx context.Context, s Applications, rr ...*types.Application) error { + return s.DeleteApplication(ctx, rr...) +} + +// DeleteApplicationByID Deletes Application from store +func DeleteApplicationByID(ctx context.Context, s Applications, ID uint64) error { + return s.DeleteApplicationByID(ctx, ID) +} + +// TruncateApplications Deletes all Applications from store +func TruncateApplications(ctx context.Context, s Applications) error { + return s.TruncateApplications(ctx) +} + +func ApplicationMetrics(ctx context.Context, s Applications) (*types.ApplicationMetrics, error) { + return s.ApplicationMetrics(ctx) +} diff --git a/store/applications.yaml b/store/applications.yaml index 605a1189d..798000c85 100644 --- a/store/applications.yaml +++ b/store/applications.yaml @@ -1,10 +1,6 @@ import: - github.com/cortezaproject/corteza-server/system/types -interface: - - system/service - - provision/system - fields: - { field: ID } - { field: Name, sortable: true } @@ -22,6 +18,10 @@ lookups: It returns application even if deleted +functions: + - name: ApplicationMetrics + return: [ "*types.ApplicationMetrics", "error" ] + rdbms: alias: app table: applications diff --git a/store/attachments.gen.go b/store/attachments.gen.go new file mode 100644 index 000000000..793738d2d --- /dev/null +++ b/store/attachments.gen.go @@ -0,0 +1,83 @@ +package store + +// This file is auto-generated. +// +// Template: pkg/codegen/assets/store_base.gen.go.tpl +// Definitions: store/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/system/types" +) + +type ( + Attachments interface { + SearchAttachments(ctx context.Context, f types.AttachmentFilter) (types.AttachmentSet, types.AttachmentFilter, error) + LookupAttachmentByID(ctx context.Context, id uint64) (*types.Attachment, error) + + CreateAttachment(ctx context.Context, rr ...*types.Attachment) error + + UpdateAttachment(ctx context.Context, rr ...*types.Attachment) error + PartialAttachmentUpdate(ctx context.Context, onlyColumns []string, rr ...*types.Attachment) error + + UpsertAttachment(ctx context.Context, rr ...*types.Attachment) error + + DeleteAttachment(ctx context.Context, rr ...*types.Attachment) error + DeleteAttachmentByID(ctx context.Context, ID uint64) error + + TruncateAttachments(ctx context.Context) error + } +) + +var _ *types.Attachment +var _ context.Context + +// SearchAttachments returns all matching Attachments from store +func SearchAttachments(ctx context.Context, s Attachments, f types.AttachmentFilter) (types.AttachmentSet, types.AttachmentFilter, error) { + return s.SearchAttachments(ctx, f) +} + +// LookupAttachmentByID searches for attachment by its ID +// +// It returns attachment even if deleted +func LookupAttachmentByID(ctx context.Context, s Attachments, id uint64) (*types.Attachment, error) { + return s.LookupAttachmentByID(ctx, id) +} + +// CreateAttachment creates one or more Attachments in store +func CreateAttachment(ctx context.Context, s Attachments, rr ...*types.Attachment) error { + return s.CreateAttachment(ctx, rr...) +} + +// UpdateAttachment updates one or more (existing) Attachments in store +func UpdateAttachment(ctx context.Context, s Attachments, rr ...*types.Attachment) error { + return s.UpdateAttachment(ctx, rr...) +} + +// PartialAttachmentUpdate updates one or more existing Attachments in store +func PartialAttachmentUpdate(ctx context.Context, s Attachments, onlyColumns []string, rr ...*types.Attachment) error { + return s.PartialAttachmentUpdate(ctx, onlyColumns, rr...) +} + +// UpsertAttachment creates new or updates existing one or more Attachments in store +func UpsertAttachment(ctx context.Context, s Attachments, rr ...*types.Attachment) error { + return s.UpsertAttachment(ctx, rr...) +} + +// DeleteAttachment Deletes one or more Attachments from store +func DeleteAttachment(ctx context.Context, s Attachments, rr ...*types.Attachment) error { + return s.DeleteAttachment(ctx, rr...) +} + +// DeleteAttachmentByID Deletes Attachment from store +func DeleteAttachmentByID(ctx context.Context, s Attachments, ID uint64) error { + return s.DeleteAttachmentByID(ctx, ID) +} + +// TruncateAttachments Deletes all Attachments from store +func TruncateAttachments(ctx context.Context, s Attachments) error { + return s.TruncateAttachments(ctx) +} diff --git a/store/attachments.yaml b/store/attachments.yaml index 166f63a3e..457d19731 100644 --- a/store/attachments.yaml +++ b/store/attachments.yaml @@ -1,9 +1,6 @@ import: - github.com/cortezaproject/corteza-server/system/types -interface: - - system/service - types: base: Attachment @@ -27,8 +24,8 @@ lookups: It returns attachment even if deleted search: - disableSorting: true - disablePaging: true + enableSorting: false + enablePaging: false rdbms: alias: att diff --git a/store/bulk/actionlog.gen.go b/store/bulk/actionlog.gen.go deleted file mode 100644 index 79275e1a4..000000000 --- a/store/bulk/actionlog.gen.go +++ /dev/null @@ -1,72 +0,0 @@ -package bulk - -// This file is auto-generated. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// Definitions file that controls how this file is generated: -// store/actionlog.yaml - -import ( - "context" - "github.com/cortezaproject/corteza-server/pkg/actionlog" -) - -type ( - actionlogCreate struct { - Done chan struct{} - res *actionlog.Action - err error - } - - actionlogUpdate struct { - Done chan struct{} - res *actionlog.Action - err error - } - - actionlogRemove struct { - Done chan struct{} - res *actionlog.Action - err error - } -) - -// CreateActionlog creates a new Actionlog -// create job that can be pushed to store's transaction handler -func CreateActionlog(res *actionlog.Action) *actionlogCreate { - return &actionlogCreate{res: res} -} - -// Do Executes actionlogCreate job -func (j *actionlogCreate) Do(ctx context.Context, s storeInterface) error { - j.err = s.CreateActionlog(ctx, j.res) - j.Done <- struct{}{} - return j.err -} - -// UpdateActionlog creates a new Actionlog -// update job that can be pushed to store's transaction handler -func UpdateActionlog(res *actionlog.Action) *actionlogUpdate { - return &actionlogUpdate{res: res} -} - -// Do Executes actionlogUpdate job -func (j *actionlogUpdate) Do(ctx context.Context, s storeInterface) error { - j.err = s.UpdateActionlog(ctx, j.res) - j.Done <- struct{}{} - return j.err -} - -// RemoveActionlog creates a new Actionlog -// remove job that can be pushed to store's transaction handler -func RemoveActionlog(res *actionlog.Action) *actionlogRemove { - return &actionlogRemove{res: res} -} - -// Do Executes actionlogRemove job -func (j *actionlogRemove) Do(ctx context.Context, s storeInterface) error { - j.err = s.RemoveActionlog(ctx, j.res) - j.Done <- struct{}{} - return j.err -} diff --git a/store/bulk/applications.gen.go b/store/bulk/applications.gen.go deleted file mode 100644 index 937d18518..000000000 --- a/store/bulk/applications.gen.go +++ /dev/null @@ -1,72 +0,0 @@ -package bulk - -// This file is auto-generated. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// Definitions file that controls how this file is generated: -// store/applications.yaml - -import ( - "context" - "github.com/cortezaproject/corteza-server/system/types" -) - -type ( - applicationCreate struct { - Done chan struct{} - res *types.Application - err error - } - - applicationUpdate struct { - Done chan struct{} - res *types.Application - err error - } - - applicationRemove struct { - Done chan struct{} - res *types.Application - err error - } -) - -// CreateApplication creates a new Application -// create job that can be pushed to store's transaction handler -func CreateApplication(res *types.Application) *applicationCreate { - return &applicationCreate{res: res} -} - -// Do Executes applicationCreate job -func (j *applicationCreate) Do(ctx context.Context, s storeInterface) error { - j.err = s.CreateApplication(ctx, j.res) - j.Done <- struct{}{} - return j.err -} - -// UpdateApplication creates a new Application -// update job that can be pushed to store's transaction handler -func UpdateApplication(res *types.Application) *applicationUpdate { - return &applicationUpdate{res: res} -} - -// Do Executes applicationUpdate job -func (j *applicationUpdate) Do(ctx context.Context, s storeInterface) error { - j.err = s.UpdateApplication(ctx, j.res) - j.Done <- struct{}{} - return j.err -} - -// RemoveApplication creates a new Application -// remove job that can be pushed to store's transaction handler -func RemoveApplication(res *types.Application) *applicationRemove { - return &applicationRemove{res: res} -} - -// Do Executes applicationRemove job -func (j *applicationRemove) Do(ctx context.Context, s storeInterface) error { - j.err = s.RemoveApplication(ctx, j.res) - j.Done <- struct{}{} - return j.err -} diff --git a/store/bulk/attachments.gen.go b/store/bulk/attachments.gen.go deleted file mode 100644 index ad9ae6d81..000000000 --- a/store/bulk/attachments.gen.go +++ /dev/null @@ -1,72 +0,0 @@ -package bulk - -// This file is auto-generated. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// Definitions file that controls how this file is generated: -// store/attachments.yaml - -import ( - "context" - "github.com/cortezaproject/corteza-server/system/types" -) - -type ( - attachmentCreate struct { - Done chan struct{} - res *types.Attachment - err error - } - - attachmentUpdate struct { - Done chan struct{} - res *types.Attachment - err error - } - - attachmentRemove struct { - Done chan struct{} - res *types.Attachment - err error - } -) - -// CreateAttachment creates a new Attachment -// create job that can be pushed to store's transaction handler -func CreateAttachment(res *types.Attachment) *attachmentCreate { - return &attachmentCreate{res: res} -} - -// Do Executes attachmentCreate job -func (j *attachmentCreate) Do(ctx context.Context, s storeInterface) error { - j.err = s.CreateAttachment(ctx, j.res) - j.Done <- struct{}{} - return j.err -} - -// UpdateAttachment creates a new Attachment -// update job that can be pushed to store's transaction handler -func UpdateAttachment(res *types.Attachment) *attachmentUpdate { - return &attachmentUpdate{res: res} -} - -// Do Executes attachmentUpdate job -func (j *attachmentUpdate) Do(ctx context.Context, s storeInterface) error { - j.err = s.UpdateAttachment(ctx, j.res) - j.Done <- struct{}{} - return j.err -} - -// RemoveAttachment creates a new Attachment -// remove job that can be pushed to store's transaction handler -func RemoveAttachment(res *types.Attachment) *attachmentRemove { - return &attachmentRemove{res: res} -} - -// Do Executes attachmentRemove job -func (j *attachmentRemove) Do(ctx context.Context, s storeInterface) error { - j.err = s.RemoveAttachment(ctx, j.res) - j.Done <- struct{}{} - return j.err -} diff --git a/store/bulk/bulk.go b/store/bulk/bulk.go deleted file mode 100644 index d50b9f91e..000000000 --- a/store/bulk/bulk.go +++ /dev/null @@ -1,15 +0,0 @@ -package bulk - -import ( - "context" -) - -type ( - storeInterface interface { - storeGeneratedInterfaces - } - - Job interface { - Do(ctx context.Context, s storeInterface) error - } -) diff --git a/store/bulk/compose_charts.gen.go b/store/bulk/compose_charts.gen.go deleted file mode 100644 index 40ea4f6cf..000000000 --- a/store/bulk/compose_charts.gen.go +++ /dev/null @@ -1,72 +0,0 @@ -package bulk - -// This file is auto-generated. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// Definitions file that controls how this file is generated: -// store/compose_charts.yaml - -import ( - "context" - "github.com/cortezaproject/corteza-server/compose/types" -) - -type ( - composeChartCreate struct { - Done chan struct{} - res *types.Chart - err error - } - - composeChartUpdate struct { - Done chan struct{} - res *types.Chart - err error - } - - composeChartRemove struct { - Done chan struct{} - res *types.Chart - err error - } -) - -// CreateComposeChart creates a new ComposeChart -// create job that can be pushed to store's transaction handler -func CreateComposeChart(res *types.Chart) *composeChartCreate { - return &composeChartCreate{res: res} -} - -// Do Executes composeChartCreate job -func (j *composeChartCreate) Do(ctx context.Context, s storeInterface) error { - j.err = s.CreateComposeChart(ctx, j.res) - j.Done <- struct{}{} - return j.err -} - -// UpdateComposeChart creates a new ComposeChart -// update job that can be pushed to store's transaction handler -func UpdateComposeChart(res *types.Chart) *composeChartUpdate { - return &composeChartUpdate{res: res} -} - -// Do Executes composeChartUpdate job -func (j *composeChartUpdate) Do(ctx context.Context, s storeInterface) error { - j.err = s.UpdateComposeChart(ctx, j.res) - j.Done <- struct{}{} - return j.err -} - -// RemoveComposeChart creates a new ComposeChart -// remove job that can be pushed to store's transaction handler -func RemoveComposeChart(res *types.Chart) *composeChartRemove { - return &composeChartRemove{res: res} -} - -// Do Executes composeChartRemove job -func (j *composeChartRemove) Do(ctx context.Context, s storeInterface) error { - j.err = s.RemoveComposeChart(ctx, j.res) - j.Done <- struct{}{} - return j.err -} diff --git a/store/bulk/compose_module_fields.gen.go b/store/bulk/compose_module_fields.gen.go deleted file mode 100644 index f1bb57764..000000000 --- a/store/bulk/compose_module_fields.gen.go +++ /dev/null @@ -1,72 +0,0 @@ -package bulk - -// This file is auto-generated. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// Definitions file that controls how this file is generated: -// store/compose_module_fields.yaml - -import ( - "context" - "github.com/cortezaproject/corteza-server/compose/types" -) - -type ( - composeModuleFieldCreate struct { - Done chan struct{} - res *types.ModuleField - err error - } - - composeModuleFieldUpdate struct { - Done chan struct{} - res *types.ModuleField - err error - } - - composeModuleFieldRemove struct { - Done chan struct{} - res *types.ModuleField - err error - } -) - -// CreateComposeModuleField creates a new ComposeModuleField -// create job that can be pushed to store's transaction handler -func CreateComposeModuleField(res *types.ModuleField) *composeModuleFieldCreate { - return &composeModuleFieldCreate{res: res} -} - -// Do Executes composeModuleFieldCreate job -func (j *composeModuleFieldCreate) Do(ctx context.Context, s storeInterface) error { - j.err = s.CreateComposeModuleField(ctx, j.res) - j.Done <- struct{}{} - return j.err -} - -// UpdateComposeModuleField creates a new ComposeModuleField -// update job that can be pushed to store's transaction handler -func UpdateComposeModuleField(res *types.ModuleField) *composeModuleFieldUpdate { - return &composeModuleFieldUpdate{res: res} -} - -// Do Executes composeModuleFieldUpdate job -func (j *composeModuleFieldUpdate) Do(ctx context.Context, s storeInterface) error { - j.err = s.UpdateComposeModuleField(ctx, j.res) - j.Done <- struct{}{} - return j.err -} - -// RemoveComposeModuleField creates a new ComposeModuleField -// remove job that can be pushed to store's transaction handler -func RemoveComposeModuleField(res *types.ModuleField) *composeModuleFieldRemove { - return &composeModuleFieldRemove{res: res} -} - -// Do Executes composeModuleFieldRemove job -func (j *composeModuleFieldRemove) Do(ctx context.Context, s storeInterface) error { - j.err = s.RemoveComposeModuleField(ctx, j.res) - j.Done <- struct{}{} - return j.err -} diff --git a/store/bulk/compose_modules.gen.go b/store/bulk/compose_modules.gen.go deleted file mode 100644 index 30902c9f4..000000000 --- a/store/bulk/compose_modules.gen.go +++ /dev/null @@ -1,72 +0,0 @@ -package bulk - -// This file is auto-generated. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// Definitions file that controls how this file is generated: -// store/compose_modules.yaml - -import ( - "context" - "github.com/cortezaproject/corteza-server/compose/types" -) - -type ( - composeModuleCreate struct { - Done chan struct{} - res *types.Module - err error - } - - composeModuleUpdate struct { - Done chan struct{} - res *types.Module - err error - } - - composeModuleRemove struct { - Done chan struct{} - res *types.Module - err error - } -) - -// CreateComposeModule creates a new ComposeModule -// create job that can be pushed to store's transaction handler -func CreateComposeModule(res *types.Module) *composeModuleCreate { - return &composeModuleCreate{res: res} -} - -// Do Executes composeModuleCreate job -func (j *composeModuleCreate) Do(ctx context.Context, s storeInterface) error { - j.err = s.CreateComposeModule(ctx, j.res) - j.Done <- struct{}{} - return j.err -} - -// UpdateComposeModule creates a new ComposeModule -// update job that can be pushed to store's transaction handler -func UpdateComposeModule(res *types.Module) *composeModuleUpdate { - return &composeModuleUpdate{res: res} -} - -// Do Executes composeModuleUpdate job -func (j *composeModuleUpdate) Do(ctx context.Context, s storeInterface) error { - j.err = s.UpdateComposeModule(ctx, j.res) - j.Done <- struct{}{} - return j.err -} - -// RemoveComposeModule creates a new ComposeModule -// remove job that can be pushed to store's transaction handler -func RemoveComposeModule(res *types.Module) *composeModuleRemove { - return &composeModuleRemove{res: res} -} - -// Do Executes composeModuleRemove job -func (j *composeModuleRemove) Do(ctx context.Context, s storeInterface) error { - j.err = s.RemoveComposeModule(ctx, j.res) - j.Done <- struct{}{} - return j.err -} diff --git a/store/bulk/compose_namespaces.gen.go b/store/bulk/compose_namespaces.gen.go deleted file mode 100644 index 208b884d7..000000000 --- a/store/bulk/compose_namespaces.gen.go +++ /dev/null @@ -1,72 +0,0 @@ -package bulk - -// This file is auto-generated. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// Definitions file that controls how this file is generated: -// store/compose_namespaces.yaml - -import ( - "context" - "github.com/cortezaproject/corteza-server/compose/types" -) - -type ( - composeNamespaceCreate struct { - Done chan struct{} - res *types.Namespace - err error - } - - composeNamespaceUpdate struct { - Done chan struct{} - res *types.Namespace - err error - } - - composeNamespaceRemove struct { - Done chan struct{} - res *types.Namespace - err error - } -) - -// CreateComposeNamespace creates a new ComposeNamespace -// create job that can be pushed to store's transaction handler -func CreateComposeNamespace(res *types.Namespace) *composeNamespaceCreate { - return &composeNamespaceCreate{res: res} -} - -// Do Executes composeNamespaceCreate job -func (j *composeNamespaceCreate) Do(ctx context.Context, s storeInterface) error { - j.err = s.CreateComposeNamespace(ctx, j.res) - j.Done <- struct{}{} - return j.err -} - -// UpdateComposeNamespace creates a new ComposeNamespace -// update job that can be pushed to store's transaction handler -func UpdateComposeNamespace(res *types.Namespace) *composeNamespaceUpdate { - return &composeNamespaceUpdate{res: res} -} - -// Do Executes composeNamespaceUpdate job -func (j *composeNamespaceUpdate) Do(ctx context.Context, s storeInterface) error { - j.err = s.UpdateComposeNamespace(ctx, j.res) - j.Done <- struct{}{} - return j.err -} - -// RemoveComposeNamespace creates a new ComposeNamespace -// remove job that can be pushed to store's transaction handler -func RemoveComposeNamespace(res *types.Namespace) *composeNamespaceRemove { - return &composeNamespaceRemove{res: res} -} - -// Do Executes composeNamespaceRemove job -func (j *composeNamespaceRemove) Do(ctx context.Context, s storeInterface) error { - j.err = s.RemoveComposeNamespace(ctx, j.res) - j.Done <- struct{}{} - return j.err -} diff --git a/store/bulk/compose_pages.gen.go b/store/bulk/compose_pages.gen.go deleted file mode 100644 index 875881377..000000000 --- a/store/bulk/compose_pages.gen.go +++ /dev/null @@ -1,72 +0,0 @@ -package bulk - -// This file is auto-generated. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// Definitions file that controls how this file is generated: -// store/compose_pages.yaml - -import ( - "context" - "github.com/cortezaproject/corteza-server/compose/types" -) - -type ( - composePageCreate struct { - Done chan struct{} - res *types.Page - err error - } - - composePageUpdate struct { - Done chan struct{} - res *types.Page - err error - } - - composePageRemove struct { - Done chan struct{} - res *types.Page - err error - } -) - -// CreateComposePage creates a new ComposePage -// create job that can be pushed to store's transaction handler -func CreateComposePage(res *types.Page) *composePageCreate { - return &composePageCreate{res: res} -} - -// Do Executes composePageCreate job -func (j *composePageCreate) Do(ctx context.Context, s storeInterface) error { - j.err = s.CreateComposePage(ctx, j.res) - j.Done <- struct{}{} - return j.err -} - -// UpdateComposePage creates a new ComposePage -// update job that can be pushed to store's transaction handler -func UpdateComposePage(res *types.Page) *composePageUpdate { - return &composePageUpdate{res: res} -} - -// Do Executes composePageUpdate job -func (j *composePageUpdate) Do(ctx context.Context, s storeInterface) error { - j.err = s.UpdateComposePage(ctx, j.res) - j.Done <- struct{}{} - return j.err -} - -// RemoveComposePage creates a new ComposePage -// remove job that can be pushed to store's transaction handler -func RemoveComposePage(res *types.Page) *composePageRemove { - return &composePageRemove{res: res} -} - -// Do Executes composePageRemove job -func (j *composePageRemove) Do(ctx context.Context, s storeInterface) error { - j.err = s.RemoveComposePage(ctx, j.res) - j.Done <- struct{}{} - return j.err -} diff --git a/store/bulk/compose_records.go b/store/bulk/compose_records.go deleted file mode 100644 index 31d91424b..000000000 --- a/store/bulk/compose_records.go +++ /dev/null @@ -1,81 +0,0 @@ -package bulk - -// This file is auto-generated. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// Definitions file that controls how this file is generated: -// store/compose_records.yaml - -import ( - "context" - "github.com/cortezaproject/corteza-server/compose/types" -) - -type ( - composeRecordCreate struct { - Done chan struct{} - rec *types.Record - mod *types.Module - err error - } - - composeRecordUpdate struct { - Done chan struct{} - rec *types.Record - mod *types.Module - err error - } - - composeRecordRemove struct { - Done chan struct{} - rec *types.Record - mod *types.Module - err error - } - - composeRecordStore interface { - CreateComposeRecord(context.Context, *types.Module, ...*types.Record) error - UpdateComposeRecord(context.Context, *types.Module, ...*types.Record) error - RemoveComposeRecord(context.Context, *types.Module, ...*types.Record) error - } -) - -// CreateComposeRecord creates a new ComposeRecord -// create job that can be pushed to store's transaction handler -func CreateComposeRecord(mod *types.Module, rec *types.Record) *composeRecordCreate { - return &composeRecordCreate{mod: mod, rec: rec} -} - -// Do Executes composeRecordCreate job -func (j *composeRecordCreate) Do(ctx context.Context, s composeRecordStore) error { - j.err = s.CreateComposeRecord(ctx, j.mod, j.rec) - j.Done <- struct{}{} - return j.err -} - -// UpdateComposeRecord creates a new ComposeRecord -// update job that can be pushed to store's transaction handler -func UpdateComposeRecord(mod *types.Module, rec *types.Record) *composeRecordUpdate { - return &composeRecordUpdate{mod: mod, rec: rec} -} - -// Do Executes composeRecordUpdate job -func (j *composeRecordUpdate) Do(ctx context.Context, s composeRecordStore) error { - j.err = s.UpdateComposeRecord(ctx, j.mod, j.rec) - j.Done <- struct{}{} - return j.err -} - -// RemoveComposeRecord creates a new ComposeRecord -// remove job that can be pushed to store's transaction handler -func RemoveComposeRecord(mod *types.Module, rec *types.Record) *composeRecordRemove { - return &composeRecordRemove{mod: mod, rec: rec} -} - -// Do Executes composeRecordRemove job -func (j *composeRecordRemove) Do(ctx context.Context, s composeRecordStore) error { - j.err = s.RemoveComposeRecord(ctx, j.mod, j.rec) - j.Done <- struct{}{} - return j.err -} diff --git a/store/bulk/credentials.gen.go b/store/bulk/credentials.gen.go deleted file mode 100644 index aa7baa6a7..000000000 --- a/store/bulk/credentials.gen.go +++ /dev/null @@ -1,72 +0,0 @@ -package bulk - -// This file is auto-generated. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// Definitions file that controls how this file is generated: -// store/credentials.yaml - -import ( - "context" - "github.com/cortezaproject/corteza-server/system/types" -) - -type ( - credentialsCreate struct { - Done chan struct{} - res *types.Credentials - err error - } - - credentialsUpdate struct { - Done chan struct{} - res *types.Credentials - err error - } - - credentialsRemove struct { - Done chan struct{} - res *types.Credentials - err error - } -) - -// CreateCredentials creates a new Credentials -// create job that can be pushed to store's transaction handler -func CreateCredentials(res *types.Credentials) *credentialsCreate { - return &credentialsCreate{res: res} -} - -// Do Executes credentialsCreate job -func (j *credentialsCreate) Do(ctx context.Context, s storeInterface) error { - j.err = s.CreateCredentials(ctx, j.res) - j.Done <- struct{}{} - return j.err -} - -// UpdateCredentials creates a new Credentials -// update job that can be pushed to store's transaction handler -func UpdateCredentials(res *types.Credentials) *credentialsUpdate { - return &credentialsUpdate{res: res} -} - -// Do Executes credentialsUpdate job -func (j *credentialsUpdate) Do(ctx context.Context, s storeInterface) error { - j.err = s.UpdateCredentials(ctx, j.res) - j.Done <- struct{}{} - return j.err -} - -// RemoveCredentials creates a new Credentials -// remove job that can be pushed to store's transaction handler -func RemoveCredentials(res *types.Credentials) *credentialsRemove { - return &credentialsRemove{res: res} -} - -// Do Executes credentialsRemove job -func (j *credentialsRemove) Do(ctx context.Context, s storeInterface) error { - j.err = s.RemoveCredentials(ctx, j.res) - j.Done <- struct{}{} - return j.err -} diff --git a/store/bulk/rbac_rules.gen.go b/store/bulk/rbac_rules.gen.go deleted file mode 100644 index 71f2f67a7..000000000 --- a/store/bulk/rbac_rules.gen.go +++ /dev/null @@ -1,72 +0,0 @@ -package bulk - -// This file is auto-generated. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// Definitions file that controls how this file is generated: -// store/rbac_rules.yaml - -import ( - "context" - "github.com/cortezaproject/corteza-server/pkg/permissions" -) - -type ( - rbacRuleCreate struct { - Done chan struct{} - res *permissions.Rule - err error - } - - rbacRuleUpdate struct { - Done chan struct{} - res *permissions.Rule - err error - } - - rbacRuleRemove struct { - Done chan struct{} - res *permissions.Rule - err error - } -) - -// CreateRbacRule creates a new RbacRule -// create job that can be pushed to store's transaction handler -func CreateRbacRule(res *permissions.Rule) *rbacRuleCreate { - return &rbacRuleCreate{res: res} -} - -// Do Executes rbacRuleCreate job -func (j *rbacRuleCreate) Do(ctx context.Context, s storeInterface) error { - j.err = s.CreateRbacRule(ctx, j.res) - j.Done <- struct{}{} - return j.err -} - -// UpdateRbacRule creates a new RbacRule -// update job that can be pushed to store's transaction handler -func UpdateRbacRule(res *permissions.Rule) *rbacRuleUpdate { - return &rbacRuleUpdate{res: res} -} - -// Do Executes rbacRuleUpdate job -func (j *rbacRuleUpdate) Do(ctx context.Context, s storeInterface) error { - j.err = s.UpdateRbacRule(ctx, j.res) - j.Done <- struct{}{} - return j.err -} - -// RemoveRbacRule creates a new RbacRule -// remove job that can be pushed to store's transaction handler -func RemoveRbacRule(res *permissions.Rule) *rbacRuleRemove { - return &rbacRuleRemove{res: res} -} - -// Do Executes rbacRuleRemove job -func (j *rbacRuleRemove) Do(ctx context.Context, s storeInterface) error { - j.err = s.RemoveRbacRule(ctx, j.res) - j.Done <- struct{}{} - return j.err -} diff --git a/store/bulk/reminders.gen.go b/store/bulk/reminders.gen.go deleted file mode 100644 index fcc116287..000000000 --- a/store/bulk/reminders.gen.go +++ /dev/null @@ -1,72 +0,0 @@ -package bulk - -// This file is auto-generated. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// Definitions file that controls how this file is generated: -// store/reminders.yaml - -import ( - "context" - "github.com/cortezaproject/corteza-server/system/types" -) - -type ( - reminderCreate struct { - Done chan struct{} - res *types.Reminder - err error - } - - reminderUpdate struct { - Done chan struct{} - res *types.Reminder - err error - } - - reminderRemove struct { - Done chan struct{} - res *types.Reminder - err error - } -) - -// CreateReminder creates a new Reminder -// create job that can be pushed to store's transaction handler -func CreateReminder(res *types.Reminder) *reminderCreate { - return &reminderCreate{res: res} -} - -// Do Executes reminderCreate job -func (j *reminderCreate) Do(ctx context.Context, s storeInterface) error { - j.err = s.CreateReminder(ctx, j.res) - j.Done <- struct{}{} - return j.err -} - -// UpdateReminder creates a new Reminder -// update job that can be pushed to store's transaction handler -func UpdateReminder(res *types.Reminder) *reminderUpdate { - return &reminderUpdate{res: res} -} - -// Do Executes reminderUpdate job -func (j *reminderUpdate) Do(ctx context.Context, s storeInterface) error { - j.err = s.UpdateReminder(ctx, j.res) - j.Done <- struct{}{} - return j.err -} - -// RemoveReminder creates a new Reminder -// remove job that can be pushed to store's transaction handler -func RemoveReminder(res *types.Reminder) *reminderRemove { - return &reminderRemove{res: res} -} - -// Do Executes reminderRemove job -func (j *reminderRemove) Do(ctx context.Context, s storeInterface) error { - j.err = s.RemoveReminder(ctx, j.res) - j.Done <- struct{}{} - return j.err -} diff --git a/store/bulk/role_members.gen.go b/store/bulk/role_members.gen.go deleted file mode 100644 index 768dfded0..000000000 --- a/store/bulk/role_members.gen.go +++ /dev/null @@ -1,72 +0,0 @@ -package bulk - -// This file is auto-generated. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// Definitions file that controls how this file is generated: -// store/role_members.yaml - -import ( - "context" - "github.com/cortezaproject/corteza-server/system/types" -) - -type ( - roleMemberCreate struct { - Done chan struct{} - res *types.RoleMember - err error - } - - roleMemberUpdate struct { - Done chan struct{} - res *types.RoleMember - err error - } - - roleMemberRemove struct { - Done chan struct{} - res *types.RoleMember - err error - } -) - -// CreateRoleMember creates a new RoleMember -// create job that can be pushed to store's transaction handler -func CreateRoleMember(res *types.RoleMember) *roleMemberCreate { - return &roleMemberCreate{res: res} -} - -// Do Executes roleMemberCreate job -func (j *roleMemberCreate) Do(ctx context.Context, s storeInterface) error { - j.err = s.CreateRoleMember(ctx, j.res) - j.Done <- struct{}{} - return j.err -} - -// UpdateRoleMember creates a new RoleMember -// update job that can be pushed to store's transaction handler -func UpdateRoleMember(res *types.RoleMember) *roleMemberUpdate { - return &roleMemberUpdate{res: res} -} - -// Do Executes roleMemberUpdate job -func (j *roleMemberUpdate) Do(ctx context.Context, s storeInterface) error { - j.err = s.UpdateRoleMember(ctx, j.res) - j.Done <- struct{}{} - return j.err -} - -// RemoveRoleMember creates a new RoleMember -// remove job that can be pushed to store's transaction handler -func RemoveRoleMember(res *types.RoleMember) *roleMemberRemove { - return &roleMemberRemove{res: res} -} - -// Do Executes roleMemberRemove job -func (j *roleMemberRemove) Do(ctx context.Context, s storeInterface) error { - j.err = s.RemoveRoleMember(ctx, j.res) - j.Done <- struct{}{} - return j.err -} diff --git a/store/bulk/roles.gen.go b/store/bulk/roles.gen.go deleted file mode 100644 index 36fcb98dc..000000000 --- a/store/bulk/roles.gen.go +++ /dev/null @@ -1,72 +0,0 @@ -package bulk - -// This file is auto-generated. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// Definitions file that controls how this file is generated: -// store/roles.yaml - -import ( - "context" - "github.com/cortezaproject/corteza-server/system/types" -) - -type ( - roleCreate struct { - Done chan struct{} - res *types.Role - err error - } - - roleUpdate struct { - Done chan struct{} - res *types.Role - err error - } - - roleRemove struct { - Done chan struct{} - res *types.Role - err error - } -) - -// CreateRole creates a new Role -// create job that can be pushed to store's transaction handler -func CreateRole(res *types.Role) *roleCreate { - return &roleCreate{res: res} -} - -// Do Executes roleCreate job -func (j *roleCreate) Do(ctx context.Context, s storeInterface) error { - j.err = s.CreateRole(ctx, j.res) - j.Done <- struct{}{} - return j.err -} - -// UpdateRole creates a new Role -// update job that can be pushed to store's transaction handler -func UpdateRole(res *types.Role) *roleUpdate { - return &roleUpdate{res: res} -} - -// Do Executes roleUpdate job -func (j *roleUpdate) Do(ctx context.Context, s storeInterface) error { - j.err = s.UpdateRole(ctx, j.res) - j.Done <- struct{}{} - return j.err -} - -// RemoveRole creates a new Role -// remove job that can be pushed to store's transaction handler -func RemoveRole(res *types.Role) *roleRemove { - return &roleRemove{res: res} -} - -// Do Executes roleRemove job -func (j *roleRemove) Do(ctx context.Context, s storeInterface) error { - j.err = s.RemoveRole(ctx, j.res) - j.Done <- struct{}{} - return j.err -} diff --git a/store/bulk/settings.gen.go b/store/bulk/settings.gen.go deleted file mode 100644 index 1a8244649..000000000 --- a/store/bulk/settings.gen.go +++ /dev/null @@ -1,72 +0,0 @@ -package bulk - -// This file is auto-generated. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// Definitions file that controls how this file is generated: -// store/settings.yaml - -import ( - "context" - "github.com/cortezaproject/corteza-server/system/types" -) - -type ( - settingCreate struct { - Done chan struct{} - res *types.SettingValue - err error - } - - settingUpdate struct { - Done chan struct{} - res *types.SettingValue - err error - } - - settingRemove struct { - Done chan struct{} - res *types.SettingValue - err error - } -) - -// CreateSetting creates a new Setting -// create job that can be pushed to store's transaction handler -func CreateSetting(res *types.SettingValue) *settingCreate { - return &settingCreate{res: res} -} - -// Do Executes settingCreate job -func (j *settingCreate) Do(ctx context.Context, s storeInterface) error { - j.err = s.CreateSetting(ctx, j.res) - j.Done <- struct{}{} - return j.err -} - -// UpdateSetting creates a new Setting -// update job that can be pushed to store's transaction handler -func UpdateSetting(res *types.SettingValue) *settingUpdate { - return &settingUpdate{res: res} -} - -// Do Executes settingUpdate job -func (j *settingUpdate) Do(ctx context.Context, s storeInterface) error { - j.err = s.UpdateSetting(ctx, j.res) - j.Done <- struct{}{} - return j.err -} - -// RemoveSetting creates a new Setting -// remove job that can be pushed to store's transaction handler -func RemoveSetting(res *types.SettingValue) *settingRemove { - return &settingRemove{res: res} -} - -// Do Executes settingRemove job -func (j *settingRemove) Do(ctx context.Context, s storeInterface) error { - j.err = s.RemoveSetting(ctx, j.res) - j.Done <- struct{}{} - return j.err -} diff --git a/store/bulk/store_interface.gen.go b/store/bulk/store_interface.gen.go deleted file mode 100644 index 68f6d902a..000000000 --- a/store/bulk/store_interface.gen.go +++ /dev/null @@ -1,46 +0,0 @@ -package bulk - -// This file is auto-generated. -// -// Template: pkg/store_interfaces_joined.gen.go.tpl -// Definitions: -// - store/actionlog.yaml -// - store/applications.yaml -// - store/attachments.yaml -// - store/compose_charts.yaml -// - store/compose_module_fields.yaml -// - store/compose_modules.yaml -// - store/compose_namespaces.yaml -// - store/compose_pages.yaml -// - store/credentials.yaml -// - store/rbac_rules.yaml -// - store/reminders.yaml -// - store/role_members.yaml -// - store/roles.yaml -// - store/settings.yaml -// - store/users.yaml -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// - -type ( - // Interface combines interfaces of all supported store interfaces - storeGeneratedInterfaces interface { - actionlogsStore - applicationsStore - attachmentsStore - composeChartsStore - composeModuleFieldsStore - composeModulesStore - composeNamespacesStore - composePagesStore - credentialsStore - rbacRulesStore - remindersStore - roleMembersStore - rolesStore - settingsStore - usersStore - } -) diff --git a/store/bulk/store_interface_actionlog.gen.go b/store/bulk/store_interface_actionlog.gen.go deleted file mode 100644 index 089af0f47..000000000 --- a/store/bulk/store_interface_actionlog.gen.go +++ /dev/null @@ -1,27 +0,0 @@ -package bulk - -// This file is auto-generated. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -// Definitions file that controls how this file is generated: -// - store/actionlog.yaml - -import ( - "context" - "github.com/cortezaproject/corteza-server/pkg/actionlog" -) - -type ( - actionlogsStore interface { - SearchActionlogs(ctx context.Context, f actionlog.Filter) (actionlog.ActionSet, actionlog.Filter, error) - CreateActionlog(ctx context.Context, rr ...*actionlog.Action) error - UpdateActionlog(ctx context.Context, rr ...*actionlog.Action) error - PartialUpdateActionlog(ctx context.Context, onlyColumns []string, rr ...*actionlog.Action) error - RemoveActionlog(ctx context.Context, rr ...*actionlog.Action) error - RemoveActionlogByID(ctx context.Context, ID uint64) error - - TruncateActionlogs(ctx context.Context) error - } -) diff --git a/store/bulk/store_interface_applications.gen.go b/store/bulk/store_interface_applications.gen.go deleted file mode 100644 index 3695bd896..000000000 --- a/store/bulk/store_interface_applications.gen.go +++ /dev/null @@ -1,28 +0,0 @@ -package bulk - -// This file is auto-generated. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -// Definitions file that controls how this file is generated: -// - store/applications.yaml - -import ( - "context" - "github.com/cortezaproject/corteza-server/system/types" -) - -type ( - applicationsStore interface { - SearchApplications(ctx context.Context, f types.ApplicationFilter) (types.ApplicationSet, types.ApplicationFilter, error) - LookupApplicationByID(ctx context.Context, id uint64) (*types.Application, error) - CreateApplication(ctx context.Context, rr ...*types.Application) error - UpdateApplication(ctx context.Context, rr ...*types.Application) error - PartialUpdateApplication(ctx context.Context, onlyColumns []string, rr ...*types.Application) error - RemoveApplication(ctx context.Context, rr ...*types.Application) error - RemoveApplicationByID(ctx context.Context, ID uint64) error - - TruncateApplications(ctx context.Context) error - } -) diff --git a/store/bulk/store_interface_attachments.gen.go b/store/bulk/store_interface_attachments.gen.go deleted file mode 100644 index 3d0f49477..000000000 --- a/store/bulk/store_interface_attachments.gen.go +++ /dev/null @@ -1,28 +0,0 @@ -package bulk - -// This file is auto-generated. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -// Definitions file that controls how this file is generated: -// - store/attachments.yaml - -import ( - "context" - "github.com/cortezaproject/corteza-server/system/types" -) - -type ( - attachmentsStore interface { - SearchAttachments(ctx context.Context, f types.AttachmentFilter) (types.AttachmentSet, types.AttachmentFilter, error) - LookupAttachmentByID(ctx context.Context, id uint64) (*types.Attachment, error) - CreateAttachment(ctx context.Context, rr ...*types.Attachment) error - UpdateAttachment(ctx context.Context, rr ...*types.Attachment) error - PartialUpdateAttachment(ctx context.Context, onlyColumns []string, rr ...*types.Attachment) error - RemoveAttachment(ctx context.Context, rr ...*types.Attachment) error - RemoveAttachmentByID(ctx context.Context, ID uint64) error - - TruncateAttachments(ctx context.Context) error - } -) diff --git a/store/bulk/store_interface_compose_charts.gen.go b/store/bulk/store_interface_compose_charts.gen.go deleted file mode 100644 index 2103d7bcc..000000000 --- a/store/bulk/store_interface_compose_charts.gen.go +++ /dev/null @@ -1,29 +0,0 @@ -package bulk - -// This file is auto-generated. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -// Definitions file that controls how this file is generated: -// - store/compose_charts.yaml - -import ( - "context" - "github.com/cortezaproject/corteza-server/compose/types" -) - -type ( - composeChartsStore interface { - SearchComposeCharts(ctx context.Context, f types.ChartFilter) (types.ChartSet, types.ChartFilter, error) - LookupComposeChartByID(ctx context.Context, id uint64) (*types.Chart, error) - LookupComposeChartByHandle(ctx context.Context, handle string) (*types.Chart, error) - CreateComposeChart(ctx context.Context, rr ...*types.Chart) error - UpdateComposeChart(ctx context.Context, rr ...*types.Chart) error - PartialUpdateComposeChart(ctx context.Context, onlyColumns []string, rr ...*types.Chart) error - RemoveComposeChart(ctx context.Context, rr ...*types.Chart) error - RemoveComposeChartByID(ctx context.Context, ID uint64) error - - TruncateComposeCharts(ctx context.Context) error - } -) diff --git a/store/bulk/store_interface_compose_module_fields.gen.go b/store/bulk/store_interface_compose_module_fields.gen.go deleted file mode 100644 index 7573349f4..000000000 --- a/store/bulk/store_interface_compose_module_fields.gen.go +++ /dev/null @@ -1,26 +0,0 @@ -package bulk - -// This file is auto-generated. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -// Definitions file that controls how this file is generated: -// - store/compose_module_fields.yaml - -import ( - "context" - "github.com/cortezaproject/corteza-server/compose/types" -) - -type ( - composeModuleFieldsStore interface { - CreateComposeModuleField(ctx context.Context, rr ...*types.ModuleField) error - UpdateComposeModuleField(ctx context.Context, rr ...*types.ModuleField) error - PartialUpdateComposeModuleField(ctx context.Context, onlyColumns []string, rr ...*types.ModuleField) error - RemoveComposeModuleField(ctx context.Context, rr ...*types.ModuleField) error - RemoveComposeModuleFieldByID(ctx context.Context, ID uint64) error - - TruncateComposeModuleFields(ctx context.Context) error - } -) diff --git a/store/bulk/store_interface_compose_modules.gen.go b/store/bulk/store_interface_compose_modules.gen.go deleted file mode 100644 index 2401b9275..000000000 --- a/store/bulk/store_interface_compose_modules.gen.go +++ /dev/null @@ -1,29 +0,0 @@ -package bulk - -// This file is auto-generated. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -// Definitions file that controls how this file is generated: -// - store/compose_modules.yaml - -import ( - "context" - "github.com/cortezaproject/corteza-server/compose/types" -) - -type ( - composeModulesStore interface { - SearchComposeModules(ctx context.Context, f types.ModuleFilter) (types.ModuleSet, types.ModuleFilter, error) - LookupComposeModuleByHandle(ctx context.Context, handle string) (*types.Module, error) - LookupComposeModuleByID(ctx context.Context, id uint64) (*types.Module, error) - CreateComposeModule(ctx context.Context, rr ...*types.Module) error - UpdateComposeModule(ctx context.Context, rr ...*types.Module) error - PartialUpdateComposeModule(ctx context.Context, onlyColumns []string, rr ...*types.Module) error - RemoveComposeModule(ctx context.Context, rr ...*types.Module) error - RemoveComposeModuleByID(ctx context.Context, ID uint64) error - - TruncateComposeModules(ctx context.Context) error - } -) diff --git a/store/bulk/store_interface_compose_namespaces.gen.go b/store/bulk/store_interface_compose_namespaces.gen.go deleted file mode 100644 index e153a9920..000000000 --- a/store/bulk/store_interface_compose_namespaces.gen.go +++ /dev/null @@ -1,29 +0,0 @@ -package bulk - -// This file is auto-generated. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -// Definitions file that controls how this file is generated: -// - store/compose_namespaces.yaml - -import ( - "context" - "github.com/cortezaproject/corteza-server/compose/types" -) - -type ( - composeNamespacesStore interface { - SearchComposeNamespaces(ctx context.Context, f types.NamespaceFilter) (types.NamespaceSet, types.NamespaceFilter, error) - LookupComposeNamespaceBySlug(ctx context.Context, slug string) (*types.Namespace, error) - LookupComposeNamespaceByID(ctx context.Context, id uint64) (*types.Namespace, error) - CreateComposeNamespace(ctx context.Context, rr ...*types.Namespace) error - UpdateComposeNamespace(ctx context.Context, rr ...*types.Namespace) error - PartialUpdateComposeNamespace(ctx context.Context, onlyColumns []string, rr ...*types.Namespace) error - RemoveComposeNamespace(ctx context.Context, rr ...*types.Namespace) error - RemoveComposeNamespaceByID(ctx context.Context, ID uint64) error - - TruncateComposeNamespaces(ctx context.Context) error - } -) diff --git a/store/bulk/store_interface_compose_pages.gen.go b/store/bulk/store_interface_compose_pages.gen.go deleted file mode 100644 index f6846db65..000000000 --- a/store/bulk/store_interface_compose_pages.gen.go +++ /dev/null @@ -1,29 +0,0 @@ -package bulk - -// This file is auto-generated. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -// Definitions file that controls how this file is generated: -// - store/compose_pages.yaml - -import ( - "context" - "github.com/cortezaproject/corteza-server/compose/types" -) - -type ( - composePagesStore interface { - SearchComposePages(ctx context.Context, f types.PageFilter) (types.PageSet, types.PageFilter, error) - LookupComposePageByHandle(ctx context.Context, handle string) (*types.Page, error) - LookupComposePageByID(ctx context.Context, id uint64) (*types.Page, error) - CreateComposePage(ctx context.Context, rr ...*types.Page) error - UpdateComposePage(ctx context.Context, rr ...*types.Page) error - PartialUpdateComposePage(ctx context.Context, onlyColumns []string, rr ...*types.Page) error - RemoveComposePage(ctx context.Context, rr ...*types.Page) error - RemoveComposePageByID(ctx context.Context, ID uint64) error - - TruncateComposePages(ctx context.Context) error - } -) diff --git a/store/bulk/store_interface_credentials.gen.go b/store/bulk/store_interface_credentials.gen.go deleted file mode 100644 index c6a76eb7d..000000000 --- a/store/bulk/store_interface_credentials.gen.go +++ /dev/null @@ -1,28 +0,0 @@ -package bulk - -// This file is auto-generated. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -// Definitions file that controls how this file is generated: -// - store/credentials.yaml - -import ( - "context" - "github.com/cortezaproject/corteza-server/system/types" -) - -type ( - credentialsStore interface { - SearchCredentials(ctx context.Context, f types.CredentialsFilter) (types.CredentialsSet, types.CredentialsFilter, error) - LookupCredentialsByID(ctx context.Context, id uint64) (*types.Credentials, error) - CreateCredentials(ctx context.Context, rr ...*types.Credentials) error - UpdateCredentials(ctx context.Context, rr ...*types.Credentials) error - PartialUpdateCredentials(ctx context.Context, onlyColumns []string, rr ...*types.Credentials) error - RemoveCredentials(ctx context.Context, rr ...*types.Credentials) error - RemoveCredentialsByID(ctx context.Context, ID uint64) error - - TruncateCredentials(ctx context.Context) error - } -) diff --git a/store/bulk/store_interface_rbac_rules.gen.go b/store/bulk/store_interface_rbac_rules.gen.go deleted file mode 100644 index 7e1711094..000000000 --- a/store/bulk/store_interface_rbac_rules.gen.go +++ /dev/null @@ -1,27 +0,0 @@ -package bulk - -// This file is auto-generated. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -// Definitions file that controls how this file is generated: -// - store/rbac_rules.yaml - -import ( - "context" - "github.com/cortezaproject/corteza-server/pkg/permissions" -) - -type ( - rbacRulesStore interface { - SearchRbacRules(ctx context.Context, f permissions.RuleFilter) (permissions.RuleSet, permissions.RuleFilter, error) - CreateRbacRule(ctx context.Context, rr ...*permissions.Rule) error - UpdateRbacRule(ctx context.Context, rr ...*permissions.Rule) error - PartialUpdateRbacRule(ctx context.Context, onlyColumns []string, rr ...*permissions.Rule) error - RemoveRbacRule(ctx context.Context, rr ...*permissions.Rule) error - RemoveRbacRuleByRoleIDResourceOperation(ctx context.Context, roleID uint64, resource string, operation string) error - - TruncateRbacRules(ctx context.Context) error - } -) diff --git a/store/bulk/store_interface_reminders.gen.go b/store/bulk/store_interface_reminders.gen.go deleted file mode 100644 index 6a1cb3612..000000000 --- a/store/bulk/store_interface_reminders.gen.go +++ /dev/null @@ -1,28 +0,0 @@ -package bulk - -// This file is auto-generated. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -// Definitions file that controls how this file is generated: -// - store/reminders.yaml - -import ( - "context" - "github.com/cortezaproject/corteza-server/system/types" -) - -type ( - remindersStore interface { - SearchReminders(ctx context.Context, f types.ReminderFilter) (types.ReminderSet, types.ReminderFilter, error) - LookupReminderByID(ctx context.Context, id uint64) (*types.Reminder, error) - CreateReminder(ctx context.Context, rr ...*types.Reminder) error - UpdateReminder(ctx context.Context, rr ...*types.Reminder) error - PartialUpdateReminder(ctx context.Context, onlyColumns []string, rr ...*types.Reminder) error - RemoveReminder(ctx context.Context, rr ...*types.Reminder) error - RemoveReminderByID(ctx context.Context, ID uint64) error - - TruncateReminders(ctx context.Context) error - } -) diff --git a/store/bulk/store_interface_role_members.gen.go b/store/bulk/store_interface_role_members.gen.go deleted file mode 100644 index 833debb54..000000000 --- a/store/bulk/store_interface_role_members.gen.go +++ /dev/null @@ -1,27 +0,0 @@ -package bulk - -// This file is auto-generated. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -// Definitions file that controls how this file is generated: -// - store/role_members.yaml - -import ( - "context" - "github.com/cortezaproject/corteza-server/system/types" -) - -type ( - roleMembersStore interface { - SearchRoleMembers(ctx context.Context, f types.RoleMemberFilter) (types.RoleMemberSet, types.RoleMemberFilter, error) - CreateRoleMember(ctx context.Context, rr ...*types.RoleMember) error - UpdateRoleMember(ctx context.Context, rr ...*types.RoleMember) error - PartialUpdateRoleMember(ctx context.Context, onlyColumns []string, rr ...*types.RoleMember) error - RemoveRoleMember(ctx context.Context, rr ...*types.RoleMember) error - RemoveRoleMemberByUserIDRoleID(ctx context.Context, userID uint64, roleID uint64) error - - TruncateRoleMembers(ctx context.Context) error - } -) diff --git a/store/bulk/store_interface_roles.gen.go b/store/bulk/store_interface_roles.gen.go deleted file mode 100644 index dc6c668f2..000000000 --- a/store/bulk/store_interface_roles.gen.go +++ /dev/null @@ -1,30 +0,0 @@ -package bulk - -// This file is auto-generated. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -// Definitions file that controls how this file is generated: -// - store/roles.yaml - -import ( - "context" - "github.com/cortezaproject/corteza-server/system/types" -) - -type ( - rolesStore interface { - SearchRoles(ctx context.Context, f types.RoleFilter) (types.RoleSet, types.RoleFilter, error) - LookupRoleByID(ctx context.Context, id uint64) (*types.Role, error) - LookupRoleByHandle(ctx context.Context, handle string) (*types.Role, error) - LookupRoleByName(ctx context.Context, name string) (*types.Role, error) - CreateRole(ctx context.Context, rr ...*types.Role) error - UpdateRole(ctx context.Context, rr ...*types.Role) error - PartialUpdateRole(ctx context.Context, onlyColumns []string, rr ...*types.Role) error - RemoveRole(ctx context.Context, rr ...*types.Role) error - RemoveRoleByID(ctx context.Context, ID uint64) error - - TruncateRoles(ctx context.Context) error - } -) diff --git a/store/bulk/store_interface_settings.gen.go b/store/bulk/store_interface_settings.gen.go deleted file mode 100644 index 38e3ab950..000000000 --- a/store/bulk/store_interface_settings.gen.go +++ /dev/null @@ -1,28 +0,0 @@ -package bulk - -// This file is auto-generated. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -// Definitions file that controls how this file is generated: -// - store/settings.yaml - -import ( - "context" - "github.com/cortezaproject/corteza-server/system/types" -) - -type ( - settingsStore interface { - SearchSettings(ctx context.Context, f types.SettingsFilter) (types.SettingValueSet, types.SettingsFilter, error) - LookupSettingByNameOwnedBy(ctx context.Context, name string, owned_by uint64) (*types.SettingValue, error) - CreateSetting(ctx context.Context, rr ...*types.SettingValue) error - UpdateSetting(ctx context.Context, rr ...*types.SettingValue) error - PartialUpdateSetting(ctx context.Context, onlyColumns []string, rr ...*types.SettingValue) error - RemoveSetting(ctx context.Context, rr ...*types.SettingValue) error - RemoveSettingByNameOwnedBy(ctx context.Context, name string, ownedBy uint64) error - - TruncateSettings(ctx context.Context) error - } -) diff --git a/store/bulk/store_interface_users.gen.go b/store/bulk/store_interface_users.gen.go deleted file mode 100644 index 82d39c247..000000000 --- a/store/bulk/store_interface_users.gen.go +++ /dev/null @@ -1,31 +0,0 @@ -package bulk - -// This file is auto-generated. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -// Definitions file that controls how this file is generated: -// - store/users.yaml - -import ( - "context" - "github.com/cortezaproject/corteza-server/system/types" -) - -type ( - usersStore interface { - SearchUsers(ctx context.Context, f types.UserFilter) (types.UserSet, types.UserFilter, error) - LookupUserByID(ctx context.Context, id uint64) (*types.User, error) - LookupUserByEmail(ctx context.Context, email string) (*types.User, error) - LookupUserByHandle(ctx context.Context, handle string) (*types.User, error) - LookupUserByUsername(ctx context.Context, username string) (*types.User, error) - CreateUser(ctx context.Context, rr ...*types.User) error - UpdateUser(ctx context.Context, rr ...*types.User) error - PartialUpdateUser(ctx context.Context, onlyColumns []string, rr ...*types.User) error - RemoveUser(ctx context.Context, rr ...*types.User) error - RemoveUserByID(ctx context.Context, ID uint64) error - - TruncateUsers(ctx context.Context) error - } -) diff --git a/store/bulk/users.gen.go b/store/bulk/users.gen.go deleted file mode 100644 index 66d93c360..000000000 --- a/store/bulk/users.gen.go +++ /dev/null @@ -1,72 +0,0 @@ -package bulk - -// This file is auto-generated. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// Definitions file that controls how this file is generated: -// store/users.yaml - -import ( - "context" - "github.com/cortezaproject/corteza-server/system/types" -) - -type ( - userCreate struct { - Done chan struct{} - res *types.User - err error - } - - userUpdate struct { - Done chan struct{} - res *types.User - err error - } - - userRemove struct { - Done chan struct{} - res *types.User - err error - } -) - -// CreateUser creates a new User -// create job that can be pushed to store's transaction handler -func CreateUser(res *types.User) *userCreate { - return &userCreate{res: res} -} - -// Do Executes userCreate job -func (j *userCreate) Do(ctx context.Context, s storeInterface) error { - j.err = s.CreateUser(ctx, j.res) - j.Done <- struct{}{} - return j.err -} - -// UpdateUser creates a new User -// update job that can be pushed to store's transaction handler -func UpdateUser(res *types.User) *userUpdate { - return &userUpdate{res: res} -} - -// Do Executes userUpdate job -func (j *userUpdate) Do(ctx context.Context, s storeInterface) error { - j.err = s.UpdateUser(ctx, j.res) - j.Done <- struct{}{} - return j.err -} - -// RemoveUser creates a new User -// remove job that can be pushed to store's transaction handler -func RemoveUser(res *types.User) *userRemove { - return &userRemove{res: res} -} - -// Do Executes userRemove job -func (j *userRemove) Do(ctx context.Context, s storeInterface) error { - j.err = s.RemoveUser(ctx, j.res) - j.Done <- struct{}{} - return j.err -} diff --git a/store/compose_charts.gen.go b/store/compose_charts.gen.go new file mode 100644 index 000000000..4d1426e2c --- /dev/null +++ b/store/compose_charts.gen.go @@ -0,0 +1,89 @@ +package store + +// This file is auto-generated. +// +// Template: pkg/codegen/assets/store_base.gen.go.tpl +// Definitions: store/compose_charts.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 ( + ComposeCharts interface { + SearchComposeCharts(ctx context.Context, f types.ChartFilter) (types.ChartSet, types.ChartFilter, error) + LookupComposeChartByID(ctx context.Context, id uint64) (*types.Chart, error) + LookupComposeChartByNamespaceIDHandle(ctx context.Context, namespace_id uint64, handle string) (*types.Chart, error) + + CreateComposeChart(ctx context.Context, rr ...*types.Chart) error + + UpdateComposeChart(ctx context.Context, rr ...*types.Chart) error + PartialComposeChartUpdate(ctx context.Context, onlyColumns []string, rr ...*types.Chart) error + + UpsertComposeChart(ctx context.Context, rr ...*types.Chart) error + + DeleteComposeChart(ctx context.Context, rr ...*types.Chart) error + DeleteComposeChartByID(ctx context.Context, ID uint64) error + + TruncateComposeCharts(ctx context.Context) error + } +) + +var _ *types.Chart +var _ context.Context + +// SearchComposeCharts returns all matching ComposeCharts from store +func SearchComposeCharts(ctx context.Context, s ComposeCharts, f types.ChartFilter) (types.ChartSet, types.ChartFilter, error) { + return s.SearchComposeCharts(ctx, f) +} + +// LookupComposeChartByID searches for compose chart by ID +// +// It returns compose chart even if deleted +func LookupComposeChartByID(ctx context.Context, s ComposeCharts, id uint64) (*types.Chart, error) { + return s.LookupComposeChartByID(ctx, id) +} + +// LookupComposeChartByNamespaceIDHandle searches for compose chart by handle (case-insensitive) +func LookupComposeChartByNamespaceIDHandle(ctx context.Context, s ComposeCharts, namespace_id uint64, handle string) (*types.Chart, error) { + return s.LookupComposeChartByNamespaceIDHandle(ctx, namespace_id, handle) +} + +// CreateComposeChart creates one or more ComposeCharts in store +func CreateComposeChart(ctx context.Context, s ComposeCharts, rr ...*types.Chart) error { + return s.CreateComposeChart(ctx, rr...) +} + +// UpdateComposeChart updates one or more (existing) ComposeCharts in store +func UpdateComposeChart(ctx context.Context, s ComposeCharts, rr ...*types.Chart) error { + return s.UpdateComposeChart(ctx, rr...) +} + +// PartialComposeChartUpdate updates one or more existing ComposeCharts in store +func PartialComposeChartUpdate(ctx context.Context, s ComposeCharts, onlyColumns []string, rr ...*types.Chart) error { + return s.PartialComposeChartUpdate(ctx, onlyColumns, rr...) +} + +// UpsertComposeChart creates new or updates existing one or more ComposeCharts in store +func UpsertComposeChart(ctx context.Context, s ComposeCharts, rr ...*types.Chart) error { + return s.UpsertComposeChart(ctx, rr...) +} + +// DeleteComposeChart Deletes one or more ComposeCharts from store +func DeleteComposeChart(ctx context.Context, s ComposeCharts, rr ...*types.Chart) error { + return s.DeleteComposeChart(ctx, rr...) +} + +// DeleteComposeChartByID Deletes ComposeChart from store +func DeleteComposeChartByID(ctx context.Context, s ComposeCharts, ID uint64) error { + return s.DeleteComposeChartByID(ctx, ID) +} + +// TruncateComposeCharts Deletes all ComposeCharts from store +func TruncateComposeCharts(ctx context.Context, s ComposeCharts) error { + return s.TruncateComposeCharts(ctx) +} diff --git a/store/compose_charts.yaml b/store/compose_charts.yaml index dfa2079b4..8ef27e2e8 100644 --- a/store/compose_charts.yaml +++ b/store/compose_charts.yaml @@ -1,9 +1,6 @@ import: - github.com/cortezaproject/corteza-server/compose/types -interface: - - compose/service - types: type: types.Chart @@ -23,7 +20,7 @@ lookups: searches for compose chart by ID It returns compose chart even if deleted - - fields: [ Handle ] + - fields: [ NamespaceID, Handle ] description: |- searches for compose chart by handle (case-insensitive) diff --git a/store/compose_module_fields.gen.go b/store/compose_module_fields.gen.go new file mode 100644 index 000000000..670b7ee17 --- /dev/null +++ b/store/compose_module_fields.gen.go @@ -0,0 +1,81 @@ +package store + +// This file is auto-generated. +// +// Template: pkg/codegen/assets/store_base.gen.go.tpl +// Definitions: store/compose_module_fields.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 ( + ComposeModuleFields interface { + SearchComposeModuleFields(ctx context.Context, f types.ModuleFieldFilter) (types.ModuleFieldSet, types.ModuleFieldFilter, error) + LookupComposeModuleFieldByModuleIDName(ctx context.Context, module_id uint64, name string) (*types.ModuleField, error) + + CreateComposeModuleField(ctx context.Context, rr ...*types.ModuleField) error + + UpdateComposeModuleField(ctx context.Context, rr ...*types.ModuleField) error + PartialComposeModuleFieldUpdate(ctx context.Context, onlyColumns []string, rr ...*types.ModuleField) error + + UpsertComposeModuleField(ctx context.Context, rr ...*types.ModuleField) error + + DeleteComposeModuleField(ctx context.Context, rr ...*types.ModuleField) error + DeleteComposeModuleFieldByID(ctx context.Context, ID uint64) error + + TruncateComposeModuleFields(ctx context.Context) error + } +) + +var _ *types.ModuleField +var _ context.Context + +// SearchComposeModuleFields returns all matching ComposeModuleFields from store +func SearchComposeModuleFields(ctx context.Context, s ComposeModuleFields, f types.ModuleFieldFilter) (types.ModuleFieldSet, types.ModuleFieldFilter, error) { + return s.SearchComposeModuleFields(ctx, f) +} + +// LookupComposeModuleFieldByModuleIDName searches for compose module field by name (case-insensitive) +func LookupComposeModuleFieldByModuleIDName(ctx context.Context, s ComposeModuleFields, module_id uint64, name string) (*types.ModuleField, error) { + return s.LookupComposeModuleFieldByModuleIDName(ctx, module_id, name) +} + +// CreateComposeModuleField creates one or more ComposeModuleFields in store +func CreateComposeModuleField(ctx context.Context, s ComposeModuleFields, rr ...*types.ModuleField) error { + return s.CreateComposeModuleField(ctx, rr...) +} + +// UpdateComposeModuleField updates one or more (existing) ComposeModuleFields in store +func UpdateComposeModuleField(ctx context.Context, s ComposeModuleFields, rr ...*types.ModuleField) error { + return s.UpdateComposeModuleField(ctx, rr...) +} + +// PartialComposeModuleFieldUpdate updates one or more existing ComposeModuleFields in store +func PartialComposeModuleFieldUpdate(ctx context.Context, s ComposeModuleFields, onlyColumns []string, rr ...*types.ModuleField) error { + return s.PartialComposeModuleFieldUpdate(ctx, onlyColumns, rr...) +} + +// UpsertComposeModuleField creates new or updates existing one or more ComposeModuleFields in store +func UpsertComposeModuleField(ctx context.Context, s ComposeModuleFields, rr ...*types.ModuleField) error { + return s.UpsertComposeModuleField(ctx, rr...) +} + +// DeleteComposeModuleField Deletes one or more ComposeModuleFields from store +func DeleteComposeModuleField(ctx context.Context, s ComposeModuleFields, rr ...*types.ModuleField) error { + return s.DeleteComposeModuleField(ctx, rr...) +} + +// DeleteComposeModuleFieldByID Deletes ComposeModuleField from store +func DeleteComposeModuleFieldByID(ctx context.Context, s ComposeModuleFields, ID uint64) error { + return s.DeleteComposeModuleFieldByID(ctx, ID) +} + +// TruncateComposeModuleFields Deletes all ComposeModuleFields from store +func TruncateComposeModuleFields(ctx context.Context, s ComposeModuleFields) error { + return s.TruncateComposeModuleFields(ctx) +} diff --git a/store/compose_module_fields.yaml b/store/compose_module_fields.yaml index f14121b99..f9fccd107 100644 --- a/store/compose_module_fields.yaml +++ b/store/compose_module_fields.yaml @@ -1,15 +1,12 @@ import: - github.com/cortezaproject/corteza-server/compose/types -interface: - - compose/service - types: type: types.ModuleField fields: - { field: ID } - - { field: Name } + - { field: Name, lookupFilterPreprocessor: lower, unique: true} - { field: ModuleID } - { field: Place, type: int } - { field: Kind } @@ -24,6 +21,11 @@ fields: - { field: UpdatedAt } - { field: DeletedAt } +lookups: + - fields: [ ModuleID, Name ] + uniqueConstraintCheck: true + description: |- + searches for compose module field by name (case-insensitive) rdbms: alias: cmf @@ -32,4 +34,6 @@ rdbms: search: - disable: true + enablePaging: false + enableSorting: false + enableFilterCheckFunction: false diff --git a/store/compose_modules.gen.go b/store/compose_modules.gen.go new file mode 100644 index 000000000..5ffd21672 --- /dev/null +++ b/store/compose_modules.gen.go @@ -0,0 +1,95 @@ +package store + +// This file is auto-generated. +// +// Template: pkg/codegen/assets/store_base.gen.go.tpl +// Definitions: store/compose_modules.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 ( + ComposeModules interface { + SearchComposeModules(ctx context.Context, f types.ModuleFilter) (types.ModuleSet, types.ModuleFilter, error) + LookupComposeModuleByNamespaceIDHandle(ctx context.Context, namespace_id uint64, handle string) (*types.Module, error) + LookupComposeModuleByNamespaceIDName(ctx context.Context, namespace_id uint64, name string) (*types.Module, error) + LookupComposeModuleByID(ctx context.Context, id uint64) (*types.Module, error) + + CreateComposeModule(ctx context.Context, rr ...*types.Module) error + + UpdateComposeModule(ctx context.Context, rr ...*types.Module) error + PartialComposeModuleUpdate(ctx context.Context, onlyColumns []string, rr ...*types.Module) error + + UpsertComposeModule(ctx context.Context, rr ...*types.Module) error + + DeleteComposeModule(ctx context.Context, rr ...*types.Module) error + DeleteComposeModuleByID(ctx context.Context, ID uint64) error + + TruncateComposeModules(ctx context.Context) error + } +) + +var _ *types.Module +var _ context.Context + +// SearchComposeModules returns all matching ComposeModules from store +func SearchComposeModules(ctx context.Context, s ComposeModules, f types.ModuleFilter) (types.ModuleSet, types.ModuleFilter, error) { + return s.SearchComposeModules(ctx, f) +} + +// LookupComposeModuleByNamespaceIDHandle searches for compose module by handle (case-insensitive) +func LookupComposeModuleByNamespaceIDHandle(ctx context.Context, s ComposeModules, namespace_id uint64, handle string) (*types.Module, error) { + return s.LookupComposeModuleByNamespaceIDHandle(ctx, namespace_id, handle) +} + +// LookupComposeModuleByNamespaceIDName searches for compose module by name (case-insensitive) +func LookupComposeModuleByNamespaceIDName(ctx context.Context, s ComposeModules, namespace_id uint64, name string) (*types.Module, error) { + return s.LookupComposeModuleByNamespaceIDName(ctx, namespace_id, name) +} + +// LookupComposeModuleByID searches for compose module by ID +// +// It returns compose module even if deleted +func LookupComposeModuleByID(ctx context.Context, s ComposeModules, id uint64) (*types.Module, error) { + return s.LookupComposeModuleByID(ctx, id) +} + +// CreateComposeModule creates one or more ComposeModules in store +func CreateComposeModule(ctx context.Context, s ComposeModules, rr ...*types.Module) error { + return s.CreateComposeModule(ctx, rr...) +} + +// UpdateComposeModule updates one or more (existing) ComposeModules in store +func UpdateComposeModule(ctx context.Context, s ComposeModules, rr ...*types.Module) error { + return s.UpdateComposeModule(ctx, rr...) +} + +// PartialComposeModuleUpdate updates one or more existing ComposeModules in store +func PartialComposeModuleUpdate(ctx context.Context, s ComposeModules, onlyColumns []string, rr ...*types.Module) error { + return s.PartialComposeModuleUpdate(ctx, onlyColumns, rr...) +} + +// UpsertComposeModule creates new or updates existing one or more ComposeModules in store +func UpsertComposeModule(ctx context.Context, s ComposeModules, rr ...*types.Module) error { + return s.UpsertComposeModule(ctx, rr...) +} + +// DeleteComposeModule Deletes one or more ComposeModules from store +func DeleteComposeModule(ctx context.Context, s ComposeModules, rr ...*types.Module) error { + return s.DeleteComposeModule(ctx, rr...) +} + +// DeleteComposeModuleByID Deletes ComposeModule from store +func DeleteComposeModuleByID(ctx context.Context, s ComposeModules, ID uint64) error { + return s.DeleteComposeModuleByID(ctx, ID) +} + +// TruncateComposeModules Deletes all ComposeModules from store +func TruncateComposeModules(ctx context.Context, s ComposeModules) error { + return s.TruncateComposeModules(ctx) +} diff --git a/store/compose_modules.yaml b/store/compose_modules.yaml index af316dd50..eda44b2f4 100644 --- a/store/compose_modules.yaml +++ b/store/compose_modules.yaml @@ -1,16 +1,13 @@ import: - github.com/cortezaproject/corteza-server/compose/types -interface: - - compose/service - types: type: types.Module fields: - { field: ID } - { field: Handle, lookupFilterPreprocessor: lower, unique: true, sortable: true } - - { field: Name, sortable: true } + - { field: Name, lookupFilterPreprocessor: lower, sortable: true } - { field: Meta, type: "types.JSONText" } - { field: NamespaceID } - { field: CreatedAt, sortable: true } @@ -19,10 +16,15 @@ fields: lookups: - - fields: [ Handle ] + - fields: [ NamespaceID, Handle ] + uniqueConstraintCheck: true description: |- searches for compose module by handle (case-insensitive) + - fields: [ NamespaceID, Name ] + description: |- + searches for compose module by name (case-insensitive) + - fields: [ ID ] description: |- searches for compose module by ID diff --git a/store/compose_namespaces.gen.go b/store/compose_namespaces.gen.go new file mode 100644 index 000000000..037531333 --- /dev/null +++ b/store/compose_namespaces.gen.go @@ -0,0 +1,89 @@ +package store + +// This file is auto-generated. +// +// Template: pkg/codegen/assets/store_base.gen.go.tpl +// Definitions: store/compose_namespaces.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 ( + ComposeNamespaces interface { + SearchComposeNamespaces(ctx context.Context, f types.NamespaceFilter) (types.NamespaceSet, types.NamespaceFilter, error) + LookupComposeNamespaceBySlug(ctx context.Context, slug string) (*types.Namespace, error) + LookupComposeNamespaceByID(ctx context.Context, id uint64) (*types.Namespace, error) + + CreateComposeNamespace(ctx context.Context, rr ...*types.Namespace) error + + UpdateComposeNamespace(ctx context.Context, rr ...*types.Namespace) error + PartialComposeNamespaceUpdate(ctx context.Context, onlyColumns []string, rr ...*types.Namespace) error + + UpsertComposeNamespace(ctx context.Context, rr ...*types.Namespace) error + + DeleteComposeNamespace(ctx context.Context, rr ...*types.Namespace) error + DeleteComposeNamespaceByID(ctx context.Context, ID uint64) error + + TruncateComposeNamespaces(ctx context.Context) error + } +) + +var _ *types.Namespace +var _ context.Context + +// SearchComposeNamespaces returns all matching ComposeNamespaces from store +func SearchComposeNamespaces(ctx context.Context, s ComposeNamespaces, f types.NamespaceFilter) (types.NamespaceSet, types.NamespaceFilter, error) { + return s.SearchComposeNamespaces(ctx, f) +} + +// LookupComposeNamespaceBySlug searches for namespace by slug (case-insensitive) +func LookupComposeNamespaceBySlug(ctx context.Context, s ComposeNamespaces, slug string) (*types.Namespace, error) { + return s.LookupComposeNamespaceBySlug(ctx, slug) +} + +// LookupComposeNamespaceByID searches for compose namespace by ID +// +// It returns compose namespace even if deleted +func LookupComposeNamespaceByID(ctx context.Context, s ComposeNamespaces, id uint64) (*types.Namespace, error) { + return s.LookupComposeNamespaceByID(ctx, id) +} + +// CreateComposeNamespace creates one or more ComposeNamespaces in store +func CreateComposeNamespace(ctx context.Context, s ComposeNamespaces, rr ...*types.Namespace) error { + return s.CreateComposeNamespace(ctx, rr...) +} + +// UpdateComposeNamespace updates one or more (existing) ComposeNamespaces in store +func UpdateComposeNamespace(ctx context.Context, s ComposeNamespaces, rr ...*types.Namespace) error { + return s.UpdateComposeNamespace(ctx, rr...) +} + +// PartialComposeNamespaceUpdate updates one or more existing ComposeNamespaces in store +func PartialComposeNamespaceUpdate(ctx context.Context, s ComposeNamespaces, onlyColumns []string, rr ...*types.Namespace) error { + return s.PartialComposeNamespaceUpdate(ctx, onlyColumns, rr...) +} + +// UpsertComposeNamespace creates new or updates existing one or more ComposeNamespaces in store +func UpsertComposeNamespace(ctx context.Context, s ComposeNamespaces, rr ...*types.Namespace) error { + return s.UpsertComposeNamespace(ctx, rr...) +} + +// DeleteComposeNamespace Deletes one or more ComposeNamespaces from store +func DeleteComposeNamespace(ctx context.Context, s ComposeNamespaces, rr ...*types.Namespace) error { + return s.DeleteComposeNamespace(ctx, rr...) +} + +// DeleteComposeNamespaceByID Deletes ComposeNamespace from store +func DeleteComposeNamespaceByID(ctx context.Context, s ComposeNamespaces, ID uint64) error { + return s.DeleteComposeNamespaceByID(ctx, ID) +} + +// TruncateComposeNamespaces Deletes all ComposeNamespaces from store +func TruncateComposeNamespaces(ctx context.Context, s ComposeNamespaces) error { + return s.TruncateComposeNamespaces(ctx) +} diff --git a/store/compose_namespaces.yaml b/store/compose_namespaces.yaml index 2d31eb0cf..5e79b46f7 100644 --- a/store/compose_namespaces.yaml +++ b/store/compose_namespaces.yaml @@ -1,9 +1,6 @@ import: - github.com/cortezaproject/corteza-server/compose/types -interface: - - compose/service - types: type: types.Namespace @@ -19,6 +16,7 @@ fields: lookups: - fields: [ Slug ] + uniqueConstraintCheck: true description: |- searches for namespace by slug (case-insensitive) diff --git a/store/compose_pages.gen.go b/store/compose_pages.gen.go new file mode 100644 index 000000000..9c8c58c71 --- /dev/null +++ b/store/compose_pages.gen.go @@ -0,0 +1,104 @@ +package store + +// This file is auto-generated. +// +// Template: pkg/codegen/assets/store_base.gen.go.tpl +// Definitions: store/compose_pages.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 ( + ComposePages interface { + SearchComposePages(ctx context.Context, f types.PageFilter) (types.PageSet, types.PageFilter, error) + LookupComposePageByNamespaceIDHandle(ctx context.Context, namespace_id uint64, handle string) (*types.Page, error) + LookupComposePageByNamespaceIDModuleID(ctx context.Context, namespace_id uint64, module_id uint64) (*types.Page, error) + LookupComposePageByID(ctx context.Context, id uint64) (*types.Page, error) + + CreateComposePage(ctx context.Context, rr ...*types.Page) error + + UpdateComposePage(ctx context.Context, rr ...*types.Page) error + PartialComposePageUpdate(ctx context.Context, onlyColumns []string, rr ...*types.Page) error + + UpsertComposePage(ctx context.Context, rr ...*types.Page) error + + DeleteComposePage(ctx context.Context, rr ...*types.Page) error + DeleteComposePageByID(ctx context.Context, ID uint64) error + + TruncateComposePages(ctx context.Context) error + + // Additional custom functions + + // ReorderComposePages (custom function) + ReorderComposePages(ctx context.Context, _namespaceID uint64, _parentID uint64, _pageIDs []uint64) error + } +) + +var _ *types.Page +var _ context.Context + +// SearchComposePages returns all matching ComposePages from store +func SearchComposePages(ctx context.Context, s ComposePages, f types.PageFilter) (types.PageSet, types.PageFilter, error) { + return s.SearchComposePages(ctx, f) +} + +// LookupComposePageByNamespaceIDHandle searches for page by handle (case-insensitive) +func LookupComposePageByNamespaceIDHandle(ctx context.Context, s ComposePages, namespace_id uint64, handle string) (*types.Page, error) { + return s.LookupComposePageByNamespaceIDHandle(ctx, namespace_id, handle) +} + +// LookupComposePageByNamespaceIDModuleID searches for page by moduleID +func LookupComposePageByNamespaceIDModuleID(ctx context.Context, s ComposePages, namespace_id uint64, module_id uint64) (*types.Page, error) { + return s.LookupComposePageByNamespaceIDModuleID(ctx, namespace_id, module_id) +} + +// LookupComposePageByID searches for compose page by ID +// +// It returns compose page even if deleted +func LookupComposePageByID(ctx context.Context, s ComposePages, id uint64) (*types.Page, error) { + return s.LookupComposePageByID(ctx, id) +} + +// CreateComposePage creates one or more ComposePages in store +func CreateComposePage(ctx context.Context, s ComposePages, rr ...*types.Page) error { + return s.CreateComposePage(ctx, rr...) +} + +// UpdateComposePage updates one or more (existing) ComposePages in store +func UpdateComposePage(ctx context.Context, s ComposePages, rr ...*types.Page) error { + return s.UpdateComposePage(ctx, rr...) +} + +// PartialComposePageUpdate updates one or more existing ComposePages in store +func PartialComposePageUpdate(ctx context.Context, s ComposePages, onlyColumns []string, rr ...*types.Page) error { + return s.PartialComposePageUpdate(ctx, onlyColumns, rr...) +} + +// UpsertComposePage creates new or updates existing one or more ComposePages in store +func UpsertComposePage(ctx context.Context, s ComposePages, rr ...*types.Page) error { + return s.UpsertComposePage(ctx, rr...) +} + +// DeleteComposePage Deletes one or more ComposePages from store +func DeleteComposePage(ctx context.Context, s ComposePages, rr ...*types.Page) error { + return s.DeleteComposePage(ctx, rr...) +} + +// DeleteComposePageByID Deletes ComposePage from store +func DeleteComposePageByID(ctx context.Context, s ComposePages, ID uint64) error { + return s.DeleteComposePageByID(ctx, ID) +} + +// TruncateComposePages Deletes all ComposePages from store +func TruncateComposePages(ctx context.Context, s ComposePages) error { + return s.TruncateComposePages(ctx) +} + +func ReorderComposePages(ctx context.Context, s ComposePages, _namespaceID uint64, _parentID uint64, _pageIDs []uint64) error { + return s.ReorderComposePages(ctx, _namespaceID, _parentID, _pageIDs) +} diff --git a/store/compose_pages.yaml b/store/compose_pages.yaml index 5014e6ea5..87cc2c2ef 100644 --- a/store/compose_pages.yaml +++ b/store/compose_pages.yaml @@ -1,9 +1,6 @@ import: - github.com/cortezaproject/corteza-server/compose/types -interface: - - compose/service - types: type: types.Page @@ -23,9 +20,13 @@ fields: - { field: DeletedAt, sortable: true } lookups: - - fields: [ Handle ] + - fields: [ NamespaceID, Handle ] description: |- - searches for page chart by handle (case-insensitive) + searches for page by handle (case-insensitive) + + - fields: [ NamespaceID, ModuleID ] + description: |- + searches for page by moduleID - fields: [ ID ] description: |- @@ -33,6 +34,14 @@ lookups: It returns compose page even if deleted +functions: + - name: ReorderComposePages + arguments: + - { name: namespaceID, type: "uint64" } + - { name: parentID, type: "uint64" } + - { name: pageIDs, type: "[]uint64" } + return: [ "error" ] + rdbms: alias: cpg table: compose_page diff --git a/store/compose_record_values.gen.go b/store/compose_record_values.gen.go new file mode 100644 index 000000000..623336c11 --- /dev/null +++ b/store/compose_record_values.gen.go @@ -0,0 +1,31 @@ +package store + +// This file is auto-generated. +// +// Template: pkg/codegen/assets/store_base.gen.go.tpl +// Definitions: store/compose_record_values.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 ( + ComposeRecordValues interface { + + // Additional custom functions + + // ComposeRecordValueRefLookup (custom function) + ComposeRecordValueRefLookup(ctx context.Context, _mod *types.Module, _field string, _ref uint64) (uint64, error) + } +) + +var _ *types.RecordValue +var _ context.Context + +func ComposeRecordValueRefLookup(ctx context.Context, s ComposeRecordValues, _mod *types.Module, _field string, _ref uint64) (uint64, error) { + return s.ComposeRecordValueRefLookup(ctx, _mod, _field, _ref) +} diff --git a/store/compose_record_values.yaml b/store/compose_record_values.yaml new file mode 100644 index 000000000..fbdb85f68 --- /dev/null +++ b/store/compose_record_values.yaml @@ -0,0 +1,54 @@ +import: + - github.com/cortezaproject/corteza-server/compose/types + +publish: false + +types: + type: types.RecordValue + +fields: + - { field: RecordID, isPrimaryKey: true, column: record_id } + - { field: Name, isPrimaryKey: true } + - { field: Place, type: uint, isPrimaryKey: true } + - { field: Value } + - { field: Ref, type: uint64 } + - { field: DeletedAt } + +functions: + - name: ComposeRecordValueRefLookup + arguments: + - { name: mod, type: "*types.Module" } + - { name: field, type: string } + - { name: ref, type: uint64 } + return: [ uint64, error ] + + +arguments: + - name: mod + type: "*types.Module" + +rdbms: + alias: crv + table: compose_record_value + customFilterConverter: true + +search: + enablePaging: false + enableSorting: false + enableFilterCheckFunction: false + export: false + +create: + export: false + +update: + export: false + +upsert: + export: false + +Delete: + export: false + +truncate: + export: false diff --git a/store/compose_records.gen.go b/store/compose_records.gen.go new file mode 100644 index 000000000..23051b941 --- /dev/null +++ b/store/compose_records.gen.go @@ -0,0 +1,82 @@ +package store + +// This file is auto-generated. +// +// Template: pkg/codegen/assets/store_base.gen.go.tpl +// Definitions: store/compose_records.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 ( + ComposeRecords interface { + SearchComposeRecords(ctx context.Context, _mod *types.Module, f types.RecordFilter) (types.RecordSet, types.RecordFilter, error) + LookupComposeRecordByID(ctx context.Context, _mod *types.Module, id uint64) (*types.Record, error) + + CreateComposeRecord(ctx context.Context, _mod *types.Module, rr ...*types.Record) error + + UpdateComposeRecord(ctx context.Context, _mod *types.Module, rr ...*types.Record) error + PartialComposeRecordUpdate(ctx context.Context, _mod *types.Module, onlyColumns []string, rr ...*types.Record) error + + UpsertComposeRecord(ctx context.Context, _mod *types.Module, rr ...*types.Record) error + + DeleteComposeRecord(ctx context.Context, _mod *types.Module, rr ...*types.Record) error + DeleteComposeRecordByID(ctx context.Context, _mod *types.Module, ID uint64) error + + TruncateComposeRecords(ctx context.Context, _mod *types.Module) error + } +) + +var _ *types.Record +var _ context.Context + +// SearchComposeRecords returns all matching ComposeRecords from store +func SearchComposeRecords(ctx context.Context, s ComposeRecords, _mod *types.Module, f types.RecordFilter) (types.RecordSet, types.RecordFilter, error) { + return s.SearchComposeRecords(ctx, _mod, f) +} + +// LookupComposeRecordByID searches for compose record by ID +// It returns compose record even if deleted +func LookupComposeRecordByID(ctx context.Context, s ComposeRecords, _mod *types.Module, id uint64) (*types.Record, error) { + return s.LookupComposeRecordByID(ctx, _mod, id) +} + +// CreateComposeRecord creates one or more ComposeRecords in store +func CreateComposeRecord(ctx context.Context, s ComposeRecords, _mod *types.Module, rr ...*types.Record) error { + return s.CreateComposeRecord(ctx, _mod, rr...) +} + +// UpdateComposeRecord updates one or more (existing) ComposeRecords in store +func UpdateComposeRecord(ctx context.Context, s ComposeRecords, _mod *types.Module, rr ...*types.Record) error { + return s.UpdateComposeRecord(ctx, _mod, rr...) +} + +// PartialComposeRecordUpdate updates one or more existing ComposeRecords in store +func PartialComposeRecordUpdate(ctx context.Context, s ComposeRecords, _mod *types.Module, onlyColumns []string, rr ...*types.Record) error { + return s.PartialComposeRecordUpdate(ctx, _mod, onlyColumns, rr...) +} + +// UpsertComposeRecord creates new or updates existing one or more ComposeRecords in store +func UpsertComposeRecord(ctx context.Context, s ComposeRecords, _mod *types.Module, rr ...*types.Record) error { + return s.UpsertComposeRecord(ctx, _mod, rr...) +} + +// DeleteComposeRecord Deletes one or more ComposeRecords from store +func DeleteComposeRecord(ctx context.Context, s ComposeRecords, _mod *types.Module, rr ...*types.Record) error { + return s.DeleteComposeRecord(ctx, _mod, rr...) +} + +// DeleteComposeRecordByID Deletes ComposeRecord from store +func DeleteComposeRecordByID(ctx context.Context, s ComposeRecords, _mod *types.Module, ID uint64) error { + return s.DeleteComposeRecordByID(ctx, _mod, ID) +} + +// TruncateComposeRecords Deletes all ComposeRecords from store +func TruncateComposeRecords(ctx context.Context, s ComposeRecords, _mod *types.Module) error { + return s.TruncateComposeRecords(ctx, _mod) +} diff --git a/store/compose_records.yaml b/store/compose_records.yaml new file mode 100644 index 000000000..5111a4d9a --- /dev/null +++ b/store/compose_records.yaml @@ -0,0 +1,52 @@ +import: + - github.com/cortezaproject/corteza-server/compose/types + +types: + type: types.Record + +fields: + - { field: ID } + - { field: ModuleID, column: module_id } + - { field: NamespaceID } + - { field: OwnedBy } + - { field: CreatedBy } + - { field: UpdatedBy } + - { field: DeletedBy } + - { field: CreatedAt, sortable: true } + - { field: UpdatedAt, sortable: true } + - { field: DeletedAt, sortable: true } + +lookups: + - fields: [ ID ] + export: false + description: |- + searches for compose record by ID + It returns compose record even if deleted + +arguments: + - name: mod + type: "*types.Module" + +rdbms: + alias: crd + table: compose_record + customFilterConverter: true + +search: + export: false + +create: + export: false + +update: + export: false + +upsert: + export: false + +Delete: + export: false + +truncate: + export: false + diff --git a/store/credentials.gen.go b/store/credentials.gen.go new file mode 100644 index 000000000..56ea7571b --- /dev/null +++ b/store/credentials.gen.go @@ -0,0 +1,83 @@ +package store + +// This file is auto-generated. +// +// Template: pkg/codegen/assets/store_base.gen.go.tpl +// Definitions: store/credentials.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/system/types" +) + +type ( + Credentials interface { + SearchCredentials(ctx context.Context, f types.CredentialsFilter) (types.CredentialsSet, types.CredentialsFilter, error) + LookupCredentialsByID(ctx context.Context, id uint64) (*types.Credentials, error) + + CreateCredentials(ctx context.Context, rr ...*types.Credentials) error + + UpdateCredentials(ctx context.Context, rr ...*types.Credentials) error + PartialCredentialsUpdate(ctx context.Context, onlyColumns []string, rr ...*types.Credentials) error + + UpsertCredentials(ctx context.Context, rr ...*types.Credentials) error + + DeleteCredentials(ctx context.Context, rr ...*types.Credentials) error + DeleteCredentialsByID(ctx context.Context, ID uint64) error + + TruncateCredentials(ctx context.Context) error + } +) + +var _ *types.Credentials +var _ context.Context + +// SearchCredentials returns all matching Credentials from store +func SearchCredentials(ctx context.Context, s Credentials, f types.CredentialsFilter) (types.CredentialsSet, types.CredentialsFilter, error) { + return s.SearchCredentials(ctx, f) +} + +// LookupCredentialsByID searches for credentials by ID +// +// It returns credentials even if deleted +func LookupCredentialsByID(ctx context.Context, s Credentials, id uint64) (*types.Credentials, error) { + return s.LookupCredentialsByID(ctx, id) +} + +// CreateCredentials creates one or more Credentials in store +func CreateCredentials(ctx context.Context, s Credentials, rr ...*types.Credentials) error { + return s.CreateCredentials(ctx, rr...) +} + +// UpdateCredentials updates one or more (existing) Credentials in store +func UpdateCredentials(ctx context.Context, s Credentials, rr ...*types.Credentials) error { + return s.UpdateCredentials(ctx, rr...) +} + +// PartialCredentialsUpdate updates one or more existing Credentials in store +func PartialCredentialsUpdate(ctx context.Context, s Credentials, onlyColumns []string, rr ...*types.Credentials) error { + return s.PartialCredentialsUpdate(ctx, onlyColumns, rr...) +} + +// UpsertCredentials creates new or updates existing one or more Credentials in store +func UpsertCredentials(ctx context.Context, s Credentials, rr ...*types.Credentials) error { + return s.UpsertCredentials(ctx, rr...) +} + +// DeleteCredentials Deletes one or more Credentials from store +func DeleteCredentials(ctx context.Context, s Credentials, rr ...*types.Credentials) error { + return s.DeleteCredentials(ctx, rr...) +} + +// DeleteCredentialsByID Deletes Credentials from store +func DeleteCredentialsByID(ctx context.Context, s Credentials, ID uint64) error { + return s.DeleteCredentialsByID(ctx, ID) +} + +// TruncateCredentials Deletes all Credentials from store +func TruncateCredentials(ctx context.Context, s Credentials) error { + return s.TruncateCredentials(ctx) +} diff --git a/store/credentials.yaml b/store/credentials.yaml index 018872b0c..4518d3fde 100644 --- a/store/credentials.yaml +++ b/store/credentials.yaml @@ -1,9 +1,6 @@ import: - github.com/cortezaproject/corteza-server/system/types -interface: - - system/service - types: singular: Credentials plural: Credentials @@ -29,8 +26,9 @@ lookups: It returns credentials even if deleted search: - disablePaging: true - disableSorting: true + enablePaging: false + enableSorting: false + enableFilterCheckFunction: false rdbms: alias: crd diff --git a/store/interfaces.gen.go b/store/interfaces.gen.go new file mode 100644 index 000000000..58a707b81 --- /dev/null +++ b/store/interfaces.gen.go @@ -0,0 +1,60 @@ +package store + +// This file is auto-generated. +// +// Template: pkg/codegen/assets/store_interfaces_joined.gen.go.tpl +// Definitions: +// - store/actionlog.yaml +// - store/applications.yaml +// - store/attachments.yaml +// - store/compose_charts.yaml +// - store/compose_module_fields.yaml +// - store/compose_modules.yaml +// - store/compose_namespaces.yaml +// - store/compose_pages.yaml +// - store/compose_record_values.yaml +// - store/compose_records.yaml +// - store/credentials.yaml +// - store/rbac_rules.yaml +// - store/reminders.yaml +// - store/role_members.yaml +// - store/roles.yaml +// - store/settings.yaml +// - store/users.yaml +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// + +import ( + "context" +) + +type ( + Transactionable interface { + Tx(context.Context, func(context.Context, Storable) error) error + } + + // Sortable interface combines interfaces of all supported store interfaces + Storable interface { + Transactionable + + Actionlogs + Applications + Attachments + ComposeCharts + ComposeModuleFields + ComposeModules + ComposeNamespaces + ComposePages + ComposeRecordValues + ComposeRecords + Credentials + RbacRules + Reminders + RoleMembers + Roles + Settings + Users + } +) diff --git a/store/mysql/builder.go b/store/mysql/builder.go new file mode 100644 index 000000000..6c1943b4d --- /dev/null +++ b/store/mysql/builder.go @@ -0,0 +1,49 @@ +package mysql + +import ( + "github.com/Masterminds/squirrel" + "github.com/cortezaproject/corteza-server/store" + "github.com/cortezaproject/corteza-server/store/rdbms" +) + +// UpsertBuilder combines insert and update builder into upsert (INSERT ... ON CONFLICT UPDATE ...) +// +// It accepts name of the table we're inserting to, payload with values that should be inserted and list of +// columns that represent unique keys +// +// This is MySQL eddition that differs a bit from the standard SQL +// https://dev.mysql.com/doc/refman/5.7/en/insert-on-duplicate.html +func UpsertBuilder(cfg *rdbms.Config, table string, payload store.Payload, columns ...string) (squirrel.InsertBuilder, error) { + var ( + updatePayload = store.Payload{} + + // where to cutoff the update query + updateCutoff = len("UPDATE " + table + " SET ") + ) + + for k, v := range payload { + updatePayload[k] = v + } + + for _, c := range columns { + if _, has := updatePayload[c]; has { + delete(updatePayload, c) + } + } + + sql, args, err := squirrel. + Update(table). + PlaceholderFormat(cfg.PlaceholderFormat). + SetMap(updatePayload). + ToSql() + + if err != nil { + return squirrel.InsertBuilder{}, err + } + + return squirrel. + Insert(table). + PlaceholderFormat(cfg.PlaceholderFormat). + SetMap(payload). + Suffix("ON DUPLICATE KEY UPDATE "+sql[updateCutoff:], args...), nil +} diff --git a/store/mysql/builder_test.go b/store/mysql/builder_test.go new file mode 100644 index 000000000..6f019034c --- /dev/null +++ b/store/mysql/builder_test.go @@ -0,0 +1,21 @@ +package mysql + +import ( + "github.com/cortezaproject/corteza-server/store" + "github.com/stretchr/testify/require" + "testing" +) + +func TestBuilder(t *testing.T) { + var ( + req = require.New(t) + ) + + upsert, err := UpsertBuilder("tbl", store.Payload{"c1": "v1", "c2": "v2"}, "c1") + req.NoError(err) + sql, args, err := upsert.ToSql() + req.NoError(err) + req.Contains(sql, "ON DUPLICATE KEY UPDATE SET") + req.Contains(sql, "INSERT INTO tbl") + req.Equal([]interface{}{"v1", "v2", "v2"}, args) +} diff --git a/store/mysql/mysql.go b/store/mysql/mysql.go index 4ecffb3f5..bd0d9631e 100644 --- a/store/mysql/mysql.go +++ b/store/mysql/mysql.go @@ -28,6 +28,7 @@ func New(ctx context.Context, dsn string) (s *Store, err error) { cfg.PlaceholderFormat = squirrel.Question cfg.TxRetryErrHandler = txRetryErrHandler cfg.ErrorHandler = errorHandler + cfg.UpsertBuilder = UpsertBuilder s = new(Store) if s.Store, err = rdbms.New(ctx, cfg); err != nil { @@ -62,7 +63,8 @@ func ProcDataSourceName(in string) (*rdbms.Config, error) { var ( endOfSchema = strings.Index(in, schemeDel) - c = &rdbms.Config{} + + c = &rdbms.Config{} ) if endOfSchema > 0 && (in[:endOfSchema] == validScheme || strings.HasPrefix(in[:endOfSchema], validScheme+"+")) { diff --git a/store/mysql/upgrade.go b/store/mysql/upgrade.go index ed8831f1e..5b73ab02b 100644 --- a/store/mysql/upgrade.go +++ b/store/mysql/upgrade.go @@ -74,7 +74,7 @@ func (u upgrader) Before(ctx context.Context) error { return err } - u.log.Debug(fmt.Sprintf("%s table removed", migrations)) + u.log.Debug(fmt.Sprintf("%s table Deleted", migrations)) return nil }() diff --git a/store/payload.go b/store/payload.go index 190c9cfd3..4e78b8e6e 100644 --- a/store/payload.go +++ b/store/payload.go @@ -1,6 +1,8 @@ package store -import "github.com/cortezaproject/corteza-server/pkg/slice" +import ( + "github.com/cortezaproject/corteza-server/pkg/slice" +) type ( // Payload servers as a generic interface between incoming structs scheduled diff --git a/store/rbac_rules.gen.go b/store/rbac_rules.gen.go new file mode 100644 index 000000000..90b14ce01 --- /dev/null +++ b/store/rbac_rules.gen.go @@ -0,0 +1,75 @@ +package store + +// This file is auto-generated. +// +// Template: pkg/codegen/assets/store_base.gen.go.tpl +// Definitions: store/rbac_rules.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/pkg/permissions" +) + +type ( + RbacRules interface { + SearchRbacRules(ctx context.Context, f permissions.RuleFilter) (permissions.RuleSet, permissions.RuleFilter, error) + + CreateRbacRule(ctx context.Context, rr ...*permissions.Rule) error + + UpdateRbacRule(ctx context.Context, rr ...*permissions.Rule) error + PartialRbacRuleUpdate(ctx context.Context, onlyColumns []string, rr ...*permissions.Rule) error + + UpsertRbacRule(ctx context.Context, rr ...*permissions.Rule) error + + DeleteRbacRule(ctx context.Context, rr ...*permissions.Rule) error + DeleteRbacRuleByRoleIDResourceOperation(ctx context.Context, roleID uint64, resource string, operation string) error + + TruncateRbacRules(ctx context.Context) error + } +) + +var _ *permissions.Rule +var _ context.Context + +// SearchRbacRules returns all matching RbacRules from store +func SearchRbacRules(ctx context.Context, s RbacRules, f permissions.RuleFilter) (permissions.RuleSet, permissions.RuleFilter, error) { + return s.SearchRbacRules(ctx, f) +} + +// CreateRbacRule creates one or more RbacRules in store +func CreateRbacRule(ctx context.Context, s RbacRules, rr ...*permissions.Rule) error { + return s.CreateRbacRule(ctx, rr...) +} + +// UpdateRbacRule updates one or more (existing) RbacRules in store +func UpdateRbacRule(ctx context.Context, s RbacRules, rr ...*permissions.Rule) error { + return s.UpdateRbacRule(ctx, rr...) +} + +// PartialRbacRuleUpdate updates one or more existing RbacRules in store +func PartialRbacRuleUpdate(ctx context.Context, s RbacRules, onlyColumns []string, rr ...*permissions.Rule) error { + return s.PartialRbacRuleUpdate(ctx, onlyColumns, rr...) +} + +// UpsertRbacRule creates new or updates existing one or more RbacRules in store +func UpsertRbacRule(ctx context.Context, s RbacRules, rr ...*permissions.Rule) error { + return s.UpsertRbacRule(ctx, rr...) +} + +// DeleteRbacRule Deletes one or more RbacRules from store +func DeleteRbacRule(ctx context.Context, s RbacRules, rr ...*permissions.Rule) error { + return s.DeleteRbacRule(ctx, rr...) +} + +// DeleteRbacRuleByRoleIDResourceOperation Deletes RbacRule from store +func DeleteRbacRuleByRoleIDResourceOperation(ctx context.Context, s RbacRules, roleID uint64, resource string, operation string) error { + return s.DeleteRbacRuleByRoleIDResourceOperation(ctx, roleID, resource, operation) +} + +// TruncateRbacRules Deletes all RbacRules from store +func TruncateRbacRules(ctx context.Context, s RbacRules) error { + return s.TruncateRbacRules(ctx) +} diff --git a/store/rbac_rules.yaml b/store/rbac_rules.yaml index 2fb572887..61cf518e4 100644 --- a/store/rbac_rules.yaml +++ b/store/rbac_rules.yaml @@ -1,14 +1,6 @@ import: - github.com/cortezaproject/corteza-server/pkg/permissions -interface: - - system/service - - compose/service - - messaging/service - - tests/compose - - tests/messaging - - tests/system - types: type: permissions.Rule @@ -23,5 +15,10 @@ rdbms: table: rbac_rules search: - disablePaging: true - disableSorting: true + enablePaging: false + enableSorting: false + enableFilterCheckFunction: false + +upsert: + enable: true + diff --git a/store/rdbms/actionlog.gen.go b/store/rdbms/actionlog.gen.go index f331bc5c4..f7cf7abaa 100644 --- a/store/rdbms/actionlog.gen.go +++ b/store/rdbms/actionlog.gen.go @@ -11,13 +11,21 @@ package rdbms import ( "context" "database/sql" + "errors" "fmt" "github.com/Masterminds/squirrel" "github.com/cortezaproject/corteza-server/pkg/actionlog" + "github.com/cortezaproject/corteza-server/pkg/filter" "github.com/cortezaproject/corteza-server/store" "strings" ) +var _ = errors.Is + +const ( + TriggerBeforeActionlogCreate triggerKey = "actionlogBeforeCreate" +) + // SearchActionlogs returns all matching rows // // This function calls convertActionlogFilter with the given @@ -39,11 +47,11 @@ func (s Store) SearchActionlogs(ctx context.Context, f actionlog.Filter) (action reverseCursor := f.PageCursor != nil && f.PageCursor.Reverse // Sorting is disabled in definition yaml file - // {search: {disableSorting:true}} + // {search: {enablePaging:false}} // // We still need to sort the results by primary key for paging purposes - sort := store.SortExprSet{ - &store.SortExpr{Column: "id", Descending: true}, + sort := filter.SortExprSet{ + &filter.SortExpr{Column: "id", Descending: true}, } if scap == 0 { @@ -52,7 +60,7 @@ func (s Store) SearchActionlogs(ctx context.Context, f actionlog.Filter) (action var ( set = make([]*actionlog.Action, 0, scap) - // fetches rows and scans them into Actionlog.Action resource this is then passed to Check function on filter + // fetches rows and scans them into actionlog.Action resource this is then passed to Check function on filter // to help determine if fetched resource fits or not // // Note that limit is passed explicitly and is not necessarily equal to filter's limit. We want @@ -61,7 +69,7 @@ func (s Store) SearchActionlogs(ctx context.Context, f actionlog.Filter) (action // The value for cursor is used and set directly from/to the filter! // // It returns total number of fetched pages and modifies PageCursor value for paging - fetchPage = func(cursor *store.PagingCursor, limit uint) (fetched uint, err error) { + fetchPage = func(cursor *filter.PagingCursor, limit uint) (fetched uint, err error) { var ( res *actionlog.Action @@ -92,7 +100,12 @@ func (s Store) SearchActionlogs(ctx context.Context, f actionlog.Filter) (action for rows.Next() { fetched++ - if res, err = s.internalActionlogRowScanner(rows, rows.Err()); err != nil { + + if rows.Err() == nil { + res, err = s.internalActionlogRowScanner(rows) + } + + if err != nil { if cerr := rows.Close(); cerr != nil { err = fmt.Errorf("could not close rows (%v) after scan error: %w", cerr, err) } @@ -193,78 +206,60 @@ func (s Store) SearchActionlogs(ctx context.Context, f actionlog.Filter) (action // CreateActionlog creates one or more rows in actionlog table func (s Store) CreateActionlog(ctx context.Context, rr ...*actionlog.Action) (err error) { for _, res := range rr { - err = ExecuteSqlizer(ctx, s.DB(), s.Insert(s.ActionlogTable()).SetMap(s.internalActionlogEncoder(res))) + err = s.checkActionlogConstraints(ctx, res) if err != nil { - return s.config.ErrorHandler(err) + return err + } + + // err = s.actionlogHook(ctx, TriggerBeforeActionlogCreate, res) + // if err != nil { + // return err + // } + + err = s.execCreateActionlogs(ctx, s.internalActionlogEncoder(res)) + if err != nil { + return err } } return } -// UpdateActionlog updates one or more existing rows in actionlog -func (s Store) UpdateActionlog(ctx context.Context, rr ...*actionlog.Action) error { - return s.config.ErrorHandler(s.PartialUpdateActionlog(ctx, nil, rr...)) -} - -// PartialUpdateActionlog updates one or more existing rows in actionlog -// -// It wraps the update into transaction and can perform partial update by providing list of updatable columns -func (s Store) PartialUpdateActionlog(ctx context.Context, onlyColumns []string, rr ...*actionlog.Action) (err error) { - for _, res := range rr { - err = s.ExecUpdateActionlogs( - ctx, - squirrel.Eq{s.preprocessColumn("alg.id", ""): s.preprocessValue(res.ID, "")}, - s.internalActionlogEncoder(res).Skip("id").Only(onlyColumns...)) - if err != nil { - return s.config.ErrorHandler(err) - } - } - - return -} - -// RemoveActionlog removes one or more rows from actionlog table -func (s Store) RemoveActionlog(ctx context.Context, rr ...*actionlog.Action) (err error) { - for _, res := range rr { - err = ExecuteSqlizer(ctx, s.DB(), s.Delete(s.ActionlogTable("alg")).Where(squirrel.Eq{s.preprocessColumn("alg.id", ""): s.preprocessValue(res.ID, "")})) - if err != nil { - return s.config.ErrorHandler(err) - } - } - - return nil -} - -// RemoveActionlogByID removes row from the actionlog table -func (s Store) RemoveActionlogByID(ctx context.Context, ID uint64) error { - return s.config.ErrorHandler(ExecuteSqlizer(ctx, s.DB(), s.Delete(s.ActionlogTable("alg")).Where(squirrel.Eq{s.preprocessColumn("alg.id", ""): s.preprocessValue(ID, "")}))) -} - -// TruncateActionlogs removes all rows from the actionlog table +// TruncateActionlogs Deletes all rows from the actionlog table func (s Store) TruncateActionlogs(ctx context.Context) error { - return s.config.ErrorHandler(Truncate(ctx, s.DB(), s.ActionlogTable())) + return s.config.ErrorHandler(s.Truncate(ctx, s.actionlogTable())) } -// ExecUpdateActionlogs updates all matched (by cnd) rows in actionlog with given data -func (s Store) ExecUpdateActionlogs(ctx context.Context, cnd squirrel.Sqlizer, set store.Payload) error { - return s.config.ErrorHandler(ExecuteSqlizer(ctx, s.DB(), s.Update(s.ActionlogTable("alg")).Where(cnd).SetMap(set))) -} - -// ActionlogLookup prepares Actionlog query and executes it, +// execLookupActionlog prepares Actionlog query and executes it, // returning actionlog.Action (or error) -func (s Store) ActionlogLookup(ctx context.Context, cnd squirrel.Sqlizer) (*actionlog.Action, error) { - return s.internalActionlogRowScanner(s.QueryRow(ctx, s.QueryActionlogs().Where(cnd))) -} +func (s Store) execLookupActionlog(ctx context.Context, cnd squirrel.Sqlizer) (res *actionlog.Action, err error) { + var ( + row rowScanner + ) -func (s Store) internalActionlogRowScanner(row rowScanner, err error) (*actionlog.Action, error) { + row, err = s.QueryRow(ctx, s.actionlogsSelectBuilder().Where(cnd)) if err != nil { - return nil, err + return } - var res = &actionlog.Action{} + res, err = s.internalActionlogRowScanner(row) + if err != nil { + return + } + + return res, nil +} + +// execCreateActionlogs updates all matched (by cnd) rows in actionlog with given data +func (s Store) execCreateActionlogs(ctx context.Context, payload store.Payload) error { + return s.config.ErrorHandler(s.Exec(ctx, s.InsertBuilder(s.actionlogTable()).SetMap(payload))) +} + +func (s Store) internalActionlogRowScanner(row rowScanner) (res *actionlog.Action, err error) { + res = &actionlog.Action{} + if _, has := s.config.RowScanners["actionlog"]; has { - scanner := s.config.RowScanners["actionlog"].(func(rowScanner, *actionlog.Action) error) + scanner := s.config.RowScanners["actionlog"].(func(_ rowScanner, _ *actionlog.Action) error) err = scanner(row, res) } else { err = s.scanActionlogRow(row, res) @@ -282,12 +277,12 @@ func (s Store) internalActionlogRowScanner(row rowScanner, err error) (*actionlo } // QueryActionlogs returns squirrel.SelectBuilder with set table and all columns -func (s Store) QueryActionlogs() squirrel.SelectBuilder { - return s.Select(s.ActionlogTable("alg"), s.ActionlogColumns("alg")...) +func (s Store) actionlogsSelectBuilder() squirrel.SelectBuilder { + return s.SelectBuilder(s.actionlogTable("alg"), s.actionlogColumns("alg")...) } -// ActionlogTable name of the db table -func (Store) ActionlogTable(aa ...string) string { +// actionlogTable name of the db table +func (Store) actionlogTable(aa ...string) string { var alias string if len(aa) > 0 { alias = " AS " + aa[0] @@ -299,7 +294,7 @@ func (Store) ActionlogTable(aa ...string) string { // ActionlogColumns returns all defined table columns // // With optional string arg, all columns are returned aliased -func (Store) ActionlogColumns(aa ...string) []string { +func (Store) actionlogColumns(aa ...string) []string { var alias string if len(aa) > 0 { alias = aa[0] + "." @@ -321,7 +316,7 @@ func (Store) ActionlogColumns(aa ...string) []string { } } -// {false false true true} +// {true true true false false} // internalActionlogEncoder encodes fields from actionlog.Action to store.Payload (map) // @@ -331,9 +326,9 @@ func (s Store) internalActionlogEncoder(res *actionlog.Action) store.Payload { return s.encodeActionlog(res) } -func (s Store) collectActionlogCursorValues(res *actionlog.Action, cc ...string) *store.PagingCursor { +func (s Store) collectActionlogCursorValues(res *actionlog.Action, cc ...string) *filter.PagingCursor { var ( - cursor = &store.PagingCursor{} + cursor = &filter.PagingCursor{} hasUnique bool @@ -357,3 +352,16 @@ func (s Store) collectActionlogCursorValues(res *actionlog.Action, cc ...string) return cursor } + +func (s *Store) checkActionlogConstraints(ctx context.Context, res *actionlog.Action) error { + + return nil +} + +// func (s *Store) actionlogHook(ctx context.Context, key triggerKey, res *actionlog.Action) error { +// if fn, has := s.config.TriggerHandlers[key]; has { +// return fn.(func (ctx context.Context, s *Store, res *actionlog.Action) error)(ctx, s, res) +// } +// +// return nil +// } diff --git a/store/rdbms/actionlog.go b/store/rdbms/actionlog.go index 6eb817a4f..30cc20398 100644 --- a/store/rdbms/actionlog.go +++ b/store/rdbms/actionlog.go @@ -8,7 +8,7 @@ import ( ) func (s Store) convertActionlogFilter(f actionlog.Filter) (query squirrel.SelectBuilder, err error) { - query = s.QueryActionlogs() + query = s.actionlogsSelectBuilder() if f.From != nil { query = query.Where(squirrel.GtOrEq{"ts": f.From}) diff --git a/store/rdbms/applications.gen.go b/store/rdbms/applications.gen.go index 11f7c2d16..31ce90a0d 100644 --- a/store/rdbms/applications.gen.go +++ b/store/rdbms/applications.gen.go @@ -11,13 +11,24 @@ package rdbms import ( "context" "database/sql" + "errors" "fmt" "github.com/Masterminds/squirrel" + "github.com/cortezaproject/corteza-server/pkg/filter" "github.com/cortezaproject/corteza-server/store" "github.com/cortezaproject/corteza-server/system/types" "strings" ) +var _ = errors.Is + +const ( + TriggerBeforeApplicationCreate triggerKey = "applicationBeforeCreate" + TriggerBeforeApplicationUpdate triggerKey = "applicationBeforeUpdate" + TriggerBeforeApplicationUpsert triggerKey = "applicationBeforeUpsert" + TriggerBeforeApplicationDelete triggerKey = "applicationBeforeDelete" +) + // SearchApplications returns all matching rows // // This function calls convertApplicationFilter with the given @@ -38,7 +49,7 @@ func (s Store) SearchApplications(ctx context.Context, f types.ApplicationFilter // This tells us to flip the descending flag on all used sort keys reverseCursor := f.PageCursor != nil && f.PageCursor.Reverse - if err = f.Sort.Validate(s.sortableApplicationColumns()...); err != nil { + if err := f.Sort.Validate(s.sortableApplicationColumns()...); err != nil { return nil, f, fmt.Errorf("could not validate sort: %v", err) } @@ -68,7 +79,7 @@ func (s Store) SearchApplications(ctx context.Context, f types.ApplicationFilter var ( set = make([]*types.Application, 0, scap) - // fetches rows and scans them into Types.Application resource this is then passed to Check function on filter + // fetches rows and scans them into types.Application resource this is then passed to Check function on filter // to help determine if fetched resource fits or not // // Note that limit is passed explicitly and is not necessarily equal to filter's limit. We want @@ -77,7 +88,7 @@ func (s Store) SearchApplications(ctx context.Context, f types.ApplicationFilter // The value for cursor is used and set directly from/to the filter! // // It returns total number of fetched pages and modifies PageCursor value for paging - fetchPage = func(cursor *store.PagingCursor, limit uint) (fetched uint, err error) { + fetchPage = func(cursor *filter.PagingCursor, limit uint) (fetched uint, err error) { var ( res *types.Application @@ -108,7 +119,12 @@ func (s Store) SearchApplications(ctx context.Context, f types.ApplicationFilter for rows.Next() { fetched++ - if res, err = s.internalApplicationRowScanner(rows, rows.Err()); err != nil { + + if rows.Err() == nil { + res, err = s.internalApplicationRowScanner(rows) + } + + if err != nil { if cerr := rows.Close(); cerr != nil { err = fmt.Errorf("could not close rows (%v) after scan error: %w", cerr, err) } @@ -225,17 +241,27 @@ func (s Store) SearchApplications(ctx context.Context, f types.ApplicationFilter // // It returns application even if deleted func (s Store) LookupApplicationByID(ctx context.Context, id uint64) (*types.Application, error) { - return s.ApplicationLookup(ctx, squirrel.Eq{ - "app.id": id, + return s.execLookupApplication(ctx, squirrel.Eq{ + s.preprocessColumn("app.id", ""): s.preprocessValue(id, ""), }) } // CreateApplication creates one or more rows in applications table func (s Store) CreateApplication(ctx context.Context, rr ...*types.Application) (err error) { for _, res := range rr { - err = ExecuteSqlizer(ctx, s.DB(), s.Insert(s.ApplicationTable()).SetMap(s.internalApplicationEncoder(res))) + err = s.checkApplicationConstraints(ctx, res) if err != nil { - return s.config.ErrorHandler(err) + return err + } + + // err = s.applicationHook(ctx, TriggerBeforeApplicationCreate, res) + // if err != nil { + // return err + // } + + err = s.execCreateApplications(ctx, s.internalApplicationEncoder(res)) + if err != nil { + return err } } @@ -244,17 +270,27 @@ func (s Store) CreateApplication(ctx context.Context, rr ...*types.Application) // UpdateApplication updates one or more existing rows in applications func (s Store) UpdateApplication(ctx context.Context, rr ...*types.Application) error { - return s.config.ErrorHandler(s.PartialUpdateApplication(ctx, nil, rr...)) + return s.config.ErrorHandler(s.PartialApplicationUpdate(ctx, nil, rr...)) } -// PartialUpdateApplication updates one or more existing rows in applications -// -// It wraps the update into transaction and can perform partial update by providing list of updatable columns -func (s Store) PartialUpdateApplication(ctx context.Context, onlyColumns []string, rr ...*types.Application) (err error) { +// PartialApplicationUpdate updates one or more existing rows in applications +func (s Store) PartialApplicationUpdate(ctx context.Context, onlyColumns []string, rr ...*types.Application) (err error) { for _, res := range rr { - err = s.ExecUpdateApplications( + err = s.checkApplicationConstraints(ctx, res) + if err != nil { + return err + } + + // err = s.applicationHook(ctx, TriggerBeforeApplicationUpdate, res) + // if err != nil { + // return err + // } + + err = s.execUpdateApplications( ctx, - squirrel.Eq{s.preprocessColumn("app.id", ""): s.preprocessValue(res.ID, "")}, + squirrel.Eq{ + s.preprocessColumn("app.id", ""): s.preprocessValue(res.ID, ""), + }, s.internalApplicationEncoder(res).Skip("id").Only(onlyColumns...)) if err != nil { return s.config.ErrorHandler(err) @@ -264,10 +300,39 @@ func (s Store) PartialUpdateApplication(ctx context.Context, onlyColumns []strin return } -// RemoveApplication removes one or more rows from applications table -func (s Store) RemoveApplication(ctx context.Context, rr ...*types.Application) (err error) { +// UpsertApplication updates one or more existing rows in applications +func (s Store) UpsertApplication(ctx context.Context, rr ...*types.Application) (err error) { for _, res := range rr { - err = ExecuteSqlizer(ctx, s.DB(), s.Delete(s.ApplicationTable("app")).Where(squirrel.Eq{s.preprocessColumn("app.id", ""): s.preprocessValue(res.ID, "")})) + err = s.checkApplicationConstraints(ctx, res) + if err != nil { + return err + } + + // err = s.applicationHook(ctx, TriggerBeforeApplicationUpsert, res) + // if err != nil { + // return err + // } + + err = s.config.ErrorHandler(s.execUpsertApplications(ctx, s.internalApplicationEncoder(res))) + if err != nil { + return err + } + } + + return nil +} + +// DeleteApplication Deletes one or more rows from applications table +func (s Store) DeleteApplication(ctx context.Context, rr ...*types.Application) (err error) { + for _, res := range rr { + // err = s.applicationHook(ctx, TriggerBeforeApplicationDelete, res) + // if err != nil { + // return err + // } + + err = s.execDeleteApplications(ctx, squirrel.Eq{ + s.preprocessColumn("app.id", ""): s.preprocessValue(res.ID, ""), + }) if err != nil { return s.config.ErrorHandler(err) } @@ -276,35 +341,74 @@ func (s Store) RemoveApplication(ctx context.Context, rr ...*types.Application) return nil } -// RemoveApplicationByID removes row from the applications table -func (s Store) RemoveApplicationByID(ctx context.Context, ID uint64) error { - return s.config.ErrorHandler(ExecuteSqlizer(ctx, s.DB(), s.Delete(s.ApplicationTable("app")).Where(squirrel.Eq{s.preprocessColumn("app.id", ""): s.preprocessValue(ID, "")}))) +// DeleteApplicationByID Deletes row from the applications table +func (s Store) DeleteApplicationByID(ctx context.Context, ID uint64) error { + return s.execDeleteApplications(ctx, squirrel.Eq{ + s.preprocessColumn("app.id", ""): s.preprocessValue(ID, ""), + }) } -// TruncateApplications removes all rows from the applications table +// TruncateApplications Deletes all rows from the applications table func (s Store) TruncateApplications(ctx context.Context) error { - return s.config.ErrorHandler(Truncate(ctx, s.DB(), s.ApplicationTable())) + return s.config.ErrorHandler(s.Truncate(ctx, s.applicationTable())) } -// ExecUpdateApplications updates all matched (by cnd) rows in applications with given data -func (s Store) ExecUpdateApplications(ctx context.Context, cnd squirrel.Sqlizer, set store.Payload) error { - return s.config.ErrorHandler(ExecuteSqlizer(ctx, s.DB(), s.Update(s.ApplicationTable("app")).Where(cnd).SetMap(set))) -} - -// ApplicationLookup prepares Application query and executes it, +// execLookupApplication prepares Application query and executes it, // returning types.Application (or error) -func (s Store) ApplicationLookup(ctx context.Context, cnd squirrel.Sqlizer) (*types.Application, error) { - return s.internalApplicationRowScanner(s.QueryRow(ctx, s.QueryApplications().Where(cnd))) -} +func (s Store) execLookupApplication(ctx context.Context, cnd squirrel.Sqlizer) (res *types.Application, err error) { + var ( + row rowScanner + ) -func (s Store) internalApplicationRowScanner(row rowScanner, err error) (*types.Application, error) { + row, err = s.QueryRow(ctx, s.applicationsSelectBuilder().Where(cnd)) if err != nil { - return nil, err + return } - var res = &types.Application{} + res, err = s.internalApplicationRowScanner(row) + if err != nil { + return + } + + return res, nil +} + +// execCreateApplications updates all matched (by cnd) rows in applications with given data +func (s Store) execCreateApplications(ctx context.Context, payload store.Payload) error { + return s.config.ErrorHandler(s.Exec(ctx, s.InsertBuilder(s.applicationTable()).SetMap(payload))) +} + +// execUpdateApplications updates all matched (by cnd) rows in applications with given data +func (s Store) execUpdateApplications(ctx context.Context, cnd squirrel.Sqlizer, set store.Payload) error { + return s.config.ErrorHandler(s.Exec(ctx, s.UpdateBuilder(s.applicationTable("app")).Where(cnd).SetMap(set))) +} + +// execUpsertApplications inserts new or updates matching (by-primary-key) rows in applications with given data +func (s Store) execUpsertApplications(ctx context.Context, set store.Payload) error { + upsert, err := s.config.UpsertBuilder( + s.config, + s.applicationTable(), + set, + "id", + ) + + if err != nil { + return err + } + + return s.config.ErrorHandler(s.Exec(ctx, upsert)) +} + +// execDeleteApplications Deletes all matched (by cnd) rows in applications with given data +func (s Store) execDeleteApplications(ctx context.Context, cnd squirrel.Sqlizer) error { + return s.config.ErrorHandler(s.Exec(ctx, s.DeleteBuilder(s.applicationTable("app")).Where(cnd))) +} + +func (s Store) internalApplicationRowScanner(row rowScanner) (res *types.Application, err error) { + res = &types.Application{} + if _, has := s.config.RowScanners["application"]; has { - scanner := s.config.RowScanners["application"].(func(rowScanner, *types.Application) error) + scanner := s.config.RowScanners["application"].(func(_ rowScanner, _ *types.Application) error) err = scanner(row, res) } else { err = row.Scan( @@ -331,12 +435,12 @@ func (s Store) internalApplicationRowScanner(row rowScanner, err error) (*types. } // QueryApplications returns squirrel.SelectBuilder with set table and all columns -func (s Store) QueryApplications() squirrel.SelectBuilder { - return s.Select(s.ApplicationTable("app"), s.ApplicationColumns("app")...) +func (s Store) applicationsSelectBuilder() squirrel.SelectBuilder { + return s.SelectBuilder(s.applicationTable("app"), s.applicationColumns("app")...) } -// ApplicationTable name of the db table -func (Store) ApplicationTable(aa ...string) string { +// applicationTable name of the db table +func (Store) applicationTable(aa ...string) string { var alias string if len(aa) > 0 { alias = " AS " + aa[0] @@ -348,7 +452,7 @@ func (Store) ApplicationTable(aa ...string) string { // ApplicationColumns returns all defined table columns // // With optional string arg, all columns are returned aliased -func (Store) ApplicationColumns(aa ...string) []string { +func (Store) applicationColumns(aa ...string) []string { var alias string if len(aa) > 0 { alias = aa[0] + "." @@ -366,7 +470,7 @@ func (Store) ApplicationColumns(aa ...string) []string { } } -// {false false false false} +// {true true true true true} // sortableApplicationColumns returns all Application columns flagged as sortable // @@ -398,9 +502,9 @@ func (s Store) internalApplicationEncoder(res *types.Application) store.Payload } } -func (s Store) collectApplicationCursorValues(res *types.Application, cc ...string) *store.PagingCursor { +func (s Store) collectApplicationCursorValues(res *types.Application, cc ...string) *filter.PagingCursor { var ( - cursor = &store.PagingCursor{} + cursor = &filter.PagingCursor{} hasUnique bool @@ -432,3 +536,16 @@ func (s Store) collectApplicationCursorValues(res *types.Application, cc ...stri return cursor } + +func (s *Store) checkApplicationConstraints(ctx context.Context, res *types.Application) error { + + return nil +} + +// func (s *Store) applicationHook(ctx context.Context, key triggerKey, res *types.Application) error { +// if fn, has := s.config.TriggerHandlers[key]; has { +// return fn.(func (ctx context.Context, s *Store, res *types.Application) error)(ctx, s, res) +// } +// +// return nil +// } diff --git a/store/rdbms/applications.go b/store/rdbms/applications.go index d868b892c..e6a89da40 100644 --- a/store/rdbms/applications.go +++ b/store/rdbms/applications.go @@ -8,7 +8,7 @@ import ( ) func (s Store) convertApplicationFilter(f types.ApplicationFilter) (query squirrel.SelectBuilder, err error) { - query = s.QueryApplications() + query = s.applicationsSelectBuilder() query = rh.FilterNullByState(query, "app.deleted_at", f.Deleted) @@ -26,28 +26,28 @@ func (s Store) convertApplicationFilter(f types.ApplicationFilter) (query squirr return } -func (s Store) ApplicationMetrics(ctx context.Context) (rval *types.ApplicationMetrics, err error) { +func (s Store) ApplicationMetrics(ctx context.Context) (*types.ApplicationMetrics, error) { var ( counters = squirrel. - Select( + Select( "COUNT(*) as total", "SUM(IF(deleted_at IS NULL, 0, 1)) as deleted", "SUM(IF(deleted_at IS NULL, 1, 0)) as valid", ). - From(s.UserTable("u")) + From(s.applicationTable("u")) + + rval = &types.ApplicationMetrics{} + row, err = s.QueryRow(ctx, counters) ) - rval = &types.ApplicationMetrics{} - - var ( - sql, args = counters.MustSql() - row = s.db.QueryRowContext(ctx, sql, args...) - ) + if err != nil { + return nil, err + } err = row.Scan(&rval.Total, &rval.Deleted, &rval.Valid) if err != nil { return nil, err } - return + return rval, nil } diff --git a/store/rdbms/attachments.gen.go b/store/rdbms/attachments.gen.go index 1260e9229..488bafd2a 100644 --- a/store/rdbms/attachments.gen.go +++ b/store/rdbms/attachments.gen.go @@ -11,12 +11,22 @@ package rdbms import ( "context" "database/sql" + "errors" "fmt" "github.com/Masterminds/squirrel" "github.com/cortezaproject/corteza-server/store" "github.com/cortezaproject/corteza-server/system/types" ) +var _ = errors.Is + +const ( + TriggerBeforeAttachmentCreate triggerKey = "attachmentBeforeCreate" + TriggerBeforeAttachmentUpdate triggerKey = "attachmentBeforeUpdate" + TriggerBeforeAttachmentUpsert triggerKey = "attachmentBeforeUpsert" + TriggerBeforeAttachmentDelete triggerKey = "attachmentBeforeDelete" +) + // SearchAttachments returns all matching rows // // This function calls convertAttachmentFilter with the given @@ -35,7 +45,7 @@ func (s Store) SearchAttachments(ctx context.Context, f types.AttachmentFilter) var ( set = make([]*types.Attachment, 0, scap) // Paging is disabled in definition yaml file - // {search: {disablePaging:true}} and this allows + // {search: {enablePaging:false}} and this allows // a much simpler row fetching logic fetch = func() error { var ( @@ -48,14 +58,33 @@ func (s Store) SearchAttachments(ctx context.Context, f types.AttachmentFilter) } for rows.Next() { - if res, err = s.internalAttachmentRowScanner(rows, rows.Err()); err != nil { + if rows.Err() == nil { + res, err = s.internalAttachmentRowScanner(rows) + } + + if err != nil { if cerr := rows.Close(); cerr != nil { - return fmt.Errorf("could not close rows (%v) after scan error: %w", cerr, err) + 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) } @@ -70,17 +99,27 @@ func (s Store) SearchAttachments(ctx context.Context, f types.AttachmentFilter) // // It returns attachment even if deleted func (s Store) LookupAttachmentByID(ctx context.Context, id uint64) (*types.Attachment, error) { - return s.AttachmentLookup(ctx, squirrel.Eq{ - "att.id": id, + return s.execLookupAttachment(ctx, squirrel.Eq{ + s.preprocessColumn("att.id", ""): s.preprocessValue(id, ""), }) } // CreateAttachment creates one or more rows in attachments table func (s Store) CreateAttachment(ctx context.Context, rr ...*types.Attachment) (err error) { for _, res := range rr { - err = ExecuteSqlizer(ctx, s.DB(), s.Insert(s.AttachmentTable()).SetMap(s.internalAttachmentEncoder(res))) + err = s.checkAttachmentConstraints(ctx, res) if err != nil { - return s.config.ErrorHandler(err) + return err + } + + // err = s.attachmentHook(ctx, TriggerBeforeAttachmentCreate, res) + // if err != nil { + // return err + // } + + err = s.execCreateAttachments(ctx, s.internalAttachmentEncoder(res)) + if err != nil { + return err } } @@ -89,17 +128,27 @@ func (s Store) CreateAttachment(ctx context.Context, rr ...*types.Attachment) (e // UpdateAttachment updates one or more existing rows in attachments func (s Store) UpdateAttachment(ctx context.Context, rr ...*types.Attachment) error { - return s.config.ErrorHandler(s.PartialUpdateAttachment(ctx, nil, rr...)) + return s.config.ErrorHandler(s.PartialAttachmentUpdate(ctx, nil, rr...)) } -// PartialUpdateAttachment updates one or more existing rows in attachments -// -// It wraps the update into transaction and can perform partial update by providing list of updatable columns -func (s Store) PartialUpdateAttachment(ctx context.Context, onlyColumns []string, rr ...*types.Attachment) (err error) { +// PartialAttachmentUpdate updates one or more existing rows in attachments +func (s Store) PartialAttachmentUpdate(ctx context.Context, onlyColumns []string, rr ...*types.Attachment) (err error) { for _, res := range rr { - err = s.ExecUpdateAttachments( + err = s.checkAttachmentConstraints(ctx, res) + if err != nil { + return err + } + + // err = s.attachmentHook(ctx, TriggerBeforeAttachmentUpdate, res) + // if err != nil { + // return err + // } + + err = s.execUpdateAttachments( ctx, - squirrel.Eq{s.preprocessColumn("att.id", ""): s.preprocessValue(res.ID, "")}, + squirrel.Eq{ + s.preprocessColumn("att.id", ""): s.preprocessValue(res.ID, ""), + }, s.internalAttachmentEncoder(res).Skip("id").Only(onlyColumns...)) if err != nil { return s.config.ErrorHandler(err) @@ -109,10 +158,39 @@ func (s Store) PartialUpdateAttachment(ctx context.Context, onlyColumns []string return } -// RemoveAttachment removes one or more rows from attachments table -func (s Store) RemoveAttachment(ctx context.Context, rr ...*types.Attachment) (err error) { +// UpsertAttachment updates one or more existing rows in attachments +func (s Store) UpsertAttachment(ctx context.Context, rr ...*types.Attachment) (err error) { for _, res := range rr { - err = ExecuteSqlizer(ctx, s.DB(), s.Delete(s.AttachmentTable("att")).Where(squirrel.Eq{s.preprocessColumn("att.id", ""): s.preprocessValue(res.ID, "")})) + err = s.checkAttachmentConstraints(ctx, res) + if err != nil { + return err + } + + // err = s.attachmentHook(ctx, TriggerBeforeAttachmentUpsert, res) + // if err != nil { + // return err + // } + + err = s.config.ErrorHandler(s.execUpsertAttachments(ctx, s.internalAttachmentEncoder(res))) + if err != nil { + return err + } + } + + return nil +} + +// DeleteAttachment Deletes one or more rows from attachments table +func (s Store) DeleteAttachment(ctx context.Context, rr ...*types.Attachment) (err error) { + for _, res := range rr { + // err = s.attachmentHook(ctx, TriggerBeforeAttachmentDelete, res) + // if err != nil { + // return err + // } + + err = s.execDeleteAttachments(ctx, squirrel.Eq{ + s.preprocessColumn("att.id", ""): s.preprocessValue(res.ID, ""), + }) if err != nil { return s.config.ErrorHandler(err) } @@ -121,35 +199,74 @@ func (s Store) RemoveAttachment(ctx context.Context, rr ...*types.Attachment) (e return nil } -// RemoveAttachmentByID removes row from the attachments table -func (s Store) RemoveAttachmentByID(ctx context.Context, ID uint64) error { - return s.config.ErrorHandler(ExecuteSqlizer(ctx, s.DB(), s.Delete(s.AttachmentTable("att")).Where(squirrel.Eq{s.preprocessColumn("att.id", ""): s.preprocessValue(ID, "")}))) +// DeleteAttachmentByID Deletes row from the attachments table +func (s Store) DeleteAttachmentByID(ctx context.Context, ID uint64) error { + return s.execDeleteAttachments(ctx, squirrel.Eq{ + s.preprocessColumn("att.id", ""): s.preprocessValue(ID, ""), + }) } -// TruncateAttachments removes all rows from the attachments table +// TruncateAttachments Deletes all rows from the attachments table func (s Store) TruncateAttachments(ctx context.Context) error { - return s.config.ErrorHandler(Truncate(ctx, s.DB(), s.AttachmentTable())) + return s.config.ErrorHandler(s.Truncate(ctx, s.attachmentTable())) } -// ExecUpdateAttachments updates all matched (by cnd) rows in attachments with given data -func (s Store) ExecUpdateAttachments(ctx context.Context, cnd squirrel.Sqlizer, set store.Payload) error { - return s.config.ErrorHandler(ExecuteSqlizer(ctx, s.DB(), s.Update(s.AttachmentTable("att")).Where(cnd).SetMap(set))) -} - -// AttachmentLookup prepares Attachment query and executes it, +// execLookupAttachment prepares Attachment query and executes it, // returning types.Attachment (or error) -func (s Store) AttachmentLookup(ctx context.Context, cnd squirrel.Sqlizer) (*types.Attachment, error) { - return s.internalAttachmentRowScanner(s.QueryRow(ctx, s.QueryAttachments().Where(cnd))) -} +func (s Store) execLookupAttachment(ctx context.Context, cnd squirrel.Sqlizer) (res *types.Attachment, err error) { + var ( + row rowScanner + ) -func (s Store) internalAttachmentRowScanner(row rowScanner, err error) (*types.Attachment, error) { + row, err = s.QueryRow(ctx, s.attachmentsSelectBuilder().Where(cnd)) if err != nil { - return nil, err + return } - var res = &types.Attachment{} + res, err = s.internalAttachmentRowScanner(row) + if err != nil { + return + } + + return res, nil +} + +// execCreateAttachments updates all matched (by cnd) rows in attachments with given data +func (s Store) execCreateAttachments(ctx context.Context, payload store.Payload) error { + return s.config.ErrorHandler(s.Exec(ctx, s.InsertBuilder(s.attachmentTable()).SetMap(payload))) +} + +// execUpdateAttachments updates all matched (by cnd) rows in attachments with given data +func (s Store) execUpdateAttachments(ctx context.Context, cnd squirrel.Sqlizer, set store.Payload) error { + return s.config.ErrorHandler(s.Exec(ctx, s.UpdateBuilder(s.attachmentTable("att")).Where(cnd).SetMap(set))) +} + +// execUpsertAttachments inserts new or updates matching (by-primary-key) rows in attachments with given data +func (s Store) execUpsertAttachments(ctx context.Context, set store.Payload) error { + upsert, err := s.config.UpsertBuilder( + s.config, + s.attachmentTable(), + set, + "id", + ) + + if err != nil { + return err + } + + return s.config.ErrorHandler(s.Exec(ctx, upsert)) +} + +// execDeleteAttachments Deletes all matched (by cnd) rows in attachments with given data +func (s Store) execDeleteAttachments(ctx context.Context, cnd squirrel.Sqlizer) error { + return s.config.ErrorHandler(s.Exec(ctx, s.DeleteBuilder(s.attachmentTable("att")).Where(cnd))) +} + +func (s Store) internalAttachmentRowScanner(row rowScanner) (res *types.Attachment, err error) { + res = &types.Attachment{} + if _, has := s.config.RowScanners["attachment"]; has { - scanner := s.config.RowScanners["attachment"].(func(rowScanner, *types.Attachment) error) + scanner := s.config.RowScanners["attachment"].(func(_ rowScanner, _ *types.Attachment) error) err = scanner(row, res) } else { err = row.Scan( @@ -178,12 +295,12 @@ func (s Store) internalAttachmentRowScanner(row rowScanner, err error) (*types.A } // QueryAttachments returns squirrel.SelectBuilder with set table and all columns -func (s Store) QueryAttachments() squirrel.SelectBuilder { - return s.Select(s.AttachmentTable("att"), s.AttachmentColumns("att")...) +func (s Store) attachmentsSelectBuilder() squirrel.SelectBuilder { + return s.SelectBuilder(s.attachmentTable("att"), s.attachmentColumns("att")...) } -// AttachmentTable name of the db table -func (Store) AttachmentTable(aa ...string) string { +// attachmentTable name of the db table +func (Store) attachmentTable(aa ...string) string { var alias string if len(aa) > 0 { alias = " AS " + aa[0] @@ -195,7 +312,7 @@ func (Store) AttachmentTable(aa ...string) string { // AttachmentColumns returns all defined table columns // // With optional string arg, all columns are returned aliased -func (Store) AttachmentColumns(aa ...string) []string { +func (Store) attachmentColumns(aa ...string) []string { var alias string if len(aa) > 0 { alias = aa[0] + "." @@ -215,7 +332,7 @@ func (Store) AttachmentColumns(aa ...string) []string { } } -// {false true true false} +// {true true false false true} // internalAttachmentEncoder encodes fields from types.Attachment to store.Payload (map) // @@ -235,3 +352,16 @@ func (s Store) internalAttachmentEncoder(res *types.Attachment) store.Payload { "deleted_at": res.DeletedAt, } } + +func (s *Store) checkAttachmentConstraints(ctx context.Context, res *types.Attachment) error { + + return nil +} + +// func (s *Store) attachmentHook(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 +// } diff --git a/store/rdbms/attachments.go b/store/rdbms/attachments.go index bb65402e6..0c639be0c 100644 --- a/store/rdbms/attachments.go +++ b/store/rdbms/attachments.go @@ -6,7 +6,7 @@ import ( ) func (s Store) convertAttachmentFilter(f types.AttachmentFilter) (query squirrel.SelectBuilder, err error) { - query = s.QueryAttachments() + query = s.attachmentsSelectBuilder() if f.Kind != "" { query = query.Where(squirrel.Eq{"att.kind": f.Kind}) diff --git a/store/rdbms/builder.go b/store/rdbms/builder.go new file mode 100644 index 000000000..581d19167 --- /dev/null +++ b/store/rdbms/builder.go @@ -0,0 +1,51 @@ +package rdbms + +import ( + "fmt" + "github.com/Masterminds/squirrel" + "github.com/cortezaproject/corteza-server/store" + "strings" +) + +// UpsertBuilder combines insert and update builder into upsert (INSERT ... ON CONFLICT UPDATE ...) +// +// It accepts name of the table we're inserting to, payload with values that should be inserted and list of +// columns that represent unique keys +// +// https://www.postgresql.org/docs/12/sql-insert.html +// https://sqlite.org/lang_UPSERT.html +func UpsertBuilder(cfg *Config, table string, payload store.Payload, columns ...string) (squirrel.InsertBuilder, error) { + var ( + updatePayload = store.Payload{} + + // where to cutoff the update query + updateCutoff = len("UPDATE " + table + " ") + ) + + for k, v := range payload { + updatePayload[k] = v + } + + for _, c := range columns { + if _, has := updatePayload[c]; has { + delete(updatePayload, c) + } + } + + sql, args, err := squirrel. + Update(table). + SetMap(updatePayload). + ToSql() + + if err != nil { + return squirrel.InsertBuilder{}, err + } + + suffix := fmt.Sprintf("ON CONFLICT (%s) DO UPDATE %s", strings.Join(columns, ","), sql[updateCutoff:]) + + return squirrel. + Insert(table). + PlaceholderFormat(cfg.PlaceholderFormat). + SetMap(payload). + Suffix(suffix, args...), nil +} diff --git a/store/rdbms/builder_test.go b/store/rdbms/builder_test.go new file mode 100644 index 000000000..be01e5316 --- /dev/null +++ b/store/rdbms/builder_test.go @@ -0,0 +1,22 @@ +package rdbms + +import ( + "github.com/cortezaproject/corteza-server/store" + "github.com/stretchr/testify/require" + "testing" +) + +func TestBuilder(t *testing.T) { + var ( + req = require.New(t) + ) + + upsert, err := UpsertBuilder("tbl", store.Payload{"c1": "v1", "c2": "v2"}, "c1") + req.NoError(err) + sql, args, err := upsert.ToSql() + req.NoError(err) + req.Contains(sql, "ON CONFLICT UPDATE SET") + req.Contains(sql, "INSERT INTO tbl") + req.Equal([]interface{}{"v1", "v2", "v2", "v1"}, args) + +} diff --git a/store/rdbms/compose_charts.gen.go b/store/rdbms/compose_charts.gen.go index fb053214d..433423096 100644 --- a/store/rdbms/compose_charts.gen.go +++ b/store/rdbms/compose_charts.gen.go @@ -11,13 +11,24 @@ package rdbms import ( "context" "database/sql" + "errors" "fmt" "github.com/Masterminds/squirrel" "github.com/cortezaproject/corteza-server/compose/types" + "github.com/cortezaproject/corteza-server/pkg/filter" "github.com/cortezaproject/corteza-server/store" "strings" ) +var _ = errors.Is + +const ( + TriggerBeforeComposeChartCreate triggerKey = "composeChartBeforeCreate" + TriggerBeforeComposeChartUpdate triggerKey = "composeChartBeforeUpdate" + TriggerBeforeComposeChartUpsert triggerKey = "composeChartBeforeUpsert" + TriggerBeforeComposeChartDelete triggerKey = "composeChartBeforeDelete" +) + // SearchComposeCharts returns all matching rows // // This function calls convertComposeChartFilter with the given @@ -38,7 +49,7 @@ func (s Store) SearchComposeCharts(ctx context.Context, f types.ChartFilter) (ty // This tells us to flip the descending flag on all used sort keys reverseCursor := f.PageCursor != nil && f.PageCursor.Reverse - if err = f.Sort.Validate(s.sortableComposeChartColumns()...); err != nil { + if err := f.Sort.Validate(s.sortableComposeChartColumns()...); err != nil { return nil, f, fmt.Errorf("could not validate sort: %v", err) } @@ -68,7 +79,7 @@ func (s Store) SearchComposeCharts(ctx context.Context, f types.ChartFilter) (ty var ( set = make([]*types.Chart, 0, scap) - // fetches rows and scans them into Types.Chart resource this is then passed to Check function on filter + // fetches rows and scans them into types.Chart resource this is then passed to Check function on filter // to help determine if fetched resource fits or not // // Note that limit is passed explicitly and is not necessarily equal to filter's limit. We want @@ -77,7 +88,7 @@ func (s Store) SearchComposeCharts(ctx context.Context, f types.ChartFilter) (ty // The value for cursor is used and set directly from/to the filter! // // It returns total number of fetched pages and modifies PageCursor value for paging - fetchPage = func(cursor *store.PagingCursor, limit uint) (fetched uint, err error) { + fetchPage = func(cursor *filter.PagingCursor, limit uint) (fetched uint, err error) { var ( res *types.Chart @@ -108,7 +119,12 @@ func (s Store) SearchComposeCharts(ctx context.Context, f types.ChartFilter) (ty for rows.Next() { fetched++ - if res, err = s.internalComposeChartRowScanner(rows, rows.Err()); err != nil { + + if rows.Err() == nil { + res, err = s.internalComposeChartRowScanner(rows) + } + + if err != nil { if cerr := rows.Close(); cerr != nil { err = fmt.Errorf("could not close rows (%v) after scan error: %w", cerr, err) } @@ -225,24 +241,35 @@ func (s Store) SearchComposeCharts(ctx context.Context, f types.ChartFilter) (ty // // It returns compose chart even if deleted func (s Store) LookupComposeChartByID(ctx context.Context, id uint64) (*types.Chart, error) { - return s.ComposeChartLookup(ctx, squirrel.Eq{ - "cch.id": id, + return s.execLookupComposeChart(ctx, squirrel.Eq{ + s.preprocessColumn("cch.id", ""): s.preprocessValue(id, ""), }) } -// LookupComposeChartByHandle searches for compose chart by handle (case-insensitive) -func (s Store) LookupComposeChartByHandle(ctx context.Context, handle string) (*types.Chart, error) { - return s.ComposeChartLookup(ctx, squirrel.Eq{ - "cch.handle": handle, +// LookupComposeChartByNamespaceIDHandle searches for compose chart by handle (case-insensitive) +func (s Store) LookupComposeChartByNamespaceIDHandle(ctx context.Context, namespace_id uint64, handle string) (*types.Chart, error) { + return s.execLookupComposeChart(ctx, squirrel.Eq{ + s.preprocessColumn("cch.rel_namespace", ""): s.preprocessValue(namespace_id, ""), + s.preprocessColumn("cch.handle", "lower"): s.preprocessValue(handle, "lower"), }) } // CreateComposeChart creates one or more rows in compose_chart table func (s Store) CreateComposeChart(ctx context.Context, rr ...*types.Chart) (err error) { for _, res := range rr { - err = ExecuteSqlizer(ctx, s.DB(), s.Insert(s.ComposeChartTable()).SetMap(s.internalComposeChartEncoder(res))) + err = s.checkComposeChartConstraints(ctx, res) if err != nil { - return s.config.ErrorHandler(err) + return err + } + + // err = s.composeChartHook(ctx, TriggerBeforeComposeChartCreate, res) + // if err != nil { + // return err + // } + + err = s.execCreateComposeCharts(ctx, s.internalComposeChartEncoder(res)) + if err != nil { + return err } } @@ -251,17 +278,27 @@ func (s Store) CreateComposeChart(ctx context.Context, rr ...*types.Chart) (err // UpdateComposeChart updates one or more existing rows in compose_chart func (s Store) UpdateComposeChart(ctx context.Context, rr ...*types.Chart) error { - return s.config.ErrorHandler(s.PartialUpdateComposeChart(ctx, nil, rr...)) + return s.config.ErrorHandler(s.PartialComposeChartUpdate(ctx, nil, rr...)) } -// PartialUpdateComposeChart updates one or more existing rows in compose_chart -// -// It wraps the update into transaction and can perform partial update by providing list of updatable columns -func (s Store) PartialUpdateComposeChart(ctx context.Context, onlyColumns []string, rr ...*types.Chart) (err error) { +// PartialComposeChartUpdate updates one or more existing rows in compose_chart +func (s Store) PartialComposeChartUpdate(ctx context.Context, onlyColumns []string, rr ...*types.Chart) (err error) { for _, res := range rr { - err = s.ExecUpdateComposeCharts( + err = s.checkComposeChartConstraints(ctx, res) + if err != nil { + return err + } + + // err = s.composeChartHook(ctx, TriggerBeforeComposeChartUpdate, res) + // if err != nil { + // return err + // } + + err = s.execUpdateComposeCharts( ctx, - squirrel.Eq{s.preprocessColumn("cch.id", ""): s.preprocessValue(res.ID, "")}, + squirrel.Eq{ + s.preprocessColumn("cch.id", ""): s.preprocessValue(res.ID, ""), + }, s.internalComposeChartEncoder(res).Skip("id").Only(onlyColumns...)) if err != nil { return s.config.ErrorHandler(err) @@ -271,10 +308,39 @@ func (s Store) PartialUpdateComposeChart(ctx context.Context, onlyColumns []stri return } -// RemoveComposeChart removes one or more rows from compose_chart table -func (s Store) RemoveComposeChart(ctx context.Context, rr ...*types.Chart) (err error) { +// UpsertComposeChart updates one or more existing rows in compose_chart +func (s Store) UpsertComposeChart(ctx context.Context, rr ...*types.Chart) (err error) { for _, res := range rr { - err = ExecuteSqlizer(ctx, s.DB(), s.Delete(s.ComposeChartTable("cch")).Where(squirrel.Eq{s.preprocessColumn("cch.id", ""): s.preprocessValue(res.ID, "")})) + err = s.checkComposeChartConstraints(ctx, res) + if err != nil { + return err + } + + // err = s.composeChartHook(ctx, TriggerBeforeComposeChartUpsert, res) + // if err != nil { + // return err + // } + + err = s.config.ErrorHandler(s.execUpsertComposeCharts(ctx, s.internalComposeChartEncoder(res))) + if err != nil { + return err + } + } + + return nil +} + +// DeleteComposeChart Deletes one or more rows from compose_chart table +func (s Store) DeleteComposeChart(ctx context.Context, rr ...*types.Chart) (err error) { + for _, res := range rr { + // err = s.composeChartHook(ctx, TriggerBeforeComposeChartDelete, res) + // if err != nil { + // return err + // } + + err = s.execDeleteComposeCharts(ctx, squirrel.Eq{ + s.preprocessColumn("cch.id", ""): s.preprocessValue(res.ID, ""), + }) if err != nil { return s.config.ErrorHandler(err) } @@ -283,35 +349,74 @@ func (s Store) RemoveComposeChart(ctx context.Context, rr ...*types.Chart) (err return nil } -// RemoveComposeChartByID removes row from the compose_chart table -func (s Store) RemoveComposeChartByID(ctx context.Context, ID uint64) error { - return s.config.ErrorHandler(ExecuteSqlizer(ctx, s.DB(), s.Delete(s.ComposeChartTable("cch")).Where(squirrel.Eq{s.preprocessColumn("cch.id", ""): s.preprocessValue(ID, "")}))) +// DeleteComposeChartByID Deletes row from the compose_chart table +func (s Store) DeleteComposeChartByID(ctx context.Context, ID uint64) error { + return s.execDeleteComposeCharts(ctx, squirrel.Eq{ + s.preprocessColumn("cch.id", ""): s.preprocessValue(ID, ""), + }) } -// TruncateComposeCharts removes all rows from the compose_chart table +// TruncateComposeCharts Deletes all rows from the compose_chart table func (s Store) TruncateComposeCharts(ctx context.Context) error { - return s.config.ErrorHandler(Truncate(ctx, s.DB(), s.ComposeChartTable())) + return s.config.ErrorHandler(s.Truncate(ctx, s.composeChartTable())) } -// ExecUpdateComposeCharts updates all matched (by cnd) rows in compose_chart with given data -func (s Store) ExecUpdateComposeCharts(ctx context.Context, cnd squirrel.Sqlizer, set store.Payload) error { - return s.config.ErrorHandler(ExecuteSqlizer(ctx, s.DB(), s.Update(s.ComposeChartTable("cch")).Where(cnd).SetMap(set))) -} - -// ComposeChartLookup prepares ComposeChart query and executes it, +// execLookupComposeChart prepares ComposeChart query and executes it, // returning types.Chart (or error) -func (s Store) ComposeChartLookup(ctx context.Context, cnd squirrel.Sqlizer) (*types.Chart, error) { - return s.internalComposeChartRowScanner(s.QueryRow(ctx, s.QueryComposeCharts().Where(cnd))) -} +func (s Store) execLookupComposeChart(ctx context.Context, cnd squirrel.Sqlizer) (res *types.Chart, err error) { + var ( + row rowScanner + ) -func (s Store) internalComposeChartRowScanner(row rowScanner, err error) (*types.Chart, error) { + row, err = s.QueryRow(ctx, s.composeChartsSelectBuilder().Where(cnd)) if err != nil { - return nil, err + return } - var res = &types.Chart{} + res, err = s.internalComposeChartRowScanner(row) + if err != nil { + return + } + + return res, nil +} + +// execCreateComposeCharts updates all matched (by cnd) rows in compose_chart with given data +func (s Store) execCreateComposeCharts(ctx context.Context, payload store.Payload) error { + return s.config.ErrorHandler(s.Exec(ctx, s.InsertBuilder(s.composeChartTable()).SetMap(payload))) +} + +// execUpdateComposeCharts updates all matched (by cnd) rows in compose_chart with given data +func (s Store) execUpdateComposeCharts(ctx context.Context, cnd squirrel.Sqlizer, set store.Payload) error { + return s.config.ErrorHandler(s.Exec(ctx, s.UpdateBuilder(s.composeChartTable("cch")).Where(cnd).SetMap(set))) +} + +// execUpsertComposeCharts inserts new or updates matching (by-primary-key) rows in compose_chart with given data +func (s Store) execUpsertComposeCharts(ctx context.Context, set store.Payload) error { + upsert, err := s.config.UpsertBuilder( + s.config, + s.composeChartTable(), + set, + "id", + ) + + if err != nil { + return err + } + + return s.config.ErrorHandler(s.Exec(ctx, upsert)) +} + +// execDeleteComposeCharts Deletes all matched (by cnd) rows in compose_chart with given data +func (s Store) execDeleteComposeCharts(ctx context.Context, cnd squirrel.Sqlizer) error { + return s.config.ErrorHandler(s.Exec(ctx, s.DeleteBuilder(s.composeChartTable("cch")).Where(cnd))) +} + +func (s Store) internalComposeChartRowScanner(row rowScanner) (res *types.Chart, err error) { + res = &types.Chart{} + if _, has := s.config.RowScanners["composeChart"]; has { - scanner := s.config.RowScanners["composeChart"].(func(rowScanner, *types.Chart) error) + scanner := s.config.RowScanners["composeChart"].(func(_ rowScanner, _ *types.Chart) error) err = scanner(row, res) } else { err = row.Scan( @@ -338,12 +443,12 @@ func (s Store) internalComposeChartRowScanner(row rowScanner, err error) (*types } // QueryComposeCharts returns squirrel.SelectBuilder with set table and all columns -func (s Store) QueryComposeCharts() squirrel.SelectBuilder { - return s.Select(s.ComposeChartTable("cch"), s.ComposeChartColumns("cch")...) +func (s Store) composeChartsSelectBuilder() squirrel.SelectBuilder { + return s.SelectBuilder(s.composeChartTable("cch"), s.composeChartColumns("cch")...) } -// ComposeChartTable name of the db table -func (Store) ComposeChartTable(aa ...string) string { +// composeChartTable name of the db table +func (Store) composeChartTable(aa ...string) string { var alias string if len(aa) > 0 { alias = " AS " + aa[0] @@ -355,7 +460,7 @@ func (Store) ComposeChartTable(aa ...string) string { // ComposeChartColumns returns all defined table columns // // With optional string arg, all columns are returned aliased -func (Store) ComposeChartColumns(aa ...string) []string { +func (Store) composeChartColumns(aa ...string) []string { var alias string if len(aa) > 0 { alias = aa[0] + "." @@ -373,7 +478,7 @@ func (Store) ComposeChartColumns(aa ...string) []string { } } -// {false false false false} +// {true true true true true} // sortableComposeChartColumns returns all ComposeChart columns flagged as sortable // @@ -406,9 +511,9 @@ func (s Store) internalComposeChartEncoder(res *types.Chart) store.Payload { } } -func (s Store) collectComposeChartCursorValues(res *types.Chart, cc ...string) *store.PagingCursor { +func (s Store) collectComposeChartCursorValues(res *types.Chart, cc ...string) *filter.PagingCursor { var ( - cursor = &store.PagingCursor{} + cursor = &filter.PagingCursor{} hasUnique bool @@ -443,3 +548,16 @@ func (s Store) collectComposeChartCursorValues(res *types.Chart, cc ...string) * return cursor } + +func (s *Store) checkComposeChartConstraints(ctx context.Context, res *types.Chart) error { + + return nil +} + +// func (s *Store) composeChartHook(ctx context.Context, key triggerKey, res *types.Chart) error { +// if fn, has := s.config.TriggerHandlers[key]; has { +// return fn.(func (ctx context.Context, s *Store, res *types.Chart) error)(ctx, s, res) +// } +// +// return nil +// } diff --git a/store/rdbms/compose_charts.go b/store/rdbms/compose_charts.go index 854e944f7..92f41c191 100644 --- a/store/rdbms/compose_charts.go +++ b/store/rdbms/compose_charts.go @@ -8,7 +8,7 @@ import ( ) func (s Store) convertComposeChartFilter(f types.ChartFilter) (query squirrel.SelectBuilder, err error) { - query = s.QueryComposeCharts() + query = s.composeChartsSelectBuilder() query = rh.FilterNullByState(query, "cch.deleted_at", f.Deleted) diff --git a/store/rdbms/compose_module_fields.gen.go b/store/rdbms/compose_module_fields.gen.go index 77fb6ea7b..5b0c76b81 100644 --- a/store/rdbms/compose_module_fields.gen.go +++ b/store/rdbms/compose_module_fields.gen.go @@ -11,18 +11,100 @@ package rdbms 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 ( + TriggerBeforeComposeModuleFieldCreate triggerKey = "composeModuleFieldBeforeCreate" + TriggerBeforeComposeModuleFieldUpdate triggerKey = "composeModuleFieldBeforeUpdate" + TriggerBeforeComposeModuleFieldUpsert triggerKey = "composeModuleFieldBeforeUpsert" + TriggerBeforeComposeModuleFieldDelete triggerKey = "composeModuleFieldBeforeDelete" +) + +// SearchComposeModuleFields returns all matching rows +// +// This function calls convertComposeModuleFieldFilter with the given +// types.ModuleFieldFilter and expects to receive a working squirrel.SelectBuilder +func (s Store) SearchComposeModuleFields(ctx context.Context, f types.ModuleFieldFilter) (types.ModuleFieldSet, types.ModuleFieldFilter, error) { + var scap uint + q, err := s.convertComposeModuleFieldFilter(f) + if err != nil { + return nil, f, err + } + + if scap == 0 { + scap = DefaultSliceCapacity + } + + var ( + set = make([]*types.ModuleField, 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.ModuleField + rows, err = s.Query(ctx, q) + ) + + if err != nil { + return err + } + + for rows.Next() { + if rows.Err() == nil { + res, err = s.internalComposeModuleFieldRowScanner(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 + set = append(set, res) + } + + return rows.Close() + } + ) + + return set, f, s.config.ErrorHandler(fetch()) +} + +// LookupComposeModuleFieldByModuleIDName searches for compose module field by name (case-insensitive) +func (s Store) LookupComposeModuleFieldByModuleIDName(ctx context.Context, module_id uint64, name string) (*types.ModuleField, error) { + return s.execLookupComposeModuleField(ctx, squirrel.Eq{ + s.preprocessColumn("cmf.rel_module", ""): s.preprocessValue(module_id, ""), + s.preprocessColumn("cmf.name", "lower"): s.preprocessValue(name, "lower"), + }) +} + // CreateComposeModuleField creates one or more rows in compose_module_field table func (s Store) CreateComposeModuleField(ctx context.Context, rr ...*types.ModuleField) (err error) { for _, res := range rr { - err = ExecuteSqlizer(ctx, s.DB(), s.Insert(s.ComposeModuleFieldTable()).SetMap(s.internalComposeModuleFieldEncoder(res))) + err = s.checkComposeModuleFieldConstraints(ctx, res) if err != nil { - return s.config.ErrorHandler(err) + return err + } + + // err = s.composeModuleFieldHook(ctx, TriggerBeforeComposeModuleFieldCreate, res) + // if err != nil { + // return err + // } + + err = s.execCreateComposeModuleFields(ctx, s.internalComposeModuleFieldEncoder(res)) + if err != nil { + return err } } @@ -31,17 +113,27 @@ func (s Store) CreateComposeModuleField(ctx context.Context, rr ...*types.Module // UpdateComposeModuleField updates one or more existing rows in compose_module_field func (s Store) UpdateComposeModuleField(ctx context.Context, rr ...*types.ModuleField) error { - return s.config.ErrorHandler(s.PartialUpdateComposeModuleField(ctx, nil, rr...)) + return s.config.ErrorHandler(s.PartialComposeModuleFieldUpdate(ctx, nil, rr...)) } -// PartialUpdateComposeModuleField updates one or more existing rows in compose_module_field -// -// It wraps the update into transaction and can perform partial update by providing list of updatable columns -func (s Store) PartialUpdateComposeModuleField(ctx context.Context, onlyColumns []string, rr ...*types.ModuleField) (err error) { +// PartialComposeModuleFieldUpdate updates one or more existing rows in compose_module_field +func (s Store) PartialComposeModuleFieldUpdate(ctx context.Context, onlyColumns []string, rr ...*types.ModuleField) (err error) { for _, res := range rr { - err = s.ExecUpdateComposeModuleFields( + err = s.checkComposeModuleFieldConstraints(ctx, res) + if err != nil { + return err + } + + // err = s.composeModuleFieldHook(ctx, TriggerBeforeComposeModuleFieldUpdate, res) + // if err != nil { + // return err + // } + + err = s.execUpdateComposeModuleFields( ctx, - squirrel.Eq{s.preprocessColumn("cmf.id", ""): s.preprocessValue(res.ID, "")}, + squirrel.Eq{ + s.preprocessColumn("cmf.id", ""): s.preprocessValue(res.ID, ""), + }, s.internalComposeModuleFieldEncoder(res).Skip("id").Only(onlyColumns...)) if err != nil { return s.config.ErrorHandler(err) @@ -51,10 +143,39 @@ func (s Store) PartialUpdateComposeModuleField(ctx context.Context, onlyColumns return } -// RemoveComposeModuleField removes one or more rows from compose_module_field table -func (s Store) RemoveComposeModuleField(ctx context.Context, rr ...*types.ModuleField) (err error) { +// UpsertComposeModuleField updates one or more existing rows in compose_module_field +func (s Store) UpsertComposeModuleField(ctx context.Context, rr ...*types.ModuleField) (err error) { for _, res := range rr { - err = ExecuteSqlizer(ctx, s.DB(), s.Delete(s.ComposeModuleFieldTable("cmf")).Where(squirrel.Eq{s.preprocessColumn("cmf.id", ""): s.preprocessValue(res.ID, "")})) + err = s.checkComposeModuleFieldConstraints(ctx, res) + if err != nil { + return err + } + + // err = s.composeModuleFieldHook(ctx, TriggerBeforeComposeModuleFieldUpsert, res) + // if err != nil { + // return err + // } + + err = s.config.ErrorHandler(s.execUpsertComposeModuleFields(ctx, s.internalComposeModuleFieldEncoder(res))) + if err != nil { + return err + } + } + + return nil +} + +// DeleteComposeModuleField Deletes one or more rows from compose_module_field table +func (s Store) DeleteComposeModuleField(ctx context.Context, rr ...*types.ModuleField) (err error) { + for _, res := range rr { + // err = s.composeModuleFieldHook(ctx, TriggerBeforeComposeModuleFieldDelete, res) + // if err != nil { + // return err + // } + + err = s.execDeleteComposeModuleFields(ctx, squirrel.Eq{ + s.preprocessColumn("cmf.id", ""): s.preprocessValue(res.ID, ""), + }) if err != nil { return s.config.ErrorHandler(err) } @@ -63,35 +184,74 @@ func (s Store) RemoveComposeModuleField(ctx context.Context, rr ...*types.Module return nil } -// RemoveComposeModuleFieldByID removes row from the compose_module_field table -func (s Store) RemoveComposeModuleFieldByID(ctx context.Context, ID uint64) error { - return s.config.ErrorHandler(ExecuteSqlizer(ctx, s.DB(), s.Delete(s.ComposeModuleFieldTable("cmf")).Where(squirrel.Eq{s.preprocessColumn("cmf.id", ""): s.preprocessValue(ID, "")}))) +// DeleteComposeModuleFieldByID Deletes row from the compose_module_field table +func (s Store) DeleteComposeModuleFieldByID(ctx context.Context, ID uint64) error { + return s.execDeleteComposeModuleFields(ctx, squirrel.Eq{ + s.preprocessColumn("cmf.id", ""): s.preprocessValue(ID, ""), + }) } -// TruncateComposeModuleFields removes all rows from the compose_module_field table +// TruncateComposeModuleFields Deletes all rows from the compose_module_field table func (s Store) TruncateComposeModuleFields(ctx context.Context) error { - return s.config.ErrorHandler(Truncate(ctx, s.DB(), s.ComposeModuleFieldTable())) + return s.config.ErrorHandler(s.Truncate(ctx, s.composeModuleFieldTable())) } -// ExecUpdateComposeModuleFields updates all matched (by cnd) rows in compose_module_field with given data -func (s Store) ExecUpdateComposeModuleFields(ctx context.Context, cnd squirrel.Sqlizer, set store.Payload) error { - return s.config.ErrorHandler(ExecuteSqlizer(ctx, s.DB(), s.Update(s.ComposeModuleFieldTable("cmf")).Where(cnd).SetMap(set))) -} - -// ComposeModuleFieldLookup prepares ComposeModuleField query and executes it, +// execLookupComposeModuleField prepares ComposeModuleField query and executes it, // returning types.ModuleField (or error) -func (s Store) ComposeModuleFieldLookup(ctx context.Context, cnd squirrel.Sqlizer) (*types.ModuleField, error) { - return s.internalComposeModuleFieldRowScanner(s.QueryRow(ctx, s.QueryComposeModuleFields().Where(cnd))) -} +func (s Store) execLookupComposeModuleField(ctx context.Context, cnd squirrel.Sqlizer) (res *types.ModuleField, err error) { + var ( + row rowScanner + ) -func (s Store) internalComposeModuleFieldRowScanner(row rowScanner, err error) (*types.ModuleField, error) { + row, err = s.QueryRow(ctx, s.composeModuleFieldsSelectBuilder().Where(cnd)) if err != nil { - return nil, err + return } - var res = &types.ModuleField{} + res, err = s.internalComposeModuleFieldRowScanner(row) + if err != nil { + return + } + + return res, nil +} + +// execCreateComposeModuleFields updates all matched (by cnd) rows in compose_module_field with given data +func (s Store) execCreateComposeModuleFields(ctx context.Context, payload store.Payload) error { + return s.config.ErrorHandler(s.Exec(ctx, s.InsertBuilder(s.composeModuleFieldTable()).SetMap(payload))) +} + +// execUpdateComposeModuleFields updates all matched (by cnd) rows in compose_module_field with given data +func (s Store) execUpdateComposeModuleFields(ctx context.Context, cnd squirrel.Sqlizer, set store.Payload) error { + return s.config.ErrorHandler(s.Exec(ctx, s.UpdateBuilder(s.composeModuleFieldTable("cmf")).Where(cnd).SetMap(set))) +} + +// execUpsertComposeModuleFields inserts new or updates matching (by-primary-key) rows in compose_module_field with given data +func (s Store) execUpsertComposeModuleFields(ctx context.Context, set store.Payload) error { + upsert, err := s.config.UpsertBuilder( + s.config, + s.composeModuleFieldTable(), + set, + "id", + ) + + if err != nil { + return err + } + + return s.config.ErrorHandler(s.Exec(ctx, upsert)) +} + +// execDeleteComposeModuleFields Deletes all matched (by cnd) rows in compose_module_field with given data +func (s Store) execDeleteComposeModuleFields(ctx context.Context, cnd squirrel.Sqlizer) error { + return s.config.ErrorHandler(s.Exec(ctx, s.DeleteBuilder(s.composeModuleFieldTable("cmf")).Where(cnd))) +} + +func (s Store) internalComposeModuleFieldRowScanner(row rowScanner) (res *types.ModuleField, err error) { + res = &types.ModuleField{} + if _, has := s.config.RowScanners["composeModuleField"]; has { - scanner := s.config.RowScanners["composeModuleField"].(func(rowScanner, *types.ModuleField) error) + scanner := s.config.RowScanners["composeModuleField"].(func(_ rowScanner, _ *types.ModuleField) error) err = scanner(row, res) } else { err = row.Scan( @@ -125,12 +285,12 @@ func (s Store) internalComposeModuleFieldRowScanner(row rowScanner, err error) ( } // QueryComposeModuleFields returns squirrel.SelectBuilder with set table and all columns -func (s Store) QueryComposeModuleFields() squirrel.SelectBuilder { - return s.Select(s.ComposeModuleFieldTable("cmf"), s.ComposeModuleFieldColumns("cmf")...) +func (s Store) composeModuleFieldsSelectBuilder() squirrel.SelectBuilder { + return s.SelectBuilder(s.composeModuleFieldTable("cmf"), s.composeModuleFieldColumns("cmf")...) } -// ComposeModuleFieldTable name of the db table -func (Store) ComposeModuleFieldTable(aa ...string) string { +// composeModuleFieldTable name of the db table +func (Store) composeModuleFieldTable(aa ...string) string { var alias string if len(aa) > 0 { alias = " AS " + aa[0] @@ -142,7 +302,7 @@ func (Store) ComposeModuleFieldTable(aa ...string) string { // ComposeModuleFieldColumns returns all defined table columns // // With optional string arg, all columns are returned aliased -func (Store) ComposeModuleFieldColumns(aa ...string) []string { +func (Store) composeModuleFieldColumns(aa ...string) []string { var alias string if len(aa) > 0 { alias = aa[0] + "." @@ -167,7 +327,7 @@ func (Store) ComposeModuleFieldColumns(aa ...string) []string { } } -// {true true true true} +// {true true false false false} // internalComposeModuleFieldEncoder encodes fields from types.ModuleField to store.Payload (map) // @@ -192,3 +352,25 @@ func (s Store) internalComposeModuleFieldEncoder(res *types.ModuleField) store.P "deleted_at": res.DeletedAt, } } + +func (s *Store) checkComposeModuleFieldConstraints(ctx context.Context, res *types.ModuleField) error { + + { + ex, err := s.LookupComposeModuleFieldByModuleIDName(ctx, res.ModuleID, res.Name) + if err == nil && ex != nil && ex.ID != res.ID { + return store.ErrNotUnique + } else if !errors.Is(err, store.ErrNotFound) { + return err + } + } + + return nil +} + +// func (s *Store) composeModuleFieldHook(ctx context.Context, key triggerKey, res *types.ModuleField) error { +// if fn, has := s.config.TriggerHandlers[key]; has { +// return fn.(func (ctx context.Context, s *Store, res *types.ModuleField) error)(ctx, s, res) +// } +// +// return nil +// } diff --git a/store/rdbms/compose_modules.gen.go b/store/rdbms/compose_modules.gen.go index 49219b97d..6e454d6a2 100644 --- a/store/rdbms/compose_modules.gen.go +++ b/store/rdbms/compose_modules.gen.go @@ -11,13 +11,24 @@ package rdbms import ( "context" "database/sql" + "errors" "fmt" "github.com/Masterminds/squirrel" "github.com/cortezaproject/corteza-server/compose/types" + "github.com/cortezaproject/corteza-server/pkg/filter" "github.com/cortezaproject/corteza-server/store" "strings" ) +var _ = errors.Is + +const ( + TriggerBeforeComposeModuleCreate triggerKey = "composeModuleBeforeCreate" + TriggerBeforeComposeModuleUpdate triggerKey = "composeModuleBeforeUpdate" + TriggerBeforeComposeModuleUpsert triggerKey = "composeModuleBeforeUpsert" + TriggerBeforeComposeModuleDelete triggerKey = "composeModuleBeforeDelete" +) + // SearchComposeModules returns all matching rows // // This function calls convertComposeModuleFilter with the given @@ -38,7 +49,7 @@ func (s Store) SearchComposeModules(ctx context.Context, f types.ModuleFilter) ( // This tells us to flip the descending flag on all used sort keys reverseCursor := f.PageCursor != nil && f.PageCursor.Reverse - if err = f.Sort.Validate(s.sortableComposeModuleColumns()...); err != nil { + if err := f.Sort.Validate(s.sortableComposeModuleColumns()...); err != nil { return nil, f, fmt.Errorf("could not validate sort: %v", err) } @@ -68,7 +79,7 @@ func (s Store) SearchComposeModules(ctx context.Context, f types.ModuleFilter) ( var ( set = make([]*types.Module, 0, scap) - // fetches rows and scans them into Types.Module resource this is then passed to Check function on filter + // fetches rows and scans them into types.Module resource this is then passed to Check function on filter // to help determine if fetched resource fits or not // // Note that limit is passed explicitly and is not necessarily equal to filter's limit. We want @@ -77,7 +88,7 @@ func (s Store) SearchComposeModules(ctx context.Context, f types.ModuleFilter) ( // The value for cursor is used and set directly from/to the filter! // // It returns total number of fetched pages and modifies PageCursor value for paging - fetchPage = func(cursor *store.PagingCursor, limit uint) (fetched uint, err error) { + fetchPage = func(cursor *filter.PagingCursor, limit uint) (fetched uint, err error) { var ( res *types.Module @@ -108,7 +119,12 @@ func (s Store) SearchComposeModules(ctx context.Context, f types.ModuleFilter) ( for rows.Next() { fetched++ - if res, err = s.internalComposeModuleRowScanner(rows, rows.Err()); err != nil { + + if rows.Err() == nil { + res, err = s.internalComposeModuleRowScanner(rows) + } + + if err != nil { if cerr := rows.Close(); cerr != nil { err = fmt.Errorf("could not close rows (%v) after scan error: %w", cerr, err) } @@ -221,10 +237,19 @@ func (s Store) SearchComposeModules(ctx context.Context, f types.ModuleFilter) ( return set, f, s.config.ErrorHandler(fetch()) } -// LookupComposeModuleByHandle searches for compose module by handle (case-insensitive) -func (s Store) LookupComposeModuleByHandle(ctx context.Context, handle string) (*types.Module, error) { - return s.ComposeModuleLookup(ctx, squirrel.Eq{ - "cmd.handle": handle, +// LookupComposeModuleByNamespaceIDHandle searches for compose module by handle (case-insensitive) +func (s Store) LookupComposeModuleByNamespaceIDHandle(ctx context.Context, namespace_id uint64, handle string) (*types.Module, error) { + return s.execLookupComposeModule(ctx, squirrel.Eq{ + s.preprocessColumn("cmd.rel_namespace", ""): s.preprocessValue(namespace_id, ""), + s.preprocessColumn("cmd.handle", "lower"): s.preprocessValue(handle, "lower"), + }) +} + +// LookupComposeModuleByNamespaceIDName searches for compose module by name (case-insensitive) +func (s Store) LookupComposeModuleByNamespaceIDName(ctx context.Context, namespace_id uint64, name string) (*types.Module, error) { + return s.execLookupComposeModule(ctx, squirrel.Eq{ + s.preprocessColumn("cmd.rel_namespace", ""): s.preprocessValue(namespace_id, ""), + s.preprocessColumn("cmd.name", "lower"): s.preprocessValue(name, "lower"), }) } @@ -232,17 +257,27 @@ func (s Store) LookupComposeModuleByHandle(ctx context.Context, handle string) ( // // It returns compose module even if deleted func (s Store) LookupComposeModuleByID(ctx context.Context, id uint64) (*types.Module, error) { - return s.ComposeModuleLookup(ctx, squirrel.Eq{ - "cmd.id": id, + return s.execLookupComposeModule(ctx, squirrel.Eq{ + s.preprocessColumn("cmd.id", ""): s.preprocessValue(id, ""), }) } // CreateComposeModule creates one or more rows in compose_module table func (s Store) CreateComposeModule(ctx context.Context, rr ...*types.Module) (err error) { for _, res := range rr { - err = ExecuteSqlizer(ctx, s.DB(), s.Insert(s.ComposeModuleTable()).SetMap(s.internalComposeModuleEncoder(res))) + err = s.checkComposeModuleConstraints(ctx, res) if err != nil { - return s.config.ErrorHandler(err) + return err + } + + // err = s.composeModuleHook(ctx, TriggerBeforeComposeModuleCreate, res) + // if err != nil { + // return err + // } + + err = s.execCreateComposeModules(ctx, s.internalComposeModuleEncoder(res)) + if err != nil { + return err } } @@ -251,17 +286,27 @@ func (s Store) CreateComposeModule(ctx context.Context, rr ...*types.Module) (er // UpdateComposeModule updates one or more existing rows in compose_module func (s Store) UpdateComposeModule(ctx context.Context, rr ...*types.Module) error { - return s.config.ErrorHandler(s.PartialUpdateComposeModule(ctx, nil, rr...)) + return s.config.ErrorHandler(s.PartialComposeModuleUpdate(ctx, nil, rr...)) } -// PartialUpdateComposeModule updates one or more existing rows in compose_module -// -// It wraps the update into transaction and can perform partial update by providing list of updatable columns -func (s Store) PartialUpdateComposeModule(ctx context.Context, onlyColumns []string, rr ...*types.Module) (err error) { +// PartialComposeModuleUpdate updates one or more existing rows in compose_module +func (s Store) PartialComposeModuleUpdate(ctx context.Context, onlyColumns []string, rr ...*types.Module) (err error) { for _, res := range rr { - err = s.ExecUpdateComposeModules( + err = s.checkComposeModuleConstraints(ctx, res) + if err != nil { + return err + } + + // err = s.composeModuleHook(ctx, TriggerBeforeComposeModuleUpdate, res) + // if err != nil { + // return err + // } + + err = s.execUpdateComposeModules( ctx, - squirrel.Eq{s.preprocessColumn("cmd.id", ""): s.preprocessValue(res.ID, "")}, + squirrel.Eq{ + s.preprocessColumn("cmd.id", ""): s.preprocessValue(res.ID, ""), + }, s.internalComposeModuleEncoder(res).Skip("id").Only(onlyColumns...)) if err != nil { return s.config.ErrorHandler(err) @@ -271,10 +316,39 @@ func (s Store) PartialUpdateComposeModule(ctx context.Context, onlyColumns []str return } -// RemoveComposeModule removes one or more rows from compose_module table -func (s Store) RemoveComposeModule(ctx context.Context, rr ...*types.Module) (err error) { +// UpsertComposeModule updates one or more existing rows in compose_module +func (s Store) UpsertComposeModule(ctx context.Context, rr ...*types.Module) (err error) { for _, res := range rr { - err = ExecuteSqlizer(ctx, s.DB(), s.Delete(s.ComposeModuleTable("cmd")).Where(squirrel.Eq{s.preprocessColumn("cmd.id", ""): s.preprocessValue(res.ID, "")})) + err = s.checkComposeModuleConstraints(ctx, res) + if err != nil { + return err + } + + // err = s.composeModuleHook(ctx, TriggerBeforeComposeModuleUpsert, res) + // if err != nil { + // return err + // } + + err = s.config.ErrorHandler(s.execUpsertComposeModules(ctx, s.internalComposeModuleEncoder(res))) + if err != nil { + return err + } + } + + return nil +} + +// DeleteComposeModule Deletes one or more rows from compose_module table +func (s Store) DeleteComposeModule(ctx context.Context, rr ...*types.Module) (err error) { + for _, res := range rr { + // err = s.composeModuleHook(ctx, TriggerBeforeComposeModuleDelete, res) + // if err != nil { + // return err + // } + + err = s.execDeleteComposeModules(ctx, squirrel.Eq{ + s.preprocessColumn("cmd.id", ""): s.preprocessValue(res.ID, ""), + }) if err != nil { return s.config.ErrorHandler(err) } @@ -283,35 +357,74 @@ func (s Store) RemoveComposeModule(ctx context.Context, rr ...*types.Module) (er return nil } -// RemoveComposeModuleByID removes row from the compose_module table -func (s Store) RemoveComposeModuleByID(ctx context.Context, ID uint64) error { - return s.config.ErrorHandler(ExecuteSqlizer(ctx, s.DB(), s.Delete(s.ComposeModuleTable("cmd")).Where(squirrel.Eq{s.preprocessColumn("cmd.id", ""): s.preprocessValue(ID, "")}))) +// DeleteComposeModuleByID Deletes row from the compose_module table +func (s Store) DeleteComposeModuleByID(ctx context.Context, ID uint64) error { + return s.execDeleteComposeModules(ctx, squirrel.Eq{ + s.preprocessColumn("cmd.id", ""): s.preprocessValue(ID, ""), + }) } -// TruncateComposeModules removes all rows from the compose_module table +// TruncateComposeModules Deletes all rows from the compose_module table func (s Store) TruncateComposeModules(ctx context.Context) error { - return s.config.ErrorHandler(Truncate(ctx, s.DB(), s.ComposeModuleTable())) + return s.config.ErrorHandler(s.Truncate(ctx, s.composeModuleTable())) } -// ExecUpdateComposeModules updates all matched (by cnd) rows in compose_module with given data -func (s Store) ExecUpdateComposeModules(ctx context.Context, cnd squirrel.Sqlizer, set store.Payload) error { - return s.config.ErrorHandler(ExecuteSqlizer(ctx, s.DB(), s.Update(s.ComposeModuleTable("cmd")).Where(cnd).SetMap(set))) -} - -// ComposeModuleLookup prepares ComposeModule query and executes it, +// execLookupComposeModule prepares ComposeModule query and executes it, // returning types.Module (or error) -func (s Store) ComposeModuleLookup(ctx context.Context, cnd squirrel.Sqlizer) (*types.Module, error) { - return s.internalComposeModuleRowScanner(s.QueryRow(ctx, s.QueryComposeModules().Where(cnd))) -} +func (s Store) execLookupComposeModule(ctx context.Context, cnd squirrel.Sqlizer) (res *types.Module, err error) { + var ( + row rowScanner + ) -func (s Store) internalComposeModuleRowScanner(row rowScanner, err error) (*types.Module, error) { + row, err = s.QueryRow(ctx, s.composeModulesSelectBuilder().Where(cnd)) if err != nil { - return nil, err + return } - var res = &types.Module{} + res, err = s.internalComposeModuleRowScanner(row) + if err != nil { + return + } + + return res, nil +} + +// execCreateComposeModules updates all matched (by cnd) rows in compose_module with given data +func (s Store) execCreateComposeModules(ctx context.Context, payload store.Payload) error { + return s.config.ErrorHandler(s.Exec(ctx, s.InsertBuilder(s.composeModuleTable()).SetMap(payload))) +} + +// execUpdateComposeModules updates all matched (by cnd) rows in compose_module with given data +func (s Store) execUpdateComposeModules(ctx context.Context, cnd squirrel.Sqlizer, set store.Payload) error { + return s.config.ErrorHandler(s.Exec(ctx, s.UpdateBuilder(s.composeModuleTable("cmd")).Where(cnd).SetMap(set))) +} + +// execUpsertComposeModules inserts new or updates matching (by-primary-key) rows in compose_module with given data +func (s Store) execUpsertComposeModules(ctx context.Context, set store.Payload) error { + upsert, err := s.config.UpsertBuilder( + s.config, + s.composeModuleTable(), + set, + "id", + ) + + if err != nil { + return err + } + + return s.config.ErrorHandler(s.Exec(ctx, upsert)) +} + +// execDeleteComposeModules Deletes all matched (by cnd) rows in compose_module with given data +func (s Store) execDeleteComposeModules(ctx context.Context, cnd squirrel.Sqlizer) error { + return s.config.ErrorHandler(s.Exec(ctx, s.DeleteBuilder(s.composeModuleTable("cmd")).Where(cnd))) +} + +func (s Store) internalComposeModuleRowScanner(row rowScanner) (res *types.Module, err error) { + res = &types.Module{} + if _, has := s.config.RowScanners["composeModule"]; has { - scanner := s.config.RowScanners["composeModule"].(func(rowScanner, *types.Module) error) + scanner := s.config.RowScanners["composeModule"].(func(_ rowScanner, _ *types.Module) error) err = scanner(row, res) } else { err = row.Scan( @@ -338,12 +451,12 @@ func (s Store) internalComposeModuleRowScanner(row rowScanner, err error) (*type } // QueryComposeModules returns squirrel.SelectBuilder with set table and all columns -func (s Store) QueryComposeModules() squirrel.SelectBuilder { - return s.Select(s.ComposeModuleTable("cmd"), s.ComposeModuleColumns("cmd")...) +func (s Store) composeModulesSelectBuilder() squirrel.SelectBuilder { + return s.SelectBuilder(s.composeModuleTable("cmd"), s.composeModuleColumns("cmd")...) } -// ComposeModuleTable name of the db table -func (Store) ComposeModuleTable(aa ...string) string { +// composeModuleTable name of the db table +func (Store) composeModuleTable(aa ...string) string { var alias string if len(aa) > 0 { alias = " AS " + aa[0] @@ -355,7 +468,7 @@ func (Store) ComposeModuleTable(aa ...string) string { // ComposeModuleColumns returns all defined table columns // // With optional string arg, all columns are returned aliased -func (Store) ComposeModuleColumns(aa ...string) []string { +func (Store) composeModuleColumns(aa ...string) []string { var alias string if len(aa) > 0 { alias = aa[0] + "." @@ -373,7 +486,7 @@ func (Store) ComposeModuleColumns(aa ...string) []string { } } -// {false false false false} +// {true true true true true} // sortableComposeModuleColumns returns all ComposeModule columns flagged as sortable // @@ -406,9 +519,9 @@ func (s Store) internalComposeModuleEncoder(res *types.Module) store.Payload { } } -func (s Store) collectComposeModuleCursorValues(res *types.Module, cc ...string) *store.PagingCursor { +func (s Store) collectComposeModuleCursorValues(res *types.Module, cc ...string) *filter.PagingCursor { var ( - cursor = &store.PagingCursor{} + cursor = &filter.PagingCursor{} hasUnique bool @@ -443,3 +556,25 @@ func (s Store) collectComposeModuleCursorValues(res *types.Module, cc ...string) return cursor } + +func (s *Store) checkComposeModuleConstraints(ctx context.Context, res *types.Module) error { + + { + ex, err := s.LookupComposeModuleByNamespaceIDHandle(ctx, res.NamespaceID, res.Handle) + if err == nil && ex != nil && ex.ID != res.ID { + return store.ErrNotUnique + } else if !errors.Is(err, store.ErrNotFound) { + return err + } + } + + return nil +} + +// func (s *Store) composeModuleHook(ctx context.Context, key triggerKey, res *types.Module) error { +// if fn, has := s.config.TriggerHandlers[key]; has { +// return fn.(func (ctx context.Context, s *Store, res *types.Module) error)(ctx, s, res) +// } +// +// return nil +// } diff --git a/store/rdbms/compose_modules.go b/store/rdbms/compose_modules.go index f161b2071..7dc0495d9 100644 --- a/store/rdbms/compose_modules.go +++ b/store/rdbms/compose_modules.go @@ -8,7 +8,7 @@ import ( ) func (s Store) convertComposeModuleFilter(f types.ModuleFilter) (query squirrel.SelectBuilder, err error) { - query = s.QueryComposeModules() + query = s.composeModulesSelectBuilder() query = rh.FilterNullByState(query, "cmd.deleted_at", f.Deleted) diff --git a/store/rdbms/compose_modules_fields.go b/store/rdbms/compose_modules_fields.go new file mode 100644 index 000000000..eff1a0a3a --- /dev/null +++ b/store/rdbms/compose_modules_fields.go @@ -0,0 +1,22 @@ +package rdbms + +import ( + "fmt" + "github.com/Masterminds/squirrel" + "github.com/cortezaproject/corteza-server/compose/types" + "github.com/cortezaproject/corteza-server/pkg/rh" +) + +func (s Store) convertComposeModuleFieldFilter(f types.ModuleFieldFilter) (query squirrel.SelectBuilder, err error) { + query = s.composeModuleFieldsSelectBuilder() + + if len(f.ModuleID) == 0 { + err = fmt.Errorf("can not search for module fields without module IDs") + return + } + + query = rh.FilterNullByState(query, "cmf.deleted_at", f.Deleted) + query = query.Where(squirrel.Eq{"cmf.rel_module": f.ModuleID}) + + return +} diff --git a/store/rdbms/compose_namespaces.gen.go b/store/rdbms/compose_namespaces.gen.go index dd2c6499e..5d1f2eb04 100644 --- a/store/rdbms/compose_namespaces.gen.go +++ b/store/rdbms/compose_namespaces.gen.go @@ -11,13 +11,24 @@ package rdbms import ( "context" "database/sql" + "errors" "fmt" "github.com/Masterminds/squirrel" "github.com/cortezaproject/corteza-server/compose/types" + "github.com/cortezaproject/corteza-server/pkg/filter" "github.com/cortezaproject/corteza-server/store" "strings" ) +var _ = errors.Is + +const ( + TriggerBeforeComposeNamespaceCreate triggerKey = "composeNamespaceBeforeCreate" + TriggerBeforeComposeNamespaceUpdate triggerKey = "composeNamespaceBeforeUpdate" + TriggerBeforeComposeNamespaceUpsert triggerKey = "composeNamespaceBeforeUpsert" + TriggerBeforeComposeNamespaceDelete triggerKey = "composeNamespaceBeforeDelete" +) + // SearchComposeNamespaces returns all matching rows // // This function calls convertComposeNamespaceFilter with the given @@ -38,7 +49,7 @@ func (s Store) SearchComposeNamespaces(ctx context.Context, f types.NamespaceFil // This tells us to flip the descending flag on all used sort keys reverseCursor := f.PageCursor != nil && f.PageCursor.Reverse - if err = f.Sort.Validate(s.sortableComposeNamespaceColumns()...); err != nil { + if err := f.Sort.Validate(s.sortableComposeNamespaceColumns()...); err != nil { return nil, f, fmt.Errorf("could not validate sort: %v", err) } @@ -68,7 +79,7 @@ func (s Store) SearchComposeNamespaces(ctx context.Context, f types.NamespaceFil var ( set = make([]*types.Namespace, 0, scap) - // fetches rows and scans them into Types.Namespace resource this is then passed to Check function on filter + // fetches rows and scans them into types.Namespace resource this is then passed to Check function on filter // to help determine if fetched resource fits or not // // Note that limit is passed explicitly and is not necessarily equal to filter's limit. We want @@ -77,7 +88,7 @@ func (s Store) SearchComposeNamespaces(ctx context.Context, f types.NamespaceFil // The value for cursor is used and set directly from/to the filter! // // It returns total number of fetched pages and modifies PageCursor value for paging - fetchPage = func(cursor *store.PagingCursor, limit uint) (fetched uint, err error) { + fetchPage = func(cursor *filter.PagingCursor, limit uint) (fetched uint, err error) { var ( res *types.Namespace @@ -108,7 +119,12 @@ func (s Store) SearchComposeNamespaces(ctx context.Context, f types.NamespaceFil for rows.Next() { fetched++ - if res, err = s.internalComposeNamespaceRowScanner(rows, rows.Err()); err != nil { + + if rows.Err() == nil { + res, err = s.internalComposeNamespaceRowScanner(rows) + } + + if err != nil { if cerr := rows.Close(); cerr != nil { err = fmt.Errorf("could not close rows (%v) after scan error: %w", cerr, err) } @@ -223,8 +239,8 @@ func (s Store) SearchComposeNamespaces(ctx context.Context, f types.NamespaceFil // LookupComposeNamespaceBySlug searches for namespace by slug (case-insensitive) func (s Store) LookupComposeNamespaceBySlug(ctx context.Context, slug string) (*types.Namespace, error) { - return s.ComposeNamespaceLookup(ctx, squirrel.Eq{ - "cns.slug": slug, + return s.execLookupComposeNamespace(ctx, squirrel.Eq{ + s.preprocessColumn("cns.slug", "lower"): s.preprocessValue(slug, "lower"), }) } @@ -232,17 +248,27 @@ func (s Store) LookupComposeNamespaceBySlug(ctx context.Context, slug string) (* // // It returns compose namespace even if deleted func (s Store) LookupComposeNamespaceByID(ctx context.Context, id uint64) (*types.Namespace, error) { - return s.ComposeNamespaceLookup(ctx, squirrel.Eq{ - "cns.id": id, + return s.execLookupComposeNamespace(ctx, squirrel.Eq{ + s.preprocessColumn("cns.id", ""): s.preprocessValue(id, ""), }) } // CreateComposeNamespace creates one or more rows in compose_namespace table func (s Store) CreateComposeNamespace(ctx context.Context, rr ...*types.Namespace) (err error) { for _, res := range rr { - err = ExecuteSqlizer(ctx, s.DB(), s.Insert(s.ComposeNamespaceTable()).SetMap(s.internalComposeNamespaceEncoder(res))) + err = s.checkComposeNamespaceConstraints(ctx, res) if err != nil { - return s.config.ErrorHandler(err) + return err + } + + // err = s.composeNamespaceHook(ctx, TriggerBeforeComposeNamespaceCreate, res) + // if err != nil { + // return err + // } + + err = s.execCreateComposeNamespaces(ctx, s.internalComposeNamespaceEncoder(res)) + if err != nil { + return err } } @@ -251,17 +277,27 @@ func (s Store) CreateComposeNamespace(ctx context.Context, rr ...*types.Namespac // UpdateComposeNamespace updates one or more existing rows in compose_namespace func (s Store) UpdateComposeNamespace(ctx context.Context, rr ...*types.Namespace) error { - return s.config.ErrorHandler(s.PartialUpdateComposeNamespace(ctx, nil, rr...)) + return s.config.ErrorHandler(s.PartialComposeNamespaceUpdate(ctx, nil, rr...)) } -// PartialUpdateComposeNamespace updates one or more existing rows in compose_namespace -// -// It wraps the update into transaction and can perform partial update by providing list of updatable columns -func (s Store) PartialUpdateComposeNamespace(ctx context.Context, onlyColumns []string, rr ...*types.Namespace) (err error) { +// PartialComposeNamespaceUpdate updates one or more existing rows in compose_namespace +func (s Store) PartialComposeNamespaceUpdate(ctx context.Context, onlyColumns []string, rr ...*types.Namespace) (err error) { for _, res := range rr { - err = s.ExecUpdateComposeNamespaces( + err = s.checkComposeNamespaceConstraints(ctx, res) + if err != nil { + return err + } + + // err = s.composeNamespaceHook(ctx, TriggerBeforeComposeNamespaceUpdate, res) + // if err != nil { + // return err + // } + + err = s.execUpdateComposeNamespaces( ctx, - squirrel.Eq{s.preprocessColumn("cns.id", ""): s.preprocessValue(res.ID, "")}, + squirrel.Eq{ + s.preprocessColumn("cns.id", ""): s.preprocessValue(res.ID, ""), + }, s.internalComposeNamespaceEncoder(res).Skip("id").Only(onlyColumns...)) if err != nil { return s.config.ErrorHandler(err) @@ -271,10 +307,39 @@ func (s Store) PartialUpdateComposeNamespace(ctx context.Context, onlyColumns [] return } -// RemoveComposeNamespace removes one or more rows from compose_namespace table -func (s Store) RemoveComposeNamespace(ctx context.Context, rr ...*types.Namespace) (err error) { +// UpsertComposeNamespace updates one or more existing rows in compose_namespace +func (s Store) UpsertComposeNamespace(ctx context.Context, rr ...*types.Namespace) (err error) { for _, res := range rr { - err = ExecuteSqlizer(ctx, s.DB(), s.Delete(s.ComposeNamespaceTable("cns")).Where(squirrel.Eq{s.preprocessColumn("cns.id", ""): s.preprocessValue(res.ID, "")})) + err = s.checkComposeNamespaceConstraints(ctx, res) + if err != nil { + return err + } + + // err = s.composeNamespaceHook(ctx, TriggerBeforeComposeNamespaceUpsert, res) + // if err != nil { + // return err + // } + + err = s.config.ErrorHandler(s.execUpsertComposeNamespaces(ctx, s.internalComposeNamespaceEncoder(res))) + if err != nil { + return err + } + } + + return nil +} + +// DeleteComposeNamespace Deletes one or more rows from compose_namespace table +func (s Store) DeleteComposeNamespace(ctx context.Context, rr ...*types.Namespace) (err error) { + for _, res := range rr { + // err = s.composeNamespaceHook(ctx, TriggerBeforeComposeNamespaceDelete, res) + // if err != nil { + // return err + // } + + err = s.execDeleteComposeNamespaces(ctx, squirrel.Eq{ + s.preprocessColumn("cns.id", ""): s.preprocessValue(res.ID, ""), + }) if err != nil { return s.config.ErrorHandler(err) } @@ -283,35 +348,74 @@ func (s Store) RemoveComposeNamespace(ctx context.Context, rr ...*types.Namespac return nil } -// RemoveComposeNamespaceByID removes row from the compose_namespace table -func (s Store) RemoveComposeNamespaceByID(ctx context.Context, ID uint64) error { - return s.config.ErrorHandler(ExecuteSqlizer(ctx, s.DB(), s.Delete(s.ComposeNamespaceTable("cns")).Where(squirrel.Eq{s.preprocessColumn("cns.id", ""): s.preprocessValue(ID, "")}))) +// DeleteComposeNamespaceByID Deletes row from the compose_namespace table +func (s Store) DeleteComposeNamespaceByID(ctx context.Context, ID uint64) error { + return s.execDeleteComposeNamespaces(ctx, squirrel.Eq{ + s.preprocessColumn("cns.id", ""): s.preprocessValue(ID, ""), + }) } -// TruncateComposeNamespaces removes all rows from the compose_namespace table +// TruncateComposeNamespaces Deletes all rows from the compose_namespace table func (s Store) TruncateComposeNamespaces(ctx context.Context) error { - return s.config.ErrorHandler(Truncate(ctx, s.DB(), s.ComposeNamespaceTable())) + return s.config.ErrorHandler(s.Truncate(ctx, s.composeNamespaceTable())) } -// ExecUpdateComposeNamespaces updates all matched (by cnd) rows in compose_namespace with given data -func (s Store) ExecUpdateComposeNamespaces(ctx context.Context, cnd squirrel.Sqlizer, set store.Payload) error { - return s.config.ErrorHandler(ExecuteSqlizer(ctx, s.DB(), s.Update(s.ComposeNamespaceTable("cns")).Where(cnd).SetMap(set))) -} - -// ComposeNamespaceLookup prepares ComposeNamespace query and executes it, +// execLookupComposeNamespace prepares ComposeNamespace query and executes it, // returning types.Namespace (or error) -func (s Store) ComposeNamespaceLookup(ctx context.Context, cnd squirrel.Sqlizer) (*types.Namespace, error) { - return s.internalComposeNamespaceRowScanner(s.QueryRow(ctx, s.QueryComposeNamespaces().Where(cnd))) -} +func (s Store) execLookupComposeNamespace(ctx context.Context, cnd squirrel.Sqlizer) (res *types.Namespace, err error) { + var ( + row rowScanner + ) -func (s Store) internalComposeNamespaceRowScanner(row rowScanner, err error) (*types.Namespace, error) { + row, err = s.QueryRow(ctx, s.composeNamespacesSelectBuilder().Where(cnd)) if err != nil { - return nil, err + return } - var res = &types.Namespace{} + res, err = s.internalComposeNamespaceRowScanner(row) + if err != nil { + return + } + + return res, nil +} + +// execCreateComposeNamespaces updates all matched (by cnd) rows in compose_namespace with given data +func (s Store) execCreateComposeNamespaces(ctx context.Context, payload store.Payload) error { + return s.config.ErrorHandler(s.Exec(ctx, s.InsertBuilder(s.composeNamespaceTable()).SetMap(payload))) +} + +// execUpdateComposeNamespaces updates all matched (by cnd) rows in compose_namespace with given data +func (s Store) execUpdateComposeNamespaces(ctx context.Context, cnd squirrel.Sqlizer, set store.Payload) error { + return s.config.ErrorHandler(s.Exec(ctx, s.UpdateBuilder(s.composeNamespaceTable("cns")).Where(cnd).SetMap(set))) +} + +// execUpsertComposeNamespaces inserts new or updates matching (by-primary-key) rows in compose_namespace with given data +func (s Store) execUpsertComposeNamespaces(ctx context.Context, set store.Payload) error { + upsert, err := s.config.UpsertBuilder( + s.config, + s.composeNamespaceTable(), + set, + "id", + ) + + if err != nil { + return err + } + + return s.config.ErrorHandler(s.Exec(ctx, upsert)) +} + +// execDeleteComposeNamespaces Deletes all matched (by cnd) rows in compose_namespace with given data +func (s Store) execDeleteComposeNamespaces(ctx context.Context, cnd squirrel.Sqlizer) error { + return s.config.ErrorHandler(s.Exec(ctx, s.DeleteBuilder(s.composeNamespaceTable("cns")).Where(cnd))) +} + +func (s Store) internalComposeNamespaceRowScanner(row rowScanner) (res *types.Namespace, err error) { + res = &types.Namespace{} + if _, has := s.config.RowScanners["composeNamespace"]; has { - scanner := s.config.RowScanners["composeNamespace"].(func(rowScanner, *types.Namespace) error) + scanner := s.config.RowScanners["composeNamespace"].(func(_ rowScanner, _ *types.Namespace) error) err = scanner(row, res) } else { err = row.Scan( @@ -338,12 +442,12 @@ func (s Store) internalComposeNamespaceRowScanner(row rowScanner, err error) (*t } // QueryComposeNamespaces returns squirrel.SelectBuilder with set table and all columns -func (s Store) QueryComposeNamespaces() squirrel.SelectBuilder { - return s.Select(s.ComposeNamespaceTable("cns"), s.ComposeNamespaceColumns("cns")...) +func (s Store) composeNamespacesSelectBuilder() squirrel.SelectBuilder { + return s.SelectBuilder(s.composeNamespaceTable("cns"), s.composeNamespaceColumns("cns")...) } -// ComposeNamespaceTable name of the db table -func (Store) ComposeNamespaceTable(aa ...string) string { +// composeNamespaceTable name of the db table +func (Store) composeNamespaceTable(aa ...string) string { var alias string if len(aa) > 0 { alias = " AS " + aa[0] @@ -355,7 +459,7 @@ func (Store) ComposeNamespaceTable(aa ...string) string { // ComposeNamespaceColumns returns all defined table columns // // With optional string arg, all columns are returned aliased -func (Store) ComposeNamespaceColumns(aa ...string) []string { +func (Store) composeNamespaceColumns(aa ...string) []string { var alias string if len(aa) > 0 { alias = aa[0] + "." @@ -373,7 +477,7 @@ func (Store) ComposeNamespaceColumns(aa ...string) []string { } } -// {false false false false} +// {true true true true true} // sortableComposeNamespaceColumns returns all ComposeNamespace columns flagged as sortable // @@ -406,9 +510,9 @@ func (s Store) internalComposeNamespaceEncoder(res *types.Namespace) store.Paylo } } -func (s Store) collectComposeNamespaceCursorValues(res *types.Namespace, cc ...string) *store.PagingCursor { +func (s Store) collectComposeNamespaceCursorValues(res *types.Namespace, cc ...string) *filter.PagingCursor { var ( - cursor = &store.PagingCursor{} + cursor = &filter.PagingCursor{} hasUnique bool @@ -443,3 +547,25 @@ func (s Store) collectComposeNamespaceCursorValues(res *types.Namespace, cc ...s return cursor } + +func (s *Store) checkComposeNamespaceConstraints(ctx context.Context, res *types.Namespace) error { + + { + ex, err := s.LookupComposeNamespaceBySlug(ctx, res.Slug) + if err == nil && ex != nil && ex.ID != res.ID { + return store.ErrNotUnique + } else if !errors.Is(err, store.ErrNotFound) { + return err + } + } + + return nil +} + +// func (s *Store) composeNamespaceHook(ctx context.Context, key triggerKey, res *types.Namespace) error { +// if fn, has := s.config.TriggerHandlers[key]; has { +// return fn.(func (ctx context.Context, s *Store, res *types.Namespace) error)(ctx, s, res) +// } +// +// return nil +// } diff --git a/store/rdbms/compose_namespaces.go b/store/rdbms/compose_namespaces.go index caddb1419..d1c961f3d 100644 --- a/store/rdbms/compose_namespaces.go +++ b/store/rdbms/compose_namespaces.go @@ -8,7 +8,7 @@ import ( ) func (s Store) convertComposeNamespaceFilter(f types.NamespaceFilter) (query squirrel.SelectBuilder, err error) { - query = s.QueryComposeNamespaces() + query = s.composeNamespacesSelectBuilder() query = rh.FilterNullByState(query, "cns.deleted_at", f.Deleted) diff --git a/store/rdbms/compose_pages.gen.go b/store/rdbms/compose_pages.gen.go index 1ed5de63d..5dbcb0c3a 100644 --- a/store/rdbms/compose_pages.gen.go +++ b/store/rdbms/compose_pages.gen.go @@ -11,13 +11,24 @@ package rdbms import ( "context" "database/sql" + "errors" "fmt" "github.com/Masterminds/squirrel" "github.com/cortezaproject/corteza-server/compose/types" + "github.com/cortezaproject/corteza-server/pkg/filter" "github.com/cortezaproject/corteza-server/store" "strings" ) +var _ = errors.Is + +const ( + TriggerBeforeComposePageCreate triggerKey = "composePageBeforeCreate" + TriggerBeforeComposePageUpdate triggerKey = "composePageBeforeUpdate" + TriggerBeforeComposePageUpsert triggerKey = "composePageBeforeUpsert" + TriggerBeforeComposePageDelete triggerKey = "composePageBeforeDelete" +) + // SearchComposePages returns all matching rows // // This function calls convertComposePageFilter with the given @@ -38,7 +49,7 @@ func (s Store) SearchComposePages(ctx context.Context, f types.PageFilter) (type // This tells us to flip the descending flag on all used sort keys reverseCursor := f.PageCursor != nil && f.PageCursor.Reverse - if err = f.Sort.Validate(s.sortableComposePageColumns()...); err != nil { + if err := f.Sort.Validate(s.sortableComposePageColumns()...); err != nil { return nil, f, fmt.Errorf("could not validate sort: %v", err) } @@ -68,7 +79,7 @@ func (s Store) SearchComposePages(ctx context.Context, f types.PageFilter) (type var ( set = make([]*types.Page, 0, scap) - // fetches rows and scans them into Types.Page resource this is then passed to Check function on filter + // fetches rows and scans them into types.Page resource this is then passed to Check function on filter // to help determine if fetched resource fits or not // // Note that limit is passed explicitly and is not necessarily equal to filter's limit. We want @@ -77,7 +88,7 @@ func (s Store) SearchComposePages(ctx context.Context, f types.PageFilter) (type // The value for cursor is used and set directly from/to the filter! // // It returns total number of fetched pages and modifies PageCursor value for paging - fetchPage = func(cursor *store.PagingCursor, limit uint) (fetched uint, err error) { + fetchPage = func(cursor *filter.PagingCursor, limit uint) (fetched uint, err error) { var ( res *types.Page @@ -108,7 +119,12 @@ func (s Store) SearchComposePages(ctx context.Context, f types.PageFilter) (type for rows.Next() { fetched++ - if res, err = s.internalComposePageRowScanner(rows, rows.Err()); err != nil { + + if rows.Err() == nil { + res, err = s.internalComposePageRowScanner(rows) + } + + if err != nil { if cerr := rows.Close(); cerr != nil { err = fmt.Errorf("could not close rows (%v) after scan error: %w", cerr, err) } @@ -221,10 +237,19 @@ func (s Store) SearchComposePages(ctx context.Context, f types.PageFilter) (type return set, f, s.config.ErrorHandler(fetch()) } -// LookupComposePageByHandle searches for page chart by handle (case-insensitive) -func (s Store) LookupComposePageByHandle(ctx context.Context, handle string) (*types.Page, error) { - return s.ComposePageLookup(ctx, squirrel.Eq{ - "cpg.handle": handle, +// LookupComposePageByNamespaceIDHandle searches for page by handle (case-insensitive) +func (s Store) LookupComposePageByNamespaceIDHandle(ctx context.Context, namespace_id uint64, handle string) (*types.Page, error) { + return s.execLookupComposePage(ctx, squirrel.Eq{ + s.preprocessColumn("cpg.rel_namespace", ""): s.preprocessValue(namespace_id, ""), + s.preprocessColumn("cpg.handle", "lower"): s.preprocessValue(handle, "lower"), + }) +} + +// LookupComposePageByNamespaceIDModuleID searches for page by moduleID +func (s Store) LookupComposePageByNamespaceIDModuleID(ctx context.Context, namespace_id uint64, module_id uint64) (*types.Page, error) { + return s.execLookupComposePage(ctx, squirrel.Eq{ + s.preprocessColumn("cpg.rel_namespace", ""): s.preprocessValue(namespace_id, ""), + s.preprocessColumn("cpg.rel_module", ""): s.preprocessValue(module_id, ""), }) } @@ -232,17 +257,27 @@ func (s Store) LookupComposePageByHandle(ctx context.Context, handle string) (*t // // It returns compose page even if deleted func (s Store) LookupComposePageByID(ctx context.Context, id uint64) (*types.Page, error) { - return s.ComposePageLookup(ctx, squirrel.Eq{ - "cpg.id": id, + return s.execLookupComposePage(ctx, squirrel.Eq{ + s.preprocessColumn("cpg.id", ""): s.preprocessValue(id, ""), }) } // CreateComposePage creates one or more rows in compose_page table func (s Store) CreateComposePage(ctx context.Context, rr ...*types.Page) (err error) { for _, res := range rr { - err = ExecuteSqlizer(ctx, s.DB(), s.Insert(s.ComposePageTable()).SetMap(s.internalComposePageEncoder(res))) + err = s.checkComposePageConstraints(ctx, res) if err != nil { - return s.config.ErrorHandler(err) + return err + } + + // err = s.composePageHook(ctx, TriggerBeforeComposePageCreate, res) + // if err != nil { + // return err + // } + + err = s.execCreateComposePages(ctx, s.internalComposePageEncoder(res)) + if err != nil { + return err } } @@ -251,17 +286,27 @@ func (s Store) CreateComposePage(ctx context.Context, rr ...*types.Page) (err er // UpdateComposePage updates one or more existing rows in compose_page func (s Store) UpdateComposePage(ctx context.Context, rr ...*types.Page) error { - return s.config.ErrorHandler(s.PartialUpdateComposePage(ctx, nil, rr...)) + return s.config.ErrorHandler(s.PartialComposePageUpdate(ctx, nil, rr...)) } -// PartialUpdateComposePage updates one or more existing rows in compose_page -// -// It wraps the update into transaction and can perform partial update by providing list of updatable columns -func (s Store) PartialUpdateComposePage(ctx context.Context, onlyColumns []string, rr ...*types.Page) (err error) { +// PartialComposePageUpdate updates one or more existing rows in compose_page +func (s Store) PartialComposePageUpdate(ctx context.Context, onlyColumns []string, rr ...*types.Page) (err error) { for _, res := range rr { - err = s.ExecUpdateComposePages( + err = s.checkComposePageConstraints(ctx, res) + if err != nil { + return err + } + + // err = s.composePageHook(ctx, TriggerBeforeComposePageUpdate, res) + // if err != nil { + // return err + // } + + err = s.execUpdateComposePages( ctx, - squirrel.Eq{s.preprocessColumn("cpg.id", ""): s.preprocessValue(res.ID, "")}, + squirrel.Eq{ + s.preprocessColumn("cpg.id", ""): s.preprocessValue(res.ID, ""), + }, s.internalComposePageEncoder(res).Skip("id").Only(onlyColumns...)) if err != nil { return s.config.ErrorHandler(err) @@ -271,10 +316,39 @@ func (s Store) PartialUpdateComposePage(ctx context.Context, onlyColumns []strin return } -// RemoveComposePage removes one or more rows from compose_page table -func (s Store) RemoveComposePage(ctx context.Context, rr ...*types.Page) (err error) { +// UpsertComposePage updates one or more existing rows in compose_page +func (s Store) UpsertComposePage(ctx context.Context, rr ...*types.Page) (err error) { for _, res := range rr { - err = ExecuteSqlizer(ctx, s.DB(), s.Delete(s.ComposePageTable("cpg")).Where(squirrel.Eq{s.preprocessColumn("cpg.id", ""): s.preprocessValue(res.ID, "")})) + err = s.checkComposePageConstraints(ctx, res) + if err != nil { + return err + } + + // err = s.composePageHook(ctx, TriggerBeforeComposePageUpsert, res) + // if err != nil { + // return err + // } + + err = s.config.ErrorHandler(s.execUpsertComposePages(ctx, s.internalComposePageEncoder(res))) + if err != nil { + return err + } + } + + return nil +} + +// DeleteComposePage Deletes one or more rows from compose_page table +func (s Store) DeleteComposePage(ctx context.Context, rr ...*types.Page) (err error) { + for _, res := range rr { + // err = s.composePageHook(ctx, TriggerBeforeComposePageDelete, res) + // if err != nil { + // return err + // } + + err = s.execDeleteComposePages(ctx, squirrel.Eq{ + s.preprocessColumn("cpg.id", ""): s.preprocessValue(res.ID, ""), + }) if err != nil { return s.config.ErrorHandler(err) } @@ -283,35 +357,74 @@ func (s Store) RemoveComposePage(ctx context.Context, rr ...*types.Page) (err er return nil } -// RemoveComposePageByID removes row from the compose_page table -func (s Store) RemoveComposePageByID(ctx context.Context, ID uint64) error { - return s.config.ErrorHandler(ExecuteSqlizer(ctx, s.DB(), s.Delete(s.ComposePageTable("cpg")).Where(squirrel.Eq{s.preprocessColumn("cpg.id", ""): s.preprocessValue(ID, "")}))) +// DeleteComposePageByID Deletes row from the compose_page table +func (s Store) DeleteComposePageByID(ctx context.Context, ID uint64) error { + return s.execDeleteComposePages(ctx, squirrel.Eq{ + s.preprocessColumn("cpg.id", ""): s.preprocessValue(ID, ""), + }) } -// TruncateComposePages removes all rows from the compose_page table +// TruncateComposePages Deletes all rows from the compose_page table func (s Store) TruncateComposePages(ctx context.Context) error { - return s.config.ErrorHandler(Truncate(ctx, s.DB(), s.ComposePageTable())) + return s.config.ErrorHandler(s.Truncate(ctx, s.composePageTable())) } -// ExecUpdateComposePages updates all matched (by cnd) rows in compose_page with given data -func (s Store) ExecUpdateComposePages(ctx context.Context, cnd squirrel.Sqlizer, set store.Payload) error { - return s.config.ErrorHandler(ExecuteSqlizer(ctx, s.DB(), s.Update(s.ComposePageTable("cpg")).Where(cnd).SetMap(set))) -} - -// ComposePageLookup prepares ComposePage query and executes it, +// execLookupComposePage prepares ComposePage query and executes it, // returning types.Page (or error) -func (s Store) ComposePageLookup(ctx context.Context, cnd squirrel.Sqlizer) (*types.Page, error) { - return s.internalComposePageRowScanner(s.QueryRow(ctx, s.QueryComposePages().Where(cnd))) -} +func (s Store) execLookupComposePage(ctx context.Context, cnd squirrel.Sqlizer) (res *types.Page, err error) { + var ( + row rowScanner + ) -func (s Store) internalComposePageRowScanner(row rowScanner, err error) (*types.Page, error) { + row, err = s.QueryRow(ctx, s.composePagesSelectBuilder().Where(cnd)) if err != nil { - return nil, err + return } - var res = &types.Page{} + res, err = s.internalComposePageRowScanner(row) + if err != nil { + return + } + + return res, nil +} + +// execCreateComposePages updates all matched (by cnd) rows in compose_page with given data +func (s Store) execCreateComposePages(ctx context.Context, payload store.Payload) error { + return s.config.ErrorHandler(s.Exec(ctx, s.InsertBuilder(s.composePageTable()).SetMap(payload))) +} + +// execUpdateComposePages updates all matched (by cnd) rows in compose_page with given data +func (s Store) execUpdateComposePages(ctx context.Context, cnd squirrel.Sqlizer, set store.Payload) error { + return s.config.ErrorHandler(s.Exec(ctx, s.UpdateBuilder(s.composePageTable("cpg")).Where(cnd).SetMap(set))) +} + +// execUpsertComposePages inserts new or updates matching (by-primary-key) rows in compose_page with given data +func (s Store) execUpsertComposePages(ctx context.Context, set store.Payload) error { + upsert, err := s.config.UpsertBuilder( + s.config, + s.composePageTable(), + set, + "id", + ) + + if err != nil { + return err + } + + return s.config.ErrorHandler(s.Exec(ctx, upsert)) +} + +// execDeleteComposePages Deletes all matched (by cnd) rows in compose_page with given data +func (s Store) execDeleteComposePages(ctx context.Context, cnd squirrel.Sqlizer) error { + return s.config.ErrorHandler(s.Exec(ctx, s.DeleteBuilder(s.composePageTable("cpg")).Where(cnd))) +} + +func (s Store) internalComposePageRowScanner(row rowScanner) (res *types.Page, err error) { + res = &types.Page{} + if _, has := s.config.RowScanners["composePage"]; has { - scanner := s.config.RowScanners["composePage"].(func(rowScanner, *types.Page) error) + scanner := s.config.RowScanners["composePage"].(func(_ rowScanner, _ *types.Page) error) err = scanner(row, res) } else { err = row.Scan( @@ -343,12 +456,12 @@ func (s Store) internalComposePageRowScanner(row rowScanner, err error) (*types. } // QueryComposePages returns squirrel.SelectBuilder with set table and all columns -func (s Store) QueryComposePages() squirrel.SelectBuilder { - return s.Select(s.ComposePageTable("cpg"), s.ComposePageColumns("cpg")...) +func (s Store) composePagesSelectBuilder() squirrel.SelectBuilder { + return s.SelectBuilder(s.composePageTable("cpg"), s.composePageColumns("cpg")...) } -// ComposePageTable name of the db table -func (Store) ComposePageTable(aa ...string) string { +// composePageTable name of the db table +func (Store) composePageTable(aa ...string) string { var alias string if len(aa) > 0 { alias = " AS " + aa[0] @@ -360,7 +473,7 @@ func (Store) ComposePageTable(aa ...string) string { // ComposePageColumns returns all defined table columns // // With optional string arg, all columns are returned aliased -func (Store) ComposePageColumns(aa ...string) []string { +func (Store) composePageColumns(aa ...string) []string { var alias string if len(aa) > 0 { alias = aa[0] + "." @@ -383,7 +496,7 @@ func (Store) ComposePageColumns(aa ...string) []string { } } -// {false false false false} +// {true true true true true} // sortableComposePageColumns returns all ComposePage columns flagged as sortable // @@ -419,9 +532,9 @@ func (s Store) internalComposePageEncoder(res *types.Page) store.Payload { } } -func (s Store) collectComposePageCursorValues(res *types.Page, cc ...string) *store.PagingCursor { +func (s Store) collectComposePageCursorValues(res *types.Page, cc ...string) *filter.PagingCursor { var ( - cursor = &store.PagingCursor{} + cursor = &filter.PagingCursor{} hasUnique bool @@ -451,3 +564,16 @@ func (s Store) collectComposePageCursorValues(res *types.Page, cc ...string) *st return cursor } + +func (s *Store) checkComposePageConstraints(ctx context.Context, res *types.Page) error { + + return nil +} + +// func (s *Store) composePageHook(ctx context.Context, key triggerKey, res *types.Page) error { +// if fn, has := s.config.TriggerHandlers[key]; has { +// return fn.(func (ctx context.Context, s *Store, res *types.Page) error)(ctx, s, res) +// } +// +// return nil +// } diff --git a/store/rdbms/compose_pages.go b/store/rdbms/compose_pages.go index 9a20dad79..270aa4107 100644 --- a/store/rdbms/compose_pages.go +++ b/store/rdbms/compose_pages.go @@ -1,14 +1,16 @@ package rdbms import ( + "context" "github.com/Masterminds/squirrel" "github.com/cortezaproject/corteza-server/compose/types" "github.com/cortezaproject/corteza-server/pkg/rh" + "github.com/cortezaproject/corteza-server/store" "strings" ) func (s Store) convertComposePageFilter(f types.PageFilter) (query squirrel.SelectBuilder, err error) { - query = s.QueryComposePages() + query = s.composePagesSelectBuilder() query = rh.FilterNullByState(query, "cpg.deleted_at", f.Deleted) @@ -37,3 +39,56 @@ func (s Store) convertComposePageFilter(f types.PageFilter) (query squirrel.Sele return } + +func (s Store) ReorderComposePages(ctx context.Context, namespaceID, parentID uint64, pageIDs []uint64) (err error) { + var ( + pages types.PageSet + pageMap = map[uint64]bool{} + weight = 1 + + f = types.PageFilter{ParentID: parentID, NamespaceID: namespaceID} + ) + + if pages, _, err = s.SearchComposePages(ctx, f); err != nil { + return + } + + for _, page := range pages { + pageMap[page.ID] = true + } + + // honor parameter first + for _, pageID := range pageIDs { + if pageMap[pageID] { + pageMap[pageID] = false + err = s.execUpdateComposePages(ctx, + squirrel.Eq{"cpg.id": pageID, "cpg.self_id": parentID}, + store.Payload{"weight": weight}) + + if err != nil { + return + } + + weight++ + } + } + + for pageID, update := range pageMap { + if !update { + continue + } + + err = s.execUpdateComposePages(ctx, + squirrel.Eq{"cpg.id": pageID, "cpg.self_id": parentID}, + store.Payload{"weight": weight}) + + if err != nil { + return + } + + weight++ + + } + + return +} diff --git a/store/rdbms/compose_record_value.go b/store/rdbms/compose_record_value.go new file mode 100644 index 000000000..c20102242 --- /dev/null +++ b/store/rdbms/compose_record_value.go @@ -0,0 +1,43 @@ +package rdbms + +import ( + "context" + "database/sql" + "errors" + "github.com/Masterminds/squirrel" + "github.com/cortezaproject/corteza-server/compose/types" +) + +func (s Store) convertComposeRecordValueFilter(m *types.Module, f types.RecordValueFilter) (query squirrel.SelectBuilder, err error) { + query = s.composeRecordValuesSelectBuilder().Where(squirrel.Eq{"crv.record_id": f.RecordID}) + + return query, nil +} + +func (s Store) ComposeRecordValueRefLookup(ctx context.Context, m *types.Module, field string, ref uint64) (uint64, error) { + q := s.composeRecordValuesSelectBuilder(). + Join(s.composeRecordTable("crd"), "crv.record_id = crd.id"). + Where(squirrel.Eq{ + "crv.name": field, + "crv.ref": ref, + "crv.deleted_at": nil, + "crd.module_id": m.ID, + "crd.deleted_at": nil, + }). + Column("record_id"). + Limit(1) + + row, err := s.QueryRow(ctx, q) + if errors.Is(err, sql.ErrNoRows) { + return 0, nil + } else if err != nil { + return 0, err + } + + var recordID uint64 + if err = row.Scan(&recordID); err != nil { + return 0, err + } + + return recordID, nil +} diff --git a/store/rdbms/compose_record_values.gen.go b/store/rdbms/compose_record_values.gen.go new file mode 100644 index 000000000..c9d0c6c7d --- /dev/null +++ b/store/rdbms/compose_record_values.gen.go @@ -0,0 +1,336 @@ +package rdbms + +// This file is an auto-generated file +// +// Template: pkg/codegen/assets/store_rdbms.gen.go.tpl +// Definitions: store/compose_record_values.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 ( + TriggerBeforeComposeRecordValueCreate triggerKey = "composeRecordValueBeforeCreate" + TriggerBeforeComposeRecordValueUpdate triggerKey = "composeRecordValueBeforeUpdate" + TriggerBeforeComposeRecordValueUpsert triggerKey = "composeRecordValueBeforeUpsert" + TriggerBeforeComposeRecordValueDelete triggerKey = "composeRecordValueBeforeDelete" +) + +// searchComposeRecordValues returns all matching rows +// +// This function calls convertComposeRecordValueFilter with the given +// types.RecordValueFilter and expects to receive a working squirrel.SelectBuilder +func (s Store) searchComposeRecordValues(ctx context.Context, _mod *types.Module, f types.RecordValueFilter) (types.RecordValueSet, types.RecordValueFilter, error) { + var scap uint + q, err := s.convertComposeRecordValueFilter(_mod, f) + if err != nil { + return nil, f, err + } + + if scap == 0 { + scap = DefaultSliceCapacity + } + + var ( + set = make([]*types.RecordValue, 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.RecordValue + rows, err = s.Query(ctx, q) + ) + + if err != nil { + return err + } + + for rows.Next() { + if rows.Err() == nil { + res, err = s.internalComposeRecordValueRowScanner(_mod, 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 + set = append(set, res) + } + + return rows.Close() + } + ) + + return set, f, s.config.ErrorHandler(fetch()) +} + +// createComposeRecordValue creates one or more rows in compose_record_value table +func (s Store) createComposeRecordValue(ctx context.Context, _mod *types.Module, rr ...*types.RecordValue) (err error) { + for _, res := range rr { + err = s.checkComposeRecordValueConstraints(ctx, _mod, res) + if err != nil { + return err + } + + // err = s.composeRecordValueHook(ctx, TriggerBeforeComposeRecordValueCreate, _mod, res) + // if err != nil { + // return err + // } + + err = s.execCreateComposeRecordValues(ctx, s.internalComposeRecordValueEncoder(res)) + if err != nil { + return err + } + } + + return +} + +// updateComposeRecordValue updates one or more existing rows in compose_record_value +func (s Store) updateComposeRecordValue(ctx context.Context, _mod *types.Module, rr ...*types.RecordValue) error { + return s.config.ErrorHandler(s.partialComposeRecordValueUpdate(ctx, _mod, nil, rr...)) +} + +// partialComposeRecordValueUpdate updates one or more existing rows in compose_record_value +func (s Store) partialComposeRecordValueUpdate(ctx context.Context, _mod *types.Module, onlyColumns []string, rr ...*types.RecordValue) (err error) { + for _, res := range rr { + err = s.checkComposeRecordValueConstraints(ctx, _mod, res) + if err != nil { + return err + } + + // err = s.composeRecordValueHook(ctx, TriggerBeforeComposeRecordValueUpdate, _mod, res) + // if err != nil { + // return err + // } + + err = s.execUpdateComposeRecordValues( + ctx, + squirrel.Eq{ + s.preprocessColumn("crv.record_id", ""): s.preprocessValue(res.RecordID, ""), s.preprocessColumn("crv.name", ""): s.preprocessValue(res.Name, ""), s.preprocessColumn("crv.place", ""): s.preprocessValue(res.Place, ""), + }, + s.internalComposeRecordValueEncoder(res).Skip("record_id", "name", "place").Only(onlyColumns...)) + if err != nil { + return s.config.ErrorHandler(err) + } + } + + return +} + +// upsertComposeRecordValue updates one or more existing rows in compose_record_value +func (s Store) upsertComposeRecordValue(ctx context.Context, _mod *types.Module, rr ...*types.RecordValue) (err error) { + for _, res := range rr { + err = s.checkComposeRecordValueConstraints(ctx, _mod, res) + if err != nil { + return err + } + + // err = s.composeRecordValueHook(ctx, TriggerBeforeComposeRecordValueUpsert, _mod, res) + // if err != nil { + // return err + // } + + err = s.config.ErrorHandler(s.execUpsertComposeRecordValues(ctx, s.internalComposeRecordValueEncoder(res))) + if err != nil { + return err + } + } + + return nil +} + +// deleteComposeRecordValue Deletes one or more rows from compose_record_value table +func (s Store) deleteComposeRecordValue(ctx context.Context, _mod *types.Module, rr ...*types.RecordValue) (err error) { + for _, res := range rr { + // err = s.composeRecordValueHook(ctx, TriggerBeforeComposeRecordValueDelete, _mod, res) + // if err != nil { + // return err + // } + + err = s.execDeleteComposeRecordValues(ctx, squirrel.Eq{ + s.preprocessColumn("crv.record_id", ""): s.preprocessValue(res.RecordID, ""), s.preprocessColumn("crv.name", ""): s.preprocessValue(res.Name, ""), s.preprocessColumn("crv.place", ""): s.preprocessValue(res.Place, ""), + }) + if err != nil { + return s.config.ErrorHandler(err) + } + } + + return nil +} + +// deleteComposeRecordValueByRecordIDNamePlace Deletes row from the compose_record_value table +func (s Store) deleteComposeRecordValueByRecordIDNamePlace(ctx context.Context, _mod *types.Module, recordID uint64, name string, place uint) error { + return s.execDeleteComposeRecordValues(ctx, squirrel.Eq{ + s.preprocessColumn("crv.record_id", ""): s.preprocessValue(recordID, ""), + s.preprocessColumn("crv.name", ""): s.preprocessValue(name, ""), + s.preprocessColumn("crv.place", ""): s.preprocessValue(place, ""), + }) +} + +// truncateComposeRecordValues Deletes all rows from the compose_record_value table +func (s Store) truncateComposeRecordValues(ctx context.Context, _mod *types.Module) error { + return s.config.ErrorHandler(s.Truncate(ctx, s.composeRecordValueTable())) +} + +// execLookupComposeRecordValue prepares ComposeRecordValue query and executes it, +// returning types.RecordValue (or error) +func (s Store) execLookupComposeRecordValue(ctx context.Context, _mod *types.Module, cnd squirrel.Sqlizer) (res *types.RecordValue, err error) { + var ( + row rowScanner + ) + + row, err = s.QueryRow(ctx, s.composeRecordValuesSelectBuilder().Where(cnd)) + if err != nil { + return + } + + res, err = s.internalComposeRecordValueRowScanner(_mod, row) + if err != nil { + return + } + + return res, nil +} + +// execCreateComposeRecordValues updates all matched (by cnd) rows in compose_record_value with given data +func (s Store) execCreateComposeRecordValues(ctx context.Context, payload store.Payload) error { + return s.config.ErrorHandler(s.Exec(ctx, s.InsertBuilder(s.composeRecordValueTable()).SetMap(payload))) +} + +// execUpdateComposeRecordValues updates all matched (by cnd) rows in compose_record_value with given data +func (s Store) execUpdateComposeRecordValues(ctx context.Context, cnd squirrel.Sqlizer, set store.Payload) error { + return s.config.ErrorHandler(s.Exec(ctx, s.UpdateBuilder(s.composeRecordValueTable("crv")).Where(cnd).SetMap(set))) +} + +// execUpsertComposeRecordValues inserts new or updates matching (by-primary-key) rows in compose_record_value with given data +func (s Store) execUpsertComposeRecordValues(ctx context.Context, set store.Payload) error { + upsert, err := s.config.UpsertBuilder( + s.config, + s.composeRecordValueTable(), + set, + "record_id", + "name", + "place", + ) + + if err != nil { + return err + } + + return s.config.ErrorHandler(s.Exec(ctx, upsert)) +} + +// execDeleteComposeRecordValues Deletes all matched (by cnd) rows in compose_record_value with given data +func (s Store) execDeleteComposeRecordValues(ctx context.Context, cnd squirrel.Sqlizer) error { + return s.config.ErrorHandler(s.Exec(ctx, s.DeleteBuilder(s.composeRecordValueTable("crv")).Where(cnd))) +} + +func (s Store) internalComposeRecordValueRowScanner(_mod *types.Module, row rowScanner) (res *types.RecordValue, err error) { + res = &types.RecordValue{} + + if _, has := s.config.RowScanners["composeRecordValue"]; has { + scanner := s.config.RowScanners["composeRecordValue"].(func(_mod *types.Module, _ rowScanner, _ *types.RecordValue) error) + err = scanner(_mod, row, res) + } else { + err = row.Scan( + &res.RecordID, + &res.Name, + &res.Place, + &res.Value, + &res.Ref, + &res.DeletedAt, + ) + } + + if err == sql.ErrNoRows { + return nil, store.ErrNotFound + } + + if err != nil { + return nil, fmt.Errorf("could not scan db row for ComposeRecordValue: %w", err) + } else { + return res, nil + } +} + +// QueryComposeRecordValues returns squirrel.SelectBuilder with set table and all columns +func (s Store) composeRecordValuesSelectBuilder() squirrel.SelectBuilder { + return s.SelectBuilder(s.composeRecordValueTable("crv"), s.composeRecordValueColumns("crv")...) +} + +// composeRecordValueTable name of the db table +func (Store) composeRecordValueTable(aa ...string) string { + var alias string + if len(aa) > 0 { + alias = " AS " + aa[0] + } + + return "compose_record_value" + alias +} + +// ComposeRecordValueColumns returns all defined table columns +// +// With optional string arg, all columns are returned aliased +func (Store) composeRecordValueColumns(aa ...string) []string { + var alias string + if len(aa) > 0 { + alias = aa[0] + "." + } + + return []string{ + alias + "record_id", + alias + "name", + alias + "place", + alias + "value", + alias + "ref", + alias + "deleted_at", + } +} + +// {true false false false false} + +// internalComposeRecordValueEncoder encodes fields from types.RecordValue to store.Payload (map) +// +// Encoding is done by using generic approach or by calling encodeComposeRecordValue +// func when rdbms.customEncoder=true +func (s Store) internalComposeRecordValueEncoder(res *types.RecordValue) store.Payload { + return store.Payload{ + "record_id": res.RecordID, + "name": res.Name, + "place": res.Place, + "value": res.Value, + "ref": res.Ref, + "deleted_at": res.DeletedAt, + } +} + +func (s *Store) checkComposeRecordValueConstraints(ctx context.Context, _mod *types.Module, res *types.RecordValue) error { + + return nil +} + +// func (s *Store) composeRecordValueHook(ctx context.Context, key triggerKey, _mod *types.Module, res *types.RecordValue) error { +// if fn, has := s.config.TriggerHandlers[key]; has { +// return fn.(func (ctx context.Context, s *Store, _mod *types.Module, res *types.RecordValue) error)(ctx, s, _mod, res) +// } +// +// return nil +// } diff --git a/store/rdbms/compose_records.gen.go b/store/rdbms/compose_records.gen.go new file mode 100644 index 000000000..6b7b26f13 --- /dev/null +++ b/store/rdbms/compose_records.gen.go @@ -0,0 +1,553 @@ +package rdbms + +// This file is an auto-generated file +// +// Template: pkg/codegen/assets/store_rdbms.gen.go.tpl +// Definitions: store/compose_records.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/pkg/filter" + "github.com/cortezaproject/corteza-server/store" + "strings" +) + +var _ = errors.Is + +const ( + TriggerBeforeComposeRecordCreate triggerKey = "composeRecordBeforeCreate" + TriggerBeforeComposeRecordUpdate triggerKey = "composeRecordBeforeUpdate" + TriggerBeforeComposeRecordUpsert triggerKey = "composeRecordBeforeUpsert" + TriggerBeforeComposeRecordDelete triggerKey = "composeRecordBeforeDelete" +) + +// searchComposeRecords returns all matching rows +// +// This function calls convertComposeRecordFilter with the given +// types.RecordFilter and expects to receive a working squirrel.SelectBuilder +func (s Store) searchComposeRecords(ctx context.Context, _mod *types.Module, f types.RecordFilter) (types.RecordSet, types.RecordFilter, error) { + var scap uint + q, err := s.convertComposeRecordFilter(_mod, f) + if err != nil { + return nil, f, err + } + + scap = f.Limit + + // Cleanup anything we've accidentally received... + f.PrevPage, f.NextPage = nil, nil + + // When cursor for a previous page is used it's marked as reversed + // This tells us to flip the descending flag on all used sort keys + reverseCursor := f.PageCursor != nil && f.PageCursor.Reverse + + if err := f.Sort.Validate(s.sortableComposeRecordColumns()...); err != nil { + return nil, f, fmt.Errorf("could not validate sort: %v", err) + } + + // If paging with reverse cursor, change the sorting + // direction for all columns we're sorting by + sort := f.Sort.Clone() + if reverseCursor { + sort.Reverse() + } + + // Apply sorting expr from filter to query + if len(sort) > 0 { + sqlSort := make([]string, len(sort)) + for i := range sort { + sqlSort[i] = sort[i].Column + if sort[i].Descending { + sqlSort[i] += " DESC" + } + } + + q = q.OrderBy(sqlSort...) + } + + if scap == 0 { + scap = DefaultSliceCapacity + } + + var ( + set = make([]*types.Record, 0, scap) + // fetches rows and scans them into types.Record resource this is then passed to Check function on filter + // to help determine if fetched resource fits or not + // + // Note that limit is passed explicitly and is not necessarily equal to filter's limit. We want + // to keep that value intact. + // + // The value for cursor is used and set directly from/to the filter! + // + // It returns total number of fetched pages and modifies PageCursor value for paging + fetchPage = func(cursor *filter.PagingCursor, limit uint) (fetched uint, err error) { + var ( + res *types.Record + + // Make a copy of the select query builder so that we don't change + // the original query + slct = q.Options() + ) + + if limit > 0 { + slct = slct.Limit(uint64(limit)) + + if cursor != nil && len(cursor.Keys()) > 0 { + const cursorTpl = `(%s) %s (?%s)` + op := ">" + if cursor.Reverse { + op = "<" + } + + pred := fmt.Sprintf(cursorTpl, strings.Join(cursor.Keys(), ", "), op, strings.Repeat(", ?", len(cursor.Keys())-1)) + slct = slct.Where(pred, cursor.Values()...) + } + } + + rows, err := s.Query(ctx, slct) + if err != nil { + return + } + + for rows.Next() { + fetched++ + + if rows.Err() == nil { + res, err = s.internalComposeRecordRowScanner(_mod, 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 + } + + // If check function is set, call it and act accordingly + + if f.Check != nil { + var chk bool + 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 + } else if !chk { + // did not pass the check + // go with the next row + continue + } + } + set = append(set, res) + + if f.Limit > 0 { + if uint(len(set)) >= f.Limit { + // make sure we do not fetch more than requested! + break + } + } + } + + err = rows.Close() + return + } + + fetch = func() error { + var ( + // how many items were actually fetched + fetched uint + + // starting offset & limit are from filter arg + // note that this will have to be improved with key-based pagination + limit = f.Limit + + // Copy cursor value + // + // This is where we'll start fetching and this value will be overwritten when + // results come back + cursor = f.PageCursor + + lastSetFull bool + ) + + for refetch := 0; refetch < MaxRefetches; refetch++ { + if fetched, err = fetchPage(cursor, limit); err != nil { + return err + } + + // if limit is not set or we've already collected enough items + // we can break the loop right away + if limit == 0 || fetched == 0 || fetched < limit { + break + } + + if uint(len(set)) >= f.Limit { + // we should return as much as requested + set = set[0:f.Limit] + lastSetFull = true + break + } + + // In case limit is set very low and we've missed records in the first fetch, + // make sure next fetch limit is a bit higher + if limit < MinRefetchLimit { + limit = MinRefetchLimit + } + + // @todo it might be good to implement different kind of strategies + // (beyond min-refetch-limit above) that can adjust limit on + // retry to more optimal number + } + + if reverseCursor { + // Cursor for previous page was used + // Fetched set needs to be reverseCursor because we've forced a descending order to + // get the previus page + for i, j := 0, len(set)-1; i < j; i, j = i+1, j-1 { + set[i], set[j] = set[j], set[i] + } + } + + if f.Limit > 0 && len(set) > 0 { + if f.PageCursor != nil && (!f.PageCursor.Reverse || lastSetFull) { + f.PrevPage = s.collectComposeRecordCursorValues(set[0], sort.Columns()...) + f.PrevPage.Reverse = true + } + + // Less items fetched then requested by page-limit + // not very likely there's another page + f.NextPage = s.collectComposeRecordCursorValues(set[len(set)-1], sort.Columns()...) + } + + f.PageCursor = nil + return nil + } + ) + + return set, f, s.config.ErrorHandler(fetch()) +} + +// lookupComposeRecordByID searches for compose record by ID +// It returns compose record even if deleted +func (s Store) lookupComposeRecordByID(ctx context.Context, _mod *types.Module, id uint64) (*types.Record, error) { + return s.execLookupComposeRecord(ctx, _mod, squirrel.Eq{ + s.preprocessColumn("crd.id", ""): s.preprocessValue(id, ""), + }) +} + +// createComposeRecord creates one or more rows in compose_record table +func (s Store) createComposeRecord(ctx context.Context, _mod *types.Module, rr ...*types.Record) (err error) { + for _, res := range rr { + err = s.checkComposeRecordConstraints(ctx, _mod, res) + if err != nil { + return err + } + + // err = s.composeRecordHook(ctx, TriggerBeforeComposeRecordCreate, _mod, res) + // if err != nil { + // return err + // } + + err = s.execCreateComposeRecords(ctx, s.internalComposeRecordEncoder(res)) + if err != nil { + return err + } + } + + return +} + +// updateComposeRecord updates one or more existing rows in compose_record +func (s Store) updateComposeRecord(ctx context.Context, _mod *types.Module, rr ...*types.Record) error { + return s.config.ErrorHandler(s.partialComposeRecordUpdate(ctx, _mod, nil, rr...)) +} + +// partialComposeRecordUpdate updates one or more existing rows in compose_record +func (s Store) partialComposeRecordUpdate(ctx context.Context, _mod *types.Module, onlyColumns []string, rr ...*types.Record) (err error) { + for _, res := range rr { + err = s.checkComposeRecordConstraints(ctx, _mod, res) + if err != nil { + return err + } + + // err = s.composeRecordHook(ctx, TriggerBeforeComposeRecordUpdate, _mod, res) + // if err != nil { + // return err + // } + + err = s.execUpdateComposeRecords( + ctx, + squirrel.Eq{ + s.preprocessColumn("crd.id", ""): s.preprocessValue(res.ID, ""), + }, + s.internalComposeRecordEncoder(res).Skip("id").Only(onlyColumns...)) + if err != nil { + return s.config.ErrorHandler(err) + } + } + + return +} + +// upsertComposeRecord updates one or more existing rows in compose_record +func (s Store) upsertComposeRecord(ctx context.Context, _mod *types.Module, rr ...*types.Record) (err error) { + for _, res := range rr { + err = s.checkComposeRecordConstraints(ctx, _mod, res) + if err != nil { + return err + } + + // err = s.composeRecordHook(ctx, TriggerBeforeComposeRecordUpsert, _mod, res) + // if err != nil { + // return err + // } + + err = s.config.ErrorHandler(s.execUpsertComposeRecords(ctx, s.internalComposeRecordEncoder(res))) + if err != nil { + return err + } + } + + return nil +} + +// deleteComposeRecord Deletes one or more rows from compose_record table +func (s Store) deleteComposeRecord(ctx context.Context, _mod *types.Module, rr ...*types.Record) (err error) { + for _, res := range rr { + // err = s.composeRecordHook(ctx, TriggerBeforeComposeRecordDelete, _mod, res) + // if err != nil { + // return err + // } + + err = s.execDeleteComposeRecords(ctx, squirrel.Eq{ + s.preprocessColumn("crd.id", ""): s.preprocessValue(res.ID, ""), + }) + if err != nil { + return s.config.ErrorHandler(err) + } + } + + return nil +} + +// deleteComposeRecordByID Deletes row from the compose_record table +func (s Store) deleteComposeRecordByID(ctx context.Context, _mod *types.Module, ID uint64) error { + return s.execDeleteComposeRecords(ctx, squirrel.Eq{ + s.preprocessColumn("crd.id", ""): s.preprocessValue(ID, ""), + }) +} + +// truncateComposeRecords Deletes all rows from the compose_record table +func (s Store) truncateComposeRecords(ctx context.Context, _mod *types.Module) error { + return s.config.ErrorHandler(s.Truncate(ctx, s.composeRecordTable())) +} + +// execLookupComposeRecord prepares ComposeRecord query and executes it, +// returning types.Record (or error) +func (s Store) execLookupComposeRecord(ctx context.Context, _mod *types.Module, cnd squirrel.Sqlizer) (res *types.Record, err error) { + var ( + row rowScanner + ) + + row, err = s.QueryRow(ctx, s.composeRecordsSelectBuilder().Where(cnd)) + if err != nil { + return + } + + res, err = s.internalComposeRecordRowScanner(_mod, row) + if err != nil { + return + } + + return res, nil +} + +// execCreateComposeRecords updates all matched (by cnd) rows in compose_record with given data +func (s Store) execCreateComposeRecords(ctx context.Context, payload store.Payload) error { + return s.config.ErrorHandler(s.Exec(ctx, s.InsertBuilder(s.composeRecordTable()).SetMap(payload))) +} + +// execUpdateComposeRecords updates all matched (by cnd) rows in compose_record with given data +func (s Store) execUpdateComposeRecords(ctx context.Context, cnd squirrel.Sqlizer, set store.Payload) error { + return s.config.ErrorHandler(s.Exec(ctx, s.UpdateBuilder(s.composeRecordTable("crd")).Where(cnd).SetMap(set))) +} + +// execUpsertComposeRecords inserts new or updates matching (by-primary-key) rows in compose_record with given data +func (s Store) execUpsertComposeRecords(ctx context.Context, set store.Payload) error { + upsert, err := s.config.UpsertBuilder( + s.config, + s.composeRecordTable(), + set, + "id", + ) + + if err != nil { + return err + } + + return s.config.ErrorHandler(s.Exec(ctx, upsert)) +} + +// execDeleteComposeRecords Deletes all matched (by cnd) rows in compose_record with given data +func (s Store) execDeleteComposeRecords(ctx context.Context, cnd squirrel.Sqlizer) error { + return s.config.ErrorHandler(s.Exec(ctx, s.DeleteBuilder(s.composeRecordTable("crd")).Where(cnd))) +} + +func (s Store) internalComposeRecordRowScanner(_mod *types.Module, row rowScanner) (res *types.Record, err error) { + res = &types.Record{} + + if _, has := s.config.RowScanners["composeRecord"]; has { + scanner := s.config.RowScanners["composeRecord"].(func(_mod *types.Module, _ rowScanner, _ *types.Record) error) + err = scanner(_mod, row, res) + } else { + err = row.Scan( + &res.ID, + &res.ModuleID, + &res.NamespaceID, + &res.OwnedBy, + &res.CreatedBy, + &res.UpdatedBy, + &res.DeletedBy, + &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 ComposeRecord: %w", err) + } else { + return res, nil + } +} + +// QueryComposeRecords returns squirrel.SelectBuilder with set table and all columns +func (s Store) composeRecordsSelectBuilder() squirrel.SelectBuilder { + return s.SelectBuilder(s.composeRecordTable("crd"), s.composeRecordColumns("crd")...) +} + +// composeRecordTable name of the db table +func (Store) composeRecordTable(aa ...string) string { + var alias string + if len(aa) > 0 { + alias = " AS " + aa[0] + } + + return "compose_record" + alias +} + +// ComposeRecordColumns returns all defined table columns +// +// With optional string arg, all columns are returned aliased +func (Store) composeRecordColumns(aa ...string) []string { + var alias string + if len(aa) > 0 { + alias = aa[0] + "." + } + + return []string{ + alias + "id", + alias + "module_id", + alias + "rel_namespace", + alias + "owned_by", + alias + "created_by", + alias + "updated_by", + alias + "deleted_by", + alias + "created_at", + alias + "updated_at", + alias + "deleted_at", + } +} + +// {true false true true true} + +// sortableComposeRecordColumns returns all ComposeRecord columns flagged as sortable +// +// With optional string arg, all columns are returned aliased +func (Store) sortableComposeRecordColumns() []string { + return []string{ + "id", + "created_at", + "updated_at", + "deleted_at", + } +} + +// internalComposeRecordEncoder encodes fields from types.Record to store.Payload (map) +// +// Encoding is done by using generic approach or by calling encodeComposeRecord +// func when rdbms.customEncoder=true +func (s Store) internalComposeRecordEncoder(res *types.Record) store.Payload { + return store.Payload{ + "id": res.ID, + "module_id": res.ModuleID, + "rel_namespace": res.NamespaceID, + "owned_by": res.OwnedBy, + "created_by": res.CreatedBy, + "updated_by": res.UpdatedBy, + "deleted_by": res.DeletedBy, + "created_at": res.CreatedAt, + "updated_at": res.UpdatedAt, + "deleted_at": res.DeletedAt, + } +} + +func (s Store) collectComposeRecordCursorValues(res *types.Record, cc ...string) *filter.PagingCursor { + var ( + cursor = &filter.PagingCursor{} + + hasUnique bool + + collect = func(cc ...string) { + for _, c := range cc { + switch c { + case "id": + cursor.Set(c, res.ID, false) + case "created_at": + cursor.Set(c, res.CreatedAt, false) + case "updated_at": + cursor.Set(c, res.UpdatedAt, false) + case "deleted_at": + cursor.Set(c, res.DeletedAt, false) + + } + } + } + ) + + collect(cc...) + if !hasUnique { + collect( + "id", + ) + } + + return cursor +} + +func (s *Store) checkComposeRecordConstraints(ctx context.Context, _mod *types.Module, res *types.Record) error { + + return nil +} + +// func (s *Store) composeRecordHook(ctx context.Context, key triggerKey, _mod *types.Module, res *types.Record) error { +// if fn, has := s.config.TriggerHandlers[key]; has { +// return fn.(func (ctx context.Context, s *Store, _mod *types.Module, res *types.Record) error)(ctx, s, _mod, res) +// } +// +// return nil +// } diff --git a/store/rdbms/compose_records.go b/store/rdbms/compose_records.go index 943d8a5e3..1020ef912 100644 --- a/store/rdbms/compose_records.go +++ b/store/rdbms/compose_records.go @@ -1,70 +1,198 @@ package rdbms -// Handles record values -// -// This file IS NOT autogenerated like most of other store handlers -// because of the specificities that are required for compose record (value) store handling -// -// Main difference is record (value) searching and storage is based on record module settings. - import ( "context" - "database/sql" "fmt" "github.com/Masterminds/squirrel" "github.com/cortezaproject/corteza-server/compose/types" "github.com/cortezaproject/corteza-server/pkg/ql" "github.com/cortezaproject/corteza-server/pkg/rh" "github.com/cortezaproject/corteza-server/store" - "github.com/jmoiron/sqlx" "strings" ) -// SearchComposeRecords returns all matching rows -// -// This function calls convertComposeRecordFilter with the given -// types.RecordFilter and expects to receive a working squirrel.SelectBuilder -func (s Store) SearchComposeRecords(ctx context.Context, m *types.Module, f types.RecordFilter) (types.RecordSet, types.RecordFilter, error) { - q, err := s.convertComposeRecordFilter(m, f) +// @todo support for partitioned records (records are partitioned into multiple record tables +// in this case, values are no longer separated into record_value (key-value) table but encoded as JSON +// @todo support for partitioned record with (optional) physical columns for record values +// physical columns are part of module-field configuration + +// SearchComposeRecords returns all matching ComposeRecords from store +func (s Store) SearchComposeRecords(ctx context.Context, m *types.Module, f types.RecordFilter) (set types.RecordSet, filter types.RecordFilter, err error) { + // In when module requires this, + set, filter, err = s.searchComposeRecords(ctx, m, f) if err != nil { - return nil, f, err + return } - q = ApplyPaging(q, f.PageFilter) + return +} - cap := f.PerPage - if cap == 0 { - cap = DefaultSliceCapacity +// LookupComposeRecordByID searches for compose record by ID +// It returns compose record even if deleted +func (s Store) LookupComposeRecordByID(ctx context.Context, _ *types.Module, id uint64) (res *types.Record, err error) { + res, err = s.lookupComposeRecordByID(ctx, nil, id) + if err != nil { + return } - var ( - set = make([]*types.Record, 0, cap) - res *types.Record - ) + res.Values, _, err = s.searchComposeRecordValues(ctx, nil, types.RecordValueFilter{RecordID: []uint64{id}}) + if err != nil { + return nil, err + } - return set, f, func() error { - if f.Count, err = Count(ctx, s.db, q); err != nil || f.Count == 0 { - return err - } - rows, err := s.Query(ctx, q) + return +} + +// CreateComposeRecord creates one or more ComposeRecords in store +func (s Store) CreateComposeRecord(ctx context.Context, m *types.Module, rr ...*types.Record) (err error) { + if err = validateRecordModule(m, rr...); err != nil { + return + } + + for _, res := range rr { + + err = s.createComposeRecord(ctx, nil, res) if err != nil { - return err + return } - defer rows.Close() - for rows.Next() { - if res, err = s.scanComposeRecord(rows, rows.Err()); err != nil { - return err + err = s.createComposeRecordValue(ctx, nil, res.Values...) + if err != nil { + return + } + } + + return +} + +// UpdateComposeRecord updates one or more (existing) ComposeRecords in store +func (s Store) UpdateComposeRecord(ctx context.Context, m *types.Module, rr ...*types.Record) (err error) { + if err = validateRecordModule(m, rr...); err != nil { + return + } + + for _, res := range rr { + err = s.updateComposeRecord(ctx, nil, res) + if err != nil { + return + } + + cnd := squirrel.Eq{"record_id": res.ID} + + if res.DeletedAt != nil { + // Record was deleted, set all values to deleted too + err = s.execUpdateComposeRecordValues(ctx, cnd, store.Payload{"deleted_at": res.DeletedAt}) + if err != nil { + return + } + } else { + // we're following the old implementation here for now + // all old values are Deleted and new inserted + err = s.execDeleteComposeRecordValues(ctx, cnd) + if err != nil { + return } - set = append(set, res) + err = s.createComposeRecordValue(ctx, nil, res.Values...) } + } - return nil - }() + return +} + +// PartialComposeRecordUpdate updates one or more existing ComposeRecords in store +func (s Store) PartialComposeRecordUpdate(ctx context.Context, m *types.Module, onlyColumns []string, rr ...*types.Record) (err error) { + if err = validateRecordModule(m, rr...); err != nil { + return + } + + return fmt.Errorf("partial compose record update is not supported") +} + +// DeleteComposeRecord Deletes one or more ComposeRecords from store +func (s Store) DeleteComposeRecord(ctx context.Context, m *types.Module, rr ...*types.Record) (err error) { + if err = validateRecordModule(m, rr...); err != nil { + return + } + + for _, res := range rr { + err = s.DeleteComposeRecordByID(ctx, m, res.ID) + if err != nil { + return + } + } + + return +} + +// DeleteComposeRecord Deletes one or more ComposeRecords from store +func (s Store) UpsertComposeRecord(ctx context.Context, m *types.Module, rr ...*types.Record) (err error) { + if err = validateRecordModule(m, rr...); err != nil { + return + } + + for _, res := range rr { + err = s.upsertComposeRecord(ctx, m, res) + if err != nil { + return + } + } + + return +} + +// DeleteComposeRecordByID Deletes ComposeRecord from store +func (s Store) DeleteComposeRecordByID(ctx context.Context, _ *types.Module, ID uint64) (err error) { + err = s.DeleteComposeRecordByID(ctx, nil, ID) + if err != nil { + return + } + + err = s.Exec(ctx, s.DeleteBuilder(s.composeRecordValueTable("crv")).Where(squirrel.Eq{"crv.record_id": ID})) + if err != nil { + return + } + + return +} + +// TruncateComposeRecords Deletes all ComposeRecords from store +func (s Store) TruncateComposeRecords(ctx context.Context, _ *types.Module) (err error) { + err = s.truncateComposeRecords(ctx, nil) + if err != nil { + return + } + + err = s.truncateComposeRecordValues(ctx, nil) + if err != nil { + return + } + + return } func (s Store) convertComposeRecordFilter(m *types.Module, f types.RecordFilter) (query squirrel.SelectBuilder, err error) { + if m == nil { + err = fmt.Errorf("module not provided") + return + } + + if f.ModuleID == 0 { + // In case Module ID on filter is not set, + // values from provided module can be used + f.ModuleID = m.ID + } else if m.ID != f.ModuleID { + err = fmt.Errorf("provided module does not match filter module ID") + } + + if f.NamespaceID == 0 { + // In case Namespace ID on filter is not set, + // values from provided module can be used + f.NamespaceID = m.NamespaceID + } else if m.NamespaceID != f.NamespaceID { + err = fmt.Errorf("provided module namespace ID does not match filter namespace ID") + } + var ( joinedFields = []string{} alreadyJoined = func(f string) bool { @@ -91,7 +219,7 @@ func (s Store) convertComposeRecordFilter(m *types.Module, f types.RecordFilter) if !alreadyJoined(i.Value) { query = query.LeftJoin(fmt.Sprintf( - "compose_record_value AS rv_%s ON (rv_%s.record_id = crc.id AND rv_%s.name = ? AND rv_%s.deleted_at IS NULL)", + "compose_record_value AS rv_%s ON (rv_%s.record_id = crd.id AND rv_%s.name = ? AND rv_%s.deleted_at IS NULL)", i.Value, i.Value, i.Value, i.Value, ), i.Value) } @@ -116,12 +244,12 @@ func (s Store) convertComposeRecordFilter(m *types.Module, f types.RecordFilter) ) // Create query for fetching and counting records. - query = s.QueryComposeRecords(m). - Where("crc.module_id = ?", m.ID). - Where("crc.rel_namespace = ?", m.NamespaceID) + query = s.composeRecordsSelectBuilder(). + Where("crd.module_id = ?", m.ID). + Where("crd.rel_namespace = ?", m.NamespaceID) // Inc/exclude deleted records according to filter settings - query = rh.FilterNullByState(query, "crc.deleted_at", f.Deleted) + query = rh.FilterNullByState(query, "crd.deleted_at", f.Deleted) // Parse filters. if f.Query != "" { @@ -146,399 +274,37 @@ func (s Store) convertComposeRecordFilter(m *types.Module, f types.RecordFilter) } } - if f.Sort != "" { - var ( - // Sort parser - sp = ql.NewParser() - - // Sort columns - sc ql.Columns - ) - - // Resolve all identifiers found in sort - // into their table/column counterparts - sp.OnIdent = identResolver - - if sc, err = sp.ParseColumns(f.Sort); err != nil { - return - } - - query = query.OrderBy(sc.Strings()...) - } + // @todo refactor + //if f.Sort != "" { + // var ( + // // Sort parser + // sp = ql.NewParser() + // + // // Sort columns + // sc ql.Columns + // ) + // + // // Resolve all identifiers found in sort + // // into their table/column counterparts + // sp.OnIdent = identResolver + // + // if sc, err = sp.ParseColumns(f.Sort); err != nil { + // return + // } + // + // query = query.OrderBy(sc.Strings()...) + //} return } -// LookupComposeRecordByID searches for compose page by ID -// -// It returns compose page even if deleted -func (s Store) LookupComposeRecordByID(ctx context.Context, m *types.Module, id uint64) (*types.Record, error) { - return s.ComposeRecordLookup(ctx, m, squirrel.Eq{ - "crc.id": id, - }) -} - -// CreateComposeRecord creates one or more rows in compose_record table -func (s Store) CreateComposeRecord(ctx context.Context, m *types.Module, rr ...*types.Record) error { - if len(rr) == 0 { - return nil - } - - return Tx(ctx, s.db, s.config, nil, func(db *sqlx.Tx) (err error) { - for _, res := range rr { - err = ExecuteSqlizer(ctx, s.DB(), s.Insert(s.ComposeRecordTable(m)).SetMap(s.ComposeRecordEnc(res))) - if err != nil { - return err - } - } - - return nil - }) -} - -// CreateComposeRecordValue creates one or more rows in compose_record_value table -// -// @this can probbably be merged with CreateComposeRecord -func (s Store) CreateComposeRecordValue(ctx context.Context, m *types.Module, rr ...*types.RecordValue) error { - if len(rr) == 0 { - return nil - } - - return Tx(ctx, s.db, s.config, nil, func(db *sqlx.Tx) (err error) { - for _, res := range rr { - err = ExecuteSqlizer(ctx, s.DB(), s.Insert(s.ComposeRecordValueTable(m)).SetMap(s.ComposeRecordValueEnc(res))) - if err != nil { - return err - } - } - - return nil - }) -} - -// UpdateComposeRecord updates one or more existing rows in compose_record -func (s Store) UpdateComposeRecord(ctx context.Context, m *types.Module, rr ...*types.Record) error { - return s.PartialUpdateComposeRecord(ctx, m, nil, rr...) -} - -// UpdateComposeRecordValue updates one or more existing rows in compose_record_value -// -// @todo this can probably be merged with UpdateComposeRecord -func (s Store) UpdateComposeRecordValue(ctx context.Context, m *types.Module, rr ...*types.RecordValue) error { - return s.PartialUpdateComposeRecordValue(ctx, m, rr...) -} - -// PartialUpdateComposeRecord updates one or more existing rows in compose_record -// -// It wraps the update into transaction and can perform partial update by providing list of updatable columns -func (s Store) PartialUpdateComposeRecord(ctx context.Context, m *types.Module, onlyColumns []string, rr ...*types.Record) error { - if len(rr) == 0 { - return nil - } - - return Tx(ctx, s.db, s.config, nil, func(db *sqlx.Tx) (err error) { - for _, res := range rr { - err = s.ExecUpdateComposeRecords( - ctx, m, - squirrel.Eq{s.preprocessColumn("crc.id", ""): s.preprocessValue(res.ID, "")}, - s.ComposeRecordEnc(res).Skip("id").Only(onlyColumns...)) - if err != nil { - return err - } - } - - return nil - }) -} - -// PartialUpdateComposeRecordValue updates one or more existing rows in compose_record_value -// -// It wraps the update into transaction and can perform partial update by providing list of updatable columns -// -// @todo this can probably be merged with PartialUpdateComposeRecord -func (s Store) PartialUpdateComposeRecordValue(ctx context.Context, m *types.Module, rr ...*types.RecordValue) error { - if len(rr) == 0 { - return nil - } - - return Tx(ctx, s.db, s.config, nil, func(db *sqlx.Tx) (err error) { - for _, res := range rr { - err = s.ExecUpdateComposeRecordValues( - ctx, m, - squirrel.Eq{s.preprocessColumn("crv.record_id", ""): s.preprocessValue(res.RecordID, ""), - s.preprocessColumn("crv.place", ""): s.preprocessValue(res.Place, ""), - s.preprocessColumn("crv.name", ""): s.preprocessValue(res.Name, ""), - }, - s.ComposeRecordValueEnc(res).Skip("record_id", "place", "name")) - if err != nil { - return err - } - } - - return nil - }) -} - -// RemoveComposeRecord removes one or more rows from compose_record table -func (s Store) RemoveComposeRecord(ctx context.Context, m *types.Module, rr ...*types.Record) error { - if len(rr) == 0 { - return nil - } - - return Tx(ctx, s.db, s.config, nil, func(db *sqlx.Tx) (err error) { - for _, res := range rr { - err = ExecuteSqlizer(ctx, s.DB(), s.Delete(s.ComposeRecordTable(m, "crc")).Where(squirrel.Eq{s.preprocessColumn("crc.id", ""): s.preprocessValue(res.ID, "")})) - if err != nil { - return err - } - } - - return nil - }) -} - -// RemoveComposeRecordValue removes one or more rows from compose_record_value table -// -// @todo this can probably be merged with RemoveComposeRecord -func (s Store) RemoveComposeRecordValue(ctx context.Context, m *types.Module, rr ...*types.RecordValue) error { - if len(rr) == 0 { - return nil - } - - return Tx(ctx, s.db, s.config, nil, func(db *sqlx.Tx) (err error) { - for _, res := range rr { - err = ExecuteSqlizer(ctx, s.DB(), s.Delete(s.ComposeRecordValueTable(m, "crv")).Where(squirrel.Eq{s.preprocessColumn("crv.record_id", ""): s.preprocessValue(res.RecordID, ""), - s.preprocessColumn("crv.place", ""): s.preprocessValue(res.Place, ""), - s.preprocessColumn("crv.name", ""): s.preprocessValue(res.Name, ""), - })) - if err != nil { - return err - } - } - - return nil - }) -} - -// RemoveComposeRecordByID removes row from the compose_record table -func (s Store) RemoveComposeRecordByID(ctx context.Context, m *types.Module, ID uint64) error { - return ExecuteSqlizer(ctx, s.DB(), s.Delete(s.ComposeRecordTable(m)).Where(squirrel.Eq{s.preprocessColumn("crc.id", ""): s.preprocessValue(ID, "")})) -} - -// RemoveComposeRecordValueByRecordIDPlaceName removes row from the compose_record_value table -// -// @todo this can probably be merged with RemoveComposeRecordByID -func (s Store) RemoveComposeRecordValueByRecordID(ctx context.Context, m *types.Module, recordID uint64, place int, name string) error { - return ExecuteSqlizer(ctx, s.DB(), s.Delete(s.ComposeRecordValueTable(m)).Where(squirrel.Eq{s.preprocessColumn("crv.record_id", ""): s.preprocessValue(recordID, "")})) -} - -// TruncateComposeRecords removes all rows from the compose_record table -func (s Store) TruncateComposeRecords(ctx context.Context, m *types.Module) error { - return Truncate(ctx, s.DB(), s.ComposeRecordTable(m)) -} - -// TruncateComposeRecordValues removes all rows from the compose_record_value table -// -// @todo this can probably be merged with TruncateComposeRecords -func (s Store) TruncateComposeRecordValues(ctx context.Context, m *types.Module) error { - return Truncate(ctx, s.DB(), s.ComposeRecordValueTable(m)) -} - -// ExecUpdateComposeRecords updates all matchhed (cnd) rows in compose_record with given data -func (s Store) ExecUpdateComposeRecords(ctx context.Context, m *types.Module, cnd squirrel.Sqlizer, set store.Payload) error { - return ExecuteSqlizer(ctx, s.DB(), s.Update(s.ComposeRecordTable(m, "crc")).Where(cnd).SetMap(set)) -} - -// ExecUpdateComposeRecordValues updates all matchhed (cnd) rows in compose_record_value with given data -func (s Store) ExecUpdateComposeRecordValues(ctx context.Context, m *types.Module, cnd squirrel.Sqlizer, set store.Payload) error { - return ExecuteSqlizer(ctx, s.DB(), s.Update(s.ComposeRecordValueTable(m, "crv")).Where(cnd).SetMap(set)) -} - -// ComposeRecordLookup prepares ComposeRecord query and executes it, -// returning types.Record (or error) -func (s Store) ComposeRecordLookup(ctx context.Context, m *types.Module, cnd squirrel.Sqlizer) (*types.Record, error) { - return s.scanComposeRecord(s.QueryRow(ctx, s.QueryComposeRecords(m).Where(cnd))) -} - -// ComposeRecordValueLookup prepares ComposeRecordValue query and executes it, -// returning types.RecordValue (or error) -func (s Store) ComposeRecordValueLookup(ctx context.Context, m *types.Module, cnd squirrel.Sqlizer) (*types.RecordValue, error) { - return s.scanComposeRecordValue(s.QueryRow(ctx, s.QueryComposeRecordValues(m).Where(cnd))) -} - -func (s Store) scanComposeRecord(row rowScanner, err error) (*types.Record, error) { - if err != nil { - return nil, err - } - - var res = &types.Record{} - if _, has := s.config.RowScanners["ComposeRecord"]; has { - scanner := s.config.RowScanners["ComposeRecord"].(func(rowScanner, *types.Record) error) - err = scanner(row, res) - } else { - err = row.Scan( - &res.ID, - &res.ModuleID, - &res.NamespaceID, - &res.OwnedBy, - &res.CreatedAt, - &res.CreatedBy, - &res.UpdatedAt, - &res.UpdatedBy, - &res.DeletedAt, - &res.DeletedBy, - ) - } - - if err == sql.ErrNoRows { - return nil, store.ErrNotFound - } - - if err != nil { - return nil, fmt.Errorf("could not scan db row for ComposeRecord: %w", err) - } else { - return res, nil - } -} - -func (s Store) scanComposeRecordValue(row rowScanner, err error) (*types.RecordValue, error) { - if err != nil { - return nil, err - } - - var res = &types.RecordValue{} - if _, has := s.config.RowScanners["ComposeRecordValue"]; has { - scanner := s.config.RowScanners["ComposeRecordValue"].(func(rowScanner, *types.RecordValue) error) - err = scanner(row, res) - } else { - err = row.Scan( - &res.RecordID, - &res.Place, - &res.Name, - &res.Ref, - &res.Value, - &res.DeletedAt, - ) - } - - if err == sql.ErrNoRows { - return nil, store.ErrNotFound - } - - if err != nil { - return nil, fmt.Errorf("could not scan db row for ComposeRecordValue: %w", err) - } else { - return res, nil - } -} - -// QueryComposeRecords returns squirrel.SelectBuilder with set table and all columns -func (s Store) QueryComposeRecords(m *types.Module) squirrel.SelectBuilder { - return s.Select(s.ComposeRecordTable(m, "crc"), s.ComposeRecordColumns("crc")...) -} - -// ComposeRecordTable name of the db table -func (Store) ComposeRecordTable(m *types.Module, aa ...string) string { - var alias string - if len(aa) > 0 { - alias = " AS " + aa[0] - } - - return "compose_record" + alias -} - -// ComposeRecordColumns returns all defined table columns -// -// With optional string arg, all columns are returned aliased -func (Store) ComposeRecordColumns(aa ...string) []string { - var alias string - if len(aa) > 0 { - alias = aa[0] + "." - } - - return []string{ - alias + "id", - alias + "module_id", - alias + "rel_namespace", - alias + "owned_by", - alias + "created_at", - alias + "created_by", - alias + "updated_at", - alias + "updated_by", - alias + "deleted_at", - alias + "deleted_by", - } -} - -// ComposeRecordEnc encodes fields from types.Record to store.Payload (map) -func (Store) ComposeRecordEnc(res *types.Record) store.Payload { - return store.Payload{ - "id": res.ID, - "module_id": res.ModuleID, - "rel_namespace": res.NamespaceID, - "owned_by": res.OwnedBy, - "created_at": res.CreatedAt, - "created_by": res.CreatedBy, - "updated_at": res.UpdatedAt, - "updated_by": res.UpdatedBy, - "deleted_at": res.DeletedAt, - "deleted_by": res.DeletedBy, - } -} - -// QueryComposeRecordValues returns squirrel.SelectBuilder with set table and all columns -func (s Store) QueryComposeRecordValues(m *types.Module) squirrel.SelectBuilder { - return s.Select(s.ComposeRecordValueTable(m, "crv"), s.ComposeRecordValueColumns("crv")...) -} - -// ComposeRecordValueTable name of the db table -func (Store) ComposeRecordValueTable(m *types.Module, aa ...string) string { - var alias string - if len(aa) > 0 { - alias = " AS " + aa[0] - } - - return "compose_record_value" + alias -} - -// ComposeRecordValueColumns returns all defined table columns -// -// With optional string arg, all columns are returned aliased -func (Store) ComposeRecordValueColumns(aa ...string) []string { - var alias string - if len(aa) > 0 { - alias = aa[0] + "." - } - - return []string{ - alias + "record_id", - alias + "place", - alias + "name", - alias + "ref", - alias + "value", - alias + "deleted_at", - } -} - -// ComposeRecordValueEnc encodes fields from types.RecordValue to store.Payload (map) -func (Store) ComposeRecordValueEnc(res *types.RecordValue) store.Payload { - return store.Payload{ - "record_id": res.RecordID, - "place": res.Place, - "name": res.Name, - "ref": res.Ref, - "value": res.Value, - "deleted_at": res.DeletedAt, - } -} - -// Checks if field name is "real column", reformats it and returns +//// Checks if field name is "real column", reformats it and returns func isRealRecordCol(name string) (string, bool) { switch name { case "recordID", "id": - return "crc.id", true + return "crd.id", true case "module_id", "owned_by", @@ -548,7 +314,7 @@ func isRealRecordCol(name string) (string, bool) { "updated_at", "deleted_by", "deleted_at": - return "crc." + name, true + return "crd." + name, true case "moduleID", @@ -559,8 +325,23 @@ func isRealRecordCol(name string) (string, bool) { "updatedAt", "deletedBy", "deletedAt": - return "crc." + name[0:len(name)-2] + "_" + strings.ToLower(name[len(name)-2:]), true + return "crd." + name[0:len(name)-2] + "_" + strings.ToLower(name[len(name)-2:]), true } return name, false } + +// Verifies if module and namespace ID on record match IDs on module +func validateRecordModule(m *types.Module, rr ...*types.Record) error { + for _, r := range rr { + if m.ID != r.ModuleID { + return fmt.Errorf("provided module does not match module ID on record") + } + + if m.NamespaceID != r.NamespaceID { + return fmt.Errorf("provided module namespace ID does not match namespace ID on record") + } + } + + return nil +} diff --git a/store/rdbms/credentials.gen.go b/store/rdbms/credentials.gen.go index d78197188..c9d4ee9b7 100644 --- a/store/rdbms/credentials.gen.go +++ b/store/rdbms/credentials.gen.go @@ -11,12 +11,22 @@ package rdbms import ( "context" "database/sql" + "errors" "fmt" "github.com/Masterminds/squirrel" "github.com/cortezaproject/corteza-server/store" "github.com/cortezaproject/corteza-server/system/types" ) +var _ = errors.Is + +const ( + TriggerBeforeCredentialsCreate triggerKey = "credentialsBeforeCreate" + TriggerBeforeCredentialsUpdate triggerKey = "credentialsBeforeUpdate" + TriggerBeforeCredentialsUpsert triggerKey = "credentialsBeforeUpsert" + TriggerBeforeCredentialsDelete triggerKey = "credentialsBeforeDelete" +) + // SearchCredentials returns all matching rows // // This function calls convertCredentialsFilter with the given @@ -35,7 +45,7 @@ func (s Store) SearchCredentials(ctx context.Context, f types.CredentialsFilter) var ( set = make([]*types.Credentials, 0, scap) // Paging is disabled in definition yaml file - // {search: {disablePaging:true}} and this allows + // {search: {enablePaging:false}} and this allows // a much simpler row fetching logic fetch = func() error { var ( @@ -48,14 +58,19 @@ func (s Store) SearchCredentials(ctx context.Context, f types.CredentialsFilter) } for rows.Next() { - if res, err = s.internalCredentialsRowScanner(rows, rows.Err()); err != nil { + if rows.Err() == nil { + res, err = s.internalCredentialsRowScanner(rows) + } + + if err != nil { if cerr := rows.Close(); cerr != nil { - return fmt.Errorf("could not close rows (%v) after scan error: %w", cerr, err) + 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 set = append(set, res) } @@ -70,17 +85,27 @@ func (s Store) SearchCredentials(ctx context.Context, f types.CredentialsFilter) // // It returns credentials even if deleted func (s Store) LookupCredentialsByID(ctx context.Context, id uint64) (*types.Credentials, error) { - return s.CredentialsLookup(ctx, squirrel.Eq{ - "crd.id": id, + return s.execLookupCredentials(ctx, squirrel.Eq{ + s.preprocessColumn("crd.id", ""): s.preprocessValue(id, ""), }) } // CreateCredentials creates one or more rows in credentials table func (s Store) CreateCredentials(ctx context.Context, rr ...*types.Credentials) (err error) { for _, res := range rr { - err = ExecuteSqlizer(ctx, s.DB(), s.Insert(s.CredentialsTable()).SetMap(s.internalCredentialsEncoder(res))) + err = s.checkCredentialsConstraints(ctx, res) if err != nil { - return s.config.ErrorHandler(err) + return err + } + + // err = s.credentialsHook(ctx, TriggerBeforeCredentialsCreate, res) + // if err != nil { + // return err + // } + + err = s.execCreateCredentials(ctx, s.internalCredentialsEncoder(res)) + if err != nil { + return err } } @@ -89,17 +114,27 @@ func (s Store) CreateCredentials(ctx context.Context, rr ...*types.Credentials) // UpdateCredentials updates one or more existing rows in credentials func (s Store) UpdateCredentials(ctx context.Context, rr ...*types.Credentials) error { - return s.config.ErrorHandler(s.PartialUpdateCredentials(ctx, nil, rr...)) + return s.config.ErrorHandler(s.PartialCredentialsUpdate(ctx, nil, rr...)) } -// PartialUpdateCredentials updates one or more existing rows in credentials -// -// It wraps the update into transaction and can perform partial update by providing list of updatable columns -func (s Store) PartialUpdateCredentials(ctx context.Context, onlyColumns []string, rr ...*types.Credentials) (err error) { +// PartialCredentialsUpdate updates one or more existing rows in credentials +func (s Store) PartialCredentialsUpdate(ctx context.Context, onlyColumns []string, rr ...*types.Credentials) (err error) { for _, res := range rr { - err = s.ExecUpdateCredentials( + err = s.checkCredentialsConstraints(ctx, res) + if err != nil { + return err + } + + // err = s.credentialsHook(ctx, TriggerBeforeCredentialsUpdate, res) + // if err != nil { + // return err + // } + + err = s.execUpdateCredentials( ctx, - squirrel.Eq{s.preprocessColumn("crd.id", ""): s.preprocessValue(res.ID, "")}, + squirrel.Eq{ + s.preprocessColumn("crd.id", ""): s.preprocessValue(res.ID, ""), + }, s.internalCredentialsEncoder(res).Skip("id").Only(onlyColumns...)) if err != nil { return s.config.ErrorHandler(err) @@ -109,10 +144,39 @@ func (s Store) PartialUpdateCredentials(ctx context.Context, onlyColumns []strin return } -// RemoveCredentials removes one or more rows from credentials table -func (s Store) RemoveCredentials(ctx context.Context, rr ...*types.Credentials) (err error) { +// UpsertCredentials updates one or more existing rows in credentials +func (s Store) UpsertCredentials(ctx context.Context, rr ...*types.Credentials) (err error) { for _, res := range rr { - err = ExecuteSqlizer(ctx, s.DB(), s.Delete(s.CredentialsTable("crd")).Where(squirrel.Eq{s.preprocessColumn("crd.id", ""): s.preprocessValue(res.ID, "")})) + err = s.checkCredentialsConstraints(ctx, res) + if err != nil { + return err + } + + // err = s.credentialsHook(ctx, TriggerBeforeCredentialsUpsert, res) + // if err != nil { + // return err + // } + + err = s.config.ErrorHandler(s.execUpsertCredentials(ctx, s.internalCredentialsEncoder(res))) + if err != nil { + return err + } + } + + return nil +} + +// DeleteCredentials Deletes one or more rows from credentials table +func (s Store) DeleteCredentials(ctx context.Context, rr ...*types.Credentials) (err error) { + for _, res := range rr { + // err = s.credentialsHook(ctx, TriggerBeforeCredentialsDelete, res) + // if err != nil { + // return err + // } + + err = s.execDeleteCredentials(ctx, squirrel.Eq{ + s.preprocessColumn("crd.id", ""): s.preprocessValue(res.ID, ""), + }) if err != nil { return s.config.ErrorHandler(err) } @@ -121,35 +185,74 @@ func (s Store) RemoveCredentials(ctx context.Context, rr ...*types.Credentials) return nil } -// RemoveCredentialsByID removes row from the credentials table -func (s Store) RemoveCredentialsByID(ctx context.Context, ID uint64) error { - return s.config.ErrorHandler(ExecuteSqlizer(ctx, s.DB(), s.Delete(s.CredentialsTable("crd")).Where(squirrel.Eq{s.preprocessColumn("crd.id", ""): s.preprocessValue(ID, "")}))) +// DeleteCredentialsByID Deletes row from the credentials table +func (s Store) DeleteCredentialsByID(ctx context.Context, ID uint64) error { + return s.execDeleteCredentials(ctx, squirrel.Eq{ + s.preprocessColumn("crd.id", ""): s.preprocessValue(ID, ""), + }) } -// TruncateCredentials removes all rows from the credentials table +// TruncateCredentials Deletes all rows from the credentials table func (s Store) TruncateCredentials(ctx context.Context) error { - return s.config.ErrorHandler(Truncate(ctx, s.DB(), s.CredentialsTable())) + return s.config.ErrorHandler(s.Truncate(ctx, s.credentialsTable())) } -// ExecUpdateCredentials updates all matched (by cnd) rows in credentials with given data -func (s Store) ExecUpdateCredentials(ctx context.Context, cnd squirrel.Sqlizer, set store.Payload) error { - return s.config.ErrorHandler(ExecuteSqlizer(ctx, s.DB(), s.Update(s.CredentialsTable("crd")).Where(cnd).SetMap(set))) -} - -// CredentialsLookup prepares Credentials query and executes it, +// execLookupCredentials prepares Credentials query and executes it, // returning types.Credentials (or error) -func (s Store) CredentialsLookup(ctx context.Context, cnd squirrel.Sqlizer) (*types.Credentials, error) { - return s.internalCredentialsRowScanner(s.QueryRow(ctx, s.QueryCredentials().Where(cnd))) -} +func (s Store) execLookupCredentials(ctx context.Context, cnd squirrel.Sqlizer) (res *types.Credentials, err error) { + var ( + row rowScanner + ) -func (s Store) internalCredentialsRowScanner(row rowScanner, err error) (*types.Credentials, error) { + row, err = s.QueryRow(ctx, s.credentialsSelectBuilder().Where(cnd)) if err != nil { - return nil, err + return } - var res = &types.Credentials{} + res, err = s.internalCredentialsRowScanner(row) + if err != nil { + return + } + + return res, nil +} + +// execCreateCredentials updates all matched (by cnd) rows in credentials with given data +func (s Store) execCreateCredentials(ctx context.Context, payload store.Payload) error { + return s.config.ErrorHandler(s.Exec(ctx, s.InsertBuilder(s.credentialsTable()).SetMap(payload))) +} + +// execUpdateCredentials updates all matched (by cnd) rows in credentials with given data +func (s Store) execUpdateCredentials(ctx context.Context, cnd squirrel.Sqlizer, set store.Payload) error { + return s.config.ErrorHandler(s.Exec(ctx, s.UpdateBuilder(s.credentialsTable("crd")).Where(cnd).SetMap(set))) +} + +// execUpsertCredentials inserts new or updates matching (by-primary-key) rows in credentials with given data +func (s Store) execUpsertCredentials(ctx context.Context, set store.Payload) error { + upsert, err := s.config.UpsertBuilder( + s.config, + s.credentialsTable(), + set, + "id", + ) + + if err != nil { + return err + } + + return s.config.ErrorHandler(s.Exec(ctx, upsert)) +} + +// execDeleteCredentials Deletes all matched (by cnd) rows in credentials with given data +func (s Store) execDeleteCredentials(ctx context.Context, cnd squirrel.Sqlizer) error { + return s.config.ErrorHandler(s.Exec(ctx, s.DeleteBuilder(s.credentialsTable("crd")).Where(cnd))) +} + +func (s Store) internalCredentialsRowScanner(row rowScanner) (res *types.Credentials, err error) { + res = &types.Credentials{} + if _, has := s.config.RowScanners["credentials"]; has { - scanner := s.config.RowScanners["credentials"].(func(rowScanner, *types.Credentials) error) + scanner := s.config.RowScanners["credentials"].(func(_ rowScanner, _ *types.Credentials) error) err = scanner(row, res) } else { err = row.Scan( @@ -179,12 +282,12 @@ func (s Store) internalCredentialsRowScanner(row rowScanner, err error) (*types. } // QueryCredentials returns squirrel.SelectBuilder with set table and all columns -func (s Store) QueryCredentials() squirrel.SelectBuilder { - return s.Select(s.CredentialsTable("crd"), s.CredentialsColumns("crd")...) +func (s Store) credentialsSelectBuilder() squirrel.SelectBuilder { + return s.SelectBuilder(s.credentialsTable("crd"), s.credentialsColumns("crd")...) } -// CredentialsTable name of the db table -func (Store) CredentialsTable(aa ...string) string { +// credentialsTable name of the db table +func (Store) credentialsTable(aa ...string) string { var alias string if len(aa) > 0 { alias = " AS " + aa[0] @@ -196,7 +299,7 @@ func (Store) CredentialsTable(aa ...string) string { // CredentialsColumns returns all defined table columns // // With optional string arg, all columns are returned aliased -func (Store) CredentialsColumns(aa ...string) []string { +func (Store) credentialsColumns(aa ...string) []string { var alias string if len(aa) > 0 { alias = aa[0] + "." @@ -217,7 +320,7 @@ func (Store) CredentialsColumns(aa ...string) []string { } } -// {false true true false} +// {true true false false false} // internalCredentialsEncoder encodes fields from types.Credentials to store.Payload (map) // @@ -238,3 +341,16 @@ func (s Store) internalCredentialsEncoder(res *types.Credentials) store.Payload "deleted_at": res.DeletedAt, } } + +func (s *Store) checkCredentialsConstraints(ctx context.Context, res *types.Credentials) error { + + return nil +} + +// func (s *Store) credentialsHook(ctx context.Context, key triggerKey, res *types.Credentials) error { +// if fn, has := s.config.TriggerHandlers[key]; has { +// return fn.(func (ctx context.Context, s *Store, res *types.Credentials) error)(ctx, s, res) +// } +// +// return nil +// } diff --git a/store/rdbms/credentials.go b/store/rdbms/credentials.go index d7bb637d0..3fe955227 100644 --- a/store/rdbms/credentials.go +++ b/store/rdbms/credentials.go @@ -6,7 +6,7 @@ import ( ) func (s Store) convertCredentialsFilter(f types.CredentialsFilter) (query squirrel.SelectBuilder, err error) { - query = s.QueryCredentials() + query = s.credentialsSelectBuilder() if f.Kind != "" { query = query.Where(squirrel.Eq{"crd.kind": f.Kind}) diff --git a/store/rdbms/generic_selectors.go b/store/rdbms/generic_selectors.go index e243175bc..e7e3f08e2 100644 --- a/store/rdbms/generic_selectors.go +++ b/store/rdbms/generic_selectors.go @@ -4,13 +4,12 @@ import ( "context" "github.com/Masterminds/squirrel" "github.com/cortezaproject/corteza-server/pkg/rh" - "github.com/jmoiron/sqlx" "github.com/lann/builder" ) // Count counts all rows that match conditions from given query builder -func Count(ctx context.Context, db *sqlx.DB, q squirrel.SelectBuilder) (count uint, err error) { - // Remove order-bys for counting +func Count(ctx context.Context, db dbLayer, q squirrel.SelectBuilder) (count uint, err error) { + // Delete order-bys for counting q = builder.Delete(q, "OrderByParts").(squirrel.SelectBuilder) // Replace columns with count(*) @@ -61,43 +60,3 @@ func ApplyPaging(q squirrel.SelectBuilder, p rh.PageFilter) squirrel.SelectBuild return q } - -// FetchPaged fetches paged rows -// -// Deprecated -func FetchPaged(ctx context.Context, db *sqlx.DB, q squirrel.SelectBuilder, p rh.PageFilter, set interface{}) error { - if p.Limit+p.Offset == 0 { - // When both, offset & limit are 0, - // calculate both values from page/perPage params - if p.PerPage > 0 { - p.Limit = p.PerPage - } - - if p.Page < 1 { - p.Page = 1 - } - - p.Offset = (p.Page - 1) * p.PerPage - } - - if p.Limit > 0 { - q = q.Limit(uint64(p.Limit)) - } - - if p.Offset > 0 { - q = q.Offset(uint64(p.Offset)) - } - - return FetchAll(ctx, db, q, set) -} - -// FetchPaged fetches paged rows -// -// Deprecated -func FetchAll(ctx context.Context, db *sqlx.DB, q squirrel.Sqlizer, set interface{}) error { - if sqlSelect, argsSelect, err := q.ToSql(); err != nil { - return err - } else { - return db.SelectContext(ctx, set, sqlSelect, argsSelect...) - } -} diff --git a/store/rdbms/generic_upgrades.go b/store/rdbms/generic_upgrades.go index 94f540dd2..e48b57a6d 100644 --- a/store/rdbms/generic_upgrades.go +++ b/store/rdbms/generic_upgrades.go @@ -103,7 +103,7 @@ func (g genericUpgrades) MergeSettingsTables(ctx context.Context) error { if exists, err := g.u.TableExists(ctx, t.tbl); err != nil { return err } else if !exists { - g.log.Debug(fmt.Sprintf("skipping settings merge, table %s already removed", t.tbl)) + g.log.Debug(fmt.Sprintf("skipping settings merge, table %s already Deleted", t.tbl)) continue } @@ -117,7 +117,7 @@ func (g genericUpgrades) MergeSettingsTables(ctx context.Context) error { return fmt.Errorf("could not drop %s: %w", t.tbl, err) } - g.log.Debug(fmt.Sprintf("table %s merged into settings and removed", t.tbl)) + g.log.Debug(fmt.Sprintf("table %s merged into settings and Deleted", t.tbl)) } return nil @@ -147,7 +147,7 @@ func (g genericUpgrades) MergePermissionRulesTables(ctx context.Context) error { if exists, err := g.u.TableExists(ctx, t.tbl); err != nil { return err } else if !exists { - g.log.Debug(fmt.Sprintf("skipping rbac rules merge, table %s already removed", t.tbl)) + g.log.Debug(fmt.Sprintf("skipping rbac rules merge, table %s already Deleted", t.tbl)) continue } @@ -161,7 +161,7 @@ func (g genericUpgrades) MergePermissionRulesTables(ctx context.Context) error { return fmt.Errorf("could not drop %s: %w", t.tbl, err) } - g.log.Debug(fmt.Sprintf("table %s merged into rbac_rules and removed", t.tbl)) + g.log.Debug(fmt.Sprintf("table %s merged into rbac_rules and Deleted", t.tbl)) } return nil diff --git a/store/rdbms/metrics.go b/store/rdbms/metrics.go index 3a4ca3e26..e81d5788e 100644 --- a/store/rdbms/metrics.go +++ b/store/rdbms/metrics.go @@ -27,14 +27,7 @@ func (s Store) multiDailyMetrics(ctx context.Context, q squirrel.SelectBuilder, // // This function is copied from old repository and adapted to work under store, // might be good to refactor it to fit into this store pattern a bit more -func (s Store) dailyMetrics(ctx context.Context, q squirrel.SelectBuilder, field string) (rval []uint, err error) { - var ( - aux = make([]struct { - Timestamp uint - Value uint - }, 0) - ) - +func (s Store) dailyMetrics(ctx context.Context, q squirrel.SelectBuilder, field string) ([]uint, error) { q = q. Column(fmt.Sprintf("UNIX_TIMESTAMP(DATE(%s)) timestamp", field)). Column("COUNT(*) AS value"). @@ -42,16 +35,27 @@ func (s Store) dailyMetrics(ctx context.Context, q squirrel.SelectBuilder, field OrderBy("timestamp"). GroupBy("timestamp") - sql, args := q.MustSql() + var ( + rval = make([]uint, 0, 100) + ts, v, i uint + rows, err = s.Query(ctx, q) + ) + + if err != nil { + return nil, err + } + + return rval, func() (err error) { + defer rows.Close() + for rows.Next() { + if err = rows.Scan(&ts, &v); err != nil { + return err + } + + rval[2*i], rval[2*i+1] = ts, v + i++ + } - if err = s.db.SelectContext(ctx, &aux, sql, args...); err != nil { return - } - - rval = make([]uint, 2*len(aux)) - for i := 0; i < len(aux); i++ { - rval[2*i], rval[2*i+1] = aux[i].Timestamp, aux[i].Value - } - - return + }() } diff --git a/store/rdbms/rbac_rules.gen.go b/store/rdbms/rbac_rules.gen.go index 14775e6ff..e5bc0b27c 100644 --- a/store/rdbms/rbac_rules.gen.go +++ b/store/rdbms/rbac_rules.gen.go @@ -11,19 +11,29 @@ package rdbms import ( "context" "database/sql" + "errors" "fmt" "github.com/Masterminds/squirrel" "github.com/cortezaproject/corteza-server/pkg/permissions" "github.com/cortezaproject/corteza-server/store" ) +var _ = errors.Is + +const ( + TriggerBeforeRbacRuleCreate triggerKey = "rbacRuleBeforeCreate" + TriggerBeforeRbacRuleUpdate triggerKey = "rbacRuleBeforeUpdate" + TriggerBeforeRbacRuleUpsert triggerKey = "rbacRuleBeforeUpsert" + TriggerBeforeRbacRuleDelete triggerKey = "rbacRuleBeforeDelete" +) + // SearchRbacRules returns all matching rows // // This function calls convertRbacRuleFilter with the given // permissions.RuleFilter and expects to receive a working squirrel.SelectBuilder func (s Store) SearchRbacRules(ctx context.Context, f permissions.RuleFilter) (permissions.RuleSet, permissions.RuleFilter, error) { var scap uint - q := s.QueryRbacRules() + q := s.rbacRulesSelectBuilder() if scap == 0 { scap = DefaultSliceCapacity @@ -32,7 +42,7 @@ func (s Store) SearchRbacRules(ctx context.Context, f permissions.RuleFilter) (p var ( set = make([]*permissions.Rule, 0, scap) // Paging is disabled in definition yaml file - // {search: {disablePaging:true}} and this allows + // {search: {enablePaging:false}} and this allows // a much simpler row fetching logic fetch = func() error { var ( @@ -45,14 +55,19 @@ func (s Store) SearchRbacRules(ctx context.Context, f permissions.RuleFilter) (p } for rows.Next() { - if res, err = s.internalRbacRuleRowScanner(rows, rows.Err()); err != nil { + if rows.Err() == nil { + res, err = s.internalRbacRuleRowScanner(rows) + } + + if err != nil { if cerr := rows.Close(); cerr != nil { - return fmt.Errorf("could not close rows (%v) after scan error: %w", cerr, err) + 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 set = append(set, res) } @@ -66,9 +81,19 @@ func (s Store) SearchRbacRules(ctx context.Context, f permissions.RuleFilter) (p // CreateRbacRule creates one or more rows in rbac_rules table func (s Store) CreateRbacRule(ctx context.Context, rr ...*permissions.Rule) (err error) { for _, res := range rr { - err = ExecuteSqlizer(ctx, s.DB(), s.Insert(s.RbacRuleTable()).SetMap(s.internalRbacRuleEncoder(res))) + err = s.checkRbacRuleConstraints(ctx, res) if err != nil { - return s.config.ErrorHandler(err) + return err + } + + // err = s.rbacRuleHook(ctx, TriggerBeforeRbacRuleCreate, res) + // if err != nil { + // return err + // } + + err = s.execCreateRbacRules(ctx, s.internalRbacRuleEncoder(res)) + if err != nil { + return err } } @@ -77,19 +102,26 @@ func (s Store) CreateRbacRule(ctx context.Context, rr ...*permissions.Rule) (err // UpdateRbacRule updates one or more existing rows in rbac_rules func (s Store) UpdateRbacRule(ctx context.Context, rr ...*permissions.Rule) error { - return s.config.ErrorHandler(s.PartialUpdateRbacRule(ctx, nil, rr...)) + return s.config.ErrorHandler(s.PartialRbacRuleUpdate(ctx, nil, rr...)) } -// PartialUpdateRbacRule updates one or more existing rows in rbac_rules -// -// It wraps the update into transaction and can perform partial update by providing list of updatable columns -func (s Store) PartialUpdateRbacRule(ctx context.Context, onlyColumns []string, rr ...*permissions.Rule) (err error) { +// PartialRbacRuleUpdate updates one or more existing rows in rbac_rules +func (s Store) PartialRbacRuleUpdate(ctx context.Context, onlyColumns []string, rr ...*permissions.Rule) (err error) { for _, res := range rr { - err = s.ExecUpdateRbacRules( + err = s.checkRbacRuleConstraints(ctx, res) + if err != nil { + return err + } + + // err = s.rbacRuleHook(ctx, TriggerBeforeRbacRuleUpdate, res) + // if err != nil { + // return err + // } + + err = s.execUpdateRbacRules( ctx, - squirrel.Eq{s.preprocessColumn("rls.rel_role", ""): s.preprocessValue(res.RoleID, ""), - s.preprocessColumn("rls.resource", ""): s.preprocessValue(res.Resource, ""), - s.preprocessColumn("rls.operation", ""): s.preprocessValue(res.Operation, ""), + squirrel.Eq{ + s.preprocessColumn("rls.rel_role", ""): s.preprocessValue(res.RoleID, ""), s.preprocessColumn("rls.resource", ""): s.preprocessValue(res.Resource, ""), s.preprocessColumn("rls.operation", ""): s.preprocessValue(res.Operation, ""), }, s.internalRbacRuleEncoder(res).Skip("rel_role", "resource", "operation").Only(onlyColumns...)) if err != nil { @@ -100,13 +132,39 @@ func (s Store) PartialUpdateRbacRule(ctx context.Context, onlyColumns []string, return } -// RemoveRbacRule removes one or more rows from rbac_rules table -func (s Store) RemoveRbacRule(ctx context.Context, rr ...*permissions.Rule) (err error) { +// UpsertRbacRule updates one or more existing rows in rbac_rules +func (s Store) UpsertRbacRule(ctx context.Context, rr ...*permissions.Rule) (err error) { for _, res := range rr { - err = ExecuteSqlizer(ctx, s.DB(), s.Delete(s.RbacRuleTable("rls")).Where(squirrel.Eq{s.preprocessColumn("rls.rel_role", ""): s.preprocessValue(res.RoleID, ""), - s.preprocessColumn("rls.resource", ""): s.preprocessValue(res.Resource, ""), - s.preprocessColumn("rls.operation", ""): s.preprocessValue(res.Operation, ""), - })) + err = s.checkRbacRuleConstraints(ctx, res) + if err != nil { + return err + } + + // err = s.rbacRuleHook(ctx, TriggerBeforeRbacRuleUpsert, res) + // if err != nil { + // return err + // } + + err = s.config.ErrorHandler(s.execUpsertRbacRules(ctx, s.internalRbacRuleEncoder(res))) + if err != nil { + return err + } + } + + return nil +} + +// DeleteRbacRule Deletes one or more rows from rbac_rules table +func (s Store) DeleteRbacRule(ctx context.Context, rr ...*permissions.Rule) (err error) { + for _, res := range rr { + // err = s.rbacRuleHook(ctx, TriggerBeforeRbacRuleDelete, res) + // if err != nil { + // return err + // } + + err = s.execDeleteRbacRules(ctx, squirrel.Eq{ + s.preprocessColumn("rls.rel_role", ""): s.preprocessValue(res.RoleID, ""), s.preprocessColumn("rls.resource", ""): s.preprocessValue(res.Resource, ""), s.preprocessColumn("rls.operation", ""): s.preprocessValue(res.Operation, ""), + }) if err != nil { return s.config.ErrorHandler(err) } @@ -115,40 +173,78 @@ func (s Store) RemoveRbacRule(ctx context.Context, rr ...*permissions.Rule) (err return nil } -// RemoveRbacRuleByRoleIDResourceOperation removes row from the rbac_rules table -func (s Store) RemoveRbacRuleByRoleIDResourceOperation(ctx context.Context, roleID uint64, resource string, operation string) error { - return s.config.ErrorHandler(ExecuteSqlizer(ctx, s.DB(), s.Delete(s.RbacRuleTable("rls")).Where(squirrel.Eq{s.preprocessColumn("rls.rel_role", ""): s.preprocessValue(roleID, ""), - - s.preprocessColumn("rls.resource", ""): s.preprocessValue(resource, ""), - +// DeleteRbacRuleByRoleIDResourceOperation Deletes row from the rbac_rules table +func (s Store) DeleteRbacRuleByRoleIDResourceOperation(ctx context.Context, roleID uint64, resource string, operation string) error { + return s.execDeleteRbacRules(ctx, squirrel.Eq{ + s.preprocessColumn("rls.rel_role", ""): s.preprocessValue(roleID, ""), + s.preprocessColumn("rls.resource", ""): s.preprocessValue(resource, ""), s.preprocessColumn("rls.operation", ""): s.preprocessValue(operation, ""), - }))) + }) } -// TruncateRbacRules removes all rows from the rbac_rules table +// TruncateRbacRules Deletes all rows from the rbac_rules table func (s Store) TruncateRbacRules(ctx context.Context) error { - return s.config.ErrorHandler(Truncate(ctx, s.DB(), s.RbacRuleTable())) + return s.config.ErrorHandler(s.Truncate(ctx, s.rbacRuleTable())) } -// ExecUpdateRbacRules updates all matched (by cnd) rows in rbac_rules with given data -func (s Store) ExecUpdateRbacRules(ctx context.Context, cnd squirrel.Sqlizer, set store.Payload) error { - return s.config.ErrorHandler(ExecuteSqlizer(ctx, s.DB(), s.Update(s.RbacRuleTable("rls")).Where(cnd).SetMap(set))) -} - -// RbacRuleLookup prepares RbacRule query and executes it, +// execLookupRbacRule prepares RbacRule query and executes it, // returning permissions.Rule (or error) -func (s Store) RbacRuleLookup(ctx context.Context, cnd squirrel.Sqlizer) (*permissions.Rule, error) { - return s.internalRbacRuleRowScanner(s.QueryRow(ctx, s.QueryRbacRules().Where(cnd))) -} +func (s Store) execLookupRbacRule(ctx context.Context, cnd squirrel.Sqlizer) (res *permissions.Rule, err error) { + var ( + row rowScanner + ) -func (s Store) internalRbacRuleRowScanner(row rowScanner, err error) (*permissions.Rule, error) { + row, err = s.QueryRow(ctx, s.rbacRulesSelectBuilder().Where(cnd)) if err != nil { - return nil, err + return } - var res = &permissions.Rule{} + res, err = s.internalRbacRuleRowScanner(row) + if err != nil { + return + } + + return res, nil +} + +// execCreateRbacRules updates all matched (by cnd) rows in rbac_rules with given data +func (s Store) execCreateRbacRules(ctx context.Context, payload store.Payload) error { + return s.config.ErrorHandler(s.Exec(ctx, s.InsertBuilder(s.rbacRuleTable()).SetMap(payload))) +} + +// execUpdateRbacRules updates all matched (by cnd) rows in rbac_rules with given data +func (s Store) execUpdateRbacRules(ctx context.Context, cnd squirrel.Sqlizer, set store.Payload) error { + return s.config.ErrorHandler(s.Exec(ctx, s.UpdateBuilder(s.rbacRuleTable("rls")).Where(cnd).SetMap(set))) +} + +// execUpsertRbacRules inserts new or updates matching (by-primary-key) rows in rbac_rules with given data +func (s Store) execUpsertRbacRules(ctx context.Context, set store.Payload) error { + upsert, err := s.config.UpsertBuilder( + s.config, + s.rbacRuleTable(), + set, + "rel_role", + "resource", + "operation", + ) + + if err != nil { + return err + } + + return s.config.ErrorHandler(s.Exec(ctx, upsert)) +} + +// execDeleteRbacRules Deletes all matched (by cnd) rows in rbac_rules with given data +func (s Store) execDeleteRbacRules(ctx context.Context, cnd squirrel.Sqlizer) error { + return s.config.ErrorHandler(s.Exec(ctx, s.DeleteBuilder(s.rbacRuleTable("rls")).Where(cnd))) +} + +func (s Store) internalRbacRuleRowScanner(row rowScanner) (res *permissions.Rule, err error) { + res = &permissions.Rule{} + if _, has := s.config.RowScanners["rbacRule"]; has { - scanner := s.config.RowScanners["rbacRule"].(func(rowScanner, *permissions.Rule) error) + scanner := s.config.RowScanners["rbacRule"].(func(_ rowScanner, _ *permissions.Rule) error) err = scanner(row, res) } else { err = row.Scan( @@ -171,12 +267,12 @@ func (s Store) internalRbacRuleRowScanner(row rowScanner, err error) (*permissio } // QueryRbacRules returns squirrel.SelectBuilder with set table and all columns -func (s Store) QueryRbacRules() squirrel.SelectBuilder { - return s.Select(s.RbacRuleTable("rls"), s.RbacRuleColumns("rls")...) +func (s Store) rbacRulesSelectBuilder() squirrel.SelectBuilder { + return s.SelectBuilder(s.rbacRuleTable("rls"), s.rbacRuleColumns("rls")...) } -// RbacRuleTable name of the db table -func (Store) RbacRuleTable(aa ...string) string { +// rbacRuleTable name of the db table +func (Store) rbacRuleTable(aa ...string) string { var alias string if len(aa) > 0 { alias = " AS " + aa[0] @@ -188,7 +284,7 @@ func (Store) RbacRuleTable(aa ...string) string { // RbacRuleColumns returns all defined table columns // // With optional string arg, all columns are returned aliased -func (Store) RbacRuleColumns(aa ...string) []string { +func (Store) rbacRuleColumns(aa ...string) []string { var alias string if len(aa) > 0 { alias = aa[0] + "." @@ -202,7 +298,7 @@ func (Store) RbacRuleColumns(aa ...string) []string { } } -// {false true true false} +// {true true false false false} // internalRbacRuleEncoder encodes fields from permissions.Rule to store.Payload (map) // @@ -216,3 +312,16 @@ func (s Store) internalRbacRuleEncoder(res *permissions.Rule) store.Payload { "access": res.Access, } } + +func (s *Store) checkRbacRuleConstraints(ctx context.Context, res *permissions.Rule) error { + + return nil +} + +// func (s *Store) rbacRuleHook(ctx context.Context, key triggerKey, res *permissions.Rule) error { +// if fn, has := s.config.TriggerHandlers[key]; has { +// return fn.(func (ctx context.Context, s *Store, res *permissions.Rule) error)(ctx, s, res) +// } +// +// return nil +// } diff --git a/store/rdbms/rdbms.go b/store/rdbms/rdbms.go index b97d88b22..14fc7fe9b 100644 --- a/store/rdbms/rdbms.go +++ b/store/rdbms/rdbms.go @@ -5,7 +5,7 @@ import ( "database/sql" "fmt" "github.com/Masterminds/squirrel" - "github.com/cortezaproject/corteza-server/store/bulk" + "github.com/cortezaproject/corteza-server/store" "github.com/cortezaproject/corteza-server/store/rdbms/ddl" "github.com/jmoiron/sqlx" "strings" @@ -27,6 +27,7 @@ type ( columnPreprocFn func(string, string) string valuePreprocFn func(interface{}, string) interface{} errorHandler func(error) error + triggerKey string schemaUpgradeGenerator interface { TableExists(string) bool @@ -38,6 +39,8 @@ type ( Scan(...interface{}) error } + TriggerHandlers map[triggerKey]interface{} + Config struct { DriverName string DataSourceName string @@ -45,6 +48,14 @@ type ( PlaceholderFormat squirrel.PlaceholderFormat + // These 3 are passed directly to connection + MaxOpenConns int + ConnMaxLifetime time.Duration + MaxIdleConns int + + // Disable transactions + TxDisabled bool + // How many times should we retry failed transaction? TxMaxRetries int @@ -63,6 +74,19 @@ type ( // Implementations can override internal RDBMS row scanners RowScanners map[string]interface{} + + // Different store backend implementation might handle upsert differently... + UpsertBuilder func(*Config, string, store.Payload, ...string) (squirrel.InsertBuilder, error) + + // TriggerHandlers handle various exceptions that can not be handled generally within RDBMS package. + // see triggerKey type and defined constants to see where the hooks are and how can they be called + TriggerHandlers TriggerHandlers + + // UniqueConstraintCheck flag controls if unique constraints should be explicitly checked within + // store or is this handled inside the storage + // + // + UniqueConstraintCheck bool } Store struct { @@ -72,7 +96,19 @@ type ( // to implementation specific SQL sug schemaUpgradeGenerator - db *sqlx.DB + db dbLayer + } + + dbLayer interface { + sqlx.ExecerContext + SelectContext(context.Context, interface{}, string, ...interface{}) error + GetContext(context.Context, interface{}, string, ...interface{}) error + QueryRowContext(context.Context, string, ...interface{}) *sql.Row + QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error) + } + + dbTransactionMaker interface { + BeginTxx(ctx context.Context, opts *sql.TxOptions) (*sqlx.Tx, error) } ) @@ -86,27 +122,6 @@ const ( MaxRefetches = 100 ) -var ( - now = func() time.Time { - return time.Now() - } -) - -//func Instrumentation(log *zap.Logger) { -// logger := instrumentedsql.LoggerFunc(func(ctx context.Context, msg string, keyvals ...interface{}) { -// //spew.Dump(msg, keyvals) -// log.With(zap.Any("kv", keyvals)).Info(msg) -// }) -// -// sql.Register( -// "mysql+instrumented", -// instrumentedsql.WrapDriver(&mysql.MySQLDriver{}, instrumentedsql.WithLogger(logger))) -// -// sql.Register( -// "postgres+instrumented", -// instrumentedsql.WrapDriver(&pq.Driver{}, instrumentedsql.WithLogger(logger))) -//} - func New(ctx context.Context, cfg *Config) (*Store, error) { var s = &Store{ config: cfg, @@ -129,6 +144,19 @@ func New(ctx context.Context, cfg *Config) (*Store, error) { s.config.ErrorHandler = ErrHandlerFallthrough } + if s.config.UpsertBuilder == nil { + s.config.UpsertBuilder = UpsertBuilder + } + + if s.config.MaxIdleConns == 0 { + // Same as default in the db/sql + s.config.MaxIdleConns = 2 + } + + if s.config.TriggerHandlers == nil { + s.config.TriggerHandlers = TriggerHandlers{} + } + if err := s.Connect(ctx); err != nil { return nil, err } @@ -136,16 +164,27 @@ func New(ctx context.Context, cfg *Config) (*Store, error) { return s, nil } -func (s *Store) Connect(ctx context.Context) (err error) { - s.db, err = sqlx.ConnectContext(ctx, s.config.DriverName, s.config.DataSourceName) - return err +// WithTx spins up new store instance with transaction +func (s *Store) withTx(tx dbLayer) *Store { + return &Store{ + config: s.config, + sug: s.sug, + db: tx, + } } -// Select is a shorthand for squirrel.SelectBuilder -// -// Sets passed table & columns and configured placeholder format -func (s Store) Select(table string, cc ...string) squirrel.SelectBuilder { - return squirrel.Select(cc...).From(table).PlaceholderFormat(s.config.PlaceholderFormat) +func (s *Store) Connect(ctx context.Context) error { + db, err := sqlx.ConnectContext(ctx, s.config.DriverName, s.config.DataSourceName) + if err != nil { + return err + } + + db.SetMaxOpenConns(s.config.MaxOpenConns) + db.SetConnMaxLifetime(s.config.ConnMaxLifetime) + db.SetMaxIdleConns(s.config.MaxIdleConns) + + s.db = db + return err } func (s Store) Query(ctx context.Context, q squirrel.SelectBuilder) (*sql.Rows, error) { @@ -167,28 +206,57 @@ func (s Store) QueryRow(ctx context.Context, q squirrel.SelectBuilder) (*sql.Row return s.db.QueryRowContext(ctx, query, args...), nil } -// Insert is a shorthand for squirrel.InsertBuilder +func (s Store) Exec(ctx context.Context, sqlizer squirrel.Sqlizer) error { + query, args, err := sqlizer.ToSql() + + if err != nil { + return err + } + + _, err = s.db.ExecContext(ctx, query, args...) + return err +} + +func (s Store) Tx(ctx context.Context, fn func(context.Context, store.Storable) error) error { + return tx(ctx, s.db, s.config, nil, func(ctx context.Context, tx dbLayer) error { + return fn(ctx, s.withTx(tx)) + }) +} + +func (s Store) Truncate(ctx context.Context, table string) error { + _, err := s.db.ExecContext(ctx, "DELETE FROM "+table) + return err +} + +// SelectBuilder is a shorthand for squirrel.selectBuilder +// +// Sets passed table & columns and configured placeholder format +func (s Store) SelectBuilder(table string, cc ...string) squirrel.SelectBuilder { + return squirrel.Select(cc...).From(table).PlaceholderFormat(s.config.PlaceholderFormat) +} + +// InsertBuilder is a shorthand for squirrel.insertBuilder // // Sets passed table and configured placeholder format -func (s Store) Insert(table string) squirrel.InsertBuilder { +func (s Store) InsertBuilder(table string) squirrel.InsertBuilder { return squirrel.Insert(table).PlaceholderFormat(s.config.PlaceholderFormat) } -// Update is a shorthand for squirrel.UpdateBuilder +// UpdateBuilder is a shorthand for squirrel.updateBuilder // // Sets passed table and configured placeholder format -func (s Store) Update(table string) squirrel.UpdateBuilder { +func (s Store) UpdateBuilder(table string) squirrel.UpdateBuilder { return squirrel.Update(table).PlaceholderFormat(s.config.PlaceholderFormat) } -// Delete is a shorthand for squirrel.DeleteBuilder +// DeleteBuilder is a shorthand for squirrel.deleteBuilder // // Sets passed table and configured placeholder format -func (s Store) Delete(table string) squirrel.DeleteBuilder { +func (s Store) DeleteBuilder(table string) squirrel.DeleteBuilder { return squirrel.Delete(table).PlaceholderFormat(s.config.PlaceholderFormat) } -func (s Store) DB() *sqlx.DB { +func (s Store) DB() dbLayer { return s.db } @@ -196,33 +264,6 @@ func (s Store) Config() *Config { return s.config } -// Bulk returns channel that accepts jobs and executes them inside a transaction -// -// Note: This is experimental function! -// Final version might not return channel directly -// -func (s Store) Bulk(ctx context.Context) chan bulk.Job { - jc := make(chan bulk.Job) - - go Tx(ctx, s.db, s.config, nil, func(db *sqlx.Tx) (err error) { - var job bulk.Job - - for { - select { - case <-ctx.Done(): - return ctx.Err() - - case job = <-jc: - if err = job.Do(ctx, s); err != nil { - return - } - } - } - }) - - return jc -} - // column preprocessor logic to modify db value before using it in condition filter // // It checks registered ColumnPreprocessors from config @@ -269,34 +310,35 @@ func (s Store) preprocessValue(val interface{}, p string) interface{} { } } -func ExecuteSqlizer(ctx context.Context, db sqlx.ExecerContext, sqlizer squirrel.Sqlizer) error { - query, args, err := sqlizer.ToSql() - - if err != nil { - return err - } - - _, err = db.ExecContext(ctx, query, args...) - return err -} - -func Truncate(ctx context.Context, db sqlx.ExecerContext, table string) error { - _, err := db.ExecContext(ctx, "DELETE FROM "+table) - return err -} - -// Tx begins a new db transaction and handles it's retries when possible +// tx begins a new db transaction and handles it's retries when possible // // It utilizes configured transaction error handlers and max-retry limits // to determine if and how many times transaction should be retried -func Tx(ctx context.Context, db *sqlx.DB, cfg *Config, txOpt *sql.TxOptions, task func(*sqlx.Tx) error) error { +// +func tx(ctx context.Context, dbCandidate interface{}, cfg *Config, txOpt *sql.TxOptions, task func(context.Context, dbLayer) error) error { + if cfg.TxDisabled { + return task(ctx, dbCandidate.(dbLayer)) + } + var ( lastTaskErr error err error + db *sqlx.DB tx *sqlx.Tx try = 1 ) + switch dbCandidate.(type) { + case dbTransactionMaker: + // we can make a transaction, yay + db = dbCandidate.(*sqlx.DB) + case dbLayer: + // Already in a transaction, run the given task and finish + return task(ctx, dbCandidate.(dbLayer)) + default: + return fmt.Errorf("could not use the db connection for transaction") + } + for { try++ @@ -306,7 +348,7 @@ func Tx(ctx context.Context, db *sqlx.DB, cfg *Config, txOpt *sql.TxOptions, tas return nil } - if lastTaskErr = task(tx); lastTaskErr == nil { + if lastTaskErr = task(ctx, tx); lastTaskErr == nil { // Task completed successfully return tx.Commit() } @@ -325,7 +367,7 @@ func Tx(ctx context.Context, db *sqlx.DB, cfg *Config, txOpt *sql.TxOptions, tas return fmt.Errorf("failed to complete transaction: %w", lastTaskErr) } - // Tx error handlers can take current number of tries into account and + // tx error handlers can take current number of tries into account and // break the retry-loop earlier, but that might not be always the case // // We'll check the configured and hard-limit maximums diff --git a/store/rdbms/rdbms_schema.go b/store/rdbms/rdbms_schema.go index 6ada8c091..2cde260ca 100644 --- a/store/rdbms/rdbms_schema.go +++ b/store/rdbms/rdbms_schema.go @@ -89,6 +89,7 @@ func (Schema) Users() *Table { CUDTimestamps, AddIndex("unique_email", IExpr("LOWER(email)"), IWhere("LENGTH(email) > 0 AND deleted_at IS NULL AND suspended_at IS NULL")), + AddIndex("unique_username", IExpr("LOWER(username)"), IWhere("LENGTH(username) > 0 AND deleted_at IS NULL AND suspended_at IS NULL")), AddIndex("unique_handle", IExpr("LOWER(handle)"), IWhere("LENGTH(handle) > 0 AND deleted_at IS NULL AND suspended_at IS NULL")), ) } @@ -203,7 +204,7 @@ func (Schema) RbacRules() *Table { ColumnDef("operation", ColumnTypeVarchar, ColumnTypeLength(50)), ColumnDef("access", ColumnTypeInteger), - PrimaryKey(IColumn("rel_role", "resource", "operation", "access")), + PrimaryKey(IColumn("rel_role", "resource", "operation")), ) } diff --git a/store/rdbms/reminder.go b/store/rdbms/reminder.go index ddb8844a1..1d0b1a220 100644 --- a/store/rdbms/reminder.go +++ b/store/rdbms/reminder.go @@ -7,7 +7,7 @@ import ( ) func (s Store) convertReminderFilter(f types.ReminderFilter) (query squirrel.SelectBuilder, err error) { - query = s.QueryReminders() + query = s.remindersSelectBuilder() if len(f.ReminderID) > 0 { query = query.Where(squirrel.Eq{"rmd.ID": f.ReminderID}) diff --git a/store/rdbms/reminders.gen.go b/store/rdbms/reminders.gen.go index 8732e32c8..83b5acfa8 100644 --- a/store/rdbms/reminders.gen.go +++ b/store/rdbms/reminders.gen.go @@ -11,13 +11,24 @@ package rdbms import ( "context" "database/sql" + "errors" "fmt" "github.com/Masterminds/squirrel" + "github.com/cortezaproject/corteza-server/pkg/filter" "github.com/cortezaproject/corteza-server/store" "github.com/cortezaproject/corteza-server/system/types" "strings" ) +var _ = errors.Is + +const ( + TriggerBeforeReminderCreate triggerKey = "reminderBeforeCreate" + TriggerBeforeReminderUpdate triggerKey = "reminderBeforeUpdate" + TriggerBeforeReminderUpsert triggerKey = "reminderBeforeUpsert" + TriggerBeforeReminderDelete triggerKey = "reminderBeforeDelete" +) + // SearchReminders returns all matching rows // // This function calls convertReminderFilter with the given @@ -38,7 +49,7 @@ func (s Store) SearchReminders(ctx context.Context, f types.ReminderFilter) (typ // This tells us to flip the descending flag on all used sort keys reverseCursor := f.PageCursor != nil && f.PageCursor.Reverse - if err = f.Sort.Validate(s.sortableReminderColumns()...); err != nil { + if err := f.Sort.Validate(s.sortableReminderColumns()...); err != nil { return nil, f, fmt.Errorf("could not validate sort: %v", err) } @@ -68,7 +79,7 @@ func (s Store) SearchReminders(ctx context.Context, f types.ReminderFilter) (typ var ( set = make([]*types.Reminder, 0, scap) - // fetches rows and scans them into Types.Reminder resource this is then passed to Check function on filter + // fetches rows and scans them into types.Reminder resource this is then passed to Check function on filter // to help determine if fetched resource fits or not // // Note that limit is passed explicitly and is not necessarily equal to filter's limit. We want @@ -77,7 +88,7 @@ func (s Store) SearchReminders(ctx context.Context, f types.ReminderFilter) (typ // The value for cursor is used and set directly from/to the filter! // // It returns total number of fetched pages and modifies PageCursor value for paging - fetchPage = func(cursor *store.PagingCursor, limit uint) (fetched uint, err error) { + fetchPage = func(cursor *filter.PagingCursor, limit uint) (fetched uint, err error) { var ( res *types.Reminder @@ -108,7 +119,12 @@ func (s Store) SearchReminders(ctx context.Context, f types.ReminderFilter) (typ for rows.Next() { fetched++ - if res, err = s.internalReminderRowScanner(rows, rows.Err()); err != nil { + + if rows.Err() == nil { + res, err = s.internalReminderRowScanner(rows) + } + + if err != nil { if cerr := rows.Close(); cerr != nil { err = fmt.Errorf("could not close rows (%v) after scan error: %w", cerr, err) } @@ -225,17 +241,27 @@ func (s Store) SearchReminders(ctx context.Context, f types.ReminderFilter) (typ // // It returns reminder even if deleted or suspended func (s Store) LookupReminderByID(ctx context.Context, id uint64) (*types.Reminder, error) { - return s.ReminderLookup(ctx, squirrel.Eq{ - "rmd.id": id, + return s.execLookupReminder(ctx, squirrel.Eq{ + s.preprocessColumn("rmd.id", ""): s.preprocessValue(id, ""), }) } // CreateReminder creates one or more rows in reminders table func (s Store) CreateReminder(ctx context.Context, rr ...*types.Reminder) (err error) { for _, res := range rr { - err = ExecuteSqlizer(ctx, s.DB(), s.Insert(s.ReminderTable()).SetMap(s.internalReminderEncoder(res))) + err = s.checkReminderConstraints(ctx, res) if err != nil { - return s.config.ErrorHandler(err) + return err + } + + // err = s.reminderHook(ctx, TriggerBeforeReminderCreate, res) + // if err != nil { + // return err + // } + + err = s.execCreateReminders(ctx, s.internalReminderEncoder(res)) + if err != nil { + return err } } @@ -244,17 +270,27 @@ func (s Store) CreateReminder(ctx context.Context, rr ...*types.Reminder) (err e // UpdateReminder updates one or more existing rows in reminders func (s Store) UpdateReminder(ctx context.Context, rr ...*types.Reminder) error { - return s.config.ErrorHandler(s.PartialUpdateReminder(ctx, nil, rr...)) + return s.config.ErrorHandler(s.PartialReminderUpdate(ctx, nil, rr...)) } -// PartialUpdateReminder updates one or more existing rows in reminders -// -// It wraps the update into transaction and can perform partial update by providing list of updatable columns -func (s Store) PartialUpdateReminder(ctx context.Context, onlyColumns []string, rr ...*types.Reminder) (err error) { +// PartialReminderUpdate updates one or more existing rows in reminders +func (s Store) PartialReminderUpdate(ctx context.Context, onlyColumns []string, rr ...*types.Reminder) (err error) { for _, res := range rr { - err = s.ExecUpdateReminders( + err = s.checkReminderConstraints(ctx, res) + if err != nil { + return err + } + + // err = s.reminderHook(ctx, TriggerBeforeReminderUpdate, res) + // if err != nil { + // return err + // } + + err = s.execUpdateReminders( ctx, - squirrel.Eq{s.preprocessColumn("rmd.id", ""): s.preprocessValue(res.ID, "")}, + squirrel.Eq{ + s.preprocessColumn("rmd.id", ""): s.preprocessValue(res.ID, ""), + }, s.internalReminderEncoder(res).Skip("id").Only(onlyColumns...)) if err != nil { return s.config.ErrorHandler(err) @@ -264,10 +300,39 @@ func (s Store) PartialUpdateReminder(ctx context.Context, onlyColumns []string, return } -// RemoveReminder removes one or more rows from reminders table -func (s Store) RemoveReminder(ctx context.Context, rr ...*types.Reminder) (err error) { +// UpsertReminder updates one or more existing rows in reminders +func (s Store) UpsertReminder(ctx context.Context, rr ...*types.Reminder) (err error) { for _, res := range rr { - err = ExecuteSqlizer(ctx, s.DB(), s.Delete(s.ReminderTable("rmd")).Where(squirrel.Eq{s.preprocessColumn("rmd.id", ""): s.preprocessValue(res.ID, "")})) + err = s.checkReminderConstraints(ctx, res) + if err != nil { + return err + } + + // err = s.reminderHook(ctx, TriggerBeforeReminderUpsert, res) + // if err != nil { + // return err + // } + + err = s.config.ErrorHandler(s.execUpsertReminders(ctx, s.internalReminderEncoder(res))) + if err != nil { + return err + } + } + + return nil +} + +// DeleteReminder Deletes one or more rows from reminders table +func (s Store) DeleteReminder(ctx context.Context, rr ...*types.Reminder) (err error) { + for _, res := range rr { + // err = s.reminderHook(ctx, TriggerBeforeReminderDelete, res) + // if err != nil { + // return err + // } + + err = s.execDeleteReminders(ctx, squirrel.Eq{ + s.preprocessColumn("rmd.id", ""): s.preprocessValue(res.ID, ""), + }) if err != nil { return s.config.ErrorHandler(err) } @@ -276,35 +341,74 @@ func (s Store) RemoveReminder(ctx context.Context, rr ...*types.Reminder) (err e return nil } -// RemoveReminderByID removes row from the reminders table -func (s Store) RemoveReminderByID(ctx context.Context, ID uint64) error { - return s.config.ErrorHandler(ExecuteSqlizer(ctx, s.DB(), s.Delete(s.ReminderTable("rmd")).Where(squirrel.Eq{s.preprocessColumn("rmd.id", ""): s.preprocessValue(ID, "")}))) +// DeleteReminderByID Deletes row from the reminders table +func (s Store) DeleteReminderByID(ctx context.Context, ID uint64) error { + return s.execDeleteReminders(ctx, squirrel.Eq{ + s.preprocessColumn("rmd.id", ""): s.preprocessValue(ID, ""), + }) } -// TruncateReminders removes all rows from the reminders table +// TruncateReminders Deletes all rows from the reminders table func (s Store) TruncateReminders(ctx context.Context) error { - return s.config.ErrorHandler(Truncate(ctx, s.DB(), s.ReminderTable())) + return s.config.ErrorHandler(s.Truncate(ctx, s.reminderTable())) } -// ExecUpdateReminders updates all matched (by cnd) rows in reminders with given data -func (s Store) ExecUpdateReminders(ctx context.Context, cnd squirrel.Sqlizer, set store.Payload) error { - return s.config.ErrorHandler(ExecuteSqlizer(ctx, s.DB(), s.Update(s.ReminderTable("rmd")).Where(cnd).SetMap(set))) -} - -// ReminderLookup prepares Reminder query and executes it, +// execLookupReminder prepares Reminder query and executes it, // returning types.Reminder (or error) -func (s Store) ReminderLookup(ctx context.Context, cnd squirrel.Sqlizer) (*types.Reminder, error) { - return s.internalReminderRowScanner(s.QueryRow(ctx, s.QueryReminders().Where(cnd))) -} +func (s Store) execLookupReminder(ctx context.Context, cnd squirrel.Sqlizer) (res *types.Reminder, err error) { + var ( + row rowScanner + ) -func (s Store) internalReminderRowScanner(row rowScanner, err error) (*types.Reminder, error) { + row, err = s.QueryRow(ctx, s.remindersSelectBuilder().Where(cnd)) if err != nil { - return nil, err + return } - var res = &types.Reminder{} + res, err = s.internalReminderRowScanner(row) + if err != nil { + return + } + + return res, nil +} + +// execCreateReminders updates all matched (by cnd) rows in reminders with given data +func (s Store) execCreateReminders(ctx context.Context, payload store.Payload) error { + return s.config.ErrorHandler(s.Exec(ctx, s.InsertBuilder(s.reminderTable()).SetMap(payload))) +} + +// execUpdateReminders updates all matched (by cnd) rows in reminders with given data +func (s Store) execUpdateReminders(ctx context.Context, cnd squirrel.Sqlizer, set store.Payload) error { + return s.config.ErrorHandler(s.Exec(ctx, s.UpdateBuilder(s.reminderTable("rmd")).Where(cnd).SetMap(set))) +} + +// execUpsertReminders inserts new or updates matching (by-primary-key) rows in reminders with given data +func (s Store) execUpsertReminders(ctx context.Context, set store.Payload) error { + upsert, err := s.config.UpsertBuilder( + s.config, + s.reminderTable(), + set, + "id", + ) + + if err != nil { + return err + } + + return s.config.ErrorHandler(s.Exec(ctx, upsert)) +} + +// execDeleteReminders Deletes all matched (by cnd) rows in reminders with given data +func (s Store) execDeleteReminders(ctx context.Context, cnd squirrel.Sqlizer) error { + return s.config.ErrorHandler(s.Exec(ctx, s.DeleteBuilder(s.reminderTable("rmd")).Where(cnd))) +} + +func (s Store) internalReminderRowScanner(row rowScanner) (res *types.Reminder, err error) { + res = &types.Reminder{} + if _, has := s.config.RowScanners["reminder"]; has { - scanner := s.config.RowScanners["reminder"].(func(rowScanner, *types.Reminder) error) + scanner := s.config.RowScanners["reminder"].(func(_ rowScanner, _ *types.Reminder) error) err = scanner(row, res) } else { err = row.Scan( @@ -336,12 +440,12 @@ func (s Store) internalReminderRowScanner(row rowScanner, err error) (*types.Rem } // QueryReminders returns squirrel.SelectBuilder with set table and all columns -func (s Store) QueryReminders() squirrel.SelectBuilder { - return s.Select(s.ReminderTable("rmd"), s.ReminderColumns("rmd")...) +func (s Store) remindersSelectBuilder() squirrel.SelectBuilder { + return s.SelectBuilder(s.reminderTable("rmd"), s.reminderColumns("rmd")...) } -// ReminderTable name of the db table -func (Store) ReminderTable(aa ...string) string { +// reminderTable name of the db table +func (Store) reminderTable(aa ...string) string { var alias string if len(aa) > 0 { alias = " AS " + aa[0] @@ -353,7 +457,7 @@ func (Store) ReminderTable(aa ...string) string { // ReminderColumns returns all defined table columns // // With optional string arg, all columns are returned aliased -func (Store) ReminderColumns(aa ...string) []string { +func (Store) reminderColumns(aa ...string) []string { var alias string if len(aa) > 0 { alias = aa[0] + "." @@ -376,7 +480,7 @@ func (Store) ReminderColumns(aa ...string) []string { } } -// {false false false false} +// {true true true true true} // sortableReminderColumns returns all Reminder columns flagged as sortable // @@ -413,9 +517,9 @@ func (s Store) internalReminderEncoder(res *types.Reminder) store.Payload { } } -func (s Store) collectReminderCursorValues(res *types.Reminder, cc ...string) *store.PagingCursor { +func (s Store) collectReminderCursorValues(res *types.Reminder, cc ...string) *filter.PagingCursor { var ( - cursor = &store.PagingCursor{} + cursor = &filter.PagingCursor{} hasUnique bool @@ -447,3 +551,16 @@ func (s Store) collectReminderCursorValues(res *types.Reminder, cc ...string) *s return cursor } + +func (s *Store) checkReminderConstraints(ctx context.Context, res *types.Reminder) error { + + return nil +} + +// func (s *Store) reminderHook(ctx context.Context, key triggerKey, res *types.Reminder) error { +// if fn, has := s.config.TriggerHandlers[key]; has { +// return fn.(func (ctx context.Context, s *Store, res *types.Reminder) error)(ctx, s, res) +// } +// +// return nil +// } diff --git a/store/rdbms/role_members.gen.go b/store/rdbms/role_members.gen.go index fddb28169..22fe1aa4f 100644 --- a/store/rdbms/role_members.gen.go +++ b/store/rdbms/role_members.gen.go @@ -11,19 +11,29 @@ package rdbms import ( "context" "database/sql" + "errors" "fmt" "github.com/Masterminds/squirrel" "github.com/cortezaproject/corteza-server/store" "github.com/cortezaproject/corteza-server/system/types" ) +var _ = errors.Is + +const ( + TriggerBeforeRoleMemberCreate triggerKey = "roleMemberBeforeCreate" + TriggerBeforeRoleMemberUpdate triggerKey = "roleMemberBeforeUpdate" + TriggerBeforeRoleMemberUpsert triggerKey = "roleMemberBeforeUpsert" + TriggerBeforeRoleMemberDelete triggerKey = "roleMemberBeforeDelete" +) + // SearchRoleMembers returns all matching rows // // This function calls convertRoleMemberFilter with the given // types.RoleMemberFilter and expects to receive a working squirrel.SelectBuilder func (s Store) SearchRoleMembers(ctx context.Context, f types.RoleMemberFilter) (types.RoleMemberSet, types.RoleMemberFilter, error) { var scap uint - q := s.QueryRoleMembers() + q := s.roleMembersSelectBuilder() if scap == 0 { scap = DefaultSliceCapacity @@ -32,7 +42,7 @@ func (s Store) SearchRoleMembers(ctx context.Context, f types.RoleMemberFilter) var ( set = make([]*types.RoleMember, 0, scap) // Paging is disabled in definition yaml file - // {search: {disablePaging:true}} and this allows + // {search: {enablePaging:false}} and this allows // a much simpler row fetching logic fetch = func() error { var ( @@ -45,14 +55,19 @@ func (s Store) SearchRoleMembers(ctx context.Context, f types.RoleMemberFilter) } for rows.Next() { - if res, err = s.internalRoleMemberRowScanner(rows, rows.Err()); err != nil { + if rows.Err() == nil { + res, err = s.internalRoleMemberRowScanner(rows) + } + + if err != nil { if cerr := rows.Close(); cerr != nil { - return fmt.Errorf("could not close rows (%v) after scan error: %w", cerr, err) + 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 set = append(set, res) } @@ -66,9 +81,19 @@ func (s Store) SearchRoleMembers(ctx context.Context, f types.RoleMemberFilter) // CreateRoleMember creates one or more rows in role_members table func (s Store) CreateRoleMember(ctx context.Context, rr ...*types.RoleMember) (err error) { for _, res := range rr { - err = ExecuteSqlizer(ctx, s.DB(), s.Insert(s.RoleMemberTable()).SetMap(s.internalRoleMemberEncoder(res))) + err = s.checkRoleMemberConstraints(ctx, res) if err != nil { - return s.config.ErrorHandler(err) + return err + } + + // err = s.roleMemberHook(ctx, TriggerBeforeRoleMemberCreate, res) + // if err != nil { + // return err + // } + + err = s.execCreateRoleMembers(ctx, s.internalRoleMemberEncoder(res)) + if err != nil { + return err } } @@ -77,18 +102,26 @@ func (s Store) CreateRoleMember(ctx context.Context, rr ...*types.RoleMember) (e // UpdateRoleMember updates one or more existing rows in role_members func (s Store) UpdateRoleMember(ctx context.Context, rr ...*types.RoleMember) error { - return s.config.ErrorHandler(s.PartialUpdateRoleMember(ctx, nil, rr...)) + return s.config.ErrorHandler(s.PartialRoleMemberUpdate(ctx, nil, rr...)) } -// PartialUpdateRoleMember updates one or more existing rows in role_members -// -// It wraps the update into transaction and can perform partial update by providing list of updatable columns -func (s Store) PartialUpdateRoleMember(ctx context.Context, onlyColumns []string, rr ...*types.RoleMember) (err error) { +// PartialRoleMemberUpdate updates one or more existing rows in role_members +func (s Store) PartialRoleMemberUpdate(ctx context.Context, onlyColumns []string, rr ...*types.RoleMember) (err error) { for _, res := range rr { - err = s.ExecUpdateRoleMembers( + err = s.checkRoleMemberConstraints(ctx, res) + if err != nil { + return err + } + + // err = s.roleMemberHook(ctx, TriggerBeforeRoleMemberUpdate, res) + // if err != nil { + // return err + // } + + err = s.execUpdateRoleMembers( ctx, - squirrel.Eq{s.preprocessColumn("rm.rel_user", ""): s.preprocessValue(res.UserID, ""), - s.preprocessColumn("rm.rel_role", ""): s.preprocessValue(res.RoleID, ""), + squirrel.Eq{ + s.preprocessColumn("rm.rel_user", ""): s.preprocessValue(res.UserID, ""), s.preprocessColumn("rm.rel_role", ""): s.preprocessValue(res.RoleID, ""), }, s.internalRoleMemberEncoder(res).Skip("rel_user", "rel_role").Only(onlyColumns...)) if err != nil { @@ -99,12 +132,39 @@ func (s Store) PartialUpdateRoleMember(ctx context.Context, onlyColumns []string return } -// RemoveRoleMember removes one or more rows from role_members table -func (s Store) RemoveRoleMember(ctx context.Context, rr ...*types.RoleMember) (err error) { +// UpsertRoleMember updates one or more existing rows in role_members +func (s Store) UpsertRoleMember(ctx context.Context, rr ...*types.RoleMember) (err error) { for _, res := range rr { - err = ExecuteSqlizer(ctx, s.DB(), s.Delete(s.RoleMemberTable("rm")).Where(squirrel.Eq{s.preprocessColumn("rm.rel_user", ""): s.preprocessValue(res.UserID, ""), - s.preprocessColumn("rm.rel_role", ""): s.preprocessValue(res.RoleID, ""), - })) + err = s.checkRoleMemberConstraints(ctx, res) + if err != nil { + return err + } + + // err = s.roleMemberHook(ctx, TriggerBeforeRoleMemberUpsert, res) + // if err != nil { + // return err + // } + + err = s.config.ErrorHandler(s.execUpsertRoleMembers(ctx, s.internalRoleMemberEncoder(res))) + if err != nil { + return err + } + } + + return nil +} + +// DeleteRoleMember Deletes one or more rows from role_members table +func (s Store) DeleteRoleMember(ctx context.Context, rr ...*types.RoleMember) (err error) { + for _, res := range rr { + // err = s.roleMemberHook(ctx, TriggerBeforeRoleMemberDelete, res) + // if err != nil { + // return err + // } + + err = s.execDeleteRoleMembers(ctx, squirrel.Eq{ + s.preprocessColumn("rm.rel_user", ""): s.preprocessValue(res.UserID, ""), s.preprocessColumn("rm.rel_role", ""): s.preprocessValue(res.RoleID, ""), + }) if err != nil { return s.config.ErrorHandler(err) } @@ -113,38 +173,76 @@ func (s Store) RemoveRoleMember(ctx context.Context, rr ...*types.RoleMember) (e return nil } -// RemoveRoleMemberByUserIDRoleID removes row from the role_members table -func (s Store) RemoveRoleMemberByUserIDRoleID(ctx context.Context, userID uint64, roleID uint64) error { - return s.config.ErrorHandler(ExecuteSqlizer(ctx, s.DB(), s.Delete(s.RoleMemberTable("rm")).Where(squirrel.Eq{s.preprocessColumn("rm.rel_user", ""): s.preprocessValue(userID, ""), - +// DeleteRoleMemberByUserIDRoleID Deletes row from the role_members table +func (s Store) DeleteRoleMemberByUserIDRoleID(ctx context.Context, userID uint64, roleID uint64) error { + return s.execDeleteRoleMembers(ctx, squirrel.Eq{ + s.preprocessColumn("rm.rel_user", ""): s.preprocessValue(userID, ""), s.preprocessColumn("rm.rel_role", ""): s.preprocessValue(roleID, ""), - }))) + }) } -// TruncateRoleMembers removes all rows from the role_members table +// TruncateRoleMembers Deletes all rows from the role_members table func (s Store) TruncateRoleMembers(ctx context.Context) error { - return s.config.ErrorHandler(Truncate(ctx, s.DB(), s.RoleMemberTable())) + return s.config.ErrorHandler(s.Truncate(ctx, s.roleMemberTable())) } -// ExecUpdateRoleMembers updates all matched (by cnd) rows in role_members with given data -func (s Store) ExecUpdateRoleMembers(ctx context.Context, cnd squirrel.Sqlizer, set store.Payload) error { - return s.config.ErrorHandler(ExecuteSqlizer(ctx, s.DB(), s.Update(s.RoleMemberTable("rm")).Where(cnd).SetMap(set))) -} - -// RoleMemberLookup prepares RoleMember query and executes it, +// execLookupRoleMember prepares RoleMember query and executes it, // returning types.RoleMember (or error) -func (s Store) RoleMemberLookup(ctx context.Context, cnd squirrel.Sqlizer) (*types.RoleMember, error) { - return s.internalRoleMemberRowScanner(s.QueryRow(ctx, s.QueryRoleMembers().Where(cnd))) -} +func (s Store) execLookupRoleMember(ctx context.Context, cnd squirrel.Sqlizer) (res *types.RoleMember, err error) { + var ( + row rowScanner + ) -func (s Store) internalRoleMemberRowScanner(row rowScanner, err error) (*types.RoleMember, error) { + row, err = s.QueryRow(ctx, s.roleMembersSelectBuilder().Where(cnd)) if err != nil { - return nil, err + return } - var res = &types.RoleMember{} + res, err = s.internalRoleMemberRowScanner(row) + if err != nil { + return + } + + return res, nil +} + +// execCreateRoleMembers updates all matched (by cnd) rows in role_members with given data +func (s Store) execCreateRoleMembers(ctx context.Context, payload store.Payload) error { + return s.config.ErrorHandler(s.Exec(ctx, s.InsertBuilder(s.roleMemberTable()).SetMap(payload))) +} + +// execUpdateRoleMembers updates all matched (by cnd) rows in role_members with given data +func (s Store) execUpdateRoleMembers(ctx context.Context, cnd squirrel.Sqlizer, set store.Payload) error { + return s.config.ErrorHandler(s.Exec(ctx, s.UpdateBuilder(s.roleMemberTable("rm")).Where(cnd).SetMap(set))) +} + +// execUpsertRoleMembers inserts new or updates matching (by-primary-key) rows in role_members with given data +func (s Store) execUpsertRoleMembers(ctx context.Context, set store.Payload) error { + upsert, err := s.config.UpsertBuilder( + s.config, + s.roleMemberTable(), + set, + "rel_user", + "rel_role", + ) + + if err != nil { + return err + } + + return s.config.ErrorHandler(s.Exec(ctx, upsert)) +} + +// execDeleteRoleMembers Deletes all matched (by cnd) rows in role_members with given data +func (s Store) execDeleteRoleMembers(ctx context.Context, cnd squirrel.Sqlizer) error { + return s.config.ErrorHandler(s.Exec(ctx, s.DeleteBuilder(s.roleMemberTable("rm")).Where(cnd))) +} + +func (s Store) internalRoleMemberRowScanner(row rowScanner) (res *types.RoleMember, err error) { + res = &types.RoleMember{} + if _, has := s.config.RowScanners["roleMember"]; has { - scanner := s.config.RowScanners["roleMember"].(func(rowScanner, *types.RoleMember) error) + scanner := s.config.RowScanners["roleMember"].(func(_ rowScanner, _ *types.RoleMember) error) err = scanner(row, res) } else { err = row.Scan( @@ -165,12 +263,12 @@ func (s Store) internalRoleMemberRowScanner(row rowScanner, err error) (*types.R } // QueryRoleMembers returns squirrel.SelectBuilder with set table and all columns -func (s Store) QueryRoleMembers() squirrel.SelectBuilder { - return s.Select(s.RoleMemberTable("rm"), s.RoleMemberColumns("rm")...) +func (s Store) roleMembersSelectBuilder() squirrel.SelectBuilder { + return s.SelectBuilder(s.roleMemberTable("rm"), s.roleMemberColumns("rm")...) } -// RoleMemberTable name of the db table -func (Store) RoleMemberTable(aa ...string) string { +// roleMemberTable name of the db table +func (Store) roleMemberTable(aa ...string) string { var alias string if len(aa) > 0 { alias = " AS " + aa[0] @@ -182,7 +280,7 @@ func (Store) RoleMemberTable(aa ...string) string { // RoleMemberColumns returns all defined table columns // // With optional string arg, all columns are returned aliased -func (Store) RoleMemberColumns(aa ...string) []string { +func (Store) roleMemberColumns(aa ...string) []string { var alias string if len(aa) > 0 { alias = aa[0] + "." @@ -194,7 +292,7 @@ func (Store) RoleMemberColumns(aa ...string) []string { } } -// {false true true false} +// {true true false false false} // internalRoleMemberEncoder encodes fields from types.RoleMember to store.Payload (map) // @@ -206,3 +304,16 @@ func (s Store) internalRoleMemberEncoder(res *types.RoleMember) store.Payload { "rel_role": res.RoleID, } } + +func (s *Store) checkRoleMemberConstraints(ctx context.Context, res *types.RoleMember) error { + + return nil +} + +// func (s *Store) roleMemberHook(ctx context.Context, key triggerKey, res *types.RoleMember) error { +// if fn, has := s.config.TriggerHandlers[key]; has { +// return fn.(func (ctx context.Context, s *Store, res *types.RoleMember) error)(ctx, s, res) +// } +// +// return nil +// } diff --git a/store/rdbms/roles.gen.go b/store/rdbms/roles.gen.go index 80d601902..85e4ef10e 100644 --- a/store/rdbms/roles.gen.go +++ b/store/rdbms/roles.gen.go @@ -11,13 +11,24 @@ package rdbms import ( "context" "database/sql" + "errors" "fmt" "github.com/Masterminds/squirrel" + "github.com/cortezaproject/corteza-server/pkg/filter" "github.com/cortezaproject/corteza-server/store" "github.com/cortezaproject/corteza-server/system/types" "strings" ) +var _ = errors.Is + +const ( + TriggerBeforeRoleCreate triggerKey = "roleBeforeCreate" + TriggerBeforeRoleUpdate triggerKey = "roleBeforeUpdate" + TriggerBeforeRoleUpsert triggerKey = "roleBeforeUpsert" + TriggerBeforeRoleDelete triggerKey = "roleBeforeDelete" +) + // SearchRoles returns all matching rows // // This function calls convertRoleFilter with the given @@ -38,7 +49,7 @@ func (s Store) SearchRoles(ctx context.Context, f types.RoleFilter) (types.RoleS // This tells us to flip the descending flag on all used sort keys reverseCursor := f.PageCursor != nil && f.PageCursor.Reverse - if err = f.Sort.Validate(s.sortableRoleColumns()...); err != nil { + if err := f.Sort.Validate(s.sortableRoleColumns()...); err != nil { return nil, f, fmt.Errorf("could not validate sort: %v", err) } @@ -68,7 +79,7 @@ func (s Store) SearchRoles(ctx context.Context, f types.RoleFilter) (types.RoleS var ( set = make([]*types.Role, 0, scap) - // fetches rows and scans them into Types.Role resource this is then passed to Check function on filter + // fetches rows and scans them into types.Role resource this is then passed to Check function on filter // to help determine if fetched resource fits or not // // Note that limit is passed explicitly and is not necessarily equal to filter's limit. We want @@ -77,7 +88,7 @@ func (s Store) SearchRoles(ctx context.Context, f types.RoleFilter) (types.RoleS // The value for cursor is used and set directly from/to the filter! // // It returns total number of fetched pages and modifies PageCursor value for paging - fetchPage = func(cursor *store.PagingCursor, limit uint) (fetched uint, err error) { + fetchPage = func(cursor *filter.PagingCursor, limit uint) (fetched uint, err error) { var ( res *types.Role @@ -108,7 +119,12 @@ func (s Store) SearchRoles(ctx context.Context, f types.RoleFilter) (types.RoleS for rows.Next() { fetched++ - if res, err = s.internalRoleRowScanner(rows, rows.Err()); err != nil { + + if rows.Err() == nil { + res, err = s.internalRoleRowScanner(rows) + } + + if err != nil { if cerr := rows.Close(); cerr != nil { err = fmt.Errorf("could not close rows (%v) after scan error: %w", cerr, err) } @@ -225,8 +241,8 @@ func (s Store) SearchRoles(ctx context.Context, f types.RoleFilter) (types.RoleS // // It returns role even if deleted or suspended func (s Store) LookupRoleByID(ctx context.Context, id uint64) (*types.Role, error) { - return s.RoleLookup(ctx, squirrel.Eq{ - "rl.id": id, + return s.execLookupRole(ctx, squirrel.Eq{ + s.preprocessColumn("rl.id", ""): s.preprocessValue(id, ""), }) } @@ -234,8 +250,9 @@ func (s Store) LookupRoleByID(ctx context.Context, id uint64) (*types.Role, erro // // It returns only valid roles (not deleted, not archived) func (s Store) LookupRoleByHandle(ctx context.Context, handle string) (*types.Role, error) { - return s.RoleLookup(ctx, squirrel.Eq{ - "rl.handle": handle, + return s.execLookupRole(ctx, squirrel.Eq{ + s.preprocessColumn("rl.handle", "lower"): s.preprocessValue(handle, "lower"), + "rl.archived_at": nil, "rl.deleted_at": nil, }) @@ -245,8 +262,9 @@ func (s Store) LookupRoleByHandle(ctx context.Context, handle string) (*types.Ro // // It returns only valid roles (not deleted, not archived) func (s Store) LookupRoleByName(ctx context.Context, name string) (*types.Role, error) { - return s.RoleLookup(ctx, squirrel.Eq{ - "rl.name": name, + return s.execLookupRole(ctx, squirrel.Eq{ + s.preprocessColumn("rl.name", ""): s.preprocessValue(name, ""), + "rl.archived_at": nil, "rl.deleted_at": nil, }) @@ -255,9 +273,19 @@ func (s Store) LookupRoleByName(ctx context.Context, name string) (*types.Role, // CreateRole creates one or more rows in roles table func (s Store) CreateRole(ctx context.Context, rr ...*types.Role) (err error) { for _, res := range rr { - err = ExecuteSqlizer(ctx, s.DB(), s.Insert(s.RoleTable()).SetMap(s.internalRoleEncoder(res))) + err = s.checkRoleConstraints(ctx, res) if err != nil { - return s.config.ErrorHandler(err) + return err + } + + // err = s.roleHook(ctx, TriggerBeforeRoleCreate, res) + // if err != nil { + // return err + // } + + err = s.execCreateRoles(ctx, s.internalRoleEncoder(res)) + if err != nil { + return err } } @@ -266,17 +294,27 @@ func (s Store) CreateRole(ctx context.Context, rr ...*types.Role) (err error) { // UpdateRole updates one or more existing rows in roles func (s Store) UpdateRole(ctx context.Context, rr ...*types.Role) error { - return s.config.ErrorHandler(s.PartialUpdateRole(ctx, nil, rr...)) + return s.config.ErrorHandler(s.PartialRoleUpdate(ctx, nil, rr...)) } -// PartialUpdateRole updates one or more existing rows in roles -// -// It wraps the update into transaction and can perform partial update by providing list of updatable columns -func (s Store) PartialUpdateRole(ctx context.Context, onlyColumns []string, rr ...*types.Role) (err error) { +// PartialRoleUpdate updates one or more existing rows in roles +func (s Store) PartialRoleUpdate(ctx context.Context, onlyColumns []string, rr ...*types.Role) (err error) { for _, res := range rr { - err = s.ExecUpdateRoles( + err = s.checkRoleConstraints(ctx, res) + if err != nil { + return err + } + + // err = s.roleHook(ctx, TriggerBeforeRoleUpdate, res) + // if err != nil { + // return err + // } + + err = s.execUpdateRoles( ctx, - squirrel.Eq{s.preprocessColumn("rl.id", ""): s.preprocessValue(res.ID, "")}, + squirrel.Eq{ + s.preprocessColumn("rl.id", ""): s.preprocessValue(res.ID, ""), + }, s.internalRoleEncoder(res).Skip("id").Only(onlyColumns...)) if err != nil { return s.config.ErrorHandler(err) @@ -286,10 +324,39 @@ func (s Store) PartialUpdateRole(ctx context.Context, onlyColumns []string, rr . return } -// RemoveRole removes one or more rows from roles table -func (s Store) RemoveRole(ctx context.Context, rr ...*types.Role) (err error) { +// UpsertRole updates one or more existing rows in roles +func (s Store) UpsertRole(ctx context.Context, rr ...*types.Role) (err error) { for _, res := range rr { - err = ExecuteSqlizer(ctx, s.DB(), s.Delete(s.RoleTable("rl")).Where(squirrel.Eq{s.preprocessColumn("rl.id", ""): s.preprocessValue(res.ID, "")})) + err = s.checkRoleConstraints(ctx, res) + if err != nil { + return err + } + + // err = s.roleHook(ctx, TriggerBeforeRoleUpsert, res) + // if err != nil { + // return err + // } + + err = s.config.ErrorHandler(s.execUpsertRoles(ctx, s.internalRoleEncoder(res))) + if err != nil { + return err + } + } + + return nil +} + +// DeleteRole Deletes one or more rows from roles table +func (s Store) DeleteRole(ctx context.Context, rr ...*types.Role) (err error) { + for _, res := range rr { + // err = s.roleHook(ctx, TriggerBeforeRoleDelete, res) + // if err != nil { + // return err + // } + + err = s.execDeleteRoles(ctx, squirrel.Eq{ + s.preprocessColumn("rl.id", ""): s.preprocessValue(res.ID, ""), + }) if err != nil { return s.config.ErrorHandler(err) } @@ -298,35 +365,74 @@ func (s Store) RemoveRole(ctx context.Context, rr ...*types.Role) (err error) { return nil } -// RemoveRoleByID removes row from the roles table -func (s Store) RemoveRoleByID(ctx context.Context, ID uint64) error { - return s.config.ErrorHandler(ExecuteSqlizer(ctx, s.DB(), s.Delete(s.RoleTable("rl")).Where(squirrel.Eq{s.preprocessColumn("rl.id", ""): s.preprocessValue(ID, "")}))) +// DeleteRoleByID Deletes row from the roles table +func (s Store) DeleteRoleByID(ctx context.Context, ID uint64) error { + return s.execDeleteRoles(ctx, squirrel.Eq{ + s.preprocessColumn("rl.id", ""): s.preprocessValue(ID, ""), + }) } -// TruncateRoles removes all rows from the roles table +// TruncateRoles Deletes all rows from the roles table func (s Store) TruncateRoles(ctx context.Context) error { - return s.config.ErrorHandler(Truncate(ctx, s.DB(), s.RoleTable())) + return s.config.ErrorHandler(s.Truncate(ctx, s.roleTable())) } -// ExecUpdateRoles updates all matched (by cnd) rows in roles with given data -func (s Store) ExecUpdateRoles(ctx context.Context, cnd squirrel.Sqlizer, set store.Payload) error { - return s.config.ErrorHandler(ExecuteSqlizer(ctx, s.DB(), s.Update(s.RoleTable("rl")).Where(cnd).SetMap(set))) -} - -// RoleLookup prepares Role query and executes it, +// execLookupRole prepares Role query and executes it, // returning types.Role (or error) -func (s Store) RoleLookup(ctx context.Context, cnd squirrel.Sqlizer) (*types.Role, error) { - return s.internalRoleRowScanner(s.QueryRow(ctx, s.QueryRoles().Where(cnd))) -} +func (s Store) execLookupRole(ctx context.Context, cnd squirrel.Sqlizer) (res *types.Role, err error) { + var ( + row rowScanner + ) -func (s Store) internalRoleRowScanner(row rowScanner, err error) (*types.Role, error) { + row, err = s.QueryRow(ctx, s.rolesSelectBuilder().Where(cnd)) if err != nil { - return nil, err + return } - var res = &types.Role{} + res, err = s.internalRoleRowScanner(row) + if err != nil { + return + } + + return res, nil +} + +// execCreateRoles updates all matched (by cnd) rows in roles with given data +func (s Store) execCreateRoles(ctx context.Context, payload store.Payload) error { + return s.config.ErrorHandler(s.Exec(ctx, s.InsertBuilder(s.roleTable()).SetMap(payload))) +} + +// execUpdateRoles updates all matched (by cnd) rows in roles with given data +func (s Store) execUpdateRoles(ctx context.Context, cnd squirrel.Sqlizer, set store.Payload) error { + return s.config.ErrorHandler(s.Exec(ctx, s.UpdateBuilder(s.roleTable("rl")).Where(cnd).SetMap(set))) +} + +// execUpsertRoles inserts new or updates matching (by-primary-key) rows in roles with given data +func (s Store) execUpsertRoles(ctx context.Context, set store.Payload) error { + upsert, err := s.config.UpsertBuilder( + s.config, + s.roleTable(), + set, + "id", + ) + + if err != nil { + return err + } + + return s.config.ErrorHandler(s.Exec(ctx, upsert)) +} + +// execDeleteRoles Deletes all matched (by cnd) rows in roles with given data +func (s Store) execDeleteRoles(ctx context.Context, cnd squirrel.Sqlizer) error { + return s.config.ErrorHandler(s.Exec(ctx, s.DeleteBuilder(s.roleTable("rl")).Where(cnd))) +} + +func (s Store) internalRoleRowScanner(row rowScanner) (res *types.Role, err error) { + res = &types.Role{} + if _, has := s.config.RowScanners["role"]; has { - scanner := s.config.RowScanners["role"].(func(rowScanner, *types.Role) error) + scanner := s.config.RowScanners["role"].(func(_ rowScanner, _ *types.Role) error) err = scanner(row, res) } else { err = row.Scan( @@ -352,12 +458,12 @@ func (s Store) internalRoleRowScanner(row rowScanner, err error) (*types.Role, e } // QueryRoles returns squirrel.SelectBuilder with set table and all columns -func (s Store) QueryRoles() squirrel.SelectBuilder { - return s.Select(s.RoleTable("rl"), s.RoleColumns("rl")...) +func (s Store) rolesSelectBuilder() squirrel.SelectBuilder { + return s.SelectBuilder(s.roleTable("rl"), s.roleColumns("rl")...) } -// RoleTable name of the db table -func (Store) RoleTable(aa ...string) string { +// roleTable name of the db table +func (Store) roleTable(aa ...string) string { var alias string if len(aa) > 0 { alias = " AS " + aa[0] @@ -369,7 +475,7 @@ func (Store) RoleTable(aa ...string) string { // RoleColumns returns all defined table columns // // With optional string arg, all columns are returned aliased -func (Store) RoleColumns(aa ...string) []string { +func (Store) roleColumns(aa ...string) []string { var alias string if len(aa) > 0 { alias = aa[0] + "." @@ -386,7 +492,7 @@ func (Store) RoleColumns(aa ...string) []string { } } -// {false false false false} +// {true true true true true} // sortableRoleColumns returns all Role columns flagged as sortable // @@ -419,9 +525,9 @@ func (s Store) internalRoleEncoder(res *types.Role) store.Payload { } } -func (s Store) collectRoleCursorValues(res *types.Role, cc ...string) *store.PagingCursor { +func (s Store) collectRoleCursorValues(res *types.Role, cc ...string) *filter.PagingCursor { var ( - cursor = &store.PagingCursor{} + cursor = &filter.PagingCursor{} hasUnique bool @@ -458,3 +564,25 @@ func (s Store) collectRoleCursorValues(res *types.Role, cc ...string) *store.Pag return cursor } + +func (s *Store) checkRoleConstraints(ctx context.Context, res *types.Role) error { + + { + ex, err := s.LookupRoleByHandle(ctx, res.Handle) + if err == nil && ex != nil && ex.ID != res.ID { + return store.ErrNotUnique + } else if !errors.Is(err, store.ErrNotFound) { + return err + } + } + + return nil +} + +// func (s *Store) roleHook(ctx context.Context, key triggerKey, res *types.Role) error { +// if fn, has := s.config.TriggerHandlers[key]; has { +// return fn.(func (ctx context.Context, s *Store, res *types.Role) error)(ctx, s, res) +// } +// +// return nil +// } diff --git a/store/rdbms/roles.go b/store/rdbms/roles.go index 85ec8fd2c..ba3f7e8eb 100644 --- a/store/rdbms/roles.go +++ b/store/rdbms/roles.go @@ -8,7 +8,7 @@ import ( ) func (s Store) convertRoleFilter(f types.RoleFilter) (query squirrel.SelectBuilder, err error) { - query = s.QueryRoles() + query = s.rolesSelectBuilder() query = rh.FilterNullByState(query, "rl.deleted_at", f.Deleted) query = rh.FilterNullByState(query, "rl.archived_at", f.Archived) @@ -40,24 +40,24 @@ func (s Store) convertRoleFilter(f types.RoleFilter) (query squirrel.SelectBuild return } -func (s Store) RoleMetrics(ctx context.Context) (rval *types.RoleMetrics, err error) { +func (s Store) RoleMetrics(ctx context.Context) (*types.RoleMetrics, error) { var ( counters = squirrel. - Select( + Select( "COUNT(*) as total", "SUM(IF(deleted_at IS NULL, 0, 1)) as deleted", "SUM(IF(archived_at IS NULL, 0, 1)) as archived", "SUM(IF(deleted_at IS NULL AND archived_at IS NULL, 1, 0)) as valid", ). - From(s.UserTable("u")) + From(s.roleTable("u")) + + rval = &types.RoleMetrics{} + row, err = s.QueryRow(ctx, counters) ) - rval = &types.RoleMetrics{} - - var ( - sql, args = counters.MustSql() - row = s.db.QueryRowContext(ctx, sql, args...) - ) + if err != nil { + return nil, err + } err = row.Scan(&rval.Total, &rval.Deleted, &rval.Archived, &rval.Valid) if err != nil { @@ -67,7 +67,7 @@ func (s Store) RoleMetrics(ctx context.Context) (rval *types.RoleMetrics, err er // Fetch daily metrics for created, updated, deleted and suspended users err = s.multiDailyMetrics( ctx, - squirrel.Select().From(s.UserTable("u")), + squirrel.Select().From(s.roleTable("u")), []string{ "created_at", "updated_at", @@ -81,8 +81,8 @@ func (s Store) RoleMetrics(ctx context.Context) (rval *types.RoleMetrics, err er ) if err != nil { - return + return nil, err } - return + return rval, nil } diff --git a/store/rdbms/settings.gen.go b/store/rdbms/settings.gen.go index a2fbe74b6..610d5fcd8 100644 --- a/store/rdbms/settings.gen.go +++ b/store/rdbms/settings.gen.go @@ -11,12 +11,22 @@ package rdbms import ( "context" "database/sql" + "errors" "fmt" "github.com/Masterminds/squirrel" "github.com/cortezaproject/corteza-server/store" "github.com/cortezaproject/corteza-server/system/types" ) +var _ = errors.Is + +const ( + TriggerBeforeSettingCreate triggerKey = "settingBeforeCreate" + TriggerBeforeSettingUpdate triggerKey = "settingBeforeUpdate" + TriggerBeforeSettingUpsert triggerKey = "settingBeforeUpsert" + TriggerBeforeSettingDelete triggerKey = "settingBeforeDelete" +) + // SearchSettings returns all matching rows // // This function calls convertSettingFilter with the given @@ -35,7 +45,7 @@ func (s Store) SearchSettings(ctx context.Context, f types.SettingsFilter) (type var ( set = make([]*types.SettingValue, 0, scap) // Paging is disabled in definition yaml file - // {search: {disablePaging:true}} and this allows + // {search: {enablePaging:false}} and this allows // a much simpler row fetching logic fetch = func() error { var ( @@ -48,14 +58,33 @@ func (s Store) SearchSettings(ctx context.Context, f types.SettingsFilter) (type } for rows.Next() { - if res, err = s.internalSettingRowScanner(rows, rows.Err()); err != nil { + if rows.Err() == nil { + res, err = s.internalSettingRowScanner(rows) + } + + if err != nil { if cerr := rows.Close(); cerr != nil { - return fmt.Errorf("could not close rows (%v) after scan error: %w", cerr, err) + 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) } @@ -68,18 +97,28 @@ func (s Store) SearchSettings(ctx context.Context, f types.SettingsFilter) (type // LookupSettingByNameOwnedBy searches for settings by name and owner func (s Store) LookupSettingByNameOwnedBy(ctx context.Context, name string, owned_by uint64) (*types.SettingValue, error) { - return s.SettingLookup(ctx, squirrel.Eq{ - "st.name": name, - "st.rel_owner": owned_by, + return s.execLookupSetting(ctx, squirrel.Eq{ + s.preprocessColumn("st.name", ""): s.preprocessValue(name, ""), + s.preprocessColumn("st.rel_owner", ""): s.preprocessValue(owned_by, ""), }) } // CreateSetting creates one or more rows in settings table func (s Store) CreateSetting(ctx context.Context, rr ...*types.SettingValue) (err error) { for _, res := range rr { - err = ExecuteSqlizer(ctx, s.DB(), s.Insert(s.SettingTable()).SetMap(s.internalSettingEncoder(res))) + err = s.checkSettingConstraints(ctx, res) if err != nil { - return s.config.ErrorHandler(err) + return err + } + + // err = s.settingHook(ctx, TriggerBeforeSettingCreate, res) + // if err != nil { + // return err + // } + + err = s.execCreateSettings(ctx, s.internalSettingEncoder(res)) + if err != nil { + return err } } @@ -88,18 +127,26 @@ func (s Store) CreateSetting(ctx context.Context, rr ...*types.SettingValue) (er // UpdateSetting updates one or more existing rows in settings func (s Store) UpdateSetting(ctx context.Context, rr ...*types.SettingValue) error { - return s.config.ErrorHandler(s.PartialUpdateSetting(ctx, nil, rr...)) + return s.config.ErrorHandler(s.PartialSettingUpdate(ctx, nil, rr...)) } -// PartialUpdateSetting updates one or more existing rows in settings -// -// It wraps the update into transaction and can perform partial update by providing list of updatable columns -func (s Store) PartialUpdateSetting(ctx context.Context, onlyColumns []string, rr ...*types.SettingValue) (err error) { +// PartialSettingUpdate updates one or more existing rows in settings +func (s Store) PartialSettingUpdate(ctx context.Context, onlyColumns []string, rr ...*types.SettingValue) (err error) { for _, res := range rr { - err = s.ExecUpdateSettings( + err = s.checkSettingConstraints(ctx, res) + if err != nil { + return err + } + + // err = s.settingHook(ctx, TriggerBeforeSettingUpdate, res) + // if err != nil { + // return err + // } + + err = s.execUpdateSettings( ctx, - squirrel.Eq{s.preprocessColumn("st.name", ""): s.preprocessValue(res.Name, ""), - s.preprocessColumn("st.rel_owner", ""): s.preprocessValue(res.OwnedBy, ""), + squirrel.Eq{ + s.preprocessColumn("st.name", ""): s.preprocessValue(res.Name, ""), s.preprocessColumn("st.rel_owner", ""): s.preprocessValue(res.OwnedBy, ""), }, s.internalSettingEncoder(res).Skip("name", "rel_owner").Only(onlyColumns...)) if err != nil { @@ -110,12 +157,39 @@ func (s Store) PartialUpdateSetting(ctx context.Context, onlyColumns []string, r return } -// RemoveSetting removes one or more rows from settings table -func (s Store) RemoveSetting(ctx context.Context, rr ...*types.SettingValue) (err error) { +// UpsertSetting updates one or more existing rows in settings +func (s Store) UpsertSetting(ctx context.Context, rr ...*types.SettingValue) (err error) { for _, res := range rr { - err = ExecuteSqlizer(ctx, s.DB(), s.Delete(s.SettingTable("st")).Where(squirrel.Eq{s.preprocessColumn("st.name", ""): s.preprocessValue(res.Name, ""), - s.preprocessColumn("st.rel_owner", ""): s.preprocessValue(res.OwnedBy, ""), - })) + err = s.checkSettingConstraints(ctx, res) + if err != nil { + return err + } + + // err = s.settingHook(ctx, TriggerBeforeSettingUpsert, res) + // if err != nil { + // return err + // } + + err = s.config.ErrorHandler(s.execUpsertSettings(ctx, s.internalSettingEncoder(res))) + if err != nil { + return err + } + } + + return nil +} + +// DeleteSetting Deletes one or more rows from settings table +func (s Store) DeleteSetting(ctx context.Context, rr ...*types.SettingValue) (err error) { + for _, res := range rr { + // err = s.settingHook(ctx, TriggerBeforeSettingDelete, res) + // if err != nil { + // return err + // } + + err = s.execDeleteSettings(ctx, squirrel.Eq{ + s.preprocessColumn("st.name", ""): s.preprocessValue(res.Name, ""), s.preprocessColumn("st.rel_owner", ""): s.preprocessValue(res.OwnedBy, ""), + }) if err != nil { return s.config.ErrorHandler(err) } @@ -124,38 +198,76 @@ func (s Store) RemoveSetting(ctx context.Context, rr ...*types.SettingValue) (er return nil } -// RemoveSettingByNameOwnedBy removes row from the settings table -func (s Store) RemoveSettingByNameOwnedBy(ctx context.Context, name string, ownedBy uint64) error { - return s.config.ErrorHandler(ExecuteSqlizer(ctx, s.DB(), s.Delete(s.SettingTable("st")).Where(squirrel.Eq{s.preprocessColumn("st.name", ""): s.preprocessValue(name, ""), - +// DeleteSettingByNameOwnedBy Deletes row from the settings table +func (s Store) DeleteSettingByNameOwnedBy(ctx context.Context, name string, ownedBy uint64) error { + return s.execDeleteSettings(ctx, squirrel.Eq{ + s.preprocessColumn("st.name", ""): s.preprocessValue(name, ""), s.preprocessColumn("st.rel_owner", ""): s.preprocessValue(ownedBy, ""), - }))) + }) } -// TruncateSettings removes all rows from the settings table +// TruncateSettings Deletes all rows from the settings table func (s Store) TruncateSettings(ctx context.Context) error { - return s.config.ErrorHandler(Truncate(ctx, s.DB(), s.SettingTable())) + return s.config.ErrorHandler(s.Truncate(ctx, s.settingTable())) } -// ExecUpdateSettings updates all matched (by cnd) rows in settings with given data -func (s Store) ExecUpdateSettings(ctx context.Context, cnd squirrel.Sqlizer, set store.Payload) error { - return s.config.ErrorHandler(ExecuteSqlizer(ctx, s.DB(), s.Update(s.SettingTable("st")).Where(cnd).SetMap(set))) -} - -// SettingLookup prepares Setting query and executes it, +// execLookupSetting prepares Setting query and executes it, // returning types.SettingValue (or error) -func (s Store) SettingLookup(ctx context.Context, cnd squirrel.Sqlizer) (*types.SettingValue, error) { - return s.internalSettingRowScanner(s.QueryRow(ctx, s.QuerySettings().Where(cnd))) -} +func (s Store) execLookupSetting(ctx context.Context, cnd squirrel.Sqlizer) (res *types.SettingValue, err error) { + var ( + row rowScanner + ) -func (s Store) internalSettingRowScanner(row rowScanner, err error) (*types.SettingValue, error) { + row, err = s.QueryRow(ctx, s.settingsSelectBuilder().Where(cnd)) if err != nil { - return nil, err + return } - var res = &types.SettingValue{} + res, err = s.internalSettingRowScanner(row) + if err != nil { + return + } + + return res, nil +} + +// execCreateSettings updates all matched (by cnd) rows in settings with given data +func (s Store) execCreateSettings(ctx context.Context, payload store.Payload) error { + return s.config.ErrorHandler(s.Exec(ctx, s.InsertBuilder(s.settingTable()).SetMap(payload))) +} + +// execUpdateSettings updates all matched (by cnd) rows in settings with given data +func (s Store) execUpdateSettings(ctx context.Context, cnd squirrel.Sqlizer, set store.Payload) error { + return s.config.ErrorHandler(s.Exec(ctx, s.UpdateBuilder(s.settingTable("st")).Where(cnd).SetMap(set))) +} + +// execUpsertSettings inserts new or updates matching (by-primary-key) rows in settings with given data +func (s Store) execUpsertSettings(ctx context.Context, set store.Payload) error { + upsert, err := s.config.UpsertBuilder( + s.config, + s.settingTable(), + set, + "name", + "rel_owner", + ) + + if err != nil { + return err + } + + return s.config.ErrorHandler(s.Exec(ctx, upsert)) +} + +// execDeleteSettings Deletes all matched (by cnd) rows in settings with given data +func (s Store) execDeleteSettings(ctx context.Context, cnd squirrel.Sqlizer) error { + return s.config.ErrorHandler(s.Exec(ctx, s.DeleteBuilder(s.settingTable("st")).Where(cnd))) +} + +func (s Store) internalSettingRowScanner(row rowScanner) (res *types.SettingValue, err error) { + res = &types.SettingValue{} + if _, has := s.config.RowScanners["setting"]; has { - scanner := s.config.RowScanners["setting"].(func(rowScanner, *types.SettingValue) error) + scanner := s.config.RowScanners["setting"].(func(_ rowScanner, _ *types.SettingValue) error) err = scanner(row, res) } else { err = row.Scan( @@ -179,12 +291,12 @@ func (s Store) internalSettingRowScanner(row rowScanner, err error) (*types.Sett } // QuerySettings returns squirrel.SelectBuilder with set table and all columns -func (s Store) QuerySettings() squirrel.SelectBuilder { - return s.Select(s.SettingTable("st"), s.SettingColumns("st")...) +func (s Store) settingsSelectBuilder() squirrel.SelectBuilder { + return s.SelectBuilder(s.settingTable("st"), s.settingColumns("st")...) } -// SettingTable name of the db table -func (Store) SettingTable(aa ...string) string { +// settingTable name of the db table +func (Store) settingTable(aa ...string) string { var alias string if len(aa) > 0 { alias = " AS " + aa[0] @@ -196,7 +308,7 @@ func (Store) SettingTable(aa ...string) string { // SettingColumns returns all defined table columns // // With optional string arg, all columns are returned aliased -func (Store) SettingColumns(aa ...string) []string { +func (Store) settingColumns(aa ...string) []string { var alias string if len(aa) > 0 { alias = aa[0] + "." @@ -211,7 +323,7 @@ func (Store) SettingColumns(aa ...string) []string { } } -// {false true true false} +// {true true false false true} // internalSettingEncoder encodes fields from types.SettingValue to store.Payload (map) // @@ -226,3 +338,16 @@ func (s Store) internalSettingEncoder(res *types.SettingValue) store.Payload { "updated_at": res.UpdatedAt, } } + +func (s *Store) checkSettingConstraints(ctx context.Context, res *types.SettingValue) error { + + return nil +} + +// func (s *Store) settingHook(ctx context.Context, key triggerKey, res *types.SettingValue) error { +// if fn, has := s.config.TriggerHandlers[key]; has { +// return fn.(func (ctx context.Context, s *Store, res *types.SettingValue) error)(ctx, s, res) +// } +// +// return nil +// } diff --git a/store/rdbms/settings.go b/store/rdbms/settings.go index 7a0fcc208..d34782624 100644 --- a/store/rdbms/settings.go +++ b/store/rdbms/settings.go @@ -16,7 +16,7 @@ func (s Store) columns() []string { } func (s Store) convertSettingFilter(f types.SettingsFilter) (query squirrel.SelectBuilder, err error) { - query = s.QuerySettings().Where(squirrel.Eq{"rel_owner": f.OwnedBy}) + query = s.settingsSelectBuilder().Where(squirrel.Eq{"rel_owner": f.OwnedBy}) if len(f.Prefix) > 0 { query = query.Where("name LIKE ?", f.Prefix+"%") @@ -61,9 +61,9 @@ func (s Store) convertSettingFilter(f types.SettingsFilter) (query squirrel.Sele // return v, err //} // -//// Returns squirrel.SelectBuilder -//func (s Store) QuerySettings() squirrel.SelectBuilder { -// return s.Select(s.SettingsTable()+" AS stngs", s.SettingsColumns()...) +//// Returns squirrel.selectBuilder +//func (s Store) QuerySettings() squirrel.selectBuilder { +// return s.selectBuilder(s.SettingsTable()+" AS stngs", s.SettingsColumns()...) //} // //// Name of the db table diff --git a/store/rdbms/users.gen.go b/store/rdbms/users.gen.go index 40a30b58c..a19a86897 100644 --- a/store/rdbms/users.gen.go +++ b/store/rdbms/users.gen.go @@ -11,13 +11,24 @@ package rdbms import ( "context" "database/sql" + "errors" "fmt" "github.com/Masterminds/squirrel" + "github.com/cortezaproject/corteza-server/pkg/filter" "github.com/cortezaproject/corteza-server/store" "github.com/cortezaproject/corteza-server/system/types" "strings" ) +var _ = errors.Is + +const ( + TriggerBeforeUserCreate triggerKey = "userBeforeCreate" + TriggerBeforeUserUpdate triggerKey = "userBeforeUpdate" + TriggerBeforeUserUpsert triggerKey = "userBeforeUpsert" + TriggerBeforeUserDelete triggerKey = "userBeforeDelete" +) + // SearchUsers returns all matching rows // // This function calls convertUserFilter with the given @@ -38,7 +49,7 @@ func (s Store) SearchUsers(ctx context.Context, f types.UserFilter) (types.UserS // This tells us to flip the descending flag on all used sort keys reverseCursor := f.PageCursor != nil && f.PageCursor.Reverse - if err = f.Sort.Validate(s.sortableUserColumns()...); err != nil { + if err := f.Sort.Validate(s.sortableUserColumns()...); err != nil { return nil, f, fmt.Errorf("could not validate sort: %v", err) } @@ -68,7 +79,7 @@ func (s Store) SearchUsers(ctx context.Context, f types.UserFilter) (types.UserS var ( set = make([]*types.User, 0, scap) - // fetches rows and scans them into Types.User resource this is then passed to Check function on filter + // fetches rows and scans them into types.User resource this is then passed to Check function on filter // to help determine if fetched resource fits or not // // Note that limit is passed explicitly and is not necessarily equal to filter's limit. We want @@ -77,7 +88,7 @@ func (s Store) SearchUsers(ctx context.Context, f types.UserFilter) (types.UserS // The value for cursor is used and set directly from/to the filter! // // It returns total number of fetched pages and modifies PageCursor value for paging - fetchPage = func(cursor *store.PagingCursor, limit uint) (fetched uint, err error) { + fetchPage = func(cursor *filter.PagingCursor, limit uint) (fetched uint, err error) { var ( res *types.User @@ -108,7 +119,12 @@ func (s Store) SearchUsers(ctx context.Context, f types.UserFilter) (types.UserS for rows.Next() { fetched++ - if res, err = s.internalUserRowScanner(rows, rows.Err()); err != nil { + + if rows.Err() == nil { + res, err = s.internalUserRowScanner(rows) + } + + if err != nil { if cerr := rows.Close(); cerr != nil { err = fmt.Errorf("could not close rows (%v) after scan error: %w", cerr, err) } @@ -225,8 +241,8 @@ func (s Store) SearchUsers(ctx context.Context, f types.UserFilter) (types.UserS // // It returns user even if deleted or suspended func (s Store) LookupUserByID(ctx context.Context, id uint64) (*types.User, error) { - return s.UserLookup(ctx, squirrel.Eq{ - "usr.id": id, + return s.execLookupUser(ctx, squirrel.Eq{ + s.preprocessColumn("usr.id", ""): s.preprocessValue(id, ""), }) } @@ -234,8 +250,9 @@ func (s Store) LookupUserByID(ctx context.Context, id uint64) (*types.User, erro // // It returns only valid users (not deleted, not suspended) func (s Store) LookupUserByEmail(ctx context.Context, email string) (*types.User, error) { - return s.UserLookup(ctx, squirrel.Eq{ - "usr.email": email, + return s.execLookupUser(ctx, squirrel.Eq{ + s.preprocessColumn("usr.email", "lower"): s.preprocessValue(email, "lower"), + "usr.deleted_at": nil, "usr.suspended_at": nil, }) @@ -245,8 +262,9 @@ func (s Store) LookupUserByEmail(ctx context.Context, email string) (*types.User // // It returns only valid users (not deleted, not suspended) func (s Store) LookupUserByHandle(ctx context.Context, handle string) (*types.User, error) { - return s.UserLookup(ctx, squirrel.Eq{ - "usr.handle": handle, + return s.execLookupUser(ctx, squirrel.Eq{ + s.preprocessColumn("usr.handle", "lower"): s.preprocessValue(handle, "lower"), + "usr.deleted_at": nil, "usr.suspended_at": nil, }) @@ -256,8 +274,9 @@ func (s Store) LookupUserByHandle(ctx context.Context, handle string) (*types.Us // // It returns only valid users (not deleted, not suspended) func (s Store) LookupUserByUsername(ctx context.Context, username string) (*types.User, error) { - return s.UserLookup(ctx, squirrel.Eq{ - "usr.username": username, + return s.execLookupUser(ctx, squirrel.Eq{ + s.preprocessColumn("usr.username", "lower"): s.preprocessValue(username, "lower"), + "usr.deleted_at": nil, "usr.suspended_at": nil, }) @@ -266,9 +285,19 @@ func (s Store) LookupUserByUsername(ctx context.Context, username string) (*type // CreateUser creates one or more rows in users table func (s Store) CreateUser(ctx context.Context, rr ...*types.User) (err error) { for _, res := range rr { - err = ExecuteSqlizer(ctx, s.DB(), s.Insert(s.UserTable()).SetMap(s.internalUserEncoder(res))) + err = s.checkUserConstraints(ctx, res) if err != nil { - return s.config.ErrorHandler(err) + return err + } + + // err = s.userHook(ctx, TriggerBeforeUserCreate, res) + // if err != nil { + // return err + // } + + err = s.execCreateUsers(ctx, s.internalUserEncoder(res)) + if err != nil { + return err } } @@ -277,17 +306,27 @@ func (s Store) CreateUser(ctx context.Context, rr ...*types.User) (err error) { // UpdateUser updates one or more existing rows in users func (s Store) UpdateUser(ctx context.Context, rr ...*types.User) error { - return s.config.ErrorHandler(s.PartialUpdateUser(ctx, nil, rr...)) + return s.config.ErrorHandler(s.PartialUserUpdate(ctx, nil, rr...)) } -// PartialUpdateUser updates one or more existing rows in users -// -// It wraps the update into transaction and can perform partial update by providing list of updatable columns -func (s Store) PartialUpdateUser(ctx context.Context, onlyColumns []string, rr ...*types.User) (err error) { +// PartialUserUpdate updates one or more existing rows in users +func (s Store) PartialUserUpdate(ctx context.Context, onlyColumns []string, rr ...*types.User) (err error) { for _, res := range rr { - err = s.ExecUpdateUsers( + err = s.checkUserConstraints(ctx, res) + if err != nil { + return err + } + + // err = s.userHook(ctx, TriggerBeforeUserUpdate, res) + // if err != nil { + // return err + // } + + err = s.execUpdateUsers( ctx, - squirrel.Eq{s.preprocessColumn("usr.id", ""): s.preprocessValue(res.ID, "")}, + squirrel.Eq{ + s.preprocessColumn("usr.id", ""): s.preprocessValue(res.ID, ""), + }, s.internalUserEncoder(res).Skip("id").Only(onlyColumns...)) if err != nil { return s.config.ErrorHandler(err) @@ -297,10 +336,39 @@ func (s Store) PartialUpdateUser(ctx context.Context, onlyColumns []string, rr . return } -// RemoveUser removes one or more rows from users table -func (s Store) RemoveUser(ctx context.Context, rr ...*types.User) (err error) { +// UpsertUser updates one or more existing rows in users +func (s Store) UpsertUser(ctx context.Context, rr ...*types.User) (err error) { for _, res := range rr { - err = ExecuteSqlizer(ctx, s.DB(), s.Delete(s.UserTable("usr")).Where(squirrel.Eq{s.preprocessColumn("usr.id", ""): s.preprocessValue(res.ID, "")})) + err = s.checkUserConstraints(ctx, res) + if err != nil { + return err + } + + // err = s.userHook(ctx, TriggerBeforeUserUpsert, res) + // if err != nil { + // return err + // } + + err = s.config.ErrorHandler(s.execUpsertUsers(ctx, s.internalUserEncoder(res))) + if err != nil { + return err + } + } + + return nil +} + +// DeleteUser Deletes one or more rows from users table +func (s Store) DeleteUser(ctx context.Context, rr ...*types.User) (err error) { + for _, res := range rr { + // err = s.userHook(ctx, TriggerBeforeUserDelete, res) + // if err != nil { + // return err + // } + + err = s.execDeleteUsers(ctx, squirrel.Eq{ + s.preprocessColumn("usr.id", ""): s.preprocessValue(res.ID, ""), + }) if err != nil { return s.config.ErrorHandler(err) } @@ -309,35 +377,74 @@ func (s Store) RemoveUser(ctx context.Context, rr ...*types.User) (err error) { return nil } -// RemoveUserByID removes row from the users table -func (s Store) RemoveUserByID(ctx context.Context, ID uint64) error { - return s.config.ErrorHandler(ExecuteSqlizer(ctx, s.DB(), s.Delete(s.UserTable("usr")).Where(squirrel.Eq{s.preprocessColumn("usr.id", ""): s.preprocessValue(ID, "")}))) +// DeleteUserByID Deletes row from the users table +func (s Store) DeleteUserByID(ctx context.Context, ID uint64) error { + return s.execDeleteUsers(ctx, squirrel.Eq{ + s.preprocessColumn("usr.id", ""): s.preprocessValue(ID, ""), + }) } -// TruncateUsers removes all rows from the users table +// TruncateUsers Deletes all rows from the users table func (s Store) TruncateUsers(ctx context.Context) error { - return s.config.ErrorHandler(Truncate(ctx, s.DB(), s.UserTable())) + return s.config.ErrorHandler(s.Truncate(ctx, s.userTable())) } -// ExecUpdateUsers updates all matched (by cnd) rows in users with given data -func (s Store) ExecUpdateUsers(ctx context.Context, cnd squirrel.Sqlizer, set store.Payload) error { - return s.config.ErrorHandler(ExecuteSqlizer(ctx, s.DB(), s.Update(s.UserTable("usr")).Where(cnd).SetMap(set))) -} - -// UserLookup prepares User query and executes it, +// execLookupUser prepares User query and executes it, // returning types.User (or error) -func (s Store) UserLookup(ctx context.Context, cnd squirrel.Sqlizer) (*types.User, error) { - return s.internalUserRowScanner(s.QueryRow(ctx, s.QueryUsers().Where(cnd))) -} +func (s Store) execLookupUser(ctx context.Context, cnd squirrel.Sqlizer) (res *types.User, err error) { + var ( + row rowScanner + ) -func (s Store) internalUserRowScanner(row rowScanner, err error) (*types.User, error) { + row, err = s.QueryRow(ctx, s.usersSelectBuilder().Where(cnd)) if err != nil { - return nil, err + return } - var res = &types.User{} + res, err = s.internalUserRowScanner(row) + if err != nil { + return + } + + return res, nil +} + +// execCreateUsers updates all matched (by cnd) rows in users with given data +func (s Store) execCreateUsers(ctx context.Context, payload store.Payload) error { + return s.config.ErrorHandler(s.Exec(ctx, s.InsertBuilder(s.userTable()).SetMap(payload))) +} + +// execUpdateUsers updates all matched (by cnd) rows in users with given data +func (s Store) execUpdateUsers(ctx context.Context, cnd squirrel.Sqlizer, set store.Payload) error { + return s.config.ErrorHandler(s.Exec(ctx, s.UpdateBuilder(s.userTable("usr")).Where(cnd).SetMap(set))) +} + +// execUpsertUsers inserts new or updates matching (by-primary-key) rows in users with given data +func (s Store) execUpsertUsers(ctx context.Context, set store.Payload) error { + upsert, err := s.config.UpsertBuilder( + s.config, + s.userTable(), + set, + "id", + ) + + if err != nil { + return err + } + + return s.config.ErrorHandler(s.Exec(ctx, upsert)) +} + +// execDeleteUsers Deletes all matched (by cnd) rows in users with given data +func (s Store) execDeleteUsers(ctx context.Context, cnd squirrel.Sqlizer) error { + return s.config.ErrorHandler(s.Exec(ctx, s.DeleteBuilder(s.userTable("usr")).Where(cnd))) +} + +func (s Store) internalUserRowScanner(row rowScanner) (res *types.User, err error) { + res = &types.User{} + if _, has := s.config.RowScanners["user"]; has { - scanner := s.config.RowScanners["user"].(func(rowScanner, *types.User) error) + scanner := s.config.RowScanners["user"].(func(_ rowScanner, _ *types.User) error) err = scanner(row, res) } else { err = row.Scan( @@ -368,12 +475,12 @@ func (s Store) internalUserRowScanner(row rowScanner, err error) (*types.User, e } // QueryUsers returns squirrel.SelectBuilder with set table and all columns -func (s Store) QueryUsers() squirrel.SelectBuilder { - return s.Select(s.UserTable("usr"), s.UserColumns("usr")...) +func (s Store) usersSelectBuilder() squirrel.SelectBuilder { + return s.SelectBuilder(s.userTable("usr"), s.userColumns("usr")...) } -// UserTable name of the db table -func (Store) UserTable(aa ...string) string { +// userTable name of the db table +func (Store) userTable(aa ...string) string { var alias string if len(aa) > 0 { alias = " AS " + aa[0] @@ -385,7 +492,7 @@ func (Store) UserTable(aa ...string) string { // UserColumns returns all defined table columns // // With optional string arg, all columns are returned aliased -func (Store) UserColumns(aa ...string) []string { +func (Store) userColumns(aa ...string) []string { var alias string if len(aa) > 0 { alias = aa[0] + "." @@ -407,7 +514,7 @@ func (Store) UserColumns(aa ...string) []string { } } -// {false false false false} +// {true true true true true} // sortableUserColumns returns all User columns flagged as sortable // @@ -447,9 +554,9 @@ func (s Store) internalUserEncoder(res *types.User) store.Payload { } } -func (s Store) collectUserCursorValues(res *types.User, cc ...string) *store.PagingCursor { +func (s Store) collectUserCursorValues(res *types.User, cc ...string) *filter.PagingCursor { var ( - cursor = &store.PagingCursor{} + cursor = &filter.PagingCursor{} hasUnique bool @@ -492,3 +599,43 @@ func (s Store) collectUserCursorValues(res *types.User, cc ...string) *store.Pag return cursor } + +func (s *Store) checkUserConstraints(ctx context.Context, res *types.User) error { + + { + ex, err := s.LookupUserByEmail(ctx, res.Email) + if err == nil && ex != nil && ex.ID != res.ID { + return store.ErrNotUnique + } else if !errors.Is(err, store.ErrNotFound) { + return err + } + } + + { + ex, err := s.LookupUserByHandle(ctx, res.Handle) + if err == nil && ex != nil && ex.ID != res.ID { + return store.ErrNotUnique + } else if !errors.Is(err, store.ErrNotFound) { + return err + } + } + + { + ex, err := s.LookupUserByUsername(ctx, res.Username) + if err == nil && ex != nil && ex.ID != res.ID { + return store.ErrNotUnique + } else if !errors.Is(err, store.ErrNotFound) { + return err + } + } + + return nil +} + +// func (s *Store) userHook(ctx context.Context, key triggerKey, res *types.User) error { +// if fn, has := s.config.TriggerHandlers[key]; has { +// return fn.(func (ctx context.Context, s *Store, res *types.User) error)(ctx, s, res) +// } +// +// return nil +// } diff --git a/store/rdbms/users.go b/store/rdbms/users.go index e2a0d20a3..2a0009df8 100644 --- a/store/rdbms/users.go +++ b/store/rdbms/users.go @@ -9,7 +9,7 @@ import ( ) func (s Store) convertUserFilter(f types.UserFilter) (query squirrel.SelectBuilder, err error) { - query = s.QueryUsers() + query = s.usersSelectBuilder() query = rh.FilterNullByState(query, "usr.deleted_at", f.Deleted) query = rh.FilterNullByState(query, "usr.suspended_at", f.Suspended) @@ -68,24 +68,24 @@ func (s Store) CountUsers(ctx context.Context, f types.UserFilter) (uint, error) } } -func (s Store) UserMetrics(ctx context.Context) (rval *types.UserMetrics, err error) { +func (s Store) UserMetrics(ctx context.Context) (*types.UserMetrics, error) { var ( counters = squirrel. - Select( + Select( "COUNT(*) as total", "SUM(IF(deleted_at IS NULL, 0, 1)) as deleted", "SUM(IF(suspended_at IS NULL, 0, 1)) as suspended", "SUM(IF(deleted_at IS NULL AND suspended_at IS NULL, 1, 0)) as valid", ). - From(s.UserTable("u")) + From(s.userTable("u")) + + row, err = s.QueryRow(ctx, counters) + rval = &types.UserMetrics{} ) - rval = &types.UserMetrics{} - - var ( - sql, args = counters.MustSql() - row = s.db.QueryRowContext(ctx, sql, args...) - ) + if err != nil { + return nil, err + } err = row.Scan(&rval.Total, &rval.Deleted, &rval.Suspended, &rval.Valid) if err != nil { @@ -95,7 +95,7 @@ func (s Store) UserMetrics(ctx context.Context) (rval *types.UserMetrics, err er // Fetch daily metrics for created, updated, deleted and suspended users err = s.multiDailyMetrics( ctx, - squirrel.Select().From(s.UserTable("u")), + squirrel.Select().From(s.userTable("u")), []string{ "created_at", "updated_at", @@ -109,8 +109,8 @@ func (s Store) UserMetrics(ctx context.Context) (rval *types.UserMetrics, err er ) if err != nil { - return + return nil, err } - return + return rval, nil } diff --git a/store/reminders.gen.go b/store/reminders.gen.go new file mode 100644 index 000000000..eda83adc3 --- /dev/null +++ b/store/reminders.gen.go @@ -0,0 +1,83 @@ +package store + +// This file is auto-generated. +// +// Template: pkg/codegen/assets/store_base.gen.go.tpl +// Definitions: store/reminders.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/system/types" +) + +type ( + Reminders interface { + SearchReminders(ctx context.Context, f types.ReminderFilter) (types.ReminderSet, types.ReminderFilter, error) + LookupReminderByID(ctx context.Context, id uint64) (*types.Reminder, error) + + CreateReminder(ctx context.Context, rr ...*types.Reminder) error + + UpdateReminder(ctx context.Context, rr ...*types.Reminder) error + PartialReminderUpdate(ctx context.Context, onlyColumns []string, rr ...*types.Reminder) error + + UpsertReminder(ctx context.Context, rr ...*types.Reminder) error + + DeleteReminder(ctx context.Context, rr ...*types.Reminder) error + DeleteReminderByID(ctx context.Context, ID uint64) error + + TruncateReminders(ctx context.Context) error + } +) + +var _ *types.Reminder +var _ context.Context + +// SearchReminders returns all matching Reminders from store +func SearchReminders(ctx context.Context, s Reminders, f types.ReminderFilter) (types.ReminderSet, types.ReminderFilter, error) { + return s.SearchReminders(ctx, f) +} + +// LookupReminderByID searches for reminder by its ID +// +// It returns reminder even if deleted or suspended +func LookupReminderByID(ctx context.Context, s Reminders, id uint64) (*types.Reminder, error) { + return s.LookupReminderByID(ctx, id) +} + +// CreateReminder creates one or more Reminders in store +func CreateReminder(ctx context.Context, s Reminders, rr ...*types.Reminder) error { + return s.CreateReminder(ctx, rr...) +} + +// UpdateReminder updates one or more (existing) Reminders in store +func UpdateReminder(ctx context.Context, s Reminders, rr ...*types.Reminder) error { + return s.UpdateReminder(ctx, rr...) +} + +// PartialReminderUpdate updates one or more existing Reminders in store +func PartialReminderUpdate(ctx context.Context, s Reminders, onlyColumns []string, rr ...*types.Reminder) error { + return s.PartialReminderUpdate(ctx, onlyColumns, rr...) +} + +// UpsertReminder creates new or updates existing one or more Reminders in store +func UpsertReminder(ctx context.Context, s Reminders, rr ...*types.Reminder) error { + return s.UpsertReminder(ctx, rr...) +} + +// DeleteReminder Deletes one or more Reminders from store +func DeleteReminder(ctx context.Context, s Reminders, rr ...*types.Reminder) error { + return s.DeleteReminder(ctx, rr...) +} + +// DeleteReminderByID Deletes Reminder from store +func DeleteReminderByID(ctx context.Context, s Reminders, ID uint64) error { + return s.DeleteReminderByID(ctx, ID) +} + +// TruncateReminders Deletes all Reminders from store +func TruncateReminders(ctx context.Context, s Reminders) error { + return s.TruncateReminders(ctx) +} diff --git a/store/reminders.yaml b/store/reminders.yaml index 30b73e6f8..75a0d0ea1 100644 --- a/store/reminders.yaml +++ b/store/reminders.yaml @@ -1,9 +1,6 @@ import: - github.com/cortezaproject/corteza-server/system/types -interface: - - system/service - fields: - { field: ID } - { field: Resource } diff --git a/store/role_members.gen.go b/store/role_members.gen.go new file mode 100644 index 000000000..48194d5f1 --- /dev/null +++ b/store/role_members.gen.go @@ -0,0 +1,75 @@ +package store + +// This file is auto-generated. +// +// Template: pkg/codegen/assets/store_base.gen.go.tpl +// Definitions: store/role_members.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/system/types" +) + +type ( + RoleMembers interface { + SearchRoleMembers(ctx context.Context, f types.RoleMemberFilter) (types.RoleMemberSet, types.RoleMemberFilter, error) + + CreateRoleMember(ctx context.Context, rr ...*types.RoleMember) error + + UpdateRoleMember(ctx context.Context, rr ...*types.RoleMember) error + PartialRoleMemberUpdate(ctx context.Context, onlyColumns []string, rr ...*types.RoleMember) error + + UpsertRoleMember(ctx context.Context, rr ...*types.RoleMember) error + + DeleteRoleMember(ctx context.Context, rr ...*types.RoleMember) error + DeleteRoleMemberByUserIDRoleID(ctx context.Context, userID uint64, roleID uint64) error + + TruncateRoleMembers(ctx context.Context) error + } +) + +var _ *types.RoleMember +var _ context.Context + +// SearchRoleMembers returns all matching RoleMembers from store +func SearchRoleMembers(ctx context.Context, s RoleMembers, f types.RoleMemberFilter) (types.RoleMemberSet, types.RoleMemberFilter, error) { + return s.SearchRoleMembers(ctx, f) +} + +// CreateRoleMember creates one or more RoleMembers in store +func CreateRoleMember(ctx context.Context, s RoleMembers, rr ...*types.RoleMember) error { + return s.CreateRoleMember(ctx, rr...) +} + +// UpdateRoleMember updates one or more (existing) RoleMembers in store +func UpdateRoleMember(ctx context.Context, s RoleMembers, rr ...*types.RoleMember) error { + return s.UpdateRoleMember(ctx, rr...) +} + +// PartialRoleMemberUpdate updates one or more existing RoleMembers in store +func PartialRoleMemberUpdate(ctx context.Context, s RoleMembers, onlyColumns []string, rr ...*types.RoleMember) error { + return s.PartialRoleMemberUpdate(ctx, onlyColumns, rr...) +} + +// UpsertRoleMember creates new or updates existing one or more RoleMembers in store +func UpsertRoleMember(ctx context.Context, s RoleMembers, rr ...*types.RoleMember) error { + return s.UpsertRoleMember(ctx, rr...) +} + +// DeleteRoleMember Deletes one or more RoleMembers from store +func DeleteRoleMember(ctx context.Context, s RoleMembers, rr ...*types.RoleMember) error { + return s.DeleteRoleMember(ctx, rr...) +} + +// DeleteRoleMemberByUserIDRoleID Deletes RoleMember from store +func DeleteRoleMemberByUserIDRoleID(ctx context.Context, s RoleMembers, userID uint64, roleID uint64) error { + return s.DeleteRoleMemberByUserIDRoleID(ctx, userID, roleID) +} + +// TruncateRoleMembers Deletes all RoleMembers from store +func TruncateRoleMembers(ctx context.Context, s RoleMembers) error { + return s.TruncateRoleMembers(ctx) +} diff --git a/store/role_members.yaml b/store/role_members.yaml index 578079ffd..3bf6e0d23 100644 --- a/store/role_members.yaml +++ b/store/role_members.yaml @@ -1,9 +1,6 @@ import: - github.com/cortezaproject/corteza-server/system/types -interface: - - system/service - fields: - { field: UserID, isPrimaryKey: true } - { field: RoleID, isPrimaryKey: true } @@ -13,6 +10,7 @@ rdbms: table: role_members search: - disableSorting: true - disablePaging: true + enableSorting: false + enablePaging: false customFilterConverter: true + enableFilterCheckFunction: false diff --git a/store/roles.gen.go b/store/roles.gen.go new file mode 100644 index 000000000..5777ea930 --- /dev/null +++ b/store/roles.gen.go @@ -0,0 +1,108 @@ +package store + +// This file is auto-generated. +// +// Template: pkg/codegen/assets/store_base.gen.go.tpl +// Definitions: store/roles.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/system/types" +) + +type ( + Roles interface { + SearchRoles(ctx context.Context, f types.RoleFilter) (types.RoleSet, types.RoleFilter, error) + LookupRoleByID(ctx context.Context, id uint64) (*types.Role, error) + LookupRoleByHandle(ctx context.Context, handle string) (*types.Role, error) + LookupRoleByName(ctx context.Context, name string) (*types.Role, error) + + CreateRole(ctx context.Context, rr ...*types.Role) error + + UpdateRole(ctx context.Context, rr ...*types.Role) error + PartialRoleUpdate(ctx context.Context, onlyColumns []string, rr ...*types.Role) error + + UpsertRole(ctx context.Context, rr ...*types.Role) error + + DeleteRole(ctx context.Context, rr ...*types.Role) error + DeleteRoleByID(ctx context.Context, ID uint64) error + + TruncateRoles(ctx context.Context) error + + // Additional custom functions + + // RoleMetrics (custom function) + RoleMetrics(ctx context.Context) (*types.RoleMetrics, error) + } +) + +var _ *types.Role +var _ context.Context + +// SearchRoles returns all matching Roles from store +func SearchRoles(ctx context.Context, s Roles, f types.RoleFilter) (types.RoleSet, types.RoleFilter, error) { + return s.SearchRoles(ctx, f) +} + +// LookupRoleByID searches for role by ID +// +// It returns role even if deleted or suspended +func LookupRoleByID(ctx context.Context, s Roles, id uint64) (*types.Role, error) { + return s.LookupRoleByID(ctx, id) +} + +// LookupRoleByHandle searches for role by its handle +// +// It returns only valid roles (not deleted, not archived) +func LookupRoleByHandle(ctx context.Context, s Roles, handle string) (*types.Role, error) { + return s.LookupRoleByHandle(ctx, handle) +} + +// LookupRoleByName searches for role by its name +// +// It returns only valid roles (not deleted, not archived) +func LookupRoleByName(ctx context.Context, s Roles, name string) (*types.Role, error) { + return s.LookupRoleByName(ctx, name) +} + +// CreateRole creates one or more Roles in store +func CreateRole(ctx context.Context, s Roles, rr ...*types.Role) error { + return s.CreateRole(ctx, rr...) +} + +// UpdateRole updates one or more (existing) Roles in store +func UpdateRole(ctx context.Context, s Roles, rr ...*types.Role) error { + return s.UpdateRole(ctx, rr...) +} + +// PartialRoleUpdate updates one or more existing Roles in store +func PartialRoleUpdate(ctx context.Context, s Roles, onlyColumns []string, rr ...*types.Role) error { + return s.PartialRoleUpdate(ctx, onlyColumns, rr...) +} + +// UpsertRole creates new or updates existing one or more Roles in store +func UpsertRole(ctx context.Context, s Roles, rr ...*types.Role) error { + return s.UpsertRole(ctx, rr...) +} + +// DeleteRole Deletes one or more Roles from store +func DeleteRole(ctx context.Context, s Roles, rr ...*types.Role) error { + return s.DeleteRole(ctx, rr...) +} + +// DeleteRoleByID Deletes Role from store +func DeleteRoleByID(ctx context.Context, s Roles, ID uint64) error { + return s.DeleteRoleByID(ctx, ID) +} + +// TruncateRoles Deletes all Roles from store +func TruncateRoles(ctx context.Context, s Roles) error { + return s.TruncateRoles(ctx) +} + +func RoleMetrics(ctx context.Context, s Roles) (*types.RoleMetrics, error) { + return s.RoleMetrics(ctx) +} diff --git a/store/roles.yaml b/store/roles.yaml index 4e8090ab2..58b74b1b3 100644 --- a/store/roles.yaml +++ b/store/roles.yaml @@ -1,13 +1,10 @@ import: - github.com/cortezaproject/corteza-server/system/types -interface: - - system/service - fields: - { field: ID } - { field: Name, sortable: true } - - { field: Handle, sortable: true, unique: true } + - { field: Handle, sortable: true, unique: true, lookupFilterPreprocessor: lower } - { field: CreatedAt, sortable: true } - { field: UpdatedAt, sortable: true } - { field: ArchivedAt, sortable: true } @@ -20,6 +17,7 @@ lookups: It returns role even if deleted or suspended - fields: [ Handle ] + uniqueConstraintCheck: true filter: { DeletedAt: nil, ArchivedAt: nil } description: |- searches for role by its handle @@ -32,6 +30,10 @@ lookups: It returns only valid roles (not deleted, not archived) +functions: + - name: RoleMetrics + return: [ "*types.RoleMetrics", "error" ] + rdbms: alias: rl table: roles diff --git a/store/settings.gen.go b/store/settings.gen.go new file mode 100644 index 000000000..23fea5e06 --- /dev/null +++ b/store/settings.gen.go @@ -0,0 +1,81 @@ +package store + +// This file is auto-generated. +// +// Template: pkg/codegen/assets/store_base.gen.go.tpl +// Definitions: store/settings.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/system/types" +) + +type ( + Settings interface { + SearchSettings(ctx context.Context, f types.SettingsFilter) (types.SettingValueSet, types.SettingsFilter, error) + LookupSettingByNameOwnedBy(ctx context.Context, name string, owned_by uint64) (*types.SettingValue, error) + + CreateSetting(ctx context.Context, rr ...*types.SettingValue) error + + UpdateSetting(ctx context.Context, rr ...*types.SettingValue) error + PartialSettingUpdate(ctx context.Context, onlyColumns []string, rr ...*types.SettingValue) error + + UpsertSetting(ctx context.Context, rr ...*types.SettingValue) error + + DeleteSetting(ctx context.Context, rr ...*types.SettingValue) error + DeleteSettingByNameOwnedBy(ctx context.Context, name string, ownedBy uint64) error + + TruncateSettings(ctx context.Context) error + } +) + +var _ *types.SettingValue +var _ context.Context + +// SearchSettings returns all matching Settings from store +func SearchSettings(ctx context.Context, s Settings, f types.SettingsFilter) (types.SettingValueSet, types.SettingsFilter, error) { + return s.SearchSettings(ctx, f) +} + +// LookupSettingByNameOwnedBy searches for settings by name and owner +func LookupSettingByNameOwnedBy(ctx context.Context, s Settings, name string, owned_by uint64) (*types.SettingValue, error) { + return s.LookupSettingByNameOwnedBy(ctx, name, owned_by) +} + +// CreateSetting creates one or more Settings in store +func CreateSetting(ctx context.Context, s Settings, rr ...*types.SettingValue) error { + return s.CreateSetting(ctx, rr...) +} + +// UpdateSetting updates one or more (existing) Settings in store +func UpdateSetting(ctx context.Context, s Settings, rr ...*types.SettingValue) error { + return s.UpdateSetting(ctx, rr...) +} + +// PartialSettingUpdate updates one or more existing Settings in store +func PartialSettingUpdate(ctx context.Context, s Settings, onlyColumns []string, rr ...*types.SettingValue) error { + return s.PartialSettingUpdate(ctx, onlyColumns, rr...) +} + +// UpsertSetting creates new or updates existing one or more Settings in store +func UpsertSetting(ctx context.Context, s Settings, rr ...*types.SettingValue) error { + return s.UpsertSetting(ctx, rr...) +} + +// DeleteSetting Deletes one or more Settings from store +func DeleteSetting(ctx context.Context, s Settings, rr ...*types.SettingValue) error { + return s.DeleteSetting(ctx, rr...) +} + +// DeleteSettingByNameOwnedBy Deletes Setting from store +func DeleteSettingByNameOwnedBy(ctx context.Context, s Settings, name string, ownedBy uint64) error { + return s.DeleteSettingByNameOwnedBy(ctx, name, ownedBy) +} + +// TruncateSettings Deletes all Settings from store +func TruncateSettings(ctx context.Context, s Settings) error { + return s.TruncateSettings(ctx) +} diff --git a/store/settings.yaml b/store/settings.yaml index 980654811..1745f1e43 100644 --- a/store/settings.yaml +++ b/store/settings.yaml @@ -1,9 +1,6 @@ import: - github.com/cortezaproject/corteza-server/system/types -interface: - - system/service - types: type: types.SettingValue filterType: types.SettingsFilter @@ -21,8 +18,8 @@ lookups: searches for settings by name and owner search: - disablePaging: true - disableSorting: true + enablePaging: false + enableSorting: false rdbms: alias: st diff --git a/store/sqlite/sqlite.go b/store/sqlite/sqlite.go index 771641490..8f4bfe662 100644 --- a/store/sqlite/sqlite.go +++ b/store/sqlite/sqlite.go @@ -2,6 +2,7 @@ package sqlite import ( "context" + "errors" "fmt" "github.com/Masterminds/squirrel" "github.com/cortezaproject/corteza-server/store" @@ -25,7 +26,9 @@ func New(ctx context.Context, dsn string) (s *Store, err error) { } cfg.PlaceholderFormat = squirrel.Dollar + cfg.TxRetryErrHandler = txRetryErrHandler cfg.ErrorHandler = errorHandler + //cfg.TxDisabled = true s = new(Store) if s.Store, err = rdbms.New(ctx, cfg); err != nil { @@ -36,23 +39,7 @@ func New(ctx context.Context, dsn string) (s *Store, err error) { } func NewInMemory(ctx context.Context) (s *Store, err error) { - var ( - dsn = "sqlite3://file::memory:?cache=shared" - cfg *rdbms.Config - ) - - if cfg, err = ProcDataSourceName(dsn); err != nil { - return nil, err - } - - cfg.PlaceholderFormat = squirrel.Dollar - - s = new(Store) - if s.Store, err = rdbms.New(ctx, cfg); err != nil { - return nil, err - } - - return s, nil + return New(ctx, "sqlite3://file::memory:?cache=shared&mode=rwc") } func (s *Store) Upgrade(ctx context.Context, log *zap.Logger) (err error) { @@ -86,6 +73,25 @@ func ProcDataSourceName(in string) (*rdbms.Config, error) { return c, nil } +func txRetryErrHandler(try int, err error) bool { + for errors.Unwrap(err) != nil { + err = errors.Unwrap(err) + } + + var sqliteErr, ok = err.(sqlite3.Error) + if !ok { + return false + } + + switch sqliteErr.Code { + case sqlite3.ErrLocked: + return true + + } + + return false +} + func errorHandler(err error) error { if err != nil { if implErr, ok := err.(sqlite3.Error); ok { diff --git a/store/tests/actionlog_test.go b/store/tests/actionlog_test.go index f5c75cc42..7129d1f28 100644 --- a/store/tests/actionlog_test.go +++ b/store/tests/actionlog_test.go @@ -3,23 +3,51 @@ package tests import ( "context" "github.com/cortezaproject/corteza-server/pkg/actionlog" + "github.com/cortezaproject/corteza-server/pkg/id" + "github.com/cortezaproject/corteza-server/pkg/rand" + "github.com/cortezaproject/corteza-server/store" _ "github.com/joho/godotenv/autoload" "github.com/stretchr/testify/require" + "strings" "testing" "time" ) -func testActionlog(t *testing.T, s actionlogsStore) { +func testActionlog(t *testing.T, s store.Actionlogs) { var ( ctx = context.Background() - req = require.New(t) - //err error - action *actionlog.Action + makeNew = func(dd ...string) *actionlog.Action { + // minimum data set for new user + desc := strings.Join(dd, "") + return &actionlog.Action{ + ID: id.Next(), + Timestamp: *now(), + Action: "test-action", + Resource: "test-resource", + Description: desc, + } + } + + truncAndFill = func(t *testing.T, l int) (*require.Assertions, actionlog.ActionSet) { + req := require.New(t) + req.NoError(s.TruncateActionlogs(ctx)) + + set := make([]*actionlog.Action, l) + + for i := 0; i < l; i++ { + set[i] = makeNew(string(rand.Bytes(10))) + } + + req.NoError(s.CreateActionlog(ctx, set...)) + return req, set + } ) t.Run("create", func(t *testing.T) { - action = &actionlog.Action{ + req := require.New(t) + req.NoError(s.TruncateActionlogs(ctx)) + action := &actionlog.Action{ ID: 42, Timestamp: time.Now(), Resource: "resource", @@ -28,16 +56,22 @@ func testActionlog(t *testing.T, s actionlogsStore) { req.NoError(s.CreateActionlog(ctx, action)) }) - t.Run("search by resource", func(t *testing.T) { - set, _, err := s.SearchActionlogs(ctx, actionlog.Filter{Resource: action.Resource}) - req.NoError(err) - req.Len(set, 1) - }) + t.Run("search", func(t *testing.T) { + t.Run("by resource", func(t *testing.T) { + req, set := truncAndFill(t, 5) - t.Run("search by action", func(t *testing.T) { - set, _, err := s.SearchActionlogs(ctx, actionlog.Filter{Action: action.Action}) - req.NoError(err) - req.Len(set, 1) + set, _, err := s.SearchActionlogs(ctx, actionlog.Filter{Resource: "test-resource"}) + req.NoError(err) + req.Len(set, 5) + }) + + t.Run("by action", func(t *testing.T) { + req, set := truncAndFill(t, 5) + + set, _, err := s.SearchActionlogs(ctx, actionlog.Filter{Action: "test-action"}) + req.NoError(err) + req.Len(set, 5) + }) }) } diff --git a/store/tests/applications_test.go b/store/tests/applications_test.go index 78774186c..c50d90389 100644 --- a/store/tests/applications_test.go +++ b/store/tests/applications_test.go @@ -4,13 +4,14 @@ import ( "context" "github.com/cortezaproject/corteza-server/pkg/id" "github.com/cortezaproject/corteza-server/pkg/rh" + "github.com/cortezaproject/corteza-server/store" "github.com/cortezaproject/corteza-server/system/types" "github.com/stretchr/testify/require" "testing" "time" ) -func testApplications(t *testing.T, s applicationsStore) { +func testApplications(t *testing.T, s store.Applications) { var ( ctx = context.Background() req = require.New(t) @@ -43,16 +44,16 @@ func testApplications(t *testing.T, s applicationsStore) { req.Nil(fetched.DeletedAt) }) - t.Run("remove", func(t *testing.T) { - application := makeNew("remove") + t.Run("Delete", func(t *testing.T) { + application := makeNew("Delete") req.NoError(s.CreateApplication(ctx, application)) - req.NoError(s.RemoveApplication(ctx)) + req.NoError(s.DeleteApplication(ctx)) }) - t.Run("remove by ID", func(t *testing.T) { - application := makeNew("remove by id") + t.Run("Delete by ID", func(t *testing.T) { + application := makeNew("Delete by id") req.NoError(s.CreateApplication(ctx, application)) - req.NoError(s.RemoveApplication(ctx)) + req.NoError(s.DeleteApplication(ctx)) }) t.Run("update", func(t *testing.T) { diff --git a/store/tests/attachments_test.go b/store/tests/attachments_test.go index 5eaca0d45..40397a848 100644 --- a/store/tests/attachments_test.go +++ b/store/tests/attachments_test.go @@ -2,52 +2,71 @@ package tests import ( "context" + "github.com/cortezaproject/corteza-server/pkg/id" + "github.com/cortezaproject/corteza-server/store" "github.com/cortezaproject/corteza-server/system/types" _ "github.com/joho/godotenv/autoload" "github.com/stretchr/testify/require" + "strings" "testing" - "time" ) -func testAttachment(t *testing.T, s attachmentsStore) { +func testAttachment(t *testing.T, s store.Attachments) { var ( ctx = context.Background() - req = require.New(t) - attachment *types.Attachment + 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.TruncateAttachments(ctx)) + res := makeNew() + req.NoError(s.CreateAttachment(ctx, res)) + return req, res + } ) t.Run("create", func(t *testing.T) { - attachment = &types.Attachment{ - ID: 42, - CreatedAt: time.Now(), + req := require.New(t) + attachment := &types.Attachment{ + ID: id.Next(), + CreatedAt: *now(), } req.NoError(s.CreateAttachment(ctx, attachment)) }) t.Run("lookup by ID", func(t *testing.T) { - fetched, err := s.LookupAttachmentByID(ctx, attachment.ID) + req, att := truncAndCreate(t) + + fetched, err := s.LookupAttachmentByID(ctx, att.ID) req.NoError(err) - req.Equal(attachment.ID, fetched.ID) + 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) { - attachment = &types.Attachment{ - ID: 42, - CreatedAt: time.Now(), - } - req.NoError(s.UpdateAttachment(ctx, attachment)) + req, att := truncAndCreate(t) + att.Url = "url" + req.NoError(s.UpdateAttachment(ctx, att)) + fetched, err := s.LookupAttachmentByID(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") - //set, f, err := s.SearchAttachments(ctx, types.AttachmentFilter{}) - //req.NoError(err) - //req.Len(set, 1) - //req.Equal(uint(1), f.Count) }) t.Run("search by *", func(t *testing.T) { diff --git a/store/tests/compose_charts_test.go b/store/tests/compose_charts_test.go index 2c2146fa0..7746dc38b 100644 --- a/store/tests/compose_charts_test.go +++ b/store/tests/compose_charts_test.go @@ -5,12 +5,13 @@ import ( "github.com/cortezaproject/corteza-server/compose/types" "github.com/cortezaproject/corteza-server/pkg/id" "github.com/cortezaproject/corteza-server/pkg/rh" + "github.com/cortezaproject/corteza-server/store" "github.com/stretchr/testify/require" "testing" "time" ) -func testComposeCharts(t *testing.T, s composeChartsStore) { +func testComposeCharts(t *testing.T, s store.Storable) { var ( ctx = context.Background() req = require.New(t) @@ -29,6 +30,29 @@ func testComposeCharts(t *testing.T, s composeChartsStore) { } ) + t.Run("tx", func(t *testing.T) { + req = require.New(t) + req.NoError(s.TruncateComposeCharts(ctx)) + c := makeNew("foo", "foo") + + err := store.Tx(ctx, s, func(ctx context.Context, s store.Storable) error { + req.NoError(store.CreateComposeChart(ctx, s, c)) + fetched, err := store.LookupComposeChartByID(ctx, s, c.ID) + req.NoError(err) + req.Equal(c.Name, fetched.Name) + req.Equal(c.ID, fetched.ID) + + // force tx to rollback + return store.ErrNotFound + }) + + req.Error(err) + + _, err = store.LookupComposeChartByID(ctx, s, c.ID) + req.EqualError(err, store.ErrNotFound.Error()) + + }) + t.Run("create", func(t *testing.T) { composeChart := makeNew("ComposeChartCRUD", "compose-chart-crud") req.NoError(s.CreateComposeChart(ctx, composeChart)) @@ -50,16 +74,16 @@ func testComposeCharts(t *testing.T, s composeChartsStore) { req.Nil(fetched.DeletedAt) }) - t.Run("remove", func(t *testing.T) { - composeChart := makeNew("remove", "remove") + t.Run("Delete", func(t *testing.T) { + composeChart := makeNew("Delete", "Delete") req.NoError(s.CreateComposeChart(ctx, composeChart)) - req.NoError(s.RemoveComposeChart(ctx)) + req.NoError(s.DeleteComposeChart(ctx)) }) - t.Run("remove by ID", func(t *testing.T) { - composeChart := makeNew("remove by id", "remove-by-id") + t.Run("Delete by ID", func(t *testing.T) { + composeChart := makeNew("Delete by id", "Delete-by-id") req.NoError(s.CreateComposeChart(ctx, composeChart)) - req.NoError(s.RemoveComposeChart(ctx)) + req.NoError(s.DeleteComposeChart(ctx)) }) t.Run("update", func(t *testing.T) { diff --git a/store/tests/compose_module_fields_test.go b/store/tests/compose_module_fields_test.go index 6e0aab639..6d8f48569 100644 --- a/store/tests/compose_module_fields_test.go +++ b/store/tests/compose_module_fields_test.go @@ -4,51 +4,62 @@ import ( "context" "github.com/cortezaproject/corteza-server/compose/types" "github.com/cortezaproject/corteza-server/pkg/id" + "github.com/cortezaproject/corteza-server/pkg/rand" + "github.com/cortezaproject/corteza-server/store" "github.com/stretchr/testify/require" "testing" "time" ) -func testComposeModuleFields(t *testing.T, s composeModuleFieldsStore) { +func testComposeModuleFields(t *testing.T, s store.ComposeModuleFields) { var ( ctx = context.Background() - req = require.New(t) moduleID = id.Next() - makeNew = func(name, handle string) *types.ModuleField { + makeNew = func(name, label string) *types.ModuleField { // minimum data set for new composeModuleField return &types.ModuleField{ ID: id.Next(), ModuleID: moduleID, CreatedAt: time.Now(), Name: name, + Label: label, } } + + truncAndCreate = func(t *testing.T) (*require.Assertions, *types.ModuleField) { + req := require.New(t) + req.NoError(s.TruncateComposeModuleFields(ctx)) + res := makeNew(string(rand.Bytes(10)), string(rand.Bytes(10))) + req.NoError(s.CreateComposeModuleField(ctx, res)) + return req, res + } ) t.Run("create", func(t *testing.T) { + req := require.New(t) + req.NoError(s.TruncateComposeModuleFields(ctx)) composeModuleField := makeNew("ComposeModuleFieldCRUD", "compose-moduleField-crud") req.NoError(s.CreateComposeModuleField(ctx, composeModuleField)) }) - t.Run("create with duplicate handle", func(t *testing.T) { + t.Run("create with duplicate name", func(t *testing.T) { + req := require.New(t) + req.NoError(s.TruncateComposeModuleFields(ctx)) t.Skip("not implemented") }) - t.Run("remove", func(t *testing.T) { - composeModuleField := makeNew("remove", "remove") - req.NoError(s.CreateComposeModuleField(ctx, composeModuleField)) - req.NoError(s.RemoveComposeModuleField(ctx)) - }) - - t.Run("remove by ID", func(t *testing.T) { - composeModuleField := makeNew("remove by id", "remove-by-id") - req.NoError(s.CreateComposeModuleField(ctx, composeModuleField)) - req.NoError(s.RemoveComposeModuleField(ctx)) + t.Run("Delete", func(t *testing.T) { + req, fld := truncAndCreate(t) + req.NoError(s.DeleteComposeModuleField(ctx, fld)) + fetched, _, err := s.SearchComposeModuleFields(ctx, types.ModuleFieldFilter{ModuleID: []uint64{fld.ModuleID}}) + req.NoError(err) + req.Empty(fetched) }) t.Run("update", func(t *testing.T) { + req := require.New(t) composeModuleField := makeNew("update me", "update-me") req.NoError(s.CreateComposeModuleField(ctx, composeModuleField)) diff --git a/store/tests/compose_modules_test.go b/store/tests/compose_modules_test.go index 503e56e77..1801136c3 100644 --- a/store/tests/compose_modules_test.go +++ b/store/tests/compose_modules_test.go @@ -5,12 +5,13 @@ import ( "github.com/cortezaproject/corteza-server/compose/types" "github.com/cortezaproject/corteza-server/pkg/id" "github.com/cortezaproject/corteza-server/pkg/rh" + "github.com/cortezaproject/corteza-server/store" "github.com/stretchr/testify/require" "testing" "time" ) -func testComposeModules(t *testing.T, s composeModulesStore) { +func testComposeModules(t *testing.T, s store.ComposeModules) { var ( ctx = context.Background() req = require.New(t) @@ -50,16 +51,16 @@ func testComposeModules(t *testing.T, s composeModulesStore) { req.Nil(fetched.DeletedAt) }) - t.Run("remove", func(t *testing.T) { - composeModule := makeNew("remove", "remove") + t.Run("Delete", func(t *testing.T) { + composeModule := makeNew("Delete", "Delete") req.NoError(s.CreateComposeModule(ctx, composeModule)) - req.NoError(s.RemoveComposeModule(ctx)) + req.NoError(s.DeleteComposeModule(ctx)) }) - t.Run("remove by ID", func(t *testing.T) { - composeModule := makeNew("remove by id", "remove-by-id") + t.Run("Delete by ID", func(t *testing.T) { + composeModule := makeNew("Delete by id", "Delete-by-id") req.NoError(s.CreateComposeModule(ctx, composeModule)) - req.NoError(s.RemoveComposeModule(ctx)) + req.NoError(s.DeleteComposeModule(ctx)) }) t.Run("update", func(t *testing.T) { diff --git a/store/tests/compose_namespaces_test.go b/store/tests/compose_namespaces_test.go index 986b93ce1..d6a04a389 100644 --- a/store/tests/compose_namespaces_test.go +++ b/store/tests/compose_namespaces_test.go @@ -5,12 +5,13 @@ import ( "github.com/cortezaproject/corteza-server/compose/types" "github.com/cortezaproject/corteza-server/pkg/id" "github.com/cortezaproject/corteza-server/pkg/rh" + "github.com/cortezaproject/corteza-server/store" "github.com/stretchr/testify/require" "testing" "time" ) -func testComposeNamespaces(t *testing.T, s composeNamespacesStore) { +func testComposeNamespaces(t *testing.T, s store.ComposeNamespaces) { var ( ctx = context.Background() req = require.New(t) @@ -47,16 +48,16 @@ func testComposeNamespaces(t *testing.T, s composeNamespacesStore) { req.Nil(fetched.DeletedAt) }) - t.Run("remove", func(t *testing.T) { - composeNamespace := makeNew("remove", "remove") + t.Run("Delete", func(t *testing.T) { + composeNamespace := makeNew("Delete", "Delete") req.NoError(s.CreateComposeNamespace(ctx, composeNamespace)) - req.NoError(s.RemoveComposeNamespace(ctx)) + req.NoError(s.DeleteComposeNamespace(ctx)) }) - t.Run("remove by ID", func(t *testing.T) { - composeNamespace := makeNew("remove by id", "remove-by-id") + t.Run("Delete by ID", func(t *testing.T) { + composeNamespace := makeNew("Delete by id", "Delete-by-id") req.NoError(s.CreateComposeNamespace(ctx, composeNamespace)) - req.NoError(s.RemoveComposeNamespace(ctx)) + req.NoError(s.DeleteComposeNamespace(ctx)) }) t.Run("update", func(t *testing.T) { diff --git a/store/tests/compose_pages_test.go b/store/tests/compose_pages_test.go index 5424135bd..ef3db781b 100644 --- a/store/tests/compose_pages_test.go +++ b/store/tests/compose_pages_test.go @@ -5,12 +5,13 @@ import ( "github.com/cortezaproject/corteza-server/compose/types" "github.com/cortezaproject/corteza-server/pkg/id" "github.com/cortezaproject/corteza-server/pkg/rh" + "github.com/cortezaproject/corteza-server/store" "github.com/stretchr/testify/require" "testing" "time" ) -func testComposePages(t *testing.T, s composePagesStore) { +func testComposePages(t *testing.T, s store.ComposePages) { var ( ctx = context.Background() req = require.New(t) @@ -50,16 +51,16 @@ func testComposePages(t *testing.T, s composePagesStore) { req.Nil(fetched.DeletedAt) }) - t.Run("remove", func(t *testing.T) { - composePage := makeNew("remove", "remove") + t.Run("Delete", func(t *testing.T) { + composePage := makeNew("Delete", "Delete") req.NoError(s.CreateComposePage(ctx, composePage)) - req.NoError(s.RemoveComposePage(ctx)) + req.NoError(s.DeleteComposePage(ctx)) }) - t.Run("remove by ID", func(t *testing.T) { - composePage := makeNew("remove by id", "remove-by-id") + t.Run("Delete by ID", func(t *testing.T) { + composePage := makeNew("Delete by id", "Delete-by-id") req.NoError(s.CreateComposePage(ctx, composePage)) - req.NoError(s.RemoveComposePage(ctx)) + req.NoError(s.DeleteComposePage(ctx)) }) t.Run("update", func(t *testing.T) { diff --git a/store/tests/compose_records_test.go b/store/tests/compose_records_test.go index 84afba880..a08d3a41f 100644 --- a/store/tests/compose_records_test.go +++ b/store/tests/compose_records_test.go @@ -5,41 +5,15 @@ import ( "github.com/cortezaproject/corteza-server/compose/types" "github.com/cortezaproject/corteza-server/pkg/id" "github.com/cortezaproject/corteza-server/pkg/rh" + "github.com/cortezaproject/corteza-server/store" "github.com/stretchr/testify/require" "testing" "time" ) -type ( - // For - composeRecordsStore interface { - SearchComposeRecords(ctx context.Context, m *types.Module, f types.RecordFilter) (types.RecordSet, types.RecordFilter, error) - LookupComposeRecordByID(ctx context.Context, m *types.Module, id uint64) (*types.Record, error) - CreateComposeRecord(ctx context.Context, m *types.Module, rr ...*types.Record) error - //CreateComposeRecordValue(ctx context.Context, m *types.Module, rr ...*types.RecordValue) error - UpdateComposeRecord(ctx context.Context, m *types.Module, rr ...*types.Record) error - //UpdateComposeRecordValue(ctx context.Context, m *types.Module, rr ...*types.RecordValue) error - //PartialUpdateComposeRecord(ctx context.Context, m *types.Module, onlyColumns []string, rr ...*types.Record) error - //PartialUpdateComposeRecordValue(ctx context.Context, m *types.Module, rr ...*types.RecordValue) error - RemoveComposeRecord(ctx context.Context, m *types.Module, rr ...*types.Record) error - //RemoveComposeRecordValue(ctx context.Context, m *types.Module, rr ...*types.RecordValue) error - //RemoveComposeRecordByID(ctx context.Context, m *types.Module, ID uint64) error - //RemoveComposeRecordValueByRecordID(ctx context.Context, m *types.Module, recordID uint64, place int, name string) error - TruncateComposeRecords(ctx context.Context, m *types.Module) error - //TruncateComposeRecordValues(ctx context.Context, m *types.Module) error - //ExecUpdateComposeRecords(ctx context.Context, m *types.Module, cnd squirrel.Sqlizer, set store.Payload) error - //ExecUpdateComposeRecordValues(ctx context.Context, m *types.Module, cnd squirrel.Sqlizer, set store.Payload) error - //ComposeRecordLookup(ctx context.Context, m *types.Module, cnd squirrel.Sqlizer) (*types.Record, error) - //ComposeRecordValueLookup(ctx context.Context, m *types.Module, cnd squirrel.Sqlizer) (*types.RecordValue, error) - //QueryComposeRecords(m *types.Module) squirrel.SelectBuilder - //QueryComposeRecordValues(m *types.Module) squirrel.SelectBuilder - } -) - -func testComposeRecords(t *testing.T, s composeRecordsStore) { +func testComposeRecords(t *testing.T, s store.ComposeRecords) { var ( ctx = context.Background() - req = require.New(t) mod = &types.Module{ ID: id.Next(), @@ -47,204 +21,234 @@ func testComposeRecords(t *testing.T, s composeRecordsStore) { Handle: "", Name: "testComposeRecords", CreatedAt: time.Now(), + Fields: types.ModuleFieldSet{ + &types.ModuleField{Kind: "string", Name: "f1"}, + &types.ModuleField{Kind: "string", Name: "f2"}, + &types.ModuleField{Kind: "string", Name: "f3"}, + }, } - makeNew = func() *types.Record { + makeNew = func(vv ...*types.RecordValue) *types.Record { // minimum data set for new composeRecord + var recordID = id.Next() + + for _, v := range vv { + v.RecordID = recordID + } + return &types.Record{ - ID: id.Next(), + ID: recordID, NamespaceID: mod.NamespaceID, ModuleID: mod.ID, CreatedAt: time.Now(), + Values: vv, } } + + truncAndCreate = func(t *testing.T, rr ...*types.Record) (*require.Assertions, types.RecordSet) { + req := require.New(t) + req.NoError(s.TruncateComposeRecords(ctx, mod)) + + if len(rr) == 0 { + rr = []*types.Record{makeNew()} + } + + for _, rec := range rr { + req.NoError(s.CreateComposeRecord(ctx, mod, rec)) + } + + return req, rr + } ) t.Run("create", func(t *testing.T) { + req := require.New(t) composeRecord := makeNew() req.NoError(s.CreateComposeRecord(ctx, mod, composeRecord)) }) - t.Run("create with duplicate handle", func(t *testing.T) { - t.Skip("not implemented") - }) - t.Run("lookup by ID", func(t *testing.T) { - composeRecord := makeNew() - req.NoError(s.CreateComposeRecord(ctx, mod, composeRecord)) - fetched, err := s.LookupComposeRecordByID(ctx, mod, composeRecord.ID) + req, rr := truncAndCreate(t, makeNew( + &types.RecordValue{Name: "f1", Value: "v1", Ref: 1}, + &types.RecordValue{Name: "f2", Value: "v2", Ref: 2}, + &types.RecordValue{Name: "f3", Value: "v3", Ref: 3}, + )) + rec := rr[0] + + fetched, err := s.LookupComposeRecordByID(ctx, mod, rec.ID) req.NoError(err) - req.Equal(composeRecord.ID, fetched.ID) + req.Equal(rec.ID, fetched.ID) req.NotNil(fetched.CreatedAt) req.Nil(fetched.UpdatedAt) req.Nil(fetched.DeletedAt) + req.Len(fetched.Values, len(rec.Values)) + req.Equal("f2", fetched.Values[1].Name) + req.Equal("v2", fetched.Values[1].Value) + req.Equal(uint64(2), fetched.Values[1].Ref) }) - t.Run("remove", func(t *testing.T) { - composeRecord := makeNew() - req.NoError(s.CreateComposeRecord(ctx, mod, composeRecord)) - req.NoError(s.RemoveComposeRecord(ctx, mod)) + t.Run("Delete", func(t *testing.T) { + req, rr := truncAndCreate(t) + rec := rr[0] + + req.NoError(s.DeleteComposeRecord(ctx, mod, rec)) + _, err := s.LookupComposeRecordByID(ctx, mod, rec.ID) + req.EqualError(err, store.ErrNotFound.Error()) }) - t.Run("remove by ID", func(t *testing.T) { - composeRecord := makeNew() - req.NoError(s.CreateComposeRecord(ctx, mod, composeRecord)) - req.NoError(s.RemoveComposeRecord(ctx, mod)) + t.Run("Delete by ID", func(t *testing.T) { + req, rr := truncAndCreate(t) + rec := rr[0] + + req.NoError(s.DeleteComposeRecordByID(ctx, mod, rec.ID)) + _, err := s.LookupComposeRecordByID(ctx, mod, rec.ID) + req.EqualError(err, store.ErrNotFound.Error()) }) t.Run("update", func(t *testing.T) { - composeRecord := makeNew() - req.NoError(s.CreateComposeRecord(ctx, mod, composeRecord)) + req, rr := truncAndCreate(t) + rec := rr[0] - composeRecord = &types.Record{ - ID: composeRecord.ID, - CreatedAt: composeRecord.CreatedAt, - OwnedBy: 1, + rec = &types.Record{ + ID: rec.ID, + CreatedAt: rec.CreatedAt, + ModuleID: mod.ID, + NamespaceID: mod.NamespaceID, + OwnedBy: id.Next(), } - req.NoError(s.UpdateComposeRecord(ctx, mod, composeRecord)) - updated, err := s.LookupComposeRecordByID(ctx, mod, composeRecord.ID) + req.NoError(s.UpdateComposeRecord(ctx, mod, rec)) + + updated, err := s.LookupComposeRecordByID(ctx, mod, rec.ID) req.NoError(err) - req.Equal(composeRecord.OwnedBy, updated.OwnedBy) + req.Equal(rec.OwnedBy, updated.OwnedBy) + }) + + t.Run("update values", func(t *testing.T) { + req, rr := truncAndCreate(t, makeNew( + &types.RecordValue{Name: "f1", Value: "v1", Ref: 1}, + &types.RecordValue{Name: "f2", Value: "v2", Ref: 2}, + )) + rec := rr[0] + + rec = &types.Record{ + ID: rec.ID, + CreatedAt: rec.CreatedAt, + OwnedBy: id.Next(), + Values: rec.Values, + ModuleID: mod.ID, + NamespaceID: mod.NamespaceID, + } + + rec.Values[0].Value = "vv10" + rec.Values[1].Value = "vv20" + rec.Values = append(rec.Values, &types.RecordValue{Name: "f3", Value: "vv30", Ref: 3}) + rec.Values.SetRecordID(rec.ID) + + req.NoError(s.UpdateComposeRecord(ctx, mod, rec)) + + updated, err := s.LookupComposeRecordByID(ctx, mod, rec.ID) + req.NoError(err) + req.Equal(rec.OwnedBy, updated.OwnedBy) + req.Len(updated.Values, len(rec.Values)) + req.Equal("f2", updated.Values[1].Name) + req.Equal("vv20", updated.Values[1].Value) + }) + + t.Run("soft delete values", func(t *testing.T) { + req, rr := truncAndCreate(t, makeNew( + &types.RecordValue{Name: "f1", Value: "v1", Ref: 1}, + &types.RecordValue{Name: "f2", Value: "v2", Ref: 2}, + )) + rec := rr[0] + rec.DeletedAt = &rec.CreatedAt + + req.NoError(s.UpdateComposeRecord(ctx, mod, rec)) + + updated, err := s.LookupComposeRecordByID(ctx, mod, rec.ID) + + req.NoError(err) + req.NotNil(rec) + req.NotNil(rec.DeletedAt) + req.Len(updated.Values, len(rec.Values)) + req.NotNil(updated.Values[0].DeletedAt) + req.NotNil(updated.Values[1].DeletedAt) }) t.Run("search", func(t *testing.T) { - prefill := []*types.Record{ - makeNew(), - makeNew(), - makeNew(), - makeNew(), - makeNew(), - } + t.Run("by record attributes", func(t *testing.T) { + prefill := []*types.Record{ + makeNew(), + makeNew(), + makeNew(), + makeNew(), + makeNew(), + } - count := len(prefill) + count := len(prefill) - prefill[4].DeletedAt = &prefill[4].CreatedAt - valid := count - 1 + prefill[4].DeletedAt = &prefill[4].CreatedAt + valid := count - 1 - req.NoError(s.TruncateComposeRecords(ctx, mod)) - req.NoError(s.CreateComposeRecord(ctx, mod, prefill...)) + req, _ := truncAndCreate(t, prefill...) - // search for all valid - set, f, err := s.SearchComposeRecords(ctx, mod, types.RecordFilter{}) - req.NoError(err) - req.Len(set, valid) // we've deleted one + // search for all valid + set, _, err := s.SearchComposeRecords(ctx, mod, types.RecordFilter{}) + req.NoError(err) + req.Len(set, valid) // we've deleted one - // search for ALL - set, f, err = s.SearchComposeRecords(ctx, mod, types.RecordFilter{Deleted: rh.FilterStateInclusive}) - req.NoError(err) - req.Len(set, count) // we've deleted one + // search for ALL + set, _, err = s.SearchComposeRecords(ctx, mod, types.RecordFilter{Deleted: rh.FilterStateInclusive}) + req.NoError(err) + req.Len(set, count) // we've deleted one - // search for deleted only - set, f, err = s.SearchComposeRecords(ctx, mod, types.RecordFilter{Deleted: rh.FilterStateExclusive}) - req.NoError(err) - req.Len(set, 1) // we've deleted one + // search for deleted only + set, _, err = s.SearchComposeRecords(ctx, mod, types.RecordFilter{Deleted: rh.FilterStateExclusive}) + req.NoError(err) + req.Len(set, 1) // we've deleted one + }) - _ = f // dummy - }) + t.Run("by values", func(t *testing.T) { + var ( + err error + set types.RecordSet - t.Run("filtered search", func(t *testing.T) { - // testComposeRecordsSearch(t, s) + req, _ = truncAndCreate(t, + makeNew(&types.RecordValue{Name: "f1", Value: "v1"}, &types.RecordValue{Name: "f2", Value: "same"}, &types.RecordValue{Name: "f3", Value: "three"}), + makeNew(&types.RecordValue{Name: "f1", Value: "v2"}, &types.RecordValue{Name: "f2", Value: "same"}, &types.RecordValue{Name: "f3", Value: "three"}), + makeNew(&types.RecordValue{Name: "f1", Value: "v3"}, &types.RecordValue{Name: "f2", Value: "same"}, &types.RecordValue{Name: "f3", Value: "three"}), + makeNew(&types.RecordValue{Name: "f1", Value: "v4"}, &types.RecordValue{Name: "f2", Value: "same"}), + makeNew(&types.RecordValue{Name: "f1", Value: "v5"}, &types.RecordValue{Name: "f2", Value: "same"}), + + // Add one additional record with deleted values + makeNew(&types.RecordValue{Name: "f1", Value: "v6", DeletedAt: now()}, &types.RecordValue{Name: "f2", Value: "deleted", DeletedAt: now()}), + ) + + f = types.RecordFilter{ + ModuleID: mod.ID, + NamespaceID: mod.NamespaceID, + } + ) + + f.Query = `f1 = 'v1'` + set, _, err = s.SearchComposeRecords(ctx, mod, f) + req.NoError(err) + req.Len(set, 1) + + f.Query = `f2 = 'same'` + set, _, err = s.SearchComposeRecords(ctx, mod, f) + req.NoError(err) + req.Len(set, 5) + + f.Query = `f2 = 'different'` + set, _, err = s.SearchComposeRecords(ctx, mod, f) + req.NoError(err) + req.Len(set, 0) + + f.Query = `f3 = 'three' AND f1 = 'v1'` + set, _, err = s.SearchComposeRecords(ctx, mod, f) + req.NoError(err) + req.Len(set, 1) + }) }) } - -//func testComposeRecordsSearch(t *testing.T, s store.ComposeRecords) { -// m := &types.Module{ -// ID: 123, -// NamespaceID: 456, -// Fields: types.ModuleFieldSet{ -// &types.ModuleField{Name: "foo"}, -// &types.ModuleField{Name: "bar"}, -// &types.ModuleField{Name: "booly", Kind: "Bool"}, -// }, -// } -// -// ttc := []struct { -// name string -// f types.RecordFilter -// match []string -// noMatch []string -// args []interface{} -// err string -// }{ -// { -// name: "default filter", -// match: []string{ -// "SELECT r.id, r.module_id, r.rel_namespace, r.owned_by, r.created_at, " + -// "r.created_by, r.updated_at, r.updated_by, r.deleted_at, r.deleted_by " + -// "FROM compose_record AS r " + -// "WHERE r.module_id = ? AND r.rel_namespace = ? AND r.deleted_at IS NULL", -// }, -// }, -// { -// name: "simple query", -// f: types.RecordFilter{Query: "id = 5 AND foo = 7"}, -// match: []string{ -// "r.id = 5", -// "rv_foo.value = 7"}, -// args: []interface{}{"foo"}, -// }, -// { -// name: "sorting", -// f: types.RecordFilter{Sort: "id ASC, bar DESC"}, -// match: []string{ -// " r.id ASC", -// " rv_bar.value DESC", -// }, -// args: []interface{}{"bar"}, -// }, -// { -// name: "exclude deleted records (def. behaviour)", -// f: types.RecordFilter{Deleted: rh.FilterStateExcluded}, -// match: []string{" r.deleted_at IS "}, -// }, -// { -// name: "include deleted records", -// f: types.RecordFilter{Deleted: rh.FilterStateInclusive}, -// noMatch: []string{" r.deleted_at IS NULL "}, -// }, -// { -// name: "only deleted record", -// f: types.RecordFilter{Deleted: rh.FilterStateExclusive}, -// match: []string{" r.deleted_at IS NOT NULL"}, -// }, -// { -// name: "boolean", -// f: types.RecordFilter{Query: "booly"}, -// match: []string{"(rv_booly.value NOT IN ("}, -// args: []interface{}{"booly"}, -// }, -// } -// -// for _, tc := range ttc { -// t.Run(tc.name, func(t *testing.T) { -// req := require.New(t) -// sb, err := s.query(m, tc.f) -// -// if tc.err != nil { -// req.Error(err, tc.err, "buildQuery(%+v) did not return an expected error %q but %q", tc.f, tc.err, err) -// } else { -// req.NoError(err,"buildQuery(%+v) returned an unexpected error: %v", tc.f, err) -// } -// -// sql, args, err := sb.ToSql() -// -// for _, m := range tc.match { -// require.True(t, strings.Contains(sql, m), -// "assertion failed; query %q \n "+ -// " did not contain %q", sql, m) -// } -// -// for _, m := range tc.noMatch { -// require.False(t, strings.Contains(sql, m), -// "assertion failed; query %q \n "+ -// " must not contain %q", sql, m) -// } -// -// tc.args = append(tc.args, m.ID, m.NamespaceID) -// require.True(t, fmt.Sprintf("%+v", args) == fmt.Sprintf("%+v", tc.args), -// "assertion failed; args %+v \n "+ -// " do not match expected %+v", args, tc.args) -// }) -// } -//} diff --git a/store/tests/credentials_test.go b/store/tests/credentials_test.go index 55668a018..e65b2677a 100644 --- a/store/tests/credentials_test.go +++ b/store/tests/credentials_test.go @@ -2,23 +2,44 @@ package tests import ( "context" + "github.com/cortezaproject/corteza-server/pkg/id" + "github.com/cortezaproject/corteza-server/store" "github.com/cortezaproject/corteza-server/system/types" _ "github.com/joho/godotenv/autoload" "github.com/stretchr/testify/require" + "strings" "testing" "time" ) -func testCredentials(t *testing.T, s credentialsStore) { +func testCredentials(t *testing.T, s store.Credentials) { var ( - ctx = context.Background() - req = require.New(t) - credentials *types.Credentials - //err error + ctx = context.Background() + makeNew = func(nn ...string) *types.Credentials { + // minimum data set for new user + name := strings.Join(nn, "") + return &types.Credentials{ + ID: id.Next(), + CreatedAt: time.Now(), + Credentials: name, + Kind: "test-kind", + } + } + + truncAndCreate = func(t *testing.T) (*require.Assertions, *types.Credentials) { + req := require.New(t) + req.NoError(s.TruncateCredentials(ctx)) + user := makeNew() + req.NoError(s.CreateCredentials(ctx, user)) + return req, user + } ) t.Run("create", func(t *testing.T) { - credentials = &types.Credentials{ + req := require.New(t) + req.NoError(s.TruncateCredentials(ctx)) + + credentials := &types.Credentials{ ID: 42, CreatedAt: time.Now(), Label: "CredentialsCRUD", @@ -27,25 +48,22 @@ func testCredentials(t *testing.T, s credentialsStore) { }) t.Run("lookup by ID", func(t *testing.T) { - fetched, err := s.LookupCredentialsByID(ctx, credentials.ID) + req, crd := truncAndCreate(t) + fetched, err := s.LookupCredentialsByID(ctx, crd.ID) req.NoError(err) - req.Equal(credentials.ID, fetched.ID) + req.Equal(crd.ID, fetched.ID) req.NotNil(fetched.CreatedAt) req.Nil(fetched.UpdatedAt) req.Nil(fetched.DeletedAt) }) t.Run("update", func(t *testing.T) { + req, crd := truncAndCreate(t) + crd.Credentials = "new-credentials" + req.NoError(s.UpdateCredentials(ctx, crd)) + fetched, err := s.LookupCredentialsByID(ctx, crd.ID) + req.NoError(err) + req.Equal("new-credentials", fetched.Credentials) - credentials = &types.Credentials{ - ID: 42, - CreatedAt: time.Now(), - Label: "CredentialsCRUD+2", - } - req.NoError(s.UpdateCredentials(ctx, credentials)) - }) - - t.Run("search by *", func(t *testing.T) { - t.Skip("not implemented") }) } diff --git a/store/tests/gen_test.go b/store/tests/gen_test.go index 9a5c15faf..e9534176f 100644 --- a/store/tests/gen_test.go +++ b/store/tests/gen_test.go @@ -1,122 +1,108 @@ package tests // This file is auto-generated. +// +// Template: pkg/codegen/assets/store_test_all.gen.go +// Definitions: +// - store/actionlog.yaml +// - store/applications.yaml +// - store/attachments.yaml +// - store/compose_charts.yaml +// - store/compose_module_fields.yaml +// - store/compose_modules.yaml +// - store/compose_namespaces.yaml +// - store/compose_pages.yaml +// - store/credentials.yaml +// - store/rbac_rules.yaml +// - store/reminders.yaml +// - store/role_members.yaml +// - store/roles.yaml +// - store/settings.yaml +// - store/users.yaml + // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // import ( - "context" - "github.com/stretchr/testify/require" + "github.com/cortezaproject/corteza-server/store" "testing" ) -func testAllGenerated(t *testing.T, all interface{}) { - +func testAllGenerated(t *testing.T, s store.Storable) { // Run generated tests for Actionlog t.Run("Actionlog", func(t *testing.T) { - var s = all.(actionlogsStore) - require.New(t).NoError(s.TruncateActionlogs(context.Background())) testActionlog(t, s) }) // Run generated tests for Applications t.Run("Applications", func(t *testing.T) { - var s = all.(applicationsStore) - require.New(t).NoError(s.TruncateApplications(context.Background())) testApplications(t, s) }) // Run generated tests for Attachment t.Run("Attachment", func(t *testing.T) { - var s = all.(attachmentsStore) - require.New(t).NoError(s.TruncateAttachments(context.Background())) testAttachment(t, s) }) // Run generated tests for ComposeCharts t.Run("ComposeCharts", func(t *testing.T) { - var s = all.(composeChartsStore) - require.New(t).NoError(s.TruncateComposeCharts(context.Background())) testComposeCharts(t, s) }) // Run generated tests for ComposeModuleFields t.Run("ComposeModuleFields", func(t *testing.T) { - var s = all.(composeModuleFieldsStore) - require.New(t).NoError(s.TruncateComposeModuleFields(context.Background())) testComposeModuleFields(t, s) }) // Run generated tests for ComposeModules t.Run("ComposeModules", func(t *testing.T) { - var s = all.(composeModulesStore) - require.New(t).NoError(s.TruncateComposeModules(context.Background())) testComposeModules(t, s) }) // Run generated tests for ComposeNamespaces t.Run("ComposeNamespaces", func(t *testing.T) { - var s = all.(composeNamespacesStore) - require.New(t).NoError(s.TruncateComposeNamespaces(context.Background())) testComposeNamespaces(t, s) }) // Run generated tests for ComposePages t.Run("ComposePages", func(t *testing.T) { - var s = all.(composePagesStore) - require.New(t).NoError(s.TruncateComposePages(context.Background())) testComposePages(t, s) }) // Run generated tests for Credentials t.Run("Credentials", func(t *testing.T) { - var s = all.(credentialsStore) - require.New(t).NoError(s.TruncateCredentials(context.Background())) testCredentials(t, s) }) // Run generated tests for RbacRules t.Run("RbacRules", func(t *testing.T) { - var s = all.(rbacRulesStore) - require.New(t).NoError(s.TruncateRbacRules(context.Background())) testRbacRules(t, s) }) // Run generated tests for Reminders t.Run("Reminders", func(t *testing.T) { - var s = all.(remindersStore) - require.New(t).NoError(s.TruncateReminders(context.Background())) testReminders(t, s) }) // Run generated tests for RoleMembers t.Run("RoleMembers", func(t *testing.T) { - var s = all.(roleMembersStore) - require.New(t).NoError(s.TruncateRoleMembers(context.Background())) testRoleMembers(t, s) }) // Run generated tests for Roles t.Run("Roles", func(t *testing.T) { - var s = all.(rolesStore) - require.New(t).NoError(s.TruncateRoles(context.Background())) testRoles(t, s) }) // Run generated tests for Settings t.Run("Settings", func(t *testing.T) { - var s = all.(settingsStore) - require.New(t).NoError(s.TruncateSettings(context.Background())) testSettings(t, s) }) // Run generated tests for Users t.Run("Users", func(t *testing.T) { - var s = all.(usersStore) - require.New(t).NoError(s.TruncateUsers(context.Background())) testUsers(t, s) }) - } diff --git a/store/tests/main_test.go b/store/tests/main_test.go index 70232d890..79b8d2af5 100644 --- a/store/tests/main_test.go +++ b/store/tests/main_test.go @@ -2,6 +2,7 @@ package tests import ( "context" + "github.com/cortezaproject/corteza-server/store" "github.com/cortezaproject/corteza-server/store/mysql" "github.com/cortezaproject/corteza-server/store/pgsql" "github.com/cortezaproject/corteza-server/store/sqlite" @@ -10,11 +11,13 @@ import ( "go.uber.org/zap" "os" "testing" + "time" ) -type ( - storeInterface interface { - storeGeneratedInterfaces +var ( + now = func() *time.Time { + n := time.Now() + return &n } ) @@ -27,7 +30,7 @@ func Test_Store(t *testing.T) { suite struct { name string dsnEnvKey string - init func(ctx context.Context, dsn string) (storeInterface, error) + init func(ctx context.Context, dsn string) (store.Storable, error) } upgrader interface { @@ -42,12 +45,12 @@ func Test_Store(t *testing.T) { { name: "MySQL", dsnEnvKey: "RDBMS_MYSQL_DSN", - init: func(ctx context.Context, dsn string) (storeInterface, error) { return mysql.New(ctx, dsn) }, + init: func(ctx context.Context, dsn string) (store.Storable, error) { return mysql.New(ctx, dsn) }, }, { name: "PostgreSQL", dsnEnvKey: "RDBMS_PGSQL_DSN", - init: func(ctx context.Context, dsn string) (storeInterface, error) { return pgsql.New(ctx, dsn) }, + init: func(ctx context.Context, dsn string) (store.Storable, error) { return pgsql.New(ctx, dsn) }, }, { name: "CockroachDB", @@ -57,7 +60,7 @@ func Test_Store(t *testing.T) { { name: "SQLite", dsnEnvKey: "RDBMS_SQLITE_DSN", - init: func(ctx context.Context, dsn string) (storeInterface, error) { return sqlite.New(ctx, dsn) }, + init: func(ctx context.Context, dsn string) (store.Storable, error) { return sqlite.New(ctx, dsn) }, }, { name: "InMemory", @@ -102,11 +105,6 @@ func Test_Store(t *testing.T) { } testAllGenerated(t, genericStore) - - t.Run("ComposeRecords", func(t *testing.T) { - var s = genericStore.(composeRecordsStore) - testComposeRecords(t, s) - }) }) } diff --git a/store/tests/rbac_rules_test.go b/store/tests/rbac_rules_test.go index c0cdf3b34..21e35aab3 100644 --- a/store/tests/rbac_rules_test.go +++ b/store/tests/rbac_rules_test.go @@ -3,42 +3,55 @@ package tests import ( "context" "github.com/cortezaproject/corteza-server/pkg/permissions" + "github.com/cortezaproject/corteza-server/store" _ "github.com/joho/godotenv/autoload" "github.com/stretchr/testify/require" "testing" ) -func testRbacRules(t *testing.T, s rbacRulesStore) { +func testRbacRules(t *testing.T, s store.RbacRules) { var ( ctx = context.Background() - req = require.New(t) - - rule *permissions.Rule ) t.Run("create", func(t *testing.T) { - rule = &permissions.Rule{ - RoleID: 42, - Resource: permissions.Resource("res"), - Operation: permissions.Operation("op"), - Access: permissions.Allow, - } - req.NoError(s.CreateRbacRule(ctx, rule)) + req := require.New(t) + req.NoError(s.TruncateRbacRules(ctx)) + req.NoError(s.CreateRbacRule(ctx, permissions.AllowRule(42, "res1", "op1"))) }) t.Run("update", func(t *testing.T) { - rule = &permissions.Rule{ - RoleID: 42, - Resource: permissions.Resource("res"), - Operation: permissions.Operation("op"), - Access: permissions.Allow, - } - req.NoError(s.UpdateRbacRule(ctx, rule)) + req := require.New(t) + req.NoError(s.TruncateRbacRules(ctx)) + req.NoError(s.UpdateRbacRule(ctx, permissions.AllowRule(42, "res1", "op1"))) }) - t.Run("search", func(t *testing.T) { + t.Run("upsert", func(t *testing.T) { + req := require.New(t) + req.NoError(s.TruncateRbacRules(ctx)) + req.NoError(s.UpsertRbacRule(ctx, permissions.AllowRule(42, "res1", "op1"))) set, _, err := s.SearchRbacRules(ctx, permissions.RuleFilter{}) req.NoError(err) req.Len(set, 1) + req.True(set[0].Access == permissions.Allow) + + req.NoError(s.UpsertRbacRule(ctx, permissions.DenyRule(42, "res1", "op1"))) + set, _, err = s.SearchRbacRules(ctx, permissions.RuleFilter{}) + req.NoError(err) + req.Len(set, 1) + req.True(set[0].Access == permissions.Deny) + }) + + t.Run("search", func(t *testing.T) { + req := require.New(t) + req.NoError(s.TruncateRbacRules(ctx)) + req.NoError(s.CreateRbacRule(ctx, + permissions.AllowRule(42, "res1", "op1"), + permissions.AllowRule(42, "res2", "op2"), + )) + + set, _, err := s.SearchRbacRules(ctx, permissions.RuleFilter{}) + req.NoError(err) + req.Len(set, 2) }) } diff --git a/store/tests/reminders_test.go b/store/tests/reminders_test.go index fbeb430ed..b0a148bf1 100644 --- a/store/tests/reminders_test.go +++ b/store/tests/reminders_test.go @@ -4,6 +4,7 @@ import ( "context" "github.com/cortezaproject/corteza-server/pkg/id" "github.com/cortezaproject/corteza-server/pkg/rand" + "github.com/cortezaproject/corteza-server/store" "github.com/cortezaproject/corteza-server/system/types" _ "github.com/joho/godotenv/autoload" "github.com/stretchr/testify/require" @@ -12,7 +13,7 @@ import ( "time" ) -func testReminders(t *testing.T, s remindersStore) { +func testReminders(t *testing.T, s store.Reminders) { var ( ctx = context.Background() @@ -21,8 +22,8 @@ func testReminders(t *testing.T, s remindersStore) { name := strings.Join(nn, "") thisID := id.Next() return &types.Reminder{ - ID: thisID, - Resource: "resource+" + name, + ID: thisID, + Resource: "resource+" + name, AssignedTo: thisID, CreatedAt: time.Now(), AssignedAt: time.Now(), @@ -50,7 +51,6 @@ func testReminders(t *testing.T, s remindersStore) { req.NoError(s.CreateReminder(ctx, set...)) return req, set } - ) t.Run("create", func(t *testing.T) { diff --git a/store/tests/role_members_test.go b/store/tests/role_members_test.go index 35ddf18b1..abba64825 100644 --- a/store/tests/role_members_test.go +++ b/store/tests/role_members_test.go @@ -3,13 +3,14 @@ package tests import ( "context" "github.com/cortezaproject/corteza-server/pkg/id" + "github.com/cortezaproject/corteza-server/store" "github.com/cortezaproject/corteza-server/system/types" _ "github.com/joho/godotenv/autoload" "github.com/stretchr/testify/require" "testing" ) -func testRoleMembers(t *testing.T, s roleMembersStore) { +func testRoleMembers(t *testing.T, s store.RoleMembers) { var ( ctx = context.Background() req = require.New(t) diff --git a/store/tests/roles_test.go b/store/tests/roles_test.go index 6011b47e8..c5f0dca82 100644 --- a/store/tests/roles_test.go +++ b/store/tests/roles_test.go @@ -2,9 +2,10 @@ package tests import ( "context" - "github.com/cortezaproject/corteza-server/pkg/rh" "github.com/cortezaproject/corteza-server/pkg/id" "github.com/cortezaproject/corteza-server/pkg/rand" + "github.com/cortezaproject/corteza-server/pkg/rh" + "github.com/cortezaproject/corteza-server/store" "github.com/cortezaproject/corteza-server/system/types" _ "github.com/joho/godotenv/autoload" "github.com/stretchr/testify/require" @@ -13,7 +14,7 @@ import ( "time" ) -func testRoles(t *testing.T, s rolesStore) { +func testRoles(t *testing.T, s store.Roles) { var ( ctx = context.Background() @@ -153,7 +154,6 @@ func testRoles(t *testing.T, s rolesStore) { }) }) - t.Run("ordered search", func(t *testing.T) { t.Skip("not implemented") }) diff --git a/store/tests/settings_test.go b/store/tests/settings_test.go index 11453bb50..54aaf4930 100644 --- a/store/tests/settings_test.go +++ b/store/tests/settings_test.go @@ -1,10 +1,17 @@ package tests import ( + "context" + "github.com/cortezaproject/corteza-server/store" "testing" ) -func testSettings(t *testing.T, s settingsStore) { +func testSettings(t *testing.T, s store.Settings) { + var ( + ctx = context.Background() + _ = ctx + ) + t.Run("create", func(t *testing.T) { t.Skip("not implemented") }) diff --git a/store/tests/store_interface.gen.go b/store/tests/store_interface.gen.go deleted file mode 100644 index 7596ae035..000000000 --- a/store/tests/store_interface.gen.go +++ /dev/null @@ -1,46 +0,0 @@ -package tests - -// This file is auto-generated. -// -// Template: pkg/store_interfaces_joined.gen.go.tpl -// Definitions: -// - store/actionlog.yaml -// - store/applications.yaml -// - store/attachments.yaml -// - store/compose_charts.yaml -// - store/compose_module_fields.yaml -// - store/compose_modules.yaml -// - store/compose_namespaces.yaml -// - store/compose_pages.yaml -// - store/credentials.yaml -// - store/rbac_rules.yaml -// - store/reminders.yaml -// - store/role_members.yaml -// - store/roles.yaml -// - store/settings.yaml -// - store/users.yaml -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// - -type ( - // Interface combines interfaces of all supported store interfaces - storeGeneratedInterfaces interface { - actionlogsStore - applicationsStore - attachmentsStore - composeChartsStore - composeModuleFieldsStore - composeModulesStore - composeNamespacesStore - composePagesStore - credentialsStore - rbacRulesStore - remindersStore - roleMembersStore - rolesStore - settingsStore - usersStore - } -) diff --git a/store/tests/store_interface_actionlog.gen.go b/store/tests/store_interface_actionlog.gen.go deleted file mode 100644 index eac543af7..000000000 --- a/store/tests/store_interface_actionlog.gen.go +++ /dev/null @@ -1,27 +0,0 @@ -package tests - -// This file is auto-generated. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -// Definitions file that controls how this file is generated: -// - store/actionlog.yaml - -import ( - "context" - "github.com/cortezaproject/corteza-server/pkg/actionlog" -) - -type ( - actionlogsStore interface { - SearchActionlogs(ctx context.Context, f actionlog.Filter) (actionlog.ActionSet, actionlog.Filter, error) - CreateActionlog(ctx context.Context, rr ...*actionlog.Action) error - UpdateActionlog(ctx context.Context, rr ...*actionlog.Action) error - PartialUpdateActionlog(ctx context.Context, onlyColumns []string, rr ...*actionlog.Action) error - RemoveActionlog(ctx context.Context, rr ...*actionlog.Action) error - RemoveActionlogByID(ctx context.Context, ID uint64) error - - TruncateActionlogs(ctx context.Context) error - } -) diff --git a/store/tests/store_interface_applications.gen.go b/store/tests/store_interface_applications.gen.go deleted file mode 100644 index c01efe25d..000000000 --- a/store/tests/store_interface_applications.gen.go +++ /dev/null @@ -1,28 +0,0 @@ -package tests - -// This file is auto-generated. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -// Definitions file that controls how this file is generated: -// - store/applications.yaml - -import ( - "context" - "github.com/cortezaproject/corteza-server/system/types" -) - -type ( - applicationsStore interface { - SearchApplications(ctx context.Context, f types.ApplicationFilter) (types.ApplicationSet, types.ApplicationFilter, error) - LookupApplicationByID(ctx context.Context, id uint64) (*types.Application, error) - CreateApplication(ctx context.Context, rr ...*types.Application) error - UpdateApplication(ctx context.Context, rr ...*types.Application) error - PartialUpdateApplication(ctx context.Context, onlyColumns []string, rr ...*types.Application) error - RemoveApplication(ctx context.Context, rr ...*types.Application) error - RemoveApplicationByID(ctx context.Context, ID uint64) error - - TruncateApplications(ctx context.Context) error - } -) diff --git a/store/tests/store_interface_attachments.gen.go b/store/tests/store_interface_attachments.gen.go deleted file mode 100644 index 45bbf6d50..000000000 --- a/store/tests/store_interface_attachments.gen.go +++ /dev/null @@ -1,28 +0,0 @@ -package tests - -// This file is auto-generated. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -// Definitions file that controls how this file is generated: -// - store/attachments.yaml - -import ( - "context" - "github.com/cortezaproject/corteza-server/system/types" -) - -type ( - attachmentsStore interface { - SearchAttachments(ctx context.Context, f types.AttachmentFilter) (types.AttachmentSet, types.AttachmentFilter, error) - LookupAttachmentByID(ctx context.Context, id uint64) (*types.Attachment, error) - CreateAttachment(ctx context.Context, rr ...*types.Attachment) error - UpdateAttachment(ctx context.Context, rr ...*types.Attachment) error - PartialUpdateAttachment(ctx context.Context, onlyColumns []string, rr ...*types.Attachment) error - RemoveAttachment(ctx context.Context, rr ...*types.Attachment) error - RemoveAttachmentByID(ctx context.Context, ID uint64) error - - TruncateAttachments(ctx context.Context) error - } -) diff --git a/store/tests/store_interface_compose_charts.gen.go b/store/tests/store_interface_compose_charts.gen.go deleted file mode 100644 index 70477d342..000000000 --- a/store/tests/store_interface_compose_charts.gen.go +++ /dev/null @@ -1,29 +0,0 @@ -package tests - -// This file is auto-generated. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -// Definitions file that controls how this file is generated: -// - store/compose_charts.yaml - -import ( - "context" - "github.com/cortezaproject/corteza-server/compose/types" -) - -type ( - composeChartsStore interface { - SearchComposeCharts(ctx context.Context, f types.ChartFilter) (types.ChartSet, types.ChartFilter, error) - LookupComposeChartByID(ctx context.Context, id uint64) (*types.Chart, error) - LookupComposeChartByHandle(ctx context.Context, handle string) (*types.Chart, error) - CreateComposeChart(ctx context.Context, rr ...*types.Chart) error - UpdateComposeChart(ctx context.Context, rr ...*types.Chart) error - PartialUpdateComposeChart(ctx context.Context, onlyColumns []string, rr ...*types.Chart) error - RemoveComposeChart(ctx context.Context, rr ...*types.Chart) error - RemoveComposeChartByID(ctx context.Context, ID uint64) error - - TruncateComposeCharts(ctx context.Context) error - } -) diff --git a/store/tests/store_interface_compose_module_fields.gen.go b/store/tests/store_interface_compose_module_fields.gen.go deleted file mode 100644 index d7b646758..000000000 --- a/store/tests/store_interface_compose_module_fields.gen.go +++ /dev/null @@ -1,26 +0,0 @@ -package tests - -// This file is auto-generated. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -// Definitions file that controls how this file is generated: -// - store/compose_module_fields.yaml - -import ( - "context" - "github.com/cortezaproject/corteza-server/compose/types" -) - -type ( - composeModuleFieldsStore interface { - CreateComposeModuleField(ctx context.Context, rr ...*types.ModuleField) error - UpdateComposeModuleField(ctx context.Context, rr ...*types.ModuleField) error - PartialUpdateComposeModuleField(ctx context.Context, onlyColumns []string, rr ...*types.ModuleField) error - RemoveComposeModuleField(ctx context.Context, rr ...*types.ModuleField) error - RemoveComposeModuleFieldByID(ctx context.Context, ID uint64) error - - TruncateComposeModuleFields(ctx context.Context) error - } -) diff --git a/store/tests/store_interface_compose_modules.gen.go b/store/tests/store_interface_compose_modules.gen.go deleted file mode 100644 index 87e1a880b..000000000 --- a/store/tests/store_interface_compose_modules.gen.go +++ /dev/null @@ -1,29 +0,0 @@ -package tests - -// This file is auto-generated. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -// Definitions file that controls how this file is generated: -// - store/compose_modules.yaml - -import ( - "context" - "github.com/cortezaproject/corteza-server/compose/types" -) - -type ( - composeModulesStore interface { - SearchComposeModules(ctx context.Context, f types.ModuleFilter) (types.ModuleSet, types.ModuleFilter, error) - LookupComposeModuleByHandle(ctx context.Context, handle string) (*types.Module, error) - LookupComposeModuleByID(ctx context.Context, id uint64) (*types.Module, error) - CreateComposeModule(ctx context.Context, rr ...*types.Module) error - UpdateComposeModule(ctx context.Context, rr ...*types.Module) error - PartialUpdateComposeModule(ctx context.Context, onlyColumns []string, rr ...*types.Module) error - RemoveComposeModule(ctx context.Context, rr ...*types.Module) error - RemoveComposeModuleByID(ctx context.Context, ID uint64) error - - TruncateComposeModules(ctx context.Context) error - } -) diff --git a/store/tests/store_interface_compose_namespaces.gen.go b/store/tests/store_interface_compose_namespaces.gen.go deleted file mode 100644 index e200dc0ca..000000000 --- a/store/tests/store_interface_compose_namespaces.gen.go +++ /dev/null @@ -1,29 +0,0 @@ -package tests - -// This file is auto-generated. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -// Definitions file that controls how this file is generated: -// - store/compose_namespaces.yaml - -import ( - "context" - "github.com/cortezaproject/corteza-server/compose/types" -) - -type ( - composeNamespacesStore interface { - SearchComposeNamespaces(ctx context.Context, f types.NamespaceFilter) (types.NamespaceSet, types.NamespaceFilter, error) - LookupComposeNamespaceBySlug(ctx context.Context, slug string) (*types.Namespace, error) - LookupComposeNamespaceByID(ctx context.Context, id uint64) (*types.Namespace, error) - CreateComposeNamespace(ctx context.Context, rr ...*types.Namespace) error - UpdateComposeNamespace(ctx context.Context, rr ...*types.Namespace) error - PartialUpdateComposeNamespace(ctx context.Context, onlyColumns []string, rr ...*types.Namespace) error - RemoveComposeNamespace(ctx context.Context, rr ...*types.Namespace) error - RemoveComposeNamespaceByID(ctx context.Context, ID uint64) error - - TruncateComposeNamespaces(ctx context.Context) error - } -) diff --git a/store/tests/store_interface_compose_pages.gen.go b/store/tests/store_interface_compose_pages.gen.go deleted file mode 100644 index bbd1b2475..000000000 --- a/store/tests/store_interface_compose_pages.gen.go +++ /dev/null @@ -1,29 +0,0 @@ -package tests - -// This file is auto-generated. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -// Definitions file that controls how this file is generated: -// - store/compose_pages.yaml - -import ( - "context" - "github.com/cortezaproject/corteza-server/compose/types" -) - -type ( - composePagesStore interface { - SearchComposePages(ctx context.Context, f types.PageFilter) (types.PageSet, types.PageFilter, error) - LookupComposePageByHandle(ctx context.Context, handle string) (*types.Page, error) - LookupComposePageByID(ctx context.Context, id uint64) (*types.Page, error) - CreateComposePage(ctx context.Context, rr ...*types.Page) error - UpdateComposePage(ctx context.Context, rr ...*types.Page) error - PartialUpdateComposePage(ctx context.Context, onlyColumns []string, rr ...*types.Page) error - RemoveComposePage(ctx context.Context, rr ...*types.Page) error - RemoveComposePageByID(ctx context.Context, ID uint64) error - - TruncateComposePages(ctx context.Context) error - } -) diff --git a/store/tests/store_interface_credentials.gen.go b/store/tests/store_interface_credentials.gen.go deleted file mode 100644 index d59e7d881..000000000 --- a/store/tests/store_interface_credentials.gen.go +++ /dev/null @@ -1,28 +0,0 @@ -package tests - -// This file is auto-generated. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -// Definitions file that controls how this file is generated: -// - store/credentials.yaml - -import ( - "context" - "github.com/cortezaproject/corteza-server/system/types" -) - -type ( - credentialsStore interface { - SearchCredentials(ctx context.Context, f types.CredentialsFilter) (types.CredentialsSet, types.CredentialsFilter, error) - LookupCredentialsByID(ctx context.Context, id uint64) (*types.Credentials, error) - CreateCredentials(ctx context.Context, rr ...*types.Credentials) error - UpdateCredentials(ctx context.Context, rr ...*types.Credentials) error - PartialUpdateCredentials(ctx context.Context, onlyColumns []string, rr ...*types.Credentials) error - RemoveCredentials(ctx context.Context, rr ...*types.Credentials) error - RemoveCredentialsByID(ctx context.Context, ID uint64) error - - TruncateCredentials(ctx context.Context) error - } -) diff --git a/store/tests/store_interface_rbac_rules.gen.go b/store/tests/store_interface_rbac_rules.gen.go deleted file mode 100644 index fad961e13..000000000 --- a/store/tests/store_interface_rbac_rules.gen.go +++ /dev/null @@ -1,27 +0,0 @@ -package tests - -// This file is auto-generated. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -// Definitions file that controls how this file is generated: -// - store/rbac_rules.yaml - -import ( - "context" - "github.com/cortezaproject/corteza-server/pkg/permissions" -) - -type ( - rbacRulesStore interface { - SearchRbacRules(ctx context.Context, f permissions.RuleFilter) (permissions.RuleSet, permissions.RuleFilter, error) - CreateRbacRule(ctx context.Context, rr ...*permissions.Rule) error - UpdateRbacRule(ctx context.Context, rr ...*permissions.Rule) error - PartialUpdateRbacRule(ctx context.Context, onlyColumns []string, rr ...*permissions.Rule) error - RemoveRbacRule(ctx context.Context, rr ...*permissions.Rule) error - RemoveRbacRuleByRoleIDResourceOperation(ctx context.Context, roleID uint64, resource string, operation string) error - - TruncateRbacRules(ctx context.Context) error - } -) diff --git a/store/tests/store_interface_reminders.gen.go b/store/tests/store_interface_reminders.gen.go deleted file mode 100644 index 2eec909d7..000000000 --- a/store/tests/store_interface_reminders.gen.go +++ /dev/null @@ -1,28 +0,0 @@ -package tests - -// This file is auto-generated. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -// Definitions file that controls how this file is generated: -// - store/reminders.yaml - -import ( - "context" - "github.com/cortezaproject/corteza-server/system/types" -) - -type ( - remindersStore interface { - SearchReminders(ctx context.Context, f types.ReminderFilter) (types.ReminderSet, types.ReminderFilter, error) - LookupReminderByID(ctx context.Context, id uint64) (*types.Reminder, error) - CreateReminder(ctx context.Context, rr ...*types.Reminder) error - UpdateReminder(ctx context.Context, rr ...*types.Reminder) error - PartialUpdateReminder(ctx context.Context, onlyColumns []string, rr ...*types.Reminder) error - RemoveReminder(ctx context.Context, rr ...*types.Reminder) error - RemoveReminderByID(ctx context.Context, ID uint64) error - - TruncateReminders(ctx context.Context) error - } -) diff --git a/store/tests/store_interface_role_members.gen.go b/store/tests/store_interface_role_members.gen.go deleted file mode 100644 index 52522241d..000000000 --- a/store/tests/store_interface_role_members.gen.go +++ /dev/null @@ -1,27 +0,0 @@ -package tests - -// This file is auto-generated. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -// Definitions file that controls how this file is generated: -// - store/role_members.yaml - -import ( - "context" - "github.com/cortezaproject/corteza-server/system/types" -) - -type ( - roleMembersStore interface { - SearchRoleMembers(ctx context.Context, f types.RoleMemberFilter) (types.RoleMemberSet, types.RoleMemberFilter, error) - CreateRoleMember(ctx context.Context, rr ...*types.RoleMember) error - UpdateRoleMember(ctx context.Context, rr ...*types.RoleMember) error - PartialUpdateRoleMember(ctx context.Context, onlyColumns []string, rr ...*types.RoleMember) error - RemoveRoleMember(ctx context.Context, rr ...*types.RoleMember) error - RemoveRoleMemberByUserIDRoleID(ctx context.Context, userID uint64, roleID uint64) error - - TruncateRoleMembers(ctx context.Context) error - } -) diff --git a/store/tests/store_interface_roles.gen.go b/store/tests/store_interface_roles.gen.go deleted file mode 100644 index 2607796d1..000000000 --- a/store/tests/store_interface_roles.gen.go +++ /dev/null @@ -1,30 +0,0 @@ -package tests - -// This file is auto-generated. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -// Definitions file that controls how this file is generated: -// - store/roles.yaml - -import ( - "context" - "github.com/cortezaproject/corteza-server/system/types" -) - -type ( - rolesStore interface { - SearchRoles(ctx context.Context, f types.RoleFilter) (types.RoleSet, types.RoleFilter, error) - LookupRoleByID(ctx context.Context, id uint64) (*types.Role, error) - LookupRoleByHandle(ctx context.Context, handle string) (*types.Role, error) - LookupRoleByName(ctx context.Context, name string) (*types.Role, error) - CreateRole(ctx context.Context, rr ...*types.Role) error - UpdateRole(ctx context.Context, rr ...*types.Role) error - PartialUpdateRole(ctx context.Context, onlyColumns []string, rr ...*types.Role) error - RemoveRole(ctx context.Context, rr ...*types.Role) error - RemoveRoleByID(ctx context.Context, ID uint64) error - - TruncateRoles(ctx context.Context) error - } -) diff --git a/store/tests/store_interface_settings.gen.go b/store/tests/store_interface_settings.gen.go deleted file mode 100644 index 03de17e86..000000000 --- a/store/tests/store_interface_settings.gen.go +++ /dev/null @@ -1,28 +0,0 @@ -package tests - -// This file is auto-generated. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -// Definitions file that controls how this file is generated: -// - store/settings.yaml - -import ( - "context" - "github.com/cortezaproject/corteza-server/system/types" -) - -type ( - settingsStore interface { - SearchSettings(ctx context.Context, f types.SettingsFilter) (types.SettingValueSet, types.SettingsFilter, error) - LookupSettingByNameOwnedBy(ctx context.Context, name string, owned_by uint64) (*types.SettingValue, error) - CreateSetting(ctx context.Context, rr ...*types.SettingValue) error - UpdateSetting(ctx context.Context, rr ...*types.SettingValue) error - PartialUpdateSetting(ctx context.Context, onlyColumns []string, rr ...*types.SettingValue) error - RemoveSetting(ctx context.Context, rr ...*types.SettingValue) error - RemoveSettingByNameOwnedBy(ctx context.Context, name string, ownedBy uint64) error - - TruncateSettings(ctx context.Context) error - } -) diff --git a/store/tests/store_interface_users.gen.go b/store/tests/store_interface_users.gen.go deleted file mode 100644 index 12d871810..000000000 --- a/store/tests/store_interface_users.gen.go +++ /dev/null @@ -1,31 +0,0 @@ -package tests - -// This file is auto-generated. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -// Definitions file that controls how this file is generated: -// - store/users.yaml - -import ( - "context" - "github.com/cortezaproject/corteza-server/system/types" -) - -type ( - usersStore interface { - SearchUsers(ctx context.Context, f types.UserFilter) (types.UserSet, types.UserFilter, error) - LookupUserByID(ctx context.Context, id uint64) (*types.User, error) - LookupUserByEmail(ctx context.Context, email string) (*types.User, error) - LookupUserByHandle(ctx context.Context, handle string) (*types.User, error) - LookupUserByUsername(ctx context.Context, username string) (*types.User, error) - CreateUser(ctx context.Context, rr ...*types.User) error - UpdateUser(ctx context.Context, rr ...*types.User) error - PartialUpdateUser(ctx context.Context, onlyColumns []string, rr ...*types.User) error - RemoveUser(ctx context.Context, rr ...*types.User) error - RemoveUserByID(ctx context.Context, ID uint64) error - - TruncateUsers(ctx context.Context) error - } -) diff --git a/store/tests/users_test.go b/store/tests/users_test.go index 5a265d313..f070d6e8f 100644 --- a/store/tests/users_test.go +++ b/store/tests/users_test.go @@ -3,6 +3,7 @@ package tests import ( "context" "fmt" + "github.com/cortezaproject/corteza-server/pkg/filter" "github.com/cortezaproject/corteza-server/pkg/id" "github.com/cortezaproject/corteza-server/pkg/rand" "github.com/cortezaproject/corteza-server/store" @@ -14,19 +15,10 @@ import ( "time" ) -type ( - usersStoreAdt interface { - usersStore - CountUsers(ctx context.Context, f types.UserFilter) (uint, error) - } -) - -func testUsers(t *testing.T, tmp interface{}) { +func testUsers(t *testing.T, s store.Users) { var ( ctx = context.Background() - s = tmp.(usersStoreAdt) - makeNew = func(nn ...string) *types.User { // minimum data set for new user name := strings.Join(nn, "") @@ -71,7 +63,8 @@ func testUsers(t *testing.T, tmp interface{}) { t.Run("create", func(t *testing.T) { req := require.New(t) - req.NoError(s.CreateUser(ctx, makeNew())) + req.NoError(s.TruncateUsers(ctx)) + req.NoError(store.CreateUser(ctx, s, makeNew())) }) t.Run("create with duplicate email", func(t *testing.T) { @@ -93,7 +86,7 @@ func testUsers(t *testing.T, tmp interface{}) { t.Run("lookup by ID", func(t *testing.T) { req, user := truncAndCreate(t) - fetched, err := s.LookupUserByID(ctx, user.ID) + fetched, err := store.LookupUserByID(ctx, s, user.ID) req.NoError(err) req.Equal(user.Email, fetched.Email) req.Equal(user.Username, fetched.Username) @@ -113,7 +106,7 @@ func testUsers(t *testing.T, tmp interface{}) { t.Run("lookup by email", func(t *testing.T) { req, user := truncAndCreate(t) - fetched, err := s.LookupUserByEmail(ctx, user.Email) + fetched, err := store.LookupUserByEmail(ctx, s, user.Email) req.NoError(err) req.Equal(user.Email, fetched.Email) }) @@ -121,7 +114,7 @@ func testUsers(t *testing.T, tmp interface{}) { t.Run("lookup by handle", func(t *testing.T) { req, user := truncAndCreate(t) - fetched, err := s.LookupUserByHandle(ctx, user.Handle) + fetched, err := store.LookupUserByHandle(ctx, s, user.Handle) req.NoError(err) req.Equal(user.ID, fetched.ID) }) @@ -129,7 +122,7 @@ func testUsers(t *testing.T, tmp interface{}) { t.Run("lookup by nonexisting handle", func(t *testing.T) { req, _ := truncAndCreate(t) - fetched, err := s.LookupUserByHandle(ctx, "no such handle") + fetched, err := store.LookupUserByHandle(ctx, s, "no such handle") req.EqualError(err, "not found") req.Nil(fetched) }) @@ -137,7 +130,7 @@ func testUsers(t *testing.T, tmp interface{}) { t.Run("lookup by username", func(t *testing.T) { req, user := truncAndCreate(t) - fetched, err := s.LookupUserByUsername(ctx, user.Username) + fetched, err := store.LookupUserByUsername(ctx, s, user.Username) req.NoError(err) req.Equal(user.ID, fetched.ID) }) @@ -146,7 +139,7 @@ func testUsers(t *testing.T, tmp interface{}) { t.Run("by ID", func(t *testing.T) { req, prefill := truncAndFill(t, 5) - set, f, err := s.SearchUsers(ctx, types.UserFilter{UserID: []uint64{prefill[0].ID}}) + set, f, err := store.SearchUsers(ctx, s, types.UserFilter{UserID: []uint64{prefill[0].ID}}) req.NoError(err) req.Equal([]uint64{prefill[0].ID}, f.UserID) req.Len(set, 1) @@ -156,35 +149,35 @@ func testUsers(t *testing.T, tmp interface{}) { t.Run("by email", func(t *testing.T) { req, prefill := truncAndFill(t, 5) - set, _, err := s.SearchUsers(ctx, types.UserFilter{Email: prefill[0].Email}) + set, _, err := store.SearchUsers(ctx, s, types.UserFilter{Email: prefill[0].Email}) req.NoError(err) req.Len(set, 1) }) t.Run("by username", func(t *testing.T) { req, prefill := truncAndFill(t, 5) - set, _, err := s.SearchUsers(ctx, types.UserFilter{Username: prefill[0].Username}) + set, _, err := store.SearchUsers(ctx, s, types.UserFilter{Username: prefill[0].Username}) req.NoError(err) req.Len(set, 1) }) t.Run("by query", func(t *testing.T) { req, prefill := truncAndFill(t, 5) - set, _, err := s.SearchUsers(ctx, types.UserFilter{Query: prefill[0].Handle}) + set, _, err := store.SearchUsers(ctx, s, types.UserFilter{Query: prefill[0].Handle}) req.NoError(err) req.Len(set, 1) }) t.Run("by username", func(t *testing.T) { req, _ := truncAndFill(t, 5) - set, _, err := s.SearchUsers(ctx, types.UserFilter{Username: "no such username"}) + set, _, err := store.SearchUsers(ctx, s, types.UserFilter{Username: "no such username"}) req.NoError(err) req.Len(set, 0) }) t.Run("with check", func(t *testing.T) { req, prefill := truncAndFill(t, 5) - set, _, err := s.SearchUsers(ctx, types.UserFilter{ + set, _, err := store.SearchUsers(ctx, s, types.UserFilter{ Check: func(user *types.User) (bool, error) { // simple check that matches with the first user from prefill return user.ID == prefill[0].ID, nil @@ -214,11 +207,11 @@ func testUsers(t *testing.T, tmp interface{}) { req.NoError(s.CreateUser(ctx, set...)) f := types.UserFilter{} - f.Sort = store.SortExprSet{&store.SortExpr{Column: "email"}} + f.Sort = filter.SortExprSet{&filter.SortExpr{Column: "email"}} // Fetch first page f.Limit = 3 - set, f, err := s.SearchUsers(ctx, f) + set, f, err := store.SearchUsers(ctx, s, f) req.NoError(err) req.Len(set, 3) req.NotNil(f.NextPage) @@ -229,7 +222,7 @@ func testUsers(t *testing.T, tmp interface{}) { // 2nd page f.Limit = 6 f.PageCursor = f.NextPage - set, f, err = s.SearchUsers(ctx, f) + set, f, err = store.SearchUsers(ctx, s, f) req.NoError(err) req.Len(set, 6) req.NotNil(f.NextPage) @@ -240,7 +233,7 @@ func testUsers(t *testing.T, tmp interface{}) { // 3rd, last page (1 item left) f.Limit = 2 f.PageCursor = f.NextPage - set, f, err = s.SearchUsers(ctx, f) + set, f, err = store.SearchUsers(ctx, s, f) req.NoError(err) req.Len(set, 1) req.NotNil(f.NextPage) @@ -249,14 +242,14 @@ func testUsers(t *testing.T, tmp interface{}) { // try and go pass the last page f.PageCursor = f.NextPage - set, _, err = s.SearchUsers(ctx, f) + set, _, err = store.SearchUsers(ctx, s, f) req.NoError(err) req.Len(set, 0) // now, in reverse, last 3 items f.Limit = 3 f.PageCursor = f.PrevPage - set, f, err = s.SearchUsers(ctx, f) + set, f, err = store.SearchUsers(ctx, s, f) req.NoError(err) req.Len(set, 3) req.NotNil(f.NextPage) @@ -267,7 +260,7 @@ func testUsers(t *testing.T, tmp interface{}) { // still in reverse, next 6 items f.Limit = 5 f.PageCursor = f.PrevPage - set, f, err = s.SearchUsers(ctx, f) + set, f, err = store.SearchUsers(ctx, s, f) req.NoError(err) req.Len(set, 5) req.NotNil(f.NextPage) @@ -278,7 +271,7 @@ func testUsers(t *testing.T, tmp interface{}) { // still in reverse, last 5 items (actually, we'll only get 1) f.Limit = 5 f.PageCursor = f.PrevPage - set, f, err = s.SearchUsers(ctx, f) + set, f, err = store.SearchUsers(ctx, s, f) req.NoError(err) req.Len(set, 1) req.Nil(f.PrevPage) @@ -300,11 +293,11 @@ func testUsers(t *testing.T, tmp interface{}) { req.NoError(s.CreateUser(ctx, set...)) f := types.UserFilter{} - f.Sort = store.SortExprSet{&store.SortExpr{Column: "email", Descending: true}, &store.SortExpr{Column: "handle", Descending: true}} + f.Sort = filter.SortExprSet{&filter.SortExpr{Column: "email", Descending: true}, &filter.SortExpr{Column: "handle", Descending: true}} // Fetch first page f.Limit = 3 - set, f, err := s.SearchUsers(ctx, f) + set, f, err := store.SearchUsers(ctx, s, f) req.NoError(err) req.Len(set, 3) req.NotNil(f.NextPage) @@ -333,18 +326,18 @@ func testUsers(t *testing.T, tmp interface{}) { user = &types.User{ID: id.Next(), CreatedAt: time.Now(), Email: fmt.Sprintf("user-crud+%s@crust.test", time.Now().String())} ) - c1, err = s.CountUsers(ctx, f) + c1, err = store.CountUsers(ctx, s, f) req.NoError(err) req.NoError(s.CreateUser(ctx, user)) - c2, err = s.CountUsers(ctx, f) + c2, err = store.CountUsers(ctx, s, f) req.NoError(err) req.Equal(c1+1, c2) - req.NoError(s.RemoveUserByID(ctx, user.ID)) + req.NoError(s.DeleteUserByID(ctx, user.ID)) - c2, err = s.CountUsers(ctx, f) + c2, err = store.CountUsers(ctx, s, f) req.NoError(err) req.Equal(c1, c2) }) diff --git a/store/tx.go b/store/tx.go new file mode 100644 index 000000000..1aa545f7b --- /dev/null +++ b/store/tx.go @@ -0,0 +1,7 @@ +package store + +import "context" + +func Tx(ctx context.Context, s Storable, fn func(context.Context, Storable) error) error { + return s.Tx(ctx, fn) +} diff --git a/store/users.gen.go b/store/users.gen.go new file mode 100644 index 000000000..ab3119d28 --- /dev/null +++ b/store/users.gen.go @@ -0,0 +1,123 @@ +package store + +// This file is auto-generated. +// +// Template: pkg/codegen/assets/store_base.gen.go.tpl +// Definitions: store/users.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/system/types" +) + +type ( + Users interface { + SearchUsers(ctx context.Context, f types.UserFilter) (types.UserSet, types.UserFilter, error) + LookupUserByID(ctx context.Context, id uint64) (*types.User, error) + LookupUserByEmail(ctx context.Context, email string) (*types.User, error) + LookupUserByHandle(ctx context.Context, handle string) (*types.User, error) + LookupUserByUsername(ctx context.Context, username string) (*types.User, error) + + CreateUser(ctx context.Context, rr ...*types.User) error + + UpdateUser(ctx context.Context, rr ...*types.User) error + PartialUserUpdate(ctx context.Context, onlyColumns []string, rr ...*types.User) error + + UpsertUser(ctx context.Context, rr ...*types.User) error + + DeleteUser(ctx context.Context, rr ...*types.User) error + DeleteUserByID(ctx context.Context, ID uint64) error + + TruncateUsers(ctx context.Context) error + + // Additional custom functions + + // CountUsers (custom function) + CountUsers(ctx context.Context, _f types.UserFilter) (uint, error) + + // UserMetrics (custom function) + UserMetrics(ctx context.Context) (*types.UserMetrics, error) + } +) + +var _ *types.User +var _ context.Context + +// SearchUsers returns all matching Users from store +func SearchUsers(ctx context.Context, s Users, f types.UserFilter) (types.UserSet, types.UserFilter, error) { + return s.SearchUsers(ctx, f) +} + +// LookupUserByID searches for user by ID +// +// It returns user even if deleted or suspended +func LookupUserByID(ctx context.Context, s Users, id uint64) (*types.User, error) { + return s.LookupUserByID(ctx, id) +} + +// LookupUserByEmail searches for user by their email +// +// It returns only valid users (not deleted, not suspended) +func LookupUserByEmail(ctx context.Context, s Users, email string) (*types.User, error) { + return s.LookupUserByEmail(ctx, email) +} + +// LookupUserByHandle searches for user by their email +// +// It returns only valid users (not deleted, not suspended) +func LookupUserByHandle(ctx context.Context, s Users, handle string) (*types.User, error) { + return s.LookupUserByHandle(ctx, handle) +} + +// LookupUserByUsername searches for user by their username +// +// It returns only valid users (not deleted, not suspended) +func LookupUserByUsername(ctx context.Context, s Users, username string) (*types.User, error) { + return s.LookupUserByUsername(ctx, username) +} + +// CreateUser creates one or more Users in store +func CreateUser(ctx context.Context, s Users, rr ...*types.User) error { + return s.CreateUser(ctx, rr...) +} + +// UpdateUser updates one or more (existing) Users in store +func UpdateUser(ctx context.Context, s Users, rr ...*types.User) error { + return s.UpdateUser(ctx, rr...) +} + +// PartialUserUpdate updates one or more existing Users in store +func PartialUserUpdate(ctx context.Context, s Users, onlyColumns []string, rr ...*types.User) error { + return s.PartialUserUpdate(ctx, onlyColumns, rr...) +} + +// UpsertUser creates new or updates existing one or more Users in store +func UpsertUser(ctx context.Context, s Users, rr ...*types.User) error { + return s.UpsertUser(ctx, rr...) +} + +// DeleteUser Deletes one or more Users from store +func DeleteUser(ctx context.Context, s Users, rr ...*types.User) error { + return s.DeleteUser(ctx, rr...) +} + +// DeleteUserByID Deletes User from store +func DeleteUserByID(ctx context.Context, s Users, ID uint64) error { + return s.DeleteUserByID(ctx, ID) +} + +// TruncateUsers Deletes all Users from store +func TruncateUsers(ctx context.Context, s Users) error { + return s.TruncateUsers(ctx) +} + +func CountUsers(ctx context.Context, s Users, _f types.UserFilter) (uint, error) { + return s.CountUsers(ctx, _f) +} + +func UserMetrics(ctx context.Context, s Users) (*types.UserMetrics, error) { + return s.UserMetrics(ctx) +} diff --git a/store/users.yaml b/store/users.yaml index 5d6ac0078..f5159bde5 100644 --- a/store/users.yaml +++ b/store/users.yaml @@ -1,16 +1,13 @@ import: - github.com/cortezaproject/corteza-server/system/types -interface: - - system/service - fields: - { field: ID } - - { field: Email, sortable: true, unique: true, fts: true } + - { field: Email, sortable: true, unique: true, fts: true, lookupFilterPreprocessor: lower } - { field: EmailConfirmed } - - { field: Username, sortable: true, unique: true, fts: true } + - { field: Username, sortable: true, unique: true, fts: true, lookupFilterPreprocessor: lower } - { field: Name, sortable: true, fts: true } - - { field: Handle, sortable: true, unique: true, fts: true } + - { field: Handle, sortable: true, unique: true, fts: true, lookupFilterPreprocessor: lower } - { field: Meta, type: "*types.UserMeta" } - { field: Kind } - { field: CreatedAt, sortable: true } @@ -26,23 +23,33 @@ lookups: It returns user even if deleted or suspended - fields: [ Email ] filter: { DeletedAt: nil, SuspendedAt: nil } + uniqueConstraintCheck: true description: |- searches for user by their email It returns only valid users (not deleted, not suspended) - fields: [ Handle ] filter: { DeletedAt: nil, SuspendedAt: nil } + uniqueConstraintCheck: true description: |- searches for user by their email It returns only valid users (not deleted, not suspended) - fields: [ Username ] filter: { DeletedAt: nil, SuspendedAt: nil } + uniqueConstraintCheck: true description: |- searches for user by their username It returns only valid users (not deleted, not suspended) +functions: + - name: CountUsers + arguments: [ { name: f, type: types.UserFilter } ] + return: [ "uint", "error" ] + - name: UserMetrics + return: [ "*types.UserMetrics", "error" ] + rdbms: alias: usr table: users diff --git a/system/commands/users.go b/system/commands/users.go index 8c5a7ca4b..39d1488db 100644 --- a/system/commands/users.go +++ b/system/commands/users.go @@ -4,7 +4,7 @@ import ( "fmt" "github.com/cortezaproject/corteza-server/pkg/auth" "github.com/cortezaproject/corteza-server/pkg/cli" - "github.com/cortezaproject/corteza-server/store" + "github.com/cortezaproject/corteza-server/pkg/filter" "github.com/cortezaproject/corteza-server/system/service" "github.com/cortezaproject/corteza-server/system/types" "github.com/spf13/cobra" @@ -45,7 +45,7 @@ func Users(app serviceInitializer) *cobra.Command { cli.HandleError(err) uf := types.UserFilter{Query: queryFlag} - uf.Sort = store.SortExprSet{&store.SortExpr{Column: "updated_at"}} + uf.Sort = filter.SortExprSet{&filter.SortExpr{Column: "updated_at"}} uf.Limit = uint(limit) users, _, err := service.DefaultNgStore.SearchUsers(ctx, uf) diff --git a/system/rest/actionlog.go b/system/rest/actionlog.go index 7d3af5081..d99bda8a5 100644 --- a/system/rest/actionlog.go +++ b/system/rest/actionlog.go @@ -3,9 +3,9 @@ package rest import ( "context" "github.com/cortezaproject/corteza-server/pkg/actionlog" + "github.com/cortezaproject/corteza-server/pkg/filter" "github.com/cortezaproject/corteza-server/pkg/payload" "github.com/cortezaproject/corteza-server/pkg/rh" - "github.com/cortezaproject/corteza-server/store" "github.com/cortezaproject/corteza-server/system/rest/request" "github.com/cortezaproject/corteza-server/system/service" "github.com/cortezaproject/corteza-server/system/types" @@ -49,7 +49,7 @@ func (ctrl *Actionlog) List(ctx context.Context, r *request.ActionlogList) (inte } ) - if f.Paging, err = store.NewPaging(r.Limit, r.PageCursor); err != nil { + if f.Paging, err = filter.NewPaging(r.Limit, r.PageCursor); err != nil { return nil, err } diff --git a/system/rest/application.go b/system/rest/application.go index 4acf42950..3b34f2645 100644 --- a/system/rest/application.go +++ b/system/rest/application.go @@ -2,7 +2,7 @@ package rest import ( "context" - "github.com/cortezaproject/corteza-server/store" + "github.com/cortezaproject/corteza-server/pkg/filter" "github.com/titpetric/factory/resputil" "github.com/cortezaproject/corteza-server/pkg/corredor" @@ -71,11 +71,11 @@ func (ctrl *Application) List(ctx context.Context, r *request.ApplicationList) ( } ) - if f.Paging, err = store.NewPaging(r.Limit, r.PageCursor); err != nil { + if f.Paging, err = filter.NewPaging(r.Limit, r.PageCursor); err != nil { return nil, err } - if f.Sorting, err = store.NewSorting(r.Sort); err != nil { + if f.Sorting, err = filter.NewSorting(r.Sort); err != nil { return nil, err } diff --git a/system/rest/reminder.go b/system/rest/reminder.go index c6bc28829..f3d30bebc 100644 --- a/system/rest/reminder.go +++ b/system/rest/reminder.go @@ -2,7 +2,7 @@ package rest import ( "context" - "github.com/cortezaproject/corteza-server/store" + "github.com/cortezaproject/corteza-server/pkg/filter" "time" "github.com/titpetric/factory/resputil" @@ -49,11 +49,11 @@ func (ctrl *Reminder) List(ctx context.Context, r *request.ReminderList) (interf } ) - if f.Paging, err = store.NewPaging(r.Limit, r.PageCursor); err != nil { + if f.Paging, err = filter.NewPaging(r.Limit, r.PageCursor); err != nil { return nil, err } - if f.Sorting, err = store.NewSorting(r.Sort); err != nil { + if f.Sorting, err = filter.NewSorting(r.Sort); err != nil { return nil, err } diff --git a/system/rest/role.go b/system/rest/role.go index 3d3704248..f15e9d833 100644 --- a/system/rest/role.go +++ b/system/rest/role.go @@ -2,8 +2,7 @@ package rest import ( "context" - "github.com/cortezaproject/corteza-server/store" - + "github.com/cortezaproject/corteza-server/pkg/filter" "github.com/pkg/errors" "github.com/titpetric/factory/resputil" @@ -68,11 +67,11 @@ func (ctrl Role) List(ctx context.Context, r *request.RoleList) (interface{}, er } ) - if f.Paging, err = store.NewPaging(r.Limit, r.PageCursor); err != nil { + if f.Paging, err = filter.NewPaging(r.Limit, r.PageCursor); err != nil { return nil, err } - if f.Sorting, err = store.NewSorting(r.Sort); err != nil { + if f.Sorting, err = filter.NewSorting(r.Sort); err != nil { return nil, err } diff --git a/system/rest/user.go b/system/rest/user.go index 2d2d4f918..7e75c7991 100644 --- a/system/rest/user.go +++ b/system/rest/user.go @@ -2,7 +2,7 @@ package rest import ( "context" - "github.com/cortezaproject/corteza-server/store" + "github.com/cortezaproject/corteza-server/pkg/filter" "github.com/pkg/errors" "github.com/titpetric/factory/resputil" @@ -52,11 +52,11 @@ func (ctrl User) List(ctx context.Context, r *request.UserList) (interface{}, er } ) - if f.Paging, err = store.NewPaging(r.Limit, r.PageCursor); err != nil { + if f.Paging, err = filter.NewPaging(r.Limit, r.PageCursor); err != nil { return nil, err } - if f.Sorting, err = store.NewSorting(r.Sort); err != nil { + if f.Sorting, err = filter.NewSorting(r.Sort); err != nil { return nil, err } diff --git a/system/service/application.go b/system/service/application.go index 0ec132ad9..d55919042 100644 --- a/system/service/application.go +++ b/system/service/application.go @@ -5,6 +5,7 @@ import ( "github.com/cortezaproject/corteza-server/pkg/actionlog" "github.com/cortezaproject/corteza-server/pkg/permissions" "github.com/cortezaproject/corteza-server/pkg/rh" + "github.com/cortezaproject/corteza-server/store" "github.com/cortezaproject/corteza-server/system/service/event" "github.com/cortezaproject/corteza-server/system/types" ) @@ -14,7 +15,7 @@ type ( ac applicationAccessController eventbus eventDispatcher actionlog actionlog.Recorder - store applicationsStore + store store.Applications } applicationAccessController interface { @@ -29,7 +30,7 @@ type ( ) // Application is a default application service initializer -func Application(s applicationsStore, ac applicationAccessController, al actionlog.Recorder, eb eventDispatcher) *application { +func Application(s store.Applications, ac applicationAccessController, al actionlog.Recorder, eb eventDispatcher) *application { return &application{store: s, ac: ac, actionlog: al, eventbus: eb} } @@ -189,7 +190,7 @@ func (svc *application) Delete(ctx context.Context, ID uint64) (err error) { } app.DeletedAt = nowPtr() - if err = svc.store.PartialUpdateApplication(ctx, []string{"UpdatedAt"}, app); err != nil { + if err = svc.store.PartialApplicationUpdate(ctx, []string{"UpdatedAt"}, app); err != nil { return } @@ -227,7 +228,7 @@ func (svc *application) Undelete(ctx context.Context, ID uint64) (err error) { // } app.DeletedAt = nil - if err = svc.store.PartialUpdateApplication(ctx, []string{"UpdatedAt"}, app); err != nil { + if err = svc.store.PartialApplicationUpdate(ctx, []string{"UpdatedAt"}, app); err != nil { return } diff --git a/system/service/attachment.go b/system/service/attachment.go index 01f49c5fd..bf6ee5dfd 100644 --- a/system/service/attachment.go +++ b/system/service/attachment.go @@ -7,6 +7,7 @@ import ( intAuth "github.com/cortezaproject/corteza-server/pkg/auth" "github.com/cortezaproject/corteza-server/pkg/id" "github.com/cortezaproject/corteza-server/pkg/store" + ngStore "github.com/cortezaproject/corteza-server/store" "github.com/cortezaproject/corteza-server/system/types" "github.com/disintegration/imaging" "github.com/edwvee/exiffix" @@ -30,7 +31,7 @@ type ( actionlog actionlog.Recorder files store.Store ac attachmentAccessController - store attachmentsStore + store ngStore.Storable } attachmentAccessController interface { diff --git a/system/service/auth.go b/system/service/auth.go index 689331349..daeb94b86 100644 --- a/system/service/auth.go +++ b/system/service/auth.go @@ -28,7 +28,7 @@ type ( eventbus eventDispatcher subscription authSubscriptionChecker - store authServiceStore + store store.Storable settings *types.AppSettings notifications AuthNotificationService @@ -42,15 +42,6 @@ type ( authSubscriptionChecker interface { CanRegister(uint) error } - - authServiceStore interface { - usersStore - rolesStore - roleMembersStore - credentialsStore - - CountUsers(context.Context, types.UserFilter) (uint, error) - } ) const ( @@ -143,7 +134,7 @@ func (svc auth) External(ctx context.Context, profile goth.User) (u *types.User, if errors.Is(err, store.ErrNotFound) { // Orphaned credentials (no owner) // try to auto-fix this by removing credentials and recreating user - if err = svc.store.RemoveCredentialsByID(ctx, c.ID); err != nil { + if err = svc.store.DeleteCredentialsByID(ctx, c.ID); err != nil { return err } else { goto findByEmail @@ -678,7 +669,7 @@ func (svc auth) SetPasswordCredentials(ctx context.Context, userID uint64, passw }) // Do a partial update and soft-delete all - if err = svc.store.PartialUpdateCredentials(ctx, []string{"deleted_at"}, cc...); err != nil { + if err = svc.store.PartialCredentialsUpdate(ctx, []string{"deleted_at"}, cc...); err != nil { return } @@ -933,7 +924,7 @@ func (svc auth) loadUserFromToken(ctx context.Context, token, kind string) (u *t return } - if err = svc.store.RemoveCredentialsByID(ctx, c.ID); err != nil { + if err = svc.store.DeleteCredentialsByID(ctx, c.ID); err != nil { return } diff --git a/system/service/reminder.go b/system/service/reminder.go index ebf84894b..ec0bd580c 100644 --- a/system/service/reminder.go +++ b/system/service/reminder.go @@ -5,6 +5,7 @@ import ( "github.com/cortezaproject/corteza-server/pkg/actionlog" intAuth "github.com/cortezaproject/corteza-server/pkg/auth" "github.com/cortezaproject/corteza-server/pkg/id" + "github.com/cortezaproject/corteza-server/store" "github.com/cortezaproject/corteza-server/system/types" "time" ) @@ -14,7 +15,7 @@ type ( ac reminderAccessController actionlog actionlog.Recorder - store remindersStore + store store.Reminders } reminderAccessController interface { diff --git a/system/service/role.go b/system/service/role.go index 1cb12913d..db711be1f 100644 --- a/system/service/role.go +++ b/system/service/role.go @@ -7,6 +7,7 @@ import ( "github.com/cortezaproject/corteza-server/pkg/handle" "github.com/cortezaproject/corteza-server/pkg/id" "github.com/cortezaproject/corteza-server/pkg/permissions" + "github.com/cortezaproject/corteza-server/store" "github.com/cortezaproject/corteza-server/system/service/event" "github.com/cortezaproject/corteza-server/system/types" "strconv" @@ -23,10 +24,7 @@ type ( user UserService - store interface { - rolesStore - roleMembersStore - } + store store.Storable } roleAccessController interface { @@ -542,7 +540,7 @@ func (svc role) MemberRemove(roleID, memberID uint64) (err error) { return RoleErrNotAllowedToManageMembers() } - if err = svc.store.RemoveRoleMember(svc.ctx, &types.RoleMember{RoleID: r.ID, UserID: m.ID}); err != nil { + if err = svc.store.DeleteRoleMember(svc.ctx, &types.RoleMember{RoleID: r.ID, UserID: m.ID}); err != nil { return } diff --git a/system/service/service.go b/system/service/service.go index 095da029a..39de13f81 100644 --- a/system/service/service.go +++ b/system/service/service.go @@ -3,20 +3,20 @@ package service import ( "context" "errors" - "github.com/cortezaproject/corteza-server/pkg/healthcheck" - "github.com/cortezaproject/corteza-server/pkg/id" - "github.com/cortezaproject/corteza-server/system/types" - "go.uber.org/zap" - "time" - "github.com/cortezaproject/corteza-server/pkg/actionlog" intAuth "github.com/cortezaproject/corteza-server/pkg/auth" "github.com/cortezaproject/corteza-server/pkg/eventbus" + "github.com/cortezaproject/corteza-server/pkg/healthcheck" + "github.com/cortezaproject/corteza-server/pkg/id" "github.com/cortezaproject/corteza-server/pkg/options" "github.com/cortezaproject/corteza-server/pkg/permissions" "github.com/cortezaproject/corteza-server/pkg/store" "github.com/cortezaproject/corteza-server/pkg/store/minio" "github.com/cortezaproject/corteza-server/pkg/store/plain" + ngStore "github.com/cortezaproject/corteza-server/store" + "github.com/cortezaproject/corteza-server/system/types" + "go.uber.org/zap" + "time" ) type ( @@ -40,16 +40,6 @@ type ( WaitFor(ctx context.Context, ev eventbus.Event) (err error) Dispatch(ctx context.Context, ev eventbus.Event) } - - // storeInterface wraps generated interfaces to enable extensions - storeInterface interface { - // Include generated interfaces - storeGeneratedInterfaces - - CountUsers(context.Context, types.UserFilter) (uint, error) - - statisticsStore - } ) var ( @@ -58,7 +48,7 @@ var ( // DefaultNgStore is an interface to storage backend(s) // ng (next-gen) is a temporary prefix // so that we can differentiate between it and the file-only store - DefaultNgStore storeInterface + DefaultNgStore ngStore.Storable DefaultLogger *zap.Logger @@ -117,14 +107,14 @@ var ( } ) -func Initialize(ctx context.Context, log *zap.Logger, s interface{}, c Config) (err error) { +func Initialize(ctx context.Context, log *zap.Logger, s ngStore.Storable, c Config) (err error) { var ( hcd = healthcheck.Defaults() ) // we're doing conversion to avoid having // store interface exposed or generated inside app package - DefaultNgStore = s.(storeInterface) + DefaultNgStore = s DefaultLogger = log.Named("service") diff --git a/system/service/settings.go b/system/service/settings.go index 0523e17dc..eb190c67e 100644 --- a/system/service/settings.go +++ b/system/service/settings.go @@ -14,7 +14,7 @@ import ( type ( settings struct { - store settingsStore + store store.Settings accessControl accessController logger *zap.Logger @@ -23,15 +23,6 @@ type ( current interface{} } - //Service interface { - // FindByPrefix(ctx context.Context, pp ...string) (vv types.SettingValueSet, err error) - // BulkSet(ctx context.Context, vv types.SettingValue) (err error) - // Set(ctx context.Context, v *types.SettingValue) (err error) - // Get(ctx context.Context, name string, ownedBy uint64) (out *types.SettingValue, err error) - // Delete(ctx context.Context, name string, ownedBy uint64) error - // UpdateCurrent(ctx context.Context) error - //} - accessController interface { CanReadSettings(ctx context.Context) bool CanManageSettings(ctx context.Context) bool @@ -43,7 +34,7 @@ var ( ErrNoManagePermission = fmt.Errorf("not allowed to manage settings") ) -func Settings(s settingsStore, log *zap.Logger, ac accessController, current interface{}) *settings { +func Settings(s store.Settings, log *zap.Logger, ac accessController, current interface{}) *settings { svc := &settings{ store: s, accessControl: ac, @@ -192,7 +183,7 @@ func (svc settings) Delete(ctx context.Context, name string, ownedBy uint64) (er return } - err = svc.store.RemoveSetting(ctx, current) + err = svc.store.DeleteSetting(ctx, current) if err != nil { return } diff --git a/system/service/statistics.go b/system/service/statistics.go index accc7ff3f..5c830696d 100644 --- a/system/service/statistics.go +++ b/system/service/statistics.go @@ -2,6 +2,7 @@ package service import ( "context" + "github.com/cortezaproject/corteza-server/store" "github.com/cortezaproject/corteza-server/pkg/actionlog" "github.com/cortezaproject/corteza-server/system/types" @@ -11,7 +12,7 @@ type ( statistics struct { actionlog actionlog.Recorder ac statisticsAccessController - store statisticsStore + store store.Storable } statisticsAccessController interface { @@ -23,12 +24,6 @@ type ( Roles *types.RoleMetrics `json:"roles"` Applications *types.ApplicationMetrics `json:"applications"` } - - statisticsStore interface { - UserMetrics(ctx context.Context) (rval *types.UserMetrics, err error) - RoleMetrics(ctx context.Context) (rval *types.RoleMetrics, err error) - ApplicationMetrics(ctx context.Context) (rval *types.ApplicationMetrics, err error) - } ) func Statistics() *statistics { diff --git a/system/service/store_interface.gen.go b/system/service/store_interface.gen.go deleted file mode 100644 index a41ea2227..000000000 --- a/system/service/store_interface.gen.go +++ /dev/null @@ -1,36 +0,0 @@ -package service - -// This file is auto-generated. -// -// Template: pkg/store_interfaces_joined.gen.go.tpl -// Definitions: -// - store/actionlog.yaml -// - store/applications.yaml -// - store/attachments.yaml -// - store/credentials.yaml -// - store/rbac_rules.yaml -// - store/reminders.yaml -// - store/role_members.yaml -// - store/roles.yaml -// - store/settings.yaml -// - store/users.yaml -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// - -type ( - // Interface combines interfaces of all supported store interfaces - storeGeneratedInterfaces interface { - actionlogsStore - applicationsStore - attachmentsStore - credentialsStore - rbacRulesStore - remindersStore - roleMembersStore - rolesStore - settingsStore - usersStore - } -) diff --git a/system/service/store_interface_actionlog.gen.go b/system/service/store_interface_actionlog.gen.go deleted file mode 100644 index 1b67c3e64..000000000 --- a/system/service/store_interface_actionlog.gen.go +++ /dev/null @@ -1,27 +0,0 @@ -package service - -// This file is auto-generated. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -// Definitions file that controls how this file is generated: -// - store/actionlog.yaml - -import ( - "context" - "github.com/cortezaproject/corteza-server/pkg/actionlog" -) - -type ( - actionlogsStore interface { - SearchActionlogs(ctx context.Context, f actionlog.Filter) (actionlog.ActionSet, actionlog.Filter, error) - CreateActionlog(ctx context.Context, rr ...*actionlog.Action) error - UpdateActionlog(ctx context.Context, rr ...*actionlog.Action) error - PartialUpdateActionlog(ctx context.Context, onlyColumns []string, rr ...*actionlog.Action) error - RemoveActionlog(ctx context.Context, rr ...*actionlog.Action) error - RemoveActionlogByID(ctx context.Context, ID uint64) error - - TruncateActionlogs(ctx context.Context) error - } -) diff --git a/system/service/store_interface_applications.gen.go b/system/service/store_interface_applications.gen.go deleted file mode 100644 index 01a004c9d..000000000 --- a/system/service/store_interface_applications.gen.go +++ /dev/null @@ -1,28 +0,0 @@ -package service - -// This file is auto-generated. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -// Definitions file that controls how this file is generated: -// - store/applications.yaml - -import ( - "context" - "github.com/cortezaproject/corteza-server/system/types" -) - -type ( - applicationsStore interface { - SearchApplications(ctx context.Context, f types.ApplicationFilter) (types.ApplicationSet, types.ApplicationFilter, error) - LookupApplicationByID(ctx context.Context, id uint64) (*types.Application, error) - CreateApplication(ctx context.Context, rr ...*types.Application) error - UpdateApplication(ctx context.Context, rr ...*types.Application) error - PartialUpdateApplication(ctx context.Context, onlyColumns []string, rr ...*types.Application) error - RemoveApplication(ctx context.Context, rr ...*types.Application) error - RemoveApplicationByID(ctx context.Context, ID uint64) error - - TruncateApplications(ctx context.Context) error - } -) diff --git a/system/service/store_interface_attachments.gen.go b/system/service/store_interface_attachments.gen.go deleted file mode 100644 index a3024df2d..000000000 --- a/system/service/store_interface_attachments.gen.go +++ /dev/null @@ -1,28 +0,0 @@ -package service - -// This file is auto-generated. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -// Definitions file that controls how this file is generated: -// - store/attachments.yaml - -import ( - "context" - "github.com/cortezaproject/corteza-server/system/types" -) - -type ( - attachmentsStore interface { - SearchAttachments(ctx context.Context, f types.AttachmentFilter) (types.AttachmentSet, types.AttachmentFilter, error) - LookupAttachmentByID(ctx context.Context, id uint64) (*types.Attachment, error) - CreateAttachment(ctx context.Context, rr ...*types.Attachment) error - UpdateAttachment(ctx context.Context, rr ...*types.Attachment) error - PartialUpdateAttachment(ctx context.Context, onlyColumns []string, rr ...*types.Attachment) error - RemoveAttachment(ctx context.Context, rr ...*types.Attachment) error - RemoveAttachmentByID(ctx context.Context, ID uint64) error - - TruncateAttachments(ctx context.Context) error - } -) diff --git a/system/service/store_interface_credentials.gen.go b/system/service/store_interface_credentials.gen.go deleted file mode 100644 index ec327c966..000000000 --- a/system/service/store_interface_credentials.gen.go +++ /dev/null @@ -1,28 +0,0 @@ -package service - -// This file is auto-generated. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -// Definitions file that controls how this file is generated: -// - store/credentials.yaml - -import ( - "context" - "github.com/cortezaproject/corteza-server/system/types" -) - -type ( - credentialsStore interface { - SearchCredentials(ctx context.Context, f types.CredentialsFilter) (types.CredentialsSet, types.CredentialsFilter, error) - LookupCredentialsByID(ctx context.Context, id uint64) (*types.Credentials, error) - CreateCredentials(ctx context.Context, rr ...*types.Credentials) error - UpdateCredentials(ctx context.Context, rr ...*types.Credentials) error - PartialUpdateCredentials(ctx context.Context, onlyColumns []string, rr ...*types.Credentials) error - RemoveCredentials(ctx context.Context, rr ...*types.Credentials) error - RemoveCredentialsByID(ctx context.Context, ID uint64) error - - TruncateCredentials(ctx context.Context) error - } -) diff --git a/system/service/store_interface_rbac_rules.gen.go b/system/service/store_interface_rbac_rules.gen.go deleted file mode 100644 index c244d22ab..000000000 --- a/system/service/store_interface_rbac_rules.gen.go +++ /dev/null @@ -1,27 +0,0 @@ -package service - -// This file is auto-generated. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -// Definitions file that controls how this file is generated: -// - store/rbac_rules.yaml - -import ( - "context" - "github.com/cortezaproject/corteza-server/pkg/permissions" -) - -type ( - rbacRulesStore interface { - SearchRbacRules(ctx context.Context, f permissions.RuleFilter) (permissions.RuleSet, permissions.RuleFilter, error) - CreateRbacRule(ctx context.Context, rr ...*permissions.Rule) error - UpdateRbacRule(ctx context.Context, rr ...*permissions.Rule) error - PartialUpdateRbacRule(ctx context.Context, onlyColumns []string, rr ...*permissions.Rule) error - RemoveRbacRule(ctx context.Context, rr ...*permissions.Rule) error - RemoveRbacRuleByRoleIDResourceOperation(ctx context.Context, roleID uint64, resource string, operation string) error - - TruncateRbacRules(ctx context.Context) error - } -) diff --git a/system/service/store_interface_reminders.gen.go b/system/service/store_interface_reminders.gen.go deleted file mode 100644 index 354fb2291..000000000 --- a/system/service/store_interface_reminders.gen.go +++ /dev/null @@ -1,28 +0,0 @@ -package service - -// This file is auto-generated. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -// Definitions file that controls how this file is generated: -// - store/reminders.yaml - -import ( - "context" - "github.com/cortezaproject/corteza-server/system/types" -) - -type ( - remindersStore interface { - SearchReminders(ctx context.Context, f types.ReminderFilter) (types.ReminderSet, types.ReminderFilter, error) - LookupReminderByID(ctx context.Context, id uint64) (*types.Reminder, error) - CreateReminder(ctx context.Context, rr ...*types.Reminder) error - UpdateReminder(ctx context.Context, rr ...*types.Reminder) error - PartialUpdateReminder(ctx context.Context, onlyColumns []string, rr ...*types.Reminder) error - RemoveReminder(ctx context.Context, rr ...*types.Reminder) error - RemoveReminderByID(ctx context.Context, ID uint64) error - - TruncateReminders(ctx context.Context) error - } -) diff --git a/system/service/store_interface_role_members.gen.go b/system/service/store_interface_role_members.gen.go deleted file mode 100644 index 852e017ac..000000000 --- a/system/service/store_interface_role_members.gen.go +++ /dev/null @@ -1,27 +0,0 @@ -package service - -// This file is auto-generated. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -// Definitions file that controls how this file is generated: -// - store/role_members.yaml - -import ( - "context" - "github.com/cortezaproject/corteza-server/system/types" -) - -type ( - roleMembersStore interface { - SearchRoleMembers(ctx context.Context, f types.RoleMemberFilter) (types.RoleMemberSet, types.RoleMemberFilter, error) - CreateRoleMember(ctx context.Context, rr ...*types.RoleMember) error - UpdateRoleMember(ctx context.Context, rr ...*types.RoleMember) error - PartialUpdateRoleMember(ctx context.Context, onlyColumns []string, rr ...*types.RoleMember) error - RemoveRoleMember(ctx context.Context, rr ...*types.RoleMember) error - RemoveRoleMemberByUserIDRoleID(ctx context.Context, userID uint64, roleID uint64) error - - TruncateRoleMembers(ctx context.Context) error - } -) diff --git a/system/service/store_interface_roles.gen.go b/system/service/store_interface_roles.gen.go deleted file mode 100644 index a17184246..000000000 --- a/system/service/store_interface_roles.gen.go +++ /dev/null @@ -1,30 +0,0 @@ -package service - -// This file is auto-generated. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -// Definitions file that controls how this file is generated: -// - store/roles.yaml - -import ( - "context" - "github.com/cortezaproject/corteza-server/system/types" -) - -type ( - rolesStore interface { - SearchRoles(ctx context.Context, f types.RoleFilter) (types.RoleSet, types.RoleFilter, error) - LookupRoleByID(ctx context.Context, id uint64) (*types.Role, error) - LookupRoleByHandle(ctx context.Context, handle string) (*types.Role, error) - LookupRoleByName(ctx context.Context, name string) (*types.Role, error) - CreateRole(ctx context.Context, rr ...*types.Role) error - UpdateRole(ctx context.Context, rr ...*types.Role) error - PartialUpdateRole(ctx context.Context, onlyColumns []string, rr ...*types.Role) error - RemoveRole(ctx context.Context, rr ...*types.Role) error - RemoveRoleByID(ctx context.Context, ID uint64) error - - TruncateRoles(ctx context.Context) error - } -) diff --git a/system/service/store_interface_settings.gen.go b/system/service/store_interface_settings.gen.go deleted file mode 100644 index dc8497765..000000000 --- a/system/service/store_interface_settings.gen.go +++ /dev/null @@ -1,28 +0,0 @@ -package service - -// This file is auto-generated. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -// Definitions file that controls how this file is generated: -// - store/settings.yaml - -import ( - "context" - "github.com/cortezaproject/corteza-server/system/types" -) - -type ( - settingsStore interface { - SearchSettings(ctx context.Context, f types.SettingsFilter) (types.SettingValueSet, types.SettingsFilter, error) - LookupSettingByNameOwnedBy(ctx context.Context, name string, owned_by uint64) (*types.SettingValue, error) - CreateSetting(ctx context.Context, rr ...*types.SettingValue) error - UpdateSetting(ctx context.Context, rr ...*types.SettingValue) error - PartialUpdateSetting(ctx context.Context, onlyColumns []string, rr ...*types.SettingValue) error - RemoveSetting(ctx context.Context, rr ...*types.SettingValue) error - RemoveSettingByNameOwnedBy(ctx context.Context, name string, ownedBy uint64) error - - TruncateSettings(ctx context.Context) error - } -) diff --git a/system/service/store_interface_users.gen.go b/system/service/store_interface_users.gen.go deleted file mode 100644 index cd82e62db..000000000 --- a/system/service/store_interface_users.gen.go +++ /dev/null @@ -1,31 +0,0 @@ -package service - -// This file is auto-generated. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -// Definitions file that controls how this file is generated: -// - store/users.yaml - -import ( - "context" - "github.com/cortezaproject/corteza-server/system/types" -) - -type ( - usersStore interface { - SearchUsers(ctx context.Context, f types.UserFilter) (types.UserSet, types.UserFilter, error) - LookupUserByID(ctx context.Context, id uint64) (*types.User, error) - LookupUserByEmail(ctx context.Context, email string) (*types.User, error) - LookupUserByHandle(ctx context.Context, handle string) (*types.User, error) - LookupUserByUsername(ctx context.Context, username string) (*types.User, error) - CreateUser(ctx context.Context, rr ...*types.User) error - UpdateUser(ctx context.Context, rr ...*types.User) error - PartialUpdateUser(ctx context.Context, onlyColumns []string, rr ...*types.User) error - RemoveUser(ctx context.Context, rr ...*types.User) error - RemoveUserByID(ctx context.Context, ID uint64) error - - TruncateUsers(ctx context.Context) error - } -) diff --git a/system/service/user.go b/system/service/user.go index 2eda91b9e..4fc0eb7b8 100644 --- a/system/service/user.go +++ b/system/service/user.go @@ -39,12 +39,7 @@ type ( ac userAccessController eventbus eventDispatcher - store interface { - usersStore - rolesStore - - CountUsers(context.Context, types.UserFilter) (uint, error) - } + store store.Storable } userAuth interface { @@ -718,7 +713,7 @@ rangeLoop: return uu.Walk(s) } -func createHandle(ctx context.Context, s usersStore, u *types.User) { +func createHandle(ctx context.Context, s store.Users, u *types.User) { if u.Handle == "" { u.Handle, _ = handle.Cast( // Must not exist before diff --git a/system/types/applications.go b/system/types/applications.go index 9656cc4ca..a3b1badf4 100644 --- a/system/types/applications.go +++ b/system/types/applications.go @@ -3,7 +3,7 @@ package types import ( "database/sql/driver" "encoding/json" - "github.com/cortezaproject/corteza-server/store" + "github.com/cortezaproject/corteza-server/pkg/filter" "time" "github.com/pkg/errors" @@ -49,8 +49,8 @@ type ( Check func(*Application) (bool, error) `json:"-"` // Standard helpers for paging and sorting - store.Sorting - store.Paging + filter.Sorting + filter.Paging } ApplicationMetrics struct { diff --git a/system/types/attachment.go b/system/types/attachment.go index 1d22928c0..dc7b62f73 100644 --- a/system/types/attachment.go +++ b/system/types/attachment.go @@ -3,7 +3,7 @@ package types import ( "database/sql/driver" "encoding/json" - "github.com/cortezaproject/corteza-server/store" + "github.com/cortezaproject/corteza-server/pkg/filter" "time" "github.com/pkg/errors" @@ -37,7 +37,7 @@ type ( Check func(*Attachment) (bool, error) `json:"-"` // Standard helpers for paging and sorting - store.Paging + filter.Paging } attachmentImageMeta struct { diff --git a/system/types/reminder.go b/system/types/reminder.go index 0511dcbc0..7c0da63a9 100644 --- a/system/types/reminder.go +++ b/system/types/reminder.go @@ -1,7 +1,7 @@ package types import ( - "github.com/cortezaproject/corteza-server/store" + "github.com/cortezaproject/corteza-server/pkg/filter" "time" "github.com/jmoiron/sqlx/types" @@ -44,7 +44,7 @@ type ( Check func(*Reminder) (bool, error) `json:"-"` // Standard helpers for paging and sorting - store.Sorting - store.Paging + filter.Sorting + filter.Paging } ) diff --git a/system/types/role.go b/system/types/role.go index abd7fb1e5..37d2ba28b 100644 --- a/system/types/role.go +++ b/system/types/role.go @@ -1,7 +1,7 @@ package types import ( - "github.com/cortezaproject/corteza-server/store" + "github.com/cortezaproject/corteza-server/pkg/filter" "time" "github.com/cortezaproject/corteza-server/pkg/permissions" @@ -39,8 +39,8 @@ type ( Check func(*Role) (bool, error) `json:"-"` // Standard helpers for paging and sorting - store.Sorting - store.Paging + filter.Sorting + filter.Paging } RoleMetrics struct { diff --git a/system/types/user.go b/system/types/user.go index d84fbc3fb..a3f9d6b7f 100644 --- a/system/types/user.go +++ b/system/types/user.go @@ -4,7 +4,7 @@ import ( "database/sql/driver" "encoding/json" "fmt" - "github.com/cortezaproject/corteza-server/store" + "github.com/cortezaproject/corteza-server/pkg/filter" "time" "github.com/pkg/errors" @@ -60,8 +60,8 @@ type ( Check func(*User) (bool, error) `json:"-"` // Standard helpers for paging and sorting - store.Sorting - store.Paging + filter.Sorting + filter.Paging } UserKind string diff --git a/tests/compose/store_interface.gen.go b/tests/compose/store_interface.gen.go deleted file mode 100644 index 2009fd0a0..000000000 --- a/tests/compose/store_interface.gen.go +++ /dev/null @@ -1,18 +0,0 @@ -package compose - -// This file is auto-generated. -// -// Template: pkg/store_interfaces_joined.gen.go.tpl -// Definitions: -// - store/rbac_rules.yaml -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// - -type ( - // Interface combines interfaces of all supported store interfaces - storeGeneratedInterfaces interface { - rbacRulesStore - } -) diff --git a/tests/compose/store_interface_rbac_rules.gen.go b/tests/compose/store_interface_rbac_rules.gen.go deleted file mode 100644 index ccb2ade07..000000000 --- a/tests/compose/store_interface_rbac_rules.gen.go +++ /dev/null @@ -1,27 +0,0 @@ -package compose - -// This file is auto-generated. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -// Definitions file that controls how this file is generated: -// - store/rbac_rules.yaml - -import ( - "context" - "github.com/cortezaproject/corteza-server/pkg/permissions" -) - -type ( - rbacRulesStore interface { - SearchRbacRules(ctx context.Context, f permissions.RuleFilter) (permissions.RuleSet, permissions.RuleFilter, error) - CreateRbacRule(ctx context.Context, rr ...*permissions.Rule) error - UpdateRbacRule(ctx context.Context, rr ...*permissions.Rule) error - PartialUpdateRbacRule(ctx context.Context, onlyColumns []string, rr ...*permissions.Rule) error - RemoveRbacRule(ctx context.Context, rr ...*permissions.Rule) error - RemoveRbacRuleByRoleIDResourceOperation(ctx context.Context, roleID uint64, resource string, operation string) error - - TruncateRbacRules(ctx context.Context) error - } -) diff --git a/tests/messaging/store_interface.gen.go b/tests/messaging/store_interface.gen.go deleted file mode 100644 index 5ea9194fc..000000000 --- a/tests/messaging/store_interface.gen.go +++ /dev/null @@ -1,18 +0,0 @@ -package messaging - -// This file is auto-generated. -// -// Template: pkg/store_interfaces_joined.gen.go.tpl -// Definitions: -// - store/rbac_rules.yaml -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// - -type ( - // Interface combines interfaces of all supported store interfaces - storeGeneratedInterfaces interface { - rbacRulesStore - } -) diff --git a/tests/messaging/store_interface_rbac_rules.gen.go b/tests/messaging/store_interface_rbac_rules.gen.go deleted file mode 100644 index 627a8d781..000000000 --- a/tests/messaging/store_interface_rbac_rules.gen.go +++ /dev/null @@ -1,27 +0,0 @@ -package messaging - -// This file is auto-generated. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -// Definitions file that controls how this file is generated: -// - store/rbac_rules.yaml - -import ( - "context" - "github.com/cortezaproject/corteza-server/pkg/permissions" -) - -type ( - rbacRulesStore interface { - SearchRbacRules(ctx context.Context, f permissions.RuleFilter) (permissions.RuleSet, permissions.RuleFilter, error) - CreateRbacRule(ctx context.Context, rr ...*permissions.Rule) error - UpdateRbacRule(ctx context.Context, rr ...*permissions.Rule) error - PartialUpdateRbacRule(ctx context.Context, onlyColumns []string, rr ...*permissions.Rule) error - RemoveRbacRule(ctx context.Context, rr ...*permissions.Rule) error - RemoveRbacRuleByRoleIDResourceOperation(ctx context.Context, roleID uint64, resource string, operation string) error - - TruncateRbacRules(ctx context.Context) error - } -) diff --git a/tests/system/application_test.go b/tests/system/application_test.go index 585f35fc3..9a3caa15e 100644 --- a/tests/system/application_test.go +++ b/tests/system/application_test.go @@ -1,161 +1,161 @@ -package system - -import ( - "context" - "fmt" - "net/http" - "testing" - - jsonpath "github.com/steinfletcher/apitest-jsonpath" - - "github.com/cortezaproject/corteza-server/system/repository" - "github.com/cortezaproject/corteza-server/system/types" - "github.com/cortezaproject/corteza-server/tests/helpers" -) - -func (h helper) repoApplication() repository.ApplicationRepository { - return repository.Application(context.Background(), db()) -} - -func (h helper) repoMakeApplication(name string) *types.Application { - a, err := h. - repoApplication(). - Create(&types.Application{Name: name}) - h.a.NoError(err) - - return a -} - -func TestApplicationRead(t *testing.T) { - h := newHelper(t) - - a := h.repoMakeApplication("one-app") - - h.apiInit(). - Get(fmt.Sprintf("/application/%d", a.ID)). - Expect(t). - Status(http.StatusOK). - Assert(helpers.AssertNoErrors). - Assert(jsonpath.Equal(`$.response.name`, a.Name)). - Assert(jsonpath.Equal(`$.response.applicationID`, fmt.Sprintf("%d", a.ID))). - End() -} - -func TestApplicationList(t *testing.T) { - h := newHelper(t) - - h.repoMakeApplication("app") - h.repoMakeApplication("app") - - h.apiInit(). - Get("/application/"). - Expect(t). - Status(http.StatusOK). - Assert(helpers.AssertNoErrors). - End() -} - -func TestApplicationList_filterForbiden(t *testing.T) { - h := newHelper(t) - - h.repoMakeApplication("app") - f := h.repoMakeApplication("app_forbiden") - - h.deny(types.ApplicationPermissionResource.AppendID(f.ID), "read") - - h.apiInit(). - Get("/application/"). - Expect(t). - Status(http.StatusOK). - Assert(helpers.AssertNoErrors). - Assert(jsonpath.NotPresent(`$.response.set[? @.name=="app_forbiden"]`)). - End() -} - -func TestApplicationCreateForbidden(t *testing.T) { - h := newHelper(t) - - h.apiInit(). - Post("/application/"). - FormData("name", "my-app"). - Expect(t). - Status(http.StatusOK). - Assert(helpers.AssertError("not allowed to create applications")). - End() -} - -func TestApplicationCreate(t *testing.T) { - h := newHelper(t) - h.allow(types.SystemPermissionResource, "application.create") - - h.apiInit(). - Post("/application/"). - FormData("name", "my-app"). - Expect(t). - Status(http.StatusOK). - Assert(helpers.AssertNoErrors). - End() -} - -func TestApplicationUpdateForbidden(t *testing.T) { - h := newHelper(t) - a := h.repoMakeApplication("one-app") - - h.apiInit(). - Put(fmt.Sprintf("/application/%d", a.ID)). - FormData("name", "changed-name"). - Expect(t). - Status(http.StatusOK). - Assert(helpers.AssertError("not allowed to update this application")). - End() -} - -func TestApplicationUpdate(t *testing.T) { - h := newHelper(t) - a := h.repoMakeApplication("one-app") - h.allow(types.ApplicationPermissionResource.AppendWildcard(), "update") - - h.apiInit(). - Put(fmt.Sprintf("/application/%d", a.ID)). - FormData("name", "changed-name"). - Expect(t). - Status(http.StatusOK). - Assert(helpers.AssertNoErrors). - End() - - a, err := h.repoApplication().FindByID(a.ID) - h.a.NoError(err) - h.a.NotNil(a) - h.a.Equal("changed-name", a.Name) -} - -func TestApplicationDeleteForbidden(t *testing.T) { - h := newHelper(t) - a := h.repoMakeApplication("one-app") - - h.apiInit(). - Delete(fmt.Sprintf("/application/%d", a.ID)). - Expect(t). - Status(http.StatusOK). - Assert(helpers.AssertError("not allowed to delete this application")). - End() -} - -func TestApplicationDelete(t *testing.T) { - h := newHelper(t) - h.allow(types.ApplicationPermissionResource.AppendWildcard(), "delete") - - a := h.repoMakeApplication("one-app") - - h.apiInit(). - Delete(fmt.Sprintf("/application/%d", a.ID)). - Expect(t). - Status(http.StatusOK). - Assert(helpers.AssertNoErrors). - End() - - a, err := h.repoApplication().FindByID(a.ID) - h.a.NoError(err) - h.a.NotNil(a) - h.a.NotNil(a.DeletedAt) -} +//package system +// +//import ( +// "context" +// "fmt" +// "net/http" +// "testing" +// +// jsonpath "github.com/steinfletcher/apitest-jsonpath" +// +// "github.com/cortezaproject/corteza-server/system/repository" +// "github.com/cortezaproject/corteza-server/system/types" +// "github.com/cortezaproject/corteza-server/tests/helpers" +//) +// +//func (h helper) repoApplication() repository.ApplicationRepository { +// return repository.Application(context.Background(), db()) +//} +// +//func (h helper) repoMakeApplication(name string) *types.Application { +// a, err := h. +// repoApplication(). +// Create(&types.Application{Name: name}) +// h.a.NoError(err) +// +// return a +//} +// +//func TestApplicationRead(t *testing.T) { +// h := newHelper(t) +// +// a := h.repoMakeApplication("one-app") +// +// h.apiInit(). +// Get(fmt.Sprintf("/application/%d", a.ID)). +// Expect(t). +// Status(http.StatusOK). +// Assert(helpers.AssertNoErrors). +// Assert(jsonpath.Equal(`$.response.name`, a.Name)). +// Assert(jsonpath.Equal(`$.response.applicationID`, fmt.Sprintf("%d", a.ID))). +// End() +//} +// +//func TestApplicationList(t *testing.T) { +// h := newHelper(t) +// +// h.repoMakeApplication("app") +// h.repoMakeApplication("app") +// +// h.apiInit(). +// Get("/application/"). +// Expect(t). +// Status(http.StatusOK). +// Assert(helpers.AssertNoErrors). +// End() +//} +// +//func TestApplicationList_filterForbiden(t *testing.T) { +// h := newHelper(t) +// +// h.repoMakeApplication("app") +// f := h.repoMakeApplication("app_forbiden") +// +// h.deny(types.ApplicationPermissionResource.AppendID(f.ID), "read") +// +// h.apiInit(). +// Get("/application/"). +// Expect(t). +// Status(http.StatusOK). +// Assert(helpers.AssertNoErrors). +// Assert(jsonpath.NotPresent(`$.response.set[? @.name=="app_forbiden"]`)). +// End() +//} +// +//func TestApplicationCreateForbidden(t *testing.T) { +// h := newHelper(t) +// +// h.apiInit(). +// Post("/application/"). +// FormData("name", "my-app"). +// Expect(t). +// Status(http.StatusOK). +// Assert(helpers.AssertError("not allowed to create applications")). +// End() +//} +// +//func TestApplicationCreate(t *testing.T) { +// h := newHelper(t) +// h.allow(types.SystemPermissionResource, "application.create") +// +// h.apiInit(). +// Post("/application/"). +// FormData("name", "my-app"). +// Expect(t). +// Status(http.StatusOK). +// Assert(helpers.AssertNoErrors). +// End() +//} +// +//func TestApplicationUpdateForbidden(t *testing.T) { +// h := newHelper(t) +// a := h.repoMakeApplication("one-app") +// +// h.apiInit(). +// Put(fmt.Sprintf("/application/%d", a.ID)). +// FormData("name", "changed-name"). +// Expect(t). +// Status(http.StatusOK). +// Assert(helpers.AssertError("not allowed to update this application")). +// End() +//} +// +//func TestApplicationUpdate(t *testing.T) { +// h := newHelper(t) +// a := h.repoMakeApplication("one-app") +// h.allow(types.ApplicationPermissionResource.AppendWildcard(), "update") +// +// h.apiInit(). +// Put(fmt.Sprintf("/application/%d", a.ID)). +// FormData("name", "changed-name"). +// Expect(t). +// Status(http.StatusOK). +// Assert(helpers.AssertNoErrors). +// End() +// +// a, err := h.repoApplication().FindByID(a.ID) +// h.a.NoError(err) +// h.a.NotNil(a) +// h.a.Equal("changed-name", a.Name) +//} +// +//func TestApplicationDeleteForbidden(t *testing.T) { +// h := newHelper(t) +// a := h.repoMakeApplication("one-app") +// +// h.apiInit(). +// Delete(fmt.Sprintf("/application/%d", a.ID)). +// Expect(t). +// Status(http.StatusOK). +// Assert(helpers.AssertError("not allowed to delete this application")). +// End() +//} +// +//func TestApplicationDelete(t *testing.T) { +// h := newHelper(t) +// h.allow(types.ApplicationPermissionResource.AppendWildcard(), "delete") +// +// a := h.repoMakeApplication("one-app") +// +// h.apiInit(). +// Delete(fmt.Sprintf("/application/%d", a.ID)). +// Expect(t). +// Status(http.StatusOK). +// Assert(helpers.AssertNoErrors). +// End() +// +// a, err := h.repoApplication().FindByID(a.ID) +// h.a.NoError(err) +// h.a.NotNil(a) +// h.a.NotNil(a.DeletedAt) +//} diff --git a/tests/system/reminder_test.go b/tests/system/reminder_test.go index 2271d8c9d..6edcfd622 100644 --- a/tests/system/reminder_test.go +++ b/tests/system/reminder_test.go @@ -1,353 +1,354 @@ package system -import ( - "context" - "fmt" - "net/http" - "testing" - "time" - - "github.com/cortezaproject/corteza-server/pkg/auth" - "github.com/cortezaproject/corteza-server/system/repository" - "github.com/cortezaproject/corteza-server/system/types" - "github.com/cortezaproject/corteza-server/tests/helpers" - st "github.com/jmoiron/sqlx/types" - jsonpath "github.com/steinfletcher/apitest-jsonpath" -) - -func (h helper) repoReminder() repository.ReminderRepository { - return repository.Reminder(context.Background(), db()) -} - -func (h helper) tPtr(tt time.Time) *time.Time { - return &tt -} - -func (h helper) repoMakeReminder(resource string, payload st.JSONText, assignedTo uint64, remindAt *time.Time) *types.Reminder { - a, err := h. - repoReminder(). - Create(&types.Reminder{ - Resource: resource, - Payload: payload, - AssignedAt: time.Now(), - AssignedBy: auth.GetIdentityFromContext(h.secCtx()).Identity(), - AssignedTo: assignedTo, - RemindAt: remindAt, - }) - - h.a.NoError(err) - - return a -} - -func (h helper) repoDismissReminder(rr *types.Reminder) { - tt := time.Now() - rr.DismissedAt = &tt - _, err := h. - repoReminder(). - Update(rr) - - h.a.NoError(err) -} - -func TestReminderList(t *testing.T) { - h := newHelper(t) - - h.repoMakeReminder("test_lst_namespace:*", nil, 0, nil) - - h.apiInit(). - Get("/reminder/"). - Expect(t). - Status(http.StatusOK). - Assert(helpers.AssertNoErrors). - Assert(jsonpath.Present(`$.response.set[? @.resource=="test_lst_namespace:*"]`)). - End() -} - -func TestReminderListSpecificResource(t *testing.T) { - h := newHelper(t) - - h.repoMakeReminder("test_ls_e_namespace:*", nil, 0, nil) - h.repoMakeReminder("test_ls_ne_namespace:*", nil, 0, nil) - - h.apiInit(). - Get("/reminder/"). - QueryParams(map[string]string{ - "resource": "test_ls_e_namespace:*", - }). - Expect(t). - Status(http.StatusOK). - Assert(helpers.AssertNoErrors). - Assert(jsonpath.Present(`$.response.set[? @.resource=="test_ls_e_namespace:*"]`)). - Assert(jsonpath.NotPresent(`$.response.set[? @.resource=="test_ls_ne_namespace:*"]`)). - End() -} - -func TestReminderListAssignee(t *testing.T) { - h := newHelper(t) - - h.repoMakeReminder("test_la_yes_namespace:*", nil, 111, nil) - h.repoMakeReminder("test_la_no_namespace:*", nil, 222, nil) - - h.apiInit(). - Get("/reminder/"). - QueryParams(map[string]string{ - "assignedTo": "111", - }). - Expect(t). - Status(http.StatusOK). - Assert(helpers.AssertNoErrors). - Assert(jsonpath.Present(`$.response.set[? @.resource=="test_la_yes_namespace:*"]`)). - Assert(jsonpath.NotPresent(`$.response.set[? @.resource=="test_la_no_namespace:*"]`)). - End() -} - -func TestReminderListDateRange(t *testing.T) { - h := newHelper(t) - - nn := time.Now().Round(time.Millisecond).Round(time.Second) - - // Out of range - h.repoMakeReminder("test_lrn_ol_namespace:*", nil, 0, h.tPtr(nn.Add(time.Hour*-2))) - h.repoMakeReminder("test_lrn_or_namespace:*", nil, 0, h.tPtr(nn.Add(time.Hour*2))) - - // In range - h.repoMakeReminder("test_lrn_el_namespace:*", nil, 0, h.tPtr(nn.Add(time.Hour*-1))) - h.repoMakeReminder("test_lrn_cc_namespace:*", nil, 0, h.tPtr(nn)) - h.repoMakeReminder("test_lrn_er_namespace:*", nil, 0, h.tPtr(nn.Add(time.Hour*1))) - - h.apiInit(). - Get("/reminder/"). - QueryParams(map[string]string{ - "scheduledFrom": nn.Add(time.Hour * -1).Format(time.RFC3339), - "scheduledUntil": nn.Add(time.Hour * 1).Format(time.RFC3339), - }). - Expect(t). - Status(http.StatusOK). - Assert(helpers.AssertNoErrors). - Assert(jsonpath.Present(`$.response.set[? @.resource=="test_lrn_el_namespace:*"]`)). - Assert(jsonpath.Present(`$.response.set[? @.resource=="test_lrn_cc_namespace:*"]`)). - Assert(jsonpath.Present(`$.response.set[? @.resource=="test_lrn_er_namespace:*"]`)). - Assert(jsonpath.NotPresent(`$.response.set[? @.resource=="test_lrn_ol_namespace:*"]`)). - Assert(jsonpath.NotPresent(`$.response.set[? @.resource=="test_lrn_or_namespace:*"]`)). - End() -} - -func TestReminderListOnlyScheduled(t *testing.T) { - h := newHelper(t) - - tt := time.Now() - h.repoMakeReminder("test_lsch_yes_namespace:*", nil, 111, &tt) - h.repoMakeReminder("test_lsch_no_namespace:*", nil, 222, nil) - - h.apiInit(). - Get("/reminder/"). - QueryParams(map[string]string{ - "scheduledOnly": "true", - }). - Expect(t). - Status(http.StatusOK). - Assert(helpers.AssertNoErrors). - Assert(jsonpath.Present(`$.response.set[? @.resource=="test_lsch_yes_namespace:*"]`)). - Assert(jsonpath.NotPresent(`$.response.set[? @.resource=="test_lsch_no_namespace:*"]`)). - End() -} - -func TestReminderListExcludeDismissed(t *testing.T) { - h := newHelper(t) - - h.repoMakeReminder("test_ldsm_yes_namespace:*", nil, 0, nil) - - rr := h.repoMakeReminder("test_ldsm_no_namespace:*", nil, 0, nil) - h.repoDismissReminder(rr) - - h.apiInit(). - Get("/reminder/"). - QueryParams(map[string]string{ - "excludeDismissed": "true", - }). - Expect(t). - Status(http.StatusOK). - Assert(helpers.AssertNoErrors). - Assert(jsonpath.Present(`$.response.set[? @.resource=="test_ldsm_yes_namespace:*"]`)). - Assert(jsonpath.NotPresent(`$.response.set[? @.resource=="test_ldsm_no_namespace:*"]`)). - End() -} - -func TestReminderCreate(t *testing.T) { - h := newHelper(t) - - h.apiInit(). - Post("/reminder/"). - JSON(fmt.Sprintf(`{"resource":"test_c_namespace:*","assignedTo":"%d","payload":{}}`, h.cUser.Identity())). - Expect(t). - Status(http.StatusOK). - Assert(helpers.AssertNoErrors). - Assert(jsonpath.Present(`$.response.resource=="test_c_namespace:*"`)). - End() -} - -func TestReminderCreateSetAssignee(t *testing.T) { - h := newHelper(t) - - h.allow(types.SystemPermissionResource, "reminder.assign") - - h.apiInit(). - Post("/reminder/"). - JSON(`{"resource":"test_c_as_namespace:*","assignedTo":"222","payload":{}}`). - Expect(t). - Status(http.StatusOK). - Assert(helpers.AssertNoErrors). - Assert(jsonpath.Present(`$.response.resource=="test_c_as_namespace:*"`)). - End() -} - -func TestReminderCreateSetAssigneeForbidden(t *testing.T) { - h := newHelper(t) - - h.deny(types.SystemPermissionResource, "reminder.assign") - - h.apiInit(). - Post("/reminder/"). - JSON(`{"resource":"test_rf_as_namespace:*","assignedTo":"222","payload":{}}`). - Expect(t). - Status(http.StatusOK). - Assert(helpers.AssertError("not allowed to assign reminders to other users")). - End() -} - -func TestReminderUpdate(t *testing.T) { - h := newHelper(t) - - r := h.repoMakeReminder("test_upd_namespace:*", nil, 0, nil) - - h.apiInit(). - Put(fmt.Sprintf("/reminder/%d", r.ID)). - JSON(fmt.Sprintf(`{"resource":"test_upd_u_namespace:*","assignedTo":"%d","payload":{}}`, h.cUser.Identity())). - Expect(t). - Status(http.StatusOK). - Assert(helpers.AssertNoErrors). - Assert(jsonpath.Present(`$.response.resource=="test_upd_u_namespace:*"`)). - Assert(jsonpath.NotPresent(`$.response.resource=="test_upd_namespace:*"`)). - End() - - h.apiInit(). - Get(fmt.Sprintf("/reminder/%d", r.ID)). - Expect(t). - Status(http.StatusOK). - Assert(helpers.AssertNoErrors). - Assert(jsonpath.Present(`$.response.resource=="test_upd_u_namespace:*"`)). - Assert(jsonpath.NotPresent(`$.response.resource=="test_upd_namespace:*"`)). - End() -} - -func TestReminderUpdateSetAssignee(t *testing.T) { - h := newHelper(t) - - h.allow(types.SystemPermissionResource, "reminder.assign") - r := h.repoMakeReminder("test_upd_as_namespace:*", nil, 0, nil) - - h.apiInit(). - Put(fmt.Sprintf("/reminder/%d", r.ID)). - JSON(`{"resource":"test_upd_as_namespace:*","assignedTo":"111","payload":{}}`). - Expect(t). - Status(http.StatusOK). - Assert(helpers.AssertNoErrors). - Assert(jsonpath.Present(`$.response.resource=="test_upd_as_namespace:*"`)). - Assert(jsonpath.Present(`$.response.assignedTo=="111"`)). - End() - - h.apiInit(). - Get(fmt.Sprintf("/reminder/%d", r.ID)). - Expect(t). - Status(http.StatusOK). - Assert(helpers.AssertNoErrors). - Assert(jsonpath.Present(`$.response.resource=="test_upd_as_namespace:*"`)). - Assert(jsonpath.Present(`$.response.assignedTo=="111"`)). - End() -} - -func TestReminderUpdateSetAssigneeForbidden(t *testing.T) { - h := newHelper(t) - - h.deny(types.SystemPermissionResource, "reminder.assign") - r := h.repoMakeReminder("test_updf_as_namespace:*", nil, 0, nil) - - h.apiInit(). - Put(fmt.Sprintf("/reminder/%d", r.ID)). - JSON(`{"resource":"test_updf_as_namespace:*","assignedTo":"111","payload":{}}`). - Expect(t). - Status(http.StatusOK). - Assert(helpers.AssertError("not allowed to assign reminders to other users")). - End() -} - -func TestReminderRead(t *testing.T) { - h := newHelper(t) - - r := h.repoMakeReminder("test_r_namespace:*", nil, 0, nil) - - h.apiInit(). - Get(fmt.Sprintf("/reminder/%d", r.ID)). - Expect(t). - Status(http.StatusOK). - Assert(helpers.AssertNoErrors). - Assert(jsonpath.Present(`$.response.resource=="test_r_namespace:*"`)). - End() -} - -func TestReminderDelete(t *testing.T) { - h := newHelper(t) - - r := h.repoMakeReminder("test_d_namespace:*", nil, 0, nil) - - h.apiInit(). - Delete(fmt.Sprintf("/reminder/%d", r.ID)). - Expect(t). - Status(http.StatusOK). - Assert(helpers.AssertNoErrors). - Assert(jsonpath.Present(`$.success.message=="OK"`)). - End() -} - -func TestReminderDismiss(t *testing.T) { - h := newHelper(t) - - r := h.repoMakeReminder("test_dsm_namespace:*", nil, 0, nil) - - h.apiInit(). - Patch(fmt.Sprintf("/reminder/%d/dismiss", r.ID)). - Expect(t). - Status(http.StatusOK). - Assert(jsonpath.Present(`$.success.message=="OK"`)). - End() - - h.apiInit(). - Get(fmt.Sprintf("/reminder/%d", r.ID)). - Expect(t). - Status(http.StatusOK). - Assert(jsonpath.Present(`$.response.dismissedAt!="null"`)). - End() -} - -func TestReminderSnooze(t *testing.T) { - h := newHelper(t) - - r := h.repoMakeReminder("test_snz_namespace:*", nil, 0, nil) - - h.apiInit(). - Patch(fmt.Sprintf("/reminder/%d/snooze", r.ID)). - JSON(`{"remindAt":"2001-01-01T01:00:00Z"}`). - Expect(t). - Status(http.StatusOK). - Assert(jsonpath.Present(`$.success.message=="OK"`)). - End() - - h.apiInit(). - Get(fmt.Sprintf("/reminder/%d", r.ID)). - Expect(t). - Status(http.StatusOK). - Assert(jsonpath.Present(`$.response.snoozeCount==1`)). - Assert(jsonpath.Present(`$.response.remindAt!="null"`)). - End() -} +// +//import ( +// "context" +// "fmt" +// "net/http" +// "testing" +// "time" +// +// "github.com/cortezaproject/corteza-server/pkg/auth" +// "github.com/cortezaproject/corteza-server/system/repository" +// "github.com/cortezaproject/corteza-server/system/types" +// "github.com/cortezaproject/corteza-server/tests/helpers" +// st "github.com/jmoiron/sqlx/types" +// jsonpath "github.com/steinfletcher/apitest-jsonpath" +//) +// +//func (h helper) repoReminder() repository.ReminderRepository { +// return repository.Reminder(context.Background(), db()) +//} +// +//func (h helper) tPtr(tt time.Time) *time.Time { +// return &tt +//} +// +//func (h helper) repoMakeReminder(resource string, payload st.JSONText, assignedTo uint64, remindAt *time.Time) *types.Reminder { +// a, err := h. +// repoReminder(). +// Create(&types.Reminder{ +// Resource: resource, +// Payload: payload, +// AssignedAt: time.Now(), +// AssignedBy: auth.GetIdentityFromContext(h.secCtx()).Identity(), +// AssignedTo: assignedTo, +// RemindAt: remindAt, +// }) +// +// h.a.NoError(err) +// +// return a +//} +// +//func (h helper) repoDismissReminder(rr *types.Reminder) { +// tt := time.Now() +// rr.DismissedAt = &tt +// _, err := h. +// repoReminder(). +// Update(rr) +// +// h.a.NoError(err) +//} +// +//func TestReminderList(t *testing.T) { +// h := newHelper(t) +// +// h.repoMakeReminder("test_lst_namespace:*", nil, 0, nil) +// +// h.apiInit(). +// Get("/reminder/"). +// Expect(t). +// Status(http.StatusOK). +// Assert(helpers.AssertNoErrors). +// Assert(jsonpath.Present(`$.response.set[? @.resource=="test_lst_namespace:*"]`)). +// End() +//} +// +//func TestReminderListSpecificResource(t *testing.T) { +// h := newHelper(t) +// +// h.repoMakeReminder("test_ls_e_namespace:*", nil, 0, nil) +// h.repoMakeReminder("test_ls_ne_namespace:*", nil, 0, nil) +// +// h.apiInit(). +// Get("/reminder/"). +// QueryParams(map[string]string{ +// "resource": "test_ls_e_namespace:*", +// }). +// Expect(t). +// Status(http.StatusOK). +// Assert(helpers.AssertNoErrors). +// Assert(jsonpath.Present(`$.response.set[? @.resource=="test_ls_e_namespace:*"]`)). +// Assert(jsonpath.NotPresent(`$.response.set[? @.resource=="test_ls_ne_namespace:*"]`)). +// End() +//} +// +//func TestReminderListAssignee(t *testing.T) { +// h := newHelper(t) +// +// h.repoMakeReminder("test_la_yes_namespace:*", nil, 111, nil) +// h.repoMakeReminder("test_la_no_namespace:*", nil, 222, nil) +// +// h.apiInit(). +// Get("/reminder/"). +// QueryParams(map[string]string{ +// "assignedTo": "111", +// }). +// Expect(t). +// Status(http.StatusOK). +// Assert(helpers.AssertNoErrors). +// Assert(jsonpath.Present(`$.response.set[? @.resource=="test_la_yes_namespace:*"]`)). +// Assert(jsonpath.NotPresent(`$.response.set[? @.resource=="test_la_no_namespace:*"]`)). +// End() +//} +// +//func TestReminderListDateRange(t *testing.T) { +// h := newHelper(t) +// +// nn := time.Now().Round(time.Millisecond).Round(time.Second) +// +// // Out of range +// h.repoMakeReminder("test_lrn_ol_namespace:*", nil, 0, h.tPtr(nn.Add(time.Hour*-2))) +// h.repoMakeReminder("test_lrn_or_namespace:*", nil, 0, h.tPtr(nn.Add(time.Hour*2))) +// +// // In range +// h.repoMakeReminder("test_lrn_el_namespace:*", nil, 0, h.tPtr(nn.Add(time.Hour*-1))) +// h.repoMakeReminder("test_lrn_cc_namespace:*", nil, 0, h.tPtr(nn)) +// h.repoMakeReminder("test_lrn_er_namespace:*", nil, 0, h.tPtr(nn.Add(time.Hour*1))) +// +// h.apiInit(). +// Get("/reminder/"). +// QueryParams(map[string]string{ +// "scheduledFrom": nn.Add(time.Hour * -1).Format(time.RFC3339), +// "scheduledUntil": nn.Add(time.Hour * 1).Format(time.RFC3339), +// }). +// Expect(t). +// Status(http.StatusOK). +// Assert(helpers.AssertNoErrors). +// Assert(jsonpath.Present(`$.response.set[? @.resource=="test_lrn_el_namespace:*"]`)). +// Assert(jsonpath.Present(`$.response.set[? @.resource=="test_lrn_cc_namespace:*"]`)). +// Assert(jsonpath.Present(`$.response.set[? @.resource=="test_lrn_er_namespace:*"]`)). +// Assert(jsonpath.NotPresent(`$.response.set[? @.resource=="test_lrn_ol_namespace:*"]`)). +// Assert(jsonpath.NotPresent(`$.response.set[? @.resource=="test_lrn_or_namespace:*"]`)). +// End() +//} +// +//func TestReminderListOnlyScheduled(t *testing.T) { +// h := newHelper(t) +// +// tt := time.Now() +// h.repoMakeReminder("test_lsch_yes_namespace:*", nil, 111, &tt) +// h.repoMakeReminder("test_lsch_no_namespace:*", nil, 222, nil) +// +// h.apiInit(). +// Get("/reminder/"). +// QueryParams(map[string]string{ +// "scheduledOnly": "true", +// }). +// Expect(t). +// Status(http.StatusOK). +// Assert(helpers.AssertNoErrors). +// Assert(jsonpath.Present(`$.response.set[? @.resource=="test_lsch_yes_namespace:*"]`)). +// Assert(jsonpath.NotPresent(`$.response.set[? @.resource=="test_lsch_no_namespace:*"]`)). +// End() +//} +// +//func TestReminderListExcludeDismissed(t *testing.T) { +// h := newHelper(t) +// +// h.repoMakeReminder("test_ldsm_yes_namespace:*", nil, 0, nil) +// +// rr := h.repoMakeReminder("test_ldsm_no_namespace:*", nil, 0, nil) +// h.repoDismissReminder(rr) +// +// h.apiInit(). +// Get("/reminder/"). +// QueryParams(map[string]string{ +// "excludeDismissed": "true", +// }). +// Expect(t). +// Status(http.StatusOK). +// Assert(helpers.AssertNoErrors). +// Assert(jsonpath.Present(`$.response.set[? @.resource=="test_ldsm_yes_namespace:*"]`)). +// Assert(jsonpath.NotPresent(`$.response.set[? @.resource=="test_ldsm_no_namespace:*"]`)). +// End() +//} +// +//func TestReminderCreate(t *testing.T) { +// h := newHelper(t) +// +// h.apiInit(). +// Post("/reminder/"). +// JSON(fmt.Sprintf(`{"resource":"test_c_namespace:*","assignedTo":"%d","payload":{}}`, h.cUser.Identity())). +// Expect(t). +// Status(http.StatusOK). +// Assert(helpers.AssertNoErrors). +// Assert(jsonpath.Present(`$.response.resource=="test_c_namespace:*"`)). +// End() +//} +// +//func TestReminderCreateSetAssignee(t *testing.T) { +// h := newHelper(t) +// +// h.allow(types.SystemPermissionResource, "reminder.assign") +// +// h.apiInit(). +// Post("/reminder/"). +// JSON(`{"resource":"test_c_as_namespace:*","assignedTo":"222","payload":{}}`). +// Expect(t). +// Status(http.StatusOK). +// Assert(helpers.AssertNoErrors). +// Assert(jsonpath.Present(`$.response.resource=="test_c_as_namespace:*"`)). +// End() +//} +// +//func TestReminderCreateSetAssigneeForbidden(t *testing.T) { +// h := newHelper(t) +// +// h.deny(types.SystemPermissionResource, "reminder.assign") +// +// h.apiInit(). +// Post("/reminder/"). +// JSON(`{"resource":"test_rf_as_namespace:*","assignedTo":"222","payload":{}}`). +// Expect(t). +// Status(http.StatusOK). +// Assert(helpers.AssertError("not allowed to assign reminders to other users")). +// End() +//} +// +//func TestReminderUpdate(t *testing.T) { +// h := newHelper(t) +// +// r := h.repoMakeReminder("test_upd_namespace:*", nil, 0, nil) +// +// h.apiInit(). +// Put(fmt.Sprintf("/reminder/%d", r.ID)). +// JSON(fmt.Sprintf(`{"resource":"test_upd_u_namespace:*","assignedTo":"%d","payload":{}}`, h.cUser.Identity())). +// Expect(t). +// Status(http.StatusOK). +// Assert(helpers.AssertNoErrors). +// Assert(jsonpath.Present(`$.response.resource=="test_upd_u_namespace:*"`)). +// Assert(jsonpath.NotPresent(`$.response.resource=="test_upd_namespace:*"`)). +// End() +// +// h.apiInit(). +// Get(fmt.Sprintf("/reminder/%d", r.ID)). +// Expect(t). +// Status(http.StatusOK). +// Assert(helpers.AssertNoErrors). +// Assert(jsonpath.Present(`$.response.resource=="test_upd_u_namespace:*"`)). +// Assert(jsonpath.NotPresent(`$.response.resource=="test_upd_namespace:*"`)). +// End() +//} +// +//func TestReminderUpdateSetAssignee(t *testing.T) { +// h := newHelper(t) +// +// h.allow(types.SystemPermissionResource, "reminder.assign") +// r := h.repoMakeReminder("test_upd_as_namespace:*", nil, 0, nil) +// +// h.apiInit(). +// Put(fmt.Sprintf("/reminder/%d", r.ID)). +// JSON(`{"resource":"test_upd_as_namespace:*","assignedTo":"111","payload":{}}`). +// Expect(t). +// Status(http.StatusOK). +// Assert(helpers.AssertNoErrors). +// Assert(jsonpath.Present(`$.response.resource=="test_upd_as_namespace:*"`)). +// Assert(jsonpath.Present(`$.response.assignedTo=="111"`)). +// End() +// +// h.apiInit(). +// Get(fmt.Sprintf("/reminder/%d", r.ID)). +// Expect(t). +// Status(http.StatusOK). +// Assert(helpers.AssertNoErrors). +// Assert(jsonpath.Present(`$.response.resource=="test_upd_as_namespace:*"`)). +// Assert(jsonpath.Present(`$.response.assignedTo=="111"`)). +// End() +//} +// +//func TestReminderUpdateSetAssigneeForbidden(t *testing.T) { +// h := newHelper(t) +// +// h.deny(types.SystemPermissionResource, "reminder.assign") +// r := h.repoMakeReminder("test_updf_as_namespace:*", nil, 0, nil) +// +// h.apiInit(). +// Put(fmt.Sprintf("/reminder/%d", r.ID)). +// JSON(`{"resource":"test_updf_as_namespace:*","assignedTo":"111","payload":{}}`). +// Expect(t). +// Status(http.StatusOK). +// Assert(helpers.AssertError("not allowed to assign reminders to other users")). +// End() +//} +// +//func TestReminderRead(t *testing.T) { +// h := newHelper(t) +// +// r := h.repoMakeReminder("test_r_namespace:*", nil, 0, nil) +// +// h.apiInit(). +// Get(fmt.Sprintf("/reminder/%d", r.ID)). +// Expect(t). +// Status(http.StatusOK). +// Assert(helpers.AssertNoErrors). +// Assert(jsonpath.Present(`$.response.resource=="test_r_namespace:*"`)). +// End() +//} +// +//func TestReminderDelete(t *testing.T) { +// h := newHelper(t) +// +// r := h.repoMakeReminder("test_d_namespace:*", nil, 0, nil) +// +// h.apiInit(). +// Delete(fmt.Sprintf("/reminder/%d", r.ID)). +// Expect(t). +// Status(http.StatusOK). +// Assert(helpers.AssertNoErrors). +// Assert(jsonpath.Present(`$.success.message=="OK"`)). +// End() +//} +// +//func TestReminderDismiss(t *testing.T) { +// h := newHelper(t) +// +// r := h.repoMakeReminder("test_dsm_namespace:*", nil, 0, nil) +// +// h.apiInit(). +// Patch(fmt.Sprintf("/reminder/%d/dismiss", r.ID)). +// Expect(t). +// Status(http.StatusOK). +// Assert(jsonpath.Present(`$.success.message=="OK"`)). +// End() +// +// h.apiInit(). +// Get(fmt.Sprintf("/reminder/%d", r.ID)). +// Expect(t). +// Status(http.StatusOK). +// Assert(jsonpath.Present(`$.response.dismissedAt!="null"`)). +// End() +//} +// +//func TestReminderSnooze(t *testing.T) { +// h := newHelper(t) +// +// r := h.repoMakeReminder("test_snz_namespace:*", nil, 0, nil) +// +// h.apiInit(). +// Patch(fmt.Sprintf("/reminder/%d/snooze", r.ID)). +// JSON(`{"remindAt":"2001-01-01T01:00:00Z"}`). +// Expect(t). +// Status(http.StatusOK). +// Assert(jsonpath.Present(`$.success.message=="OK"`)). +// End() +// +// h.apiInit(). +// Get(fmt.Sprintf("/reminder/%d", r.ID)). +// Expect(t). +// Status(http.StatusOK). +// Assert(jsonpath.Present(`$.response.snoozeCount==1`)). +// Assert(jsonpath.Present(`$.response.remindAt!="null"`)). +// End() +//} diff --git a/tests/system/role_test.go b/tests/system/role_test.go index c624971de..4487c1931 100644 --- a/tests/system/role_test.go +++ b/tests/system/role_test.go @@ -1,210 +1,211 @@ package system -import ( - "context" - "fmt" - "net/http" - "testing" - - jsonpath "github.com/steinfletcher/apitest-jsonpath" - - "github.com/cortezaproject/corteza-server/system/repository" - "github.com/cortezaproject/corteza-server/system/types" - "github.com/cortezaproject/corteza-server/tests/helpers" -) - -func (h helper) repoRole() repository.RoleRepository { - return repository.Role(context.Background(), db()) -} - -func (h helper) repoMakeRole(ss ...string) *types.Role { - var r = &types.Role{} - if len(ss) > 1 { - r.Handle = ss[1] - } else { - r.Handle = "h_" + rs() - - } - if len(ss) > 0 { - r.Name = ss[0] - } else { - r.Name = "n_" + rs() - } - - r, err := h. - repoRole(). - Create(r) - h.a.NoError(err) - - return r -} - -func TestRoleRead(t *testing.T) { - h := newHelper(t) - - u := h.repoMakeRole() - - h.apiInit(). - Get(fmt.Sprintf("/roles/%d", u.ID)). - Expect(t). - Status(http.StatusOK). - Assert(helpers.AssertNoErrors). - Assert(jsonpath.Equal(`$.response.name`, u.Name)). - Assert(jsonpath.Equal(`$.response.roleID`, fmt.Sprintf("%d", u.ID))). - End() -} - -func TestRoleList(t *testing.T) { - h := newHelper(t) - - h.repoMakeRole(h.randEmail()) - h.repoMakeRole(h.randEmail()) - - h.apiInit(). - Get("/roles/"). - Expect(t). - Status(http.StatusOK). - Assert(helpers.AssertNoErrors). - End() -} - -func TestRoleList_filterForbidden(t *testing.T) { - h := newHelper(t) - - // @todo this can be a problematic test because it leaves - // behind roles that are not denied this context - // db purge might be needed - - h.repoMakeRole("role") - f := h.repoMakeRole() - - h.deny(types.RolePermissionResource.AppendID(f.ID), "read") - - h.apiInit(). - Get("/roles/"). - Query("handle", f.Handle). - Expect(t). - Status(http.StatusOK). - Assert(helpers.AssertNoErrors). - Assert(jsonpath.NotPresent(fmt.Sprintf(`$.response.set[? @.handle=="%s"]`, f.Handle))). - End() -} - -func TestRoleCreateForbidden(t *testing.T) { - h := newHelper(t) - - h.apiInit(). - Post("/roles/"). - FormData("name", rs()). - Expect(t). - Status(http.StatusOK). - Assert(helpers.AssertError("not allowed to create roles")). - End() -} - -func TestRoleCreateNotUnique(t *testing.T) { - h := newHelper(t) - h.allow(types.SystemPermissionResource, "role.create") - - role := h.repoMakeRole() - h.apiInit(). - Post("/roles/"). - FormData("name", rs()). - FormData("handle", role.Handle). - Expect(t). - Status(http.StatusOK). - Assert(helpers.AssertError("role handle not unique")). - End() - - h.apiInit(). - Post("/roles/"). - FormData("name", role.Name). - FormData("handle", "handle_"+rs()). - Expect(t). - Status(http.StatusOK). - Assert(helpers.AssertError("role name not unique")). - End() - -} - -func TestRoleCreate(t *testing.T) { - h := newHelper(t) - h.allow(types.SystemPermissionResource, "role.create") - - h.apiInit(). - Post("/roles/"). - FormData("name", rs()). - FormData("handle", "handle_"+rs()). - Expect(t). - Status(http.StatusOK). - Assert(helpers.AssertNoErrors). - End() -} - -func TestRoleUpdateForbidden(t *testing.T) { - h := newHelper(t) - u := h.repoMakeRole() - - h.apiInit(). - Put(fmt.Sprintf("/roles/%d", u.ID)). - FormData("email", h.randEmail()). - Expect(t). - Status(http.StatusOK). - Assert(helpers.AssertError("not allowed to update this role")). - End() -} - -func TestRoleUpdate(t *testing.T) { - h := newHelper(t) - u := h.repoMakeRole() - h.allow(types.RolePermissionResource.AppendWildcard(), "update") - - newName := "updated-" + rs() - newHandle := "updated-" + rs() - - h.apiInit(). - Put(fmt.Sprintf("/roles/%d", u.ID)). - FormData("name", newName). - FormData("handle", newHandle). - Expect(t). - Status(http.StatusOK). - Assert(helpers.AssertNoErrors). - End() - - u, err := h.repoRole().FindByID(u.ID) - h.a.NoError(err) - h.a.NotNil(u) - h.a.Equal(newName, u.Name) - h.a.Equal(newHandle, u.Handle) -} - -func TestRoleDeleteForbidden(t *testing.T) { - h := newHelper(t) - u := h.repoMakeRole() - - h.apiInit(). - Delete(fmt.Sprintf("/roles/%d", u.ID)). - Expect(t). - Status(http.StatusOK). - Assert(helpers.AssertError("not allowed to delete this role")). - End() -} - -func TestRoleDelete(t *testing.T) { - h := newHelper(t) - h.allow(types.RolePermissionResource.AppendWildcard(), "delete") - - r := h.repoMakeRole() - - h.apiInit(). - Delete(fmt.Sprintf("/roles/%d", r.ID)). - Expect(t). - Status(http.StatusOK). - Assert(helpers.AssertNoErrors). - End() - - r, err := h.repoRole().FindByID(r.ID) - h.a.NoError(err) - h.a.NotNil(r) - h.a.NotNil(r.DeletedAt) -} +// +//import ( +// "context" +// "fmt" +// "net/http" +// "testing" +// +// jsonpath "github.com/steinfletcher/apitest-jsonpath" +// +// "github.com/cortezaproject/corteza-server/system/repository" +// "github.com/cortezaproject/corteza-server/system/types" +// "github.com/cortezaproject/corteza-server/tests/helpers" +//) +// +//func (h helper) repoRole() repository.RoleRepository { +// return repository.Role(context.Background(), db()) +//} +// +//func (h helper) repoMakeRole(ss ...string) *types.Role { +// var r = &types.Role{} +// if len(ss) > 1 { +// r.Handle = ss[1] +// } else { +// r.Handle = "h_" + rs() +// +// } +// if len(ss) > 0 { +// r.Name = ss[0] +// } else { +// r.Name = "n_" + rs() +// } +// +// r, err := h. +// repoRole(). +// Create(r) +// h.a.NoError(err) +// +// return r +//} +// +//func TestRoleRead(t *testing.T) { +// h := newHelper(t) +// +// u := h.repoMakeRole() +// +// h.apiInit(). +// Get(fmt.Sprintf("/roles/%d", u.ID)). +// Expect(t). +// Status(http.StatusOK). +// Assert(helpers.AssertNoErrors). +// Assert(jsonpath.Equal(`$.response.name`, u.Name)). +// Assert(jsonpath.Equal(`$.response.roleID`, fmt.Sprintf("%d", u.ID))). +// End() +//} +// +//func TestRoleList(t *testing.T) { +// h := newHelper(t) +// +// h.repoMakeRole(h.randEmail()) +// h.repoMakeRole(h.randEmail()) +// +// h.apiInit(). +// Get("/roles/"). +// Expect(t). +// Status(http.StatusOK). +// Assert(helpers.AssertNoErrors). +// End() +//} +// +//func TestRoleList_filterForbidden(t *testing.T) { +// h := newHelper(t) +// +// // @todo this can be a problematic test because it leaves +// // behind roles that are not denied this context +// // db purge might be needed +// +// h.repoMakeRole("role") +// f := h.repoMakeRole() +// +// h.deny(types.RolePermissionResource.AppendID(f.ID), "read") +// +// h.apiInit(). +// Get("/roles/"). +// Query("handle", f.Handle). +// Expect(t). +// Status(http.StatusOK). +// Assert(helpers.AssertNoErrors). +// Assert(jsonpath.NotPresent(fmt.Sprintf(`$.response.set[? @.handle=="%s"]`, f.Handle))). +// End() +//} +// +//func TestRoleCreateForbidden(t *testing.T) { +// h := newHelper(t) +// +// h.apiInit(). +// Post("/roles/"). +// FormData("name", rs()). +// Expect(t). +// Status(http.StatusOK). +// Assert(helpers.AssertError("not allowed to create roles")). +// End() +//} +// +//func TestRoleCreateNotUnique(t *testing.T) { +// h := newHelper(t) +// h.allow(types.SystemPermissionResource, "role.create") +// +// role := h.repoMakeRole() +// h.apiInit(). +// Post("/roles/"). +// FormData("name", rs()). +// FormData("handle", role.Handle). +// Expect(t). +// Status(http.StatusOK). +// Assert(helpers.AssertError("role handle not unique")). +// End() +// +// h.apiInit(). +// Post("/roles/"). +// FormData("name", role.Name). +// FormData("handle", "handle_"+rs()). +// Expect(t). +// Status(http.StatusOK). +// Assert(helpers.AssertError("role name not unique")). +// End() +// +//} +// +//func TestRoleCreate(t *testing.T) { +// h := newHelper(t) +// h.allow(types.SystemPermissionResource, "role.create") +// +// h.apiInit(). +// Post("/roles/"). +// FormData("name", rs()). +// FormData("handle", "handle_"+rs()). +// Expect(t). +// Status(http.StatusOK). +// Assert(helpers.AssertNoErrors). +// End() +//} +// +//func TestRoleUpdateForbidden(t *testing.T) { +// h := newHelper(t) +// u := h.repoMakeRole() +// +// h.apiInit(). +// Put(fmt.Sprintf("/roles/%d", u.ID)). +// FormData("email", h.randEmail()). +// Expect(t). +// Status(http.StatusOK). +// Assert(helpers.AssertError("not allowed to update this role")). +// End() +//} +// +//func TestRoleUpdate(t *testing.T) { +// h := newHelper(t) +// u := h.repoMakeRole() +// h.allow(types.RolePermissionResource.AppendWildcard(), "update") +// +// newName := "updated-" + rs() +// newHandle := "updated-" + rs() +// +// h.apiInit(). +// Put(fmt.Sprintf("/roles/%d", u.ID)). +// FormData("name", newName). +// FormData("handle", newHandle). +// Expect(t). +// Status(http.StatusOK). +// Assert(helpers.AssertNoErrors). +// End() +// +// u, err := h.repoRole().FindByID(u.ID) +// h.a.NoError(err) +// h.a.NotNil(u) +// h.a.Equal(newName, u.Name) +// h.a.Equal(newHandle, u.Handle) +//} +// +//func TestRoleDeleteForbidden(t *testing.T) { +// h := newHelper(t) +// u := h.repoMakeRole() +// +// h.apiInit(). +// Delete(fmt.Sprintf("/roles/%d", u.ID)). +// Expect(t). +// Status(http.StatusOK). +// Assert(helpers.AssertError("not allowed to delete this role")). +// End() +//} +// +//func TestRoleDelete(t *testing.T) { +// h := newHelper(t) +// h.allow(types.RolePermissionResource.AppendWildcard(), "delete") +// +// r := h.repoMakeRole() +// +// h.apiInit(). +// Delete(fmt.Sprintf("/roles/%d", r.ID)). +// Expect(t). +// Status(http.StatusOK). +// Assert(helpers.AssertNoErrors). +// End() +// +// r, err := h.repoRole().FindByID(r.ID) +// h.a.NoError(err) +// h.a.NotNil(r) +// h.a.NotNil(r.DeletedAt) +//} diff --git a/tests/system/store_interface.gen.go b/tests/system/store_interface.gen.go deleted file mode 100644 index 10900f1f9..000000000 --- a/tests/system/store_interface.gen.go +++ /dev/null @@ -1,18 +0,0 @@ -package system - -// This file is auto-generated. -// -// Template: pkg/store_interfaces_joined.gen.go.tpl -// Definitions: -// - store/rbac_rules.yaml -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// - -type ( - // Interface combines interfaces of all supported store interfaces - storeGeneratedInterfaces interface { - rbacRulesStore - } -) diff --git a/tests/system/store_interface_rbac_rules.gen.go b/tests/system/store_interface_rbac_rules.gen.go deleted file mode 100644 index 1bfb461d8..000000000 --- a/tests/system/store_interface_rbac_rules.gen.go +++ /dev/null @@ -1,27 +0,0 @@ -package system - -// This file is auto-generated. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -// Definitions file that controls how this file is generated: -// - store/rbac_rules.yaml - -import ( - "context" - "github.com/cortezaproject/corteza-server/pkg/permissions" -) - -type ( - rbacRulesStore interface { - SearchRbacRules(ctx context.Context, f permissions.RuleFilter) (permissions.RuleSet, permissions.RuleFilter, error) - CreateRbacRule(ctx context.Context, rr ...*permissions.Rule) error - UpdateRbacRule(ctx context.Context, rr ...*permissions.Rule) error - PartialUpdateRbacRule(ctx context.Context, onlyColumns []string, rr ...*permissions.Rule) error - RemoveRbacRule(ctx context.Context, rr ...*permissions.Rule) error - RemoveRbacRuleByRoleIDResourceOperation(ctx context.Context, roleID uint64, resource string, operation string) error - - TruncateRbacRules(ctx context.Context) error - } -) diff --git a/vendor/github.com/Masterminds/goutils/.travis.yml b/vendor/github.com/Masterminds/goutils/.travis.yml new file mode 100644 index 000000000..4025e01ec --- /dev/null +++ b/vendor/github.com/Masterminds/goutils/.travis.yml @@ -0,0 +1,18 @@ +language: go + +go: + - 1.6 + - 1.7 + - 1.8 + - tip + +script: + - go test -v + +notifications: + webhooks: + urls: + - https://webhooks.gitter.im/e/06e3328629952dabe3e0 + on_success: change # options: [always|never|change] default: always + on_failure: always # options: [always|never|change] default: always + on_start: never # options: [always|never|change] default: always diff --git a/vendor/github.com/Masterminds/goutils/CHANGELOG.md b/vendor/github.com/Masterminds/goutils/CHANGELOG.md new file mode 100644 index 000000000..d700ec47f --- /dev/null +++ b/vendor/github.com/Masterminds/goutils/CHANGELOG.md @@ -0,0 +1,8 @@ +# 1.0.1 (2017-05-31) + +## Fixed +- #21: Fix generation of alphanumeric strings (thanks @dbarranco) + +# 1.0.0 (2014-04-30) + +- Initial release. diff --git a/vendor/github.com/Masterminds/goutils/LICENSE.txt b/vendor/github.com/Masterminds/goutils/LICENSE.txt new file mode 100644 index 000000000..d64569567 --- /dev/null +++ b/vendor/github.com/Masterminds/goutils/LICENSE.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/Masterminds/goutils/README.md b/vendor/github.com/Masterminds/goutils/README.md new file mode 100644 index 000000000..163ffe72a --- /dev/null +++ b/vendor/github.com/Masterminds/goutils/README.md @@ -0,0 +1,70 @@ +GoUtils +=========== +[![Stability: Maintenance](https://masterminds.github.io/stability/maintenance.svg)](https://masterminds.github.io/stability/maintenance.html) +[![GoDoc](https://godoc.org/github.com/Masterminds/goutils?status.png)](https://godoc.org/github.com/Masterminds/goutils) [![Build Status](https://travis-ci.org/Masterminds/goutils.svg?branch=master)](https://travis-ci.org/Masterminds/goutils) [![Build status](https://ci.appveyor.com/api/projects/status/sc2b1ew0m7f0aiju?svg=true)](https://ci.appveyor.com/project/mattfarina/goutils) + + +GoUtils provides users with utility functions to manipulate strings in various ways. It is a Go implementation of some +string manipulation libraries of Java Apache Commons. GoUtils includes the following Java Apache Commons classes: +* WordUtils +* RandomStringUtils +* StringUtils (partial implementation) + +## Installation +If you have Go set up on your system, from the GOPATH directory within the command line/terminal, enter this: + + go get github.com/Masterminds/goutils + +If you do not have Go set up on your system, please follow the [Go installation directions from the documenation](http://golang.org/doc/install), and then follow the instructions above to install GoUtils. + + +## Documentation +GoUtils doc is available here: [![GoDoc](https://godoc.org/github.com/Masterminds/goutils?status.png)](https://godoc.org/github.com/Masterminds/goutils) + + +## Usage +The code snippets below show examples of how to use GoUtils. Some functions return errors while others do not. The first instance below, which does not return an error, is the `Initials` function (located within the `wordutils.go` file). + + package main + + import ( + "fmt" + "github.com/Masterminds/goutils" + ) + + func main() { + + // EXAMPLE 1: A goutils function which returns no errors + fmt.Println (goutils.Initials("John Doe Foo")) // Prints out "JDF" + + } +Some functions return errors mainly due to illegal arguements used as parameters. The code example below illustrates how to deal with function that returns an error. In this instance, the function is the `Random` function (located within the `randomstringutils.go` file). + + package main + + import ( + "fmt" + "github.com/Masterminds/goutils" + ) + + func main() { + + // EXAMPLE 2: A goutils function which returns an error + rand1, err1 := goutils.Random (-1, 0, 0, true, true) + + if err1 != nil { + fmt.Println(err1) // Prints out error message because -1 was entered as the first parameter in goutils.Random(...) + } else { + fmt.Println(rand1) + } + + } + +## License +GoUtils is licensed under the Apache License, Version 2.0. Please check the LICENSE.txt file or visit http://www.apache.org/licenses/LICENSE-2.0 for a copy of the license. + +## Issue Reporting +Make suggestions or report issues using the Git issue tracker: https://github.com/Masterminds/goutils/issues + +## Website +* [GoUtils webpage](http://Masterminds.github.io/goutils/) diff --git a/vendor/github.com/Masterminds/goutils/appveyor.yml b/vendor/github.com/Masterminds/goutils/appveyor.yml new file mode 100644 index 000000000..657564a84 --- /dev/null +++ b/vendor/github.com/Masterminds/goutils/appveyor.yml @@ -0,0 +1,21 @@ +version: build-{build}.{branch} + +clone_folder: C:\gopath\src\github.com\Masterminds\goutils +shallow_clone: true + +environment: + GOPATH: C:\gopath + +platform: + - x64 + +build: off + +install: + - go version + - go env + +test_script: + - go test -v + +deploy: off diff --git a/vendor/github.com/Masterminds/goutils/cryptorandomstringutils.go b/vendor/github.com/Masterminds/goutils/cryptorandomstringutils.go new file mode 100644 index 000000000..177dd8658 --- /dev/null +++ b/vendor/github.com/Masterminds/goutils/cryptorandomstringutils.go @@ -0,0 +1,251 @@ +/* +Copyright 2014 Alexander Okoli + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package goutils + +import ( + "crypto/rand" + "fmt" + "math" + "math/big" + "regexp" + "unicode" +) + +/* +CryptoRandomNonAlphaNumeric creates a random string whose length is the number of characters specified. +Characters will be chosen from the set of all characters (ASCII/Unicode values between 0 to 2,147,483,647 (math.MaxInt32)). + +Parameter: + count - the length of random string to create + +Returns: + string - the random string + error - an error stemming from an invalid parameter within underlying function, CryptoRandom(...) +*/ +func CryptoRandomNonAlphaNumeric(count int) (string, error) { + return CryptoRandomAlphaNumericCustom(count, false, false) +} + +/* +CryptoRandomAscii creates a random string whose length is the number of characters specified. +Characters will be chosen from the set of characters whose ASCII value is between 32 and 126 (inclusive). + +Parameter: + count - the length of random string to create + +Returns: + string - the random string + error - an error stemming from an invalid parameter within underlying function, CryptoRandom(...) +*/ +func CryptoRandomAscii(count int) (string, error) { + return CryptoRandom(count, 32, 127, false, false) +} + +/* +CryptoRandomNumeric creates a random string whose length is the number of characters specified. +Characters will be chosen from the set of numeric characters. + +Parameter: + count - the length of random string to create + +Returns: + string - the random string + error - an error stemming from an invalid parameter within underlying function, CryptoRandom(...) +*/ +func CryptoRandomNumeric(count int) (string, error) { + return CryptoRandom(count, 0, 0, false, true) +} + +/* +CryptoRandomAlphabetic creates a random string whose length is the number of characters specified. +Characters will be chosen from the set of alpha-numeric characters as indicated by the arguments. + +Parameters: + count - the length of random string to create + letters - if true, generated string may include alphabetic characters + numbers - if true, generated string may include numeric characters + +Returns: + string - the random string + error - an error stemming from an invalid parameter within underlying function, CryptoRandom(...) +*/ +func CryptoRandomAlphabetic(count int) (string, error) { + return CryptoRandom(count, 0, 0, true, false) +} + +/* +CryptoRandomAlphaNumeric creates a random string whose length is the number of characters specified. +Characters will be chosen from the set of alpha-numeric characters. + +Parameter: + count - the length of random string to create + +Returns: + string - the random string + error - an error stemming from an invalid parameter within underlying function, CryptoRandom(...) +*/ +func CryptoRandomAlphaNumeric(count int) (string, error) { + if count == 0 { + return "", nil + } + RandomString, err := CryptoRandom(count, 0, 0, true, true) + if err != nil { + return "", fmt.Errorf("Error: %s", err) + } + match, err := regexp.MatchString("([0-9]+)", RandomString) + if err != nil { + panic(err) + } + + if !match { + //Get the position between 0 and the length of the string-1 to insert a random number + position := getCryptoRandomInt(count) + //Insert a random number between [0-9] in the position + RandomString = RandomString[:position] + string('0' + getCryptoRandomInt(10)) + RandomString[position + 1:] + return RandomString, err + } + return RandomString, err + +} + +/* +CryptoRandomAlphaNumericCustom creates a random string whose length is the number of characters specified. +Characters will be chosen from the set of alpha-numeric characters as indicated by the arguments. + +Parameters: + count - the length of random string to create + letters - if true, generated string may include alphabetic characters + numbers - if true, generated string may include numeric characters + +Returns: + string - the random string + error - an error stemming from an invalid parameter within underlying function, CryptoRandom(...) +*/ +func CryptoRandomAlphaNumericCustom(count int, letters bool, numbers bool) (string, error) { + return CryptoRandom(count, 0, 0, letters, numbers) +} + +/* +CryptoRandom creates a random string based on a variety of options, using using golang's crypto/rand source of randomness. +If the parameters start and end are both 0, start and end are set to ' ' and 'z', the ASCII printable characters, will be used, +unless letters and numbers are both false, in which case, start and end are set to 0 and math.MaxInt32, respectively. +If chars is not nil, characters stored in chars that are between start and end are chosen. + +Parameters: + count - the length of random string to create + start - the position in set of chars (ASCII/Unicode int) to start at + end - the position in set of chars (ASCII/Unicode int) to end before + letters - if true, generated string may include alphabetic characters + numbers - if true, generated string may include numeric characters + chars - the set of chars to choose randoms from. If nil, then it will use the set of all chars. + +Returns: + string - the random string + error - an error stemming from invalid parameters: if count < 0; or the provided chars array is empty; or end <= start; or end > len(chars) +*/ +func CryptoRandom(count int, start int, end int, letters bool, numbers bool, chars ...rune) (string, error) { + if count == 0 { + return "", nil + } else if count < 0 { + err := fmt.Errorf("randomstringutils illegal argument: Requested random string length %v is less than 0.", count) // equiv to err := errors.New("...") + return "", err + } + if chars != nil && len(chars) == 0 { + err := fmt.Errorf("randomstringutils illegal argument: The chars array must not be empty") + return "", err + } + + if start == 0 && end == 0 { + if chars != nil { + end = len(chars) + } else { + if !letters && !numbers { + end = math.MaxInt32 + } else { + end = 'z' + 1 + start = ' ' + } + } + } else { + if end <= start { + err := fmt.Errorf("randomstringutils illegal argument: Parameter end (%v) must be greater than start (%v)", end, start) + return "", err + } + + if chars != nil && end > len(chars) { + err := fmt.Errorf("randomstringutils illegal argument: Parameter end (%v) cannot be greater than len(chars) (%v)", end, len(chars)) + return "", err + } + } + + buffer := make([]rune, count) + gap := end - start + + // high-surrogates range, (\uD800-\uDBFF) = 55296 - 56319 + // low-surrogates range, (\uDC00-\uDFFF) = 56320 - 57343 + + for count != 0 { + count-- + var ch rune + if chars == nil { + ch = rune(getCryptoRandomInt(gap) + int64(start)) + } else { + ch = chars[getCryptoRandomInt(gap) + int64(start)] + } + + if letters && unicode.IsLetter(ch) || numbers && unicode.IsDigit(ch) || !letters && !numbers { + if ch >= 56320 && ch <= 57343 { // low surrogate range + if count == 0 { + count++ + } else { + // Insert low surrogate + buffer[count] = ch + count-- + // Insert high surrogate + buffer[count] = rune(55296 + getCryptoRandomInt(128)) + } + } else if ch >= 55296 && ch <= 56191 { // High surrogates range (Partial) + if count == 0 { + count++ + } else { + // Insert low surrogate + buffer[count] = rune(56320 + getCryptoRandomInt(128)) + count-- + // Insert high surrogate + buffer[count] = ch + } + } else if ch >= 56192 && ch <= 56319 { + // private high surrogate, skip it + count++ + } else { + // not one of the surrogates* + buffer[count] = ch + } + } else { + count++ + } + } + return string(buffer), nil +} + +func getCryptoRandomInt(count int) int64 { + nBig, err := rand.Int(rand.Reader, big.NewInt(int64(count))) + if err != nil { + panic(err) + } + return nBig.Int64() +} diff --git a/vendor/github.com/Masterminds/goutils/randomstringutils.go b/vendor/github.com/Masterminds/goutils/randomstringutils.go new file mode 100644 index 000000000..1364e0caf --- /dev/null +++ b/vendor/github.com/Masterminds/goutils/randomstringutils.go @@ -0,0 +1,268 @@ +/* +Copyright 2014 Alexander Okoli + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package goutils + +import ( + "fmt" + "math" + "math/rand" + "regexp" + "time" + "unicode" +) + +// RANDOM provides the time-based seed used to generate random numbers +var RANDOM = rand.New(rand.NewSource(time.Now().UnixNano())) + +/* +RandomNonAlphaNumeric creates a random string whose length is the number of characters specified. +Characters will be chosen from the set of all characters (ASCII/Unicode values between 0 to 2,147,483,647 (math.MaxInt32)). + +Parameter: + count - the length of random string to create + +Returns: + string - the random string + error - an error stemming from an invalid parameter within underlying function, RandomSeed(...) +*/ +func RandomNonAlphaNumeric(count int) (string, error) { + return RandomAlphaNumericCustom(count, false, false) +} + +/* +RandomAscii creates a random string whose length is the number of characters specified. +Characters will be chosen from the set of characters whose ASCII value is between 32 and 126 (inclusive). + +Parameter: + count - the length of random string to create + +Returns: + string - the random string + error - an error stemming from an invalid parameter within underlying function, RandomSeed(...) +*/ +func RandomAscii(count int) (string, error) { + return Random(count, 32, 127, false, false) +} + +/* +RandomNumeric creates a random string whose length is the number of characters specified. +Characters will be chosen from the set of numeric characters. + +Parameter: + count - the length of random string to create + +Returns: + string - the random string + error - an error stemming from an invalid parameter within underlying function, RandomSeed(...) +*/ +func RandomNumeric(count int) (string, error) { + return Random(count, 0, 0, false, true) +} + +/* +RandomAlphabetic creates a random string whose length is the number of characters specified. +Characters will be chosen from the set of alpha-numeric characters as indicated by the arguments. + +Parameters: + count - the length of random string to create + letters - if true, generated string may include alphabetic characters + numbers - if true, generated string may include numeric characters + +Returns: + string - the random string + error - an error stemming from an invalid parameter within underlying function, RandomSeed(...) +*/ +func RandomAlphabetic(count int) (string, error) { + return Random(count, 0, 0, true, false) +} + +/* +RandomAlphaNumeric creates a random string whose length is the number of characters specified. +Characters will be chosen from the set of alpha-numeric characters. + +Parameter: + count - the length of random string to create + +Returns: + string - the random string + error - an error stemming from an invalid parameter within underlying function, RandomSeed(...) +*/ +func RandomAlphaNumeric(count int) (string, error) { + RandomString, err := Random(count, 0, 0, true, true) + if err != nil { + return "", fmt.Errorf("Error: %s", err) + } + match, err := regexp.MatchString("([0-9]+)", RandomString) + if err != nil { + panic(err) + } + + if !match { + //Get the position between 0 and the length of the string-1 to insert a random number + position := rand.Intn(count) + //Insert a random number between [0-9] in the position + RandomString = RandomString[:position] + string('0'+rand.Intn(10)) + RandomString[position+1:] + return RandomString, err + } + return RandomString, err + +} + +/* +RandomAlphaNumericCustom creates a random string whose length is the number of characters specified. +Characters will be chosen from the set of alpha-numeric characters as indicated by the arguments. + +Parameters: + count - the length of random string to create + letters - if true, generated string may include alphabetic characters + numbers - if true, generated string may include numeric characters + +Returns: + string - the random string + error - an error stemming from an invalid parameter within underlying function, RandomSeed(...) +*/ +func RandomAlphaNumericCustom(count int, letters bool, numbers bool) (string, error) { + return Random(count, 0, 0, letters, numbers) +} + +/* +Random creates a random string based on a variety of options, using default source of randomness. +This method has exactly the same semantics as RandomSeed(int, int, int, bool, bool, []char, *rand.Rand), but +instead of using an externally supplied source of randomness, it uses the internal *rand.Rand instance. + +Parameters: + count - the length of random string to create + start - the position in set of chars (ASCII/Unicode int) to start at + end - the position in set of chars (ASCII/Unicode int) to end before + letters - if true, generated string may include alphabetic characters + numbers - if true, generated string may include numeric characters + chars - the set of chars to choose randoms from. If nil, then it will use the set of all chars. + +Returns: + string - the random string + error - an error stemming from an invalid parameter within underlying function, RandomSeed(...) +*/ +func Random(count int, start int, end int, letters bool, numbers bool, chars ...rune) (string, error) { + return RandomSeed(count, start, end, letters, numbers, chars, RANDOM) +} + +/* +RandomSeed creates a random string based on a variety of options, using supplied source of randomness. +If the parameters start and end are both 0, start and end are set to ' ' and 'z', the ASCII printable characters, will be used, +unless letters and numbers are both false, in which case, start and end are set to 0 and math.MaxInt32, respectively. +If chars is not nil, characters stored in chars that are between start and end are chosen. +This method accepts a user-supplied *rand.Rand instance to use as a source of randomness. By seeding a single *rand.Rand instance +with a fixed seed and using it for each call, the same random sequence of strings can be generated repeatedly and predictably. + +Parameters: + count - the length of random string to create + start - the position in set of chars (ASCII/Unicode decimals) to start at + end - the position in set of chars (ASCII/Unicode decimals) to end before + letters - if true, generated string may include alphabetic characters + numbers - if true, generated string may include numeric characters + chars - the set of chars to choose randoms from. If nil, then it will use the set of all chars. + random - a source of randomness. + +Returns: + string - the random string + error - an error stemming from invalid parameters: if count < 0; or the provided chars array is empty; or end <= start; or end > len(chars) +*/ +func RandomSeed(count int, start int, end int, letters bool, numbers bool, chars []rune, random *rand.Rand) (string, error) { + + if count == 0 { + return "", nil + } else if count < 0 { + err := fmt.Errorf("randomstringutils illegal argument: Requested random string length %v is less than 0.", count) // equiv to err := errors.New("...") + return "", err + } + if chars != nil && len(chars) == 0 { + err := fmt.Errorf("randomstringutils illegal argument: The chars array must not be empty") + return "", err + } + + if start == 0 && end == 0 { + if chars != nil { + end = len(chars) + } else { + if !letters && !numbers { + end = math.MaxInt32 + } else { + end = 'z' + 1 + start = ' ' + } + } + } else { + if end <= start { + err := fmt.Errorf("randomstringutils illegal argument: Parameter end (%v) must be greater than start (%v)", end, start) + return "", err + } + + if chars != nil && end > len(chars) { + err := fmt.Errorf("randomstringutils illegal argument: Parameter end (%v) cannot be greater than len(chars) (%v)", end, len(chars)) + return "", err + } + } + + buffer := make([]rune, count) + gap := end - start + + // high-surrogates range, (\uD800-\uDBFF) = 55296 - 56319 + // low-surrogates range, (\uDC00-\uDFFF) = 56320 - 57343 + + for count != 0 { + count-- + var ch rune + if chars == nil { + ch = rune(random.Intn(gap) + start) + } else { + ch = chars[random.Intn(gap)+start] + } + + if letters && unicode.IsLetter(ch) || numbers && unicode.IsDigit(ch) || !letters && !numbers { + if ch >= 56320 && ch <= 57343 { // low surrogate range + if count == 0 { + count++ + } else { + // Insert low surrogate + buffer[count] = ch + count-- + // Insert high surrogate + buffer[count] = rune(55296 + random.Intn(128)) + } + } else if ch >= 55296 && ch <= 56191 { // High surrogates range (Partial) + if count == 0 { + count++ + } else { + // Insert low surrogate + buffer[count] = rune(56320 + random.Intn(128)) + count-- + // Insert high surrogate + buffer[count] = ch + } + } else if ch >= 56192 && ch <= 56319 { + // private high surrogate, skip it + count++ + } else { + // not one of the surrogates* + buffer[count] = ch + } + } else { + count++ + } + } + return string(buffer), nil +} diff --git a/vendor/github.com/Masterminds/goutils/stringutils.go b/vendor/github.com/Masterminds/goutils/stringutils.go new file mode 100644 index 000000000..5037c4516 --- /dev/null +++ b/vendor/github.com/Masterminds/goutils/stringutils.go @@ -0,0 +1,224 @@ +/* +Copyright 2014 Alexander Okoli + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package goutils + +import ( + "bytes" + "fmt" + "strings" + "unicode" +) + +// Typically returned by functions where a searched item cannot be found +const INDEX_NOT_FOUND = -1 + +/* +Abbreviate abbreviates a string using ellipses. This will turn the string "Now is the time for all good men" into "Now is the time for..." + +Specifically, the algorithm is as follows: + + - If str is less than maxWidth characters long, return it. + - Else abbreviate it to (str[0:maxWidth - 3] + "..."). + - If maxWidth is less than 4, return an illegal argument error. + - In no case will it return a string of length greater than maxWidth. + +Parameters: + str - the string to check + maxWidth - maximum length of result string, must be at least 4 + +Returns: + string - abbreviated string + error - if the width is too small +*/ +func Abbreviate(str string, maxWidth int) (string, error) { + return AbbreviateFull(str, 0, maxWidth) +} + +/* +AbbreviateFull abbreviates a string using ellipses. This will turn the string "Now is the time for all good men" into "...is the time for..." +This function works like Abbreviate(string, int), but allows you to specify a "left edge" offset. Note that this left edge is not +necessarily going to be the leftmost character in the result, or the first character following the ellipses, but it will appear +somewhere in the result. +In no case will it return a string of length greater than maxWidth. + +Parameters: + str - the string to check + offset - left edge of source string + maxWidth - maximum length of result string, must be at least 4 + +Returns: + string - abbreviated string + error - if the width is too small +*/ +func AbbreviateFull(str string, offset int, maxWidth int) (string, error) { + if str == "" { + return "", nil + } + if maxWidth < 4 { + err := fmt.Errorf("stringutils illegal argument: Minimum abbreviation width is 4") + return "", err + } + if len(str) <= maxWidth { + return str, nil + } + if offset > len(str) { + offset = len(str) + } + if len(str)-offset < (maxWidth - 3) { // 15 - 5 < 10 - 3 = 10 < 7 + offset = len(str) - (maxWidth - 3) + } + abrevMarker := "..." + if offset <= 4 { + return str[0:maxWidth-3] + abrevMarker, nil // str.substring(0, maxWidth - 3) + abrevMarker; + } + if maxWidth < 7 { + err := fmt.Errorf("stringutils illegal argument: Minimum abbreviation width with offset is 7") + return "", err + } + if (offset + maxWidth - 3) < len(str) { // 5 + (10-3) < 15 = 12 < 15 + abrevStr, _ := Abbreviate(str[offset:len(str)], (maxWidth - 3)) + return abrevMarker + abrevStr, nil // abrevMarker + abbreviate(str.substring(offset), maxWidth - 3); + } + return abrevMarker + str[(len(str)-(maxWidth-3)):len(str)], nil // abrevMarker + str.substring(str.length() - (maxWidth - 3)); +} + +/* +DeleteWhiteSpace deletes all whitespaces from a string as defined by unicode.IsSpace(rune). +It returns the string without whitespaces. + +Parameter: + str - the string to delete whitespace from, may be nil + +Returns: + the string without whitespaces +*/ +func DeleteWhiteSpace(str string) string { + if str == "" { + return str + } + sz := len(str) + var chs bytes.Buffer + count := 0 + for i := 0; i < sz; i++ { + ch := rune(str[i]) + if !unicode.IsSpace(ch) { + chs.WriteRune(ch) + count++ + } + } + if count == sz { + return str + } + return chs.String() +} + +/* +IndexOfDifference compares two strings, and returns the index at which the strings begin to differ. + +Parameters: + str1 - the first string + str2 - the second string + +Returns: + the index where str1 and str2 begin to differ; -1 if they are equal +*/ +func IndexOfDifference(str1 string, str2 string) int { + if str1 == str2 { + return INDEX_NOT_FOUND + } + if IsEmpty(str1) || IsEmpty(str2) { + return 0 + } + var i int + for i = 0; i < len(str1) && i < len(str2); i++ { + if rune(str1[i]) != rune(str2[i]) { + break + } + } + if i < len(str2) || i < len(str1) { + return i + } + return INDEX_NOT_FOUND +} + +/* +IsBlank checks if a string is whitespace or empty (""). Observe the following behavior: + + goutils.IsBlank("") = true + goutils.IsBlank(" ") = true + goutils.IsBlank("bob") = false + goutils.IsBlank(" bob ") = false + +Parameter: + str - the string to check + +Returns: + true - if the string is whitespace or empty ("") +*/ +func IsBlank(str string) bool { + strLen := len(str) + if str == "" || strLen == 0 { + return true + } + for i := 0; i < strLen; i++ { + if unicode.IsSpace(rune(str[i])) == false { + return false + } + } + return true +} + +/* +IndexOf returns the index of the first instance of sub in str, with the search beginning from the +index start point specified. -1 is returned if sub is not present in str. + +An empty string ("") will return -1 (INDEX_NOT_FOUND). A negative start position is treated as zero. +A start position greater than the string length returns -1. + +Parameters: + str - the string to check + sub - the substring to find + start - the start position; negative treated as zero + +Returns: + the first index where the sub string was found (always >= start) +*/ +func IndexOf(str string, sub string, start int) int { + + if start < 0 { + start = 0 + } + + if len(str) < start { + return INDEX_NOT_FOUND + } + + if IsEmpty(str) || IsEmpty(sub) { + return INDEX_NOT_FOUND + } + + partialIndex := strings.Index(str[start:len(str)], sub) + if partialIndex == -1 { + return INDEX_NOT_FOUND + } + return partialIndex + start +} + +// IsEmpty checks if a string is empty (""). Returns true if empty, and false otherwise. +func IsEmpty(str string) bool { + return len(str) == 0 +} diff --git a/vendor/github.com/Masterminds/goutils/wordutils.go b/vendor/github.com/Masterminds/goutils/wordutils.go new file mode 100644 index 000000000..034cad8e2 --- /dev/null +++ b/vendor/github.com/Masterminds/goutils/wordutils.go @@ -0,0 +1,357 @@ +/* +Copyright 2014 Alexander Okoli + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +/* +Package goutils provides utility functions to manipulate strings in various ways. +The code snippets below show examples of how to use goutils. Some functions return +errors while others do not, so usage would vary as a result. + +Example: + + package main + + import ( + "fmt" + "github.com/aokoli/goutils" + ) + + func main() { + + // EXAMPLE 1: A goutils function which returns no errors + fmt.Println (goutils.Initials("John Doe Foo")) // Prints out "JDF" + + + + // EXAMPLE 2: A goutils function which returns an error + rand1, err1 := goutils.Random (-1, 0, 0, true, true) + + if err1 != nil { + fmt.Println(err1) // Prints out error message because -1 was entered as the first parameter in goutils.Random(...) + } else { + fmt.Println(rand1) + } + } +*/ +package goutils + +import ( + "bytes" + "strings" + "unicode" +) + +// VERSION indicates the current version of goutils +const VERSION = "1.0.0" + +/* +Wrap wraps a single line of text, identifying words by ' '. +New lines will be separated by '\n'. Very long words, such as URLs will not be wrapped. +Leading spaces on a new line are stripped. Trailing spaces are not stripped. + +Parameters: + str - the string to be word wrapped + wrapLength - the column (a column can fit only one character) to wrap the words at, less than 1 is treated as 1 + +Returns: + a line with newlines inserted +*/ +func Wrap(str string, wrapLength int) string { + return WrapCustom(str, wrapLength, "", false) +} + +/* +WrapCustom wraps a single line of text, identifying words by ' '. +Leading spaces on a new line are stripped. Trailing spaces are not stripped. + +Parameters: + str - the string to be word wrapped + wrapLength - the column number (a column can fit only one character) to wrap the words at, less than 1 is treated as 1 + newLineStr - the string to insert for a new line, "" uses '\n' + wrapLongWords - true if long words (such as URLs) should be wrapped + +Returns: + a line with newlines inserted +*/ +func WrapCustom(str string, wrapLength int, newLineStr string, wrapLongWords bool) string { + + if str == "" { + return "" + } + if newLineStr == "" { + newLineStr = "\n" // TODO Assumes "\n" is seperator. Explore SystemUtils.LINE_SEPARATOR from Apache Commons + } + if wrapLength < 1 { + wrapLength = 1 + } + + inputLineLength := len(str) + offset := 0 + + var wrappedLine bytes.Buffer + + for inputLineLength-offset > wrapLength { + + if rune(str[offset]) == ' ' { + offset++ + continue + } + + end := wrapLength + offset + 1 + spaceToWrapAt := strings.LastIndex(str[offset:end], " ") + offset + + if spaceToWrapAt >= offset { + // normal word (not longer than wrapLength) + wrappedLine.WriteString(str[offset:spaceToWrapAt]) + wrappedLine.WriteString(newLineStr) + offset = spaceToWrapAt + 1 + + } else { + // long word or URL + if wrapLongWords { + end := wrapLength + offset + // long words are wrapped one line at a time + wrappedLine.WriteString(str[offset:end]) + wrappedLine.WriteString(newLineStr) + offset += wrapLength + } else { + // long words aren't wrapped, just extended beyond limit + end := wrapLength + offset + index := strings.IndexRune(str[end:len(str)], ' ') + if index == -1 { + wrappedLine.WriteString(str[offset:len(str)]) + offset = inputLineLength + } else { + spaceToWrapAt = index + end + wrappedLine.WriteString(str[offset:spaceToWrapAt]) + wrappedLine.WriteString(newLineStr) + offset = spaceToWrapAt + 1 + } + } + } + } + + wrappedLine.WriteString(str[offset:len(str)]) + + return wrappedLine.String() + +} + +/* +Capitalize capitalizes all the delimiter separated words in a string. Only the first letter of each word is changed. +To convert the rest of each word to lowercase at the same time, use CapitalizeFully(str string, delimiters ...rune). +The delimiters represent a set of characters understood to separate words. The first string character +and the first non-delimiter character after a delimiter will be capitalized. A "" input string returns "". +Capitalization uses the Unicode title case, normally equivalent to upper case. + +Parameters: + str - the string to capitalize + delimiters - set of characters to determine capitalization, exclusion of this parameter means whitespace would be delimeter + +Returns: + capitalized string +*/ +func Capitalize(str string, delimiters ...rune) string { + + var delimLen int + + if delimiters == nil { + delimLen = -1 + } else { + delimLen = len(delimiters) + } + + if str == "" || delimLen == 0 { + return str + } + + buffer := []rune(str) + capitalizeNext := true + for i := 0; i < len(buffer); i++ { + ch := buffer[i] + if isDelimiter(ch, delimiters...) { + capitalizeNext = true + } else if capitalizeNext { + buffer[i] = unicode.ToTitle(ch) + capitalizeNext = false + } + } + return string(buffer) + +} + +/* +CapitalizeFully converts all the delimiter separated words in a string into capitalized words, that is each word is made up of a +titlecase character and then a series of lowercase characters. The delimiters represent a set of characters understood +to separate words. The first string character and the first non-delimiter character after a delimiter will be capitalized. +Capitalization uses the Unicode title case, normally equivalent to upper case. + +Parameters: + str - the string to capitalize fully + delimiters - set of characters to determine capitalization, exclusion of this parameter means whitespace would be delimeter + +Returns: + capitalized string +*/ +func CapitalizeFully(str string, delimiters ...rune) string { + + var delimLen int + + if delimiters == nil { + delimLen = -1 + } else { + delimLen = len(delimiters) + } + + if str == "" || delimLen == 0 { + return str + } + str = strings.ToLower(str) + return Capitalize(str, delimiters...) +} + +/* +Uncapitalize uncapitalizes all the whitespace separated words in a string. Only the first letter of each word is changed. +The delimiters represent a set of characters understood to separate words. The first string character and the first non-delimiter +character after a delimiter will be uncapitalized. Whitespace is defined by unicode.IsSpace(char). + +Parameters: + str - the string to uncapitalize fully + delimiters - set of characters to determine capitalization, exclusion of this parameter means whitespace would be delimeter + +Returns: + uncapitalized string +*/ +func Uncapitalize(str string, delimiters ...rune) string { + + var delimLen int + + if delimiters == nil { + delimLen = -1 + } else { + delimLen = len(delimiters) + } + + if str == "" || delimLen == 0 { + return str + } + + buffer := []rune(str) + uncapitalizeNext := true // TODO Always makes capitalize/un apply to first char. + for i := 0; i < len(buffer); i++ { + ch := buffer[i] + if isDelimiter(ch, delimiters...) { + uncapitalizeNext = true + } else if uncapitalizeNext { + buffer[i] = unicode.ToLower(ch) + uncapitalizeNext = false + } + } + return string(buffer) +} + +/* +SwapCase swaps the case of a string using a word based algorithm. + +Conversion algorithm: + + Upper case character converts to Lower case + Title case character converts to Lower case + Lower case character after Whitespace or at start converts to Title case + Other Lower case character converts to Upper case + Whitespace is defined by unicode.IsSpace(char). + +Parameters: + str - the string to swap case + +Returns: + the changed string +*/ +func SwapCase(str string) string { + if str == "" { + return str + } + buffer := []rune(str) + + whitespace := true + + for i := 0; i < len(buffer); i++ { + ch := buffer[i] + if unicode.IsUpper(ch) { + buffer[i] = unicode.ToLower(ch) + whitespace = false + } else if unicode.IsTitle(ch) { + buffer[i] = unicode.ToLower(ch) + whitespace = false + } else if unicode.IsLower(ch) { + if whitespace { + buffer[i] = unicode.ToTitle(ch) + whitespace = false + } else { + buffer[i] = unicode.ToUpper(ch) + } + } else { + whitespace = unicode.IsSpace(ch) + } + } + return string(buffer) +} + +/* +Initials extracts the initial letters from each word in the string. The first letter of the string and all first +letters after the defined delimiters are returned as a new string. Their case is not changed. If the delimiters +parameter is excluded, then Whitespace is used. Whitespace is defined by unicode.IsSpacea(char). An empty delimiter array returns an empty string. + +Parameters: + str - the string to get initials from + delimiters - set of characters to determine words, exclusion of this parameter means whitespace would be delimeter +Returns: + string of initial letters +*/ +func Initials(str string, delimiters ...rune) string { + if str == "" { + return str + } + if delimiters != nil && len(delimiters) == 0 { + return "" + } + strLen := len(str) + var buf bytes.Buffer + lastWasGap := true + for i := 0; i < strLen; i++ { + ch := rune(str[i]) + + if isDelimiter(ch, delimiters...) { + lastWasGap = true + } else if lastWasGap { + buf.WriteRune(ch) + lastWasGap = false + } + } + return buf.String() +} + +// private function (lower case func name) +func isDelimiter(ch rune, delimiters ...rune) bool { + if delimiters == nil { + return unicode.IsSpace(ch) + } + for _, delimiter := range delimiters { + if ch == delimiter { + return true + } + } + return false +} diff --git a/vendor/github.com/Masterminds/semver/.travis.yml b/vendor/github.com/Masterminds/semver/.travis.yml new file mode 100644 index 000000000..096369d44 --- /dev/null +++ b/vendor/github.com/Masterminds/semver/.travis.yml @@ -0,0 +1,29 @@ +language: go + +go: + - 1.6.x + - 1.7.x + - 1.8.x + - 1.9.x + - 1.10.x + - 1.11.x + - 1.12.x + - tip + +# Setting sudo access to false will let Travis CI use containers rather than +# VMs to run the tests. For more details see: +# - http://docs.travis-ci.com/user/workers/container-based-infrastructure/ +# - http://docs.travis-ci.com/user/workers/standard-infrastructure/ +sudo: false + +script: + - make setup + - make test + +notifications: + webhooks: + urls: + - https://webhooks.gitter.im/e/06e3328629952dabe3e0 + on_success: change # options: [always|never|change] default: always + on_failure: always # options: [always|never|change] default: always + on_start: never # options: [always|never|change] default: always diff --git a/vendor/github.com/Masterminds/semver/CHANGELOG.md b/vendor/github.com/Masterminds/semver/CHANGELOG.md new file mode 100644 index 000000000..e405c9a84 --- /dev/null +++ b/vendor/github.com/Masterminds/semver/CHANGELOG.md @@ -0,0 +1,109 @@ +# 1.5.0 (2019-09-11) + +## Added + +- #103: Add basic fuzzing for `NewVersion()` (thanks @jesse-c) + +## Changed + +- #82: Clarify wildcard meaning in range constraints and update tests for it (thanks @greysteil) +- #83: Clarify caret operator range for pre-1.0.0 dependencies (thanks @greysteil) +- #72: Adding docs comment pointing to vert for a cli +- #71: Update the docs on pre-release comparator handling +- #89: Test with new go versions (thanks @thedevsaddam) +- #87: Added $ to ValidPrerelease for better validation (thanks @jeremycarroll) + +## Fixed + +- #78: Fix unchecked error in example code (thanks @ravron) +- #70: Fix the handling of pre-releases and the 0.0.0 release edge case +- #97: Fixed copyright file for proper display on GitHub +- #107: Fix handling prerelease when sorting alphanum and num +- #109: Fixed where Validate sometimes returns wrong message on error + +# 1.4.2 (2018-04-10) + +## Changed +- #72: Updated the docs to point to vert for a console appliaction +- #71: Update the docs on pre-release comparator handling + +## Fixed +- #70: Fix the handling of pre-releases and the 0.0.0 release edge case + +# 1.4.1 (2018-04-02) + +## Fixed +- Fixed #64: Fix pre-release precedence issue (thanks @uudashr) + +# 1.4.0 (2017-10-04) + +## Changed +- #61: Update NewVersion to parse ints with a 64bit int size (thanks @zknill) + +# 1.3.1 (2017-07-10) + +## Fixed +- Fixed #57: number comparisons in prerelease sometimes inaccurate + +# 1.3.0 (2017-05-02) + +## Added +- #45: Added json (un)marshaling support (thanks @mh-cbon) +- Stability marker. See https://masterminds.github.io/stability/ + +## Fixed +- #51: Fix handling of single digit tilde constraint (thanks @dgodd) + +## Changed +- #55: The godoc icon moved from png to svg + +# 1.2.3 (2017-04-03) + +## Fixed +- #46: Fixed 0.x.x and 0.0.x in constraints being treated as * + +# Release 1.2.2 (2016-12-13) + +## Fixed +- #34: Fixed issue where hyphen range was not working with pre-release parsing. + +# Release 1.2.1 (2016-11-28) + +## Fixed +- #24: Fixed edge case issue where constraint "> 0" does not handle "0.0.1-alpha" + properly. + +# Release 1.2.0 (2016-11-04) + +## Added +- #20: Added MustParse function for versions (thanks @adamreese) +- #15: Added increment methods on versions (thanks @mh-cbon) + +## Fixed +- Issue #21: Per the SemVer spec (section 9) a pre-release is unstable and + might not satisfy the intended compatibility. The change here ignores pre-releases + on constraint checks (e.g., ~ or ^) when a pre-release is not part of the + constraint. For example, `^1.2.3` will ignore pre-releases while + `^1.2.3-alpha` will include them. + +# Release 1.1.1 (2016-06-30) + +## Changed +- Issue #9: Speed up version comparison performance (thanks @sdboyer) +- Issue #8: Added benchmarks (thanks @sdboyer) +- Updated Go Report Card URL to new location +- Updated Readme to add code snippet formatting (thanks @mh-cbon) +- Updating tagging to v[SemVer] structure for compatibility with other tools. + +# Release 1.1.0 (2016-03-11) + +- Issue #2: Implemented validation to provide reasons a versions failed a + constraint. + +# Release 1.0.1 (2015-12-31) + +- Fixed #1: * constraint failing on valid versions. + +# Release 1.0.0 (2015-10-20) + +- Initial release diff --git a/vendor/github.com/Masterminds/semver/LICENSE.txt b/vendor/github.com/Masterminds/semver/LICENSE.txt new file mode 100644 index 000000000..9ff7da9c4 --- /dev/null +++ b/vendor/github.com/Masterminds/semver/LICENSE.txt @@ -0,0 +1,19 @@ +Copyright (C) 2014-2019, Matt Butcher and Matt Farina + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/github.com/Masterminds/semver/Makefile b/vendor/github.com/Masterminds/semver/Makefile new file mode 100644 index 000000000..a7a1b4e36 --- /dev/null +++ b/vendor/github.com/Masterminds/semver/Makefile @@ -0,0 +1,36 @@ +.PHONY: setup +setup: + go get -u gopkg.in/alecthomas/gometalinter.v1 + gometalinter.v1 --install + +.PHONY: test +test: validate lint + @echo "==> Running tests" + go test -v + +.PHONY: validate +validate: + @echo "==> Running static validations" + @gometalinter.v1 \ + --disable-all \ + --enable deadcode \ + --severity deadcode:error \ + --enable gofmt \ + --enable gosimple \ + --enable ineffassign \ + --enable misspell \ + --enable vet \ + --tests \ + --vendor \ + --deadline 60s \ + ./... || exit_code=1 + +.PHONY: lint +lint: + @echo "==> Running linters" + @gometalinter.v1 \ + --disable-all \ + --enable golint \ + --vendor \ + --deadline 60s \ + ./... || : diff --git a/vendor/github.com/Masterminds/semver/README.md b/vendor/github.com/Masterminds/semver/README.md new file mode 100644 index 000000000..1b52d2f43 --- /dev/null +++ b/vendor/github.com/Masterminds/semver/README.md @@ -0,0 +1,194 @@ +# SemVer + +The `semver` package provides the ability to work with [Semantic Versions](http://semver.org) in Go. Specifically it provides the ability to: + +* Parse semantic versions +* Sort semantic versions +* Check if a semantic version fits within a set of constraints +* Optionally work with a `v` prefix + +[![Stability: +Active](https://masterminds.github.io/stability/active.svg)](https://masterminds.github.io/stability/active.html) +[![Build Status](https://travis-ci.org/Masterminds/semver.svg)](https://travis-ci.org/Masterminds/semver) [![Build status](https://ci.appveyor.com/api/projects/status/jfk66lib7hb985k8/branch/master?svg=true&passingText=windows%20build%20passing&failingText=windows%20build%20failing)](https://ci.appveyor.com/project/mattfarina/semver/branch/master) [![GoDoc](https://godoc.org/github.com/Masterminds/semver?status.svg)](https://godoc.org/github.com/Masterminds/semver) [![Go Report Card](https://goreportcard.com/badge/github.com/Masterminds/semver)](https://goreportcard.com/report/github.com/Masterminds/semver) + +If you are looking for a command line tool for version comparisons please see +[vert](https://github.com/Masterminds/vert) which uses this library. + +## Parsing Semantic Versions + +To parse a semantic version use the `NewVersion` function. For example, + +```go + v, err := semver.NewVersion("1.2.3-beta.1+build345") +``` + +If there is an error the version wasn't parseable. The version object has methods +to get the parts of the version, compare it to other versions, convert the +version back into a string, and get the original string. For more details +please see the [documentation](https://godoc.org/github.com/Masterminds/semver). + +## Sorting Semantic Versions + +A set of versions can be sorted using the [`sort`](https://golang.org/pkg/sort/) +package from the standard library. For example, + +```go + raw := []string{"1.2.3", "1.0", "1.3", "2", "0.4.2",} + vs := make([]*semver.Version, len(raw)) + for i, r := range raw { + v, err := semver.NewVersion(r) + if err != nil { + t.Errorf("Error parsing version: %s", err) + } + + vs[i] = v + } + + sort.Sort(semver.Collection(vs)) +``` + +## Checking Version Constraints + +Checking a version against version constraints is one of the most featureful +parts of the package. + +```go + c, err := semver.NewConstraint(">= 1.2.3") + if err != nil { + // Handle constraint not being parseable. + } + + v, _ := semver.NewVersion("1.3") + if err != nil { + // Handle version not being parseable. + } + // Check if the version meets the constraints. The a variable will be true. + a := c.Check(v) +``` + +## Basic Comparisons + +There are two elements to the comparisons. First, a comparison string is a list +of comma separated and comparisons. These are then separated by || separated or +comparisons. For example, `">= 1.2, < 3.0.0 || >= 4.2.3"` is looking for a +comparison that's greater than or equal to 1.2 and less than 3.0.0 or is +greater than or equal to 4.2.3. + +The basic comparisons are: + +* `=`: equal (aliased to no operator) +* `!=`: not equal +* `>`: greater than +* `<`: less than +* `>=`: greater than or equal to +* `<=`: less than or equal to + +## Working With Pre-release Versions + +Pre-releases, for those not familiar with them, are used for software releases +prior to stable or generally available releases. Examples of pre-releases include +development, alpha, beta, and release candidate releases. A pre-release may be +a version such as `1.2.3-beta.1` while the stable release would be `1.2.3`. In the +order of precidence, pre-releases come before their associated releases. In this +example `1.2.3-beta.1 < 1.2.3`. + +According to the Semantic Version specification pre-releases may not be +API compliant with their release counterpart. It says, + +> A pre-release version indicates that the version is unstable and might not satisfy the intended compatibility requirements as denoted by its associated normal version. + +SemVer comparisons without a pre-release comparator will skip pre-release versions. +For example, `>=1.2.3` will skip pre-releases when looking at a list of releases +while `>=1.2.3-0` will evaluate and find pre-releases. + +The reason for the `0` as a pre-release version in the example comparison is +because pre-releases can only contain ASCII alphanumerics and hyphens (along with +`.` separators), per the spec. Sorting happens in ASCII sort order, again per the spec. The lowest character is a `0` in ASCII sort order (see an [ASCII Table](http://www.asciitable.com/)) + +Understanding ASCII sort ordering is important because A-Z comes before a-z. That +means `>=1.2.3-BETA` will return `1.2.3-alpha`. What you might expect from case +sensitivity doesn't apply here. This is due to ASCII sort ordering which is what +the spec specifies. + +## Hyphen Range Comparisons + +There are multiple methods to handle ranges and the first is hyphens ranges. +These look like: + +* `1.2 - 1.4.5` which is equivalent to `>= 1.2, <= 1.4.5` +* `2.3.4 - 4.5` which is equivalent to `>= 2.3.4, <= 4.5` + +## Wildcards In Comparisons + +The `x`, `X`, and `*` characters can be used as a wildcard character. This works +for all comparison operators. When used on the `=` operator it falls +back to the pack level comparison (see tilde below). For example, + +* `1.2.x` is equivalent to `>= 1.2.0, < 1.3.0` +* `>= 1.2.x` is equivalent to `>= 1.2.0` +* `<= 2.x` is equivalent to `< 3` +* `*` is equivalent to `>= 0.0.0` + +## Tilde Range Comparisons (Patch) + +The tilde (`~`) comparison operator is for patch level ranges when a minor +version is specified and major level changes when the minor number is missing. +For example, + +* `~1.2.3` is equivalent to `>= 1.2.3, < 1.3.0` +* `~1` is equivalent to `>= 1, < 2` +* `~2.3` is equivalent to `>= 2.3, < 2.4` +* `~1.2.x` is equivalent to `>= 1.2.0, < 1.3.0` +* `~1.x` is equivalent to `>= 1, < 2` + +## Caret Range Comparisons (Major) + +The caret (`^`) comparison operator is for major level changes. This is useful +when comparisons of API versions as a major change is API breaking. For example, + +* `^1.2.3` is equivalent to `>= 1.2.3, < 2.0.0` +* `^0.0.1` is equivalent to `>= 0.0.1, < 1.0.0` +* `^1.2.x` is equivalent to `>= 1.2.0, < 2.0.0` +* `^2.3` is equivalent to `>= 2.3, < 3` +* `^2.x` is equivalent to `>= 2.0.0, < 3` + +# Validation + +In addition to testing a version against a constraint, a version can be validated +against a constraint. When validation fails a slice of errors containing why a +version didn't meet the constraint is returned. For example, + +```go + c, err := semver.NewConstraint("<= 1.2.3, >= 1.4") + if err != nil { + // Handle constraint not being parseable. + } + + v, _ := semver.NewVersion("1.3") + if err != nil { + // Handle version not being parseable. + } + + // Validate a version against a constraint. + a, msgs := c.Validate(v) + // a is false + for _, m := range msgs { + fmt.Println(m) + + // Loops over the errors which would read + // "1.3 is greater than 1.2.3" + // "1.3 is less than 1.4" + } +``` + +# Fuzzing + + [dvyukov/go-fuzz](https://github.com/dvyukov/go-fuzz) is used for fuzzing. + +1. `go-fuzz-build` +2. `go-fuzz -workdir=fuzz` + +# Contribute + +If you find an issue or want to contribute please file an [issue](https://github.com/Masterminds/semver/issues) +or [create a pull request](https://github.com/Masterminds/semver/pulls). diff --git a/vendor/github.com/Masterminds/semver/appveyor.yml b/vendor/github.com/Masterminds/semver/appveyor.yml new file mode 100644 index 000000000..b2778df15 --- /dev/null +++ b/vendor/github.com/Masterminds/semver/appveyor.yml @@ -0,0 +1,44 @@ +version: build-{build}.{branch} + +clone_folder: C:\gopath\src\github.com\Masterminds\semver +shallow_clone: true + +environment: + GOPATH: C:\gopath + +platform: + - x64 + +install: + - go version + - go env + - go get -u gopkg.in/alecthomas/gometalinter.v1 + - set PATH=%PATH%;%GOPATH%\bin + - gometalinter.v1.exe --install + +build_script: + - go install -v ./... + +test_script: + - "gometalinter.v1 \ + --disable-all \ + --enable deadcode \ + --severity deadcode:error \ + --enable gofmt \ + --enable gosimple \ + --enable ineffassign \ + --enable misspell \ + --enable vet \ + --tests \ + --vendor \ + --deadline 60s \ + ./... || exit_code=1" + - "gometalinter.v1 \ + --disable-all \ + --enable golint \ + --vendor \ + --deadline 60s \ + ./... || :" + - go test -v + +deploy: off diff --git a/vendor/github.com/Masterminds/semver/collection.go b/vendor/github.com/Masterminds/semver/collection.go new file mode 100644 index 000000000..a78235895 --- /dev/null +++ b/vendor/github.com/Masterminds/semver/collection.go @@ -0,0 +1,24 @@ +package semver + +// Collection is a collection of Version instances and implements the sort +// interface. See the sort package for more details. +// https://golang.org/pkg/sort/ +type Collection []*Version + +// Len returns the length of a collection. The number of Version instances +// on the slice. +func (c Collection) Len() int { + return len(c) +} + +// Less is needed for the sort interface to compare two Version objects on the +// slice. If checks if one is less than the other. +func (c Collection) Less(i, j int) bool { + return c[i].LessThan(c[j]) +} + +// Swap is needed for the sort interface to replace the Version objects +// at two different positions in the slice. +func (c Collection) Swap(i, j int) { + c[i], c[j] = c[j], c[i] +} diff --git a/vendor/github.com/Masterminds/semver/constraints.go b/vendor/github.com/Masterminds/semver/constraints.go new file mode 100644 index 000000000..b94b93413 --- /dev/null +++ b/vendor/github.com/Masterminds/semver/constraints.go @@ -0,0 +1,423 @@ +package semver + +import ( + "errors" + "fmt" + "regexp" + "strings" +) + +// Constraints is one or more constraint that a semantic version can be +// checked against. +type Constraints struct { + constraints [][]*constraint +} + +// NewConstraint returns a Constraints instance that a Version instance can +// be checked against. If there is a parse error it will be returned. +func NewConstraint(c string) (*Constraints, error) { + + // Rewrite - ranges into a comparison operation. + c = rewriteRange(c) + + ors := strings.Split(c, "||") + or := make([][]*constraint, len(ors)) + for k, v := range ors { + cs := strings.Split(v, ",") + result := make([]*constraint, len(cs)) + for i, s := range cs { + pc, err := parseConstraint(s) + if err != nil { + return nil, err + } + + result[i] = pc + } + or[k] = result + } + + o := &Constraints{constraints: or} + return o, nil +} + +// Check tests if a version satisfies the constraints. +func (cs Constraints) Check(v *Version) bool { + // loop over the ORs and check the inner ANDs + for _, o := range cs.constraints { + joy := true + for _, c := range o { + if !c.check(v) { + joy = false + break + } + } + + if joy { + return true + } + } + + return false +} + +// Validate checks if a version satisfies a constraint. If not a slice of +// reasons for the failure are returned in addition to a bool. +func (cs Constraints) Validate(v *Version) (bool, []error) { + // loop over the ORs and check the inner ANDs + var e []error + + // Capture the prerelease message only once. When it happens the first time + // this var is marked + var prerelesase bool + for _, o := range cs.constraints { + joy := true + for _, c := range o { + // Before running the check handle the case there the version is + // a prerelease and the check is not searching for prereleases. + if c.con.pre == "" && v.pre != "" { + if !prerelesase { + em := fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v) + e = append(e, em) + prerelesase = true + } + joy = false + + } else { + + if !c.check(v) { + em := fmt.Errorf(c.msg, v, c.orig) + e = append(e, em) + joy = false + } + } + } + + if joy { + return true, []error{} + } + } + + return false, e +} + +var constraintOps map[string]cfunc +var constraintMsg map[string]string +var constraintRegex *regexp.Regexp + +func init() { + constraintOps = map[string]cfunc{ + "": constraintTildeOrEqual, + "=": constraintTildeOrEqual, + "!=": constraintNotEqual, + ">": constraintGreaterThan, + "<": constraintLessThan, + ">=": constraintGreaterThanEqual, + "=>": constraintGreaterThanEqual, + "<=": constraintLessThanEqual, + "=<": constraintLessThanEqual, + "~": constraintTilde, + "~>": constraintTilde, + "^": constraintCaret, + } + + constraintMsg = map[string]string{ + "": "%s is not equal to %s", + "=": "%s is not equal to %s", + "!=": "%s is equal to %s", + ">": "%s is less than or equal to %s", + "<": "%s is greater than or equal to %s", + ">=": "%s is less than %s", + "=>": "%s is less than %s", + "<=": "%s is greater than %s", + "=<": "%s is greater than %s", + "~": "%s does not have same major and minor version as %s", + "~>": "%s does not have same major and minor version as %s", + "^": "%s does not have same major version as %s", + } + + ops := make([]string, 0, len(constraintOps)) + for k := range constraintOps { + ops = append(ops, regexp.QuoteMeta(k)) + } + + constraintRegex = regexp.MustCompile(fmt.Sprintf( + `^\s*(%s)\s*(%s)\s*$`, + strings.Join(ops, "|"), + cvRegex)) + + constraintRangeRegex = regexp.MustCompile(fmt.Sprintf( + `\s*(%s)\s+-\s+(%s)\s*`, + cvRegex, cvRegex)) +} + +// An individual constraint +type constraint struct { + // The callback function for the restraint. It performs the logic for + // the constraint. + function cfunc + + msg string + + // The version used in the constraint check. For example, if a constraint + // is '<= 2.0.0' the con a version instance representing 2.0.0. + con *Version + + // The original parsed version (e.g., 4.x from != 4.x) + orig string + + // When an x is used as part of the version (e.g., 1.x) + minorDirty bool + dirty bool + patchDirty bool +} + +// Check if a version meets the constraint +func (c *constraint) check(v *Version) bool { + return c.function(v, c) +} + +type cfunc func(v *Version, c *constraint) bool + +func parseConstraint(c string) (*constraint, error) { + m := constraintRegex.FindStringSubmatch(c) + if m == nil { + return nil, fmt.Errorf("improper constraint: %s", c) + } + + ver := m[2] + orig := ver + minorDirty := false + patchDirty := false + dirty := false + if isX(m[3]) { + ver = "0.0.0" + dirty = true + } else if isX(strings.TrimPrefix(m[4], ".")) || m[4] == "" { + minorDirty = true + dirty = true + ver = fmt.Sprintf("%s.0.0%s", m[3], m[6]) + } else if isX(strings.TrimPrefix(m[5], ".")) { + dirty = true + patchDirty = true + ver = fmt.Sprintf("%s%s.0%s", m[3], m[4], m[6]) + } + + con, err := NewVersion(ver) + if err != nil { + + // The constraintRegex should catch any regex parsing errors. So, + // we should never get here. + return nil, errors.New("constraint Parser Error") + } + + cs := &constraint{ + function: constraintOps[m[1]], + msg: constraintMsg[m[1]], + con: con, + orig: orig, + minorDirty: minorDirty, + patchDirty: patchDirty, + dirty: dirty, + } + return cs, nil +} + +// Constraint functions +func constraintNotEqual(v *Version, c *constraint) bool { + if c.dirty { + + // If there is a pre-release on the version but the constraint isn't looking + // for them assume that pre-releases are not compatible. See issue 21 for + // more details. + if v.Prerelease() != "" && c.con.Prerelease() == "" { + return false + } + + if c.con.Major() != v.Major() { + return true + } + if c.con.Minor() != v.Minor() && !c.minorDirty { + return true + } else if c.minorDirty { + return false + } + + return false + } + + return !v.Equal(c.con) +} + +func constraintGreaterThan(v *Version, c *constraint) bool { + + // If there is a pre-release on the version but the constraint isn't looking + // for them assume that pre-releases are not compatible. See issue 21 for + // more details. + if v.Prerelease() != "" && c.con.Prerelease() == "" { + return false + } + + return v.Compare(c.con) == 1 +} + +func constraintLessThan(v *Version, c *constraint) bool { + // If there is a pre-release on the version but the constraint isn't looking + // for them assume that pre-releases are not compatible. See issue 21 for + // more details. + if v.Prerelease() != "" && c.con.Prerelease() == "" { + return false + } + + if !c.dirty { + return v.Compare(c.con) < 0 + } + + if v.Major() > c.con.Major() { + return false + } else if v.Minor() > c.con.Minor() && !c.minorDirty { + return false + } + + return true +} + +func constraintGreaterThanEqual(v *Version, c *constraint) bool { + + // If there is a pre-release on the version but the constraint isn't looking + // for them assume that pre-releases are not compatible. See issue 21 for + // more details. + if v.Prerelease() != "" && c.con.Prerelease() == "" { + return false + } + + return v.Compare(c.con) >= 0 +} + +func constraintLessThanEqual(v *Version, c *constraint) bool { + // If there is a pre-release on the version but the constraint isn't looking + // for them assume that pre-releases are not compatible. See issue 21 for + // more details. + if v.Prerelease() != "" && c.con.Prerelease() == "" { + return false + } + + if !c.dirty { + return v.Compare(c.con) <= 0 + } + + if v.Major() > c.con.Major() { + return false + } else if v.Minor() > c.con.Minor() && !c.minorDirty { + return false + } + + return true +} + +// ~*, ~>* --> >= 0.0.0 (any) +// ~2, ~2.x, ~2.x.x, ~>2, ~>2.x ~>2.x.x --> >=2.0.0, <3.0.0 +// ~2.0, ~2.0.x, ~>2.0, ~>2.0.x --> >=2.0.0, <2.1.0 +// ~1.2, ~1.2.x, ~>1.2, ~>1.2.x --> >=1.2.0, <1.3.0 +// ~1.2.3, ~>1.2.3 --> >=1.2.3, <1.3.0 +// ~1.2.0, ~>1.2.0 --> >=1.2.0, <1.3.0 +func constraintTilde(v *Version, c *constraint) bool { + // If there is a pre-release on the version but the constraint isn't looking + // for them assume that pre-releases are not compatible. See issue 21 for + // more details. + if v.Prerelease() != "" && c.con.Prerelease() == "" { + return false + } + + if v.LessThan(c.con) { + return false + } + + // ~0.0.0 is a special case where all constraints are accepted. It's + // equivalent to >= 0.0.0. + if c.con.Major() == 0 && c.con.Minor() == 0 && c.con.Patch() == 0 && + !c.minorDirty && !c.patchDirty { + return true + } + + if v.Major() != c.con.Major() { + return false + } + + if v.Minor() != c.con.Minor() && !c.minorDirty { + return false + } + + return true +} + +// When there is a .x (dirty) status it automatically opts in to ~. Otherwise +// it's a straight = +func constraintTildeOrEqual(v *Version, c *constraint) bool { + // If there is a pre-release on the version but the constraint isn't looking + // for them assume that pre-releases are not compatible. See issue 21 for + // more details. + if v.Prerelease() != "" && c.con.Prerelease() == "" { + return false + } + + if c.dirty { + c.msg = constraintMsg["~"] + return constraintTilde(v, c) + } + + return v.Equal(c.con) +} + +// ^* --> (any) +// ^2, ^2.x, ^2.x.x --> >=2.0.0, <3.0.0 +// ^2.0, ^2.0.x --> >=2.0.0, <3.0.0 +// ^1.2, ^1.2.x --> >=1.2.0, <2.0.0 +// ^1.2.3 --> >=1.2.3, <2.0.0 +// ^1.2.0 --> >=1.2.0, <2.0.0 +func constraintCaret(v *Version, c *constraint) bool { + // If there is a pre-release on the version but the constraint isn't looking + // for them assume that pre-releases are not compatible. See issue 21 for + // more details. + if v.Prerelease() != "" && c.con.Prerelease() == "" { + return false + } + + if v.LessThan(c.con) { + return false + } + + if v.Major() != c.con.Major() { + return false + } + + return true +} + +var constraintRangeRegex *regexp.Regexp + +const cvRegex string = `v?([0-9|x|X|\*]+)(\.[0-9|x|X|\*]+)?(\.[0-9|x|X|\*]+)?` + + `(-([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?` + + `(\+([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?` + +func isX(x string) bool { + switch x { + case "x", "*", "X": + return true + default: + return false + } +} + +func rewriteRange(i string) string { + m := constraintRangeRegex.FindAllStringSubmatch(i, -1) + if m == nil { + return i + } + o := i + for _, v := range m { + t := fmt.Sprintf(">= %s, <= %s", v[1], v[11]) + o = strings.Replace(o, v[0], t, 1) + } + + return o +} diff --git a/vendor/github.com/Masterminds/semver/doc.go b/vendor/github.com/Masterminds/semver/doc.go new file mode 100644 index 000000000..6a6c24c6d --- /dev/null +++ b/vendor/github.com/Masterminds/semver/doc.go @@ -0,0 +1,115 @@ +/* +Package semver provides the ability to work with Semantic Versions (http://semver.org) in Go. + +Specifically it provides the ability to: + + * Parse semantic versions + * Sort semantic versions + * Check if a semantic version fits within a set of constraints + * Optionally work with a `v` prefix + +Parsing Semantic Versions + +To parse a semantic version use the `NewVersion` function. For example, + + v, err := semver.NewVersion("1.2.3-beta.1+build345") + +If there is an error the version wasn't parseable. The version object has methods +to get the parts of the version, compare it to other versions, convert the +version back into a string, and get the original string. For more details +please see the documentation at https://godoc.org/github.com/Masterminds/semver. + +Sorting Semantic Versions + +A set of versions can be sorted using the `sort` package from the standard library. +For example, + + raw := []string{"1.2.3", "1.0", "1.3", "2", "0.4.2",} + vs := make([]*semver.Version, len(raw)) + for i, r := range raw { + v, err := semver.NewVersion(r) + if err != nil { + t.Errorf("Error parsing version: %s", err) + } + + vs[i] = v + } + + sort.Sort(semver.Collection(vs)) + +Checking Version Constraints + +Checking a version against version constraints is one of the most featureful +parts of the package. + + c, err := semver.NewConstraint(">= 1.2.3") + if err != nil { + // Handle constraint not being parseable. + } + + v, err := semver.NewVersion("1.3") + if err != nil { + // Handle version not being parseable. + } + // Check if the version meets the constraints. The a variable will be true. + a := c.Check(v) + +Basic Comparisons + +There are two elements to the comparisons. First, a comparison string is a list +of comma separated and comparisons. These are then separated by || separated or +comparisons. For example, `">= 1.2, < 3.0.0 || >= 4.2.3"` is looking for a +comparison that's greater than or equal to 1.2 and less than 3.0.0 or is +greater than or equal to 4.2.3. + +The basic comparisons are: + + * `=`: equal (aliased to no operator) + * `!=`: not equal + * `>`: greater than + * `<`: less than + * `>=`: greater than or equal to + * `<=`: less than or equal to + +Hyphen Range Comparisons + +There are multiple methods to handle ranges and the first is hyphens ranges. +These look like: + + * `1.2 - 1.4.5` which is equivalent to `>= 1.2, <= 1.4.5` + * `2.3.4 - 4.5` which is equivalent to `>= 2.3.4, <= 4.5` + +Wildcards In Comparisons + +The `x`, `X`, and `*` characters can be used as a wildcard character. This works +for all comparison operators. When used on the `=` operator it falls +back to the pack level comparison (see tilde below). For example, + + * `1.2.x` is equivalent to `>= 1.2.0, < 1.3.0` + * `>= 1.2.x` is equivalent to `>= 1.2.0` + * `<= 2.x` is equivalent to `<= 3` + * `*` is equivalent to `>= 0.0.0` + +Tilde Range Comparisons (Patch) + +The tilde (`~`) comparison operator is for patch level ranges when a minor +version is specified and major level changes when the minor number is missing. +For example, + + * `~1.2.3` is equivalent to `>= 1.2.3, < 1.3.0` + * `~1` is equivalent to `>= 1, < 2` + * `~2.3` is equivalent to `>= 2.3, < 2.4` + * `~1.2.x` is equivalent to `>= 1.2.0, < 1.3.0` + * `~1.x` is equivalent to `>= 1, < 2` + +Caret Range Comparisons (Major) + +The caret (`^`) comparison operator is for major level changes. This is useful +when comparisons of API versions as a major change is API breaking. For example, + + * `^1.2.3` is equivalent to `>= 1.2.3, < 2.0.0` + * `^1.2.x` is equivalent to `>= 1.2.0, < 2.0.0` + * `^2.3` is equivalent to `>= 2.3, < 3` + * `^2.x` is equivalent to `>= 2.0.0, < 3` +*/ +package semver diff --git a/vendor/github.com/Masterminds/semver/version.go b/vendor/github.com/Masterminds/semver/version.go new file mode 100644 index 000000000..400d4f934 --- /dev/null +++ b/vendor/github.com/Masterminds/semver/version.go @@ -0,0 +1,425 @@ +package semver + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "regexp" + "strconv" + "strings" +) + +// The compiled version of the regex created at init() is cached here so it +// only needs to be created once. +var versionRegex *regexp.Regexp +var validPrereleaseRegex *regexp.Regexp + +var ( + // ErrInvalidSemVer is returned a version is found to be invalid when + // being parsed. + ErrInvalidSemVer = errors.New("Invalid Semantic Version") + + // ErrInvalidMetadata is returned when the metadata is an invalid format + ErrInvalidMetadata = errors.New("Invalid Metadata string") + + // ErrInvalidPrerelease is returned when the pre-release is an invalid format + ErrInvalidPrerelease = errors.New("Invalid Prerelease string") +) + +// SemVerRegex is the regular expression used to parse a semantic version. +const SemVerRegex string = `v?([0-9]+)(\.[0-9]+)?(\.[0-9]+)?` + + `(-([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?` + + `(\+([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?` + +// ValidPrerelease is the regular expression which validates +// both prerelease and metadata values. +const ValidPrerelease string = `^([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*)$` + +// Version represents a single semantic version. +type Version struct { + major, minor, patch int64 + pre string + metadata string + original string +} + +func init() { + versionRegex = regexp.MustCompile("^" + SemVerRegex + "$") + validPrereleaseRegex = regexp.MustCompile(ValidPrerelease) +} + +// NewVersion parses a given version and returns an instance of Version or +// an error if unable to parse the version. +func NewVersion(v string) (*Version, error) { + m := versionRegex.FindStringSubmatch(v) + if m == nil { + return nil, ErrInvalidSemVer + } + + sv := &Version{ + metadata: m[8], + pre: m[5], + original: v, + } + + var temp int64 + temp, err := strconv.ParseInt(m[1], 10, 64) + if err != nil { + return nil, fmt.Errorf("Error parsing version segment: %s", err) + } + sv.major = temp + + if m[2] != "" { + temp, err = strconv.ParseInt(strings.TrimPrefix(m[2], "."), 10, 64) + if err != nil { + return nil, fmt.Errorf("Error parsing version segment: %s", err) + } + sv.minor = temp + } else { + sv.minor = 0 + } + + if m[3] != "" { + temp, err = strconv.ParseInt(strings.TrimPrefix(m[3], "."), 10, 64) + if err != nil { + return nil, fmt.Errorf("Error parsing version segment: %s", err) + } + sv.patch = temp + } else { + sv.patch = 0 + } + + return sv, nil +} + +// MustParse parses a given version and panics on error. +func MustParse(v string) *Version { + sv, err := NewVersion(v) + if err != nil { + panic(err) + } + return sv +} + +// String converts a Version object to a string. +// Note, if the original version contained a leading v this version will not. +// See the Original() method to retrieve the original value. Semantic Versions +// don't contain a leading v per the spec. Instead it's optional on +// implementation. +func (v *Version) String() string { + var buf bytes.Buffer + + fmt.Fprintf(&buf, "%d.%d.%d", v.major, v.minor, v.patch) + if v.pre != "" { + fmt.Fprintf(&buf, "-%s", v.pre) + } + if v.metadata != "" { + fmt.Fprintf(&buf, "+%s", v.metadata) + } + + return buf.String() +} + +// Original returns the original value passed in to be parsed. +func (v *Version) Original() string { + return v.original +} + +// Major returns the major version. +func (v *Version) Major() int64 { + return v.major +} + +// Minor returns the minor version. +func (v *Version) Minor() int64 { + return v.minor +} + +// Patch returns the patch version. +func (v *Version) Patch() int64 { + return v.patch +} + +// Prerelease returns the pre-release version. +func (v *Version) Prerelease() string { + return v.pre +} + +// Metadata returns the metadata on the version. +func (v *Version) Metadata() string { + return v.metadata +} + +// originalVPrefix returns the original 'v' prefix if any. +func (v *Version) originalVPrefix() string { + + // Note, only lowercase v is supported as a prefix by the parser. + if v.original != "" && v.original[:1] == "v" { + return v.original[:1] + } + return "" +} + +// IncPatch produces the next patch version. +// If the current version does not have prerelease/metadata information, +// it unsets metadata and prerelease values, increments patch number. +// If the current version has any of prerelease or metadata information, +// it unsets both values and keeps curent patch value +func (v Version) IncPatch() Version { + vNext := v + // according to http://semver.org/#spec-item-9 + // Pre-release versions have a lower precedence than the associated normal version. + // according to http://semver.org/#spec-item-10 + // Build metadata SHOULD be ignored when determining version precedence. + if v.pre != "" { + vNext.metadata = "" + vNext.pre = "" + } else { + vNext.metadata = "" + vNext.pre = "" + vNext.patch = v.patch + 1 + } + vNext.original = v.originalVPrefix() + "" + vNext.String() + return vNext +} + +// IncMinor produces the next minor version. +// Sets patch to 0. +// Increments minor number. +// Unsets metadata. +// Unsets prerelease status. +func (v Version) IncMinor() Version { + vNext := v + vNext.metadata = "" + vNext.pre = "" + vNext.patch = 0 + vNext.minor = v.minor + 1 + vNext.original = v.originalVPrefix() + "" + vNext.String() + return vNext +} + +// IncMajor produces the next major version. +// Sets patch to 0. +// Sets minor to 0. +// Increments major number. +// Unsets metadata. +// Unsets prerelease status. +func (v Version) IncMajor() Version { + vNext := v + vNext.metadata = "" + vNext.pre = "" + vNext.patch = 0 + vNext.minor = 0 + vNext.major = v.major + 1 + vNext.original = v.originalVPrefix() + "" + vNext.String() + return vNext +} + +// SetPrerelease defines the prerelease value. +// Value must not include the required 'hypen' prefix. +func (v Version) SetPrerelease(prerelease string) (Version, error) { + vNext := v + if len(prerelease) > 0 && !validPrereleaseRegex.MatchString(prerelease) { + return vNext, ErrInvalidPrerelease + } + vNext.pre = prerelease + vNext.original = v.originalVPrefix() + "" + vNext.String() + return vNext, nil +} + +// SetMetadata defines metadata value. +// Value must not include the required 'plus' prefix. +func (v Version) SetMetadata(metadata string) (Version, error) { + vNext := v + if len(metadata) > 0 && !validPrereleaseRegex.MatchString(metadata) { + return vNext, ErrInvalidMetadata + } + vNext.metadata = metadata + vNext.original = v.originalVPrefix() + "" + vNext.String() + return vNext, nil +} + +// LessThan tests if one version is less than another one. +func (v *Version) LessThan(o *Version) bool { + return v.Compare(o) < 0 +} + +// GreaterThan tests if one version is greater than another one. +func (v *Version) GreaterThan(o *Version) bool { + return v.Compare(o) > 0 +} + +// Equal tests if two versions are equal to each other. +// Note, versions can be equal with different metadata since metadata +// is not considered part of the comparable version. +func (v *Version) Equal(o *Version) bool { + return v.Compare(o) == 0 +} + +// Compare compares this version to another one. It returns -1, 0, or 1 if +// the version smaller, equal, or larger than the other version. +// +// Versions are compared by X.Y.Z. Build metadata is ignored. Prerelease is +// lower than the version without a prerelease. +func (v *Version) Compare(o *Version) int { + // Compare the major, minor, and patch version for differences. If a + // difference is found return the comparison. + if d := compareSegment(v.Major(), o.Major()); d != 0 { + return d + } + if d := compareSegment(v.Minor(), o.Minor()); d != 0 { + return d + } + if d := compareSegment(v.Patch(), o.Patch()); d != 0 { + return d + } + + // At this point the major, minor, and patch versions are the same. + ps := v.pre + po := o.Prerelease() + + if ps == "" && po == "" { + return 0 + } + if ps == "" { + return 1 + } + if po == "" { + return -1 + } + + return comparePrerelease(ps, po) +} + +// UnmarshalJSON implements JSON.Unmarshaler interface. +func (v *Version) UnmarshalJSON(b []byte) error { + var s string + if err := json.Unmarshal(b, &s); err != nil { + return err + } + temp, err := NewVersion(s) + if err != nil { + return err + } + v.major = temp.major + v.minor = temp.minor + v.patch = temp.patch + v.pre = temp.pre + v.metadata = temp.metadata + v.original = temp.original + temp = nil + return nil +} + +// MarshalJSON implements JSON.Marshaler interface. +func (v *Version) MarshalJSON() ([]byte, error) { + return json.Marshal(v.String()) +} + +func compareSegment(v, o int64) int { + if v < o { + return -1 + } + if v > o { + return 1 + } + + return 0 +} + +func comparePrerelease(v, o string) int { + + // split the prelease versions by their part. The separator, per the spec, + // is a . + sparts := strings.Split(v, ".") + oparts := strings.Split(o, ".") + + // Find the longer length of the parts to know how many loop iterations to + // go through. + slen := len(sparts) + olen := len(oparts) + + l := slen + if olen > slen { + l = olen + } + + // Iterate over each part of the prereleases to compare the differences. + for i := 0; i < l; i++ { + // Since the lentgh of the parts can be different we need to create + // a placeholder. This is to avoid out of bounds issues. + stemp := "" + if i < slen { + stemp = sparts[i] + } + + otemp := "" + if i < olen { + otemp = oparts[i] + } + + d := comparePrePart(stemp, otemp) + if d != 0 { + return d + } + } + + // Reaching here means two versions are of equal value but have different + // metadata (the part following a +). They are not identical in string form + // but the version comparison finds them to be equal. + return 0 +} + +func comparePrePart(s, o string) int { + // Fastpath if they are equal + if s == o { + return 0 + } + + // When s or o are empty we can use the other in an attempt to determine + // the response. + if s == "" { + if o != "" { + return -1 + } + return 1 + } + + if o == "" { + if s != "" { + return 1 + } + return -1 + } + + // When comparing strings "99" is greater than "103". To handle + // cases like this we need to detect numbers and compare them. According + // to the semver spec, numbers are always positive. If there is a - at the + // start like -99 this is to be evaluated as an alphanum. numbers always + // have precedence over alphanum. Parsing as Uints because negative numbers + // are ignored. + + oi, n1 := strconv.ParseUint(o, 10, 64) + si, n2 := strconv.ParseUint(s, 10, 64) + + // The case where both are strings compare the strings + if n1 != nil && n2 != nil { + if s > o { + return 1 + } + return -1 + } else if n1 != nil { + // o is a string and s is a number + return -1 + } else if n2 != nil { + // s is a string and o is a number + return 1 + } + // Both are numbers + if si > oi { + return 1 + } + return -1 + +} diff --git a/vendor/github.com/Masterminds/semver/version_fuzz.go b/vendor/github.com/Masterminds/semver/version_fuzz.go new file mode 100644 index 000000000..b42bcd62b --- /dev/null +++ b/vendor/github.com/Masterminds/semver/version_fuzz.go @@ -0,0 +1,10 @@ +// +build gofuzz + +package semver + +func Fuzz(data []byte) int { + if _, err := NewVersion(string(data)); err != nil { + return 0 + } + return 1 +} diff --git a/vendor/github.com/Masterminds/sprig/.gitignore b/vendor/github.com/Masterminds/sprig/.gitignore new file mode 100644 index 000000000..5e3002f88 --- /dev/null +++ b/vendor/github.com/Masterminds/sprig/.gitignore @@ -0,0 +1,2 @@ +vendor/ +/.glide diff --git a/vendor/github.com/Masterminds/sprig/.travis.yml b/vendor/github.com/Masterminds/sprig/.travis.yml new file mode 100644 index 000000000..b9da8b825 --- /dev/null +++ b/vendor/github.com/Masterminds/sprig/.travis.yml @@ -0,0 +1,26 @@ +language: go + +go: + - 1.9.x + - 1.10.x + - 1.11.x + - 1.12.x + - 1.13.x + - tip + +# Setting sudo access to false will let Travis CI use containers rather than +# VMs to run the tests. For more details see: +# - http://docs.travis-ci.com/user/workers/container-based-infrastructure/ +# - http://docs.travis-ci.com/user/workers/standard-infrastructure/ +sudo: false + +script: + - make setup test + +notifications: + webhooks: + urls: + - https://webhooks.gitter.im/e/06e3328629952dabe3e0 + on_success: change # options: [always|never|change] default: always + on_failure: always # options: [always|never|change] default: always + on_start: never # options: [always|never|change] default: always diff --git a/vendor/github.com/Masterminds/sprig/CHANGELOG.md b/vendor/github.com/Masterminds/sprig/CHANGELOG.md new file mode 100644 index 000000000..6a79fbde4 --- /dev/null +++ b/vendor/github.com/Masterminds/sprig/CHANGELOG.md @@ -0,0 +1,282 @@ +# Changelog + +## Release 2.22.0 (2019-10-02) + +### Added + +- #173: Added getHostByName function to resolve dns names to ips (thanks @fcgravalos) +- #195: Added deepCopy function for use with dicts + +### Changed + +- Updated merge and mergeOverwrite documentation to explain copying and how to + use deepCopy with it + +## Release 2.21.0 (2019-09-18) + +### Added + +- #122: Added encryptAES/decryptAES functions (thanks @n0madic) +- #128: Added toDecimal support (thanks @Dean-Coakley) +- #169: Added list contcat (thanks @astorath) +- #174: Added deepEqual function (thanks @bonifaido) +- #170: Added url parse and join functions (thanks @astorath) + +### Changed + +- #171: Updated glide config for Google UUID to v1 and to add ranges to semver and testify + +### Fixed + +- #172: Fix semver wildcard example (thanks @piepmatz) +- #175: Fix dateInZone doc example (thanks @s3than) + +## Release 2.20.0 (2019-06-18) + +### Added + +- #164: Adding function to get unix epoch for a time (@mattfarina) +- #166: Adding tests for date_in_zone (@mattfarina) + +### Changed + +- #144: Fix function comments based on best practices from Effective Go (@CodeLingoTeam) +- #150: Handles pointer type for time.Time in "htmlDate" (@mapreal19) +- #161, #157, #160, #153, #158, #156, #155, #159, #152 documentation updates (@badeadan) + +### Fixed + +## Release 2.19.0 (2019-03-02) + +IMPORTANT: This release reverts a change from 2.18.0 + +In the previous release (2.18), we prematurely merged a partial change to the crypto functions that led to creating two sets of crypto functions (I blame @technosophos -- since that's me). This release rolls back that change, and does what was originally intended: It alters the existing crypto functions to use secure random. + +We debated whether this classifies as a change worthy of major revision, but given the proximity to the last release, we have decided that treating 2.18 as a faulty release is the correct course of action. We apologize for any inconvenience. + +### Changed + +- Fix substr panic 35fb796 (Alexey igrychev) +- Remove extra period 1eb7729 (Matthew Lorimor) +- Make random string functions use crypto by default 6ceff26 (Matthew Lorimor) +- README edits/fixes/suggestions 08fe136 (Lauri Apple) + + +## Release 2.18.0 (2019-02-12) + +### Added + +- Added mergeOverwrite function +- cryptographic functions that use secure random (see fe1de12) + +### Changed + +- Improve documentation of regexMatch function, resolves #139 90b89ce (Jan Tagscherer) +- Handle has for nil list 9c10885 (Daniel Cohen) +- Document behaviour of mergeOverwrite fe0dbe9 (Lukas Rieder) +- doc: adds missing documentation. 4b871e6 (Fernandez Ludovic) +- Replace outdated goutils imports 01893d2 (Matthew Lorimor) +- Surface crypto secure random strings from goutils fe1de12 (Matthew Lorimor) +- Handle untyped nil values as paramters to string functions 2b2ec8f (Morten Torkildsen) + +### Fixed + +- Fix dict merge issue and provide mergeOverwrite .dst .src1 to overwrite from src -> dst 4c59c12 (Lukas Rieder) +- Fix substr var names and comments d581f80 (Dean Coakley) +- Fix substr documentation 2737203 (Dean Coakley) + +## Release 2.17.1 (2019-01-03) + +### Fixed + +The 2.17.0 release did not have a version pinned for xstrings, which caused compilation failures when xstrings < 1.2 was used. This adds the correct version string to glide.yaml. + +## Release 2.17.0 (2019-01-03) + +### Added + +- adds alder32sum function and test 6908fc2 (marshallford) +- Added kebabcase function ca331a1 (Ilyes512) + +### Changed + +- Update goutils to 1.1.0 4e1125d (Matt Butcher) + +### Fixed + +- Fix 'has' documentation e3f2a85 (dean-coakley) +- docs(dict): fix typo in pick example dc424f9 (Dustin Specker) +- fixes spelling errors... not sure how that happened 4cf188a (marshallford) + +## Release 2.16.0 (2018-08-13) + +### Added + +- add splitn function fccb0b0 (Helgi Þorbjörnsson) +- Add slice func df28ca7 (gongdo) +- Generate serial number a3bdffd (Cody Coons) +- Extract values of dict with values function df39312 (Lawrence Jones) + +### Changed + +- Modify panic message for list.slice ae38335 (gongdo) +- Minor improvement in code quality - Removed an unreachable piece of code at defaults.go#L26:6 - Resolve formatting issues. 5834241 (Abhishek Kashyap) +- Remove duplicated documentation 1d97af1 (Matthew Fisher) +- Test on go 1.11 49df809 (Helgi Þormar Þorbjörnsson) + +### Fixed + +- Fix file permissions c5f40b5 (gongdo) +- Fix example for buildCustomCert 7779e0d (Tin Lam) + +## Release 2.15.0 (2018-04-02) + +### Added + +- #68 and #69: Add json helpers to docs (thanks @arunvelsriram) +- #66: Add ternary function (thanks @binoculars) +- #67: Allow keys function to take multiple dicts (thanks @binoculars) +- #89: Added sha1sum to crypto function (thanks @benkeil) +- #81: Allow customizing Root CA that used by genSignedCert (thanks @chenzhiwei) +- #92: Add travis testing for go 1.10 +- #93: Adding appveyor config for windows testing + +### Changed + +- #90: Updating to more recent dependencies +- #73: replace satori/go.uuid with google/uuid (thanks @petterw) + +### Fixed + +- #76: Fixed documentation typos (thanks @Thiht) +- Fixed rounding issue on the `ago` function. Note, the removes support for Go 1.8 and older + +## Release 2.14.1 (2017-12-01) + +### Fixed + +- #60: Fix typo in function name documentation (thanks @neil-ca-moore) +- #61: Removing line with {{ due to blocking github pages genertion +- #64: Update the list functions to handle int, string, and other slices for compatibility + +## Release 2.14.0 (2017-10-06) + +This new version of Sprig adds a set of functions for generating and working with SSL certificates. + +- `genCA` generates an SSL Certificate Authority +- `genSelfSignedCert` generates an SSL self-signed certificate +- `genSignedCert` generates an SSL certificate and key based on a given CA + +## Release 2.13.0 (2017-09-18) + +This release adds new functions, including: + +- `regexMatch`, `regexFindAll`, `regexFind`, `regexReplaceAll`, `regexReplaceAllLiteral`, and `regexSplit` to work with regular expressions +- `floor`, `ceil`, and `round` math functions +- `toDate` converts a string to a date +- `nindent` is just like `indent` but also prepends a new line +- `ago` returns the time from `time.Now` + +### Added + +- #40: Added basic regex functionality (thanks @alanquillin) +- #41: Added ceil floor and round functions (thanks @alanquillin) +- #48: Added toDate function (thanks @andreynering) +- #50: Added nindent function (thanks @binoculars) +- #46: Added ago function (thanks @slayer) + +### Changed + +- #51: Updated godocs to include new string functions (thanks @curtisallen) +- #49: Added ability to merge multiple dicts (thanks @binoculars) + +## Release 2.12.0 (2017-05-17) + +- `snakecase`, `camelcase`, and `shuffle` are three new string functions +- `fail` allows you to bail out of a template render when conditions are not met + +## Release 2.11.0 (2017-05-02) + +- Added `toJson` and `toPrettyJson` +- Added `merge` +- Refactored documentation + +## Release 2.10.0 (2017-03-15) + +- Added `semver` and `semverCompare` for Semantic Versions +- `list` replaces `tuple` +- Fixed issue with `join` +- Added `first`, `last`, `intial`, `rest`, `prepend`, `append`, `toString`, `toStrings`, `sortAlpha`, `reverse`, `coalesce`, `pluck`, `pick`, `compact`, `keys`, `omit`, `uniq`, `has`, `without` + +## Release 2.9.0 (2017-02-23) + +- Added `splitList` to split a list +- Added crypto functions of `genPrivateKey` and `derivePassword` + +## Release 2.8.0 (2016-12-21) + +- Added access to several path functions (`base`, `dir`, `clean`, `ext`, and `abs`) +- Added functions for _mutating_ dictionaries (`set`, `unset`, `hasKey`) + +## Release 2.7.0 (2016-12-01) + +- Added `sha256sum` to generate a hash of an input +- Added functions to convert a numeric or string to `int`, `int64`, `float64` + +## Release 2.6.0 (2016-10-03) + +- Added a `uuidv4` template function for generating UUIDs inside of a template. + +## Release 2.5.0 (2016-08-19) + +- New `trimSuffix`, `trimPrefix`, `hasSuffix`, and `hasPrefix` functions +- New aliases have been added for a few functions that didn't follow the naming conventions (`trimAll` and `abbrevBoth`) +- `trimall` and `abbrevboth` (notice the case) are deprecated and will be removed in 3.0.0 + +## Release 2.4.0 (2016-08-16) + +- Adds two functions: `until` and `untilStep` + +## Release 2.3.0 (2016-06-21) + +- cat: Concatenate strings with whitespace separators. +- replace: Replace parts of a string: `replace " " "-" "Me First"` renders "Me-First" +- plural: Format plurals: `len "foo" | plural "one foo" "many foos"` renders "many foos" +- indent: Indent blocks of text in a way that is sensitive to "\n" characters. + +## Release 2.2.0 (2016-04-21) + +- Added a `genPrivateKey` function (Thanks @bacongobbler) + +## Release 2.1.0 (2016-03-30) + +- `default` now prints the default value when it does not receive a value down the pipeline. It is much safer now to do `{{.Foo | default "bar"}}`. +- Added accessors for "hermetic" functions. These return only functions that, when given the same input, produce the same output. + +## Release 2.0.0 (2016-03-29) + +Because we switched from `int` to `int64` as the return value for all integer math functions, the library's major version number has been incremented. + +- `min` complements `max` (formerly `biggest`) +- `empty` indicates that a value is the empty value for its type +- `tuple` creates a tuple inside of a template: `{{$t := tuple "a", "b" "c"}}` +- `dict` creates a dictionary inside of a template `{{$d := dict "key1" "val1" "key2" "val2"}}` +- Date formatters have been added for HTML dates (as used in `date` input fields) +- Integer math functions can convert from a number of types, including `string` (via `strconv.ParseInt`). + +## Release 1.2.0 (2016-02-01) + +- Added quote and squote +- Added b32enc and b32dec +- add now takes varargs +- biggest now takes varargs + +## Release 1.1.0 (2015-12-29) + +- Added #4: Added contains function. strings.Contains, but with the arguments + switched to simplify common pipelines. (thanks krancour) +- Added Travis-CI testing support + +## Release 1.0.0 (2015-12-23) + +- Initial release diff --git a/vendor/github.com/Masterminds/sprig/LICENSE.txt b/vendor/github.com/Masterminds/sprig/LICENSE.txt new file mode 100644 index 000000000..5c95accc2 --- /dev/null +++ b/vendor/github.com/Masterminds/sprig/LICENSE.txt @@ -0,0 +1,20 @@ +Sprig +Copyright (C) 2013 Masterminds + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/github.com/Masterminds/sprig/Makefile b/vendor/github.com/Masterminds/sprig/Makefile new file mode 100644 index 000000000..63a93fdf7 --- /dev/null +++ b/vendor/github.com/Masterminds/sprig/Makefile @@ -0,0 +1,13 @@ + +HAS_GLIDE := $(shell command -v glide;) + +.PHONY: test +test: + go test -v . + +.PHONY: setup +setup: +ifndef HAS_GLIDE + go get -u github.com/Masterminds/glide +endif + glide install diff --git a/vendor/github.com/Masterminds/sprig/README.md b/vendor/github.com/Masterminds/sprig/README.md new file mode 100644 index 000000000..b70569585 --- /dev/null +++ b/vendor/github.com/Masterminds/sprig/README.md @@ -0,0 +1,78 @@ +# Sprig: Template functions for Go templates +[![Stability: Sustained](https://masterminds.github.io/stability/sustained.svg)](https://masterminds.github.io/stability/sustained.html) +[![Build Status](https://travis-ci.org/Masterminds/sprig.svg?branch=master)](https://travis-ci.org/Masterminds/sprig) + +The Go language comes with a [built-in template +language](http://golang.org/pkg/text/template/), but not +very many template functions. Sprig is a library that provides more than 100 commonly +used template functions. + +It is inspired by the template functions found in +[Twig](http://twig.sensiolabs.org/documentation) and in various +JavaScript libraries, such as [underscore.js](http://underscorejs.org/). + +## Usage + +**Template developers**: Please use Sprig's [function documentation](http://masterminds.github.io/sprig/) for +detailed instructions and code snippets for the >100 template functions available. + +**Go developers**: If you'd like to include Sprig as a library in your program, +our API documentation is available [at GoDoc.org](http://godoc.org/github.com/Masterminds/sprig). + +For standard usage, read on. + +### Load the Sprig library + +To load the Sprig `FuncMap`: + +```go + +import ( + "github.com/Masterminds/sprig" + "html/template" +) + +// This example illustrates that the FuncMap *must* be set before the +// templates themselves are loaded. +tpl := template.Must( + template.New("base").Funcs(sprig.FuncMap()).ParseGlob("*.html") +) + + +``` + +### Calling the functions inside of templates + +By convention, all functions are lowercase. This seems to follow the Go +idiom for template functions (as opposed to template methods, which are +TitleCase). For example, this: + +``` +{{ "hello!" | upper | repeat 5 }} +``` + +produces this: + +``` +HELLO!HELLO!HELLO!HELLO!HELLO! +``` + +## Principles Driving Our Function Selection + +We followed these principles to decide which functions to add and how to implement them: + +- Use template functions to build layout. The following + types of operations are within the domain of template functions: + - Formatting + - Layout + - Simple type conversions + - Utilities that assist in handling common formatting and layout needs (e.g. arithmetic) +- Template functions should not return errors unless there is no way to print + a sensible value. For example, converting a string to an integer should not + produce an error if conversion fails. Instead, it should display a default + value. +- Simple math is necessary for grid layouts, pagers, and so on. Complex math + (anything other than arithmetic) should be done outside of templates. +- Template functions only deal with the data passed into them. They never retrieve + data from a source. +- Finally, do not override core Go template functions. diff --git a/vendor/github.com/Masterminds/sprig/appveyor.yml b/vendor/github.com/Masterminds/sprig/appveyor.yml new file mode 100644 index 000000000..d545a987a --- /dev/null +++ b/vendor/github.com/Masterminds/sprig/appveyor.yml @@ -0,0 +1,26 @@ + +version: build-{build}.{branch} + +clone_folder: C:\gopath\src\github.com\Masterminds\sprig +shallow_clone: true + +environment: + GOPATH: C:\gopath + +platform: + - x64 + +install: + - go get -u github.com/Masterminds/glide + - set PATH=%GOPATH%\bin;%PATH% + - go version + - go env + +build_script: + - glide install + - go install ./... + +test_script: + - go test -v + +deploy: off diff --git a/vendor/github.com/Masterminds/sprig/crypto.go b/vendor/github.com/Masterminds/sprig/crypto.go new file mode 100644 index 000000000..7a418ba88 --- /dev/null +++ b/vendor/github.com/Masterminds/sprig/crypto.go @@ -0,0 +1,502 @@ +package sprig + +import ( + "bytes" + "crypto/aes" + "crypto/cipher" + "crypto/dsa" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/hmac" + "crypto/rand" + "crypto/rsa" + "crypto/sha1" + "crypto/sha256" + "crypto/x509" + "crypto/x509/pkix" + "encoding/asn1" + "encoding/base64" + "encoding/binary" + "encoding/hex" + "encoding/pem" + "errors" + "fmt" + "io" + "hash/adler32" + "math/big" + "net" + "time" + + "github.com/google/uuid" + "golang.org/x/crypto/scrypt" +) + +func sha256sum(input string) string { + hash := sha256.Sum256([]byte(input)) + return hex.EncodeToString(hash[:]) +} + +func sha1sum(input string) string { + hash := sha1.Sum([]byte(input)) + return hex.EncodeToString(hash[:]) +} + +func adler32sum(input string) string { + hash := adler32.Checksum([]byte(input)) + return fmt.Sprintf("%d", hash) +} + +// uuidv4 provides a safe and secure UUID v4 implementation +func uuidv4() string { + return fmt.Sprintf("%s", uuid.New()) +} + +var master_password_seed = "com.lyndir.masterpassword" + +var password_type_templates = map[string][][]byte{ + "maximum": {[]byte("anoxxxxxxxxxxxxxxxxx"), []byte("axxxxxxxxxxxxxxxxxno")}, + "long": {[]byte("CvcvnoCvcvCvcv"), []byte("CvcvCvcvnoCvcv"), []byte("CvcvCvcvCvcvno"), []byte("CvccnoCvcvCvcv"), []byte("CvccCvcvnoCvcv"), + []byte("CvccCvcvCvcvno"), []byte("CvcvnoCvccCvcv"), []byte("CvcvCvccnoCvcv"), []byte("CvcvCvccCvcvno"), []byte("CvcvnoCvcvCvcc"), + []byte("CvcvCvcvnoCvcc"), []byte("CvcvCvcvCvccno"), []byte("CvccnoCvccCvcv"), []byte("CvccCvccnoCvcv"), []byte("CvccCvccCvcvno"), + []byte("CvcvnoCvccCvcc"), []byte("CvcvCvccnoCvcc"), []byte("CvcvCvccCvccno"), []byte("CvccnoCvcvCvcc"), []byte("CvccCvcvnoCvcc"), + []byte("CvccCvcvCvccno")}, + "medium": {[]byte("CvcnoCvc"), []byte("CvcCvcno")}, + "short": {[]byte("Cvcn")}, + "basic": {[]byte("aaanaaan"), []byte("aannaaan"), []byte("aaannaaa")}, + "pin": {[]byte("nnnn")}, +} + +var template_characters = map[byte]string{ + 'V': "AEIOU", + 'C': "BCDFGHJKLMNPQRSTVWXYZ", + 'v': "aeiou", + 'c': "bcdfghjklmnpqrstvwxyz", + 'A': "AEIOUBCDFGHJKLMNPQRSTVWXYZ", + 'a': "AEIOUaeiouBCDFGHJKLMNPQRSTVWXYZbcdfghjklmnpqrstvwxyz", + 'n': "0123456789", + 'o': "@&%?,=[]_:-+*$#!'^~;()/.", + 'x': "AEIOUaeiouBCDFGHJKLMNPQRSTVWXYZbcdfghjklmnpqrstvwxyz0123456789!@#$%^&*()", +} + +func derivePassword(counter uint32, password_type, password, user, site string) string { + var templates = password_type_templates[password_type] + if templates == nil { + return fmt.Sprintf("cannot find password template %s", password_type) + } + + var buffer bytes.Buffer + buffer.WriteString(master_password_seed) + binary.Write(&buffer, binary.BigEndian, uint32(len(user))) + buffer.WriteString(user) + + salt := buffer.Bytes() + key, err := scrypt.Key([]byte(password), salt, 32768, 8, 2, 64) + if err != nil { + return fmt.Sprintf("failed to derive password: %s", err) + } + + buffer.Truncate(len(master_password_seed)) + binary.Write(&buffer, binary.BigEndian, uint32(len(site))) + buffer.WriteString(site) + binary.Write(&buffer, binary.BigEndian, counter) + + var hmacv = hmac.New(sha256.New, key) + hmacv.Write(buffer.Bytes()) + var seed = hmacv.Sum(nil) + var temp = templates[int(seed[0])%len(templates)] + + buffer.Truncate(0) + for i, element := range temp { + pass_chars := template_characters[element] + pass_char := pass_chars[int(seed[i+1])%len(pass_chars)] + buffer.WriteByte(pass_char) + } + + return buffer.String() +} + +func generatePrivateKey(typ string) string { + var priv interface{} + var err error + switch typ { + case "", "rsa": + // good enough for government work + priv, err = rsa.GenerateKey(rand.Reader, 4096) + case "dsa": + key := new(dsa.PrivateKey) + // again, good enough for government work + if err = dsa.GenerateParameters(&key.Parameters, rand.Reader, dsa.L2048N256); err != nil { + return fmt.Sprintf("failed to generate dsa params: %s", err) + } + err = dsa.GenerateKey(key, rand.Reader) + priv = key + case "ecdsa": + // again, good enough for government work + priv, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + default: + return "Unknown type " + typ + } + if err != nil { + return fmt.Sprintf("failed to generate private key: %s", err) + } + + return string(pem.EncodeToMemory(pemBlockForKey(priv))) +} + +type DSAKeyFormat struct { + Version int + P, Q, G, Y, X *big.Int +} + +func pemBlockForKey(priv interface{}) *pem.Block { + switch k := priv.(type) { + case *rsa.PrivateKey: + return &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(k)} + case *dsa.PrivateKey: + val := DSAKeyFormat{ + P: k.P, Q: k.Q, G: k.G, + Y: k.Y, X: k.X, + } + bytes, _ := asn1.Marshal(val) + return &pem.Block{Type: "DSA PRIVATE KEY", Bytes: bytes} + case *ecdsa.PrivateKey: + b, _ := x509.MarshalECPrivateKey(k) + return &pem.Block{Type: "EC PRIVATE KEY", Bytes: b} + default: + return nil + } +} + +type certificate struct { + Cert string + Key string +} + +func buildCustomCertificate(b64cert string, b64key string) (certificate, error) { + crt := certificate{} + + cert, err := base64.StdEncoding.DecodeString(b64cert) + if err != nil { + return crt, errors.New("unable to decode base64 certificate") + } + + key, err := base64.StdEncoding.DecodeString(b64key) + if err != nil { + return crt, errors.New("unable to decode base64 private key") + } + + decodedCert, _ := pem.Decode(cert) + if decodedCert == nil { + return crt, errors.New("unable to decode certificate") + } + _, err = x509.ParseCertificate(decodedCert.Bytes) + if err != nil { + return crt, fmt.Errorf( + "error parsing certificate: decodedCert.Bytes: %s", + err, + ) + } + + decodedKey, _ := pem.Decode(key) + if decodedKey == nil { + return crt, errors.New("unable to decode key") + } + _, err = x509.ParsePKCS1PrivateKey(decodedKey.Bytes) + if err != nil { + return crt, fmt.Errorf( + "error parsing prive key: decodedKey.Bytes: %s", + err, + ) + } + + crt.Cert = string(cert) + crt.Key = string(key) + + return crt, nil +} + +func generateCertificateAuthority( + cn string, + daysValid int, +) (certificate, error) { + ca := certificate{} + + template, err := getBaseCertTemplate(cn, nil, nil, daysValid) + if err != nil { + return ca, err + } + // Override KeyUsage and IsCA + template.KeyUsage = x509.KeyUsageKeyEncipherment | + x509.KeyUsageDigitalSignature | + x509.KeyUsageCertSign + template.IsCA = true + + priv, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + return ca, fmt.Errorf("error generating rsa key: %s", err) + } + + ca.Cert, ca.Key, err = getCertAndKey(template, priv, template, priv) + if err != nil { + return ca, err + } + + return ca, nil +} + +func generateSelfSignedCertificate( + cn string, + ips []interface{}, + alternateDNS []interface{}, + daysValid int, +) (certificate, error) { + cert := certificate{} + + template, err := getBaseCertTemplate(cn, ips, alternateDNS, daysValid) + if err != nil { + return cert, err + } + + priv, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + return cert, fmt.Errorf("error generating rsa key: %s", err) + } + + cert.Cert, cert.Key, err = getCertAndKey(template, priv, template, priv) + if err != nil { + return cert, err + } + + return cert, nil +} + +func generateSignedCertificate( + cn string, + ips []interface{}, + alternateDNS []interface{}, + daysValid int, + ca certificate, +) (certificate, error) { + cert := certificate{} + + decodedSignerCert, _ := pem.Decode([]byte(ca.Cert)) + if decodedSignerCert == nil { + return cert, errors.New("unable to decode certificate") + } + signerCert, err := x509.ParseCertificate(decodedSignerCert.Bytes) + if err != nil { + return cert, fmt.Errorf( + "error parsing certificate: decodedSignerCert.Bytes: %s", + err, + ) + } + decodedSignerKey, _ := pem.Decode([]byte(ca.Key)) + if decodedSignerKey == nil { + return cert, errors.New("unable to decode key") + } + signerKey, err := x509.ParsePKCS1PrivateKey(decodedSignerKey.Bytes) + if err != nil { + return cert, fmt.Errorf( + "error parsing prive key: decodedSignerKey.Bytes: %s", + err, + ) + } + + template, err := getBaseCertTemplate(cn, ips, alternateDNS, daysValid) + if err != nil { + return cert, err + } + + priv, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + return cert, fmt.Errorf("error generating rsa key: %s", err) + } + + cert.Cert, cert.Key, err = getCertAndKey( + template, + priv, + signerCert, + signerKey, + ) + if err != nil { + return cert, err + } + + return cert, nil +} + +func getCertAndKey( + template *x509.Certificate, + signeeKey *rsa.PrivateKey, + parent *x509.Certificate, + signingKey *rsa.PrivateKey, +) (string, string, error) { + derBytes, err := x509.CreateCertificate( + rand.Reader, + template, + parent, + &signeeKey.PublicKey, + signingKey, + ) + if err != nil { + return "", "", fmt.Errorf("error creating certificate: %s", err) + } + + certBuffer := bytes.Buffer{} + if err := pem.Encode( + &certBuffer, + &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}, + ); err != nil { + return "", "", fmt.Errorf("error pem-encoding certificate: %s", err) + } + + keyBuffer := bytes.Buffer{} + if err := pem.Encode( + &keyBuffer, + &pem.Block{ + Type: "RSA PRIVATE KEY", + Bytes: x509.MarshalPKCS1PrivateKey(signeeKey), + }, + ); err != nil { + return "", "", fmt.Errorf("error pem-encoding key: %s", err) + } + + return string(certBuffer.Bytes()), string(keyBuffer.Bytes()), nil +} + +func getBaseCertTemplate( + cn string, + ips []interface{}, + alternateDNS []interface{}, + daysValid int, +) (*x509.Certificate, error) { + ipAddresses, err := getNetIPs(ips) + if err != nil { + return nil, err + } + dnsNames, err := getAlternateDNSStrs(alternateDNS) + if err != nil { + return nil, err + } + serialNumberUpperBound := new(big.Int).Lsh(big.NewInt(1), 128) + serialNumber, err := rand.Int(rand.Reader, serialNumberUpperBound) + if err != nil { + return nil, err + } + return &x509.Certificate{ + SerialNumber: serialNumber, + Subject: pkix.Name{ + CommonName: cn, + }, + IPAddresses: ipAddresses, + DNSNames: dnsNames, + NotBefore: time.Now(), + NotAfter: time.Now().Add(time.Hour * 24 * time.Duration(daysValid)), + KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, + ExtKeyUsage: []x509.ExtKeyUsage{ + x509.ExtKeyUsageServerAuth, + x509.ExtKeyUsageClientAuth, + }, + BasicConstraintsValid: true, + }, nil +} + +func getNetIPs(ips []interface{}) ([]net.IP, error) { + if ips == nil { + return []net.IP{}, nil + } + var ipStr string + var ok bool + var netIP net.IP + netIPs := make([]net.IP, len(ips)) + for i, ip := range ips { + ipStr, ok = ip.(string) + if !ok { + return nil, fmt.Errorf("error parsing ip: %v is not a string", ip) + } + netIP = net.ParseIP(ipStr) + if netIP == nil { + return nil, fmt.Errorf("error parsing ip: %s", ipStr) + } + netIPs[i] = netIP + } + return netIPs, nil +} + +func getAlternateDNSStrs(alternateDNS []interface{}) ([]string, error) { + if alternateDNS == nil { + return []string{}, nil + } + var dnsStr string + var ok bool + alternateDNSStrs := make([]string, len(alternateDNS)) + for i, dns := range alternateDNS { + dnsStr, ok = dns.(string) + if !ok { + return nil, fmt.Errorf( + "error processing alternate dns name: %v is not a string", + dns, + ) + } + alternateDNSStrs[i] = dnsStr + } + return alternateDNSStrs, nil +} + +func encryptAES(password string, plaintext string) (string, error) { + if plaintext == "" { + return "", nil + } + + key := make([]byte, 32) + copy(key, []byte(password)) + block, err := aes.NewCipher(key) + if err != nil { + return "", err + } + + content := []byte(plaintext) + blockSize := block.BlockSize() + padding := blockSize - len(content)%blockSize + padtext := bytes.Repeat([]byte{byte(padding)}, padding) + content = append(content, padtext...) + + ciphertext := make([]byte, aes.BlockSize+len(content)) + + iv := ciphertext[:aes.BlockSize] + if _, err := io.ReadFull(rand.Reader, iv); err != nil { + return "", err + } + + mode := cipher.NewCBCEncrypter(block, iv) + mode.CryptBlocks(ciphertext[aes.BlockSize:], content) + + return base64.StdEncoding.EncodeToString(ciphertext), nil +} + +func decryptAES(password string, crypt64 string) (string, error) { + if crypt64 == "" { + return "", nil + } + + key := make([]byte, 32) + copy(key, []byte(password)) + + crypt, err := base64.StdEncoding.DecodeString(crypt64) + if err != nil { + return "", err + } + + block, err := aes.NewCipher(key) + if err != nil { + return "", err + } + + iv := crypt[:aes.BlockSize] + crypt = crypt[aes.BlockSize:] + decrypted := make([]byte, len(crypt)) + mode := cipher.NewCBCDecrypter(block, iv) + mode.CryptBlocks(decrypted, crypt) + + return string(decrypted[:len(decrypted)-int(decrypted[len(decrypted)-1])]), nil +} diff --git a/vendor/github.com/Masterminds/sprig/date.go b/vendor/github.com/Masterminds/sprig/date.go new file mode 100644 index 000000000..d1d6155d7 --- /dev/null +++ b/vendor/github.com/Masterminds/sprig/date.go @@ -0,0 +1,83 @@ +package sprig + +import ( + "strconv" + "time" +) + +// Given a format and a date, format the date string. +// +// Date can be a `time.Time` or an `int, int32, int64`. +// In the later case, it is treated as seconds since UNIX +// epoch. +func date(fmt string, date interface{}) string { + return dateInZone(fmt, date, "Local") +} + +func htmlDate(date interface{}) string { + return dateInZone("2006-01-02", date, "Local") +} + +func htmlDateInZone(date interface{}, zone string) string { + return dateInZone("2006-01-02", date, zone) +} + +func dateInZone(fmt string, date interface{}, zone string) string { + var t time.Time + switch date := date.(type) { + default: + t = time.Now() + case time.Time: + t = date + case *time.Time: + t = *date + case int64: + t = time.Unix(date, 0) + case int: + t = time.Unix(int64(date), 0) + case int32: + t = time.Unix(int64(date), 0) + } + + loc, err := time.LoadLocation(zone) + if err != nil { + loc, _ = time.LoadLocation("UTC") + } + + return t.In(loc).Format(fmt) +} + +func dateModify(fmt string, date time.Time) time.Time { + d, err := time.ParseDuration(fmt) + if err != nil { + return date + } + return date.Add(d) +} + +func dateAgo(date interface{}) string { + var t time.Time + + switch date := date.(type) { + default: + t = time.Now() + case time.Time: + t = date + case int64: + t = time.Unix(date, 0) + case int: + t = time.Unix(int64(date), 0) + } + // Drop resolution to seconds + duration := time.Since(t).Round(time.Second) + return duration.String() +} + +func toDate(fmt, str string) time.Time { + t, _ := time.ParseInLocation(fmt, str, time.Local) + return t +} + +func unixEpoch(date time.Time) string { + return strconv.FormatInt(date.Unix(), 10) +} diff --git a/vendor/github.com/Masterminds/sprig/defaults.go b/vendor/github.com/Masterminds/sprig/defaults.go new file mode 100644 index 000000000..ed6a8ab29 --- /dev/null +++ b/vendor/github.com/Masterminds/sprig/defaults.go @@ -0,0 +1,83 @@ +package sprig + +import ( + "encoding/json" + "reflect" +) + +// dfault checks whether `given` is set, and returns default if not set. +// +// This returns `d` if `given` appears not to be set, and `given` otherwise. +// +// For numeric types 0 is unset. +// For strings, maps, arrays, and slices, len() = 0 is considered unset. +// For bool, false is unset. +// Structs are never considered unset. +// +// For everything else, including pointers, a nil value is unset. +func dfault(d interface{}, given ...interface{}) interface{} { + + if empty(given) || empty(given[0]) { + return d + } + return given[0] +} + +// empty returns true if the given value has the zero value for its type. +func empty(given interface{}) bool { + g := reflect.ValueOf(given) + if !g.IsValid() { + return true + } + + // Basically adapted from text/template.isTrue + switch g.Kind() { + default: + return g.IsNil() + case reflect.Array, reflect.Slice, reflect.Map, reflect.String: + return g.Len() == 0 + case reflect.Bool: + return g.Bool() == false + case reflect.Complex64, reflect.Complex128: + return g.Complex() == 0 + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return g.Int() == 0 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return g.Uint() == 0 + case reflect.Float32, reflect.Float64: + return g.Float() == 0 + case reflect.Struct: + return false + } +} + +// coalesce returns the first non-empty value. +func coalesce(v ...interface{}) interface{} { + for _, val := range v { + if !empty(val) { + return val + } + } + return nil +} + +// toJson encodes an item into a JSON string +func toJson(v interface{}) string { + output, _ := json.Marshal(v) + return string(output) +} + +// toPrettyJson encodes an item into a pretty (indented) JSON string +func toPrettyJson(v interface{}) string { + output, _ := json.MarshalIndent(v, "", " ") + return string(output) +} + +// ternary returns the first value if the last value is true, otherwise returns the second value. +func ternary(vt interface{}, vf interface{}, v bool) interface{} { + if v { + return vt + } + + return vf +} diff --git a/vendor/github.com/Masterminds/sprig/dict.go b/vendor/github.com/Masterminds/sprig/dict.go new file mode 100644 index 000000000..738405b43 --- /dev/null +++ b/vendor/github.com/Masterminds/sprig/dict.go @@ -0,0 +1,119 @@ +package sprig + +import ( + "github.com/imdario/mergo" + "github.com/mitchellh/copystructure" +) + +func set(d map[string]interface{}, key string, value interface{}) map[string]interface{} { + d[key] = value + return d +} + +func unset(d map[string]interface{}, key string) map[string]interface{} { + delete(d, key) + return d +} + +func hasKey(d map[string]interface{}, key string) bool { + _, ok := d[key] + return ok +} + +func pluck(key string, d ...map[string]interface{}) []interface{} { + res := []interface{}{} + for _, dict := range d { + if val, ok := dict[key]; ok { + res = append(res, val) + } + } + return res +} + +func keys(dicts ...map[string]interface{}) []string { + k := []string{} + for _, dict := range dicts { + for key := range dict { + k = append(k, key) + } + } + return k +} + +func pick(dict map[string]interface{}, keys ...string) map[string]interface{} { + res := map[string]interface{}{} + for _, k := range keys { + if v, ok := dict[k]; ok { + res[k] = v + } + } + return res +} + +func omit(dict map[string]interface{}, keys ...string) map[string]interface{} { + res := map[string]interface{}{} + + omit := make(map[string]bool, len(keys)) + for _, k := range keys { + omit[k] = true + } + + for k, v := range dict { + if _, ok := omit[k]; !ok { + res[k] = v + } + } + return res +} + +func dict(v ...interface{}) map[string]interface{} { + dict := map[string]interface{}{} + lenv := len(v) + for i := 0; i < lenv; i += 2 { + key := strval(v[i]) + if i+1 >= lenv { + dict[key] = "" + continue + } + dict[key] = v[i+1] + } + return dict +} + +func merge(dst map[string]interface{}, srcs ...map[string]interface{}) interface{} { + for _, src := range srcs { + if err := mergo.Merge(&dst, src); err != nil { + // Swallow errors inside of a template. + return "" + } + } + return dst +} + +func mergeOverwrite(dst map[string]interface{}, srcs ...map[string]interface{}) interface{} { + for _, src := range srcs { + if err := mergo.MergeWithOverwrite(&dst, src); err != nil { + // Swallow errors inside of a template. + return "" + } + } + return dst +} + +func values(dict map[string]interface{}) []interface{} { + values := []interface{}{} + for _, value := range dict { + values = append(values, value) + } + + return values +} + +func deepCopy(i interface{}) interface{} { + c, err := copystructure.Copy(i) + if err != nil { + panic("deepCopy error: " + err.Error()) + } + + return c +} diff --git a/vendor/github.com/Masterminds/sprig/doc.go b/vendor/github.com/Masterminds/sprig/doc.go new file mode 100644 index 000000000..8f8f1d737 --- /dev/null +++ b/vendor/github.com/Masterminds/sprig/doc.go @@ -0,0 +1,19 @@ +/* +Sprig: Template functions for Go. + +This package contains a number of utility functions for working with data +inside of Go `html/template` and `text/template` files. + +To add these functions, use the `template.Funcs()` method: + + t := templates.New("foo").Funcs(sprig.FuncMap()) + +Note that you should add the function map before you parse any template files. + + In several cases, Sprig reverses the order of arguments from the way they + appear in the standard library. This is to make it easier to pipe + arguments into functions. + +See http://masterminds.github.io/sprig/ for more detailed documentation on each of the available functions. +*/ +package sprig diff --git a/vendor/github.com/Masterminds/sprig/functions.go b/vendor/github.com/Masterminds/sprig/functions.go new file mode 100644 index 000000000..7b5b0af86 --- /dev/null +++ b/vendor/github.com/Masterminds/sprig/functions.go @@ -0,0 +1,306 @@ +package sprig + +import ( + "errors" + "html/template" + "os" + "path" + "reflect" + "strconv" + "strings" + ttemplate "text/template" + "time" + + util "github.com/Masterminds/goutils" + "github.com/huandu/xstrings" +) + +// Produce the function map. +// +// Use this to pass the functions into the template engine: +// +// tpl := template.New("foo").Funcs(sprig.FuncMap())) +// +func FuncMap() template.FuncMap { + return HtmlFuncMap() +} + +// HermeticTxtFuncMap returns a 'text/template'.FuncMap with only repeatable functions. +func HermeticTxtFuncMap() ttemplate.FuncMap { + r := TxtFuncMap() + for _, name := range nonhermeticFunctions { + delete(r, name) + } + return r +} + +// HermeticHtmlFuncMap returns an 'html/template'.Funcmap with only repeatable functions. +func HermeticHtmlFuncMap() template.FuncMap { + r := HtmlFuncMap() + for _, name := range nonhermeticFunctions { + delete(r, name) + } + return r +} + +// TxtFuncMap returns a 'text/template'.FuncMap +func TxtFuncMap() ttemplate.FuncMap { + return ttemplate.FuncMap(GenericFuncMap()) +} + +// HtmlFuncMap returns an 'html/template'.Funcmap +func HtmlFuncMap() template.FuncMap { + return template.FuncMap(GenericFuncMap()) +} + +// GenericFuncMap returns a copy of the basic function map as a map[string]interface{}. +func GenericFuncMap() map[string]interface{} { + gfm := make(map[string]interface{}, len(genericMap)) + for k, v := range genericMap { + gfm[k] = v + } + return gfm +} + +// These functions are not guaranteed to evaluate to the same result for given input, because they +// refer to the environemnt or global state. +var nonhermeticFunctions = []string{ + // Date functions + "date", + "date_in_zone", + "date_modify", + "now", + "htmlDate", + "htmlDateInZone", + "dateInZone", + "dateModify", + + // Strings + "randAlphaNum", + "randAlpha", + "randAscii", + "randNumeric", + "uuidv4", + + // OS + "env", + "expandenv", + + // Network + "getHostByName", +} + +var genericMap = map[string]interface{}{ + "hello": func() string { return "Hello!" }, + + // Date functions + "date": date, + "date_in_zone": dateInZone, + "date_modify": dateModify, + "now": func() time.Time { return time.Now() }, + "htmlDate": htmlDate, + "htmlDateInZone": htmlDateInZone, + "dateInZone": dateInZone, + "dateModify": dateModify, + "ago": dateAgo, + "toDate": toDate, + "unixEpoch": unixEpoch, + + // Strings + "abbrev": abbrev, + "abbrevboth": abbrevboth, + "trunc": trunc, + "trim": strings.TrimSpace, + "upper": strings.ToUpper, + "lower": strings.ToLower, + "title": strings.Title, + "untitle": untitle, + "substr": substring, + // Switch order so that "foo" | repeat 5 + "repeat": func(count int, str string) string { return strings.Repeat(str, count) }, + // Deprecated: Use trimAll. + "trimall": func(a, b string) string { return strings.Trim(b, a) }, + // Switch order so that "$foo" | trimall "$" + "trimAll": func(a, b string) string { return strings.Trim(b, a) }, + "trimSuffix": func(a, b string) string { return strings.TrimSuffix(b, a) }, + "trimPrefix": func(a, b string) string { return strings.TrimPrefix(b, a) }, + "nospace": util.DeleteWhiteSpace, + "initials": initials, + "randAlphaNum": randAlphaNumeric, + "randAlpha": randAlpha, + "randAscii": randAscii, + "randNumeric": randNumeric, + "swapcase": util.SwapCase, + "shuffle": xstrings.Shuffle, + "snakecase": xstrings.ToSnakeCase, + "camelcase": xstrings.ToCamelCase, + "kebabcase": xstrings.ToKebabCase, + "wrap": func(l int, s string) string { return util.Wrap(s, l) }, + "wrapWith": func(l int, sep, str string) string { return util.WrapCustom(str, l, sep, true) }, + // Switch order so that "foobar" | contains "foo" + "contains": func(substr string, str string) bool { return strings.Contains(str, substr) }, + "hasPrefix": func(substr string, str string) bool { return strings.HasPrefix(str, substr) }, + "hasSuffix": func(substr string, str string) bool { return strings.HasSuffix(str, substr) }, + "quote": quote, + "squote": squote, + "cat": cat, + "indent": indent, + "nindent": nindent, + "replace": replace, + "plural": plural, + "sha1sum": sha1sum, + "sha256sum": sha256sum, + "adler32sum": adler32sum, + "toString": strval, + + // Wrap Atoi to stop errors. + "atoi": func(a string) int { i, _ := strconv.Atoi(a); return i }, + "int64": toInt64, + "int": toInt, + "float64": toFloat64, + "toDecimal": toDecimal, + + //"gt": func(a, b int) bool {return a > b}, + //"gte": func(a, b int) bool {return a >= b}, + //"lt": func(a, b int) bool {return a < b}, + //"lte": func(a, b int) bool {return a <= b}, + + // split "/" foo/bar returns map[int]string{0: foo, 1: bar} + "split": split, + "splitList": func(sep, orig string) []string { return strings.Split(orig, sep) }, + // splitn "/" foo/bar/fuu returns map[int]string{0: foo, 1: bar/fuu} + "splitn": splitn, + "toStrings": strslice, + + "until": until, + "untilStep": untilStep, + + // VERY basic arithmetic. + "add1": func(i interface{}) int64 { return toInt64(i) + 1 }, + "add": func(i ...interface{}) int64 { + var a int64 = 0 + for _, b := range i { + a += toInt64(b) + } + return a + }, + "sub": func(a, b interface{}) int64 { return toInt64(a) - toInt64(b) }, + "div": func(a, b interface{}) int64 { return toInt64(a) / toInt64(b) }, + "mod": func(a, b interface{}) int64 { return toInt64(a) % toInt64(b) }, + "mul": func(a interface{}, v ...interface{}) int64 { + val := toInt64(a) + for _, b := range v { + val = val * toInt64(b) + } + return val + }, + "biggest": max, + "max": max, + "min": min, + "ceil": ceil, + "floor": floor, + "round": round, + + // string slices. Note that we reverse the order b/c that's better + // for template processing. + "join": join, + "sortAlpha": sortAlpha, + + // Defaults + "default": dfault, + "empty": empty, + "coalesce": coalesce, + "compact": compact, + "deepCopy": deepCopy, + "toJson": toJson, + "toPrettyJson": toPrettyJson, + "ternary": ternary, + + // Reflection + "typeOf": typeOf, + "typeIs": typeIs, + "typeIsLike": typeIsLike, + "kindOf": kindOf, + "kindIs": kindIs, + "deepEqual": reflect.DeepEqual, + + // OS: + "env": func(s string) string { return os.Getenv(s) }, + "expandenv": func(s string) string { return os.ExpandEnv(s) }, + + // Network: + "getHostByName": getHostByName, + + // File Paths: + "base": path.Base, + "dir": path.Dir, + "clean": path.Clean, + "ext": path.Ext, + "isAbs": path.IsAbs, + + // Encoding: + "b64enc": base64encode, + "b64dec": base64decode, + "b32enc": base32encode, + "b32dec": base32decode, + + // Data Structures: + "tuple": list, // FIXME: with the addition of append/prepend these are no longer immutable. + "list": list, + "dict": dict, + "set": set, + "unset": unset, + "hasKey": hasKey, + "pluck": pluck, + "keys": keys, + "pick": pick, + "omit": omit, + "merge": merge, + "mergeOverwrite": mergeOverwrite, + "values": values, + + "append": push, "push": push, + "prepend": prepend, + "first": first, + "rest": rest, + "last": last, + "initial": initial, + "reverse": reverse, + "uniq": uniq, + "without": without, + "has": has, + "slice": slice, + "concat": concat, + + // Crypto: + "genPrivateKey": generatePrivateKey, + "derivePassword": derivePassword, + "buildCustomCert": buildCustomCertificate, + "genCA": generateCertificateAuthority, + "genSelfSignedCert": generateSelfSignedCertificate, + "genSignedCert": generateSignedCertificate, + "encryptAES": encryptAES, + "decryptAES": decryptAES, + + // UUIDs: + "uuidv4": uuidv4, + + // SemVer: + "semver": semver, + "semverCompare": semverCompare, + + // Flow Control: + "fail": func(msg string) (string, error) { return "", errors.New(msg) }, + + // Regex + "regexMatch": regexMatch, + "regexFindAll": regexFindAll, + "regexFind": regexFind, + "regexReplaceAll": regexReplaceAll, + "regexReplaceAllLiteral": regexReplaceAllLiteral, + "regexSplit": regexSplit, + + // URLs: + "urlParse": urlParse, + "urlJoin": urlJoin, +} diff --git a/vendor/github.com/Masterminds/sprig/glide.yaml b/vendor/github.com/Masterminds/sprig/glide.yaml new file mode 100644 index 000000000..f317d2b2b --- /dev/null +++ b/vendor/github.com/Masterminds/sprig/glide.yaml @@ -0,0 +1,19 @@ +package: github.com/Masterminds/sprig +import: +- package: github.com/Masterminds/goutils + version: ^1.0.0 +- package: github.com/google/uuid + version: ^1.0.0 +- package: golang.org/x/crypto + subpackages: + - scrypt +- package: github.com/Masterminds/semver + version: ^v1.2.2 +- package: github.com/stretchr/testify + version: ^v1.2.2 +- package: github.com/imdario/mergo + version: ~0.3.7 +- package: github.com/huandu/xstrings + version: ^1.2 +- package: github.com/mitchellh/copystructure + version: ^1.0.0 diff --git a/vendor/github.com/Masterminds/sprig/list.go b/vendor/github.com/Masterminds/sprig/list.go new file mode 100644 index 000000000..c0381bbb6 --- /dev/null +++ b/vendor/github.com/Masterminds/sprig/list.go @@ -0,0 +1,311 @@ +package sprig + +import ( + "fmt" + "reflect" + "sort" +) + +// Reflection is used in these functions so that slices and arrays of strings, +// ints, and other types not implementing []interface{} can be worked with. +// For example, this is useful if you need to work on the output of regexs. + +func list(v ...interface{}) []interface{} { + return v +} + +func push(list interface{}, v interface{}) []interface{} { + tp := reflect.TypeOf(list).Kind() + switch tp { + case reflect.Slice, reflect.Array: + l2 := reflect.ValueOf(list) + + l := l2.Len() + nl := make([]interface{}, l) + for i := 0; i < l; i++ { + nl[i] = l2.Index(i).Interface() + } + + return append(nl, v) + + default: + panic(fmt.Sprintf("Cannot push on type %s", tp)) + } +} + +func prepend(list interface{}, v interface{}) []interface{} { + //return append([]interface{}{v}, list...) + + tp := reflect.TypeOf(list).Kind() + switch tp { + case reflect.Slice, reflect.Array: + l2 := reflect.ValueOf(list) + + l := l2.Len() + nl := make([]interface{}, l) + for i := 0; i < l; i++ { + nl[i] = l2.Index(i).Interface() + } + + return append([]interface{}{v}, nl...) + + default: + panic(fmt.Sprintf("Cannot prepend on type %s", tp)) + } +} + +func last(list interface{}) interface{} { + tp := reflect.TypeOf(list).Kind() + switch tp { + case reflect.Slice, reflect.Array: + l2 := reflect.ValueOf(list) + + l := l2.Len() + if l == 0 { + return nil + } + + return l2.Index(l - 1).Interface() + default: + panic(fmt.Sprintf("Cannot find last on type %s", tp)) + } +} + +func first(list interface{}) interface{} { + tp := reflect.TypeOf(list).Kind() + switch tp { + case reflect.Slice, reflect.Array: + l2 := reflect.ValueOf(list) + + l := l2.Len() + if l == 0 { + return nil + } + + return l2.Index(0).Interface() + default: + panic(fmt.Sprintf("Cannot find first on type %s", tp)) + } +} + +func rest(list interface{}) []interface{} { + tp := reflect.TypeOf(list).Kind() + switch tp { + case reflect.Slice, reflect.Array: + l2 := reflect.ValueOf(list) + + l := l2.Len() + if l == 0 { + return nil + } + + nl := make([]interface{}, l-1) + for i := 1; i < l; i++ { + nl[i-1] = l2.Index(i).Interface() + } + + return nl + default: + panic(fmt.Sprintf("Cannot find rest on type %s", tp)) + } +} + +func initial(list interface{}) []interface{} { + tp := reflect.TypeOf(list).Kind() + switch tp { + case reflect.Slice, reflect.Array: + l2 := reflect.ValueOf(list) + + l := l2.Len() + if l == 0 { + return nil + } + + nl := make([]interface{}, l-1) + for i := 0; i < l-1; i++ { + nl[i] = l2.Index(i).Interface() + } + + return nl + default: + panic(fmt.Sprintf("Cannot find initial on type %s", tp)) + } +} + +func sortAlpha(list interface{}) []string { + k := reflect.Indirect(reflect.ValueOf(list)).Kind() + switch k { + case reflect.Slice, reflect.Array: + a := strslice(list) + s := sort.StringSlice(a) + s.Sort() + return s + } + return []string{strval(list)} +} + +func reverse(v interface{}) []interface{} { + tp := reflect.TypeOf(v).Kind() + switch tp { + case reflect.Slice, reflect.Array: + l2 := reflect.ValueOf(v) + + l := l2.Len() + // We do not sort in place because the incoming array should not be altered. + nl := make([]interface{}, l) + for i := 0; i < l; i++ { + nl[l-i-1] = l2.Index(i).Interface() + } + + return nl + default: + panic(fmt.Sprintf("Cannot find reverse on type %s", tp)) + } +} + +func compact(list interface{}) []interface{} { + tp := reflect.TypeOf(list).Kind() + switch tp { + case reflect.Slice, reflect.Array: + l2 := reflect.ValueOf(list) + + l := l2.Len() + nl := []interface{}{} + var item interface{} + for i := 0; i < l; i++ { + item = l2.Index(i).Interface() + if !empty(item) { + nl = append(nl, item) + } + } + + return nl + default: + panic(fmt.Sprintf("Cannot compact on type %s", tp)) + } +} + +func uniq(list interface{}) []interface{} { + tp := reflect.TypeOf(list).Kind() + switch tp { + case reflect.Slice, reflect.Array: + l2 := reflect.ValueOf(list) + + l := l2.Len() + dest := []interface{}{} + var item interface{} + for i := 0; i < l; i++ { + item = l2.Index(i).Interface() + if !inList(dest, item) { + dest = append(dest, item) + } + } + + return dest + default: + panic(fmt.Sprintf("Cannot find uniq on type %s", tp)) + } +} + +func inList(haystack []interface{}, needle interface{}) bool { + for _, h := range haystack { + if reflect.DeepEqual(needle, h) { + return true + } + } + return false +} + +func without(list interface{}, omit ...interface{}) []interface{} { + tp := reflect.TypeOf(list).Kind() + switch tp { + case reflect.Slice, reflect.Array: + l2 := reflect.ValueOf(list) + + l := l2.Len() + res := []interface{}{} + var item interface{} + for i := 0; i < l; i++ { + item = l2.Index(i).Interface() + if !inList(omit, item) { + res = append(res, item) + } + } + + return res + default: + panic(fmt.Sprintf("Cannot find without on type %s", tp)) + } +} + +func has(needle interface{}, haystack interface{}) bool { + if haystack == nil { + return false + } + tp := reflect.TypeOf(haystack).Kind() + switch tp { + case reflect.Slice, reflect.Array: + l2 := reflect.ValueOf(haystack) + var item interface{} + l := l2.Len() + for i := 0; i < l; i++ { + item = l2.Index(i).Interface() + if reflect.DeepEqual(needle, item) { + return true + } + } + + return false + default: + panic(fmt.Sprintf("Cannot find has on type %s", tp)) + } +} + +// $list := [1, 2, 3, 4, 5] +// slice $list -> list[0:5] = list[:] +// slice $list 0 3 -> list[0:3] = list[:3] +// slice $list 3 5 -> list[3:5] +// slice $list 3 -> list[3:5] = list[3:] +func slice(list interface{}, indices ...interface{}) interface{} { + tp := reflect.TypeOf(list).Kind() + switch tp { + case reflect.Slice, reflect.Array: + l2 := reflect.ValueOf(list) + + l := l2.Len() + if l == 0 { + return nil + } + + var start, end int + if len(indices) > 0 { + start = toInt(indices[0]) + } + if len(indices) < 2 { + end = l + } else { + end = toInt(indices[1]) + } + + return l2.Slice(start, end).Interface() + default: + panic(fmt.Sprintf("list should be type of slice or array but %s", tp)) + } +} + +func concat(lists ...interface{}) interface{} { + var res []interface{} + for _, list := range lists { + tp := reflect.TypeOf(list).Kind() + switch tp { + case reflect.Slice, reflect.Array: + l2 := reflect.ValueOf(list) + for i := 0; i < l2.Len(); i++ { + res = append(res, l2.Index(i).Interface()) + } + default: + panic(fmt.Sprintf("Cannot concat type %s as list", tp)) + } + } + return res +} diff --git a/vendor/github.com/Masterminds/sprig/network.go b/vendor/github.com/Masterminds/sprig/network.go new file mode 100644 index 000000000..d786cc736 --- /dev/null +++ b/vendor/github.com/Masterminds/sprig/network.go @@ -0,0 +1,12 @@ +package sprig + +import ( + "math/rand" + "net" +) + +func getHostByName(name string) string { + addrs, _ := net.LookupHost(name) + //TODO: add error handing when release v3 cames out + return addrs[rand.Intn(len(addrs))] +} diff --git a/vendor/github.com/Masterminds/sprig/numeric.go b/vendor/github.com/Masterminds/sprig/numeric.go new file mode 100644 index 000000000..f4af4af2a --- /dev/null +++ b/vendor/github.com/Masterminds/sprig/numeric.go @@ -0,0 +1,169 @@ +package sprig + +import ( + "fmt" + "math" + "reflect" + "strconv" +) + +// toFloat64 converts 64-bit floats +func toFloat64(v interface{}) float64 { + if str, ok := v.(string); ok { + iv, err := strconv.ParseFloat(str, 64) + if err != nil { + return 0 + } + return iv + } + + val := reflect.Indirect(reflect.ValueOf(v)) + switch val.Kind() { + case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int: + return float64(val.Int()) + case reflect.Uint8, reflect.Uint16, reflect.Uint32: + return float64(val.Uint()) + case reflect.Uint, reflect.Uint64: + return float64(val.Uint()) + case reflect.Float32, reflect.Float64: + return val.Float() + case reflect.Bool: + if val.Bool() == true { + return 1 + } + return 0 + default: + return 0 + } +} + +func toInt(v interface{}) int { + //It's not optimal. Bud I don't want duplicate toInt64 code. + return int(toInt64(v)) +} + +// toInt64 converts integer types to 64-bit integers +func toInt64(v interface{}) int64 { + if str, ok := v.(string); ok { + iv, err := strconv.ParseInt(str, 10, 64) + if err != nil { + return 0 + } + return iv + } + + val := reflect.Indirect(reflect.ValueOf(v)) + switch val.Kind() { + case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int: + return val.Int() + case reflect.Uint8, reflect.Uint16, reflect.Uint32: + return int64(val.Uint()) + case reflect.Uint, reflect.Uint64: + tv := val.Uint() + if tv <= math.MaxInt64 { + return int64(tv) + } + // TODO: What is the sensible thing to do here? + return math.MaxInt64 + case reflect.Float32, reflect.Float64: + return int64(val.Float()) + case reflect.Bool: + if val.Bool() == true { + return 1 + } + return 0 + default: + return 0 + } +} + +func max(a interface{}, i ...interface{}) int64 { + aa := toInt64(a) + for _, b := range i { + bb := toInt64(b) + if bb > aa { + aa = bb + } + } + return aa +} + +func min(a interface{}, i ...interface{}) int64 { + aa := toInt64(a) + for _, b := range i { + bb := toInt64(b) + if bb < aa { + aa = bb + } + } + return aa +} + +func until(count int) []int { + step := 1 + if count < 0 { + step = -1 + } + return untilStep(0, count, step) +} + +func untilStep(start, stop, step int) []int { + v := []int{} + + if stop < start { + if step >= 0 { + return v + } + for i := start; i > stop; i += step { + v = append(v, i) + } + return v + } + + if step <= 0 { + return v + } + for i := start; i < stop; i += step { + v = append(v, i) + } + return v +} + +func floor(a interface{}) float64 { + aa := toFloat64(a) + return math.Floor(aa) +} + +func ceil(a interface{}) float64 { + aa := toFloat64(a) + return math.Ceil(aa) +} + +func round(a interface{}, p int, r_opt ...float64) float64 { + roundOn := .5 + if len(r_opt) > 0 { + roundOn = r_opt[0] + } + val := toFloat64(a) + places := toFloat64(p) + + var round float64 + pow := math.Pow(10, places) + digit := pow * val + _, div := math.Modf(digit) + if div >= roundOn { + round = math.Ceil(digit) + } else { + round = math.Floor(digit) + } + return round / pow +} + +// converts unix octal to decimal +func toDecimal(v interface{}) int64 { + result, err := strconv.ParseInt(fmt.Sprint(v), 8, 64) + if err != nil { + return 0 + } + return result +} diff --git a/vendor/github.com/Masterminds/sprig/reflect.go b/vendor/github.com/Masterminds/sprig/reflect.go new file mode 100644 index 000000000..8a65c132f --- /dev/null +++ b/vendor/github.com/Masterminds/sprig/reflect.go @@ -0,0 +1,28 @@ +package sprig + +import ( + "fmt" + "reflect" +) + +// typeIs returns true if the src is the type named in target. +func typeIs(target string, src interface{}) bool { + return target == typeOf(src) +} + +func typeIsLike(target string, src interface{}) bool { + t := typeOf(src) + return target == t || "*"+target == t +} + +func typeOf(src interface{}) string { + return fmt.Sprintf("%T", src) +} + +func kindIs(target string, src interface{}) bool { + return target == kindOf(src) +} + +func kindOf(src interface{}) string { + return reflect.ValueOf(src).Kind().String() +} diff --git a/vendor/github.com/Masterminds/sprig/regex.go b/vendor/github.com/Masterminds/sprig/regex.go new file mode 100644 index 000000000..2016f6633 --- /dev/null +++ b/vendor/github.com/Masterminds/sprig/regex.go @@ -0,0 +1,35 @@ +package sprig + +import ( + "regexp" +) + +func regexMatch(regex string, s string) bool { + match, _ := regexp.MatchString(regex, s) + return match +} + +func regexFindAll(regex string, s string, n int) []string { + r := regexp.MustCompile(regex) + return r.FindAllString(s, n) +} + +func regexFind(regex string, s string) string { + r := regexp.MustCompile(regex) + return r.FindString(s) +} + +func regexReplaceAll(regex string, s string, repl string) string { + r := regexp.MustCompile(regex) + return r.ReplaceAllString(s, repl) +} + +func regexReplaceAllLiteral(regex string, s string, repl string) string { + r := regexp.MustCompile(regex) + return r.ReplaceAllLiteralString(s, repl) +} + +func regexSplit(regex string, s string, n int) []string { + r := regexp.MustCompile(regex) + return r.Split(s, n) +} diff --git a/vendor/github.com/Masterminds/sprig/semver.go b/vendor/github.com/Masterminds/sprig/semver.go new file mode 100644 index 000000000..c2bf8a1fd --- /dev/null +++ b/vendor/github.com/Masterminds/sprig/semver.go @@ -0,0 +1,23 @@ +package sprig + +import ( + sv2 "github.com/Masterminds/semver" +) + +func semverCompare(constraint, version string) (bool, error) { + c, err := sv2.NewConstraint(constraint) + if err != nil { + return false, err + } + + v, err := sv2.NewVersion(version) + if err != nil { + return false, err + } + + return c.Check(v), nil +} + +func semver(version string) (*sv2.Version, error) { + return sv2.NewVersion(version) +} diff --git a/vendor/github.com/Masterminds/sprig/strings.go b/vendor/github.com/Masterminds/sprig/strings.go new file mode 100644 index 000000000..943fa3e8a --- /dev/null +++ b/vendor/github.com/Masterminds/sprig/strings.go @@ -0,0 +1,233 @@ +package sprig + +import ( + "encoding/base32" + "encoding/base64" + "fmt" + "reflect" + "strconv" + "strings" + + util "github.com/Masterminds/goutils" +) + +func base64encode(v string) string { + return base64.StdEncoding.EncodeToString([]byte(v)) +} + +func base64decode(v string) string { + data, err := base64.StdEncoding.DecodeString(v) + if err != nil { + return err.Error() + } + return string(data) +} + +func base32encode(v string) string { + return base32.StdEncoding.EncodeToString([]byte(v)) +} + +func base32decode(v string) string { + data, err := base32.StdEncoding.DecodeString(v) + if err != nil { + return err.Error() + } + return string(data) +} + +func abbrev(width int, s string) string { + if width < 4 { + return s + } + r, _ := util.Abbreviate(s, width) + return r +} + +func abbrevboth(left, right int, s string) string { + if right < 4 || left > 0 && right < 7 { + return s + } + r, _ := util.AbbreviateFull(s, left, right) + return r +} +func initials(s string) string { + // Wrap this just to eliminate the var args, which templates don't do well. + return util.Initials(s) +} + +func randAlphaNumeric(count int) string { + // It is not possible, it appears, to actually generate an error here. + r, _ := util.CryptoRandomAlphaNumeric(count) + return r +} + +func randAlpha(count int) string { + r, _ := util.CryptoRandomAlphabetic(count) + return r +} + +func randAscii(count int) string { + r, _ := util.CryptoRandomAscii(count) + return r +} + +func randNumeric(count int) string { + r, _ := util.CryptoRandomNumeric(count) + return r +} + +func untitle(str string) string { + return util.Uncapitalize(str) +} + +func quote(str ...interface{}) string { + out := make([]string, 0, len(str)) + for _, s := range str { + if s != nil { + out = append(out, fmt.Sprintf("%q", strval(s))) + } + } + return strings.Join(out, " ") +} + +func squote(str ...interface{}) string { + out := make([]string, 0, len(str)) + for _, s := range str { + if s != nil { + out = append(out, fmt.Sprintf("'%v'", s)) + } + } + return strings.Join(out, " ") +} + +func cat(v ...interface{}) string { + v = removeNilElements(v) + r := strings.TrimSpace(strings.Repeat("%v ", len(v))) + return fmt.Sprintf(r, v...) +} + +func indent(spaces int, v string) string { + pad := strings.Repeat(" ", spaces) + return pad + strings.Replace(v, "\n", "\n"+pad, -1) +} + +func nindent(spaces int, v string) string { + return "\n" + indent(spaces, v) +} + +func replace(old, new, src string) string { + return strings.Replace(src, old, new, -1) +} + +func plural(one, many string, count int) string { + if count == 1 { + return one + } + return many +} + +func strslice(v interface{}) []string { + switch v := v.(type) { + case []string: + return v + case []interface{}: + b := make([]string, 0, len(v)) + for _, s := range v { + if s != nil { + b = append(b, strval(s)) + } + } + return b + default: + val := reflect.ValueOf(v) + switch val.Kind() { + case reflect.Array, reflect.Slice: + l := val.Len() + b := make([]string, 0, l) + for i := 0; i < l; i++ { + value := val.Index(i).Interface() + if value != nil { + b = append(b, strval(value)) + } + } + return b + default: + if v == nil { + return []string{} + } else { + return []string{strval(v)} + } + } + } +} + +func removeNilElements(v []interface{}) []interface{} { + newSlice := make([]interface{}, 0, len(v)) + for _, i := range v { + if i != nil { + newSlice = append(newSlice, i) + } + } + return newSlice +} + +func strval(v interface{}) string { + switch v := v.(type) { + case string: + return v + case []byte: + return string(v) + case error: + return v.Error() + case fmt.Stringer: + return v.String() + default: + return fmt.Sprintf("%v", v) + } +} + +func trunc(c int, s string) string { + if len(s) <= c { + return s + } + return s[0:c] +} + +func join(sep string, v interface{}) string { + return strings.Join(strslice(v), sep) +} + +func split(sep, orig string) map[string]string { + parts := strings.Split(orig, sep) + res := make(map[string]string, len(parts)) + for i, v := range parts { + res["_"+strconv.Itoa(i)] = v + } + return res +} + +func splitn(sep string, n int, orig string) map[string]string { + parts := strings.SplitN(orig, sep, n) + res := make(map[string]string, len(parts)) + for i, v := range parts { + res["_"+strconv.Itoa(i)] = v + } + return res +} + +// substring creates a substring of the given string. +// +// If start is < 0, this calls string[:end]. +// +// If start is >= 0 and end < 0 or end bigger than s length, this calls string[start:] +// +// Otherwise, this calls string[start, end]. +func substring(start, end int, s string) string { + if start < 0 { + return s[:end] + } + if end < 0 || end > len(s) { + return s[start:] + } + return s[start:end] +} diff --git a/vendor/github.com/Masterminds/sprig/url.go b/vendor/github.com/Masterminds/sprig/url.go new file mode 100644 index 000000000..5f22d801f --- /dev/null +++ b/vendor/github.com/Masterminds/sprig/url.go @@ -0,0 +1,66 @@ +package sprig + +import ( + "fmt" + "net/url" + "reflect" +) + +func dictGetOrEmpty(dict map[string]interface{}, key string) string { + value, ok := dict[key]; if !ok { + return "" + } + tp := reflect.TypeOf(value).Kind() + if tp != reflect.String { + panic(fmt.Sprintf("unable to parse %s key, must be of type string, but %s found", key, tp.String())) + } + return reflect.ValueOf(value).String() +} + +// parses given URL to return dict object +func urlParse(v string) map[string]interface{} { + dict := map[string]interface{}{} + parsedUrl, err := url.Parse(v) + if err != nil { + panic(fmt.Sprintf("unable to parse url: %s", err)) + } + dict["scheme"] = parsedUrl.Scheme + dict["host"] = parsedUrl.Host + dict["hostname"] = parsedUrl.Hostname() + dict["path"] = parsedUrl.Path + dict["query"] = parsedUrl.RawQuery + dict["opaque"] = parsedUrl.Opaque + dict["fragment"] = parsedUrl.Fragment + if parsedUrl.User != nil { + dict["userinfo"] = parsedUrl.User.String() + } else { + dict["userinfo"] = "" + } + + return dict +} + +// join given dict to URL string +func urlJoin(d map[string]interface{}) string { + resUrl := url.URL{ + Scheme: dictGetOrEmpty(d, "scheme"), + Host: dictGetOrEmpty(d, "host"), + Path: dictGetOrEmpty(d, "path"), + RawQuery: dictGetOrEmpty(d, "query"), + Opaque: dictGetOrEmpty(d, "opaque"), + Fragment: dictGetOrEmpty(d, "fragment"), + + } + userinfo := dictGetOrEmpty(d, "userinfo") + var user *url.Userinfo = nil + if userinfo != "" { + tempUrl, err := url.Parse(fmt.Sprintf("proto://%s@host", userinfo)) + if err != nil { + panic(fmt.Sprintf("unable to parse userinfo in dict: %s", err)) + } + user = tempUrl.User + } + + resUrl.User = user + return resUrl.String() +} diff --git a/vendor/github.com/fsnotify/fsnotify/.editorconfig b/vendor/github.com/fsnotify/fsnotify/.editorconfig new file mode 100644 index 000000000..fad895851 --- /dev/null +++ b/vendor/github.com/fsnotify/fsnotify/.editorconfig @@ -0,0 +1,12 @@ +root = true + +[*.go] +indent_style = tab +indent_size = 4 +insert_final_newline = true + +[*.{yml,yaml}] +indent_style = space +indent_size = 2 +insert_final_newline = true +trim_trailing_whitespace = true diff --git a/vendor/github.com/fsnotify/fsnotify/.gitattributes b/vendor/github.com/fsnotify/fsnotify/.gitattributes new file mode 100644 index 000000000..32f1001be --- /dev/null +++ b/vendor/github.com/fsnotify/fsnotify/.gitattributes @@ -0,0 +1 @@ +go.sum linguist-generated diff --git a/vendor/github.com/fsnotify/fsnotify/.gitignore b/vendor/github.com/fsnotify/fsnotify/.gitignore new file mode 100644 index 000000000..4cd0cbaf4 --- /dev/null +++ b/vendor/github.com/fsnotify/fsnotify/.gitignore @@ -0,0 +1,6 @@ +# Setup a Global .gitignore for OS and editor generated files: +# https://help.github.com/articles/ignoring-files +# git config --global core.excludesfile ~/.gitignore_global + +.vagrant +*.sublime-project diff --git a/vendor/github.com/fsnotify/fsnotify/.travis.yml b/vendor/github.com/fsnotify/fsnotify/.travis.yml new file mode 100644 index 000000000..a9c30165c --- /dev/null +++ b/vendor/github.com/fsnotify/fsnotify/.travis.yml @@ -0,0 +1,36 @@ +sudo: false +language: go + +go: + - "stable" + - "1.11.x" + - "1.10.x" + - "1.9.x" + +matrix: + include: + - go: "stable" + env: GOLINT=true + allow_failures: + - go: tip + fast_finish: true + + +before_install: + - if [ ! -z "${GOLINT}" ]; then go get -u golang.org/x/lint/golint; fi + +script: + - go test --race ./... + +after_script: + - test -z "$(gofmt -s -l -w . | tee /dev/stderr)" + - if [ ! -z "${GOLINT}" ]; then echo running golint; golint --set_exit_status ./...; else echo skipping golint; fi + - go vet ./... + +os: + - linux + - osx + - windows + +notifications: + email: false diff --git a/vendor/github.com/fsnotify/fsnotify/AUTHORS b/vendor/github.com/fsnotify/fsnotify/AUTHORS new file mode 100644 index 000000000..5ab5d41c5 --- /dev/null +++ b/vendor/github.com/fsnotify/fsnotify/AUTHORS @@ -0,0 +1,52 @@ +# Names should be added to this file as +# Name or Organization +# The email address is not required for organizations. + +# You can update this list using the following command: +# +# $ git shortlog -se | awk '{print $2 " " $3 " " $4}' + +# Please keep the list sorted. + +Aaron L +Adrien Bustany +Amit Krishnan +Anmol Sethi +Bjørn Erik Pedersen +Bruno Bigras +Caleb Spare +Case Nelson +Chris Howey +Christoffer Buchholz +Daniel Wagner-Hall +Dave Cheney +Evan Phoenix +Francisco Souza +Hari haran +John C Barstow +Kelvin Fo +Ken-ichirou MATSUZAWA +Matt Layher +Nathan Youngman +Nickolai Zeldovich +Patrick +Paul Hammond +Pawel Knap +Pieter Droogendijk +Pursuit92 +Riku Voipio +Rob Figueiredo +Rodrigo Chiossi +Slawek Ligus +Soge Zhang +Tiffany Jernigan +Tilak Sharma +Tom Payne +Travis Cline +Tudor Golubenco +Vahe Khachikyan +Yukang +bronze1man +debrando +henrikedwards +铁哥 diff --git a/vendor/github.com/fsnotify/fsnotify/CHANGELOG.md b/vendor/github.com/fsnotify/fsnotify/CHANGELOG.md new file mode 100644 index 000000000..be4d7ea2c --- /dev/null +++ b/vendor/github.com/fsnotify/fsnotify/CHANGELOG.md @@ -0,0 +1,317 @@ +# Changelog + +## v1.4.7 / 2018-01-09 + +* BSD/macOS: Fix possible deadlock on closing the watcher on kqueue (thanks @nhooyr and @glycerine) +* Tests: Fix missing verb on format string (thanks @rchiossi) +* Linux: Fix deadlock in Remove (thanks @aarondl) +* Linux: Watch.Add improvements (avoid race, fix consistency, reduce garbage) (thanks @twpayne) +* Docs: Moved FAQ into the README (thanks @vahe) +* Linux: Properly handle inotify's IN_Q_OVERFLOW event (thanks @zeldovich) +* Docs: replace references to OS X with macOS + +## v1.4.2 / 2016-10-10 + +* Linux: use InotifyInit1 with IN_CLOEXEC to stop leaking a file descriptor to a child process when using fork/exec [#178](https://github.com/fsnotify/fsnotify/pull/178) (thanks @pattyshack) + +## v1.4.1 / 2016-10-04 + +* Fix flaky inotify stress test on Linux [#177](https://github.com/fsnotify/fsnotify/pull/177) (thanks @pattyshack) + +## v1.4.0 / 2016-10-01 + +* add a String() method to Event.Op [#165](https://github.com/fsnotify/fsnotify/pull/165) (thanks @oozie) + +## v1.3.1 / 2016-06-28 + +* Windows: fix for double backslash when watching the root of a drive [#151](https://github.com/fsnotify/fsnotify/issues/151) (thanks @brunoqc) + +## v1.3.0 / 2016-04-19 + +* Support linux/arm64 by [patching](https://go-review.googlesource.com/#/c/21971/) x/sys/unix and switching to to it from syscall (thanks @suihkulokki) [#135](https://github.com/fsnotify/fsnotify/pull/135) + +## v1.2.10 / 2016-03-02 + +* Fix golint errors in windows.go [#121](https://github.com/fsnotify/fsnotify/pull/121) (thanks @tiffanyfj) + +## v1.2.9 / 2016-01-13 + +kqueue: Fix logic for CREATE after REMOVE [#111](https://github.com/fsnotify/fsnotify/pull/111) (thanks @bep) + +## v1.2.8 / 2015-12-17 + +* kqueue: fix race condition in Close [#105](https://github.com/fsnotify/fsnotify/pull/105) (thanks @djui for reporting the issue and @ppknap for writing a failing test) +* inotify: fix race in test +* enable race detection for continuous integration (Linux, Mac, Windows) + +## v1.2.5 / 2015-10-17 + +* inotify: use epoll_create1 for arm64 support (requires Linux 2.6.27 or later) [#100](https://github.com/fsnotify/fsnotify/pull/100) (thanks @suihkulokki) +* inotify: fix path leaks [#73](https://github.com/fsnotify/fsnotify/pull/73) (thanks @chamaken) +* kqueue: watch for rename events on subdirectories [#83](https://github.com/fsnotify/fsnotify/pull/83) (thanks @guotie) +* kqueue: avoid infinite loops from symlinks cycles [#101](https://github.com/fsnotify/fsnotify/pull/101) (thanks @illicitonion) + +## v1.2.1 / 2015-10-14 + +* kqueue: don't watch named pipes [#98](https://github.com/fsnotify/fsnotify/pull/98) (thanks @evanphx) + +## v1.2.0 / 2015-02-08 + +* inotify: use epoll to wake up readEvents [#66](https://github.com/fsnotify/fsnotify/pull/66) (thanks @PieterD) +* inotify: closing watcher should now always shut down goroutine [#63](https://github.com/fsnotify/fsnotify/pull/63) (thanks @PieterD) +* kqueue: close kqueue after removing watches, fixes [#59](https://github.com/fsnotify/fsnotify/issues/59) + +## v1.1.1 / 2015-02-05 + +* inotify: Retry read on EINTR [#61](https://github.com/fsnotify/fsnotify/issues/61) (thanks @PieterD) + +## v1.1.0 / 2014-12-12 + +* kqueue: rework internals [#43](https://github.com/fsnotify/fsnotify/pull/43) + * add low-level functions + * only need to store flags on directories + * less mutexes [#13](https://github.com/fsnotify/fsnotify/issues/13) + * done can be an unbuffered channel + * remove calls to os.NewSyscallError +* More efficient string concatenation for Event.String() [#52](https://github.com/fsnotify/fsnotify/pull/52) (thanks @mdlayher) +* kqueue: fix regression in rework causing subdirectories to be watched [#48](https://github.com/fsnotify/fsnotify/issues/48) +* kqueue: cleanup internal watch before sending remove event [#51](https://github.com/fsnotify/fsnotify/issues/51) + +## v1.0.4 / 2014-09-07 + +* kqueue: add dragonfly to the build tags. +* Rename source code files, rearrange code so exported APIs are at the top. +* Add done channel to example code. [#37](https://github.com/fsnotify/fsnotify/pull/37) (thanks @chenyukang) + +## v1.0.3 / 2014-08-19 + +* [Fix] Windows MOVED_TO now translates to Create like on BSD and Linux. [#36](https://github.com/fsnotify/fsnotify/issues/36) + +## v1.0.2 / 2014-08-17 + +* [Fix] Missing create events on macOS. [#14](https://github.com/fsnotify/fsnotify/issues/14) (thanks @zhsso) +* [Fix] Make ./path and path equivalent. (thanks @zhsso) + +## v1.0.0 / 2014-08-15 + +* [API] Remove AddWatch on Windows, use Add. +* Improve documentation for exported identifiers. [#30](https://github.com/fsnotify/fsnotify/issues/30) +* Minor updates based on feedback from golint. + +## dev / 2014-07-09 + +* Moved to [github.com/fsnotify/fsnotify](https://github.com/fsnotify/fsnotify). +* Use os.NewSyscallError instead of returning errno (thanks @hariharan-uno) + +## dev / 2014-07-04 + +* kqueue: fix incorrect mutex used in Close() +* Update example to demonstrate usage of Op. + +## dev / 2014-06-28 + +* [API] Don't set the Write Op for attribute notifications [#4](https://github.com/fsnotify/fsnotify/issues/4) +* Fix for String() method on Event (thanks Alex Brainman) +* Don't build on Plan 9 or Solaris (thanks @4ad) + +## dev / 2014-06-21 + +* Events channel of type Event rather than *Event. +* [internal] use syscall constants directly for inotify and kqueue. +* [internal] kqueue: rename events to kevents and fileEvent to event. + +## dev / 2014-06-19 + +* Go 1.3+ required on Windows (uses syscall.ERROR_MORE_DATA internally). +* [internal] remove cookie from Event struct (unused). +* [internal] Event struct has the same definition across every OS. +* [internal] remove internal watch and removeWatch methods. + +## dev / 2014-06-12 + +* [API] Renamed Watch() to Add() and RemoveWatch() to Remove(). +* [API] Pluralized channel names: Events and Errors. +* [API] Renamed FileEvent struct to Event. +* [API] Op constants replace methods like IsCreate(). + +## dev / 2014-06-12 + +* Fix data race on kevent buffer (thanks @tilaks) [#98](https://github.com/howeyc/fsnotify/pull/98) + +## dev / 2014-05-23 + +* [API] Remove current implementation of WatchFlags. + * current implementation doesn't take advantage of OS for efficiency + * provides little benefit over filtering events as they are received, but has extra bookkeeping and mutexes + * no tests for the current implementation + * not fully implemented on Windows [#93](https://github.com/howeyc/fsnotify/issues/93#issuecomment-39285195) + +## v0.9.3 / 2014-12-31 + +* kqueue: cleanup internal watch before sending remove event [#51](https://github.com/fsnotify/fsnotify/issues/51) + +## v0.9.2 / 2014-08-17 + +* [Backport] Fix missing create events on macOS. [#14](https://github.com/fsnotify/fsnotify/issues/14) (thanks @zhsso) + +## v0.9.1 / 2014-06-12 + +* Fix data race on kevent buffer (thanks @tilaks) [#98](https://github.com/howeyc/fsnotify/pull/98) + +## v0.9.0 / 2014-01-17 + +* IsAttrib() for events that only concern a file's metadata [#79][] (thanks @abustany) +* [Fix] kqueue: fix deadlock [#77][] (thanks @cespare) +* [NOTICE] Development has moved to `code.google.com/p/go.exp/fsnotify` in preparation for inclusion in the Go standard library. + +## v0.8.12 / 2013-11-13 + +* [API] Remove FD_SET and friends from Linux adapter + +## v0.8.11 / 2013-11-02 + +* [Doc] Add Changelog [#72][] (thanks @nathany) +* [Doc] Spotlight and double modify events on macOS [#62][] (reported by @paulhammond) + +## v0.8.10 / 2013-10-19 + +* [Fix] kqueue: remove file watches when parent directory is removed [#71][] (reported by @mdwhatcott) +* [Fix] kqueue: race between Close and readEvents [#70][] (reported by @bernerdschaefer) +* [Doc] specify OS-specific limits in README (thanks @debrando) + +## v0.8.9 / 2013-09-08 + +* [Doc] Contributing (thanks @nathany) +* [Doc] update package path in example code [#63][] (thanks @paulhammond) +* [Doc] GoCI badge in README (Linux only) [#60][] +* [Doc] Cross-platform testing with Vagrant [#59][] (thanks @nathany) + +## v0.8.8 / 2013-06-17 + +* [Fix] Windows: handle `ERROR_MORE_DATA` on Windows [#49][] (thanks @jbowtie) + +## v0.8.7 / 2013-06-03 + +* [API] Make syscall flags internal +* [Fix] inotify: ignore event changes +* [Fix] race in symlink test [#45][] (reported by @srid) +* [Fix] tests on Windows +* lower case error messages + +## v0.8.6 / 2013-05-23 + +* kqueue: Use EVT_ONLY flag on Darwin +* [Doc] Update README with full example + +## v0.8.5 / 2013-05-09 + +* [Fix] inotify: allow monitoring of "broken" symlinks (thanks @tsg) + +## v0.8.4 / 2013-04-07 + +* [Fix] kqueue: watch all file events [#40][] (thanks @ChrisBuchholz) + +## v0.8.3 / 2013-03-13 + +* [Fix] inoitfy/kqueue memory leak [#36][] (reported by @nbkolchin) +* [Fix] kqueue: use fsnFlags for watching a directory [#33][] (reported by @nbkolchin) + +## v0.8.2 / 2013-02-07 + +* [Doc] add Authors +* [Fix] fix data races for map access [#29][] (thanks @fsouza) + +## v0.8.1 / 2013-01-09 + +* [Fix] Windows path separators +* [Doc] BSD License + +## v0.8.0 / 2012-11-09 + +* kqueue: directory watching improvements (thanks @vmirage) +* inotify: add `IN_MOVED_TO` [#25][] (requested by @cpisto) +* [Fix] kqueue: deleting watched directory [#24][] (reported by @jakerr) + +## v0.7.4 / 2012-10-09 + +* [Fix] inotify: fixes from https://codereview.appspot.com/5418045/ (ugorji) +* [Fix] kqueue: preserve watch flags when watching for delete [#21][] (reported by @robfig) +* [Fix] kqueue: watch the directory even if it isn't a new watch (thanks @robfig) +* [Fix] kqueue: modify after recreation of file + +## v0.7.3 / 2012-09-27 + +* [Fix] kqueue: watch with an existing folder inside the watched folder (thanks @vmirage) +* [Fix] kqueue: no longer get duplicate CREATE events + +## v0.7.2 / 2012-09-01 + +* kqueue: events for created directories + +## v0.7.1 / 2012-07-14 + +* [Fix] for renaming files + +## v0.7.0 / 2012-07-02 + +* [Feature] FSNotify flags +* [Fix] inotify: Added file name back to event path + +## v0.6.0 / 2012-06-06 + +* kqueue: watch files after directory created (thanks @tmc) + +## v0.5.1 / 2012-05-22 + +* [Fix] inotify: remove all watches before Close() + +## v0.5.0 / 2012-05-03 + +* [API] kqueue: return errors during watch instead of sending over channel +* kqueue: match symlink behavior on Linux +* inotify: add `DELETE_SELF` (requested by @taralx) +* [Fix] kqueue: handle EINTR (reported by @robfig) +* [Doc] Godoc example [#1][] (thanks @davecheney) + +## v0.4.0 / 2012-03-30 + +* Go 1 released: build with go tool +* [Feature] Windows support using winfsnotify +* Windows does not have attribute change notifications +* Roll attribute notifications into IsModify + +## v0.3.0 / 2012-02-19 + +* kqueue: add files when watch directory + +## v0.2.0 / 2011-12-30 + +* update to latest Go weekly code + +## v0.1.0 / 2011-10-19 + +* kqueue: add watch on file creation to match inotify +* kqueue: create file event +* inotify: ignore `IN_IGNORED` events +* event String() +* linux: common FileEvent functions +* initial commit + +[#79]: https://github.com/howeyc/fsnotify/pull/79 +[#77]: https://github.com/howeyc/fsnotify/pull/77 +[#72]: https://github.com/howeyc/fsnotify/issues/72 +[#71]: https://github.com/howeyc/fsnotify/issues/71 +[#70]: https://github.com/howeyc/fsnotify/issues/70 +[#63]: https://github.com/howeyc/fsnotify/issues/63 +[#62]: https://github.com/howeyc/fsnotify/issues/62 +[#60]: https://github.com/howeyc/fsnotify/issues/60 +[#59]: https://github.com/howeyc/fsnotify/issues/59 +[#49]: https://github.com/howeyc/fsnotify/issues/49 +[#45]: https://github.com/howeyc/fsnotify/issues/45 +[#40]: https://github.com/howeyc/fsnotify/issues/40 +[#36]: https://github.com/howeyc/fsnotify/issues/36 +[#33]: https://github.com/howeyc/fsnotify/issues/33 +[#29]: https://github.com/howeyc/fsnotify/issues/29 +[#25]: https://github.com/howeyc/fsnotify/issues/25 +[#24]: https://github.com/howeyc/fsnotify/issues/24 +[#21]: https://github.com/howeyc/fsnotify/issues/21 diff --git a/vendor/github.com/fsnotify/fsnotify/CONTRIBUTING.md b/vendor/github.com/fsnotify/fsnotify/CONTRIBUTING.md new file mode 100644 index 000000000..828a60b24 --- /dev/null +++ b/vendor/github.com/fsnotify/fsnotify/CONTRIBUTING.md @@ -0,0 +1,77 @@ +# Contributing + +## Issues + +* Request features and report bugs using the [GitHub Issue Tracker](https://github.com/fsnotify/fsnotify/issues). +* Please indicate the platform you are using fsnotify on. +* A code example to reproduce the problem is appreciated. + +## Pull Requests + +### Contributor License Agreement + +fsnotify is derived from code in the [golang.org/x/exp](https://godoc.org/golang.org/x/exp) package and it may be included [in the standard library](https://github.com/fsnotify/fsnotify/issues/1) in the future. Therefore fsnotify carries the same [LICENSE](https://github.com/fsnotify/fsnotify/blob/master/LICENSE) as Go. Contributors retain their copyright, so you need to fill out a short form before we can accept your contribution: [Google Individual Contributor License Agreement](https://developers.google.com/open-source/cla/individual). + +Please indicate that you have signed the CLA in your pull request. + +### How fsnotify is Developed + +* Development is done on feature branches. +* Tests are run on BSD, Linux, macOS and Windows. +* Pull requests are reviewed and [applied to master][am] using [hub][]. + * Maintainers may modify or squash commits rather than asking contributors to. +* To issue a new release, the maintainers will: + * Update the CHANGELOG + * Tag a version, which will become available through gopkg.in. + +### How to Fork + +For smooth sailing, always use the original import path. Installing with `go get` makes this easy. + +1. Install from GitHub (`go get -u github.com/fsnotify/fsnotify`) +2. Create your feature branch (`git checkout -b my-new-feature`) +3. Ensure everything works and the tests pass (see below) +4. Commit your changes (`git commit -am 'Add some feature'`) + +Contribute upstream: + +1. Fork fsnotify on GitHub +2. Add your remote (`git remote add fork git@github.com:mycompany/repo.git`) +3. Push to the branch (`git push fork my-new-feature`) +4. Create a new Pull Request on GitHub + +This workflow is [thoroughly explained by Katrina Owen](https://splice.com/blog/contributing-open-source-git-repositories-go/). + +### Testing + +fsnotify uses build tags to compile different code on Linux, BSD, macOS, and Windows. + +Before doing a pull request, please do your best to test your changes on multiple platforms, and list which platforms you were able/unable to test on. + +To aid in cross-platform testing there is a Vagrantfile for Linux and BSD. + +* Install [Vagrant](http://www.vagrantup.com/) and [VirtualBox](https://www.virtualbox.org/) +* Setup [Vagrant Gopher](https://github.com/nathany/vagrant-gopher) in your `src` folder. +* Run `vagrant up` from the project folder. You can also setup just one box with `vagrant up linux` or `vagrant up bsd` (note: the BSD box doesn't support Windows hosts at this time, and NFS may prompt for your host OS password) +* Once setup, you can run the test suite on a given OS with a single command `vagrant ssh linux -c 'cd fsnotify/fsnotify; go test'`. +* When you're done, you will want to halt or destroy the Vagrant boxes. + +Notice: fsnotify file system events won't trigger in shared folders. The tests get around this limitation by using the /tmp directory. + +Right now there is no equivalent solution for Windows and macOS, but there are Windows VMs [freely available from Microsoft](http://www.modern.ie/en-us/virtualization-tools#downloads). + +### Maintainers + +Help maintaining fsnotify is welcome. To be a maintainer: + +* Submit a pull request and sign the CLA as above. +* You must be able to run the test suite on Mac, Windows, Linux and BSD. + +To keep master clean, the fsnotify project uses the "apply mail" workflow outlined in Nathaniel Talbott's post ["Merge pull request" Considered Harmful][am]. This requires installing [hub][]. + +All code changes should be internal pull requests. + +Releases are tagged using [Semantic Versioning](http://semver.org/). + +[hub]: https://github.com/github/hub +[am]: http://blog.spreedly.com/2014/06/24/merge-pull-request-considered-harmful/#.VGa5yZPF_Zs diff --git a/vendor/github.com/fsnotify/fsnotify/LICENSE b/vendor/github.com/fsnotify/fsnotify/LICENSE new file mode 100644 index 000000000..e180c8fb0 --- /dev/null +++ b/vendor/github.com/fsnotify/fsnotify/LICENSE @@ -0,0 +1,28 @@ +Copyright (c) 2012 The Go Authors. All rights reserved. +Copyright (c) 2012-2019 fsnotify Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/fsnotify/fsnotify/README.md b/vendor/github.com/fsnotify/fsnotify/README.md new file mode 100644 index 000000000..b2629e522 --- /dev/null +++ b/vendor/github.com/fsnotify/fsnotify/README.md @@ -0,0 +1,130 @@ +# File system notifications for Go + +[![GoDoc](https://godoc.org/github.com/fsnotify/fsnotify?status.svg)](https://godoc.org/github.com/fsnotify/fsnotify) [![Go Report Card](https://goreportcard.com/badge/github.com/fsnotify/fsnotify)](https://goreportcard.com/report/github.com/fsnotify/fsnotify) + +fsnotify utilizes [golang.org/x/sys](https://godoc.org/golang.org/x/sys) rather than `syscall` from the standard library. Ensure you have the latest version installed by running: + +```console +go get -u golang.org/x/sys/... +``` + +Cross platform: Windows, Linux, BSD and macOS. + +| Adapter | OS | Status | +| --------------------- | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | +| inotify | Linux 2.6.27 or later, Android\* | Supported [![Build Status](https://travis-ci.org/fsnotify/fsnotify.svg?branch=master)](https://travis-ci.org/fsnotify/fsnotify) | +| kqueue | BSD, macOS, iOS\* | Supported [![Build Status](https://travis-ci.org/fsnotify/fsnotify.svg?branch=master)](https://travis-ci.org/fsnotify/fsnotify) | +| ReadDirectoryChangesW | Windows | Supported [![Build Status](https://travis-ci.org/fsnotify/fsnotify.svg?branch=master)](https://travis-ci.org/fsnotify/fsnotify) | +| FSEvents | macOS | [Planned](https://github.com/fsnotify/fsnotify/issues/11) | +| FEN | Solaris 11 | [In Progress](https://github.com/fsnotify/fsnotify/issues/12) | +| fanotify | Linux 2.6.37+ | [Planned](https://github.com/fsnotify/fsnotify/issues/114) | +| USN Journals | Windows | [Maybe](https://github.com/fsnotify/fsnotify/issues/53) | +| Polling | *All* | [Maybe](https://github.com/fsnotify/fsnotify/issues/9) | + +\* Android and iOS are untested. + +Please see [the documentation](https://godoc.org/github.com/fsnotify/fsnotify) and consult the [FAQ](#faq) for usage information. + +## API stability + +fsnotify is a fork of [howeyc/fsnotify](https://godoc.org/github.com/howeyc/fsnotify) with a new API as of v1.0. The API is based on [this design document](http://goo.gl/MrYxyA). + +All [releases](https://github.com/fsnotify/fsnotify/releases) are tagged based on [Semantic Versioning](http://semver.org/). Further API changes are [planned](https://github.com/fsnotify/fsnotify/milestones), and will be tagged with a new major revision number. + +Go 1.6 supports dependencies located in the `vendor/` folder. Unless you are creating a library, it is recommended that you copy fsnotify into `vendor/github.com/fsnotify/fsnotify` within your project, and likewise for `golang.org/x/sys`. + +## Usage + +```go +package main + +import ( + "log" + + "github.com/fsnotify/fsnotify" +) + +func main() { + watcher, err := fsnotify.NewWatcher() + if err != nil { + log.Fatal(err) + } + defer watcher.Close() + + done := make(chan bool) + go func() { + for { + select { + case event, ok := <-watcher.Events: + if !ok { + return + } + log.Println("event:", event) + if event.Op&fsnotify.Write == fsnotify.Write { + log.Println("modified file:", event.Name) + } + case err, ok := <-watcher.Errors: + if !ok { + return + } + log.Println("error:", err) + } + } + }() + + err = watcher.Add("/tmp/foo") + if err != nil { + log.Fatal(err) + } + <-done +} +``` + +## Contributing + +Please refer to [CONTRIBUTING][] before opening an issue or pull request. + +## Example + +See [example_test.go](https://github.com/fsnotify/fsnotify/blob/master/example_test.go). + +## FAQ + +**When a file is moved to another directory is it still being watched?** + +No (it shouldn't be, unless you are watching where it was moved to). + +**When I watch a directory, are all subdirectories watched as well?** + +No, you must add watches for any directory you want to watch (a recursive watcher is on the roadmap [#18][]). + +**Do I have to watch the Error and Event channels in a separate goroutine?** + +As of now, yes. Looking into making this single-thread friendly (see [howeyc #7][#7]) + +**Why am I receiving multiple events for the same file on OS X?** + +Spotlight indexing on OS X can result in multiple events (see [howeyc #62][#62]). A temporary workaround is to add your folder(s) to the *Spotlight Privacy settings* until we have a native FSEvents implementation (see [#11][]). + +**How many files can be watched at once?** + +There are OS-specific limits as to how many watches can be created: +* Linux: /proc/sys/fs/inotify/max_user_watches contains the limit, reaching this limit results in a "no space left on device" error. +* BSD / OSX: sysctl variables "kern.maxfiles" and "kern.maxfilesperproc", reaching these limits results in a "too many open files" error. + +**Why don't notifications work with NFS filesystems or filesystem in userspace (FUSE)?** + +fsnotify requires support from underlying OS to work. The current NFS protocol does not provide network level support for file notifications. + +[#62]: https://github.com/howeyc/fsnotify/issues/62 +[#18]: https://github.com/fsnotify/fsnotify/issues/18 +[#11]: https://github.com/fsnotify/fsnotify/issues/11 +[#7]: https://github.com/howeyc/fsnotify/issues/7 + +[contributing]: https://github.com/fsnotify/fsnotify/blob/master/CONTRIBUTING.md + +## Related Projects + +* [notify](https://github.com/rjeczalik/notify) +* [fsevents](https://github.com/fsnotify/fsevents) + diff --git a/vendor/github.com/fsnotify/fsnotify/fen.go b/vendor/github.com/fsnotify/fsnotify/fen.go new file mode 100644 index 000000000..ced39cb88 --- /dev/null +++ b/vendor/github.com/fsnotify/fsnotify/fen.go @@ -0,0 +1,37 @@ +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build solaris + +package fsnotify + +import ( + "errors" +) + +// Watcher watches a set of files, delivering events to a channel. +type Watcher struct { + Events chan Event + Errors chan error +} + +// NewWatcher establishes a new watcher with the underlying OS and begins waiting for events. +func NewWatcher() (*Watcher, error) { + return nil, errors.New("FEN based watcher not yet supported for fsnotify\n") +} + +// Close removes all watches and closes the events channel. +func (w *Watcher) Close() error { + return nil +} + +// Add starts watching the named file or directory (non-recursively). +func (w *Watcher) Add(name string) error { + return nil +} + +// Remove stops watching the the named file or directory (non-recursively). +func (w *Watcher) Remove(name string) error { + return nil +} diff --git a/vendor/github.com/fsnotify/fsnotify/fsnotify.go b/vendor/github.com/fsnotify/fsnotify/fsnotify.go new file mode 100644 index 000000000..89cab046d --- /dev/null +++ b/vendor/github.com/fsnotify/fsnotify/fsnotify.go @@ -0,0 +1,68 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build !plan9 + +// Package fsnotify provides a platform-independent interface for file system notifications. +package fsnotify + +import ( + "bytes" + "errors" + "fmt" +) + +// Event represents a single file system notification. +type Event struct { + Name string // Relative path to the file or directory. + Op Op // File operation that triggered the event. +} + +// Op describes a set of file operations. +type Op uint32 + +// These are the generalized file operations that can trigger a notification. +const ( + Create Op = 1 << iota + Write + Remove + Rename + Chmod +) + +func (op Op) String() string { + // Use a buffer for efficient string concatenation + var buffer bytes.Buffer + + if op&Create == Create { + buffer.WriteString("|CREATE") + } + if op&Remove == Remove { + buffer.WriteString("|REMOVE") + } + if op&Write == Write { + buffer.WriteString("|WRITE") + } + if op&Rename == Rename { + buffer.WriteString("|RENAME") + } + if op&Chmod == Chmod { + buffer.WriteString("|CHMOD") + } + if buffer.Len() == 0 { + return "" + } + return buffer.String()[1:] // Strip leading pipe +} + +// String returns a string representation of the event in the form +// "file: REMOVE|WRITE|..." +func (e Event) String() string { + return fmt.Sprintf("%q: %s", e.Name, e.Op.String()) +} + +// Common errors that can be reported by a watcher +var ( + ErrEventOverflow = errors.New("fsnotify queue overflow") +) diff --git a/vendor/github.com/fsnotify/fsnotify/go.mod b/vendor/github.com/fsnotify/fsnotify/go.mod new file mode 100644 index 000000000..ff11e13f2 --- /dev/null +++ b/vendor/github.com/fsnotify/fsnotify/go.mod @@ -0,0 +1,5 @@ +module github.com/fsnotify/fsnotify + +go 1.13 + +require golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9 diff --git a/vendor/github.com/fsnotify/fsnotify/go.sum b/vendor/github.com/fsnotify/fsnotify/go.sum new file mode 100644 index 000000000..f60af9855 --- /dev/null +++ b/vendor/github.com/fsnotify/fsnotify/go.sum @@ -0,0 +1,2 @@ +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9 h1:L2auWcuQIvxz9xSEqzESnV/QN/gNRXNApHi3fYwl2w0= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/vendor/github.com/fsnotify/fsnotify/inotify.go b/vendor/github.com/fsnotify/fsnotify/inotify.go new file mode 100644 index 000000000..d9fd1b88a --- /dev/null +++ b/vendor/github.com/fsnotify/fsnotify/inotify.go @@ -0,0 +1,337 @@ +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build linux + +package fsnotify + +import ( + "errors" + "fmt" + "io" + "os" + "path/filepath" + "strings" + "sync" + "unsafe" + + "golang.org/x/sys/unix" +) + +// Watcher watches a set of files, delivering events to a channel. +type Watcher struct { + Events chan Event + Errors chan error + mu sync.Mutex // Map access + fd int + poller *fdPoller + watches map[string]*watch // Map of inotify watches (key: path) + paths map[int]string // Map of watched paths (key: watch descriptor) + done chan struct{} // Channel for sending a "quit message" to the reader goroutine + doneResp chan struct{} // Channel to respond to Close +} + +// NewWatcher establishes a new watcher with the underlying OS and begins waiting for events. +func NewWatcher() (*Watcher, error) { + // Create inotify fd + fd, errno := unix.InotifyInit1(unix.IN_CLOEXEC) + if fd == -1 { + return nil, errno + } + // Create epoll + poller, err := newFdPoller(fd) + if err != nil { + unix.Close(fd) + return nil, err + } + w := &Watcher{ + fd: fd, + poller: poller, + watches: make(map[string]*watch), + paths: make(map[int]string), + Events: make(chan Event), + Errors: make(chan error), + done: make(chan struct{}), + doneResp: make(chan struct{}), + } + + go w.readEvents() + return w, nil +} + +func (w *Watcher) isClosed() bool { + select { + case <-w.done: + return true + default: + return false + } +} + +// Close removes all watches and closes the events channel. +func (w *Watcher) Close() error { + if w.isClosed() { + return nil + } + + // Send 'close' signal to goroutine, and set the Watcher to closed. + close(w.done) + + // Wake up goroutine + w.poller.wake() + + // Wait for goroutine to close + <-w.doneResp + + return nil +} + +// Add starts watching the named file or directory (non-recursively). +func (w *Watcher) Add(name string) error { + name = filepath.Clean(name) + if w.isClosed() { + return errors.New("inotify instance already closed") + } + + const agnosticEvents = unix.IN_MOVED_TO | unix.IN_MOVED_FROM | + unix.IN_CREATE | unix.IN_ATTRIB | unix.IN_MODIFY | + unix.IN_MOVE_SELF | unix.IN_DELETE | unix.IN_DELETE_SELF + + var flags uint32 = agnosticEvents + + w.mu.Lock() + defer w.mu.Unlock() + watchEntry := w.watches[name] + if watchEntry != nil { + flags |= watchEntry.flags | unix.IN_MASK_ADD + } + wd, errno := unix.InotifyAddWatch(w.fd, name, flags) + if wd == -1 { + return errno + } + + if watchEntry == nil { + w.watches[name] = &watch{wd: uint32(wd), flags: flags} + w.paths[wd] = name + } else { + watchEntry.wd = uint32(wd) + watchEntry.flags = flags + } + + return nil +} + +// Remove stops watching the named file or directory (non-recursively). +func (w *Watcher) Remove(name string) error { + name = filepath.Clean(name) + + // Fetch the watch. + w.mu.Lock() + defer w.mu.Unlock() + watch, ok := w.watches[name] + + // Remove it from inotify. + if !ok { + return fmt.Errorf("can't remove non-existent inotify watch for: %s", name) + } + + // We successfully removed the watch if InotifyRmWatch doesn't return an + // error, we need to clean up our internal state to ensure it matches + // inotify's kernel state. + delete(w.paths, int(watch.wd)) + delete(w.watches, name) + + // inotify_rm_watch will return EINVAL if the file has been deleted; + // the inotify will already have been removed. + // watches and pathes are deleted in ignoreLinux() implicitly and asynchronously + // by calling inotify_rm_watch() below. e.g. readEvents() goroutine receives IN_IGNORE + // so that EINVAL means that the wd is being rm_watch()ed or its file removed + // by another thread and we have not received IN_IGNORE event. + success, errno := unix.InotifyRmWatch(w.fd, watch.wd) + if success == -1 { + // TODO: Perhaps it's not helpful to return an error here in every case. + // the only two possible errors are: + // EBADF, which happens when w.fd is not a valid file descriptor of any kind. + // EINVAL, which is when fd is not an inotify descriptor or wd is not a valid watch descriptor. + // Watch descriptors are invalidated when they are removed explicitly or implicitly; + // explicitly by inotify_rm_watch, implicitly when the file they are watching is deleted. + return errno + } + + return nil +} + +type watch struct { + wd uint32 // Watch descriptor (as returned by the inotify_add_watch() syscall) + flags uint32 // inotify flags of this watch (see inotify(7) for the list of valid flags) +} + +// readEvents reads from the inotify file descriptor, converts the +// received events into Event objects and sends them via the Events channel +func (w *Watcher) readEvents() { + var ( + buf [unix.SizeofInotifyEvent * 4096]byte // Buffer for a maximum of 4096 raw events + n int // Number of bytes read with read() + errno error // Syscall errno + ok bool // For poller.wait + ) + + defer close(w.doneResp) + defer close(w.Errors) + defer close(w.Events) + defer unix.Close(w.fd) + defer w.poller.close() + + for { + // See if we have been closed. + if w.isClosed() { + return + } + + ok, errno = w.poller.wait() + if errno != nil { + select { + case w.Errors <- errno: + case <-w.done: + return + } + continue + } + + if !ok { + continue + } + + n, errno = unix.Read(w.fd, buf[:]) + // If a signal interrupted execution, see if we've been asked to close, and try again. + // http://man7.org/linux/man-pages/man7/signal.7.html : + // "Before Linux 3.8, reads from an inotify(7) file descriptor were not restartable" + if errno == unix.EINTR { + continue + } + + // unix.Read might have been woken up by Close. If so, we're done. + if w.isClosed() { + return + } + + if n < unix.SizeofInotifyEvent { + var err error + if n == 0 { + // If EOF is received. This should really never happen. + err = io.EOF + } else if n < 0 { + // If an error occurred while reading. + err = errno + } else { + // Read was too short. + err = errors.New("notify: short read in readEvents()") + } + select { + case w.Errors <- err: + case <-w.done: + return + } + continue + } + + var offset uint32 + // We don't know how many events we just read into the buffer + // While the offset points to at least one whole event... + for offset <= uint32(n-unix.SizeofInotifyEvent) { + // Point "raw" to the event in the buffer + raw := (*unix.InotifyEvent)(unsafe.Pointer(&buf[offset])) + + mask := uint32(raw.Mask) + nameLen := uint32(raw.Len) + + if mask&unix.IN_Q_OVERFLOW != 0 { + select { + case w.Errors <- ErrEventOverflow: + case <-w.done: + return + } + } + + // If the event happened to the watched directory or the watched file, the kernel + // doesn't append the filename to the event, but we would like to always fill the + // the "Name" field with a valid filename. We retrieve the path of the watch from + // the "paths" map. + w.mu.Lock() + name, ok := w.paths[int(raw.Wd)] + // IN_DELETE_SELF occurs when the file/directory being watched is removed. + // This is a sign to clean up the maps, otherwise we are no longer in sync + // with the inotify kernel state which has already deleted the watch + // automatically. + if ok && mask&unix.IN_DELETE_SELF == unix.IN_DELETE_SELF { + delete(w.paths, int(raw.Wd)) + delete(w.watches, name) + } + w.mu.Unlock() + + if nameLen > 0 { + // Point "bytes" at the first byte of the filename + bytes := (*[unix.PathMax]byte)(unsafe.Pointer(&buf[offset+unix.SizeofInotifyEvent])) + // The filename is padded with NULL bytes. TrimRight() gets rid of those. + name += "/" + strings.TrimRight(string(bytes[0:nameLen]), "\000") + } + + event := newEvent(name, mask) + + // Send the events that are not ignored on the events channel + if !event.ignoreLinux(mask) { + select { + case w.Events <- event: + case <-w.done: + return + } + } + + // Move to the next event in the buffer + offset += unix.SizeofInotifyEvent + nameLen + } + } +} + +// Certain types of events can be "ignored" and not sent over the Events +// channel. Such as events marked ignore by the kernel, or MODIFY events +// against files that do not exist. +func (e *Event) ignoreLinux(mask uint32) bool { + // Ignore anything the inotify API says to ignore + if mask&unix.IN_IGNORED == unix.IN_IGNORED { + return true + } + + // If the event is not a DELETE or RENAME, the file must exist. + // Otherwise the event is ignored. + // *Note*: this was put in place because it was seen that a MODIFY + // event was sent after the DELETE. This ignores that MODIFY and + // assumes a DELETE will come or has come if the file doesn't exist. + if !(e.Op&Remove == Remove || e.Op&Rename == Rename) { + _, statErr := os.Lstat(e.Name) + return os.IsNotExist(statErr) + } + return false +} + +// newEvent returns an platform-independent Event based on an inotify mask. +func newEvent(name string, mask uint32) Event { + e := Event{Name: name} + if mask&unix.IN_CREATE == unix.IN_CREATE || mask&unix.IN_MOVED_TO == unix.IN_MOVED_TO { + e.Op |= Create + } + if mask&unix.IN_DELETE_SELF == unix.IN_DELETE_SELF || mask&unix.IN_DELETE == unix.IN_DELETE { + e.Op |= Remove + } + if mask&unix.IN_MODIFY == unix.IN_MODIFY { + e.Op |= Write + } + if mask&unix.IN_MOVE_SELF == unix.IN_MOVE_SELF || mask&unix.IN_MOVED_FROM == unix.IN_MOVED_FROM { + e.Op |= Rename + } + if mask&unix.IN_ATTRIB == unix.IN_ATTRIB { + e.Op |= Chmod + } + return e +} diff --git a/vendor/github.com/fsnotify/fsnotify/inotify_poller.go b/vendor/github.com/fsnotify/fsnotify/inotify_poller.go new file mode 100644 index 000000000..b33f2b4d4 --- /dev/null +++ b/vendor/github.com/fsnotify/fsnotify/inotify_poller.go @@ -0,0 +1,187 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build linux + +package fsnotify + +import ( + "errors" + + "golang.org/x/sys/unix" +) + +type fdPoller struct { + fd int // File descriptor (as returned by the inotify_init() syscall) + epfd int // Epoll file descriptor + pipe [2]int // Pipe for waking up +} + +func emptyPoller(fd int) *fdPoller { + poller := new(fdPoller) + poller.fd = fd + poller.epfd = -1 + poller.pipe[0] = -1 + poller.pipe[1] = -1 + return poller +} + +// Create a new inotify poller. +// This creates an inotify handler, and an epoll handler. +func newFdPoller(fd int) (*fdPoller, error) { + var errno error + poller := emptyPoller(fd) + defer func() { + if errno != nil { + poller.close() + } + }() + poller.fd = fd + + // Create epoll fd + poller.epfd, errno = unix.EpollCreate1(unix.EPOLL_CLOEXEC) + if poller.epfd == -1 { + return nil, errno + } + // Create pipe; pipe[0] is the read end, pipe[1] the write end. + errno = unix.Pipe2(poller.pipe[:], unix.O_NONBLOCK|unix.O_CLOEXEC) + if errno != nil { + return nil, errno + } + + // Register inotify fd with epoll + event := unix.EpollEvent{ + Fd: int32(poller.fd), + Events: unix.EPOLLIN, + } + errno = unix.EpollCtl(poller.epfd, unix.EPOLL_CTL_ADD, poller.fd, &event) + if errno != nil { + return nil, errno + } + + // Register pipe fd with epoll + event = unix.EpollEvent{ + Fd: int32(poller.pipe[0]), + Events: unix.EPOLLIN, + } + errno = unix.EpollCtl(poller.epfd, unix.EPOLL_CTL_ADD, poller.pipe[0], &event) + if errno != nil { + return nil, errno + } + + return poller, nil +} + +// Wait using epoll. +// Returns true if something is ready to be read, +// false if there is not. +func (poller *fdPoller) wait() (bool, error) { + // 3 possible events per fd, and 2 fds, makes a maximum of 6 events. + // I don't know whether epoll_wait returns the number of events returned, + // or the total number of events ready. + // I decided to catch both by making the buffer one larger than the maximum. + events := make([]unix.EpollEvent, 7) + for { + n, errno := unix.EpollWait(poller.epfd, events, -1) + if n == -1 { + if errno == unix.EINTR { + continue + } + return false, errno + } + if n == 0 { + // If there are no events, try again. + continue + } + if n > 6 { + // This should never happen. More events were returned than should be possible. + return false, errors.New("epoll_wait returned more events than I know what to do with") + } + ready := events[:n] + epollhup := false + epollerr := false + epollin := false + for _, event := range ready { + if event.Fd == int32(poller.fd) { + if event.Events&unix.EPOLLHUP != 0 { + // This should not happen, but if it does, treat it as a wakeup. + epollhup = true + } + if event.Events&unix.EPOLLERR != 0 { + // If an error is waiting on the file descriptor, we should pretend + // something is ready to read, and let unix.Read pick up the error. + epollerr = true + } + if event.Events&unix.EPOLLIN != 0 { + // There is data to read. + epollin = true + } + } + if event.Fd == int32(poller.pipe[0]) { + if event.Events&unix.EPOLLHUP != 0 { + // Write pipe descriptor was closed, by us. This means we're closing down the + // watcher, and we should wake up. + } + if event.Events&unix.EPOLLERR != 0 { + // If an error is waiting on the pipe file descriptor. + // This is an absolute mystery, and should never ever happen. + return false, errors.New("Error on the pipe descriptor.") + } + if event.Events&unix.EPOLLIN != 0 { + // This is a regular wakeup, so we have to clear the buffer. + err := poller.clearWake() + if err != nil { + return false, err + } + } + } + } + + if epollhup || epollerr || epollin { + return true, nil + } + return false, nil + } +} + +// Close the write end of the poller. +func (poller *fdPoller) wake() error { + buf := make([]byte, 1) + n, errno := unix.Write(poller.pipe[1], buf) + if n == -1 { + if errno == unix.EAGAIN { + // Buffer is full, poller will wake. + return nil + } + return errno + } + return nil +} + +func (poller *fdPoller) clearWake() error { + // You have to be woken up a LOT in order to get to 100! + buf := make([]byte, 100) + n, errno := unix.Read(poller.pipe[0], buf) + if n == -1 { + if errno == unix.EAGAIN { + // Buffer is empty, someone else cleared our wake. + return nil + } + return errno + } + return nil +} + +// Close all poller file descriptors, but not the one passed to it. +func (poller *fdPoller) close() { + if poller.pipe[1] != -1 { + unix.Close(poller.pipe[1]) + } + if poller.pipe[0] != -1 { + unix.Close(poller.pipe[0]) + } + if poller.epfd != -1 { + unix.Close(poller.epfd) + } +} diff --git a/vendor/github.com/fsnotify/fsnotify/kqueue.go b/vendor/github.com/fsnotify/fsnotify/kqueue.go new file mode 100644 index 000000000..86e76a3d6 --- /dev/null +++ b/vendor/github.com/fsnotify/fsnotify/kqueue.go @@ -0,0 +1,521 @@ +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build freebsd openbsd netbsd dragonfly darwin + +package fsnotify + +import ( + "errors" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "sync" + "time" + + "golang.org/x/sys/unix" +) + +// Watcher watches a set of files, delivering events to a channel. +type Watcher struct { + Events chan Event + Errors chan error + done chan struct{} // Channel for sending a "quit message" to the reader goroutine + + kq int // File descriptor (as returned by the kqueue() syscall). + + mu sync.Mutex // Protects access to watcher data + watches map[string]int // Map of watched file descriptors (key: path). + externalWatches map[string]bool // Map of watches added by user of the library. + dirFlags map[string]uint32 // Map of watched directories to fflags used in kqueue. + paths map[int]pathInfo // Map file descriptors to path names for processing kqueue events. + fileExists map[string]bool // Keep track of if we know this file exists (to stop duplicate create events). + isClosed bool // Set to true when Close() is first called +} + +type pathInfo struct { + name string + isDir bool +} + +// NewWatcher establishes a new watcher with the underlying OS and begins waiting for events. +func NewWatcher() (*Watcher, error) { + kq, err := kqueue() + if err != nil { + return nil, err + } + + w := &Watcher{ + kq: kq, + watches: make(map[string]int), + dirFlags: make(map[string]uint32), + paths: make(map[int]pathInfo), + fileExists: make(map[string]bool), + externalWatches: make(map[string]bool), + Events: make(chan Event), + Errors: make(chan error), + done: make(chan struct{}), + } + + go w.readEvents() + return w, nil +} + +// Close removes all watches and closes the events channel. +func (w *Watcher) Close() error { + w.mu.Lock() + if w.isClosed { + w.mu.Unlock() + return nil + } + w.isClosed = true + + // copy paths to remove while locked + var pathsToRemove = make([]string, 0, len(w.watches)) + for name := range w.watches { + pathsToRemove = append(pathsToRemove, name) + } + w.mu.Unlock() + // unlock before calling Remove, which also locks + + for _, name := range pathsToRemove { + w.Remove(name) + } + + // send a "quit" message to the reader goroutine + close(w.done) + + return nil +} + +// Add starts watching the named file or directory (non-recursively). +func (w *Watcher) Add(name string) error { + w.mu.Lock() + w.externalWatches[name] = true + w.mu.Unlock() + _, err := w.addWatch(name, noteAllEvents) + return err +} + +// Remove stops watching the the named file or directory (non-recursively). +func (w *Watcher) Remove(name string) error { + name = filepath.Clean(name) + w.mu.Lock() + watchfd, ok := w.watches[name] + w.mu.Unlock() + if !ok { + return fmt.Errorf("can't remove non-existent kevent watch for: %s", name) + } + + const registerRemove = unix.EV_DELETE + if err := register(w.kq, []int{watchfd}, registerRemove, 0); err != nil { + return err + } + + unix.Close(watchfd) + + w.mu.Lock() + isDir := w.paths[watchfd].isDir + delete(w.watches, name) + delete(w.paths, watchfd) + delete(w.dirFlags, name) + w.mu.Unlock() + + // Find all watched paths that are in this directory that are not external. + if isDir { + var pathsToRemove []string + w.mu.Lock() + for _, path := range w.paths { + wdir, _ := filepath.Split(path.name) + if filepath.Clean(wdir) == name { + if !w.externalWatches[path.name] { + pathsToRemove = append(pathsToRemove, path.name) + } + } + } + w.mu.Unlock() + for _, name := range pathsToRemove { + // Since these are internal, not much sense in propagating error + // to the user, as that will just confuse them with an error about + // a path they did not explicitly watch themselves. + w.Remove(name) + } + } + + return nil +} + +// Watch all events (except NOTE_EXTEND, NOTE_LINK, NOTE_REVOKE) +const noteAllEvents = unix.NOTE_DELETE | unix.NOTE_WRITE | unix.NOTE_ATTRIB | unix.NOTE_RENAME + +// keventWaitTime to block on each read from kevent +var keventWaitTime = durationToTimespec(100 * time.Millisecond) + +// addWatch adds name to the watched file set. +// The flags are interpreted as described in kevent(2). +// Returns the real path to the file which was added, if any, which may be different from the one passed in the case of symlinks. +func (w *Watcher) addWatch(name string, flags uint32) (string, error) { + var isDir bool + // Make ./name and name equivalent + name = filepath.Clean(name) + + w.mu.Lock() + if w.isClosed { + w.mu.Unlock() + return "", errors.New("kevent instance already closed") + } + watchfd, alreadyWatching := w.watches[name] + // We already have a watch, but we can still override flags. + if alreadyWatching { + isDir = w.paths[watchfd].isDir + } + w.mu.Unlock() + + if !alreadyWatching { + fi, err := os.Lstat(name) + if err != nil { + return "", err + } + + // Don't watch sockets. + if fi.Mode()&os.ModeSocket == os.ModeSocket { + return "", nil + } + + // Don't watch named pipes. + if fi.Mode()&os.ModeNamedPipe == os.ModeNamedPipe { + return "", nil + } + + // Follow Symlinks + // Unfortunately, Linux can add bogus symlinks to watch list without + // issue, and Windows can't do symlinks period (AFAIK). To maintain + // consistency, we will act like everything is fine. There will simply + // be no file events for broken symlinks. + // Hence the returns of nil on errors. + if fi.Mode()&os.ModeSymlink == os.ModeSymlink { + name, err = filepath.EvalSymlinks(name) + if err != nil { + return "", nil + } + + w.mu.Lock() + _, alreadyWatching = w.watches[name] + w.mu.Unlock() + + if alreadyWatching { + return name, nil + } + + fi, err = os.Lstat(name) + if err != nil { + return "", nil + } + } + + watchfd, err = unix.Open(name, openMode, 0700) + if watchfd == -1 { + return "", err + } + + isDir = fi.IsDir() + } + + const registerAdd = unix.EV_ADD | unix.EV_CLEAR | unix.EV_ENABLE + if err := register(w.kq, []int{watchfd}, registerAdd, flags); err != nil { + unix.Close(watchfd) + return "", err + } + + if !alreadyWatching { + w.mu.Lock() + w.watches[name] = watchfd + w.paths[watchfd] = pathInfo{name: name, isDir: isDir} + w.mu.Unlock() + } + + if isDir { + // Watch the directory if it has not been watched before, + // or if it was watched before, but perhaps only a NOTE_DELETE (watchDirectoryFiles) + w.mu.Lock() + + watchDir := (flags&unix.NOTE_WRITE) == unix.NOTE_WRITE && + (!alreadyWatching || (w.dirFlags[name]&unix.NOTE_WRITE) != unix.NOTE_WRITE) + // Store flags so this watch can be updated later + w.dirFlags[name] = flags + w.mu.Unlock() + + if watchDir { + if err := w.watchDirectoryFiles(name); err != nil { + return "", err + } + } + } + return name, nil +} + +// readEvents reads from kqueue and converts the received kevents into +// Event values that it sends down the Events channel. +func (w *Watcher) readEvents() { + eventBuffer := make([]unix.Kevent_t, 10) + +loop: + for { + // See if there is a message on the "done" channel + select { + case <-w.done: + break loop + default: + } + + // Get new events + kevents, err := read(w.kq, eventBuffer, &keventWaitTime) + // EINTR is okay, the syscall was interrupted before timeout expired. + if err != nil && err != unix.EINTR { + select { + case w.Errors <- err: + case <-w.done: + break loop + } + continue + } + + // Flush the events we received to the Events channel + for len(kevents) > 0 { + kevent := &kevents[0] + watchfd := int(kevent.Ident) + mask := uint32(kevent.Fflags) + w.mu.Lock() + path := w.paths[watchfd] + w.mu.Unlock() + event := newEvent(path.name, mask) + + if path.isDir && !(event.Op&Remove == Remove) { + // Double check to make sure the directory exists. This can happen when + // we do a rm -fr on a recursively watched folders and we receive a + // modification event first but the folder has been deleted and later + // receive the delete event + if _, err := os.Lstat(event.Name); os.IsNotExist(err) { + // mark is as delete event + event.Op |= Remove + } + } + + if event.Op&Rename == Rename || event.Op&Remove == Remove { + w.Remove(event.Name) + w.mu.Lock() + delete(w.fileExists, event.Name) + w.mu.Unlock() + } + + if path.isDir && event.Op&Write == Write && !(event.Op&Remove == Remove) { + w.sendDirectoryChangeEvents(event.Name) + } else { + // Send the event on the Events channel. + select { + case w.Events <- event: + case <-w.done: + break loop + } + } + + if event.Op&Remove == Remove { + // Look for a file that may have overwritten this. + // For example, mv f1 f2 will delete f2, then create f2. + if path.isDir { + fileDir := filepath.Clean(event.Name) + w.mu.Lock() + _, found := w.watches[fileDir] + w.mu.Unlock() + if found { + // make sure the directory exists before we watch for changes. When we + // do a recursive watch and perform rm -fr, the parent directory might + // have gone missing, ignore the missing directory and let the + // upcoming delete event remove the watch from the parent directory. + if _, err := os.Lstat(fileDir); err == nil { + w.sendDirectoryChangeEvents(fileDir) + } + } + } else { + filePath := filepath.Clean(event.Name) + if fileInfo, err := os.Lstat(filePath); err == nil { + w.sendFileCreatedEventIfNew(filePath, fileInfo) + } + } + } + + // Move to next event + kevents = kevents[1:] + } + } + + // cleanup + err := unix.Close(w.kq) + if err != nil { + // only way the previous loop breaks is if w.done was closed so we need to async send to w.Errors. + select { + case w.Errors <- err: + default: + } + } + close(w.Events) + close(w.Errors) +} + +// newEvent returns an platform-independent Event based on kqueue Fflags. +func newEvent(name string, mask uint32) Event { + e := Event{Name: name} + if mask&unix.NOTE_DELETE == unix.NOTE_DELETE { + e.Op |= Remove + } + if mask&unix.NOTE_WRITE == unix.NOTE_WRITE { + e.Op |= Write + } + if mask&unix.NOTE_RENAME == unix.NOTE_RENAME { + e.Op |= Rename + } + if mask&unix.NOTE_ATTRIB == unix.NOTE_ATTRIB { + e.Op |= Chmod + } + return e +} + +func newCreateEvent(name string) Event { + return Event{Name: name, Op: Create} +} + +// watchDirectoryFiles to mimic inotify when adding a watch on a directory +func (w *Watcher) watchDirectoryFiles(dirPath string) error { + // Get all files + files, err := ioutil.ReadDir(dirPath) + if err != nil { + return err + } + + for _, fileInfo := range files { + filePath := filepath.Join(dirPath, fileInfo.Name()) + filePath, err = w.internalWatch(filePath, fileInfo) + if err != nil { + return err + } + + w.mu.Lock() + w.fileExists[filePath] = true + w.mu.Unlock() + } + + return nil +} + +// sendDirectoryEvents searches the directory for newly created files +// and sends them over the event channel. This functionality is to have +// the BSD version of fsnotify match Linux inotify which provides a +// create event for files created in a watched directory. +func (w *Watcher) sendDirectoryChangeEvents(dirPath string) { + // Get all files + files, err := ioutil.ReadDir(dirPath) + if err != nil { + select { + case w.Errors <- err: + case <-w.done: + return + } + } + + // Search for new files + for _, fileInfo := range files { + filePath := filepath.Join(dirPath, fileInfo.Name()) + err := w.sendFileCreatedEventIfNew(filePath, fileInfo) + + if err != nil { + return + } + } +} + +// sendFileCreatedEvent sends a create event if the file isn't already being tracked. +func (w *Watcher) sendFileCreatedEventIfNew(filePath string, fileInfo os.FileInfo) (err error) { + w.mu.Lock() + _, doesExist := w.fileExists[filePath] + w.mu.Unlock() + if !doesExist { + // Send create event + select { + case w.Events <- newCreateEvent(filePath): + case <-w.done: + return + } + } + + // like watchDirectoryFiles (but without doing another ReadDir) + filePath, err = w.internalWatch(filePath, fileInfo) + if err != nil { + return err + } + + w.mu.Lock() + w.fileExists[filePath] = true + w.mu.Unlock() + + return nil +} + +func (w *Watcher) internalWatch(name string, fileInfo os.FileInfo) (string, error) { + if fileInfo.IsDir() { + // mimic Linux providing delete events for subdirectories + // but preserve the flags used if currently watching subdirectory + w.mu.Lock() + flags := w.dirFlags[name] + w.mu.Unlock() + + flags |= unix.NOTE_DELETE | unix.NOTE_RENAME + return w.addWatch(name, flags) + } + + // watch file to mimic Linux inotify + return w.addWatch(name, noteAllEvents) +} + +// kqueue creates a new kernel event queue and returns a descriptor. +func kqueue() (kq int, err error) { + kq, err = unix.Kqueue() + if kq == -1 { + return kq, err + } + return kq, nil +} + +// register events with the queue +func register(kq int, fds []int, flags int, fflags uint32) error { + changes := make([]unix.Kevent_t, len(fds)) + + for i, fd := range fds { + // SetKevent converts int to the platform-specific types: + unix.SetKevent(&changes[i], fd, unix.EVFILT_VNODE, flags) + changes[i].Fflags = fflags + } + + // register the events + success, err := unix.Kevent(kq, changes, nil, nil) + if success == -1 { + return err + } + return nil +} + +// read retrieves pending events, or waits until an event occurs. +// A timeout of nil blocks indefinitely, while 0 polls the queue. +func read(kq int, events []unix.Kevent_t, timeout *unix.Timespec) ([]unix.Kevent_t, error) { + n, err := unix.Kevent(kq, nil, events, timeout) + if err != nil { + return nil, err + } + return events[0:n], nil +} + +// durationToTimespec prepares a timeout value +func durationToTimespec(d time.Duration) unix.Timespec { + return unix.NsecToTimespec(d.Nanoseconds()) +} diff --git a/vendor/github.com/fsnotify/fsnotify/open_mode_bsd.go b/vendor/github.com/fsnotify/fsnotify/open_mode_bsd.go new file mode 100644 index 000000000..2306c4620 --- /dev/null +++ b/vendor/github.com/fsnotify/fsnotify/open_mode_bsd.go @@ -0,0 +1,11 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build freebsd openbsd netbsd dragonfly + +package fsnotify + +import "golang.org/x/sys/unix" + +const openMode = unix.O_NONBLOCK | unix.O_RDONLY | unix.O_CLOEXEC diff --git a/vendor/github.com/fsnotify/fsnotify/open_mode_darwin.go b/vendor/github.com/fsnotify/fsnotify/open_mode_darwin.go new file mode 100644 index 000000000..870c4d6d1 --- /dev/null +++ b/vendor/github.com/fsnotify/fsnotify/open_mode_darwin.go @@ -0,0 +1,12 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build darwin + +package fsnotify + +import "golang.org/x/sys/unix" + +// note: this constant is not defined on BSD +const openMode = unix.O_EVTONLY | unix.O_CLOEXEC diff --git a/vendor/github.com/fsnotify/fsnotify/windows.go b/vendor/github.com/fsnotify/fsnotify/windows.go new file mode 100644 index 000000000..09436f31d --- /dev/null +++ b/vendor/github.com/fsnotify/fsnotify/windows.go @@ -0,0 +1,561 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build windows + +package fsnotify + +import ( + "errors" + "fmt" + "os" + "path/filepath" + "runtime" + "sync" + "syscall" + "unsafe" +) + +// Watcher watches a set of files, delivering events to a channel. +type Watcher struct { + Events chan Event + Errors chan error + isClosed bool // Set to true when Close() is first called + mu sync.Mutex // Map access + port syscall.Handle // Handle to completion port + watches watchMap // Map of watches (key: i-number) + input chan *input // Inputs to the reader are sent on this channel + quit chan chan<- error +} + +// NewWatcher establishes a new watcher with the underlying OS and begins waiting for events. +func NewWatcher() (*Watcher, error) { + port, e := syscall.CreateIoCompletionPort(syscall.InvalidHandle, 0, 0, 0) + if e != nil { + return nil, os.NewSyscallError("CreateIoCompletionPort", e) + } + w := &Watcher{ + port: port, + watches: make(watchMap), + input: make(chan *input, 1), + Events: make(chan Event, 50), + Errors: make(chan error), + quit: make(chan chan<- error, 1), + } + go w.readEvents() + return w, nil +} + +// Close removes all watches and closes the events channel. +func (w *Watcher) Close() error { + if w.isClosed { + return nil + } + w.isClosed = true + + // Send "quit" message to the reader goroutine + ch := make(chan error) + w.quit <- ch + if err := w.wakeupReader(); err != nil { + return err + } + return <-ch +} + +// Add starts watching the named file or directory (non-recursively). +func (w *Watcher) Add(name string) error { + if w.isClosed { + return errors.New("watcher already closed") + } + in := &input{ + op: opAddWatch, + path: filepath.Clean(name), + flags: sysFSALLEVENTS, + reply: make(chan error), + } + w.input <- in + if err := w.wakeupReader(); err != nil { + return err + } + return <-in.reply +} + +// Remove stops watching the the named file or directory (non-recursively). +func (w *Watcher) Remove(name string) error { + in := &input{ + op: opRemoveWatch, + path: filepath.Clean(name), + reply: make(chan error), + } + w.input <- in + if err := w.wakeupReader(); err != nil { + return err + } + return <-in.reply +} + +const ( + // Options for AddWatch + sysFSONESHOT = 0x80000000 + sysFSONLYDIR = 0x1000000 + + // Events + sysFSACCESS = 0x1 + sysFSALLEVENTS = 0xfff + sysFSATTRIB = 0x4 + sysFSCLOSE = 0x18 + sysFSCREATE = 0x100 + sysFSDELETE = 0x200 + sysFSDELETESELF = 0x400 + sysFSMODIFY = 0x2 + sysFSMOVE = 0xc0 + sysFSMOVEDFROM = 0x40 + sysFSMOVEDTO = 0x80 + sysFSMOVESELF = 0x800 + + // Special events + sysFSIGNORED = 0x8000 + sysFSQOVERFLOW = 0x4000 +) + +func newEvent(name string, mask uint32) Event { + e := Event{Name: name} + if mask&sysFSCREATE == sysFSCREATE || mask&sysFSMOVEDTO == sysFSMOVEDTO { + e.Op |= Create + } + if mask&sysFSDELETE == sysFSDELETE || mask&sysFSDELETESELF == sysFSDELETESELF { + e.Op |= Remove + } + if mask&sysFSMODIFY == sysFSMODIFY { + e.Op |= Write + } + if mask&sysFSMOVE == sysFSMOVE || mask&sysFSMOVESELF == sysFSMOVESELF || mask&sysFSMOVEDFROM == sysFSMOVEDFROM { + e.Op |= Rename + } + if mask&sysFSATTRIB == sysFSATTRIB { + e.Op |= Chmod + } + return e +} + +const ( + opAddWatch = iota + opRemoveWatch +) + +const ( + provisional uint64 = 1 << (32 + iota) +) + +type input struct { + op int + path string + flags uint32 + reply chan error +} + +type inode struct { + handle syscall.Handle + volume uint32 + index uint64 +} + +type watch struct { + ov syscall.Overlapped + ino *inode // i-number + path string // Directory path + mask uint64 // Directory itself is being watched with these notify flags + names map[string]uint64 // Map of names being watched and their notify flags + rename string // Remembers the old name while renaming a file + buf [4096]byte +} + +type indexMap map[uint64]*watch +type watchMap map[uint32]indexMap + +func (w *Watcher) wakeupReader() error { + e := syscall.PostQueuedCompletionStatus(w.port, 0, 0, nil) + if e != nil { + return os.NewSyscallError("PostQueuedCompletionStatus", e) + } + return nil +} + +func getDir(pathname string) (dir string, err error) { + attr, e := syscall.GetFileAttributes(syscall.StringToUTF16Ptr(pathname)) + if e != nil { + return "", os.NewSyscallError("GetFileAttributes", e) + } + if attr&syscall.FILE_ATTRIBUTE_DIRECTORY != 0 { + dir = pathname + } else { + dir, _ = filepath.Split(pathname) + dir = filepath.Clean(dir) + } + return +} + +func getIno(path string) (ino *inode, err error) { + h, e := syscall.CreateFile(syscall.StringToUTF16Ptr(path), + syscall.FILE_LIST_DIRECTORY, + syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE|syscall.FILE_SHARE_DELETE, + nil, syscall.OPEN_EXISTING, + syscall.FILE_FLAG_BACKUP_SEMANTICS|syscall.FILE_FLAG_OVERLAPPED, 0) + if e != nil { + return nil, os.NewSyscallError("CreateFile", e) + } + var fi syscall.ByHandleFileInformation + if e = syscall.GetFileInformationByHandle(h, &fi); e != nil { + syscall.CloseHandle(h) + return nil, os.NewSyscallError("GetFileInformationByHandle", e) + } + ino = &inode{ + handle: h, + volume: fi.VolumeSerialNumber, + index: uint64(fi.FileIndexHigh)<<32 | uint64(fi.FileIndexLow), + } + return ino, nil +} + +// Must run within the I/O thread. +func (m watchMap) get(ino *inode) *watch { + if i := m[ino.volume]; i != nil { + return i[ino.index] + } + return nil +} + +// Must run within the I/O thread. +func (m watchMap) set(ino *inode, watch *watch) { + i := m[ino.volume] + if i == nil { + i = make(indexMap) + m[ino.volume] = i + } + i[ino.index] = watch +} + +// Must run within the I/O thread. +func (w *Watcher) addWatch(pathname string, flags uint64) error { + dir, err := getDir(pathname) + if err != nil { + return err + } + if flags&sysFSONLYDIR != 0 && pathname != dir { + return nil + } + ino, err := getIno(dir) + if err != nil { + return err + } + w.mu.Lock() + watchEntry := w.watches.get(ino) + w.mu.Unlock() + if watchEntry == nil { + if _, e := syscall.CreateIoCompletionPort(ino.handle, w.port, 0, 0); e != nil { + syscall.CloseHandle(ino.handle) + return os.NewSyscallError("CreateIoCompletionPort", e) + } + watchEntry = &watch{ + ino: ino, + path: dir, + names: make(map[string]uint64), + } + w.mu.Lock() + w.watches.set(ino, watchEntry) + w.mu.Unlock() + flags |= provisional + } else { + syscall.CloseHandle(ino.handle) + } + if pathname == dir { + watchEntry.mask |= flags + } else { + watchEntry.names[filepath.Base(pathname)] |= flags + } + if err = w.startRead(watchEntry); err != nil { + return err + } + if pathname == dir { + watchEntry.mask &= ^provisional + } else { + watchEntry.names[filepath.Base(pathname)] &= ^provisional + } + return nil +} + +// Must run within the I/O thread. +func (w *Watcher) remWatch(pathname string) error { + dir, err := getDir(pathname) + if err != nil { + return err + } + ino, err := getIno(dir) + if err != nil { + return err + } + w.mu.Lock() + watch := w.watches.get(ino) + w.mu.Unlock() + if watch == nil { + return fmt.Errorf("can't remove non-existent watch for: %s", pathname) + } + if pathname == dir { + w.sendEvent(watch.path, watch.mask&sysFSIGNORED) + watch.mask = 0 + } else { + name := filepath.Base(pathname) + w.sendEvent(filepath.Join(watch.path, name), watch.names[name]&sysFSIGNORED) + delete(watch.names, name) + } + return w.startRead(watch) +} + +// Must run within the I/O thread. +func (w *Watcher) deleteWatch(watch *watch) { + for name, mask := range watch.names { + if mask&provisional == 0 { + w.sendEvent(filepath.Join(watch.path, name), mask&sysFSIGNORED) + } + delete(watch.names, name) + } + if watch.mask != 0 { + if watch.mask&provisional == 0 { + w.sendEvent(watch.path, watch.mask&sysFSIGNORED) + } + watch.mask = 0 + } +} + +// Must run within the I/O thread. +func (w *Watcher) startRead(watch *watch) error { + if e := syscall.CancelIo(watch.ino.handle); e != nil { + w.Errors <- os.NewSyscallError("CancelIo", e) + w.deleteWatch(watch) + } + mask := toWindowsFlags(watch.mask) + for _, m := range watch.names { + mask |= toWindowsFlags(m) + } + if mask == 0 { + if e := syscall.CloseHandle(watch.ino.handle); e != nil { + w.Errors <- os.NewSyscallError("CloseHandle", e) + } + w.mu.Lock() + delete(w.watches[watch.ino.volume], watch.ino.index) + w.mu.Unlock() + return nil + } + e := syscall.ReadDirectoryChanges(watch.ino.handle, &watch.buf[0], + uint32(unsafe.Sizeof(watch.buf)), false, mask, nil, &watch.ov, 0) + if e != nil { + err := os.NewSyscallError("ReadDirectoryChanges", e) + if e == syscall.ERROR_ACCESS_DENIED && watch.mask&provisional == 0 { + // Watched directory was probably removed + if w.sendEvent(watch.path, watch.mask&sysFSDELETESELF) { + if watch.mask&sysFSONESHOT != 0 { + watch.mask = 0 + } + } + err = nil + } + w.deleteWatch(watch) + w.startRead(watch) + return err + } + return nil +} + +// readEvents reads from the I/O completion port, converts the +// received events into Event objects and sends them via the Events channel. +// Entry point to the I/O thread. +func (w *Watcher) readEvents() { + var ( + n, key uint32 + ov *syscall.Overlapped + ) + runtime.LockOSThread() + + for { + e := syscall.GetQueuedCompletionStatus(w.port, &n, &key, &ov, syscall.INFINITE) + watch := (*watch)(unsafe.Pointer(ov)) + + if watch == nil { + select { + case ch := <-w.quit: + w.mu.Lock() + var indexes []indexMap + for _, index := range w.watches { + indexes = append(indexes, index) + } + w.mu.Unlock() + for _, index := range indexes { + for _, watch := range index { + w.deleteWatch(watch) + w.startRead(watch) + } + } + var err error + if e := syscall.CloseHandle(w.port); e != nil { + err = os.NewSyscallError("CloseHandle", e) + } + close(w.Events) + close(w.Errors) + ch <- err + return + case in := <-w.input: + switch in.op { + case opAddWatch: + in.reply <- w.addWatch(in.path, uint64(in.flags)) + case opRemoveWatch: + in.reply <- w.remWatch(in.path) + } + default: + } + continue + } + + switch e { + case syscall.ERROR_MORE_DATA: + if watch == nil { + w.Errors <- errors.New("ERROR_MORE_DATA has unexpectedly null lpOverlapped buffer") + } else { + // The i/o succeeded but the buffer is full. + // In theory we should be building up a full packet. + // In practice we can get away with just carrying on. + n = uint32(unsafe.Sizeof(watch.buf)) + } + case syscall.ERROR_ACCESS_DENIED: + // Watched directory was probably removed + w.sendEvent(watch.path, watch.mask&sysFSDELETESELF) + w.deleteWatch(watch) + w.startRead(watch) + continue + case syscall.ERROR_OPERATION_ABORTED: + // CancelIo was called on this handle + continue + default: + w.Errors <- os.NewSyscallError("GetQueuedCompletionPort", e) + continue + case nil: + } + + var offset uint32 + for { + if n == 0 { + w.Events <- newEvent("", sysFSQOVERFLOW) + w.Errors <- errors.New("short read in readEvents()") + break + } + + // Point "raw" to the event in the buffer + raw := (*syscall.FileNotifyInformation)(unsafe.Pointer(&watch.buf[offset])) + buf := (*[syscall.MAX_PATH]uint16)(unsafe.Pointer(&raw.FileName)) + name := syscall.UTF16ToString(buf[:raw.FileNameLength/2]) + fullname := filepath.Join(watch.path, name) + + var mask uint64 + switch raw.Action { + case syscall.FILE_ACTION_REMOVED: + mask = sysFSDELETESELF + case syscall.FILE_ACTION_MODIFIED: + mask = sysFSMODIFY + case syscall.FILE_ACTION_RENAMED_OLD_NAME: + watch.rename = name + case syscall.FILE_ACTION_RENAMED_NEW_NAME: + if watch.names[watch.rename] != 0 { + watch.names[name] |= watch.names[watch.rename] + delete(watch.names, watch.rename) + mask = sysFSMOVESELF + } + } + + sendNameEvent := func() { + if w.sendEvent(fullname, watch.names[name]&mask) { + if watch.names[name]&sysFSONESHOT != 0 { + delete(watch.names, name) + } + } + } + if raw.Action != syscall.FILE_ACTION_RENAMED_NEW_NAME { + sendNameEvent() + } + if raw.Action == syscall.FILE_ACTION_REMOVED { + w.sendEvent(fullname, watch.names[name]&sysFSIGNORED) + delete(watch.names, name) + } + if w.sendEvent(fullname, watch.mask&toFSnotifyFlags(raw.Action)) { + if watch.mask&sysFSONESHOT != 0 { + watch.mask = 0 + } + } + if raw.Action == syscall.FILE_ACTION_RENAMED_NEW_NAME { + fullname = filepath.Join(watch.path, watch.rename) + sendNameEvent() + } + + // Move to the next event in the buffer + if raw.NextEntryOffset == 0 { + break + } + offset += raw.NextEntryOffset + + // Error! + if offset >= n { + w.Errors <- errors.New("Windows system assumed buffer larger than it is, events have likely been missed.") + break + } + } + + if err := w.startRead(watch); err != nil { + w.Errors <- err + } + } +} + +func (w *Watcher) sendEvent(name string, mask uint64) bool { + if mask == 0 { + return false + } + event := newEvent(name, uint32(mask)) + select { + case ch := <-w.quit: + w.quit <- ch + case w.Events <- event: + } + return true +} + +func toWindowsFlags(mask uint64) uint32 { + var m uint32 + if mask&sysFSACCESS != 0 { + m |= syscall.FILE_NOTIFY_CHANGE_LAST_ACCESS + } + if mask&sysFSMODIFY != 0 { + m |= syscall.FILE_NOTIFY_CHANGE_LAST_WRITE + } + if mask&sysFSATTRIB != 0 { + m |= syscall.FILE_NOTIFY_CHANGE_ATTRIBUTES + } + if mask&(sysFSMOVE|sysFSCREATE|sysFSDELETE) != 0 { + m |= syscall.FILE_NOTIFY_CHANGE_FILE_NAME | syscall.FILE_NOTIFY_CHANGE_DIR_NAME + } + return m +} + +func toFSnotifyFlags(action uint32) uint64 { + switch action { + case syscall.FILE_ACTION_ADDED: + return sysFSCREATE + case syscall.FILE_ACTION_REMOVED: + return sysFSDELETE + case syscall.FILE_ACTION_MODIFIED: + return sysFSMODIFY + case syscall.FILE_ACTION_RENAMED_OLD_NAME: + return sysFSMOVEDFROM + case syscall.FILE_ACTION_RENAMED_NEW_NAME: + return sysFSMOVEDTO + } + return 0 +} diff --git a/vendor/github.com/google/uuid/.travis.yml b/vendor/github.com/google/uuid/.travis.yml new file mode 100644 index 000000000..d8156a60b --- /dev/null +++ b/vendor/github.com/google/uuid/.travis.yml @@ -0,0 +1,9 @@ +language: go + +go: + - 1.4.3 + - 1.5.3 + - tip + +script: + - go test -v ./... diff --git a/vendor/github.com/google/uuid/CONTRIBUTING.md b/vendor/github.com/google/uuid/CONTRIBUTING.md new file mode 100644 index 000000000..04fdf09f1 --- /dev/null +++ b/vendor/github.com/google/uuid/CONTRIBUTING.md @@ -0,0 +1,10 @@ +# How to contribute + +We definitely welcome patches and contribution to this project! + +### Legal requirements + +In order to protect both you and ourselves, you will need to sign the +[Contributor License Agreement](https://cla.developers.google.com/clas). + +You may have already signed it for other Google projects. diff --git a/vendor/github.com/google/uuid/CONTRIBUTORS b/vendor/github.com/google/uuid/CONTRIBUTORS new file mode 100644 index 000000000..b4bb97f6b --- /dev/null +++ b/vendor/github.com/google/uuid/CONTRIBUTORS @@ -0,0 +1,9 @@ +Paul Borman +bmatsuo +shawnps +theory +jboverfelt +dsymonds +cd1 +wallclockbuilder +dansouza diff --git a/vendor/github.com/google/uuid/LICENSE b/vendor/github.com/google/uuid/LICENSE new file mode 100644 index 000000000..5dc68268d --- /dev/null +++ b/vendor/github.com/google/uuid/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2009,2014 Google Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/google/uuid/README.md b/vendor/github.com/google/uuid/README.md new file mode 100644 index 000000000..9d92c11f1 --- /dev/null +++ b/vendor/github.com/google/uuid/README.md @@ -0,0 +1,19 @@ +# uuid ![build status](https://travis-ci.org/google/uuid.svg?branch=master) +The uuid package generates and inspects UUIDs based on +[RFC 4122](http://tools.ietf.org/html/rfc4122) +and DCE 1.1: Authentication and Security Services. + +This package is based on the github.com/pborman/uuid package (previously named +code.google.com/p/go-uuid). It differs from these earlier packages in that +a UUID is a 16 byte array rather than a byte slice. One loss due to this +change is the ability to represent an invalid UUID (vs a NIL UUID). + +###### Install +`go get github.com/google/uuid` + +###### Documentation +[![GoDoc](https://godoc.org/github.com/google/uuid?status.svg)](http://godoc.org/github.com/google/uuid) + +Full `go doc` style documentation for the package can be viewed online without +installing this package by using the GoDoc site here: +http://godoc.org/github.com/google/uuid diff --git a/vendor/github.com/google/uuid/dce.go b/vendor/github.com/google/uuid/dce.go new file mode 100644 index 000000000..fa820b9d3 --- /dev/null +++ b/vendor/github.com/google/uuid/dce.go @@ -0,0 +1,80 @@ +// Copyright 2016 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import ( + "encoding/binary" + "fmt" + "os" +) + +// A Domain represents a Version 2 domain +type Domain byte + +// Domain constants for DCE Security (Version 2) UUIDs. +const ( + Person = Domain(0) + Group = Domain(1) + Org = Domain(2) +) + +// NewDCESecurity returns a DCE Security (Version 2) UUID. +// +// The domain should be one of Person, Group or Org. +// On a POSIX system the id should be the users UID for the Person +// domain and the users GID for the Group. The meaning of id for +// the domain Org or on non-POSIX systems is site defined. +// +// For a given domain/id pair the same token may be returned for up to +// 7 minutes and 10 seconds. +func NewDCESecurity(domain Domain, id uint32) (UUID, error) { + uuid, err := NewUUID() + if err == nil { + uuid[6] = (uuid[6] & 0x0f) | 0x20 // Version 2 + uuid[9] = byte(domain) + binary.BigEndian.PutUint32(uuid[0:], id) + } + return uuid, err +} + +// NewDCEPerson returns a DCE Security (Version 2) UUID in the person +// domain with the id returned by os.Getuid. +// +// NewDCESecurity(Person, uint32(os.Getuid())) +func NewDCEPerson() (UUID, error) { + return NewDCESecurity(Person, uint32(os.Getuid())) +} + +// NewDCEGroup returns a DCE Security (Version 2) UUID in the group +// domain with the id returned by os.Getgid. +// +// NewDCESecurity(Group, uint32(os.Getgid())) +func NewDCEGroup() (UUID, error) { + return NewDCESecurity(Group, uint32(os.Getgid())) +} + +// Domain returns the domain for a Version 2 UUID. Domains are only defined +// for Version 2 UUIDs. +func (uuid UUID) Domain() Domain { + return Domain(uuid[9]) +} + +// ID returns the id for a Version 2 UUID. IDs are only defined for Version 2 +// UUIDs. +func (uuid UUID) ID() uint32 { + return binary.BigEndian.Uint32(uuid[0:4]) +} + +func (d Domain) String() string { + switch d { + case Person: + return "Person" + case Group: + return "Group" + case Org: + return "Org" + } + return fmt.Sprintf("Domain%d", int(d)) +} diff --git a/vendor/github.com/google/uuid/doc.go b/vendor/github.com/google/uuid/doc.go new file mode 100644 index 000000000..5b8a4b9af --- /dev/null +++ b/vendor/github.com/google/uuid/doc.go @@ -0,0 +1,12 @@ +// Copyright 2016 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package uuid generates and inspects UUIDs. +// +// UUIDs are based on RFC 4122 and DCE 1.1: Authentication and Security +// Services. +// +// A UUID is a 16 byte (128 bit) array. UUIDs may be used as keys to +// maps or compared directly. +package uuid diff --git a/vendor/github.com/google/uuid/go.mod b/vendor/github.com/google/uuid/go.mod new file mode 100644 index 000000000..fc84cd79d --- /dev/null +++ b/vendor/github.com/google/uuid/go.mod @@ -0,0 +1 @@ +module github.com/google/uuid diff --git a/vendor/github.com/google/uuid/hash.go b/vendor/github.com/google/uuid/hash.go new file mode 100644 index 000000000..b17461631 --- /dev/null +++ b/vendor/github.com/google/uuid/hash.go @@ -0,0 +1,53 @@ +// Copyright 2016 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import ( + "crypto/md5" + "crypto/sha1" + "hash" +) + +// Well known namespace IDs and UUIDs +var ( + NameSpaceDNS = Must(Parse("6ba7b810-9dad-11d1-80b4-00c04fd430c8")) + NameSpaceURL = Must(Parse("6ba7b811-9dad-11d1-80b4-00c04fd430c8")) + NameSpaceOID = Must(Parse("6ba7b812-9dad-11d1-80b4-00c04fd430c8")) + NameSpaceX500 = Must(Parse("6ba7b814-9dad-11d1-80b4-00c04fd430c8")) + Nil UUID // empty UUID, all zeros +) + +// NewHash returns a new UUID derived from the hash of space concatenated with +// data generated by h. The hash should be at least 16 byte in length. The +// first 16 bytes of the hash are used to form the UUID. The version of the +// UUID will be the lower 4 bits of version. NewHash is used to implement +// NewMD5 and NewSHA1. +func NewHash(h hash.Hash, space UUID, data []byte, version int) UUID { + h.Reset() + h.Write(space[:]) + h.Write(data) + s := h.Sum(nil) + var uuid UUID + copy(uuid[:], s) + uuid[6] = (uuid[6] & 0x0f) | uint8((version&0xf)<<4) + uuid[8] = (uuid[8] & 0x3f) | 0x80 // RFC 4122 variant + return uuid +} + +// NewMD5 returns a new MD5 (Version 3) UUID based on the +// supplied name space and data. It is the same as calling: +// +// NewHash(md5.New(), space, data, 3) +func NewMD5(space UUID, data []byte) UUID { + return NewHash(md5.New(), space, data, 3) +} + +// NewSHA1 returns a new SHA1 (Version 5) UUID based on the +// supplied name space and data. It is the same as calling: +// +// NewHash(sha1.New(), space, data, 5) +func NewSHA1(space UUID, data []byte) UUID { + return NewHash(sha1.New(), space, data, 5) +} diff --git a/vendor/github.com/google/uuid/marshal.go b/vendor/github.com/google/uuid/marshal.go new file mode 100644 index 000000000..7f9e0c6c0 --- /dev/null +++ b/vendor/github.com/google/uuid/marshal.go @@ -0,0 +1,37 @@ +// Copyright 2016 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import "fmt" + +// MarshalText implements encoding.TextMarshaler. +func (uuid UUID) MarshalText() ([]byte, error) { + var js [36]byte + encodeHex(js[:], uuid) + return js[:], nil +} + +// UnmarshalText implements encoding.TextUnmarshaler. +func (uuid *UUID) UnmarshalText(data []byte) error { + id, err := ParseBytes(data) + if err == nil { + *uuid = id + } + return err +} + +// MarshalBinary implements encoding.BinaryMarshaler. +func (uuid UUID) MarshalBinary() ([]byte, error) { + return uuid[:], nil +} + +// UnmarshalBinary implements encoding.BinaryUnmarshaler. +func (uuid *UUID) UnmarshalBinary(data []byte) error { + if len(data) != 16 { + return fmt.Errorf("invalid UUID (got %d bytes)", len(data)) + } + copy(uuid[:], data) + return nil +} diff --git a/vendor/github.com/google/uuid/node.go b/vendor/github.com/google/uuid/node.go new file mode 100644 index 000000000..d651a2b06 --- /dev/null +++ b/vendor/github.com/google/uuid/node.go @@ -0,0 +1,90 @@ +// Copyright 2016 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import ( + "sync" +) + +var ( + nodeMu sync.Mutex + ifname string // name of interface being used + nodeID [6]byte // hardware for version 1 UUIDs + zeroID [6]byte // nodeID with only 0's +) + +// NodeInterface returns the name of the interface from which the NodeID was +// derived. The interface "user" is returned if the NodeID was set by +// SetNodeID. +func NodeInterface() string { + defer nodeMu.Unlock() + nodeMu.Lock() + return ifname +} + +// SetNodeInterface selects the hardware address to be used for Version 1 UUIDs. +// If name is "" then the first usable interface found will be used or a random +// Node ID will be generated. If a named interface cannot be found then false +// is returned. +// +// SetNodeInterface never fails when name is "". +func SetNodeInterface(name string) bool { + defer nodeMu.Unlock() + nodeMu.Lock() + return setNodeInterface(name) +} + +func setNodeInterface(name string) bool { + iname, addr := getHardwareInterface(name) // null implementation for js + if iname != "" && addr != nil { + ifname = iname + copy(nodeID[:], addr) + return true + } + + // We found no interfaces with a valid hardware address. If name + // does not specify a specific interface generate a random Node ID + // (section 4.1.6) + if name == "" { + ifname = "random" + randomBits(nodeID[:]) + return true + } + return false +} + +// NodeID returns a slice of a copy of the current Node ID, setting the Node ID +// if not already set. +func NodeID() []byte { + defer nodeMu.Unlock() + nodeMu.Lock() + if nodeID == zeroID { + setNodeInterface("") + } + nid := nodeID + return nid[:] +} + +// SetNodeID sets the Node ID to be used for Version 1 UUIDs. The first 6 bytes +// of id are used. If id is less than 6 bytes then false is returned and the +// Node ID is not set. +func SetNodeID(id []byte) bool { + if len(id) < 6 { + return false + } + defer nodeMu.Unlock() + nodeMu.Lock() + copy(nodeID[:], id) + ifname = "user" + return true +} + +// NodeID returns the 6 byte node id encoded in uuid. It returns nil if uuid is +// not valid. The NodeID is only well defined for version 1 and 2 UUIDs. +func (uuid UUID) NodeID() []byte { + var node [6]byte + copy(node[:], uuid[10:]) + return node[:] +} diff --git a/vendor/github.com/google/uuid/node_js.go b/vendor/github.com/google/uuid/node_js.go new file mode 100644 index 000000000..24b78edc9 --- /dev/null +++ b/vendor/github.com/google/uuid/node_js.go @@ -0,0 +1,12 @@ +// Copyright 2017 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build js + +package uuid + +// getHardwareInterface returns nil values for the JS version of the code. +// This remvoves the "net" dependency, because it is not used in the browser. +// Using the "net" library inflates the size of the transpiled JS code by 673k bytes. +func getHardwareInterface(name string) (string, []byte) { return "", nil } diff --git a/vendor/github.com/google/uuid/node_net.go b/vendor/github.com/google/uuid/node_net.go new file mode 100644 index 000000000..0cbbcddbd --- /dev/null +++ b/vendor/github.com/google/uuid/node_net.go @@ -0,0 +1,33 @@ +// Copyright 2017 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build !js + +package uuid + +import "net" + +var interfaces []net.Interface // cached list of interfaces + +// getHardwareInterface returns the name and hardware address of interface name. +// If name is "" then the name and hardware address of one of the system's +// interfaces is returned. If no interfaces are found (name does not exist or +// there are no interfaces) then "", nil is returned. +// +// Only addresses of at least 6 bytes are returned. +func getHardwareInterface(name string) (string, []byte) { + if interfaces == nil { + var err error + interfaces, err = net.Interfaces() + if err != nil { + return "", nil + } + } + for _, ifs := range interfaces { + if len(ifs.HardwareAddr) >= 6 && (name == "" || name == ifs.Name) { + return ifs.Name, ifs.HardwareAddr + } + } + return "", nil +} diff --git a/vendor/github.com/google/uuid/sql.go b/vendor/github.com/google/uuid/sql.go new file mode 100644 index 000000000..f326b54db --- /dev/null +++ b/vendor/github.com/google/uuid/sql.go @@ -0,0 +1,59 @@ +// Copyright 2016 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import ( + "database/sql/driver" + "fmt" +) + +// Scan implements sql.Scanner so UUIDs can be read from databases transparently +// Currently, database types that map to string and []byte are supported. Please +// consult database-specific driver documentation for matching types. +func (uuid *UUID) Scan(src interface{}) error { + switch src := src.(type) { + case nil: + return nil + + case string: + // if an empty UUID comes from a table, we return a null UUID + if src == "" { + return nil + } + + // see Parse for required string format + u, err := Parse(src) + if err != nil { + return fmt.Errorf("Scan: %v", err) + } + + *uuid = u + + case []byte: + // if an empty UUID comes from a table, we return a null UUID + if len(src) == 0 { + return nil + } + + // assumes a simple slice of bytes if 16 bytes + // otherwise attempts to parse + if len(src) != 16 { + return uuid.Scan(string(src)) + } + copy((*uuid)[:], src) + + default: + return fmt.Errorf("Scan: unable to scan type %T into UUID", src) + } + + return nil +} + +// Value implements sql.Valuer so that UUIDs can be written to databases +// transparently. Currently, UUIDs map to strings. Please consult +// database-specific driver documentation for matching types. +func (uuid UUID) Value() (driver.Value, error) { + return uuid.String(), nil +} diff --git a/vendor/github.com/google/uuid/time.go b/vendor/github.com/google/uuid/time.go new file mode 100644 index 000000000..e6ef06cdc --- /dev/null +++ b/vendor/github.com/google/uuid/time.go @@ -0,0 +1,123 @@ +// Copyright 2016 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import ( + "encoding/binary" + "sync" + "time" +) + +// A Time represents a time as the number of 100's of nanoseconds since 15 Oct +// 1582. +type Time int64 + +const ( + lillian = 2299160 // Julian day of 15 Oct 1582 + unix = 2440587 // Julian day of 1 Jan 1970 + epoch = unix - lillian // Days between epochs + g1582 = epoch * 86400 // seconds between epochs + g1582ns100 = g1582 * 10000000 // 100s of a nanoseconds between epochs +) + +var ( + timeMu sync.Mutex + lasttime uint64 // last time we returned + clockSeq uint16 // clock sequence for this run + + timeNow = time.Now // for testing +) + +// UnixTime converts t the number of seconds and nanoseconds using the Unix +// epoch of 1 Jan 1970. +func (t Time) UnixTime() (sec, nsec int64) { + sec = int64(t - g1582ns100) + nsec = (sec % 10000000) * 100 + sec /= 10000000 + return sec, nsec +} + +// GetTime returns the current Time (100s of nanoseconds since 15 Oct 1582) and +// clock sequence as well as adjusting the clock sequence as needed. An error +// is returned if the current time cannot be determined. +func GetTime() (Time, uint16, error) { + defer timeMu.Unlock() + timeMu.Lock() + return getTime() +} + +func getTime() (Time, uint16, error) { + t := timeNow() + + // If we don't have a clock sequence already, set one. + if clockSeq == 0 { + setClockSequence(-1) + } + now := uint64(t.UnixNano()/100) + g1582ns100 + + // If time has gone backwards with this clock sequence then we + // increment the clock sequence + if now <= lasttime { + clockSeq = ((clockSeq + 1) & 0x3fff) | 0x8000 + } + lasttime = now + return Time(now), clockSeq, nil +} + +// ClockSequence returns the current clock sequence, generating one if not +// already set. The clock sequence is only used for Version 1 UUIDs. +// +// The uuid package does not use global static storage for the clock sequence or +// the last time a UUID was generated. Unless SetClockSequence is used, a new +// random clock sequence is generated the first time a clock sequence is +// requested by ClockSequence, GetTime, or NewUUID. (section 4.2.1.1) +func ClockSequence() int { + defer timeMu.Unlock() + timeMu.Lock() + return clockSequence() +} + +func clockSequence() int { + if clockSeq == 0 { + setClockSequence(-1) + } + return int(clockSeq & 0x3fff) +} + +// SetClockSequence sets the clock sequence to the lower 14 bits of seq. Setting to +// -1 causes a new sequence to be generated. +func SetClockSequence(seq int) { + defer timeMu.Unlock() + timeMu.Lock() + setClockSequence(seq) +} + +func setClockSequence(seq int) { + if seq == -1 { + var b [2]byte + randomBits(b[:]) // clock sequence + seq = int(b[0])<<8 | int(b[1]) + } + oldSeq := clockSeq + clockSeq = uint16(seq&0x3fff) | 0x8000 // Set our variant + if oldSeq != clockSeq { + lasttime = 0 + } +} + +// Time returns the time in 100s of nanoseconds since 15 Oct 1582 encoded in +// uuid. The time is only defined for version 1 and 2 UUIDs. +func (uuid UUID) Time() Time { + time := int64(binary.BigEndian.Uint32(uuid[0:4])) + time |= int64(binary.BigEndian.Uint16(uuid[4:6])) << 32 + time |= int64(binary.BigEndian.Uint16(uuid[6:8])&0xfff) << 48 + return Time(time) +} + +// ClockSequence returns the clock sequence encoded in uuid. +// The clock sequence is only well defined for version 1 and 2 UUIDs. +func (uuid UUID) ClockSequence() int { + return int(binary.BigEndian.Uint16(uuid[8:10])) & 0x3fff +} diff --git a/vendor/github.com/google/uuid/util.go b/vendor/github.com/google/uuid/util.go new file mode 100644 index 000000000..5ea6c7378 --- /dev/null +++ b/vendor/github.com/google/uuid/util.go @@ -0,0 +1,43 @@ +// Copyright 2016 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import ( + "io" +) + +// randomBits completely fills slice b with random data. +func randomBits(b []byte) { + if _, err := io.ReadFull(rander, b); err != nil { + panic(err.Error()) // rand should never fail + } +} + +// xvalues returns the value of a byte as a hexadecimal digit or 255. +var xvalues = [256]byte{ + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 255, 255, 255, 255, 255, 255, + 255, 10, 11, 12, 13, 14, 15, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 10, 11, 12, 13, 14, 15, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, +} + +// xtob converts hex characters x1 and x2 into a byte. +func xtob(x1, x2 byte) (byte, bool) { + b1 := xvalues[x1] + b2 := xvalues[x2] + return (b1 << 4) | b2, b1 != 255 && b2 != 255 +} diff --git a/vendor/github.com/google/uuid/uuid.go b/vendor/github.com/google/uuid/uuid.go new file mode 100644 index 000000000..524404cc5 --- /dev/null +++ b/vendor/github.com/google/uuid/uuid.go @@ -0,0 +1,245 @@ +// Copyright 2018 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import ( + "bytes" + "crypto/rand" + "encoding/hex" + "errors" + "fmt" + "io" + "strings" +) + +// A UUID is a 128 bit (16 byte) Universal Unique IDentifier as defined in RFC +// 4122. +type UUID [16]byte + +// A Version represents a UUID's version. +type Version byte + +// A Variant represents a UUID's variant. +type Variant byte + +// Constants returned by Variant. +const ( + Invalid = Variant(iota) // Invalid UUID + RFC4122 // The variant specified in RFC4122 + Reserved // Reserved, NCS backward compatibility. + Microsoft // Reserved, Microsoft Corporation backward compatibility. + Future // Reserved for future definition. +) + +var rander = rand.Reader // random function + +// Parse decodes s into a UUID or returns an error. Both the standard UUID +// forms of xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx and +// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx are decoded as well as the +// Microsoft encoding {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx} and the raw hex +// encoding: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx. +func Parse(s string) (UUID, error) { + var uuid UUID + switch len(s) { + // xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx + case 36: + + // urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx + case 36 + 9: + if strings.ToLower(s[:9]) != "urn:uuid:" { + return uuid, fmt.Errorf("invalid urn prefix: %q", s[:9]) + } + s = s[9:] + + // {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx} + case 36 + 2: + s = s[1:] + + // xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + case 32: + var ok bool + for i := range uuid { + uuid[i], ok = xtob(s[i*2], s[i*2+1]) + if !ok { + return uuid, errors.New("invalid UUID format") + } + } + return uuid, nil + default: + return uuid, fmt.Errorf("invalid UUID length: %d", len(s)) + } + // s is now at least 36 bytes long + // it must be of the form xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx + if s[8] != '-' || s[13] != '-' || s[18] != '-' || s[23] != '-' { + return uuid, errors.New("invalid UUID format") + } + for i, x := range [16]int{ + 0, 2, 4, 6, + 9, 11, + 14, 16, + 19, 21, + 24, 26, 28, 30, 32, 34} { + v, ok := xtob(s[x], s[x+1]) + if !ok { + return uuid, errors.New("invalid UUID format") + } + uuid[i] = v + } + return uuid, nil +} + +// ParseBytes is like Parse, except it parses a byte slice instead of a string. +func ParseBytes(b []byte) (UUID, error) { + var uuid UUID + switch len(b) { + case 36: // xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx + case 36 + 9: // urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx + if !bytes.Equal(bytes.ToLower(b[:9]), []byte("urn:uuid:")) { + return uuid, fmt.Errorf("invalid urn prefix: %q", b[:9]) + } + b = b[9:] + case 36 + 2: // {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx} + b = b[1:] + case 32: // xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + var ok bool + for i := 0; i < 32; i += 2 { + uuid[i/2], ok = xtob(b[i], b[i+1]) + if !ok { + return uuid, errors.New("invalid UUID format") + } + } + return uuid, nil + default: + return uuid, fmt.Errorf("invalid UUID length: %d", len(b)) + } + // s is now at least 36 bytes long + // it must be of the form xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx + if b[8] != '-' || b[13] != '-' || b[18] != '-' || b[23] != '-' { + return uuid, errors.New("invalid UUID format") + } + for i, x := range [16]int{ + 0, 2, 4, 6, + 9, 11, + 14, 16, + 19, 21, + 24, 26, 28, 30, 32, 34} { + v, ok := xtob(b[x], b[x+1]) + if !ok { + return uuid, errors.New("invalid UUID format") + } + uuid[i] = v + } + return uuid, nil +} + +// MustParse is like Parse but panics if the string cannot be parsed. +// It simplifies safe initialization of global variables holding compiled UUIDs. +func MustParse(s string) UUID { + uuid, err := Parse(s) + if err != nil { + panic(`uuid: Parse(` + s + `): ` + err.Error()) + } + return uuid +} + +// FromBytes creates a new UUID from a byte slice. Returns an error if the slice +// does not have a length of 16. The bytes are copied from the slice. +func FromBytes(b []byte) (uuid UUID, err error) { + err = uuid.UnmarshalBinary(b) + return uuid, err +} + +// Must returns uuid if err is nil and panics otherwise. +func Must(uuid UUID, err error) UUID { + if err != nil { + panic(err) + } + return uuid +} + +// String returns the string form of uuid, xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx +// , or "" if uuid is invalid. +func (uuid UUID) String() string { + var buf [36]byte + encodeHex(buf[:], uuid) + return string(buf[:]) +} + +// URN returns the RFC 2141 URN form of uuid, +// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx, or "" if uuid is invalid. +func (uuid UUID) URN() string { + var buf [36 + 9]byte + copy(buf[:], "urn:uuid:") + encodeHex(buf[9:], uuid) + return string(buf[:]) +} + +func encodeHex(dst []byte, uuid UUID) { + hex.Encode(dst, uuid[:4]) + dst[8] = '-' + hex.Encode(dst[9:13], uuid[4:6]) + dst[13] = '-' + hex.Encode(dst[14:18], uuid[6:8]) + dst[18] = '-' + hex.Encode(dst[19:23], uuid[8:10]) + dst[23] = '-' + hex.Encode(dst[24:], uuid[10:]) +} + +// Variant returns the variant encoded in uuid. +func (uuid UUID) Variant() Variant { + switch { + case (uuid[8] & 0xc0) == 0x80: + return RFC4122 + case (uuid[8] & 0xe0) == 0xc0: + return Microsoft + case (uuid[8] & 0xe0) == 0xe0: + return Future + default: + return Reserved + } +} + +// Version returns the version of uuid. +func (uuid UUID) Version() Version { + return Version(uuid[6] >> 4) +} + +func (v Version) String() string { + if v > 15 { + return fmt.Sprintf("BAD_VERSION_%d", v) + } + return fmt.Sprintf("VERSION_%d", v) +} + +func (v Variant) String() string { + switch v { + case RFC4122: + return "RFC4122" + case Reserved: + return "Reserved" + case Microsoft: + return "Microsoft" + case Future: + return "Future" + case Invalid: + return "Invalid" + } + return fmt.Sprintf("BadVariant%d", int(v)) +} + +// SetRand sets the random number generator to r, which implements io.Reader. +// If r.Read returns an error when the package requests random data then +// a panic will be issued. +// +// Calling SetRand with nil sets the random number generator to the default +// generator. +func SetRand(r io.Reader) { + if r == nil { + rander = rand.Reader + return + } + rander = r +} diff --git a/vendor/github.com/google/uuid/version1.go b/vendor/github.com/google/uuid/version1.go new file mode 100644 index 000000000..199a1ac65 --- /dev/null +++ b/vendor/github.com/google/uuid/version1.go @@ -0,0 +1,44 @@ +// Copyright 2016 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import ( + "encoding/binary" +) + +// NewUUID returns a Version 1 UUID based on the current NodeID and clock +// sequence, and the current time. If the NodeID has not been set by SetNodeID +// or SetNodeInterface then it will be set automatically. If the NodeID cannot +// be set NewUUID returns nil. If clock sequence has not been set by +// SetClockSequence then it will be set automatically. If GetTime fails to +// return the current NewUUID returns nil and an error. +// +// In most cases, New should be used. +func NewUUID() (UUID, error) { + nodeMu.Lock() + if nodeID == zeroID { + setNodeInterface("") + } + nodeMu.Unlock() + + var uuid UUID + now, seq, err := GetTime() + if err != nil { + return uuid, err + } + + timeLow := uint32(now & 0xffffffff) + timeMid := uint16((now >> 32) & 0xffff) + timeHi := uint16((now >> 48) & 0x0fff) + timeHi |= 0x1000 // Version 1 + + binary.BigEndian.PutUint32(uuid[0:], timeLow) + binary.BigEndian.PutUint16(uuid[4:], timeMid) + binary.BigEndian.PutUint16(uuid[6:], timeHi) + binary.BigEndian.PutUint16(uuid[8:], seq) + copy(uuid[10:], nodeID[:]) + + return uuid, nil +} diff --git a/vendor/github.com/google/uuid/version4.go b/vendor/github.com/google/uuid/version4.go new file mode 100644 index 000000000..84af91c9f --- /dev/null +++ b/vendor/github.com/google/uuid/version4.go @@ -0,0 +1,38 @@ +// Copyright 2016 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import "io" + +// New creates a new random UUID or panics. New is equivalent to +// the expression +// +// uuid.Must(uuid.NewRandom()) +func New() UUID { + return Must(NewRandom()) +} + +// NewRandom returns a Random (Version 4) UUID. +// +// The strength of the UUIDs is based on the strength of the crypto/rand +// package. +// +// A note about uniqueness derived from the UUID Wikipedia entry: +// +// Randomly generated UUIDs have 122 random bits. One's annual risk of being +// hit by a meteorite is estimated to be one chance in 17 billion, that +// means the probability is about 0.00000000006 (6 × 10−11), +// equivalent to the odds of creating a few tens of trillions of UUIDs in a +// year and having one duplicate. +func NewRandom() (UUID, error) { + var uuid UUID + _, err := io.ReadFull(rander, uuid[:]) + if err != nil { + return Nil, err + } + uuid[6] = (uuid[6] & 0x0f) | 0x40 // Version 4 + uuid[8] = (uuid[8] & 0x3f) | 0x80 // Variant is 10 + return uuid, nil +} diff --git a/vendor/github.com/huandu/xstrings/.gitignore b/vendor/github.com/huandu/xstrings/.gitignore new file mode 100644 index 000000000..daf913b1b --- /dev/null +++ b/vendor/github.com/huandu/xstrings/.gitignore @@ -0,0 +1,24 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test +*.prof diff --git a/vendor/github.com/huandu/xstrings/.travis.yml b/vendor/github.com/huandu/xstrings/.travis.yml new file mode 100644 index 000000000..d6460be41 --- /dev/null +++ b/vendor/github.com/huandu/xstrings/.travis.yml @@ -0,0 +1,7 @@ +language: go +install: + - go get golang.org/x/tools/cmd/cover + - go get github.com/mattn/goveralls +script: + - go test -v -covermode=count -coverprofile=coverage.out + - 'if [ "$TRAVIS_PULL_REQUEST" = "false" ] && [ ! -z "$COVERALLS_TOKEN" ]; then $HOME/gopath/bin/goveralls -coverprofile=coverage.out -service=travis-ci -repotoken $COVERALLS_TOKEN; fi' diff --git a/vendor/github.com/huandu/xstrings/CONTRIBUTING.md b/vendor/github.com/huandu/xstrings/CONTRIBUTING.md new file mode 100644 index 000000000..d7b4b8d58 --- /dev/null +++ b/vendor/github.com/huandu/xstrings/CONTRIBUTING.md @@ -0,0 +1,23 @@ +# Contributing # + +Thanks for your contribution in advance. No matter what you will contribute to this project, pull request or bug report or feature discussion, it's always highly appreciated. + +## New API or feature ## + +I want to speak more about how to add new functions to this package. + +Package `xstring` is a collection of useful string functions which should be implemented in Go. It's a bit subject to say which function should be included and which should not. I set up following rules in order to make it clear and as objective as possible. + +* Rule 1: Only string algorithm, which takes string as input, can be included. +* Rule 2: If a function has been implemented in package `string`, it must not be included. +* Rule 3: If a function is not language neutral, it must not be included. +* Rule 4: If a function is a part of standard library in other languages, it can be included. +* Rule 5: If a function is quite useful in some famous framework or library, it can be included. + +New function must be discussed in project issues before submitting any code. If a pull request with new functions is sent without any ref issue, it will be rejected. + +## Pull request ## + +Pull request is always welcome. Just make sure you have run `go fmt` and all test cases passed before submit. + +If the pull request is to add a new API or feature, don't forget to update README.md and add new API in function list. diff --git a/vendor/github.com/huandu/xstrings/LICENSE b/vendor/github.com/huandu/xstrings/LICENSE new file mode 100644 index 000000000..270177259 --- /dev/null +++ b/vendor/github.com/huandu/xstrings/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 Huan Du + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/vendor/github.com/huandu/xstrings/README.md b/vendor/github.com/huandu/xstrings/README.md new file mode 100644 index 000000000..292bf2f39 --- /dev/null +++ b/vendor/github.com/huandu/xstrings/README.md @@ -0,0 +1,117 @@ +# xstrings # + +[![Build Status](https://travis-ci.org/huandu/xstrings.svg?branch=master)](https://travis-ci.org/huandu/xstrings) +[![GoDoc](https://godoc.org/github.com/huandu/xstrings?status.svg)](https://godoc.org/github.com/huandu/xstrings) +[![Go Report](https://goreportcard.com/badge/github.com/huandu/xstrings)](https://goreportcard.com/report/github.com/huandu/xstrings) +[![Coverage Status](https://coveralls.io/repos/github/huandu/xstrings/badge.svg?branch=master)](https://coveralls.io/github/huandu/xstrings?branch=master) + +Go package [xstrings](https://godoc.org/github.com/huandu/xstrings) is a collection of string functions, which are widely used in other languages but absent in Go package [strings](http://golang.org/pkg/strings). + +All functions are well tested and carefully tuned for performance. + +## Propose a new function ## + +Please review [contributing guideline](CONTRIBUTING.md) and [create new issue](https://github.com/huandu/xstrings/issues) to state why it should be included. + +## Install ## + +Use `go get` to install this library. + + go get github.com/huandu/xstrings + +## API document ## + +See [GoDoc](https://godoc.org/github.com/huandu/xstrings) for full document. + +## Function list ## + +Go functions have a unique naming style. One, who has experience in other language but new in Go, may have difficulties to find out right string function to use. + +Here is a list of functions in [strings](http://golang.org/pkg/strings) and [xstrings](https://godoc.org/github.com/huandu/xstrings) with enough extra information about how to map these functions to their friends in other languages. Hope this list could be helpful for fresh gophers. + +### Package `xstrings` functions ### + +*Keep this table sorted by Function in ascending order.* + +| Function | Friends | # | +| -------- | ------- | --- | +| [Center](https://godoc.org/github.com/huandu/xstrings#Center) | `str.center` in Python; `String#center` in Ruby | [#30](https://github.com/huandu/xstrings/issues/30) | +| [Count](https://godoc.org/github.com/huandu/xstrings#Count) | `String#count` in Ruby | [#16](https://github.com/huandu/xstrings/issues/16) | +| [Delete](https://godoc.org/github.com/huandu/xstrings#Delete) | `String#delete` in Ruby | [#17](https://github.com/huandu/xstrings/issues/17) | +| [ExpandTabs](https://godoc.org/github.com/huandu/xstrings#ExpandTabs) | `str.expandtabs` in Python | [#27](https://github.com/huandu/xstrings/issues/27) | +| [FirstRuneToLower](https://godoc.org/github.com/huandu/xstrings#FirstRuneToLower) | `lcfirst` in PHP or Perl | [#15](https://github.com/huandu/xstrings/issues/15) | +| [FirstRuneToUpper](https://godoc.org/github.com/huandu/xstrings#FirstRuneToUpper) | `String#capitalize` in Ruby; `ucfirst` in PHP or Perl | [#15](https://github.com/huandu/xstrings/issues/15) | +| [Insert](https://godoc.org/github.com/huandu/xstrings#Insert) | `String#insert` in Ruby | [#18](https://github.com/huandu/xstrings/issues/18) | +| [LastPartition](https://godoc.org/github.com/huandu/xstrings#LastPartition) | `str.rpartition` in Python; `String#rpartition` in Ruby | [#19](https://github.com/huandu/xstrings/issues/19) | +| [LeftJustify](https://godoc.org/github.com/huandu/xstrings#LeftJustify) | `str.ljust` in Python; `String#ljust` in Ruby | [#28](https://github.com/huandu/xstrings/issues/28) | +| [Len](https://godoc.org/github.com/huandu/xstrings#Len) | `mb_strlen` in PHP | [#23](https://github.com/huandu/xstrings/issues/23) | +| [Partition](https://godoc.org/github.com/huandu/xstrings#Partition) | `str.partition` in Python; `String#partition` in Ruby | [#10](https://github.com/huandu/xstrings/issues/10) | +| [Reverse](https://godoc.org/github.com/huandu/xstrings#Reverse) | `String#reverse` in Ruby; `strrev` in PHP; `reverse` in Perl | [#7](https://github.com/huandu/xstrings/issues/7) | +| [RightJustify](https://godoc.org/github.com/huandu/xstrings#RightJustify) | `str.rjust` in Python; `String#rjust` in Ruby | [#29](https://github.com/huandu/xstrings/issues/29) | +| [RuneWidth](https://godoc.org/github.com/huandu/xstrings#RuneWidth) | - | [#27](https://github.com/huandu/xstrings/issues/27) | +| [Scrub](https://godoc.org/github.com/huandu/xstrings#Scrub) | `String#scrub` in Ruby | [#20](https://github.com/huandu/xstrings/issues/20) | +| [Shuffle](https://godoc.org/github.com/huandu/xstrings#Shuffle) | `str_shuffle` in PHP | [#13](https://github.com/huandu/xstrings/issues/13) | +| [ShuffleSource](https://godoc.org/github.com/huandu/xstrings#ShuffleSource) | `str_shuffle` in PHP | [#13](https://github.com/huandu/xstrings/issues/13) | +| [Slice](https://godoc.org/github.com/huandu/xstrings#Slice) | `mb_substr` in PHP | [#9](https://github.com/huandu/xstrings/issues/9) | +| [Squeeze](https://godoc.org/github.com/huandu/xstrings#Squeeze) | `String#squeeze` in Ruby | [#11](https://github.com/huandu/xstrings/issues/11) | +| [Successor](https://godoc.org/github.com/huandu/xstrings#Successor) | `String#succ` or `String#next` in Ruby | [#22](https://github.com/huandu/xstrings/issues/22) | +| [SwapCase](https://godoc.org/github.com/huandu/xstrings#SwapCase) | `str.swapcase` in Python; `String#swapcase` in Ruby | [#12](https://github.com/huandu/xstrings/issues/12) | +| [ToCamelCase](https://godoc.org/github.com/huandu/xstrings#ToCamelCase) | `String#camelize` in RoR | [#1](https://github.com/huandu/xstrings/issues/1) | +| [ToKebab](https://godoc.org/github.com/huandu/xstrings#ToKebabCase) | - | [#41](https://github.com/huandu/xstrings/issues/41) | +| [ToSnakeCase](https://godoc.org/github.com/huandu/xstrings#ToSnakeCase) | `String#underscore` in RoR | [#1](https://github.com/huandu/xstrings/issues/1) | +| [Translate](https://godoc.org/github.com/huandu/xstrings#Translate) | `str.translate` in Python; `String#tr` in Ruby; `strtr` in PHP; `tr///` in Perl | [#21](https://github.com/huandu/xstrings/issues/21) | +| [Width](https://godoc.org/github.com/huandu/xstrings#Width) | `mb_strwidth` in PHP | [#26](https://github.com/huandu/xstrings/issues/26) | +| [WordCount](https://godoc.org/github.com/huandu/xstrings#WordCount) | `str_word_count` in PHP | [#14](https://github.com/huandu/xstrings/issues/14) | +| [WordSplit](https://godoc.org/github.com/huandu/xstrings#WordSplit) | - | [#14](https://github.com/huandu/xstrings/issues/14) | + +### Package `strings` functions ### + +*Keep this table sorted by Function in ascending order.* + +| Function | Friends | +| -------- | ------- | +| [Contains](http://golang.org/pkg/strings/#Contains) | `String#include?` in Ruby | +| [ContainsAny](http://golang.org/pkg/strings/#ContainsAny) | - | +| [ContainsRune](http://golang.org/pkg/strings/#ContainsRune) | - | +| [Count](http://golang.org/pkg/strings/#Count) | `str.count` in Python; `substr_count` in PHP | +| [EqualFold](http://golang.org/pkg/strings/#EqualFold) | `stricmp` in PHP; `String#casecmp` in Ruby | +| [Fields](http://golang.org/pkg/strings/#Fields) | `str.split` in Python; `split` in Perl; `String#split` in Ruby | +| [FieldsFunc](http://golang.org/pkg/strings/#FieldsFunc) | - | +| [HasPrefix](http://golang.org/pkg/strings/#HasPrefix) | `str.startswith` in Python; `String#start_with?` in Ruby | +| [HasSuffix](http://golang.org/pkg/strings/#HasSuffix) | `str.endswith` in Python; `String#end_with?` in Ruby | +| [Index](http://golang.org/pkg/strings/#Index) | `str.index` in Python; `String#index` in Ruby; `strpos` in PHP; `index` in Perl | +| [IndexAny](http://golang.org/pkg/strings/#IndexAny) | - | +| [IndexByte](http://golang.org/pkg/strings/#IndexByte) | - | +| [IndexFunc](http://golang.org/pkg/strings/#IndexFunc) | - | +| [IndexRune](http://golang.org/pkg/strings/#IndexRune) | - | +| [Join](http://golang.org/pkg/strings/#Join) | `str.join` in Python; `Array#join` in Ruby; `implode` in PHP; `join` in Perl | +| [LastIndex](http://golang.org/pkg/strings/#LastIndex) | `str.rindex` in Python; `String#rindex`; `strrpos` in PHP; `rindex` in Perl | +| [LastIndexAny](http://golang.org/pkg/strings/#LastIndexAny) | - | +| [LastIndexFunc](http://golang.org/pkg/strings/#LastIndexFunc) | - | +| [Map](http://golang.org/pkg/strings/#Map) | `String#each_codepoint` in Ruby | +| [Repeat](http://golang.org/pkg/strings/#Repeat) | operator `*` in Python and Ruby; `str_repeat` in PHP | +| [Replace](http://golang.org/pkg/strings/#Replace) | `str.replace` in Python; `String#sub` in Ruby; `str_replace` in PHP | +| [Split](http://golang.org/pkg/strings/#Split) | `str.split` in Python; `String#split` in Ruby; `explode` in PHP; `split` in Perl | +| [SplitAfter](http://golang.org/pkg/strings/#SplitAfter) | - | +| [SplitAfterN](http://golang.org/pkg/strings/#SplitAfterN) | - | +| [SplitN](http://golang.org/pkg/strings/#SplitN) | `str.split` in Python; `String#split` in Ruby; `explode` in PHP; `split` in Perl | +| [Title](http://golang.org/pkg/strings/#Title) | `str.title` in Python | +| [ToLower](http://golang.org/pkg/strings/#ToLower) | `str.lower` in Python; `String#downcase` in Ruby; `strtolower` in PHP; `lc` in Perl | +| [ToLowerSpecial](http://golang.org/pkg/strings/#ToLowerSpecial) | - | +| [ToTitle](http://golang.org/pkg/strings/#ToTitle) | - | +| [ToTitleSpecial](http://golang.org/pkg/strings/#ToTitleSpecial) | - | +| [ToUpper](http://golang.org/pkg/strings/#ToUpper) | `str.upper` in Python; `String#upcase` in Ruby; `strtoupper` in PHP; `uc` in Perl | +| [ToUpperSpecial](http://golang.org/pkg/strings/#ToUpperSpecial) | - | +| [Trim](http://golang.org/pkg/strings/#Trim) | `str.strip` in Python; `String#strip` in Ruby; `trim` in PHP | +| [TrimFunc](http://golang.org/pkg/strings/#TrimFunc) | - | +| [TrimLeft](http://golang.org/pkg/strings/#TrimLeft) | `str.lstrip` in Python; `String#lstrip` in Ruby; `ltrim` in PHP | +| [TrimLeftFunc](http://golang.org/pkg/strings/#TrimLeftFunc) | - | +| [TrimPrefix](http://golang.org/pkg/strings/#TrimPrefix) | - | +| [TrimRight](http://golang.org/pkg/strings/#TrimRight) | `str.rstrip` in Python; `String#rstrip` in Ruby; `rtrim` in PHP | +| [TrimRightFunc](http://golang.org/pkg/strings/#TrimRightFunc) | - | +| [TrimSpace](http://golang.org/pkg/strings/#TrimSpace) | `str.strip` in Python; `String#strip` in Ruby; `trim` in PHP | +| [TrimSuffix](http://golang.org/pkg/strings/#TrimSuffix) | `String#chomp` in Ruby; `chomp` in Perl | + +## License ## + +This library is licensed under MIT license. See LICENSE for details. diff --git a/vendor/github.com/huandu/xstrings/common.go b/vendor/github.com/huandu/xstrings/common.go new file mode 100644 index 000000000..f427cc84e --- /dev/null +++ b/vendor/github.com/huandu/xstrings/common.go @@ -0,0 +1,21 @@ +// Copyright 2015 Huan Du. All rights reserved. +// Licensed under the MIT license that can be found in the LICENSE file. + +package xstrings + +const bufferMaxInitGrowSize = 2048 + +// Lazy initialize a buffer. +func allocBuffer(orig, cur string) *stringBuilder { + output := &stringBuilder{} + maxSize := len(orig) * 4 + + // Avoid to reserve too much memory at once. + if maxSize > bufferMaxInitGrowSize { + maxSize = bufferMaxInitGrowSize + } + + output.Grow(maxSize) + output.WriteString(orig[:len(orig)-len(cur)]) + return output +} diff --git a/vendor/github.com/huandu/xstrings/convert.go b/vendor/github.com/huandu/xstrings/convert.go new file mode 100644 index 000000000..3d5a34950 --- /dev/null +++ b/vendor/github.com/huandu/xstrings/convert.go @@ -0,0 +1,590 @@ +// Copyright 2015 Huan Du. All rights reserved. +// Licensed under the MIT license that can be found in the LICENSE file. + +package xstrings + +import ( + "math/rand" + "unicode" + "unicode/utf8" +) + +// ToCamelCase is to convert words separated by space, underscore and hyphen to camel case. +// +// Some samples. +// "some_words" => "SomeWords" +// "http_server" => "HttpServer" +// "no_https" => "NoHttps" +// "_complex__case_" => "_Complex_Case_" +// "some words" => "SomeWords" +func ToCamelCase(str string) string { + if len(str) == 0 { + return "" + } + + buf := &stringBuilder{} + var r0, r1 rune + var size int + + // leading connector will appear in output. + for len(str) > 0 { + r0, size = utf8.DecodeRuneInString(str) + str = str[size:] + + if !isConnector(r0) { + r0 = unicode.ToUpper(r0) + break + } + + buf.WriteRune(r0) + } + + if len(str) == 0 { + // A special case for a string contains only 1 rune. + if size != 0 { + buf.WriteRune(r0) + } + + return buf.String() + } + + for len(str) > 0 { + r1 = r0 + r0, size = utf8.DecodeRuneInString(str) + str = str[size:] + + if isConnector(r0) && isConnector(r1) { + buf.WriteRune(r1) + continue + } + + if isConnector(r1) { + r0 = unicode.ToUpper(r0) + } else { + r0 = unicode.ToLower(r0) + buf.WriteRune(r1) + } + } + + buf.WriteRune(r0) + return buf.String() +} + +// ToSnakeCase can convert all upper case characters in a string to +// snake case format. +// +// Some samples. +// "FirstName" => "first_name" +// "HTTPServer" => "http_server" +// "NoHTTPS" => "no_https" +// "GO_PATH" => "go_path" +// "GO PATH" => "go_path" // space is converted to underscore. +// "GO-PATH" => "go_path" // hyphen is converted to underscore. +// "http2xx" => "http_2xx" // insert an underscore before a number and after an alphabet. +// "HTTP20xOK" => "http_20x_ok" +// "Duration2m3s" => "duration_2m3s" +// "Bld4Floor3rd" => "bld4_floor_3rd" +func ToSnakeCase(str string) string { + return camelCaseToLowerCase(str, '_') +} + +// ToKebabCase can convert all upper case characters in a string to +// kebab case format. +// +// Some samples. +// "FirstName" => "first-name" +// "HTTPServer" => "http-server" +// "NoHTTPS" => "no-https" +// "GO_PATH" => "go-path" +// "GO PATH" => "go-path" // space is converted to '-'. +// "GO-PATH" => "go-path" // hyphen is converted to '-'. +// "http2xx" => "http-2xx" // insert an underscore before a number and after an alphabet. +// "HTTP20xOK" => "http-20x-ok" +// "Duration2m3s" => "duration-2m3s" +// "Bld4Floor3rd" => "bld4-floor-3rd" +func ToKebabCase(str string) string { + return camelCaseToLowerCase(str, '-') +} + +func camelCaseToLowerCase(str string, connector rune) string { + if len(str) == 0 { + return "" + } + + buf := &stringBuilder{} + wt, word, remaining := nextWord(str) + + for len(remaining) > 0 { + if wt != connectorWord { + toLower(buf, wt, word, connector) + } + + prev := wt + last := word + wt, word, remaining = nextWord(remaining) + + switch prev { + case numberWord: + for wt == alphabetWord || wt == numberWord { + toLower(buf, wt, word, connector) + wt, word, remaining = nextWord(remaining) + } + + if wt != invalidWord && wt != punctWord { + buf.WriteRune(connector) + } + + case connectorWord: + toLower(buf, prev, last, connector) + + case punctWord: + // nothing. + + default: + if wt != numberWord { + if wt != connectorWord && wt != punctWord { + buf.WriteRune(connector) + } + + break + } + + if len(remaining) == 0 { + break + } + + last := word + wt, word, remaining = nextWord(remaining) + + // consider number as a part of previous word. + // e.g. "Bld4Floor" => "bld4_floor" + if wt != alphabetWord { + toLower(buf, numberWord, last, connector) + + if wt != connectorWord && wt != punctWord { + buf.WriteRune(connector) + } + + break + } + + // if there are some lower case letters following a number, + // add connector before the number. + // e.g. "HTTP2xx" => "http_2xx" + buf.WriteRune(connector) + toLower(buf, numberWord, last, connector) + + for wt == alphabetWord || wt == numberWord { + toLower(buf, wt, word, connector) + wt, word, remaining = nextWord(remaining) + } + + if wt != invalidWord && wt != connectorWord && wt != punctWord { + buf.WriteRune(connector) + } + } + } + + toLower(buf, wt, word, connector) + return buf.String() +} + +func isConnector(r rune) bool { + return r == '-' || r == '_' || unicode.IsSpace(r) +} + +type wordType int + +const ( + invalidWord wordType = iota + numberWord + upperCaseWord + alphabetWord + connectorWord + punctWord + otherWord +) + +func nextWord(str string) (wt wordType, word, remaining string) { + if len(str) == 0 { + return + } + + var offset int + remaining = str + r, size := nextValidRune(remaining, utf8.RuneError) + offset += size + + if r == utf8.RuneError { + wt = invalidWord + word = str[:offset] + remaining = str[offset:] + return + } + + switch { + case isConnector(r): + wt = connectorWord + remaining = remaining[size:] + + for len(remaining) > 0 { + r, size = nextValidRune(remaining, r) + + if !isConnector(r) { + break + } + + offset += size + remaining = remaining[size:] + } + + case unicode.IsPunct(r): + wt = punctWord + remaining = remaining[size:] + + for len(remaining) > 0 { + r, size = nextValidRune(remaining, r) + + if !unicode.IsPunct(r) { + break + } + + offset += size + remaining = remaining[size:] + } + + case unicode.IsUpper(r): + wt = upperCaseWord + remaining = remaining[size:] + + if len(remaining) == 0 { + break + } + + r, size = nextValidRune(remaining, r) + + switch { + case unicode.IsUpper(r): + prevSize := size + offset += size + remaining = remaining[size:] + + for len(remaining) > 0 { + r, size = nextValidRune(remaining, r) + + if !unicode.IsUpper(r) { + break + } + + prevSize = size + offset += size + remaining = remaining[size:] + } + + // it's a bit complex when dealing with a case like "HTTPStatus". + // it's expected to be splitted into "HTTP" and "Status". + // Therefore "S" should be in remaining instead of word. + if len(remaining) > 0 && isAlphabet(r) { + offset -= prevSize + remaining = str[offset:] + } + + case isAlphabet(r): + offset += size + remaining = remaining[size:] + + for len(remaining) > 0 { + r, size = nextValidRune(remaining, r) + + if !isAlphabet(r) || unicode.IsUpper(r) { + break + } + + offset += size + remaining = remaining[size:] + } + } + + case isAlphabet(r): + wt = alphabetWord + remaining = remaining[size:] + + for len(remaining) > 0 { + r, size = nextValidRune(remaining, r) + + if !isAlphabet(r) || unicode.IsUpper(r) { + break + } + + offset += size + remaining = remaining[size:] + } + + case unicode.IsNumber(r): + wt = numberWord + remaining = remaining[size:] + + for len(remaining) > 0 { + r, size = nextValidRune(remaining, r) + + if !unicode.IsNumber(r) { + break + } + + offset += size + remaining = remaining[size:] + } + + default: + wt = otherWord + remaining = remaining[size:] + + for len(remaining) > 0 { + r, size = nextValidRune(remaining, r) + + if size == 0 || isConnector(r) || isAlphabet(r) || unicode.IsNumber(r) || unicode.IsPunct(r) { + break + } + + offset += size + remaining = remaining[size:] + } + } + + word = str[:offset] + return +} + +func nextValidRune(str string, prev rune) (r rune, size int) { + var sz int + + for len(str) > 0 { + r, sz = utf8.DecodeRuneInString(str) + size += sz + + if r != utf8.RuneError { + return + } + + str = str[sz:] + } + + r = prev + return +} + +func toLower(buf *stringBuilder, wt wordType, str string, connector rune) { + buf.Grow(buf.Len() + len(str)) + + if wt != upperCaseWord && wt != connectorWord { + buf.WriteString(str) + return + } + + for len(str) > 0 { + r, size := utf8.DecodeRuneInString(str) + str = str[size:] + + if isConnector(r) { + buf.WriteRune(connector) + } else if unicode.IsUpper(r) { + buf.WriteRune(unicode.ToLower(r)) + } else { + buf.WriteRune(r) + } + } +} + +// SwapCase will swap characters case from upper to lower or lower to upper. +func SwapCase(str string) string { + var r rune + var size int + + buf := &stringBuilder{} + + for len(str) > 0 { + r, size = utf8.DecodeRuneInString(str) + + switch { + case unicode.IsUpper(r): + buf.WriteRune(unicode.ToLower(r)) + + case unicode.IsLower(r): + buf.WriteRune(unicode.ToUpper(r)) + + default: + buf.WriteRune(r) + } + + str = str[size:] + } + + return buf.String() +} + +// FirstRuneToUpper converts first rune to upper case if necessary. +func FirstRuneToUpper(str string) string { + if str == "" { + return str + } + + r, size := utf8.DecodeRuneInString(str) + + if !unicode.IsLower(r) { + return str + } + + buf := &stringBuilder{} + buf.WriteRune(unicode.ToUpper(r)) + buf.WriteString(str[size:]) + return buf.String() +} + +// FirstRuneToLower converts first rune to lower case if necessary. +func FirstRuneToLower(str string) string { + if str == "" { + return str + } + + r, size := utf8.DecodeRuneInString(str) + + if !unicode.IsUpper(r) { + return str + } + + buf := &stringBuilder{} + buf.WriteRune(unicode.ToLower(r)) + buf.WriteString(str[size:]) + return buf.String() +} + +// Shuffle randomizes runes in a string and returns the result. +// It uses default random source in `math/rand`. +func Shuffle(str string) string { + if str == "" { + return str + } + + runes := []rune(str) + index := 0 + + for i := len(runes) - 1; i > 0; i-- { + index = rand.Intn(i + 1) + + if i != index { + runes[i], runes[index] = runes[index], runes[i] + } + } + + return string(runes) +} + +// ShuffleSource randomizes runes in a string with given random source. +func ShuffleSource(str string, src rand.Source) string { + if str == "" { + return str + } + + runes := []rune(str) + index := 0 + r := rand.New(src) + + for i := len(runes) - 1; i > 0; i-- { + index = r.Intn(i + 1) + + if i != index { + runes[i], runes[index] = runes[index], runes[i] + } + } + + return string(runes) +} + +// Successor returns the successor to string. +// +// If there is one alphanumeric rune is found in string, increase the rune by 1. +// If increment generates a "carry", the rune to the left of it is incremented. +// This process repeats until there is no carry, adding an additional rune if necessary. +// +// If there is no alphanumeric rune, the rightmost rune will be increased by 1 +// regardless whether the result is a valid rune or not. +// +// Only following characters are alphanumeric. +// * a - z +// * A - Z +// * 0 - 9 +// +// Samples (borrowed from ruby's String#succ document): +// "abcd" => "abce" +// "THX1138" => "THX1139" +// "<>" => "<>" +// "1999zzz" => "2000aaa" +// "ZZZ9999" => "AAAA0000" +// "***" => "**+" +func Successor(str string) string { + if str == "" { + return str + } + + var r rune + var i int + carry := ' ' + runes := []rune(str) + l := len(runes) + lastAlphanumeric := l + + for i = l - 1; i >= 0; i-- { + r = runes[i] + + if ('a' <= r && r <= 'y') || + ('A' <= r && r <= 'Y') || + ('0' <= r && r <= '8') { + runes[i]++ + carry = ' ' + lastAlphanumeric = i + break + } + + switch r { + case 'z': + runes[i] = 'a' + carry = 'a' + lastAlphanumeric = i + + case 'Z': + runes[i] = 'A' + carry = 'A' + lastAlphanumeric = i + + case '9': + runes[i] = '0' + carry = '0' + lastAlphanumeric = i + } + } + + // Needs to add one character for carry. + if i < 0 && carry != ' ' { + buf := &stringBuilder{} + buf.Grow(l + 4) // Reserve enough space for write. + + if lastAlphanumeric != 0 { + buf.WriteString(str[:lastAlphanumeric]) + } + + buf.WriteRune(carry) + + for _, r = range runes[lastAlphanumeric:] { + buf.WriteRune(r) + } + + return buf.String() + } + + // No alphanumeric character. Simply increase last rune's value. + if lastAlphanumeric == l { + runes[l-1]++ + } + + return string(runes) +} diff --git a/vendor/github.com/huandu/xstrings/count.go b/vendor/github.com/huandu/xstrings/count.go new file mode 100644 index 000000000..f96e38703 --- /dev/null +++ b/vendor/github.com/huandu/xstrings/count.go @@ -0,0 +1,120 @@ +// Copyright 2015 Huan Du. All rights reserved. +// Licensed under the MIT license that can be found in the LICENSE file. + +package xstrings + +import ( + "unicode" + "unicode/utf8" +) + +// Len returns str's utf8 rune length. +func Len(str string) int { + return utf8.RuneCountInString(str) +} + +// WordCount returns number of words in a string. +// +// Word is defined as a locale dependent string containing alphabetic characters, +// which may also contain but not start with `'` and `-` characters. +func WordCount(str string) int { + var r rune + var size, n int + + inWord := false + + for len(str) > 0 { + r, size = utf8.DecodeRuneInString(str) + + switch { + case isAlphabet(r): + if !inWord { + inWord = true + n++ + } + + case inWord && (r == '\'' || r == '-'): + // Still in word. + + default: + inWord = false + } + + str = str[size:] + } + + return n +} + +const minCJKCharacter = '\u3400' + +// Checks r is a letter but not CJK character. +func isAlphabet(r rune) bool { + if !unicode.IsLetter(r) { + return false + } + + switch { + // Quick check for non-CJK character. + case r < minCJKCharacter: + return true + + // Common CJK characters. + case r >= '\u4E00' && r <= '\u9FCC': + return false + + // Rare CJK characters. + case r >= '\u3400' && r <= '\u4D85': + return false + + // Rare and historic CJK characters. + case r >= '\U00020000' && r <= '\U0002B81D': + return false + } + + return true +} + +// Width returns string width in monotype font. +// Multi-byte characters are usually twice the width of single byte characters. +// +// Algorithm comes from `mb_strwidth` in PHP. +// http://php.net/manual/en/function.mb-strwidth.php +func Width(str string) int { + var r rune + var size, n int + + for len(str) > 0 { + r, size = utf8.DecodeRuneInString(str) + n += RuneWidth(r) + str = str[size:] + } + + return n +} + +// RuneWidth returns character width in monotype font. +// Multi-byte characters are usually twice the width of single byte characters. +// +// Algorithm comes from `mb_strwidth` in PHP. +// http://php.net/manual/en/function.mb-strwidth.php +func RuneWidth(r rune) int { + switch { + case r == utf8.RuneError || r < '\x20': + return 0 + + case '\x20' <= r && r < '\u2000': + return 1 + + case '\u2000' <= r && r < '\uFF61': + return 2 + + case '\uFF61' <= r && r < '\uFFA0': + return 1 + + case '\uFFA0' <= r: + return 2 + } + + return 0 +} diff --git a/vendor/github.com/huandu/xstrings/doc.go b/vendor/github.com/huandu/xstrings/doc.go new file mode 100644 index 000000000..1a6ef069f --- /dev/null +++ b/vendor/github.com/huandu/xstrings/doc.go @@ -0,0 +1,8 @@ +// Copyright 2015 Huan Du. All rights reserved. +// Licensed under the MIT license that can be found in the LICENSE file. + +// Package xstrings is to provide string algorithms which are useful but not included in `strings` package. +// See project home page for details. https://github.com/huandu/xstrings +// +// Package xstrings assumes all strings are encoded in utf8. +package xstrings diff --git a/vendor/github.com/huandu/xstrings/format.go b/vendor/github.com/huandu/xstrings/format.go new file mode 100644 index 000000000..8cd76c525 --- /dev/null +++ b/vendor/github.com/huandu/xstrings/format.go @@ -0,0 +1,169 @@ +// Copyright 2015 Huan Du. All rights reserved. +// Licensed under the MIT license that can be found in the LICENSE file. + +package xstrings + +import ( + "unicode/utf8" +) + +// ExpandTabs can expand tabs ('\t') rune in str to one or more spaces dpending on +// current column and tabSize. +// The column number is reset to zero after each newline ('\n') occurring in the str. +// +// ExpandTabs uses RuneWidth to decide rune's width. +// For example, CJK characters will be treated as two characters. +// +// If tabSize <= 0, ExpandTabs panics with error. +// +// Samples: +// ExpandTabs("a\tbc\tdef\tghij\tk", 4) => "a bc def ghij k" +// ExpandTabs("abcdefg\thij\nk\tl", 4) => "abcdefg hij\nk l" +// ExpandTabs("z中\t文\tw", 4) => "z中 文 w" +func ExpandTabs(str string, tabSize int) string { + if tabSize <= 0 { + panic("tab size must be positive") + } + + var r rune + var i, size, column, expand int + var output *stringBuilder + + orig := str + + for len(str) > 0 { + r, size = utf8.DecodeRuneInString(str) + + if r == '\t' { + expand = tabSize - column%tabSize + + if output == nil { + output = allocBuffer(orig, str) + } + + for i = 0; i < expand; i++ { + output.WriteRune(' ') + } + + column += expand + } else { + if r == '\n' { + column = 0 + } else { + column += RuneWidth(r) + } + + if output != nil { + output.WriteRune(r) + } + } + + str = str[size:] + } + + if output == nil { + return orig + } + + return output.String() +} + +// LeftJustify returns a string with pad string at right side if str's rune length is smaller than length. +// If str's rune length is larger than length, str itself will be returned. +// +// If pad is an empty string, str will be returned. +// +// Samples: +// LeftJustify("hello", 4, " ") => "hello" +// LeftJustify("hello", 10, " ") => "hello " +// LeftJustify("hello", 10, "123") => "hello12312" +func LeftJustify(str string, length int, pad string) string { + l := Len(str) + + if l >= length || pad == "" { + return str + } + + remains := length - l + padLen := Len(pad) + + output := &stringBuilder{} + output.Grow(len(str) + (remains/padLen+1)*len(pad)) + output.WriteString(str) + writePadString(output, pad, padLen, remains) + return output.String() +} + +// RightJustify returns a string with pad string at left side if str's rune length is smaller than length. +// If str's rune length is larger than length, str itself will be returned. +// +// If pad is an empty string, str will be returned. +// +// Samples: +// RightJustify("hello", 4, " ") => "hello" +// RightJustify("hello", 10, " ") => " hello" +// RightJustify("hello", 10, "123") => "12312hello" +func RightJustify(str string, length int, pad string) string { + l := Len(str) + + if l >= length || pad == "" { + return str + } + + remains := length - l + padLen := Len(pad) + + output := &stringBuilder{} + output.Grow(len(str) + (remains/padLen+1)*len(pad)) + writePadString(output, pad, padLen, remains) + output.WriteString(str) + return output.String() +} + +// Center returns a string with pad string at both side if str's rune length is smaller than length. +// If str's rune length is larger than length, str itself will be returned. +// +// If pad is an empty string, str will be returned. +// +// Samples: +// Center("hello", 4, " ") => "hello" +// Center("hello", 10, " ") => " hello " +// Center("hello", 10, "123") => "12hello123" +func Center(str string, length int, pad string) string { + l := Len(str) + + if l >= length || pad == "" { + return str + } + + remains := length - l + padLen := Len(pad) + + output := &stringBuilder{} + output.Grow(len(str) + (remains/padLen+1)*len(pad)) + writePadString(output, pad, padLen, remains/2) + output.WriteString(str) + writePadString(output, pad, padLen, (remains+1)/2) + return output.String() +} + +func writePadString(output *stringBuilder, pad string, padLen, remains int) { + var r rune + var size int + + repeats := remains / padLen + + for i := 0; i < repeats; i++ { + output.WriteString(pad) + } + + remains = remains % padLen + + if remains != 0 { + for i := 0; i < remains; i++ { + r, size = utf8.DecodeRuneInString(pad) + output.WriteRune(r) + pad = pad[size:] + } + } +} diff --git a/vendor/github.com/huandu/xstrings/go.mod b/vendor/github.com/huandu/xstrings/go.mod new file mode 100644 index 000000000..3982c204c --- /dev/null +++ b/vendor/github.com/huandu/xstrings/go.mod @@ -0,0 +1,3 @@ +module github.com/huandu/xstrings + +go 1.12 diff --git a/vendor/github.com/huandu/xstrings/manipulate.go b/vendor/github.com/huandu/xstrings/manipulate.go new file mode 100644 index 000000000..64075f9bb --- /dev/null +++ b/vendor/github.com/huandu/xstrings/manipulate.go @@ -0,0 +1,216 @@ +// Copyright 2015 Huan Du. All rights reserved. +// Licensed under the MIT license that can be found in the LICENSE file. + +package xstrings + +import ( + "strings" + "unicode/utf8" +) + +// Reverse a utf8 encoded string. +func Reverse(str string) string { + var size int + + tail := len(str) + buf := make([]byte, tail) + s := buf + + for len(str) > 0 { + _, size = utf8.DecodeRuneInString(str) + tail -= size + s = append(s[:tail], []byte(str[:size])...) + str = str[size:] + } + + return string(buf) +} + +// Slice a string by rune. +// +// Start must satisfy 0 <= start <= rune length. +// +// End can be positive, zero or negative. +// If end >= 0, start and end must satisfy start <= end <= rune length. +// If end < 0, it means slice to the end of string. +// +// Otherwise, Slice will panic as out of range. +func Slice(str string, start, end int) string { + var size, startPos, endPos int + + origin := str + + if start < 0 || end > len(str) || (end >= 0 && start > end) { + panic("out of range") + } + + if end >= 0 { + end -= start + } + + for start > 0 && len(str) > 0 { + _, size = utf8.DecodeRuneInString(str) + start-- + startPos += size + str = str[size:] + } + + if end < 0 { + return origin[startPos:] + } + + endPos = startPos + + for end > 0 && len(str) > 0 { + _, size = utf8.DecodeRuneInString(str) + end-- + endPos += size + str = str[size:] + } + + if len(str) == 0 && (start > 0 || end > 0) { + panic("out of range") + } + + return origin[startPos:endPos] +} + +// Partition splits a string by sep into three parts. +// The return value is a slice of strings with head, match and tail. +// +// If str contains sep, for example "hello" and "l", Partition returns +// "he", "l", "lo" +// +// If str doesn't contain sep, for example "hello" and "x", Partition returns +// "hello", "", "" +func Partition(str, sep string) (head, match, tail string) { + index := strings.Index(str, sep) + + if index == -1 { + head = str + return + } + + head = str[:index] + match = str[index : index+len(sep)] + tail = str[index+len(sep):] + return +} + +// LastPartition splits a string by last instance of sep into three parts. +// The return value is a slice of strings with head, match and tail. +// +// If str contains sep, for example "hello" and "l", LastPartition returns +// "hel", "l", "o" +// +// If str doesn't contain sep, for example "hello" and "x", LastPartition returns +// "", "", "hello" +func LastPartition(str, sep string) (head, match, tail string) { + index := strings.LastIndex(str, sep) + + if index == -1 { + tail = str + return + } + + head = str[:index] + match = str[index : index+len(sep)] + tail = str[index+len(sep):] + return +} + +// Insert src into dst at given rune index. +// Index is counted by runes instead of bytes. +// +// If index is out of range of dst, panic with out of range. +func Insert(dst, src string, index int) string { + return Slice(dst, 0, index) + src + Slice(dst, index, -1) +} + +// Scrub scrubs invalid utf8 bytes with repl string. +// Adjacent invalid bytes are replaced only once. +func Scrub(str, repl string) string { + var buf *stringBuilder + var r rune + var size, pos int + var hasError bool + + origin := str + + for len(str) > 0 { + r, size = utf8.DecodeRuneInString(str) + + if r == utf8.RuneError { + if !hasError { + if buf == nil { + buf = &stringBuilder{} + } + + buf.WriteString(origin[:pos]) + hasError = true + } + } else if hasError { + hasError = false + buf.WriteString(repl) + + origin = origin[pos:] + pos = 0 + } + + pos += size + str = str[size:] + } + + if buf != nil { + buf.WriteString(origin) + return buf.String() + } + + // No invalid byte. + return origin +} + +// WordSplit splits a string into words. Returns a slice of words. +// If there is no word in a string, return nil. +// +// Word is defined as a locale dependent string containing alphabetic characters, +// which may also contain but not start with `'` and `-` characters. +func WordSplit(str string) []string { + var word string + var words []string + var r rune + var size, pos int + + inWord := false + + for len(str) > 0 { + r, size = utf8.DecodeRuneInString(str) + + switch { + case isAlphabet(r): + if !inWord { + inWord = true + word = str + pos = 0 + } + + case inWord && (r == '\'' || r == '-'): + // Still in word. + + default: + if inWord { + inWord = false + words = append(words, word[:pos]) + } + } + + pos += size + str = str[size:] + } + + if inWord { + words = append(words, word[:pos]) + } + + return words +} diff --git a/vendor/github.com/huandu/xstrings/stringbuilder.go b/vendor/github.com/huandu/xstrings/stringbuilder.go new file mode 100644 index 000000000..bb0919d32 --- /dev/null +++ b/vendor/github.com/huandu/xstrings/stringbuilder.go @@ -0,0 +1,7 @@ +//+build go1.10 + +package xstrings + +import "strings" + +type stringBuilder = strings.Builder diff --git a/vendor/github.com/huandu/xstrings/stringbuilder_go110.go b/vendor/github.com/huandu/xstrings/stringbuilder_go110.go new file mode 100644 index 000000000..dac389d13 --- /dev/null +++ b/vendor/github.com/huandu/xstrings/stringbuilder_go110.go @@ -0,0 +1,9 @@ +//+build !go1.10 + +package xstrings + +import "bytes" + +type stringBuilder struct { + bytes.Buffer +} diff --git a/vendor/github.com/huandu/xstrings/translate.go b/vendor/github.com/huandu/xstrings/translate.go new file mode 100644 index 000000000..42e694fb1 --- /dev/null +++ b/vendor/github.com/huandu/xstrings/translate.go @@ -0,0 +1,546 @@ +// Copyright 2015 Huan Du. All rights reserved. +// Licensed under the MIT license that can be found in the LICENSE file. + +package xstrings + +import ( + "unicode" + "unicode/utf8" +) + +type runeRangeMap struct { + FromLo rune // Lower bound of range map. + FromHi rune // An inclusive higher bound of range map. + ToLo rune + ToHi rune +} + +type runeDict struct { + Dict [unicode.MaxASCII + 1]rune +} + +type runeMap map[rune]rune + +// Translator can translate string with pre-compiled from and to patterns. +// If a from/to pattern pair needs to be used more than once, it's recommended +// to create a Translator and reuse it. +type Translator struct { + quickDict *runeDict // A quick dictionary to look up rune by index. Only available for latin runes. + runeMap runeMap // Rune map for translation. + ranges []*runeRangeMap // Ranges of runes. + mappedRune rune // If mappedRune >= 0, all matched runes are translated to the mappedRune. + reverted bool // If to pattern is empty, all matched characters will be deleted. + hasPattern bool +} + +// NewTranslator creates new Translator through a from/to pattern pair. +func NewTranslator(from, to string) *Translator { + tr := &Translator{} + + if from == "" { + return tr + } + + reverted := from[0] == '^' + deletion := len(to) == 0 + + if reverted { + from = from[1:] + } + + var fromStart, fromEnd, fromRangeStep rune + var toStart, toEnd, toRangeStep rune + var fromRangeSize, toRangeSize rune + var singleRunes []rune + + // Update the to rune range. + updateRange := func() { + // No more rune to read in the to rune pattern. + if toEnd == utf8.RuneError { + return + } + + if toRangeStep == 0 { + to, toStart, toEnd, toRangeStep = nextRuneRange(to, toEnd) + return + } + + // Current range is not empty. Consume 1 rune from start. + if toStart != toEnd { + toStart += toRangeStep + return + } + + // No more rune. Repeat the last rune. + if to == "" { + toEnd = utf8.RuneError + return + } + + // Both start and end are used. Read two more runes from the to pattern. + to, toStart, toEnd, toRangeStep = nextRuneRange(to, utf8.RuneError) + } + + if deletion { + toStart = utf8.RuneError + toEnd = utf8.RuneError + } else { + // If from pattern is reverted, only the last rune in the to pattern will be used. + if reverted { + var size int + + for len(to) > 0 { + toStart, size = utf8.DecodeRuneInString(to) + to = to[size:] + } + + toEnd = utf8.RuneError + } else { + to, toStart, toEnd, toRangeStep = nextRuneRange(to, utf8.RuneError) + } + } + + fromEnd = utf8.RuneError + + for len(from) > 0 { + from, fromStart, fromEnd, fromRangeStep = nextRuneRange(from, fromEnd) + + // fromStart is a single character. Just map it with a rune in the to pattern. + if fromRangeStep == 0 { + singleRunes = tr.addRune(fromStart, toStart, singleRunes) + updateRange() + continue + } + + for toEnd != utf8.RuneError && fromStart != fromEnd { + // If mapped rune is a single character instead of a range, simply shift first + // rune in the range. + if toRangeStep == 0 { + singleRunes = tr.addRune(fromStart, toStart, singleRunes) + updateRange() + fromStart += fromRangeStep + continue + } + + fromRangeSize = (fromEnd - fromStart) * fromRangeStep + toRangeSize = (toEnd - toStart) * toRangeStep + + // Not enough runes in the to pattern. Need to read more. + if fromRangeSize > toRangeSize { + fromStart, toStart = tr.addRuneRange(fromStart, fromStart+toRangeSize*fromRangeStep, toStart, toEnd, singleRunes) + fromStart += fromRangeStep + updateRange() + + // Edge case: If fromRangeSize == toRangeSize + 1, the last fromStart value needs be considered + // as a single rune. + if fromStart == fromEnd { + singleRunes = tr.addRune(fromStart, toStart, singleRunes) + updateRange() + } + + continue + } + + fromStart, toStart = tr.addRuneRange(fromStart, fromEnd, toStart, toStart+fromRangeSize*toRangeStep, singleRunes) + updateRange() + break + } + + if fromStart == fromEnd { + fromEnd = utf8.RuneError + continue + } + + _, toStart = tr.addRuneRange(fromStart, fromEnd, toStart, toStart, singleRunes) + fromEnd = utf8.RuneError + } + + if fromEnd != utf8.RuneError { + tr.addRune(fromEnd, toStart, singleRunes) + } + + tr.reverted = reverted + tr.mappedRune = -1 + tr.hasPattern = true + + // Translate RuneError only if in deletion or reverted mode. + if deletion || reverted { + tr.mappedRune = toStart + } + + return tr +} + +func (tr *Translator) addRune(from, to rune, singleRunes []rune) []rune { + if from <= unicode.MaxASCII { + if tr.quickDict == nil { + tr.quickDict = &runeDict{} + } + + tr.quickDict.Dict[from] = to + } else { + if tr.runeMap == nil { + tr.runeMap = make(runeMap) + } + + tr.runeMap[from] = to + } + + singleRunes = append(singleRunes, from) + return singleRunes +} + +func (tr *Translator) addRuneRange(fromLo, fromHi, toLo, toHi rune, singleRunes []rune) (rune, rune) { + var r rune + var rrm *runeRangeMap + + if fromLo < fromHi { + rrm = &runeRangeMap{ + FromLo: fromLo, + FromHi: fromHi, + ToLo: toLo, + ToHi: toHi, + } + } else { + rrm = &runeRangeMap{ + FromLo: fromHi, + FromHi: fromLo, + ToLo: toHi, + ToHi: toLo, + } + } + + // If there is any single rune conflicts with this rune range, clear single rune record. + for _, r = range singleRunes { + if rrm.FromLo <= r && r <= rrm.FromHi { + if r <= unicode.MaxASCII { + tr.quickDict.Dict[r] = 0 + } else { + delete(tr.runeMap, r) + } + } + } + + tr.ranges = append(tr.ranges, rrm) + return fromHi, toHi +} + +func nextRuneRange(str string, last rune) (remaining string, start, end rune, rangeStep rune) { + var r rune + var size int + + remaining = str + escaping := false + isRange := false + + for len(remaining) > 0 { + r, size = utf8.DecodeRuneInString(remaining) + remaining = remaining[size:] + + // Parse special characters. + if !escaping { + if r == '\\' { + escaping = true + continue + } + + if r == '-' { + // Ignore slash at beginning of string. + if last == utf8.RuneError { + continue + } + + start = last + isRange = true + continue + } + } + + escaping = false + + if last != utf8.RuneError { + // This is a range which start and end are the same. + // Considier it as a normal character. + if isRange && last == r { + isRange = false + continue + } + + start = last + end = r + + if isRange { + if start < end { + rangeStep = 1 + } else { + rangeStep = -1 + } + } + + return + } + + last = r + } + + start = last + end = utf8.RuneError + return +} + +// Translate str with a from/to pattern pair. +// +// See comment in Translate function for usage and samples. +func (tr *Translator) Translate(str string) string { + if !tr.hasPattern || str == "" { + return str + } + + var r rune + var size int + var needTr bool + + orig := str + + var output *stringBuilder + + for len(str) > 0 { + r, size = utf8.DecodeRuneInString(str) + r, needTr = tr.TranslateRune(r) + + if needTr && output == nil { + output = allocBuffer(orig, str) + } + + if r != utf8.RuneError && output != nil { + output.WriteRune(r) + } + + str = str[size:] + } + + // No character is translated. + if output == nil { + return orig + } + + return output.String() +} + +// TranslateRune return translated rune and true if r matches the from pattern. +// If r doesn't match the pattern, original r is returned and translated is false. +func (tr *Translator) TranslateRune(r rune) (result rune, translated bool) { + switch { + case tr.quickDict != nil: + if r <= unicode.MaxASCII { + result = tr.quickDict.Dict[r] + + if result != 0 { + translated = true + + if tr.mappedRune >= 0 { + result = tr.mappedRune + } + + break + } + } + + fallthrough + + case tr.runeMap != nil: + var ok bool + + if result, ok = tr.runeMap[r]; ok { + translated = true + + if tr.mappedRune >= 0 { + result = tr.mappedRune + } + + break + } + + fallthrough + + default: + var rrm *runeRangeMap + ranges := tr.ranges + + for i := len(ranges) - 1; i >= 0; i-- { + rrm = ranges[i] + + if rrm.FromLo <= r && r <= rrm.FromHi { + translated = true + + if tr.mappedRune >= 0 { + result = tr.mappedRune + break + } + + if rrm.ToLo < rrm.ToHi { + result = rrm.ToLo + r - rrm.FromLo + } else if rrm.ToLo > rrm.ToHi { + // ToHi can be smaller than ToLo if range is from higher to lower. + result = rrm.ToLo - r + rrm.FromLo + } else { + result = rrm.ToLo + } + + break + } + } + } + + if tr.reverted { + if !translated { + result = tr.mappedRune + } + + translated = !translated + } + + if !translated { + result = r + } + + return +} + +// HasPattern returns true if Translator has one pattern at least. +func (tr *Translator) HasPattern() bool { + return tr.hasPattern +} + +// Translate str with the characters defined in from replaced by characters defined in to. +// +// From and to are patterns representing a set of characters. Pattern is defined as following. +// +// * Special characters +// * '-' means a range of runes, e.g. +// * "a-z" means all characters from 'a' to 'z' inclusive; +// * "z-a" means all characters from 'z' to 'a' inclusive. +// * '^' as first character means a set of all runes excepted listed, e.g. +// * "^a-z" means all characters except 'a' to 'z' inclusive. +// * '\' escapes special characters. +// * Normal character represents itself, e.g. "abc" is a set including 'a', 'b' and 'c'. +// +// Translate will try to find a 1:1 mapping from from to to. +// If to is smaller than from, last rune in to will be used to map "out of range" characters in from. +// +// Note that '^' only works in the from pattern. It will be considered as a normal character in the to pattern. +// +// If the to pattern is an empty string, Translate works exactly the same as Delete. +// +// Samples: +// Translate("hello", "aeiou", "12345") => "h2ll4" +// Translate("hello", "a-z", "A-Z") => "HELLO" +// Translate("hello", "z-a", "a-z") => "svool" +// Translate("hello", "aeiou", "*") => "h*ll*" +// Translate("hello", "^l", "*") => "**ll*" +// Translate("hello ^ world", `\^lo`, "*") => "he*** * w*r*d" +func Translate(str, from, to string) string { + tr := NewTranslator(from, to) + return tr.Translate(str) +} + +// Delete runes in str matching the pattern. +// Pattern is defined in Translate function. +// +// Samples: +// Delete("hello", "aeiou") => "hll" +// Delete("hello", "a-k") => "llo" +// Delete("hello", "^a-k") => "he" +func Delete(str, pattern string) string { + tr := NewTranslator(pattern, "") + return tr.Translate(str) +} + +// Count how many runes in str match the pattern. +// Pattern is defined in Translate function. +// +// Samples: +// Count("hello", "aeiou") => 3 +// Count("hello", "a-k") => 3 +// Count("hello", "^a-k") => 2 +func Count(str, pattern string) int { + if pattern == "" || str == "" { + return 0 + } + + var r rune + var size int + var matched bool + + tr := NewTranslator(pattern, "") + cnt := 0 + + for len(str) > 0 { + r, size = utf8.DecodeRuneInString(str) + str = str[size:] + + if _, matched = tr.TranslateRune(r); matched { + cnt++ + } + } + + return cnt +} + +// Squeeze deletes adjacent repeated runes in str. +// If pattern is not empty, only runes matching the pattern will be squeezed. +// +// Samples: +// Squeeze("hello", "") => "helo" +// Squeeze("hello", "m-z") => "hello" +// Squeeze("hello world", " ") => "hello world" +func Squeeze(str, pattern string) string { + var last, r rune + var size int + var skipSqueeze, matched bool + var tr *Translator + var output *stringBuilder + + orig := str + last = -1 + + if len(pattern) > 0 { + tr = NewTranslator(pattern, "") + } + + for len(str) > 0 { + r, size = utf8.DecodeRuneInString(str) + + // Need to squeeze the str. + if last == r && !skipSqueeze { + if tr != nil { + if _, matched = tr.TranslateRune(r); !matched { + skipSqueeze = true + } + } + + if output == nil { + output = allocBuffer(orig, str) + } + + if skipSqueeze { + output.WriteRune(r) + } + } else { + if output != nil { + output.WriteRune(r) + } + + last = r + skipSqueeze = false + } + + str = str[size:] + } + + if output == nil { + return orig + } + + return output.String() +} diff --git a/vendor/github.com/imdario/mergo/.deepsource.toml b/vendor/github.com/imdario/mergo/.deepsource.toml new file mode 100644 index 000000000..8a0681af8 --- /dev/null +++ b/vendor/github.com/imdario/mergo/.deepsource.toml @@ -0,0 +1,12 @@ +version = 1 + +test_patterns = [ + "*_test.go" +] + +[[analyzers]] +name = "go" +enabled = true + + [analyzers.meta] + import_path = "github.com/imdario/mergo" \ No newline at end of file diff --git a/vendor/github.com/imdario/mergo/.gitignore b/vendor/github.com/imdario/mergo/.gitignore new file mode 100644 index 000000000..529c3412b --- /dev/null +++ b/vendor/github.com/imdario/mergo/.gitignore @@ -0,0 +1,33 @@ +#### joe made this: http://goel.io/joe + +#### go #### +# Binaries for programs and plugins +*.exe +*.dll +*.so +*.dylib + +# Test binary, build with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 +.glide/ + +#### vim #### +# Swap +[._]*.s[a-v][a-z] +[._]*.sw[a-p] +[._]s[a-v][a-z] +[._]sw[a-p] + +# Session +Session.vim + +# Temporary +.netrwhist +*~ +# Auto-generated tag files +tags diff --git a/vendor/github.com/imdario/mergo/.travis.yml b/vendor/github.com/imdario/mergo/.travis.yml new file mode 100644 index 000000000..dad29725f --- /dev/null +++ b/vendor/github.com/imdario/mergo/.travis.yml @@ -0,0 +1,9 @@ +language: go +install: + - go get -t + - go get golang.org/x/tools/cmd/cover + - go get github.com/mattn/goveralls +script: + - go test -race -v ./... +after_script: + - $HOME/gopath/bin/goveralls -service=travis-ci -repotoken $COVERALLS_TOKEN diff --git a/vendor/github.com/imdario/mergo/CODE_OF_CONDUCT.md b/vendor/github.com/imdario/mergo/CODE_OF_CONDUCT.md new file mode 100644 index 000000000..469b44907 --- /dev/null +++ b/vendor/github.com/imdario/mergo/CODE_OF_CONDUCT.md @@ -0,0 +1,46 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at i@dario.im. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] + +[homepage]: http://contributor-covenant.org +[version]: http://contributor-covenant.org/version/1/4/ diff --git a/vendor/github.com/imdario/mergo/LICENSE b/vendor/github.com/imdario/mergo/LICENSE new file mode 100644 index 000000000..686680298 --- /dev/null +++ b/vendor/github.com/imdario/mergo/LICENSE @@ -0,0 +1,28 @@ +Copyright (c) 2013 Dario Castañé. All rights reserved. +Copyright (c) 2012 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/imdario/mergo/README.md b/vendor/github.com/imdario/mergo/README.md new file mode 100644 index 000000000..876abb500 --- /dev/null +++ b/vendor/github.com/imdario/mergo/README.md @@ -0,0 +1,247 @@ +# Mergo + + +[![GoDoc][3]][4] +[![GitHub release][5]][6] +[![GoCard][7]][8] +[![Build Status][1]][2] +[![Coverage Status][9]][10] +[![Sourcegraph][11]][12] +[![FOSSA Status][13]][14] + +[![GoCenter Kudos][15]][16] + +[1]: https://travis-ci.org/imdario/mergo.png +[2]: https://travis-ci.org/imdario/mergo +[3]: https://godoc.org/github.com/imdario/mergo?status.svg +[4]: https://godoc.org/github.com/imdario/mergo +[5]: https://img.shields.io/github/release/imdario/mergo.svg +[6]: https://github.com/imdario/mergo/releases +[7]: https://goreportcard.com/badge/imdario/mergo +[8]: https://goreportcard.com/report/github.com/imdario/mergo +[9]: https://coveralls.io/repos/github/imdario/mergo/badge.svg?branch=master +[10]: https://coveralls.io/github/imdario/mergo?branch=master +[11]: https://sourcegraph.com/github.com/imdario/mergo/-/badge.svg +[12]: https://sourcegraph.com/github.com/imdario/mergo?badge +[13]: https://app.fossa.io/api/projects/git%2Bgithub.com%2Fimdario%2Fmergo.svg?type=shield +[14]: https://app.fossa.io/projects/git%2Bgithub.com%2Fimdario%2Fmergo?ref=badge_shield +[15]: https://search.gocenter.io/api/ui/badge/github.com%2Fimdario%2Fmergo +[16]: https://search.gocenter.io/github.com/imdario/mergo + +A helper to merge structs and maps in Golang. Useful for configuration default values, avoiding messy if-statements. + +Mergo merges same-type structs and maps by setting default values in zero-value fields. Mergo won't merge unexported (private) fields. It will do recursively any exported one. It also won't merge structs inside maps (because they are not addressable using Go reflection). + +Also a lovely [comune](http://en.wikipedia.org/wiki/Mergo) (municipality) in the Province of Ancona in the Italian region of Marche. + +## Status + +It is ready for production use. [It is used in several projects by Docker, Google, The Linux Foundation, VMWare, Shopify, etc](https://github.com/imdario/mergo#mergo-in-the-wild). + +### Important note + +Please keep in mind that a problematic PR broke [0.3.9](//github.com/imdario/mergo/releases/tag/0.3.9). I reverted it in [0.3.10](//github.com/imdario/mergo/releases/tag/0.3.10), and I consider it stable but not bug-free. Also, this version adds suppot for go modules. + +Keep in mind that in [0.3.2](//github.com/imdario/mergo/releases/tag/0.3.2), Mergo changed `Merge()`and `Map()` signatures to support [transformers](#transformers). I added an optional/variadic argument so that it won't break the existing code. + +If you were using Mergo before April 6th, 2015, please check your project works as intended after updating your local copy with ```go get -u github.com/imdario/mergo```. I apologize for any issue caused by its previous behavior and any future bug that Mergo could cause in existing projects after the change (release 0.2.0). + +### Donations + +If Mergo is useful to you, consider buying me a coffee, a beer, or making a monthly donation to allow me to keep building great free software. :heart_eyes: + +Buy Me a Coffee at ko-fi.com +[![Beerpay](https://beerpay.io/imdario/mergo/badge.svg)](https://beerpay.io/imdario/mergo) +[![Beerpay](https://beerpay.io/imdario/mergo/make-wish.svg)](https://beerpay.io/imdario/mergo) +Donate using Liberapay + +### Mergo in the wild + +- [moby/moby](https://github.com/moby/moby) +- [kubernetes/kubernetes](https://github.com/kubernetes/kubernetes) +- [vmware/dispatch](https://github.com/vmware/dispatch) +- [Shopify/themekit](https://github.com/Shopify/themekit) +- [imdario/zas](https://github.com/imdario/zas) +- [matcornic/hermes](https://github.com/matcornic/hermes) +- [OpenBazaar/openbazaar-go](https://github.com/OpenBazaar/openbazaar-go) +- [kataras/iris](https://github.com/kataras/iris) +- [michaelsauter/crane](https://github.com/michaelsauter/crane) +- [go-task/task](https://github.com/go-task/task) +- [sensu/uchiwa](https://github.com/sensu/uchiwa) +- [ory/hydra](https://github.com/ory/hydra) +- [sisatech/vcli](https://github.com/sisatech/vcli) +- [dairycart/dairycart](https://github.com/dairycart/dairycart) +- [projectcalico/felix](https://github.com/projectcalico/felix) +- [resin-os/balena](https://github.com/resin-os/balena) +- [go-kivik/kivik](https://github.com/go-kivik/kivik) +- [Telefonica/govice](https://github.com/Telefonica/govice) +- [supergiant/supergiant](supergiant/supergiant) +- [SergeyTsalkov/brooce](https://github.com/SergeyTsalkov/brooce) +- [soniah/dnsmadeeasy](https://github.com/soniah/dnsmadeeasy) +- [ohsu-comp-bio/funnel](https://github.com/ohsu-comp-bio/funnel) +- [EagerIO/Stout](https://github.com/EagerIO/Stout) +- [lynndylanhurley/defsynth-api](https://github.com/lynndylanhurley/defsynth-api) +- [russross/canvasassignments](https://github.com/russross/canvasassignments) +- [rdegges/cryptly-api](https://github.com/rdegges/cryptly-api) +- [casualjim/exeggutor](https://github.com/casualjim/exeggutor) +- [divshot/gitling](https://github.com/divshot/gitling) +- [RWJMurphy/gorl](https://github.com/RWJMurphy/gorl) +- [andrerocker/deploy42](https://github.com/andrerocker/deploy42) +- [elwinar/rambler](https://github.com/elwinar/rambler) +- [tmaiaroto/gopartman](https://github.com/tmaiaroto/gopartman) +- [jfbus/impressionist](https://github.com/jfbus/impressionist) +- [Jmeyering/zealot](https://github.com/Jmeyering/zealot) +- [godep-migrator/rigger-host](https://github.com/godep-migrator/rigger-host) +- [Dronevery/MultiwaySwitch-Go](https://github.com/Dronevery/MultiwaySwitch-Go) +- [thoas/picfit](https://github.com/thoas/picfit) +- [mantasmatelis/whooplist-server](https://github.com/mantasmatelis/whooplist-server) +- [jnuthong/item_search](https://github.com/jnuthong/item_search) +- [bukalapak/snowboard](https://github.com/bukalapak/snowboard) +- [janoszen/containerssh](https://github.com/janoszen/containerssh) + +## Install + + go get github.com/imdario/mergo + + // use in your .go code + import ( + "github.com/imdario/mergo" + ) + +## Usage + +You can only merge same-type structs with exported fields initialized as zero value of their type and same-types maps. Mergo won't merge unexported (private) fields but will do recursively any exported one. It won't merge empty structs value as [they are zero values](https://golang.org/ref/spec#The_zero_value) too. Also, maps will be merged recursively except for structs inside maps (because they are not addressable using Go reflection). + +```go +if err := mergo.Merge(&dst, src); err != nil { + // ... +} +``` + +Also, you can merge overwriting values using the transformer `WithOverride`. + +```go +if err := mergo.Merge(&dst, src, mergo.WithOverride); err != nil { + // ... +} +``` + +Additionally, you can map a `map[string]interface{}` to a struct (and otherwise, from struct to map), following the same restrictions as in `Merge()`. Keys are capitalized to find each corresponding exported field. + +```go +if err := mergo.Map(&dst, srcMap); err != nil { + // ... +} +``` + +Warning: if you map a struct to map, it won't do it recursively. Don't expect Mergo to map struct members of your struct as `map[string]interface{}`. They will be just assigned as values. + +Here is a nice example: + +```go +package main + +import ( + "fmt" + "github.com/imdario/mergo" +) + +type Foo struct { + A string + B int64 +} + +func main() { + src := Foo{ + A: "one", + B: 2, + } + dest := Foo{ + A: "two", + } + mergo.Merge(&dest, src) + fmt.Println(dest) + // Will print + // {two 2} +} +``` + +Note: if test are failing due missing package, please execute: + + go get gopkg.in/yaml.v2 + +### Transformers + +Transformers allow to merge specific types differently than in the default behavior. In other words, now you can customize how some types are merged. For example, `time.Time` is a struct; it doesn't have zero value but IsZero can return true because it has fields with zero value. How can we merge a non-zero `time.Time`? + +```go +package main + +import ( + "fmt" + "github.com/imdario/mergo" + "reflect" + "time" +) + +type timeTransformer struct { +} + +func (t timeTransformer) Transformer(typ reflect.Type) func(dst, src reflect.Value) error { + if typ == reflect.TypeOf(time.Time{}) { + return func(dst, src reflect.Value) error { + if dst.CanSet() { + isZero := dst.MethodByName("IsZero") + result := isZero.Call([]reflect.Value{}) + if result[0].Bool() { + dst.Set(src) + } + } + return nil + } + } + return nil +} + +type Snapshot struct { + Time time.Time + // ... +} + +func main() { + src := Snapshot{time.Now()} + dest := Snapshot{} + mergo.Merge(&dest, src, mergo.WithTransformers(timeTransformer{})) + fmt.Println(dest) + // Will print + // { 2018-01-12 01:15:00 +0000 UTC m=+0.000000001 } +} +``` + + +## Contact me + +If I can help you, you have an idea or you are using Mergo in your projects, don't hesitate to drop me a line (or a pull request): [@im_dario](https://twitter.com/im_dario) + +## About + +Written by [Dario Castañé](http://dario.im). + +## Top Contributors + +[![0](https://sourcerer.io/fame/imdario/imdario/mergo/images/0)](https://sourcerer.io/fame/imdario/imdario/mergo/links/0) +[![1](https://sourcerer.io/fame/imdario/imdario/mergo/images/1)](https://sourcerer.io/fame/imdario/imdario/mergo/links/1) +[![2](https://sourcerer.io/fame/imdario/imdario/mergo/images/2)](https://sourcerer.io/fame/imdario/imdario/mergo/links/2) +[![3](https://sourcerer.io/fame/imdario/imdario/mergo/images/3)](https://sourcerer.io/fame/imdario/imdario/mergo/links/3) +[![4](https://sourcerer.io/fame/imdario/imdario/mergo/images/4)](https://sourcerer.io/fame/imdario/imdario/mergo/links/4) +[![5](https://sourcerer.io/fame/imdario/imdario/mergo/images/5)](https://sourcerer.io/fame/imdario/imdario/mergo/links/5) +[![6](https://sourcerer.io/fame/imdario/imdario/mergo/images/6)](https://sourcerer.io/fame/imdario/imdario/mergo/links/6) +[![7](https://sourcerer.io/fame/imdario/imdario/mergo/images/7)](https://sourcerer.io/fame/imdario/imdario/mergo/links/7) + + +## License + +[BSD 3-Clause](http://opensource.org/licenses/BSD-3-Clause) license, as [Go language](http://golang.org/LICENSE). + + +[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fimdario%2Fmergo.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2Fimdario%2Fmergo?ref=badge_large) diff --git a/vendor/github.com/imdario/mergo/doc.go b/vendor/github.com/imdario/mergo/doc.go new file mode 100644 index 000000000..fcd985f99 --- /dev/null +++ b/vendor/github.com/imdario/mergo/doc.go @@ -0,0 +1,143 @@ +// Copyright 2013 Dario Castañé. All rights reserved. +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +/* +A helper to merge structs and maps in Golang. Useful for configuration default values, avoiding messy if-statements. + +Mergo merges same-type structs and maps by setting default values in zero-value fields. Mergo won't merge unexported (private) fields. It will do recursively any exported one. It also won't merge structs inside maps (because they are not addressable using Go reflection). + +Status + +It is ready for production use. It is used in several projects by Docker, Google, The Linux Foundation, VMWare, Shopify, etc. + +Important note + +Please keep in mind that a problematic PR broke 0.3.9. We reverted it in 0.3.10. We consider 0.3.10 as stable but not bug-free. . Also, this version adds suppot for go modules. + +Keep in mind that in 0.3.2, Mergo changed Merge() and Map() signatures to support transformers. We added an optional/variadic argument so that it won't break the existing code. + +If you were using Mergo before April 6th, 2015, please check your project works as intended after updating your local copy with go get -u github.com/imdario/mergo. I apologize for any issue caused by its previous behavior and any future bug that Mergo could cause in existing projects after the change (release 0.2.0). + +Install + +Do your usual installation procedure: + + go get github.com/imdario/mergo + + // use in your .go code + import ( + "github.com/imdario/mergo" + ) + +Usage + +You can only merge same-type structs with exported fields initialized as zero value of their type and same-types maps. Mergo won't merge unexported (private) fields but will do recursively any exported one. It won't merge empty structs value as they are zero values too. Also, maps will be merged recursively except for structs inside maps (because they are not addressable using Go reflection). + + if err := mergo.Merge(&dst, src); err != nil { + // ... + } + +Also, you can merge overwriting values using the transformer WithOverride. + + if err := mergo.Merge(&dst, src, mergo.WithOverride); err != nil { + // ... + } + +Additionally, you can map a map[string]interface{} to a struct (and otherwise, from struct to map), following the same restrictions as in Merge(). Keys are capitalized to find each corresponding exported field. + + if err := mergo.Map(&dst, srcMap); err != nil { + // ... + } + +Warning: if you map a struct to map, it won't do it recursively. Don't expect Mergo to map struct members of your struct as map[string]interface{}. They will be just assigned as values. + +Here is a nice example: + + package main + + import ( + "fmt" + "github.com/imdario/mergo" + ) + + type Foo struct { + A string + B int64 + } + + func main() { + src := Foo{ + A: "one", + B: 2, + } + dest := Foo{ + A: "two", + } + mergo.Merge(&dest, src) + fmt.Println(dest) + // Will print + // {two 2} + } + +Transformers + +Transformers allow to merge specific types differently than in the default behavior. In other words, now you can customize how some types are merged. For example, time.Time is a struct; it doesn't have zero value but IsZero can return true because it has fields with zero value. How can we merge a non-zero time.Time? + + package main + + import ( + "fmt" + "github.com/imdario/mergo" + "reflect" + "time" + ) + + type timeTransformer struct { + } + + func (t timeTransformer) Transformer(typ reflect.Type) func(dst, src reflect.Value) error { + if typ == reflect.TypeOf(time.Time{}) { + return func(dst, src reflect.Value) error { + if dst.CanSet() { + isZero := dst.MethodByName("IsZero") + result := isZero.Call([]reflect.Value{}) + if result[0].Bool() { + dst.Set(src) + } + } + return nil + } + } + return nil + } + + type Snapshot struct { + Time time.Time + // ... + } + + func main() { + src := Snapshot{time.Now()} + dest := Snapshot{} + mergo.Merge(&dest, src, mergo.WithTransformers(timeTransformer{})) + fmt.Println(dest) + // Will print + // { 2018-01-12 01:15:00 +0000 UTC m=+0.000000001 } + } + +Contact me + +If I can help you, you have an idea or you are using Mergo in your projects, don't hesitate to drop me a line (or a pull request): https://twitter.com/im_dario + +About + +Written by Dario Castañé: https://da.rio.hn + +License + +BSD 3-Clause license, as Go language. + +*/ +package mergo diff --git a/vendor/github.com/imdario/mergo/go.mod b/vendor/github.com/imdario/mergo/go.mod new file mode 100644 index 000000000..3d689d93e --- /dev/null +++ b/vendor/github.com/imdario/mergo/go.mod @@ -0,0 +1,5 @@ +module github.com/imdario/mergo + +go 1.13 + +require gopkg.in/yaml.v2 v2.3.0 diff --git a/vendor/github.com/imdario/mergo/go.sum b/vendor/github.com/imdario/mergo/go.sum new file mode 100644 index 000000000..168980da5 --- /dev/null +++ b/vendor/github.com/imdario/mergo/go.sum @@ -0,0 +1,4 @@ +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/vendor/github.com/imdario/mergo/map.go b/vendor/github.com/imdario/mergo/map.go new file mode 100644 index 000000000..a13a7ee46 --- /dev/null +++ b/vendor/github.com/imdario/mergo/map.go @@ -0,0 +1,178 @@ +// Copyright 2014 Dario Castañé. All rights reserved. +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Based on src/pkg/reflect/deepequal.go from official +// golang's stdlib. + +package mergo + +import ( + "fmt" + "reflect" + "unicode" + "unicode/utf8" +) + +func changeInitialCase(s string, mapper func(rune) rune) string { + if s == "" { + return s + } + r, n := utf8.DecodeRuneInString(s) + return string(mapper(r)) + s[n:] +} + +func isExported(field reflect.StructField) bool { + r, _ := utf8.DecodeRuneInString(field.Name) + return r >= 'A' && r <= 'Z' +} + +// Traverses recursively both values, assigning src's fields values to dst. +// The map argument tracks comparisons that have already been seen, which allows +// short circuiting on recursive types. +func deepMap(dst, src reflect.Value, visited map[uintptr]*visit, depth int, config *Config) (err error) { + overwrite := config.Overwrite + if dst.CanAddr() { + addr := dst.UnsafeAddr() + h := 17 * addr + seen := visited[h] + typ := dst.Type() + for p := seen; p != nil; p = p.next { + if p.ptr == addr && p.typ == typ { + return nil + } + } + // Remember, remember... + visited[h] = &visit{addr, typ, seen} + } + zeroValue := reflect.Value{} + switch dst.Kind() { + case reflect.Map: + dstMap := dst.Interface().(map[string]interface{}) + for i, n := 0, src.NumField(); i < n; i++ { + srcType := src.Type() + field := srcType.Field(i) + if !isExported(field) { + continue + } + fieldName := field.Name + fieldName = changeInitialCase(fieldName, unicode.ToLower) + if v, ok := dstMap[fieldName]; !ok || (isEmptyValue(reflect.ValueOf(v)) || overwrite) { + dstMap[fieldName] = src.Field(i).Interface() + } + } + case reflect.Ptr: + if dst.IsNil() { + v := reflect.New(dst.Type().Elem()) + dst.Set(v) + } + dst = dst.Elem() + fallthrough + case reflect.Struct: + srcMap := src.Interface().(map[string]interface{}) + for key := range srcMap { + config.overwriteWithEmptyValue = true + srcValue := srcMap[key] + fieldName := changeInitialCase(key, unicode.ToUpper) + dstElement := dst.FieldByName(fieldName) + if dstElement == zeroValue { + // We discard it because the field doesn't exist. + continue + } + srcElement := reflect.ValueOf(srcValue) + dstKind := dstElement.Kind() + srcKind := srcElement.Kind() + if srcKind == reflect.Ptr && dstKind != reflect.Ptr { + srcElement = srcElement.Elem() + srcKind = reflect.TypeOf(srcElement.Interface()).Kind() + } else if dstKind == reflect.Ptr { + // Can this work? I guess it can't. + if srcKind != reflect.Ptr && srcElement.CanAddr() { + srcPtr := srcElement.Addr() + srcElement = reflect.ValueOf(srcPtr) + srcKind = reflect.Ptr + } + } + + if !srcElement.IsValid() { + continue + } + if srcKind == dstKind { + if err = deepMerge(dstElement, srcElement, visited, depth+1, config); err != nil { + return + } + } else if dstKind == reflect.Interface && dstElement.Kind() == reflect.Interface { + if err = deepMerge(dstElement, srcElement, visited, depth+1, config); err != nil { + return + } + } else if srcKind == reflect.Map { + if err = deepMap(dstElement, srcElement, visited, depth+1, config); err != nil { + return + } + } else { + return fmt.Errorf("type mismatch on %s field: found %v, expected %v", fieldName, srcKind, dstKind) + } + } + } + return +} + +// Map sets fields' values in dst from src. +// src can be a map with string keys or a struct. dst must be the opposite: +// if src is a map, dst must be a valid pointer to struct. If src is a struct, +// dst must be map[string]interface{}. +// It won't merge unexported (private) fields and will do recursively +// any exported field. +// If dst is a map, keys will be src fields' names in lower camel case. +// Missing key in src that doesn't match a field in dst will be skipped. This +// doesn't apply if dst is a map. +// This is separated method from Merge because it is cleaner and it keeps sane +// semantics: merging equal types, mapping different (restricted) types. +func Map(dst, src interface{}, opts ...func(*Config)) error { + return _map(dst, src, opts...) +} + +// MapWithOverwrite will do the same as Map except that non-empty dst attributes will be overridden by +// non-empty src attribute values. +// Deprecated: Use Map(…) with WithOverride +func MapWithOverwrite(dst, src interface{}, opts ...func(*Config)) error { + return _map(dst, src, append(opts, WithOverride)...) +} + +func _map(dst, src interface{}, opts ...func(*Config)) error { + if dst != nil && reflect.ValueOf(dst).Kind() != reflect.Ptr { + return ErrNonPointerAgument + } + var ( + vDst, vSrc reflect.Value + err error + ) + config := &Config{} + + for _, opt := range opts { + opt(config) + } + + if vDst, vSrc, err = resolveValues(dst, src); err != nil { + return err + } + // To be friction-less, we redirect equal-type arguments + // to deepMerge. Only because arguments can be anything. + if vSrc.Kind() == vDst.Kind() { + return deepMerge(vDst, vSrc, make(map[uintptr]*visit), 0, config) + } + switch vSrc.Kind() { + case reflect.Struct: + if vDst.Kind() != reflect.Map { + return ErrExpectedMapAsDestination + } + case reflect.Map: + if vDst.Kind() != reflect.Struct { + return ErrExpectedStructAsDestination + } + default: + return ErrNotSupported + } + return deepMap(vDst, vSrc, make(map[uintptr]*visit), 0, config) +} diff --git a/vendor/github.com/imdario/mergo/merge.go b/vendor/github.com/imdario/mergo/merge.go new file mode 100644 index 000000000..afa84a1e2 --- /dev/null +++ b/vendor/github.com/imdario/mergo/merge.go @@ -0,0 +1,375 @@ +// Copyright 2013 Dario Castañé. All rights reserved. +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Based on src/pkg/reflect/deepequal.go from official +// golang's stdlib. + +package mergo + +import ( + "fmt" + "reflect" +) + +func hasMergeableFields(dst reflect.Value) (exported bool) { + for i, n := 0, dst.NumField(); i < n; i++ { + field := dst.Type().Field(i) + if field.Anonymous && dst.Field(i).Kind() == reflect.Struct { + exported = exported || hasMergeableFields(dst.Field(i)) + } else if isExportedComponent(&field) { + exported = exported || len(field.PkgPath) == 0 + } + } + return +} + +func isExportedComponent(field *reflect.StructField) bool { + pkgPath := field.PkgPath + if len(pkgPath) > 0 { + return false + } + c := field.Name[0] + if 'a' <= c && c <= 'z' || c == '_' { + return false + } + return true +} + +type Config struct { + Overwrite bool + AppendSlice bool + TypeCheck bool + Transformers Transformers + overwriteWithEmptyValue bool + overwriteSliceWithEmptyValue bool + sliceDeepCopy bool + debug bool +} + +type Transformers interface { + Transformer(reflect.Type) func(dst, src reflect.Value) error +} + +// Traverses recursively both values, assigning src's fields values to dst. +// The map argument tracks comparisons that have already been seen, which allows +// short circuiting on recursive types. +func deepMerge(dst, src reflect.Value, visited map[uintptr]*visit, depth int, config *Config) (err error) { + overwrite := config.Overwrite + typeCheck := config.TypeCheck + overwriteWithEmptySrc := config.overwriteWithEmptyValue + overwriteSliceWithEmptySrc := config.overwriteSliceWithEmptyValue + sliceDeepCopy := config.sliceDeepCopy + + if !src.IsValid() { + return + } + if dst.CanAddr() { + addr := dst.UnsafeAddr() + h := 17 * addr + seen := visited[h] + typ := dst.Type() + for p := seen; p != nil; p = p.next { + if p.ptr == addr && p.typ == typ { + return nil + } + } + // Remember, remember... + visited[h] = &visit{addr, typ, seen} + } + + if config.Transformers != nil && !isEmptyValue(dst) { + if fn := config.Transformers.Transformer(dst.Type()); fn != nil { + err = fn(dst, src) + return + } + } + + switch dst.Kind() { + case reflect.Struct: + if hasMergeableFields(dst) { + for i, n := 0, dst.NumField(); i < n; i++ { + if err = deepMerge(dst.Field(i), src.Field(i), visited, depth+1, config); err != nil { + return + } + } + } else { + if (isReflectNil(dst) || overwrite) && (!isEmptyValue(src) || overwriteWithEmptySrc) { + dst.Set(src) + } + } + case reflect.Map: + if dst.IsNil() && !src.IsNil() { + dst.Set(reflect.MakeMap(dst.Type())) + } + + if src.Kind() != reflect.Map { + if overwrite { + dst.Set(src) + } + return + } + + for _, key := range src.MapKeys() { + srcElement := src.MapIndex(key) + if !srcElement.IsValid() { + continue + } + dstElement := dst.MapIndex(key) + switch srcElement.Kind() { + case reflect.Chan, reflect.Func, reflect.Map, reflect.Interface, reflect.Slice: + if srcElement.IsNil() { + if overwrite { + dst.SetMapIndex(key, srcElement) + } + continue + } + fallthrough + default: + if !srcElement.CanInterface() { + continue + } + switch reflect.TypeOf(srcElement.Interface()).Kind() { + case reflect.Struct: + fallthrough + case reflect.Ptr: + fallthrough + case reflect.Map: + srcMapElm := srcElement + dstMapElm := dstElement + if srcMapElm.CanInterface() { + srcMapElm = reflect.ValueOf(srcMapElm.Interface()) + if dstMapElm.IsValid() { + dstMapElm = reflect.ValueOf(dstMapElm.Interface()) + } + } + if err = deepMerge(dstMapElm, srcMapElm, visited, depth+1, config); err != nil { + return + } + case reflect.Slice: + srcSlice := reflect.ValueOf(srcElement.Interface()) + + var dstSlice reflect.Value + if !dstElement.IsValid() || dstElement.IsNil() { + dstSlice = reflect.MakeSlice(srcSlice.Type(), 0, srcSlice.Len()) + } else { + dstSlice = reflect.ValueOf(dstElement.Interface()) + } + + if (!isEmptyValue(src) || overwriteWithEmptySrc || overwriteSliceWithEmptySrc) && (overwrite || isEmptyValue(dst)) && !config.AppendSlice && !sliceDeepCopy { + if typeCheck && srcSlice.Type() != dstSlice.Type() { + return fmt.Errorf("cannot override two slices with different type (%s, %s)", srcSlice.Type(), dstSlice.Type()) + } + dstSlice = srcSlice + } else if config.AppendSlice { + if srcSlice.Type() != dstSlice.Type() { + return fmt.Errorf("cannot append two slices with different type (%s, %s)", srcSlice.Type(), dstSlice.Type()) + } + dstSlice = reflect.AppendSlice(dstSlice, srcSlice) + } else if sliceDeepCopy { + i := 0 + for ; i < srcSlice.Len() && i < dstSlice.Len(); i++ { + srcElement := srcSlice.Index(i) + dstElement := dstSlice.Index(i) + + if srcElement.CanInterface() { + srcElement = reflect.ValueOf(srcElement.Interface()) + } + if dstElement.CanInterface() { + dstElement = reflect.ValueOf(dstElement.Interface()) + } + + if err = deepMerge(dstElement, srcElement, visited, depth+1, config); err != nil { + return + } + } + + } + dst.SetMapIndex(key, dstSlice) + } + } + if dstElement.IsValid() && !isEmptyValue(dstElement) && (reflect.TypeOf(srcElement.Interface()).Kind() == reflect.Map || reflect.TypeOf(srcElement.Interface()).Kind() == reflect.Slice) { + continue + } + + if srcElement.IsValid() && ((srcElement.Kind() != reflect.Ptr && overwrite) || !dstElement.IsValid() || isEmptyValue(dstElement)) { + if dst.IsNil() { + dst.Set(reflect.MakeMap(dst.Type())) + } + dst.SetMapIndex(key, srcElement) + } + } + case reflect.Slice: + if !dst.CanSet() { + break + } + if (!isEmptyValue(src) || overwriteWithEmptySrc || overwriteSliceWithEmptySrc) && (overwrite || isEmptyValue(dst)) && !config.AppendSlice && !sliceDeepCopy { + dst.Set(src) + } else if config.AppendSlice { + if src.Type() != dst.Type() { + return fmt.Errorf("cannot append two slice with different type (%s, %s)", src.Type(), dst.Type()) + } + dst.Set(reflect.AppendSlice(dst, src)) + } else if sliceDeepCopy { + for i := 0; i < src.Len() && i < dst.Len(); i++ { + srcElement := src.Index(i) + dstElement := dst.Index(i) + if srcElement.CanInterface() { + srcElement = reflect.ValueOf(srcElement.Interface()) + } + if dstElement.CanInterface() { + dstElement = reflect.ValueOf(dstElement.Interface()) + } + + if err = deepMerge(dstElement, srcElement, visited, depth+1, config); err != nil { + return + } + } + } + case reflect.Ptr: + fallthrough + case reflect.Interface: + if isReflectNil(src) { + if overwriteWithEmptySrc && dst.CanSet() && src.Type().AssignableTo(dst.Type()) { + dst.Set(src) + } + break + } + + if src.Kind() != reflect.Interface { + if dst.IsNil() || (src.Kind() != reflect.Ptr && overwrite) { + if dst.CanSet() && (overwrite || isEmptyValue(dst)) { + dst.Set(src) + } + } else if src.Kind() == reflect.Ptr { + if err = deepMerge(dst.Elem(), src.Elem(), visited, depth+1, config); err != nil { + return + } + } else if dst.Elem().Type() == src.Type() { + if err = deepMerge(dst.Elem(), src, visited, depth+1, config); err != nil { + return + } + } else { + return ErrDifferentArgumentsTypes + } + break + } + + if dst.IsNil() || overwrite { + if dst.CanSet() && (overwrite || isEmptyValue(dst)) { + dst.Set(src) + } + break + } + + if dst.Elem().Kind() == src.Elem().Kind() { + if err = deepMerge(dst.Elem(), src.Elem(), visited, depth+1, config); err != nil { + return + } + break + } + default: + mustSet := (isEmptyValue(dst) || overwrite) && (!isEmptyValue(src) || overwriteWithEmptySrc) + if mustSet { + if dst.CanSet() { + dst.Set(src) + } else { + dst = src + } + } + } + + return +} + +// Merge will fill any empty for value type attributes on the dst struct using corresponding +// src attributes if they themselves are not empty. dst and src must be valid same-type structs +// and dst must be a pointer to struct. +// It won't merge unexported (private) fields and will do recursively any exported field. +func Merge(dst, src interface{}, opts ...func(*Config)) error { + return merge(dst, src, opts...) +} + +// MergeWithOverwrite will do the same as Merge except that non-empty dst attributes will be overridden by +// non-empty src attribute values. +// Deprecated: use Merge(…) with WithOverride +func MergeWithOverwrite(dst, src interface{}, opts ...func(*Config)) error { + return merge(dst, src, append(opts, WithOverride)...) +} + +// WithTransformers adds transformers to merge, allowing to customize the merging of some types. +func WithTransformers(transformers Transformers) func(*Config) { + return func(config *Config) { + config.Transformers = transformers + } +} + +// WithOverride will make merge override non-empty dst attributes with non-empty src attributes values. +func WithOverride(config *Config) { + config.Overwrite = true +} + +// WithOverwriteWithEmptyValue will make merge override non empty dst attributes with empty src attributes values. +func WithOverwriteWithEmptyValue(config *Config) { + config.Overwrite = true + config.overwriteWithEmptyValue = true +} + +// WithOverrideEmptySlice will make merge override empty dst slice with empty src slice. +func WithOverrideEmptySlice(config *Config) { + config.overwriteSliceWithEmptyValue = true +} + +// WithAppendSlice will make merge append slices instead of overwriting it. +func WithAppendSlice(config *Config) { + config.AppendSlice = true +} + +// WithTypeCheck will make merge check types while overwriting it (must be used with WithOverride). +func WithTypeCheck(config *Config) { + config.TypeCheck = true +} + +// WithSliceDeepCopy will merge slice element one by one with Overwrite flag. +func WithSliceDeepCopy(config *Config) { + config.sliceDeepCopy = true + config.Overwrite = true +} + +func merge(dst, src interface{}, opts ...func(*Config)) error { + if dst != nil && reflect.ValueOf(dst).Kind() != reflect.Ptr { + return ErrNonPointerAgument + } + var ( + vDst, vSrc reflect.Value + err error + ) + + config := &Config{} + + for _, opt := range opts { + opt(config) + } + + if vDst, vSrc, err = resolveValues(dst, src); err != nil { + return err + } + if vDst.Type() != vSrc.Type() { + return ErrDifferentArgumentsTypes + } + return deepMerge(vDst, vSrc, make(map[uintptr]*visit), 0, config) +} + +// IsReflectNil is the reflect value provided nil +func isReflectNil(v reflect.Value) bool { + k := v.Kind() + switch k { + case reflect.Interface, reflect.Slice, reflect.Chan, reflect.Func, reflect.Map, reflect.Ptr: + // Both interface and slice are nil if first word is 0. + // Both are always bigger than a word; assume flagIndir. + return v.IsNil() + default: + return false + } +} diff --git a/vendor/github.com/imdario/mergo/mergo.go b/vendor/github.com/imdario/mergo/mergo.go new file mode 100644 index 000000000..3cc926c7f --- /dev/null +++ b/vendor/github.com/imdario/mergo/mergo.go @@ -0,0 +1,78 @@ +// Copyright 2013 Dario Castañé. All rights reserved. +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Based on src/pkg/reflect/deepequal.go from official +// golang's stdlib. + +package mergo + +import ( + "errors" + "reflect" +) + +// Errors reported by Mergo when it finds invalid arguments. +var ( + ErrNilArguments = errors.New("src and dst must not be nil") + ErrDifferentArgumentsTypes = errors.New("src and dst must be of same type") + ErrNotSupported = errors.New("only structs and maps are supported") + ErrExpectedMapAsDestination = errors.New("dst was expected to be a map") + ErrExpectedStructAsDestination = errors.New("dst was expected to be a struct") + ErrNonPointerAgument = errors.New("dst must be a pointer") +) + +// During deepMerge, must keep track of checks that are +// in progress. The comparison algorithm assumes that all +// checks in progress are true when it reencounters them. +// Visited are stored in a map indexed by 17 * a1 + a2; +type visit struct { + ptr uintptr + typ reflect.Type + next *visit +} + +// From src/pkg/encoding/json/encode.go. +func isEmptyValue(v reflect.Value) bool { + switch v.Kind() { + case reflect.Array, reflect.Map, reflect.Slice, reflect.String: + return v.Len() == 0 + case reflect.Bool: + return !v.Bool() + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return v.Int() == 0 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return v.Uint() == 0 + case reflect.Float32, reflect.Float64: + return v.Float() == 0 + case reflect.Interface, reflect.Ptr: + if v.IsNil() { + return true + } + return isEmptyValue(v.Elem()) + case reflect.Func: + return v.IsNil() + case reflect.Invalid: + return true + } + return false +} + +func resolveValues(dst, src interface{}) (vDst, vSrc reflect.Value, err error) { + if dst == nil || src == nil { + err = ErrNilArguments + return + } + vDst = reflect.ValueOf(dst).Elem() + if vDst.Kind() != reflect.Struct && vDst.Kind() != reflect.Map { + err = ErrNotSupported + return + } + vSrc = reflect.ValueOf(src) + // We check if vSrc is a pointer to dereference it. + if vSrc.Kind() == reflect.Ptr { + vSrc = vSrc.Elem() + } + return +} diff --git a/vendor/github.com/mitchellh/copystructure/.travis.yml b/vendor/github.com/mitchellh/copystructure/.travis.yml new file mode 100644 index 000000000..d7b9589ab --- /dev/null +++ b/vendor/github.com/mitchellh/copystructure/.travis.yml @@ -0,0 +1,12 @@ +language: go + +go: + - 1.7 + - tip + +script: + - go test + +matrix: + allow_failures: + - go: tip diff --git a/vendor/github.com/mitchellh/copystructure/LICENSE b/vendor/github.com/mitchellh/copystructure/LICENSE new file mode 100644 index 000000000..229851590 --- /dev/null +++ b/vendor/github.com/mitchellh/copystructure/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 Mitchell Hashimoto + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/github.com/mitchellh/copystructure/README.md b/vendor/github.com/mitchellh/copystructure/README.md new file mode 100644 index 000000000..bcb8c8d2c --- /dev/null +++ b/vendor/github.com/mitchellh/copystructure/README.md @@ -0,0 +1,21 @@ +# copystructure + +copystructure is a Go library for deep copying values in Go. + +This allows you to copy Go values that may contain reference values +such as maps, slices, or pointers, and copy their data as well instead +of just their references. + +## Installation + +Standard `go get`: + +``` +$ go get github.com/mitchellh/copystructure +``` + +## Usage & Example + +For usage and examples see the [Godoc](http://godoc.org/github.com/mitchellh/copystructure). + +The `Copy` function has examples associated with it there. diff --git a/vendor/github.com/mitchellh/copystructure/copier_time.go b/vendor/github.com/mitchellh/copystructure/copier_time.go new file mode 100644 index 000000000..db6a6aa1a --- /dev/null +++ b/vendor/github.com/mitchellh/copystructure/copier_time.go @@ -0,0 +1,15 @@ +package copystructure + +import ( + "reflect" + "time" +) + +func init() { + Copiers[reflect.TypeOf(time.Time{})] = timeCopier +} + +func timeCopier(v interface{}) (interface{}, error) { + // Just... copy it. + return v.(time.Time), nil +} diff --git a/vendor/github.com/mitchellh/copystructure/copystructure.go b/vendor/github.com/mitchellh/copystructure/copystructure.go new file mode 100644 index 000000000..140435255 --- /dev/null +++ b/vendor/github.com/mitchellh/copystructure/copystructure.go @@ -0,0 +1,548 @@ +package copystructure + +import ( + "errors" + "reflect" + "sync" + + "github.com/mitchellh/reflectwalk" +) + +// Copy returns a deep copy of v. +func Copy(v interface{}) (interface{}, error) { + return Config{}.Copy(v) +} + +// CopierFunc is a function that knows how to deep copy a specific type. +// Register these globally with the Copiers variable. +type CopierFunc func(interface{}) (interface{}, error) + +// Copiers is a map of types that behave specially when they are copied. +// If a type is found in this map while deep copying, this function +// will be called to copy it instead of attempting to copy all fields. +// +// The key should be the type, obtained using: reflect.TypeOf(value with type). +// +// It is unsafe to write to this map after Copies have started. If you +// are writing to this map while also copying, wrap all modifications to +// this map as well as to Copy in a mutex. +var Copiers map[reflect.Type]CopierFunc = make(map[reflect.Type]CopierFunc) + +// Must is a helper that wraps a call to a function returning +// (interface{}, error) and panics if the error is non-nil. It is intended +// for use in variable initializations and should only be used when a copy +// error should be a crashing case. +func Must(v interface{}, err error) interface{} { + if err != nil { + panic("copy error: " + err.Error()) + } + + return v +} + +var errPointerRequired = errors.New("Copy argument must be a pointer when Lock is true") + +type Config struct { + // Lock any types that are a sync.Locker and are not a mutex while copying. + // If there is an RLocker method, use that to get the sync.Locker. + Lock bool + + // Copiers is a map of types associated with a CopierFunc. Use the global + // Copiers map if this is nil. + Copiers map[reflect.Type]CopierFunc +} + +func (c Config) Copy(v interface{}) (interface{}, error) { + if c.Lock && reflect.ValueOf(v).Kind() != reflect.Ptr { + return nil, errPointerRequired + } + + w := new(walker) + if c.Lock { + w.useLocks = true + } + + if c.Copiers == nil { + c.Copiers = Copiers + } + + err := reflectwalk.Walk(v, w) + if err != nil { + return nil, err + } + + // Get the result. If the result is nil, then we want to turn it + // into a typed nil if we can. + result := w.Result + if result == nil { + val := reflect.ValueOf(v) + result = reflect.Indirect(reflect.New(val.Type())).Interface() + } + + return result, nil +} + +// Return the key used to index interfaces types we've seen. Store the number +// of pointers in the upper 32bits, and the depth in the lower 32bits. This is +// easy to calculate, easy to match a key with our current depth, and we don't +// need to deal with initializing and cleaning up nested maps or slices. +func ifaceKey(pointers, depth int) uint64 { + return uint64(pointers)<<32 | uint64(depth) +} + +type walker struct { + Result interface{} + + depth int + ignoreDepth int + vals []reflect.Value + cs []reflect.Value + + // This stores the number of pointers we've walked over, indexed by depth. + ps []int + + // If an interface is indirected by a pointer, we need to know the type of + // interface to create when creating the new value. Store the interface + // types here, indexed by both the walk depth and the number of pointers + // already seen at that depth. Use ifaceKey to calculate the proper uint64 + // value. + ifaceTypes map[uint64]reflect.Type + + // any locks we've taken, indexed by depth + locks []sync.Locker + // take locks while walking the structure + useLocks bool +} + +func (w *walker) Enter(l reflectwalk.Location) error { + w.depth++ + + // ensure we have enough elements to index via w.depth + for w.depth >= len(w.locks) { + w.locks = append(w.locks, nil) + } + + for len(w.ps) < w.depth+1 { + w.ps = append(w.ps, 0) + } + + return nil +} + +func (w *walker) Exit(l reflectwalk.Location) error { + locker := w.locks[w.depth] + w.locks[w.depth] = nil + if locker != nil { + defer locker.Unlock() + } + + // clear out pointers and interfaces as we exit the stack + w.ps[w.depth] = 0 + + for k := range w.ifaceTypes { + mask := uint64(^uint32(0)) + if k&mask == uint64(w.depth) { + delete(w.ifaceTypes, k) + } + } + + w.depth-- + if w.ignoreDepth > w.depth { + w.ignoreDepth = 0 + } + + if w.ignoring() { + return nil + } + + switch l { + case reflectwalk.Array: + fallthrough + case reflectwalk.Map: + fallthrough + case reflectwalk.Slice: + w.replacePointerMaybe() + + // Pop map off our container + w.cs = w.cs[:len(w.cs)-1] + case reflectwalk.MapValue: + // Pop off the key and value + mv := w.valPop() + mk := w.valPop() + m := w.cs[len(w.cs)-1] + + // If mv is the zero value, SetMapIndex deletes the key form the map, + // or in this case never adds it. We need to create a properly typed + // zero value so that this key can be set. + if !mv.IsValid() { + mv = reflect.Zero(m.Elem().Type().Elem()) + } + m.Elem().SetMapIndex(mk, mv) + case reflectwalk.ArrayElem: + // Pop off the value and the index and set it on the array + v := w.valPop() + i := w.valPop().Interface().(int) + if v.IsValid() { + a := w.cs[len(w.cs)-1] + ae := a.Elem().Index(i) // storing array as pointer on stack - so need Elem() call + if ae.CanSet() { + ae.Set(v) + } + } + case reflectwalk.SliceElem: + // Pop off the value and the index and set it on the slice + v := w.valPop() + i := w.valPop().Interface().(int) + if v.IsValid() { + s := w.cs[len(w.cs)-1] + se := s.Elem().Index(i) + if se.CanSet() { + se.Set(v) + } + } + case reflectwalk.Struct: + w.replacePointerMaybe() + + // Remove the struct from the container stack + w.cs = w.cs[:len(w.cs)-1] + case reflectwalk.StructField: + // Pop off the value and the field + v := w.valPop() + f := w.valPop().Interface().(reflect.StructField) + if v.IsValid() { + s := w.cs[len(w.cs)-1] + sf := reflect.Indirect(s).FieldByName(f.Name) + + if sf.CanSet() { + sf.Set(v) + } + } + case reflectwalk.WalkLoc: + // Clear out the slices for GC + w.cs = nil + w.vals = nil + } + + return nil +} + +func (w *walker) Map(m reflect.Value) error { + if w.ignoring() { + return nil + } + w.lock(m) + + // Create the map. If the map itself is nil, then just make a nil map + var newMap reflect.Value + if m.IsNil() { + newMap = reflect.New(m.Type()) + } else { + newMap = wrapPtr(reflect.MakeMap(m.Type())) + } + + w.cs = append(w.cs, newMap) + w.valPush(newMap) + return nil +} + +func (w *walker) MapElem(m, k, v reflect.Value) error { + return nil +} + +func (w *walker) PointerEnter(v bool) error { + if v { + w.ps[w.depth]++ + } + return nil +} + +func (w *walker) PointerExit(v bool) error { + if v { + w.ps[w.depth]-- + } + return nil +} + +func (w *walker) Interface(v reflect.Value) error { + if !v.IsValid() { + return nil + } + if w.ifaceTypes == nil { + w.ifaceTypes = make(map[uint64]reflect.Type) + } + + w.ifaceTypes[ifaceKey(w.ps[w.depth], w.depth)] = v.Type() + return nil +} + +func (w *walker) Primitive(v reflect.Value) error { + if w.ignoring() { + return nil + } + w.lock(v) + + // IsValid verifies the v is non-zero and CanInterface verifies + // that we're allowed to read this value (unexported fields). + var newV reflect.Value + if v.IsValid() && v.CanInterface() { + newV = reflect.New(v.Type()) + newV.Elem().Set(v) + } + + w.valPush(newV) + w.replacePointerMaybe() + return nil +} + +func (w *walker) Slice(s reflect.Value) error { + if w.ignoring() { + return nil + } + w.lock(s) + + var newS reflect.Value + if s.IsNil() { + newS = reflect.New(s.Type()) + } else { + newS = wrapPtr(reflect.MakeSlice(s.Type(), s.Len(), s.Cap())) + } + + w.cs = append(w.cs, newS) + w.valPush(newS) + return nil +} + +func (w *walker) SliceElem(i int, elem reflect.Value) error { + if w.ignoring() { + return nil + } + + // We don't write the slice here because elem might still be + // arbitrarily complex. Just record the index and continue on. + w.valPush(reflect.ValueOf(i)) + + return nil +} + +func (w *walker) Array(a reflect.Value) error { + if w.ignoring() { + return nil + } + w.lock(a) + + newA := reflect.New(a.Type()) + + w.cs = append(w.cs, newA) + w.valPush(newA) + return nil +} + +func (w *walker) ArrayElem(i int, elem reflect.Value) error { + if w.ignoring() { + return nil + } + + // We don't write the array here because elem might still be + // arbitrarily complex. Just record the index and continue on. + w.valPush(reflect.ValueOf(i)) + + return nil +} + +func (w *walker) Struct(s reflect.Value) error { + if w.ignoring() { + return nil + } + w.lock(s) + + var v reflect.Value + if c, ok := Copiers[s.Type()]; ok { + // We have a Copier for this struct, so we use that copier to + // get the copy, and we ignore anything deeper than this. + w.ignoreDepth = w.depth + + dup, err := c(s.Interface()) + if err != nil { + return err + } + + // We need to put a pointer to the value on the value stack, + // so allocate a new pointer and set it. + v = reflect.New(s.Type()) + reflect.Indirect(v).Set(reflect.ValueOf(dup)) + } else { + // No copier, we copy ourselves and allow reflectwalk to guide + // us deeper into the structure for copying. + v = reflect.New(s.Type()) + } + + // Push the value onto the value stack for setting the struct field, + // and add the struct itself to the containers stack in case we walk + // deeper so that its own fields can be modified. + w.valPush(v) + w.cs = append(w.cs, v) + + return nil +} + +func (w *walker) StructField(f reflect.StructField, v reflect.Value) error { + if w.ignoring() { + return nil + } + + // If PkgPath is non-empty, this is a private (unexported) field. + // We do not set this unexported since the Go runtime doesn't allow us. + if f.PkgPath != "" { + return reflectwalk.SkipEntry + } + + // Push the field onto the stack, we'll handle it when we exit + // the struct field in Exit... + w.valPush(reflect.ValueOf(f)) + return nil +} + +// ignore causes the walker to ignore any more values until we exit this on +func (w *walker) ignore() { + w.ignoreDepth = w.depth +} + +func (w *walker) ignoring() bool { + return w.ignoreDepth > 0 && w.depth >= w.ignoreDepth +} + +func (w *walker) pointerPeek() bool { + return w.ps[w.depth] > 0 +} + +func (w *walker) valPop() reflect.Value { + result := w.vals[len(w.vals)-1] + w.vals = w.vals[:len(w.vals)-1] + + // If we're out of values, that means we popped everything off. In + // this case, we reset the result so the next pushed value becomes + // the result. + if len(w.vals) == 0 { + w.Result = nil + } + + return result +} + +func (w *walker) valPush(v reflect.Value) { + w.vals = append(w.vals, v) + + // If we haven't set the result yet, then this is the result since + // it is the first (outermost) value we're seeing. + if w.Result == nil && v.IsValid() { + w.Result = v.Interface() + } +} + +func (w *walker) replacePointerMaybe() { + // Determine the last pointer value. If it is NOT a pointer, then + // we need to push that onto the stack. + if !w.pointerPeek() { + w.valPush(reflect.Indirect(w.valPop())) + return + } + + v := w.valPop() + + // If the expected type is a pointer to an interface of any depth, + // such as *interface{}, **interface{}, etc., then we need to convert + // the value "v" from *CONCRETE to *interface{} so types match for + // Set. + // + // Example if v is type *Foo where Foo is a struct, v would become + // *interface{} instead. This only happens if we have an interface expectation + // at this depth. + // + // For more info, see GH-16 + if iType, ok := w.ifaceTypes[ifaceKey(w.ps[w.depth], w.depth)]; ok && iType.Kind() == reflect.Interface { + y := reflect.New(iType) // Create *interface{} + y.Elem().Set(reflect.Indirect(v)) // Assign "Foo" to interface{} (dereferenced) + v = y // v is now typed *interface{} (where *v = Foo) + } + + for i := 1; i < w.ps[w.depth]; i++ { + if iType, ok := w.ifaceTypes[ifaceKey(w.ps[w.depth]-i, w.depth)]; ok { + iface := reflect.New(iType).Elem() + iface.Set(v) + v = iface + } + + p := reflect.New(v.Type()) + p.Elem().Set(v) + v = p + } + + w.valPush(v) +} + +// if this value is a Locker, lock it and add it to the locks slice +func (w *walker) lock(v reflect.Value) { + if !w.useLocks { + return + } + + if !v.IsValid() || !v.CanInterface() { + return + } + + type rlocker interface { + RLocker() sync.Locker + } + + var locker sync.Locker + + // We can't call Interface() on a value directly, since that requires + // a copy. This is OK, since the pointer to a value which is a sync.Locker + // is also a sync.Locker. + if v.Kind() == reflect.Ptr { + switch l := v.Interface().(type) { + case rlocker: + // don't lock a mutex directly + if _, ok := l.(*sync.RWMutex); !ok { + locker = l.RLocker() + } + case sync.Locker: + locker = l + } + } else if v.CanAddr() { + switch l := v.Addr().Interface().(type) { + case rlocker: + // don't lock a mutex directly + if _, ok := l.(*sync.RWMutex); !ok { + locker = l.RLocker() + } + case sync.Locker: + locker = l + } + } + + // still no callable locker + if locker == nil { + return + } + + // don't lock a mutex directly + switch locker.(type) { + case *sync.Mutex, *sync.RWMutex: + return + } + + locker.Lock() + w.locks[w.depth] = locker +} + +// wrapPtr is a helper that takes v and always make it *v. copystructure +// stores things internally as pointers until the last moment before unwrapping +func wrapPtr(v reflect.Value) reflect.Value { + if !v.IsValid() { + return v + } + vPtr := reflect.New(v.Type()) + vPtr.Elem().Set(v) + return vPtr +} diff --git a/vendor/github.com/mitchellh/copystructure/go.mod b/vendor/github.com/mitchellh/copystructure/go.mod new file mode 100644 index 000000000..d01864309 --- /dev/null +++ b/vendor/github.com/mitchellh/copystructure/go.mod @@ -0,0 +1,3 @@ +module github.com/mitchellh/copystructure + +require github.com/mitchellh/reflectwalk v1.0.0 diff --git a/vendor/github.com/mitchellh/copystructure/go.sum b/vendor/github.com/mitchellh/copystructure/go.sum new file mode 100644 index 000000000..be5724561 --- /dev/null +++ b/vendor/github.com/mitchellh/copystructure/go.sum @@ -0,0 +1,2 @@ +github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY= +github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= diff --git a/vendor/github.com/mitchellh/reflectwalk/.travis.yml b/vendor/github.com/mitchellh/reflectwalk/.travis.yml new file mode 100644 index 000000000..4f2ee4d97 --- /dev/null +++ b/vendor/github.com/mitchellh/reflectwalk/.travis.yml @@ -0,0 +1 @@ +language: go diff --git a/vendor/github.com/mitchellh/reflectwalk/LICENSE b/vendor/github.com/mitchellh/reflectwalk/LICENSE new file mode 100644 index 000000000..f9c841a51 --- /dev/null +++ b/vendor/github.com/mitchellh/reflectwalk/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2013 Mitchell Hashimoto + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/github.com/mitchellh/reflectwalk/README.md b/vendor/github.com/mitchellh/reflectwalk/README.md new file mode 100644 index 000000000..ac82cd2e1 --- /dev/null +++ b/vendor/github.com/mitchellh/reflectwalk/README.md @@ -0,0 +1,6 @@ +# reflectwalk + +reflectwalk is a Go library for "walking" a value in Go using reflection, +in the same way a directory tree can be "walked" on the filesystem. Walking +a complex structure can allow you to do manipulations on unknown structures +such as those decoded from JSON. diff --git a/vendor/github.com/mitchellh/reflectwalk/go.mod b/vendor/github.com/mitchellh/reflectwalk/go.mod new file mode 100644 index 000000000..52bb7c469 --- /dev/null +++ b/vendor/github.com/mitchellh/reflectwalk/go.mod @@ -0,0 +1 @@ +module github.com/mitchellh/reflectwalk diff --git a/vendor/github.com/mitchellh/reflectwalk/location.go b/vendor/github.com/mitchellh/reflectwalk/location.go new file mode 100644 index 000000000..6a7f17611 --- /dev/null +++ b/vendor/github.com/mitchellh/reflectwalk/location.go @@ -0,0 +1,19 @@ +package reflectwalk + +//go:generate stringer -type=Location location.go + +type Location uint + +const ( + None Location = iota + Map + MapKey + MapValue + Slice + SliceElem + Array + ArrayElem + Struct + StructField + WalkLoc +) diff --git a/vendor/github.com/mitchellh/reflectwalk/location_string.go b/vendor/github.com/mitchellh/reflectwalk/location_string.go new file mode 100644 index 000000000..70760cf4c --- /dev/null +++ b/vendor/github.com/mitchellh/reflectwalk/location_string.go @@ -0,0 +1,16 @@ +// Code generated by "stringer -type=Location location.go"; DO NOT EDIT. + +package reflectwalk + +import "fmt" + +const _Location_name = "NoneMapMapKeyMapValueSliceSliceElemArrayArrayElemStructStructFieldWalkLoc" + +var _Location_index = [...]uint8{0, 4, 7, 13, 21, 26, 35, 40, 49, 55, 66, 73} + +func (i Location) String() string { + if i >= Location(len(_Location_index)-1) { + return fmt.Sprintf("Location(%d)", i) + } + return _Location_name[_Location_index[i]:_Location_index[i+1]] +} diff --git a/vendor/github.com/mitchellh/reflectwalk/reflectwalk.go b/vendor/github.com/mitchellh/reflectwalk/reflectwalk.go new file mode 100644 index 000000000..d7ab7b6d7 --- /dev/null +++ b/vendor/github.com/mitchellh/reflectwalk/reflectwalk.go @@ -0,0 +1,401 @@ +// reflectwalk is a package that allows you to "walk" complex structures +// similar to how you may "walk" a filesystem: visiting every element one +// by one and calling callback functions allowing you to handle and manipulate +// those elements. +package reflectwalk + +import ( + "errors" + "reflect" +) + +// PrimitiveWalker implementations are able to handle primitive values +// within complex structures. Primitive values are numbers, strings, +// booleans, funcs, chans. +// +// These primitive values are often members of more complex +// structures (slices, maps, etc.) that are walkable by other interfaces. +type PrimitiveWalker interface { + Primitive(reflect.Value) error +} + +// InterfaceWalker implementations are able to handle interface values as they +// are encountered during the walk. +type InterfaceWalker interface { + Interface(reflect.Value) error +} + +// MapWalker implementations are able to handle individual elements +// found within a map structure. +type MapWalker interface { + Map(m reflect.Value) error + MapElem(m, k, v reflect.Value) error +} + +// SliceWalker implementations are able to handle slice elements found +// within complex structures. +type SliceWalker interface { + Slice(reflect.Value) error + SliceElem(int, reflect.Value) error +} + +// ArrayWalker implementations are able to handle array elements found +// within complex structures. +type ArrayWalker interface { + Array(reflect.Value) error + ArrayElem(int, reflect.Value) error +} + +// StructWalker is an interface that has methods that are called for +// structs when a Walk is done. +type StructWalker interface { + Struct(reflect.Value) error + StructField(reflect.StructField, reflect.Value) error +} + +// EnterExitWalker implementations are notified before and after +// they walk deeper into complex structures (into struct fields, +// into slice elements, etc.) +type EnterExitWalker interface { + Enter(Location) error + Exit(Location) error +} + +// PointerWalker implementations are notified when the value they're +// walking is a pointer or not. Pointer is called for _every_ value whether +// it is a pointer or not. +type PointerWalker interface { + PointerEnter(bool) error + PointerExit(bool) error +} + +// SkipEntry can be returned from walk functions to skip walking +// the value of this field. This is only valid in the following functions: +// +// - Struct: skips all fields from being walked +// - StructField: skips walking the struct value +// +var SkipEntry = errors.New("skip this entry") + +// Walk takes an arbitrary value and an interface and traverses the +// value, calling callbacks on the interface if they are supported. +// The interface should implement one or more of the walker interfaces +// in this package, such as PrimitiveWalker, StructWalker, etc. +func Walk(data, walker interface{}) (err error) { + v := reflect.ValueOf(data) + ew, ok := walker.(EnterExitWalker) + if ok { + err = ew.Enter(WalkLoc) + } + + if err == nil { + err = walk(v, walker) + } + + if ok && err == nil { + err = ew.Exit(WalkLoc) + } + + return +} + +func walk(v reflect.Value, w interface{}) (err error) { + // Determine if we're receiving a pointer and if so notify the walker. + // The logic here is convoluted but very important (tests will fail if + // almost any part is changed). I will try to explain here. + // + // First, we check if the value is an interface, if so, we really need + // to check the interface's VALUE to see whether it is a pointer. + // + // Check whether the value is then a pointer. If so, then set pointer + // to true to notify the user. + // + // If we still have a pointer or an interface after the indirections, then + // we unwrap another level + // + // At this time, we also set "v" to be the dereferenced value. This is + // because once we've unwrapped the pointer we want to use that value. + pointer := false + pointerV := v + + for { + if pointerV.Kind() == reflect.Interface { + if iw, ok := w.(InterfaceWalker); ok { + if err = iw.Interface(pointerV); err != nil { + return + } + } + + pointerV = pointerV.Elem() + } + + if pointerV.Kind() == reflect.Ptr { + pointer = true + v = reflect.Indirect(pointerV) + } + if pw, ok := w.(PointerWalker); ok { + if err = pw.PointerEnter(pointer); err != nil { + return + } + + defer func(pointer bool) { + if err != nil { + return + } + + err = pw.PointerExit(pointer) + }(pointer) + } + + if pointer { + pointerV = v + } + pointer = false + + // If we still have a pointer or interface we have to indirect another level. + switch pointerV.Kind() { + case reflect.Ptr, reflect.Interface: + continue + } + break + } + + // We preserve the original value here because if it is an interface + // type, we want to pass that directly into the walkPrimitive, so that + // we can set it. + originalV := v + if v.Kind() == reflect.Interface { + v = v.Elem() + } + + k := v.Kind() + if k >= reflect.Int && k <= reflect.Complex128 { + k = reflect.Int + } + + switch k { + // Primitives + case reflect.Bool, reflect.Chan, reflect.Func, reflect.Int, reflect.String, reflect.Invalid: + err = walkPrimitive(originalV, w) + return + case reflect.Map: + err = walkMap(v, w) + return + case reflect.Slice: + err = walkSlice(v, w) + return + case reflect.Struct: + err = walkStruct(v, w) + return + case reflect.Array: + err = walkArray(v, w) + return + default: + panic("unsupported type: " + k.String()) + } +} + +func walkMap(v reflect.Value, w interface{}) error { + ew, ewok := w.(EnterExitWalker) + if ewok { + ew.Enter(Map) + } + + if mw, ok := w.(MapWalker); ok { + if err := mw.Map(v); err != nil { + return err + } + } + + for _, k := range v.MapKeys() { + kv := v.MapIndex(k) + + if mw, ok := w.(MapWalker); ok { + if err := mw.MapElem(v, k, kv); err != nil { + return err + } + } + + ew, ok := w.(EnterExitWalker) + if ok { + ew.Enter(MapKey) + } + + if err := walk(k, w); err != nil { + return err + } + + if ok { + ew.Exit(MapKey) + ew.Enter(MapValue) + } + + if err := walk(kv, w); err != nil { + return err + } + + if ok { + ew.Exit(MapValue) + } + } + + if ewok { + ew.Exit(Map) + } + + return nil +} + +func walkPrimitive(v reflect.Value, w interface{}) error { + if pw, ok := w.(PrimitiveWalker); ok { + return pw.Primitive(v) + } + + return nil +} + +func walkSlice(v reflect.Value, w interface{}) (err error) { + ew, ok := w.(EnterExitWalker) + if ok { + ew.Enter(Slice) + } + + if sw, ok := w.(SliceWalker); ok { + if err := sw.Slice(v); err != nil { + return err + } + } + + for i := 0; i < v.Len(); i++ { + elem := v.Index(i) + + if sw, ok := w.(SliceWalker); ok { + if err := sw.SliceElem(i, elem); err != nil { + return err + } + } + + ew, ok := w.(EnterExitWalker) + if ok { + ew.Enter(SliceElem) + } + + if err := walk(elem, w); err != nil { + return err + } + + if ok { + ew.Exit(SliceElem) + } + } + + ew, ok = w.(EnterExitWalker) + if ok { + ew.Exit(Slice) + } + + return nil +} + +func walkArray(v reflect.Value, w interface{}) (err error) { + ew, ok := w.(EnterExitWalker) + if ok { + ew.Enter(Array) + } + + if aw, ok := w.(ArrayWalker); ok { + if err := aw.Array(v); err != nil { + return err + } + } + + for i := 0; i < v.Len(); i++ { + elem := v.Index(i) + + if aw, ok := w.(ArrayWalker); ok { + if err := aw.ArrayElem(i, elem); err != nil { + return err + } + } + + ew, ok := w.(EnterExitWalker) + if ok { + ew.Enter(ArrayElem) + } + + if err := walk(elem, w); err != nil { + return err + } + + if ok { + ew.Exit(ArrayElem) + } + } + + ew, ok = w.(EnterExitWalker) + if ok { + ew.Exit(Array) + } + + return nil +} + +func walkStruct(v reflect.Value, w interface{}) (err error) { + ew, ewok := w.(EnterExitWalker) + if ewok { + ew.Enter(Struct) + } + + skip := false + if sw, ok := w.(StructWalker); ok { + err = sw.Struct(v) + if err == SkipEntry { + skip = true + err = nil + } + if err != nil { + return + } + } + + if !skip { + vt := v.Type() + for i := 0; i < vt.NumField(); i++ { + sf := vt.Field(i) + f := v.FieldByIndex([]int{i}) + + if sw, ok := w.(StructWalker); ok { + err = sw.StructField(sf, f) + + // SkipEntry just pretends this field doesn't even exist + if err == SkipEntry { + continue + } + + if err != nil { + return + } + } + + ew, ok := w.(EnterExitWalker) + if ok { + ew.Enter(StructField) + } + + err = walk(f, w) + if err != nil { + return + } + + if ok { + ew.Exit(StructField) + } + } + } + + if ewok { + ew.Exit(Struct) + } + + return nil +} diff --git a/vendor/golang.org/x/crypto/scrypt/scrypt.go b/vendor/golang.org/x/crypto/scrypt/scrypt.go new file mode 100644 index 000000000..2f81fe414 --- /dev/null +++ b/vendor/golang.org/x/crypto/scrypt/scrypt.go @@ -0,0 +1,213 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package scrypt implements the scrypt key derivation function as defined in +// Colin Percival's paper "Stronger Key Derivation via Sequential Memory-Hard +// Functions" (https://www.tarsnap.com/scrypt/scrypt.pdf). +package scrypt // import "golang.org/x/crypto/scrypt" + +import ( + "crypto/sha256" + "errors" + "math/bits" + + "golang.org/x/crypto/pbkdf2" +) + +const maxInt = int(^uint(0) >> 1) + +// blockCopy copies n numbers from src into dst. +func blockCopy(dst, src []uint32, n int) { + copy(dst, src[:n]) +} + +// blockXOR XORs numbers from dst with n numbers from src. +func blockXOR(dst, src []uint32, n int) { + for i, v := range src[:n] { + dst[i] ^= v + } +} + +// salsaXOR applies Salsa20/8 to the XOR of 16 numbers from tmp and in, +// and puts the result into both tmp and out. +func salsaXOR(tmp *[16]uint32, in, out []uint32) { + w0 := tmp[0] ^ in[0] + w1 := tmp[1] ^ in[1] + w2 := tmp[2] ^ in[2] + w3 := tmp[3] ^ in[3] + w4 := tmp[4] ^ in[4] + w5 := tmp[5] ^ in[5] + w6 := tmp[6] ^ in[6] + w7 := tmp[7] ^ in[7] + w8 := tmp[8] ^ in[8] + w9 := tmp[9] ^ in[9] + w10 := tmp[10] ^ in[10] + w11 := tmp[11] ^ in[11] + w12 := tmp[12] ^ in[12] + w13 := tmp[13] ^ in[13] + w14 := tmp[14] ^ in[14] + w15 := tmp[15] ^ in[15] + + x0, x1, x2, x3, x4, x5, x6, x7, x8 := w0, w1, w2, w3, w4, w5, w6, w7, w8 + x9, x10, x11, x12, x13, x14, x15 := w9, w10, w11, w12, w13, w14, w15 + + for i := 0; i < 8; i += 2 { + x4 ^= bits.RotateLeft32(x0+x12, 7) + x8 ^= bits.RotateLeft32(x4+x0, 9) + x12 ^= bits.RotateLeft32(x8+x4, 13) + x0 ^= bits.RotateLeft32(x12+x8, 18) + + x9 ^= bits.RotateLeft32(x5+x1, 7) + x13 ^= bits.RotateLeft32(x9+x5, 9) + x1 ^= bits.RotateLeft32(x13+x9, 13) + x5 ^= bits.RotateLeft32(x1+x13, 18) + + x14 ^= bits.RotateLeft32(x10+x6, 7) + x2 ^= bits.RotateLeft32(x14+x10, 9) + x6 ^= bits.RotateLeft32(x2+x14, 13) + x10 ^= bits.RotateLeft32(x6+x2, 18) + + x3 ^= bits.RotateLeft32(x15+x11, 7) + x7 ^= bits.RotateLeft32(x3+x15, 9) + x11 ^= bits.RotateLeft32(x7+x3, 13) + x15 ^= bits.RotateLeft32(x11+x7, 18) + + x1 ^= bits.RotateLeft32(x0+x3, 7) + x2 ^= bits.RotateLeft32(x1+x0, 9) + x3 ^= bits.RotateLeft32(x2+x1, 13) + x0 ^= bits.RotateLeft32(x3+x2, 18) + + x6 ^= bits.RotateLeft32(x5+x4, 7) + x7 ^= bits.RotateLeft32(x6+x5, 9) + x4 ^= bits.RotateLeft32(x7+x6, 13) + x5 ^= bits.RotateLeft32(x4+x7, 18) + + x11 ^= bits.RotateLeft32(x10+x9, 7) + x8 ^= bits.RotateLeft32(x11+x10, 9) + x9 ^= bits.RotateLeft32(x8+x11, 13) + x10 ^= bits.RotateLeft32(x9+x8, 18) + + x12 ^= bits.RotateLeft32(x15+x14, 7) + x13 ^= bits.RotateLeft32(x12+x15, 9) + x14 ^= bits.RotateLeft32(x13+x12, 13) + x15 ^= bits.RotateLeft32(x14+x13, 18) + } + x0 += w0 + x1 += w1 + x2 += w2 + x3 += w3 + x4 += w4 + x5 += w5 + x6 += w6 + x7 += w7 + x8 += w8 + x9 += w9 + x10 += w10 + x11 += w11 + x12 += w12 + x13 += w13 + x14 += w14 + x15 += w15 + + out[0], tmp[0] = x0, x0 + out[1], tmp[1] = x1, x1 + out[2], tmp[2] = x2, x2 + out[3], tmp[3] = x3, x3 + out[4], tmp[4] = x4, x4 + out[5], tmp[5] = x5, x5 + out[6], tmp[6] = x6, x6 + out[7], tmp[7] = x7, x7 + out[8], tmp[8] = x8, x8 + out[9], tmp[9] = x9, x9 + out[10], tmp[10] = x10, x10 + out[11], tmp[11] = x11, x11 + out[12], tmp[12] = x12, x12 + out[13], tmp[13] = x13, x13 + out[14], tmp[14] = x14, x14 + out[15], tmp[15] = x15, x15 +} + +func blockMix(tmp *[16]uint32, in, out []uint32, r int) { + blockCopy(tmp[:], in[(2*r-1)*16:], 16) + for i := 0; i < 2*r; i += 2 { + salsaXOR(tmp, in[i*16:], out[i*8:]) + salsaXOR(tmp, in[i*16+16:], out[i*8+r*16:]) + } +} + +func integer(b []uint32, r int) uint64 { + j := (2*r - 1) * 16 + return uint64(b[j]) | uint64(b[j+1])<<32 +} + +func smix(b []byte, r, N int, v, xy []uint32) { + var tmp [16]uint32 + x := xy + y := xy[32*r:] + + j := 0 + for i := 0; i < 32*r; i++ { + x[i] = uint32(b[j]) | uint32(b[j+1])<<8 | uint32(b[j+2])<<16 | uint32(b[j+3])<<24 + j += 4 + } + for i := 0; i < N; i += 2 { + blockCopy(v[i*(32*r):], x, 32*r) + blockMix(&tmp, x, y, r) + + blockCopy(v[(i+1)*(32*r):], y, 32*r) + blockMix(&tmp, y, x, r) + } + for i := 0; i < N; i += 2 { + j := int(integer(x, r) & uint64(N-1)) + blockXOR(x, v[j*(32*r):], 32*r) + blockMix(&tmp, x, y, r) + + j = int(integer(y, r) & uint64(N-1)) + blockXOR(y, v[j*(32*r):], 32*r) + blockMix(&tmp, y, x, r) + } + j = 0 + for _, v := range x[:32*r] { + b[j+0] = byte(v >> 0) + b[j+1] = byte(v >> 8) + b[j+2] = byte(v >> 16) + b[j+3] = byte(v >> 24) + j += 4 + } +} + +// Key derives a key from the password, salt, and cost parameters, returning +// a byte slice of length keyLen that can be used as cryptographic key. +// +// N is a CPU/memory cost parameter, which must be a power of two greater than 1. +// r and p must satisfy r * p < 2³⁰. If the parameters do not satisfy the +// limits, the function returns a nil byte slice and an error. +// +// For example, you can get a derived key for e.g. AES-256 (which needs a +// 32-byte key) by doing: +// +// dk, err := scrypt.Key([]byte("some password"), salt, 32768, 8, 1, 32) +// +// The recommended parameters for interactive logins as of 2017 are N=32768, r=8 +// and p=1. The parameters N, r, and p should be increased as memory latency and +// CPU parallelism increases; consider setting N to the highest power of 2 you +// can derive within 100 milliseconds. Remember to get a good random salt. +func Key(password, salt []byte, N, r, p, keyLen int) ([]byte, error) { + if N <= 1 || N&(N-1) != 0 { + return nil, errors.New("scrypt: N must be > 1 and a power of 2") + } + if uint64(r)*uint64(p) >= 1<<30 || r > maxInt/128/p || r > maxInt/256 || N > maxInt/128/r { + return nil, errors.New("scrypt: parameters are too large") + } + + xy := make([]uint32, 64*r) + v := make([]uint32, 32*N*r) + b := pbkdf2.Key(password, salt, 1, p*128*r, sha256.New) + + for i := 0; i < p; i++ { + smix(b[i*128*r:], r, N, v, xy) + } + + return pbkdf2.Key(password, b, 1, keyLen, sha256.New), nil +} diff --git a/vendor/gopkg.in/yaml.v2/apic.go b/vendor/gopkg.in/yaml.v2/apic.go index 1f7e87e67..d2c2308f1 100644 --- a/vendor/gopkg.in/yaml.v2/apic.go +++ b/vendor/gopkg.in/yaml.v2/apic.go @@ -86,6 +86,7 @@ func yaml_emitter_initialize(emitter *yaml_emitter_t) { raw_buffer: make([]byte, 0, output_raw_buffer_size), states: make([]yaml_emitter_state_t, 0, initial_stack_size), events: make([]yaml_event_t, 0, initial_queue_size), + best_width: -1, } } diff --git a/vendor/modules.txt b/vendor/modules.txt index cf12a3e32..c4b082613 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -6,6 +6,12 @@ github.com/360EntSecGroup-Skylar/excelize/v2 github.com/766b/chi-prometheus # github.com/99designs/basicauth-go v0.0.0-20160802081356-2a93ba0f464d github.com/99designs/basicauth-go +# github.com/Masterminds/goutils v1.1.0 +github.com/Masterminds/goutils +# github.com/Masterminds/semver v1.5.0 +github.com/Masterminds/semver +# github.com/Masterminds/sprig v2.22.0+incompatible +github.com/Masterminds/sprig # github.com/Masterminds/squirrel v1.1.1-0.20191017225151-12f2162c8d8d github.com/Masterminds/squirrel # github.com/PaesslerAG/gval v1.0.1 @@ -27,6 +33,8 @@ github.com/dgrijalva/jwt-go github.com/disintegration/imaging # github.com/edwvee/exiffix v0.0.0-20180602190213-b57537c92a6b github.com/edwvee/exiffix +# github.com/fsnotify/fsnotify v1.4.9 +github.com/fsnotify/fsnotify # github.com/gabriel-vasile/mimetype v0.3.17 github.com/gabriel-vasile/mimetype github.com/gabriel-vasile/mimetype/internal/json @@ -54,6 +62,8 @@ github.com/golang/protobuf/ptypes/timestamp # github.com/gomodule/redigo v2.0.0+incompatible github.com/gomodule/redigo/internal github.com/gomodule/redigo/redis +# github.com/google/uuid v1.1.1 +github.com/google/uuid # github.com/gorhill/cronexpr v0.0.0-20180427100037-88b0669f7d75 github.com/gorhill/cronexpr # github.com/gorilla/context v1.1.1 @@ -68,6 +78,10 @@ github.com/gorilla/sessions github.com/gorilla/websocket # github.com/goware/statik v0.2.0 github.com/goware/statik/fs +# github.com/huandu/xstrings v1.3.2 +github.com/huandu/xstrings +# github.com/imdario/mergo v0.3.11 +github.com/imdario/mergo # github.com/inconshreveable/mousetrap v1.0.0 github.com/inconshreveable/mousetrap # github.com/jmoiron/sqlx v1.2.0 @@ -108,8 +122,12 @@ github.com/minio/minio-go/v6/pkg/set github.com/minio/sha256-simd # github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db github.com/mitchellh/colorstring +# github.com/mitchellh/copystructure v1.0.0 +github.com/mitchellh/copystructure # github.com/mitchellh/go-homedir v1.1.0 github.com/mitchellh/go-homedir +# github.com/mitchellh/reflectwalk v1.0.0 +github.com/mitchellh/reflectwalk # github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 github.com/mohae/deepcopy # github.com/pkg/errors v0.8.1 @@ -178,6 +196,7 @@ golang.org/x/crypto/blowfish golang.org/x/crypto/ed25519 golang.org/x/crypto/ed25519/internal/edwards25519 golang.org/x/crypto/pbkdf2 +golang.org/x/crypto/scrypt golang.org/x/crypto/ssh/terminal # golang.org/x/image v0.0.0-20190910094157-69e4b8554b2a golang.org/x/image/bmp @@ -272,7 +291,7 @@ gopkg.in/mail.v2 gopkg.in/square/go-jose.v2 gopkg.in/square/go-jose.v2/cipher gopkg.in/square/go-jose.v2/json -# gopkg.in/yaml.v2 v2.2.8 +# gopkg.in/yaml.v2 v2.3.0 gopkg.in/yaml.v2 # gopkg.in/yaml.v3 v3.0.0-20200601152816-913338de1bd2 gopkg.in/yaml.v3