diff --git a/api/compose/spec.json b/api/compose/spec.json index d14d4b019..30286ec97 100644 --- a/api/compose/spec.json +++ b/api/compose/spec.json @@ -746,6 +746,12 @@ "required": false, "title": "Filtering condition (same as query, deprecated)" }, + { + "name": "deleted", + "required": false, + "title": "Exclude (0, default), include (1) or return only (2) deleted records", + "type": "uint" + }, {"type": "uint", "name": "limit", "title": "Limit"}, {"type": "uint", "name": "offset", "title": "Offset"}, {"type": "uint", "name": "page", "title": "Page number (1-based)"}, diff --git a/api/compose/spec/record.json b/api/compose/spec/record.json index 4908aa046..0c59ce6b2 100644 --- a/api/compose/spec/record.json +++ b/api/compose/spec/record.json @@ -76,6 +76,12 @@ "title": "Filtering condition (same as query, deprecated)", "type": "string" }, + { + "name": "deleted", + "required": false, + "title": "Exclude (0, default), include (1) or return only (2) deleted records", + "type": "uint" + }, { "name": "limit", "title": "Limit", diff --git a/compose/repository/record.go b/compose/repository/record.go index f65c3d8ca..70ae66996 100644 --- a/compose/repository/record.go +++ b/compose/repository/record.go @@ -77,8 +77,7 @@ func (r record) columns() []string { func (r record) query() squirrel.SelectBuilder { return squirrel. Select(r.columns()...). - From(r.table() + " AS r"). - Where("r.deleted_at IS NULL") + From(r.table() + " AS r") } // @todo: update to accepted DeletedAt column semantics from Messaging @@ -92,6 +91,7 @@ func (r record) findOneBy(namespaceID uint64, field string, value interface{}) ( 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) @@ -172,6 +172,9 @@ func (r record) buildQuery(module *types.Module, f types.RecordFilter) (query sq return false } + // Inc/exclude deleted records according to filter settings + query = rh.FilterNullByState(query, "r.deleted_at", f.Deleted) + // Parse filters. if f.Query != "" { var ( diff --git a/compose/repository/record_test.go b/compose/repository/record_test.go index 01a4631c2..a5214084b 100644 --- a/compose/repository/record_test.go +++ b/compose/repository/record_test.go @@ -2,6 +2,7 @@ package repository import ( "fmt" + "github.com/cortezaproject/corteza-server/pkg/rh" "strings" "testing" @@ -22,13 +23,19 @@ func TestRecordFinder(t *testing.T) { } ttc := []struct { - f types.RecordFilter - match []string - args []interface{} - err error + f types.RecordFilter + match []string + noMatch []string + args []interface{} + err error }{ { - 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.deleted_at IS NULL AND r.module_id = ? AND r.rel_namespace = ?"}, + 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", + }, }, { f: types.RecordFilter{Query: "id = 5 AND foo = 7"}, @@ -45,6 +52,18 @@ func TestRecordFinder(t *testing.T) { }, args: []interface{}{"bar"}, }, + { + f: types.RecordFilter{Deleted: rh.FilterStateExcluded}, + match: []string{" r.deleted_at IS "}, + }, + { + f: types.RecordFilter{Deleted: rh.FilterStateInclusive}, + noMatch: []string{" r.deleted_at IS "}, + }, + { + f: types.RecordFilter{Deleted: rh.FilterStateExclusive}, + match: []string{" r.deleted_at IS NOT NULL"}, + }, } for _, tc := range ttc { @@ -64,6 +83,12 @@ func TestRecordFinder(t *testing.T) { " 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 "+ diff --git a/compose/rest/record.go b/compose/rest/record.go index e7337ebdd..dc153331b 100644 --- a/compose/rest/record.go +++ b/compose/rest/record.go @@ -82,6 +82,8 @@ func (ctrl *Record) List(ctx context.Context, r *request.RecordList) (interface{ ModuleID: r.ModuleID, Sort: r.Sort, + Deleted: rh.FilterState(r.Deleted), + PageFilter: rh.Paging(r), } ) diff --git a/compose/rest/request/record.go b/compose/rest/request/record.go index 2d35b0b2b..3abfeac93 100644 --- a/compose/rest/request/record.go +++ b/compose/rest/request/record.go @@ -138,6 +138,10 @@ type RecordList struct { rawFilter string Filter string + hasDeleted bool + rawDeleted string + Deleted uint + hasLimit bool rawLimit string Limit uint @@ -178,6 +182,7 @@ func (r RecordList) Auditable() map[string]interface{} { out["query"] = r.Query out["filter"] = r.Filter + out["deleted"] = r.Deleted out["limit"] = r.Limit out["offset"] = r.Offset out["page"] = r.Page @@ -227,6 +232,11 @@ func (r *RecordList) Fill(req *http.Request) (err error) { r.rawFilter = val r.Filter = val } + if val, ok := get["deleted"]; ok { + r.hasDeleted = true + r.rawDeleted = val + r.Deleted = parseUint(val) + } if val, ok := get["limit"]; ok { r.hasLimit = true r.rawLimit = val @@ -1448,6 +1458,21 @@ func (r *RecordList) GetFilter() string { return r.Filter } +// HasDeleted returns true if deleted was set +func (r *RecordList) HasDeleted() bool { + return r.hasDeleted +} + +// RawDeleted returns raw value of deleted parameter +func (r *RecordList) RawDeleted() string { + return r.rawDeleted +} + +// GetDeleted returns casted value of deleted parameter +func (r *RecordList) GetDeleted() uint { + return r.Deleted +} + // HasLimit returns true if limit was set func (r *RecordList) HasLimit() bool { return r.hasLimit diff --git a/compose/types/record.go b/compose/types/record.go index 15c0293be..5dfbb8a7f 100644 --- a/compose/types/record.go +++ b/compose/types/record.go @@ -35,6 +35,8 @@ type ( // Standard paging fields & helpers rh.PageFilter + + Deleted rh.FilterState `json:"deleted"` } ) diff --git a/docs/compose/README.md b/docs/compose/README.md index 2c3dad016..5ea585f76 100644 --- a/docs/compose/README.md +++ b/docs/compose/README.md @@ -872,6 +872,7 @@ Compose records | --------- | ---- | ------ | ----------- | ------- | --------- | | query | string | GET | Record filtering query | N/A | NO | | filter | string | GET | Filtering condition (same as query, deprecated) | N/A | NO | +| deleted | uint | GET | Exclude (0, default), include (1) or return only (2) deleted records | N/A | NO | | limit | uint | GET | Limit | N/A | NO | | offset | uint | GET | Offset | N/A | NO | | page | uint | GET | Page number (1-based) | N/A | NO |