diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 010eaf2..139908b 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -11,40 +11,32 @@ jobs: name: linters runs-on: ubuntu-latest steps: - - - name: checkout staging + - name: checkout staging uses: actions/checkout@v4 - - - name: install Go + + - name: install Go uses: actions/setup-go@v5 with: go-version: '1.23' - - - name: golangci-lint + + - name: golangci-lint uses: golangci/golangci-lint-action@v4 with: version: v1.62.2 tests: runs-on: ubuntu-latest + needs: linter steps: - - - name: checkout staging + - name: checkout staging uses: actions/checkout@v4 - - - name: install Go + + - name: install Go uses: actions/setup-go@v5 with: go-version: '1.23' - - - run: | - sudo apt-get -y update - sudo apt-get install -y libwebp-dev wkhtmltopdf - - - name: build - run: go build -v ./... - - - name: Test + + - name: Test run: go test -v ./... deploy: diff --git a/Makefile b/Makefile index 8e6294d..54ef2bc 100644 --- a/Makefile +++ b/Makefile @@ -39,7 +39,7 @@ build_all: build_auth build_user build_poll build_board run_tests: @echo "==> Running tests..." - @go test $(GOFLAGS) -coverprofile ../coverage_raw.out -v ./... + @go test $(GOFLAGS) -coverprofile coverage_raw.out -v ./... test: run_tests @echo "==> Calculating coverage..." diff --git a/database/elastic/migrator.go b/database/elastic/migrator.go index 63aec7e..7b6f8f9 100644 --- a/database/elastic/migrator.go +++ b/database/elastic/migrator.go @@ -6,8 +6,8 @@ import ( "context" "encoding/json" "fmt" - "io/ioutil" "log" + "os" "strings" "github.com/elastic/go-elasticsearch/v8" @@ -110,7 +110,7 @@ func deleteIndex(index string, es *elasticsearch.Client) { func createIndexWithMapping(index, mappingFile string, es *elasticsearch.Client) { // Read mapping JSON from file - mapping, err := ioutil.ReadFile(mappingFile) + mapping, err := os.ReadFile(mappingFile) if err != nil { log.Fatalf("Error reading mapping file: %s", err) } @@ -189,10 +189,22 @@ func loadDataToElasticsearch(ctx context.Context, cfg Configuration, es *elastic } // Write to buffer - writer.Write(metaLine) - writer.WriteByte('\n') - writer.Write(dataLine) - writer.WriteByte('\n') + _, err = writer.Write(metaLine) + if err != nil { + return fmt.Errorf("elastic: %w", err) + } + err = writer.WriteByte('\n') + if err != nil { + return fmt.Errorf("elastic: %w", err) + } + _, err = writer.Write(dataLine) + if err != nil { + return fmt.Errorf("elastic: %w", err) + } + err = writer.WriteByte('\n') + if err != nil { + return fmt.Errorf("elastic: %w", err) + } count++ if count%batchSize == 0 { diff --git a/internal/pkg/board/repository/elastic.go b/internal/pkg/board/repository/elastic.go index 0ce17fd..b690faa 100644 --- a/internal/pkg/board/repository/elastic.go +++ b/internal/pkg/board/repository/elastic.go @@ -47,10 +47,10 @@ func (be *BoardElasticRepository) CreateCard(ctx context.Context, boardID int64, } func (be *BoardElasticRepository) UpdateCard(ctx context.Context, boardID int64, cardID int64, cardTitle string) error { - funcName := "UpdateCard" - if 1 == 1 { - panic(funcName + " not implemented") - } + // funcName := "UpdateCard" + // if 1 == 1 { + // panic(funcName + " not implemented") + // } return nil } diff --git a/internal/pkg/board/repository/members.go b/internal/pkg/board/repository/members.go index 079bca7..fdc9131 100644 --- a/internal/pkg/board/repository/members.go +++ b/internal/pkg/board/repository/members.go @@ -681,7 +681,7 @@ func (r *BoardRepository) RearrangeCards(ctx context.Context, column1 []models.C return fmt.Errorf("%s (batch query): %w", funcName, err) } - tx.Commit(ctx) + err = tx.Commit(ctx) logging.Debug(ctx, funcName, " commit has err: ", err) if err != nil { return fmt.Errorf("%s (commit): %w", funcName, err) @@ -713,7 +713,7 @@ func (r *BoardRepository) RearrangeColumns(ctx context.Context, columns []models return fmt.Errorf("%s (batch query): %w", funcName, err) } - tx.Commit(ctx) + err = tx.Commit(ctx) logging.Debug(ctx, funcName, " commit has err: ", err) if err != nil { return fmt.Errorf("%s (commit): %w", funcName, err) diff --git a/internal/pkg/board/usecase/board_uc.go b/internal/pkg/board/usecase/board_uc.go index b168c6c..089c482 100644 --- a/internal/pkg/board/usecase/board_uc.go +++ b/internal/pkg/board/usecase/board_uc.go @@ -734,7 +734,10 @@ func (uc *BoardUsecase) MoveColumn(ctx context.Context, userID int64, columnID i columns = slices.Insert(columns, destIdx-1, col) } - uc.boardRepository.RearrangeColumns(ctx, columns) + err = uc.boardRepository.RearrangeColumns(ctx, columns) + if err != nil { + return fmt.Errorf("%s: %w", funcName, err) + } return nil } @@ -805,6 +808,9 @@ func (uc *BoardUsecase) GetSharedCard(ctx context.Context, userID int64, cardUUI } board, err := uc.boardRepository.GetBoard(ctx, boardID, -1) + if err != nil { + return nil, nil, err + } return nil, &models.SharedCardDummyResponse{ Card: cardDetails, diff --git a/internal/pkg/middleware/logging_middleware/middleware.go b/internal/pkg/middleware/logging_middleware/middleware.go index 0083863..d2b9dfe 100644 --- a/internal/pkg/middleware/logging_middleware/middleware.go +++ b/internal/pkg/middleware/logging_middleware/middleware.go @@ -1,6 +1,7 @@ package logging_middleware import ( + "RPO_back/internal/pkg/utils/logging" "context" "net/http" "sync" @@ -8,10 +9,6 @@ import ( log "github.com/sirupsen/logrus" ) -type contextKey string - -const rIDKey = contextKey("requestID") - var ( rIDCounter uint64 = 1 mu sync.Mutex @@ -24,7 +21,7 @@ func LoggingMiddleware(next http.Handler) http.Handler { rID := rIDCounter mu.Unlock() - ctx := context.WithValue(r.Context(), rIDKey, rID) + ctx := context.WithValue(r.Context(), logging.RequestIDkey, rID) log.Infof("Запрос: %s %s, RequestID: %d", r.Method, r.RequestURI, rID) next.ServeHTTP(w, r.WithContext(ctx)) diff --git a/internal/pkg/middleware/logging_middleware/middleware_test.go b/internal/pkg/middleware/logging_middleware/middleware_test.go index 84f68c3..38606c6 100644 --- a/internal/pkg/middleware/logging_middleware/middleware_test.go +++ b/internal/pkg/middleware/logging_middleware/middleware_test.go @@ -27,15 +27,15 @@ func TestLoggingMiddleware(t *testing.T) { assert.Equal(t, http.StatusOK, resp.StatusCode) } -type testWriter struct { - content []byte -} - -func (tw *testWriter) Write(p []byte) (n int, err error) { - tw.content = append(tw.content, p...) - return len(p), nil -} - -func (tw *testWriter) String() string { - return string(tw.content) -} +// type testWriter struct { +// content []byte +// } + +// func (tw *testWriter) Write(p []byte) (n int, err error) { +// tw.content = append(tw.content, p...) +// return len(p), nil +// } + +// func (tw *testWriter) String() string { +// return string(tw.content) +// } diff --git a/internal/pkg/user/usecase/user_uc.go b/internal/pkg/user/usecase/user_uc.go index 8630cbd..ec205fc 100644 --- a/internal/pkg/user/usecase/user_uc.go +++ b/internal/pkg/user/usecase/user_uc.go @@ -51,6 +51,9 @@ func (uc *UserUsecase) SetMyAvatar(ctx context.Context, userID int64, file *mode } err = uc.userRepo.SetUserAvatar(ctx, userID, fileID) + if err != nil { + return nil, fmt.Errorf("%s: %w", funcName, err) + } return uc.userRepo.GetUserProfile(ctx, userID) } diff --git a/internal/pkg/utils/logging/logging_test.go b/internal/pkg/utils/logging/logging_test.go deleted file mode 100644 index 23ccf9d..0000000 --- a/internal/pkg/utils/logging/logging_test.go +++ /dev/null @@ -1,59 +0,0 @@ -package logging - -import ( - "bytes" - "context" - "fmt" - "os" - "testing" - - log "github.com/sirupsen/logrus" -) - -func TestSetupLogger(t *testing.T) { - // Создаем временный файл для проверки записи JSON-логов - tmpFile, err := os.CreateTemp("", "test-log.json") - if err != nil { - t.Fatalf("Не удалось создать временный файл: %v", err) - } - defer os.Remove(tmpFile.Name()) - - // Настраиваем логгер - SetupLogger(tmpFile) - - // Проверяем, что вывод в консоль настроен правильно - consoleBuffer := &bytes.Buffer{} - log.SetOutput(consoleBuffer) - - log.Info("Test log to console") - if !bytes.Contains(consoleBuffer.Bytes(), []byte("Test log to console")) { - t.Errorf("Лог не записан в консоль") - } - - // Проверяем, что запись в файл работает - log.Info("Test log to file") - fileContent, err := os.ReadFile(tmpFile.Name()) - if err != nil { - t.Fatalf("Не удалось прочитать содержимое временного файла: %v", err) - } - - if !bytes.Contains(fileContent, []byte("Test log to file")) { - t.Errorf("Лог не записан в файл") - } -} - -func TestLogFunc(t *testing.T) { - ctx := context.WithValue(context.Background(), "requestID", uint64(123456)) - - Warn(ctx, "This is a warning message") - Info(ctx, "This is an info message") - Error(ctx, "This is an error message") - Debug(ctx, "This is a debug message") - - Warnf(ctx, "Warning with format: %s", "formatted warning") - Infof(ctx, "Info with format: %s", "formatted info") - Errorf(ctx, "Error with format: %s", "formatted error") - Debugf(ctx, "Debug with format: %s", "formatted debug") - - fmt.Println("All functions tested.") -} diff --git a/internal/pkg/utils/logging/setupLogrus.go b/internal/pkg/utils/logging/logrus.go similarity index 94% rename from internal/pkg/utils/logging/setupLogrus.go rename to internal/pkg/utils/logging/logrus.go index 8b3e47e..9e5d7c9 100644 --- a/internal/pkg/utils/logging/setupLogrus.go +++ b/internal/pkg/utils/logging/logrus.go @@ -1,96 +1,102 @@ -package logging - -import ( - "context" - "fmt" - "os" - - log "github.com/sirupsen/logrus" -) - -// SetupLogger настраивает logrus, чтобы он писал в консоль цветом и в файл json-ом -func SetupLogger(jsonFile *os.File) { - // Настраиваем цвета - textFormatter := &log.TextFormatter{ - ForceColors: true, - FullTimestamp: true, - } - log.SetFormatter(textFormatter) - - // Устанавливаем вывод в терминал - log.SetOutput(os.Stdout) - - // Создаем хук для записи JSON-логов в файл - jsonFormatter := &log.JSONFormatter{} - log.AddHook(&fileHook{ - Writer: jsonFile, - Formatter: jsonFormatter, - }) -} - -// fileHook реализует хук для записи логов в файл с определенным форматом -type fileHook struct { - Writer *os.File - Formatter log.Formatter -} - -func (hook *fileHook) Levels() []log.Level { - return log.AllLevels -} - -func (hook *fileHook) Fire(entry *log.Entry) error { - line, err := hook.Formatter.Format(entry) - if err != nil { - return err - } - _, err = hook.Writer.Write(line) - return err -} - -func GetRequestID(ctx context.Context) uint64 { - requestID, ok := ctx.Value("requestID").(uint64) - if !ok { - return 0 - } - return requestID -} - -func Warn(ctx context.Context, data ...interface{}) { - logData := append([]interface{}{fmt.Sprintf("rid=%d ", GetRequestID(ctx))}, data...) - log.Warn(logData...) -} - -func Info(ctx context.Context, data ...interface{}) { - logData := append([]interface{}{fmt.Sprintf("rid=%d ", GetRequestID(ctx))}, data...) - log.Info(logData...) -} - -func Error(ctx context.Context, data ...interface{}) { - logData := append([]interface{}{fmt.Sprintf("rid=%d ", GetRequestID(ctx))}, data...) - log.Error(logData...) -} - -func Debug(ctx context.Context, data ...interface{}) { - logData := append([]interface{}{fmt.Sprintf("rid=%d ", GetRequestID(ctx))}, data...) - log.Info(logData...) -} - -func Warnf(ctx context.Context, format string, data ...interface{}) { - ridInfo := fmt.Sprintf("rid=%d ", GetRequestID(ctx)) - log.Warnf(ridInfo+format, data...) -} - -func Infof(ctx context.Context, format string, data ...interface{}) { - ridInfo := fmt.Sprintf("rid=%d ", GetRequestID(ctx)) - log.Infof(ridInfo+format, data...) -} - -func Errorf(ctx context.Context, format string, data ...interface{}) { - ridInfo := fmt.Sprintf("rid=%d ", GetRequestID(ctx)) - log.Errorf(ridInfo+format, data...) -} - -func Debugf(ctx context.Context, format string, data ...interface{}) { - ridInfo := fmt.Sprintf("rid=%d ", GetRequestID(ctx)) - log.Debugf(ridInfo+format, data...) -} +package logging + +import ( + "context" + "fmt" + "os" + + log "github.com/sirupsen/logrus" +) + +type contextKey string + +const ( + RequestIDkey = contextKey("requestID") +) + +// SetupLogger настраивает logrus, чтобы он писал в консоль цветом и в файл json-ом +func SetupLogger(jsonFile *os.File) { + // Настраиваем цвета + textFormatter := &log.TextFormatter{ + ForceColors: true, + FullTimestamp: true, + } + log.SetFormatter(textFormatter) + + // Устанавливаем вывод в терминал + log.SetOutput(os.Stdout) + + // Создаем хук для записи JSON-логов в файл + jsonFormatter := &log.JSONFormatter{} + log.AddHook(&fileHook{ + Writer: jsonFile, + Formatter: jsonFormatter, + }) +} + +// fileHook реализует хук для записи логов в файл с определенным форматом +type fileHook struct { + Writer *os.File + Formatter log.Formatter +} + +func (hook *fileHook) Levels() []log.Level { + return log.AllLevels +} + +func (hook *fileHook) Fire(entry *log.Entry) error { + line, err := hook.Formatter.Format(entry) + if err != nil { + return err + } + _, err = hook.Writer.Write(line) + return err +} + +func GetRequestID(ctx context.Context) uint64 { + requestID, ok := ctx.Value(RequestIDkey).(uint64) + if !ok { + return 0 + } + return requestID +} + +func Warn(ctx context.Context, data ...interface{}) { + logData := append([]interface{}{fmt.Sprintf("rid=%d ", GetRequestID(ctx))}, data...) + log.Warn(logData...) +} + +func Info(ctx context.Context, data ...interface{}) { + logData := append([]interface{}{fmt.Sprintf("rid=%d ", GetRequestID(ctx))}, data...) + log.Info(logData...) +} + +func Error(ctx context.Context, data ...interface{}) { + logData := append([]interface{}{fmt.Sprintf("rid=%d ", GetRequestID(ctx))}, data...) + log.Error(logData...) +} + +func Debug(ctx context.Context, data ...interface{}) { + logData := append([]interface{}{fmt.Sprintf("rid=%d ", GetRequestID(ctx))}, data...) + log.Info(logData...) +} + +func Warnf(ctx context.Context, format string, data ...interface{}) { + ridInfo := fmt.Sprintf("rid=%d ", GetRequestID(ctx)) + log.Warnf(ridInfo+format, data...) +} + +func Infof(ctx context.Context, format string, data ...interface{}) { + ridInfo := fmt.Sprintf("rid=%d ", GetRequestID(ctx)) + log.Infof(ridInfo+format, data...) +} + +func Errorf(ctx context.Context, format string, data ...interface{}) { + ridInfo := fmt.Sprintf("rid=%d ", GetRequestID(ctx)) + log.Errorf(ridInfo+format, data...) +} + +func Debugf(ctx context.Context, format string, data ...interface{}) { + ridInfo := fmt.Sprintf("rid=%d ", GetRequestID(ctx)) + log.Debugf(ridInfo+format, data...) +} diff --git a/internal/pkg/utils/responses/responses.go b/internal/pkg/utils/responses/responses.go index ad22e21..28318cb 100644 --- a/internal/pkg/utils/responses/responses.go +++ b/internal/pkg/utils/responses/responses.go @@ -28,7 +28,10 @@ func DoBadResponseAndLog(r *http.Request, w http.ResponseWriter, statusCode int, http.Error(w, "internal error", http.StatusInternalServerError) return } - w.Write(jsonResponse) + _, err = w.Write(jsonResponse) + if err != nil { + panic(err) + } logging.Error(r.Context(), "Bad response with status ", statusCode, " and message ", message) } @@ -36,7 +39,10 @@ func DoBadResponseAndLog(r *http.Request, w http.ResponseWriter, statusCode int, func DoEmptyOkResponse(w http.ResponseWriter) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - w.Write([]byte("{\"status\": 200, \"text\": \"success\"}")) + _, err := w.Write([]byte("{\"status\": 200, \"text\": \"success\"}")) + if err != nil { + panic(err) + } } func DoJSONResponse(r *http.Request, w http.ResponseWriter, responseData interface{}, successStatusCode int) { diff --git a/internal/pkg/utils/uploads/uploads.go b/internal/pkg/utils/uploads/uploads.go index 6423171..f0584fb 100644 --- a/internal/pkg/utils/uploads/uploads.go +++ b/internal/pkg/utils/uploads/uploads.go @@ -78,7 +78,10 @@ func CompareFiles(fileNames []string, fileIDs []int64, newFile *models.UploadedF } func FormFile(r *http.Request) (file *models.UploadedFile, err error) { - r.ParseMultipartForm(10 << 20) + err = r.ParseMultipartForm(10 << 20) + if err != nil { + return nil, fmt.Errorf("FormFile: %w", err) + } fileContent, fileHeader, err := r.FormFile("file") if err != nil { diff --git a/internal/pkg/utils/validate/validate.go b/internal/pkg/utils/validate/validate.go index 24faaa5..30d499e 100644 --- a/internal/pkg/utils/validate/validate.go +++ b/internal/pkg/utils/validate/validate.go @@ -35,7 +35,7 @@ func Validate(ctx context.Context, v interface{}) error { return err } - if v, ok := v.(Validatable); ok == true && v != nil { + if v, ok := v.(Validatable); ok && v != nil { if err := v.Validate(); err != nil { return fmt.Errorf("Validate: %w", err) } @@ -73,10 +73,8 @@ func CheckUserName(name string) error { return fmt.Errorf("%w: name must be between 3 and 30 characters", errs.ErrValidation) } - done, err := regexp.MatchString("^([A-Za-z0-9_.-])+$", name) - if err != nil { - return fmt.Errorf("CheckUserName (MatchString [Alphanumeric]): %w", err) - } + done := usernameRegex.Match([]byte(name)) + if !done { return fmt.Errorf("%w: name contains forbidden characters. Only allowed A-Z, a-z, 0-9, '_', '-', '.'", errs.ErrValidation) }