mirror of
https://github.com/mattermost/mattermost.git
synced 2026-05-28 04:35:04 -04:00
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 e2735ab0f8.
* 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>
This commit is contained in:
parent
d2b334e605
commit
ecdce71fc4
35 changed files with 3793 additions and 0 deletions
44
server/channels/app/properties/property_field.go
Normal file
44
server/channels/app/properties/property_field.go
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package properties
|
||||
|
||||
import (
|
||||
"github.com/mattermost/mattermost/server/public/model"
|
||||
)
|
||||
|
||||
func (ps *PropertyService) CreatePropertyField(field *model.PropertyField) (*model.PropertyField, error) {
|
||||
return ps.fieldStore.Create(field)
|
||||
}
|
||||
|
||||
func (ps *PropertyService) GetPropertyField(id string) (*model.PropertyField, error) {
|
||||
return ps.fieldStore.Get(id)
|
||||
}
|
||||
|
||||
func (ps *PropertyService) GetPropertyFields(ids []string) ([]*model.PropertyField, error) {
|
||||
return ps.fieldStore.GetMany(ids)
|
||||
}
|
||||
|
||||
func (ps *PropertyService) SearchPropertyFields(opts model.PropertyFieldSearchOpts) ([]*model.PropertyField, error) {
|
||||
return ps.fieldStore.SearchPropertyFields(opts)
|
||||
}
|
||||
|
||||
func (ps *PropertyService) UpdatePropertyField(field *model.PropertyField) (*model.PropertyField, error) {
|
||||
fields, err := ps.UpdatePropertyFields([]*model.PropertyField{field})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return fields[0], nil
|
||||
}
|
||||
|
||||
func (ps *PropertyService) UpdatePropertyFields(fields []*model.PropertyField) ([]*model.PropertyField, error) {
|
||||
return ps.fieldStore.Update(fields)
|
||||
}
|
||||
|
||||
func (ps *PropertyService) DeletePropertyField(id string) error {
|
||||
if err := ps.valueStore.DeleteForField(id); err != nil {
|
||||
return err
|
||||
}
|
||||
return ps.fieldStore.Delete(id)
|
||||
}
|
||||
16
server/channels/app/properties/property_group.go
Normal file
16
server/channels/app/properties/property_group.go
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package properties
|
||||
|
||||
import (
|
||||
"github.com/mattermost/mattermost/server/public/model"
|
||||
)
|
||||
|
||||
func (ps *PropertyService) RegisterPropertyGroup(name string) (*model.PropertyGroup, error) {
|
||||
return ps.groupStore.Register(name)
|
||||
}
|
||||
|
||||
func (ps *PropertyService) GetPropertyGroup(name string) (*model.PropertyGroup, error) {
|
||||
return ps.groupStore.Get(name)
|
||||
}
|
||||
41
server/channels/app/properties/property_value.go
Normal file
41
server/channels/app/properties/property_value.go
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package properties
|
||||
|
||||
import (
|
||||
"github.com/mattermost/mattermost/server/public/model"
|
||||
)
|
||||
|
||||
func (ps *PropertyService) CreatePropertyValue(value *model.PropertyValue) (*model.PropertyValue, error) {
|
||||
return ps.valueStore.Create(value)
|
||||
}
|
||||
|
||||
func (ps *PropertyService) GetPropertyValue(id string) (*model.PropertyValue, error) {
|
||||
return ps.valueStore.Get(id)
|
||||
}
|
||||
|
||||
func (ps *PropertyService) GetPropertyValues(ids []string) ([]*model.PropertyValue, error) {
|
||||
return ps.valueStore.GetMany(ids)
|
||||
}
|
||||
|
||||
func (ps *PropertyService) SearchPropertyValues(opts model.PropertyValueSearchOpts) ([]*model.PropertyValue, error) {
|
||||
return ps.valueStore.SearchPropertyValues(opts)
|
||||
}
|
||||
|
||||
func (ps *PropertyService) UpdatePropertyValue(value *model.PropertyValue) (*model.PropertyValue, error) {
|
||||
values, err := ps.UpdatePropertyValues([]*model.PropertyValue{value})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return values[0], nil
|
||||
}
|
||||
|
||||
func (ps *PropertyService) UpdatePropertyValues(values []*model.PropertyValue) ([]*model.PropertyValue, error) {
|
||||
return ps.valueStore.Update(values)
|
||||
}
|
||||
|
||||
func (ps *PropertyService) DeletePropertyValue(id string) error {
|
||||
return ps.valueStore.Delete(id)
|
||||
}
|
||||
41
server/channels/app/properties/service.go
Normal file
41
server/channels/app/properties/service.go
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package properties
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/mattermost/mattermost/server/v8/channels/store"
|
||||
)
|
||||
|
||||
type PropertyService struct {
|
||||
groupStore store.PropertyGroupStore
|
||||
fieldStore store.PropertyFieldStore
|
||||
valueStore store.PropertyValueStore
|
||||
}
|
||||
|
||||
type ServiceConfig struct {
|
||||
PropertyGroupStore store.PropertyGroupStore
|
||||
PropertyFieldStore store.PropertyFieldStore
|
||||
PropertyValueStore store.PropertyValueStore
|
||||
}
|
||||
|
||||
func New(c ServiceConfig) (*PropertyService, error) {
|
||||
if err := c.validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &PropertyService{
|
||||
groupStore: c.PropertyGroupStore,
|
||||
fieldStore: c.PropertyFieldStore,
|
||||
valueStore: c.PropertyValueStore,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *ServiceConfig) validate() error {
|
||||
if c.PropertyGroupStore == nil || c.PropertyFieldStore == nil || c.PropertyValueStore == nil {
|
||||
return errors.New("required parameters are not provided")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
@ -35,6 +35,7 @@ import (
|
|||
"github.com/mattermost/mattermost/server/public/shared/timezones"
|
||||
"github.com/mattermost/mattermost/server/v8/channels/app/email"
|
||||
"github.com/mattermost/mattermost/server/v8/channels/app/platform"
|
||||
"github.com/mattermost/mattermost/server/v8/channels/app/properties"
|
||||
"github.com/mattermost/mattermost/server/v8/channels/app/teams"
|
||||
"github.com/mattermost/mattermost/server/v8/channels/app/users"
|
||||
"github.com/mattermost/mattermost/server/v8/channels/audit"
|
||||
|
|
@ -136,6 +137,7 @@ type Server struct {
|
|||
telemetryService *telemetry.TelemetryService
|
||||
userService *users.UserService
|
||||
teamService *teams.TeamService
|
||||
propertyService *properties.PropertyService
|
||||
|
||||
serviceMux sync.RWMutex
|
||||
remoteClusterService remotecluster.RemoteClusterServiceIFace
|
||||
|
|
@ -239,6 +241,15 @@ func NewServer(options ...Option) (*Server, error) {
|
|||
return nil, errors.Wrapf(err, "unable to create teams service")
|
||||
}
|
||||
|
||||
s.propertyService, err = properties.New(properties.ServiceConfig{
|
||||
PropertyGroupStore: s.Store().PropertyGroup(),
|
||||
PropertyFieldStore: s.Store().PropertyField(),
|
||||
PropertyValueStore: s.Store().PropertyValue(),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "unable to create properties service")
|
||||
}
|
||||
|
||||
// It is important to initialize the hub only after the global logger is set
|
||||
// to avoid race conditions while logging from inside the hub.
|
||||
// Step 4: Start platform
|
||||
|
|
|
|||
|
|
@ -253,6 +253,8 @@ channels/db/migrations/mysql/000127_add_mfa_used_ts_to_users.down.sql
|
|||
channels/db/migrations/mysql/000127_add_mfa_used_ts_to_users.up.sql
|
||||
channels/db/migrations/mysql/000128_create_scheduled_posts.down.sql
|
||||
channels/db/migrations/mysql/000128_create_scheduled_posts.up.sql
|
||||
channels/db/migrations/mysql/000129_add_property_system_architecture.down.sql
|
||||
channels/db/migrations/mysql/000129_add_property_system_architecture.up.sql
|
||||
channels/db/migrations/postgres/000001_create_teams.down.sql
|
||||
channels/db/migrations/postgres/000001_create_teams.up.sql
|
||||
channels/db/migrations/postgres/000002_create_team_members.down.sql
|
||||
|
|
@ -507,3 +509,5 @@ channels/db/migrations/postgres/000127_add_mfa_used_ts_to_users.down.sql
|
|||
channels/db/migrations/postgres/000127_add_mfa_used_ts_to_users.up.sql
|
||||
channels/db/migrations/postgres/000128_create_scheduled_posts.down.sql
|
||||
channels/db/migrations/postgres/000128_create_scheduled_posts.up.sql
|
||||
channels/db/migrations/postgres/000129_add_property_system_architecture.down.sql
|
||||
channels/db/migrations/postgres/000129_add_property_system_architecture.up.sql
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
DROP TABLE IF EXISTS PropertyGroups;
|
||||
DROP TABLE IF EXISTS PropertyFields;
|
||||
DROP TABLE IF EXISTS PropertyValues;
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
CREATE TABLE IF NOT EXISTS PropertyGroups (
|
||||
ID varchar(26) PRIMARY KEY,
|
||||
Name varchar(64) NOT NULL,
|
||||
UNIQUE(Name)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS PropertyFields (
|
||||
ID varchar(26) PRIMARY KEY,
|
||||
GroupID varchar(26) NOT NULL,
|
||||
Name varchar(255) NOT NULL,
|
||||
Type enum('text', 'select', 'multiselect', 'date', 'user', 'multiuser'),
|
||||
Attrs json,
|
||||
TargetID varchar(255),
|
||||
TargetType varchar(255),
|
||||
CreateAt bigint(20),
|
||||
UpdateAt bigint(20),
|
||||
DeleteAt bigint(20),
|
||||
UNIQUE(GroupID, TargetID, Name, DeleteAt)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS PropertyValues (
|
||||
ID varchar(26) PRIMARY KEY,
|
||||
TargetID varchar(255) NOT NULL,
|
||||
TargetType varchar(255) NOT NULL,
|
||||
GroupID varchar(26) NOT NULL,
|
||||
FieldID varchar(26) NOT NULL,
|
||||
Value json,
|
||||
CreateAt bigint(20),
|
||||
UpdateAt bigint(20),
|
||||
DeleteAt bigint(20),
|
||||
UNIQUE(GroupID, TargetID, FieldID, DeleteAt)
|
||||
);
|
||||
|
||||
SET @preparedStatement = (SELECT IF(
|
||||
(
|
||||
SELECT COUNT(*) FROM INFORMATION_SCHEMA.STATISTICS
|
||||
WHERE table_name = 'PropertyValues'
|
||||
AND table_schema = DATABASE()
|
||||
AND index_name = 'idx_propertyvalues_targetid_groupid'
|
||||
) > 0,
|
||||
'SELECT 1',
|
||||
'CREATE INDEX idx_propertyvalues_targetid_groupid ON PropertyValues (TargetID, GroupID);'
|
||||
));
|
||||
|
||||
PREPARE createIndexIfNotExists FROM @preparedStatement;
|
||||
EXECUTE createIndexIfNotExists;
|
||||
DEALLOCATE PREPARE createIndexIfNotExists;
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
DROP TABLE IF EXISTS PropertyGroups;
|
||||
DROP TABLE IF EXISTS PropertyFields;
|
||||
DROP TABLE IF EXISTS PropertyValues;
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
CREATE TABLE IF NOT EXISTS PropertyGroups (
|
||||
ID varchar(26) PRIMARY KEY,
|
||||
Name varchar(64) NOT NULL,
|
||||
UNIQUE(Name)
|
||||
);
|
||||
|
||||
DO
|
||||
$$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT * FROM pg_type typ
|
||||
INNER JOIN pg_namespace nsp ON nsp.oid = typ.typnamespace
|
||||
WHERE nsp.nspname = current_schema()
|
||||
AND typ.typname = 'property_field_type') THEN
|
||||
CREATE TYPE property_field_type AS ENUM (
|
||||
'text',
|
||||
'select',
|
||||
'multiselect',
|
||||
'date',
|
||||
'user',
|
||||
'multiuser'
|
||||
);
|
||||
END IF;
|
||||
END;
|
||||
$$
|
||||
LANGUAGE plpgsql;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS PropertyFields (
|
||||
ID varchar(26) PRIMARY KEY,
|
||||
GroupID varchar(26) NOT NULL,
|
||||
Name varchar(255) NOT NULL,
|
||||
Type property_field_type,
|
||||
Attrs jsonb,
|
||||
TargetID varchar(255),
|
||||
TargetType varchar(255),
|
||||
CreateAt bigint NOT NULL,
|
||||
UpdateAt bigint NOT NULL,
|
||||
DeleteAt bigint NOT NULL
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_propertyfields_unique ON PropertyFields (GroupID, TargetID, Name) WHERE DeleteAt = 0;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS PropertyValues (
|
||||
ID varchar(26) PRIMARY KEY,
|
||||
TargetID varchar(255) NOT NULL,
|
||||
TargetType varchar(255) NOT NULL,
|
||||
GroupID varchar(26) NOT NULL,
|
||||
FieldID varchar(26) NOT NULL,
|
||||
Value jsonb NOT NULL,
|
||||
CreateAt bigint NOT NULL,
|
||||
UpdateAt bigint NOT NULL,
|
||||
DeleteAt bigint NOT NULL
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_propertyvalues_unique ON PropertyValues (GroupID, TargetID, FieldID) WHERE DeleteAt = 0;
|
||||
CREATE INDEX IF NOT EXISTS idx_propertyvalues_targetid_groupid ON PropertyValues (TargetID, GroupID);
|
||||
|
|
@ -46,6 +46,9 @@ type OpenTracingLayer struct {
|
|||
PostPriorityStore store.PostPriorityStore
|
||||
PreferenceStore store.PreferenceStore
|
||||
ProductNoticesStore store.ProductNoticesStore
|
||||
PropertyFieldStore store.PropertyFieldStore
|
||||
PropertyGroupStore store.PropertyGroupStore
|
||||
PropertyValueStore store.PropertyValueStore
|
||||
ReactionStore store.ReactionStore
|
||||
RemoteClusterStore store.RemoteClusterStore
|
||||
RetentionPolicyStore store.RetentionPolicyStore
|
||||
|
|
@ -175,6 +178,18 @@ func (s *OpenTracingLayer) ProductNotices() store.ProductNoticesStore {
|
|||
return s.ProductNoticesStore
|
||||
}
|
||||
|
||||
func (s *OpenTracingLayer) PropertyField() store.PropertyFieldStore {
|
||||
return s.PropertyFieldStore
|
||||
}
|
||||
|
||||
func (s *OpenTracingLayer) PropertyGroup() store.PropertyGroupStore {
|
||||
return s.PropertyGroupStore
|
||||
}
|
||||
|
||||
func (s *OpenTracingLayer) PropertyValue() store.PropertyValueStore {
|
||||
return s.PropertyValueStore
|
||||
}
|
||||
|
||||
func (s *OpenTracingLayer) Reaction() store.ReactionStore {
|
||||
return s.ReactionStore
|
||||
}
|
||||
|
|
@ -386,6 +401,21 @@ type OpenTracingLayerProductNoticesStore struct {
|
|||
Root *OpenTracingLayer
|
||||
}
|
||||
|
||||
type OpenTracingLayerPropertyFieldStore struct {
|
||||
store.PropertyFieldStore
|
||||
Root *OpenTracingLayer
|
||||
}
|
||||
|
||||
type OpenTracingLayerPropertyGroupStore struct {
|
||||
store.PropertyGroupStore
|
||||
Root *OpenTracingLayer
|
||||
}
|
||||
|
||||
type OpenTracingLayerPropertyValueStore struct {
|
||||
store.PropertyValueStore
|
||||
Root *OpenTracingLayer
|
||||
}
|
||||
|
||||
type OpenTracingLayerReactionStore struct {
|
||||
store.ReactionStore
|
||||
Root *OpenTracingLayer
|
||||
|
|
@ -7730,6 +7760,276 @@ func (s *OpenTracingLayerProductNoticesStore) View(userID string, notices []stri
|
|||
return err
|
||||
}
|
||||
|
||||
func (s *OpenTracingLayerPropertyFieldStore) Create(field *model.PropertyField) (*model.PropertyField, error) {
|
||||
origCtx := s.Root.Store.Context()
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "PropertyFieldStore.Create")
|
||||
s.Root.Store.SetContext(newCtx)
|
||||
defer func() {
|
||||
s.Root.Store.SetContext(origCtx)
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
result, err := s.PropertyFieldStore.Create(field)
|
||||
if err != nil {
|
||||
span.LogFields(spanlog.Error(err))
|
||||
ext.Error.Set(span, true)
|
||||
}
|
||||
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *OpenTracingLayerPropertyFieldStore) Delete(id string) error {
|
||||
origCtx := s.Root.Store.Context()
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "PropertyFieldStore.Delete")
|
||||
s.Root.Store.SetContext(newCtx)
|
||||
defer func() {
|
||||
s.Root.Store.SetContext(origCtx)
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
err := s.PropertyFieldStore.Delete(id)
|
||||
if err != nil {
|
||||
span.LogFields(spanlog.Error(err))
|
||||
ext.Error.Set(span, true)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *OpenTracingLayerPropertyFieldStore) Get(id string) (*model.PropertyField, error) {
|
||||
origCtx := s.Root.Store.Context()
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "PropertyFieldStore.Get")
|
||||
s.Root.Store.SetContext(newCtx)
|
||||
defer func() {
|
||||
s.Root.Store.SetContext(origCtx)
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
result, err := s.PropertyFieldStore.Get(id)
|
||||
if err != nil {
|
||||
span.LogFields(spanlog.Error(err))
|
||||
ext.Error.Set(span, true)
|
||||
}
|
||||
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *OpenTracingLayerPropertyFieldStore) GetMany(ids []string) ([]*model.PropertyField, error) {
|
||||
origCtx := s.Root.Store.Context()
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "PropertyFieldStore.GetMany")
|
||||
s.Root.Store.SetContext(newCtx)
|
||||
defer func() {
|
||||
s.Root.Store.SetContext(origCtx)
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
result, err := s.PropertyFieldStore.GetMany(ids)
|
||||
if err != nil {
|
||||
span.LogFields(spanlog.Error(err))
|
||||
ext.Error.Set(span, true)
|
||||
}
|
||||
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *OpenTracingLayerPropertyFieldStore) SearchPropertyFields(opts model.PropertyFieldSearchOpts) ([]*model.PropertyField, error) {
|
||||
origCtx := s.Root.Store.Context()
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "PropertyFieldStore.SearchPropertyFields")
|
||||
s.Root.Store.SetContext(newCtx)
|
||||
defer func() {
|
||||
s.Root.Store.SetContext(origCtx)
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
result, err := s.PropertyFieldStore.SearchPropertyFields(opts)
|
||||
if err != nil {
|
||||
span.LogFields(spanlog.Error(err))
|
||||
ext.Error.Set(span, true)
|
||||
}
|
||||
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *OpenTracingLayerPropertyFieldStore) Update(field []*model.PropertyField) ([]*model.PropertyField, error) {
|
||||
origCtx := s.Root.Store.Context()
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "PropertyFieldStore.Update")
|
||||
s.Root.Store.SetContext(newCtx)
|
||||
defer func() {
|
||||
s.Root.Store.SetContext(origCtx)
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
result, err := s.PropertyFieldStore.Update(field)
|
||||
if err != nil {
|
||||
span.LogFields(spanlog.Error(err))
|
||||
ext.Error.Set(span, true)
|
||||
}
|
||||
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *OpenTracingLayerPropertyGroupStore) Get(name string) (*model.PropertyGroup, error) {
|
||||
origCtx := s.Root.Store.Context()
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "PropertyGroupStore.Get")
|
||||
s.Root.Store.SetContext(newCtx)
|
||||
defer func() {
|
||||
s.Root.Store.SetContext(origCtx)
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
result, err := s.PropertyGroupStore.Get(name)
|
||||
if err != nil {
|
||||
span.LogFields(spanlog.Error(err))
|
||||
ext.Error.Set(span, true)
|
||||
}
|
||||
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *OpenTracingLayerPropertyGroupStore) Register(name string) (*model.PropertyGroup, error) {
|
||||
origCtx := s.Root.Store.Context()
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "PropertyGroupStore.Register")
|
||||
s.Root.Store.SetContext(newCtx)
|
||||
defer func() {
|
||||
s.Root.Store.SetContext(origCtx)
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
result, err := s.PropertyGroupStore.Register(name)
|
||||
if err != nil {
|
||||
span.LogFields(spanlog.Error(err))
|
||||
ext.Error.Set(span, true)
|
||||
}
|
||||
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *OpenTracingLayerPropertyValueStore) Create(value *model.PropertyValue) (*model.PropertyValue, error) {
|
||||
origCtx := s.Root.Store.Context()
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "PropertyValueStore.Create")
|
||||
s.Root.Store.SetContext(newCtx)
|
||||
defer func() {
|
||||
s.Root.Store.SetContext(origCtx)
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
result, err := s.PropertyValueStore.Create(value)
|
||||
if err != nil {
|
||||
span.LogFields(spanlog.Error(err))
|
||||
ext.Error.Set(span, true)
|
||||
}
|
||||
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *OpenTracingLayerPropertyValueStore) Delete(id string) error {
|
||||
origCtx := s.Root.Store.Context()
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "PropertyValueStore.Delete")
|
||||
s.Root.Store.SetContext(newCtx)
|
||||
defer func() {
|
||||
s.Root.Store.SetContext(origCtx)
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
err := s.PropertyValueStore.Delete(id)
|
||||
if err != nil {
|
||||
span.LogFields(spanlog.Error(err))
|
||||
ext.Error.Set(span, true)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *OpenTracingLayerPropertyValueStore) DeleteForField(id string) error {
|
||||
origCtx := s.Root.Store.Context()
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "PropertyValueStore.DeleteForField")
|
||||
s.Root.Store.SetContext(newCtx)
|
||||
defer func() {
|
||||
s.Root.Store.SetContext(origCtx)
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
err := s.PropertyValueStore.DeleteForField(id)
|
||||
if err != nil {
|
||||
span.LogFields(spanlog.Error(err))
|
||||
ext.Error.Set(span, true)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *OpenTracingLayerPropertyValueStore) Get(id string) (*model.PropertyValue, error) {
|
||||
origCtx := s.Root.Store.Context()
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "PropertyValueStore.Get")
|
||||
s.Root.Store.SetContext(newCtx)
|
||||
defer func() {
|
||||
s.Root.Store.SetContext(origCtx)
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
result, err := s.PropertyValueStore.Get(id)
|
||||
if err != nil {
|
||||
span.LogFields(spanlog.Error(err))
|
||||
ext.Error.Set(span, true)
|
||||
}
|
||||
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *OpenTracingLayerPropertyValueStore) GetMany(ids []string) ([]*model.PropertyValue, error) {
|
||||
origCtx := s.Root.Store.Context()
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "PropertyValueStore.GetMany")
|
||||
s.Root.Store.SetContext(newCtx)
|
||||
defer func() {
|
||||
s.Root.Store.SetContext(origCtx)
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
result, err := s.PropertyValueStore.GetMany(ids)
|
||||
if err != nil {
|
||||
span.LogFields(spanlog.Error(err))
|
||||
ext.Error.Set(span, true)
|
||||
}
|
||||
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *OpenTracingLayerPropertyValueStore) SearchPropertyValues(opts model.PropertyValueSearchOpts) ([]*model.PropertyValue, error) {
|
||||
origCtx := s.Root.Store.Context()
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "PropertyValueStore.SearchPropertyValues")
|
||||
s.Root.Store.SetContext(newCtx)
|
||||
defer func() {
|
||||
s.Root.Store.SetContext(origCtx)
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
result, err := s.PropertyValueStore.SearchPropertyValues(opts)
|
||||
if err != nil {
|
||||
span.LogFields(spanlog.Error(err))
|
||||
ext.Error.Set(span, true)
|
||||
}
|
||||
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *OpenTracingLayerPropertyValueStore) Update(field []*model.PropertyValue) ([]*model.PropertyValue, error) {
|
||||
origCtx := s.Root.Store.Context()
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "PropertyValueStore.Update")
|
||||
s.Root.Store.SetContext(newCtx)
|
||||
defer func() {
|
||||
s.Root.Store.SetContext(origCtx)
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
result, err := s.PropertyValueStore.Update(field)
|
||||
if err != nil {
|
||||
span.LogFields(spanlog.Error(err))
|
||||
ext.Error.Set(span, true)
|
||||
}
|
||||
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *OpenTracingLayerReactionStore) BulkGetForPosts(postIds []string) ([]*model.Reaction, error) {
|
||||
origCtx := s.Root.Store.Context()
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ReactionStore.BulkGetForPosts")
|
||||
|
|
@ -13853,6 +14153,9 @@ func New(childStore store.Store, ctx context.Context) *OpenTracingLayer {
|
|||
newStore.PostPriorityStore = &OpenTracingLayerPostPriorityStore{PostPriorityStore: childStore.PostPriority(), Root: &newStore}
|
||||
newStore.PreferenceStore = &OpenTracingLayerPreferenceStore{PreferenceStore: childStore.Preference(), Root: &newStore}
|
||||
newStore.ProductNoticesStore = &OpenTracingLayerProductNoticesStore{ProductNoticesStore: childStore.ProductNotices(), Root: &newStore}
|
||||
newStore.PropertyFieldStore = &OpenTracingLayerPropertyFieldStore{PropertyFieldStore: childStore.PropertyField(), Root: &newStore}
|
||||
newStore.PropertyGroupStore = &OpenTracingLayerPropertyGroupStore{PropertyGroupStore: childStore.PropertyGroup(), Root: &newStore}
|
||||
newStore.PropertyValueStore = &OpenTracingLayerPropertyValueStore{PropertyValueStore: childStore.PropertyValue(), Root: &newStore}
|
||||
newStore.ReactionStore = &OpenTracingLayerReactionStore{ReactionStore: childStore.Reaction(), Root: &newStore}
|
||||
newStore.RemoteClusterStore = &OpenTracingLayerRemoteClusterStore{RemoteClusterStore: childStore.RemoteCluster(), Root: &newStore}
|
||||
newStore.RetentionPolicyStore = &OpenTracingLayerRetentionPolicyStore{RetentionPolicyStore: childStore.RetentionPolicy(), Root: &newStore}
|
||||
|
|
|
|||
|
|
@ -50,6 +50,9 @@ type RetryLayer struct {
|
|||
PostPriorityStore store.PostPriorityStore
|
||||
PreferenceStore store.PreferenceStore
|
||||
ProductNoticesStore store.ProductNoticesStore
|
||||
PropertyFieldStore store.PropertyFieldStore
|
||||
PropertyGroupStore store.PropertyGroupStore
|
||||
PropertyValueStore store.PropertyValueStore
|
||||
ReactionStore store.ReactionStore
|
||||
RemoteClusterStore store.RemoteClusterStore
|
||||
RetentionPolicyStore store.RetentionPolicyStore
|
||||
|
|
@ -179,6 +182,18 @@ func (s *RetryLayer) ProductNotices() store.ProductNoticesStore {
|
|||
return s.ProductNoticesStore
|
||||
}
|
||||
|
||||
func (s *RetryLayer) PropertyField() store.PropertyFieldStore {
|
||||
return s.PropertyFieldStore
|
||||
}
|
||||
|
||||
func (s *RetryLayer) PropertyGroup() store.PropertyGroupStore {
|
||||
return s.PropertyGroupStore
|
||||
}
|
||||
|
||||
func (s *RetryLayer) PropertyValue() store.PropertyValueStore {
|
||||
return s.PropertyValueStore
|
||||
}
|
||||
|
||||
func (s *RetryLayer) Reaction() store.ReactionStore {
|
||||
return s.ReactionStore
|
||||
}
|
||||
|
|
@ -390,6 +405,21 @@ type RetryLayerProductNoticesStore struct {
|
|||
Root *RetryLayer
|
||||
}
|
||||
|
||||
type RetryLayerPropertyFieldStore struct {
|
||||
store.PropertyFieldStore
|
||||
Root *RetryLayer
|
||||
}
|
||||
|
||||
type RetryLayerPropertyGroupStore struct {
|
||||
store.PropertyGroupStore
|
||||
Root *RetryLayer
|
||||
}
|
||||
|
||||
type RetryLayerPropertyValueStore struct {
|
||||
store.PropertyValueStore
|
||||
Root *RetryLayer
|
||||
}
|
||||
|
||||
type RetryLayerReactionStore struct {
|
||||
store.ReactionStore
|
||||
Root *RetryLayer
|
||||
|
|
@ -8793,6 +8823,321 @@ func (s *RetryLayerProductNoticesStore) View(userID string, notices []string) er
|
|||
|
||||
}
|
||||
|
||||
func (s *RetryLayerPropertyFieldStore) Create(field *model.PropertyField) (*model.PropertyField, error) {
|
||||
|
||||
tries := 0
|
||||
for {
|
||||
result, err := s.PropertyFieldStore.Create(field)
|
||||
if err == nil {
|
||||
return result, nil
|
||||
}
|
||||
if !isRepeatableError(err) {
|
||||
return result, err
|
||||
}
|
||||
tries++
|
||||
if tries >= 3 {
|
||||
err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
|
||||
return result, err
|
||||
}
|
||||
timepkg.Sleep(100 * timepkg.Millisecond)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerPropertyFieldStore) Delete(id string) error {
|
||||
|
||||
tries := 0
|
||||
for {
|
||||
err := s.PropertyFieldStore.Delete(id)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
if !isRepeatableError(err) {
|
||||
return err
|
||||
}
|
||||
tries++
|
||||
if tries >= 3 {
|
||||
err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
|
||||
return err
|
||||
}
|
||||
timepkg.Sleep(100 * timepkg.Millisecond)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerPropertyFieldStore) Get(id string) (*model.PropertyField, error) {
|
||||
|
||||
tries := 0
|
||||
for {
|
||||
result, err := s.PropertyFieldStore.Get(id)
|
||||
if err == nil {
|
||||
return result, nil
|
||||
}
|
||||
if !isRepeatableError(err) {
|
||||
return result, err
|
||||
}
|
||||
tries++
|
||||
if tries >= 3 {
|
||||
err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
|
||||
return result, err
|
||||
}
|
||||
timepkg.Sleep(100 * timepkg.Millisecond)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerPropertyFieldStore) GetMany(ids []string) ([]*model.PropertyField, error) {
|
||||
|
||||
tries := 0
|
||||
for {
|
||||
result, err := s.PropertyFieldStore.GetMany(ids)
|
||||
if err == nil {
|
||||
return result, nil
|
||||
}
|
||||
if !isRepeatableError(err) {
|
||||
return result, err
|
||||
}
|
||||
tries++
|
||||
if tries >= 3 {
|
||||
err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
|
||||
return result, err
|
||||
}
|
||||
timepkg.Sleep(100 * timepkg.Millisecond)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerPropertyFieldStore) SearchPropertyFields(opts model.PropertyFieldSearchOpts) ([]*model.PropertyField, error) {
|
||||
|
||||
tries := 0
|
||||
for {
|
||||
result, err := s.PropertyFieldStore.SearchPropertyFields(opts)
|
||||
if err == nil {
|
||||
return result, nil
|
||||
}
|
||||
if !isRepeatableError(err) {
|
||||
return result, err
|
||||
}
|
||||
tries++
|
||||
if tries >= 3 {
|
||||
err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
|
||||
return result, err
|
||||
}
|
||||
timepkg.Sleep(100 * timepkg.Millisecond)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerPropertyFieldStore) Update(field []*model.PropertyField) ([]*model.PropertyField, error) {
|
||||
|
||||
tries := 0
|
||||
for {
|
||||
result, err := s.PropertyFieldStore.Update(field)
|
||||
if err == nil {
|
||||
return result, nil
|
||||
}
|
||||
if !isRepeatableError(err) {
|
||||
return result, err
|
||||
}
|
||||
tries++
|
||||
if tries >= 3 {
|
||||
err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
|
||||
return result, err
|
||||
}
|
||||
timepkg.Sleep(100 * timepkg.Millisecond)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerPropertyGroupStore) Get(name string) (*model.PropertyGroup, error) {
|
||||
|
||||
tries := 0
|
||||
for {
|
||||
result, err := s.PropertyGroupStore.Get(name)
|
||||
if err == nil {
|
||||
return result, nil
|
||||
}
|
||||
if !isRepeatableError(err) {
|
||||
return result, err
|
||||
}
|
||||
tries++
|
||||
if tries >= 3 {
|
||||
err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
|
||||
return result, err
|
||||
}
|
||||
timepkg.Sleep(100 * timepkg.Millisecond)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerPropertyGroupStore) Register(name string) (*model.PropertyGroup, error) {
|
||||
|
||||
tries := 0
|
||||
for {
|
||||
result, err := s.PropertyGroupStore.Register(name)
|
||||
if err == nil {
|
||||
return result, nil
|
||||
}
|
||||
if !isRepeatableError(err) {
|
||||
return result, err
|
||||
}
|
||||
tries++
|
||||
if tries >= 3 {
|
||||
err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
|
||||
return result, err
|
||||
}
|
||||
timepkg.Sleep(100 * timepkg.Millisecond)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerPropertyValueStore) Create(value *model.PropertyValue) (*model.PropertyValue, error) {
|
||||
|
||||
tries := 0
|
||||
for {
|
||||
result, err := s.PropertyValueStore.Create(value)
|
||||
if err == nil {
|
||||
return result, nil
|
||||
}
|
||||
if !isRepeatableError(err) {
|
||||
return result, err
|
||||
}
|
||||
tries++
|
||||
if tries >= 3 {
|
||||
err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
|
||||
return result, err
|
||||
}
|
||||
timepkg.Sleep(100 * timepkg.Millisecond)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerPropertyValueStore) Delete(id string) error {
|
||||
|
||||
tries := 0
|
||||
for {
|
||||
err := s.PropertyValueStore.Delete(id)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
if !isRepeatableError(err) {
|
||||
return err
|
||||
}
|
||||
tries++
|
||||
if tries >= 3 {
|
||||
err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
|
||||
return err
|
||||
}
|
||||
timepkg.Sleep(100 * timepkg.Millisecond)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerPropertyValueStore) DeleteForField(id string) error {
|
||||
|
||||
tries := 0
|
||||
for {
|
||||
err := s.PropertyValueStore.DeleteForField(id)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
if !isRepeatableError(err) {
|
||||
return err
|
||||
}
|
||||
tries++
|
||||
if tries >= 3 {
|
||||
err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
|
||||
return err
|
||||
}
|
||||
timepkg.Sleep(100 * timepkg.Millisecond)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerPropertyValueStore) Get(id string) (*model.PropertyValue, error) {
|
||||
|
||||
tries := 0
|
||||
for {
|
||||
result, err := s.PropertyValueStore.Get(id)
|
||||
if err == nil {
|
||||
return result, nil
|
||||
}
|
||||
if !isRepeatableError(err) {
|
||||
return result, err
|
||||
}
|
||||
tries++
|
||||
if tries >= 3 {
|
||||
err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
|
||||
return result, err
|
||||
}
|
||||
timepkg.Sleep(100 * timepkg.Millisecond)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerPropertyValueStore) GetMany(ids []string) ([]*model.PropertyValue, error) {
|
||||
|
||||
tries := 0
|
||||
for {
|
||||
result, err := s.PropertyValueStore.GetMany(ids)
|
||||
if err == nil {
|
||||
return result, nil
|
||||
}
|
||||
if !isRepeatableError(err) {
|
||||
return result, err
|
||||
}
|
||||
tries++
|
||||
if tries >= 3 {
|
||||
err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
|
||||
return result, err
|
||||
}
|
||||
timepkg.Sleep(100 * timepkg.Millisecond)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerPropertyValueStore) SearchPropertyValues(opts model.PropertyValueSearchOpts) ([]*model.PropertyValue, error) {
|
||||
|
||||
tries := 0
|
||||
for {
|
||||
result, err := s.PropertyValueStore.SearchPropertyValues(opts)
|
||||
if err == nil {
|
||||
return result, nil
|
||||
}
|
||||
if !isRepeatableError(err) {
|
||||
return result, err
|
||||
}
|
||||
tries++
|
||||
if tries >= 3 {
|
||||
err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
|
||||
return result, err
|
||||
}
|
||||
timepkg.Sleep(100 * timepkg.Millisecond)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerPropertyValueStore) Update(field []*model.PropertyValue) ([]*model.PropertyValue, error) {
|
||||
|
||||
tries := 0
|
||||
for {
|
||||
result, err := s.PropertyValueStore.Update(field)
|
||||
if err == nil {
|
||||
return result, nil
|
||||
}
|
||||
if !isRepeatableError(err) {
|
||||
return result, err
|
||||
}
|
||||
tries++
|
||||
if tries >= 3 {
|
||||
err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
|
||||
return result, err
|
||||
}
|
||||
timepkg.Sleep(100 * timepkg.Millisecond)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerReactionStore) BulkGetForPosts(postIds []string) ([]*model.Reaction, error) {
|
||||
|
||||
tries := 0
|
||||
|
|
@ -15806,6 +16151,9 @@ func New(childStore store.Store) *RetryLayer {
|
|||
newStore.PostPriorityStore = &RetryLayerPostPriorityStore{PostPriorityStore: childStore.PostPriority(), Root: &newStore}
|
||||
newStore.PreferenceStore = &RetryLayerPreferenceStore{PreferenceStore: childStore.Preference(), Root: &newStore}
|
||||
newStore.ProductNoticesStore = &RetryLayerProductNoticesStore{ProductNoticesStore: childStore.ProductNotices(), Root: &newStore}
|
||||
newStore.PropertyFieldStore = &RetryLayerPropertyFieldStore{PropertyFieldStore: childStore.PropertyField(), Root: &newStore}
|
||||
newStore.PropertyGroupStore = &RetryLayerPropertyGroupStore{PropertyGroupStore: childStore.PropertyGroup(), Root: &newStore}
|
||||
newStore.PropertyValueStore = &RetryLayerPropertyValueStore{PropertyValueStore: childStore.PropertyValue(), Root: &newStore}
|
||||
newStore.ReactionStore = &RetryLayerReactionStore{ReactionStore: childStore.Reaction(), Root: &newStore}
|
||||
newStore.RemoteClusterStore = &RetryLayerRemoteClusterStore{RemoteClusterStore: childStore.RemoteCluster(), Root: &newStore}
|
||||
newStore.RetentionPolicyStore = &RetryLayerRetentionPolicyStore{RetentionPolicyStore: childStore.RetentionPolicy(), Root: &newStore}
|
||||
|
|
|
|||
|
|
@ -63,6 +63,9 @@ func genStore() *mocks.Store {
|
|||
mock.On("DesktopTokens").Return(&mocks.DesktopTokensStore{})
|
||||
mock.On("ChannelBookmark").Return(&mocks.ChannelBookmarkStore{})
|
||||
mock.On("ScheduledPost").Return(&mocks.ScheduledPostStore{})
|
||||
mock.On("PropertyField").Return(&mocks.PropertyFieldStore{})
|
||||
mock.On("PropertyGroup").Return(&mocks.PropertyGroupStore{})
|
||||
mock.On("PropertyValue").Return(&mocks.PropertyValueStore{})
|
||||
return mock
|
||||
}
|
||||
|
||||
|
|
|
|||
321
server/channels/store/sqlstore/property_field_store.go
Normal file
321
server/channels/store/sqlstore/property_field_store.go
Normal file
|
|
@ -0,0 +1,321 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package sqlstore
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
sq "github.com/mattermost/squirrel"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/mattermost/mattermost/server/public/model"
|
||||
"github.com/mattermost/mattermost/server/v8/channels/store"
|
||||
)
|
||||
|
||||
func (s *SqlPropertyFieldStore) propertyFieldToInsertMap(field *model.PropertyField) (map[string]any, error) {
|
||||
attrsJSON, err := json.Marshal(field.Attrs)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "property_field_to_insert_map_marshal_attrs")
|
||||
}
|
||||
if s.IsBinaryParamEnabled() {
|
||||
attrsJSON = AppendBinaryFlag(attrsJSON)
|
||||
}
|
||||
|
||||
return map[string]any{
|
||||
"ID": field.ID,
|
||||
"GroupID": field.GroupID,
|
||||
"Name": field.Name,
|
||||
"Type": field.Type,
|
||||
"Attrs": attrsJSON,
|
||||
"TargetID": field.TargetID,
|
||||
"TargetType": field.TargetType,
|
||||
"CreateAt": field.CreateAt,
|
||||
"UpdateAt": field.UpdateAt,
|
||||
"DeleteAt": field.DeleteAt,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *SqlPropertyFieldStore) propertyFieldToUpdateMap(field *model.PropertyField) (map[string]any, error) {
|
||||
attrsJSON, err := json.Marshal(field.Attrs)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "property_field_to_update_map_marshal_attrs")
|
||||
}
|
||||
if s.IsBinaryParamEnabled() {
|
||||
attrsJSON = AppendBinaryFlag(attrsJSON)
|
||||
}
|
||||
|
||||
return map[string]any{
|
||||
"Name": field.Name,
|
||||
"Type": field.Type,
|
||||
"Attrs": attrsJSON,
|
||||
"TargetID": field.TargetID,
|
||||
"TargetType": field.TargetType,
|
||||
"UpdateAt": field.UpdateAt,
|
||||
"DeleteAt": field.DeleteAt,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func propertyFieldsFromRows(rows *sql.Rows) ([]*model.PropertyField, error) {
|
||||
results := []*model.PropertyField{}
|
||||
|
||||
for rows.Next() {
|
||||
var field model.PropertyField
|
||||
var attrsJSON string
|
||||
|
||||
err := rows.Scan(
|
||||
&field.ID,
|
||||
&field.GroupID,
|
||||
&field.Name,
|
||||
&field.Type,
|
||||
&attrsJSON,
|
||||
&field.TargetID,
|
||||
&field.TargetType,
|
||||
&field.CreateAt,
|
||||
&field.UpdateAt,
|
||||
&field.DeleteAt,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := json.Unmarshal([]byte(attrsJSON), &field.Attrs); err != nil {
|
||||
return nil, errors.Wrap(err, "property_fields_from_rows_unmarshal_attrs")
|
||||
}
|
||||
|
||||
results = append(results, &field)
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func propertyFieldFromRows(rows *sql.Rows) (*model.PropertyField, error) {
|
||||
fields, err := propertyFieldsFromRows(rows)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(fields) > 0 {
|
||||
return fields[0], nil
|
||||
}
|
||||
|
||||
return nil, sql.ErrNoRows
|
||||
}
|
||||
|
||||
type SqlPropertyFieldStore struct {
|
||||
*SqlStore
|
||||
|
||||
tableSelectQuery sq.SelectBuilder
|
||||
}
|
||||
|
||||
func newPropertyFieldStore(sqlStore *SqlStore) store.PropertyFieldStore {
|
||||
s := SqlPropertyFieldStore{SqlStore: sqlStore}
|
||||
|
||||
s.tableSelectQuery = s.getQueryBuilder().
|
||||
Select("ID", "GroupID", "Name", "Type", "Attrs", "TargetID", "TargetType", "CreateAt", "UpdateAt", "DeleteAt").
|
||||
From("PropertyFields")
|
||||
|
||||
return &s
|
||||
}
|
||||
|
||||
func (s *SqlPropertyFieldStore) Create(field *model.PropertyField) (*model.PropertyField, error) {
|
||||
if field.ID != "" {
|
||||
return nil, store.NewErrInvalidInput("PropertyField", "id", field.ID)
|
||||
}
|
||||
|
||||
field.PreSave()
|
||||
|
||||
if err := field.IsValid(); err != nil {
|
||||
return nil, errors.Wrap(err, "property_field_create_isvalid")
|
||||
}
|
||||
|
||||
insertMap, err := s.propertyFieldToInsertMap(field)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
builder := s.getQueryBuilder().
|
||||
Insert("PropertyFields").
|
||||
SetMap(insertMap)
|
||||
|
||||
if _, err := s.GetMaster().ExecBuilder(builder); err != nil {
|
||||
return nil, errors.Wrap(err, "property_field_create_insert")
|
||||
}
|
||||
|
||||
return field, nil
|
||||
}
|
||||
|
||||
func (s *SqlPropertyFieldStore) Get(id string) (*model.PropertyField, error) {
|
||||
queryString, args, err := s.tableSelectQuery.
|
||||
Where(sq.Eq{"id": id}).
|
||||
ToSql()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "property_field_get_tosql")
|
||||
}
|
||||
|
||||
rows, err := s.GetReplica().Query(queryString, args...)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "property_field_get_select")
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
field, err := propertyFieldFromRows(rows)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "property_field_get_propertyfieldfromrows")
|
||||
}
|
||||
|
||||
return field, nil
|
||||
}
|
||||
|
||||
func (s *SqlPropertyFieldStore) GetMany(ids []string) ([]*model.PropertyField, error) {
|
||||
queryString, args, err := s.tableSelectQuery.
|
||||
Where(sq.Eq{"id": ids}).
|
||||
ToSql()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "property_field_get_many_tosql")
|
||||
}
|
||||
|
||||
rows, err := s.GetReplica().Query(queryString, args...)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "property_field_get_many_query")
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
fields, err := propertyFieldsFromRows(rows)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "property_field_get_many_propertyfieldfromrows")
|
||||
}
|
||||
|
||||
if len(fields) < len(ids) {
|
||||
return nil, fmt.Errorf("missmatch results: got %d results of the %d ids passed", len(fields), len(ids))
|
||||
}
|
||||
|
||||
return fields, nil
|
||||
}
|
||||
|
||||
func (s *SqlPropertyFieldStore) SearchPropertyFields(opts model.PropertyFieldSearchOpts) ([]*model.PropertyField, error) {
|
||||
if opts.Page < 0 {
|
||||
return nil, errors.New("page must be positive integer")
|
||||
}
|
||||
|
||||
if opts.PerPage < 1 {
|
||||
return nil, errors.New("per page must be positive integer greater than zero")
|
||||
}
|
||||
|
||||
query := s.tableSelectQuery.
|
||||
OrderBy("CreateAt ASC").
|
||||
Offset(uint64(opts.Page * opts.PerPage)).
|
||||
Limit(uint64(opts.PerPage))
|
||||
|
||||
if !opts.IncludeDeleted {
|
||||
query = query.Where(sq.Eq{"DeleteAt": 0})
|
||||
}
|
||||
|
||||
if opts.GroupID != "" {
|
||||
query = query.Where(sq.Eq{"GroupID": opts.GroupID})
|
||||
}
|
||||
|
||||
if opts.TargetType != "" {
|
||||
query = query.Where(sq.Eq{"TargetType": opts.TargetType})
|
||||
}
|
||||
|
||||
if opts.TargetID != "" {
|
||||
query = query.Where(sq.Eq{"TargetID": opts.TargetID})
|
||||
}
|
||||
|
||||
queryString, args, err := query.ToSql()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "property_field_search_tosql")
|
||||
}
|
||||
|
||||
rows, err := s.GetReplica().Query(queryString, args...)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "property_field_search_query")
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
fields, err := propertyFieldsFromRows(rows)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "property_field_search_propertyfieldfromrows")
|
||||
}
|
||||
|
||||
return fields, nil
|
||||
}
|
||||
|
||||
func (s *SqlPropertyFieldStore) Update(fields []*model.PropertyField) (_ []*model.PropertyField, err error) {
|
||||
if len(fields) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
transaction, err := s.GetMaster().Beginx()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "property_field_update_begin_transaction")
|
||||
}
|
||||
defer finalizeTransactionX(transaction, &err)
|
||||
|
||||
updateTime := model.GetMillis()
|
||||
for _, field := range fields {
|
||||
field.UpdateAt = updateTime
|
||||
|
||||
if vErr := field.IsValid(); vErr != nil {
|
||||
return nil, errors.Wrap(vErr, "property_field_update_isvalid")
|
||||
}
|
||||
|
||||
updateMap, err := s.propertyFieldToUpdateMap(field)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
queryString, args, err := s.getQueryBuilder().
|
||||
Update("PropertyFields").
|
||||
SetMap(updateMap).
|
||||
Where(sq.Eq{"id": field.ID}).
|
||||
ToSql()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "property_field_update_tosql")
|
||||
}
|
||||
|
||||
result, err := transaction.Exec(queryString, args...)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to update property field with id: %s", field.ID)
|
||||
}
|
||||
|
||||
count, err := result.RowsAffected()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "property_field_update_rowsaffected")
|
||||
}
|
||||
if count == 0 {
|
||||
return nil, store.NewErrNotFound("PropertyField", field.ID)
|
||||
}
|
||||
}
|
||||
|
||||
if err := transaction.Commit(); err != nil {
|
||||
return nil, errors.Wrap(err, "property_field_update_commit")
|
||||
}
|
||||
|
||||
return fields, nil
|
||||
}
|
||||
|
||||
func (s *SqlPropertyFieldStore) Delete(id string) error {
|
||||
builder := s.getQueryBuilder().
|
||||
Update("PropertyFields").
|
||||
Set("DeleteAt", model.GetMillis()).
|
||||
Where(sq.Eq{"id": id})
|
||||
|
||||
result, err := s.GetMaster().ExecBuilder(builder)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to delete property field with id: %s", id)
|
||||
}
|
||||
|
||||
count, err := result.RowsAffected()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "property_field_delete_rowsaffected")
|
||||
}
|
||||
if count == 0 {
|
||||
return store.NewErrNotFound("PropertyField", id)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
14
server/channels/store/sqlstore/property_field_store_test.go
Normal file
14
server/channels/store/sqlstore/property_field_store_test.go
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package sqlstore
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/mattermost/mattermost/server/v8/channels/store/storetest"
|
||||
)
|
||||
|
||||
func TestPropertyFieldStore(t *testing.T) {
|
||||
StoreTestWithSqlStore(t, storetest.TestPropertyFieldStore)
|
||||
}
|
||||
78
server/channels/store/sqlstore/property_group_store.go
Normal file
78
server/channels/store/sqlstore/property_group_store.go
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package sqlstore
|
||||
|
||||
import (
|
||||
sq "github.com/mattermost/squirrel"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/mattermost/mattermost/server/public/model"
|
||||
"github.com/mattermost/mattermost/server/v8/channels/store"
|
||||
)
|
||||
|
||||
var propertyGroupColumns = []string{"ID", "Name"}
|
||||
|
||||
type SqlPropertyGroupStore struct {
|
||||
*SqlStore
|
||||
}
|
||||
|
||||
func newPropertyGroupStore(sqlStore *SqlStore) store.PropertyGroupStore {
|
||||
return &SqlPropertyGroupStore{sqlStore}
|
||||
}
|
||||
|
||||
func (s *SqlPropertyGroupStore) Register(name string) (*model.PropertyGroup, error) {
|
||||
if name == "" {
|
||||
return nil, store.NewErrInvalidInput("PropertyGroup", "name", name)
|
||||
}
|
||||
|
||||
group := &model.PropertyGroup{Name: name}
|
||||
group.PreSave()
|
||||
|
||||
builder := s.getQueryBuilder().
|
||||
Insert("PropertyGroups").
|
||||
Columns("ID", "Name").
|
||||
Values(group.ID, group.Name)
|
||||
|
||||
if s.DriverName() == model.DatabaseDriverMysql {
|
||||
builder = builder.SuffixExpr(sq.Expr("ON DUPLICATE KEY UPDATE Name=Name"))
|
||||
} else {
|
||||
builder = builder.SuffixExpr(sq.Expr("ON CONFLICT (Name) DO NOTHING"))
|
||||
}
|
||||
|
||||
r, err := s.GetMaster().ExecBuilder(builder)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "property_group_register_insert")
|
||||
}
|
||||
|
||||
rowsAffected, err := r.RowsAffected()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "property_group_register_rows_affected")
|
||||
}
|
||||
|
||||
// there was a conflict during the insert, so we need to fetch the
|
||||
// group to get its data
|
||||
if rowsAffected == 0 {
|
||||
return s.Get(name)
|
||||
}
|
||||
|
||||
return group, nil
|
||||
}
|
||||
|
||||
func (s *SqlPropertyGroupStore) Get(name string) (*model.PropertyGroup, error) {
|
||||
queryString, args, err := s.getQueryBuilder().
|
||||
Select(propertyGroupColumns...).
|
||||
From("PropertyGroups").
|
||||
Where(sq.Eq{"Name": name}).
|
||||
ToSql()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "property_group_get_tosql")
|
||||
}
|
||||
|
||||
var propertyGroup model.PropertyGroup
|
||||
if err := s.GetReplica().Get(&propertyGroup, queryString, args...); err != nil {
|
||||
return nil, store.NewErrNotFound("PropertyGroup", name)
|
||||
}
|
||||
|
||||
return &propertyGroup, nil
|
||||
}
|
||||
14
server/channels/store/sqlstore/property_group_store_test.go
Normal file
14
server/channels/store/sqlstore/property_group_store_test.go
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package sqlstore
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/mattermost/mattermost/server/v8/channels/store/storetest"
|
||||
)
|
||||
|
||||
func TestPropertyGroupStore(t *testing.T) {
|
||||
StoreTestWithSqlStore(t, storetest.TestPropertyGroupStore)
|
||||
}
|
||||
332
server/channels/store/sqlstore/property_value_store.go
Normal file
332
server/channels/store/sqlstore/property_value_store.go
Normal file
|
|
@ -0,0 +1,332 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package sqlstore
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
sq "github.com/mattermost/squirrel"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/mattermost/mattermost/server/public/model"
|
||||
"github.com/mattermost/mattermost/server/v8/channels/store"
|
||||
)
|
||||
|
||||
func (s *SqlPropertyValueStore) propertyValueToInsertMap(value *model.PropertyValue) (map[string]any, error) {
|
||||
valueJSON, err := json.Marshal(value.Value)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "property_value_to_insert_map_marshal_value")
|
||||
}
|
||||
if s.IsBinaryParamEnabled() {
|
||||
valueJSON = AppendBinaryFlag(valueJSON)
|
||||
}
|
||||
|
||||
return map[string]any{
|
||||
"ID": value.ID,
|
||||
"TargetID": value.TargetID,
|
||||
"TargetType": value.TargetType,
|
||||
"GroupID": value.GroupID,
|
||||
"FieldID": value.FieldID,
|
||||
"Value": valueJSON,
|
||||
"CreateAt": value.CreateAt,
|
||||
"UpdateAt": value.UpdateAt,
|
||||
"DeleteAt": value.DeleteAt,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *SqlPropertyValueStore) propertyValueToUpdateMap(value *model.PropertyValue) (map[string]any, error) {
|
||||
valueJSON, err := json.Marshal(value.Value)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "property_value_to_udpate_map_marshal_value")
|
||||
}
|
||||
if s.IsBinaryParamEnabled() {
|
||||
valueJSON = AppendBinaryFlag(valueJSON)
|
||||
}
|
||||
|
||||
return map[string]any{
|
||||
"Value": valueJSON,
|
||||
"UpdateAt": value.UpdateAt,
|
||||
"DeleteAt": value.DeleteAt,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func propertyValuesFromRows(rows *sql.Rows) ([]*model.PropertyValue, error) {
|
||||
results := []*model.PropertyValue{}
|
||||
|
||||
for rows.Next() {
|
||||
var value model.PropertyValue
|
||||
var valueJSON string
|
||||
|
||||
err := rows.Scan(
|
||||
&value.ID,
|
||||
&value.TargetID,
|
||||
&value.TargetType,
|
||||
&value.GroupID,
|
||||
&value.FieldID,
|
||||
&valueJSON,
|
||||
&value.CreateAt,
|
||||
&value.UpdateAt,
|
||||
&value.DeleteAt,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := json.Unmarshal([]byte(valueJSON), &value.Value); err != nil {
|
||||
return nil, errors.Wrap(err, "property_values_from_rows_unmarshal_value")
|
||||
}
|
||||
|
||||
results = append(results, &value)
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func propertyValueFromRows(rows *sql.Rows) (*model.PropertyValue, error) {
|
||||
values, err := propertyValuesFromRows(rows)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(values) > 0 {
|
||||
return values[0], nil
|
||||
}
|
||||
|
||||
return nil, sql.ErrNoRows
|
||||
}
|
||||
|
||||
type SqlPropertyValueStore struct {
|
||||
*SqlStore
|
||||
|
||||
tableSelectQuery sq.SelectBuilder
|
||||
}
|
||||
|
||||
func newPropertyValueStore(sqlStore *SqlStore) store.PropertyValueStore {
|
||||
s := SqlPropertyValueStore{SqlStore: sqlStore}
|
||||
|
||||
s.tableSelectQuery = s.getQueryBuilder().
|
||||
Select("ID", "TargetID", "TargetType", "GroupID", "FieldID", "Value", "CreateAt", "UpdateAt", "DeleteAt").
|
||||
From("PropertyValues")
|
||||
|
||||
return &s
|
||||
}
|
||||
|
||||
func (s *SqlPropertyValueStore) Create(value *model.PropertyValue) (*model.PropertyValue, error) {
|
||||
if value.ID != "" {
|
||||
return nil, store.NewErrInvalidInput("PropertyValue", "id", value.ID)
|
||||
}
|
||||
|
||||
value.PreSave()
|
||||
|
||||
if err := value.IsValid(); err != nil {
|
||||
return nil, errors.Wrap(err, "property_value_create_isvalid")
|
||||
}
|
||||
|
||||
insertMap, err := s.propertyValueToInsertMap(value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
builder := s.getQueryBuilder().
|
||||
Insert("PropertyValues").
|
||||
SetMap(insertMap)
|
||||
|
||||
if _, err := s.GetMaster().ExecBuilder(builder); err != nil {
|
||||
return nil, errors.Wrap(err, "property_value_create_insert")
|
||||
}
|
||||
|
||||
return value, nil
|
||||
}
|
||||
|
||||
func (s *SqlPropertyValueStore) Get(id string) (*model.PropertyValue, error) {
|
||||
queryString, args, err := s.tableSelectQuery.
|
||||
Where(sq.Eq{"id": id}).
|
||||
ToSql()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "property_value_get_tosql")
|
||||
}
|
||||
|
||||
rows, err := s.GetReplica().Query(queryString, args...)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "property_value_get_select")
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
value, err := propertyValueFromRows(rows)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "property_value_get_propertyvaluefromrows")
|
||||
}
|
||||
|
||||
return value, nil
|
||||
}
|
||||
|
||||
func (s *SqlPropertyValueStore) GetMany(ids []string) ([]*model.PropertyValue, error) {
|
||||
queryString, args, err := s.tableSelectQuery.
|
||||
Where(sq.Eq{"id": ids}).
|
||||
ToSql()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "property_value_get_many_tosql")
|
||||
}
|
||||
|
||||
rows, err := s.GetReplica().Query(queryString, args...)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "property_value_get_many_query")
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
values, err := propertyValuesFromRows(rows)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "property_value_get_many_propertyvaluesfromrows")
|
||||
}
|
||||
|
||||
if len(values) < len(ids) {
|
||||
return nil, fmt.Errorf("missmatch results: got %d results of the %d ids passed", len(values), len(ids))
|
||||
}
|
||||
|
||||
return values, nil
|
||||
}
|
||||
|
||||
func (s *SqlPropertyValueStore) SearchPropertyValues(opts model.PropertyValueSearchOpts) ([]*model.PropertyValue, error) {
|
||||
if opts.Page < 0 {
|
||||
return nil, errors.New("page must be positive integer")
|
||||
}
|
||||
|
||||
if opts.PerPage < 1 {
|
||||
return nil, errors.New("per page must be positive integer greater than zero")
|
||||
}
|
||||
|
||||
query := s.tableSelectQuery.
|
||||
OrderBy("CreateAt ASC").
|
||||
Offset(uint64(opts.Page * opts.PerPage)).
|
||||
Limit(uint64(opts.PerPage))
|
||||
|
||||
if !opts.IncludeDeleted {
|
||||
query = query.Where(sq.Eq{"DeleteAt": 0})
|
||||
}
|
||||
|
||||
if opts.GroupID != "" {
|
||||
query = query.Where(sq.Eq{"GroupID": opts.GroupID})
|
||||
}
|
||||
|
||||
if opts.TargetType != "" {
|
||||
query = query.Where(sq.Eq{"TargetType": opts.TargetType})
|
||||
}
|
||||
|
||||
if opts.TargetID != "" {
|
||||
query = query.Where(sq.Eq{"TargetID": opts.TargetID})
|
||||
}
|
||||
|
||||
if opts.FieldID != "" {
|
||||
query = query.Where(sq.Eq{"FieldID": opts.FieldID})
|
||||
}
|
||||
|
||||
queryString, args, err := query.ToSql()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "property_value_search_tosql")
|
||||
}
|
||||
|
||||
rows, err := s.GetReplica().Query(queryString, args...)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "property_value_search_query")
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
values, err := propertyValuesFromRows(rows)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "property_value_search_propertyvaluesfromrows")
|
||||
}
|
||||
|
||||
return values, nil
|
||||
}
|
||||
|
||||
func (s *SqlPropertyValueStore) Update(values []*model.PropertyValue) (_ []*model.PropertyValue, err error) {
|
||||
if len(values) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
transaction, err := s.GetMaster().Beginx()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "property_value_update_begin_transaction")
|
||||
}
|
||||
defer finalizeTransactionX(transaction, &err)
|
||||
|
||||
updateTime := model.GetMillis()
|
||||
for _, value := range values {
|
||||
value.UpdateAt = updateTime
|
||||
|
||||
if err := value.IsValid(); err != nil {
|
||||
return nil, errors.Wrap(err, "property_value_update_isvalid")
|
||||
}
|
||||
|
||||
updateMap, err := s.propertyValueToUpdateMap(value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
queryString, args, err := s.getQueryBuilder().
|
||||
Update("PropertyValues").
|
||||
SetMap(updateMap).
|
||||
Where(sq.Eq{"id": value.ID}).
|
||||
ToSql()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "property_value_update_tosql")
|
||||
}
|
||||
|
||||
result, err := transaction.Exec(queryString, args...)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to update property value with id: %s", value.ID)
|
||||
}
|
||||
|
||||
count, err := result.RowsAffected()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "property_value_update_rowsaffected")
|
||||
}
|
||||
if count == 0 {
|
||||
return nil, store.NewErrNotFound("PropertyValue", value.ID)
|
||||
}
|
||||
}
|
||||
|
||||
if err := transaction.Commit(); err != nil {
|
||||
return nil, errors.Wrap(err, "property_value_update_commit")
|
||||
}
|
||||
|
||||
return values, nil
|
||||
}
|
||||
|
||||
func (s *SqlPropertyValueStore) Delete(id string) error {
|
||||
builder := s.getQueryBuilder().
|
||||
Update("PropertyValues").
|
||||
Set("DeleteAt", model.GetMillis()).
|
||||
Where(sq.Eq{"id": id})
|
||||
|
||||
result, err := s.GetMaster().ExecBuilder(builder)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to delete property value with id: %s", id)
|
||||
}
|
||||
|
||||
count, err := result.RowsAffected()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "property_value_delete_rowsaffected")
|
||||
}
|
||||
if count == 0 {
|
||||
return store.NewErrNotFound("PropertyValue", id)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SqlPropertyValueStore) DeleteForField(fieldID string) error {
|
||||
builder := s.getQueryBuilder().
|
||||
Update("PropertyValues").
|
||||
Set("DeleteAt", model.GetMillis()).
|
||||
Where(sq.Eq{"FieldID": fieldID})
|
||||
|
||||
if _, err := s.GetMaster().ExecBuilder(builder); err != nil {
|
||||
return errors.Wrap(err, "property_value_delete_for_field_exec")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
14
server/channels/store/sqlstore/property_value_store_test.go
Normal file
14
server/channels/store/sqlstore/property_value_store_test.go
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package sqlstore
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/mattermost/mattermost/server/v8/channels/store/storetest"
|
||||
)
|
||||
|
||||
func TestPropertyValueStore(t *testing.T) {
|
||||
StoreTestWithSqlStore(t, storetest.TestPropertyValueStore)
|
||||
}
|
||||
|
|
@ -115,6 +115,9 @@ type SqlStoreStores struct {
|
|||
desktopTokens store.DesktopTokensStore
|
||||
channelBookmarks store.ChannelBookmarkStore
|
||||
scheduledPost store.ScheduledPostStore
|
||||
propertyGroup store.PropertyGroupStore
|
||||
propertyField store.PropertyFieldStore
|
||||
propertyValue store.PropertyValueStore
|
||||
}
|
||||
|
||||
type SqlStore struct {
|
||||
|
|
@ -257,6 +260,9 @@ func New(settings model.SqlSettings, logger mlog.LoggerIFace, metrics einterface
|
|||
store.stores.desktopTokens = newSqlDesktopTokensStore(store, metrics)
|
||||
store.stores.channelBookmarks = newSqlChannelBookmarkStore(store)
|
||||
store.stores.scheduledPost = newScheduledPostStore(store)
|
||||
store.stores.propertyGroup = newPropertyGroupStore(store)
|
||||
store.stores.propertyField = newPropertyFieldStore(store)
|
||||
store.stores.propertyValue = newPropertyValueStore(store)
|
||||
|
||||
store.stores.preference.(*SqlPreferenceStore).deleteUnusedFeatures()
|
||||
|
||||
|
|
@ -1060,6 +1066,18 @@ func (ss *SqlStore) ChannelBookmark() store.ChannelBookmarkStore {
|
|||
return ss.stores.channelBookmarks
|
||||
}
|
||||
|
||||
func (ss *SqlStore) PropertyGroup() store.PropertyGroupStore {
|
||||
return ss.stores.propertyGroup
|
||||
}
|
||||
|
||||
func (ss *SqlStore) PropertyField() store.PropertyFieldStore {
|
||||
return ss.stores.propertyField
|
||||
}
|
||||
|
||||
func (ss *SqlStore) PropertyValue() store.PropertyValueStore {
|
||||
return ss.stores.propertyValue
|
||||
}
|
||||
|
||||
func (ss *SqlStore) DropAllTables() {
|
||||
if ss.DriverName() == model.DatabaseDriverPostgres {
|
||||
ss.masterX.Exec(`DO
|
||||
|
|
|
|||
|
|
@ -92,6 +92,9 @@ type Store interface {
|
|||
DesktopTokens() DesktopTokensStore
|
||||
ChannelBookmark() ChannelBookmarkStore
|
||||
ScheduledPost() ScheduledPostStore
|
||||
PropertyGroup() PropertyGroupStore
|
||||
PropertyField() PropertyFieldStore
|
||||
PropertyValue() PropertyValueStore
|
||||
}
|
||||
|
||||
type RetentionPolicyStore interface {
|
||||
|
|
@ -1069,6 +1072,30 @@ type ScheduledPostStore interface {
|
|||
PermanentDeleteByUser(userId string) error
|
||||
}
|
||||
|
||||
type PropertyGroupStore interface {
|
||||
Register(name string) (*model.PropertyGroup, error)
|
||||
Get(name string) (*model.PropertyGroup, error)
|
||||
}
|
||||
|
||||
type PropertyFieldStore interface {
|
||||
Create(field *model.PropertyField) (*model.PropertyField, error)
|
||||
Get(id string) (*model.PropertyField, error)
|
||||
GetMany(ids []string) ([]*model.PropertyField, error)
|
||||
SearchPropertyFields(opts model.PropertyFieldSearchOpts) ([]*model.PropertyField, error)
|
||||
Update(field []*model.PropertyField) ([]*model.PropertyField, error)
|
||||
Delete(id string) error
|
||||
}
|
||||
|
||||
type PropertyValueStore interface {
|
||||
Create(value *model.PropertyValue) (*model.PropertyValue, error)
|
||||
Get(id string) (*model.PropertyValue, error)
|
||||
GetMany(ids []string) ([]*model.PropertyValue, error)
|
||||
SearchPropertyValues(opts model.PropertyValueSearchOpts) ([]*model.PropertyValue, error)
|
||||
Update(field []*model.PropertyValue) ([]*model.PropertyValue, error)
|
||||
Delete(id string) error
|
||||
DeleteForField(id string) error
|
||||
}
|
||||
|
||||
// ChannelSearchOpts contains options for searching channels.
|
||||
//
|
||||
// NotAssociatedToGroup will exclude channels that have associated, active GroupChannels records.
|
||||
|
|
|
|||
197
server/channels/store/storetest/mocks/PropertyFieldStore.go
Normal file
197
server/channels/store/storetest/mocks/PropertyFieldStore.go
Normal file
|
|
@ -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
|
||||
}
|
||||
89
server/channels/store/storetest/mocks/PropertyGroupStore.go
Normal file
89
server/channels/store/storetest/mocks/PropertyGroupStore.go
Normal file
|
|
@ -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
|
||||
}
|
||||
215
server/channels/store/storetest/mocks/PropertyValueStore.go
Normal file
215
server/channels/store/storetest/mocks/PropertyValueStore.go
Normal file
|
|
@ -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()
|
||||
|
|
|
|||
461
server/channels/store/storetest/property_field_store.go
Normal file
461
server/channels/store/storetest/property_field_store.go
Normal file
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
36
server/channels/store/storetest/property_group_store.go
Normal file
36
server/channels/store/storetest/property_group_store.go
Normal file
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
535
server/channels/store/storetest/property_value_store.go
Normal file
535
server/channels/store/storetest/property_value_store.go
Normal file
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,6 +46,9 @@ type TimerLayer struct {
|
|||
PostPriorityStore store.PostPriorityStore
|
||||
PreferenceStore store.PreferenceStore
|
||||
ProductNoticesStore store.ProductNoticesStore
|
||||
PropertyFieldStore store.PropertyFieldStore
|
||||
PropertyGroupStore store.PropertyGroupStore
|
||||
PropertyValueStore store.PropertyValueStore
|
||||
ReactionStore store.ReactionStore
|
||||
RemoteClusterStore store.RemoteClusterStore
|
||||
RetentionPolicyStore store.RetentionPolicyStore
|
||||
|
|
@ -175,6 +178,18 @@ func (s *TimerLayer) ProductNotices() store.ProductNoticesStore {
|
|||
return s.ProductNoticesStore
|
||||
}
|
||||
|
||||
func (s *TimerLayer) PropertyField() store.PropertyFieldStore {
|
||||
return s.PropertyFieldStore
|
||||
}
|
||||
|
||||
func (s *TimerLayer) PropertyGroup() store.PropertyGroupStore {
|
||||
return s.PropertyGroupStore
|
||||
}
|
||||
|
||||
func (s *TimerLayer) PropertyValue() store.PropertyValueStore {
|
||||
return s.PropertyValueStore
|
||||
}
|
||||
|
||||
func (s *TimerLayer) Reaction() store.ReactionStore {
|
||||
return s.ReactionStore
|
||||
}
|
||||
|
|
@ -386,6 +401,21 @@ type TimerLayerProductNoticesStore struct {
|
|||
Root *TimerLayer
|
||||
}
|
||||
|
||||
type TimerLayerPropertyFieldStore struct {
|
||||
store.PropertyFieldStore
|
||||
Root *TimerLayer
|
||||
}
|
||||
|
||||
type TimerLayerPropertyGroupStore struct {
|
||||
store.PropertyGroupStore
|
||||
Root *TimerLayer
|
||||
}
|
||||
|
||||
type TimerLayerPropertyValueStore struct {
|
||||
store.PropertyValueStore
|
||||
Root *TimerLayer
|
||||
}
|
||||
|
||||
type TimerLayerReactionStore struct {
|
||||
store.ReactionStore
|
||||
Root *TimerLayer
|
||||
|
|
@ -6979,6 +7009,246 @@ func (s *TimerLayerProductNoticesStore) View(userID string, notices []string) er
|
|||
return err
|
||||
}
|
||||
|
||||
func (s *TimerLayerPropertyFieldStore) Create(field *model.PropertyField) (*model.PropertyField, error) {
|
||||
start := time.Now()
|
||||
|
||||
result, err := s.PropertyFieldStore.Create(field)
|
||||
|
||||
elapsed := float64(time.Since(start)) / float64(time.Second)
|
||||
if s.Root.Metrics != nil {
|
||||
success := "false"
|
||||
if err == nil {
|
||||
success = "true"
|
||||
}
|
||||
s.Root.Metrics.ObserveStoreMethodDuration("PropertyFieldStore.Create", success, elapsed)
|
||||
}
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *TimerLayerPropertyFieldStore) Delete(id string) error {
|
||||
start := time.Now()
|
||||
|
||||
err := s.PropertyFieldStore.Delete(id)
|
||||
|
||||
elapsed := float64(time.Since(start)) / float64(time.Second)
|
||||
if s.Root.Metrics != nil {
|
||||
success := "false"
|
||||
if err == nil {
|
||||
success = "true"
|
||||
}
|
||||
s.Root.Metrics.ObserveStoreMethodDuration("PropertyFieldStore.Delete", success, elapsed)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *TimerLayerPropertyFieldStore) Get(id string) (*model.PropertyField, error) {
|
||||
start := time.Now()
|
||||
|
||||
result, err := s.PropertyFieldStore.Get(id)
|
||||
|
||||
elapsed := float64(time.Since(start)) / float64(time.Second)
|
||||
if s.Root.Metrics != nil {
|
||||
success := "false"
|
||||
if err == nil {
|
||||
success = "true"
|
||||
}
|
||||
s.Root.Metrics.ObserveStoreMethodDuration("PropertyFieldStore.Get", success, elapsed)
|
||||
}
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *TimerLayerPropertyFieldStore) GetMany(ids []string) ([]*model.PropertyField, error) {
|
||||
start := time.Now()
|
||||
|
||||
result, err := s.PropertyFieldStore.GetMany(ids)
|
||||
|
||||
elapsed := float64(time.Since(start)) / float64(time.Second)
|
||||
if s.Root.Metrics != nil {
|
||||
success := "false"
|
||||
if err == nil {
|
||||
success = "true"
|
||||
}
|
||||
s.Root.Metrics.ObserveStoreMethodDuration("PropertyFieldStore.GetMany", success, elapsed)
|
||||
}
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *TimerLayerPropertyFieldStore) SearchPropertyFields(opts model.PropertyFieldSearchOpts) ([]*model.PropertyField, error) {
|
||||
start := time.Now()
|
||||
|
||||
result, err := s.PropertyFieldStore.SearchPropertyFields(opts)
|
||||
|
||||
elapsed := float64(time.Since(start)) / float64(time.Second)
|
||||
if s.Root.Metrics != nil {
|
||||
success := "false"
|
||||
if err == nil {
|
||||
success = "true"
|
||||
}
|
||||
s.Root.Metrics.ObserveStoreMethodDuration("PropertyFieldStore.SearchPropertyFields", success, elapsed)
|
||||
}
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *TimerLayerPropertyFieldStore) Update(field []*model.PropertyField) ([]*model.PropertyField, error) {
|
||||
start := time.Now()
|
||||
|
||||
result, err := s.PropertyFieldStore.Update(field)
|
||||
|
||||
elapsed := float64(time.Since(start)) / float64(time.Second)
|
||||
if s.Root.Metrics != nil {
|
||||
success := "false"
|
||||
if err == nil {
|
||||
success = "true"
|
||||
}
|
||||
s.Root.Metrics.ObserveStoreMethodDuration("PropertyFieldStore.Update", success, elapsed)
|
||||
}
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *TimerLayerPropertyGroupStore) Get(name string) (*model.PropertyGroup, error) {
|
||||
start := time.Now()
|
||||
|
||||
result, err := s.PropertyGroupStore.Get(name)
|
||||
|
||||
elapsed := float64(time.Since(start)) / float64(time.Second)
|
||||
if s.Root.Metrics != nil {
|
||||
success := "false"
|
||||
if err == nil {
|
||||
success = "true"
|
||||
}
|
||||
s.Root.Metrics.ObserveStoreMethodDuration("PropertyGroupStore.Get", success, elapsed)
|
||||
}
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *TimerLayerPropertyGroupStore) Register(name string) (*model.PropertyGroup, error) {
|
||||
start := time.Now()
|
||||
|
||||
result, err := s.PropertyGroupStore.Register(name)
|
||||
|
||||
elapsed := float64(time.Since(start)) / float64(time.Second)
|
||||
if s.Root.Metrics != nil {
|
||||
success := "false"
|
||||
if err == nil {
|
||||
success = "true"
|
||||
}
|
||||
s.Root.Metrics.ObserveStoreMethodDuration("PropertyGroupStore.Register", success, elapsed)
|
||||
}
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *TimerLayerPropertyValueStore) Create(value *model.PropertyValue) (*model.PropertyValue, error) {
|
||||
start := time.Now()
|
||||
|
||||
result, err := s.PropertyValueStore.Create(value)
|
||||
|
||||
elapsed := float64(time.Since(start)) / float64(time.Second)
|
||||
if s.Root.Metrics != nil {
|
||||
success := "false"
|
||||
if err == nil {
|
||||
success = "true"
|
||||
}
|
||||
s.Root.Metrics.ObserveStoreMethodDuration("PropertyValueStore.Create", success, elapsed)
|
||||
}
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *TimerLayerPropertyValueStore) Delete(id string) error {
|
||||
start := time.Now()
|
||||
|
||||
err := s.PropertyValueStore.Delete(id)
|
||||
|
||||
elapsed := float64(time.Since(start)) / float64(time.Second)
|
||||
if s.Root.Metrics != nil {
|
||||
success := "false"
|
||||
if err == nil {
|
||||
success = "true"
|
||||
}
|
||||
s.Root.Metrics.ObserveStoreMethodDuration("PropertyValueStore.Delete", success, elapsed)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *TimerLayerPropertyValueStore) DeleteForField(id string) error {
|
||||
start := time.Now()
|
||||
|
||||
err := s.PropertyValueStore.DeleteForField(id)
|
||||
|
||||
elapsed := float64(time.Since(start)) / float64(time.Second)
|
||||
if s.Root.Metrics != nil {
|
||||
success := "false"
|
||||
if err == nil {
|
||||
success = "true"
|
||||
}
|
||||
s.Root.Metrics.ObserveStoreMethodDuration("PropertyValueStore.DeleteForField", success, elapsed)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *TimerLayerPropertyValueStore) Get(id string) (*model.PropertyValue, error) {
|
||||
start := time.Now()
|
||||
|
||||
result, err := s.PropertyValueStore.Get(id)
|
||||
|
||||
elapsed := float64(time.Since(start)) / float64(time.Second)
|
||||
if s.Root.Metrics != nil {
|
||||
success := "false"
|
||||
if err == nil {
|
||||
success = "true"
|
||||
}
|
||||
s.Root.Metrics.ObserveStoreMethodDuration("PropertyValueStore.Get", success, elapsed)
|
||||
}
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *TimerLayerPropertyValueStore) GetMany(ids []string) ([]*model.PropertyValue, error) {
|
||||
start := time.Now()
|
||||
|
||||
result, err := s.PropertyValueStore.GetMany(ids)
|
||||
|
||||
elapsed := float64(time.Since(start)) / float64(time.Second)
|
||||
if s.Root.Metrics != nil {
|
||||
success := "false"
|
||||
if err == nil {
|
||||
success = "true"
|
||||
}
|
||||
s.Root.Metrics.ObserveStoreMethodDuration("PropertyValueStore.GetMany", success, elapsed)
|
||||
}
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *TimerLayerPropertyValueStore) SearchPropertyValues(opts model.PropertyValueSearchOpts) ([]*model.PropertyValue, error) {
|
||||
start := time.Now()
|
||||
|
||||
result, err := s.PropertyValueStore.SearchPropertyValues(opts)
|
||||
|
||||
elapsed := float64(time.Since(start)) / float64(time.Second)
|
||||
if s.Root.Metrics != nil {
|
||||
success := "false"
|
||||
if err == nil {
|
||||
success = "true"
|
||||
}
|
||||
s.Root.Metrics.ObserveStoreMethodDuration("PropertyValueStore.SearchPropertyValues", success, elapsed)
|
||||
}
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *TimerLayerPropertyValueStore) Update(field []*model.PropertyValue) ([]*model.PropertyValue, error) {
|
||||
start := time.Now()
|
||||
|
||||
result, err := s.PropertyValueStore.Update(field)
|
||||
|
||||
elapsed := float64(time.Since(start)) / float64(time.Second)
|
||||
if s.Root.Metrics != nil {
|
||||
success := "false"
|
||||
if err == nil {
|
||||
success = "true"
|
||||
}
|
||||
s.Root.Metrics.ObserveStoreMethodDuration("PropertyValueStore.Update", success, elapsed)
|
||||
}
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *TimerLayerReactionStore) BulkGetForPosts(postIds []string) ([]*model.Reaction, error) {
|
||||
start := time.Now()
|
||||
|
||||
|
|
@ -12479,6 +12749,9 @@ func New(childStore store.Store, metrics einterfaces.MetricsInterface) *TimerLay
|
|||
newStore.PostPriorityStore = &TimerLayerPostPriorityStore{PostPriorityStore: childStore.PostPriority(), Root: &newStore}
|
||||
newStore.PreferenceStore = &TimerLayerPreferenceStore{PreferenceStore: childStore.Preference(), Root: &newStore}
|
||||
newStore.ProductNoticesStore = &TimerLayerProductNoticesStore{ProductNoticesStore: childStore.ProductNotices(), Root: &newStore}
|
||||
newStore.PropertyFieldStore = &TimerLayerPropertyFieldStore{PropertyFieldStore: childStore.PropertyField(), Root: &newStore}
|
||||
newStore.PropertyGroupStore = &TimerLayerPropertyGroupStore{PropertyGroupStore: childStore.PropertyGroup(), Root: &newStore}
|
||||
newStore.PropertyValueStore = &TimerLayerPropertyValueStore{PropertyValueStore: childStore.PropertyValue(), Root: &newStore}
|
||||
newStore.ReactionStore = &TimerLayerReactionStore{ReactionStore: childStore.Reaction(), Root: &newStore}
|
||||
newStore.RemoteClusterStore = &TimerLayerRemoteClusterStore{RemoteClusterStore: childStore.RemoteCluster(), Root: &newStore}
|
||||
newStore.RetentionPolicyStore = &TimerLayerRetentionPolicyStore{RetentionPolicyStore: childStore.RetentionPolicy(), Root: &newStore}
|
||||
|
|
|
|||
|
|
@ -114,6 +114,10 @@ func GetMockStoreForSetupFunctions() *mocks.Store {
|
|||
pluginStore := mocks.PluginStore{}
|
||||
pluginStore.On("List", mock.Anything, mock.Anything, mock.Anything).Return([]string{}, nil)
|
||||
|
||||
propertyGroupStore := mocks.PropertyGroupStore{}
|
||||
propertyFieldStore := mocks.PropertyFieldStore{}
|
||||
propertyValueStore := mocks.PropertyValueStore{}
|
||||
|
||||
mockStore.On("System").Return(&systemStore)
|
||||
mockStore.On("User").Return(&userStore)
|
||||
mockStore.On("Post").Return(&postStore)
|
||||
|
|
@ -130,6 +134,9 @@ func GetMockStoreForSetupFunctions() *mocks.Store {
|
|||
mockStore.On("Group").Return(&groupStore)
|
||||
mockStore.On("GetDBSchemaVersion").Return(1, nil)
|
||||
mockStore.On("Plugin").Return(&pluginStore)
|
||||
mockStore.On("PropertyGroup").Return(&propertyGroupStore)
|
||||
mockStore.On("PropertyField").Return(&propertyFieldStore)
|
||||
mockStore.On("PropertyValue").Return(&propertyValueStore)
|
||||
|
||||
return &mockStore
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9652,6 +9652,14 @@
|
|||
"id": "model.preference.is_valid.value.app_error",
|
||||
"translation": "Value is too long."
|
||||
},
|
||||
{
|
||||
"id": "model.property_field.is_valid.app_error",
|
||||
"translation": "Invalid property field: {{.FieldName}} ({{.Reason}})."
|
||||
},
|
||||
{
|
||||
"id": "model.property_value.is_valid.app_error",
|
||||
"translation": "Invalid property value: {{.FieldName}} ({{.Reason}})."
|
||||
},
|
||||
{
|
||||
"id": "model.reaction.is_valid.create_at.app_error",
|
||||
"translation": "Create at must be a valid time."
|
||||
|
|
|
|||
83
server/public/model/property_field.go
Normal file
83
server/public/model/property_field.go
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package model
|
||||
|
||||
import "net/http"
|
||||
|
||||
type PropertyFieldType string
|
||||
|
||||
const (
|
||||
PropertyFieldTypeText PropertyFieldType = "text"
|
||||
PropertyFieldTypeSelect PropertyFieldType = "select"
|
||||
PropertyFieldTypeMultiselect PropertyFieldType = "multiselect"
|
||||
PropertyFieldTypeDate PropertyFieldType = "date"
|
||||
PropertyFieldTypeUser PropertyFieldType = "user"
|
||||
PropertyFieldTypeMultiuser PropertyFieldType = "multiuser"
|
||||
)
|
||||
|
||||
type PropertyField struct {
|
||||
ID string `json:"id"`
|
||||
GroupID string `json:"group_id"`
|
||||
Name string `json:"name"`
|
||||
Type PropertyFieldType `json:"type"`
|
||||
Attrs map[string]any `json:"attrs"`
|
||||
TargetID string `json:"target_id"`
|
||||
TargetType string `json:"target_type"`
|
||||
CreateAt int64 `json:"create_at"`
|
||||
UpdateAt int64 `json:"update_at"`
|
||||
DeleteAt int64 `json:"delete_at"`
|
||||
}
|
||||
|
||||
func (pf *PropertyField) PreSave() {
|
||||
if pf.ID == "" {
|
||||
pf.ID = NewId()
|
||||
}
|
||||
|
||||
if pf.CreateAt == 0 {
|
||||
pf.CreateAt = GetMillis()
|
||||
}
|
||||
pf.UpdateAt = pf.CreateAt
|
||||
}
|
||||
|
||||
func (pf *PropertyField) IsValid() error {
|
||||
if !IsValidId(pf.ID) {
|
||||
return NewAppError("PropertyField.IsValid", "model.property_field.is_valid.app_error", map[string]any{"FieldName": "id", "Reason": "invalid id"}, "", http.StatusBadRequest)
|
||||
}
|
||||
|
||||
if !IsValidId(pf.GroupID) {
|
||||
return NewAppError("PropertyField.IsValid", "model.property_field.is_valid.app_error", map[string]any{"FieldName": "group_id", "Reason": "invalid id"}, "id="+pf.ID, http.StatusBadRequest)
|
||||
}
|
||||
|
||||
if pf.Name == "" {
|
||||
return NewAppError("PropertyField.IsValid", "model.property_field.is_valid.app_error", map[string]any{"FieldName": "name", "Reason": "value cannot be empty"}, "id="+pf.ID, http.StatusBadRequest)
|
||||
}
|
||||
|
||||
if !(pf.Type == PropertyFieldTypeText ||
|
||||
pf.Type == PropertyFieldTypeSelect ||
|
||||
pf.Type == PropertyFieldTypeMultiselect ||
|
||||
pf.Type == PropertyFieldTypeDate ||
|
||||
pf.Type == PropertyFieldTypeUser ||
|
||||
pf.Type == PropertyFieldTypeMultiuser) {
|
||||
return NewAppError("PropertyField.IsValid", "model.property_field.is_valid.app_error", map[string]any{"FieldName": "type", "Reason": "unknown value"}, "id="+pf.ID, http.StatusBadRequest)
|
||||
}
|
||||
|
||||
if pf.CreateAt == 0 {
|
||||
return NewAppError("PropertyField.IsValid", "model.property_field.is_valid.app_error", map[string]any{"FieldName": "create_at", "Reason": "value cannot be zero"}, "id="+pf.ID, http.StatusBadRequest)
|
||||
}
|
||||
|
||||
if pf.UpdateAt == 0 {
|
||||
return NewAppError("PropertyField.IsValid", "model.property_field.is_valid.app_error", map[string]any{"FieldName": "update_at", "Reason": "value cannot be zero"}, "id="+pf.ID, http.StatusBadRequest)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type PropertyFieldSearchOpts struct {
|
||||
GroupID string
|
||||
TargetType string
|
||||
TargetID string
|
||||
IncludeDeleted bool
|
||||
Page int
|
||||
PerPage int
|
||||
}
|
||||
15
server/public/model/property_group.go
Normal file
15
server/public/model/property_group.go
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package model
|
||||
|
||||
type PropertyGroup struct {
|
||||
ID string
|
||||
Name string
|
||||
}
|
||||
|
||||
func (pg *PropertyGroup) PreSave() {
|
||||
if pg.ID == "" {
|
||||
pg.ID = NewId()
|
||||
}
|
||||
}
|
||||
71
server/public/model/property_value.go
Normal file
71
server/public/model/property_value.go
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package model
|
||||
|
||||
import "net/http"
|
||||
|
||||
type PropertyValue struct {
|
||||
ID string `json:"id"`
|
||||
TargetID string `json:"target_id"`
|
||||
TargetType string `json:"target_type"`
|
||||
GroupID string `json:"group_id"`
|
||||
FieldID string `json:"field_id"`
|
||||
Value string `json:"value"`
|
||||
CreateAt int64 `json:"create_at"`
|
||||
UpdateAt int64 `json:"update_at"`
|
||||
DeleteAt int64 `json:"delete_at"`
|
||||
}
|
||||
|
||||
func (pv *PropertyValue) PreSave() {
|
||||
if pv.ID == "" {
|
||||
pv.ID = NewId()
|
||||
}
|
||||
|
||||
if pv.CreateAt == 0 {
|
||||
pv.CreateAt = GetMillis()
|
||||
}
|
||||
pv.UpdateAt = pv.CreateAt
|
||||
}
|
||||
|
||||
func (pv *PropertyValue) IsValid() error {
|
||||
if !IsValidId(pv.ID) {
|
||||
return NewAppError("PropertyValue.IsValid", "model.property_value.is_valid.app_error", map[string]any{"FieldName": "id", "Reason": "invalid id"}, "", http.StatusBadRequest)
|
||||
}
|
||||
|
||||
if !IsValidId(pv.TargetID) {
|
||||
return NewAppError("PropertyValue.IsValid", "model.property_value.is_valid.app_error", map[string]any{"FieldName": "target_id", "Reason": "invalid id"}, "id="+pv.ID, http.StatusBadRequest)
|
||||
}
|
||||
|
||||
if pv.TargetType == "" {
|
||||
return NewAppError("PropertyValue.IsValid", "model.property_value.is_valid.app_error", map[string]any{"FieldName": "target_type", "Reason": "value cannot be empty"}, "id="+pv.ID, http.StatusBadRequest)
|
||||
}
|
||||
|
||||
if !IsValidId(pv.GroupID) {
|
||||
return NewAppError("PropertyValue.IsValid", "model.property_value.is_valid.app_error", map[string]any{"FieldName": "group_id", "Reason": "invalid id"}, "id="+pv.ID, http.StatusBadRequest)
|
||||
}
|
||||
|
||||
if !IsValidId(pv.FieldID) {
|
||||
return NewAppError("PropertyValue.IsValid", "model.property_value.is_valid.app_error", map[string]any{"FieldName": "field_id", "Reason": "invalid id"}, "id="+pv.ID, http.StatusBadRequest)
|
||||
}
|
||||
|
||||
if pv.CreateAt == 0 {
|
||||
return NewAppError("PropertyValue.IsValid", "model.property_value.is_valid.app_error", map[string]any{"FieldName": "create_at", "Reason": "value cannot be zero"}, "id="+pv.ID, http.StatusBadRequest)
|
||||
}
|
||||
|
||||
if pv.UpdateAt == 0 {
|
||||
return NewAppError("PropertyValue.IsValid", "model.property_value.is_valid.app_error", map[string]any{"FieldName": "update_at", "Reason": "value cannot be zero"}, "id="+pv.ID, http.StatusBadRequest)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type PropertyValueSearchOpts struct {
|
||||
GroupID string
|
||||
TargetType string
|
||||
TargetID string
|
||||
FieldID string
|
||||
IncludeDeleted bool
|
||||
Page int
|
||||
PerPage int
|
||||
}
|
||||
Loading…
Reference in a new issue