mirror of
https://github.com/hanzoai/team.git
synced 2026-10-30 14:49:35 +00:00
290 lines
9.1 KiB
Go
290 lines
9.1 KiB
Go
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
// See LICENSE.txt for license information.
|
|
|
|
package properties
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/http"
|
|
|
|
"github.com/mattermost/mattermost/server/public/model"
|
|
"github.com/mattermost/mattermost/server/public/shared/request"
|
|
"github.com/mattermost/mattermost/server/v8/channels/store"
|
|
)
|
|
|
|
// rejectTemplateValues checks that none of the given values target a template
|
|
// field. Template fields are definition-only and must never hold values.
|
|
// This is enforced at the service layer to cover all entry points (API,
|
|
// CPA endpoints, plugin API).
|
|
func (ps *PropertyService) rejectTemplateValues(values []*model.PropertyValue) error {
|
|
// Collect unique field IDs
|
|
seen := make(map[string]struct{}, len(values))
|
|
for _, v := range values {
|
|
if v == nil {
|
|
continue
|
|
}
|
|
seen[v.FieldID] = struct{}{}
|
|
}
|
|
if len(seen) == 0 {
|
|
return nil
|
|
}
|
|
|
|
fieldIDs := make([]string, 0, len(seen))
|
|
for id := range seen {
|
|
fieldIDs = append(fieldIDs, id)
|
|
}
|
|
|
|
// Batch lookup from master to avoid replication lag
|
|
fields, err := ps.fieldStore.GetMany(store.WithMaster(context.Background()), "", fieldIDs)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to look up fields for template check: %w", err)
|
|
}
|
|
|
|
for _, field := range fields {
|
|
if field.ObjectType == model.PropertyFieldObjectTypeTemplate {
|
|
return model.NewAppError(
|
|
"PropertyService",
|
|
"app.property_value.template_no_values.app_error",
|
|
nil,
|
|
fmt.Sprintf("template field %q cannot have values", field.ID),
|
|
http.StatusBadRequest,
|
|
)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Private implementation methods (database access)
|
|
|
|
func (ps *PropertyService) createPropertyValue(value *model.PropertyValue) (*model.PropertyValue, error) {
|
|
if err := ps.rejectTemplateValues([]*model.PropertyValue{value}); err != nil {
|
|
return nil, err
|
|
}
|
|
return ps.valueStore.Create(value)
|
|
}
|
|
|
|
func (ps *PropertyService) createPropertyValues(values []*model.PropertyValue) ([]*model.PropertyValue, error) {
|
|
if err := ps.rejectTemplateValues(values); err != nil {
|
|
return nil, err
|
|
}
|
|
return ps.valueStore.CreateMany(values)
|
|
}
|
|
|
|
func (ps *PropertyService) getPropertyValue(groupID, id string) (*model.PropertyValue, error) {
|
|
return ps.valueStore.Get(groupID, id)
|
|
}
|
|
|
|
func (ps *PropertyService) getPropertyValues(groupID string, ids []string) ([]*model.PropertyValue, error) {
|
|
return ps.valueStore.GetMany(groupID, ids)
|
|
}
|
|
|
|
func (ps *PropertyService) searchPropertyValues(groupID string, opts model.PropertyValueSearchOpts) ([]*model.PropertyValue, error) {
|
|
// groupID is part of the search method signature to
|
|
// incentivize the use of the database indexes in searches
|
|
opts.GroupID = groupID
|
|
return ps.valueStore.SearchPropertyValues(opts)
|
|
}
|
|
|
|
func (ps *PropertyService) updatePropertyValue(groupID string, value *model.PropertyValue) (*model.PropertyValue, error) {
|
|
values, err := ps.updatePropertyValues(groupID, []*model.PropertyValue{value})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return values[0], nil
|
|
}
|
|
|
|
func (ps *PropertyService) updatePropertyValues(groupID string, values []*model.PropertyValue) ([]*model.PropertyValue, error) {
|
|
if err := ps.rejectTemplateValues(values); err != nil {
|
|
return nil, err
|
|
}
|
|
return ps.valueStore.Update(groupID, values)
|
|
}
|
|
|
|
func (ps *PropertyService) upsertPropertyValue(value *model.PropertyValue) (*model.PropertyValue, error) {
|
|
values, err := ps.upsertPropertyValues([]*model.PropertyValue{value})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return values[0], nil
|
|
}
|
|
|
|
func (ps *PropertyService) upsertPropertyValues(values []*model.PropertyValue) ([]*model.PropertyValue, error) {
|
|
if err := ps.rejectTemplateValues(values); err != nil {
|
|
return nil, err
|
|
}
|
|
return ps.valueStore.Upsert(values)
|
|
}
|
|
|
|
func (ps *PropertyService) deletePropertyValue(groupID, id string) error {
|
|
return ps.valueStore.Delete(groupID, id)
|
|
}
|
|
|
|
func (ps *PropertyService) deletePropertyValuesForTarget(groupID string, targetType string, targetID string) error {
|
|
return ps.valueStore.DeleteForTarget(groupID, targetType, targetID)
|
|
}
|
|
|
|
func (ps *PropertyService) deletePropertyValuesForField(groupID, fieldID string) error {
|
|
return ps.valueStore.DeleteForField(groupID, fieldID)
|
|
}
|
|
|
|
// Public methods
|
|
|
|
func (ps *PropertyService) CreatePropertyValue(rctx request.CTX, value *model.PropertyValue) (*model.PropertyValue, error) {
|
|
if value == nil {
|
|
return nil, fmt.Errorf("CreatePropertyValue: value cannot be nil")
|
|
}
|
|
|
|
value, err := ps.runPreCreatePropertyValue(rctx, value)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("CreatePropertyValue: %w", err)
|
|
}
|
|
|
|
return ps.createPropertyValue(value)
|
|
}
|
|
|
|
func (ps *PropertyService) CreatePropertyValues(rctx request.CTX, values []*model.PropertyValue) ([]*model.PropertyValue, error) {
|
|
if len(values) == 0 {
|
|
return values, nil
|
|
}
|
|
|
|
for i, v := range values {
|
|
if v == nil {
|
|
return nil, fmt.Errorf("CreatePropertyValues: nil element at index %d", i)
|
|
}
|
|
if v.GroupID != values[0].GroupID {
|
|
return nil, fmt.Errorf("CreatePropertyValues: mixed group IDs in batch")
|
|
}
|
|
}
|
|
|
|
values, err := ps.runPreCreatePropertyValues(rctx, values)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("CreatePropertyValues: %w", err)
|
|
}
|
|
|
|
return ps.createPropertyValues(values)
|
|
}
|
|
|
|
func (ps *PropertyService) GetPropertyValue(rctx request.CTX, groupID, id string) (*model.PropertyValue, error) {
|
|
value, err := ps.getPropertyValue(groupID, id)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("GetPropertyValue: %w", err)
|
|
}
|
|
|
|
return ps.runPostGetPropertyValue(rctx, value)
|
|
}
|
|
|
|
func (ps *PropertyService) GetPropertyValues(rctx request.CTX, groupID string, ids []string) ([]*model.PropertyValue, error) {
|
|
values, err := ps.getPropertyValues(groupID, ids)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("GetPropertyValues: %w", err)
|
|
}
|
|
|
|
return ps.runPostGetPropertyValues(rctx, values)
|
|
}
|
|
|
|
func (ps *PropertyService) SearchPropertyValues(rctx request.CTX, groupID string, opts model.PropertyValueSearchOpts) ([]*model.PropertyValue, error) {
|
|
values, err := ps.searchPropertyValues(groupID, opts)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("SearchPropertyValues: %w", err)
|
|
}
|
|
|
|
return ps.runPostGetPropertyValues(rctx, values)
|
|
}
|
|
|
|
func (ps *PropertyService) UpdatePropertyValue(rctx request.CTX, groupID string, value *model.PropertyValue) (*model.PropertyValue, error) {
|
|
value, err := ps.runPreUpdatePropertyValue(rctx, groupID, value)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("UpdatePropertyValue: %w", err)
|
|
}
|
|
|
|
return ps.updatePropertyValue(groupID, value)
|
|
}
|
|
|
|
func (ps *PropertyService) UpdatePropertyValues(rctx request.CTX, groupID string, values []*model.PropertyValue) ([]*model.PropertyValue, error) {
|
|
if len(values) == 0 {
|
|
return values, nil
|
|
}
|
|
|
|
// Hooks gate on values[0].GroupID for batch operations, so enforce
|
|
// single-group batches at the public boundary — otherwise a mixed
|
|
// batch could silently bypass per-group hook logic (license,
|
|
// validation, access control).
|
|
for i, v := range values {
|
|
if v == nil {
|
|
return nil, fmt.Errorf("UpdatePropertyValues: nil element at index %d", i)
|
|
}
|
|
if v.GroupID != values[0].GroupID {
|
|
return nil, fmt.Errorf("UpdatePropertyValues: mixed group IDs in batch")
|
|
}
|
|
}
|
|
|
|
values, err := ps.runPreUpdatePropertyValues(rctx, groupID, values)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("UpdatePropertyValues: %w", err)
|
|
}
|
|
|
|
return ps.updatePropertyValues(groupID, values)
|
|
}
|
|
|
|
func (ps *PropertyService) UpsertPropertyValue(rctx request.CTX, value *model.PropertyValue) (*model.PropertyValue, error) {
|
|
if value == nil {
|
|
return nil, fmt.Errorf("UpsertPropertyValue: value cannot be nil")
|
|
}
|
|
|
|
value, err := ps.runPreUpsertPropertyValue(rctx, value)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("UpsertPropertyValue: %w", err)
|
|
}
|
|
|
|
return ps.upsertPropertyValue(value)
|
|
}
|
|
|
|
func (ps *PropertyService) UpsertPropertyValues(rctx request.CTX, values []*model.PropertyValue) ([]*model.PropertyValue, error) {
|
|
if len(values) == 0 {
|
|
return values, nil
|
|
}
|
|
|
|
for i, v := range values {
|
|
if v == nil {
|
|
return nil, fmt.Errorf("UpsertPropertyValues: nil element at index %d", i)
|
|
}
|
|
if v.GroupID != values[0].GroupID {
|
|
return nil, fmt.Errorf("UpsertPropertyValues: mixed group IDs in batch")
|
|
}
|
|
}
|
|
|
|
values, err := ps.runPreUpsertPropertyValues(rctx, values)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("UpsertPropertyValues: %w", err)
|
|
}
|
|
|
|
return ps.upsertPropertyValues(values)
|
|
}
|
|
|
|
func (ps *PropertyService) DeletePropertyValue(rctx request.CTX, groupID, id string) error {
|
|
if err := ps.runPreDeletePropertyValue(rctx, groupID, id); err != nil {
|
|
return fmt.Errorf("DeletePropertyValue: %w", err)
|
|
}
|
|
|
|
return ps.deletePropertyValue(groupID, id)
|
|
}
|
|
|
|
func (ps *PropertyService) DeletePropertyValuesForTarget(rctx request.CTX, groupID string, targetType string, targetID string) error {
|
|
if err := ps.runPreDeletePropertyValuesForTarget(rctx, groupID, targetType, targetID); err != nil {
|
|
return fmt.Errorf("DeletePropertyValuesForTarget: %w", err)
|
|
}
|
|
|
|
return ps.deletePropertyValuesForTarget(groupID, targetType, targetID)
|
|
}
|
|
|
|
func (ps *PropertyService) DeletePropertyValuesForField(rctx request.CTX, groupID, fieldID string) error {
|
|
if err := ps.runPreDeletePropertyValuesForField(rctx, groupID, fieldID); err != nil {
|
|
return fmt.Errorf("DeletePropertyValuesForField: %w", err)
|
|
}
|
|
|
|
return ps.deletePropertyValuesForField(groupID, fieldID)
|
|
}
|