mirror of
https://github.com/hanzoai/team.git
synced 2026-09-30 01:57:35 +00:00
The server module named github.com/hanzoai/team/server/v8 — a path that resolves to the retired workspace's repository, not this one — while server/public still published github.com/mattermost/mattermost/server/public. Both are github.com/hanzoteam/server/... now, which is where this code actually lives. That second half is what made the binary unbootable. Upstream's public path stayed claimed by upstream, mattermost-plugin-ai imported it, and the build linked public/model twice; public/plugin's gob.Register then ran twice under one name and panicked at init, before main. Reverting the rename traded the brand for a working binary. Rebranding the plugin alongside it needs no such trade: the agents fork is github.com/hanzoteam/agents/v2 against our public module, so no module named mattermost/mattermost is in the graph at all. Measured: `go list -deps ./cmd/mattermost` names exactly one public/model, and it is ours.
367 lines
15 KiB
Go
367 lines
15 KiB
Go
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
// See LICENSE.txt for license information.
|
|
|
|
package app
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
|
|
"github.com/hanzoteam/server/server/public/model"
|
|
"github.com/hanzoteam/server/server/public/shared/mlog"
|
|
"github.com/hanzoteam/server/server/public/shared/request"
|
|
)
|
|
|
|
func (a *App) resolveValueBroadcastParams(rctx request.CTX, objectType, targetID string) (teamID, channelID string, err *model.AppError) {
|
|
switch objectType {
|
|
case model.PropertyFieldObjectTypePost:
|
|
post, appErr := a.GetSinglePost(rctx, targetID, false)
|
|
if appErr != nil {
|
|
return "", "", appErr
|
|
}
|
|
return "", post.ChannelId, nil
|
|
case model.PropertyFieldObjectTypeChannel:
|
|
return "", targetID, nil
|
|
case model.PropertyFieldObjectTypeUser:
|
|
return "", "", nil // system-wide
|
|
case model.PropertyFieldObjectTypeSystem:
|
|
return "", "", nil // system-wide
|
|
default:
|
|
return "", "", model.NewAppError(
|
|
"resolveValueBroadcastParams",
|
|
"app.property_value.resolve_broadcast_params.unknown_object_type.app_error",
|
|
map[string]any{"ObjectType": objectType},
|
|
"unrecognized object type",
|
|
http.StatusBadRequest,
|
|
)
|
|
}
|
|
}
|
|
|
|
// CreatePropertyValue creates a new property value.
|
|
func (a *App) CreatePropertyValue(rctx request.CTX, value *model.PropertyValue) (*model.PropertyValue, *model.AppError) {
|
|
if value == nil {
|
|
return nil, model.NewAppError("CreatePropertyValue", "app.property_value.invalid_input.app_error", nil, "property value is required", http.StatusBadRequest)
|
|
}
|
|
value.Value = model.SanitizePropertyValue(value.Value)
|
|
|
|
createdValue, err := a.Srv().propertyService.CreatePropertyValue(rctx, value)
|
|
if err != nil {
|
|
if appErr := mapPropertyServiceError("CreatePropertyValue", err); appErr != nil {
|
|
return nil, appErr
|
|
}
|
|
return nil, model.NewAppError("CreatePropertyValue", "app.property_value.create.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
|
}
|
|
return createdValue, nil
|
|
}
|
|
|
|
// CreatePropertyValues creates multiple property values.
|
|
func (a *App) CreatePropertyValues(rctx request.CTX, values []*model.PropertyValue) ([]*model.PropertyValue, *model.AppError) {
|
|
if len(values) == 0 {
|
|
return nil, model.NewAppError("CreatePropertyValues", "app.property_value.invalid_input.app_error", nil, "property values are required", http.StatusBadRequest)
|
|
}
|
|
for _, v := range values {
|
|
v.Value = model.SanitizePropertyValue(v.Value)
|
|
}
|
|
|
|
createdValues, err := a.Srv().propertyService.CreatePropertyValues(rctx, values)
|
|
if err != nil {
|
|
if appErr := mapPropertyServiceError("CreatePropertyValues", err); appErr != nil {
|
|
return nil, appErr
|
|
}
|
|
return nil, model.NewAppError("CreatePropertyValues", "app.property_value.create_many.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
|
}
|
|
return createdValues, nil
|
|
}
|
|
|
|
// GetPropertyValue retrieves a property value by group ID and value ID.
|
|
func (a *App) GetPropertyValue(rctx request.CTX, groupID, valueID string) (*model.PropertyValue, *model.AppError) {
|
|
value, err := a.Srv().propertyService.GetPropertyValue(rctx, groupID, valueID)
|
|
if err != nil {
|
|
if appErr := mapPropertyServiceError("GetPropertyValue", err); appErr != nil {
|
|
return nil, appErr
|
|
}
|
|
return nil, model.NewAppError("GetPropertyValue", "app.property_value.get.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
|
}
|
|
return value, nil
|
|
}
|
|
|
|
// GetPropertyValues retrieves multiple property values by their IDs.
|
|
func (a *App) GetPropertyValues(rctx request.CTX, groupID string, ids []string) ([]*model.PropertyValue, *model.AppError) {
|
|
values, err := a.Srv().propertyService.GetPropertyValues(rctx, groupID, ids)
|
|
if err != nil {
|
|
if appErr := mapPropertyServiceError("GetPropertyValues", err); appErr != nil {
|
|
return nil, appErr
|
|
}
|
|
return nil, model.NewAppError("GetPropertyValues", "app.property_value.get_many.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
|
}
|
|
return values, nil
|
|
}
|
|
|
|
// SearchPropertyValues searches for property values matching the given options.
|
|
func (a *App) SearchPropertyValues(rctx request.CTX, groupID string, opts model.PropertyValueSearchOpts) ([]*model.PropertyValue, *model.AppError) {
|
|
values, err := a.Srv().propertyService.SearchPropertyValues(rctx, groupID, opts)
|
|
if err != nil {
|
|
if appErr := mapPropertyServiceError("SearchPropertyValues", err); appErr != nil {
|
|
return nil, appErr
|
|
}
|
|
return nil, model.NewAppError("SearchPropertyValues", "app.property_value.search.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
|
}
|
|
return values, nil
|
|
}
|
|
|
|
// UpdatePropertyValue updates an existing property value.
|
|
func (a *App) UpdatePropertyValue(rctx request.CTX, groupID string, value *model.PropertyValue) (*model.PropertyValue, *model.AppError) {
|
|
if value == nil {
|
|
return nil, model.NewAppError("UpdatePropertyValue", "app.property_value.invalid_input.app_error", nil, "property value is required", http.StatusBadRequest)
|
|
}
|
|
value.Value = model.SanitizePropertyValue(value.Value)
|
|
|
|
updatedValue, err := a.Srv().propertyService.UpdatePropertyValue(rctx, groupID, value)
|
|
if err != nil {
|
|
if appErr := mapPropertyServiceError("UpdatePropertyValue", err); appErr != nil {
|
|
return nil, appErr
|
|
}
|
|
return nil, model.NewAppError("UpdatePropertyValue", "app.property_value.update.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
|
}
|
|
return updatedValue, nil
|
|
}
|
|
|
|
// UpdatePropertyValues updates multiple property values.
|
|
func (a *App) UpdatePropertyValues(rctx request.CTX, groupID string, values []*model.PropertyValue) ([]*model.PropertyValue, *model.AppError) {
|
|
if len(values) == 0 {
|
|
return nil, model.NewAppError("UpdatePropertyValues", "app.property_value.invalid_input.app_error", nil, "property values are required", http.StatusBadRequest)
|
|
}
|
|
for _, v := range values {
|
|
v.Value = model.SanitizePropertyValue(v.Value)
|
|
}
|
|
|
|
updatedValues, err := a.Srv().propertyService.UpdatePropertyValues(rctx, groupID, values)
|
|
if err != nil {
|
|
if appErr := mapPropertyServiceError("UpdatePropertyValues", err); appErr != nil {
|
|
return nil, appErr
|
|
}
|
|
return nil, model.NewAppError("UpdatePropertyValues", "app.property_value.update_many.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
|
}
|
|
return updatedValues, nil
|
|
}
|
|
|
|
// UpsertPropertyValue creates or updates a property value.
|
|
func (a *App) UpsertPropertyValue(rctx request.CTX, value *model.PropertyValue) (*model.PropertyValue, *model.AppError) {
|
|
if value == nil {
|
|
return nil, model.NewAppError("UpsertPropertyValue", "app.property_value.invalid_input.app_error", nil, "property value is required", http.StatusBadRequest)
|
|
}
|
|
value.Value = model.SanitizePropertyValue(value.Value)
|
|
|
|
upsertedValue, err := a.Srv().propertyService.UpsertPropertyValue(rctx, value)
|
|
if err != nil {
|
|
if appErr := mapPropertyServiceError("UpsertPropertyValue", err); appErr != nil {
|
|
return nil, appErr
|
|
}
|
|
return nil, model.NewAppError("UpsertPropertyValue", "app.property_value.upsert.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
|
}
|
|
return upsertedValue, nil
|
|
}
|
|
|
|
// UpsertPropertyValues creates or updates multiple property values.
|
|
// When objectType is non-empty, WebSocket events are broadcast to notify
|
|
// clients of the updated values, and every referenced field is required
|
|
// to have a matching ObjectType.
|
|
func (a *App) UpsertPropertyValues(rctx request.CTX, values []*model.PropertyValue, objectType, targetID, connectionID string) ([]*model.PropertyValue, *model.AppError) {
|
|
if len(values) == 0 {
|
|
return nil, model.NewAppError("UpsertPropertyValues", "app.property_value.invalid_input.app_error", nil, "property values are required", http.StatusBadRequest)
|
|
}
|
|
|
|
// Intrinsic invariants — apply to every caller (HTTP, plugin, internal).
|
|
// Single-group invariant must run before the bulk-load below, since
|
|
// GetPropertyFields takes a single groupID. Guard values[0] explicitly
|
|
// because the per-element nil check inside the loop would otherwise be
|
|
// reached after the values[0].GroupID dereference.
|
|
if values[0] == nil {
|
|
return nil, model.NewAppError("UpsertPropertyValues", "app.property_value.invalid_input.app_error", nil, "nil property value in batch", http.StatusBadRequest)
|
|
}
|
|
groupID := values[0].GroupID
|
|
seenIDs := make(map[string]bool, len(values))
|
|
fieldIDs := make([]string, 0, len(values))
|
|
for _, v := range values {
|
|
if v == nil {
|
|
return nil, model.NewAppError("UpsertPropertyValues", "app.property_value.invalid_input.app_error", nil, "nil property value in batch", http.StatusBadRequest)
|
|
}
|
|
if v.GroupID != groupID {
|
|
return nil, model.NewAppError(
|
|
"UpsertPropertyValues",
|
|
"app.property_value.upsert.mixed_groups.app_error",
|
|
nil,
|
|
"all values in a batch must belong to the same group",
|
|
http.StatusBadRequest,
|
|
)
|
|
}
|
|
if !model.IsValidId(v.FieldID) {
|
|
return nil, model.NewAppError(
|
|
"UpsertPropertyValues",
|
|
"app.property_value.upsert.invalid_field_id.app_error",
|
|
map[string]any{"FieldID": v.FieldID},
|
|
"invalid field ID",
|
|
http.StatusBadRequest,
|
|
)
|
|
}
|
|
if seenIDs[v.FieldID] {
|
|
return nil, model.NewAppError(
|
|
"UpsertPropertyValues",
|
|
"app.property_value.upsert.duplicate_field_id.app_error",
|
|
map[string]any{"FieldID": v.FieldID},
|
|
"duplicate field ID in batch",
|
|
http.StatusBadRequest,
|
|
)
|
|
}
|
|
seenIDs[v.FieldID] = true
|
|
fieldIDs = append(fieldIDs, v.FieldID)
|
|
v.Value = model.SanitizePropertyValue(v.Value)
|
|
}
|
|
|
|
// ObjectType-mismatch check is gated on a non-empty objectType argument.
|
|
// Plugin API today always passes objectType="" and keeps its loose
|
|
// contract on this specific check.
|
|
if objectType != "" {
|
|
fields, fieldsErr := a.GetPropertyFields(rctx, groupID, fieldIDs)
|
|
if fieldsErr != nil {
|
|
return nil, fieldsErr
|
|
}
|
|
fieldByID := make(map[string]*model.PropertyField, len(fields))
|
|
for _, f := range fields {
|
|
fieldByID[f.ID] = f
|
|
}
|
|
for _, v := range values {
|
|
f, ok := fieldByID[v.FieldID]
|
|
if !ok {
|
|
return nil, model.NewAppError(
|
|
"UpsertPropertyValues",
|
|
"app.property_value.upsert.field_not_found.app_error",
|
|
map[string]any{"FieldID": v.FieldID},
|
|
"field not found",
|
|
http.StatusNotFound,
|
|
)
|
|
}
|
|
if f.ObjectType != objectType {
|
|
// 404 matches the shape of a non-existent field so callers
|
|
// cannot distinguish "no such field" from "field exists but
|
|
// in a different object-type bucket".
|
|
return nil, model.NewAppError(
|
|
"UpsertPropertyValues",
|
|
"app.property_value.upsert.object_type_mismatch.app_error",
|
|
map[string]any{"FieldID": v.FieldID},
|
|
"object type mismatch",
|
|
http.StatusNotFound,
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
result, err := a.Srv().propertyService.UpsertPropertyValues(rctx, values)
|
|
if err != nil {
|
|
if appErr := mapPropertyServiceError("UpsertPropertyValues", err); appErr != nil {
|
|
return nil, appErr
|
|
}
|
|
return nil, model.NewAppError("UpsertPropertyValues", "app.property_value.upsert_many.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
|
}
|
|
|
|
// Only publish websocket events for PSAv2 properties (those with an ObjectType)
|
|
if objectType != "" {
|
|
teamID, channelID, appErr := a.resolveValueBroadcastParams(rctx, objectType, targetID)
|
|
if appErr != nil {
|
|
rctx.Logger().Warn("Failed to resolve broadcast params for property values", mlog.Err(appErr))
|
|
} else {
|
|
valuesJSON, jsonErr := json.Marshal(result)
|
|
if jsonErr != nil {
|
|
rctx.Logger().Warn("Failed to encode property values to JSON", mlog.Err(jsonErr))
|
|
} else {
|
|
message := model.NewWebSocketEvent(model.WebsocketEventPropertyValuesUpdated, teamID, channelID, "", nil, connectionID)
|
|
message.Add("object_type", objectType)
|
|
message.Add("target_id", targetID)
|
|
message.Add("values", string(valuesJSON))
|
|
a.Publish(message)
|
|
}
|
|
}
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
// DeletePropertyValue deletes a property value and broadcasts a property_values_updated event.
|
|
func (a *App) DeletePropertyValue(rctx request.CTX, groupID, valueID string) *model.AppError {
|
|
value, appErr := a.GetPropertyValue(rctx, groupID, valueID)
|
|
if appErr != nil {
|
|
return appErr
|
|
}
|
|
|
|
if err := a.Srv().propertyService.DeletePropertyValue(rctx, groupID, valueID); err != nil {
|
|
if mappedErr := mapPropertyServiceError("DeletePropertyValue", err); mappedErr != nil {
|
|
return mappedErr
|
|
}
|
|
return model.NewAppError("DeletePropertyValue", "app.property_value.delete.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
|
}
|
|
|
|
teamID, channelID, appErr := a.resolveValueBroadcastParams(rctx, value.TargetType, value.TargetID)
|
|
if appErr != nil {
|
|
rctx.Logger().Warn("Failed to resolve broadcast params for property value deletion", mlog.Err(appErr))
|
|
return nil
|
|
}
|
|
|
|
deleted := &model.PropertyValue{
|
|
TargetID: value.TargetID,
|
|
TargetType: value.TargetType,
|
|
GroupID: value.GroupID,
|
|
FieldID: value.FieldID,
|
|
}
|
|
valuesJSON, jsonErr := json.Marshal([]*model.PropertyValue{deleted})
|
|
if jsonErr != nil {
|
|
rctx.Logger().Warn("Failed to encode deleted property value to JSON", mlog.Err(jsonErr))
|
|
return nil
|
|
}
|
|
|
|
message := model.NewWebSocketEvent(model.WebsocketEventPropertyValuesUpdated, teamID, channelID, "", nil, "")
|
|
message.Add("object_type", value.TargetType)
|
|
message.Add("target_id", value.TargetID)
|
|
message.Add("values", string(valuesJSON))
|
|
a.Publish(message)
|
|
return nil
|
|
}
|
|
|
|
// DeletePropertyValuesForTarget deletes all property values for a target and broadcasts a property_values_updated event.
|
|
func (a *App) DeletePropertyValuesForTarget(rctx request.CTX, groupID, targetType, targetID string) *model.AppError {
|
|
if err := a.Srv().propertyService.DeletePropertyValuesForTarget(rctx, groupID, targetType, targetID); err != nil {
|
|
if appErr := mapPropertyServiceError("DeletePropertyValuesForTarget", err); appErr != nil {
|
|
return appErr
|
|
}
|
|
return model.NewAppError("DeletePropertyValuesForTarget", "app.property_value.delete_for_target.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
|
}
|
|
|
|
teamID, channelID, appErr := a.resolveValueBroadcastParams(rctx, targetType, targetID)
|
|
if appErr != nil {
|
|
rctx.Logger().Warn("Failed to resolve broadcast params for property value deletion", mlog.Err(appErr))
|
|
return nil
|
|
}
|
|
|
|
message := model.NewWebSocketEvent(model.WebsocketEventPropertyValuesUpdated, teamID, channelID, "", nil, "")
|
|
message.Add("object_type", targetType)
|
|
message.Add("target_id", targetID)
|
|
message.Add("values", "[]")
|
|
a.Publish(message)
|
|
return nil
|
|
}
|
|
|
|
// DeletePropertyValuesForField deletes all property values for a field and broadcasts a property_values_updated event.
|
|
func (a *App) DeletePropertyValuesForField(rctx request.CTX, groupID, fieldID string) *model.AppError {
|
|
if err := a.Srv().propertyService.DeletePropertyValuesForField(rctx, groupID, fieldID); err != nil {
|
|
if appErr := mapPropertyServiceError("DeletePropertyValuesForField", err); appErr != nil {
|
|
return appErr
|
|
}
|
|
return model.NewAppError("DeletePropertyValuesForField", "app.property_value.delete_for_field.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
|
}
|
|
|
|
message := model.NewWebSocketEvent(model.WebsocketEventPropertyValuesUpdated, "", "", "", nil, "")
|
|
message.Add("field_id", fieldID)
|
|
message.Add("values", "[]")
|
|
a.Publish(message)
|
|
return nil
|
|
}
|