Adds the main Property System Architecture components (#29644)

* Adds the main Property System Architecture components

This change adds the necessary migrations for the Property Groups,
Fields and Values tables to be created, the store layer and a Property
Service that can be used from the app layer.

* Update property field type to use user instead of person

* Update PropertyFields to allow for unique nondeleted fields and remove redundant indexes

* Update PropertyValues to allow for unique nondeleted fields and remove redundant indexes

* Use StringMap instead of the map[string]any on property fields

* Add i18n strings

* Revert "Use StringMap instead of the map[string]any on property fields"

This reverts commit e2735ab0f8589d2524d636419ca0cb144575c4d6.

* Cast JSON binary data to string and add todo note for StringMap use

* Add mocks to the retrylayer tests

* Cast JSON binary data to string in property value store

* Check for binary parameter instead of casting to string for JSON data

* Check property field type is one of the allowed ones

* Avoid reusing err variable to be explicit about the returned value

* Merge Property System Migrations into one file

* Adds NOT NULL to timestamps at the DB level

* Update stores to use tableSelectQuery instead of a slice var

* Update PropertyField model translations to be more explicit and avoid repetition

* Update PropertyValue model translations to be more explicit and avoid repetition

* Use ExecBuilder instead of ToSql&Exec

* Update property field errors to add context

* Ensure PerPage is greater than zero

* Update store errors to give more context

* Use ExecBuilder in the property stores where possible

* Add an on conflict suffix to the group register to avoid race conditions

* Remove badly used translation string

* Remove unused get in register group method

---------

Co-authored-by: Mattermost Build <build@mattermost.com>
Этот коммит содержится в:
Miguel de la Cruz
2025-01-13 12:41:44 +01:00
коммит произвёл GitHub
родитель d2b334e605
Коммит ecdce71fc4
35 изменённых файлов: 3793 добавлений и 0 удалений

Просмотреть файл

@@ -0,0 +1,197 @@
// Code generated by mockery v2.42.2. DO NOT EDIT.
// Regenerate this file using `make store-mocks`.
package mocks
import (
model "github.com/mattermost/mattermost/server/public/model"
mock "github.com/stretchr/testify/mock"
)
// PropertyFieldStore is an autogenerated mock type for the PropertyFieldStore type
type PropertyFieldStore struct {
mock.Mock
}
// Create provides a mock function with given fields: field
func (_m *PropertyFieldStore) Create(field *model.PropertyField) (*model.PropertyField, error) {
ret := _m.Called(field)
if len(ret) == 0 {
panic("no return value specified for Create")
}
var r0 *model.PropertyField
var r1 error
if rf, ok := ret.Get(0).(func(*model.PropertyField) (*model.PropertyField, error)); ok {
return rf(field)
}
if rf, ok := ret.Get(0).(func(*model.PropertyField) *model.PropertyField); ok {
r0 = rf(field)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*model.PropertyField)
}
}
if rf, ok := ret.Get(1).(func(*model.PropertyField) error); ok {
r1 = rf(field)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Delete provides a mock function with given fields: id
func (_m *PropertyFieldStore) Delete(id string) error {
ret := _m.Called(id)
if len(ret) == 0 {
panic("no return value specified for Delete")
}
var r0 error
if rf, ok := ret.Get(0).(func(string) error); ok {
r0 = rf(id)
} else {
r0 = ret.Error(0)
}
return r0
}
// Get provides a mock function with given fields: id
func (_m *PropertyFieldStore) Get(id string) (*model.PropertyField, error) {
ret := _m.Called(id)
if len(ret) == 0 {
panic("no return value specified for Get")
}
var r0 *model.PropertyField
var r1 error
if rf, ok := ret.Get(0).(func(string) (*model.PropertyField, error)); ok {
return rf(id)
}
if rf, ok := ret.Get(0).(func(string) *model.PropertyField); ok {
r0 = rf(id)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*model.PropertyField)
}
}
if rf, ok := ret.Get(1).(func(string) error); ok {
r1 = rf(id)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GetMany provides a mock function with given fields: ids
func (_m *PropertyFieldStore) GetMany(ids []string) ([]*model.PropertyField, error) {
ret := _m.Called(ids)
if len(ret) == 0 {
panic("no return value specified for GetMany")
}
var r0 []*model.PropertyField
var r1 error
if rf, ok := ret.Get(0).(func([]string) ([]*model.PropertyField, error)); ok {
return rf(ids)
}
if rf, ok := ret.Get(0).(func([]string) []*model.PropertyField); ok {
r0 = rf(ids)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*model.PropertyField)
}
}
if rf, ok := ret.Get(1).(func([]string) error); ok {
r1 = rf(ids)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// SearchPropertyFields provides a mock function with given fields: opts
func (_m *PropertyFieldStore) SearchPropertyFields(opts model.PropertyFieldSearchOpts) ([]*model.PropertyField, error) {
ret := _m.Called(opts)
if len(ret) == 0 {
panic("no return value specified for SearchPropertyFields")
}
var r0 []*model.PropertyField
var r1 error
if rf, ok := ret.Get(0).(func(model.PropertyFieldSearchOpts) ([]*model.PropertyField, error)); ok {
return rf(opts)
}
if rf, ok := ret.Get(0).(func(model.PropertyFieldSearchOpts) []*model.PropertyField); ok {
r0 = rf(opts)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*model.PropertyField)
}
}
if rf, ok := ret.Get(1).(func(model.PropertyFieldSearchOpts) error); ok {
r1 = rf(opts)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Update provides a mock function with given fields: field
func (_m *PropertyFieldStore) Update(field []*model.PropertyField) ([]*model.PropertyField, error) {
ret := _m.Called(field)
if len(ret) == 0 {
panic("no return value specified for Update")
}
var r0 []*model.PropertyField
var r1 error
if rf, ok := ret.Get(0).(func([]*model.PropertyField) ([]*model.PropertyField, error)); ok {
return rf(field)
}
if rf, ok := ret.Get(0).(func([]*model.PropertyField) []*model.PropertyField); ok {
r0 = rf(field)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*model.PropertyField)
}
}
if rf, ok := ret.Get(1).(func([]*model.PropertyField) error); ok {
r1 = rf(field)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// NewPropertyFieldStore creates a new instance of PropertyFieldStore. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewPropertyFieldStore(t interface {
mock.TestingT
Cleanup(func())
}) *PropertyFieldStore {
mock := &PropertyFieldStore{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}

Просмотреть файл

@@ -0,0 +1,89 @@
// Code generated by mockery v2.42.2. DO NOT EDIT.
// Regenerate this file using `make store-mocks`.
package mocks
import (
model "github.com/mattermost/mattermost/server/public/model"
mock "github.com/stretchr/testify/mock"
)
// PropertyGroupStore is an autogenerated mock type for the PropertyGroupStore type
type PropertyGroupStore struct {
mock.Mock
}
// Get provides a mock function with given fields: name
func (_m *PropertyGroupStore) Get(name string) (*model.PropertyGroup, error) {
ret := _m.Called(name)
if len(ret) == 0 {
panic("no return value specified for Get")
}
var r0 *model.PropertyGroup
var r1 error
if rf, ok := ret.Get(0).(func(string) (*model.PropertyGroup, error)); ok {
return rf(name)
}
if rf, ok := ret.Get(0).(func(string) *model.PropertyGroup); ok {
r0 = rf(name)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*model.PropertyGroup)
}
}
if rf, ok := ret.Get(1).(func(string) error); ok {
r1 = rf(name)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Register provides a mock function with given fields: name
func (_m *PropertyGroupStore) Register(name string) (*model.PropertyGroup, error) {
ret := _m.Called(name)
if len(ret) == 0 {
panic("no return value specified for Register")
}
var r0 *model.PropertyGroup
var r1 error
if rf, ok := ret.Get(0).(func(string) (*model.PropertyGroup, error)); ok {
return rf(name)
}
if rf, ok := ret.Get(0).(func(string) *model.PropertyGroup); ok {
r0 = rf(name)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*model.PropertyGroup)
}
}
if rf, ok := ret.Get(1).(func(string) error); ok {
r1 = rf(name)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// NewPropertyGroupStore creates a new instance of PropertyGroupStore. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewPropertyGroupStore(t interface {
mock.TestingT
Cleanup(func())
}) *PropertyGroupStore {
mock := &PropertyGroupStore{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}

Просмотреть файл

@@ -0,0 +1,215 @@
// Code generated by mockery v2.42.2. DO NOT EDIT.
// Regenerate this file using `make store-mocks`.
package mocks
import (
model "github.com/mattermost/mattermost/server/public/model"
mock "github.com/stretchr/testify/mock"
)
// PropertyValueStore is an autogenerated mock type for the PropertyValueStore type
type PropertyValueStore struct {
mock.Mock
}
// Create provides a mock function with given fields: value
func (_m *PropertyValueStore) Create(value *model.PropertyValue) (*model.PropertyValue, error) {
ret := _m.Called(value)
if len(ret) == 0 {
panic("no return value specified for Create")
}
var r0 *model.PropertyValue
var r1 error
if rf, ok := ret.Get(0).(func(*model.PropertyValue) (*model.PropertyValue, error)); ok {
return rf(value)
}
if rf, ok := ret.Get(0).(func(*model.PropertyValue) *model.PropertyValue); ok {
r0 = rf(value)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*model.PropertyValue)
}
}
if rf, ok := ret.Get(1).(func(*model.PropertyValue) error); ok {
r1 = rf(value)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Delete provides a mock function with given fields: id
func (_m *PropertyValueStore) Delete(id string) error {
ret := _m.Called(id)
if len(ret) == 0 {
panic("no return value specified for Delete")
}
var r0 error
if rf, ok := ret.Get(0).(func(string) error); ok {
r0 = rf(id)
} else {
r0 = ret.Error(0)
}
return r0
}
// DeleteForField provides a mock function with given fields: id
func (_m *PropertyValueStore) DeleteForField(id string) error {
ret := _m.Called(id)
if len(ret) == 0 {
panic("no return value specified for DeleteForField")
}
var r0 error
if rf, ok := ret.Get(0).(func(string) error); ok {
r0 = rf(id)
} else {
r0 = ret.Error(0)
}
return r0
}
// Get provides a mock function with given fields: id
func (_m *PropertyValueStore) Get(id string) (*model.PropertyValue, error) {
ret := _m.Called(id)
if len(ret) == 0 {
panic("no return value specified for Get")
}
var r0 *model.PropertyValue
var r1 error
if rf, ok := ret.Get(0).(func(string) (*model.PropertyValue, error)); ok {
return rf(id)
}
if rf, ok := ret.Get(0).(func(string) *model.PropertyValue); ok {
r0 = rf(id)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*model.PropertyValue)
}
}
if rf, ok := ret.Get(1).(func(string) error); ok {
r1 = rf(id)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GetMany provides a mock function with given fields: ids
func (_m *PropertyValueStore) GetMany(ids []string) ([]*model.PropertyValue, error) {
ret := _m.Called(ids)
if len(ret) == 0 {
panic("no return value specified for GetMany")
}
var r0 []*model.PropertyValue
var r1 error
if rf, ok := ret.Get(0).(func([]string) ([]*model.PropertyValue, error)); ok {
return rf(ids)
}
if rf, ok := ret.Get(0).(func([]string) []*model.PropertyValue); ok {
r0 = rf(ids)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*model.PropertyValue)
}
}
if rf, ok := ret.Get(1).(func([]string) error); ok {
r1 = rf(ids)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// SearchPropertyValues provides a mock function with given fields: opts
func (_m *PropertyValueStore) SearchPropertyValues(opts model.PropertyValueSearchOpts) ([]*model.PropertyValue, error) {
ret := _m.Called(opts)
if len(ret) == 0 {
panic("no return value specified for SearchPropertyValues")
}
var r0 []*model.PropertyValue
var r1 error
if rf, ok := ret.Get(0).(func(model.PropertyValueSearchOpts) ([]*model.PropertyValue, error)); ok {
return rf(opts)
}
if rf, ok := ret.Get(0).(func(model.PropertyValueSearchOpts) []*model.PropertyValue); ok {
r0 = rf(opts)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*model.PropertyValue)
}
}
if rf, ok := ret.Get(1).(func(model.PropertyValueSearchOpts) error); ok {
r1 = rf(opts)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Update provides a mock function with given fields: field
func (_m *PropertyValueStore) Update(field []*model.PropertyValue) ([]*model.PropertyValue, error) {
ret := _m.Called(field)
if len(ret) == 0 {
panic("no return value specified for Update")
}
var r0 []*model.PropertyValue
var r1 error
if rf, ok := ret.Get(0).(func([]*model.PropertyValue) ([]*model.PropertyValue, error)); ok {
return rf(field)
}
if rf, ok := ret.Get(0).(func([]*model.PropertyValue) []*model.PropertyValue); ok {
r0 = rf(field)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*model.PropertyValue)
}
}
if rf, ok := ret.Get(1).(func([]*model.PropertyValue) error); ok {
r1 = rf(field)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// NewPropertyValueStore creates a new instance of PropertyValueStore. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewPropertyValueStore(t interface {
mock.TestingT
Cleanup(func())
}) *PropertyValueStore {
mock := &PropertyValueStore{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}

Просмотреть файл

@@ -798,6 +798,66 @@ func (_m *Store) ProductNotices() store.ProductNoticesStore {
return r0
}
// PropertyField provides a mock function with given fields:
func (_m *Store) PropertyField() store.PropertyFieldStore {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for PropertyField")
}
var r0 store.PropertyFieldStore
if rf, ok := ret.Get(0).(func() store.PropertyFieldStore); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(store.PropertyFieldStore)
}
}
return r0
}
// PropertyGroup provides a mock function with given fields:
func (_m *Store) PropertyGroup() store.PropertyGroupStore {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for PropertyGroup")
}
var r0 store.PropertyGroupStore
if rf, ok := ret.Get(0).(func() store.PropertyGroupStore); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(store.PropertyGroupStore)
}
}
return r0
}
// PropertyValue provides a mock function with given fields:
func (_m *Store) PropertyValue() store.PropertyValueStore {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for PropertyValue")
}
var r0 store.PropertyValueStore
if rf, ok := ret.Get(0).(func() store.PropertyValueStore); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(store.PropertyValueStore)
}
}
return r0
}
// Reaction provides a mock function with given fields:
func (_m *Store) Reaction() store.ReactionStore {
ret := _m.Called()

Просмотреть файл

@@ -0,0 +1,461 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package storetest
import (
"database/sql"
"testing"
"time"
"github.com/mattermost/mattermost/server/public/model"
"github.com/mattermost/mattermost/server/public/shared/request"
"github.com/mattermost/mattermost/server/v8/channels/store"
"github.com/stretchr/testify/require"
)
func TestPropertyFieldStore(t *testing.T, rctx request.CTX, ss store.Store, s SqlStore) {
t.Run("CreatePropertyField", func(t *testing.T) { testCreatePropertyField(t, rctx, ss) })
t.Run("GetPropertyField", func(t *testing.T) { testGetPropertyField(t, rctx, ss) })
t.Run("GetManyPropertyFields", func(t *testing.T) { testGetManyPropertyFields(t, rctx, ss) })
t.Run("UpdatePropertyField", func(t *testing.T) { testUpdatePropertyField(t, rctx, ss) })
t.Run("DeletePropertyField", func(t *testing.T) { testDeletePropertyField(t, rctx, ss) })
t.Run("SearchPropertyFields", func(t *testing.T) { testSearchPropertyFields(t, rctx, ss) })
}
func testCreatePropertyField(t *testing.T, _ request.CTX, ss store.Store) {
t.Run("should fail if the property field already has an ID set", func(t *testing.T) {
newField := &model.PropertyField{ID: "sampleid"}
field, err := ss.PropertyField().Create(newField)
require.Zero(t, field)
var eii *store.ErrInvalidInput
require.ErrorAs(t, err, &eii)
})
t.Run("should fail if the property field is not valid", func(t *testing.T) {
newField := &model.PropertyField{GroupID: ""}
field, err := ss.PropertyField().Create(newField)
require.Zero(t, field)
require.ErrorContains(t, err, "model.property_field.is_valid.app_error")
newField = &model.PropertyField{GroupID: model.NewId(), Name: ""}
field, err = ss.PropertyField().Create(newField)
require.Zero(t, field)
require.ErrorContains(t, err, "model.property_field.is_valid.app_error")
})
newField := &model.PropertyField{
GroupID: model.NewId(),
Name: "My new property field",
Type: model.PropertyFieldTypeText,
Attrs: map[string]any{
"locked": true,
"special": "value",
},
}
t.Run("should be able to create a property field", func(t *testing.T) {
field, err := ss.PropertyField().Create(newField)
require.NoError(t, err)
require.NotZero(t, field.ID)
require.NotZero(t, field.CreateAt)
require.NotZero(t, field.UpdateAt)
require.Zero(t, field.DeleteAt)
})
t.Run("should enforce the field's uniqueness", func(t *testing.T) {
newField.ID = ""
field, err := ss.PropertyField().Create(newField)
require.Error(t, err)
require.Empty(t, field)
})
}
func testGetPropertyField(t *testing.T, _ request.CTX, ss store.Store) {
t.Run("should fail on nonexisting field", func(t *testing.T) {
field, err := ss.PropertyField().Get(model.NewId())
require.Zero(t, field)
require.ErrorIs(t, err, sql.ErrNoRows)
})
t.Run("should be able to retrieve an existing property field", func(t *testing.T) {
newField := &model.PropertyField{
GroupID: model.NewId(),
Name: "My new property field",
Type: model.PropertyFieldTypeText,
Attrs: map[string]any{
"locked": true,
"special": "value",
},
}
_, err := ss.PropertyField().Create(newField)
require.NoError(t, err)
require.NotZero(t, newField.ID)
field, err := ss.PropertyField().Get(newField.ID)
require.NoError(t, err)
require.Equal(t, newField.ID, field.ID)
require.True(t, field.Attrs["locked"].(bool))
require.Equal(t, "value", field.Attrs["special"])
})
}
func testGetManyPropertyFields(t *testing.T, _ request.CTX, ss store.Store) {
t.Run("should fail on nonexisting fields", func(t *testing.T) {
fields, err := ss.PropertyField().GetMany([]string{model.NewId(), model.NewId()})
require.Empty(t, fields)
require.ErrorContains(t, err, "missmatch results")
})
newFields := []*model.PropertyField{}
for _, fieldName := range []string{"field1", "field2", "field3"} {
newField := &model.PropertyField{
GroupID: model.NewId(),
Name: fieldName,
Type: model.PropertyFieldTypeText,
}
_, err := ss.PropertyField().Create(newField)
require.NoError(t, err)
require.NotZero(t, newField.ID)
newFields = append(newFields, newField)
}
t.Run("should fail if at least one of the ids is nonexistent", func(t *testing.T) {
fields, err := ss.PropertyField().GetMany([]string{newFields[0].ID, newFields[1].ID, model.NewId()})
require.Empty(t, fields)
require.ErrorContains(t, err, "missmatch results")
})
t.Run("should be able to retrieve existing property fields", func(t *testing.T) {
fields, err := ss.PropertyField().GetMany([]string{newFields[0].ID, newFields[1].ID, newFields[2].ID})
require.NoError(t, err)
require.Len(t, fields, 3)
require.ElementsMatch(t, newFields, fields)
})
}
func testUpdatePropertyField(t *testing.T, _ request.CTX, ss store.Store) {
t.Run("should fail on nonexisting field", func(t *testing.T) {
field := &model.PropertyField{
ID: model.NewId(),
GroupID: model.NewId(),
Name: "My property field",
Type: model.PropertyFieldTypeText,
CreateAt: model.GetMillis(),
}
updatedField, err := ss.PropertyField().Update([]*model.PropertyField{field})
require.Zero(t, updatedField)
var enf *store.ErrNotFound
require.ErrorAs(t, err, &enf)
})
t.Run("should fail if the property field is not valid", func(t *testing.T) {
field := &model.PropertyField{
GroupID: model.NewId(),
Name: "My property field",
Type: model.PropertyFieldTypeText,
}
_, err := ss.PropertyField().Create(field)
require.NoError(t, err)
require.NotZero(t, field.ID)
field.GroupID = ""
updatedField, err := ss.PropertyField().Update([]*model.PropertyField{field})
require.Zero(t, updatedField)
require.ErrorContains(t, err, "model.property_field.is_valid.app_error")
field.GroupID = model.NewId()
field.Name = ""
updatedField, err = ss.PropertyField().Update([]*model.PropertyField{field})
require.Zero(t, updatedField)
require.ErrorContains(t, err, "model.property_field.is_valid.app_error")
})
t.Run("should be able to update multiple property fields", func(t *testing.T) {
field1 := &model.PropertyField{
GroupID: model.NewId(),
Name: "First field",
Type: model.PropertyFieldTypeText,
Attrs: map[string]any{
"locked": true,
"special": "value",
},
}
field2 := &model.PropertyField{
GroupID: model.NewId(),
Name: "Second field",
Type: model.PropertyFieldTypeSelect,
Attrs: map[string]any{
"options": []string{"a", "b"},
},
}
for _, field := range []*model.PropertyField{field1, field2} {
_, err := ss.PropertyField().Create(field)
require.NoError(t, err)
require.NotZero(t, field.ID)
}
time.Sleep(10 * time.Millisecond)
field1.Name = "Updated first"
field1.Type = model.PropertyFieldTypeSelect
field1.Attrs = map[string]any{
"locked": false,
"new_field": "new_value",
}
field2.Name = "Updated second"
field2.Attrs = map[string]any{
"options": []string{"x", "y", "z"},
}
_, err := ss.PropertyField().Update([]*model.PropertyField{field1, field2})
require.NoError(t, err)
// Verify first field
updated1, err := ss.PropertyField().Get(field1.ID)
require.NoError(t, err)
require.Equal(t, "Updated first", updated1.Name)
require.Equal(t, model.PropertyFieldTypeSelect, updated1.Type)
require.False(t, updated1.Attrs["locked"].(bool))
require.NotContains(t, updated1.Attrs, "special")
require.Equal(t, "new_value", updated1.Attrs["new_field"])
require.Greater(t, updated1.UpdateAt, updated1.CreateAt)
// Verify second field
updated2, err := ss.PropertyField().Get(field2.ID)
require.NoError(t, err)
require.Equal(t, "Updated second", updated2.Name)
require.Equal(t, model.PropertyFieldTypeSelect, updated2.Type)
require.ElementsMatch(t, []string{"x", "y", "z"}, updated2.Attrs["options"])
require.Greater(t, updated2.UpdateAt, updated2.CreateAt)
})
t.Run("should not update any fields if one update is invalid", func(t *testing.T) {
// Create two valid fields
groupID := model.NewId()
field1 := &model.PropertyField{
GroupID: groupID,
Name: "Field 1",
Type: model.PropertyFieldTypeText,
Attrs: map[string]any{
"key": "value",
},
}
field2 := &model.PropertyField{
GroupID: groupID,
Name: "Field 2",
Type: model.PropertyFieldTypeText,
Attrs: map[string]any{
"key": "value",
},
}
for _, field := range []*model.PropertyField{field1, field2} {
_, err := ss.PropertyField().Create(field)
require.NoError(t, err)
}
originalUpdateAt1 := field1.UpdateAt
originalUpdateAt2 := field2.UpdateAt
// Try to update both fields, but make one invalid
field1.Name = "Valid update"
field2.GroupID = "Invalid ID"
_, err := ss.PropertyField().Update([]*model.PropertyField{field1, field2})
require.ErrorContains(t, err, "model.property_field.is_valid.app_error")
// Check that fields were not updated
updated1, err := ss.PropertyField().Get(field1.ID)
require.NoError(t, err)
require.Equal(t, "Field 1", updated1.Name)
require.Equal(t, originalUpdateAt1, updated1.UpdateAt)
updated2, err := ss.PropertyField().Get(field2.ID)
require.NoError(t, err)
require.Equal(t, groupID, updated2.GroupID)
require.Equal(t, originalUpdateAt2, updated2.UpdateAt)
})
}
func testDeletePropertyField(t *testing.T, _ request.CTX, ss store.Store) {
t.Run("should fail on nonexisting field", func(t *testing.T) {
err := ss.PropertyField().Delete(model.NewId())
var enf *store.ErrNotFound
require.ErrorAs(t, err, &enf)
})
newField := &model.PropertyField{
GroupID: model.NewId(),
Name: "My property field",
Type: model.PropertyFieldTypeText,
}
t.Run("should be able to delete an existing property field", func(t *testing.T) {
field, err := ss.PropertyField().Create(newField)
require.NoError(t, err)
require.NotEmpty(t, field.ID)
err = ss.PropertyField().Delete(field.ID)
require.NoError(t, err)
// Verify the field was soft-deleted
deletedField, err := ss.PropertyField().Get(field.ID)
require.NoError(t, err)
require.NotZero(t, deletedField.DeleteAt)
})
t.Run("should be able to create a new field with the same details as the deleted one", func(t *testing.T) {
newField.ID = ""
field, err := ss.PropertyField().Create(newField)
require.NoError(t, err)
require.NotEmpty(t, field.ID)
})
}
func testSearchPropertyFields(t *testing.T, _ request.CTX, ss store.Store) {
groupID := model.NewId()
targetID := model.NewId()
// Define test property fields
field1 := &model.PropertyField{
GroupID: groupID,
Name: "Field 1",
Type: model.PropertyFieldTypeText,
TargetID: targetID,
TargetType: "test_type",
}
field2 := &model.PropertyField{
GroupID: groupID,
Name: "Field 2",
Type: model.PropertyFieldTypeSelect,
TargetID: targetID,
TargetType: "other_type",
}
field3 := &model.PropertyField{
GroupID: model.NewId(),
Name: "Field 3",
Type: model.PropertyFieldTypeText,
TargetType: "test_type",
}
field4 := &model.PropertyField{
GroupID: groupID,
Name: "Field 4",
Type: model.PropertyFieldTypeText,
TargetType: "test_type",
}
for _, field := range []*model.PropertyField{field1, field2, field3, field4} {
_, err := ss.PropertyField().Create(field)
require.NoError(t, err)
time.Sleep(10 * time.Millisecond)
}
// Delete one field for deletion tests
require.NoError(t, ss.PropertyField().Delete(field4.ID))
tests := []struct {
name string
opts model.PropertyFieldSearchOpts
expectedError bool
expectedIDs []string
}{
{
name: "negative page",
opts: model.PropertyFieldSearchOpts{
Page: -1,
PerPage: 10,
},
expectedError: true,
},
{
name: "negative per_page",
opts: model.PropertyFieldSearchOpts{
Page: 0,
PerPage: -1,
},
expectedError: true,
},
{
name: "filter by group_id",
opts: model.PropertyFieldSearchOpts{
GroupID: groupID,
Page: 0,
PerPage: 10,
},
expectedIDs: []string{field1.ID, field2.ID},
},
{
name: "filter by group_id including deleted",
opts: model.PropertyFieldSearchOpts{
GroupID: groupID,
Page: 0,
PerPage: 10,
IncludeDeleted: true,
},
expectedIDs: []string{field1.ID, field2.ID, field4.ID},
},
{
name: "filter by target_type",
opts: model.PropertyFieldSearchOpts{
TargetType: "test_type",
Page: 0,
PerPage: 10,
},
expectedIDs: []string{field1.ID, field3.ID},
},
{
name: "filter by target_id",
opts: model.PropertyFieldSearchOpts{
TargetID: targetID,
Page: 0,
PerPage: 10,
},
expectedIDs: []string{field1.ID, field2.ID},
},
{
name: "pagination page 0",
opts: model.PropertyFieldSearchOpts{
GroupID: groupID,
Page: 0,
PerPage: 2,
IncludeDeleted: true,
},
expectedIDs: []string{field1.ID, field2.ID},
},
{
name: "pagination page 1",
opts: model.PropertyFieldSearchOpts{
GroupID: groupID,
Page: 1,
PerPage: 2,
IncludeDeleted: true,
},
expectedIDs: []string{field4.ID},
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
results, err := ss.PropertyField().SearchPropertyFields(tc.opts)
if tc.expectedError {
require.Error(t, err)
return
}
require.NoError(t, err)
var ids = make([]string, len(results))
for i, field := range results {
ids[i] = field.ID
}
require.ElementsMatch(t, tc.expectedIDs, ids)
})
}
}

