Files
zeekay 8feb24ea1f One name for this fork, and it is ours
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.
2026-08-28 23:35:36 -07:00

280 lines
7.9 KiB
Go

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package teams
import (
"errors"
"fmt"
"github.com/hanzoteam/server/server/public/model"
"github.com/hanzoteam/server/server/public/shared/i18n"
"github.com/hanzoteam/server/server/public/shared/request"
"github.com/hanzoteam/server/server/v8/channels/store"
)
func (ts *TeamService) CreateTeam(rctx request.CTX, team *model.Team) (*model.Team, error) {
team.InviteId = ""
rteam, err := ts.store.Save(team)
if err != nil {
return nil, err
}
if _, err := ts.createDefaultChannels(rctx, rteam.Id); err != nil {
return nil, err
}
return rteam, nil
}
func (ts *TeamService) GetTeam(teamID string) (*model.Team, error) {
team, err := ts.store.Get(teamID)
if err != nil {
return nil, err
}
return team, nil
}
func (ts *TeamService) GetTeams(teamIDs []string) ([]*model.Team, error) {
teams, err := ts.store.GetMany(teamIDs)
if err != nil {
return nil, err
}
return teams, nil
}
// CreateDefaultChannels creates channels in the given team for each channel returned by (*App).DefaultChannelNames.
func (ts *TeamService) createDefaultChannels(rctx request.CTX, teamID string) ([]*model.Channel, error) {
displayNames := map[string]string{
model.DefaultChannelName: i18n.T("api.channel.create_default_channels.town_square"),
model.RandomChannelName: i18n.T("api.channel.create_default_channels.off_topic"),
}
channels := []*model.Channel{}
defaultChannelNames := ts.DefaultChannelNames()
for _, name := range defaultChannelNames {
var displayName string
if displayNameValue, ok := displayNames[name]; ok {
displayName = i18n.TDefault(displayNameValue, name)
} else {
// If the default channel is experimental (from config.json)
// we don't have to translate
displayName = name
}
channel := &model.Channel{DisplayName: displayName, Name: name, Type: model.ChannelTypeOpen, TeamId: teamID}
// We should use the channel service here (coming soon). Ideally, we should just emit an event
// and let the subscribers do the job, in this case it would be the channels service.
// Currently we are adding services to the server and because of that we are using
// the channel store here. This should be replaced in the future.
if _, err := ts.channelStore.Save(rctx, channel, *ts.config().TeamSettings.MaxChannelsPerTeam); err != nil {
return nil, err
}
channels = append(channels, channel)
}
return channels, nil
}
type UpdateOptions struct {
Sanitized bool
Imported bool
}
func (ts *TeamService) UpdateTeam(team *model.Team, opts UpdateOptions) (*model.Team, error) {
oldTeam := team
var err error
if !opts.Imported {
oldTeam, err = ts.store.Get(team.Id)
if err != nil {
return nil, err
}
if err = ts.checkValidDomains(team); err != nil {
return nil, err
}
}
if opts.Sanitized {
if team.Name != "" && team.Name != oldTeam.Name && team.Name != "-" {
_, nErr := ts.store.GetByName(team.Name)
if nErr == nil {
return oldTeam, &NameOccupiedError{Name: team.Name}
}
var nfErr *store.ErrNotFound
if !errors.As(nErr, &nfErr) {
return oldTeam, nErr
}
oldTeam.Name = team.Name
}
oldTeam.DisplayName = team.DisplayName
oldTeam.Description = team.Description
oldTeam.AllowOpenInvite = team.AllowOpenInvite
oldTeam.CompanyName = team.CompanyName
oldTeam.AllowedDomains = team.AllowedDomains
oldTeam.LastTeamIconUpdate = team.LastTeamIconUpdate
oldTeam.GroupConstrained = team.GroupConstrained
}
oldTeam, err = ts.store.Update(oldTeam)
if err != nil {
return team, err
}
return oldTeam, nil
}
func (ts *TeamService) PatchTeam(teamID string, patch *model.TeamPatch) (*model.Team, error) {
team, err := ts.store.Get(teamID)
if err != nil {
return nil, err
}
team.Patch(patch)
if patch.AllowOpenInvite != nil && !*patch.AllowOpenInvite {
team.InviteId = model.NewId()
}
if err = ts.checkValidDomains(team); err != nil {
return nil, err
}
team, err = ts.store.Update(team)
if err != nil {
return team, err
}
return team, nil
}
func applyPreSaveHooks(hooks []func(*model.TeamMember) (*model.TeamMember, error), tm *model.TeamMember) (*model.TeamMember, error) {
for _, hook := range hooks {
var err error
tm, err = hook(tm)
if err != nil {
return nil, err
}
if tm == nil {
return nil, fmt.Errorf("preSaveHook returned nil TeamMember without error")
}
}
return tm, nil
}
// JoinUserToTeam adds a user to the team and it returns three values:
// 1. a pointer to the team member, if successful
// 2. a boolean: true if the user has a non-deleted team member for that team already, otherwise false.
// 3. a pointer to an AppError if something went wrong.
//
// An optional preSaveHook can be provided to inspect/modify the TeamMember before it is persisted.
// If the hook returns an error, the addition is rejected.
func (ts *TeamService) JoinUserToTeam(rctx request.CTX, team *model.Team, user *model.User, preSaveHooks ...func(*model.TeamMember) (*model.TeamMember, error)) (*model.TeamMember, bool, error) {
if !ts.IsTeamEmailAllowed(user, team) {
return nil, false, AcceptedDomainError
}
tm := &model.TeamMember{
TeamId: team.Id,
UserId: user.Id,
SchemeGuest: user.IsGuest(),
SchemeUser: !user.IsGuest(),
CreateAt: model.GetMillis(),
}
if !user.IsGuest() {
userShouldBeAdmin, err := ts.userIsInAdminRoleGroup(user.Id, team.Id, model.GroupSyncableTypeTeam)
if err != nil {
return nil, false, err
}
tm.SchemeAdmin = userShouldBeAdmin
}
if team.Email == user.Email && !user.IsGuest() {
tm.SchemeAdmin = true
}
rtm, err := ts.store.GetMember(rctx, team.Id, user.Id)
if err != nil {
// Membership appears to be missing. Lets try to add.
tm, err = applyPreSaveHooks(preSaveHooks, tm)
if err != nil {
return nil, false, err
}
tmr, nErr := ts.store.SaveMember(rctx, tm, *ts.config().TeamSettings.MaxUsersPerTeam)
if nErr != nil {
return nil, false, nErr
}
return tmr, false, nil
}
// Membership already exists. Check if deleted and update, otherwise do nothing
// Do nothing if already added
if rtm.DeleteAt == 0 {
return rtm, true, nil
}
membersCount, err := ts.store.GetActiveMemberCount(tm.TeamId, nil)
if err != nil {
return nil, false, MemberCountError
}
if membersCount >= int64(*ts.config().TeamSettings.MaxUsersPerTeam) {
return nil, false, MaxMemberCountError
}
tm, err = applyPreSaveHooks(preSaveHooks, tm)
if err != nil {
return nil, false, err
}
member, nErr := ts.store.UpdateMember(rctx, tm)
if nErr != nil {
return nil, false, nErr
}
return member, false, nil
}
// RemoveTeamMember removes the team member from the team. This method sends
// the websocket message before actually removing so the user being removed gets it.
func (ts *TeamService) RemoveTeamMember(rctx request.CTX, teamMember *model.TeamMember) error {
/*
MM-43850: send leave_team event to user using `ReliableClusterSend` to improve safety
*/
// message for other team members
omitUsers := make(map[string]bool, 1)
omitUsers[teamMember.UserId] = true
messageTeam := model.NewWebSocketEvent(model.WebsocketEventLeaveTeam, teamMember.TeamId, "", "", omitUsers, "")
messageTeam.Add("user_id", teamMember.UserId)
messageTeam.Add("team_id", teamMember.TeamId)
ts.wh.Publish(messageTeam)
// message for teamMember.UserId
messageUser := model.NewWebSocketEvent(model.WebsocketEventLeaveTeam, "", "", teamMember.UserId, nil, "")
messageUser.Add("user_id", teamMember.UserId)
messageUser.Add("team_id", teamMember.TeamId)
ts.wh.Publish(messageUser)
// delete team member
teamMember.Roles = ""
teamMember.DeleteAt = model.GetMillis()
if _, nErr := ts.store.UpdateMember(rctx, teamMember); nErr != nil {
return nErr
}
return nil
}
// GetMember return the team member from the team.
func (ts *TeamService) GetMember(rctx request.CTX, teamID string, userID string) (*model.TeamMember, error) {
member, err := ts.store.GetMember(rctx, teamID, userID)
if err != nil {
return nil, err
}
return member, err
}