Files
upstream-mirror 240db4e2a7
Some checks failed
Server CI / Postgres (shard 0) (push) Has been skipped
Server CI / Postgres (shard 3) (push) Has been skipped
BuildEnv Docker Image / build-image (push) Failing after 4s
BuildEnv Docker Image / build-image-fips (push) Failing after 4s
Migration Review & Release Notes / Generate migration review & release notes (push) Failing after 3s
Server CI / Build Environment (push) Failing after 3s
Server CI / Postgres (shard 1) (push) Has been skipped
Tools CI / check-style (mattermost-govet) (push) Failing after 4s
Server CI / Postgres (shard 2) (push) Has been skipped
Tools CI / check-style (mmgotool) (push) Failing after 3s
Server CI / Elasticsearch v8 Compatibility (push) Has been skipped
Server CI / OpenSearch v2 Compatibility (push) Has been skipped
Server CI / Postgres FIPS (shard 0) (push) Has been skipped
Tools CI / Test (mattermost-govet) (push) Failing after 3s
Server CI / Postgres FIPS (shard 1) (push) Has been skipped
Server CI / Postgres FIPS (shard 2) (push) Has been skipped
Tools CI / Test (mmgotool) (push) Failing after 3s
Server CI / Postgres FIPS (shard 3) (push) Has been skipped
Server CI / Run mmctl tests (push) Has been skipped
Server CI / Run mmctl tests (FIPS) (push) Has been skipped
Server CI / Merge Postgres Test Results (push) Has been skipped
Server CI / Merge Postgres FIPS Test Results (push) Has been skipped
Server CI / Check backport migrations (push) Has been skipped
Server CI / Check generated files (push) Has been skipped
Server CI / check-style (push) Has been skipped
Server CI / Vet API (push) Has been skipped
Server CI / Build mattermost server app (push) Has been skipped
Server CI / Server CI Complete (push) Failing after 2s
Server CI / Server CI Artifacts (push) Has been skipped
Server CI / Sentry Upload (push) Has been skipped
Server CI / Server CI Report (push) Failing after 5m1s
Server CI Nightly Race / Race Detector (push) Has been skipped
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
Webapp CI Cache Warm / Warm Webapp node_modules Cache (push) Has been cancelled
E2E CI Cache Warm / Warm E2E npm Registry Cache (push) Has been cancelled
API / build (push) Has been cancelled
Web App CI / check-types (push) Has been cancelled
Web App CI / check-lint (push) Has been cancelled
Web App CI / check-i18n (push) Has been cancelled
Web App CI / check-external-links (push) Has been cancelled
Web App CI / test (platform) (push) Has been cancelled
Web App CI / test (mattermost-redux) (push) Has been cancelled
Web App CI / test (channels shard 1/4) (push) Has been cancelled
Web App CI / test (channels shard 2/4) (push) Has been cancelled
Web App CI / test (channels shard 3/4) (push) Has been cancelled
Web App CI / test (channels shard 4/4) (push) Has been cancelled
Web App CI / upload-coverage (push) Has been cancelled
Web App CI / build (push) Has been cancelled
YAML Lint / yamllint (push) Has been cancelled
Push mirrored docker images / cd/Push mirrored docker images (push) Has been cancelled
docs-cd / Build and deploy docs (push) Has been cancelled
Docs CI / build (push) Has been cancelled
E2E Cache Warm: Cypress deps / Warm Cypress deps cache (push) Has been cancelled
Scorecards supply-chain security / Scorecard analysis (push) Has been cancelled
Server CI Weekly / Postgres FIPS (push) Has been skipped
Server CI Weekly / Run mmctl tests (FIPS) (push) Has been skipped
Server CI Weekly / Postgres with binary parameters (push) Has been skipped
Server CI Weekly / Build Environment (push) Failing after 1s
Server CI Nightly Race / Build Environment (push) Failing after 1s
Server CI Cache Warm / Warm Go Module and Build Cache (push) Failing after 2s
snapshot of https://github.com/mattermost/mattermost.git (master, 2026-09-16; blobs > 131072B omitted)
2026-09-16 00:29:55 +05:00