Просмотреть файл

@@ -0,0 +1,36 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package storetest
import (
"testing"
"github.com/mattermost/mattermost/server/public/shared/request"
"github.com/mattermost/mattermost/server/v8/channels/store"
"github.com/stretchr/testify/require"
)
func TestPropertyGroupStore(t *testing.T, rctx request.CTX, ss store.Store, s SqlStore) {
t.Run("RegisterAndGetPropertyGroup", func(t *testing.T) { testRegisterAndGetPropertyGroup(t, rctx, ss) })
}
func testRegisterAndGetPropertyGroup(t *testing.T, _ request.CTX, ss store.Store) {
groupName := "samplename"
var id string
t.Run("should be able to register a new group", func(t *testing.T) {
group, err := ss.PropertyGroup().Register(groupName)
require.NoError(t, err)
require.NotZero(t, group.ID)
require.Equal(t, groupName, group.Name)
id = group.ID
})
t.Run("should be able to retrieve an existing group", func(t *testing.T) {
group, err := ss.PropertyGroup().Register(groupName)
require.NoError(t, err)
require.Equal(t, groupName, group.Name)
require.Equal(t, id, group.ID)
})
}

Просмотреть файл

@@ -0,0 +1,535 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package storetest
import (
"database/sql"
"fmt"
"testing"
"time"
"github.com/mattermost/mattermost/server/public/model"
"github.com/mattermost/mattermost/server/public/shared/request"
"github.com/mattermost/mattermost/server/v8/channels/store"
"github.com/stretchr/testify/require"
)
func TestPropertyValueStore(t *testing.T, rctx request.CTX, ss store.Store, s SqlStore) {
t.Run("CreatePropertyValue", func(t *testing.T) { testCreatePropertyValue(t, rctx, ss) })
t.Run("GetPropertyValue", func(t *testing.T) { testGetPropertyValue(t, rctx, ss) })
t.Run("GetManyPropertyValues", func(t *testing.T) { testGetManyPropertyValues(t, rctx, ss) })
t.Run("UpdatePropertyValue", func(t *testing.T) { testUpdatePropertyValue(t, rctx, ss) })
t.Run("DeletePropertyValue", func(t *testing.T) { testDeletePropertyValue(t, rctx, ss) })
t.Run("SearchPropertyValues", func(t *testing.T) { testSearchPropertyValues(t, rctx, ss) })
t.Run("DeleteForField", func(t *testing.T) { testDeleteForField(t, rctx, ss) })
}
func testCreatePropertyValue(t *testing.T, _ request.CTX, ss store.Store) {
t.Run("should fail if the property value already has an ID set", func(t *testing.T) {
newValue := &model.PropertyValue{ID: "sampleid"}
value, err := ss.PropertyValue().Create(newValue)
require.Zero(t, value)
var eii *store.ErrInvalidInput
require.ErrorAs(t, err, &eii)
})
t.Run("should fail if the property value is not valid", func(t *testing.T) {
newValue := &model.PropertyValue{TargetID: ""}
value, err := ss.PropertyValue().Create(newValue)
require.Zero(t, value)
require.ErrorContains(t, err, "model.property_value.is_valid.app_error")
newValue = &model.PropertyValue{TargetID: model.NewId(), TargetType: ""}
value, err = ss.PropertyValue().Create(newValue)
require.Zero(t, value)
require.ErrorContains(t, err, "model.property_value.is_valid.app_error")
})
newValue := &model.PropertyValue{
TargetID: model.NewId(),
TargetType: "test_type",
GroupID: model.NewId(),
FieldID: model.NewId(),
Value: "test value",
}
t.Run("should be able to create a property value", func(t *testing.T) {
value, err := ss.PropertyValue().Create(newValue)
require.NoError(t, err)
require.NotZero(t, value.ID)
require.NotZero(t, value.CreateAt)
require.NotZero(t, value.UpdateAt)
require.Zero(t, value.DeleteAt)
})
t.Run("should enforce the value's uniqueness", func(t *testing.T) {
newValue.ID = ""
value, err := ss.PropertyValue().Create(newValue)
require.Error(t, err)
require.Zero(t, value)
})
}
func testGetPropertyValue(t *testing.T, _ request.CTX, ss store.Store) {
t.Run("should fail on nonexisting value", func(t *testing.T) {
value, err := ss.PropertyValue().Get(model.NewId())
require.Zero(t, value)
require.ErrorIs(t, err, sql.ErrNoRows)
})
t.Run("should be able to retrieve an existing property value", func(t *testing.T) {
newValue := &model.PropertyValue{
TargetID: model.NewId(),
TargetType: "test_type",
GroupID: model.NewId(),
FieldID: model.NewId(),
Value: "test value",
}
_, err := ss.PropertyValue().Create(newValue)
require.NoError(t, err)
require.NotZero(t, newValue.ID)
value, err := ss.PropertyValue().Get(newValue.ID)
require.NoError(t, err)
require.Equal(t, newValue.ID, value.ID)
require.Equal(t, newValue.Value, value.Value)
})
}
func testGetManyPropertyValues(t *testing.T, _ request.CTX, ss store.Store) {
t.Run("should fail on nonexisting values", func(t *testing.T) {
values, err := ss.PropertyValue().GetMany([]string{model.NewId(), model.NewId()})
require.Empty(t, values)
require.ErrorContains(t, err, "missmatch results")
})
newValues := []*model.PropertyValue{}
for i := 0; i < 3; i++ {
newValue := &model.PropertyValue{
TargetID: model.NewId(),
TargetType: "test_type",
GroupID: model.NewId(),
FieldID: model.NewId(),
Value: fmt.Sprintf("test value %d", i),
}
_, err := ss.PropertyValue().Create(newValue)
require.NoError(t, err)
require.NotZero(t, newValue.ID)
newValues = append(newValues, newValue)
}
t.Run("should fail if at least one of the ids is nonexistent", func(t *testing.T) {
values, err := ss.PropertyValue().GetMany([]string{newValues[0].ID, newValues[1].ID, model.NewId()})
require.Empty(t, values)
require.ErrorContains(t, err, "missmatch results")
})
t.Run("should be able to retrieve existing property values", func(t *testing.T) {
values, err := ss.PropertyValue().GetMany([]string{newValues[0].ID, newValues[1].ID, newValues[2].ID})
require.NoError(t, err)
require.Len(t, values, 3)
require.ElementsMatch(t, newValues, values)
})
}
func testUpdatePropertyValue(t *testing.T, _ request.CTX, ss store.Store) {
t.Run("should fail on nonexisting value", func(t *testing.T) {
value := &model.PropertyValue{
ID: model.NewId(),
TargetID: model.NewId(),
TargetType: "test_type",
GroupID: model.NewId(),
FieldID: model.NewId(),
Value: "test value",
CreateAt: model.GetMillis(),
}
updatedValue, err := ss.PropertyValue().Update([]*model.PropertyValue{value})
require.Zero(t, updatedValue)
var enf *store.ErrNotFound
require.ErrorAs(t, err, &enf)
})
t.Run("should fail if the property value is not valid", func(t *testing.T) {
value := &model.PropertyValue{
TargetID: model.NewId(),
TargetType: "test_type",
GroupID: model.NewId(),
FieldID: model.NewId(),
Value: "test value",
}
_, err := ss.PropertyValue().Create(value)
require.NoError(t, err)
require.NotZero(t, value.ID)
value.TargetID = ""
updatedValue, err := ss.PropertyValue().Update([]*model.PropertyValue{value})
require.Zero(t, updatedValue)
require.ErrorContains(t, err, "model.property_value.is_valid.app_error")
value.TargetID = model.NewId()
value.GroupID = ""
updatedValue, err = ss.PropertyValue().Update([]*model.PropertyValue{value})
require.Zero(t, updatedValue)
require.ErrorContains(t, err, "model.property_value.is_valid.app_error")
})
t.Run("should be able to update multiple property values", func(t *testing.T) {
value1 := &model.PropertyValue{
TargetID: model.NewId(),
TargetType: "test_type",
GroupID: model.NewId(),
FieldID: model.NewId(),
Value: "value 1",
}
value2 := &model.PropertyValue{
TargetID: model.NewId(),
TargetType: "test_type",
GroupID: model.NewId(),
FieldID: model.NewId(),
Value: "value 2",
}
for _, value := range []*model.PropertyValue{value1, value2} {
_, err := ss.PropertyValue().Create(value)
require.NoError(t, err)
require.NotZero(t, value.ID)
}
time.Sleep(10 * time.Millisecond)
value1.Value = "updated value 1"
value2.Value = "updated value 2"
_, err := ss.PropertyValue().Update([]*model.PropertyValue{value1, value2})
require.NoError(t, err)
// Verify first value
updated1, err := ss.PropertyValue().Get(value1.ID)
require.NoError(t, err)
require.Equal(t, "updated value 1", updated1.Value)
require.Greater(t, updated1.UpdateAt, updated1.CreateAt)
// Verify second value
updated2, err := ss.PropertyValue().Get(value2.ID)
require.NoError(t, err)
require.Equal(t, "updated value 2", updated2.Value)
require.Greater(t, updated2.UpdateAt, updated2.CreateAt)
})
t.Run("should not update any fields if one update is invalid", func(t *testing.T) {
// Create two valid values
groupID := model.NewId()
value1 := &model.PropertyValue{
TargetID: model.NewId(),
TargetType: "test_type",
GroupID: groupID,
FieldID: model.NewId(),
Value: "Value 1",
}
value2 := &model.PropertyValue{
TargetID: model.NewId(),
TargetType: "test_type",
GroupID: groupID,
FieldID: model.NewId(),
Value: "Value 2",
}
for _, value := range []*model.PropertyValue{value1, value2} {
_, err := ss.PropertyValue().Create(value)
require.NoError(t, err)
}
originalUpdateAt1 := value1.UpdateAt
originalUpdateAt2 := value2.UpdateAt
// Try to update both value, but make one invalid
value1.Value = "Valid update"
value2.GroupID = "Invalid ID"
_, err := ss.PropertyValue().Update([]*model.PropertyValue{value1, value2})
require.Error(t, err)
require.Contains(t, err.Error(), "model.property_value.is_valid.app_error")
// Check that values were not updated
updated1, err := ss.PropertyValue().Get(value1.ID)
require.NoError(t, err)
require.Equal(t, "Value 1", updated1.Value)
require.Equal(t, originalUpdateAt1, updated1.UpdateAt)
updated2, err := ss.PropertyValue().Get(value2.ID)
require.NoError(t, err)
require.Equal(t, groupID, updated2.GroupID)
require.Equal(t, originalUpdateAt2, updated2.UpdateAt)
})
}
func testDeletePropertyValue(t *testing.T, _ request.CTX, ss store.Store) {
t.Run("should fail on nonexisting value", func(t *testing.T) {
err := ss.PropertyValue().Delete(model.NewId())
var enf *store.ErrNotFound
require.ErrorAs(t, err, &enf)
})
t.Run("should be able to delete an existing property value", func(t *testing.T) {
newValue := &model.PropertyValue{
TargetID: model.NewId(),
TargetType: "test_type",
GroupID: model.NewId(),
FieldID: model.NewId(),
Value: "test value",
}
value, err := ss.PropertyValue().Create(newValue)
require.NoError(t, err)
require.NotEmpty(t, value.ID)
err = ss.PropertyValue().Delete(value.ID)
require.NoError(t, err)
// Verify the value was soft-deleted
deletedValue, err := ss.PropertyValue().Get(value.ID)
require.NoError(t, err)
require.NotZero(t, deletedValue.DeleteAt)
})
t.Run("should be able to create a new value with the same details as the deleted one", func(t *testing.T) {
sameDetailsValue := &model.PropertyValue{
TargetID: model.NewId(),
TargetType: "test_type",
GroupID: model.NewId(),
FieldID: model.NewId(),
Value: "test value",
}
value, err := ss.PropertyValue().Create(sameDetailsValue)
require.NoError(t, err)
require.NotEmpty(t, value.ID)
require.Equal(t, sameDetailsValue.Value, value.Value)
})
}
func testSearchPropertyValues(t *testing.T, _ request.CTX, ss store.Store) {
groupID := model.NewId()
targetID := model.NewId()
fieldID := model.NewId()
// Define test property values
value1 := &model.PropertyValue{
GroupID: groupID,
TargetID: targetID,
TargetType: "test_type",
FieldID: fieldID,
Value: "value 1",
}
value2 := &model.PropertyValue{
GroupID: groupID,
TargetID: targetID,
TargetType: "other_type",
FieldID: model.NewId(),
Value: "value 2",
}
value3 := &model.PropertyValue{
GroupID: model.NewId(),
TargetID: model.NewId(),
TargetType: "test_type",
FieldID: model.NewId(),
Value: "value 3",
}
value4 := &model.PropertyValue{
GroupID: groupID,
TargetID: model.NewId(),
TargetType: "test_type",
FieldID: fieldID,
Value: "value 4",
}
for _, value := range []*model.PropertyValue{value1, value2, value3, value4} {
_, err := ss.PropertyValue().Create(value)
require.NoError(t, err)
time.Sleep(10 * time.Millisecond)
}
// Delete one value for deletion tests
require.NoError(t, ss.PropertyValue().Delete(value4.ID))
tests := []struct {
name string
opts model.PropertyValueSearchOpts
expectedError bool
expectedIDs []string
}{
{
name: "negative page",
opts: model.PropertyValueSearchOpts{
Page: -1,
PerPage: 10,
},
expectedError: true,
},
{
name: "negative per_page",
opts: model.PropertyValueSearchOpts{
Page: 0,
PerPage: -1,
},
expectedError: true,
},
{
name: "filter by group_id",
opts: model.PropertyValueSearchOpts{
GroupID: groupID,
Page: 0,
PerPage: 10,
},
expectedIDs: []string{value1.ID, value2.ID},
},
{
name: "filter by group_id and target_type",
opts: model.PropertyValueSearchOpts{
GroupID: groupID,
TargetType: "test_type",
Page: 0,
PerPage: 10,
},
expectedIDs: []string{value1.ID},
},
{
name: "filter by group_id and target_type including deleted",
opts: model.PropertyValueSearchOpts{
GroupID: groupID,
TargetType: "test_type",
IncludeDeleted: true,
Page: 0,
PerPage: 10,
},
expectedIDs: []string{value1.ID, value4.ID},
},
{
name: "filter by target_id",
opts: model.PropertyValueSearchOpts{
TargetID: targetID,
Page: 0,
PerPage: 10,
},
expectedIDs: []string{value1.ID, value2.ID},
},
{
name: "filter by group_id and target_id",
opts: model.PropertyValueSearchOpts{
GroupID: groupID,
TargetID: targetID,
Page: 0,
PerPage: 10,
},
expectedIDs: []string{value1.ID, value2.ID},
},
{
name: "filter by field_id",
opts: model.PropertyValueSearchOpts{
FieldID: fieldID,
Page: 0,
PerPage: 10,
},
expectedIDs: []string{value1.ID},
},
{
name: "filter by field_id including deleted",
opts: model.PropertyValueSearchOpts{
FieldID: fieldID,
IncludeDeleted: true,
Page: 0,
PerPage: 10,
},
expectedIDs: []string{value1.ID, value4.ID},
},
{
name: "pagination page 0",
opts: model.PropertyValueSearchOpts{
GroupID: groupID,
Page: 0,
PerPage: 1,
},
expectedIDs: []string{value1.ID},
},
{
name: "pagination page 1",
opts: model.PropertyValueSearchOpts{
GroupID: groupID,
Page: 1,
PerPage: 1,
},
expectedIDs: []string{value2.ID},
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
results, err := ss.PropertyValue().SearchPropertyValues(tc.opts)
if tc.expectedError {
require.Error(t, err)
return
}
require.NoError(t, err)
var ids = make([]string, len(results))
for i, value := range results {
ids[i] = value.ID
}
require.ElementsMatch(t, tc.expectedIDs, ids)
})
}
}
func testDeleteForField(t *testing.T, _ request.CTX, ss store.Store) {
fieldID := model.NewId()
// Create test values
value1 := &model.PropertyValue{
TargetID: model.NewId(),
TargetType: "test_type",
GroupID: model.NewId(),
FieldID: fieldID,
Value: "value 1",
}
value2 := &model.PropertyValue{
TargetID: model.NewId(),
TargetType: "test_type",
GroupID: model.NewId(),
FieldID: fieldID,
Value: "value 2",
}
value3 := &model.PropertyValue{
TargetID: model.NewId(),
TargetType: "test_type",
GroupID: model.NewId(),
FieldID: model.NewId(), // Different field ID
Value: "value 3",
}
for _, value := range []*model.PropertyValue{value1, value2, value3} {
_, err := ss.PropertyValue().Create(value)
require.NoError(t, err)
}
// Delete values for the field
err := ss.PropertyValue().DeleteForField(fieldID)
require.NoError(t, err)
// Verify values were soft-deleted
deletedValues, err := ss.PropertyValue().GetMany([]string{value1.ID, value2.ID})
require.NoError(t, err)
require.Len(t, deletedValues, 2)
require.NotZero(t, deletedValues[0].DeleteAt)
require.NotZero(t, deletedValues[1].DeleteAt)
// Verify value with different field ID was not deleted
nonDeletedValue, err := ss.PropertyValue().Get(value3.ID)
require.NoError(t, err)
require.Zero(t, nonDeletedValue.DeleteAt)
}

