focalboard/server/services/store/sqlstore/user.go
Harshil Sharma ab3bf6312c
Merge Onboarding feature branch into main (#2406)
* Persistent user config (#2133)

* Added user config API

* Add unit tests

* lint fix

* Fixed webapp tests

* Fixed webapp tests

* Updated props in store after updating

* Minor fixes

* Removed redundent data from audit logs

* Onboarding Tour (#2287)

* Created private board

* Roughly displayed tour

* Synced with Dhama's changes

* WIP

* Trying to add GIF

* Added 3 tour steps

* WIP

* WIP

* WIP

* checked in missed file

* Synced with feature branch

* WIp

* Adde skip tour option

* Fixed image loading for on-prem

* Made tour work on presonal server:

* Adde missed file

* Adding telemetry

* Adding telemetry

* Added tour tip telemetry

* Fixed pulsating dot styling for personal server

* reverted personal config

* Added reset tour button

* Displayed share tour tip of feature is enabled

* Lint fixes

* Fixed webapp tests

* Fixed webapp tests

* Completed webapp tests

* Completed webapp tests

* Webapp lint fixes

* Added server tests

* Testing cypress skip tour fix

* Fixed Cypress tests

* Added share board tour step

* Added share board tour step

* webapp lint fixes

* Updated logic to pick welcome board

* Updated tests:

* lint fixes

* Updating UI changes

* Fixed a bug causing card tour to re-appear

* FIxed minor issue

* FIxed bug where card tour didn't start in clickingh on card

* Fixed tests

* Make update user props use string instead of interface

* Fixed a value type

* Updating gif size

* Updating resolution breakpoint

* Updating tutorial tip

* Updating view selector

* Refactored tour components

* Misc fixes

* minor refactoring

* GH-2258: allow date range to overflow (#2268)

* allow date range to overflow

* Fixed issue with date overflowing into neighbouring column

Co-authored-by: Harshil Sharma <harshilsharma63@gmail.com>
Co-authored-by: Mattermod <mattermod@users.noreply.github.com>

* Update readme with accurate Linux standalone app build instructions (#2351)

* Bump follow-redirects from 1.14.7 to 1.14.8 in /experiments/webext (#2339)

Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.14.7 to 1.14.8.
- [Release notes](https://github.com/follow-redirects/follow-redirects/releases)
- [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.14.7...v1.14.8)

---
updated-dependencies:
- dependency-name: follow-redirects
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Switch component style fixed: selector specificity increased by adding additional class. (#2179)

* Adding sever side undelete endpoint (#2222)

* Adding sever side undelete endpoint

* Removing long lines golangci-lint errors

* Fixing linter errors

* Fixing a test problem

* Fixing tests

Co-authored-by: Mattermod <mattermod@users.noreply.github.com>

* Removing transactions from sqlite backend (#2361)

* Removing transactions from sqlite backend

* Skipping tests in sqlite because the lack of transactions

* Generating the mocks

* Fixing golangci-lint

* Fixing problem opening the tour tooltip on card open

* Fixing texts missmatch

* Adding the Product Tour entry in the user settings menu

* Fixing some tests

* Fixing tests

Co-authored-by: Asaad Mahmood <asaadmahmood@users.noreply.github.com>
Co-authored-by: Scott Bishel <scott.bishel@mattermost.com>
Co-authored-by: Mattermod <mattermod@users.noreply.github.com>
Co-authored-by: Doug Lauder <wiggin77@warpmail.net>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: kamre <eremchenko@gmail.com>
Co-authored-by: Jesús Espino <jespinog@gmail.com>

* Restored package json

* Restored package json

Co-authored-by: Asaad Mahmood <asaadmahmood@users.noreply.github.com>
Co-authored-by: Scott Bishel <scott.bishel@mattermost.com>
Co-authored-by: Mattermod <mattermod@users.noreply.github.com>
Co-authored-by: Doug Lauder <wiggin77@warpmail.net>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: kamre <eremchenko@gmail.com>
Co-authored-by: Jesús Espino <jespinog@gmail.com>
2022-02-28 12:28:16 +01:00

257 lines
5.2 KiB
Go

package sqlstore
import (
"database/sql"
"encoding/json"
"fmt"
"log"
"github.com/mattermost/focalboard/server/model"
"github.com/mattermost/focalboard/server/utils"
sq "github.com/Masterminds/squirrel"
)
type UserNotFoundError struct {
id string
}
func (unf UserNotFoundError) Error() string {
return fmt.Sprintf("user not found (%s)", unf.id)
}
func (s *SQLStore) getRegisteredUserCount(db sq.BaseRunner) (int, error) {
query := s.getQueryBuilder(db).
Select("count(*)").
From(s.tablePrefix + "users").
Where(sq.Eq{"delete_at": 0})
row := query.QueryRow()
var count int
err := row.Scan(&count)
if err != nil {
return 0, err
}
return count, nil
}
func (s *SQLStore) getUserByCondition(db sq.BaseRunner, condition sq.Eq) (*model.User, error) {
users, err := s.getUsersByCondition(db, condition)
if err != nil {
return nil, err
}
if len(users) == 0 {
return nil, nil
}
return users[0], nil
}
func (s *SQLStore) getUsersByCondition(db sq.BaseRunner, condition sq.Eq) ([]*model.User, error) {
query := s.getQueryBuilder(db).
Select(
"id",
"username",
"email",
"password",
"mfa_secret",
"auth_service",
"auth_data",
"props",
"create_at",
"update_at",
"delete_at",
).
From(s.tablePrefix + "users").
Where(sq.Eq{"delete_at": 0}).
Where(condition)
rows, err := query.Query()
if err != nil {
log.Printf("getUsersByCondition ERROR: %v", err)
return nil, err
}
defer s.CloseRows(rows)
users, err := s.usersFromRows(rows)
if err != nil {
return nil, err
}
if len(users) == 0 {
return nil, sql.ErrNoRows
}
return users, nil
}
func (s *SQLStore) getUserByID(db sq.BaseRunner, userID string) (*model.User, error) {
return s.getUserByCondition(db, sq.Eq{"id": userID})
}
func (s *SQLStore) getUserByEmail(db sq.BaseRunner, email string) (*model.User, error) {
return s.getUserByCondition(db, sq.Eq{"email": email})
}
func (s *SQLStore) getUserByUsername(db sq.BaseRunner, username string) (*model.User, error) {
return s.getUserByCondition(db, sq.Eq{"username": username})
}
func (s *SQLStore) createUser(db sq.BaseRunner, user *model.User) error {
now := utils.GetMillis()
propsBytes, err := json.Marshal(user.Props)
if err != nil {
return err
}
query := s.getQueryBuilder(db).Insert(s.tablePrefix+"users").
Columns("id", "username", "email", "password", "mfa_secret", "auth_service", "auth_data", "props", "create_at", "update_at", "delete_at").
Values(user.ID, user.Username, user.Email, user.Password, user.MfaSecret, user.AuthService, user.AuthData, propsBytes, now, now, 0)
_, err = query.Exec()
return err
}
func (s *SQLStore) updateUser(db sq.BaseRunner, user *model.User) error {
now := utils.GetMillis()
propsBytes, err := json.Marshal(user.Props)
if err != nil {
return err
}
query := s.getQueryBuilder(db).Update(s.tablePrefix+"users").
Set("username", user.Username).
Set("email", user.Email).
Set("props", propsBytes).
Set("update_at", now).
Where(sq.Eq{"id": user.ID})
result, err := query.Exec()
if err != nil {
return err
}
rowCount, err := result.RowsAffected()
if err != nil {
return err
}
if rowCount < 1 {
return UserNotFoundError{user.ID}
}
return nil
}
func (s *SQLStore) updateUserPassword(db sq.BaseRunner, username, password string) error {
now := utils.GetMillis()
query := s.getQueryBuilder(db).Update(s.tablePrefix+"users").
Set("password", password).
Set("update_at", now).
Where(sq.Eq{"username": username})
result, err := query.Exec()
if err != nil {
return err
}
rowCount, err := result.RowsAffected()
if err != nil {
return err
}
if rowCount < 1 {
return UserNotFoundError{username}
}
return nil
}
func (s *SQLStore) updateUserPasswordByID(db sq.BaseRunner, userID, password string) error {
now := utils.GetMillis()
query := s.getQueryBuilder(db).Update(s.tablePrefix+"users").
Set("password", password).
Set("update_at", now).
Where(sq.Eq{"id": userID})
result, err := query.Exec()
if err != nil {
return err
}
rowCount, err := result.RowsAffected()
if err != nil {
return err
}
if rowCount < 1 {
return UserNotFoundError{userID}
}
return nil
}
func (s *SQLStore) getUsersByWorkspace(db sq.BaseRunner, _ string) ([]*model.User, error) {
return s.getUsersByCondition(db, nil)
}
func (s *SQLStore) usersFromRows(rows *sql.Rows) ([]*model.User, error) {
users := []*model.User{}
for rows.Next() {
var user model.User
var propsBytes []byte
err := rows.Scan(
&user.ID,
&user.Username,
&user.Email,
&user.Password,
&user.MfaSecret,
&user.AuthService,
&user.AuthData,
&propsBytes,
&user.CreateAt,
&user.UpdateAt,
&user.DeleteAt,
)
if err != nil {
return nil, err
}
err = json.Unmarshal(propsBytes, &user.Props)
if err != nil {
return nil, err
}
users = append(users, &user)
}
return users, nil
}
func (s *SQLStore) patchUserProps(db sq.BaseRunner, userID string, patch model.UserPropPatch) error {
user, err := s.getUserByID(db, userID)
if err != nil {
return err
}
if user.Props == nil {
user.Props = map[string]interface{}{}
}
for _, key := range patch.DeletedFields {
delete(user.Props, key)
}
for key, value := range patch.UpdatedFields {
user.Props[key] = value
}
return s.updateUser(db, user)
}