Files
mostlymatter/server/public/model/property_field.go
Julien Tant bcc395d139 [MM-62552] Custom Profile Attributes: use json.RawMessage for the value. (#29989)
* refactor: Move property value sanitization to model layer

* feat: Add value sanitization for custom profile attributes

* refactor: Update custom profile attributes to use json.RawMessage

* refactor: Update patchCustomProfileAttribute to handle json.RawMessage directly

* refactor: Refactor custom profile attributes handler with improved validation

* refactor: Rename `patchCustomProfileAttribute` to `patchCPAValues`

* refactor: Replace ReturnJSON with json.NewEncoder and add error logging

* feat: Add encoding/json import to property_value.go

* refactor: Update property value tests to use json.RawMessage

* fix: Convert string value to json.RawMessage in property value test

* fix: Convert string literals to json.RawMessage in property value tests

* fix: Add missing encoding/json import in custom_profile_attributes.go

* fix: Preserve JSON RawMessage type in listCPAValues function

* fix: Update custom profile attributes test to use json.RawMessage

* feat: Add json import to custom_profile_attributes_test.go

* refactor: Update ListCPAValues and PatchCPAValues to use json.RawMessage

* refactor: Rename `actualValue` to `updatedValue` in custom profile attributes test

* refactor: Improve user permission and audit logging for custom profile attributes patch

* refactor: Optimize CPA field lookup by using ListCPAFields() and map

* fix: Correct user ID reference in custom profile attributes patch endpoint

* refactor: Change patchCPAValues to use map[string]json.RawMessage for results

* refactor: format and fix tests

* test: Add comprehensive unit tests for sanitizePropertyValue function

* test: Add test case for invalid property value type

* feat: Use `model.NewId()` to generate valid IDs in custom profile attributes tests

* refactor: Replace hardcoded IDs with dynamic variables in custom profile attributes test

* refactor: restore variable name

* refactor: drop undesired changes

* chore: refresh app layers

* feat: Update API definition to support string or string array values for custom profile attributes

* test: Add test cases for multiselect custom profile attribute values

* test: Add tests for multiselect custom profile attribute values

* test: Isolate array value test in separate t.Run

* test: Add test case for multiselect array values in custom profile attributes

* refactor: Move array value test from TestCreateCPAField to TestPatchCPAValue

* test: Update custom profile attributes test assertions

* test: add test case for handling array values in GetCPAValue

* test: Add array value tests for property value store

* refactor(store): no need to convert to json the rawmessage

* chore: lint

* i18n

* use model to interface with sqlx

* fix: Allow empty strings for text, date, and select profile attributes

* refactor: Filter out empty strings in multiselect and multiuser fields

* refactor: Update multiuser field sanitization to validate and error on invalid IDs

* refactor: Simplify sanitizePropertyValue function with reduced code duplication

* fix: Allow empty user ID in custom profile attribute sanitization

* refactor: Convert comment-based subtests to nested t.Run in TestSanitizePropertyValue

* refactor: Convert comment-based subtests to nested t.Run tests in TestSanitizePropertyValue

---------

Co-authored-by: Mattermost Build <build@mattermost.com>
2025-02-05 17:21:22 +00:00

152 строки
4.4 KiB
Go

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
import (
"net/http"
"strings"
)
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 StringInterface `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) Auditable() map[string]interface{} {
return map[string]interface{}{
"id": pf.ID,
"group_id": pf.GroupID,
"name": pf.Name,
"type": pf.Type,
"attrs": pf.Attrs,
"target_id": pf.TargetID,
"target_type": pf.TargetType,
"create_at": pf.CreateAt,
"update_at": pf.UpdateAt,
"delete_at": pf.DeleteAt,
}
}
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
}
func (pf *PropertyField) SanitizeInput() {
pf.Name = strings.TrimSpace(pf.Name)
}
type PropertyFieldPatch struct {
Name *string `json:"name"`
Type *PropertyFieldType `json:"type"`
Attrs *map[string]any `json:"attrs"`
TargetID *string `json:"target_id"`
TargetType *string `json:"target_type"`
}
func (pfp *PropertyFieldPatch) Auditable() map[string]interface{} {
return map[string]interface{}{
"name": pfp.Name,
"type": pfp.Type,
"attrs": pfp.Attrs,
"target_id": pfp.TargetID,
"target_type": pfp.TargetType,
}
}
func (pfp *PropertyFieldPatch) SanitizeInput() {
if pfp.Name != nil {
pfp.Name = NewPointer(strings.TrimSpace(*pfp.Name))
}
}
func (pf *PropertyField) Patch(patch *PropertyFieldPatch) {
if patch.Name != nil {
pf.Name = *patch.Name
}
if patch.Type != nil {
pf.Type = *patch.Type
}
if patch.Attrs != nil {
pf.Attrs = *patch.Attrs
}
if patch.TargetID != nil {
pf.TargetID = *patch.TargetID
}
if patch.TargetType != nil {
pf.TargetType = *patch.TargetType
}
}
type PropertyFieldSearchOpts struct {
GroupID string
TargetType string
TargetID string
IncludeDeleted bool
Page int
PerPage int
}