Просмотреть файл

@@ -66,6 +66,9 @@ type Store struct {
DesktopTokensStore mocks.DesktopTokensStore
ChannelBookmarkStore mocks.ChannelBookmarkStore
ScheduledPostStore mocks.ScheduledPostStore
PropertyGroupStore mocks.PropertyGroupStore
PropertyFieldStore mocks.PropertyFieldStore
PropertyValueStore mocks.PropertyValueStore
}
func (s *Store) SetContext(context context.Context) { s.context = context }
@@ -119,6 +122,9 @@ func (s *Store) LinkMetadata() store.LinkMetadataStore { return &s.LinkMet
func (s *Store) SharedChannel() store.SharedChannelStore { return &s.SharedChannelStore }
func (s *Store) PostPriority() store.PostPriorityStore { return &s.PostPriorityStore }
func (s *Store) ScheduledPost() store.ScheduledPostStore { return &s.ScheduledPostStore }
func (s *Store) PropertyGroup() store.PropertyGroupStore { return &s.PropertyGroupStore }
func (s *Store) PropertyField() store.PropertyFieldStore { return &s.PropertyFieldStore }
func (s *Store) PropertyValue() store.PropertyValueStore { return &s.PropertyValueStore }
func (s *Store) PostAcknowledgement() store.PostAcknowledgementStore {
return &s.PostAcknowledgementStore
}