277 lines
7.3 KiB
Go

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package sqlstore
import (
"database/sql"
"slices"
"strconv"
sq "github.com/mattermost/squirrel"
"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/channels/utils"
)
type SqlViewStore struct {
*SqlStore
viewSelect sq.SelectBuilder
}
func newSqlViewStore(sqlStore *SqlStore) store.ViewStore {
s := &SqlViewStore{
SqlStore: sqlStore,
}
s.viewSelect = s.getQueryBuilder().
Select(viewColumns()...).
From("Views")
return s
}
func viewColumns() []string {
return []string{
"Id", "ChannelId", "Type", "CreatorId", "Title",
"Description", "SortOrder", "Props",
"CreateAt", "UpdateAt", "DeleteAt",
}
}
func (s *SqlViewStore) Save(view *model.View) (*model.View, error) {
if err := s.saveViewT(s.GetMaster(), view); err != nil {
return nil, err
}
return view, nil
}
// saveViewT inserts a view row using the provided executor (either a
// transaction or a regular master DB handle). The caller is responsible for
// setting view.ChannelId before calling. PreSave + IsValid are run here so
// callers can't forget to validate.
func (s *SqlViewStore) saveViewT(ex sqlxExecutor, view *model.View) error {
view.PreSave()
if err := view.IsValid(); err != nil {
return err
}
builder := s.getQueryBuilder().
Insert("Views").
Columns(viewColumns()...).
Values(
view.Id, view.ChannelId, view.Type, view.CreatorId, view.Title,
view.Description, view.SortOrder, view.Props,
view.CreateAt, view.UpdateAt, view.DeleteAt,
)
if _, err := ex.ExecBuilder(builder); err != nil {
return errors.Wrap(err, "failed to save view")
}
return nil
}
func (s *SqlViewStore) Get(id string) (*model.View, error) {
builder := s.viewSelect.Where(sq.Eq{"Id": id, "DeleteAt": 0})
var view model.View
if err := s.GetReplica().GetBuilder(&view, builder); err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, store.NewErrNotFound("View", id)
}
return nil, errors.Wrapf(err, "failed to get view with id=%s", id)
}
return &view, nil
}
func (s *SqlViewStore) GetForChannel(channelID string, opts model.ViewQueryOpts) ([]*model.View, error) {
if channelID == "" {
return nil, store.NewErrInvalidInput("View", "channelID", channelID)
}
if opts.PerPage <= 0 {
opts.PerPage = model.ViewQueryDefaultPerPage
} else if opts.PerPage > model.ViewQueryMaxPerPage {
opts.PerPage = model.ViewQueryMaxPerPage
}
if opts.Page < 0 {
opts.Page = 0
}
builder := s.viewSelect.
Where(sq.Eq{"ChannelId": channelID, "DeleteAt": 0}).
OrderBy("SortOrder ASC", "CreateAt ASC", "Id ASC").
Limit(uint64(opts.PerPage)).
Offset(uint64(opts.Page * opts.PerPage))
var rows []model.View
if err := s.GetReplica().SelectBuilder(&rows, builder); err != nil {
return nil, errors.Wrapf(err, "failed to get views for channel %s", channelID)
}
views := make([]*model.View, len(rows))
for i := range rows {
views[i] = &rows[i]
}
return views, nil
}
func (s *SqlViewStore) CountForChannel(channelID string, opts model.ViewQueryOpts) (int64, error) {
if channelID == "" {
return 0, store.NewErrInvalidInput("View", "channelID", channelID)
}
builder := s.getQueryBuilder().
Select("COUNT(*)").
From("Views").
Where(sq.Eq{"ChannelId": channelID, "DeleteAt": 0})
var count int64
if err := s.GetReplica().GetBuilder(&count, builder); err != nil {
return 0, errors.Wrapf(err, "failed to count views for channel %s", channelID)
}
return count, nil
}
func (s *SqlViewStore) Update(view *model.View) (*model.View, error) {
view.PreUpdate()
if err := view.IsValid(); err != nil {
return nil, err
}
builder := s.getQueryBuilder().
Update("Views").
Set("Title", view.Title).
Set("Description", view.Description).
Set("SortOrder", view.SortOrder).
Set("Props", view.Props).
Set("UpdateAt", view.UpdateAt).
Where(sq.Eq{"Id": view.Id, "DeleteAt": 0})
res, err := s.GetMaster().ExecBuilder(builder)
if err != nil {
return nil, errors.Wrapf(err, "failed to update view with id=%s", view.Id)
}
rowsAffected, err := res.RowsAffected()
if err != nil {
return nil, errors.Wrapf(err, "failed to get affected rows after updating view with id=%s", view.Id)
}
if rowsAffected == 0 {
return nil, store.NewErrNotFound("View", view.Id)
}
return view, nil
}
func (s *SqlViewStore) Delete(viewID string, deleteAt int64) error {
builder := s.getQueryBuilder().
Update("Views").
Set("DeleteAt", deleteAt).
Set("UpdateAt", deleteAt).
Where(sq.Eq{"Id": viewID, "DeleteAt": 0})
res, err := s.GetMaster().ExecBuilder(builder)
if err != nil {
return errors.Wrapf(err, "failed to delete view with id=%s", viewID)
}
rowsAffected, err := res.RowsAffected()
if err != nil {
return errors.Wrapf(err, "failed to get affected rows after deleting view with id=%s", viewID)
}
if rowsAffected == 0 {
return store.NewErrNotFound("View", viewID)
}
return nil
}
func (s *SqlViewStore) UpdateSortOrder(viewID, channelID string, newIndex int64) ([]*model.View, error) {
if channelID == "" {
return nil, store.NewErrInvalidInput("View", "channelID", channelID)
}
if viewID == "" {
return nil, store.NewErrInvalidInput("View", "viewID", viewID)
}
if newIndex < 0 {
return nil, store.NewErrInvalidInput("View", "SortOrder", newIndex)
}
now := model.GetMillis()
transaction, err := s.GetMaster().Begin()
if err != nil {
return nil, errors.Wrap(err, "failed to begin transaction for UpdateSortOrder")
}
defer finalizeTransactionX(transaction, &err)
builder := s.viewSelect.
Where(sq.Eq{"ChannelId": channelID, "DeleteAt": 0}).
OrderBy("SortOrder ASC", "CreateAt ASC", "Id ASC")
var rows []model.View
if err = transaction.SelectBuilder(&rows, builder); err != nil {
return nil, errors.Wrapf(err, "failed to get views for channel %s", channelID)
}
views := make([]*model.View, len(rows))
for i := range rows {
views[i] = &rows[i]
}
if len(views) == 0 || int(newIndex) > len(views)-1 {
return nil, store.NewErrInvalidInput("View", "SortOrder", newIndex)
}
currentIndex := -1
var current *model.View
for index, v := range views {
if v.Id == viewID {
currentIndex = index
current = v
break
}
}
if currentIndex == -1 {
return nil, store.NewErrNotFound("View", viewID)
}
views = utils.RemoveElementFromSliceAtIndex(views, currentIndex)
views = slices.Insert(views, int(newIndex), current)
caseStmt := sq.Case()
query := s.getQueryBuilder().Update("Views")
ids := make([]string, 0, len(views))
for index, v := range views {
v.SortOrder = index
v.UpdateAt = now
caseStmt = caseStmt.When(sq.Eq{"Id": v.Id}, strconv.FormatInt(int64(index), 10))
ids = append(ids, v.Id)
}
query = query.Set("SortOrder", caseStmt)
query = query.Set("UpdateAt", now)
query = query.Where(sq.Eq{"Id": ids})
queryStr, args, queryErr := query.ToSql()
if queryErr != nil {
return nil, errors.Wrap(queryErr, "failed to build sort order update query for views")
}
if _, execErr := transaction.Exec(queryStr, args...); execErr != nil {
return nil, errors.Wrapf(execErr, "failed to update sort order for views in channel %s", channelID)
}
if err = transaction.Commit(); err != nil {
return nil, errors.Wrap(err, "failed to commit sort order update for views")
}
return views, nil
}