You've already forked focalboard
							
							
				mirror of
				https://github.com/mattermost/focalboard.git
				synced 2025-10-31 00:17:42 +02:00 
			
		
		
		
	fix remaining golangci linter warnings (#686)
* fix remaining linter warnings
This commit is contained in:
		| @@ -56,7 +56,7 @@ func (p *Plugin) OnActivate() error { | ||||
| 	mmconfig := p.API.GetUnsanitizedConfig() | ||||
| 	filesS3Config := config.AmazonS3Config{} | ||||
| 	if mmconfig.FileSettings.AmazonS3AccessKeyId != nil { | ||||
| 		filesS3Config.AccessKeyId = *mmconfig.FileSettings.AmazonS3AccessKeyId | ||||
| 		filesS3Config.AccessKeyID = *mmconfig.FileSettings.AmazonS3AccessKeyId | ||||
| 	} | ||||
| 	if mmconfig.FileSettings.AmazonS3SecretAccessKey != nil { | ||||
| 		filesS3Config.SecretAccessKey = *mmconfig.FileSettings.AmazonS3SecretAccessKey | ||||
|   | ||||
| @@ -43,11 +43,10 @@ linters: | ||||
|     - depguard | ||||
|     - dogsled | ||||
|     - dupl | ||||
|     - gochecknoinits | ||||
|     - goconst | ||||
|     - gocritic | ||||
|     - godot | ||||
| #    - goerr113 | ||||
|     - goerr113 | ||||
|     - goheader | ||||
|     - golint | ||||
| #    - gomnd | ||||
|   | ||||
| @@ -2,7 +2,6 @@ package api | ||||
|  | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"io/ioutil" | ||||
| 	"net/http" | ||||
| @@ -31,6 +30,14 @@ const ( | ||||
| 	ErrorNoWorkspaceMessage = "No workspace" | ||||
| ) | ||||
|  | ||||
| type PermissionError struct { | ||||
| 	msg string | ||||
| } | ||||
|  | ||||
| func (pe PermissionError) Error() string { | ||||
| 	return pe.msg | ||||
| } | ||||
|  | ||||
| // ---------------------------------------------------------------------------------------------------- | ||||
| // REST APIs | ||||
|  | ||||
| @@ -128,7 +135,7 @@ func (a *API) hasValidReadTokenForBlock(r *http.Request, container store.Contain | ||||
|  | ||||
| func (a *API) getContainerAllowingReadTokenForBlock(r *http.Request, blockID string) (*store.Container, error) { | ||||
| 	ctx := r.Context() | ||||
| 	session, _ := ctx.Value("session").(*model.Session) | ||||
| 	session, _ := ctx.Value(sessionContextKey).(*model.Session) | ||||
|  | ||||
| 	if a.MattermostAuth { | ||||
| 		// Workspace auth | ||||
| @@ -153,7 +160,7 @@ func (a *API) getContainerAllowingReadTokenForBlock(r *http.Request, blockID str | ||||
| 			return &container, nil | ||||
| 		} | ||||
|  | ||||
| 		return nil, errors.New("access denied to workspace") | ||||
| 		return nil, PermissionError{"access denied to workspace"} | ||||
| 	} | ||||
|  | ||||
| 	// Native auth: always use root workspace | ||||
| @@ -171,7 +178,7 @@ func (a *API) getContainerAllowingReadTokenForBlock(r *http.Request, blockID str | ||||
| 		return &container, nil | ||||
| 	} | ||||
|  | ||||
| 	return nil, errors.New("access denied to workspace") | ||||
| 	return nil, PermissionError{"access denied to workspace"} | ||||
| } | ||||
|  | ||||
| func (a *API) getContainer(r *http.Request) (*store.Container, error) { | ||||
| @@ -256,7 +263,7 @@ func (a *API) handleGetBlocks(w http.ResponseWriter, r *http.Request) { | ||||
|  | ||||
| func stampModificationMetadata(r *http.Request, blocks []model.Block, auditRec *audit.Record) { | ||||
| 	ctx := r.Context() | ||||
| 	session := ctx.Value("session").(*model.Session) | ||||
| 	session := ctx.Value(sessionContextKey).(*model.Session) | ||||
| 	userID := session.UserID | ||||
| 	if userID == SingleUser { | ||||
| 		userID = "" | ||||
| @@ -352,7 +359,7 @@ func (a *API) handlePostBlocks(w http.ResponseWriter, r *http.Request) { | ||||
| 	stampModificationMetadata(r, blocks, auditRec) | ||||
|  | ||||
| 	ctx := r.Context() | ||||
| 	session := ctx.Value("session").(*model.Session) | ||||
| 	session := ctx.Value(sessionContextKey).(*model.Session) | ||||
|  | ||||
| 	err = a.app.InsertBlocks(*container, blocks, session.UserID) | ||||
| 	if err != nil { | ||||
| @@ -437,7 +444,7 @@ func (a *API) handleGetMe(w http.ResponseWriter, r *http.Request) { | ||||
| 	//       "$ref": "#/definitions/ErrorResponse" | ||||
|  | ||||
| 	ctx := r.Context() | ||||
| 	session := ctx.Value("session").(*model.Session) | ||||
| 	session := ctx.Value(sessionContextKey).(*model.Session) | ||||
| 	var user *model.User | ||||
| 	var err error | ||||
|  | ||||
| @@ -503,7 +510,7 @@ func (a *API) handleDeleteBlock(w http.ResponseWriter, r *http.Request) { | ||||
| 	//       "$ref": "#/definitions/ErrorResponse" | ||||
|  | ||||
| 	ctx := r.Context() | ||||
| 	session := ctx.Value("session").(*model.Session) | ||||
| 	session := ctx.Value(sessionContextKey).(*model.Session) | ||||
| 	userID := session.UserID | ||||
|  | ||||
| 	vars := mux.Vars(r) | ||||
| @@ -782,7 +789,7 @@ func (a *API) handleImport(w http.ResponseWriter, r *http.Request) { | ||||
| 	stampModificationMetadata(r, blocks, auditRec) | ||||
|  | ||||
| 	ctx := r.Context() | ||||
| 	session := ctx.Value("session").(*model.Session) | ||||
| 	session := ctx.Value(sessionContextKey).(*model.Session) | ||||
| 	err = a.app.InsertBlocks(*container, blocks, session.UserID) | ||||
| 	if err != nil { | ||||
| 		a.errorResponse(w, http.StatusInternalServerError, "", err) | ||||
| @@ -931,7 +938,7 @@ func (a *API) handlePostSharing(w http.ResponseWriter, r *http.Request) { | ||||
|  | ||||
| 	// Stamp ModifiedBy | ||||
| 	ctx := r.Context() | ||||
| 	session := ctx.Value("session").(*model.Session) | ||||
| 	session := ctx.Value(sessionContextKey).(*model.Session) | ||||
| 	userID := session.UserID | ||||
| 	if userID == SingleUser { | ||||
| 		userID = "" | ||||
| @@ -986,7 +993,7 @@ func (a *API) handleGetWorkspace(w http.ResponseWriter, r *http.Request) { | ||||
| 		workspaceID := vars["workspaceID"] | ||||
|  | ||||
| 		ctx := r.Context() | ||||
| 		session := ctx.Value("session").(*model.Session) | ||||
| 		session := ctx.Value(sessionContextKey).(*model.Session) | ||||
| 		if !a.app.DoesUserHaveWorkspaceAccess(session.UserID, workspaceID) { | ||||
| 			a.errorResponse(w, http.StatusUnauthorized, "", nil) | ||||
| 			return | ||||
| @@ -1264,9 +1271,9 @@ func (a *API) getWorkspaceUsers(w http.ResponseWriter, r *http.Request) { | ||||
| 	workspaceID := vars["workspaceID"] | ||||
|  | ||||
| 	ctx := r.Context() | ||||
| 	session := ctx.Value("session").(*model.Session) | ||||
| 	session := ctx.Value(sessionContextKey).(*model.Session) | ||||
| 	if !a.app.DoesUserHaveWorkspaceAccess(session.UserID, workspaceID) { | ||||
| 		a.errorResponse(w, http.StatusForbidden, "Access denied to workspace", errors.New("access denied to workspace")) | ||||
| 		a.errorResponse(w, http.StatusForbidden, "Access denied to workspace", PermissionError{"access denied to workspace"}) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
|   | ||||
| @@ -12,7 +12,7 @@ func (a *API) makeAuditRecord(r *http.Request, event string, initialStatus strin | ||||
| 	ctx := r.Context() | ||||
| 	var sessionID string | ||||
| 	var userID string | ||||
| 	if session, ok := ctx.Value("session").(*model.Session); ok { | ||||
| 	if session, ok := ctx.Value(sessionContextKey).(*model.Session); ok { | ||||
| 		sessionID = session.ID | ||||
| 		userID = session.UserID | ||||
| 	} | ||||
|   | ||||
| @@ -3,7 +3,6 @@ package api | ||||
| import ( | ||||
| 	"context" | ||||
| 	"encoding/json" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"io/ioutil" | ||||
| 	"net" | ||||
| @@ -12,7 +11,6 @@ import ( | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/gorilla/mux" | ||||
| 	serverContext "github.com/mattermost/focalboard/server/context" | ||||
| 	"github.com/mattermost/focalboard/server/model" | ||||
| 	"github.com/mattermost/focalboard/server/services/audit" | ||||
| 	"github.com/mattermost/focalboard/server/services/auth" | ||||
| @@ -23,6 +21,14 @@ const ( | ||||
| 	MinimumPasswordLength = 8 | ||||
| ) | ||||
|  | ||||
| type ParamError struct { | ||||
| 	msg string | ||||
| } | ||||
|  | ||||
| func (pe ParamError) Error() string { | ||||
| 	return pe.msg | ||||
| } | ||||
|  | ||||
| // LoginRequest is a login request | ||||
| // swagger:model | ||||
| type LoginRequest struct { | ||||
| @@ -78,16 +84,16 @@ type RegisterRequest struct { | ||||
|  | ||||
| func (rd *RegisterRequest) IsValid() error { | ||||
| 	if strings.TrimSpace(rd.Username) == "" { | ||||
| 		return errors.New("username is required") | ||||
| 		return ParamError{"username is required"} | ||||
| 	} | ||||
| 	if strings.TrimSpace(rd.Email) == "" { | ||||
| 		return errors.New("email is required") | ||||
| 		return ParamError{"email is required"} | ||||
| 	} | ||||
| 	if !auth.IsEmailValid(rd.Email) { | ||||
| 		return errors.New("invalid email format") | ||||
| 		return ParamError{"invalid email format"} | ||||
| 	} | ||||
| 	if rd.Password == "" { | ||||
| 		return errors.New("password is required") | ||||
| 		return ParamError{"password is required"} | ||||
| 	} | ||||
| 	if err := isValidPassword(rd.Password); err != nil { | ||||
| 		return err | ||||
| @@ -110,10 +116,10 @@ type ChangePasswordRequest struct { | ||||
| // IsValid validates a password change request. | ||||
| func (rd *ChangePasswordRequest) IsValid() error { | ||||
| 	if rd.OldPassword == "" { | ||||
| 		return errors.New("old password is required") | ||||
| 		return ParamError{"old password is required"} | ||||
| 	} | ||||
| 	if rd.NewPassword == "" { | ||||
| 		return errors.New("new password is required") | ||||
| 		return ParamError{"new password is required"} | ||||
| 	} | ||||
| 	if err := isValidPassword(rd.NewPassword); err != nil { | ||||
| 		return err | ||||
| @@ -124,7 +130,7 @@ func (rd *ChangePasswordRequest) IsValid() error { | ||||
|  | ||||
| func isValidPassword(password string) error { | ||||
| 	if len(password) < MinimumPasswordLength { | ||||
| 		return fmt.Errorf("password must be at least %d characters", MinimumPasswordLength) | ||||
| 		return ParamError{fmt.Sprintf("password must be at least %d characters", MinimumPasswordLength)} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| @@ -387,7 +393,7 @@ func (a *API) attachSession(handler func(w http.ResponseWriter, r *http.Request) | ||||
| 				CreateAt:    now, | ||||
| 				UpdateAt:    now, | ||||
| 			} | ||||
| 			ctx := context.WithValue(r.Context(), "session", session) | ||||
| 			ctx := context.WithValue(r.Context(), sessionContextKey, session) | ||||
| 			handler(w, r.WithContext(ctx)) | ||||
| 			return | ||||
| 		} | ||||
| @@ -404,7 +410,7 @@ func (a *API) attachSession(handler func(w http.ResponseWriter, r *http.Request) | ||||
| 				CreateAt:    now, | ||||
| 				UpdateAt:    now, | ||||
| 			} | ||||
| 			ctx := context.WithValue(r.Context(), "session", session) | ||||
| 			ctx := context.WithValue(r.Context(), sessionContextKey, session) | ||||
| 			handler(w, r.WithContext(ctx)) | ||||
| 			return | ||||
| 		} | ||||
| @@ -431,7 +437,7 @@ func (a *API) attachSession(handler func(w http.ResponseWriter, r *http.Request) | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		ctx := context.WithValue(r.Context(), "session", session) | ||||
| 		ctx := context.WithValue(r.Context(), sessionContextKey, session) | ||||
| 		handler(w, r.WithContext(ctx)) | ||||
| 	} | ||||
| } | ||||
| @@ -439,7 +445,7 @@ func (a *API) attachSession(handler func(w http.ResponseWriter, r *http.Request) | ||||
| func (a *API) adminRequired(handler func(w http.ResponseWriter, r *http.Request)) func(w http.ResponseWriter, r *http.Request) { | ||||
| 	return func(w http.ResponseWriter, r *http.Request) { | ||||
| 		// Currently, admin APIs require local unix connections | ||||
| 		conn := serverContext.GetContextConn(r) | ||||
| 		conn := GetContextConn(r) | ||||
| 		if _, isUnix := conn.(*net.UnixConn); !isUnix { | ||||
| 			a.errorResponse(w, http.StatusUnauthorized, "", nil) | ||||
| 			return | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| package context | ||||
| package api | ||||
| 
 | ||||
| import ( | ||||
| 	"context" | ||||
| @@ -6,20 +6,21 @@ import ( | ||||
| 	"net/http" | ||||
| ) | ||||
| 
 | ||||
| type contextKey struct { | ||||
| 	key string | ||||
| } | ||||
| type contextKey int | ||||
| 
 | ||||
| var connContextKey = &contextKey{"http-conn"} | ||||
| const ( | ||||
| 	httpConnContextKey contextKey = iota | ||||
| 	sessionContextKey | ||||
| ) | ||||
| 
 | ||||
| // SetContextConn stores the connection in the request context. | ||||
| func SetContextConn(ctx context.Context, c net.Conn) context.Context { | ||||
| 	return context.WithValue(ctx, connContextKey, c) | ||||
| 	return context.WithValue(ctx, httpConnContextKey, c) | ||||
| } | ||||
| 
 | ||||
| // GetContextConn gets the stored connection from the request context. | ||||
| func GetContextConn(r *http.Request) net.Conn { | ||||
| 	value := r.Context().Value(connContextKey) | ||||
| 	value := r.Context().Value(httpConnContextKey) | ||||
| 	if value == nil { | ||||
| 		return nil | ||||
| 	} | ||||
| @@ -1,15 +1,23 @@ | ||||
| package app | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"github.com/mattermost/focalboard/server/model" | ||||
| 	"testing" | ||||
|  | ||||
| 	"github.com/mattermost/focalboard/server/model" | ||||
|  | ||||
| 	"github.com/golang/mock/gomock" | ||||
| 	st "github.com/mattermost/focalboard/server/services/store" | ||||
| 	"github.com/stretchr/testify/require" | ||||
| ) | ||||
|  | ||||
| type blockError struct { | ||||
| 	msg string | ||||
| } | ||||
|  | ||||
| func (be blockError) Error() string { | ||||
| 	return be.msg | ||||
| } | ||||
|  | ||||
| func TestGetParentID(t *testing.T) { | ||||
| 	th := SetupTestHelper(t) | ||||
|  | ||||
| @@ -24,10 +32,10 @@ func TestGetParentID(t *testing.T) { | ||||
| 	}) | ||||
|  | ||||
| 	t.Run("fail query", func(t *testing.T) { | ||||
| 		th.Store.EXPECT().GetParentID(gomock.Eq(container), gomock.Eq("test-id")).Return("", errors.New("block-not-found")) | ||||
| 		th.Store.EXPECT().GetParentID(gomock.Eq(container), gomock.Eq("test-id")).Return("", blockError{"block-not-found"}) | ||||
| 		_, err := th.App.GetParentID(container, "test-id") | ||||
| 		require.Error(t, err) | ||||
| 		require.Equal(t, "block-not-found", err.Error()) | ||||
| 		require.ErrorIs(t, err, blockError{"block-not-found"}) | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| @@ -47,8 +55,8 @@ func TestInsertBlock(t *testing.T) { | ||||
|  | ||||
| 	t.Run("error scenerio", func(t *testing.T) { | ||||
| 		block := model.Block{} | ||||
| 		th.Store.EXPECT().InsertBlock(gomock.Eq(container), gomock.Eq(&block), gomock.Eq("user-id-1")).Return(errors.New("dummy error")) | ||||
| 		th.Store.EXPECT().InsertBlock(gomock.Eq(container), gomock.Eq(&block), gomock.Eq("user-id-1")).Return(blockError{"error"}) | ||||
| 		err := th.App.InsertBlock(container, block, "user-id-1") | ||||
| 		require.Error(t, err, "dummy error") | ||||
| 		require.Error(t, err, "error") | ||||
| 	}) | ||||
| } | ||||
|   | ||||
| @@ -1,7 +1,6 @@ | ||||
| package app | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"path/filepath" | ||||
| @@ -25,7 +24,7 @@ func (a *App) SaveFile(reader io.Reader, workspaceID, rootID, filename string) ( | ||||
|  | ||||
| 	_, appErr := a.filesBackend.WriteFile(reader, filePath) | ||||
| 	if appErr != nil { | ||||
| 		return "", errors.New("unable to store the file in the files storage") | ||||
| 		return "", fmt.Errorf("unable to store the file in the files storage: %w", appErr) | ||||
| 	} | ||||
|  | ||||
| 	return createdFilename, nil | ||||
|   | ||||
| @@ -16,6 +16,14 @@ const ( | ||||
| 	APIURLSuffix = "/api/v1" | ||||
| ) | ||||
|  | ||||
| type RequestReaderError struct { | ||||
| 	buf []byte | ||||
| } | ||||
|  | ||||
| func (rre RequestReaderError) Error() string { | ||||
| 	return "payload: " + string(rre.buf) | ||||
| } | ||||
|  | ||||
| type Response struct { | ||||
| 	StatusCode int | ||||
| 	Error      error | ||||
| @@ -131,7 +139,7 @@ func (c *Client) doAPIRequestReader(method, url string, data io.Reader, _ /* eta | ||||
| 		if err != nil { | ||||
| 			return rp, fmt.Errorf("error when parsing response with code %d: %w", rp.StatusCode, err) | ||||
| 		} | ||||
| 		return rp, fmt.Errorf(string(b)) | ||||
| 		return rp, RequestReaderError{b} | ||||
| 	} | ||||
|  | ||||
| 	return rp, nil | ||||
|   | ||||
| @@ -72,7 +72,7 @@ func SetupTestHelper() *TestHelper { | ||||
| 	cfg := getTestConfig() | ||||
| 	db, err := server.NewStore(cfg, logger) | ||||
| 	if err != nil { | ||||
| 		logger.Fatal("server.NewStore ERROR", mlog.Err(err)) | ||||
| 		panic(err) | ||||
| 	} | ||||
| 	srv, err := server.New(cfg, sessionToken, db, logger) | ||||
| 	if err != nil { | ||||
| @@ -94,7 +94,7 @@ func (th *TestHelper) InitBasic() *TestHelper { | ||||
| 	for { | ||||
| 		URL := th.Server.Config().ServerRoot | ||||
| 		th.Server.Logger().Info("Polling server", mlog.String("url", URL)) | ||||
| 		resp, err := http.Get(URL) | ||||
| 		resp, err := http.Get(URL) //nolint:gosec | ||||
| 		if err != nil { | ||||
| 			th.Server.Logger().Error("Polling failed", mlog.Err(err)) | ||||
| 			time.Sleep(100 * time.Millisecond) | ||||
|   | ||||
| @@ -18,7 +18,6 @@ import ( | ||||
| 	"github.com/mattermost/focalboard/server/api" | ||||
| 	"github.com/mattermost/focalboard/server/app" | ||||
| 	"github.com/mattermost/focalboard/server/auth" | ||||
| 	"github.com/mattermost/focalboard/server/context" | ||||
| 	appModel "github.com/mattermost/focalboard/server/model" | ||||
| 	"github.com/mattermost/focalboard/server/services/audit" | ||||
| 	"github.com/mattermost/focalboard/server/services/config" | ||||
| @@ -42,7 +41,6 @@ const ( | ||||
| 	cleanupSessionTaskFrequency = 10 * time.Minute | ||||
| 	updateMetricsTaskFrequency  = 15 * time.Minute | ||||
|  | ||||
| 	//nolint:gomnd | ||||
| 	minSessionExpiryTime = int64(60 * 60 * 24 * 31) // 31 days | ||||
|  | ||||
| 	MattermostAuthMod = "mattermost" | ||||
| @@ -76,7 +74,7 @@ func New(cfg *config.Configuration, singleUserToken string, db store.Store, logg | ||||
| 	filesBackendSettings := filestore.FileBackendSettings{} | ||||
| 	filesBackendSettings.DriverName = cfg.FilesDriver | ||||
| 	filesBackendSettings.Directory = cfg.FilesPath | ||||
| 	filesBackendSettings.AmazonS3AccessKeyId = cfg.FilesS3Config.AccessKeyId | ||||
| 	filesBackendSettings.AmazonS3AccessKeyId = cfg.FilesS3Config.AccessKeyID | ||||
| 	filesBackendSettings.AmazonS3SecretAccessKey = cfg.FilesS3Config.SecretAccessKey | ||||
| 	filesBackendSettings.AmazonS3Bucket = cfg.FilesS3Config.Bucket | ||||
| 	filesBackendSettings.AmazonS3PathPrefix = cfg.FilesS3Config.PathPrefix | ||||
| @@ -320,7 +318,7 @@ func (s *Server) Logger() *mlog.Logger { | ||||
| func (s *Server) startLocalModeServer() error { | ||||
| 	s.localModeServer = &http.Server{ | ||||
| 		Handler:     s.localRouter, | ||||
| 		ConnContext: context.SetContextConn, | ||||
| 		ConnContext: api.SetContextConn, | ||||
| 	} | ||||
|  | ||||
| 	// TODO: Close and delete socket file on shutdown | ||||
|   | ||||
| @@ -135,7 +135,10 @@ func TestIsPasswordValidWithSettings(t *testing.T) { | ||||
| 				assert.NoError(t, err) | ||||
| 			} else { | ||||
| 				require.Error(t, err) | ||||
| 				assert.Equal(t, tc.ExpectedFailingCriterias, err.(*InvalidPasswordError).FailingCriterias) | ||||
| 				var errFC *InvalidPasswordError | ||||
| 				if assert.ErrorAs(t, err, &errFC) { | ||||
| 					assert.Equal(t, tc.ExpectedFailingCriterias, errFC.FailingCriterias) | ||||
| 				} | ||||
| 			} | ||||
| 		}) | ||||
| 	} | ||||
|   | ||||
| @@ -12,7 +12,7 @@ const ( | ||||
| ) | ||||
|  | ||||
| type AmazonS3Config struct { | ||||
| 	AccessKeyId     string | ||||
| 	AccessKeyID     string | ||||
| 	SecretAccessKey string | ||||
| 	Bucket          string | ||||
| 	PathPrefix      string | ||||
|   | ||||
| @@ -3,8 +3,6 @@ package mattermostauthlayer | ||||
| import ( | ||||
| 	"database/sql" | ||||
| 	"encoding/json" | ||||
| 	"errors" | ||||
| 	"log" | ||||
| 	"strings" | ||||
| 	"time" | ||||
|  | ||||
| @@ -20,6 +18,14 @@ const ( | ||||
| 	postgresDBType = "postgres" | ||||
| ) | ||||
|  | ||||
| type NotSupportedError struct { | ||||
| 	msg string | ||||
| } | ||||
|  | ||||
| func (pe NotSupportedError) Error() string { | ||||
| 	return pe.msg | ||||
| } | ||||
|  | ||||
| // Store represents the abstraction of the data storage. | ||||
| type MattermostAuthLayer struct { | ||||
| 	store.Store | ||||
| @@ -99,19 +105,19 @@ func (s *MattermostAuthLayer) GetUserByUsername(username string) (*model.User, e | ||||
| } | ||||
|  | ||||
| func (s *MattermostAuthLayer) CreateUser(user *model.User) error { | ||||
| 	return errors.New("no user creation allowed from focalboard, create it using mattermost") | ||||
| 	return NotSupportedError{"no user creation allowed from focalboard, create it using mattermost"} | ||||
| } | ||||
|  | ||||
| func (s *MattermostAuthLayer) UpdateUser(user *model.User) error { | ||||
| 	return errors.New("no update allowed from focalboard, update it using mattermost") | ||||
| 	return NotSupportedError{"no update allowed from focalboard, update it using mattermost"} | ||||
| } | ||||
|  | ||||
| func (s *MattermostAuthLayer) UpdateUserPassword(username, password string) error { | ||||
| 	return errors.New("no update allowed from focalboard, update it using mattermost") | ||||
| 	return NotSupportedError{"no update allowed from focalboard, update it using mattermost"} | ||||
| } | ||||
|  | ||||
| func (s *MattermostAuthLayer) UpdateUserPasswordByID(userID, password string) error { | ||||
| 	return errors.New("no update allowed from focalboard, update it using mattermost") | ||||
| 	return NotSupportedError{"no update allowed from focalboard, update it using mattermost"} | ||||
| } | ||||
|  | ||||
| // GetActiveUserCount returns the number of users with active sessions within N seconds ago. | ||||
| @@ -133,27 +139,27 @@ func (s *MattermostAuthLayer) GetActiveUserCount(updatedSecondsAgo int64) (int, | ||||
| } | ||||
|  | ||||
| func (s *MattermostAuthLayer) GetSession(token string, expireTime int64) (*model.Session, error) { | ||||
| 	return nil, errors.New("sessions not used when using mattermost") | ||||
| 	return nil, NotSupportedError{"sessions not used when using mattermost"} | ||||
| } | ||||
|  | ||||
| func (s *MattermostAuthLayer) CreateSession(session *model.Session) error { | ||||
| 	return errors.New("no update allowed from focalboard, update it using mattermost") | ||||
| 	return NotSupportedError{"no update allowed from focalboard, update it using mattermost"} | ||||
| } | ||||
|  | ||||
| func (s *MattermostAuthLayer) RefreshSession(session *model.Session) error { | ||||
| 	return errors.New("no update allowed from focalboard, update it using mattermost") | ||||
| 	return NotSupportedError{"no update allowed from focalboard, update it using mattermost"} | ||||
| } | ||||
|  | ||||
| func (s *MattermostAuthLayer) UpdateSession(session *model.Session) error { | ||||
| 	return errors.New("no update allowed from focalboard, update it using mattermost") | ||||
| 	return NotSupportedError{"no update allowed from focalboard, update it using mattermost"} | ||||
| } | ||||
|  | ||||
| func (s *MattermostAuthLayer) DeleteSession(sessionID string) error { | ||||
| 	return errors.New("no update allowed from focalboard, update it using mattermost") | ||||
| 	return NotSupportedError{"no update allowed from focalboard, update it using mattermost"} | ||||
| } | ||||
|  | ||||
| func (s *MattermostAuthLayer) CleanUpSessions(expireTime int64) error { | ||||
| 	return errors.New("no update allowed from focalboard, update it using mattermost") | ||||
| 	return NotSupportedError{"no update allowed from focalboard, update it using mattermost"} | ||||
| } | ||||
|  | ||||
| func (s *MattermostAuthLayer) GetWorkspace(id string) (*model.Workspace, error) { | ||||
| @@ -205,7 +211,7 @@ func (s *MattermostAuthLayer) GetWorkspace(id string) (*model.Workspace, error) | ||||
| 		} | ||||
| 		var name string | ||||
| 		if err := rows.Scan(&name); err != nil { | ||||
| 			log.Fatal(err) | ||||
| 			return nil, err | ||||
| 		} | ||||
| 		sb.WriteString(name) | ||||
| 	} | ||||
|   | ||||
| @@ -4,18 +4,24 @@ import ( | ||||
| 	"context" | ||||
| 	"database/sql" | ||||
| 	"encoding/json" | ||||
| 	"errors" | ||||
| 	"github.com/mattermost/focalboard/server/utils" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/mattermost/focalboard/server/utils" | ||||
|  | ||||
| 	sq "github.com/Masterminds/squirrel" | ||||
| 	_ "github.com/lib/pq" | ||||
| 	_ "github.com/lib/pq" // postgres driver | ||||
| 	"github.com/mattermost/focalboard/server/model" | ||||
| 	"github.com/mattermost/focalboard/server/services/mlog" | ||||
| 	"github.com/mattermost/focalboard/server/services/store" | ||||
| 	_ "github.com/mattn/go-sqlite3" | ||||
| 	_ "github.com/mattn/go-sqlite3" // sqlite driver | ||||
| ) | ||||
|  | ||||
| type RootIDNilError struct{} | ||||
|  | ||||
| func (re RootIDNilError) Error() string { | ||||
| 	return "rootId is nil" | ||||
| } | ||||
|  | ||||
| func (s *SQLStore) GetBlocksWithParentAndType(c store.Container, parentID string, blockType string) ([]model.Block, error) { | ||||
| 	query := s.getQueryBuilder(). | ||||
| 		Select( | ||||
| @@ -327,7 +333,7 @@ func (s *SQLStore) GetParentID(c store.Container, blockID string) (string, error | ||||
|  | ||||
| func (s *SQLStore) InsertBlock(c store.Container, block *model.Block, userID string) error { | ||||
| 	if block.RootID == "" { | ||||
| 		return errors.New("rootId is nil") | ||||
| 		return RootIDNilError{} | ||||
| 	} | ||||
|  | ||||
| 	fieldsJSON, err := json.Marshal(block.Fields) | ||||
|   | ||||
| @@ -66,7 +66,7 @@ func (s *SQLStore) isInitializationNeeded() (bool, error) { | ||||
| 	var count int | ||||
| 	err := row.Scan(&count) | ||||
| 	if err != nil { | ||||
| 		s.logger.Fatal("isInitializationNeeded", mlog.Err(err)) | ||||
| 		s.logger.Error("isInitializationNeeded", mlog.Err(err)) | ||||
| 		return false, err | ||||
| 	} | ||||
|  | ||||
|   | ||||
| @@ -17,9 +17,9 @@ import ( | ||||
| 	"github.com/golang-migrate/migrate/v4/database/postgres" | ||||
| 	"github.com/golang-migrate/migrate/v4/database/sqlite3" | ||||
| 	"github.com/golang-migrate/migrate/v4/source" | ||||
| 	_ "github.com/golang-migrate/migrate/v4/source/file" | ||||
| 	_ "github.com/golang-migrate/migrate/v4/source/file" // fileystem driver | ||||
| 	bindata "github.com/golang-migrate/migrate/v4/source/go_bindata" | ||||
| 	_ "github.com/lib/pq" | ||||
| 	_ "github.com/lib/pq" // postgres driver | ||||
| 	"github.com/mattermost/focalboard/server/services/store/sqlstore/migrations" | ||||
| ) | ||||
|  | ||||
|   | ||||
| @@ -72,7 +72,7 @@ func (s *SQLStore) getQueryBuilder() sq.StatementBuilderType { | ||||
| 	return builder.RunWith(s.db) | ||||
| } | ||||
|  | ||||
| func (s *SQLStore) escapeField(fieldName string) string { | ||||
| func (s *SQLStore) escapeField(fieldName string) string { //nolint:unparam | ||||
| 	if s.dbType == mysqlDBType { | ||||
| 		return "`" + fieldName + "`" | ||||
| 	} | ||||
|   | ||||
| @@ -3,7 +3,7 @@ package sqlstore | ||||
| import ( | ||||
| 	"database/sql" | ||||
| 	"encoding/json" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"log" | ||||
| 	"time" | ||||
|  | ||||
| @@ -12,6 +12,14 @@ import ( | ||||
| 	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() (int, error) { | ||||
| 	query := s.getQueryBuilder(). | ||||
| 		Select("count(*)"). | ||||
| @@ -132,7 +140,7 @@ func (s *SQLStore) UpdateUser(user *model.User) error { | ||||
| 	} | ||||
|  | ||||
| 	if rowCount < 1 { | ||||
| 		return errors.New("user not found") | ||||
| 		return UserNotFoundError{user.ID} | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| @@ -157,7 +165,7 @@ func (s *SQLStore) UpdateUserPassword(username, password string) error { | ||||
| 	} | ||||
|  | ||||
| 	if rowCount < 1 { | ||||
| 		return errors.New("user not found") | ||||
| 		return UserNotFoundError{username} | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| @@ -182,7 +190,7 @@ func (s *SQLStore) UpdateUserPasswordByID(userID, password string) error { | ||||
| 	} | ||||
|  | ||||
| 	if rowCount < 1 { | ||||
| 		return errors.New("user not found") | ||||
| 		return UserNotFoundError{userID} | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
|   | ||||
| @@ -1,15 +1,20 @@ | ||||
| package storetests | ||||
|  | ||||
| import ( | ||||
| 	"github.com/stretchr/testify/assert" | ||||
| 	"testing" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/stretchr/testify/assert" | ||||
|  | ||||
| 	"github.com/mattermost/focalboard/server/model" | ||||
| 	"github.com/mattermost/focalboard/server/services/store" | ||||
| 	"github.com/stretchr/testify/require" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	testUserID = "user-id" | ||||
| ) | ||||
|  | ||||
| func StoreTestBlocksStore(t *testing.T, setup func(t *testing.T) (store.Store, func())) { | ||||
| 	container := store.Container{ | ||||
| 		WorkspaceID: "0", | ||||
| @@ -38,42 +43,17 @@ func StoreTestBlocksStore(t *testing.T, setup func(t *testing.T) (store.Store, f | ||||
| 	t.Run("GetParentID", func(t *testing.T) { | ||||
| 		store, tearDown := setup(t) | ||||
| 		defer tearDown() | ||||
| 		testGetParentID(t, store, container) | ||||
| 		testGetParents(t, store, container) | ||||
| 	}) | ||||
| 	t.Run("GetRootID", func(t *testing.T) { | ||||
| 	t.Run("GetBlocks", func(t *testing.T) { | ||||
| 		store, tearDown := setup(t) | ||||
| 		defer tearDown() | ||||
| 		testGetRootID(t, store, container) | ||||
| 	}) | ||||
| 	t.Run("GetBlocksWithParentAndType", func(t *testing.T) { | ||||
| 		store, tearDown := setup(t) | ||||
| 		defer tearDown() | ||||
| 		testGetBlocksWithParentAndType(t, store, container) | ||||
| 	}) | ||||
| 	t.Run("GetBlocksWithParent", func(t *testing.T) { | ||||
| 		store, tearDown := setup(t) | ||||
| 		defer tearDown() | ||||
| 		testGetBlocksWithParent(t, store, container) | ||||
| 	}) | ||||
| 	t.Run("GetBlocksWithType", func(t *testing.T) { | ||||
| 		store, tearDown := setup(t) | ||||
| 		defer tearDown() | ||||
| 		testGetBlocksWithType(t, store, container) | ||||
| 	}) | ||||
| 	t.Run("GetBlocksWithRootID", func(t *testing.T) { | ||||
| 		store, tearDown := setup(t) | ||||
| 		defer tearDown() | ||||
| 		testGetBlocksWithRootID(t, store, container) | ||||
| 	}) | ||||
| 	t.Run("GetBlock", func(t *testing.T) { | ||||
| 		store, tearDown := setup(t) | ||||
| 		defer tearDown() | ||||
| 		testGetBlock(t, store, container) | ||||
| 		testGetBlocks(t, store, container) | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| func testInsertBlock(t *testing.T, store store.Store, container store.Container) { | ||||
| 	userID := "user-id" | ||||
| 	userID := testUserID | ||||
|  | ||||
| 	blocks, err := store.GetAllBlocks(container) | ||||
| 	require.NoError(t, err) | ||||
| @@ -198,53 +178,53 @@ func testInsertBlock(t *testing.T, store store.Store, container store.Container) | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| func testGetSubTree2(t *testing.T, store store.Store, container store.Container) { | ||||
| 	userID := "user-id" | ||||
|  | ||||
| 	blocks, err := store.GetAllBlocks(container) | ||||
| 	require.NoError(t, err) | ||||
| 	initialCount := len(blocks) | ||||
|  | ||||
| 	blocksToInsert := []model.Block{ | ||||
| var ( | ||||
| 	subtreeSampleBlocks = []model.Block{ | ||||
| 		{ | ||||
| 			ID:         "parent", | ||||
| 			RootID:     "parent", | ||||
| 			ModifiedBy: userID, | ||||
| 			ModifiedBy: testUserID, | ||||
| 		}, | ||||
| 		{ | ||||
| 			ID:         "child1", | ||||
| 			RootID:     "parent", | ||||
| 			ParentID:   "parent", | ||||
| 			ModifiedBy: userID, | ||||
| 			ModifiedBy: testUserID, | ||||
| 		}, | ||||
| 		{ | ||||
| 			ID:         "child2", | ||||
| 			RootID:     "parent", | ||||
| 			ParentID:   "parent", | ||||
| 			ModifiedBy: userID, | ||||
| 			ModifiedBy: testUserID, | ||||
| 		}, | ||||
| 		{ | ||||
| 			ID:         "grandchild1", | ||||
| 			RootID:     "parent", | ||||
| 			ParentID:   "child1", | ||||
| 			ModifiedBy: userID, | ||||
| 			ModifiedBy: testUserID, | ||||
| 		}, | ||||
| 		{ | ||||
| 			ID:         "grandchild2", | ||||
| 			RootID:     "parent", | ||||
| 			ParentID:   "child2", | ||||
| 			ModifiedBy: userID, | ||||
| 			ModifiedBy: testUserID, | ||||
| 		}, | ||||
| 		{ | ||||
| 			ID:         "greatgrandchild1", | ||||
| 			RootID:     "parent", | ||||
| 			ParentID:   "grandchild1", | ||||
| 			ModifiedBy: userID, | ||||
| 			ModifiedBy: testUserID, | ||||
| 		}, | ||||
| 	} | ||||
| ) | ||||
|  | ||||
| 	InsertBlocks(t, store, container, blocksToInsert, "user-id-1") | ||||
| 	defer DeleteBlocks(t, store, container, blocksToInsert, "test") | ||||
| func testGetSubTree2(t *testing.T, store store.Store, container store.Container) { | ||||
| 	blocks, err := store.GetAllBlocks(container) | ||||
| 	require.NoError(t, err) | ||||
| 	initialCount := len(blocks) | ||||
|  | ||||
| 	InsertBlocks(t, store, container, subtreeSampleBlocks, "user-id-1") | ||||
| 	defer DeleteBlocks(t, store, container, subtreeSampleBlocks, "test") | ||||
|  | ||||
| 	blocks, err = store.GetAllBlocks(container) | ||||
| 	require.NoError(t, err) | ||||
| @@ -275,52 +255,12 @@ func testGetSubTree2(t *testing.T, store store.Store, container store.Container) | ||||
| } | ||||
|  | ||||
| func testGetSubTree3(t *testing.T, store store.Store, container store.Container) { | ||||
| 	userID := "user-id" | ||||
|  | ||||
| 	blocks, err := store.GetAllBlocks(container) | ||||
| 	require.NoError(t, err) | ||||
| 	initialCount := len(blocks) | ||||
|  | ||||
| 	blocksToInsert := []model.Block{ | ||||
| 		{ | ||||
| 			ID:         "parent", | ||||
| 			RootID:     "parent", | ||||
| 			ModifiedBy: userID, | ||||
| 		}, | ||||
| 		{ | ||||
| 			ID:         "child1", | ||||
| 			RootID:     "parent", | ||||
| 			ParentID:   "parent", | ||||
| 			ModifiedBy: userID, | ||||
| 		}, | ||||
| 		{ | ||||
| 			ID:         "child2", | ||||
| 			RootID:     "parent", | ||||
| 			ParentID:   "parent", | ||||
| 			ModifiedBy: userID, | ||||
| 		}, | ||||
| 		{ | ||||
| 			ID:         "grandchild1", | ||||
| 			RootID:     "parent", | ||||
| 			ParentID:   "child1", | ||||
| 			ModifiedBy: userID, | ||||
| 		}, | ||||
| 		{ | ||||
| 			ID:         "grandchild2", | ||||
| 			RootID:     "parent", | ||||
| 			ParentID:   "child2", | ||||
| 			ModifiedBy: userID, | ||||
| 		}, | ||||
| 		{ | ||||
| 			ID:         "greatgrandchild1", | ||||
| 			RootID:     "parent", | ||||
| 			ParentID:   "grandchild1", | ||||
| 			ModifiedBy: userID, | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	InsertBlocks(t, store, container, blocksToInsert, "user-id-1") | ||||
| 	defer DeleteBlocks(t, store, container, blocksToInsert, "test") | ||||
| 	InsertBlocks(t, store, container, subtreeSampleBlocks, "user-id-1") | ||||
| 	defer DeleteBlocks(t, store, container, subtreeSampleBlocks, "test") | ||||
|  | ||||
| 	blocks, err = store.GetAllBlocks(container) | ||||
| 	require.NoError(t, err) | ||||
| @@ -353,148 +293,55 @@ func testGetSubTree3(t *testing.T, store store.Store, container store.Container) | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| func testGetRootID(t *testing.T, store store.Store, container store.Container) { | ||||
| 	userID := "user-id" | ||||
|  | ||||
| func testGetParents(t *testing.T, store store.Store, container store.Container) { | ||||
| 	blocks, err := store.GetAllBlocks(container) | ||||
| 	require.NoError(t, err) | ||||
| 	initialCount := len(blocks) | ||||
|  | ||||
| 	blocksToInsert := []model.Block{ | ||||
| 		{ | ||||
| 			ID:         "parent", | ||||
| 			RootID:     "parent", | ||||
| 			ModifiedBy: userID, | ||||
| 		}, | ||||
| 		{ | ||||
| 			ID:         "child1", | ||||
| 			RootID:     "parent", | ||||
| 			ParentID:   "parent", | ||||
| 			ModifiedBy: userID, | ||||
| 		}, | ||||
| 		{ | ||||
| 			ID:         "child2", | ||||
| 			RootID:     "parent", | ||||
| 			ParentID:   "parent", | ||||
| 			ModifiedBy: userID, | ||||
| 		}, | ||||
| 		{ | ||||
| 			ID:         "grandchild1", | ||||
| 			RootID:     "parent", | ||||
| 			ParentID:   "child1", | ||||
| 			ModifiedBy: userID, | ||||
| 		}, | ||||
| 		{ | ||||
| 			ID:         "grandchild2", | ||||
| 			RootID:     "parent", | ||||
| 			ParentID:   "child2", | ||||
| 			ModifiedBy: userID, | ||||
| 		}, | ||||
| 		{ | ||||
| 			ID:         "greatgrandchild1", | ||||
| 			RootID:     "parent", | ||||
| 			ParentID:   "grandchild1", | ||||
| 			ModifiedBy: userID, | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	InsertBlocks(t, store, container, blocksToInsert, "user-id-1") | ||||
| 	defer DeleteBlocks(t, store, container, blocksToInsert, "test") | ||||
| 	InsertBlocks(t, store, container, subtreeSampleBlocks, "user-id-1") | ||||
| 	defer DeleteBlocks(t, store, container, subtreeSampleBlocks, "test") | ||||
|  | ||||
| 	blocks, err = store.GetAllBlocks(container) | ||||
| 	require.NoError(t, err) | ||||
| 	require.Len(t, blocks, initialCount+6) | ||||
|  | ||||
| 	t.Run("from root id", func(t *testing.T) { | ||||
| 	t.Run("root from root id", func(t *testing.T) { | ||||
| 		rootID, err := store.GetRootID(container, "parent") | ||||
| 		require.NoError(t, err) | ||||
| 		require.Equal(t, "parent", rootID) | ||||
| 	}) | ||||
|  | ||||
| 	t.Run("from child id", func(t *testing.T) { | ||||
| 	t.Run("root from child id", func(t *testing.T) { | ||||
| 		rootID, err := store.GetRootID(container, "child1") | ||||
| 		require.NoError(t, err) | ||||
| 		require.Equal(t, "parent", rootID) | ||||
| 	}) | ||||
|  | ||||
| 	t.Run("from not existing id", func(t *testing.T) { | ||||
| 	t.Run("root from not existing id", func(t *testing.T) { | ||||
| 		_, err := store.GetRootID(container, "not-exists") | ||||
| 		require.Error(t, err) | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| func testGetParentID(t *testing.T, store store.Store, container store.Container) { | ||||
| 	userID := "user-id" | ||||
|  | ||||
| 	blocks, err := store.GetAllBlocks(container) | ||||
| 	require.NoError(t, err) | ||||
| 	initialCount := len(blocks) | ||||
|  | ||||
| 	blocksToInsert := []model.Block{ | ||||
| 		{ | ||||
| 			ID:         "parent", | ||||
| 			RootID:     "parent", | ||||
| 			ModifiedBy: userID, | ||||
| 		}, | ||||
| 		{ | ||||
| 			ID:         "child1", | ||||
| 			RootID:     "parent", | ||||
| 			ParentID:   "parent", | ||||
| 			ModifiedBy: userID, | ||||
| 		}, | ||||
| 		{ | ||||
| 			ID:         "child2", | ||||
| 			RootID:     "parent", | ||||
| 			ParentID:   "parent", | ||||
| 			ModifiedBy: userID, | ||||
| 		}, | ||||
| 		{ | ||||
| 			ID:         "grandchild1", | ||||
| 			RootID:     "parent", | ||||
| 			ParentID:   "child1", | ||||
| 			ModifiedBy: userID, | ||||
| 		}, | ||||
| 		{ | ||||
| 			ID:         "grandchild2", | ||||
| 			RootID:     "parent", | ||||
| 			ParentID:   "child2", | ||||
| 			ModifiedBy: userID, | ||||
| 		}, | ||||
| 		{ | ||||
| 			ID:         "greatgrandchild1", | ||||
| 			RootID:     "parent", | ||||
| 			ParentID:   "grandchild1", | ||||
| 			ModifiedBy: userID, | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	InsertBlocks(t, store, container, blocksToInsert, "user-id-1") | ||||
| 	defer DeleteBlocks(t, store, container, blocksToInsert, "test") | ||||
|  | ||||
| 	blocks, err = store.GetAllBlocks(container) | ||||
| 	require.NoError(t, err) | ||||
| 	require.Len(t, blocks, initialCount+6) | ||||
|  | ||||
| 	t.Run("from root id", func(t *testing.T) { | ||||
| 	t.Run("parent from root id", func(t *testing.T) { | ||||
| 		parentID, err := store.GetParentID(container, "parent") | ||||
| 		require.NoError(t, err) | ||||
| 		require.Equal(t, "", parentID) | ||||
| 	}) | ||||
|  | ||||
| 	t.Run("from child id", func(t *testing.T) { | ||||
| 	t.Run("parent from child id", func(t *testing.T) { | ||||
| 		parentID, err := store.GetParentID(container, "grandchild1") | ||||
| 		require.NoError(t, err) | ||||
| 		require.Equal(t, "child1", parentID) | ||||
| 	}) | ||||
|  | ||||
| 	t.Run("from not existing id", func(t *testing.T) { | ||||
| 	t.Run("parent from not existing id", func(t *testing.T) { | ||||
| 		_, err := store.GetParentID(container, "not-exists") | ||||
| 		require.Error(t, err) | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| func testDeleteBlock(t *testing.T, store store.Store, container store.Container) { | ||||
| 	userID := "user-id" | ||||
| 	userID := testUserID | ||||
|  | ||||
| 	blocks, err := store.GetAllBlocks(container) | ||||
| 	require.NoError(t, err) | ||||
| @@ -550,9 +397,7 @@ func testDeleteBlock(t *testing.T, store store.Store, container store.Container) | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| func testGetBlocksWithParentAndType(t *testing.T, store store.Store, container store.Container) { | ||||
| 	userID := "user-id" | ||||
|  | ||||
| func testGetBlocks(t *testing.T, store store.Store, container store.Container) { | ||||
| 	blocks, err := store.GetAllBlocks(container) | ||||
| 	require.NoError(t, err) | ||||
|  | ||||
| @@ -561,38 +406,39 @@ func testGetBlocksWithParentAndType(t *testing.T, store store.Store, container s | ||||
| 			ID:         "block1", | ||||
| 			ParentID:   "", | ||||
| 			RootID:     "block1", | ||||
| 			ModifiedBy: userID, | ||||
| 			ModifiedBy: testUserID, | ||||
| 			Type:       "test", | ||||
| 		}, | ||||
| 		{ | ||||
| 			ID:         "block2", | ||||
| 			ParentID:   "block1", | ||||
| 			RootID:     "block1", | ||||
| 			ModifiedBy: userID, | ||||
| 			ModifiedBy: testUserID, | ||||
| 			Type:       "test", | ||||
| 		}, | ||||
| 		{ | ||||
| 			ID:         "block3", | ||||
| 			ParentID:   "block1", | ||||
| 			RootID:     "block1", | ||||
| 			ModifiedBy: userID, | ||||
| 			ModifiedBy: testUserID, | ||||
| 			Type:       "test", | ||||
| 		}, | ||||
| 		{ | ||||
| 			ID:         "block4", | ||||
| 			ParentID:   "block1", | ||||
| 			RootID:     "block1", | ||||
| 			ModifiedBy: userID, | ||||
| 			ModifiedBy: testUserID, | ||||
| 			Type:       "test2", | ||||
| 		}, | ||||
| 		{ | ||||
| 			ID:         "block5", | ||||
| 			ParentID:   "block2", | ||||
| 			RootID:     "block1", | ||||
| 			ModifiedBy: userID, | ||||
| 			RootID:     "block2", | ||||
| 			ModifiedBy: testUserID, | ||||
| 			Type:       "test", | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	InsertBlocks(t, store, container, blocksToInsert, "user-id-1") | ||||
| 	defer DeleteBlocks(t, store, container, blocksToInsert, "test") | ||||
|  | ||||
| @@ -616,53 +462,6 @@ func testGetBlocksWithParentAndType(t *testing.T, store store.Store, container s | ||||
| 		require.NoError(t, err) | ||||
| 		require.Len(t, blocks, 2) | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| func testGetBlocksWithParent(t *testing.T, store store.Store, container store.Container) { | ||||
| 	userID := "user-id" | ||||
|  | ||||
| 	blocks, err := store.GetAllBlocks(container) | ||||
| 	require.NoError(t, err) | ||||
|  | ||||
| 	blocksToInsert := []model.Block{ | ||||
| 		{ | ||||
| 			ID:         "block1", | ||||
| 			ParentID:   "", | ||||
| 			RootID:     "block1", | ||||
| 			ModifiedBy: userID, | ||||
| 			Type:       "test", | ||||
| 		}, | ||||
| 		{ | ||||
| 			ID:         "block2", | ||||
| 			ParentID:   "block1", | ||||
| 			RootID:     "block1", | ||||
| 			ModifiedBy: userID, | ||||
| 			Type:       "test", | ||||
| 		}, | ||||
| 		{ | ||||
| 			ID:         "block3", | ||||
| 			ParentID:   "block1", | ||||
| 			RootID:     "block1", | ||||
| 			ModifiedBy: userID, | ||||
| 			Type:       "test", | ||||
| 		}, | ||||
| 		{ | ||||
| 			ID:         "block4", | ||||
| 			ParentID:   "block1", | ||||
| 			RootID:     "block1", | ||||
| 			ModifiedBy: userID, | ||||
| 			Type:       "test2", | ||||
| 		}, | ||||
| 		{ | ||||
| 			ID:         "block5", | ||||
| 			ParentID:   "block2", | ||||
| 			RootID:     "block1", | ||||
| 			ModifiedBy: userID, | ||||
| 			Type:       "test", | ||||
| 		}, | ||||
| 	} | ||||
| 	InsertBlocks(t, store, container, blocksToInsert, "user-id-1") | ||||
| 	defer DeleteBlocks(t, store, container, blocksToInsert, "test") | ||||
|  | ||||
| 	t.Run("not existing parent", func(t *testing.T) { | ||||
| 		time.Sleep(1 * time.Millisecond) | ||||
| @@ -677,53 +476,6 @@ func testGetBlocksWithParent(t *testing.T, store store.Store, container store.Co | ||||
| 		require.NoError(t, err) | ||||
| 		require.Len(t, blocks, 3) | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| func testGetBlocksWithType(t *testing.T, store store.Store, container store.Container) { | ||||
| 	userID := "user-id" | ||||
|  | ||||
| 	blocks, err := store.GetAllBlocks(container) | ||||
| 	require.NoError(t, err) | ||||
|  | ||||
| 	blocksToInsert := []model.Block{ | ||||
| 		{ | ||||
| 			ID:         "block1", | ||||
| 			ParentID:   "", | ||||
| 			RootID:     "block1", | ||||
| 			ModifiedBy: userID, | ||||
| 			Type:       "test", | ||||
| 		}, | ||||
| 		{ | ||||
| 			ID:         "block2", | ||||
| 			ParentID:   "block1", | ||||
| 			RootID:     "block1", | ||||
| 			ModifiedBy: userID, | ||||
| 			Type:       "test", | ||||
| 		}, | ||||
| 		{ | ||||
| 			ID:         "block3", | ||||
| 			ParentID:   "block1", | ||||
| 			RootID:     "block1", | ||||
| 			ModifiedBy: userID, | ||||
| 			Type:       "test", | ||||
| 		}, | ||||
| 		{ | ||||
| 			ID:         "block4", | ||||
| 			ParentID:   "block1", | ||||
| 			RootID:     "block1", | ||||
| 			ModifiedBy: userID, | ||||
| 			Type:       "test2", | ||||
| 		}, | ||||
| 		{ | ||||
| 			ID:         "block5", | ||||
| 			ParentID:   "block2", | ||||
| 			RootID:     "block1", | ||||
| 			ModifiedBy: userID, | ||||
| 			Type:       "test", | ||||
| 		}, | ||||
| 	} | ||||
| 	InsertBlocks(t, store, container, blocksToInsert, "user-id-1") | ||||
| 	defer DeleteBlocks(t, store, container, blocksToInsert, "test") | ||||
|  | ||||
| 	t.Run("not existing type", func(t *testing.T) { | ||||
| 		time.Sleep(1 * time.Millisecond) | ||||
| @@ -738,53 +490,6 @@ func testGetBlocksWithType(t *testing.T, store store.Store, container store.Cont | ||||
| 		require.NoError(t, err) | ||||
| 		require.Len(t, blocks, 4) | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| func testGetBlocksWithRootID(t *testing.T, store store.Store, container store.Container) { | ||||
| 	userID := "user-id" | ||||
|  | ||||
| 	blocks, err := store.GetAllBlocks(container) | ||||
| 	require.NoError(t, err) | ||||
|  | ||||
| 	blocksToInsert := []model.Block{ | ||||
| 		{ | ||||
| 			ID:         "block1", | ||||
| 			ParentID:   "", | ||||
| 			RootID:     "block1", | ||||
| 			ModifiedBy: userID, | ||||
| 			Type:       "test", | ||||
| 		}, | ||||
| 		{ | ||||
| 			ID:         "block2", | ||||
| 			ParentID:   "block1", | ||||
| 			RootID:     "block1", | ||||
| 			ModifiedBy: userID, | ||||
| 			Type:       "test", | ||||
| 		}, | ||||
| 		{ | ||||
| 			ID:         "block3", | ||||
| 			ParentID:   "block1", | ||||
| 			RootID:     "block1", | ||||
| 			ModifiedBy: userID, | ||||
| 			Type:       "test", | ||||
| 		}, | ||||
| 		{ | ||||
| 			ID:         "block4", | ||||
| 			ParentID:   "block1", | ||||
| 			RootID:     "block1", | ||||
| 			ModifiedBy: userID, | ||||
| 			Type:       "test2", | ||||
| 		}, | ||||
| 		{ | ||||
| 			ID:         "block5", | ||||
| 			ParentID:   "block2", | ||||
| 			RootID:     "block2", | ||||
| 			ModifiedBy: userID, | ||||
| 			Type:       "test", | ||||
| 		}, | ||||
| 	} | ||||
| 	InsertBlocks(t, store, container, blocksToInsert, "user-id-1") | ||||
| 	defer DeleteBlocks(t, store, container, blocksToInsert, "test") | ||||
|  | ||||
| 	t.Run("not existing parent", func(t *testing.T) { | ||||
| 		time.Sleep(1 * time.Millisecond) | ||||
|   | ||||
| @@ -26,7 +26,7 @@ func testUpsertSharingAndGetSharing(t *testing.T, store store.Store, container s | ||||
| 			ID:         "sharing-id", | ||||
| 			Enabled:    true, | ||||
| 			Token:      "token", | ||||
| 			ModifiedBy: "user-id", | ||||
| 			ModifiedBy: testUserID, | ||||
| 		} | ||||
|  | ||||
| 		err := store.UpsertSharing(container, sharing) | ||||
|   | ||||
| @@ -53,11 +53,11 @@ func (ts *Service) RegisterTracker(name string, f TrackerFunc) { | ||||
| func (ts *Service) getRudderConfig() RudderConfig { | ||||
| 	if !strings.Contains(rudderKey, "placeholder") && !strings.Contains(rudderDataplaneURL, "placeholder") { | ||||
| 		return RudderConfig{rudderKey, rudderDataplaneURL} | ||||
| 	} else if os.Getenv("RUDDER_KEY") != "" && os.Getenv("RUDDER_DATAPLANE_URL") != "" { | ||||
| 		return RudderConfig{os.Getenv("RUDDER_KEY"), os.Getenv("RUDDER_DATAPLANE_URL")} | ||||
| 	} else { | ||||
| 		return RudderConfig{} | ||||
| 	} | ||||
| 	if os.Getenv("RUDDER_KEY") != "" && os.Getenv("RUDDER_DATAPLANE_URL") != "" { | ||||
| 		return RudderConfig{os.Getenv("RUDDER_KEY"), os.Getenv("RUDDER_DATAPLANE_URL")} | ||||
| 	} | ||||
| 	return RudderConfig{} | ||||
| } | ||||
|  | ||||
| func (ts *Service) sendDailyTelemetry(override bool) { | ||||
| @@ -113,15 +113,23 @@ func (ts *Service) initRudder(endpoint, rudderKey string) { | ||||
|  | ||||
| func (ts *Service) doTelemetryIfNeeded(firstRun time.Time) { | ||||
| 	hoursSinceFirstServerRun := time.Since(firstRun).Hours() | ||||
|  | ||||
| 	// Send once every 10 minutes for the first hour | ||||
| 	// Send once every hour thereafter for the first 12 hours | ||||
| 	// Send at the 24 hour mark and every 24 hours after | ||||
| 	if hoursSinceFirstServerRun < 1 { | ||||
| 		ts.doTelemetry() | ||||
| 	} else if hoursSinceFirstServerRun <= 12 && time.Since(ts.timestampLastTelemetrySent) >= time.Hour { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// Send once every hour thereafter for the first 12 hours | ||||
| 	if hoursSinceFirstServerRun <= 12 && time.Since(ts.timestampLastTelemetrySent) >= time.Hour { | ||||
| 		ts.doTelemetry() | ||||
| 	} else if hoursSinceFirstServerRun > 12 && time.Since(ts.timestampLastTelemetrySent) >= 24*time.Hour { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// Send at the 24 hour mark and every 24 hours after | ||||
| 	if hoursSinceFirstServerRun > 12 && time.Since(ts.timestampLastTelemetrySent) >= 24*time.Hour { | ||||
| 		ts.doTelemetry() | ||||
| 		return | ||||
| 	} | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -2,7 +2,6 @@ package ws | ||||
|  | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"errors" | ||||
| 	"log" | ||||
| 	"net/http" | ||||
| 	"sync" | ||||
| @@ -220,6 +219,14 @@ func (ws *Server) authenticateListener(wsSession *websocketSession, workspaceID, | ||||
| 	ws.logger.Debug("authenticateListener: Authenticated", mlog.String("workspaceID", workspaceID)) | ||||
| } | ||||
|  | ||||
| type AuthWorkspaceError struct { | ||||
| 	msg string | ||||
| } | ||||
|  | ||||
| func (awe AuthWorkspaceError) Error() string { | ||||
| 	return awe.msg | ||||
| } | ||||
|  | ||||
| func (ws *Server) getAuthenticatedWorkspaceID(wsSession *websocketSession, command *WebsocketCommand) (string, error) { | ||||
| 	if wsSession.isAuthenticated { | ||||
| 		return wsSession.workspaceID, nil | ||||
| @@ -229,7 +236,7 @@ func (ws *Server) getAuthenticatedWorkspaceID(wsSession *websocketSession, comma | ||||
| 	workspaceID := command.WorkspaceID | ||||
| 	if len(workspaceID) == 0 { | ||||
| 		ws.logger.Error("getAuthenticatedWorkspaceID: No workspace") | ||||
| 		return "", errors.New("no workspace") | ||||
| 		return "", AuthWorkspaceError{"no workspace"} | ||||
| 	} | ||||
|  | ||||
| 	container := store.Container{ | ||||
| @@ -241,13 +248,13 @@ func (ws *Server) getAuthenticatedWorkspaceID(wsSession *websocketSession, comma | ||||
| 		for _, blockID := range command.BlockIDs { | ||||
| 			isValid, _ := ws.auth.IsValidReadToken(container, blockID, command.ReadToken) | ||||
| 			if !isValid { | ||||
| 				return "", errors.New("invalid read token for workspace") | ||||
| 				return "", AuthWorkspaceError{"invalid read token for workspace"} | ||||
| 			} | ||||
| 		} | ||||
| 		return workspaceID, nil | ||||
| 	} | ||||
|  | ||||
| 	return "", errors.New("no read token") | ||||
| 	return "", AuthWorkspaceError{"no read token"} | ||||
| } | ||||
|  | ||||
| // TODO: Refactor workspace hashing. | ||||
| @@ -314,7 +321,8 @@ func (ws *Server) removeListenerFromBlocks(wsSession *websocketSession, command | ||||
| 		// Note: A client can listen multiple times to the same block | ||||
| 		for index, listener := range listeners { | ||||
| 			if wsSession.client == listener { | ||||
| 				newListeners := append(listeners[:index], listeners[index+1:]...) | ||||
| 				newListeners := listeners[:index] | ||||
| 				newListeners = append(newListeners, listeners[index+1:]...) | ||||
| 				ws.listeners[itemID] = newListeners | ||||
|  | ||||
| 				break | ||||
|   | ||||
		Reference in New Issue
	
	Block a user