mattermost-community-enterp.../channels/store/sqlstore/bot_store.go
Claude ec1f89217a Merge: Complete Mattermost Server with Community Enterprise
Full Mattermost server source with integrated Community Enterprise features.
Includes vendor directory for offline/air-gapped builds.

Structure:
- enterprise-impl/: Enterprise feature implementations
- enterprise-community/: Init files that register implementations
- enterprise/: Bridge imports (community_imports.go)
- vendor/: All dependencies for offline builds

Build (online):
  go build ./cmd/mattermost

Build (offline/air-gapped):
  go build -mod=vendor ./cmd/mattermost

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-17 23:59:07 +09:00

271 lines
7.6 KiB
Go

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package sqlstore
import (
"database/sql"
"fmt"
"strings"
"github.com/pkg/errors"
"github.com/mattermost/mattermost/server/public/model"
"github.com/mattermost/mattermost/server/v8/channels/store"
"github.com/mattermost/mattermost/server/v8/einterfaces"
sq "github.com/mattermost/squirrel"
)
// bot is a subset of the model.Bot type, omitting the model.User fields.
type bot struct {
UserId string `json:"user_id"`
Description string `json:"description"`
OwnerId string `json:"owner_id"`
LastIconUpdate int64 `json:"last_icon_update"`
CreateAt int64 `json:"create_at"`
UpdateAt int64 `json:"update_at"`
DeleteAt int64 `json:"delete_at"`
}
func botFromModel(b *model.Bot) *bot {
return &bot{
UserId: b.UserId,
Description: b.Description,
OwnerId: b.OwnerId,
LastIconUpdate: b.LastIconUpdate,
CreateAt: b.CreateAt,
UpdateAt: b.UpdateAt,
DeleteAt: b.DeleteAt,
}
}
// SqlBotStore is a store for managing bots in the database.
// Bots are otherwise normal users with extra metadata record in the Bots table. The primary key
// for a bot matches the primary key value for corresponding User record.
type SqlBotStore struct {
*SqlStore
metrics einterfaces.MetricsInterface
// botsQuery is a starting point for all queries that return one or more Bots.
botsQuery sq.SelectBuilder
}
// newSqlBotStore creates an instance of SqlBotStore, registering the table schema in question.
func newSqlBotStore(sqlStore *SqlStore, metrics einterfaces.MetricsInterface) store.BotStore {
bs := &SqlBotStore{
SqlStore: sqlStore,
metrics: metrics,
}
// note: we are providing field names explicitly here to maintain order of columns (needed when using raw queries)
bs.botsQuery = bs.getQueryBuilder().
Select("b.UserId", "u.Username", "u.FirstName AS DisplayName", "b.Description", "b.OwnerId", "COALESCE(b.LastIconUpdate, 0) AS LastIconUpdate", "b.CreateAt", "b.UpdateAt", "b.DeleteAt").
From("Bots b").
Join("Users u ON ( u.Id = b.UserId )")
return bs
}
// Get fetches the given bot in the database.
func (us SqlBotStore) Get(botUserId string, includeDeleted bool) (*model.Bot, error) {
var excludeDeletedSql = "AND b.DeleteAt = 0"
if includeDeleted {
excludeDeletedSql = ""
}
query := `
SELECT
b.UserId,
u.Username,
u.FirstName AS DisplayName,
b.Description,
b.OwnerId,
COALESCE(b.LastIconUpdate, 0) AS LastIconUpdate,
b.CreateAt,
b.UpdateAt,
b.DeleteAt
FROM
Bots b
JOIN
Users u ON (u.Id = b.UserId)
WHERE
b.UserId = ?
` + excludeDeletedSql + `
`
var bot model.Bot
if err := us.GetReplica().Get(&bot, query, botUserId); err == sql.ErrNoRows {
return nil, store.NewErrNotFound("Bot", botUserId)
} else if err != nil {
return nil, errors.Wrapf(err, "selectone: user_id=%s", botUserId)
}
return &bot, nil
}
// GetAll fetches from all bots in the database.
func (us SqlBotStore) GetAll(options *model.BotGetOptions) ([]*model.Bot, error) {
var conditions []string
var conditionsSql string
var additionalJoin string
var args []any
if !options.IncludeDeleted {
conditions = append(conditions, "b.DeleteAt = 0")
}
if options.OwnerId != "" {
conditions = append(conditions, "b.OwnerId = ?")
args = append(args, options.OwnerId)
}
if options.OnlyOrphaned {
additionalJoin = "JOIN Users o ON (o.Id = b.OwnerId)"
conditions = append(conditions, "o.DeleteAt != 0")
}
if len(conditions) > 0 {
conditionsSql = "WHERE " + strings.Join(conditions, " AND ")
}
sql := `
SELECT
b.UserId,
u.Username,
u.FirstName AS DisplayName,
b.Description,
b.OwnerId,
COALESCE(b.LastIconUpdate, 0) AS LastIconUpdate,
b.CreateAt,
b.UpdateAt,
b.DeleteAt
FROM
Bots b
JOIN
Users u ON (u.Id = b.UserId)
` + additionalJoin + `
` + conditionsSql + `
ORDER BY
b.CreateAt ASC,
u.Username ASC
LIMIT
?
OFFSET
?
`
// append limit, offset
args = append(args, options.PerPage, options.Page*options.PerPage)
bots := []*model.Bot{}
if err := us.GetReplica().Select(&bots, sql, args...); err != nil {
return nil, errors.Wrap(err, "error selecting all bots")
}
return bots, nil
}
// Save persists a new bot to the database.
// It assumes the corresponding user was saved via the user store.
func (us SqlBotStore) Save(bot *model.Bot) (*model.Bot, error) {
bot = bot.Clone()
bot.PreSave()
if err := bot.IsValid(); err != nil { // TODO: change to return error in v6.
return nil, err
}
if _, err := us.GetMaster().NamedExec(`INSERT INTO Bots
(UserId, Description, OwnerId, LastIconUpdate, CreateAt, UpdateAt, DeleteAt)
VALUES
(:UserId, :Description, :OwnerId, :LastIconUpdate, :CreateAt, :UpdateAt, :DeleteAt)`, botFromModel(bot)); err != nil {
return nil, errors.Wrapf(err, "insert: user_id=%s", bot.UserId)
}
return bot, nil
}
// Update persists an updated bot to the database.
// It assumes the corresponding user was updated via the user store.
func (us SqlBotStore) Update(bot *model.Bot) (*model.Bot, error) {
bot = bot.Clone()
bot.PreUpdate()
if err := bot.IsValid(); err != nil { // TODO: needs to return error in v6
return nil, err
}
oldBot, err := us.Get(bot.UserId, true)
if err != nil {
return nil, err
}
oldBot.Description = bot.Description
oldBot.OwnerId = bot.OwnerId
oldBot.LastIconUpdate = bot.LastIconUpdate
oldBot.UpdateAt = bot.UpdateAt
oldBot.DeleteAt = bot.DeleteAt
bot = oldBot
res, err := us.GetMaster().NamedExec(`UPDATE Bots
SET Description=:Description, OwnerId=:OwnerId, LastIconUpdate=:LastIconUpdate,
UpdateAt=:UpdateAt, DeleteAt=:DeleteAt
WHERE UserId=:UserId`, botFromModel(bot))
if err != nil {
return nil, errors.Wrapf(err, "update: user_id=%s", bot.UserId)
}
count, err := res.RowsAffected()
if err != nil {
return nil, errors.Wrap(err, "error while getting rows_affected")
}
if count > 1 {
return nil, fmt.Errorf("unexpected count while updating bot: count=%d, userId=%s", count, bot.UserId)
}
return bot, nil
}
// PermanentDelete removes the bot from the database altogether.
// If the corresponding user is to be deleted, it must be done via the user store.
func (us SqlBotStore) PermanentDelete(botUserId string) error {
query := "DELETE FROM Bots WHERE UserId = ?"
if _, err := us.GetMaster().Exec(query, botUserId); err != nil {
return store.NewErrInvalidInput("Bot", "UserId", botUserId).Wrap(err)
}
return nil
}
func (us SqlBotStore) GetAllAfter(limit int, afterId string) ([]*model.Bot, error) {
query := us.botsQuery.Where("b.UserId > ?", afterId).OrderBy("b.UserId ASC").Limit(uint64(limit))
queryString, args, err := query.ToSql()
if err != nil {
return nil, errors.Wrap(err, "get_all_after_tosql")
}
bots := []*model.Bot{}
if err := us.GetReplica().Select(&bots, queryString, args...); err != nil {
return nil, errors.Wrap(err, "failed to find Bots")
}
return bots, nil
}
// Get fetches the given bot in the database.
func (us SqlBotStore) GetByUsername(username string) (*model.Bot, error) {
query := us.botsQuery.Where("u.Username = lower(?)", username)
queryString, args, err := query.ToSql()
if err != nil {
return nil, errors.Wrap(err, "get_by_username_tosql")
}
bot := model.Bot{}
if err := us.GetReplica().Get(&bot, queryString, args...); err != nil {
if err == sql.ErrNoRows {
return nil, errors.Wrap(store.NewErrNotFound("Bot", fmt.Sprintf("username=%s", username)), "failed to find Bot")
}
return nil, errors.Wrapf(err, "failed to find Bot with username=%s", username)
}
return &bot, nil
}