diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml
new file mode 100644
index 0000000..eb93bfb
--- /dev/null
+++ b/.github/workflows/docker.yml
@@ -0,0 +1,102 @@
+name: Build, Test, and Push Services
+
+on:
+ push:
+ branches:
+ - dev
+
+jobs:
+
+ lint:
+ name: Lint
+ runs-on: ubuntu-latest
+ timeout-minutes: 3
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 1
+ - name: Setup Go
+ uses: actions/setup-go@v5
+ with:
+ go-version: '1.23.1'
+ - name: Install golangci-lint
+ run: go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
+ - name: Run golangci-lint
+ run: golangci-lint run ./...
+
+ build-test-and-push:
+ runs-on: ubuntu-latest
+ needs:
+ - lint
+ steps:
+ # 1. Checkout repository
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ # 2. Set up Go
+ - name: Set up Go
+ uses: actions/setup-go@v5
+ with:
+ go-version: '1.23.1'
+
+ # 3. Install dependencies
+ - name: Install Go dependencies
+ run: |
+ go mod tidy
+ go get ./...
+ go mod vendor
+
+ # 4. Run tests
+ - name: Run Go tests
+ run: |
+ go test -coverpkg=./... -coverprofile=cover ./... && cat cover | grep -v "mock" | grep -v "easyjson" | grep -v "proto" | grep -v "pb" | grep -v "grpc" > cover.out && go tool cover -func=cover.out
+
+ # 5. Login to DockerHub
+ - name: Login to DockerHub Registry
+ run: echo ${{secrets.DOCKERHUB_TOKEN}} | docker login -u ${{secrets.DOCKERHUB_USERNAME}} --password-stdin
+
+ # 6. Build ads_service
+ - name: Build and push ads_service
+ uses: docker/build-push-action@v4
+ with:
+ context: .
+ file: ./microservices/ads_service/Dockerfile
+ tags: ${{secrets.DOCKERHUB_USERNAME}}/ads_service:latest
+ push: true
+
+ # 7. Build auth_service
+ - name: Build and push auth_service
+ uses: docker/build-push-action@v4
+ with:
+ context: .
+ file: ./microservices/auth_service/Dockerfile
+ tags: ${{secrets.DOCKERHUB_USERNAME}}/auth_service:latest
+ push: true
+
+ # 8. Build city_service
+ - name: Build and push city_service
+ uses: docker/build-push-action@v4
+ with:
+ context: .
+ file: ./microservices/city_service/Dockerfile
+ tags: ${{secrets.DOCKERHUB_USERNAME}}/city_service:latest
+ push: true
+
+ # 9. Build migrator
+ - name: Build and push migrator
+ uses: docker/build-push-action@v4
+ with:
+ context: .
+ file: ./cmd/migrator/Dockerfile
+ tags: ${{secrets.DOCKERHUB_USERNAME}}/migrator:latest
+ push: true
+
+ # 10. Build backend (main service)
+ - name: Build and push backend
+ uses: docker/build-push-action@v4
+ with:
+ context: .
+ file: ./Dockerfile
+ tags: ${{secrets.DOCKERHUB_USERNAME}}/backend:latest
+ push: true
+
diff --git a/.idea/shelf/Uncommitted_changes_before_Update_at_18_12_2024_3_17_[Changes]/shelved.patch b/.idea/shelf/Uncommitted_changes_before_Update_at_18_12_2024_3_17_[Changes]/shelved.patch
new file mode 100644
index 0000000..bfe0ca6
--- /dev/null
+++ b/.idea/shelf/Uncommitted_changes_before_Update_at_18_12_2024_3_17_[Changes]/shelved.patch
@@ -0,0 +1,1647 @@
+Index: internal/ads/controller/ads_controller_test.go
+IDEA additional info:
+Subsystem: com.intellij.openapi.diff.impl.patch.BaseRevisionTextPatchEP
+<+>package controller\r\n\r\nimport (\r\n\t\"2024_2_FIGHT-CLUB/internal/service/logger\"\r\n\t\"2024_2_FIGHT-CLUB/microservices/ads_service/controller/gen\"\r\n\t\"2024_2_FIGHT-CLUB/microservices/ads_service/mocks\"\r\n\t\"errors\"\r\n\t\"github.com/stretchr/testify/assert\"\r\n\t\"github.com/stretchr/testify/mock\"\r\n\t\"github.com/stretchr/testify/require\"\r\n\t\"google.golang.org/grpc/codes\"\r\n\t\"google.golang.org/grpc/status\"\r\n\t\"io\"\r\n\t\"net/http\"\r\n\t\"net/http/httptest\"\r\n\t\"testing\"\r\n)\r\n\r\nfunc TestAdHandler_GetAllPlaces_Success(t *testing.T) {\r\n\t// Инициализация логгера\r\n\trequire.NoError(t, logger.InitLoggers())\r\n\tdefer func() {\r\n\t\terr := logger.SyncLoggers()\r\n\t\tif err != nil {\r\n\t\t\treturn\r\n\t\t}\r\n\t}()\r\n\r\n\tmockGrpcClient := new(mocks.MockGrpcClient)\r\n\ttestResponse := &gen.GetAllAdsResponseList{}\r\n\tmockGrpcClient.On(\"GetAllPlaces\", mock.Anything, mock.Anything, mock.Anything).Return(testResponse, nil)\r\n\r\n\tadHandler := &AdHandler{\r\n\t\tclient: mockGrpcClient,\r\n\t}\r\n\r\n\treq := httptest.NewRequest(http.MethodGet, \"/housing?location=test\", nil)\r\n\treq.Header.Set(\"X-Real-IP\", \"127.0.0.1\")\r\n\tw := httptest.NewRecorder()\r\n\r\n\tadHandler.GetAllPlaces(w, req)\r\n\r\n\tresult := w.Result()\r\n\tdefer func(Body io.ReadCloser) {\r\n\t\terr := Body.Close()\r\n\t\tif err != nil {\r\n\t\t\treturn\r\n\t\t}\r\n\t}(result.Body)\r\n\r\n\tassert.Equal(t, http.StatusOK, result.StatusCode)\r\n\tmockGrpcClient.AssertExpectations(t)\r\n}\r\n\r\nfunc TestAdHandler_GetAllPlaces_Error(t *testing.T) {\r\n\trequire.NoError(t, logger.InitLoggers())\r\n\tdefer func() {\r\n\t\terr := logger.SyncLoggers()\r\n\t\tif err != nil {\r\n\t\t\treturn\r\n\t\t}\r\n\t}()\r\n\tgrpcErr := status.Error(codes.Internal, \"Simulated gRPC Error\")\r\n\r\n\tmockGrpcClient := new(mocks.MockGrpcClient)\r\n\tmockGrpcClient.On(\"GetAllPlaces\", mock.Anything, mock.Anything, mock.Anything).\r\n\t\tReturn((*gen.GetAllAdsResponseList)(nil), grpcErr)\r\n\r\n\tadHandler := &AdHandler{\r\n\t\tclient: mockGrpcClient,\r\n\t}\r\n\r\n\treq := httptest.NewRequest(http.MethodGet, \"/housing\", nil)\r\n\treq.Header.Set(\"X-Real-IP\", \"127.0.0.1\")\r\n\tw := httptest.NewRecorder()\r\n\r\n\tadHandler.GetAllPlaces(w, req)\r\n\r\n\tresult := w.Result()\r\n\tdefer func(Body io.ReadCloser) {\r\n\t\terr := Body.Close()\r\n\t\tif err != nil {\r\n\t\t\treturn\r\n\t\t}\r\n\t}(result.Body)\r\n\r\n\tassert.Equal(t, http.StatusInternalServerError, result.StatusCode)\r\n\tmockGrpcClient.AssertExpectations(t)\r\n}\r\n\r\nfunc TestAdHandler_GetAllPlaces_ConvertError(t *testing.T) {\r\n\trequire.NoError(t, logger.InitLoggers())\r\n\tdefer func() {\r\n\t\terr := logger.SyncLoggers()\r\n\t\tif err != nil {\r\n\t\t\treturn\r\n\t\t}\r\n\t}()\r\n\tresponse := &gen.GetAllAdsResponseList{}\r\n\tmockGrpcClient := new(mocks.MockGrpcClient)\r\n\tmockGrpcClient.On(\"GetAllPlaces\", mock.Anything, mock.Anything, mock.Anything).\r\n\t\tReturn(response, nil)\r\n\r\n\tutilsMock := &mocks.MockUtils{}\r\n\tutilsMock.On(\"ConvertGetAllAdsResponseProtoToGo\", response).\r\n\t\tReturn(nil, errors.New(\"conversion error\"))\r\n\r\n\tadHandler := &AdHandler{\r\n\t\tclient: mockGrpcClient,\r\n\t\tutils: utilsMock,\r\n\t}\r\n\r\n\treq := httptest.NewRequest(http.MethodGet, \"/housing\", nil)\r\n\tw := httptest.NewRecorder()\r\n\r\n\t// Выполнение метода\r\n\tadHandler.GetAllPlaces(w, req)\r\n\r\n\t// Проверка результата\r\n\tresult := w.Result()\r\n\tdefer func(Body io.ReadCloser) {\r\n\t\terr := Body.Close()\r\n\t\tif err != nil {\r\n\t\t\treturn\r\n\t\t}\r\n\t}(result.Body)\r\n\r\n\tassert.Equal(t, http.StatusInternalServerError, result.StatusCode)\r\n\tmockGrpcClient.AssertExpectations(t)\r\n\tutilsMock.AssertExpectations(t)\r\n}\r\n
+Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
+<+>UTF-8
+===================================================================
+diff --git a/internal/ads/controller/ads_controller_test.go b/internal/ads/controller/ads_controller_test.go
+--- a/internal/ads/controller/ads_controller_test.go (revision f68dbe94cca631281d0d1496863bdf9efcda8dd8)
++++ b/internal/ads/controller/ads_controller_test.go (date 1734480849971)
+@@ -1,16 +1,23 @@
+ package controller
+
+ import (
++ "2024_2_FIGHT-CLUB/domain"
+ "2024_2_FIGHT-CLUB/internal/service/logger"
++ "2024_2_FIGHT-CLUB/internal/service/utils"
+ "2024_2_FIGHT-CLUB/microservices/ads_service/controller/gen"
+ "2024_2_FIGHT-CLUB/microservices/ads_service/mocks"
++ "bytes"
++ "context"
++ "encoding/json"
+ "errors"
++ "github.com/gorilla/mux"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/mock"
+ "github.com/stretchr/testify/require"
+ "google.golang.org/grpc/codes"
+ "google.golang.org/grpc/status"
+ "io"
++ "mime/multipart"
+ "net/http"
+ "net/http/httptest"
+ "testing"
+@@ -29,9 +36,14 @@
+ mockGrpcClient := new(mocks.MockGrpcClient)
+ testResponse := &gen.GetAllAdsResponseList{}
+ mockGrpcClient.On("GetAllPlaces", mock.Anything, mock.Anything, mock.Anything).Return(testResponse, nil)
++ response := &gen.GetAllAdsResponseList{}
++ utilsMock := &utils.MockUtils{}
++ utilsMock.On("ConvertGetAllAdsResponseProtoToGo", response).
++ Return(nil, nil)
+
+ adHandler := &AdHandler{
+ client: mockGrpcClient,
++ utils: utilsMock,
+ }
+
+ req := httptest.NewRequest(http.MethodGet, "/housing?location=test", nil)
+@@ -101,7 +113,7 @@
+ mockGrpcClient.On("GetAllPlaces", mock.Anything, mock.Anything, mock.Anything).
+ Return(response, nil)
+
+- utilsMock := &mocks.MockUtils{}
++ utilsMock := &utils.MockUtils{}
+ utilsMock.On("ConvertGetAllAdsResponseProtoToGo", response).
+ Return(nil, errors.New("conversion error"))
+
+@@ -129,3 +141,1533 @@
+ mockGrpcClient.AssertExpectations(t)
+ utilsMock.AssertExpectations(t)
+ }
++
++func TestAdHandler_GetOnePlace_Success(t *testing.T) {
++ // Инициализация логгера
++ require.NoError(t, logger.InitLoggers())
++ defer func() {
++ require.NoError(t, logger.SyncLoggers())
++ }()
++
++ mockGrpcClient := new(mocks.MockGrpcClient)
++ mockSessionService := &mocks.MockServiceSession{}
++ mockUtils := new(utils.MockUtils)
++
++ testResponse := &gen.GetAllAdsResponse{}
++ mockGrpcClient.On("GetOnePlace", mock.Anything, mock.Anything, mock.Anything).Return(testResponse, nil)
++
++ convertedResponse := &domain.GetAllAdsResponse{}
++ mockUtils.On("ConvertAdProtoToGo", testResponse).Return(convertedResponse, nil)
++
++ mockSessionService.MockGetUserID = func(ctx context.Context, sessionID string) (string, error) {
++ return "123", nil
++ }
++
++ adHandler := &AdHandler{
++ client: mockGrpcClient,
++ sessionService: mockSessionService,
++ utils: mockUtils,
++ }
++
++ req := httptest.NewRequest(http.MethodGet, "/housing/123", nil)
++ req = mux.SetURLVars(req, map[string]string{"adId": "123"})
++ req.Header.Set("X-Real-IP", "127.0.0.1")
++ w := httptest.NewRecorder()
++
++ adHandler.GetOnePlace(w, req)
++
++ result := w.Result()
++ defer func(Body io.ReadCloser) {
++ err := Body.Close()
++ if err != nil {
++ return
++ }
++ }(result.Body)
++
++ assert.Equal(t, http.StatusOK, result.StatusCode)
++ mockGrpcClient.AssertExpectations(t)
++ mockUtils.AssertExpectations(t)
++}
++
++func TestAdHandler_GetOnePlace_GrpcError(t *testing.T) {
++ require.NoError(t, logger.InitLoggers())
++ defer func() {
++ err := logger.SyncLoggers()
++ if err != nil {
++ return
++ }
++ }()
++
++ mockGrpcClient := new(mocks.MockGrpcClient)
++ mockSessionService := &mocks.MockServiceSession{}
++
++ grpcErr := status.Error(codes.Internal, "Simulated gRPC Error")
++ mockGrpcClient.On("GetOnePlace", mock.Anything, mock.Anything, mock.Anything).Return((*gen.GetAllAdsResponse)(nil), grpcErr)
++
++ mockSessionService.MockGetUserID = func(ctx context.Context, sessionID string) (string, error) {
++ return "123", nil
++ }
++
++ adHandler := &AdHandler{
++ client: mockGrpcClient,
++ sessionService: mockSessionService,
++ }
++
++ req := httptest.NewRequest(http.MethodGet, "/housing/123", nil)
++ req = mux.SetURLVars(req, map[string]string{"adId": "123"})
++ req.Header.Set("X-Real-IP", "127.0.0.1")
++ w := httptest.NewRecorder()
++
++ adHandler.GetOnePlace(w, req)
++
++ result := w.Result()
++ defer func(Body io.ReadCloser) {
++ err := Body.Close()
++ if err != nil {
++ return
++ }
++ }(result.Body)
++
++ assert.Equal(t, http.StatusInternalServerError, result.StatusCode)
++ mockGrpcClient.AssertExpectations(t)
++}
++
++func TestAdHandler_GetOnePlace_ConvertError(t *testing.T) {
++ require.NoError(t, logger.InitLoggers())
++ defer func() {
++ err := logger.SyncLoggers()
++ if err != nil {
++ return
++ }
++ }()
++
++ mockGrpcClient := new(mocks.MockGrpcClient)
++ mockSessionService := &mocks.MockServiceSession{}
++ mockUtils := new(utils.MockUtils)
++
++ testResponse := &gen.GetAllAdsResponse{}
++ mockGrpcClient.On("GetOnePlace", mock.Anything, mock.Anything, mock.Anything).Return(testResponse, nil)
++
++ mockUtils.On("ConvertAdProtoToGo", testResponse).Return(nil, errors.New("conversion error"))
++
++ mockSessionService.MockGetUserID = func(ctx context.Context, sessionID string) (string, error) {
++ return "123", nil
++ }
++
++ adHandler := &AdHandler{
++ client: mockGrpcClient,
++ sessionService: mockSessionService,
++ utils: mockUtils,
++ }
++
++ req := httptest.NewRequest(http.MethodGet, "/housing/123", nil)
++ req = mux.SetURLVars(req, map[string]string{"adId": "123"})
++ req.Header.Set("X-Real-IP", "127.0.0.1")
++ w := httptest.NewRecorder()
++
++ adHandler.GetOnePlace(w, req)
++
++ result := w.Result()
++ defer func(Body io.ReadCloser) {
++ err := Body.Close()
++ if err != nil {
++ return
++ }
++ }(result.Body)
++
++ assert.Equal(t, http.StatusInternalServerError, result.StatusCode)
++ mockGrpcClient.AssertExpectations(t)
++ mockUtils.AssertExpectations(t)
++}
++
++func TestAdHandler_CreatePlace_Success(t *testing.T) {
++ require.NoError(t, logger.InitLoggers())
++ defer func() {
++ err := logger.SyncLoggers()
++ if err != nil {
++ return
++ }
++ }()
++ mockClient := new(mocks.MockGrpcClient)
++ handler := &AdHandler{
++ client: mockClient,
++ }
++
++ // Создаем multipart запрос с метаданными и файлами
++ body := new(bytes.Buffer)
++ writer := multipart.NewWriter(body)
++
++ // Добавляем JSON metadata
++ metadata := `{
++ "CityName": "TestCity",
++ "Description": "Test Description",
++ "Address": "Test Address",
++ "RoomsNumber": 2,
++ "SquareMeters": 60,
++ "Floor": 3,
++ "BuildingType": "Apartment",
++ "HasBalcony": true
++ }`
++ _ = writer.WriteField("metadata", metadata)
++
++ part, _ := writer.CreateFormFile("images", "image1.jpg")
++ _, err := part.Write([]byte("mock image data"))
++ if err != nil {
++ return
++ }
++
++ err = writer.Close()
++ if err != nil {
++ return
++ }
++
++ req := httptest.NewRequest("POST", "/housing", body)
++ req.Header.Set("Content-Type", writer.FormDataContentType())
++ req.Header.Set("X-CSRF-Token", "test-token")
++ req.Header.Set("X-Real-IP", "127.0.0.1")
++ req.AddCookie(&http.Cookie{
++ Name: "session_id",
++ Value: "test-session-id",
++ })
++
++ w := httptest.NewRecorder()
++
++ mockClient.On("CreatePlace", mock.Anything, mock.Anything, mock.Anything).Return(&gen.Ad{}, nil)
++
++ handler.CreatePlace(w, req)
++
++ resp := w.Result()
++ defer func(Body io.ReadCloser) {
++ err := Body.Close()
++ if err != nil {
++ return
++ }
++ }(resp.Body)
++
++ require.Equal(t, http.StatusCreated, resp.StatusCode)
++ bodyResponse, _ := io.ReadAll(resp.Body)
++ require.Contains(t, string(bodyResponse), "Successfully created ad")
++
++ mockClient.AssertExpectations(t)
++}
++
++func TestAdHandler_CreatePlace_FailedToReadFile(t *testing.T) {
++ require.NoError(t, logger.InitLoggers())
++ defer func() {
++ err := logger.SyncLoggers()
++ if err != nil {
++ return
++ }
++ }()
++ mockClient := new(mocks.MockGrpcClient)
++ handler := &AdHandler{
++ client: mockClient,
++ }
++
++ body := new(bytes.Buffer)
++ writer := multipart.NewWriter(body)
++ _, err := writer.CreateFormFile("images", "image1.jpg")
++ if err != nil {
++ return
++ }
++
++ err = writer.Close()
++ if err != nil {
++ return
++ }
++
++ req := httptest.NewRequest("POST", "/ads/create", body)
++ req.Header.Set("Content-Type", writer.FormDataContentType())
++ req.AddCookie(&http.Cookie{
++ Name: "session_id",
++ Value: "test-session-id",
++ })
++
++ w := httptest.NewRecorder()
++
++ // Выполнение метода
++ handler.CreatePlace(w, req)
++
++ resp := w.Result()
++ defer func(Body io.ReadCloser) {
++ err := Body.Close()
++ if err != nil {
++ return
++ }
++ }(resp.Body)
++
++ require.Equal(t, http.StatusInternalServerError, resp.StatusCode)
++}
++
++func TestAdHandler_CreatePlace_CreatePlaceFailure(t *testing.T) {
++ require.NoError(t, logger.InitLoggers())
++ defer func() {
++ err := logger.SyncLoggers()
++ if err != nil {
++ return
++ }
++ }()
++ mockClient := new(mocks.MockGrpcClient)
++ handler := &AdHandler{
++ client: mockClient,
++ }
++
++ // Создаем multipart запрос с метаданными и файлами
++ body := new(bytes.Buffer)
++ writer := multipart.NewWriter(body)
++ _ = writer.WriteField("metadata", `{"CityName":"TestCity"}`)
++ part, _ := writer.CreateFormFile("images", "image1.jpg")
++ _, err := part.Write([]byte("mock image data"))
++ if err != nil {
++ return
++ }
++ err = writer.Close()
++ if err != nil {
++ return
++ }
++
++ req := httptest.NewRequest("POST", "/ads/create", body)
++ req.Header.Set("Content-Type", writer.FormDataContentType())
++ req.AddCookie(&http.Cookie{
++ Name: "session_id",
++ Value: "test-session-id",
++ })
++
++ w := httptest.NewRecorder()
++ grpcErr := status.Error(codes.Internal, "Simulated gRPC Error")
++ mockClient.On("CreatePlace", mock.Anything, mock.Anything, mock.Anything).Return(&gen.Ad{}, grpcErr)
++
++ handler.CreatePlace(w, req)
++
++ resp := w.Result()
++ defer func(Body io.ReadCloser) {
++ err := Body.Close()
++ if err != nil {
++ return
++ }
++ }(resp.Body)
++
++ require.Equal(t, http.StatusInternalServerError, resp.StatusCode)
++ mockClient.AssertExpectations(t)
++}
++
++func TestAdHandler_CreatePlace_NoCookie(t *testing.T) {
++ require.NoError(t, logger.InitLoggers())
++ defer func() {
++ err := logger.SyncLoggers()
++ if err != nil {
++ return
++ }
++ }()
++ mockClient := new(mocks.MockGrpcClient)
++ handler := &AdHandler{
++ client: mockClient,
++ }
++
++ // Создаем multipart запрос с метаданными и файлами
++ body := new(bytes.Buffer)
++ writer := multipart.NewWriter(body)
++ _ = writer.WriteField("metadata", `{"CityName":"TestCity"}`)
++ part, _ := writer.CreateFormFile("images", "image1.jpg")
++ _, err := part.Write([]byte("mock image data"))
++ if err != nil {
++ return
++ }
++ err = writer.Close()
++ if err != nil {
++ return
++ }
++
++ req := httptest.NewRequest("POST", "/ads/create", body)
++ req.Header.Set("Content-Type", writer.FormDataContentType())
++
++ w := httptest.NewRecorder()
++
++ handler.CreatePlace(w, req)
++
++ resp := w.Result()
++ defer func(Body io.ReadCloser) {
++ err := Body.Close()
++ if err != nil {
++ return
++ }
++ }(resp.Body)
++
++ require.Equal(t, http.StatusInternalServerError, resp.StatusCode)
++ mockClient.AssertExpectations(t)
++}
++
++func TestAdHandler_UpdatePlace_Success(t *testing.T) {
++ require.NoError(t, logger.InitLoggers())
++ defer func() {
++ err := logger.SyncLoggers()
++ if err != nil {
++ return
++ }
++ }()
++
++ mockClient := new(mocks.MockGrpcClient)
++ handler := &AdHandler{client: mockClient}
++
++ body := new(bytes.Buffer)
++ writer := multipart.NewWriter(body)
++
++ metadata := `{
++ "CityName": "TestCity",
++ "Description": "Updated Description",
++ "Address": "Test Address",
++ "RoomsNumber": 3,
++ "SquareMeters": 70,
++ "Floor": 4,
++ "BuildingType": "Apartment",
++ "HasBalcony": false
++ }`
++
++ _ = writer.WriteField("metadata", metadata)
++
++ part, _ := writer.CreateFormFile("images", "image1.jpg")
++ _, err := part.Write([]byte("mock image data"))
++ require.NoError(t, err)
++
++ err = writer.Close()
++ require.NoError(t, err)
++
++ req := httptest.NewRequest("PUT", "/housing/123", body)
++ req.Header.Set("Content-Type", writer.FormDataContentType())
++ req.Header.Set("X-CSRF-Token", "test-token")
++ req.Header.Set("X-Real-IP", "127.0.0.1")
++ req.AddCookie(&http.Cookie{Name: "session_id", Value: "test-session-id"})
++
++ w := httptest.NewRecorder()
++
++ mockClient.On("UpdatePlace", mock.Anything, mock.Anything, mock.Anything).Return(&gen.AdResponse{}, nil)
++
++ handler.UpdatePlace(w, req)
++
++ resp := w.Result()
++ defer resp.Body.Close()
++
++ require.Equal(t, http.StatusOK, resp.StatusCode)
++ bodyResponse, _ := io.ReadAll(resp.Body)
++ require.Contains(t, string(bodyResponse), "Successfully updated ad")
++
++ mockClient.AssertExpectations(t)
++}
++
++func TestAdHandler_UpdatePlace_ParseMultipartError(t *testing.T) {
++ require.NoError(t, logger.InitLoggers())
++ defer func() {
++ err := logger.SyncLoggers()
++ if err != nil {
++ return
++ }
++ }()
++
++ handler := &AdHandler{client: new(mocks.MockGrpcClient)}
++
++ req := httptest.NewRequest("PUT", "/housing/123", bytes.NewBufferString("invalid body"))
++ req.Header.Set("Content-Type", "multipart/form-data")
++
++ w := httptest.NewRecorder()
++
++ handler.UpdatePlace(w, req)
++
++ resp := w.Result()
++ defer func(Body io.ReadCloser) {
++ err := Body.Close()
++ if err != nil {
++ return
++ }
++ }(resp.Body)
++
++ require.Equal(t, http.StatusBadRequest, resp.StatusCode)
++}
++
++func TestAdHandler_UpdatePlace_MetadataDecodeError(t *testing.T) {
++ require.NoError(t, logger.InitLoggers())
++ defer func() {
++ err := logger.SyncLoggers()
++ if err != nil {
++ return
++ }
++ }()
++
++ handler := &AdHandler{client: new(mocks.MockGrpcClient)}
++
++ body := new(bytes.Buffer)
++ writer := multipart.NewWriter(body)
++ _ = writer.WriteField("metadata", "invalid json")
++
++ _ = writer.Close()
++
++ req := httptest.NewRequest("PUT", "/housing/123", body)
++ req.Header.Set("Content-Type", writer.FormDataContentType())
++ req.AddCookie(&http.Cookie{Name: "session_id", Value: "test-session-id"})
++
++ w := httptest.NewRecorder()
++
++ handler.UpdatePlace(w, req)
++
++ resp := w.Result()
++ defer func(Body io.ReadCloser) {
++ err := Body.Close()
++ if err != nil {
++ return
++ }
++ }(resp.Body)
++
++ require.Equal(t, http.StatusBadRequest, resp.StatusCode)
++}
++
++func TestAdHandler_UpdatePlace_SessionIDError(t *testing.T) {
++ require.NoError(t, logger.InitLoggers())
++ defer func() {
++ err := logger.SyncLoggers()
++ if err != nil {
++ return
++ }
++ }()
++
++ handler := &AdHandler{client: new(mocks.MockGrpcClient)}
++
++ body := new(bytes.Buffer)
++ writer := multipart.NewWriter(body)
++ _ = writer.WriteField("metadata", "{}")
++ _ = writer.Close()
++
++ req := httptest.NewRequest("PUT", "/housing/123", body)
++ req.Header.Set("Content-Type", writer.FormDataContentType())
++
++ w := httptest.NewRecorder()
++
++ handler.UpdatePlace(w, req)
++
++ resp := w.Result()
++ defer func(Body io.ReadCloser) {
++ err := Body.Close()
++ if err != nil {
++ return
++ }
++ }(resp.Body)
++
++ require.Equal(t, http.StatusInternalServerError, resp.StatusCode)
++}
++
++func TestAdHandler_UpdatePlace_GRPCError(t *testing.T) {
++ require.NoError(t, logger.InitLoggers())
++ defer func() {
++ err := logger.SyncLoggers()
++ if err != nil {
++ return
++ }
++ }()
++
++ mockClient := new(mocks.MockGrpcClient)
++ handler := &AdHandler{client: mockClient}
++
++ body := new(bytes.Buffer)
++ writer := multipart.NewWriter(body)
++
++ metadata := `{"CityName": "TestCity"}`
++ _ = writer.WriteField("metadata", metadata)
++ _ = writer.Close()
++
++ req := httptest.NewRequest("PUT", "/housing/123", body)
++ req.Header.Set("Content-Type", writer.FormDataContentType())
++ req.AddCookie(&http.Cookie{Name: "session_id", Value: "test-session-id"})
++
++ w := httptest.NewRecorder()
++ grpcErr := status.Error(codes.Internal, "Simulated gRPC Error")
++ mockClient.On("UpdatePlace", mock.Anything, mock.Anything, mock.Anything).Return(&gen.AdResponse{}, grpcErr)
++
++ handler.UpdatePlace(w, req)
++
++ resp := w.Result()
++ defer func(Body io.ReadCloser) {
++ err := Body.Close()
++ if err != nil {
++ return
++ }
++ }(resp.Body)
++
++ require.Equal(t, http.StatusInternalServerError, resp.StatusCode)
++}
++
++func TestAdHandler_DeletePlace_Success(t *testing.T) {
++ require.NoError(t, logger.InitLoggers())
++ defer func() {
++ err := logger.SyncLoggers()
++ if err != nil {
++ return
++ }
++ }()
++
++ mockClient := new(mocks.MockGrpcClient)
++ handler := &AdHandler{client: mockClient}
++
++ req := httptest.NewRequest("DELETE", "/housing/123", nil)
++ req.Header.Set("X-CSRF-Token", "test-token")
++ req.AddCookie(&http.Cookie{Name: "session_id", Value: "test-session-id"})
++
++ w := httptest.NewRecorder()
++
++ mockClient.On("DeletePlace", mock.Anything, mock.Anything, mock.Anything).Return(&gen.DeleteResponse{}, nil)
++
++ handler.DeletePlace(w, req)
++
++ resp := w.Result()
++ defer func(Body io.ReadCloser) {
++ err := Body.Close()
++ if err != nil {
++ return
++ }
++ }(resp.Body)
++
++ require.Equal(t, http.StatusOK, resp.StatusCode)
++ require.Contains(t, w.Body.String(), "Successfully deleted place")
++
++ mockClient.AssertExpectations(t)
++}
++
++func TestAdHandler_DeletePlace_FailedToGetSessionID(t *testing.T) {
++ require.NoError(t, logger.InitLoggers())
++ defer func() {
++ err := logger.SyncLoggers()
++ if err != nil {
++ return
++ }
++ }()
++ mockClient := new(mocks.MockGrpcClient)
++ handler := &AdHandler{client: mockClient}
++
++ req := httptest.NewRequest("DELETE", "/housing/123", nil)
++ req.Header.Set("X-CSRF-Token", "test-token")
++
++ w := httptest.NewRecorder()
++
++ handler.DeletePlace(w, req)
++
++ resp := w.Result()
++ defer func(Body io.ReadCloser) {
++ err := Body.Close()
++ if err != nil {
++ return
++ }
++ }(resp.Body)
++
++ require.Equal(t, http.StatusInternalServerError, resp.StatusCode)
++ require.Contains(t, w.Body.String(), "failed to get session id from request cookie")
++
++ mockClient.AssertExpectations(t)
++}
++
++func TestAdHandler_DeletePlace_ClientError(t *testing.T) {
++ require.NoError(t, logger.InitLoggers())
++ defer func() {
++ err := logger.SyncLoggers()
++ if err != nil {
++ return
++ }
++ }()
++ mockClient := new(mocks.MockGrpcClient)
++ handler := &AdHandler{client: mockClient}
++
++ req := httptest.NewRequest("DELETE", "/housing/123", nil)
++ req.Header.Set("X-CSRF-Token", "test-token")
++ req.AddCookie(&http.Cookie{Name: "session_id", Value: "test-session-id"})
++
++ w := httptest.NewRecorder()
++ grpcErr := status.Error(codes.Internal, "failed to delete place")
++ mockClient.On("DeletePlace", mock.Anything, mock.Anything, mock.Anything).Return(&gen.DeleteResponse{}, grpcErr)
++
++ handler.DeletePlace(w, req)
++
++ resp := w.Result()
++ defer func(Body io.ReadCloser) {
++ err := Body.Close()
++ if err != nil {
++ return
++ }
++ }(resp.Body)
++
++ require.Equal(t, http.StatusInternalServerError, resp.StatusCode)
++ require.Contains(t, w.Body.String(), "failed to delete place")
++
++ mockClient.AssertExpectations(t)
++}
++
++func TestAdHandler_GetPlacesPerCity_Success(t *testing.T) {
++ require.NoError(t, logger.InitLoggers())
++ defer func() {
++ err := logger.SyncLoggers()
++ if err != nil {
++ return
++ }
++ }()
++
++ mockClient := new(mocks.MockGrpcClient)
++ utilsMock := &utils.MockUtils{}
++ handler := &AdHandler{client: mockClient, utils: utilsMock}
++
++ req := httptest.NewRequest("GET", "/housing/city/TestCity", nil)
++ req.Header.Set("X-CSRF-Token", "test-token")
++
++ w := httptest.NewRecorder()
++
++ mockClient.On("GetPlacesPerCity", mock.Anything, mock.Anything, mock.Anything).
++ Return(&gen.GetAllAdsResponseList{}, nil)
++
++ utilsMock.On("ConvertGetAllAdsResponseProtoToGo", mock.Anything).
++ Return([]domain.GetAllAdsListResponse{}, nil)
++
++ handler.GetPlacesPerCity(w, req)
++
++ result := w.Result()
++ defer func(Body io.ReadCloser) {
++ err := Body.Close()
++ if err != nil {
++ return
++ }
++ }(result.Body)
++
++ assert.Equal(t, http.StatusOK, result.StatusCode)
++
++ mockClient.AssertExpectations(t)
++ utilsMock.AssertExpectations(t)
++}
++
++func TestAdHandler_GetPlacesPerCity_GrpcError(t *testing.T) {
++ require.NoError(t, logger.InitLoggers())
++ defer func() {
++ err := logger.SyncLoggers()
++ if err != nil {
++ return
++ }
++ }()
++
++ mockClient := new(mocks.MockGrpcClient)
++ handler := &AdHandler{client: mockClient}
++
++ req := httptest.NewRequest("GET", "/housing/city/TestCity", nil)
++ req.Header.Set("X-CSRF-Token", "test-token")
++
++ w := httptest.NewRecorder()
++ grpcError := status.Error(codes.Internal, "failed to get place per city")
++ mockClient.On("GetPlacesPerCity", mock.Anything, mock.Anything, mock.Anything).
++ Return(&gen.GetAllAdsResponseList{}, grpcError)
++
++ handler.GetPlacesPerCity(w, req)
++
++ result := w.Result()
++ defer func(Body io.ReadCloser) {
++ err := Body.Close()
++ if err != nil {
++ return
++ }
++ }(result.Body)
++
++ assert.Equal(t, http.StatusInternalServerError, result.StatusCode)
++ mockClient.AssertExpectations(t)
++}
++
++func TestAdHandler_GetPlacesPerCity_ConvertError(t *testing.T) {
++ require.NoError(t, logger.InitLoggers())
++ defer func() {
++ err := logger.SyncLoggers()
++ if err != nil {
++ return
++ }
++ }()
++
++ mockClient := new(mocks.MockGrpcClient)
++ utilsMock := &utils.MockUtils{}
++ handler := &AdHandler{client: mockClient, utils: utilsMock}
++
++ req := httptest.NewRequest("GET", "/housing/city/TestCity", nil)
++ req.Header.Set("X-CSRF-Token", "test-token")
++
++ w := httptest.NewRecorder()
++
++ mockClient.On("GetPlacesPerCity", mock.Anything, mock.Anything, mock.Anything).
++ Return(&gen.GetAllAdsResponseList{}, nil)
++
++ utilsMock.On("ConvertGetAllAdsResponseProtoToGo", mock.Anything).
++ Return([]domain.GetAllAdsListResponse{}, errors.New("conversion error"))
++
++ handler.GetPlacesPerCity(w, req)
++
++ result := w.Result()
++ defer func(Body io.ReadCloser) {
++ err := Body.Close()
++ if err != nil {
++ return
++ }
++ }(result.Body)
++
++ assert.Equal(t, http.StatusInternalServerError, result.StatusCode)
++
++ mockClient.AssertExpectations(t)
++ utilsMock.AssertExpectations(t)
++}
++
++func TestAdHandler_GetUserPlaces_Success(t *testing.T) {
++ require.NoError(t, logger.InitLoggers())
++ defer func() {
++ err := logger.SyncLoggers()
++ if err != nil {
++ return
++ }
++ }()
++
++ mockClient := new(mocks.MockGrpcClient)
++ mockUtils := &utils.MockUtils{}
++ handler := &AdHandler{client: mockClient, utils: mockUtils}
++
++ req := httptest.NewRequest("GET", "/housing/user/testUserId", nil)
++ req.Header.Set("X-CSRF-Token", "test-token")
++
++ w := httptest.NewRecorder()
++
++ mockClient.On("GetUserPlaces", mock.Anything, mock.Anything, mock.Anything).
++ Return(&gen.GetAllAdsResponseList{}, nil)
++
++ mockUtils.On("ConvertGetAllAdsResponseProtoToGo", mock.Anything).
++ Return([]domain.GetAllAdsListResponse{}, nil)
++
++ handler.GetUserPlaces(w, req)
++
++ resp := w.Result()
++ defer func(Body io.ReadCloser) {
++ err := Body.Close()
++ if err != nil {
++ return
++ }
++ }(resp.Body)
++
++ require.Equal(t, http.StatusOK, resp.StatusCode)
++
++ mockClient.AssertExpectations(t)
++ mockUtils.AssertExpectations(t)
++}
++
++func TestAdHandler_GetUserPlaces_GrpcError(t *testing.T) {
++ require.NoError(t, logger.InitLoggers())
++ defer func() {
++ err := logger.SyncLoggers()
++ if err != nil {
++ return
++ }
++ }()
++
++ mockClient := new(mocks.MockGrpcClient)
++ handler := &AdHandler{client: mockClient}
++
++ req := httptest.NewRequest("GET", "/housing/user/testUserId", nil)
++ req.Header.Set("X-CSRF-Token", "test-token")
++
++ w := httptest.NewRecorder()
++ grpcError := status.Error(codes.Internal, "failed to get place per city")
++ mockClient.On("GetUserPlaces", mock.Anything, mock.Anything, mock.Anything).
++ Return(&gen.GetAllAdsResponseList{}, grpcError)
++
++ handler.GetUserPlaces(w, req)
++
++ result := w.Result()
++ defer func(Body io.ReadCloser) {
++ err := Body.Close()
++ if err != nil {
++ return
++ }
++ }(result.Body)
++
++ assert.Equal(t, http.StatusInternalServerError, result.StatusCode)
++
++ mockClient.AssertExpectations(t)
++}
++
++func TestAdHandler_GetUserPlaces_ConvertError(t *testing.T) {
++ require.NoError(t, logger.InitLoggers())
++ defer func() {
++ err := logger.SyncLoggers()
++ if err != nil {
++ return
++ }
++ }()
++
++ mockClient := new(mocks.MockGrpcClient)
++ utilsMock := &utils.MockUtils{}
++ handler := &AdHandler{client: mockClient, utils: utilsMock}
++
++ req := httptest.NewRequest("GET", "/housing/user/testUserId", nil)
++ req.Header.Set("X-CSRF-Token", "test-token")
++
++ w := httptest.NewRecorder()
++
++ mockClient.On("GetUserPlaces", mock.Anything, mock.Anything, mock.Anything).
++ Return(&gen.GetAllAdsResponseList{}, nil)
++
++ utilsMock.On("ConvertGetAllAdsResponseProtoToGo", mock.Anything).
++ Return([]domain.GetAllAdsListResponse{}, errors.New("conversion error"))
++
++ handler.GetUserPlaces(w, req)
++
++ result := w.Result()
++ defer func(Body io.ReadCloser) {
++ err := Body.Close()
++ if err != nil {
++ return
++ }
++ }(result.Body)
++
++ assert.Equal(t, http.StatusInternalServerError, result.StatusCode)
++
++ mockClient.AssertExpectations(t)
++ utilsMock.AssertExpectations(t)
++}
++
++func TestAdHandler_DeleteAdImage_Success(t *testing.T) {
++ require.NoError(t, logger.InitLoggers())
++ defer func() {
++ err := logger.SyncLoggers()
++ if err != nil {
++ return
++ }
++ }()
++
++ mockClient := new(mocks.MockGrpcClient)
++
++ handler := &AdHandler{client: mockClient}
++
++ req := httptest.NewRequest("DELETE", "/housing/{adId}/images/{imageId}", nil)
++ req.Header.Set("X-CSRF-Token", "test-token")
++ req.AddCookie(&http.Cookie{
++ Name: "session_id",
++ Value: "test-session-id",
++ })
++
++ w := httptest.NewRecorder()
++
++ mockClient.On("DeleteAdImage", mock.Anything, mock.Anything, mock.Anything).
++ Return(&gen.DeleteResponse{}, nil)
++
++ handler.DeleteAdImage(w, req)
++
++ resp := w.Result()
++ defer func(Body io.ReadCloser) {
++ err := Body.Close()
++ if err != nil {
++ return
++ }
++ }(resp.Body)
++
++ require.Equal(t, http.StatusOK, resp.StatusCode)
++ require.Contains(t, w.Body.String(), "\"message\":\"Successfully deleted ad image\"")
++
++ mockClient.AssertExpectations(t)
++}
++
++func TestAdHandler_DeleteAdImage_FailedToGetSessionID(t *testing.T) {
++ require.NoError(t, logger.InitLoggers())
++ defer func() {
++ err := logger.SyncLoggers()
++ if err != nil {
++ return
++ }
++ }()
++
++ handler := &AdHandler{}
++
++ req := httptest.NewRequest("DELETE", "/housing/{adId}/images/{imageId}", nil)
++ req.Header.Set("X-CSRF-Token", "test-token")
++
++ w := httptest.NewRecorder()
++
++ handler.DeleteAdImage(w, req)
++
++ resp := w.Result()
++ defer func(Body io.ReadCloser) {
++ err := Body.Close()
++ if err != nil {
++ return
++ }
++ }(resp.Body)
++
++ require.Equal(t, http.StatusInternalServerError, resp.StatusCode)
++ require.Contains(t, w.Body.String(), "failed to get session id from request cookie")
++}
++
++func TestAdHandler_DeleteAdImage_GrpcError(t *testing.T) {
++ require.NoError(t, logger.InitLoggers())
++ defer func() {
++ err := logger.SyncLoggers()
++ if err != nil {
++ return
++ }
++ }()
++
++ mockClient := new(mocks.MockGrpcClient)
++
++ handler := &AdHandler{client: mockClient}
++
++ req := httptest.NewRequest("DELETE", "/housing/{adId}/images/{imageId}", nil)
++ req.Header.Set("X-CSRF-Token", "test-token")
++ req.AddCookie(&http.Cookie{
++ Name: "session_id",
++ Value: "test-session-id",
++ })
++
++ w := httptest.NewRecorder()
++ grpcError := status.Error(codes.Internal, "failed to get place per city")
++ mockClient.On("DeleteAdImage", mock.Anything, mock.Anything, mock.Anything).
++ Return(&gen.DeleteResponse{}, grpcError)
++
++ handler.DeleteAdImage(w, req)
++
++ result := w.Result()
++ defer func(Body io.ReadCloser) {
++ err := Body.Close()
++ if err != nil {
++ return
++ }
++ }(result.Body)
++
++ assert.Equal(t, http.StatusInternalServerError, result.StatusCode)
++ require.Contains(t, w.Body.String(), "\"error\":\"failed to get place per city\"")
++
++ mockClient.AssertExpectations(t)
++}
++
++func TestAdHandler_AddToFavorites_Success(t *testing.T) {
++ require.NoError(t, logger.InitLoggers())
++ defer func() {
++ err := logger.SyncLoggers()
++ if err != nil {
++ return
++ }
++ }()
++
++ mockClient := new(mocks.MockGrpcClient)
++ handler := &AdHandler{client: mockClient}
++
++ req := httptest.NewRequest("POST", "/housing/{adId}/like", nil)
++ req.Header.Set("X-CSRF-Token", "test-token")
++ req.AddCookie(&http.Cookie{
++ Name: "session_id",
++ Value: "test-session-id",
++ })
++
++ w := httptest.NewRecorder()
++
++ mockClient.On("AddToFavorites", mock.Anything, mock.Anything, mock.Anything).
++ Return(&gen.AdResponse{}, nil)
++
++ handler.AddToFavorites(w, req)
++
++ resp := w.Result()
++ defer func(Body io.ReadCloser) {
++ err := Body.Close()
++ if err != nil {
++ return
++ }
++ }(resp.Body)
++
++ require.Equal(t, http.StatusOK, resp.StatusCode)
++ require.Contains(t, w.Body.String(), "\"message\":\"Successfully added to favorites\"")
++
++ mockClient.AssertExpectations(t)
++}
++
++func TestAdHandler_AddToFavorites_FailedToGetSessionID(t *testing.T) {
++ require.NoError(t, logger.InitLoggers())
++ defer func() {
++ err := logger.SyncLoggers()
++ if err != nil {
++ return
++ }
++ }()
++
++ handler := &AdHandler{}
++
++ req := httptest.NewRequest("POST", "/housing/{adId}/like", nil)
++ req.Header.Set("X-CSRF-Token", "test-token")
++
++ w := httptest.NewRecorder()
++
++ handler.AddToFavorites(w, req)
++
++ resp := w.Result()
++ defer func(Body io.ReadCloser) {
++ err := Body.Close()
++ if err != nil {
++ return
++ }
++ }(resp.Body)
++
++ require.Equal(t, http.StatusInternalServerError, resp.StatusCode)
++ require.Contains(t, w.Body.String(), "failed to get session id from request cookie")
++
++}
++
++func TestAdHandler_AddToFavorites_GrpcError(t *testing.T) {
++ require.NoError(t, logger.InitLoggers())
++ defer func() {
++ err := logger.SyncLoggers()
++ if err != nil {
++ return
++ }
++ }()
++
++ mockClient := new(mocks.MockGrpcClient)
++ handler := &AdHandler{client: mockClient}
++
++ req := httptest.NewRequest("POST", "/housing/{adId}/like", nil)
++ req.Header.Set("X-CSRF-Token", "test-token")
++ req.AddCookie(&http.Cookie{
++ Name: "session_id",
++ Value: "test-session-id",
++ })
++
++ w := httptest.NewRecorder()
++ grpcError := status.Error(codes.Internal, "failed to add to favorites")
++ mockClient.On("AddToFavorites", mock.Anything, mock.Anything, mock.Anything).
++ Return(&gen.AdResponse{}, grpcError)
++
++ handler.AddToFavorites(w, req)
++
++ result := w.Result()
++ defer func(Body io.ReadCloser) {
++ err := Body.Close()
++ if err != nil {
++ return
++ }
++ }(result.Body)
++
++ assert.Equal(t, http.StatusInternalServerError, result.StatusCode)
++ require.Contains(t, w.Body.String(), "\"error\":\"failed to add to favorites\"")
++
++ mockClient.AssertExpectations(t)
++}
++
++func TestAdHandler_DeleteFromFavorites_Success(t *testing.T) {
++ require.NoError(t, logger.InitLoggers())
++ defer func() {
++ err := logger.SyncLoggers()
++ if err != nil {
++ return
++ }
++ }()
++
++ mockClient := new(mocks.MockGrpcClient)
++ handler := &AdHandler{client: mockClient}
++
++ req := httptest.NewRequest("DELETE", "/housing/{adId}/dislike", nil)
++ req.Header.Set("X-CSRF-Token", "test-token")
++ req.AddCookie(&http.Cookie{
++ Name: "session_id",
++ Value: "test-session-id",
++ })
++
++ w := httptest.NewRecorder()
++
++ mockClient.On("DeleteFromFavorites", mock.Anything, mock.Anything, mock.Anything).
++ Return(&gen.AdResponse{}, nil)
++
++ handler.DeleteFromFavorites(w, req)
++
++ resp := w.Result()
++ defer func(Body io.ReadCloser) {
++ err := Body.Close()
++ if err != nil {
++ return
++ }
++ }(resp.Body)
++
++ require.Equal(t, http.StatusOK, resp.StatusCode)
++ require.Contains(t, w.Body.String(), "\"message\":\"Successfully deleted from favorites\"")
++
++ mockClient.AssertExpectations(t)
++}
++
++func TestAdHandler_DeleteFromFavorites_FailedToGetSessionID(t *testing.T) {
++ require.NoError(t, logger.InitLoggers())
++ defer func() {
++ err := logger.SyncLoggers()
++ if err != nil {
++ return
++ }
++ }()
++
++ handler := &AdHandler{}
++
++ req := httptest.NewRequest("POST", "/housing/{adId}/dislike", nil)
++ req.Header.Set("X-CSRF-Token", "test-token")
++
++ w := httptest.NewRecorder()
++
++ handler.DeleteFromFavorites(w, req)
++
++ resp := w.Result()
++ defer func(Body io.ReadCloser) {
++ err := Body.Close()
++ if err != nil {
++ return
++ }
++ }(resp.Body)
++
++ require.Equal(t, http.StatusInternalServerError, resp.StatusCode)
++ require.Contains(t, w.Body.String(), "failed to get session id from request cookie")
++
++}
++
++func TestAdHandler_DeleteFromFavorites_GrpcError(t *testing.T) {
++ require.NoError(t, logger.InitLoggers())
++ defer func() {
++ err := logger.SyncLoggers()
++ if err != nil {
++ return
++ }
++ }()
++
++ mockClient := new(mocks.MockGrpcClient)
++ handler := &AdHandler{client: mockClient}
++
++ req := httptest.NewRequest("POST", "/housing/{adId}/dislike", nil)
++ req.Header.Set("X-CSRF-Token", "test-token")
++ req.AddCookie(&http.Cookie{
++ Name: "session_id",
++ Value: "test-session-id",
++ })
++
++ w := httptest.NewRecorder()
++ grpcError := status.Error(codes.Internal, "failed to delete ad from favorites")
++ mockClient.On("DeleteFromFavorites", mock.Anything, mock.Anything, mock.Anything).
++ Return(&gen.AdResponse{}, grpcError)
++
++ handler.DeleteFromFavorites(w, req)
++
++ result := w.Result()
++ defer func(Body io.ReadCloser) {
++ err := Body.Close()
++ if err != nil {
++ return
++ }
++ }(result.Body)
++
++ assert.Equal(t, http.StatusInternalServerError, result.StatusCode)
++ require.Contains(t, w.Body.String(), "\"error\":\"failed to delete ad from favorites\"")
++
++ mockClient.AssertExpectations(t)
++}
++
++func TestAdHandler_GetUserFavorites_Success(t *testing.T) {
++ require.NoError(t, logger.InitLoggers())
++ defer func() {
++ err := logger.SyncLoggers()
++ if err != nil {
++ return
++ }
++ }()
++
++ mockClient := new(mocks.MockGrpcClient)
++ mockUtils := &utils.MockUtils{}
++ handler := &AdHandler{client: mockClient, utils: mockUtils}
++
++ req := httptest.NewRequest("GET", "/users/{userId}/favorites", nil)
++ req.Header.Set("X-CSRF-Token", "test-token")
++ req.AddCookie(&http.Cookie{
++ Name: "session_id",
++ Value: "test-session-id",
++ })
++
++ w := httptest.NewRecorder()
++
++ mockClient.On("GetUserFavorites", mock.Anything, mock.Anything, mock.Anything).
++ Return(&gen.GetAllAdsResponseList{}, nil)
++
++ mockUtils.On("ConvertGetAllAdsResponseProtoToGo", mock.Anything).
++ Return([]domain.GetAllAdsListResponse{}, nil)
++
++ handler.GetUserFavorites(w, req)
++
++ resp := w.Result()
++ defer func(Body io.ReadCloser) {
++ err := Body.Close()
++ if err != nil {
++ return
++ }
++ }(resp.Body)
++
++ require.Equal(t, http.StatusOK, resp.StatusCode)
++
++ mockClient.AssertExpectations(t)
++ mockUtils.AssertExpectations(t)
++}
++
++func TestAdHandler_GetUserFavorites_GrpcError(t *testing.T) {
++ require.NoError(t, logger.InitLoggers())
++ defer func() {
++ err := logger.SyncLoggers()
++ if err != nil {
++ return
++ }
++ }()
++
++ mockClient := new(mocks.MockGrpcClient)
++ handler := &AdHandler{client: mockClient}
++
++ req := httptest.NewRequest("GET", "/users/{userId}/favorites", nil)
++ req.Header.Set("X-CSRF-Token", "test-token")
++ req.AddCookie(&http.Cookie{
++ Name: "session_id",
++ Value: "test-session-id",
++ })
++
++ w := httptest.NewRecorder()
++ grpcError := status.Error(codes.Internal, "failed to user favorites")
++ mockClient.On("GetUserFavorites", mock.Anything, mock.Anything, mock.Anything).
++ Return(&gen.GetAllAdsResponseList{}, grpcError)
++
++ handler.GetUserFavorites(w, req)
++
++ result := w.Result()
++ defer func(Body io.ReadCloser) {
++ err := Body.Close()
++ if err != nil {
++ return
++ }
++ }(result.Body)
++
++ assert.Equal(t, http.StatusInternalServerError, result.StatusCode)
++
++ mockClient.AssertExpectations(t)
++}
++
++func TestAdHandler_GetUserFavorites_ConvertError(t *testing.T) {
++ require.NoError(t, logger.InitLoggers())
++ defer func() {
++ err := logger.SyncLoggers()
++ if err != nil {
++ return
++ }
++ }()
++
++ mockClient := new(mocks.MockGrpcClient)
++ utilsMock := &utils.MockUtils{}
++ handler := &AdHandler{client: mockClient, utils: utilsMock}
++
++ req := httptest.NewRequest("GET", "/users/{userId}/favorites", nil)
++ req.Header.Set("X-CSRF-Token", "test-token")
++ req.AddCookie(&http.Cookie{
++ Name: "session_id",
++ Value: "test-session-id",
++ })
++
++ w := httptest.NewRecorder()
++
++ mockClient.On("GetUserFavorites", mock.Anything, mock.Anything, mock.Anything).
++ Return(&gen.GetAllAdsResponseList{}, nil)
++
++ utilsMock.On("ConvertGetAllAdsResponseProtoToGo", mock.Anything).
++ Return([]domain.GetAllAdsListResponse{}, errors.New("conversion error"))
++
++ handler.GetUserFavorites(w, req)
++
++ result := w.Result()
++ defer func(Body io.ReadCloser) {
++ err := Body.Close()
++ if err != nil {
++ return
++ }
++ }(result.Body)
++
++ assert.Equal(t, http.StatusInternalServerError, result.StatusCode)
++
++ mockClient.AssertExpectations(t)
++ utilsMock.AssertExpectations(t)
++}
++
++func TestAdHandler_GetUserFavorites_FailedToGetSessionID(t *testing.T) {
++ require.NoError(t, logger.InitLoggers())
++ defer func() {
++ err := logger.SyncLoggers()
++ if err != nil {
++ return
++ }
++ }()
++
++ handler := &AdHandler{}
++
++ req := httptest.NewRequest("GET", "/users/{userId}/favorites", nil)
++ req.Header.Set("X-CSRF-Token", "test-token")
++
++ w := httptest.NewRecorder()
++
++ handler.GetUserFavorites(w, req)
++
++ resp := w.Result()
++ defer func(Body io.ReadCloser) {
++ err := Body.Close()
++ if err != nil {
++ return
++ }
++ }(resp.Body)
++
++ require.Equal(t, http.StatusInternalServerError, resp.StatusCode)
++ require.Contains(t, w.Body.String(), "failed to get session id from request cookie")
++}
++
++func TestAdHandler_UpdatePriorityWithPayment_Success(t *testing.T) {
++ require.NoError(t, logger.InitLoggers())
++ defer func() {
++ err := logger.SyncLoggers()
++ if err != nil {
++ return
++ }
++ }()
++
++ mockClient := new(mocks.MockGrpcClient)
++ handler := &AdHandler{client: mockClient}
++
++ card := domain.PaymentInfo{
++ DonationAmount: "100",
++ }
++ cardData, _ := json.Marshal(card)
++
++ req := httptest.NewRequest("POST", "/housing/{adId}/payment", bytes.NewReader(cardData))
++ req.Header.Set("X-CSRF-Token", "test-token")
++ req.AddCookie(&http.Cookie{
++ Name: "session_id",
++ Value: "test-session-id",
++ })
++ w := httptest.NewRecorder()
++
++ mockClient.On("UpdatePriority", mock.Anything, mock.Anything, mock.Anything).
++ Return(&gen.AdResponse{}, nil)
++
++ handler.UpdatePriorityWithPayment(w, req)
++
++ resp := w.Result()
++ defer func(Body io.ReadCloser) {
++ err := Body.Close()
++ if err != nil {
++ return
++ }
++ }(resp.Body)
++
++ require.Equal(t, http.StatusOK, resp.StatusCode)
++ require.Contains(t, w.Body.String(), "\"message\":\"Successfully update ad priority\"")
++
++ mockClient.AssertExpectations(t)
++}
++
++func TestAdHandler_UpdatePriorityWithPayment_DecodeError(t *testing.T) {
++ require.NoError(t, logger.InitLoggers())
++ defer func() {
++ err := logger.SyncLoggers()
++ if err != nil {
++ return
++ }
++ }()
++
++ mockClient := new(mocks.MockGrpcClient)
++ handler := &AdHandler{client: mockClient}
++
++ req := httptest.NewRequest("POST", "/housing/{adId}/payment", bytes.NewReader([]byte("invalid json")))
++ req.Header.Set("X-CSRF-Token", "test-token")
++ req.AddCookie(&http.Cookie{
++ Name: "session_id",
++ Value: "test-session-id",
++ })
++ w := httptest.NewRecorder()
++
++ handler.UpdatePriorityWithPayment(w, req)
++
++ resp := w.Result()
++ defer func(Body io.ReadCloser) {
++ err := Body.Close()
++ if err != nil {
++ return
++ }
++ }(resp.Body)
++
++ require.Equal(t, http.StatusInternalServerError, resp.StatusCode)
++
++ mockClient.AssertExpectations(t)
++}
++
++func TestAdHandler_UpdatePriorityWithPayment_FailedToGetSessionID(t *testing.T) {
++ require.NoError(t, logger.InitLoggers())
++ defer func() {
++ err := logger.SyncLoggers()
++ if err != nil {
++ return
++ }
++ }()
++
++ mockClient := new(mocks.MockGrpcClient)
++ handler := &AdHandler{client: mockClient}
++ card := domain.PaymentInfo{
++ DonationAmount: "100",
++ }
++ cardData, _ := json.Marshal(card)
++
++ req := httptest.NewRequest("POST", "/housing/{adId}/payment", bytes.NewReader(cardData))
++ req.Header.Set("X-CSRF-Token", "test-token")
++ w := httptest.NewRecorder()
++
++ handler.UpdatePriorityWithPayment(w, req)
++
++ resp := w.Result()
++ defer func(Body io.ReadCloser) {
++ err := Body.Close()
++ if err != nil {
++ return
++ }
++ }(resp.Body)
++
++ require.Equal(t, http.StatusInternalServerError, resp.StatusCode)
++ require.Contains(t, w.Body.String(), "failed to get session id from request cookie")
++}
++
++func TestAdHandler_UpdatePriorityWithPayment_GrpcError(t *testing.T) {
++ require.NoError(t, logger.InitLoggers())
++ defer func() {
++ err := logger.SyncLoggers()
++ if err != nil {
++ return
++ }
++ }()
++
++ mockClient := new(mocks.MockGrpcClient)
++ handler := &AdHandler{client: mockClient}
++
++ card := domain.PaymentInfo{
++ DonationAmount: "100",
++ }
++ cardData, _ := json.Marshal(card)
++
++ req := httptest.NewRequest("POST", "/housing/{adId}/payment", bytes.NewReader(cardData))
++ req.Header.Set("X-CSRF-Token", "test-token")
++ req.AddCookie(&http.Cookie{
++ Name: "session_id",
++ Value: "test-session-id",
++ })
++ w := httptest.NewRecorder()
++ grpcError := status.Error(codes.Internal, "failed to update ad priority")
++ mockClient.On("UpdatePriority", mock.Anything, mock.Anything, mock.Anything).
++ Return(&gen.AdResponse{}, grpcError)
++
++ handler.UpdatePriorityWithPayment(w, req)
++
++ result := w.Result()
++ defer func(Body io.ReadCloser) {
++ err := Body.Close()
++ if err != nil {
++ return
++ }
++ }(result.Body)
++
++ assert.Equal(t, http.StatusInternalServerError, result.StatusCode)
++
++ mockClient.AssertExpectations(t)
++}
+Index: internal/ads/controller/ads_controller.go
+IDEA additional info:
+Subsystem: com.intellij.openapi.diff.impl.patch.BaseRevisionTextPatchEP
+<+>package controller\r\n\r\nimport (\r\n\t\"2024_2_FIGHT-CLUB/domain\"\r\n\t\"2024_2_FIGHT-CLUB/internal/service/logger\"\r\n\t\"2024_2_FIGHT-CLUB/internal/service/metrics\"\r\n\t\"2024_2_FIGHT-CLUB/internal/service/middleware\"\r\n\t\"2024_2_FIGHT-CLUB/internal/service/session\"\r\n\t\"2024_2_FIGHT-CLUB/internal/service/utils\"\r\n\t\"2024_2_FIGHT-CLUB/microservices/ads_service/controller/gen\"\r\n\t\"errors\"\r\n\t\"github.com/gorilla/mux\"\r\n\t\"github.com/mailru/easyjson\"\r\n\t\"go.uber.org/zap\"\r\n\t\"google.golang.org/grpc/status\"\r\n\t\"google.golang.org/protobuf/types/known/timestamppb\"\r\n\t\"io\"\r\n\t\"net/http\"\r\n\t\"time\"\r\n)\r\n\r\ntype AdHandler struct {\r\n\tclient gen.AdsClient\r\n\tsessionService session.InterfaceSession\r\n\tjwtToken middleware.JwtTokenService\r\n\tutils utils.UtilsInterface\r\n}\r\n\r\nfunc NewAdHandler(client gen.AdsClient, sessionService session.InterfaceSession, jwtToken middleware.JwtTokenService, utils utils.UtilsInterface) *AdHandler {\r\n\treturn &AdHandler{\r\n\t\tclient: client,\r\n\t\tsessionService: sessionService,\r\n\t\tjwtToken: jwtToken,\r\n\t\tutils: utils,\r\n\t}\r\n}\r\n\r\nfunc (h *AdHandler) GetAllPlaces(w http.ResponseWriter, r *http.Request) {\r\n\tstart := time.Now()\r\n\trequestID := middleware.GetRequestID(r.Context())\r\n\tctx, cancel := middleware.WithTimeout(r.Context())\r\n\tdefer cancel()\r\n\r\n\tstatusCode := http.StatusOK\r\n\tvar err error\r\n\r\n\tclientIP := r.RemoteAddr\r\n\tif realIP := r.Header.Get(\"X-Real-IP\"); realIP != \"\" {\r\n\t\tclientIP = realIP\r\n\t} else if forwarded := r.Header.Get(\"X-Forwarded-For\"); forwarded != \"\" {\r\n\t\tclientIP = forwarded\r\n\t}\r\n\tdefer func() {\r\n\t\tsanitizedPath := metrics.SanitizeAdIdPath(r.URL.Path)\r\n\t\tif statusCode == http.StatusOK {\r\n\t\t\tmetrics.HttpRequestsTotal.WithLabelValues(r.Method, sanitizedPath, http.StatusText(statusCode), clientIP).Inc()\r\n\t\t} else {\r\n\t\t\tmetrics.HttpErrorsTotal.WithLabelValues(r.Method, sanitizedPath, http.StatusText(statusCode), err.Error(), clientIP).Inc()\r\n\t\t}\r\n\t\tduration := time.Since(start).Seconds()\r\n\t\tmetrics.HttpRequestDuration.WithLabelValues(r.Method, sanitizedPath, clientIP).Observe(duration)\r\n\t}()\r\n\r\n\tlogger.AccessLogger.Info(\"Received GetAllPlaces request\",\r\n\t\tzap.String(\"request_id\", requestID),\r\n\t\tzap.String(\"method\", r.Method),\r\n\t\tzap.String(\"url\", r.URL.String()),\r\n\t\tzap.String(\"query\", r.URL.Query().Encode()),\r\n\t)\r\n\r\n\tsessionID, err := session.GetSessionId(r)\r\n\tif err != nil || sessionID == \"\" {\r\n\t\tlogger.AccessLogger.Warn(\"Failed to get session ID\",\r\n\t\t\tzap.String(\"request_id\", requestID),\r\n\t\t\tzap.Error(err))\r\n\t}\r\n\r\n\tqueryParams := r.URL.Query()\r\n\r\n\tresponse, err := h.client.GetAllPlaces(ctx, &gen.AdFilterRequest{\r\n\t\tLocation: queryParams.Get(\"location\"),\r\n\t\tRating: queryParams.Get(\"rating\"),\r\n\t\tNewThisWeek: queryParams.Get(\"new\"),\r\n\t\tHostGender: queryParams.Get(\"gender\"),\r\n\t\tGuestCount: queryParams.Get(\"guests\"),\r\n\t\tLimit: queryParams.Get(\"limit\"),\r\n\t\tOffset: queryParams.Get(\"offset\"),\r\n\t\tDateFrom: queryParams.Get(\"dateFrom\"),\r\n\t\tDateTo: queryParams.Get(\"dateTo\"),\r\n\t\tSessionId: sessionID,\r\n\t})\r\n\tif err != nil {\r\n\t\tlogger.AccessLogger.Error(\"Failed to GetAllPlaces\",\r\n\t\t\tzap.Error(err),\r\n\t\t\tzap.String(\"request_id\", requestID),\r\n\t\t\tzap.String(\"method\", r.Method))\r\n\t\tst, ok := status.FromError(err)\r\n\t\tif ok {\r\n\t\t\tstatusCode = h.handleError(w, errors.New(st.Message()), requestID)\r\n\t\t}\r\n\t\treturn\r\n\t}\r\n\tbody, err := h.utils.ConvertGetAllAdsResponseProtoToGo(response)\r\n\tif err != nil {\r\n\t\tlogger.AccessLogger.Error(\"Failed to Convert From Proto to Go\",\r\n\t\t\tzap.Error(err),\r\n\t\t\tzap.String(\"request_id\", requestID))\r\n\t\tstatusCode = h.handleError(w, err, requestID)\r\n\t\treturn\r\n\t}\r\n\tw.Header().Set(\"Content-Type\", \"application/json; charset=UTF-8\")\r\n\tw.WriteHeader(http.StatusOK)\r\n\tif _, err = easyjson.MarshalToWriter(body, w); err != nil {\r\n\t\tlogger.AccessLogger.Error(\"Failed to encode response\",\r\n\t\t\tzap.String(\"request_id\", requestID),\r\n\t\t\tzap.Error(err),\r\n\t\t)\r\n\t\tstatusCode = h.handleError(w, err, requestID)\r\n\t\treturn\r\n\t}\r\n\r\n\tduration := time.Since(start)\r\n\tlogger.AccessLogger.Info(\"Completed GetAllPlaces request\",\r\n\t\tzap.String(\"request_id\", requestID),\r\n\t\tzap.Duration(\"duration\", duration),\r\n\t\tzap.Int(\"status\", http.StatusOK),\r\n\t)\r\n}\r\n\r\nfunc (h *AdHandler) GetOnePlace(w http.ResponseWriter, r *http.Request) {\r\n\tstart := time.Now()\r\n\trequestID := middleware.GetRequestID(r.Context())\r\n\tadId := mux.Vars(r)[\"adId\"]\r\n\tctx, cancel := middleware.WithTimeout(r.Context())\r\n\tdefer cancel()\r\n\r\n\tstatusCode := http.StatusOK\r\n\tvar err error\r\n\tclientIP := r.RemoteAddr\r\n\tif realIP := r.Header.Get(\"X-Real-IP\"); realIP != \"\" {\r\n\t\tclientIP = realIP\r\n\t} else if forwarded := r.Header.Get(\"X-Forwarded-For\"); forwarded != \"\" {\r\n\t\tclientIP = forwarded\r\n\t}\r\n\tdefer func() {\r\n\t\tsanitizedPath := metrics.SanitizeAdIdPath(r.URL.Path)\r\n\t\tif statusCode == http.StatusOK {\r\n\t\t\tmetrics.HttpRequestsTotal.WithLabelValues(r.Method, sanitizedPath, http.StatusText(statusCode), clientIP).Inc()\r\n\t\t} else {\r\n\t\t\tmetrics.HttpErrorsTotal.WithLabelValues(r.Method, sanitizedPath, http.StatusText(statusCode), err.Error(), clientIP).Inc()\r\n\t\t}\r\n\t\tduration := time.Since(start).Seconds()\r\n\t\tmetrics.HttpRequestDuration.WithLabelValues(r.Method, sanitizedPath, clientIP).Observe(duration)\r\n\t}()\r\n\r\n\tlogger.AccessLogger.Info(\"Received GetOnePlace request\",\r\n\t\tzap.String(\"request_id\", requestID),\r\n\t\tzap.String(\"adId\", adId),\r\n\t)\r\n\r\n\tisAuthorized := false\r\n\r\n\tsessionID, err := session.GetSessionId(r)\r\n\tif err != nil || sessionID == \"\" {\r\n\t\tlogger.AccessLogger.Warn(\"Failed to get session ID\",\r\n\t\t\tzap.String(\"request_id\", requestID),\r\n\t\t\tzap.Error(err))\r\n\t} else if _, err := h.sessionService.GetUserID(ctx, sessionID); err != nil {\r\n\t\tlogger.AccessLogger.Warn(\"Failed to validate session\",\r\n\t\t\tzap.String(\"request_id\", requestID),\r\n\t\t\tzap.Error(err))\r\n\t} else {\r\n\t\tisAuthorized = true\r\n\t}\r\n\r\n\tplace, err := h.client.GetOnePlace(ctx, &gen.GetPlaceByIdRequest{\r\n\t\tAdId: adId,\r\n\t\tIsAuthorized: isAuthorized,\r\n\t})\r\n\tif err != nil {\r\n\t\tlogger.AccessLogger.Error(\"Failed to GetOnePlace\",\r\n\t\t\tzap.String(\"request_id\", requestID),\r\n\t\t\tzap.Error(err))\r\n\t\tst, ok := status.FromError(err)\r\n\t\tif ok {\r\n\t\t\tstatusCode = h.handleError(w, errors.New(st.Message()), requestID)\r\n\t\t}\r\n\t\treturn\r\n\t}\r\n\r\n\tpayload, err := h.utils.ConvertAdProtoToGo(place)\r\n\tif err != nil {\r\n\t\tlogger.AccessLogger.Error(\"Failed to Convert From Proto to Go\",\r\n\t\t\tzap.Error(err),\r\n\t\t\tzap.String(\"request_id\", requestID))\r\n\t\tstatusCode = h.handleError(w, err, requestID)\r\n\t\treturn\r\n\t}\r\n\tresponse := domain.GetOneAdResponse{\r\n\t\tPlace: payload,\r\n\t}\r\n\tw.Header().Set(\"Content-Type\", \"application/json; charset=UTF-8\")\r\n\tw.WriteHeader(http.StatusOK)\r\n\tif _, err = easyjson.MarshalToWriter(response, w); err != nil {\r\n\t\tlogger.AccessLogger.Error(\"Failed to encode response\",\r\n\t\t\tzap.String(\"request_id\", requestID),\r\n\t\t\tzap.Error(err),\r\n\t\t)\r\n\t\tstatusCode = h.handleError(w, err, requestID)\r\n\t\treturn\r\n\t}\r\n\r\n\tduration := time.Since(start)\r\n\tlogger.AccessLogger.Info(\"Completed GetOnePlace request\",\r\n\t\tzap.String(\"request_id\", requestID),\r\n\t\tzap.Duration(\"duration\", duration),\r\n\t\tzap.Int(\"status\", http.StatusOK),\r\n\t)\r\n}\r\n\r\nfunc (h *AdHandler) CreatePlace(w http.ResponseWriter, r *http.Request) {\r\n\tstart := time.Now()\r\n\trequestID := middleware.GetRequestID(r.Context())\r\n\tctx, cancel := middleware.WithTimeout(r.Context())\r\n\tdefer cancel()\r\n\r\n\tstatusCode := http.StatusCreated\r\n\tvar err error\r\n\tclientIP := r.RemoteAddr\r\n\tif realIP := r.Header.Get(\"X-Real-IP\"); realIP != \"\" {\r\n\t\tclientIP = realIP\r\n\t} else if forwarded := r.Header.Get(\"X-Forwarded-For\"); forwarded != \"\" {\r\n\t\tclientIP = forwarded\r\n\t}\r\n\tdefer func() {\r\n\t\tsanitizedPath := metrics.SanitizeAdIdPath(r.URL.Path)\r\n\t\tif statusCode == http.StatusOK {\r\n\t\t\tmetrics.HttpRequestsTotal.WithLabelValues(r.Method, sanitizedPath, http.StatusText(statusCode), clientIP).Inc()\r\n\t\t} else {\r\n\t\t\tmetrics.HttpErrorsTotal.WithLabelValues(r.Method, sanitizedPath, http.StatusText(statusCode), err.Error(), clientIP).Inc()\r\n\t\t}\r\n\t\tduration := time.Since(start).Seconds()\r\n\t\tmetrics.HttpRequestDuration.WithLabelValues(r.Method, sanitizedPath, clientIP).Observe(duration)\r\n\t}()\r\n\r\n\tlogger.AccessLogger.Info(\"Received CreatePlace request\",\r\n\t\tzap.String(\"request_id\", requestID),\r\n\t)\r\n\r\n\tsessionID, err := session.GetSessionId(r)\r\n\tif err != nil {\r\n\t\tlogger.AccessLogger.Error(\"Failed to get session ID\",\r\n\t\t\tzap.String(\"request_id\", requestID),\r\n\t\t\tzap.Error(err))\r\n\t\tstatusCode = h.handleError(w, err, requestID)\r\n\t\treturn\r\n\t}\r\n\r\n\tauthHeader := r.Header.Get(\"X-CSRF-Token\")\r\n\r\n\terr = r.ParseMultipartForm(10 << 20) // 10 MB\r\n\tif err != nil {\r\n\t\tlogger.AccessLogger.Error(\"Failed to parse multipart form\", zap.String(\"request_id\", requestID), zap.Error(err))\r\n\t\tstatusCode = h.handleError(w, err, requestID)\r\n\t\treturn\r\n\t}\r\n\r\n\tmetadata := r.FormValue(\"metadata\")\r\n\tvar newPlace domain.CreateAdRequest\r\n\tif err = newPlace.UnmarshalJSON([]byte(metadata)); err != nil {\r\n\t\tlogger.AccessLogger.Error(\"Failed to decode metadata\", zap.String(\"request_id\", requestID), zap.Error(err))\r\n\t\tstatusCode = h.handleError(w, err, requestID)\r\n\t\treturn\r\n\t}\r\n\r\n\tfileHeaders := r.MultipartForm.File[\"images\"]\r\n\tif len(fileHeaders) == 0 {\r\n\t\tlogger.AccessLogger.Warn(\"No images\", zap.String(\"request_id\", requestID))\r\n\t\terr = errors.New(\"no images provided\")\r\n\t\tstatusCode = h.handleError(w, err, requestID)\r\n\t\treturn\r\n\t}\r\n\r\n\t// Преобразование файлов в [][]byte\r\n\tfiles := make([][]byte, 0, len(fileHeaders))\r\n\tfor _, fileHeader := range fileHeaders {\r\n\t\tfile, err := fileHeader.Open()\r\n\t\tif err != nil {\r\n\t\t\tlogger.AccessLogger.Error(\"Failed to open file\", zap.String(\"request_id\", requestID), zap.Error(err))\r\n\t\t\tstatusCode = h.handleError(w, errors.New(\"failed to open file\"), requestID)\r\n\t\t\treturn\r\n\t\t}\r\n\t\tdefer file.Close()\r\n\r\n\t\tdata, err := io.ReadAll(file)\r\n\t\tif err != nil {\r\n\t\t\tlogger.AccessLogger.Error(\"Failed to read file\", zap.String(\"request_id\", requestID), zap.Error(err))\r\n\t\t\tstatusCode = h.handleError(w, errors.New(\"failed to read file\"), requestID)\r\n\t\t\treturn\r\n\t\t}\r\n\r\n\t\tfiles = append(files, data)\r\n\t}\r\n\r\n\t_, err = h.client.CreatePlace(ctx, &gen.CreateAdRequest{\r\n\t\tCityName: newPlace.CityName,\r\n\t\tDescription: newPlace.Description,\r\n\t\tAddress: newPlace.Address,\r\n\t\tRoomsNumber: int32(newPlace.RoomsNumber),\r\n\t\tDateFrom: timestamppb.New(newPlace.DateFrom),\r\n\t\tDateTo: timestamppb.New(newPlace.DateTo),\r\n\t\tImages: files,\r\n\t\tAuthHeader: authHeader,\r\n\t\tSessionID: sessionID,\r\n\t\tSquareMeters: int32(newPlace.SquareMeters),\r\n\t\tFloor: int32(newPlace.Floor),\r\n\t\tBuildingType: newPlace.BuildingType,\r\n\t\tHasBalcony: newPlace.HasBalcony,\r\n\t\tHasElevator: newPlace.HasElevator,\r\n\t\tHasGas: newPlace.HasGas,\r\n\t\tRooms: middleware.ConvertRoomsToGRPC(newPlace.Rooms),\r\n\t})\r\n\tif err != nil {\r\n\t\tlogger.AccessLogger.Error(\"Failed to create place\", zap.String(\"request_id\", requestID), zap.Error(err))\r\n\t\tst, ok := status.FromError(err)\r\n\t\tif ok {\r\n\t\t\tstatusCode = h.handleError(w, errors.New(st.Message()), requestID)\r\n\t\t}\r\n\t\treturn\r\n\t}\r\n\r\n\tw.Header().Set(\"Content-Type\", \"application/json; charset=UTF-8\")\r\n\tw.WriteHeader(http.StatusOK)\r\n\tbody := domain.ResponseMessage{\r\n\t\tMessage: \"Successfully created ad\",\r\n\t}\r\n\tif _, err := easyjson.MarshalToWriter(body, w); err != nil {\r\n\t\tlogger.AccessLogger.Error(\"Failed to encode response\", zap.String(\"request_id\", requestID), zap.Error(err))\r\n\t\tstatusCode = h.handleError(w, errors.New(\"failed to encode response\"), requestID)\r\n\t\treturn\r\n\t}\r\n\r\n\tduration := time.Since(start)\r\n\tlogger.AccessLogger.Info(\"Completed CreatePlace request\",\r\n\t\tzap.String(\"request_id\", requestID),\r\n\t\tzap.Duration(\"duration\", duration),\r\n\t\tzap.Int(\"status\", http.StatusOK),\r\n\t)\r\n}\r\n\r\nfunc (h *AdHandler) UpdatePlace(w http.ResponseWriter, r *http.Request) {\r\n\tstart := time.Now()\r\n\trequestID := middleware.GetRequestID(r.Context())\r\n\tadId := mux.Vars(r)[\"adId\"]\r\n\tctx, cancel := middleware.WithTimeout(r.Context())\r\n\tdefer cancel()\r\n\r\n\tstatusCode := http.StatusOK\r\n\tvar err error\r\n\tclientIP := r.RemoteAddr\r\n\tif realIP := r.Header.Get(\"X-Real-IP\"); realIP != \"\" {\r\n\t\tclientIP = realIP\r\n\t} else if forwarded := r.Header.Get(\"X-Forwarded-For\"); forwarded != \"\" {\r\n\t\tclientIP = forwarded\r\n\t}\r\n\tdefer func() {\r\n\t\tsanitizedPath := metrics.SanitizeAdIdPath(r.URL.Path)\r\n\t\tif statusCode == http.StatusOK {\r\n\t\t\tmetrics.HttpRequestsTotal.WithLabelValues(r.Method, sanitizedPath, http.StatusText(statusCode), clientIP).Inc()\r\n\t\t} else {\r\n\t\t\tmetrics.HttpErrorsTotal.WithLabelValues(r.Method, sanitizedPath, http.StatusText(statusCode), err.Error(), clientIP).Inc()\r\n\t\t}\r\n\t\tduration := time.Since(start).Seconds()\r\n\t\tmetrics.HttpRequestDuration.WithLabelValues(r.Method, sanitizedPath, clientIP).Observe(duration)\r\n\t}()\r\n\r\n\tlogger.AccessLogger.Info(\"Received UpdatePlace request\",\r\n\t\tzap.String(\"request_id\", requestID),\r\n\t\tzap.String(\"adId\", adId),\r\n\t)\r\n\r\n\tauthHeader := r.Header.Get(\"X-CSRF-Token\")\r\n\r\n\terr = r.ParseMultipartForm(10 << 20) // 10 MB\r\n\tif err != nil {\r\n\t\tlogger.AccessLogger.Error(\"Failed to parse multipart form\", zap.String(\"request_id\", requestID), zap.Error(err))\r\n\t\tstatusCode = h.handleError(w, errors.New(\"invalid multipart form\"), requestID)\r\n\t\treturn\r\n\t}\r\n\r\n\tmetadata := r.FormValue(\"metadata\")\r\n\tvar updatedPlace domain.UpdateAdRequest\r\n\tif err = updatedPlace.UnmarshalJSON([]byte(metadata)); err != nil {\r\n\t\tlogger.AccessLogger.Error(\"Failed to decode metadata\", zap.String(\"request_id\", requestID), zap.Error(err))\r\n\t\tstatusCode = h.handleError(w, errors.New(\"invalid metadata JSON\"), requestID)\r\n\t\treturn\r\n\t}\r\n\r\n\tfileHeaders := r.MultipartForm.File[\"images\"]\r\n\r\n\t// Преобразование `[]*multipart.FileHeader` в `[][]byte`\r\n\tfiles := make([][]byte, 0, len(fileHeaders))\r\n\tfor _, fileHeader := range fileHeaders {\r\n\t\tfile, err := fileHeader.Open()\r\n\t\tif err != nil {\r\n\t\t\tlogger.AccessLogger.Error(\"Failed to open file\", zap.String(\"request_id\", requestID), zap.Error(err))\r\n\t\t\tstatusCode = h.handleError(w, errors.New(\"failed to open file\"), requestID)\r\n\t\t\treturn\r\n\t\t}\r\n\t\tdefer file.Close()\r\n\r\n\t\t// Чтение содержимого файла в []byte\r\n\t\tdata, err := io.ReadAll(file)\r\n\t\tif err != nil {\r\n\t\t\tlogger.AccessLogger.Error(\"Failed to read file\", zap.String(\"request_id\", requestID), zap.Error(err))\r\n\t\t\tstatusCode = h.handleError(w, errors.New(\"failed to read file\"), requestID)\r\n\t\t\treturn\r\n\t\t}\r\n\t\tfiles = append(files, data)\r\n\t}\r\n\r\n\tsessionID, err := session.GetSessionId(r)\r\n\tif err != nil {\r\n\t\tlogger.AccessLogger.Error(\"Failed to get session ID\",\r\n\t\t\tzap.String(\"request_id\", requestID),\r\n\t\t\tzap.Error(err))\r\n\t\tstatusCode = h.handleError(w, err, requestID)\r\n\t\treturn\r\n\t}\r\n\r\n\t_, err = h.client.UpdatePlace(ctx, &gen.UpdateAdRequest{\r\n\t\tAdId: adId,\r\n\t\tCityName: updatedPlace.CityName,\r\n\t\tAddress: updatedPlace.Address,\r\n\t\tDescription: updatedPlace.Description,\r\n\t\tRoomsNumber: int32(updatedPlace.RoomsNumber),\r\n\t\tSessionID: sessionID,\r\n\t\tAuthHeader: authHeader,\r\n\t\tImages: files,\r\n\t\tDateFrom: timestamppb.New(updatedPlace.DateFrom),\r\n\t\tDateTo: timestamppb.New(updatedPlace.DateTo),\r\n\t\tSquareMeters: int32(updatedPlace.SquareMeters),\r\n\t\tFloor: int32(updatedPlace.Floor),\r\n\t\tBuildingType: updatedPlace.BuildingType,\r\n\t\tHasBalcony: updatedPlace.HasBalcony,\r\n\t\tHasElevator: updatedPlace.HasElevator,\r\n\t\tHasGas: updatedPlace.HasGas,\r\n\t\tRooms: middleware.ConvertRoomsToGRPC(updatedPlace.Rooms),\r\n\t})\r\n\tif err != nil {\r\n\t\tlogger.AccessLogger.Error(\"Failed to update place\", zap.String(\"request_id\", requestID), zap.Error(err))\r\n\t\tst, ok := status.FromError(err)\r\n\t\tif ok {\r\n\t\t\tstatusCode = h.handleError(w, errors.New(st.Message()), requestID)\r\n\t\t}\r\n\t\treturn\r\n\t}\r\n\r\n\tw.Header().Set(\"Content-Type\", \"application/json; charset=UTF-8\")\r\n\tw.WriteHeader(http.StatusOK)\r\n\tupdateResponse := domain.ResponseMessage{\r\n\t\tMessage: \"Successfully updated ad\",\r\n\t}\r\n\tif _, err = easyjson.MarshalToWriter(updateResponse, w); err != nil {\r\n\t\tlogger.AccessLogger.Error(\"Failed to encode response\", zap.String(\"request_id\", requestID), zap.Error(err))\r\n\t\thttp.Error(w, err.Error(), http.StatusInternalServerError)\r\n\t\treturn\r\n\t}\r\n\tduration := time.Since(start)\r\n\tlogger.AccessLogger.Info(\"Completed UpdatePlace request\",\r\n\t\tzap.String(\"request_id\", requestID),\r\n\t\tzap.Duration(\"duration\", duration),\r\n\t\tzap.Int(\"status\", http.StatusOK),\r\n\t)\r\n}\r\n\r\nfunc (h *AdHandler) DeletePlace(w http.ResponseWriter, r *http.Request) {\r\n\tstart := time.Now()\r\n\trequestID := middleware.GetRequestID(r.Context())\r\n\tadId := mux.Vars(r)[\"adId\"]\r\n\tctx, cancel := middleware.WithTimeout(r.Context())\r\n\tdefer cancel()\r\n\r\n\tstatusCode := http.StatusOK\r\n\tvar err error\r\n\tclientIP := r.RemoteAddr\r\n\tif realIP := r.Header.Get(\"X-Real-IP\"); realIP != \"\" {\r\n\t\tclientIP = realIP\r\n\t} else if forwarded := r.Header.Get(\"X-Forwarded-For\"); forwarded != \"\" {\r\n\t\tclientIP = forwarded\r\n\t}\r\n\tdefer func() {\r\n\t\tsanitizedPath := metrics.SanitizeAdIdPath(r.URL.Path)\r\n\t\tif statusCode == http.StatusOK {\r\n\t\t\tmetrics.HttpRequestsTotal.WithLabelValues(r.Method, sanitizedPath, http.StatusText(statusCode), clientIP).Inc()\r\n\t\t} else {\r\n\t\t\tmetrics.HttpErrorsTotal.WithLabelValues(r.Method, sanitizedPath, http.StatusText(statusCode), err.Error(), clientIP).Inc()\r\n\t\t}\r\n\t\tduration := time.Since(start).Seconds()\r\n\t\tmetrics.HttpRequestDuration.WithLabelValues(r.Method, sanitizedPath, clientIP).Observe(duration)\r\n\t}()\r\n\r\n\tlogger.AccessLogger.Info(\"Received DeletePlace request\",\r\n\t\tzap.String(\"request_id\", requestID),\r\n\t\tzap.String(\"adId\", adId),\r\n\t)\r\n\r\n\tauthHeader := r.Header.Get(\"X-CSRF-Token\")\r\n\r\n\tsessionID, err := session.GetSessionId(r)\r\n\tif err != nil {\r\n\t\tlogger.AccessLogger.Error(\"Failed to get session ID\",\r\n\t\t\tzap.String(\"request_id\", requestID),\r\n\t\t\tzap.Error(err))\r\n\t\tstatusCode = h.handleError(w, err, requestID)\r\n\t\treturn\r\n\t}\r\n\r\n\t_, err = h.client.DeletePlace(ctx, &gen.DeletePlaceRequest{\r\n\t\tAdId: adId,\r\n\t\tSessionID: sessionID,\r\n\t\tAuthHeader: authHeader,\r\n\t})\r\n\tif err != nil {\r\n\t\tlogger.AccessLogger.Error(\"Failed to delete place\", zap.String(\"request_id\", requestID), zap.Error(err))\r\n\t\tst, ok := status.FromError(err)\r\n\t\tif ok {\r\n\t\t\tstatusCode = h.handleError(w, errors.New(st.Message()), requestID)\r\n\t\t}\r\n\t\treturn\r\n\t}\r\n\r\n\tw.Header().Set(\"Content-Type\", \"application/json; charset=UTF-8\")\r\n\tw.WriteHeader(http.StatusOK)\r\n\tdeleteResponse := domain.ResponseMessage{\r\n\t\tMessage: \"Successfully deleted place\",\r\n\t}\r\n\tif _, err = easyjson.MarshalToWriter(deleteResponse, w); err != nil {\r\n\t\tlogger.AccessLogger.Error(\"Failed to encode response\", zap.String(\"request_id\", requestID), zap.Error(err))\r\n\t\thttp.Error(w, err.Error(), http.StatusInternalServerError)\r\n\t\treturn\r\n\t}\r\n}\r\n\r\nfunc (h *AdHandler) GetPlacesPerCity(w http.ResponseWriter, r *http.Request) {\r\n\tstart := time.Now()\r\n\trequestID := middleware.GetRequestID(r.Context())\r\n\tcity := mux.Vars(r)[\"city\"]\r\n\tctx, cancel := middleware.WithTimeout(r.Context())\r\n\tdefer cancel()\r\n\r\n\tstatusCode := http.StatusOK\r\n\tvar err error\r\n\tclientIP := r.RemoteAddr\r\n\tif realIP := r.Header.Get(\"X-Real-IP\"); realIP != \"\" {\r\n\t\tclientIP = realIP\r\n\t} else if forwarded := r.Header.Get(\"X-Forwarded-For\"); forwarded != \"\" {\r\n\t\tclientIP = forwarded\r\n\t}\r\n\tdefer func() {\r\n\t\tsanitizedPath := metrics.SanitizeAdIdPath(r.URL.Path)\r\n\t\tif statusCode == http.StatusOK {\r\n\t\t\tmetrics.HttpRequestsTotal.WithLabelValues(r.Method, sanitizedPath, http.StatusText(statusCode), clientIP).Inc()\r\n\t\t} else {\r\n\t\t\tmetrics.HttpErrorsTotal.WithLabelValues(r.Method, sanitizedPath, http.StatusText(statusCode), err.Error(), clientIP).Inc()\r\n\t\t}\r\n\t\tduration := time.Since(start).Seconds()\r\n\t\tmetrics.HttpRequestDuration.WithLabelValues(r.Method, sanitizedPath, clientIP).Observe(duration)\r\n\t}()\r\n\r\n\tlogger.AccessLogger.Info(\"Received GetPlacesPerCity request\",\r\n\t\tzap.String(\"request_id\", requestID),\r\n\t\tzap.String(\"city\", city),\r\n\t)\r\n\r\n\tresponse, err := h.client.GetPlacesPerCity(ctx, &gen.GetPlacesPerCityRequest{\r\n\t\tCityName: city,\r\n\t})\r\n\tif err != nil {\r\n\t\tlogger.AccessLogger.Error(\"Failed to get places per city\", zap.String(\"request_id\", requestID), zap.Error(err))\r\n\t\tst, ok := status.FromError(err)\r\n\t\tif ok {\r\n\t\t\tstatusCode = h.handleError(w, errors.New(st.Message()), requestID)\r\n\t\t}\r\n\t\treturn\r\n\t}\r\n\r\n\tpayload, err := h.utils.ConvertGetAllAdsResponseProtoToGo(response)\r\n\tif err != nil {\r\n\t\tlogger.AccessLogger.Error(\"Failed to Convert From Proto to Go\",\r\n\t\t\tzap.Error(err),\r\n\t\t\tzap.String(\"request_id\", requestID))\r\n\t\tstatusCode = h.handleError(w, err, requestID)\r\n\t\treturn\r\n\t}\r\n\tw.Header().Set(\"Content-Type\", \"application/json; charset=UTF-8\")\r\n\tw.WriteHeader(http.StatusOK)\r\n\tbody := domain.PlacesResponse{\r\n\t\tPlaces: payload,\r\n\t}\r\n\tif _, err = easyjson.MarshalToWriter(body, w); err != nil {\r\n\t\tlogger.AccessLogger.Error(\"Failed to encode response\",\r\n\t\t\tzap.String(\"request_id\", requestID),\r\n\t\t\tzap.Error(err),\r\n\t\t)\r\n\t\tstatusCode = h.handleError(w, err, requestID)\r\n\t\treturn\r\n\t}\r\n\r\n\tduration := time.Since(start)\r\n\tlogger.AccessLogger.Info(\"Completed GetPlacesPerCity request\",\r\n\t\tzap.String(\"request_id\", requestID),\r\n\t\tzap.Duration(\"duration\", duration),\r\n\t\tzap.Int(\"status\", http.StatusOK),\r\n\t)\r\n}\r\n\r\nfunc (h *AdHandler) GetUserPlaces(w http.ResponseWriter, r *http.Request) {\r\n\tstart := time.Now()\r\n\trequestID := middleware.GetRequestID(r.Context())\r\n\tuserId := mux.Vars(r)[\"userId\"]\r\n\tctx, cancel := middleware.WithTimeout(r.Context())\r\n\tdefer cancel()\r\n\r\n\tstatusCode := http.StatusOK\r\n\tvar err error\r\n\tclientIP := r.RemoteAddr\r\n\tif realIP := r.Header.Get(\"X-Real-IP\"); realIP != \"\" {\r\n\t\tclientIP = realIP\r\n\t} else if forwarded := r.Header.Get(\"X-Forwarded-For\"); forwarded != \"\" {\r\n\t\tclientIP = forwarded\r\n\t}\r\n\tdefer func() {\r\n\t\tsanitizedPath := metrics.SanitizeUserIdPath(r.URL.Path)\r\n\t\tif statusCode == http.StatusOK {\r\n\t\t\tmetrics.HttpRequestsTotal.WithLabelValues(r.Method, sanitizedPath, http.StatusText(statusCode), clientIP).Inc()\r\n\t\t} else {\r\n\t\t\tmetrics.HttpErrorsTotal.WithLabelValues(r.Method, sanitizedPath, http.StatusText(statusCode), err.Error(), clientIP).Inc()\r\n\t\t}\r\n\t\tduration := time.Since(start).Seconds()\r\n\t\tmetrics.HttpRequestDuration.WithLabelValues(r.Method, sanitizedPath, clientIP).Observe(duration)\r\n\t}()\r\n\r\n\tlogger.AccessLogger.Info(\"Received GetUserPlaces request\",\r\n\t\tzap.String(\"request_id\", requestID),\r\n\t\tzap.String(\"userId\", userId),\r\n\t)\r\n\r\n\tresponse, err := h.client.GetUserPlaces(ctx, &gen.GetUserPlacesRequest{\r\n\t\tUserId: userId,\r\n\t})\r\n\tif err != nil {\r\n\t\tlogger.AccessLogger.Error(\"Failed to get places per user\",\r\n\t\t\tzap.String(\"request_id\", requestID),\r\n\t\t\tzap.Error(err))\r\n\t\tst, ok := status.FromError(err)\r\n\t\tif ok {\r\n\t\t\tstatusCode = h.handleError(w, errors.New(st.Message()), requestID)\r\n\t\t}\r\n\t\treturn\r\n\t}\r\n\r\n\tpayload, err := h.utils.ConvertGetAllAdsResponseProtoToGo(response)\r\n\tif err != nil {\r\n\t\tlogger.AccessLogger.Error(\"Failed to Convert From Proto to Go\",\r\n\t\t\tzap.Error(err),\r\n\t\t\tzap.String(\"request_id\", requestID))\r\n\t\tstatusCode = h.handleError(w, err, requestID)\r\n\t\treturn\r\n\t}\r\n\tw.Header().Set(\"Content-Type\", \"application/json; charset=UTF-8\")\r\n\tw.WriteHeader(http.StatusOK)\r\n\tbody := domain.PlacesResponse{\r\n\t\tPlaces: payload,\r\n\t}\r\n\tif _, err = easyjson.MarshalToWriter(body, w); err != nil {\r\n\t\tlogger.AccessLogger.Error(\"Failed to encode response\",\r\n\t\t\tzap.String(\"request_id\", requestID),\r\n\t\t\tzap.Error(err),\r\n\t\t)\r\n\t\tstatusCode = h.handleError(w, err, requestID)\r\n\t\treturn\r\n\t}\r\n\r\n\tduration := time.Since(start)\r\n\tlogger.AccessLogger.Info(\"Completed GetUserPlaces request\",\r\n\t\tzap.String(\"request_id\", requestID),\r\n\t\tzap.Duration(\"duration\", duration),\r\n\t\tzap.Int(\"status\", http.StatusOK),\r\n\t)\r\n}\r\n\r\nfunc (h *AdHandler) DeleteAdImage(w http.ResponseWriter, r *http.Request) {\r\n\tstart := time.Now()\r\n\trequestID := middleware.GetRequestID(r.Context())\r\n\timageId := mux.Vars(r)[\"imageId\"]\r\n\tadId := mux.Vars(r)[\"adId\"]\r\n\tctx, cancel := middleware.WithTimeout(r.Context())\r\n\tdefer cancel()\r\n\r\n\tstatusCode := http.StatusOK\r\n\tvar err error\r\n\tclientIP := r.RemoteAddr\r\n\tif realIP := r.Header.Get(\"X-Real-IP\"); realIP != \"\" {\r\n\t\tclientIP = realIP\r\n\t} else if forwarded := r.Header.Get(\"X-Forwarded-For\"); forwarded != \"\" {\r\n\t\tclientIP = forwarded\r\n\t}\r\n\tdefer func() {\r\n\t\tsanitizedPath := metrics.SanitizeAdIdPath(r.URL.Path)\r\n\t\tif statusCode == http.StatusOK {\r\n\t\t\tmetrics.HttpRequestsTotal.WithLabelValues(r.Method, sanitizedPath, http.StatusText(statusCode), clientIP).Inc()\r\n\t\t} else {\r\n\t\t\tmetrics.HttpErrorsTotal.WithLabelValues(r.Method, sanitizedPath, http.StatusText(statusCode), err.Error(), clientIP).Inc()\r\n\t\t}\r\n\t\tduration := time.Since(start).Seconds()\r\n\t\tmetrics.HttpRequestDuration.WithLabelValues(r.Method, sanitizedPath, clientIP).Observe(duration)\r\n\t}()\r\n\r\n\tlogger.AccessLogger.Info(\"Received DeleteAdImage request\",\r\n\t\tzap.String(\"request_id\", requestID),\r\n\t\tzap.String(\"adId\", adId),\r\n\t\tzap.String(\"imageId\", imageId))\r\n\r\n\tauthHeader := r.Header.Get(\"X-CSRF-Token\")\r\n\r\n\tsessionID, err := session.GetSessionId(r)\r\n\tif err != nil {\r\n\t\tlogger.AccessLogger.Error(\"Failed to get session ID\",\r\n\t\t\tzap.String(\"request_id\", requestID),\r\n\t\t\tzap.Error(err))\r\n\t\tstatusCode = h.handleError(w, err, requestID)\r\n\t\treturn\r\n\t}\r\n\r\n\t_, err = h.client.DeleteAdImage(ctx, &gen.DeleteAdImageRequest{\r\n\t\tAdId: adId,\r\n\t\tImageId: imageId,\r\n\t\tAuthHeader: authHeader,\r\n\t\tSessionID: sessionID,\r\n\t})\r\n\tif err != nil {\r\n\t\tlogger.AccessLogger.Error(\"Failed to delete ad image\", zap.String(\"request_id\", requestID), zap.Error(err))\r\n\t\tst, ok := status.FromError(err)\r\n\t\tif ok {\r\n\t\t\tstatusCode = h.handleError(w, errors.New(st.Message()), requestID)\r\n\t\t}\r\n\t\treturn\r\n\t}\r\n\r\n\tw.Header().Set(\"Content-Type\", \"application/json; charset=UTF-8\")\r\n\tw.WriteHeader(http.StatusOK)\r\n\tdeleteResponse := domain.ResponseMessage{\r\n\t\tMessage: \"Successfully deleted ad image\",\r\n\t}\r\n\tif _, err = easyjson.MarshalToWriter(deleteResponse, w); err != nil {\r\n\t\tlogger.AccessLogger.Error(\"Failed to encode response\", zap.String(\"request_id\", requestID), zap.Error(err))\r\n\t\thttp.Error(w, err.Error(), http.StatusInternalServerError)\r\n\t\treturn\r\n\t}\r\n\r\n\tduration := time.Since(start)\r\n\tlogger.AccessLogger.Info(\"Completed DeleteAdImage request\",\r\n\t\tzap.String(\"request_id\", requestID),\r\n\t\tzap.String(\"adId\", adId),\r\n\t\tzap.String(\"imageId\", imageId),\r\n\t\tzap.Duration(\"duration\", duration),\r\n\t)\r\n}\r\n\r\nfunc (h *AdHandler) AddToFavorites(w http.ResponseWriter, r *http.Request) {\r\n\tstart := time.Now()\r\n\trequestID := middleware.GetRequestID(r.Context())\r\n\tadId := mux.Vars(r)[\"adId\"]\r\n\tctx, cancel := middleware.WithTimeout(r.Context())\r\n\tdefer cancel()\r\n\r\n\tstatusCode := http.StatusOK\r\n\tvar err error\r\n\tclientIP := r.RemoteAddr\r\n\tif realIP := r.Header.Get(\"X-Real-IP\"); realIP != \"\" {\r\n\t\tclientIP = realIP\r\n\t} else if forwarded := r.Header.Get(\"X-Forwarded-For\"); forwarded != \"\" {\r\n\t\tclientIP = forwarded\r\n\t}\r\n\tdefer func() {\r\n\t\tsanitizedPath := metrics.SanitizeAdIdPath(r.URL.Path)\r\n\t\tif statusCode == http.StatusOK {\r\n\t\t\tmetrics.HttpRequestsTotal.WithLabelValues(r.Method, sanitizedPath, http.StatusText(statusCode), clientIP).Inc()\r\n\t\t} else {\r\n\t\t\tmetrics.HttpErrorsTotal.WithLabelValues(r.Method, sanitizedPath, http.StatusText(statusCode), err.Error(), clientIP).Inc()\r\n\t\t}\r\n\t\tduration := time.Since(start).Seconds()\r\n\t\tmetrics.HttpRequestDuration.WithLabelValues(r.Method, sanitizedPath, clientIP).Observe(duration)\r\n\t}()\r\n\r\n\tlogger.AccessLogger.Info(\"Received AddToFavorites request\",\r\n\t\tzap.String(\"request_id\", requestID),\r\n\t\tzap.String(\"adId\", adId))\r\n\r\n\tauthHeader := r.Header.Get(\"X-CSRF-Token\")\r\n\r\n\tsessionID, err := session.GetSessionId(r)\r\n\tif err != nil {\r\n\t\tlogger.AccessLogger.Error(\"Failed to get session ID\",\r\n\t\t\tzap.String(\"request_id\", requestID),\r\n\t\t\tzap.Error(err))\r\n\t\tstatusCode = h.handleError(w, err, requestID)\r\n\t\treturn\r\n\t}\r\n\r\n\t_, err = h.client.AddToFavorites(ctx, &gen.AddToFavoritesRequest{\r\n\t\tAdId: adId,\r\n\t\tAuthHeader: authHeader,\r\n\t\tSessionID: sessionID,\r\n\t})\r\n\tif err != nil {\r\n\t\tlogger.AccessLogger.Error(\"Failed to add ad to favorites\", zap.String(\"request_id\", requestID), zap.Error(err))\r\n\t\tst, ok := status.FromError(err)\r\n\t\tif ok {\r\n\t\t\tstatusCode = h.handleError(w, errors.New(st.Message()), requestID)\r\n\t\t}\r\n\t\treturn\r\n\t}\r\n\r\n\tw.Header().Set(\"Content-Type\", \"application/json; charset=UTF-8\")\r\n\tw.WriteHeader(http.StatusOK)\r\n\taddToResponse := domain.ResponseMessage{\r\n\t\tMessage: \"Successfully added to favorites\",\r\n\t}\r\n\tif _, err := easyjson.MarshalToWriter(addToResponse, w); err != nil {\r\n\t\tlogger.AccessLogger.Error(\"Failed to encode response\", zap.String(\"request_id\", requestID), zap.Error(err))\r\n\t\thttp.Error(w, err.Error(), http.StatusInternalServerError)\r\n\t\treturn\r\n\t}\r\n\r\n\tduration := time.Since(start)\r\n\tlogger.AccessLogger.Info(\"Completed AddToFavorites request\",\r\n\t\tzap.String(\"request_id\", requestID),\r\n\t\tzap.String(\"adId\", adId),\r\n\t\tzap.Duration(\"duration\", duration),\r\n\t)\r\n\r\n}\r\n\r\nfunc (h *AdHandler) DeleteFromFavorites(w http.ResponseWriter, r *http.Request) {\r\n\tstart := time.Now()\r\n\trequestID := middleware.GetRequestID(r.Context())\r\n\tadId := mux.Vars(r)[\"adId\"]\r\n\tctx, cancel := middleware.WithTimeout(r.Context())\r\n\tdefer cancel()\r\n\r\n\tstatusCode := http.StatusOK\r\n\tvar err error\r\n\tclientIP := r.RemoteAddr\r\n\tif realIP := r.Header.Get(\"X-Real-IP\"); realIP != \"\" {\r\n\t\tclientIP = realIP\r\n\t} else if forwarded := r.Header.Get(\"X-Forwarded-For\"); forwarded != \"\" {\r\n\t\tclientIP = forwarded\r\n\t}\r\n\tdefer func() {\r\n\t\tsanitizedPath := metrics.SanitizeAdIdPath(r.URL.Path)\r\n\t\tif statusCode == http.StatusOK {\r\n\t\t\tmetrics.HttpRequestsTotal.WithLabelValues(r.Method, sanitizedPath, http.StatusText(statusCode), clientIP).Inc()\r\n\t\t} else {\r\n\t\t\tmetrics.HttpErrorsTotal.WithLabelValues(r.Method, sanitizedPath, http.StatusText(statusCode), err.Error(), clientIP).Inc()\r\n\t\t}\r\n\t\tduration := time.Since(start).Seconds()\r\n\t\tmetrics.HttpRequestDuration.WithLabelValues(r.Method, sanitizedPath, clientIP).Observe(duration)\r\n\t}()\r\n\r\n\tlogger.AccessLogger.Info(\"Received DeleteFromFavorites request\",\r\n\t\tzap.String(\"request_id\", requestID),\r\n\t\tzap.String(\"adId\", adId))\r\n\r\n\tauthHeader := r.Header.Get(\"X-CSRF-Token\")\r\n\r\n\tsessionID, err := session.GetSessionId(r)\r\n\tif err != nil {\r\n\t\tlogger.AccessLogger.Error(\"Failed to get session ID\",\r\n\t\t\tzap.String(\"request_id\", requestID),\r\n\t\t\tzap.Error(err))\r\n\t\tstatusCode = h.handleError(w, err, requestID)\r\n\t\treturn\r\n\t}\r\n\r\n\t_, err = h.client.DeleteFromFavorites(ctx, &gen.DeleteFromFavoritesRequest{\r\n\t\tAdId: adId,\r\n\t\tAuthHeader: authHeader,\r\n\t\tSessionID: sessionID,\r\n\t})\r\n\tif err != nil {\r\n\t\tlogger.AccessLogger.Error(\"Failed to delete ad from favorites\", zap.String(\"request_id\", requestID), zap.Error(err))\r\n\t\tst, ok := status.FromError(err)\r\n\t\tif ok {\r\n\t\t\tstatusCode = h.handleError(w, errors.New(st.Message()), requestID)\r\n\t\t}\r\n\t\treturn\r\n\t}\r\n\r\n\tw.Header().Set(\"Content-Type\", \"application/json; charset=UTF-8\")\r\n\tw.WriteHeader(http.StatusOK)\r\n\tdeleteFromFavResponse := domain.ResponseMessage{\r\n\t\tMessage: \"Successfully deleted from favorites\",\r\n\t}\r\n\tif _, err = easyjson.MarshalToWriter(deleteFromFavResponse, w); err != nil {\r\n\t\tlogger.AccessLogger.Error(\"Failed to encode response\", zap.String(\"request_id\", requestID), zap.Error(err))\r\n\t\thttp.Error(w, err.Error(), http.StatusInternalServerError)\r\n\t\treturn\r\n\t}\r\n\r\n\tduration := time.Since(start)\r\n\tlogger.AccessLogger.Info(\"Completed DeleteFromFavorites request\",\r\n\t\tzap.String(\"request_id\", requestID),\r\n\t\tzap.String(\"adId\", adId),\r\n\t\tzap.Duration(\"duration\", duration),\r\n\t)\r\n\r\n}\r\n\r\nfunc (h *AdHandler) GetUserFavorites(w http.ResponseWriter, r *http.Request) {\r\n\tstart := time.Now()\r\n\trequestID := middleware.GetRequestID(r.Context())\r\n\tuserId := mux.Vars(r)[\"userId\"]\r\n\tctx, cancel := middleware.WithTimeout(r.Context())\r\n\tdefer cancel()\r\n\r\n\tstatusCode := http.StatusOK\r\n\tvar err error\r\n\tclientIP := r.RemoteAddr\r\n\tif realIP := r.Header.Get(\"X-Real-IP\"); realIP != \"\" {\r\n\t\tclientIP = realIP\r\n\t} else if forwarded := r.Header.Get(\"X-Forwarded-For\"); forwarded != \"\" {\r\n\t\tclientIP = forwarded\r\n\t}\r\n\tdefer func() {\r\n\t\tsanitizedPath := metrics.SanitizeUserIdPath(r.URL.Path)\r\n\t\tif statusCode == http.StatusOK {\r\n\t\t\tmetrics.HttpRequestsTotal.WithLabelValues(r.Method, sanitizedPath, http.StatusText(statusCode), clientIP).Inc()\r\n\t\t} else {\r\n\t\t\tmetrics.HttpErrorsTotal.WithLabelValues(r.Method, sanitizedPath, http.StatusText(statusCode), err.Error(), clientIP).Inc()\r\n\t\t}\r\n\t\tduration := time.Since(start).Seconds()\r\n\t\tmetrics.HttpRequestDuration.WithLabelValues(r.Method, sanitizedPath, clientIP).Observe(duration)\r\n\t}()\r\n\r\n\tlogger.AccessLogger.Info(\"Received GetUserFavorites request\",\r\n\t\tzap.String(\"request_id\", requestID),\r\n\t\tzap.String(\"userId\", userId))\r\n\r\n\tsessionID, err := session.GetSessionId(r)\r\n\tif err != nil {\r\n\t\tlogger.AccessLogger.Error(\"Failed to get session ID\",\r\n\t\t\tzap.String(\"request_id\", requestID),\r\n\t\t\tzap.Error(err))\r\n\t\tstatusCode = h.handleError(w, err, requestID)\r\n\t\treturn\r\n\t}\r\n\r\n\tresponse, err := h.client.GetUserFavorites(ctx, &gen.GetUserFavoritesRequest{\r\n\t\tUserId: userId,\r\n\t\tSessionID: sessionID,\r\n\t})\r\n\tif err != nil {\r\n\t\tlogger.AccessLogger.Error(\"Failed to delete ad from favorites\", zap.String(\"request_id\", requestID), zap.Error(err))\r\n\t\tst, ok := status.FromError(err)\r\n\t\tif ok {\r\n\t\t\tstatusCode = h.handleError(w, errors.New(st.Message()), requestID)\r\n\t\t}\r\n\t\treturn\r\n\t}\r\n\r\n\tbody, err := h.utils.ConvertGetAllAdsResponseProtoToGo(response)\r\n\tif err != nil {\r\n\t\tlogger.AccessLogger.Error(\"Failed to Convert From Proto to Go\",\r\n\t\t\tzap.Error(err),\r\n\t\t\tzap.String(\"request_id\", requestID))\r\n\t\tstatusCode = h.handleError(w, err, requestID)\r\n\t\treturn\r\n\t}\r\n\tw.Header().Set(\"Content-Type\", \"application/json; charset=UTF-8\")\r\n\tw.WriteHeader(http.StatusOK)\r\n\tif _, err = easyjson.MarshalToWriter(body, w); err != nil {\r\n\t\tlogger.AccessLogger.Error(\"Failed to encode response\",\r\n\t\t\tzap.String(\"request_id\", requestID),\r\n\t\t\tzap.Error(err),\r\n\t\t)\r\n\t\tstatusCode = h.handleError(w, err, requestID)\r\n\t\treturn\r\n\t}\r\n\r\n\tduration := time.Since(start)\r\n\tlogger.AccessLogger.Info(\"Completed GetUserFavorites request\",\r\n\t\tzap.String(\"request_id\", requestID),\r\n\t\tzap.String(\"userId\", userId),\r\n\t\tzap.Duration(\"duration\", duration),\r\n\t)\r\n\r\n}\r\n\r\nfunc (h *AdHandler) UpdatePriorityWithPayment(w http.ResponseWriter, r *http.Request) {\r\n\tstart := time.Now()\r\n\trequestID := middleware.GetRequestID(r.Context())\r\n\tadId := mux.Vars(r)[\"adId\"]\r\n\tctx, cancel := middleware.WithTimeout(r.Context())\r\n\tdefer cancel()\r\n\r\n\tstatusCode := http.StatusOK\r\n\tvar err error\r\n\tclientIP := r.RemoteAddr\r\n\tif realIP := r.Header.Get(\"X-Real-IP\"); realIP != \"\" {\r\n\t\tclientIP = realIP\r\n\t} else if forwarded := r.Header.Get(\"X-Forwarded-For\"); forwarded != \"\" {\r\n\t\tclientIP = forwarded\r\n\t}\r\n\tdefer func() {\r\n\t\tsanitizedPath := metrics.SanitizeAdIdPath(r.URL.Path)\r\n\t\tif statusCode == http.StatusOK {\r\n\t\t\tmetrics.HttpRequestsTotal.WithLabelValues(r.Method, sanitizedPath, http.StatusText(statusCode), clientIP).Inc()\r\n\t\t} else {\r\n\t\t\tmetrics.HttpErrorsTotal.WithLabelValues(r.Method, sanitizedPath, http.StatusText(statusCode), err.Error(), clientIP).Inc()\r\n\t\t}\r\n\t\tduration := time.Since(start).Seconds()\r\n\t\tmetrics.HttpRequestDuration.WithLabelValues(r.Method, sanitizedPath, clientIP).Observe(duration)\r\n\t}()\r\n\r\n\tlogger.AccessLogger.Info(\"Received UpdatePriorityWithPayment request\",\r\n\t\tzap.String(\"request_id\", requestID),\r\n\t\tzap.String(\"userId\", adId))\r\n\r\n\tvar card domain.PaymentInfo\r\n\tif err = easyjson.UnmarshalFromReader(r.Body, &card); err != nil {\r\n\t\tlogger.AccessLogger.Error(\"Failed to decode request body\",\r\n\t\t\tzap.String(\"request_id\", requestID),\r\n\t\t\tzap.Error(err),\r\n\t\t)\r\n\t\tstatusCode = h.handleError(w, err, requestID)\r\n\t\treturn\r\n\t}\r\n\r\n\tauthHeader := r.Header.Get(\"X-CSRF-Token\")\r\n\r\n\tsessionID, err := session.GetSessionId(r)\r\n\tif err != nil {\r\n\t\tlogger.AccessLogger.Error(\"Failed to get session ID\",\r\n\t\t\tzap.String(\"request_id\", requestID),\r\n\t\t\tzap.Error(err))\r\n\t\tstatusCode = h.handleError(w, err, requestID)\r\n\t\treturn\r\n\t}\r\n\r\n\t_, err = h.client.UpdatePriority(ctx, &gen.UpdatePriorityRequest{\r\n\t\tAdId: adId,\r\n\t\tAuthHeader: authHeader,\r\n\t\tSessionID: sessionID,\r\n\t\tAmount: card.DonationAmount,\r\n\t})\r\n\tif err != nil {\r\n\t\tlogger.AccessLogger.Error(\"Failed to delete ad from favorites\", zap.String(\"request_id\", requestID), zap.Error(err))\r\n\t\tst, ok := status.FromError(err)\r\n\t\tif ok {\r\n\t\t\tstatusCode = h.handleError(w, errors.New(st.Message()), requestID)\r\n\t\t}\r\n\t\treturn\r\n\t}\r\n\r\n\tw.Header().Set(\"Content-Type\", \"application/json; charset=UTF-8\")\r\n\tw.WriteHeader(http.StatusOK)\r\n\tupdatePriorResponse := domain.ResponseMessage{\r\n\t\tMessage: \"Successfully updated from favorites\",\r\n\t}\r\n\tif _, err = easyjson.MarshalToWriter(updatePriorResponse, w); err != nil {\r\n\t\tlogger.AccessLogger.Error(\"Failed to encode response\", zap.String(\"request_id\", requestID), zap.Error(err))\r\n\t\thttp.Error(w, err.Error(), http.StatusInternalServerError)\r\n\t\treturn\r\n\t}\r\n\r\n\tduration := time.Since(start)\r\n\tlogger.AccessLogger.Info(\"Completed UpdatePriorityWithPayment request\",\r\n\t\tzap.String(\"request_id\", requestID),\r\n\t\tzap.String(\"userId\", adId),\r\n\t\tzap.Duration(\"duration\", duration),\r\n\t)\r\n}\r\n\r\nfunc (h *AdHandler) handleError(w http.ResponseWriter, err error, requestID string) int {\r\n\tlogger.AccessLogger.Error(\"Handling error\",\r\n\t\tzap.String(\"request_id\", requestID),\r\n\t\tzap.Error(err),\r\n\t)\r\n\tvar statusCode int\r\n\tw.Header().Set(\"Content-Type\", \"application/json\")\r\n\terrorResponse := domain.ErrorResponse{\r\n\t\tError: err.Error(),\r\n\t}\r\n\tswitch err.Error() {\r\n\tcase \"ad not found\", \"ad date not found\", \"image not found\":\r\n\t\tstatusCode = http.StatusNotFound\r\n\tcase \"ad already exists\", \"roomsNumber out of range\", \"not owner of ad\":\r\n\t\tstatusCode = http.StatusConflict\r\n\tcase \"no active session\", \"missing X-CSRF-Token header\",\r\n\t\t\"invalid JWT token\", \"user is not host\", \"session not found\", \"user ID not found in session\":\r\n\t\tstatusCode = http.StatusUnauthorized\r\n\tcase \"invalid metadata JSON\", \"invalid multipart form\", \"input contains invalid characters\",\r\n\t\t\"input exceeds character limit\", \"invalid size, type or resolution of image\",\r\n\t\t\"query offset not int\", \"query limit not int\", \"query dateFrom not int\",\r\n\t\t\"query dateTo not int\", \"URL contains invalid characters\", \"URL exceeds character limit\",\r\n\t\t\"token parse error\", \"token invalid\", \"token expired\", \"bad sign method\",\r\n\t\t\"failed to decode metadata\", \"no images provided\", \"failed to open file\",\r\n\t\t\"failed to read file\", \"failed to encode response\", \"invalid rating value\",\r\n\t\t\"cant access other user favorites\":\r\n\t\tstatusCode = http.StatusBadRequest\r\n\tcase \"error fetching all places\", \"error fetching images for ad\", \"error fetching user\",\r\n\t\t\"error finding user\", \"error finding city\", \"error creating place\", \"error creating date\",\r\n\t\t\"error saving place\", \"error updating place\", \"error updating date\",\r\n\t\t\"error updating views count\", \"error deleting place\", \"get places error\",\r\n\t\t\"get places per city error\", \"get user places error\", \"error creating image\",\r\n\t\t\"delete ad image error\", \"failed to generate session id\", \"failed to save session\",\r\n\t\t\"failed to delete session\", \"error generating random bytes for session ID\",\r\n\t\t\"failed to get session id from request cookie\", \"error fetching rooms for ad\",\r\n\t\t\"error counting favorites\", \"error updating favorites count\", \"error creating room\", \"error parsing date\",\r\n\t\t\"adAuthor is nil\", \"ad is nil\":\r\n\t\tstatusCode = http.StatusInternalServerError\r\n\tdefault:\r\n\t\tstatusCode = http.StatusInternalServerError\r\n\t}\r\n\r\n\tw.WriteHeader(statusCode)\r\n\tif _, jsonErr := easyjson.MarshalToWriter(&errorResponse, w); jsonErr != nil {\r\n\t\tlogger.AccessLogger.Error(\"Failed to encode error response\",\r\n\t\t\tzap.String(\"request_id\", requestID),\r\n\t\t\tzap.Error(jsonErr),\r\n\t\t)\r\n\t\thttp.Error(w, jsonErr.Error(), http.StatusInternalServerError)\r\n\t}\r\n\r\n\treturn statusCode\r\n}\r\n
+Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
+<+>UTF-8
+===================================================================
+diff --git a/internal/ads/controller/ads_controller.go b/internal/ads/controller/ads_controller.go
+--- a/internal/ads/controller/ads_controller.go (revision f68dbe94cca631281d0d1496863bdf9efcda8dd8)
++++ b/internal/ads/controller/ads_controller.go (date 1734480286733)
+@@ -234,7 +234,7 @@
+ }
+ defer func() {
+ sanitizedPath := metrics.SanitizeAdIdPath(r.URL.Path)
+- if statusCode == http.StatusOK {
++ if statusCode == http.StatusCreated {
+ metrics.HttpRequestsTotal.WithLabelValues(r.Method, sanitizedPath, http.StatusText(statusCode), clientIP).Inc()
+ } else {
+ metrics.HttpErrorsTotal.WithLabelValues(r.Method, sanitizedPath, http.StatusText(statusCode), err.Error(), clientIP).Inc()
+@@ -330,7 +330,7 @@
+ }
+
+ w.Header().Set("Content-Type", "application/json; charset=UTF-8")
+- w.WriteHeader(http.StatusOK)
++ w.WriteHeader(http.StatusCreated)
+ body := domain.ResponseMessage{
+ Message: "Successfully created ad",
+ }
+@@ -344,7 +344,7 @@
+ logger.AccessLogger.Info("Completed CreatePlace request",
+ zap.String("request_id", requestID),
+ zap.Duration("duration", duration),
+- zap.Int("status", http.StatusOK),
++ zap.Int("status", http.StatusCreated),
+ )
+ }
+
+@@ -1056,7 +1056,7 @@
+ Amount: card.DonationAmount,
+ })
+ if err != nil {
+- logger.AccessLogger.Error("Failed to delete ad from favorites", zap.String("request_id", requestID), zap.Error(err))
++ logger.AccessLogger.Error("Failed to update priority", zap.String("request_id", requestID), zap.Error(err))
+ st, ok := status.FromError(err)
+ if ok {
+ statusCode = h.handleError(w, errors.New(st.Message()), requestID)
+@@ -1067,7 +1067,7 @@
+ w.Header().Set("Content-Type", "application/json; charset=UTF-8")
+ w.WriteHeader(http.StatusOK)
+ updatePriorResponse := domain.ResponseMessage{
+- Message: "Successfully updated from favorites",
++ Message: "Successfully update ad priority",
+ }
+ if _, err = easyjson.MarshalToWriter(updatePriorResponse, w); err != nil {
+ logger.AccessLogger.Error("Failed to encode response", zap.String("request_id", requestID), zap.Error(err))
diff --git a/.idea/shelf/Uncommitted_changes_before_Update_at_18_12_2024_3_17__Changes_.xml b/.idea/shelf/Uncommitted_changes_before_Update_at_18_12_2024_3_17__Changes_.xml
new file mode 100644
index 0000000..e524e05
--- /dev/null
+++ b/.idea/shelf/Uncommitted_changes_before_Update_at_18_12_2024_3_17__Changes_.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/.idea/shelf/Uncommitted_changes_before_Update_at_18_12_2024_4_11_[Changes]/shelved.patch b/.idea/shelf/Uncommitted_changes_before_Update_at_18_12_2024_4_11_[Changes]/shelved.patch
new file mode 100644
index 0000000..90e549b
--- /dev/null
+++ b/.idea/shelf/Uncommitted_changes_before_Update_at_18_12_2024_4_11_[Changes]/shelved.patch
@@ -0,0 +1,537 @@
+Index: internal/reviews/contoller/reviews_controller_tests.go
+IDEA additional info:
+Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
+<+>UTF-8
+===================================================================
+diff --git a/internal/reviews/contoller/reviews_controller_tests.go b/internal/reviews/contoller/reviews_controller_test.go
+rename from internal/reviews/contoller/reviews_controller_tests.go
+rename to internal/reviews/contoller/reviews_controller_test.go
+--- a/internal/reviews/contoller/reviews_controller_tests.go (revision 142115a99a0b0b884b796083f0d35dceb498f45e)
++++ b/internal/reviews/contoller/reviews_controller_test.go (date 1734484197031)
+@@ -1,1 +1,421 @@
+ package contoller
++
++import (
++ "2024_2_FIGHT-CLUB/domain"
++ "2024_2_FIGHT-CLUB/internal/reviews/mocks"
++ "2024_2_FIGHT-CLUB/internal/service/logger"
++ "2024_2_FIGHT-CLUB/internal/service/middleware"
++ "bytes"
++ "context"
++ "errors"
++ "github.com/gorilla/mux"
++ "github.com/stretchr/testify/assert"
++ "github.com/stretchr/testify/require"
++ "net/http"
++ "net/http/httptest"
++ "testing"
++)
++
++func TestCreateReview(t *testing.T) {
++ require.NoError(t, logger.InitLoggers())
++ defer func() {
++ err := logger.SyncLoggers()
++ if err != nil {
++ return
++ }
++ }()
++ mockJwtService := &mocks.MockJwtTokenService{}
++ mockSessionService := &mocks.MockServiceSession{}
++ mockReviewUsecase := &mocks.MockReviewsUsecase{}
++
++ handler := NewReviewHandler(mockReviewUsecase, mockSessionService, mockJwtService)
++
++ t.Run("Successful Review Creation", func(t *testing.T) {
++ mockJwtService.MockValidate = func(tokenString string, expectedSessionId string) (*middleware.JwtCsrfClaims, error) {
++ return &middleware.JwtCsrfClaims{}, nil
++ }
++ mockSessionService.MockGetUserID = func(ctx context.Context, sessionID string) (string, error) {
++ return "test-user-id", nil
++ }
++ mockReviewUsecase.MockCreateReview = func(ctx context.Context, review *domain.Review, userId string) error {
++ assert.Equal(t, "test-user-id", userId)
++ assert.Equal(t, "Test Review", review.Title)
++ return nil
++ }
++
++ reviewPayload := `{"title":"Test Review","text":"Review Content","host_id":"host1","user_id":"user1"}`
++ request := httptest.NewRequest(http.MethodPost, "/reviews", bytes.NewReader([]byte(reviewPayload)))
++ request.Header.Set("Cookie", "session_id=test-session-id")
++ request.Header.Set("X-CSRF-Token", "Bearer valid-token")
++
++ responseRecorder := httptest.NewRecorder()
++ handler.CreateReview(responseRecorder, request)
++
++ assert.Equal(t, http.StatusCreated, responseRecorder.Code)
++ })
++
++ t.Run("Missing CSRF Token", func(t *testing.T) {
++ reviewPayload := `{"title":"Test Review","text":"Review Content"}`
++ request := httptest.NewRequest(http.MethodPost, "/reviews", bytes.NewReader([]byte(reviewPayload)))
++ request.Header.Set("Cookie", "session_id=test-session-id")
++
++ responseRecorder := httptest.NewRecorder()
++ handler.CreateReview(responseRecorder, request)
++
++ assert.Equal(t, http.StatusUnauthorized, responseRecorder.Code)
++ })
++
++ t.Run("Invalid JWT Token", func(t *testing.T) {
++ mockJwtService.MockValidate = func(tokenString string, expectedSessionId string) (*middleware.JwtCsrfClaims, error) {
++ return nil, errors.New("invalid JWT token")
++ }
++
++ reviewPayload := `{"title":"Test Review","text":"Review Content"}`
++ request := httptest.NewRequest(http.MethodPost, "/reviews", bytes.NewReader([]byte(reviewPayload)))
++ request.Header.Set("Cookie", "session_id=test-session-id")
++ request.Header.Set("X-CSRF-Token", "Bearer invalid-token")
++
++ responseRecorder := httptest.NewRecorder()
++ handler.CreateReview(responseRecorder, request)
++
++ assert.Equal(t, http.StatusUnauthorized, responseRecorder.Code)
++ })
++
++ t.Run("Failed to Get User ID from Session", func(t *testing.T) {
++ mockJwtService.MockValidate = func(tokenString string, expectedSessionId string) (*middleware.JwtCsrfClaims, error) {
++ return &middleware.JwtCsrfClaims{}, nil
++ }
++ mockSessionService.MockGetUserID = func(ctx context.Context, sessionID string) (string, error) {
++ return "", errors.New("failed to get user ID")
++ }
++
++ reviewPayload := `{"title":"Test Review","text":"Review Content"}`
++ request := httptest.NewRequest(http.MethodPost, "/reviews", bytes.NewReader([]byte(reviewPayload)))
++ request.Header.Set("Cookie", "session_id=test-session-id")
++ request.Header.Set("X-CSRF-Token", "Bearer valid-token")
++
++ responseRecorder := httptest.NewRecorder()
++ handler.CreateReview(responseRecorder, request)
++
++ assert.Equal(t, http.StatusInternalServerError, responseRecorder.Code)
++ })
++ t.Run("Failed to Get User ID from Session", func(t *testing.T) {
++ mockJwtService.MockValidate = func(tokenString string, expectedSessionId string) (*middleware.JwtCsrfClaims, error) {
++ return &middleware.JwtCsrfClaims{}, nil
++ }
++ mockSessionService.MockGetUserID = func(ctx context.Context, sessionID string) (string, error) {
++ return "", errors.New("failed to get user ID")
++ }
++
++ reviewPayload := `{"title":"Test Review","text":"Review Content"}`
++ request := httptest.NewRequest(http.MethodPost, "/reviews", bytes.NewReader([]byte(reviewPayload)))
++ request.Header.Set("Cookie", "session_id=test-session-id")
++ request.Header.Set("X-CSRF-Token", "Bearer valid-token")
++
++ responseRecorder := httptest.NewRecorder()
++ handler.CreateReview(responseRecorder, request)
++
++ assert.Equal(t, http.StatusInternalServerError, responseRecorder.Code)
++ })
++}
++
++func TestGetUserReviews(t *testing.T) {
++ require.NoError(t, logger.InitLoggers())
++ defer func() {
++ err := logger.SyncLoggers()
++ if err != nil {
++ return
++ }
++ }()
++ mockJwtService := &mocks.MockJwtTokenService{}
++ mockSessionService := &mocks.MockServiceSession{}
++ mockReviewUsecase := &mocks.MockReviewsUsecase{}
++
++ handler := NewReviewHandler(mockReviewUsecase, mockSessionService, mockJwtService)
++
++ t.Run("Successful GetUserReviews", func(t *testing.T) {
++ mockReviewUsecase.MockGetUserReviews = func(ctx context.Context, userId string) ([]domain.UserReviews, error) {
++ assert.Equal(t, "", userId)
++ return []domain.UserReviews{
++ {
++ HostID: "host1",
++ Title: "Test Review 1",
++ },
++ {
++ HostID: "host2",
++ Title: "Test Review 2",
++ },
++ }, nil
++ }
++
++ request := httptest.NewRequest(http.MethodGet, "/reviews/{userId}", nil)
++ responseRecorder := httptest.NewRecorder()
++
++ handler.GetUserReviews(responseRecorder, request)
++
++ assert.Equal(t, http.StatusOK, responseRecorder.Code)
++ })
++
++ t.Run("Error from GetUserReviews Usecase", func(t *testing.T) {
++ mockReviewUsecase.MockGetUserReviews = func(ctx context.Context, userId string) ([]domain.UserReviews, error) {
++ assert.Equal(t, "", userId)
++ return nil, errors.New("database error")
++ }
++
++ request := httptest.NewRequest(http.MethodGet, "/reviews/{userId}", nil)
++ responseRecorder := httptest.NewRecorder()
++
++ handler.GetUserReviews(responseRecorder, request)
++
++ assert.Equal(t, http.StatusInternalServerError, responseRecorder.Code)
++ })
++}
++
++func TestDeleteReview(t *testing.T) {
++ require.NoError(t, logger.InitLoggers())
++ defer func() {
++ err := logger.SyncLoggers()
++ if err != nil {
++ return
++ }
++ }()
++ mockJwtService := &mocks.MockJwtTokenService{}
++ mockSessionService := &mocks.MockServiceSession{}
++ mockReviewUsecase := &mocks.MockReviewsUsecase{}
++ handler := NewReviewHandler(mockReviewUsecase, mockSessionService, mockJwtService)
++
++ t.Run("Successful DeleteReview", func(t *testing.T) {
++ mockJwtService.MockValidate = func(tokenString string, expectedSessionId string) (*middleware.JwtCsrfClaims, error) {
++ return &middleware.JwtCsrfClaims{}, nil
++ }
++ mockSessionService.MockGetUserID = func(ctx context.Context, sessionID string) (string, error) {
++ return "test-user-id", nil
++ }
++ mockReviewUsecase.MockDeleteReview = func(ctx context.Context, userId, hostId string) error {
++ assert.Equal(t, "test-user-id", userId)
++ assert.Equal(t, "test-host-id", hostId)
++ return nil
++ }
++
++ request := httptest.NewRequest(http.MethodDelete, "/reviews/{adId}", nil)
++ request = mux.SetURLVars(request, map[string]string{"hostId": "test-host-id"})
++ request.Header.Set("Cookie", "session_id=test-session-id")
++ request.Header.Set("X-CSRF-Token", "Bearer valid-token")
++
++ rr := httptest.NewRecorder()
++ handler.DeleteReview(rr, request)
++
++ assert.Equal(t, http.StatusOK, rr.Code)
++ assert.Contains(t, rr.Body.String(), "deleted successfully")
++ })
++
++ t.Run("Missing CSRF Token", func(t *testing.T) {
++ request := httptest.NewRequest(http.MethodDelete, "/reviews/{adId}", nil)
++ request = mux.SetURLVars(request, map[string]string{"hostId": "test-host-id"})
++ request.Header.Set("Cookie", "session_id=test-session-id")
++
++ rr := httptest.NewRecorder()
++ handler.DeleteReview(rr, request)
++
++ assert.Equal(t, http.StatusUnauthorized, rr.Code)
++ })
++
++ t.Run("Invalid JWT Token", func(t *testing.T) {
++ mockJwtService.MockValidate = func(tokenString string, expectedSessionId string) (*middleware.JwtCsrfClaims, error) {
++ return nil, errors.New("invalid JWT token")
++ }
++
++ request := httptest.NewRequest(http.MethodDelete, "/reviews/{adId}", nil)
++ request = mux.SetURLVars(request, map[string]string{"hostId": "test-host-id"})
++ request.Header.Set("Cookie", "session_id=test-session-id")
++ request.Header.Set("X-CSRF-Token", "Bearer invalid-token")
++
++ rr := httptest.NewRecorder()
++ handler.DeleteReview(rr, request)
++
++ assert.Equal(t, http.StatusUnauthorized, rr.Code)
++ })
++
++ t.Run("Session ID Extraction Error", func(t *testing.T) {
++ request := httptest.NewRequest(http.MethodDelete, "/reviews/{adId}", nil)
++ request = mux.SetURLVars(request, map[string]string{"hostId": "test-host-id"})
++
++ rr := httptest.NewRecorder()
++ handler.DeleteReview(rr, request)
++
++ assert.Equal(t, http.StatusInternalServerError, rr.Code)
++ })
++
++ t.Run("Error Getting UserID from Session", func(t *testing.T) {
++ mockJwtService.MockValidate = func(tokenString string, expectedSessionId string) (*middleware.JwtCsrfClaims, error) {
++ return &middleware.JwtCsrfClaims{}, nil
++ }
++ mockSessionService.MockGetUserID = func(ctx context.Context, sessionID string) (string, error) {
++ return "", errors.New("session error")
++ }
++
++ request := httptest.NewRequest(http.MethodDelete, "/reviews/{adId}", nil)
++ request = mux.SetURLVars(request, map[string]string{"hostId": "test-host-id"})
++ request.Header.Set("Cookie", "session_id=test-session-id")
++ request.Header.Set("X-CSRF-Token", "Bearer valid-token")
++
++ rr := httptest.NewRecorder()
++ handler.DeleteReview(rr, request)
++
++ assert.Equal(t, http.StatusInternalServerError, rr.Code)
++ })
++
++ t.Run("Error Deleting Review", func(t *testing.T) {
++ mockJwtService.MockValidate = func(tokenString string, expectedSessionId string) (*middleware.JwtCsrfClaims, error) {
++ return &middleware.JwtCsrfClaims{}, nil
++ }
++ mockSessionService.MockGetUserID = func(ctx context.Context, sessionID string) (string, error) {
++ return "test-user-id", nil
++ }
++ mockReviewUsecase.MockDeleteReview = func(ctx context.Context, userId, hostId string) error {
++ return errors.New("delete error")
++ }
++
++ request := httptest.NewRequest(http.MethodDelete, "/reviews/{hostId}", nil)
++ request = mux.SetURLVars(request, map[string]string{"hostId": "test-host-id"})
++ request.Header.Set("Cookie", "session_id=test-session-id")
++ request.Header.Set("X-CSRF-Token", "Bearer valid-token")
++
++ rr := httptest.NewRecorder()
++ handler.DeleteReview(rr, request)
++
++ assert.Equal(t, http.StatusInternalServerError, rr.Code)
++ })
++}
++
++func TestUpdateReview(t *testing.T) {
++ require.NoError(t, logger.InitLoggers())
++ defer func() {
++ err := logger.SyncLoggers()
++ if err != nil {
++ return
++ }
++ }()
++ mockJwtService := &mocks.MockJwtTokenService{}
++ mockSessionService := &mocks.MockServiceSession{}
++ mockReviewUsecase := &mocks.MockReviewsUsecase{}
++ handler := NewReviewHandler(mockReviewUsecase, mockSessionService, mockJwtService)
++
++ t.Run("Successful UpdateReview", func(t *testing.T) {
++ mockJwtService.MockValidate = func(tokenString string, sessionID string) (*middleware.JwtCsrfClaims, error) {
++ return &middleware.JwtCsrfClaims{}, nil
++ }
++ mockSessionService.MockGetUserID = func(ctx context.Context, sessionID string) (string, error) {
++ return "test-user-id", nil
++ }
++ mockReviewUsecase.MockUpdateReview = func(ctx context.Context, userID, hostID string, review *domain.Review) error {
++ assert.Equal(t, "test-host-id", hostID)
++ assert.Equal(t, "test-user-id", userID)
++ assert.Equal(t, "Test Title", review.Title)
++ return nil
++ }
++
++ updatedReview := `{"title":"Test Title","text":"Updated text"}`
++
++ request := httptest.NewRequest(http.MethodPut, "/reviews/{hostId}", bytes.NewBufferString(updatedReview))
++ request = mux.SetURLVars(request, map[string]string{"hostId": "test-host-id"})
++ request.Header.Set("X-CSRF-Token", "Bearer valid-token")
++ request.Header.Set("Cookie", "session_id=test-session-id")
++
++ rr := httptest.NewRecorder()
++ handler.UpdateReview(rr, request)
++
++ assert.Equal(t, http.StatusOK, rr.Code)
++ assert.Contains(t, rr.Body.String(), "updated successfully")
++ })
++
++ t.Run("Missing CSRF Token", func(t *testing.T) {
++ request := httptest.NewRequest(http.MethodPut, "/reviews/{hostId}", nil)
++ request = mux.SetURLVars(request, map[string]string{"hostId": "test-host-id"})
++ request.Header.Set("Cookie", "session_id=test-session-id")
++
++ rr := httptest.NewRecorder()
++ handler.UpdateReview(rr, request)
++
++ assert.Equal(t, http.StatusUnauthorized, rr.Code)
++ })
++
++ t.Run("Invalid JWT Token", func(t *testing.T) {
++ mockJwtService.MockValidate = func(tokenString string, sessionID string) (*middleware.JwtCsrfClaims, error) {
++ return nil, errors.New("invalid token")
++ }
++
++ request := httptest.NewRequest(http.MethodPut, "/reviews/{hostId}", nil)
++ request = mux.SetURLVars(request, map[string]string{"hostId": "test-host-id"})
++ request.Header.Set("X-CSRF-Token", "Bearer invalid-token")
++ request.Header.Set("Cookie", "session_id=test-session-id")
++
++ rr := httptest.NewRecorder()
++ handler.UpdateReview(rr, request)
++
++ assert.Equal(t, http.StatusUnauthorized, rr.Code)
++ })
++
++ t.Run("Session ID Error", func(t *testing.T) {
++ request := httptest.NewRequest(http.MethodPut, "/reviews/{hostId}", nil)
++ request = mux.SetURLVars(request, map[string]string{"hostId": "test-host-id"})
++
++ rr := httptest.NewRecorder()
++ handler.UpdateReview(rr, request)
++
++ assert.Equal(t, http.StatusInternalServerError, rr.Code)
++ })
++
++ t.Run("Failed to get UserID", func(t *testing.T) {
++ mockJwtService.MockValidate = func(tokenString string, sessionID string) (*middleware.JwtCsrfClaims, error) {
++ return &middleware.JwtCsrfClaims{}, nil
++ }
++ mockSessionService.MockGetUserID = func(ctx context.Context, sessionID string) (string, error) {
++ return "", errors.New("failed to get user ID")
++ }
++
++ request := httptest.NewRequest(http.MethodPut, "/reviews/{hostId}", nil)
++ request = mux.SetURLVars(request, map[string]string{"hostId": "test-host-id"})
++ request.Header.Set("X-CSRF-Token", "Bearer valid-token")
++ request.Header.Set("Cookie", "session_id=test-session-id")
++
++ rr := httptest.NewRecorder()
++ handler.UpdateReview(rr, request)
++
++ assert.Equal(t, http.StatusInternalServerError, rr.Code)
++ })
++
++ t.Run("Failed Unmarshal", func(t *testing.T) {
++ request := httptest.NewRequest(http.MethodPut, "/reviews/{hostId}", bytes.NewBufferString("invalid-json"))
++ request = mux.SetURLVars(request, map[string]string{"hostId": "test-host-id"})
++ request.Header.Set("X-CSRF-Token", "Bearer valid-token")
++ request.Header.Set("Cookie", "session_id=test-session-id")
++
++ rr := httptest.NewRecorder()
++ handler.UpdateReview(rr, request)
++
++ assert.Equal(t, http.StatusInternalServerError, rr.Code)
++ })
++
++ t.Run("UpdateReview Usecase Error", func(t *testing.T) {
++ mockJwtService.MockValidate = func(tokenString string, sessionID string) (*middleware.JwtCsrfClaims, error) {
++ return &middleware.JwtCsrfClaims{}, nil
++ }
++ mockSessionService.MockGetUserID = func(ctx context.Context, sessionID string) (string, error) {
++ return "test-user-id", nil
++ }
++ mockReviewUsecase.MockUpdateReview = func(ctx context.Context, userID, hostID string, review *domain.Review) error {
++ return errors.New("update error")
++ }
++
++ request := httptest.NewRequest(http.MethodPut, "/reviews/{hostId}", bytes.NewBufferString(`{"title":"Test"}`))
++ request = mux.SetURLVars(request, map[string]string{"hostId": "test-host-id"})
++ request.Header.Set("X-CSRF-Token", "Bearer valid-token")
++ request.Header.Set("Cookie", "session_id=test-session-id")
++
++ rr := httptest.NewRecorder()
++ handler.UpdateReview(rr, request)
++
++ assert.Equal(t, http.StatusInternalServerError, rr.Code)
++ })
++}
+Index: internal/reviews/mocks/mocks.go
+IDEA additional info:
+Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
+<+>UTF-8
+===================================================================
+diff --git a/internal/reviews/mocks/mocks.go b/internal/reviews/mocks/mocks.go
+new file mode 100644
+--- /dev/null (date 1734482865685)
++++ b/internal/reviews/mocks/mocks.go (date 1734482865685)
+@@ -0,0 +1,95 @@
++package mocks
++
++import (
++ "2024_2_FIGHT-CLUB/domain"
++ "2024_2_FIGHT-CLUB/internal/service/middleware"
++ "context"
++ "github.com/golang-jwt/jwt"
++)
++
++type MockJwtTokenService struct {
++ MockCreate func(session_id string, tokenExpTime int64) (string, error)
++ MockValidate func(tokenString string, expectedSessionId string) (*middleware.JwtCsrfClaims, error)
++ MockParseSecretGetter func(token *jwt.Token) (interface{}, error)
++}
++
++func (m *MockJwtTokenService) Create(session_id string, tokenExpTime int64) (string, error) {
++ return m.MockCreate(session_id, tokenExpTime)
++}
++
++func (m *MockJwtTokenService) Validate(tokenString string, expectedSessionId string) (*middleware.JwtCsrfClaims, error) {
++ return m.MockValidate(tokenString, expectedSessionId)
++}
++
++func (m *MockJwtTokenService) ParseSecretGetter(token *jwt.Token) (interface{}, error) {
++ return m.MockParseSecretGetter(token)
++}
++
++type MockServiceSession struct {
++ MockGetUserID func(ctx context.Context, sessionID string) (string, error)
++ MockLogoutSession func(ctx context.Context, sessionID string) error
++ MockCreateSession func(ctx context.Context, user *domain.User) (string, error)
++ MockGetSessionData func(ctx context.Context, sessionID string) (*domain.SessionData, error)
++}
++
++func (m *MockServiceSession) GetUserID(ctx context.Context, sessionID string) (string, error) {
++ return m.MockGetUserID(ctx, sessionID)
++}
++
++func (m *MockServiceSession) LogoutSession(ctx context.Context, sessionID string) error {
++ return m.MockLogoutSession(ctx, sessionID)
++}
++
++func (m *MockServiceSession) CreateSession(ctx context.Context, user *domain.User) (string, error) {
++ return m.MockCreateSession(ctx, user)
++}
++
++func (m *MockServiceSession) GetSessionData(ctx context.Context, sessionID string) (*domain.SessionData, error) {
++ return m.MockGetSessionData(ctx, sessionID)
++}
++
++type MockReviewsUsecase struct {
++ MockCreateReview func(ctx context.Context, review *domain.Review, userId string) error
++ MockGetUserReviews func(ctx context.Context, userId string) ([]domain.UserReviews, error)
++ MockUpdateReview func(ctx context.Context, userID, hostID string, updatedReview *domain.Review) error
++ MockDeleteReview func(ctx context.Context, userID, hostID string) error
++}
++
++func (m *MockReviewsUsecase) CreateReview(ctx context.Context, review *domain.Review, userId string) error {
++ return m.MockCreateReview(ctx, review, userId)
++}
++
++func (m *MockReviewsUsecase) GetUserReviews(ctx context.Context, userId string) ([]domain.UserReviews, error) {
++ return m.MockGetUserReviews(ctx, userId)
++}
++
++func (m *MockReviewsUsecase) UpdateReview(ctx context.Context, userID, hostID string, updatedReview *domain.Review) error {
++ return m.MockUpdateReview(ctx, userID, hostID, updatedReview)
++}
++
++func (m *MockReviewsUsecase) DeleteReview(ctx context.Context, userID, hostID string) error {
++ return m.MockDeleteReview(ctx, userID, hostID)
++}
++
++type MockReviewsRepository struct {
++ MockCreateReview func(ctx context.Context, review *domain.Review) error
++ MockGetUserReviews func(ctx context.Context, userID string) ([]domain.UserReviews, error)
++ MockDeleteReview func(ctx context.Context, userID, hostID string) error
++ MockUpdateReview func(ctx context.Context, userID, hostID string, updatedReview *domain.Review) error
++}
++
++func (m *MockReviewsRepository) CreateReveiw(ctx context.Context, review *domain.Review) error {
++ return m.MockCreateReview(ctx, review)
++}
++
++func (m *MockReviewsRepository) GetUserReviews(ctx context.Context, userID string) ([]domain.UserReviews, error) {
++ return m.MockGetUserReviews(ctx, userID)
++}
++
++func (m *MockReviewsRepository) UpdateReview(ctx context.Context, userID, hostID string, updatedReview *domain.Review) error {
++ return m.MockUpdateReview(ctx, userID, hostID, updatedReview)
++}
++
++func (m *MockReviewsRepository) DeleteReview(ctx context.Context, userID, hostID string) error {
++ return m.MockDeleteReview(ctx, userID, hostID)
++}
diff --git a/.idea/shelf/Uncommitted_changes_before_Update_at_18_12_2024_4_11__Changes_.xml b/.idea/shelf/Uncommitted_changes_before_Update_at_18_12_2024_4_11__Changes_.xml
new file mode 100644
index 0000000..05640e0
--- /dev/null
+++ b/.idea/shelf/Uncommitted_changes_before_Update_at_18_12_2024_4_11__Changes_.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..6dd55c3
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,18 @@
+# 1. Build it
+FROM golang:1.23.1 AS builder
+WORKDIR /app
+COPY . .
+RUN go mod download
+
+ENV CGO_ENABLED=0
+ENV GOOS=linux
+RUN go build -o /app/backend ./cmd/webapp
+
+
+# 2. Run it
+FROM alpine:latest
+COPY --from=builder /app/backend /app/backend
+COPY --from=builder /app/ssl/ /app/ssl/
+
+WORKDIR /app
+CMD ["/app/backend"]
\ No newline at end of file
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..8d9142d
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,35 @@
+.PHONY: build build-migrator build-ads build-auth build-city build-webapp run-migrator run-ads run-auth run-city run-webapp
+
+build: build-migrator build-ads build-auth build-city build-webapp
+
+build-migrator:
+ go build -o bin/migrator ./cmd/migrator/
+
+build-ads:
+ go build -o bin/ads_service ./microservices/ads_service/cmd/main.go
+
+build-auth:
+ go build -o bin/auth_service ./microservices/auth_service/cmd/main.go
+
+build-city:
+ go build -o bin/city_service ./microservices/city_service/cmd/main.go
+
+build-webapp:
+ go build -o bin/webapp ./cmd/webapp/
+
+run-migrator: build-migrator
+ ./bin/migrator
+
+run-ads: build-ads
+ ./bin/ads_service
+
+run-auth: build-auth
+ ./bin/auth_service
+
+run-city: build-city
+ ./bin/city_service
+
+run-webapp: build-webapp
+ ./bin/webapp
+
+run: run-migrator run-ads run-auth run-city run-webapp
\ No newline at end of file
diff --git a/README.md b/README.md
index d3676fa..ac023ab 100644
--- a/README.md
+++ b/README.md
@@ -18,7 +18,7 @@
## Деплой
-Нужно указать в `.env` нужные переменные. `docker-compose.yml` автоматически возьмет оттуда переменные
+Нужно указать в `.env` нужные переменные. `docker-compose.yml` автоматически возьмет их оттуда
## Ссылки на деплой
diff --git a/cmd/migrator/Dockerfile b/cmd/migrator/Dockerfile
new file mode 100644
index 0000000..c2042d8
--- /dev/null
+++ b/cmd/migrator/Dockerfile
@@ -0,0 +1,20 @@
+# This is a separate image for migrator
+# We use this approach because other services depend on it
+
+# 1. Build it
+FROM golang:1.23.1 AS builder
+WORKDIR /app
+COPY . .
+RUN go mod download
+
+ENV CGO_ENABLED=0
+ENV GOOS=linux
+RUN go build -o /app/migrator ./cmd/migrator
+
+
+# 2. Run it
+FROM alpine:latest
+COPY --from=builder /app/migrator /app/migrator
+COPY --from=builder /app/cities_images/ /app/cities_images/
+WORKDIR /app
+CMD ["/app/migrator"]
diff --git a/cmd/migrator/main.go b/cmd/migrator/main.go
index b5478e0..f5c32a7 100644
--- a/cmd/migrator/main.go
+++ b/cmd/migrator/main.go
@@ -5,13 +5,14 @@ import (
"2024_2_FIGHT-CLUB/internal/service/dsn"
"context"
"fmt"
+ "log"
+ "os"
+
"github.com/joho/godotenv"
"github.com/minio/minio-go/v7"
"github.com/minio/minio-go/v7/pkg/credentials"
"gorm.io/driver/postgres"
"gorm.io/gorm"
- "log"
- "os"
)
func connectMinio() (*minio.Client, error) {
@@ -50,7 +51,7 @@ func migrate() (err error) {
if err != nil {
return err
}
- err = db.AutoMigrate(&domain.User{}, &domain.City{}, &domain.Ad{}, &domain.AdPosition{}, &domain.AdAvailableDate{}, &domain.Image{}, &domain.Request{}, &domain.Review{}, &domain.Message{}, &domain.Favorites{})
+ err = db.AutoMigrate(&domain.User{}, &domain.City{}, &domain.Ad{}, &domain.AdPosition{}, &domain.AdAvailableDate{}, &domain.Image{}, &domain.VisitedRegions{}, &domain.Review{}, &domain.Message{}, &domain.Favorites{}, &domain.AdRooms{})
if err != nil {
return err
}
@@ -70,35 +71,35 @@ func main() {
func seedCities(db *gorm.DB, minioClient *minio.Client) error {
cities := []domain.City{
- {Title: "Москва", EnTitle: "Moscow", Description: "Столица России", Image: ""},
- {Title: "Санкт-Петербург", EnTitle: "Saint-Petersburg", Description: "Культурная столица России", Image: ""},
- {Title: "Новосибирск", EnTitle: "Novosibirsk", Description: "Третий по численности город России", Image: ""},
- {Title: "Екатеринбург", EnTitle: "Yekaterinburg", Description: "Административный центр Урала", Image: ""},
- {Title: "Казань", EnTitle: "Kazan", Description: "Столица Республики Татарстан", Image: ""},
- {Title: "Нижний Новгород", EnTitle: "Nizhny-Novgorod", Description: "Важный культурный и экономический центр", Image: ""},
- {Title: "Челябинск", EnTitle: "Chelyabinsk", Description: "Крупный промышленный город на Урале", Image: ""},
- {Title: "Самара", EnTitle: "Samara", Description: "Крупный город на Волге", Image: ""},
- {Title: "Омск", EnTitle: "Omsk", Description: "Один из крупнейших городов Сибири", Image: ""},
- {Title: "Ростов-на-Дону", EnTitle: "Rostov-on-Don", Description: "Город на юге России", Image: ""},
- {Title: "Уфа", EnTitle: "Ufa", Description: "Столица Башкортостана", Image: ""},
- {Title: "Красноярск", EnTitle: "Krasnoyarsk", Description: "Крупный центр Восточной Сибири", Image: ""},
- {Title: "Воронеж", EnTitle: "Voronezh", Description: "Культурный и промышленный центр", Image: ""},
- {Title: "Пермь", EnTitle: "Perm", Description: "Город на Урале", Image: ""},
- {Title: "Волгоград", EnTitle: "Volgograd", Description: "Город-герой на Волге", Image: ""},
- {Title: "Краснодар", EnTitle: "Krasnodar", Description: "Центр Краснодарского края", Image: ""},
- {Title: "Тюмень", EnTitle: "Tyumen", Description: "Один из старейших сибирских городов", Image: ""},
- {Title: "Ижевск", EnTitle: "Izhevsk", Description: "Столица Удмуртии", Image: ""},
- {Title: "Барнаул", EnTitle: "Barnaul", Description: "Крупный город в Алтайском крае", Image: ""},
- {Title: "Ульяновск", EnTitle: "Ulyanovsk", Description: "Родина В.И. Ленина", Image: ""},
- {Title: "Иркутск", EnTitle: "Irkutsk", Description: "Крупный город на Байкале", Image: ""},
- {Title: "Хабаровск", EnTitle: "Khabarovsk", Description: "Один из крупнейших городов Дальнего Востока", Image: ""},
- {Title: "Ярославль", EnTitle: "Yaroslavl", Description: "Один из старейших городов России", Image: ""},
- {Title: "Махачкала", EnTitle: "Makhachkala", Description: "Столица Дагестана", Image: ""},
- {Title: "Томск", EnTitle: "Tomsk", Description: "Крупный университетский город", Image: ""},
- {Title: "Оренбург", EnTitle: "Orenburg", Description: "Город на границе Европы и Азии", Image: ""},
- {Title: "Кемерово", EnTitle: "Kemerovo", Description: "Центр Кузбасса", Image: ""},
- {Title: "Рязань", EnTitle: "Ryazan", Description: "Один из древних городов России", Image: ""},
- {Title: "Астрахань", EnTitle: "Astrakhan", Description: "Крупный порт на Каспии", Image: ""},
+ {Title: "Москва", EnTitle: "Moscow", Description: "Столица России, город с многовековой историей. В Москве можно посетить Красную площадь, Кремль, Третьяковскую галерею, а также знаменитый парк «Зарядье». Для прогулок подойдут парк Горького, ВДНХ и Воробьевы горы. Любителям шопинга понравится улица Арбат и универмаг ГУМ. Не забудьте заглянуть в Большой театр и попробовать московскую кухню в одном из многочисленных ресторанов.", Image: ""},
+ {Title: "Санкт-Петербург", EnTitle: "Saint-Petersburg", Description: "Культурная столица России. Обязательно посетите Эрмитаж, Исаакиевский собор и Петропавловскую крепость. Прогуляйтесь по Невскому проспекту и насладитесь видами реки Невы с разводных мостов. Летние белые ночи и экскурсии по рекам и каналам оставят незабываемые впечатления. Недалеко от города находятся дворцы Петергофа и Царского Села.", Image: ""},
+ {Title: "Новосибирск", EnTitle: "Novosibirsk", Description: "Третий по численности город России и неофициальная столица Сибири. Здесь стоит посетить Новосибирский оперный театр, зоопарк, Академгородок и пляжи на берегу Обского водохранилища. Город славится своими научными институтами и музеями, такими как Музей науки.", Image: ""},
+ {Title: "Екатеринбург", EnTitle: "Yekaterinburg", Description: "Административный центр Урала. Здесь находятся Храм на Крови, посвященный семье Романовых, и Ельцин Центр. Город известен своим рок-движением и арт-пространствами. Для любителей природы подойдут прогулки в окрестностях, где находится граница Европы и Азии.", Image: ""},
+ {Title: "Казань", EnTitle: "Kazan", Description: "Столица Республики Татарстан. Обязательно посетите Казанский Кремль, мечеть Кул-Шариф и улочку Баумана. Здесь можно попробовать татарскую кухню: эчпочмак, чак-чак и кыстыбый. Летом отправляйтесь на Волгу или посетите Свияжск.", Image: ""},
+ {Title: "Нижний Новгород", EnTitle: "Nizhny-Novgorod", Description: "Город с богатой историей. Посетите Нижегородский Кремль, Чкаловскую лестницу и улицу Большая Покровская. С вершины Кремля открывается потрясающий вид на слияние рек Волги и Оки. Рядом находятся древние города Золотого кольца.\n\n", Image: ""},
+ {Title: "Челябинск", EnTitle: "Chelyabinsk", Description: "Промышленный центр на Урале. Среди достопримечательностей — Арка любви, городской сад имени Пушкина и Челябинский краеведческий музей. За пределами города находятся красивые озера и национальные парки.", Image: ""},
+ {Title: "Самара", EnTitle: "Samara", Description: "Город на Волге, известный своими космическими достижениями. Здесь можно посетить Музей космонавтики, прогулочный бульвар и бункер Сталина. В жаркий день отправляйтесь на пляжи Волги.", Image: ""},
+ {Title: "Омск", EnTitle: "Omsk", Description: "Крупный город в Сибири с богатым культурным наследием. Прогуляйтесь по улице Ленина, посетите Омский драматический театр и Успенский собор. Город славится своими музеями, в том числе музеем им. Врубеля.", Image: ""},
+ {Title: "Ростов-на-Дону", EnTitle: "Rostov-on-Don", Description: "Южный город, известный своей кухней и казацкой историей. Обязательно попробуйте донскую уху и раки. Прогуляйтесь по набережной, посетите театр имени Горького и совершите прогулку на теплоходе по Дону.", Image: ""},
+ {Title: "Уфа", EnTitle: "Ufa", Description: "Столица Башкортостана, где переплетаются русская и башкирская культуры. В Уфе стоит посетить Монумент дружбы, Ляля-Тюльпан (башкирскую мечеть) и парк Якутова. Не упустите возможность попробовать башкирский мед и традиционные блюда, такие как бешбармак и кыстыбый.", Image: ""},
+ {Title: "Красноярск", EnTitle: "Krasnoyarsk", Description: "Город на берегу Енисея, знаменитый своими природными красотами. Национальный парк «Столбы» — главное место для туристов. В самом городе посетите Красноярскую гидроэлектростанцию и часовню Параскевы Пятницы. Енисейская кухня с блюдами из рыбы и дикоросов — обязательна к дегустации.", Image: ""},
+ {Title: "Воронеж", EnTitle: "Voronezh", Description: "Культурный и промышленный центр. В Воронеже можно посетить Корабль «Гото Предестинация» — копию первого российского линкора, Адмиралтейскую площадь и музей имени Крамского. Для прогулок отлично подойдут улицы Карла Маркса и Петровская набережная.", Image: ""},
+ {Title: "Пермь", EnTitle: "Perm", Description: "Город на Урале, известный своей арт-сценой. Прогуляйтесь по набережной Камы, посетите Пермский театр оперы и балета и музей современного искусства PERMM. Недалеко от города находятся знаменитые Кунгурская ледяная пещера и Каменный город.", Image: ""},
+ {Title: "Волгоград", EnTitle: "Volgograd", Description: "Город-герой, известный своей историей. Посетите Мамаев Курган, мемориальный комплекс и музей-панораму «Сталинградская битва». Прогуляйтесь вдоль Волги и насладитесь видами на Волго-Донской канал.", Image: ""},
+ {Title: "Краснодар", EnTitle: "Krasnodar", Description: "Центр Краснодарского края, известный как «Южная столица». Главная городская достопримечательность — парк Галицкого. Также стоит посетить местные рынки с изобилием фруктов и вин. Для прогулок подойдут улица Красная и городские набережные.", Image: ""},
+ {Title: "Тюмень", EnTitle: "Tyumen", Description: "Старейший сибирский город. Здесь можно посетить горячие источники, мост Влюбленных и Тюменский драматический театр. Рядом расположены знаменитые города-заповедники Тобольск и Ишим.", Image: ""},
+ {Title: "Ижевск", EnTitle: "Izhevsk", Description: "Столица Удмуртии, известная своими оружейными традициями. Обязательно посетите музей Калашникова, Ижевский пруд и Успенский собор. Город также славится своей удмуртской кухней: попробуйте перепечи и табани.", Image: ""},
+ {Title: "Барнаул", EnTitle: "Barnaul", Description: "Центр Алтайского края. Прогуляйтесь по улице Ленина, посетите Барнаульский планетарий и краеведческий музей. Это удобная отправная точка для путешествий по Алтаю: от Белокурихи до Телецкого озера.", Image: ""},
+ {Title: "Ульяновск", EnTitle: "Ulyanovsk", Description: "Родина В.И. Ленина, где можно посетить мемориальные музеи. Также здесь находятся музеи авиации и автомобильного транспорта. Город расположен на Волге, где есть много красивых пляжей и мест для прогулок.", Image: ""},
+ {Title: "Иркутск", EnTitle: "Irkutsk", Description: "Крупный город вблизи Байкала. В Иркутске стоит прогуляться по 130-му кварталу, посетить Знаменский монастырь и музей декабристов. Байкал — главная достопримечательность, и его можно исследовать круглый год.", Image: ""},
+ {Title: "Хабаровск", EnTitle: "Khabarovsk", Description: "Один из крупнейших городов Дальнего Востока. Прогуляйтесь по Амурскому бульвару, посетите набережную Амура, краеведческий музей имени Гродекова и парк «Динамо».", Image: ""},
+ {Title: "Ярославль", EnTitle: "Yaroslavl", Description: "Один из старейших городов России, часть Золотого кольца. Посетите Спасо-Преображенский монастырь, театр имени Волкова и Волжскую набережную. Город славится своими древними храмами и музеями.", Image: ""},
+ {Title: "Махачкала", EnTitle: "Makhachkala", Description: "Столица Дагестана, расположенная на Каспийском море. Здесь можно посетить Центральную джума-мечеть, музей изобразительных искусств и насладиться видом на Каспий. В окрестностях находятся горные аулы и живописные ущелья.", Image: ""},
+ {Title: "Томск", EnTitle: "Tomsk", Description: "Университетский город с уникальной деревянной архитектурой. Прогуляйтесь по проспекту Ленина, посетите Томский политехнический музей и Музей деревянного зодчества. Окрестности Томска богаты природными достопримечательностями.", Image: ""},
+ {Title: "Оренбург", EnTitle: "Orenburg", Description: "Город на границе Европы и Азии. Здесь можно увидеть символический мост между континентами, посетить краеведческий музей и насладиться видом на реку Урал. Оренбург славится своими пуховыми платками.", Image: ""},
+ {Title: "Кемерово", EnTitle: "Kemerovo", Description: "Центр Кузбасса, региона угледобычи. В городе можно посетить музей-заповедник «Томская писаница», ботанический сад и драматический театр. Рядом находятся горнолыжные курорты Шерегеша.", Image: ""},
+ {Title: "Рязань", EnTitle: "Ryazan", Description: "Один из древнейших городов России. Здесь можно посетить Рязанский кремль, соборный парк и музеи. Город богат архитектурными памятниками и красивыми видами на реку Оку.", Image: ""},
+ {Title: "Астрахань", EnTitle: "Astrakhan", Description: "Город на Каспийском море, известный своими рыбными рынками. Здесь стоит посетить Астраханский кремль, набережную Волги и рыбные рестораны. Город является воротами к дельте Волги, где можно заняться рыбалкой или наблюдением за птицами.", Image: ""},
}
bucketName := "cities"
diff --git a/cmd/webapp/main.go b/cmd/webapp/main.go
index 9c6aad0..4ce6e61 100644
--- a/cmd/webapp/main.go
+++ b/cmd/webapp/main.go
@@ -7,6 +7,9 @@ import (
chatRepository "2024_2_FIGHT-CLUB/internal/chat/repository"
chatUseCase "2024_2_FIGHT-CLUB/internal/chat/usecase"
cityHttpDelivery "2024_2_FIGHT-CLUB/internal/cities/controller"
+ regionsContoller "2024_2_FIGHT-CLUB/internal/regions/controller"
+ regionsRepository "2024_2_FIGHT-CLUB/internal/regions/repository"
+ regionsUsecase "2024_2_FIGHT-CLUB/internal/regions/usecase"
reviewContoller "2024_2_FIGHT-CLUB/internal/reviews/contoller"
reviewRepository "2024_2_FIGHT-CLUB/internal/reviews/repository"
reviewUsecase "2024_2_FIGHT-CLUB/internal/reviews/usecase"
@@ -15,12 +18,14 @@ import (
"2024_2_FIGHT-CLUB/internal/service/middleware"
"2024_2_FIGHT-CLUB/internal/service/router"
"2024_2_FIGHT-CLUB/internal/service/session"
+ "2024_2_FIGHT-CLUB/internal/service/utils"
generatedAds "2024_2_FIGHT-CLUB/microservices/ads_service/controller/gen"
generatedAuth "2024_2_FIGHT-CLUB/microservices/auth_service/controller/gen"
generatedCity "2024_2_FIGHT-CLUB/microservices/city_service/controller/gen"
"fmt"
"github.com/joho/godotenv"
"google.golang.org/grpc"
+ "google.golang.org/grpc/credentials/insecure"
"log"
"net/http"
"os"
@@ -53,7 +58,7 @@ func main() {
if authAdress == "" {
log.Fatalf("AUTH_SERVICE_ADDRESS is not set")
}
- authConn, err := grpc.NewClient(authAdress, grpc.WithInsecure()) // Укажите адрес AuthService
+ authConn, err := grpc.NewClient(authAdress, grpc.WithTransportCredentials(insecure.NewCredentials())) // Укажите адрес AuthService
if err != nil {
log.Fatalf("Failed to connect to AuthService: %v", err)
}
@@ -63,7 +68,7 @@ func main() {
if adsAdress == "" {
log.Fatalf("ADS_SERVICE_ADDRESS is not set")
}
- adsConn, err := grpc.NewClient(adsAdress, grpc.WithInsecure())
+ adsConn, err := grpc.NewClient(adsAdress, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Fatalf("Failed to connect to AdsService: %v", err)
}
@@ -73,22 +78,23 @@ func main() {
if cityAdress == "" {
log.Fatalf("CITY_SERVICE_ADDRESS is not set")
}
- cityConn, err := grpc.NewClient(cityAdress, grpc.WithInsecure())
+ cityConn, err := grpc.NewClient(cityAdress, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Fatalf("Failed to connect to AdsService: %v", err)
}
defer cityConn.Close()
sessionService := session.NewSessionService(redisStore)
-
+ utilsService := utils.NewUtilsInterface()
authClient := generatedAuth.NewAuthClient(authConn)
- authHandler := authHttpDelivery.NewAuthHandler(authClient, sessionService, jwtToken)
+ authHandler := authHttpDelivery.NewAuthHandler(authClient, sessionService, jwtToken, utilsService)
adsClient := generatedAds.NewAdsClient(adsConn)
- adsHandler := adHttpDelivery.NewAdHandler(adsClient, sessionService, jwtToken)
+
+ adsHandler := adHttpDelivery.NewAdHandler(adsClient, sessionService, jwtToken, utilsService)
cityClient := generatedCity.NewCityServiceClient(cityConn)
- cityHandler := cityHttpDelivery.NewCityHandler(cityClient)
+ cityHandler := cityHttpDelivery.NewCityHandler(cityClient, utilsService)
chatsRepository := chatRepository.NewChatRepository(db)
chatsUseCase := chatUseCase.NewChatService(chatsRepository)
@@ -98,10 +104,14 @@ func main() {
reviewsUsecase := reviewUsecase.NewReviewUsecase(reviewsRepository)
reviewsHandler := reviewContoller.NewReviewHandler(reviewsUsecase, sessionService, jwtToken)
- mainRouter := router.SetUpRoutes(authHandler, adsHandler, cityHandler, chatsHandler, reviewsHandler)
+ regionRepository := regionsRepository.NewRegionRepository(db)
+ regionUsecase := regionsUsecase.NewRegionUsecase(regionRepository)
+ regionHandler := regionsContoller.NewRegionHandler(regionUsecase, sessionService, jwtToken)
+
+ mainRouter := router.SetUpRoutes(authHandler, adsHandler, cityHandler, chatsHandler, reviewsHandler, regionHandler)
mainRouter.Use(middleware.RequestIDMiddleware)
mainRouter.Use(middleware.RateLimitMiddleware)
- http.Handle("/", middleware.EnableCORS(mainRouter))
+ http.Handle("/", middleware.RecoverWrap(middleware.EnableCORS(mainRouter)))
if os.Getenv("HTTPS") == "TRUE" {
fmt.Printf("Starting HTTPS server on address %s\n", os.Getenv("BACKEND_URL"))
if err := http.ListenAndServeTLS(os.Getenv("BACKEND_URL"), "ssl/pootnick.crt", "ssl/pootnick.key", nil); err != nil {
diff --git a/docker-compose.yml b/docker-compose.yml
index d92a8c7..5457ccb 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -7,17 +7,15 @@ services:
# Go migrator. It applies all needed migrations.
migrator:
- image: golang:1.23.1
- working_dir: /app
- volumes:
- - .:/app
- command: sh -c "go run ./cmd/migrator"
+ image: rasulovarsen/migrator:latest
environment:
- DB_HOST=${DB_HOST}
- DB_PORT=${DB_PORT}
- DB_USER=${DB_USER}
- DB_PASSWORD=${DB_PASS}
- DB_NAME=${DB_NAME}
+ volumes:
+ - .env:/app/.env
depends_on:
- postgres
networks:
@@ -25,13 +23,9 @@ services:
# Go backend
backend:
- image: golang:1.23.1
- working_dir: /app
+ image: rasulovarsen/backend:latest
ports:
- "8008:8008"
- volumes:
- - .:/app
- command: go run ./cmd/webapp
environment:
- DB_HOST=${DB_HOST}
- DB_PORT=${DB_PORT}
@@ -44,11 +38,17 @@ services:
- MINIO_SECRET_KEY=${MINIO_SECRET_KEY}
- REDIS_HOST=redis
- REDIS_PORT=6379
+ volumes:
+ - .env:/app/.env
depends_on:
- postgres
- minio
- certs
- redis
+ - ads_service
+ - auth_service
+ - city_service
+ - migrator
networks:
- app-network
@@ -87,7 +87,7 @@ services:
image: grafana/grafana:latest
container_name: grafana
ports:
- - "3000:3000"
+ - "8060:3000"
restart: always
environment:
- GF_SECURITY_ADMIN_USER=admin
@@ -110,7 +110,7 @@ services:
image: minio/minio
ports:
- "9000:9000"
- - "8070:9001"
+ - "9001:9001"
environment:
MINIO_ROOT_USER: ${MINIO_ROOT_USER}
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD}
@@ -138,16 +138,16 @@ services:
# Ads service
ads_service:
- image: golang:1.23.1
- working_dir: /app
+ image: rasulovarsen/ads_service:latest
ports:
- "50052:50052"
volumes:
- - .:/app
- command: go run ./microservices/ads_service/cmd/main.go
+ - .env:/.env
depends_on:
- redis
- postgres
+ - minio
+ - minio-client
environment:
- DB_HOST=${DB_HOST}
- DB_PORT=${DB_PORT}
@@ -161,13 +161,11 @@ services:
# Auth service
auth_service:
- image: golang:1.23.1
- working_dir: /app
+ image: rasulovarsen/auth_service:latest
ports:
- "50051:50051"
volumes:
- - .:/app
- command: go run ./microservices/auth_service/cmd/main.go
+ - .env:/.env
depends_on:
- redis
- postgres
@@ -184,16 +182,16 @@ services:
# City service
city_service:
- image: golang:1.23.1
- working_dir: /app
+ image: rasulovarsen/city_service:latest
ports:
- "50053:50053"
volumes:
- - .:/app
- command: go run ./microservices/city_service/cmd/main.go
+ - .env:/.env
depends_on:
- redis
- postgres
+ - minio
+ - minio-client
environment:
- DB_HOST=${DB_HOST}
- DB_PORT=${DB_PORT}
@@ -204,6 +202,16 @@ services:
- REDIS_PORT=6379
networks:
- app-network
+ watchtower:
+ image: containrrr/watchtower:latest
+ container_name: watchtower
+ environment:
+ - WATCHTOWER_CLEANUP=true
+ - WATCHTOWER_ROLLING_RESTART=true
+ volumes:
+ - /var/run/docker.sock:/var/run/docker.sock
+ restart: unless-stopped
+ command: --interval 30
volumes:
postgres_data:
diff --git a/domain/adrooms.go b/domain/adrooms.go
new file mode 100644
index 0000000..57754e1
--- /dev/null
+++ b/domain/adrooms.go
@@ -0,0 +1,14 @@
+package domain
+
+type AdRooms struct {
+ ID int `gorm:"primary_key;auto_increment;column:id" json:"id"`
+ AdID string `gorm:"column:adId;not null" json:"adId"`
+ Type string `gorm:"type:text;size:100;column:type" json:"type"`
+ SquareMeters int `gorm:"column:squareMeters" json:"squareMeters"`
+ Ad Ad `gorm:"foreignKey:adId;references:UUID"`
+}
+
+type AdRoomsResponse struct {
+ Type string `json:"type"`
+ SquareMeters int `json:"squareMeters"`
+}
diff --git a/domain/ads.go b/domain/ads.go
index e6b6437..b85af92 100644
--- a/domain/ads.go
+++ b/domain/ads.go
@@ -1,10 +1,28 @@
package domain
+//go:generate easyjson -all ads.go
+
import (
"context"
"time"
)
+//easyjson:json
+type PlacesResponse struct {
+ Places GetAllAdsListResponse `json:"places"`
+}
+
+//easyjson:json
+type GetAllAdsListResponse struct {
+ Housing []GetAllAdsResponse `json:"housing"`
+}
+
+//easyjson:json
+type GetOneAdResponse struct {
+ Place GetAllAdsResponse `json:"place"`
+}
+
+//easyjson:json
type Ad struct {
UUID string `gorm:"type:uuid;primaryKey;default:gen_random_uuid();column:uuid" json:"id"`
CityID int `gorm:"column:cityId;not null" json:"cityId"`
@@ -14,6 +32,15 @@ type Ad struct {
Description string `gorm:"type:text;size:1000;column:description" json:"description"`
RoomsNumber int `gorm:"column:roomsNumber" json:"roomsNumber"`
ViewsCount int `gorm:"column:viewsCount;default:0" json:"viewsCount"`
+ SquareMeters int `gorm:"column:squareMeters" json:"squareMeters"`
+ Floor int `gorm:"column:floor" json:"floor"`
+ BuildingType string `gorm:"type:text;size:255;column:buildingType" json:"buildingType"`
+ HasBalcony bool `gorm:"type:bool;default:false;column:hasBalcony" json:"hasBalcony"`
+ HasElevator bool `gorm:"type:bool;default:false;column:hasElevator" json:"hasElevator"`
+ HasGas bool `gorm:"type:bool;default:false;column:hasGas" json:"hasGas"`
+ LikesCount int `gorm:"column:likesCount;default:0" json:"likesCount"`
+ Priority int `gorm:"column:priority;default:0" json:"priority"`
+ EndBoostDate time.Time `gorm:"type:date;column:endBoostDate" json:"endBoostDate"`
City City `gorm:"foreignKey:CityID;references:ID" json:"-"`
Author User `gorm:"foreignKey:AuthorUUID;references:UUID" json:"-"`
}
@@ -21,45 +48,69 @@ type Ad struct {
type Favorites struct {
AdId string `gorm:"primaryKey;column:adId" json:"adId"`
UserId string `gorm:"primaryKey;column:userId" json:"userId"`
-
- User User `gorm:"foreignKey:UserId;references:UUID" json:"-"`
- Ad Ad `gorm:"foreignKey:AdId;references:UUID" json:"-"`
+ User User `gorm:"foreignKey:UserId;references:UUID" json:"-"`
+ Ad Ad `gorm:"foreignKey:AdId;references:UUID" json:"-"`
}
type GetAllAdsResponse struct {
- UUID string `gorm:"type:uuid;primaryKey;default:gen_random_uuid();column:uuid" json:"id"`
- CityID int `gorm:"column:cityId;not null" json:"cityId"`
- AuthorUUID string `gorm:"column:authorUUID;not null" json:"authorUUID"`
- Address string `gorm:"type:varchar(255);column:address" json:"address"`
- PublicationDate time.Time `gorm:"type:date;column:publicationDate" json:"publicationDate"`
- Description string `gorm:"type:text;size:1000;column:description" json:"description"`
- RoomsNumber int `gorm:"column:roomsNumber" json:"roomsNumber"`
- City City `gorm:"foreignKey:CityID;references:ID" json:"-"`
- Author User `gorm:"foreignKey:AuthorUUID;references:UUID" json:"-"`
- ViewsCount int `gorm:"column:viewsCount;default:0" json:"viewsCount"`
- CityName string `json:"cityName"`
- AdDateFrom time.Time `json:"adDateFrom"`
- AdDateTo time.Time `json:"adDateTo"`
- AdAuthor UserResponce `gorm:"-" json:"author"`
- Images []ImageResponse `gorm:"-" json:"images"`
+ UUID string `gorm:"type:uuid;primaryKey;default:gen_random_uuid();column:uuid" json:"id"`
+ CityID int `gorm:"column:cityId;not null" json:"cityId"`
+ AuthorUUID string `gorm:"column:authorUUID;not null" json:"authorUUID"`
+ Address string `gorm:"type:varchar(255);column:address" json:"address"`
+ PublicationDate time.Time `gorm:"type:date;column:publicationDate" json:"publicationDate"`
+ Description string `gorm:"type:text;size:1000;column:description" json:"description"`
+ RoomsNumber int `gorm:"column:roomsNumber" json:"roomsNumber"`
+ City City `gorm:"foreignKey:CityID;references:ID" json:"-"`
+ Author User `gorm:"foreignKey:AuthorUUID;references:UUID" json:"-"`
+ ViewsCount int `gorm:"column:viewsCount;default:0" json:"viewsCount"`
+ SquareMeters int `gorm:"column:squareMeters" json:"squareMeters"`
+ Floor int `gorm:"column:floor" json:"floor"`
+ BuildingType string `gorm:"type:text;size:255;column:buildingType" json:"buildingType"`
+ HasBalcony bool `gorm:"type:bool;default:false;column:hasBalcony" json:"hasBalcony"`
+ HasElevator bool `gorm:"type:bool;default:false;column:hasElevator" json:"hasElevator"`
+ HasGas bool `gorm:"type:bool;default:false;column:hasGas" json:"hasGas"`
+ LikesCount int `gorm:"column:likesCount;default:0" json:"likesCount"`
+ Priority int `gorm:"column:priority;default:0" json:"priority"`
+ EndBoostDate time.Time `gorm:"type:date;column:endBoostDate" json:"endBoostDate"`
+ CityName string `json:"cityName"`
+ AdDateFrom time.Time `json:"adDateFrom"`
+ AdDateTo time.Time `json:"adDateTo"`
+ IsFavorite bool `json:"isFavorite"`
+ AdAuthor UserResponce `gorm:"-" json:"author"`
+ Images []ImageResponse `gorm:"-" json:"images"`
+ Rooms []AdRoomsResponse `gorm:"-" json:"rooms"`
}
type CreateAdRequest struct {
- CityName string `form:"cityName" json:"cityName"`
- Address string `form:"address" json:"address"`
- Description string `form:"description" json:"description"`
- RoomsNumber int `form:"roomsNumber" json:"roomsNumber"`
- DateFrom time.Time `form:"dateFrom" json:"dateFrom"`
- DateTo time.Time `form:"dateTo" json:"dateTo"`
+ CityName string `form:"cityName" json:"cityName"`
+ Address string `form:"address" json:"address"`
+ Description string `form:"description" json:"description"`
+ RoomsNumber int `form:"roomsNumber" json:"roomsNumber"`
+ DateFrom time.Time `form:"dateFrom" json:"dateFrom"`
+ DateTo time.Time `form:"dateTo" json:"dateTo"`
+ Rooms []AdRoomsResponse `form:"rooms" json:"rooms"`
+ SquareMeters int `form:"squareMeters" json:"squareMeters"`
+ Floor int `form:"floor" json:"floor"`
+ BuildingType string `form:"buildingType" json:"buildingType"`
+ HasBalcony bool `form:"hasBalcony" json:"hasBalcony"`
+ HasElevator bool `form:"hasElevator" json:"hasElevator"`
+ HasGas bool `form:"hasGas" json:"hasGas"`
}
type UpdateAdRequest struct {
- CityName string `form:"cityName" json:"cityName"`
- Address string `form:"address" json:"address"`
- Description string `form:"description" json:"description"`
- RoomsNumber int `form:"roomsNumber" json:"roomsNumber"`
- DateFrom time.Time `form:"dateFrom" json:"dateFrom"`
- DateTo time.Time `form:"dateTo" json:"dateTo"`
+ CityName string `form:"cityName" json:"cityName"`
+ Address string `form:"address" json:"address"`
+ Description string `form:"description" json:"description"`
+ RoomsNumber int `form:"roomsNumber" json:"roomsNumber"`
+ DateFrom time.Time `form:"dateFrom" json:"dateFrom"`
+ DateTo time.Time `form:"dateTo" json:"dateTo"`
+ Rooms []AdRoomsResponse `form:"rooms" json:"rooms"`
+ SquareMeters int `form:"squareMeters" json:"squareMeters"`
+ Floor int `form:"floor" json:"floor"`
+ BuildingType string `form:"buildingType" json:"buildingType"`
+ HasBalcony bool `form:"hasBalcony" json:"hasBalcony"`
+ HasElevator bool `form:"hasElevator" json:"hasElevator"`
+ HasGas bool `form:"hasGas" json:"hasGas"`
}
type AdFilter struct {
@@ -75,11 +126,17 @@ type AdFilter struct {
Favorites string
}
+type PaymentInfo struct {
+ CardNumber string `form:"cardNumber" json:"cardNumber"`
+ CardExpiry string `form:"cardExpiry" json:"cardExpiry"`
+ CardCvc string `form:"cardCVC" json:"cardCVC"`
+ DonationAmount string `form:"donationAmount" json:"donationAmount"`
+}
+
type AdRepository interface {
- GetAllPlaces(ctx context.Context, filter AdFilter) ([]GetAllAdsResponse, error)
+ GetAllPlaces(ctx context.Context, filter AdFilter, userId string) ([]GetAllAdsResponse, error)
GetPlaceById(ctx context.Context, adId string) (GetAllAdsResponse, error)
CreatePlace(ctx context.Context, ad *Ad, newAd CreateAdRequest, userId string) error
- SavePlace(ctx context.Context, ad *Ad) error
UpdatePlace(ctx context.Context, ad *Ad, adId string, userId string, updatedAd UpdateAdRequest) error
DeletePlace(ctx context.Context, adId string, userId string) error
GetPlacesPerCity(ctx context.Context, city string) ([]GetAllAdsResponse, error)
@@ -91,4 +148,7 @@ type AdRepository interface {
AddToFavorites(ctx context.Context, adId string, userId string) error
DeleteFromFavorites(ctx context.Context, adId string, userId string) error
GetUserFavorites(ctx context.Context, userId string) ([]GetAllAdsResponse, error)
+ UpdateFavoritesCount(ctx context.Context, adId string) error
+ UpdatePriority(ctx context.Context, adId string, userId string, amount int) error
+ ResetExpiredPriorities(ctx context.Context) error
}
diff --git a/domain/ads_easyjson.go b/domain/ads_easyjson.go
new file mode 100644
index 0000000..a527246
--- /dev/null
+++ b/domain/ads_easyjson.go
@@ -0,0 +1,1572 @@
+// Code generated by easyjson for marshaling/unmarshaling. DO NOT EDIT.
+
+package domain
+
+import (
+ json "encoding/json"
+ easyjson "github.com/mailru/easyjson"
+ jlexer "github.com/mailru/easyjson/jlexer"
+ jwriter "github.com/mailru/easyjson/jwriter"
+)
+
+// suppress unused package warning
+var (
+ _ *json.RawMessage
+ _ *jlexer.Lexer
+ _ *jwriter.Writer
+ _ easyjson.Marshaler
+)
+
+func easyjson3a862f94Decode20242FIGHTCLUBDomain(in *jlexer.Lexer, out *UpdateAdRequest) {
+ isTopLevel := in.IsStart()
+ if in.IsNull() {
+ if isTopLevel {
+ in.Consumed()
+ }
+ in.Skip()
+ return
+ }
+ in.Delim('{')
+ for !in.IsDelim('}') {
+ key := in.UnsafeFieldName(false)
+ in.WantColon()
+ if in.IsNull() {
+ in.Skip()
+ in.WantComma()
+ continue
+ }
+ switch key {
+ case "cityName":
+ out.CityName = string(in.String())
+ case "address":
+ out.Address = string(in.String())
+ case "description":
+ out.Description = string(in.String())
+ case "roomsNumber":
+ out.RoomsNumber = int(in.Int())
+ case "dateFrom":
+ if data := in.Raw(); in.Ok() {
+ in.AddError((out.DateFrom).UnmarshalJSON(data))
+ }
+ case "dateTo":
+ if data := in.Raw(); in.Ok() {
+ in.AddError((out.DateTo).UnmarshalJSON(data))
+ }
+ case "rooms":
+ if in.IsNull() {
+ in.Skip()
+ out.Rooms = nil
+ } else {
+ in.Delim('[')
+ if out.Rooms == nil {
+ if !in.IsDelim(']') {
+ out.Rooms = make([]AdRoomsResponse, 0, 2)
+ } else {
+ out.Rooms = []AdRoomsResponse{}
+ }
+ } else {
+ out.Rooms = (out.Rooms)[:0]
+ }
+ for !in.IsDelim(']') {
+ var v1 AdRoomsResponse
+ easyjson3a862f94Decode20242FIGHTCLUBDomain1(in, &v1)
+ out.Rooms = append(out.Rooms, v1)
+ in.WantComma()
+ }
+ in.Delim(']')
+ }
+ case "squareMeters":
+ out.SquareMeters = int(in.Int())
+ case "floor":
+ out.Floor = int(in.Int())
+ case "buildingType":
+ out.BuildingType = string(in.String())
+ case "hasBalcony":
+ out.HasBalcony = bool(in.Bool())
+ case "hasElevator":
+ out.HasElevator = bool(in.Bool())
+ case "hasGas":
+ out.HasGas = bool(in.Bool())
+ default:
+ in.SkipRecursive()
+ }
+ in.WantComma()
+ }
+ in.Delim('}')
+ if isTopLevel {
+ in.Consumed()
+ }
+}
+func easyjson3a862f94Encode20242FIGHTCLUBDomain(out *jwriter.Writer, in UpdateAdRequest) {
+ out.RawByte('{')
+ first := true
+ _ = first
+ {
+ const prefix string = ",\"cityName\":"
+ out.RawString(prefix[1:])
+ out.String(string(in.CityName))
+ }
+ {
+ const prefix string = ",\"address\":"
+ out.RawString(prefix)
+ out.String(string(in.Address))
+ }
+ {
+ const prefix string = ",\"description\":"
+ out.RawString(prefix)
+ out.String(string(in.Description))
+ }
+ {
+ const prefix string = ",\"roomsNumber\":"
+ out.RawString(prefix)
+ out.Int(int(in.RoomsNumber))
+ }
+ {
+ const prefix string = ",\"dateFrom\":"
+ out.RawString(prefix)
+ out.Raw((in.DateFrom).MarshalJSON())
+ }
+ {
+ const prefix string = ",\"dateTo\":"
+ out.RawString(prefix)
+ out.Raw((in.DateTo).MarshalJSON())
+ }
+ {
+ const prefix string = ",\"rooms\":"
+ out.RawString(prefix)
+ if in.Rooms == nil && (out.Flags&jwriter.NilSliceAsEmpty) == 0 {
+ out.RawString("null")
+ } else {
+ out.RawByte('[')
+ for v2, v3 := range in.Rooms {
+ if v2 > 0 {
+ out.RawByte(',')
+ }
+ easyjson3a862f94Encode20242FIGHTCLUBDomain1(out, v3)
+ }
+ out.RawByte(']')
+ }
+ }
+ {
+ const prefix string = ",\"squareMeters\":"
+ out.RawString(prefix)
+ out.Int(int(in.SquareMeters))
+ }
+ {
+ const prefix string = ",\"floor\":"
+ out.RawString(prefix)
+ out.Int(int(in.Floor))
+ }
+ {
+ const prefix string = ",\"buildingType\":"
+ out.RawString(prefix)
+ out.String(string(in.BuildingType))
+ }
+ {
+ const prefix string = ",\"hasBalcony\":"
+ out.RawString(prefix)
+ out.Bool(bool(in.HasBalcony))
+ }
+ {
+ const prefix string = ",\"hasElevator\":"
+ out.RawString(prefix)
+ out.Bool(bool(in.HasElevator))
+ }
+ {
+ const prefix string = ",\"hasGas\":"
+ out.RawString(prefix)
+ out.Bool(bool(in.HasGas))
+ }
+ out.RawByte('}')
+}
+
+// MarshalJSON supports json.Marshaler interface
+func (v UpdateAdRequest) MarshalJSON() ([]byte, error) {
+ w := jwriter.Writer{}
+ easyjson3a862f94Encode20242FIGHTCLUBDomain(&w, v)
+ return w.Buffer.BuildBytes(), w.Error
+}
+
+// MarshalEasyJSON supports easyjson.Marshaler interface
+func (v UpdateAdRequest) MarshalEasyJSON(w *jwriter.Writer) {
+ easyjson3a862f94Encode20242FIGHTCLUBDomain(w, v)
+}
+
+// UnmarshalJSON supports json.Unmarshaler interface
+func (v *UpdateAdRequest) UnmarshalJSON(data []byte) error {
+ r := jlexer.Lexer{Data: data}
+ easyjson3a862f94Decode20242FIGHTCLUBDomain(&r, v)
+ return r.Error()
+}
+
+// UnmarshalEasyJSON supports easyjson.Unmarshaler interface
+func (v *UpdateAdRequest) UnmarshalEasyJSON(l *jlexer.Lexer) {
+ easyjson3a862f94Decode20242FIGHTCLUBDomain(l, v)
+}
+func easyjson3a862f94Decode20242FIGHTCLUBDomain1(in *jlexer.Lexer, out *AdRoomsResponse) {
+ isTopLevel := in.IsStart()
+ if in.IsNull() {
+ if isTopLevel {
+ in.Consumed()
+ }
+ in.Skip()
+ return
+ }
+ in.Delim('{')
+ for !in.IsDelim('}') {
+ key := in.UnsafeFieldName(false)
+ in.WantColon()
+ if in.IsNull() {
+ in.Skip()
+ in.WantComma()
+ continue
+ }
+ switch key {
+ case "type":
+ out.Type = string(in.String())
+ case "squareMeters":
+ out.SquareMeters = int(in.Int())
+ default:
+ in.SkipRecursive()
+ }
+ in.WantComma()
+ }
+ in.Delim('}')
+ if isTopLevel {
+ in.Consumed()
+ }
+}
+func easyjson3a862f94Encode20242FIGHTCLUBDomain1(out *jwriter.Writer, in AdRoomsResponse) {
+ out.RawByte('{')
+ first := true
+ _ = first
+ {
+ const prefix string = ",\"type\":"
+ out.RawString(prefix[1:])
+ out.String(string(in.Type))
+ }
+ {
+ const prefix string = ",\"squareMeters\":"
+ out.RawString(prefix)
+ out.Int(int(in.SquareMeters))
+ }
+ out.RawByte('}')
+}
+func easyjson3a862f94Decode20242FIGHTCLUBDomain2(in *jlexer.Lexer, out *PlacesResponse) {
+ isTopLevel := in.IsStart()
+ if in.IsNull() {
+ if isTopLevel {
+ in.Consumed()
+ }
+ in.Skip()
+ return
+ }
+ in.Delim('{')
+ for !in.IsDelim('}') {
+ key := in.UnsafeFieldName(false)
+ in.WantColon()
+ if in.IsNull() {
+ in.Skip()
+ in.WantComma()
+ continue
+ }
+ switch key {
+ case "places":
+ (out.Places).UnmarshalEasyJSON(in)
+ default:
+ in.SkipRecursive()
+ }
+ in.WantComma()
+ }
+ in.Delim('}')
+ if isTopLevel {
+ in.Consumed()
+ }
+}
+func easyjson3a862f94Encode20242FIGHTCLUBDomain2(out *jwriter.Writer, in PlacesResponse) {
+ out.RawByte('{')
+ first := true
+ _ = first
+ {
+ const prefix string = ",\"places\":"
+ out.RawString(prefix[1:])
+ (in.Places).MarshalEasyJSON(out)
+ }
+ out.RawByte('}')
+}
+
+// MarshalJSON supports json.Marshaler interface
+func (v PlacesResponse) MarshalJSON() ([]byte, error) {
+ w := jwriter.Writer{}
+ easyjson3a862f94Encode20242FIGHTCLUBDomain2(&w, v)
+ return w.Buffer.BuildBytes(), w.Error
+}
+
+// MarshalEasyJSON supports easyjson.Marshaler interface
+func (v PlacesResponse) MarshalEasyJSON(w *jwriter.Writer) {
+ easyjson3a862f94Encode20242FIGHTCLUBDomain2(w, v)
+}
+
+// UnmarshalJSON supports json.Unmarshaler interface
+func (v *PlacesResponse) UnmarshalJSON(data []byte) error {
+ r := jlexer.Lexer{Data: data}
+ easyjson3a862f94Decode20242FIGHTCLUBDomain2(&r, v)
+ return r.Error()
+}
+
+// UnmarshalEasyJSON supports easyjson.Unmarshaler interface
+func (v *PlacesResponse) UnmarshalEasyJSON(l *jlexer.Lexer) {
+ easyjson3a862f94Decode20242FIGHTCLUBDomain2(l, v)
+}
+func easyjson3a862f94Decode20242FIGHTCLUBDomain3(in *jlexer.Lexer, out *PaymentInfo) {
+ isTopLevel := in.IsStart()
+ if in.IsNull() {
+ if isTopLevel {
+ in.Consumed()
+ }
+ in.Skip()
+ return
+ }
+ in.Delim('{')
+ for !in.IsDelim('}') {
+ key := in.UnsafeFieldName(false)
+ in.WantColon()
+ if in.IsNull() {
+ in.Skip()
+ in.WantComma()
+ continue
+ }
+ switch key {
+ case "cardNumber":
+ out.CardNumber = string(in.String())
+ case "cardExpiry":
+ out.CardExpiry = string(in.String())
+ case "cardCVC":
+ out.CardCvc = string(in.String())
+ case "donationAmount":
+ out.DonationAmount = string(in.String())
+ default:
+ in.SkipRecursive()
+ }
+ in.WantComma()
+ }
+ in.Delim('}')
+ if isTopLevel {
+ in.Consumed()
+ }
+}
+func easyjson3a862f94Encode20242FIGHTCLUBDomain3(out *jwriter.Writer, in PaymentInfo) {
+ out.RawByte('{')
+ first := true
+ _ = first
+ {
+ const prefix string = ",\"cardNumber\":"
+ out.RawString(prefix[1:])
+ out.String(string(in.CardNumber))
+ }
+ {
+ const prefix string = ",\"cardExpiry\":"
+ out.RawString(prefix)
+ out.String(string(in.CardExpiry))
+ }
+ {
+ const prefix string = ",\"cardCVC\":"
+ out.RawString(prefix)
+ out.String(string(in.CardCvc))
+ }
+ {
+ const prefix string = ",\"donationAmount\":"
+ out.RawString(prefix)
+ out.String(string(in.DonationAmount))
+ }
+ out.RawByte('}')
+}
+
+// MarshalJSON supports json.Marshaler interface
+func (v PaymentInfo) MarshalJSON() ([]byte, error) {
+ w := jwriter.Writer{}
+ easyjson3a862f94Encode20242FIGHTCLUBDomain3(&w, v)
+ return w.Buffer.BuildBytes(), w.Error
+}
+
+// MarshalEasyJSON supports easyjson.Marshaler interface
+func (v PaymentInfo) MarshalEasyJSON(w *jwriter.Writer) {
+ easyjson3a862f94Encode20242FIGHTCLUBDomain3(w, v)
+}
+
+// UnmarshalJSON supports json.Unmarshaler interface
+func (v *PaymentInfo) UnmarshalJSON(data []byte) error {
+ r := jlexer.Lexer{Data: data}
+ easyjson3a862f94Decode20242FIGHTCLUBDomain3(&r, v)
+ return r.Error()
+}
+
+// UnmarshalEasyJSON supports easyjson.Unmarshaler interface
+func (v *PaymentInfo) UnmarshalEasyJSON(l *jlexer.Lexer) {
+ easyjson3a862f94Decode20242FIGHTCLUBDomain3(l, v)
+}
+func easyjson3a862f94Decode20242FIGHTCLUBDomain4(in *jlexer.Lexer, out *GetOneAdResponse) {
+ isTopLevel := in.IsStart()
+ if in.IsNull() {
+ if isTopLevel {
+ in.Consumed()
+ }
+ in.Skip()
+ return
+ }
+ in.Delim('{')
+ for !in.IsDelim('}') {
+ key := in.UnsafeFieldName(false)
+ in.WantColon()
+ if in.IsNull() {
+ in.Skip()
+ in.WantComma()
+ continue
+ }
+ switch key {
+ case "place":
+ (out.Place).UnmarshalEasyJSON(in)
+ default:
+ in.SkipRecursive()
+ }
+ in.WantComma()
+ }
+ in.Delim('}')
+ if isTopLevel {
+ in.Consumed()
+ }
+}
+func easyjson3a862f94Encode20242FIGHTCLUBDomain4(out *jwriter.Writer, in GetOneAdResponse) {
+ out.RawByte('{')
+ first := true
+ _ = first
+ {
+ const prefix string = ",\"place\":"
+ out.RawString(prefix[1:])
+ (in.Place).MarshalEasyJSON(out)
+ }
+ out.RawByte('}')
+}
+
+// MarshalJSON supports json.Marshaler interface
+func (v GetOneAdResponse) MarshalJSON() ([]byte, error) {
+ w := jwriter.Writer{}
+ easyjson3a862f94Encode20242FIGHTCLUBDomain4(&w, v)
+ return w.Buffer.BuildBytes(), w.Error
+}
+
+// MarshalEasyJSON supports easyjson.Marshaler interface
+func (v GetOneAdResponse) MarshalEasyJSON(w *jwriter.Writer) {
+ easyjson3a862f94Encode20242FIGHTCLUBDomain4(w, v)
+}
+
+// UnmarshalJSON supports json.Unmarshaler interface
+func (v *GetOneAdResponse) UnmarshalJSON(data []byte) error {
+ r := jlexer.Lexer{Data: data}
+ easyjson3a862f94Decode20242FIGHTCLUBDomain4(&r, v)
+ return r.Error()
+}
+
+// UnmarshalEasyJSON supports easyjson.Unmarshaler interface
+func (v *GetOneAdResponse) UnmarshalEasyJSON(l *jlexer.Lexer) {
+ easyjson3a862f94Decode20242FIGHTCLUBDomain4(l, v)
+}
+func easyjson3a862f94Decode20242FIGHTCLUBDomain5(in *jlexer.Lexer, out *GetAllAdsResponse) {
+ isTopLevel := in.IsStart()
+ if in.IsNull() {
+ if isTopLevel {
+ in.Consumed()
+ }
+ in.Skip()
+ return
+ }
+ in.Delim('{')
+ for !in.IsDelim('}') {
+ key := in.UnsafeFieldName(false)
+ in.WantColon()
+ if in.IsNull() {
+ in.Skip()
+ in.WantComma()
+ continue
+ }
+ switch key {
+ case "id":
+ out.UUID = string(in.String())
+ case "cityId":
+ out.CityID = int(in.Int())
+ case "authorUUID":
+ out.AuthorUUID = string(in.String())
+ case "address":
+ out.Address = string(in.String())
+ case "publicationDate":
+ if data := in.Raw(); in.Ok() {
+ in.AddError((out.PublicationDate).UnmarshalJSON(data))
+ }
+ case "description":
+ out.Description = string(in.String())
+ case "roomsNumber":
+ out.RoomsNumber = int(in.Int())
+ case "viewsCount":
+ out.ViewsCount = int(in.Int())
+ case "squareMeters":
+ out.SquareMeters = int(in.Int())
+ case "floor":
+ out.Floor = int(in.Int())
+ case "buildingType":
+ out.BuildingType = string(in.String())
+ case "hasBalcony":
+ out.HasBalcony = bool(in.Bool())
+ case "hasElevator":
+ out.HasElevator = bool(in.Bool())
+ case "hasGas":
+ out.HasGas = bool(in.Bool())
+ case "likesCount":
+ out.LikesCount = int(in.Int())
+ case "priority":
+ out.Priority = int(in.Int())
+ case "endBoostDate":
+ if data := in.Raw(); in.Ok() {
+ in.AddError((out.EndBoostDate).UnmarshalJSON(data))
+ }
+ case "cityName":
+ out.CityName = string(in.String())
+ case "adDateFrom":
+ if data := in.Raw(); in.Ok() {
+ in.AddError((out.AdDateFrom).UnmarshalJSON(data))
+ }
+ case "adDateTo":
+ if data := in.Raw(); in.Ok() {
+ in.AddError((out.AdDateTo).UnmarshalJSON(data))
+ }
+ case "isFavorite":
+ out.IsFavorite = bool(in.Bool())
+ case "author":
+ easyjson3a862f94Decode20242FIGHTCLUBDomain6(in, &out.AdAuthor)
+ case "images":
+ if in.IsNull() {
+ in.Skip()
+ out.Images = nil
+ } else {
+ in.Delim('[')
+ if out.Images == nil {
+ if !in.IsDelim(']') {
+ out.Images = make([]ImageResponse, 0, 2)
+ } else {
+ out.Images = []ImageResponse{}
+ }
+ } else {
+ out.Images = (out.Images)[:0]
+ }
+ for !in.IsDelim(']') {
+ var v4 ImageResponse
+ easyjson3a862f94Decode20242FIGHTCLUBDomain7(in, &v4)
+ out.Images = append(out.Images, v4)
+ in.WantComma()
+ }
+ in.Delim(']')
+ }
+ case "rooms":
+ if in.IsNull() {
+ in.Skip()
+ out.Rooms = nil
+ } else {
+ in.Delim('[')
+ if out.Rooms == nil {
+ if !in.IsDelim(']') {
+ out.Rooms = make([]AdRoomsResponse, 0, 2)
+ } else {
+ out.Rooms = []AdRoomsResponse{}
+ }
+ } else {
+ out.Rooms = (out.Rooms)[:0]
+ }
+ for !in.IsDelim(']') {
+ var v5 AdRoomsResponse
+ easyjson3a862f94Decode20242FIGHTCLUBDomain1(in, &v5)
+ out.Rooms = append(out.Rooms, v5)
+ in.WantComma()
+ }
+ in.Delim(']')
+ }
+ default:
+ in.SkipRecursive()
+ }
+ in.WantComma()
+ }
+ in.Delim('}')
+ if isTopLevel {
+ in.Consumed()
+ }
+}
+func easyjson3a862f94Encode20242FIGHTCLUBDomain5(out *jwriter.Writer, in GetAllAdsResponse) {
+ out.RawByte('{')
+ first := true
+ _ = first
+ {
+ const prefix string = ",\"id\":"
+ out.RawString(prefix[1:])
+ out.String(string(in.UUID))
+ }
+ {
+ const prefix string = ",\"cityId\":"
+ out.RawString(prefix)
+ out.Int(int(in.CityID))
+ }
+ {
+ const prefix string = ",\"authorUUID\":"
+ out.RawString(prefix)
+ out.String(string(in.AuthorUUID))
+ }
+ {
+ const prefix string = ",\"address\":"
+ out.RawString(prefix)
+ out.String(string(in.Address))
+ }
+ {
+ const prefix string = ",\"publicationDate\":"
+ out.RawString(prefix)
+ out.Raw((in.PublicationDate).MarshalJSON())
+ }
+ {
+ const prefix string = ",\"description\":"
+ out.RawString(prefix)
+ out.String(string(in.Description))
+ }
+ {
+ const prefix string = ",\"roomsNumber\":"
+ out.RawString(prefix)
+ out.Int(int(in.RoomsNumber))
+ }
+ {
+ const prefix string = ",\"viewsCount\":"
+ out.RawString(prefix)
+ out.Int(int(in.ViewsCount))
+ }
+ {
+ const prefix string = ",\"squareMeters\":"
+ out.RawString(prefix)
+ out.Int(int(in.SquareMeters))
+ }
+ {
+ const prefix string = ",\"floor\":"
+ out.RawString(prefix)
+ out.Int(int(in.Floor))
+ }
+ {
+ const prefix string = ",\"buildingType\":"
+ out.RawString(prefix)
+ out.String(string(in.BuildingType))
+ }
+ {
+ const prefix string = ",\"hasBalcony\":"
+ out.RawString(prefix)
+ out.Bool(bool(in.HasBalcony))
+ }
+ {
+ const prefix string = ",\"hasElevator\":"
+ out.RawString(prefix)
+ out.Bool(bool(in.HasElevator))
+ }
+ {
+ const prefix string = ",\"hasGas\":"
+ out.RawString(prefix)
+ out.Bool(bool(in.HasGas))
+ }
+ {
+ const prefix string = ",\"likesCount\":"
+ out.RawString(prefix)
+ out.Int(int(in.LikesCount))
+ }
+ {
+ const prefix string = ",\"priority\":"
+ out.RawString(prefix)
+ out.Int(int(in.Priority))
+ }
+ {
+ const prefix string = ",\"endBoostDate\":"
+ out.RawString(prefix)
+ out.Raw((in.EndBoostDate).MarshalJSON())
+ }
+ {
+ const prefix string = ",\"cityName\":"
+ out.RawString(prefix)
+ out.String(string(in.CityName))
+ }
+ {
+ const prefix string = ",\"adDateFrom\":"
+ out.RawString(prefix)
+ out.Raw((in.AdDateFrom).MarshalJSON())
+ }
+ {
+ const prefix string = ",\"adDateTo\":"
+ out.RawString(prefix)
+ out.Raw((in.AdDateTo).MarshalJSON())
+ }
+ {
+ const prefix string = ",\"isFavorite\":"
+ out.RawString(prefix)
+ out.Bool(bool(in.IsFavorite))
+ }
+ {
+ const prefix string = ",\"author\":"
+ out.RawString(prefix)
+ easyjson3a862f94Encode20242FIGHTCLUBDomain6(out, in.AdAuthor)
+ }
+ {
+ const prefix string = ",\"images\":"
+ out.RawString(prefix)
+ if in.Images == nil && (out.Flags&jwriter.NilSliceAsEmpty) == 0 {
+ out.RawString("null")
+ } else {
+ out.RawByte('[')
+ for v6, v7 := range in.Images {
+ if v6 > 0 {
+ out.RawByte(',')
+ }
+ easyjson3a862f94Encode20242FIGHTCLUBDomain7(out, v7)
+ }
+ out.RawByte(']')
+ }
+ }
+ {
+ const prefix string = ",\"rooms\":"
+ out.RawString(prefix)
+ if in.Rooms == nil && (out.Flags&jwriter.NilSliceAsEmpty) == 0 {
+ out.RawString("null")
+ } else {
+ out.RawByte('[')
+ for v8, v9 := range in.Rooms {
+ if v8 > 0 {
+ out.RawByte(',')
+ }
+ easyjson3a862f94Encode20242FIGHTCLUBDomain1(out, v9)
+ }
+ out.RawByte(']')
+ }
+ }
+ out.RawByte('}')
+}
+
+// MarshalJSON supports json.Marshaler interface
+func (v GetAllAdsResponse) MarshalJSON() ([]byte, error) {
+ w := jwriter.Writer{}
+ easyjson3a862f94Encode20242FIGHTCLUBDomain5(&w, v)
+ return w.Buffer.BuildBytes(), w.Error
+}
+
+// MarshalEasyJSON supports easyjson.Marshaler interface
+func (v GetAllAdsResponse) MarshalEasyJSON(w *jwriter.Writer) {
+ easyjson3a862f94Encode20242FIGHTCLUBDomain5(w, v)
+}
+
+// UnmarshalJSON supports json.Unmarshaler interface
+func (v *GetAllAdsResponse) UnmarshalJSON(data []byte) error {
+ r := jlexer.Lexer{Data: data}
+ easyjson3a862f94Decode20242FIGHTCLUBDomain5(&r, v)
+ return r.Error()
+}
+
+// UnmarshalEasyJSON supports easyjson.Unmarshaler interface
+func (v *GetAllAdsResponse) UnmarshalEasyJSON(l *jlexer.Lexer) {
+ easyjson3a862f94Decode20242FIGHTCLUBDomain5(l, v)
+}
+func easyjson3a862f94Decode20242FIGHTCLUBDomain7(in *jlexer.Lexer, out *ImageResponse) {
+ isTopLevel := in.IsStart()
+ if in.IsNull() {
+ if isTopLevel {
+ in.Consumed()
+ }
+ in.Skip()
+ return
+ }
+ in.Delim('{')
+ for !in.IsDelim('}') {
+ key := in.UnsafeFieldName(false)
+ in.WantColon()
+ if in.IsNull() {
+ in.Skip()
+ in.WantComma()
+ continue
+ }
+ switch key {
+ case "id":
+ out.ID = int(in.Int())
+ case "path":
+ out.ImagePath = string(in.String())
+ default:
+ in.SkipRecursive()
+ }
+ in.WantComma()
+ }
+ in.Delim('}')
+ if isTopLevel {
+ in.Consumed()
+ }
+}
+func easyjson3a862f94Encode20242FIGHTCLUBDomain7(out *jwriter.Writer, in ImageResponse) {
+ out.RawByte('{')
+ first := true
+ _ = first
+ {
+ const prefix string = ",\"id\":"
+ out.RawString(prefix[1:])
+ out.Int(int(in.ID))
+ }
+ {
+ const prefix string = ",\"path\":"
+ out.RawString(prefix)
+ out.String(string(in.ImagePath))
+ }
+ out.RawByte('}')
+}
+func easyjson3a862f94Decode20242FIGHTCLUBDomain6(in *jlexer.Lexer, out *UserResponce) {
+ isTopLevel := in.IsStart()
+ if in.IsNull() {
+ if isTopLevel {
+ in.Consumed()
+ }
+ in.Skip()
+ return
+ }
+ in.Delim('{')
+ for !in.IsDelim('}') {
+ key := in.UnsafeFieldName(false)
+ in.WantColon()
+ if in.IsNull() {
+ in.Skip()
+ in.WantComma()
+ continue
+ }
+ switch key {
+ case "rating":
+ out.Rating = float64(in.Float64())
+ case "avatar":
+ out.Avatar = string(in.String())
+ case "name":
+ out.Name = string(in.String())
+ case "sex":
+ out.Sex = string(in.String())
+ case "birthDate":
+ if data := in.Raw(); in.Ok() {
+ in.AddError((out.Birthdate).UnmarshalJSON(data))
+ }
+ case "guestCount":
+ out.GuestCount = int(in.Int())
+ default:
+ in.SkipRecursive()
+ }
+ in.WantComma()
+ }
+ in.Delim('}')
+ if isTopLevel {
+ in.Consumed()
+ }
+}
+func easyjson3a862f94Encode20242FIGHTCLUBDomain6(out *jwriter.Writer, in UserResponce) {
+ out.RawByte('{')
+ first := true
+ _ = first
+ {
+ const prefix string = ",\"rating\":"
+ out.RawString(prefix[1:])
+ out.Float64(float64(in.Rating))
+ }
+ {
+ const prefix string = ",\"avatar\":"
+ out.RawString(prefix)
+ out.String(string(in.Avatar))
+ }
+ {
+ const prefix string = ",\"name\":"
+ out.RawString(prefix)
+ out.String(string(in.Name))
+ }
+ {
+ const prefix string = ",\"sex\":"
+ out.RawString(prefix)
+ out.String(string(in.Sex))
+ }
+ {
+ const prefix string = ",\"birthDate\":"
+ out.RawString(prefix)
+ out.Raw((in.Birthdate).MarshalJSON())
+ }
+ {
+ const prefix string = ",\"guestCount\":"
+ out.RawString(prefix)
+ out.Int(int(in.GuestCount))
+ }
+ out.RawByte('}')
+}
+func easyjson3a862f94Decode20242FIGHTCLUBDomain8(in *jlexer.Lexer, out *GetAllAdsListResponse) {
+ isTopLevel := in.IsStart()
+ if in.IsNull() {
+ if isTopLevel {
+ in.Consumed()
+ }
+ in.Skip()
+ return
+ }
+ in.Delim('{')
+ for !in.IsDelim('}') {
+ key := in.UnsafeFieldName(false)
+ in.WantColon()
+ if in.IsNull() {
+ in.Skip()
+ in.WantComma()
+ continue
+ }
+ switch key {
+ case "housing":
+ if in.IsNull() {
+ in.Skip()
+ out.Housing = nil
+ } else {
+ in.Delim('[')
+ if out.Housing == nil {
+ if !in.IsDelim(']') {
+ out.Housing = make([]GetAllAdsResponse, 0, 0)
+ } else {
+ out.Housing = []GetAllAdsResponse{}
+ }
+ } else {
+ out.Housing = (out.Housing)[:0]
+ }
+ for !in.IsDelim(']') {
+ var v10 GetAllAdsResponse
+ (v10).UnmarshalEasyJSON(in)
+ out.Housing = append(out.Housing, v10)
+ in.WantComma()
+ }
+ in.Delim(']')
+ }
+ default:
+ in.SkipRecursive()
+ }
+ in.WantComma()
+ }
+ in.Delim('}')
+ if isTopLevel {
+ in.Consumed()
+ }
+}
+func easyjson3a862f94Encode20242FIGHTCLUBDomain8(out *jwriter.Writer, in GetAllAdsListResponse) {
+ out.RawByte('{')
+ first := true
+ _ = first
+ {
+ const prefix string = ",\"housing\":"
+ out.RawString(prefix[1:])
+ if in.Housing == nil && (out.Flags&jwriter.NilSliceAsEmpty) == 0 {
+ out.RawString("null")
+ } else {
+ out.RawByte('[')
+ for v11, v12 := range in.Housing {
+ if v11 > 0 {
+ out.RawByte(',')
+ }
+ (v12).MarshalEasyJSON(out)
+ }
+ out.RawByte(']')
+ }
+ }
+ out.RawByte('}')
+}
+
+// MarshalJSON supports json.Marshaler interface
+func (v GetAllAdsListResponse) MarshalJSON() ([]byte, error) {
+ w := jwriter.Writer{}
+ easyjson3a862f94Encode20242FIGHTCLUBDomain8(&w, v)
+ return w.Buffer.BuildBytes(), w.Error
+}
+
+// MarshalEasyJSON supports easyjson.Marshaler interface
+func (v GetAllAdsListResponse) MarshalEasyJSON(w *jwriter.Writer) {
+ easyjson3a862f94Encode20242FIGHTCLUBDomain8(w, v)
+}
+
+// UnmarshalJSON supports json.Unmarshaler interface
+func (v *GetAllAdsListResponse) UnmarshalJSON(data []byte) error {
+ r := jlexer.Lexer{Data: data}
+ easyjson3a862f94Decode20242FIGHTCLUBDomain8(&r, v)
+ return r.Error()
+}
+
+// UnmarshalEasyJSON supports easyjson.Unmarshaler interface
+func (v *GetAllAdsListResponse) UnmarshalEasyJSON(l *jlexer.Lexer) {
+ easyjson3a862f94Decode20242FIGHTCLUBDomain8(l, v)
+}
+func easyjson3a862f94Decode20242FIGHTCLUBDomain9(in *jlexer.Lexer, out *Favorites) {
+ isTopLevel := in.IsStart()
+ if in.IsNull() {
+ if isTopLevel {
+ in.Consumed()
+ }
+ in.Skip()
+ return
+ }
+ in.Delim('{')
+ for !in.IsDelim('}') {
+ key := in.UnsafeFieldName(false)
+ in.WantColon()
+ if in.IsNull() {
+ in.Skip()
+ in.WantComma()
+ continue
+ }
+ switch key {
+ case "adId":
+ out.AdId = string(in.String())
+ case "userId":
+ out.UserId = string(in.String())
+ default:
+ in.SkipRecursive()
+ }
+ in.WantComma()
+ }
+ in.Delim('}')
+ if isTopLevel {
+ in.Consumed()
+ }
+}
+func easyjson3a862f94Encode20242FIGHTCLUBDomain9(out *jwriter.Writer, in Favorites) {
+ out.RawByte('{')
+ first := true
+ _ = first
+ {
+ const prefix string = ",\"adId\":"
+ out.RawString(prefix[1:])
+ out.String(string(in.AdId))
+ }
+ {
+ const prefix string = ",\"userId\":"
+ out.RawString(prefix)
+ out.String(string(in.UserId))
+ }
+ out.RawByte('}')
+}
+
+// MarshalJSON supports json.Marshaler interface
+func (v Favorites) MarshalJSON() ([]byte, error) {
+ w := jwriter.Writer{}
+ easyjson3a862f94Encode20242FIGHTCLUBDomain9(&w, v)
+ return w.Buffer.BuildBytes(), w.Error
+}
+
+// MarshalEasyJSON supports easyjson.Marshaler interface
+func (v Favorites) MarshalEasyJSON(w *jwriter.Writer) {
+ easyjson3a862f94Encode20242FIGHTCLUBDomain9(w, v)
+}
+
+// UnmarshalJSON supports json.Unmarshaler interface
+func (v *Favorites) UnmarshalJSON(data []byte) error {
+ r := jlexer.Lexer{Data: data}
+ easyjson3a862f94Decode20242FIGHTCLUBDomain9(&r, v)
+ return r.Error()
+}
+
+// UnmarshalEasyJSON supports easyjson.Unmarshaler interface
+func (v *Favorites) UnmarshalEasyJSON(l *jlexer.Lexer) {
+ easyjson3a862f94Decode20242FIGHTCLUBDomain9(l, v)
+}
+func easyjson3a862f94Decode20242FIGHTCLUBDomain10(in *jlexer.Lexer, out *CreateAdRequest) {
+ isTopLevel := in.IsStart()
+ if in.IsNull() {
+ if isTopLevel {
+ in.Consumed()
+ }
+ in.Skip()
+ return
+ }
+ in.Delim('{')
+ for !in.IsDelim('}') {
+ key := in.UnsafeFieldName(false)
+ in.WantColon()
+ if in.IsNull() {
+ in.Skip()
+ in.WantComma()
+ continue
+ }
+ switch key {
+ case "cityName":
+ out.CityName = string(in.String())
+ case "address":
+ out.Address = string(in.String())
+ case "description":
+ out.Description = string(in.String())
+ case "roomsNumber":
+ out.RoomsNumber = int(in.Int())
+ case "dateFrom":
+ if data := in.Raw(); in.Ok() {
+ in.AddError((out.DateFrom).UnmarshalJSON(data))
+ }
+ case "dateTo":
+ if data := in.Raw(); in.Ok() {
+ in.AddError((out.DateTo).UnmarshalJSON(data))
+ }
+ case "rooms":
+ if in.IsNull() {
+ in.Skip()
+ out.Rooms = nil
+ } else {
+ in.Delim('[')
+ if out.Rooms == nil {
+ if !in.IsDelim(']') {
+ out.Rooms = make([]AdRoomsResponse, 0, 2)
+ } else {
+ out.Rooms = []AdRoomsResponse{}
+ }
+ } else {
+ out.Rooms = (out.Rooms)[:0]
+ }
+ for !in.IsDelim(']') {
+ var v13 AdRoomsResponse
+ easyjson3a862f94Decode20242FIGHTCLUBDomain1(in, &v13)
+ out.Rooms = append(out.Rooms, v13)
+ in.WantComma()
+ }
+ in.Delim(']')
+ }
+ case "squareMeters":
+ out.SquareMeters = int(in.Int())
+ case "floor":
+ out.Floor = int(in.Int())
+ case "buildingType":
+ out.BuildingType = string(in.String())
+ case "hasBalcony":
+ out.HasBalcony = bool(in.Bool())
+ case "hasElevator":
+ out.HasElevator = bool(in.Bool())
+ case "hasGas":
+ out.HasGas = bool(in.Bool())
+ default:
+ in.SkipRecursive()
+ }
+ in.WantComma()
+ }
+ in.Delim('}')
+ if isTopLevel {
+ in.Consumed()
+ }
+}
+func easyjson3a862f94Encode20242FIGHTCLUBDomain10(out *jwriter.Writer, in CreateAdRequest) {
+ out.RawByte('{')
+ first := true
+ _ = first
+ {
+ const prefix string = ",\"cityName\":"
+ out.RawString(prefix[1:])
+ out.String(string(in.CityName))
+ }
+ {
+ const prefix string = ",\"address\":"
+ out.RawString(prefix)
+ out.String(string(in.Address))
+ }
+ {
+ const prefix string = ",\"description\":"
+ out.RawString(prefix)
+ out.String(string(in.Description))
+ }
+ {
+ const prefix string = ",\"roomsNumber\":"
+ out.RawString(prefix)
+ out.Int(int(in.RoomsNumber))
+ }
+ {
+ const prefix string = ",\"dateFrom\":"
+ out.RawString(prefix)
+ out.Raw((in.DateFrom).MarshalJSON())
+ }
+ {
+ const prefix string = ",\"dateTo\":"
+ out.RawString(prefix)
+ out.Raw((in.DateTo).MarshalJSON())
+ }
+ {
+ const prefix string = ",\"rooms\":"
+ out.RawString(prefix)
+ if in.Rooms == nil && (out.Flags&jwriter.NilSliceAsEmpty) == 0 {
+ out.RawString("null")
+ } else {
+ out.RawByte('[')
+ for v14, v15 := range in.Rooms {
+ if v14 > 0 {
+ out.RawByte(',')
+ }
+ easyjson3a862f94Encode20242FIGHTCLUBDomain1(out, v15)
+ }
+ out.RawByte(']')
+ }
+ }
+ {
+ const prefix string = ",\"squareMeters\":"
+ out.RawString(prefix)
+ out.Int(int(in.SquareMeters))
+ }
+ {
+ const prefix string = ",\"floor\":"
+ out.RawString(prefix)
+ out.Int(int(in.Floor))
+ }
+ {
+ const prefix string = ",\"buildingType\":"
+ out.RawString(prefix)
+ out.String(string(in.BuildingType))
+ }
+ {
+ const prefix string = ",\"hasBalcony\":"
+ out.RawString(prefix)
+ out.Bool(bool(in.HasBalcony))
+ }
+ {
+ const prefix string = ",\"hasElevator\":"
+ out.RawString(prefix)
+ out.Bool(bool(in.HasElevator))
+ }
+ {
+ const prefix string = ",\"hasGas\":"
+ out.RawString(prefix)
+ out.Bool(bool(in.HasGas))
+ }
+ out.RawByte('}')
+}
+
+// MarshalJSON supports json.Marshaler interface
+func (v CreateAdRequest) MarshalJSON() ([]byte, error) {
+ w := jwriter.Writer{}
+ easyjson3a862f94Encode20242FIGHTCLUBDomain10(&w, v)
+ return w.Buffer.BuildBytes(), w.Error
+}
+
+// MarshalEasyJSON supports easyjson.Marshaler interface
+func (v CreateAdRequest) MarshalEasyJSON(w *jwriter.Writer) {
+ easyjson3a862f94Encode20242FIGHTCLUBDomain10(w, v)
+}
+
+// UnmarshalJSON supports json.Unmarshaler interface
+func (v *CreateAdRequest) UnmarshalJSON(data []byte) error {
+ r := jlexer.Lexer{Data: data}
+ easyjson3a862f94Decode20242FIGHTCLUBDomain10(&r, v)
+ return r.Error()
+}
+
+// UnmarshalEasyJSON supports easyjson.Unmarshaler interface
+func (v *CreateAdRequest) UnmarshalEasyJSON(l *jlexer.Lexer) {
+ easyjson3a862f94Decode20242FIGHTCLUBDomain10(l, v)
+}
+func easyjson3a862f94Decode20242FIGHTCLUBDomain11(in *jlexer.Lexer, out *AdFilter) {
+ isTopLevel := in.IsStart()
+ if in.IsNull() {
+ if isTopLevel {
+ in.Consumed()
+ }
+ in.Skip()
+ return
+ }
+ in.Delim('{')
+ for !in.IsDelim('}') {
+ key := in.UnsafeFieldName(false)
+ in.WantColon()
+ if in.IsNull() {
+ in.Skip()
+ in.WantComma()
+ continue
+ }
+ switch key {
+ case "Location":
+ out.Location = string(in.String())
+ case "Rating":
+ out.Rating = string(in.String())
+ case "NewThisWeek":
+ out.NewThisWeek = string(in.String())
+ case "HostGender":
+ out.HostGender = string(in.String())
+ case "GuestCount":
+ out.GuestCount = string(in.String())
+ case "Limit":
+ out.Limit = int(in.Int())
+ case "Offset":
+ out.Offset = int(in.Int())
+ case "DateFrom":
+ if data := in.Raw(); in.Ok() {
+ in.AddError((out.DateFrom).UnmarshalJSON(data))
+ }
+ case "DateTo":
+ if data := in.Raw(); in.Ok() {
+ in.AddError((out.DateTo).UnmarshalJSON(data))
+ }
+ case "Favorites":
+ out.Favorites = string(in.String())
+ default:
+ in.SkipRecursive()
+ }
+ in.WantComma()
+ }
+ in.Delim('}')
+ if isTopLevel {
+ in.Consumed()
+ }
+}
+func easyjson3a862f94Encode20242FIGHTCLUBDomain11(out *jwriter.Writer, in AdFilter) {
+ out.RawByte('{')
+ first := true
+ _ = first
+ {
+ const prefix string = ",\"Location\":"
+ out.RawString(prefix[1:])
+ out.String(string(in.Location))
+ }
+ {
+ const prefix string = ",\"Rating\":"
+ out.RawString(prefix)
+ out.String(string(in.Rating))
+ }
+ {
+ const prefix string = ",\"NewThisWeek\":"
+ out.RawString(prefix)
+ out.String(string(in.NewThisWeek))
+ }
+ {
+ const prefix string = ",\"HostGender\":"
+ out.RawString(prefix)
+ out.String(string(in.HostGender))
+ }
+ {
+ const prefix string = ",\"GuestCount\":"
+ out.RawString(prefix)
+ out.String(string(in.GuestCount))
+ }
+ {
+ const prefix string = ",\"Limit\":"
+ out.RawString(prefix)
+ out.Int(int(in.Limit))
+ }
+ {
+ const prefix string = ",\"Offset\":"
+ out.RawString(prefix)
+ out.Int(int(in.Offset))
+ }
+ {
+ const prefix string = ",\"DateFrom\":"
+ out.RawString(prefix)
+ out.Raw((in.DateFrom).MarshalJSON())
+ }
+ {
+ const prefix string = ",\"DateTo\":"
+ out.RawString(prefix)
+ out.Raw((in.DateTo).MarshalJSON())
+ }
+ {
+ const prefix string = ",\"Favorites\":"
+ out.RawString(prefix)
+ out.String(string(in.Favorites))
+ }
+ out.RawByte('}')
+}
+
+// MarshalJSON supports json.Marshaler interface
+func (v AdFilter) MarshalJSON() ([]byte, error) {
+ w := jwriter.Writer{}
+ easyjson3a862f94Encode20242FIGHTCLUBDomain11(&w, v)
+ return w.Buffer.BuildBytes(), w.Error
+}
+
+// MarshalEasyJSON supports easyjson.Marshaler interface
+func (v AdFilter) MarshalEasyJSON(w *jwriter.Writer) {
+ easyjson3a862f94Encode20242FIGHTCLUBDomain11(w, v)
+}
+
+// UnmarshalJSON supports json.Unmarshaler interface
+func (v *AdFilter) UnmarshalJSON(data []byte) error {
+ r := jlexer.Lexer{Data: data}
+ easyjson3a862f94Decode20242FIGHTCLUBDomain11(&r, v)
+ return r.Error()
+}
+
+// UnmarshalEasyJSON supports easyjson.Unmarshaler interface
+func (v *AdFilter) UnmarshalEasyJSON(l *jlexer.Lexer) {
+ easyjson3a862f94Decode20242FIGHTCLUBDomain11(l, v)
+}
+func easyjson3a862f94Decode20242FIGHTCLUBDomain12(in *jlexer.Lexer, out *Ad) {
+ isTopLevel := in.IsStart()
+ if in.IsNull() {
+ if isTopLevel {
+ in.Consumed()
+ }
+ in.Skip()
+ return
+ }
+ in.Delim('{')
+ for !in.IsDelim('}') {
+ key := in.UnsafeFieldName(false)
+ in.WantColon()
+ if in.IsNull() {
+ in.Skip()
+ in.WantComma()
+ continue
+ }
+ switch key {
+ case "id":
+ out.UUID = string(in.String())
+ case "cityId":
+ out.CityID = int(in.Int())
+ case "authorUUID":
+ out.AuthorUUID = string(in.String())
+ case "address":
+ out.Address = string(in.String())
+ case "publicationDate":
+ if data := in.Raw(); in.Ok() {
+ in.AddError((out.PublicationDate).UnmarshalJSON(data))
+ }
+ case "description":
+ out.Description = string(in.String())
+ case "roomsNumber":
+ out.RoomsNumber = int(in.Int())
+ case "viewsCount":
+ out.ViewsCount = int(in.Int())
+ case "squareMeters":
+ out.SquareMeters = int(in.Int())
+ case "floor":
+ out.Floor = int(in.Int())
+ case "buildingType":
+ out.BuildingType = string(in.String())
+ case "hasBalcony":
+ out.HasBalcony = bool(in.Bool())
+ case "hasElevator":
+ out.HasElevator = bool(in.Bool())
+ case "hasGas":
+ out.HasGas = bool(in.Bool())
+ case "likesCount":
+ out.LikesCount = int(in.Int())
+ case "priority":
+ out.Priority = int(in.Int())
+ case "endBoostDate":
+ if data := in.Raw(); in.Ok() {
+ in.AddError((out.EndBoostDate).UnmarshalJSON(data))
+ }
+ default:
+ in.SkipRecursive()
+ }
+ in.WantComma()
+ }
+ in.Delim('}')
+ if isTopLevel {
+ in.Consumed()
+ }
+}
+func easyjson3a862f94Encode20242FIGHTCLUBDomain12(out *jwriter.Writer, in Ad) {
+ out.RawByte('{')
+ first := true
+ _ = first
+ {
+ const prefix string = ",\"id\":"
+ out.RawString(prefix[1:])
+ out.String(string(in.UUID))
+ }
+ {
+ const prefix string = ",\"cityId\":"
+ out.RawString(prefix)
+ out.Int(int(in.CityID))
+ }
+ {
+ const prefix string = ",\"authorUUID\":"
+ out.RawString(prefix)
+ out.String(string(in.AuthorUUID))
+ }
+ {
+ const prefix string = ",\"address\":"
+ out.RawString(prefix)
+ out.String(string(in.Address))
+ }
+ {
+ const prefix string = ",\"publicationDate\":"
+ out.RawString(prefix)
+ out.Raw((in.PublicationDate).MarshalJSON())
+ }
+ {
+ const prefix string = ",\"description\":"
+ out.RawString(prefix)
+ out.String(string(in.Description))
+ }
+ {
+ const prefix string = ",\"roomsNumber\":"
+ out.RawString(prefix)
+ out.Int(int(in.RoomsNumber))
+ }
+ {
+ const prefix string = ",\"viewsCount\":"
+ out.RawString(prefix)
+ out.Int(int(in.ViewsCount))
+ }
+ {
+ const prefix string = ",\"squareMeters\":"
+ out.RawString(prefix)
+ out.Int(int(in.SquareMeters))
+ }
+ {
+ const prefix string = ",\"floor\":"
+ out.RawString(prefix)
+ out.Int(int(in.Floor))
+ }
+ {
+ const prefix string = ",\"buildingType\":"
+ out.RawString(prefix)
+ out.String(string(in.BuildingType))
+ }
+ {
+ const prefix string = ",\"hasBalcony\":"
+ out.RawString(prefix)
+ out.Bool(bool(in.HasBalcony))
+ }
+ {
+ const prefix string = ",\"hasElevator\":"
+ out.RawString(prefix)
+ out.Bool(bool(in.HasElevator))
+ }
+ {
+ const prefix string = ",\"hasGas\":"
+ out.RawString(prefix)
+ out.Bool(bool(in.HasGas))
+ }
+ {
+ const prefix string = ",\"likesCount\":"
+ out.RawString(prefix)
+ out.Int(int(in.LikesCount))
+ }
+ {
+ const prefix string = ",\"priority\":"
+ out.RawString(prefix)
+ out.Int(int(in.Priority))
+ }
+ {
+ const prefix string = ",\"endBoostDate\":"
+ out.RawString(prefix)
+ out.Raw((in.EndBoostDate).MarshalJSON())
+ }
+ out.RawByte('}')
+}
+
+// MarshalJSON supports json.Marshaler interface
+func (v Ad) MarshalJSON() ([]byte, error) {
+ w := jwriter.Writer{}
+ easyjson3a862f94Encode20242FIGHTCLUBDomain12(&w, v)
+ return w.Buffer.BuildBytes(), w.Error
+}
+
+// MarshalEasyJSON supports easyjson.Marshaler interface
+func (v Ad) MarshalEasyJSON(w *jwriter.Writer) {
+ easyjson3a862f94Encode20242FIGHTCLUBDomain12(w, v)
+}
+
+// UnmarshalJSON supports json.Unmarshaler interface
+func (v *Ad) UnmarshalJSON(data []byte) error {
+ r := jlexer.Lexer{Data: data}
+ easyjson3a862f94Decode20242FIGHTCLUBDomain12(&r, v)
+ return r.Error()
+}
+
+// UnmarshalEasyJSON supports easyjson.Unmarshaler interface
+func (v *Ad) UnmarshalEasyJSON(l *jlexer.Lexer) {
+ easyjson3a862f94Decode20242FIGHTCLUBDomain12(l, v)
+}
diff --git a/domain/auth.go b/domain/auth.go
index cf1680c..590a7e6 100644
--- a/domain/auth.go
+++ b/domain/auth.go
@@ -1,10 +1,48 @@
package domain
+//go:generate easyjson -all auth.go
+
import (
"context"
"time"
)
+//easyjson:json
+type CSRFTokenResponse struct {
+ Token string `json:"csrf_token"`
+}
+
+//easyjson:json
+type SessionData struct {
+ Id string `json:"id"`
+ Avatar string `json:"avatar"`
+}
+
+//easyjson:json
+type GetAllUsersResponse struct {
+ Users []*UserDataResponse `json:"users"`
+}
+
+//easyjson:json
+type AuthResponse struct {
+ SessionId string `json:"session_id"`
+ User AuthData `json:"user"`
+}
+
+//easyjson:json
+type UpdateUserRegion struct {
+ RegionName string `json:"regionName"`
+ StartVisitedDate string `json:"startVisitedDate"`
+ EndVisitedDate string `json:"endVisitedDate"`
+}
+
+type AuthData struct {
+ Id string `json:"id"`
+ Username string `json:"username"`
+ Email string `json:"email"`
+}
+
+//easyjson:json
type User struct {
UUID string `gorm:"type:uuid;primaryKey;default:gen_random_uuid();column:uuid" json:"id"`
Username string `gorm:"type:varchar(20);unique;not null;column:username" json:"username"`
@@ -28,6 +66,7 @@ type UserResponce struct {
GuestCount int `json:"guestCount"`
}
+//easyjson:json
type UserDataResponse struct {
Uuid string `json:"uuid"`
Username string `json:"username"`
@@ -49,4 +88,6 @@ type AuthRepository interface {
GetUserById(ctx context.Context, userID string) (*User, error)
GetUserByName(ctx context.Context, username string) (*User, error)
GetUserByEmail(ctx context.Context, email string) (*User, error)
+ UpdateUserRegion(ctx context.Context, region UpdateUserRegion, userId string) error
+ DeleteUserRegion(ctx context.Context, regionName string, userId string) error
}
diff --git a/domain/auth_easyjson.go b/domain/auth_easyjson.go
new file mode 100644
index 0000000..52b4b24
--- /dev/null
+++ b/domain/auth_easyjson.go
@@ -0,0 +1,873 @@
+// Code generated by easyjson for marshaling/unmarshaling. DO NOT EDIT.
+
+package domain
+
+import (
+ json "encoding/json"
+ easyjson "github.com/mailru/easyjson"
+ jlexer "github.com/mailru/easyjson/jlexer"
+ jwriter "github.com/mailru/easyjson/jwriter"
+)
+
+// suppress unused package warning
+var (
+ _ *json.RawMessage
+ _ *jlexer.Lexer
+ _ *jwriter.Writer
+ _ easyjson.Marshaler
+)
+
+func easyjson4a0f95aaDecode20242FIGHTCLUBDomain(in *jlexer.Lexer, out *UserResponce) {
+ isTopLevel := in.IsStart()
+ if in.IsNull() {
+ if isTopLevel {
+ in.Consumed()
+ }
+ in.Skip()
+ return
+ }
+ in.Delim('{')
+ for !in.IsDelim('}') {
+ key := in.UnsafeFieldName(false)
+ in.WantColon()
+ if in.IsNull() {
+ in.Skip()
+ in.WantComma()
+ continue
+ }
+ switch key {
+ case "rating":
+ out.Rating = float64(in.Float64())
+ case "avatar":
+ out.Avatar = string(in.String())
+ case "name":
+ out.Name = string(in.String())
+ case "sex":
+ out.Sex = string(in.String())
+ case "birthDate":
+ if data := in.Raw(); in.Ok() {
+ in.AddError((out.Birthdate).UnmarshalJSON(data))
+ }
+ case "guestCount":
+ out.GuestCount = int(in.Int())
+ default:
+ in.SkipRecursive()
+ }
+ in.WantComma()
+ }
+ in.Delim('}')
+ if isTopLevel {
+ in.Consumed()
+ }
+}
+func easyjson4a0f95aaEncode20242FIGHTCLUBDomain(out *jwriter.Writer, in UserResponce) {
+ out.RawByte('{')
+ first := true
+ _ = first
+ {
+ const prefix string = ",\"rating\":"
+ out.RawString(prefix[1:])
+ out.Float64(float64(in.Rating))
+ }
+ {
+ const prefix string = ",\"avatar\":"
+ out.RawString(prefix)
+ out.String(string(in.Avatar))
+ }
+ {
+ const prefix string = ",\"name\":"
+ out.RawString(prefix)
+ out.String(string(in.Name))
+ }
+ {
+ const prefix string = ",\"sex\":"
+ out.RawString(prefix)
+ out.String(string(in.Sex))
+ }
+ {
+ const prefix string = ",\"birthDate\":"
+ out.RawString(prefix)
+ out.Raw((in.Birthdate).MarshalJSON())
+ }
+ {
+ const prefix string = ",\"guestCount\":"
+ out.RawString(prefix)
+ out.Int(int(in.GuestCount))
+ }
+ out.RawByte('}')
+}
+
+// MarshalJSON supports json.Marshaler interface
+func (v UserResponce) MarshalJSON() ([]byte, error) {
+ w := jwriter.Writer{}
+ easyjson4a0f95aaEncode20242FIGHTCLUBDomain(&w, v)
+ return w.Buffer.BuildBytes(), w.Error
+}
+
+// MarshalEasyJSON supports easyjson.Marshaler interface
+func (v UserResponce) MarshalEasyJSON(w *jwriter.Writer) {
+ easyjson4a0f95aaEncode20242FIGHTCLUBDomain(w, v)
+}
+
+// UnmarshalJSON supports json.Unmarshaler interface
+func (v *UserResponce) UnmarshalJSON(data []byte) error {
+ r := jlexer.Lexer{Data: data}
+ easyjson4a0f95aaDecode20242FIGHTCLUBDomain(&r, v)
+ return r.Error()
+}
+
+// UnmarshalEasyJSON supports easyjson.Unmarshaler interface
+func (v *UserResponce) UnmarshalEasyJSON(l *jlexer.Lexer) {
+ easyjson4a0f95aaDecode20242FIGHTCLUBDomain(l, v)
+}
+func easyjson4a0f95aaDecode20242FIGHTCLUBDomain1(in *jlexer.Lexer, out *UserDataResponse) {
+ isTopLevel := in.IsStart()
+ if in.IsNull() {
+ if isTopLevel {
+ in.Consumed()
+ }
+ in.Skip()
+ return
+ }
+ in.Delim('{')
+ for !in.IsDelim('}') {
+ key := in.UnsafeFieldName(false)
+ in.WantColon()
+ if in.IsNull() {
+ in.Skip()
+ in.WantComma()
+ continue
+ }
+ switch key {
+ case "uuid":
+ out.Uuid = string(in.String())
+ case "username":
+ out.Username = string(in.String())
+ case "email":
+ out.Email = string(in.String())
+ case "name":
+ out.Name = string(in.String())
+ case "score":
+ out.Score = float64(in.Float64())
+ case "avatar":
+ out.Avatar = string(in.String())
+ case "sex":
+ out.Sex = string(in.String())
+ case "guestCount":
+ out.GuestCount = int(in.Int())
+ case "birthdate":
+ if data := in.Raw(); in.Ok() {
+ in.AddError((out.Birthdate).UnmarshalJSON(data))
+ }
+ case "isHost":
+ out.IsHost = bool(in.Bool())
+ default:
+ in.SkipRecursive()
+ }
+ in.WantComma()
+ }
+ in.Delim('}')
+ if isTopLevel {
+ in.Consumed()
+ }
+}
+func easyjson4a0f95aaEncode20242FIGHTCLUBDomain1(out *jwriter.Writer, in UserDataResponse) {
+ out.RawByte('{')
+ first := true
+ _ = first
+ {
+ const prefix string = ",\"uuid\":"
+ out.RawString(prefix[1:])
+ out.String(string(in.Uuid))
+ }
+ {
+ const prefix string = ",\"username\":"
+ out.RawString(prefix)
+ out.String(string(in.Username))
+ }
+ {
+ const prefix string = ",\"email\":"
+ out.RawString(prefix)
+ out.String(string(in.Email))
+ }
+ {
+ const prefix string = ",\"name\":"
+ out.RawString(prefix)
+ out.String(string(in.Name))
+ }
+ {
+ const prefix string = ",\"score\":"
+ out.RawString(prefix)
+ out.Float64(float64(in.Score))
+ }
+ {
+ const prefix string = ",\"avatar\":"
+ out.RawString(prefix)
+ out.String(string(in.Avatar))
+ }
+ {
+ const prefix string = ",\"sex\":"
+ out.RawString(prefix)
+ out.String(string(in.Sex))
+ }
+ {
+ const prefix string = ",\"guestCount\":"
+ out.RawString(prefix)
+ out.Int(int(in.GuestCount))
+ }
+ {
+ const prefix string = ",\"birthdate\":"
+ out.RawString(prefix)
+ out.Raw((in.Birthdate).MarshalJSON())
+ }
+ {
+ const prefix string = ",\"isHost\":"
+ out.RawString(prefix)
+ out.Bool(bool(in.IsHost))
+ }
+ out.RawByte('}')
+}
+
+// MarshalJSON supports json.Marshaler interface
+func (v UserDataResponse) MarshalJSON() ([]byte, error) {
+ w := jwriter.Writer{}
+ easyjson4a0f95aaEncode20242FIGHTCLUBDomain1(&w, v)
+ return w.Buffer.BuildBytes(), w.Error
+}
+
+// MarshalEasyJSON supports easyjson.Marshaler interface
+func (v UserDataResponse) MarshalEasyJSON(w *jwriter.Writer) {
+ easyjson4a0f95aaEncode20242FIGHTCLUBDomain1(w, v)
+}
+
+// UnmarshalJSON supports json.Unmarshaler interface
+func (v *UserDataResponse) UnmarshalJSON(data []byte) error {
+ r := jlexer.Lexer{Data: data}
+ easyjson4a0f95aaDecode20242FIGHTCLUBDomain1(&r, v)
+ return r.Error()
+}
+
+// UnmarshalEasyJSON supports easyjson.Unmarshaler interface
+func (v *UserDataResponse) UnmarshalEasyJSON(l *jlexer.Lexer) {
+ easyjson4a0f95aaDecode20242FIGHTCLUBDomain1(l, v)
+}
+func easyjson4a0f95aaDecode20242FIGHTCLUBDomain2(in *jlexer.Lexer, out *User) {
+ isTopLevel := in.IsStart()
+ if in.IsNull() {
+ if isTopLevel {
+ in.Consumed()
+ }
+ in.Skip()
+ return
+ }
+ in.Delim('{')
+ for !in.IsDelim('}') {
+ key := in.UnsafeFieldName(false)
+ in.WantColon()
+ if in.IsNull() {
+ in.Skip()
+ in.WantComma()
+ continue
+ }
+ switch key {
+ case "id":
+ out.UUID = string(in.String())
+ case "username":
+ out.Username = string(in.String())
+ case "password":
+ out.Password = string(in.String())
+ case "email":
+ out.Email = string(in.String())
+ case "name":
+ out.Name = string(in.String())
+ case "score":
+ out.Score = float64(in.Float64())
+ case "avatar":
+ out.Avatar = string(in.String())
+ case "sex":
+ out.Sex = string(in.String())
+ case "guestCount":
+ out.GuestCount = int(in.Int())
+ case "birthDate":
+ if data := in.Raw(); in.Ok() {
+ in.AddError((out.Birthdate).UnmarshalJSON(data))
+ }
+ case "isHost":
+ out.IsHost = bool(in.Bool())
+ default:
+ in.SkipRecursive()
+ }
+ in.WantComma()
+ }
+ in.Delim('}')
+ if isTopLevel {
+ in.Consumed()
+ }
+}
+func easyjson4a0f95aaEncode20242FIGHTCLUBDomain2(out *jwriter.Writer, in User) {
+ out.RawByte('{')
+ first := true
+ _ = first
+ {
+ const prefix string = ",\"id\":"
+ out.RawString(prefix[1:])
+ out.String(string(in.UUID))
+ }
+ {
+ const prefix string = ",\"username\":"
+ out.RawString(prefix)
+ out.String(string(in.Username))
+ }
+ {
+ const prefix string = ",\"password\":"
+ out.RawString(prefix)
+ out.String(string(in.Password))
+ }
+ {
+ const prefix string = ",\"email\":"
+ out.RawString(prefix)
+ out.String(string(in.Email))
+ }
+ {
+ const prefix string = ",\"name\":"
+ out.RawString(prefix)
+ out.String(string(in.Name))
+ }
+ {
+ const prefix string = ",\"score\":"
+ out.RawString(prefix)
+ out.Float64(float64(in.Score))
+ }
+ {
+ const prefix string = ",\"avatar\":"
+ out.RawString(prefix)
+ out.String(string(in.Avatar))
+ }
+ {
+ const prefix string = ",\"sex\":"
+ out.RawString(prefix)
+ out.String(string(in.Sex))
+ }
+ {
+ const prefix string = ",\"guestCount\":"
+ out.RawString(prefix)
+ out.Int(int(in.GuestCount))
+ }
+ {
+ const prefix string = ",\"birthDate\":"
+ out.RawString(prefix)
+ out.Raw((in.Birthdate).MarshalJSON())
+ }
+ {
+ const prefix string = ",\"isHost\":"
+ out.RawString(prefix)
+ out.Bool(bool(in.IsHost))
+ }
+ out.RawByte('}')
+}
+
+// MarshalJSON supports json.Marshaler interface
+func (v User) MarshalJSON() ([]byte, error) {
+ w := jwriter.Writer{}
+ easyjson4a0f95aaEncode20242FIGHTCLUBDomain2(&w, v)
+ return w.Buffer.BuildBytes(), w.Error
+}
+
+// MarshalEasyJSON supports easyjson.Marshaler interface
+func (v User) MarshalEasyJSON(w *jwriter.Writer) {
+ easyjson4a0f95aaEncode20242FIGHTCLUBDomain2(w, v)
+}
+
+// UnmarshalJSON supports json.Unmarshaler interface
+func (v *User) UnmarshalJSON(data []byte) error {
+ r := jlexer.Lexer{Data: data}
+ easyjson4a0f95aaDecode20242FIGHTCLUBDomain2(&r, v)
+ return r.Error()
+}
+
+// UnmarshalEasyJSON supports easyjson.Unmarshaler interface
+func (v *User) UnmarshalEasyJSON(l *jlexer.Lexer) {
+ easyjson4a0f95aaDecode20242FIGHTCLUBDomain2(l, v)
+}
+func easyjson4a0f95aaDecode20242FIGHTCLUBDomain3(in *jlexer.Lexer, out *UpdateUserRegion) {
+ isTopLevel := in.IsStart()
+ if in.IsNull() {
+ if isTopLevel {
+ in.Consumed()
+ }
+ in.Skip()
+ return
+ }
+ in.Delim('{')
+ for !in.IsDelim('}') {
+ key := in.UnsafeFieldName(false)
+ in.WantColon()
+ if in.IsNull() {
+ in.Skip()
+ in.WantComma()
+ continue
+ }
+ switch key {
+ case "regionName":
+ out.RegionName = string(in.String())
+ case "startVisitedDate":
+ out.StartVisitedDate = string(in.String())
+ case "endVisitedDate":
+ out.EndVisitedDate = string(in.String())
+ default:
+ in.SkipRecursive()
+ }
+ in.WantComma()
+ }
+ in.Delim('}')
+ if isTopLevel {
+ in.Consumed()
+ }
+}
+func easyjson4a0f95aaEncode20242FIGHTCLUBDomain3(out *jwriter.Writer, in UpdateUserRegion) {
+ out.RawByte('{')
+ first := true
+ _ = first
+ {
+ const prefix string = ",\"regionName\":"
+ out.RawString(prefix[1:])
+ out.String(string(in.RegionName))
+ }
+ {
+ const prefix string = ",\"startVisitedDate\":"
+ out.RawString(prefix)
+ out.String(string(in.StartVisitedDate))
+ }
+ {
+ const prefix string = ",\"endVisitedDate\":"
+ out.RawString(prefix)
+ out.String(string(in.EndVisitedDate))
+ }
+ out.RawByte('}')
+}
+
+// MarshalJSON supports json.Marshaler interface
+func (v UpdateUserRegion) MarshalJSON() ([]byte, error) {
+ w := jwriter.Writer{}
+ easyjson4a0f95aaEncode20242FIGHTCLUBDomain3(&w, v)
+ return w.Buffer.BuildBytes(), w.Error
+}
+
+// MarshalEasyJSON supports easyjson.Marshaler interface
+func (v UpdateUserRegion) MarshalEasyJSON(w *jwriter.Writer) {
+ easyjson4a0f95aaEncode20242FIGHTCLUBDomain3(w, v)
+}
+
+// UnmarshalJSON supports json.Unmarshaler interface
+func (v *UpdateUserRegion) UnmarshalJSON(data []byte) error {
+ r := jlexer.Lexer{Data: data}
+ easyjson4a0f95aaDecode20242FIGHTCLUBDomain3(&r, v)
+ return r.Error()
+}
+
+// UnmarshalEasyJSON supports easyjson.Unmarshaler interface
+func (v *UpdateUserRegion) UnmarshalEasyJSON(l *jlexer.Lexer) {
+ easyjson4a0f95aaDecode20242FIGHTCLUBDomain3(l, v)
+}
+func easyjson4a0f95aaDecode20242FIGHTCLUBDomain4(in *jlexer.Lexer, out *SessionData) {
+ isTopLevel := in.IsStart()
+ if in.IsNull() {
+ if isTopLevel {
+ in.Consumed()
+ }
+ in.Skip()
+ return
+ }
+ in.Delim('{')
+ for !in.IsDelim('}') {
+ key := in.UnsafeFieldName(false)
+ in.WantColon()
+ if in.IsNull() {
+ in.Skip()
+ in.WantComma()
+ continue
+ }
+ switch key {
+ case "id":
+ out.Id = string(in.String())
+ case "avatar":
+ out.Avatar = string(in.String())
+ default:
+ in.SkipRecursive()
+ }
+ in.WantComma()
+ }
+ in.Delim('}')
+ if isTopLevel {
+ in.Consumed()
+ }
+}
+func easyjson4a0f95aaEncode20242FIGHTCLUBDomain4(out *jwriter.Writer, in SessionData) {
+ out.RawByte('{')
+ first := true
+ _ = first
+ {
+ const prefix string = ",\"id\":"
+ out.RawString(prefix[1:])
+ out.String(string(in.Id))
+ }
+ {
+ const prefix string = ",\"avatar\":"
+ out.RawString(prefix)
+ out.String(string(in.Avatar))
+ }
+ out.RawByte('}')
+}
+
+// MarshalJSON supports json.Marshaler interface
+func (v SessionData) MarshalJSON() ([]byte, error) {
+ w := jwriter.Writer{}
+ easyjson4a0f95aaEncode20242FIGHTCLUBDomain4(&w, v)
+ return w.Buffer.BuildBytes(), w.Error
+}
+
+// MarshalEasyJSON supports easyjson.Marshaler interface
+func (v SessionData) MarshalEasyJSON(w *jwriter.Writer) {
+ easyjson4a0f95aaEncode20242FIGHTCLUBDomain4(w, v)
+}
+
+// UnmarshalJSON supports json.Unmarshaler interface
+func (v *SessionData) UnmarshalJSON(data []byte) error {
+ r := jlexer.Lexer{Data: data}
+ easyjson4a0f95aaDecode20242FIGHTCLUBDomain4(&r, v)
+ return r.Error()
+}
+
+// UnmarshalEasyJSON supports easyjson.Unmarshaler interface
+func (v *SessionData) UnmarshalEasyJSON(l *jlexer.Lexer) {
+ easyjson4a0f95aaDecode20242FIGHTCLUBDomain4(l, v)
+}
+func easyjson4a0f95aaDecode20242FIGHTCLUBDomain5(in *jlexer.Lexer, out *GetAllUsersResponse) {
+ isTopLevel := in.IsStart()
+ if in.IsNull() {
+ if isTopLevel {
+ in.Consumed()
+ }
+ in.Skip()
+ return
+ }
+ in.Delim('{')
+ for !in.IsDelim('}') {
+ key := in.UnsafeFieldName(false)
+ in.WantColon()
+ if in.IsNull() {
+ in.Skip()
+ in.WantComma()
+ continue
+ }
+ switch key {
+ case "users":
+ if in.IsNull() {
+ in.Skip()
+ out.Users = nil
+ } else {
+ in.Delim('[')
+ if out.Users == nil {
+ if !in.IsDelim(']') {
+ out.Users = make([]*UserDataResponse, 0, 8)
+ } else {
+ out.Users = []*UserDataResponse{}
+ }
+ } else {
+ out.Users = (out.Users)[:0]
+ }
+ for !in.IsDelim(']') {
+ var v1 *UserDataResponse
+ if in.IsNull() {
+ in.Skip()
+ v1 = nil
+ } else {
+ if v1 == nil {
+ v1 = new(UserDataResponse)
+ }
+ (*v1).UnmarshalEasyJSON(in)
+ }
+ out.Users = append(out.Users, v1)
+ in.WantComma()
+ }
+ in.Delim(']')
+ }
+ default:
+ in.SkipRecursive()
+ }
+ in.WantComma()
+ }
+ in.Delim('}')
+ if isTopLevel {
+ in.Consumed()
+ }
+}
+func easyjson4a0f95aaEncode20242FIGHTCLUBDomain5(out *jwriter.Writer, in GetAllUsersResponse) {
+ out.RawByte('{')
+ first := true
+ _ = first
+ {
+ const prefix string = ",\"users\":"
+ out.RawString(prefix[1:])
+ if in.Users == nil && (out.Flags&jwriter.NilSliceAsEmpty) == 0 {
+ out.RawString("null")
+ } else {
+ out.RawByte('[')
+ for v2, v3 := range in.Users {
+ if v2 > 0 {
+ out.RawByte(',')
+ }
+ if v3 == nil {
+ out.RawString("null")
+ } else {
+ (*v3).MarshalEasyJSON(out)
+ }
+ }
+ out.RawByte(']')
+ }
+ }
+ out.RawByte('}')
+}
+
+// MarshalJSON supports json.Marshaler interface
+func (v GetAllUsersResponse) MarshalJSON() ([]byte, error) {
+ w := jwriter.Writer{}
+ easyjson4a0f95aaEncode20242FIGHTCLUBDomain5(&w, v)
+ return w.Buffer.BuildBytes(), w.Error
+}
+
+// MarshalEasyJSON supports easyjson.Marshaler interface
+func (v GetAllUsersResponse) MarshalEasyJSON(w *jwriter.Writer) {
+ easyjson4a0f95aaEncode20242FIGHTCLUBDomain5(w, v)
+}
+
+// UnmarshalJSON supports json.Unmarshaler interface
+func (v *GetAllUsersResponse) UnmarshalJSON(data []byte) error {
+ r := jlexer.Lexer{Data: data}
+ easyjson4a0f95aaDecode20242FIGHTCLUBDomain5(&r, v)
+ return r.Error()
+}
+
+// UnmarshalEasyJSON supports easyjson.Unmarshaler interface
+func (v *GetAllUsersResponse) UnmarshalEasyJSON(l *jlexer.Lexer) {
+ easyjson4a0f95aaDecode20242FIGHTCLUBDomain5(l, v)
+}
+func easyjson4a0f95aaDecode20242FIGHTCLUBDomain6(in *jlexer.Lexer, out *CSRFTokenResponse) {
+ isTopLevel := in.IsStart()
+ if in.IsNull() {
+ if isTopLevel {
+ in.Consumed()
+ }
+ in.Skip()
+ return
+ }
+ in.Delim('{')
+ for !in.IsDelim('}') {
+ key := in.UnsafeFieldName(false)
+ in.WantColon()
+ if in.IsNull() {
+ in.Skip()
+ in.WantComma()
+ continue
+ }
+ switch key {
+ case "csrf_token":
+ out.Token = string(in.String())
+ default:
+ in.SkipRecursive()
+ }
+ in.WantComma()
+ }
+ in.Delim('}')
+ if isTopLevel {
+ in.Consumed()
+ }
+}
+func easyjson4a0f95aaEncode20242FIGHTCLUBDomain6(out *jwriter.Writer, in CSRFTokenResponse) {
+ out.RawByte('{')
+ first := true
+ _ = first
+ {
+ const prefix string = ",\"csrf_token\":"
+ out.RawString(prefix[1:])
+ out.String(string(in.Token))
+ }
+ out.RawByte('}')
+}
+
+// MarshalJSON supports json.Marshaler interface
+func (v CSRFTokenResponse) MarshalJSON() ([]byte, error) {
+ w := jwriter.Writer{}
+ easyjson4a0f95aaEncode20242FIGHTCLUBDomain6(&w, v)
+ return w.Buffer.BuildBytes(), w.Error
+}
+
+// MarshalEasyJSON supports easyjson.Marshaler interface
+func (v CSRFTokenResponse) MarshalEasyJSON(w *jwriter.Writer) {
+ easyjson4a0f95aaEncode20242FIGHTCLUBDomain6(w, v)
+}
+
+// UnmarshalJSON supports json.Unmarshaler interface
+func (v *CSRFTokenResponse) UnmarshalJSON(data []byte) error {
+ r := jlexer.Lexer{Data: data}
+ easyjson4a0f95aaDecode20242FIGHTCLUBDomain6(&r, v)
+ return r.Error()
+}
+
+// UnmarshalEasyJSON supports easyjson.Unmarshaler interface
+func (v *CSRFTokenResponse) UnmarshalEasyJSON(l *jlexer.Lexer) {
+ easyjson4a0f95aaDecode20242FIGHTCLUBDomain6(l, v)
+}
+func easyjson4a0f95aaDecode20242FIGHTCLUBDomain7(in *jlexer.Lexer, out *AuthResponse) {
+ isTopLevel := in.IsStart()
+ if in.IsNull() {
+ if isTopLevel {
+ in.Consumed()
+ }
+ in.Skip()
+ return
+ }
+ in.Delim('{')
+ for !in.IsDelim('}') {
+ key := in.UnsafeFieldName(false)
+ in.WantColon()
+ if in.IsNull() {
+ in.Skip()
+ in.WantComma()
+ continue
+ }
+ switch key {
+ case "session_id":
+ out.SessionId = string(in.String())
+ case "user":
+ (out.User).UnmarshalEasyJSON(in)
+ default:
+ in.SkipRecursive()
+ }
+ in.WantComma()
+ }
+ in.Delim('}')
+ if isTopLevel {
+ in.Consumed()
+ }
+}
+func easyjson4a0f95aaEncode20242FIGHTCLUBDomain7(out *jwriter.Writer, in AuthResponse) {
+ out.RawByte('{')
+ first := true
+ _ = first
+ {
+ const prefix string = ",\"session_id\":"
+ out.RawString(prefix[1:])
+ out.String(string(in.SessionId))
+ }
+ {
+ const prefix string = ",\"user\":"
+ out.RawString(prefix)
+ (in.User).MarshalEasyJSON(out)
+ }
+ out.RawByte('}')
+}
+
+// MarshalJSON supports json.Marshaler interface
+func (v AuthResponse) MarshalJSON() ([]byte, error) {
+ w := jwriter.Writer{}
+ easyjson4a0f95aaEncode20242FIGHTCLUBDomain7(&w, v)
+ return w.Buffer.BuildBytes(), w.Error
+}
+
+// MarshalEasyJSON supports easyjson.Marshaler interface
+func (v AuthResponse) MarshalEasyJSON(w *jwriter.Writer) {
+ easyjson4a0f95aaEncode20242FIGHTCLUBDomain7(w, v)
+}
+
+// UnmarshalJSON supports json.Unmarshaler interface
+func (v *AuthResponse) UnmarshalJSON(data []byte) error {
+ r := jlexer.Lexer{Data: data}
+ easyjson4a0f95aaDecode20242FIGHTCLUBDomain7(&r, v)
+ return r.Error()
+}
+
+// UnmarshalEasyJSON supports easyjson.Unmarshaler interface
+func (v *AuthResponse) UnmarshalEasyJSON(l *jlexer.Lexer) {
+ easyjson4a0f95aaDecode20242FIGHTCLUBDomain7(l, v)
+}
+func easyjson4a0f95aaDecode20242FIGHTCLUBDomain8(in *jlexer.Lexer, out *AuthData) {
+ isTopLevel := in.IsStart()
+ if in.IsNull() {
+ if isTopLevel {
+ in.Consumed()
+ }
+ in.Skip()
+ return
+ }
+ in.Delim('{')
+ for !in.IsDelim('}') {
+ key := in.UnsafeFieldName(false)
+ in.WantColon()
+ if in.IsNull() {
+ in.Skip()
+ in.WantComma()
+ continue
+ }
+ switch key {
+ case "id":
+ out.Id = string(in.String())
+ case "username":
+ out.Username = string(in.String())
+ case "email":
+ out.Email = string(in.String())
+ default:
+ in.SkipRecursive()
+ }
+ in.WantComma()
+ }
+ in.Delim('}')
+ if isTopLevel {
+ in.Consumed()
+ }
+}
+func easyjson4a0f95aaEncode20242FIGHTCLUBDomain8(out *jwriter.Writer, in AuthData) {
+ out.RawByte('{')
+ first := true
+ _ = first
+ {
+ const prefix string = ",\"id\":"
+ out.RawString(prefix[1:])
+ out.String(string(in.Id))
+ }
+ {
+ const prefix string = ",\"username\":"
+ out.RawString(prefix)
+ out.String(string(in.Username))
+ }
+ {
+ const prefix string = ",\"email\":"
+ out.RawString(prefix)
+ out.String(string(in.Email))
+ }
+ out.RawByte('}')
+}
+
+// MarshalJSON supports json.Marshaler interface
+func (v AuthData) MarshalJSON() ([]byte, error) {
+ w := jwriter.Writer{}
+ easyjson4a0f95aaEncode20242FIGHTCLUBDomain8(&w, v)
+ return w.Buffer.BuildBytes(), w.Error
+}
+
+// MarshalEasyJSON supports easyjson.Marshaler interface
+func (v AuthData) MarshalEasyJSON(w *jwriter.Writer) {
+ easyjson4a0f95aaEncode20242FIGHTCLUBDomain8(w, v)
+}
+
+// UnmarshalJSON supports json.Unmarshaler interface
+func (v *AuthData) UnmarshalJSON(data []byte) error {
+ r := jlexer.Lexer{Data: data}
+ easyjson4a0f95aaDecode20242FIGHTCLUBDomain8(&r, v)
+ return r.Error()
+}
+
+// UnmarshalEasyJSON supports easyjson.Unmarshaler interface
+func (v *AuthData) UnmarshalEasyJSON(l *jlexer.Lexer) {
+ easyjson4a0f95aaDecode20242FIGHTCLUBDomain8(l, v)
+}
diff --git a/domain/chat.go b/domain/chat.go
index ce07e8e..8358fba 100644
--- a/domain/chat.go
+++ b/domain/chat.go
@@ -1,10 +1,23 @@
package domain
+//go:generate easyjson -all chat.go
+
import (
"context"
"time"
)
+//easyjson:json
+type AllMessages struct {
+ Chat []*Message `json:"chat"`
+}
+
+//easyjson:json
+type AllChats struct {
+ Chats []*Chat `json:"chats"`
+}
+
+//easyjson:json
type Chat struct {
LastMessage string `gorm:"type:text;size:1000;column:lastMessage" json:"lastMessage"`
LastDate time.Time `gorm:"type:timestamp;column:lastDate" json:"lastDate"`
@@ -13,6 +26,7 @@ type Chat struct {
AuthorUUID string `gorm:"column:authorUuid;not null" json:"authorUuid"`
}
+//easyjson:json
type Message struct {
ID int `gorm:"primary_key;auto_increment;column:id" json:"id"`
SenderID string `gorm:"column:senderId;not null" json:"senderId"`
diff --git a/domain/chat_easyjson.go b/domain/chat_easyjson.go
new file mode 100644
index 0000000..1b7b670
--- /dev/null
+++ b/domain/chat_easyjson.go
@@ -0,0 +1,431 @@
+// Code generated by easyjson for marshaling/unmarshaling. DO NOT EDIT.
+
+package domain
+
+import (
+ json "encoding/json"
+ easyjson "github.com/mailru/easyjson"
+ jlexer "github.com/mailru/easyjson/jlexer"
+ jwriter "github.com/mailru/easyjson/jwriter"
+)
+
+// suppress unused package warning
+var (
+ _ *json.RawMessage
+ _ *jlexer.Lexer
+ _ *jwriter.Writer
+ _ easyjson.Marshaler
+)
+
+func easyjson9b8f5552Decode20242FIGHTCLUBDomain(in *jlexer.Lexer, out *Message) {
+ isTopLevel := in.IsStart()
+ if in.IsNull() {
+ if isTopLevel {
+ in.Consumed()
+ }
+ in.Skip()
+ return
+ }
+ in.Delim('{')
+ for !in.IsDelim('}') {
+ key := in.UnsafeFieldName(false)
+ in.WantColon()
+ if in.IsNull() {
+ in.Skip()
+ in.WantComma()
+ continue
+ }
+ switch key {
+ case "id":
+ out.ID = int(in.Int())
+ case "senderId":
+ out.SenderID = string(in.String())
+ case "receiverId":
+ out.ReceiverID = string(in.String())
+ case "content":
+ out.Content = string(in.String())
+ case "createdAt":
+ if data := in.Raw(); in.Ok() {
+ in.AddError((out.CreatedAt).UnmarshalJSON(data))
+ }
+ default:
+ in.SkipRecursive()
+ }
+ in.WantComma()
+ }
+ in.Delim('}')
+ if isTopLevel {
+ in.Consumed()
+ }
+}
+func easyjson9b8f5552Encode20242FIGHTCLUBDomain(out *jwriter.Writer, in Message) {
+ out.RawByte('{')
+ first := true
+ _ = first
+ {
+ const prefix string = ",\"id\":"
+ out.RawString(prefix[1:])
+ out.Int(int(in.ID))
+ }
+ {
+ const prefix string = ",\"senderId\":"
+ out.RawString(prefix)
+ out.String(string(in.SenderID))
+ }
+ {
+ const prefix string = ",\"receiverId\":"
+ out.RawString(prefix)
+ out.String(string(in.ReceiverID))
+ }
+ {
+ const prefix string = ",\"content\":"
+ out.RawString(prefix)
+ out.String(string(in.Content))
+ }
+ {
+ const prefix string = ",\"createdAt\":"
+ out.RawString(prefix)
+ out.Raw((in.CreatedAt).MarshalJSON())
+ }
+ out.RawByte('}')
+}
+
+// MarshalJSON supports json.Marshaler interface
+func (v Message) MarshalJSON() ([]byte, error) {
+ w := jwriter.Writer{}
+ easyjson9b8f5552Encode20242FIGHTCLUBDomain(&w, v)
+ return w.Buffer.BuildBytes(), w.Error
+}
+
+// MarshalEasyJSON supports easyjson.Marshaler interface
+func (v Message) MarshalEasyJSON(w *jwriter.Writer) {
+ easyjson9b8f5552Encode20242FIGHTCLUBDomain(w, v)
+}
+
+// UnmarshalJSON supports json.Unmarshaler interface
+func (v *Message) UnmarshalJSON(data []byte) error {
+ r := jlexer.Lexer{Data: data}
+ easyjson9b8f5552Decode20242FIGHTCLUBDomain(&r, v)
+ return r.Error()
+}
+
+// UnmarshalEasyJSON supports easyjson.Unmarshaler interface
+func (v *Message) UnmarshalEasyJSON(l *jlexer.Lexer) {
+ easyjson9b8f5552Decode20242FIGHTCLUBDomain(l, v)
+}
+func easyjson9b8f5552Decode20242FIGHTCLUBDomain1(in *jlexer.Lexer, out *Chat) {
+ isTopLevel := in.IsStart()
+ if in.IsNull() {
+ if isTopLevel {
+ in.Consumed()
+ }
+ in.Skip()
+ return
+ }
+ in.Delim('{')
+ for !in.IsDelim('}') {
+ key := in.UnsafeFieldName(false)
+ in.WantColon()
+ if in.IsNull() {
+ in.Skip()
+ in.WantComma()
+ continue
+ }
+ switch key {
+ case "lastMessage":
+ out.LastMessage = string(in.String())
+ case "lastDate":
+ if data := in.Raw(); in.Ok() {
+ in.AddError((out.LastDate).UnmarshalJSON(data))
+ }
+ case "authorName":
+ out.AuthorName = string(in.String())
+ case "authorAvatar":
+ out.AuthorAvatar = string(in.String())
+ case "authorUuid":
+ out.AuthorUUID = string(in.String())
+ default:
+ in.SkipRecursive()
+ }
+ in.WantComma()
+ }
+ in.Delim('}')
+ if isTopLevel {
+ in.Consumed()
+ }
+}
+func easyjson9b8f5552Encode20242FIGHTCLUBDomain1(out *jwriter.Writer, in Chat) {
+ out.RawByte('{')
+ first := true
+ _ = first
+ {
+ const prefix string = ",\"lastMessage\":"
+ out.RawString(prefix[1:])
+ out.String(string(in.LastMessage))
+ }
+ {
+ const prefix string = ",\"lastDate\":"
+ out.RawString(prefix)
+ out.Raw((in.LastDate).MarshalJSON())
+ }
+ {
+ const prefix string = ",\"authorName\":"
+ out.RawString(prefix)
+ out.String(string(in.AuthorName))
+ }
+ {
+ const prefix string = ",\"authorAvatar\":"
+ out.RawString(prefix)
+ out.String(string(in.AuthorAvatar))
+ }
+ {
+ const prefix string = ",\"authorUuid\":"
+ out.RawString(prefix)
+ out.String(string(in.AuthorUUID))
+ }
+ out.RawByte('}')
+}
+
+// MarshalJSON supports json.Marshaler interface
+func (v Chat) MarshalJSON() ([]byte, error) {
+ w := jwriter.Writer{}
+ easyjson9b8f5552Encode20242FIGHTCLUBDomain1(&w, v)
+ return w.Buffer.BuildBytes(), w.Error
+}
+
+// MarshalEasyJSON supports easyjson.Marshaler interface
+func (v Chat) MarshalEasyJSON(w *jwriter.Writer) {
+ easyjson9b8f5552Encode20242FIGHTCLUBDomain1(w, v)
+}
+
+// UnmarshalJSON supports json.Unmarshaler interface
+func (v *Chat) UnmarshalJSON(data []byte) error {
+ r := jlexer.Lexer{Data: data}
+ easyjson9b8f5552Decode20242FIGHTCLUBDomain1(&r, v)
+ return r.Error()
+}
+
+// UnmarshalEasyJSON supports easyjson.Unmarshaler interface
+func (v *Chat) UnmarshalEasyJSON(l *jlexer.Lexer) {
+ easyjson9b8f5552Decode20242FIGHTCLUBDomain1(l, v)
+}
+func easyjson9b8f5552Decode20242FIGHTCLUBDomain2(in *jlexer.Lexer, out *AllMessages) {
+ isTopLevel := in.IsStart()
+ if in.IsNull() {
+ if isTopLevel {
+ in.Consumed()
+ }
+ in.Skip()
+ return
+ }
+ in.Delim('{')
+ for !in.IsDelim('}') {
+ key := in.UnsafeFieldName(false)
+ in.WantColon()
+ if in.IsNull() {
+ in.Skip()
+ in.WantComma()
+ continue
+ }
+ switch key {
+ case "chat":
+ if in.IsNull() {
+ in.Skip()
+ out.Chat = nil
+ } else {
+ in.Delim('[')
+ if out.Chat == nil {
+ if !in.IsDelim(']') {
+ out.Chat = make([]*Message, 0, 8)
+ } else {
+ out.Chat = []*Message{}
+ }
+ } else {
+ out.Chat = (out.Chat)[:0]
+ }
+ for !in.IsDelim(']') {
+ var v1 *Message
+ if in.IsNull() {
+ in.Skip()
+ v1 = nil
+ } else {
+ if v1 == nil {
+ v1 = new(Message)
+ }
+ (*v1).UnmarshalEasyJSON(in)
+ }
+ out.Chat = append(out.Chat, v1)
+ in.WantComma()
+ }
+ in.Delim(']')
+ }
+ default:
+ in.SkipRecursive()
+ }
+ in.WantComma()
+ }
+ in.Delim('}')
+ if isTopLevel {
+ in.Consumed()
+ }
+}
+func easyjson9b8f5552Encode20242FIGHTCLUBDomain2(out *jwriter.Writer, in AllMessages) {
+ out.RawByte('{')
+ first := true
+ _ = first
+ {
+ const prefix string = ",\"chat\":"
+ out.RawString(prefix[1:])
+ if in.Chat == nil && (out.Flags&jwriter.NilSliceAsEmpty) == 0 {
+ out.RawString("null")
+ } else {
+ out.RawByte('[')
+ for v2, v3 := range in.Chat {
+ if v2 > 0 {
+ out.RawByte(',')
+ }
+ if v3 == nil {
+ out.RawString("null")
+ } else {
+ (*v3).MarshalEasyJSON(out)
+ }
+ }
+ out.RawByte(']')
+ }
+ }
+ out.RawByte('}')
+}
+
+// MarshalJSON supports json.Marshaler interface
+func (v AllMessages) MarshalJSON() ([]byte, error) {
+ w := jwriter.Writer{}
+ easyjson9b8f5552Encode20242FIGHTCLUBDomain2(&w, v)
+ return w.Buffer.BuildBytes(), w.Error
+}
+
+// MarshalEasyJSON supports easyjson.Marshaler interface
+func (v AllMessages) MarshalEasyJSON(w *jwriter.Writer) {
+ easyjson9b8f5552Encode20242FIGHTCLUBDomain2(w, v)
+}
+
+// UnmarshalJSON supports json.Unmarshaler interface
+func (v *AllMessages) UnmarshalJSON(data []byte) error {
+ r := jlexer.Lexer{Data: data}
+ easyjson9b8f5552Decode20242FIGHTCLUBDomain2(&r, v)
+ return r.Error()
+}
+
+// UnmarshalEasyJSON supports easyjson.Unmarshaler interface
+func (v *AllMessages) UnmarshalEasyJSON(l *jlexer.Lexer) {
+ easyjson9b8f5552Decode20242FIGHTCLUBDomain2(l, v)
+}
+func easyjson9b8f5552Decode20242FIGHTCLUBDomain3(in *jlexer.Lexer, out *AllChats) {
+ isTopLevel := in.IsStart()
+ if in.IsNull() {
+ if isTopLevel {
+ in.Consumed()
+ }
+ in.Skip()
+ return
+ }
+ in.Delim('{')
+ for !in.IsDelim('}') {
+ key := in.UnsafeFieldName(false)
+ in.WantColon()
+ if in.IsNull() {
+ in.Skip()
+ in.WantComma()
+ continue
+ }
+ switch key {
+ case "chats":
+ if in.IsNull() {
+ in.Skip()
+ out.Chats = nil
+ } else {
+ in.Delim('[')
+ if out.Chats == nil {
+ if !in.IsDelim(']') {
+ out.Chats = make([]*Chat, 0, 8)
+ } else {
+ out.Chats = []*Chat{}
+ }
+ } else {
+ out.Chats = (out.Chats)[:0]
+ }
+ for !in.IsDelim(']') {
+ var v4 *Chat
+ if in.IsNull() {
+ in.Skip()
+ v4 = nil
+ } else {
+ if v4 == nil {
+ v4 = new(Chat)
+ }
+ (*v4).UnmarshalEasyJSON(in)
+ }
+ out.Chats = append(out.Chats, v4)
+ in.WantComma()
+ }
+ in.Delim(']')
+ }
+ default:
+ in.SkipRecursive()
+ }
+ in.WantComma()
+ }
+ in.Delim('}')
+ if isTopLevel {
+ in.Consumed()
+ }
+}
+func easyjson9b8f5552Encode20242FIGHTCLUBDomain3(out *jwriter.Writer, in AllChats) {
+ out.RawByte('{')
+ first := true
+ _ = first
+ {
+ const prefix string = ",\"chats\":"
+ out.RawString(prefix[1:])
+ if in.Chats == nil && (out.Flags&jwriter.NilSliceAsEmpty) == 0 {
+ out.RawString("null")
+ } else {
+ out.RawByte('[')
+ for v5, v6 := range in.Chats {
+ if v5 > 0 {
+ out.RawByte(',')
+ }
+ if v6 == nil {
+ out.RawString("null")
+ } else {
+ (*v6).MarshalEasyJSON(out)
+ }
+ }
+ out.RawByte(']')
+ }
+ }
+ out.RawByte('}')
+}
+
+// MarshalJSON supports json.Marshaler interface
+func (v AllChats) MarshalJSON() ([]byte, error) {
+ w := jwriter.Writer{}
+ easyjson9b8f5552Encode20242FIGHTCLUBDomain3(&w, v)
+ return w.Buffer.BuildBytes(), w.Error
+}
+
+// MarshalEasyJSON supports easyjson.Marshaler interface
+func (v AllChats) MarshalEasyJSON(w *jwriter.Writer) {
+ easyjson9b8f5552Encode20242FIGHTCLUBDomain3(w, v)
+}
+
+// UnmarshalJSON supports json.Unmarshaler interface
+func (v *AllChats) UnmarshalJSON(data []byte) error {
+ r := jlexer.Lexer{Data: data}
+ easyjson9b8f5552Decode20242FIGHTCLUBDomain3(&r, v)
+ return r.Error()
+}
+
+// UnmarshalEasyJSON supports easyjson.Unmarshaler interface
+func (v *AllChats) UnmarshalEasyJSON(l *jlexer.Lexer) {
+ easyjson9b8f5552Decode20242FIGHTCLUBDomain3(l, v)
+}
diff --git a/domain/city.go b/domain/city.go
index 6dea2f1..45aa252 100644
--- a/domain/city.go
+++ b/domain/city.go
@@ -1,7 +1,20 @@
package domain
+//go:generate easyjson -all city.go
+
import "context"
+//easyjson:json
+type OneCityResponse struct {
+ City City `json:"city"`
+}
+
+//easyjson:json
+type AllCitiesResponse struct {
+ Cities []*City `json:"cities"`
+}
+
+//easyjson:json
type City struct {
ID int `gorm:"primary_key;auto_increment;column:id" json:"id"`
Title string `gorm:"type:varchar(100);column:title" json:"title"`
diff --git a/domain/city_easyjson.go b/domain/city_easyjson.go
new file mode 100644
index 0000000..82322b9
--- /dev/null
+++ b/domain/city_easyjson.go
@@ -0,0 +1,289 @@
+// Code generated by easyjson for marshaling/unmarshaling. DO NOT EDIT.
+
+package domain
+
+import (
+ json "encoding/json"
+ easyjson "github.com/mailru/easyjson"
+ jlexer "github.com/mailru/easyjson/jlexer"
+ jwriter "github.com/mailru/easyjson/jwriter"
+)
+
+// suppress unused package warning
+var (
+ _ *json.RawMessage
+ _ *jlexer.Lexer
+ _ *jwriter.Writer
+ _ easyjson.Marshaler
+)
+
+func easyjson66d84ff1Decode20242FIGHTCLUBDomain(in *jlexer.Lexer, out *OneCityResponse) {
+ isTopLevel := in.IsStart()
+ if in.IsNull() {
+ if isTopLevel {
+ in.Consumed()
+ }
+ in.Skip()
+ return
+ }
+ in.Delim('{')
+ for !in.IsDelim('}') {
+ key := in.UnsafeFieldName(false)
+ in.WantColon()
+ if in.IsNull() {
+ in.Skip()
+ in.WantComma()
+ continue
+ }
+ switch key {
+ case "city":
+ (out.City).UnmarshalEasyJSON(in)
+ default:
+ in.SkipRecursive()
+ }
+ in.WantComma()
+ }
+ in.Delim('}')
+ if isTopLevel {
+ in.Consumed()
+ }
+}
+func easyjson66d84ff1Encode20242FIGHTCLUBDomain(out *jwriter.Writer, in OneCityResponse) {
+ out.RawByte('{')
+ first := true
+ _ = first
+ {
+ const prefix string = ",\"city\":"
+ out.RawString(prefix[1:])
+ (in.City).MarshalEasyJSON(out)
+ }
+ out.RawByte('}')
+}
+
+// MarshalJSON supports json.Marshaler interface
+func (v OneCityResponse) MarshalJSON() ([]byte, error) {
+ w := jwriter.Writer{}
+ easyjson66d84ff1Encode20242FIGHTCLUBDomain(&w, v)
+ return w.Buffer.BuildBytes(), w.Error
+}
+
+// MarshalEasyJSON supports easyjson.Marshaler interface
+func (v OneCityResponse) MarshalEasyJSON(w *jwriter.Writer) {
+ easyjson66d84ff1Encode20242FIGHTCLUBDomain(w, v)
+}
+
+// UnmarshalJSON supports json.Unmarshaler interface
+func (v *OneCityResponse) UnmarshalJSON(data []byte) error {
+ r := jlexer.Lexer{Data: data}
+ easyjson66d84ff1Decode20242FIGHTCLUBDomain(&r, v)
+ return r.Error()
+}
+
+// UnmarshalEasyJSON supports easyjson.Unmarshaler interface
+func (v *OneCityResponse) UnmarshalEasyJSON(l *jlexer.Lexer) {
+ easyjson66d84ff1Decode20242FIGHTCLUBDomain(l, v)
+}
+func easyjson66d84ff1Decode20242FIGHTCLUBDomain1(in *jlexer.Lexer, out *City) {
+ isTopLevel := in.IsStart()
+ if in.IsNull() {
+ if isTopLevel {
+ in.Consumed()
+ }
+ in.Skip()
+ return
+ }
+ in.Delim('{')
+ for !in.IsDelim('}') {
+ key := in.UnsafeFieldName(false)
+ in.WantColon()
+ if in.IsNull() {
+ in.Skip()
+ in.WantComma()
+ continue
+ }
+ switch key {
+ case "id":
+ out.ID = int(in.Int())
+ case "title":
+ out.Title = string(in.String())
+ case "enTitle":
+ out.EnTitle = string(in.String())
+ case "description":
+ out.Description = string(in.String())
+ case "image":
+ out.Image = string(in.String())
+ default:
+ in.SkipRecursive()
+ }
+ in.WantComma()
+ }
+ in.Delim('}')
+ if isTopLevel {
+ in.Consumed()
+ }
+}
+func easyjson66d84ff1Encode20242FIGHTCLUBDomain1(out *jwriter.Writer, in City) {
+ out.RawByte('{')
+ first := true
+ _ = first
+ {
+ const prefix string = ",\"id\":"
+ out.RawString(prefix[1:])
+ out.Int(int(in.ID))
+ }
+ {
+ const prefix string = ",\"title\":"
+ out.RawString(prefix)
+ out.String(string(in.Title))
+ }
+ {
+ const prefix string = ",\"enTitle\":"
+ out.RawString(prefix)
+ out.String(string(in.EnTitle))
+ }
+ {
+ const prefix string = ",\"description\":"
+ out.RawString(prefix)
+ out.String(string(in.Description))
+ }
+ {
+ const prefix string = ",\"image\":"
+ out.RawString(prefix)
+ out.String(string(in.Image))
+ }
+ out.RawByte('}')
+}
+
+// MarshalJSON supports json.Marshaler interface
+func (v City) MarshalJSON() ([]byte, error) {
+ w := jwriter.Writer{}
+ easyjson66d84ff1Encode20242FIGHTCLUBDomain1(&w, v)
+ return w.Buffer.BuildBytes(), w.Error
+}
+
+// MarshalEasyJSON supports easyjson.Marshaler interface
+func (v City) MarshalEasyJSON(w *jwriter.Writer) {
+ easyjson66d84ff1Encode20242FIGHTCLUBDomain1(w, v)
+}
+
+// UnmarshalJSON supports json.Unmarshaler interface
+func (v *City) UnmarshalJSON(data []byte) error {
+ r := jlexer.Lexer{Data: data}
+ easyjson66d84ff1Decode20242FIGHTCLUBDomain1(&r, v)
+ return r.Error()
+}
+
+// UnmarshalEasyJSON supports easyjson.Unmarshaler interface
+func (v *City) UnmarshalEasyJSON(l *jlexer.Lexer) {
+ easyjson66d84ff1Decode20242FIGHTCLUBDomain1(l, v)
+}
+func easyjson66d84ff1Decode20242FIGHTCLUBDomain2(in *jlexer.Lexer, out *AllCitiesResponse) {
+ isTopLevel := in.IsStart()
+ if in.IsNull() {
+ if isTopLevel {
+ in.Consumed()
+ }
+ in.Skip()
+ return
+ }
+ in.Delim('{')
+ for !in.IsDelim('}') {
+ key := in.UnsafeFieldName(false)
+ in.WantColon()
+ if in.IsNull() {
+ in.Skip()
+ in.WantComma()
+ continue
+ }
+ switch key {
+ case "cities":
+ if in.IsNull() {
+ in.Skip()
+ out.Cities = nil
+ } else {
+ in.Delim('[')
+ if out.Cities == nil {
+ if !in.IsDelim(']') {
+ out.Cities = make([]*City, 0, 8)
+ } else {
+ out.Cities = []*City{}
+ }
+ } else {
+ out.Cities = (out.Cities)[:0]
+ }
+ for !in.IsDelim(']') {
+ var v1 *City
+ if in.IsNull() {
+ in.Skip()
+ v1 = nil
+ } else {
+ if v1 == nil {
+ v1 = new(City)
+ }
+ (*v1).UnmarshalEasyJSON(in)
+ }
+ out.Cities = append(out.Cities, v1)
+ in.WantComma()
+ }
+ in.Delim(']')
+ }
+ default:
+ in.SkipRecursive()
+ }
+ in.WantComma()
+ }
+ in.Delim('}')
+ if isTopLevel {
+ in.Consumed()
+ }
+}
+func easyjson66d84ff1Encode20242FIGHTCLUBDomain2(out *jwriter.Writer, in AllCitiesResponse) {
+ out.RawByte('{')
+ first := true
+ _ = first
+ {
+ const prefix string = ",\"cities\":"
+ out.RawString(prefix[1:])
+ if in.Cities == nil && (out.Flags&jwriter.NilSliceAsEmpty) == 0 {
+ out.RawString("null")
+ } else {
+ out.RawByte('[')
+ for v2, v3 := range in.Cities {
+ if v2 > 0 {
+ out.RawByte(',')
+ }
+ if v3 == nil {
+ out.RawString("null")
+ } else {
+ (*v3).MarshalEasyJSON(out)
+ }
+ }
+ out.RawByte(']')
+ }
+ }
+ out.RawByte('}')
+}
+
+// MarshalJSON supports json.Marshaler interface
+func (v AllCitiesResponse) MarshalJSON() ([]byte, error) {
+ w := jwriter.Writer{}
+ easyjson66d84ff1Encode20242FIGHTCLUBDomain2(&w, v)
+ return w.Buffer.BuildBytes(), w.Error
+}
+
+// MarshalEasyJSON supports easyjson.Marshaler interface
+func (v AllCitiesResponse) MarshalEasyJSON(w *jwriter.Writer) {
+ easyjson66d84ff1Encode20242FIGHTCLUBDomain2(w, v)
+}
+
+// UnmarshalJSON supports json.Unmarshaler interface
+func (v *AllCitiesResponse) UnmarshalJSON(data []byte) error {
+ r := jlexer.Lexer{Data: data}
+ easyjson66d84ff1Decode20242FIGHTCLUBDomain2(&r, v)
+ return r.Error()
+}
+
+// UnmarshalEasyJSON supports easyjson.Unmarshaler interface
+func (v *AllCitiesResponse) UnmarshalEasyJSON(l *jlexer.Lexer) {
+ easyjson66d84ff1Decode20242FIGHTCLUBDomain2(l, v)
+}
diff --git a/domain/regions.go b/domain/regions.go
new file mode 100644
index 0000000..6f6cda2
--- /dev/null
+++ b/domain/regions.go
@@ -0,0 +1,24 @@
+package domain
+
+//go:generate easyjson -all regions.go
+
+import (
+ "context"
+ "time"
+)
+
+//easyjson:json
+type VisitedRegionsList []VisitedRegions
+
+type VisitedRegions struct {
+ ID int `gorm:"primary_key;auto_increment;column:id" json:"id"`
+ Name string `gorm:"text;size:1000;column:name" json:"name"`
+ UserID string `gorm:"column:userId;not null" json:"userId"`
+ StartVisitDate time.Time `gorm:"type:timestamp;column:startVisitDate;default:CURRENT_TIMESTAMP" json:"startVisitDate"`
+ EndVisitDate time.Time `gorm:"type:timestamp;column:endVisitDate;default:CURRENT_TIMESTAMP" json:"endVisitDate"`
+ User User `gorm:"foreignKey:UserID;references:UUID" json:"-"`
+}
+
+type RegionRepository interface {
+ GetVisitedRegions(ctx context.Context, userId string) ([]VisitedRegions, error)
+}
diff --git a/domain/regions_easyjson.go b/domain/regions_easyjson.go
new file mode 100644
index 0000000..749a569
--- /dev/null
+++ b/domain/regions_easyjson.go
@@ -0,0 +1,183 @@
+// Code generated by easyjson for marshaling/unmarshaling. DO NOT EDIT.
+
+package domain
+
+import (
+ json "encoding/json"
+ easyjson "github.com/mailru/easyjson"
+ jlexer "github.com/mailru/easyjson/jlexer"
+ jwriter "github.com/mailru/easyjson/jwriter"
+)
+
+// suppress unused package warning
+var (
+ _ *json.RawMessage
+ _ *jlexer.Lexer
+ _ *jwriter.Writer
+ _ easyjson.Marshaler
+)
+
+func easyjson896ef83dDecode20242FIGHTCLUBDomain(in *jlexer.Lexer, out *VisitedRegionsList) {
+ isTopLevel := in.IsStart()
+ if in.IsNull() {
+ in.Skip()
+ *out = nil
+ } else {
+ in.Delim('[')
+ if *out == nil {
+ if !in.IsDelim(']') {
+ *out = make(VisitedRegionsList, 0, 0)
+ } else {
+ *out = VisitedRegionsList{}
+ }
+ } else {
+ *out = (*out)[:0]
+ }
+ for !in.IsDelim(']') {
+ var v1 VisitedRegions
+ (v1).UnmarshalEasyJSON(in)
+ *out = append(*out, v1)
+ in.WantComma()
+ }
+ in.Delim(']')
+ }
+ if isTopLevel {
+ in.Consumed()
+ }
+}
+func easyjson896ef83dEncode20242FIGHTCLUBDomain(out *jwriter.Writer, in VisitedRegionsList) {
+ if in == nil && (out.Flags&jwriter.NilSliceAsEmpty) == 0 {
+ out.RawString("null")
+ } else {
+ out.RawByte('[')
+ for v2, v3 := range in {
+ if v2 > 0 {
+ out.RawByte(',')
+ }
+ (v3).MarshalEasyJSON(out)
+ }
+ out.RawByte(']')
+ }
+}
+
+// MarshalJSON supports json.Marshaler interface
+func (v VisitedRegionsList) MarshalJSON() ([]byte, error) {
+ w := jwriter.Writer{}
+ easyjson896ef83dEncode20242FIGHTCLUBDomain(&w, v)
+ return w.Buffer.BuildBytes(), w.Error
+}
+
+// MarshalEasyJSON supports easyjson.Marshaler interface
+func (v VisitedRegionsList) MarshalEasyJSON(w *jwriter.Writer) {
+ easyjson896ef83dEncode20242FIGHTCLUBDomain(w, v)
+}
+
+// UnmarshalJSON supports json.Unmarshaler interface
+func (v *VisitedRegionsList) UnmarshalJSON(data []byte) error {
+ r := jlexer.Lexer{Data: data}
+ easyjson896ef83dDecode20242FIGHTCLUBDomain(&r, v)
+ return r.Error()
+}
+
+// UnmarshalEasyJSON supports easyjson.Unmarshaler interface
+func (v *VisitedRegionsList) UnmarshalEasyJSON(l *jlexer.Lexer) {
+ easyjson896ef83dDecode20242FIGHTCLUBDomain(l, v)
+}
+func easyjson896ef83dDecode20242FIGHTCLUBDomain1(in *jlexer.Lexer, out *VisitedRegions) {
+ isTopLevel := in.IsStart()
+ if in.IsNull() {
+ if isTopLevel {
+ in.Consumed()
+ }
+ in.Skip()
+ return
+ }
+ in.Delim('{')
+ for !in.IsDelim('}') {
+ key := in.UnsafeFieldName(false)
+ in.WantColon()
+ if in.IsNull() {
+ in.Skip()
+ in.WantComma()
+ continue
+ }
+ switch key {
+ case "id":
+ out.ID = int(in.Int())
+ case "name":
+ out.Name = string(in.String())
+ case "userId":
+ out.UserID = string(in.String())
+ case "startVisitDate":
+ if data := in.Raw(); in.Ok() {
+ in.AddError((out.StartVisitDate).UnmarshalJSON(data))
+ }
+ case "endVisitDate":
+ if data := in.Raw(); in.Ok() {
+ in.AddError((out.EndVisitDate).UnmarshalJSON(data))
+ }
+ default:
+ in.SkipRecursive()
+ }
+ in.WantComma()
+ }
+ in.Delim('}')
+ if isTopLevel {
+ in.Consumed()
+ }
+}
+func easyjson896ef83dEncode20242FIGHTCLUBDomain1(out *jwriter.Writer, in VisitedRegions) {
+ out.RawByte('{')
+ first := true
+ _ = first
+ {
+ const prefix string = ",\"id\":"
+ out.RawString(prefix[1:])
+ out.Int(int(in.ID))
+ }
+ {
+ const prefix string = ",\"name\":"
+ out.RawString(prefix)
+ out.String(string(in.Name))
+ }
+ {
+ const prefix string = ",\"userId\":"
+ out.RawString(prefix)
+ out.String(string(in.UserID))
+ }
+ {
+ const prefix string = ",\"startVisitDate\":"
+ out.RawString(prefix)
+ out.Raw((in.StartVisitDate).MarshalJSON())
+ }
+ {
+ const prefix string = ",\"endVisitDate\":"
+ out.RawString(prefix)
+ out.Raw((in.EndVisitDate).MarshalJSON())
+ }
+ out.RawByte('}')
+}
+
+// MarshalJSON supports json.Marshaler interface
+func (v VisitedRegions) MarshalJSON() ([]byte, error) {
+ w := jwriter.Writer{}
+ easyjson896ef83dEncode20242FIGHTCLUBDomain1(&w, v)
+ return w.Buffer.BuildBytes(), w.Error
+}
+
+// MarshalEasyJSON supports easyjson.Marshaler interface
+func (v VisitedRegions) MarshalEasyJSON(w *jwriter.Writer) {
+ easyjson896ef83dEncode20242FIGHTCLUBDomain1(w, v)
+}
+
+// UnmarshalJSON supports json.Unmarshaler interface
+func (v *VisitedRegions) UnmarshalJSON(data []byte) error {
+ r := jlexer.Lexer{Data: data}
+ easyjson896ef83dDecode20242FIGHTCLUBDomain1(&r, v)
+ return r.Error()
+}
+
+// UnmarshalEasyJSON supports easyjson.Unmarshaler interface
+func (v *VisitedRegions) UnmarshalEasyJSON(l *jlexer.Lexer) {
+ easyjson896ef83dDecode20242FIGHTCLUBDomain1(l, v)
+}
diff --git a/domain/request.go b/domain/request.go
deleted file mode 100644
index 6575705..0000000
--- a/domain/request.go
+++ /dev/null
@@ -1,15 +0,0 @@
-package domain
-
-import "time"
-
-type Request struct {
- ID int `gorm:"primary_key;auto_increment;column:id" json:"id"`
- AdID string `gorm:"column:adId;not null" json:"adId"`
- UserID string `gorm:"column:userId;not null" json:"userId"`
- Status string `gorm:"column:status;not null;default:pending" json:"status"`
- CreatedDate time.Time `gorm:"type:timestamp;column:createDate;default:CURRENT_TIMESTAMP" json:"createDate"`
- UpdateDate time.Time `gorm:"type:timestamp;column:updateDate" json:"updateDate"`
- CloseDate time.Time `gorm:"type:timestamp;column:closeDate" json:"closeDate"`
- User User `gorm:"foreignkey:UserID;references:UUID"`
- Ad Ad `gorm:"foreignkey:AdID;references:UUID"`
-}
diff --git a/domain/response.go b/domain/response.go
new file mode 100644
index 0000000..1f3dfd0
--- /dev/null
+++ b/domain/response.go
@@ -0,0 +1,19 @@
+package domain
+
+//go:generate easyjson -all response.go
+
+//easyjson:json
+type ErrorResponse struct {
+ Error string `json:"error"`
+}
+
+//easyjson:json
+type ResponseMessage struct {
+ Message string `json:"message"`
+}
+
+//easyjson:json
+type WrongFieldErrorResponse struct {
+ Error string `json:"error"`
+ WrongFields []string `json:"wrongFields"`
+}
diff --git a/domain/response_easyjson.go b/domain/response_easyjson.go
new file mode 100644
index 0000000..8fd2cbe
--- /dev/null
+++ b/domain/response_easyjson.go
@@ -0,0 +1,256 @@
+// Code generated by easyjson for marshaling/unmarshaling. DO NOT EDIT.
+
+package domain
+
+import (
+ json "encoding/json"
+ easyjson "github.com/mailru/easyjson"
+ jlexer "github.com/mailru/easyjson/jlexer"
+ jwriter "github.com/mailru/easyjson/jwriter"
+)
+
+// suppress unused package warning
+var (
+ _ *json.RawMessage
+ _ *jlexer.Lexer
+ _ *jwriter.Writer
+ _ easyjson.Marshaler
+)
+
+func easyjson6ff3ac1dDecode20242FIGHTCLUBDomain(in *jlexer.Lexer, out *WrongFieldErrorResponse) {
+ isTopLevel := in.IsStart()
+ if in.IsNull() {
+ if isTopLevel {
+ in.Consumed()
+ }
+ in.Skip()
+ return
+ }
+ in.Delim('{')
+ for !in.IsDelim('}') {
+ key := in.UnsafeFieldName(false)
+ in.WantColon()
+ if in.IsNull() {
+ in.Skip()
+ in.WantComma()
+ continue
+ }
+ switch key {
+ case "error":
+ out.Error = string(in.String())
+ case "wrongFields":
+ if in.IsNull() {
+ in.Skip()
+ out.WrongFields = nil
+ } else {
+ in.Delim('[')
+ if out.WrongFields == nil {
+ if !in.IsDelim(']') {
+ out.WrongFields = make([]string, 0, 4)
+ } else {
+ out.WrongFields = []string{}
+ }
+ } else {
+ out.WrongFields = (out.WrongFields)[:0]
+ }
+ for !in.IsDelim(']') {
+ var v1 string
+ v1 = string(in.String())
+ out.WrongFields = append(out.WrongFields, v1)
+ in.WantComma()
+ }
+ in.Delim(']')
+ }
+ default:
+ in.SkipRecursive()
+ }
+ in.WantComma()
+ }
+ in.Delim('}')
+ if isTopLevel {
+ in.Consumed()
+ }
+}
+func easyjson6ff3ac1dEncode20242FIGHTCLUBDomain(out *jwriter.Writer, in WrongFieldErrorResponse) {
+ out.RawByte('{')
+ first := true
+ _ = first
+ {
+ const prefix string = ",\"error\":"
+ out.RawString(prefix[1:])
+ out.String(string(in.Error))
+ }
+ {
+ const prefix string = ",\"wrongFields\":"
+ out.RawString(prefix)
+ if in.WrongFields == nil && (out.Flags&jwriter.NilSliceAsEmpty) == 0 {
+ out.RawString("null")
+ } else {
+ out.RawByte('[')
+ for v2, v3 := range in.WrongFields {
+ if v2 > 0 {
+ out.RawByte(',')
+ }
+ out.String(string(v3))
+ }
+ out.RawByte(']')
+ }
+ }
+ out.RawByte('}')
+}
+
+// MarshalJSON supports json.Marshaler interface
+func (v WrongFieldErrorResponse) MarshalJSON() ([]byte, error) {
+ w := jwriter.Writer{}
+ easyjson6ff3ac1dEncode20242FIGHTCLUBDomain(&w, v)
+ return w.Buffer.BuildBytes(), w.Error
+}
+
+// MarshalEasyJSON supports easyjson.Marshaler interface
+func (v WrongFieldErrorResponse) MarshalEasyJSON(w *jwriter.Writer) {
+ easyjson6ff3ac1dEncode20242FIGHTCLUBDomain(w, v)
+}
+
+// UnmarshalJSON supports json.Unmarshaler interface
+func (v *WrongFieldErrorResponse) UnmarshalJSON(data []byte) error {
+ r := jlexer.Lexer{Data: data}
+ easyjson6ff3ac1dDecode20242FIGHTCLUBDomain(&r, v)
+ return r.Error()
+}
+
+// UnmarshalEasyJSON supports easyjson.Unmarshaler interface
+func (v *WrongFieldErrorResponse) UnmarshalEasyJSON(l *jlexer.Lexer) {
+ easyjson6ff3ac1dDecode20242FIGHTCLUBDomain(l, v)
+}
+func easyjson6ff3ac1dDecode20242FIGHTCLUBDomain1(in *jlexer.Lexer, out *ResponseMessage) {
+ isTopLevel := in.IsStart()
+ if in.IsNull() {
+ if isTopLevel {
+ in.Consumed()
+ }
+ in.Skip()
+ return
+ }
+ in.Delim('{')
+ for !in.IsDelim('}') {
+ key := in.UnsafeFieldName(false)
+ in.WantColon()
+ if in.IsNull() {
+ in.Skip()
+ in.WantComma()
+ continue
+ }
+ switch key {
+ case "message":
+ out.Message = string(in.String())
+ default:
+ in.SkipRecursive()
+ }
+ in.WantComma()
+ }
+ in.Delim('}')
+ if isTopLevel {
+ in.Consumed()
+ }
+}
+func easyjson6ff3ac1dEncode20242FIGHTCLUBDomain1(out *jwriter.Writer, in ResponseMessage) {
+ out.RawByte('{')
+ first := true
+ _ = first
+ {
+ const prefix string = ",\"message\":"
+ out.RawString(prefix[1:])
+ out.String(string(in.Message))
+ }
+ out.RawByte('}')
+}
+
+// MarshalJSON supports json.Marshaler interface
+func (v ResponseMessage) MarshalJSON() ([]byte, error) {
+ w := jwriter.Writer{}
+ easyjson6ff3ac1dEncode20242FIGHTCLUBDomain1(&w, v)
+ return w.Buffer.BuildBytes(), w.Error
+}
+
+// MarshalEasyJSON supports easyjson.Marshaler interface
+func (v ResponseMessage) MarshalEasyJSON(w *jwriter.Writer) {
+ easyjson6ff3ac1dEncode20242FIGHTCLUBDomain1(w, v)
+}
+
+// UnmarshalJSON supports json.Unmarshaler interface
+func (v *ResponseMessage) UnmarshalJSON(data []byte) error {
+ r := jlexer.Lexer{Data: data}
+ easyjson6ff3ac1dDecode20242FIGHTCLUBDomain1(&r, v)
+ return r.Error()
+}
+
+// UnmarshalEasyJSON supports easyjson.Unmarshaler interface
+func (v *ResponseMessage) UnmarshalEasyJSON(l *jlexer.Lexer) {
+ easyjson6ff3ac1dDecode20242FIGHTCLUBDomain1(l, v)
+}
+func easyjson6ff3ac1dDecode20242FIGHTCLUBDomain2(in *jlexer.Lexer, out *ErrorResponse) {
+ isTopLevel := in.IsStart()
+ if in.IsNull() {
+ if isTopLevel {
+ in.Consumed()
+ }
+ in.Skip()
+ return
+ }
+ in.Delim('{')
+ for !in.IsDelim('}') {
+ key := in.UnsafeFieldName(false)
+ in.WantColon()
+ if in.IsNull() {
+ in.Skip()
+ in.WantComma()
+ continue
+ }
+ switch key {
+ case "error":
+ out.Error = string(in.String())
+ default:
+ in.SkipRecursive()
+ }
+ in.WantComma()
+ }
+ in.Delim('}')
+ if isTopLevel {
+ in.Consumed()
+ }
+}
+func easyjson6ff3ac1dEncode20242FIGHTCLUBDomain2(out *jwriter.Writer, in ErrorResponse) {
+ out.RawByte('{')
+ first := true
+ _ = first
+ {
+ const prefix string = ",\"error\":"
+ out.RawString(prefix[1:])
+ out.String(string(in.Error))
+ }
+ out.RawByte('}')
+}
+
+// MarshalJSON supports json.Marshaler interface
+func (v ErrorResponse) MarshalJSON() ([]byte, error) {
+ w := jwriter.Writer{}
+ easyjson6ff3ac1dEncode20242FIGHTCLUBDomain2(&w, v)
+ return w.Buffer.BuildBytes(), w.Error
+}
+
+// MarshalEasyJSON supports easyjson.Marshaler interface
+func (v ErrorResponse) MarshalEasyJSON(w *jwriter.Writer) {
+ easyjson6ff3ac1dEncode20242FIGHTCLUBDomain2(w, v)
+}
+
+// UnmarshalJSON supports json.Unmarshaler interface
+func (v *ErrorResponse) UnmarshalJSON(data []byte) error {
+ r := jlexer.Lexer{Data: data}
+ easyjson6ff3ac1dDecode20242FIGHTCLUBDomain2(&r, v)
+ return r.Error()
+}
+
+// UnmarshalEasyJSON supports easyjson.Unmarshaler interface
+func (v *ErrorResponse) UnmarshalEasyJSON(l *jlexer.Lexer) {
+ easyjson6ff3ac1dDecode20242FIGHTCLUBDomain2(l, v)
+}
diff --git a/domain/review.go b/domain/review.go
index 160edd9..6164f9f 100644
--- a/domain/review.go
+++ b/domain/review.go
@@ -1,10 +1,21 @@
package domain
+//go:generate easyjson -all review.go
+
import (
"context"
"time"
)
+//easyjson:json
+type ReviewBody struct {
+ Review Review `json:"review"`
+}
+
+//easyjson:json
+type UserReviewsList []UserReviews
+
+//easyjson:json
type Review struct {
ID int `gorm:"primary_key;auto_increment;column:id" json:"id"`
UserID string `gorm:"column:userId;not null" json:"userId"`
@@ -17,6 +28,7 @@ type Review struct {
Host User `gorm:"foreignkey:HostID;references:UUID" json:"-"`
}
+//easyjson:json
type UserReviews struct {
ID int `gorm:"primary_key;auto_increment;column:id" json:"id"`
UserID string `gorm:"column:userId;not null" json:"userId"`
diff --git a/domain/review_easyjson.go b/domain/review_easyjson.go
new file mode 100644
index 0000000..8bb4cdf
--- /dev/null
+++ b/domain/review_easyjson.go
@@ -0,0 +1,385 @@
+// Code generated by easyjson for marshaling/unmarshaling. DO NOT EDIT.
+
+package domain
+
+import (
+ json "encoding/json"
+ easyjson "github.com/mailru/easyjson"
+ jlexer "github.com/mailru/easyjson/jlexer"
+ jwriter "github.com/mailru/easyjson/jwriter"
+)
+
+// suppress unused package warning
+var (
+ _ *json.RawMessage
+ _ *jlexer.Lexer
+ _ *jwriter.Writer
+ _ easyjson.Marshaler
+)
+
+func easyjson2f096870Decode20242FIGHTCLUBDomain(in *jlexer.Lexer, out *UserReviewsList) {
+ isTopLevel := in.IsStart()
+ if in.IsNull() {
+ in.Skip()
+ *out = nil
+ } else {
+ in.Delim('[')
+ if *out == nil {
+ if !in.IsDelim(']') {
+ *out = make(UserReviewsList, 0, 0)
+ } else {
+ *out = UserReviewsList{}
+ }
+ } else {
+ *out = (*out)[:0]
+ }
+ for !in.IsDelim(']') {
+ var v1 UserReviews
+ (v1).UnmarshalEasyJSON(in)
+ *out = append(*out, v1)
+ in.WantComma()
+ }
+ in.Delim(']')
+ }
+ if isTopLevel {
+ in.Consumed()
+ }
+}
+func easyjson2f096870Encode20242FIGHTCLUBDomain(out *jwriter.Writer, in UserReviewsList) {
+ if in == nil && (out.Flags&jwriter.NilSliceAsEmpty) == 0 {
+ out.RawString("null")
+ } else {
+ out.RawByte('[')
+ for v2, v3 := range in {
+ if v2 > 0 {
+ out.RawByte(',')
+ }
+ (v3).MarshalEasyJSON(out)
+ }
+ out.RawByte(']')
+ }
+}
+
+// MarshalJSON supports json.Marshaler interface
+func (v UserReviewsList) MarshalJSON() ([]byte, error) {
+ w := jwriter.Writer{}
+ easyjson2f096870Encode20242FIGHTCLUBDomain(&w, v)
+ return w.Buffer.BuildBytes(), w.Error
+}
+
+// MarshalEasyJSON supports easyjson.Marshaler interface
+func (v UserReviewsList) MarshalEasyJSON(w *jwriter.Writer) {
+ easyjson2f096870Encode20242FIGHTCLUBDomain(w, v)
+}
+
+// UnmarshalJSON supports json.Unmarshaler interface
+func (v *UserReviewsList) UnmarshalJSON(data []byte) error {
+ r := jlexer.Lexer{Data: data}
+ easyjson2f096870Decode20242FIGHTCLUBDomain(&r, v)
+ return r.Error()
+}
+
+// UnmarshalEasyJSON supports easyjson.Unmarshaler interface
+func (v *UserReviewsList) UnmarshalEasyJSON(l *jlexer.Lexer) {
+ easyjson2f096870Decode20242FIGHTCLUBDomain(l, v)
+}
+func easyjson2f096870Decode20242FIGHTCLUBDomain1(in *jlexer.Lexer, out *UserReviews) {
+ isTopLevel := in.IsStart()
+ if in.IsNull() {
+ if isTopLevel {
+ in.Consumed()
+ }
+ in.Skip()
+ return
+ }
+ in.Delim('{')
+ for !in.IsDelim('}') {
+ key := in.UnsafeFieldName(false)
+ in.WantColon()
+ if in.IsNull() {
+ in.Skip()
+ in.WantComma()
+ continue
+ }
+ switch key {
+ case "id":
+ out.ID = int(in.Int())
+ case "userId":
+ out.UserID = string(in.String())
+ case "hostId":
+ out.HostID = string(in.String())
+ case "title":
+ out.Title = string(in.String())
+ case "text":
+ out.Text = string(in.String())
+ case "rating":
+ out.Rating = int(in.Int())
+ case "createdAt":
+ if data := in.Raw(); in.Ok() {
+ in.AddError((out.CreatedAt).UnmarshalJSON(data))
+ }
+ case "userAvatar":
+ out.UserAvatar = string(in.String())
+ case "userName":
+ out.UserName = string(in.String())
+ default:
+ in.SkipRecursive()
+ }
+ in.WantComma()
+ }
+ in.Delim('}')
+ if isTopLevel {
+ in.Consumed()
+ }
+}
+func easyjson2f096870Encode20242FIGHTCLUBDomain1(out *jwriter.Writer, in UserReviews) {
+ out.RawByte('{')
+ first := true
+ _ = first
+ {
+ const prefix string = ",\"id\":"
+ out.RawString(prefix[1:])
+ out.Int(int(in.ID))
+ }
+ {
+ const prefix string = ",\"userId\":"
+ out.RawString(prefix)
+ out.String(string(in.UserID))
+ }
+ {
+ const prefix string = ",\"hostId\":"
+ out.RawString(prefix)
+ out.String(string(in.HostID))
+ }
+ {
+ const prefix string = ",\"title\":"
+ out.RawString(prefix)
+ out.String(string(in.Title))
+ }
+ {
+ const prefix string = ",\"text\":"
+ out.RawString(prefix)
+ out.String(string(in.Text))
+ }
+ {
+ const prefix string = ",\"rating\":"
+ out.RawString(prefix)
+ out.Int(int(in.Rating))
+ }
+ {
+ const prefix string = ",\"createdAt\":"
+ out.RawString(prefix)
+ out.Raw((in.CreatedAt).MarshalJSON())
+ }
+ {
+ const prefix string = ",\"userAvatar\":"
+ out.RawString(prefix)
+ out.String(string(in.UserAvatar))
+ }
+ {
+ const prefix string = ",\"userName\":"
+ out.RawString(prefix)
+ out.String(string(in.UserName))
+ }
+ out.RawByte('}')
+}
+
+// MarshalJSON supports json.Marshaler interface
+func (v UserReviews) MarshalJSON() ([]byte, error) {
+ w := jwriter.Writer{}
+ easyjson2f096870Encode20242FIGHTCLUBDomain1(&w, v)
+ return w.Buffer.BuildBytes(), w.Error
+}
+
+// MarshalEasyJSON supports easyjson.Marshaler interface
+func (v UserReviews) MarshalEasyJSON(w *jwriter.Writer) {
+ easyjson2f096870Encode20242FIGHTCLUBDomain1(w, v)
+}
+
+// UnmarshalJSON supports json.Unmarshaler interface
+func (v *UserReviews) UnmarshalJSON(data []byte) error {
+ r := jlexer.Lexer{Data: data}
+ easyjson2f096870Decode20242FIGHTCLUBDomain1(&r, v)
+ return r.Error()
+}
+
+// UnmarshalEasyJSON supports easyjson.Unmarshaler interface
+func (v *UserReviews) UnmarshalEasyJSON(l *jlexer.Lexer) {
+ easyjson2f096870Decode20242FIGHTCLUBDomain1(l, v)
+}
+func easyjson2f096870Decode20242FIGHTCLUBDomain2(in *jlexer.Lexer, out *ReviewBody) {
+ isTopLevel := in.IsStart()
+ if in.IsNull() {
+ if isTopLevel {
+ in.Consumed()
+ }
+ in.Skip()
+ return
+ }
+ in.Delim('{')
+ for !in.IsDelim('}') {
+ key := in.UnsafeFieldName(false)
+ in.WantColon()
+ if in.IsNull() {
+ in.Skip()
+ in.WantComma()
+ continue
+ }
+ switch key {
+ case "review":
+ (out.Review).UnmarshalEasyJSON(in)
+ default:
+ in.SkipRecursive()
+ }
+ in.WantComma()
+ }
+ in.Delim('}')
+ if isTopLevel {
+ in.Consumed()
+ }
+}
+func easyjson2f096870Encode20242FIGHTCLUBDomain2(out *jwriter.Writer, in ReviewBody) {
+ out.RawByte('{')
+ first := true
+ _ = first
+ {
+ const prefix string = ",\"review\":"
+ out.RawString(prefix[1:])
+ (in.Review).MarshalEasyJSON(out)
+ }
+ out.RawByte('}')
+}
+
+// MarshalJSON supports json.Marshaler interface
+func (v ReviewBody) MarshalJSON() ([]byte, error) {
+ w := jwriter.Writer{}
+ easyjson2f096870Encode20242FIGHTCLUBDomain2(&w, v)
+ return w.Buffer.BuildBytes(), w.Error
+}
+
+// MarshalEasyJSON supports easyjson.Marshaler interface
+func (v ReviewBody) MarshalEasyJSON(w *jwriter.Writer) {
+ easyjson2f096870Encode20242FIGHTCLUBDomain2(w, v)
+}
+
+// UnmarshalJSON supports json.Unmarshaler interface
+func (v *ReviewBody) UnmarshalJSON(data []byte) error {
+ r := jlexer.Lexer{Data: data}
+ easyjson2f096870Decode20242FIGHTCLUBDomain2(&r, v)
+ return r.Error()
+}
+
+// UnmarshalEasyJSON supports easyjson.Unmarshaler interface
+func (v *ReviewBody) UnmarshalEasyJSON(l *jlexer.Lexer) {
+ easyjson2f096870Decode20242FIGHTCLUBDomain2(l, v)
+}
+func easyjson2f096870Decode20242FIGHTCLUBDomain3(in *jlexer.Lexer, out *Review) {
+ isTopLevel := in.IsStart()
+ if in.IsNull() {
+ if isTopLevel {
+ in.Consumed()
+ }
+ in.Skip()
+ return
+ }
+ in.Delim('{')
+ for !in.IsDelim('}') {
+ key := in.UnsafeFieldName(false)
+ in.WantColon()
+ if in.IsNull() {
+ in.Skip()
+ in.WantComma()
+ continue
+ }
+ switch key {
+ case "id":
+ out.ID = int(in.Int())
+ case "userId":
+ out.UserID = string(in.String())
+ case "hostId":
+ out.HostID = string(in.String())
+ case "title":
+ out.Title = string(in.String())
+ case "text":
+ out.Text = string(in.String())
+ case "rating":
+ out.Rating = int(in.Int())
+ case "createdAt":
+ if data := in.Raw(); in.Ok() {
+ in.AddError((out.CreatedAt).UnmarshalJSON(data))
+ }
+ default:
+ in.SkipRecursive()
+ }
+ in.WantComma()
+ }
+ in.Delim('}')
+ if isTopLevel {
+ in.Consumed()
+ }
+}
+func easyjson2f096870Encode20242FIGHTCLUBDomain3(out *jwriter.Writer, in Review) {
+ out.RawByte('{')
+ first := true
+ _ = first
+ {
+ const prefix string = ",\"id\":"
+ out.RawString(prefix[1:])
+ out.Int(int(in.ID))
+ }
+ {
+ const prefix string = ",\"userId\":"
+ out.RawString(prefix)
+ out.String(string(in.UserID))
+ }
+ {
+ const prefix string = ",\"hostId\":"
+ out.RawString(prefix)
+ out.String(string(in.HostID))
+ }
+ {
+ const prefix string = ",\"title\":"
+ out.RawString(prefix)
+ out.String(string(in.Title))
+ }
+ {
+ const prefix string = ",\"text\":"
+ out.RawString(prefix)
+ out.String(string(in.Text))
+ }
+ {
+ const prefix string = ",\"rating\":"
+ out.RawString(prefix)
+ out.Int(int(in.Rating))
+ }
+ {
+ const prefix string = ",\"createdAt\":"
+ out.RawString(prefix)
+ out.Raw((in.CreatedAt).MarshalJSON())
+ }
+ out.RawByte('}')
+}
+
+// MarshalJSON supports json.Marshaler interface
+func (v Review) MarshalJSON() ([]byte, error) {
+ w := jwriter.Writer{}
+ easyjson2f096870Encode20242FIGHTCLUBDomain3(&w, v)
+ return w.Buffer.BuildBytes(), w.Error
+}
+
+// MarshalEasyJSON supports easyjson.Marshaler interface
+func (v Review) MarshalEasyJSON(w *jwriter.Writer) {
+ easyjson2f096870Encode20242FIGHTCLUBDomain3(w, v)
+}
+
+// UnmarshalJSON supports json.Unmarshaler interface
+func (v *Review) UnmarshalJSON(data []byte) error {
+ r := jlexer.Lexer{Data: data}
+ easyjson2f096870Decode20242FIGHTCLUBDomain3(&r, v)
+ return r.Error()
+}
+
+// UnmarshalEasyJSON supports easyjson.Unmarshaler interface
+func (v *Review) UnmarshalEasyJSON(l *jlexer.Lexer) {
+ easyjson2f096870Decode20242FIGHTCLUBDomain3(l, v)
+}
diff --git a/go.mod b/go.mod
index d380a9d..e8ed56a 100644
--- a/go.mod
+++ b/go.mod
@@ -16,6 +16,7 @@ require (
github.com/joho/godotenv v1.5.1
github.com/microcosm-cc/bluemonday v1.0.27
github.com/minio/minio-go/v7 v7.0.78
+ github.com/prometheus/client_golang v1.20.5
github.com/stretchr/testify v1.9.0
go.uber.org/zap v1.27.0
golang.org/x/crypto v0.28.0
@@ -43,17 +44,19 @@ require (
github.com/jackc/puddle/v2 v2.2.1 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
+ github.com/josharian/intern v1.0.0 // indirect
github.com/klauspost/compress v1.17.11 // indirect
github.com/klauspost/cpuid/v2 v2.2.8 // indirect
+ github.com/mailru/easyjson v0.7.7 // indirect
github.com/minio/md5-simd v1.1.2 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
- github.com/prometheus/client_golang v1.20.5 // indirect
github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.55.0 // indirect
github.com/prometheus/procfs v0.15.1 // indirect
github.com/rogpeppe/go-internal v1.13.1 // indirect
github.com/rs/xid v1.6.0 // indirect
+ github.com/stretchr/objx v0.5.2 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/net v0.30.0 // indirect
golang.org/x/sync v0.8.0 // indirect
diff --git a/go.sum b/go.sum
index 5bacda3..44ad559 100644
--- a/go.sum
+++ b/go.sum
@@ -57,17 +57,22 @@ github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
+github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
+github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/kisielk/sqlstruct v0.0.0-20201105191214-5f3e10d3ab46/go.mod h1:yyMNCyc/Ib3bDTKd379tNMpB/7/H5TjM2Y9QJ5THLbE=
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM=
github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
-github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
-github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
+github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
+github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
+github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
+github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
+github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk=
github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA=
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
@@ -97,6 +102,8 @@ github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWN
github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
+github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
diff --git a/internal/ads/controller/ads_controller.go b/internal/ads/controller/ads_controller.go
index f3620eb..9ae8747 100644
--- a/internal/ads/controller/ads_controller.go
+++ b/internal/ads/controller/ads_controller.go
@@ -6,10 +6,11 @@ import (
"2024_2_FIGHT-CLUB/internal/service/metrics"
"2024_2_FIGHT-CLUB/internal/service/middleware"
"2024_2_FIGHT-CLUB/internal/service/session"
+ "2024_2_FIGHT-CLUB/internal/service/utils"
"2024_2_FIGHT-CLUB/microservices/ads_service/controller/gen"
- "encoding/json"
"errors"
"github.com/gorilla/mux"
+ "github.com/mailru/easyjson"
"go.uber.org/zap"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/timestamppb"
@@ -22,13 +23,15 @@ type AdHandler struct {
client gen.AdsClient
sessionService session.InterfaceSession
jwtToken middleware.JwtTokenService
+ utils utils.UtilsInterface
}
-func NewAdHandler(client gen.AdsClient, sessionService session.InterfaceSession, jwtToken middleware.JwtTokenService) *AdHandler {
+func NewAdHandler(client gen.AdsClient, sessionService session.InterfaceSession, jwtToken middleware.JwtTokenService, utils utils.UtilsInterface) *AdHandler {
return &AdHandler{
client: client,
sessionService: sessionService,
jwtToken: jwtToken,
+ utils: utils,
}
}
@@ -48,17 +51,16 @@ func (h *AdHandler) GetAllPlaces(w http.ResponseWriter, r *http.Request) {
clientIP = forwarded
}
defer func() {
+ sanitizedPath := metrics.SanitizeAdIdPath(r.URL.Path)
if statusCode == http.StatusOK {
- metrics.HttpRequestsTotal.WithLabelValues(r.Method, r.URL.Path, http.StatusText(statusCode), clientIP).Inc()
+ metrics.HttpRequestsTotal.WithLabelValues(r.Method, sanitizedPath, http.StatusText(statusCode), clientIP).Inc()
} else {
- metrics.HttpErrorsTotal.WithLabelValues(r.Method, r.URL.Path, http.StatusText(statusCode), err.Error(), clientIP).Inc()
+ metrics.HttpErrorsTotal.WithLabelValues(r.Method, sanitizedPath, http.StatusText(statusCode), err.Error(), clientIP).Inc()
}
duration := time.Since(start).Seconds()
- metrics.HttpRequestDuration.WithLabelValues(r.Method, r.URL.Path, clientIP).Observe(duration)
+ metrics.HttpRequestDuration.WithLabelValues(r.Method, sanitizedPath, clientIP).Observe(duration)
}()
- ctx = middleware.WithLogger(ctx, logger.AccessLogger)
-
logger.AccessLogger.Info("Received GetAllPlaces request",
zap.String("request_id", requestID),
zap.String("method", r.Method),
@@ -66,7 +68,12 @@ func (h *AdHandler) GetAllPlaces(w http.ResponseWriter, r *http.Request) {
zap.String("query", r.URL.Query().Encode()),
)
- w.Header().Set("Content-Type", "application/json; charset=UTF-8")
+ sessionID, err := session.GetSessionId(r)
+ if err != nil || sessionID == "" {
+ logger.AccessLogger.Warn("Failed to get session ID",
+ zap.String("request_id", requestID),
+ zap.Error(err))
+ }
queryParams := r.URL.Query()
@@ -80,6 +87,7 @@ func (h *AdHandler) GetAllPlaces(w http.ResponseWriter, r *http.Request) {
Offset: queryParams.Get("offset"),
DateFrom: queryParams.Get("dateFrom"),
DateTo: queryParams.Get("dateTo"),
+ SessionId: sessionID,
})
if err != nil {
logger.AccessLogger.Error("Failed to GetAllPlaces",
@@ -92,10 +100,21 @@ func (h *AdHandler) GetAllPlaces(w http.ResponseWriter, r *http.Request) {
}
return
}
-
+ body, err := h.utils.ConvertGetAllAdsResponseProtoToGo(response)
+ if err != nil {
+ logger.AccessLogger.Error("Failed to Convert From Proto to Go",
+ zap.Error(err),
+ zap.String("request_id", requestID))
+ statusCode = h.handleError(w, err, requestID)
+ return
+ }
+ w.Header().Set("Content-Type", "application/json; charset=UTF-8")
w.WriteHeader(http.StatusOK)
- if err := json.NewEncoder(w).Encode(response); err != nil {
- logger.AccessLogger.Error("Failed to encode response", zap.String("request_id", requestID), zap.Error(err))
+ if _, err = easyjson.MarshalToWriter(body, w); err != nil {
+ logger.AccessLogger.Error("Failed to encode response",
+ zap.String("request_id", requestID),
+ zap.Error(err),
+ )
statusCode = h.handleError(w, err, requestID)
return
}
@@ -124,33 +143,32 @@ func (h *AdHandler) GetOnePlace(w http.ResponseWriter, r *http.Request) {
clientIP = forwarded
}
defer func() {
+ sanitizedPath := metrics.SanitizeAdIdPath(r.URL.Path)
if statusCode == http.StatusOK {
- metrics.HttpRequestsTotal.WithLabelValues(r.Method, r.URL.Path, http.StatusText(statusCode), clientIP).Inc()
+ metrics.HttpRequestsTotal.WithLabelValues(r.Method, sanitizedPath, http.StatusText(statusCode), clientIP).Inc()
} else {
- metrics.HttpErrorsTotal.WithLabelValues(r.Method, r.URL.Path, http.StatusText(statusCode), err.Error(), clientIP).Inc()
+ metrics.HttpErrorsTotal.WithLabelValues(r.Method, sanitizedPath, http.StatusText(statusCode), err.Error(), clientIP).Inc()
}
duration := time.Since(start).Seconds()
- metrics.HttpRequestDuration.WithLabelValues(r.Method, r.URL.Path, clientIP).Observe(duration)
+ metrics.HttpRequestDuration.WithLabelValues(r.Method, sanitizedPath, clientIP).Observe(duration)
}()
- ctx = middleware.WithLogger(ctx, logger.AccessLogger)
logger.AccessLogger.Info("Received GetOnePlace request",
zap.String("request_id", requestID),
zap.String("adId", adId),
)
- var isAuthorized bool
+ isAuthorized := false
sessionID, err := session.GetSessionId(r)
if err != nil || sessionID == "" {
logger.AccessLogger.Warn("Failed to get session ID",
zap.String("request_id", requestID),
zap.Error(err))
- isAuthorized = false
- }
-
- if _, err := h.sessionService.GetUserID(ctx, sessionID); err != nil {
- isAuthorized = false
+ } else if _, err := h.sessionService.GetUserID(ctx, sessionID); err != nil {
+ logger.AccessLogger.Warn("Failed to validate session",
+ zap.String("request_id", requestID),
+ zap.Error(err))
} else {
isAuthorized = true
}
@@ -170,13 +188,24 @@ func (h *AdHandler) GetOnePlace(w http.ResponseWriter, r *http.Request) {
return
}
- body := map[string]interface{}{
- "place": place,
+ payload, err := h.utils.ConvertAdProtoToGo(place)
+ if err != nil {
+ logger.AccessLogger.Error("Failed to Convert From Proto to Go",
+ zap.Error(err),
+ zap.String("request_id", requestID))
+ statusCode = h.handleError(w, err, requestID)
+ return
+ }
+ response := domain.GetOneAdResponse{
+ Place: payload,
}
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
w.WriteHeader(http.StatusOK)
- if err = json.NewEncoder(w).Encode(body); err != nil {
- logger.AccessLogger.Error("Failed to encode response", zap.String("request_id", requestID), zap.Error(err))
+ if _, err = easyjson.MarshalToWriter(response, w); err != nil {
+ logger.AccessLogger.Error("Failed to encode response",
+ zap.String("request_id", requestID),
+ zap.Error(err),
+ )
statusCode = h.handleError(w, err, requestID)
return
}
@@ -204,17 +233,16 @@ func (h *AdHandler) CreatePlace(w http.ResponseWriter, r *http.Request) {
clientIP = forwarded
}
defer func() {
+ sanitizedPath := metrics.SanitizeAdIdPath(r.URL.Path)
if statusCode == http.StatusCreated {
- metrics.HttpRequestsTotal.WithLabelValues(r.Method, r.URL.Path, http.StatusText(statusCode), clientIP).Inc()
+ metrics.HttpRequestsTotal.WithLabelValues(r.Method, sanitizedPath, http.StatusText(statusCode), clientIP).Inc()
} else {
- metrics.HttpErrorsTotal.WithLabelValues(r.Method, r.URL.Path, http.StatusText(statusCode), err.Error(), clientIP).Inc()
+ metrics.HttpErrorsTotal.WithLabelValues(r.Method, sanitizedPath, http.StatusText(statusCode), err.Error(), clientIP).Inc()
}
duration := time.Since(start).Seconds()
- metrics.HttpRequestDuration.WithLabelValues(r.Method, r.URL.Path, clientIP).Observe(duration)
+ metrics.HttpRequestDuration.WithLabelValues(r.Method, sanitizedPath, clientIP).Observe(duration)
}()
- ctx = middleware.WithLogger(ctx, logger.AccessLogger)
-
logger.AccessLogger.Info("Received CreatePlace request",
zap.String("request_id", requestID),
)
@@ -230,8 +258,6 @@ func (h *AdHandler) CreatePlace(w http.ResponseWriter, r *http.Request) {
authHeader := r.Header.Get("X-CSRF-Token")
- w.Header().Set("Content-Type", "application/json; charset=UTF-8")
-
err = r.ParseMultipartForm(10 << 20) // 10 MB
if err != nil {
logger.AccessLogger.Error("Failed to parse multipart form", zap.String("request_id", requestID), zap.Error(err))
@@ -241,16 +267,17 @@ func (h *AdHandler) CreatePlace(w http.ResponseWriter, r *http.Request) {
metadata := r.FormValue("metadata")
var newPlace domain.CreateAdRequest
- if err := json.Unmarshal([]byte(metadata), &newPlace); err != nil {
+ if err = newPlace.UnmarshalJSON([]byte(metadata)); err != nil {
logger.AccessLogger.Error("Failed to decode metadata", zap.String("request_id", requestID), zap.Error(err))
- statusCode = h.handleError(w, errors.New("failed to decode metadata"), requestID)
+ statusCode = h.handleError(w, err, requestID)
return
}
fileHeaders := r.MultipartForm.File["images"]
if len(fileHeaders) == 0 {
logger.AccessLogger.Warn("No images", zap.String("request_id", requestID))
- statusCode = h.handleError(w, errors.New("no images provided"), requestID)
+ err = errors.New("no images provided")
+ statusCode = h.handleError(w, err, requestID)
return
}
@@ -275,16 +302,23 @@ func (h *AdHandler) CreatePlace(w http.ResponseWriter, r *http.Request) {
files = append(files, data)
}
- response, err := h.client.CreatePlace(ctx, &gen.CreateAdRequest{
- CityName: newPlace.CityName,
- Description: newPlace.Description,
- Address: newPlace.Address,
- RoomsNumber: int32(newPlace.RoomsNumber),
- DateFrom: timestamppb.New(newPlace.DateFrom),
- DateTo: timestamppb.New(newPlace.DateTo),
- Images: files,
- AuthHeader: authHeader,
- SessionID: sessionID,
+ _, err = h.client.CreatePlace(ctx, &gen.CreateAdRequest{
+ CityName: newPlace.CityName,
+ Description: newPlace.Description,
+ Address: newPlace.Address,
+ RoomsNumber: int32(newPlace.RoomsNumber),
+ DateFrom: timestamppb.New(newPlace.DateFrom),
+ DateTo: timestamppb.New(newPlace.DateTo),
+ Images: files,
+ AuthHeader: authHeader,
+ SessionID: sessionID,
+ SquareMeters: int32(newPlace.SquareMeters),
+ Floor: int32(newPlace.Floor),
+ BuildingType: newPlace.BuildingType,
+ HasBalcony: newPlace.HasBalcony,
+ HasElevator: newPlace.HasElevator,
+ HasGas: newPlace.HasGas,
+ Rooms: middleware.ConvertRoomsToGRPC(newPlace.Rooms),
})
if err != nil {
logger.AccessLogger.Error("Failed to create place", zap.String("request_id", requestID), zap.Error(err))
@@ -295,11 +329,12 @@ func (h *AdHandler) CreatePlace(w http.ResponseWriter, r *http.Request) {
return
}
- body := map[string]interface{}{
- "place": response,
+ w.Header().Set("Content-Type", "application/json; charset=UTF-8")
+ w.WriteHeader(http.StatusCreated)
+ body := domain.ResponseMessage{
+ Message: "Successfully created ad",
}
-
- if err := json.NewEncoder(w).Encode(body); err != nil {
+ if _, err := easyjson.MarshalToWriter(body, w); err != nil {
logger.AccessLogger.Error("Failed to encode response", zap.String("request_id", requestID), zap.Error(err))
statusCode = h.handleError(w, errors.New("failed to encode response"), requestID)
return
@@ -309,7 +344,7 @@ func (h *AdHandler) CreatePlace(w http.ResponseWriter, r *http.Request) {
logger.AccessLogger.Info("Completed CreatePlace request",
zap.String("request_id", requestID),
zap.Duration("duration", duration),
- zap.Int("status", http.StatusOK),
+ zap.Int("status", http.StatusCreated),
)
}
@@ -329,17 +364,16 @@ func (h *AdHandler) UpdatePlace(w http.ResponseWriter, r *http.Request) {
clientIP = forwarded
}
defer func() {
+ sanitizedPath := metrics.SanitizeAdIdPath(r.URL.Path)
if statusCode == http.StatusOK {
- metrics.HttpRequestsTotal.WithLabelValues(r.Method, r.URL.Path, http.StatusText(statusCode), clientIP).Inc()
+ metrics.HttpRequestsTotal.WithLabelValues(r.Method, sanitizedPath, http.StatusText(statusCode), clientIP).Inc()
} else {
- metrics.HttpErrorsTotal.WithLabelValues(r.Method, r.URL.Path, http.StatusText(statusCode), err.Error(), clientIP).Inc()
+ metrics.HttpErrorsTotal.WithLabelValues(r.Method, sanitizedPath, http.StatusText(statusCode), err.Error(), clientIP).Inc()
}
duration := time.Since(start).Seconds()
- metrics.HttpRequestDuration.WithLabelValues(r.Method, r.URL.Path, clientIP).Observe(duration)
+ metrics.HttpRequestDuration.WithLabelValues(r.Method, sanitizedPath, clientIP).Observe(duration)
}()
- ctx = middleware.WithLogger(ctx, logger.AccessLogger)
-
logger.AccessLogger.Info("Received UpdatePlace request",
zap.String("request_id", requestID),
zap.String("adId", adId),
@@ -347,8 +381,6 @@ func (h *AdHandler) UpdatePlace(w http.ResponseWriter, r *http.Request) {
authHeader := r.Header.Get("X-CSRF-Token")
- w.Header().Set("Content-Type", "application/json; charset=UTF-8")
-
err = r.ParseMultipartForm(10 << 20) // 10 MB
if err != nil {
logger.AccessLogger.Error("Failed to parse multipart form", zap.String("request_id", requestID), zap.Error(err))
@@ -358,7 +390,7 @@ func (h *AdHandler) UpdatePlace(w http.ResponseWriter, r *http.Request) {
metadata := r.FormValue("metadata")
var updatedPlace domain.UpdateAdRequest
- if err := json.Unmarshal([]byte(metadata), &updatedPlace); err != nil {
+ if err = updatedPlace.UnmarshalJSON([]byte(metadata)); err != nil {
logger.AccessLogger.Error("Failed to decode metadata", zap.String("request_id", requestID), zap.Error(err))
statusCode = h.handleError(w, errors.New("invalid metadata JSON"), requestID)
return
@@ -396,17 +428,24 @@ func (h *AdHandler) UpdatePlace(w http.ResponseWriter, r *http.Request) {
return
}
- response, err := h.client.UpdatePlace(ctx, &gen.UpdateAdRequest{
- AdId: adId,
- CityName: updatedPlace.CityName,
- Address: updatedPlace.Address,
- Description: updatedPlace.Description,
- RoomsNumber: int32(updatedPlace.RoomsNumber),
- SessionID: sessionID,
- AuthHeader: authHeader,
- Images: files,
- DateFrom: timestamppb.New(updatedPlace.DateFrom),
- DateTo: timestamppb.New(updatedPlace.DateTo),
+ _, err = h.client.UpdatePlace(ctx, &gen.UpdateAdRequest{
+ AdId: adId,
+ CityName: updatedPlace.CityName,
+ Address: updatedPlace.Address,
+ Description: updatedPlace.Description,
+ RoomsNumber: int32(updatedPlace.RoomsNumber),
+ SessionID: sessionID,
+ AuthHeader: authHeader,
+ Images: files,
+ DateFrom: timestamppb.New(updatedPlace.DateFrom),
+ DateTo: timestamppb.New(updatedPlace.DateTo),
+ SquareMeters: int32(updatedPlace.SquareMeters),
+ Floor: int32(updatedPlace.Floor),
+ BuildingType: updatedPlace.BuildingType,
+ HasBalcony: updatedPlace.HasBalcony,
+ HasElevator: updatedPlace.HasElevator,
+ HasGas: updatedPlace.HasGas,
+ Rooms: middleware.ConvertRoomsToGRPC(updatedPlace.Rooms),
})
if err != nil {
logger.AccessLogger.Error("Failed to update place", zap.String("request_id", requestID), zap.Error(err))
@@ -417,9 +456,12 @@ func (h *AdHandler) UpdatePlace(w http.ResponseWriter, r *http.Request) {
return
}
+ w.Header().Set("Content-Type", "application/json; charset=UTF-8")
w.WriteHeader(http.StatusOK)
- updateResponse := map[string]string{"response": response.Response}
- if err := json.NewEncoder(w).Encode(updateResponse); err != nil {
+ updateResponse := domain.ResponseMessage{
+ Message: "Successfully updated ad",
+ }
+ if _, err = easyjson.MarshalToWriter(updateResponse, w); err != nil {
logger.AccessLogger.Error("Failed to encode response", zap.String("request_id", requestID), zap.Error(err))
http.Error(w, err.Error(), http.StatusInternalServerError)
return
@@ -448,17 +490,16 @@ func (h *AdHandler) DeletePlace(w http.ResponseWriter, r *http.Request) {
clientIP = forwarded
}
defer func() {
+ sanitizedPath := metrics.SanitizeAdIdPath(r.URL.Path)
if statusCode == http.StatusOK {
- metrics.HttpRequestsTotal.WithLabelValues(r.Method, r.URL.Path, http.StatusText(statusCode), clientIP).Inc()
+ metrics.HttpRequestsTotal.WithLabelValues(r.Method, sanitizedPath, http.StatusText(statusCode), clientIP).Inc()
} else {
- metrics.HttpErrorsTotal.WithLabelValues(r.Method, r.URL.Path, http.StatusText(statusCode), err.Error(), clientIP).Inc()
+ metrics.HttpErrorsTotal.WithLabelValues(r.Method, sanitizedPath, http.StatusText(statusCode), err.Error(), clientIP).Inc()
}
duration := time.Since(start).Seconds()
- metrics.HttpRequestDuration.WithLabelValues(r.Method, r.URL.Path, clientIP).Observe(duration)
+ metrics.HttpRequestDuration.WithLabelValues(r.Method, sanitizedPath, clientIP).Observe(duration)
}()
- ctx = middleware.WithLogger(ctx, logger.AccessLogger)
-
logger.AccessLogger.Info("Received DeletePlace request",
zap.String("request_id", requestID),
zap.String("adId", adId),
@@ -466,8 +507,6 @@ func (h *AdHandler) DeletePlace(w http.ResponseWriter, r *http.Request) {
authHeader := r.Header.Get("X-CSRF-Token")
- w.Header().Set("Content-Type", "application/json; charset=UTF-8")
-
sessionID, err := session.GetSessionId(r)
if err != nil {
logger.AccessLogger.Error("Failed to get session ID",
@@ -477,7 +516,7 @@ func (h *AdHandler) DeletePlace(w http.ResponseWriter, r *http.Request) {
return
}
- response, err := h.client.DeletePlace(ctx, &gen.DeletePlaceRequest{
+ _, err = h.client.DeletePlace(ctx, &gen.DeletePlaceRequest{
AdId: adId,
SessionID: sessionID,
AuthHeader: authHeader,
@@ -491,9 +530,12 @@ func (h *AdHandler) DeletePlace(w http.ResponseWriter, r *http.Request) {
return
}
+ w.Header().Set("Content-Type", "application/json; charset=UTF-8")
w.WriteHeader(http.StatusOK)
- updateResponse := map[string]string{"response": response.Response}
- if err := json.NewEncoder(w).Encode(updateResponse); err != nil {
+ deleteResponse := domain.ResponseMessage{
+ Message: "Successfully deleted place",
+ }
+ if _, err = easyjson.MarshalToWriter(deleteResponse, w); err != nil {
logger.AccessLogger.Error("Failed to encode response", zap.String("request_id", requestID), zap.Error(err))
http.Error(w, err.Error(), http.StatusInternalServerError)
return
@@ -516,23 +558,21 @@ func (h *AdHandler) GetPlacesPerCity(w http.ResponseWriter, r *http.Request) {
clientIP = forwarded
}
defer func() {
+ sanitizedPath := metrics.SanitizeAdIdPath(r.URL.Path)
if statusCode == http.StatusOK {
- metrics.HttpRequestsTotal.WithLabelValues(r.Method, r.URL.Path, http.StatusText(statusCode), clientIP).Inc()
+ metrics.HttpRequestsTotal.WithLabelValues(r.Method, sanitizedPath, http.StatusText(statusCode), clientIP).Inc()
} else {
- metrics.HttpErrorsTotal.WithLabelValues(r.Method, r.URL.Path, http.StatusText(statusCode), err.Error(), clientIP).Inc()
+ metrics.HttpErrorsTotal.WithLabelValues(r.Method, sanitizedPath, http.StatusText(statusCode), err.Error(), clientIP).Inc()
}
duration := time.Since(start).Seconds()
- metrics.HttpRequestDuration.WithLabelValues(r.Method, r.URL.Path, clientIP).Observe(duration)
+ metrics.HttpRequestDuration.WithLabelValues(r.Method, sanitizedPath, clientIP).Observe(duration)
}()
- ctx = middleware.WithLogger(ctx, logger.AccessLogger)
-
logger.AccessLogger.Info("Received GetPlacesPerCity request",
zap.String("request_id", requestID),
zap.String("city", city),
)
- w.Header().Set("Content-Type", "application/json; charset=UTF-8")
response, err := h.client.GetPlacesPerCity(ctx, &gen.GetPlacesPerCityRequest{
CityName: city,
})
@@ -544,13 +584,26 @@ func (h *AdHandler) GetPlacesPerCity(w http.ResponseWriter, r *http.Request) {
}
return
}
- body := map[string]interface{}{
- "places": response,
+
+ payload, err := h.utils.ConvertGetAllAdsResponseProtoToGo(response)
+ if err != nil {
+ logger.AccessLogger.Error("Failed to Convert From Proto to Go",
+ zap.Error(err),
+ zap.String("request_id", requestID))
+ statusCode = h.handleError(w, err, requestID)
+ return
}
+ w.Header().Set("Content-Type", "application/json; charset=UTF-8")
w.WriteHeader(http.StatusOK)
- if err := json.NewEncoder(w).Encode(body); err != nil {
- logger.AccessLogger.Error("Failed to encode response", zap.String("request_id", requestID), zap.Error(err))
- http.Error(w, err.Error(), http.StatusInternalServerError)
+ body := domain.PlacesResponse{
+ Places: payload,
+ }
+ if _, err = easyjson.MarshalToWriter(body, w); err != nil {
+ logger.AccessLogger.Error("Failed to encode response",
+ zap.String("request_id", requestID),
+ zap.Error(err),
+ )
+ statusCode = h.handleError(w, err, requestID)
return
}
@@ -578,23 +631,21 @@ func (h *AdHandler) GetUserPlaces(w http.ResponseWriter, r *http.Request) {
clientIP = forwarded
}
defer func() {
+ sanitizedPath := metrics.SanitizeUserIdPath(r.URL.Path)
if statusCode == http.StatusOK {
- metrics.HttpRequestsTotal.WithLabelValues(r.Method, r.URL.Path, http.StatusText(statusCode), clientIP).Inc()
+ metrics.HttpRequestsTotal.WithLabelValues(r.Method, sanitizedPath, http.StatusText(statusCode), clientIP).Inc()
} else {
- metrics.HttpErrorsTotal.WithLabelValues(r.Method, r.URL.Path, http.StatusText(statusCode), err.Error(), clientIP).Inc()
+ metrics.HttpErrorsTotal.WithLabelValues(r.Method, sanitizedPath, http.StatusText(statusCode), err.Error(), clientIP).Inc()
}
duration := time.Since(start).Seconds()
- metrics.HttpRequestDuration.WithLabelValues(r.Method, r.URL.Path, clientIP).Observe(duration)
+ metrics.HttpRequestDuration.WithLabelValues(r.Method, sanitizedPath, clientIP).Observe(duration)
}()
- ctx = middleware.WithLogger(ctx, logger.AccessLogger)
-
logger.AccessLogger.Info("Received GetUserPlaces request",
zap.String("request_id", requestID),
zap.String("userId", userId),
)
- w.Header().Set("Content-Type", "application/json; charset=UTF-8")
response, err := h.client.GetUserPlaces(ctx, &gen.GetUserPlacesRequest{
UserId: userId,
})
@@ -608,8 +659,25 @@ func (h *AdHandler) GetUserPlaces(w http.ResponseWriter, r *http.Request) {
}
return
}
- if err := json.NewEncoder(w).Encode(response); err != nil {
- logger.AccessLogger.Error("Failed to encode response", zap.String("request_id", requestID), zap.Error(err))
+
+ payload, err := h.utils.ConvertGetAllAdsResponseProtoToGo(response)
+ if err != nil {
+ logger.AccessLogger.Error("Failed to Convert From Proto to Go",
+ zap.Error(err),
+ zap.String("request_id", requestID))
+ statusCode = h.handleError(w, err, requestID)
+ return
+ }
+ w.Header().Set("Content-Type", "application/json; charset=UTF-8")
+ w.WriteHeader(http.StatusOK)
+ body := domain.PlacesResponse{
+ Places: payload,
+ }
+ if _, err = easyjson.MarshalToWriter(body, w); err != nil {
+ logger.AccessLogger.Error("Failed to encode response",
+ zap.String("request_id", requestID),
+ zap.Error(err),
+ )
statusCode = h.handleError(w, err, requestID)
return
}
@@ -639,17 +707,16 @@ func (h *AdHandler) DeleteAdImage(w http.ResponseWriter, r *http.Request) {
clientIP = forwarded
}
defer func() {
+ sanitizedPath := metrics.SanitizeAdIdPath(r.URL.Path)
if statusCode == http.StatusOK {
- metrics.HttpRequestsTotal.WithLabelValues(r.Method, r.URL.Path, http.StatusText(statusCode), clientIP).Inc()
+ metrics.HttpRequestsTotal.WithLabelValues(r.Method, sanitizedPath, http.StatusText(statusCode), clientIP).Inc()
} else {
- metrics.HttpErrorsTotal.WithLabelValues(r.Method, r.URL.Path, http.StatusText(statusCode), err.Error(), clientIP).Inc()
+ metrics.HttpErrorsTotal.WithLabelValues(r.Method, sanitizedPath, http.StatusText(statusCode), err.Error(), clientIP).Inc()
}
duration := time.Since(start).Seconds()
- metrics.HttpRequestDuration.WithLabelValues(r.Method, r.URL.Path, clientIP).Observe(duration)
+ metrics.HttpRequestDuration.WithLabelValues(r.Method, sanitizedPath, clientIP).Observe(duration)
}()
- ctx = middleware.WithLogger(ctx, logger.AccessLogger)
-
logger.AccessLogger.Info("Received DeleteAdImage request",
zap.String("request_id", requestID),
zap.String("adId", adId),
@@ -666,9 +733,7 @@ func (h *AdHandler) DeleteAdImage(w http.ResponseWriter, r *http.Request) {
return
}
- w.Header().Set("Content-Type", "application/json; charset=UTF-8")
-
- response, err := h.client.DeleteAdImage(ctx, &gen.DeleteAdImageRequest{
+ _, err = h.client.DeleteAdImage(ctx, &gen.DeleteAdImageRequest{
AdId: adId,
ImageId: imageId,
AuthHeader: authHeader,
@@ -683,9 +748,12 @@ func (h *AdHandler) DeleteAdImage(w http.ResponseWriter, r *http.Request) {
return
}
+ w.Header().Set("Content-Type", "application/json; charset=UTF-8")
w.WriteHeader(http.StatusOK)
- updateResponse := map[string]string{"response": response.Response}
- if err := json.NewEncoder(w).Encode(updateResponse); err != nil {
+ deleteResponse := domain.ResponseMessage{
+ Message: "Successfully deleted ad image",
+ }
+ if _, err = easyjson.MarshalToWriter(deleteResponse, w); err != nil {
logger.AccessLogger.Error("Failed to encode response", zap.String("request_id", requestID), zap.Error(err))
http.Error(w, err.Error(), http.StatusInternalServerError)
return
@@ -716,17 +784,16 @@ func (h *AdHandler) AddToFavorites(w http.ResponseWriter, r *http.Request) {
clientIP = forwarded
}
defer func() {
+ sanitizedPath := metrics.SanitizeAdIdPath(r.URL.Path)
if statusCode == http.StatusOK {
- metrics.HttpRequestsTotal.WithLabelValues(r.Method, r.URL.Path, http.StatusText(statusCode), clientIP).Inc()
+ metrics.HttpRequestsTotal.WithLabelValues(r.Method, sanitizedPath, http.StatusText(statusCode), clientIP).Inc()
} else {
- metrics.HttpErrorsTotal.WithLabelValues(r.Method, r.URL.Path, http.StatusText(statusCode), err.Error(), clientIP).Inc()
+ metrics.HttpErrorsTotal.WithLabelValues(r.Method, sanitizedPath, http.StatusText(statusCode), err.Error(), clientIP).Inc()
}
duration := time.Since(start).Seconds()
- metrics.HttpRequestDuration.WithLabelValues(r.Method, r.URL.Path, clientIP).Observe(duration)
+ metrics.HttpRequestDuration.WithLabelValues(r.Method, sanitizedPath, clientIP).Observe(duration)
}()
- ctx = middleware.WithLogger(ctx, logger.AccessLogger)
-
logger.AccessLogger.Info("Received AddToFavorites request",
zap.String("request_id", requestID),
zap.String("adId", adId))
@@ -742,9 +809,7 @@ func (h *AdHandler) AddToFavorites(w http.ResponseWriter, r *http.Request) {
return
}
- w.Header().Set("Content-Type", "application/json; charset=UTF-8")
-
- response, err := h.client.AddToFavorites(ctx, &gen.AddToFavoritesRequest{
+ _, err = h.client.AddToFavorites(ctx, &gen.AddToFavoritesRequest{
AdId: adId,
AuthHeader: authHeader,
SessionID: sessionID,
@@ -758,9 +823,12 @@ func (h *AdHandler) AddToFavorites(w http.ResponseWriter, r *http.Request) {
return
}
+ w.Header().Set("Content-Type", "application/json; charset=UTF-8")
w.WriteHeader(http.StatusOK)
- updateResponse := map[string]string{"response": response.Response}
- if err := json.NewEncoder(w).Encode(updateResponse); err != nil {
+ addToResponse := domain.ResponseMessage{
+ Message: "Successfully added to favorites",
+ }
+ if _, err := easyjson.MarshalToWriter(addToResponse, w); err != nil {
logger.AccessLogger.Error("Failed to encode response", zap.String("request_id", requestID), zap.Error(err))
http.Error(w, err.Error(), http.StatusInternalServerError)
return
@@ -791,17 +859,16 @@ func (h *AdHandler) DeleteFromFavorites(w http.ResponseWriter, r *http.Request)
clientIP = forwarded
}
defer func() {
+ sanitizedPath := metrics.SanitizeAdIdPath(r.URL.Path)
if statusCode == http.StatusOK {
- metrics.HttpRequestsTotal.WithLabelValues(r.Method, r.URL.Path, http.StatusText(statusCode), clientIP).Inc()
+ metrics.HttpRequestsTotal.WithLabelValues(r.Method, sanitizedPath, http.StatusText(statusCode), clientIP).Inc()
} else {
- metrics.HttpErrorsTotal.WithLabelValues(r.Method, r.URL.Path, http.StatusText(statusCode), err.Error(), clientIP).Inc()
+ metrics.HttpErrorsTotal.WithLabelValues(r.Method, sanitizedPath, http.StatusText(statusCode), err.Error(), clientIP).Inc()
}
duration := time.Since(start).Seconds()
- metrics.HttpRequestDuration.WithLabelValues(r.Method, r.URL.Path, clientIP).Observe(duration)
+ metrics.HttpRequestDuration.WithLabelValues(r.Method, sanitizedPath, clientIP).Observe(duration)
}()
- ctx = middleware.WithLogger(ctx, logger.AccessLogger)
-
logger.AccessLogger.Info("Received DeleteFromFavorites request",
zap.String("request_id", requestID),
zap.String("adId", adId))
@@ -817,9 +884,7 @@ func (h *AdHandler) DeleteFromFavorites(w http.ResponseWriter, r *http.Request)
return
}
- w.Header().Set("Content-Type", "application/json; charset=UTF-8")
-
- response, err := h.client.DeleteFromFavorites(ctx, &gen.DeleteFromFavoritesRequest{
+ _, err = h.client.DeleteFromFavorites(ctx, &gen.DeleteFromFavoritesRequest{
AdId: adId,
AuthHeader: authHeader,
SessionID: sessionID,
@@ -833,9 +898,12 @@ func (h *AdHandler) DeleteFromFavorites(w http.ResponseWriter, r *http.Request)
return
}
+ w.Header().Set("Content-Type", "application/json; charset=UTF-8")
w.WriteHeader(http.StatusOK)
- updateResponse := map[string]string{"response": response.Response}
- if err := json.NewEncoder(w).Encode(updateResponse); err != nil {
+ deleteFromFavResponse := domain.ResponseMessage{
+ Message: "Successfully deleted from favorites",
+ }
+ if _, err = easyjson.MarshalToWriter(deleteFromFavResponse, w); err != nil {
logger.AccessLogger.Error("Failed to encode response", zap.String("request_id", requestID), zap.Error(err))
http.Error(w, err.Error(), http.StatusInternalServerError)
return
@@ -866,23 +934,20 @@ func (h *AdHandler) GetUserFavorites(w http.ResponseWriter, r *http.Request) {
clientIP = forwarded
}
defer func() {
+ sanitizedPath := metrics.SanitizeUserIdPath(r.URL.Path)
if statusCode == http.StatusOK {
- metrics.HttpRequestsTotal.WithLabelValues(r.Method, r.URL.Path, http.StatusText(statusCode), clientIP).Inc()
+ metrics.HttpRequestsTotal.WithLabelValues(r.Method, sanitizedPath, http.StatusText(statusCode), clientIP).Inc()
} else {
- metrics.HttpErrorsTotal.WithLabelValues(r.Method, r.URL.Path, http.StatusText(statusCode), err.Error(), clientIP).Inc()
+ metrics.HttpErrorsTotal.WithLabelValues(r.Method, sanitizedPath, http.StatusText(statusCode), err.Error(), clientIP).Inc()
}
duration := time.Since(start).Seconds()
- metrics.HttpRequestDuration.WithLabelValues(r.Method, r.URL.Path, clientIP).Observe(duration)
+ metrics.HttpRequestDuration.WithLabelValues(r.Method, sanitizedPath, clientIP).Observe(duration)
}()
- ctx = middleware.WithLogger(ctx, logger.AccessLogger)
-
logger.AccessLogger.Info("Received GetUserFavorites request",
zap.String("request_id", requestID),
zap.String("userId", userId))
- authHeader := r.Header.Get("X-CSRF-Token")
-
sessionID, err := session.GetSessionId(r)
if err != nil {
logger.AccessLogger.Error("Failed to get session ID",
@@ -892,15 +957,106 @@ func (h *AdHandler) GetUserFavorites(w http.ResponseWriter, r *http.Request) {
return
}
+ response, err := h.client.GetUserFavorites(ctx, &gen.GetUserFavoritesRequest{
+ UserId: userId,
+ SessionID: sessionID,
+ })
+ if err != nil {
+ logger.AccessLogger.Error("Failed to delete ad from favorites", zap.String("request_id", requestID), zap.Error(err))
+ st, ok := status.FromError(err)
+ if ok {
+ statusCode = h.handleError(w, errors.New(st.Message()), requestID)
+ }
+ return
+ }
+
+ body, err := h.utils.ConvertGetAllAdsResponseProtoToGo(response)
+ if err != nil {
+ logger.AccessLogger.Error("Failed to Convert From Proto to Go",
+ zap.Error(err),
+ zap.String("request_id", requestID))
+ statusCode = h.handleError(w, err, requestID)
+ return
+ }
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
+ w.WriteHeader(http.StatusOK)
+ if _, err = easyjson.MarshalToWriter(body, w); err != nil {
+ logger.AccessLogger.Error("Failed to encode response",
+ zap.String("request_id", requestID),
+ zap.Error(err),
+ )
+ statusCode = h.handleError(w, err, requestID)
+ return
+ }
- response, err := h.client.GetUserFavorites(ctx, &gen.GetUserFavoritesRequest{
- UserId: userId,
+ duration := time.Since(start)
+ logger.AccessLogger.Info("Completed GetUserFavorites request",
+ zap.String("request_id", requestID),
+ zap.String("userId", userId),
+ zap.Duration("duration", duration),
+ )
+
+}
+
+func (h *AdHandler) UpdatePriorityWithPayment(w http.ResponseWriter, r *http.Request) {
+ start := time.Now()
+ requestID := middleware.GetRequestID(r.Context())
+ adId := mux.Vars(r)["adId"]
+ ctx, cancel := middleware.WithTimeout(r.Context())
+ defer cancel()
+
+ statusCode := http.StatusOK
+ var err error
+ clientIP := r.RemoteAddr
+ if realIP := r.Header.Get("X-Real-IP"); realIP != "" {
+ clientIP = realIP
+ } else if forwarded := r.Header.Get("X-Forwarded-For"); forwarded != "" {
+ clientIP = forwarded
+ }
+ defer func() {
+ sanitizedPath := metrics.SanitizeAdIdPath(r.URL.Path)
+ if statusCode == http.StatusOK {
+ metrics.HttpRequestsTotal.WithLabelValues(r.Method, sanitizedPath, http.StatusText(statusCode), clientIP).Inc()
+ } else {
+ metrics.HttpErrorsTotal.WithLabelValues(r.Method, sanitizedPath, http.StatusText(statusCode), err.Error(), clientIP).Inc()
+ }
+ duration := time.Since(start).Seconds()
+ metrics.HttpRequestDuration.WithLabelValues(r.Method, sanitizedPath, clientIP).Observe(duration)
+ }()
+
+ logger.AccessLogger.Info("Received UpdatePriorityWithPayment request",
+ zap.String("request_id", requestID),
+ zap.String("userId", adId))
+
+ var card domain.PaymentInfo
+ if err = easyjson.UnmarshalFromReader(r.Body, &card); err != nil {
+ logger.AccessLogger.Error("Failed to decode request body",
+ zap.String("request_id", requestID),
+ zap.Error(err),
+ )
+ statusCode = h.handleError(w, err, requestID)
+ return
+ }
+
+ authHeader := r.Header.Get("X-CSRF-Token")
+
+ sessionID, err := session.GetSessionId(r)
+ if err != nil {
+ logger.AccessLogger.Error("Failed to get session ID",
+ zap.String("request_id", requestID),
+ zap.Error(err))
+ statusCode = h.handleError(w, err, requestID)
+ return
+ }
+
+ _, err = h.client.UpdatePriority(ctx, &gen.UpdatePriorityRequest{
+ AdId: adId,
AuthHeader: authHeader,
SessionID: sessionID,
+ Amount: card.DonationAmount,
})
if err != nil {
- logger.AccessLogger.Error("Failed to delete ad from favorites", zap.String("request_id", requestID), zap.Error(err))
+ logger.AccessLogger.Error("Failed to update priority", zap.String("request_id", requestID), zap.Error(err))
st, ok := status.FromError(err)
if ok {
statusCode = h.handleError(w, errors.New(st.Message()), requestID)
@@ -908,20 +1064,23 @@ func (h *AdHandler) GetUserFavorites(w http.ResponseWriter, r *http.Request) {
return
}
+ w.Header().Set("Content-Type", "application/json; charset=UTF-8")
w.WriteHeader(http.StatusOK)
- if err := json.NewEncoder(w).Encode(response); err != nil {
+ updatePriorResponse := domain.ResponseMessage{
+ Message: "Successfully update ad priority",
+ }
+ if _, err = easyjson.MarshalToWriter(updatePriorResponse, w); err != nil {
logger.AccessLogger.Error("Failed to encode response", zap.String("request_id", requestID), zap.Error(err))
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
duration := time.Since(start)
- logger.AccessLogger.Info("Completed GetUserFavorites request",
+ logger.AccessLogger.Info("Completed UpdatePriorityWithPayment request",
zap.String("request_id", requestID),
- zap.String("userId", userId),
+ zap.String("userId", adId),
zap.Duration("duration", duration),
)
-
}
func (h *AdHandler) handleError(w http.ResponseWriter, err error, requestID string) int {
@@ -929,21 +1088,19 @@ func (h *AdHandler) handleError(w http.ResponseWriter, err error, requestID stri
zap.String("request_id", requestID),
zap.Error(err),
)
- var status int
+ var statusCode int
w.Header().Set("Content-Type", "application/json")
- errorResponse := map[string]string{"error": err.Error()}
-
+ errorResponse := domain.ErrorResponse{
+ Error: err.Error(),
+ }
switch err.Error() {
- case "ad not found", "ad date not found", "image not found":
- w.WriteHeader(http.StatusNotFound)
- status = http.StatusNotFound
- case "ad already exists", "RoomsNumber out of range", "not owner of ad":
- w.WriteHeader(http.StatusConflict)
- status = http.StatusConflict
+ case "ad not found", "ad date not found", "image not found", "error fetching all places":
+ statusCode = http.StatusNotFound
+ case "ad already exists", "roomsNumber out of range", "not owner of ad":
+ statusCode = http.StatusConflict
case "no active session", "missing X-CSRF-Token header",
"invalid JWT token", "user is not host", "session not found", "user ID not found in session":
- w.WriteHeader(http.StatusUnauthorized)
- status = http.StatusUnauthorized
+ statusCode = http.StatusUnauthorized
case "invalid metadata JSON", "invalid multipart form", "input contains invalid characters",
"input exceeds character limit", "invalid size, type or resolution of image",
"query offset not int", "query limit not int", "query dateFrom not int",
@@ -952,24 +1109,24 @@ func (h *AdHandler) handleError(w http.ResponseWriter, err error, requestID stri
"failed to decode metadata", "no images provided", "failed to open file",
"failed to read file", "failed to encode response", "invalid rating value",
"cant access other user favorites":
- w.WriteHeader(http.StatusBadRequest)
- status = http.StatusBadRequest
- case "error fetching all places", "error fetching images for ad", "error fetching user",
+ statusCode = http.StatusBadRequest
+ case "error fetching images for ad", "error fetching user",
"error finding user", "error finding city", "error creating place", "error creating date",
"error saving place", "error updating place", "error updating date",
"error updating views count", "error deleting place", "get places error",
"get places per city error", "get user places error", "error creating image",
"delete ad image error", "failed to generate session id", "failed to save session",
"failed to delete session", "error generating random bytes for session ID",
- "failed to get session id from request cookie":
- w.WriteHeader(http.StatusInternalServerError)
- status = http.StatusInternalServerError
+ "failed to get session id from request cookie", "error fetching rooms for ad",
+ "error counting favorites", "error updating favorites count", "error creating room", "error parsing date",
+ "adAuthor is nil", "ad is nil":
+ statusCode = http.StatusInternalServerError
default:
- w.WriteHeader(http.StatusInternalServerError)
- status = http.StatusInternalServerError
+ statusCode = http.StatusInternalServerError
}
- if jsonErr := json.NewEncoder(w).Encode(errorResponse); jsonErr != nil {
+ w.WriteHeader(statusCode)
+ if _, jsonErr := easyjson.MarshalToWriter(&errorResponse, w); jsonErr != nil {
logger.AccessLogger.Error("Failed to encode error response",
zap.String("request_id", requestID),
zap.Error(jsonErr),
@@ -977,5 +1134,5 @@ func (h *AdHandler) handleError(w http.ResponseWriter, err error, requestID stri
http.Error(w, jsonErr.Error(), http.StatusInternalServerError)
}
- return status
+ return statusCode
}
diff --git a/internal/ads/controller/ads_controller_test.go b/internal/ads/controller/ads_controller_test.go
index 9c4b4d7..136aa54 100644
--- a/internal/ads/controller/ads_controller_test.go
+++ b/internal/ads/controller/ads_controller_test.go
@@ -2,604 +2,1672 @@ package controller
import (
"2024_2_FIGHT-CLUB/domain"
- "2024_2_FIGHT-CLUB/internal/ads/mocks"
"2024_2_FIGHT-CLUB/internal/service/logger"
- "2024_2_FIGHT-CLUB/internal/service/middleware"
+ "2024_2_FIGHT-CLUB/internal/service/utils"
+ "2024_2_FIGHT-CLUB/microservices/ads_service/controller/gen"
+ "2024_2_FIGHT-CLUB/microservices/ads_service/mocks"
"bytes"
"context"
"encoding/json"
"errors"
- "fmt"
"github.com/gorilla/mux"
"github.com/stretchr/testify/assert"
- "log"
+ "github.com/stretchr/testify/mock"
+ "github.com/stretchr/testify/require"
+ "google.golang.org/grpc/codes"
+ "google.golang.org/grpc/status"
+ "io"
"mime/multipart"
"net/http"
"net/http/httptest"
"testing"
- "time"
)
-func TestAdHandler_GetAllPlaces(t *testing.T) {
+func TestAdHandler_GetAllPlaces_Success(t *testing.T) {
// Инициализация логгера
- if err := logger.InitLoggers(); err != nil {
- log.Fatalf("Failed to initialize loggers: %v", err)
- }
- defer logger.SyncLoggers()
-
- // Создание мока use case и других зависимостей
- mockUseCase := &mocks.MockAdUseCase{}
- mockSession := &mocks.MockServiceSession{}
- mockJwtToken := &mocks.MockJwtTokenService{}
- handler := NewAdHandler(mockUseCase, mockSession, mockJwtToken)
-
- // Определение фильтра и ожидаемых результатов
- filter := domain.AdFilter{
- Location: "Test City",
- Rating: "5",
- NewThisWeek: "true",
- HostGender: "any",
- GuestCount: "2",
+ require.NoError(t, logger.InitLoggers())
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
+
+ mockGrpcClient := new(mocks.MockGrpcClient)
+ testResponse := &gen.GetAllAdsResponseList{}
+ mockGrpcClient.On("GetAllPlaces", mock.Anything, mock.Anything, mock.Anything).Return(testResponse, nil)
+ response := &gen.GetAllAdsResponseList{}
+ utilsMock := &utils.MockUtils{}
+ utilsMock.On("ConvertGetAllAdsResponseProtoToGo", response).
+ Return(nil, nil)
+
+ adHandler := &AdHandler{
+ client: mockGrpcClient,
+ utils: utilsMock,
}
- expectedAds := []domain.GetAllAdsResponse{
- {
- UUID: "1",
- Cityname: "Test City",
- Address: "test address",
- RoomsNumber: 10,
- },
+ req := httptest.NewRequest(http.MethodGet, "/housing?location=test", nil)
+ req.Header.Set("X-Real-IP", "127.0.0.1")
+ w := httptest.NewRecorder()
+
+ adHandler.GetAllPlaces(w, req)
+
+ result := w.Result()
+ defer func(Body io.ReadCloser) {
+ err := Body.Close()
+ if err != nil {
+ return
+ }
+ }(result.Body)
+
+ assert.Equal(t, http.StatusOK, result.StatusCode)
+ mockGrpcClient.AssertExpectations(t)
+}
+
+func TestAdHandler_GetAllPlaces_Error(t *testing.T) {
+ require.NoError(t, logger.InitLoggers())
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
+ grpcErr := status.Error(codes.Internal, "Simulated gRPC Error")
+
+ mockGrpcClient := new(mocks.MockGrpcClient)
+ mockGrpcClient.On("GetAllPlaces", mock.Anything, mock.Anything, mock.Anything).
+ Return((*gen.GetAllAdsResponseList)(nil), grpcErr)
+
+ adHandler := &AdHandler{
+ client: mockGrpcClient,
}
- mockUseCase.MockGetAllPlaces = func(ctx context.Context, f domain.AdFilter) ([]domain.GetAllAdsResponse, error) {
- assert.Equal(t, filter, f, "Filter should match")
- return expectedAds, nil
+ req := httptest.NewRequest(http.MethodGet, "/housing", nil)
+ req.Header.Set("X-Real-IP", "127.0.0.1")
+ w := httptest.NewRecorder()
+
+ adHandler.GetAllPlaces(w, req)
+
+ result := w.Result()
+ defer func(Body io.ReadCloser) {
+ err := Body.Close()
+ if err != nil {
+ return
+ }
+ }(result.Body)
+
+ assert.Equal(t, http.StatusInternalServerError, result.StatusCode)
+ mockGrpcClient.AssertExpectations(t)
+}
+
+func TestAdHandler_GetAllPlaces_ConvertError(t *testing.T) {
+ require.NoError(t, logger.InitLoggers())
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
+ response := &gen.GetAllAdsResponseList{}
+ mockGrpcClient := new(mocks.MockGrpcClient)
+ mockGrpcClient.On("GetAllPlaces", mock.Anything, mock.Anything, mock.Anything).
+ Return(response, nil)
+
+ utilsMock := &utils.MockUtils{}
+ utilsMock.On("ConvertGetAllAdsResponseProtoToGo", response).
+ Return(nil, errors.New("conversion error"))
+
+ adHandler := &AdHandler{
+ client: mockGrpcClient,
+ utils: utilsMock,
}
- req, err := http.NewRequest("GET", "/api/ads/?location=Test+City&rating=5&new=true&gender=any&guests=2", nil)
- assert.NoError(t, err)
+ req := httptest.NewRequest(http.MethodGet, "/housing", nil)
+ w := httptest.NewRecorder()
- rr := httptest.NewRecorder()
+ // Выполнение метода
+ adHandler.GetAllPlaces(w, req)
- handler.GetAllPlaces(rr, req)
+ // Проверка результата
+ result := w.Result()
+ defer func(Body io.ReadCloser) {
+ err := Body.Close()
+ if err != nil {
+ return
+ }
+ }(result.Body)
+
+ assert.Equal(t, http.StatusInternalServerError, result.StatusCode)
+ mockGrpcClient.AssertExpectations(t)
+ utilsMock.AssertExpectations(t)
+}
- assert.Equal(t, http.StatusOK, rr.Code, "Expected status OK")
+func TestAdHandler_GetOnePlace_Success(t *testing.T) {
+ // Инициализация логгера
+ require.NoError(t, logger.InitLoggers())
+ defer func() {
+ require.NoError(t, logger.SyncLoggers())
+ }()
+
+ mockGrpcClient := new(mocks.MockGrpcClient)
+ mockSessionService := &mocks.MockServiceSession{}
+ mockUtils := new(utils.MockUtils)
- var response map[string]interface{}
- err = json.Unmarshal(rr.Body.Bytes(), &response)
- assert.NoError(t, err)
+ testResponse := &gen.GetAllAdsResponse{}
+ mockGrpcClient.On("GetOnePlace", mock.Anything, mock.Anything, mock.Anything).Return(testResponse, nil)
- places, ok := response["places"].([]interface{})
- assert.True(t, ok, "Expected 'places' field in response")
+ convertedResponse := &domain.GetAllAdsResponse{}
+ mockUtils.On("ConvertAdProtoToGo", testResponse).Return(convertedResponse, nil)
- assert.Equal(t, len(expectedAds), len(places), "Returned places count should match expected")
+ mockSessionService.MockGetUserID = func(ctx context.Context, sessionID string) (string, error) {
+ return "123", nil
+ }
- mockUseCase.MockGetAllPlaces = func(ctx context.Context, f domain.AdFilter) ([]domain.GetAllAdsResponse, error) {
- return nil, fmt.Errorf("server error")
+ adHandler := &AdHandler{
+ client: mockGrpcClient,
+ sessionService: mockSessionService,
+ utils: mockUtils,
}
- rr = httptest.NewRecorder()
- handler.GetAllPlaces(rr, req)
+ req := httptest.NewRequest(http.MethodGet, "/housing/123", nil)
+ req = mux.SetURLVars(req, map[string]string{"adId": "123"})
+ req.Header.Set("X-Real-IP", "127.0.0.1")
+ w := httptest.NewRecorder()
- assert.Equal(t, http.StatusInternalServerError, rr.Code, "Expected status 500")
+ adHandler.GetOnePlace(w, req)
+
+ result := w.Result()
+ defer func(Body io.ReadCloser) {
+ err := Body.Close()
+ if err != nil {
+ return
+ }
+ }(result.Body)
+
+ assert.Equal(t, http.StatusOK, result.StatusCode)
+ mockGrpcClient.AssertExpectations(t)
+ mockUtils.AssertExpectations(t)
}
-func TestAdHandler_GetOnePlace(t *testing.T) {
- if err := logger.InitLoggers(); err != nil {
- log.Fatalf("Failed to initialize loggers: %v", err)
- }
- defer logger.SyncLoggers()
-
- mockUseCase := &mocks.MockAdUseCase{}
- mockSession := &mocks.MockServiceSession{}
- mockJwtToken := &mocks.MockJwtTokenService{}
- handler := NewAdHandler(mockUseCase, mockSession, mockJwtToken)
-
- adId := "1"
- expectedAd := domain.GetAllAdsResponse{
- UUID: "1",
- Cityname: "Test City",
- Address: "test address",
- RoomsNumber: 10,
+func TestAdHandler_GetOnePlace_GrpcError(t *testing.T) {
+ require.NoError(t, logger.InitLoggers())
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
+
+ mockGrpcClient := new(mocks.MockGrpcClient)
+ mockSessionService := &mocks.MockServiceSession{}
+
+ grpcErr := status.Error(codes.Internal, "Simulated gRPC Error")
+ mockGrpcClient.On("GetOnePlace", mock.Anything, mock.Anything, mock.Anything).Return((*gen.GetAllAdsResponse)(nil), grpcErr)
+
+ mockSessionService.MockGetUserID = func(ctx context.Context, sessionID string) (string, error) {
+ return "123", nil
}
- mockUseCase.MockGetOnePlace = func(ctx context.Context, id string) (domain.GetAllAdsResponse, error) {
- assert.Equal(t, adId, id, "Ad ID should match")
- return expectedAd, nil
+ adHandler := &AdHandler{
+ client: mockGrpcClient,
+ sessionService: mockSessionService,
}
- req, err := http.NewRequest("GET", "/api/ads/"+adId, nil)
- assert.NoError(t, err)
+ req := httptest.NewRequest(http.MethodGet, "/housing/123", nil)
+ req = mux.SetURLVars(req, map[string]string{"adId": "123"})
+ req.Header.Set("X-Real-IP", "127.0.0.1")
+ w := httptest.NewRecorder()
+
+ adHandler.GetOnePlace(w, req)
- req = mux.SetURLVars(req, map[string]string{"adId": adId})
+ result := w.Result()
+ defer func(Body io.ReadCloser) {
+ err := Body.Close()
+ if err != nil {
+ return
+ }
+ }(result.Body)
+
+ assert.Equal(t, http.StatusInternalServerError, result.StatusCode)
+ mockGrpcClient.AssertExpectations(t)
+}
- rr := httptest.NewRecorder()
+func TestAdHandler_GetOnePlace_ConvertError(t *testing.T) {
+ require.NoError(t, logger.InitLoggers())
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
- handler.GetOnePlace(rr, req)
+ mockGrpcClient := new(mocks.MockGrpcClient)
+ mockSessionService := &mocks.MockServiceSession{}
+ mockUtils := new(utils.MockUtils)
- assert.Equal(t, http.StatusOK, rr.Code, "Expected status OK")
+ testResponse := &gen.GetAllAdsResponse{}
+ mockGrpcClient.On("GetOnePlace", mock.Anything, mock.Anything, mock.Anything).Return(testResponse, nil)
- var response map[string]domain.GetAllAdsResponse
- err = json.Unmarshal(rr.Body.Bytes(), &response)
- assert.NoError(t, err)
+ mockUtils.On("ConvertAdProtoToGo", testResponse).Return(nil, errors.New("conversion error"))
- assert.Equal(t, expectedAd, response["place"], "Returned place should match expected")
+ mockSessionService.MockGetUserID = func(ctx context.Context, sessionID string) (string, error) {
+ return "123", nil
+ }
- mockUseCase.MockGetOnePlace = func(ctx context.Context, id string) (domain.GetAllAdsResponse, error) {
- return domain.GetAllAdsResponse{}, fmt.Errorf("place not found")
+ adHandler := &AdHandler{
+ client: mockGrpcClient,
+ sessionService: mockSessionService,
+ utils: mockUtils,
}
- rr = httptest.NewRecorder()
- handler.GetOnePlace(rr, req)
+ req := httptest.NewRequest(http.MethodGet, "/housing/123", nil)
+ req = mux.SetURLVars(req, map[string]string{"adId": "123"})
+ req.Header.Set("X-Real-IP", "127.0.0.1")
+ w := httptest.NewRecorder()
+
+ adHandler.GetOnePlace(w, req)
- assert.Equal(t, http.StatusInternalServerError, rr.Code, "Expected status 500")
+ result := w.Result()
+ defer func(Body io.ReadCloser) {
+ err := Body.Close()
+ if err != nil {
+ return
+ }
+ }(result.Body)
+
+ assert.Equal(t, http.StatusInternalServerError, result.StatusCode)
+ mockGrpcClient.AssertExpectations(t)
+ mockUtils.AssertExpectations(t)
}
-func TestAdHandler_CreatePlace(t *testing.T) {
- // Инициализация логгера
- if err := logger.InitLoggers(); err != nil {
- log.Fatalf("Failed to initialize loggers: %v", err)
+func TestAdHandler_CreatePlace_Success(t *testing.T) {
+ require.NoError(t, logger.InitLoggers())
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
+ mockClient := new(mocks.MockGrpcClient)
+ handler := &AdHandler{
+ client: mockClient,
+ }
+
+ // Создаем multipart запрос с метаданными и файлами
+ body := new(bytes.Buffer)
+ writer := multipart.NewWriter(body)
+
+ // Добавляем JSON metadata
+ metadata := `{
+ "CityName": "TestCity",
+ "Description": "Test Description",
+ "Address": "Test Address",
+ "RoomsNumber": 2,
+ "SquareMeters": 60,
+ "Floor": 3,
+ "BuildingType": "Apartment",
+ "HasBalcony": true
+ }`
+ _ = writer.WriteField("metadata", metadata)
+
+ part, _ := writer.CreateFormFile("images", "image1.jpg")
+ _, err := part.Write([]byte("mock image data"))
+ if err != nil {
+ return
}
- defer logger.SyncLoggers()
-
- // Ручное создание мок объектов
- mockUseCase := &mocks.MockAdUseCase{}
- mockSession := &mocks.MockServiceSession{}
- mockJwtToken := &mocks.MockJwtTokenService{}
- handler := NewAdHandler(mockUseCase, mockSession, mockJwtToken)
-
- newAdRequest := domain.CreateAdRequest{
- CityName: "Test City",
- Address: "Test Street",
- Description: "Test Description",
- RoomsNumber: 3,
+
+ err = writer.Close()
+ if err != nil {
+ return
}
- testTime := time.Now()
+ req := httptest.NewRequest("POST", "/housing", body)
+ req.Header.Set("Content-Type", writer.FormDataContentType())
+ req.Header.Set("X-CSRF-Token", "test-token")
+ req.Header.Set("X-Real-IP", "127.0.0.1")
+ req.AddCookie(&http.Cookie{
+ Name: "session_id",
+ Value: "test-session-id",
+ })
+
+ w := httptest.NewRecorder()
+
+ mockClient.On("CreatePlace", mock.Anything, mock.Anything, mock.Anything).Return(&gen.Ad{}, nil)
+
+ handler.CreatePlace(w, req)
+
+ resp := w.Result()
+ defer func(Body io.ReadCloser) {
+ err := Body.Close()
+ if err != nil {
+ return
+ }
+ }(resp.Body)
+
+ require.Equal(t, http.StatusCreated, resp.StatusCode)
+ bodyResponse, _ := io.ReadAll(resp.Body)
+ require.Contains(t, string(bodyResponse), "Successfully created ad")
+
+ mockClient.AssertExpectations(t)
+}
- expectedAd := domain.Ad{
- UUID: "1",
- CityID: 123,
- AuthorUUID: "user123",
- Address: "Test Street",
- PublicationDate: testTime,
- Description: "Test Description",
- RoomsNumber: 3,
+func TestAdHandler_CreatePlace_FailedToReadFile(t *testing.T) {
+ require.NoError(t, logger.InitLoggers())
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
+ mockClient := new(mocks.MockGrpcClient)
+ handler := &AdHandler{
+ client: mockClient,
}
- // Мокаем метод CreatePlace
- mockUseCase.MockCreatePlace = func(ctx context.Context, ad *domain.Ad, files []*multipart.FileHeader, newPlace domain.CreateAdRequest) error {
- ad.UUID = expectedAd.UUID
- ad.CityID = expectedAd.CityID
- ad.AuthorUUID = expectedAd.AuthorUUID
- ad.Address = expectedAd.Address
- ad.PublicationDate = expectedAd.PublicationDate
- ad.Description = expectedAd.Description
- ad.RoomsNumber = expectedAd.RoomsNumber
- return nil
+ body := new(bytes.Buffer)
+ writer := multipart.NewWriter(body)
+ _, err := writer.CreateFormFile("images", "image1.jpg")
+ if err != nil {
+ return
}
- // Мокаем другие компоненты
- mockSession.MockGetUserID = func(ctx context.Context, r *http.Request) (string, error) {
- return "user123", nil
+ err = writer.Close()
+ if err != nil {
+ return
}
- mockJwtToken.MockValidate = func(tokenString string) (*middleware.JwtCsrfClaims, error) {
- if tokenString == "valid_token" {
- return &middleware.JwtCsrfClaims{}, nil
+ req := httptest.NewRequest("POST", "/ads/create", body)
+ req.Header.Set("Content-Type", writer.FormDataContentType())
+ req.AddCookie(&http.Cookie{
+ Name: "session_id",
+ Value: "test-session-id",
+ })
+
+ w := httptest.NewRecorder()
+
+ // Выполнение метода
+ handler.CreatePlace(w, req)
+
+ resp := w.Result()
+ defer func(Body io.ReadCloser) {
+ err := Body.Close()
+ if err != nil {
+ return
}
- return nil, fmt.Errorf("invalid token")
- }
+ }(resp.Body)
- metaData, err := json.Marshal(newAdRequest)
- assert.NoError(t, err)
+ require.Equal(t, http.StatusInternalServerError, resp.StatusCode)
+}
+
+func TestAdHandler_CreatePlace_CreatePlaceFailure(t *testing.T) {
+ require.NoError(t, logger.InitLoggers())
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
+ mockClient := new(mocks.MockGrpcClient)
+ handler := &AdHandler{
+ client: mockClient,
+ }
- body := &bytes.Buffer{}
+ // Создаем multipart запрос с метаданными и файлами
+ body := new(bytes.Buffer)
writer := multipart.NewWriter(body)
- _ = writer.WriteField("metadata", string(metaData))
- writer.Close()
+ _ = writer.WriteField("metadata", `{"CityName":"TestCity"}`)
+ part, _ := writer.CreateFormFile("images", "image1.jpg")
+ _, err := part.Write([]byte("mock image data"))
+ if err != nil {
+ return
+ }
+ err = writer.Close()
+ if err != nil {
+ return
+ }
- req, err := http.NewRequest("POST", "/api/ads", body)
- assert.NoError(t, err)
+ req := httptest.NewRequest("POST", "/ads/create", body)
req.Header.Set("Content-Type", writer.FormDataContentType())
- req.Header.Set("X-CSRF-Token", "bearer valid_token")
+ req.AddCookie(&http.Cookie{
+ Name: "session_id",
+ Value: "test-session-id",
+ })
- rr := httptest.NewRecorder()
+ w := httptest.NewRecorder()
+ grpcErr := status.Error(codes.Internal, "Simulated gRPC Error")
+ mockClient.On("CreatePlace", mock.Anything, mock.Anything, mock.Anything).Return(&gen.Ad{}, grpcErr)
- handler.CreatePlace(rr, req)
+ handler.CreatePlace(w, req)
- assert.Equal(t, http.StatusOK, rr.Code, "Expected status OK")
+ resp := w.Result()
+ defer func(Body io.ReadCloser) {
+ err := Body.Close()
+ if err != nil {
+ return
+ }
+ }(resp.Body)
- var response map[string]domain.Ad
- err = json.Unmarshal(rr.Body.Bytes(), &response)
- assert.NoError(t, err)
+ require.Equal(t, http.StatusInternalServerError, resp.StatusCode)
+ mockClient.AssertExpectations(t)
+}
- // Используем сравнение без учёта времени
- if responseAd, ok := response["place"]; ok {
- assert.Equal(t, expectedAd.UUID, responseAd.UUID)
- assert.Equal(t, expectedAd.CityID, responseAd.CityID)
- assert.Equal(t, expectedAd.AuthorUUID, responseAd.AuthorUUID)
- assert.Equal(t, expectedAd.Address, responseAd.Address)
- assert.Equal(t, expectedAd.Description, responseAd.Description)
- assert.Equal(t, expectedAd.RoomsNumber, responseAd.RoomsNumber)
- } else {
- t.FailNow()
+func TestAdHandler_CreatePlace_NoCookie(t *testing.T) {
+ require.NoError(t, logger.InitLoggers())
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
+ mockClient := new(mocks.MockGrpcClient)
+ handler := &AdHandler{
+ client: mockClient,
}
- // Тест на случай ошибки в создании
- mockUseCase.MockCreatePlace = func(ctx context.Context, ad *domain.Ad, files []*multipart.FileHeader, newPlace domain.CreateAdRequest) error {
- return fmt.Errorf("could not create place")
+ // Создаем multipart запрос с метаданными и файлами
+ body := new(bytes.Buffer)
+ writer := multipart.NewWriter(body)
+ _ = writer.WriteField("metadata", `{"CityName":"TestCity"}`)
+ part, _ := writer.CreateFormFile("images", "image1.jpg")
+ _, err := part.Write([]byte("mock image data"))
+ if err != nil {
+ return
+ }
+ err = writer.Close()
+ if err != nil {
+ return
}
- rr = httptest.NewRecorder()
- handler.CreatePlace(rr, req)
+ req := httptest.NewRequest("POST", "/ads/create", body)
+ req.Header.Set("Content-Type", writer.FormDataContentType())
+
+ w := httptest.NewRecorder()
+
+ handler.CreatePlace(w, req)
- assert.Equal(t, http.StatusInternalServerError, rr.Code, "Expected status 500")
+ resp := w.Result()
+ defer func(Body io.ReadCloser) {
+ err := Body.Close()
+ if err != nil {
+ return
+ }
+ }(resp.Body)
+
+ require.Equal(t, http.StatusInternalServerError, resp.StatusCode)
+ mockClient.AssertExpectations(t)
}
-func TestAdHandler_UpdatePlace(t *testing.T) {
- // Инициализация логгеров
- if err := logger.InitLoggers(); err != nil {
- log.Fatalf("Failed to initialize loggers: %v", err)
- }
- defer logger.SyncLoggers()
+func TestAdHandler_UpdatePlace_Success(t *testing.T) {
+ require.NoError(t, logger.InitLoggers())
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
- // Ручное создание мок-объектов для теста
- mockUseCase := &mocks.MockAdUseCase{}
- mockSession := &mocks.MockServiceSession{}
- mockJwtToken := &mocks.MockJwtTokenService{}
+ mockClient := new(mocks.MockGrpcClient)
+ handler := &AdHandler{client: mockClient}
- handler := NewAdHandler(mockUseCase, mockSession, mockJwtToken)
+ body := new(bytes.Buffer)
+ writer := multipart.NewWriter(body)
- adId := "1"
- updatedAdRequest := domain.UpdateAdRequest{
- CityName: "Updated City",
- Address: "Updated Street",
- Description: "Updated Description",
- RoomsNumber: 3,
- }
+ metadata := `{
+ "CityName": "TestCity",
+ "Description": "Updated Description",
+ "Address": "Test Address",
+ "RoomsNumber": 3,
+ "SquareMeters": 70,
+ "Floor": 4,
+ "BuildingType": "Apartment",
+ "HasBalcony": false
+ }`
- mockUseCase.MockUpdatePlace = func(ctx context.Context, ad *domain.Ad, id string, userID string, files []*multipart.FileHeader, updatedPlace domain.UpdateAdRequest) error {
- assert.Equal(t, adId, id, "Ad ID должен совпадать")
- assert.Equal(t, "user123", userID, "User ID должен совпадать")
- assert.Equal(t, updatedAdRequest, updatedPlace, "Обновленный ад должен совпадать с ожидаемым")
- return nil
- }
+ _ = writer.WriteField("metadata", metadata)
- mockSession.MockGetUserID = func(ctx context.Context, r *http.Request) (string, error) {
- return "user123", nil
- }
+ part, _ := writer.CreateFormFile("images", "image1.jpg")
+ _, err := part.Write([]byte("mock image data"))
+ require.NoError(t, err)
+
+ err = writer.Close()
+ require.NoError(t, err)
+
+ req := httptest.NewRequest("PUT", "/housing/123", body)
+ req.Header.Set("Content-Type", writer.FormDataContentType())
+ req.Header.Set("X-CSRF-Token", "test-token")
+ req.Header.Set("X-Real-IP", "127.0.0.1")
+ req.AddCookie(&http.Cookie{Name: "session_id", Value: "test-session-id"})
+
+ w := httptest.NewRecorder()
+
+ mockClient.On("UpdatePlace", mock.Anything, mock.Anything, mock.Anything).Return(&gen.AdResponse{}, nil)
+
+ handler.UpdatePlace(w, req)
+
+ resp := w.Result()
+ defer resp.Body.Close()
- mockJwtToken.MockValidate = func(tokenString string) (*middleware.JwtCsrfClaims, error) {
- if tokenString == "valid_token" {
- return &middleware.JwtCsrfClaims{}, nil
+ require.Equal(t, http.StatusOK, resp.StatusCode)
+ bodyResponse, _ := io.ReadAll(resp.Body)
+ require.Contains(t, string(bodyResponse), "Successfully updated ad")
+
+ mockClient.AssertExpectations(t)
+}
+
+func TestAdHandler_UpdatePlace_ParseMultipartError(t *testing.T) {
+ require.NoError(t, logger.InitLoggers())
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
}
- return nil, fmt.Errorf("invalid token")
- }
+ }()
+
+ handler := &AdHandler{client: new(mocks.MockGrpcClient)}
+
+ req := httptest.NewRequest("PUT", "/housing/123", bytes.NewBufferString("invalid body"))
+ req.Header.Set("Content-Type", "multipart/form-data")
+
+ w := httptest.NewRecorder()
+
+ handler.UpdatePlace(w, req)
+
+ resp := w.Result()
+ defer func(Body io.ReadCloser) {
+ err := Body.Close()
+ if err != nil {
+ return
+ }
+ }(resp.Body)
+
+ require.Equal(t, http.StatusBadRequest, resp.StatusCode)
+}
- metaData, err := json.Marshal(updatedAdRequest)
- assert.NoError(t, err)
+func TestAdHandler_UpdatePlace_MetadataDecodeError(t *testing.T) {
+ require.NoError(t, logger.InitLoggers())
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
+
+ handler := &AdHandler{client: new(mocks.MockGrpcClient)}
- body := &bytes.Buffer{}
+ body := new(bytes.Buffer)
writer := multipart.NewWriter(body)
- writer.WriteField("metadata", string(metaData))
- writer.Close()
+ _ = writer.WriteField("metadata", "invalid json")
+
+ _ = writer.Close()
- req, err := http.NewRequest("PUT", "/api/ads/"+adId, body)
- assert.NoError(t, err)
- req = mux.SetURLVars(req, map[string]string{"adId": adId})
+ req := httptest.NewRequest("PUT", "/housing/123", body)
req.Header.Set("Content-Type", writer.FormDataContentType())
- req.Header.Set("X-CSRF-Token", "Bearer valid_token")
+ req.AddCookie(&http.Cookie{Name: "session_id", Value: "test-session-id"})
+
+ w := httptest.NewRecorder()
+
+ handler.UpdatePlace(w, req)
+
+ resp := w.Result()
+ defer func(Body io.ReadCloser) {
+ err := Body.Close()
+ if err != nil {
+ return
+ }
+ }(resp.Body)
- rr := httptest.NewRecorder()
+ require.Equal(t, http.StatusBadRequest, resp.StatusCode)
+}
- handler.UpdatePlace(rr, req)
+func TestAdHandler_UpdatePlace_SessionIDError(t *testing.T) {
+ require.NoError(t, logger.InitLoggers())
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
- assert.Equal(t, http.StatusOK, rr.Code, "Expected status OK")
+ handler := &AdHandler{client: new(mocks.MockGrpcClient)}
- var response map[string]string
- err = json.Unmarshal(rr.Body.Bytes(), &response)
- assert.NoError(t, err)
+ body := new(bytes.Buffer)
+ writer := multipart.NewWriter(body)
+ _ = writer.WriteField("metadata", "{}")
+ _ = writer.Close()
- assert.Equal(t, "Update successfully", response["response"], "Response message должен совпадать с ожидаемым")
+ req := httptest.NewRequest("PUT", "/housing/123", body)
+ req.Header.Set("Content-Type", writer.FormDataContentType())
- // Проверка на случай ошибки в обновлении
- mockUseCase.MockUpdatePlace = func(ctx context.Context, ad *domain.Ad, id string, userID string, files []*multipart.FileHeader, updatedPlace domain.UpdateAdRequest) error {
- return fmt.Errorf("could not update place")
- }
+ w := httptest.NewRecorder()
- rr = httptest.NewRecorder()
- handler.UpdatePlace(rr, req)
- assert.Equal(t, http.StatusInternalServerError, rr.Code, "Expected status 500")
+ handler.UpdatePlace(w, req)
+
+ resp := w.Result()
+ defer func(Body io.ReadCloser) {
+ err := Body.Close()
+ if err != nil {
+ return
+ }
+ }(resp.Body)
+
+ require.Equal(t, http.StatusInternalServerError, resp.StatusCode)
}
-func TestAdHandler_DeletePlace(t *testing.T) {
- // Инициализация логгеров
- if err := logger.InitLoggers(); err != nil {
- log.Fatalf("Failed to initialize loggers: %v", err)
- }
- defer logger.SyncLoggers()
+func TestAdHandler_UpdatePlace_GRPCError(t *testing.T) {
+ require.NoError(t, logger.InitLoggers())
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
+
+ mockClient := new(mocks.MockGrpcClient)
+ handler := &AdHandler{client: mockClient}
- // Ручное создание мок-объектов
- mockUseCase := &mocks.MockAdUseCase{}
- mockSession := &mocks.MockServiceSession{}
- mockJwtToken := &mocks.MockJwtTokenService{}
+ body := new(bytes.Buffer)
+ writer := multipart.NewWriter(body)
- handler := NewAdHandler(mockUseCase, mockSession, mockJwtToken)
+ metadata := `{"CityName": "TestCity"}`
+ _ = writer.WriteField("metadata", metadata)
+ _ = writer.Close()
- adId := "1"
+ req := httptest.NewRequest("PUT", "/housing/123", body)
+ req.Header.Set("Content-Type", writer.FormDataContentType())
+ req.AddCookie(&http.Cookie{Name: "session_id", Value: "test-session-id"})
- mockUseCase.MockDeletePlace = func(ctx context.Context, id string, userID string) error {
- assert.Equal(t, adId, id, "Ad ID должен совпадать")
- assert.Equal(t, "user123", userID, "User ID должен совпадать")
- return nil
- }
+ w := httptest.NewRecorder()
+ grpcErr := status.Error(codes.Internal, "Simulated gRPC Error")
+ mockClient.On("UpdatePlace", mock.Anything, mock.Anything, mock.Anything).Return(&gen.AdResponse{}, grpcErr)
- mockSession.MockGetUserID = func(ctx context.Context, r *http.Request) (string, error) {
- return "user123", nil
- }
+ handler.UpdatePlace(w, req)
- mockJwtToken.MockValidate = func(tokenString string) (*middleware.JwtCsrfClaims, error) {
- if tokenString == "valid_token" {
- return &middleware.JwtCsrfClaims{}, nil
+ resp := w.Result()
+ defer func(Body io.ReadCloser) {
+ err := Body.Close()
+ if err != nil {
+ return
}
- return nil, fmt.Errorf("invalid token")
- }
+ }(resp.Body)
+
+ require.Equal(t, http.StatusInternalServerError, resp.StatusCode)
+}
- req, err := http.NewRequest("DELETE", "/api/ads/"+adId, nil)
- assert.NoError(t, err)
- req = mux.SetURLVars(req, map[string]string{"adId": adId})
- req.Header.Set("X-CSRF-Token", "Bearer valid_token")
+func TestAdHandler_DeletePlace_Success(t *testing.T) {
+ require.NoError(t, logger.InitLoggers())
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
- rr := httptest.NewRecorder()
+ mockClient := new(mocks.MockGrpcClient)
+ handler := &AdHandler{client: mockClient}
- handler.DeletePlace(rr, req)
+ req := httptest.NewRequest("DELETE", "/housing/123", nil)
+ req.Header.Set("X-CSRF-Token", "test-token")
+ req.AddCookie(&http.Cookie{Name: "session_id", Value: "test-session-id"})
- assert.Equal(t, http.StatusOK, rr.Code, "Expected status OK")
+ w := httptest.NewRecorder()
- var response map[string]string
- err = json.Unmarshal(rr.Body.Bytes(), &response)
- assert.NoError(t, err)
+ mockClient.On("DeletePlace", mock.Anything, mock.Anything, mock.Anything).Return(&gen.DeleteResponse{}, nil)
- assert.Equal(t, "Delete successfully", response["response"], "Response message должен совпадать с ожидаемым")
+ handler.DeletePlace(w, req)
- // Проверка на случай ошибки в удалении
- mockUseCase.MockDeletePlace = func(ctx context.Context, id string, userID string) error {
- return fmt.Errorf("could not delete place")
- }
+ resp := w.Result()
+ defer func(Body io.ReadCloser) {
+ err := Body.Close()
+ if err != nil {
+ return
+ }
+ }(resp.Body)
+
+ require.Equal(t, http.StatusOK, resp.StatusCode)
+ require.Contains(t, w.Body.String(), "Successfully deleted place")
- rr = httptest.NewRecorder()
- handler.DeletePlace(rr, req)
- assert.Equal(t, http.StatusInternalServerError, rr.Code, "Expected status 500")
+ mockClient.AssertExpectations(t)
}
-func TestAdHandler_GetPlacesPerCity(t *testing.T) {
- // Инициализация логгеров
- if err := logger.InitLoggers(); err != nil {
- log.Fatalf("Failed to initialize loggers: %v", err)
- }
- defer logger.SyncLoggers()
+func TestAdHandler_DeletePlace_FailedToGetSessionID(t *testing.T) {
+ require.NoError(t, logger.InitLoggers())
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
+ mockClient := new(mocks.MockGrpcClient)
+ handler := &AdHandler{client: mockClient}
- // Ручное создание мок-объектов
- mockUseCase := &mocks.MockAdUseCase{}
+ req := httptest.NewRequest("DELETE", "/housing/123", nil)
+ req.Header.Set("X-CSRF-Token", "test-token")
- handler := NewAdHandler(mockUseCase, nil, nil)
+ w := httptest.NewRecorder()
- cityName := "Test City"
- expectedAds := []domain.GetAllAdsResponse{
- {UUID: "1", Address: "Test Street", RoomsNumber: 10},
- {UUID: "2", Address: "Test Street2", RoomsNumber: 12},
- }
- // Успешный сценарий
- mockUseCase.MockGetPlacesPerCity = func(ctx context.Context, city string) ([]domain.GetAllAdsResponse, error) {
- assert.Equal(t, cityName, city, "City name должен совпадать")
- return expectedAds, nil
- }
+ handler.DeletePlace(w, req)
- req, err := http.NewRequest("GET", "/api/ads/city/"+cityName, nil)
- assert.NoError(t, err)
- req = mux.SetURLVars(req, map[string]string{"city": cityName})
+ resp := w.Result()
+ defer func(Body io.ReadCloser) {
+ err := Body.Close()
+ if err != nil {
+ return
+ }
+ }(resp.Body)
- rr := httptest.NewRecorder()
- handler.GetPlacesPerCity(rr, req)
+ require.Equal(t, http.StatusInternalServerError, resp.StatusCode)
+ require.Contains(t, w.Body.String(), "failed to get session id from request cookie")
- assert.Equal(t, http.StatusOK, rr.Code, "Expected status OK")
+ mockClient.AssertExpectations(t)
+}
- var response map[string][]domain.GetAllAdsResponse
- err = json.Unmarshal(rr.Body.Bytes(), &response)
- assert.NoError(t, err)
- assert.Equal(t, expectedAds, response["places"], "Returned places should match expected")
+func TestAdHandler_DeletePlace_ClientError(t *testing.T) {
+ require.NoError(t, logger.InitLoggers())
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
+ mockClient := new(mocks.MockGrpcClient)
+ handler := &AdHandler{client: mockClient}
- // Ошибочный сценарий
- mockUseCase.MockGetPlacesPerCity = func(ctx context.Context, city string) ([]domain.GetAllAdsResponse, error) {
- return nil, fmt.Errorf("could not get places")
- }
+ req := httptest.NewRequest("DELETE", "/housing/123", nil)
+ req.Header.Set("X-CSRF-Token", "test-token")
+ req.AddCookie(&http.Cookie{Name: "session_id", Value: "test-session-id"})
+
+ w := httptest.NewRecorder()
+ grpcErr := status.Error(codes.Internal, "failed to delete place")
+ mockClient.On("DeletePlace", mock.Anything, mock.Anything, mock.Anything).Return(&gen.DeleteResponse{}, grpcErr)
+
+ handler.DeletePlace(w, req)
- rr = httptest.NewRecorder()
- handler.GetPlacesPerCity(rr, req)
+ resp := w.Result()
+ defer func(Body io.ReadCloser) {
+ err := Body.Close()
+ if err != nil {
+ return
+ }
+ }(resp.Body)
+
+ require.Equal(t, http.StatusInternalServerError, resp.StatusCode)
+ require.Contains(t, w.Body.String(), "failed to delete place")
- assert.Equal(t, http.StatusInternalServerError, rr.Code, "Expected status 500")
+ mockClient.AssertExpectations(t)
}
-func TestAdHandler_GetUserPlaces(t *testing.T) {
- // Инициализация логгеров
- if err := logger.InitLoggers(); err != nil {
- log.Fatalf("Failed to initialize loggers: %v", err)
- }
- defer logger.SyncLoggers()
+func TestAdHandler_GetPlacesPerCity_Success(t *testing.T) {
+ require.NoError(t, logger.InitLoggers())
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
- // Ручное создание мок-объектов
- mockUseCase := &mocks.MockAdUseCase{}
+ mockClient := new(mocks.MockGrpcClient)
+ utilsMock := &utils.MockUtils{}
+ handler := &AdHandler{client: mockClient, utils: utilsMock}
- handler := NewAdHandler(mockUseCase, nil, nil)
+ req := httptest.NewRequest("GET", "/housing/city/TestCity", nil)
+ req.Header.Set("X-CSRF-Token", "test-token")
- expectedUserId := "user123"
- expectedAds := []domain.GetAllAdsResponse{
- {UUID: "1", Address: "Test Street", RoomsNumber: 10},
- {UUID: "2", Address: "Test Street2", RoomsNumber: 12},
- }
+ w := httptest.NewRecorder()
- // Успешный сценарий
- mockUseCase.MockGetUserPlaces = func(ctx context.Context, userId string) ([]domain.GetAllAdsResponse, error) {
- assert.Equal(t, expectedUserId, userId, "User ID должен совпадать")
- return expectedAds, nil
- }
+ mockClient.On("GetPlacesPerCity", mock.Anything, mock.Anything, mock.Anything).
+ Return(&gen.GetAllAdsResponseList{}, nil)
- req, err := http.NewRequest("GET", "/api/users/"+expectedUserId+"/ads", nil)
- assert.NoError(t, err)
- req = mux.SetURLVars(req, map[string]string{"userId": expectedUserId})
+ utilsMock.On("ConvertGetAllAdsResponseProtoToGo", mock.Anything).
+ Return([]domain.GetAllAdsListResponse{}, nil)
- rr := httptest.NewRecorder()
- handler.GetUserPlaces(rr, req)
+ handler.GetPlacesPerCity(w, req)
- assert.Equal(t, http.StatusOK, rr.Code, "Expected status OK")
+ result := w.Result()
+ defer func(Body io.ReadCloser) {
+ err := Body.Close()
+ if err != nil {
+ return
+ }
+ }(result.Body)
- var response map[string][]domain.GetAllAdsResponse
- err = json.Unmarshal(rr.Body.Bytes(), &response)
- assert.NoError(t, err)
- assert.Equal(t, expectedAds, response["places"], "Returned places should match expected")
+ assert.Equal(t, http.StatusOK, result.StatusCode)
- // Ошибочный сценарий
- mockUseCase.MockGetUserPlaces = func(ctx context.Context, userId string) ([]domain.GetAllAdsResponse, error) {
- return nil, fmt.Errorf("could not get places")
- }
+ mockClient.AssertExpectations(t)
+ utilsMock.AssertExpectations(t)
+}
- rr = httptest.NewRecorder()
- handler.GetUserPlaces(rr, req)
+func TestAdHandler_GetPlacesPerCity_GrpcError(t *testing.T) {
+ require.NoError(t, logger.InitLoggers())
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
+
+ mockClient := new(mocks.MockGrpcClient)
+ handler := &AdHandler{client: mockClient}
+
+ req := httptest.NewRequest("GET", "/housing/city/TestCity", nil)
+ req.Header.Set("X-CSRF-Token", "test-token")
+
+ w := httptest.NewRecorder()
+ grpcError := status.Error(codes.Internal, "failed to get place per city")
+ mockClient.On("GetPlacesPerCity", mock.Anything, mock.Anything, mock.Anything).
+ Return(&gen.GetAllAdsResponseList{}, grpcError)
+
+ handler.GetPlacesPerCity(w, req)
+
+ result := w.Result()
+ defer func(Body io.ReadCloser) {
+ err := Body.Close()
+ if err != nil {
+ return
+ }
+ }(result.Body)
- assert.Equal(t, http.StatusInternalServerError, rr.Code, "Expected status 500")
+ assert.Equal(t, http.StatusInternalServerError, result.StatusCode)
+ mockClient.AssertExpectations(t)
}
-func TestDeleteAdImage(t *testing.T) {
- if err := logger.InitLoggers(); err != nil {
- log.Fatalf("Failed to initialize loggers: %v", err)
- }
- defer logger.SyncLoggers()
-
- // Создаем экземпляры моков
- mockJWT := &mocks.MockJwtTokenService{}
- mockSession := &mocks.MockServiceSession{}
- mockAdUseCase := &mocks.MockAdUseCase{}
- handler := NewAdHandler(mockAdUseCase, mockSession, mockJWT)
-
- // Создаем стандартные параметры
- adId := "ad-uuid"
- imageId := "123"
- userId := "user-uuid"
- csrfToken := "Bearer valid_token"
-
- // Тестируем каждый сценарий
- t.Run("invalid imageId", func(t *testing.T) {
- req := httptest.NewRequest(http.MethodDelete, "/api/ads/"+adId+"/images/invalidId", nil)
- w := httptest.NewRecorder()
- req = mux.SetURLVars(req, map[string]string{"adId": adId, "imageId": "invalidId"})
-
- handler.DeleteAdImage(w, req)
-
- assert.Equal(t, http.StatusInternalServerError, w.Result().StatusCode)
- assert.Contains(t, w.Body.String(), "invalidId")
+func TestAdHandler_GetPlacesPerCity_ConvertError(t *testing.T) {
+ require.NoError(t, logger.InitLoggers())
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
+
+ mockClient := new(mocks.MockGrpcClient)
+ utilsMock := &utils.MockUtils{}
+ handler := &AdHandler{client: mockClient, utils: utilsMock}
+
+ req := httptest.NewRequest("GET", "/housing/city/TestCity", nil)
+ req.Header.Set("X-CSRF-Token", "test-token")
+
+ w := httptest.NewRecorder()
+
+ mockClient.On("GetPlacesPerCity", mock.Anything, mock.Anything, mock.Anything).
+ Return(&gen.GetAllAdsResponseList{}, nil)
+
+ utilsMock.On("ConvertGetAllAdsResponseProtoToGo", mock.Anything).
+ Return([]domain.GetAllAdsListResponse{}, errors.New("conversion error"))
+
+ handler.GetPlacesPerCity(w, req)
+
+ result := w.Result()
+ defer func(Body io.ReadCloser) {
+ err := Body.Close()
+ if err != nil {
+ return
+ }
+ }(result.Body)
+
+ assert.Equal(t, http.StatusInternalServerError, result.StatusCode)
+
+ mockClient.AssertExpectations(t)
+ utilsMock.AssertExpectations(t)
+}
+
+func TestAdHandler_GetUserPlaces_Success(t *testing.T) {
+ require.NoError(t, logger.InitLoggers())
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
+
+ mockClient := new(mocks.MockGrpcClient)
+ mockUtils := &utils.MockUtils{}
+ handler := &AdHandler{client: mockClient, utils: mockUtils}
+
+ req := httptest.NewRequest("GET", "/housing/user/testUserId", nil)
+ req.Header.Set("X-CSRF-Token", "test-token")
+
+ w := httptest.NewRecorder()
+
+ mockClient.On("GetUserPlaces", mock.Anything, mock.Anything, mock.Anything).
+ Return(&gen.GetAllAdsResponseList{}, nil)
+
+ mockUtils.On("ConvertGetAllAdsResponseProtoToGo", mock.Anything).
+ Return([]domain.GetAllAdsListResponse{}, nil)
+
+ handler.GetUserPlaces(w, req)
+
+ resp := w.Result()
+ defer func(Body io.ReadCloser) {
+ err := Body.Close()
+ if err != nil {
+ return
+ }
+ }(resp.Body)
+
+ require.Equal(t, http.StatusOK, resp.StatusCode)
+
+ mockClient.AssertExpectations(t)
+ mockUtils.AssertExpectations(t)
+}
+
+func TestAdHandler_GetUserPlaces_GrpcError(t *testing.T) {
+ require.NoError(t, logger.InitLoggers())
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
+
+ mockClient := new(mocks.MockGrpcClient)
+ handler := &AdHandler{client: mockClient}
+
+ req := httptest.NewRequest("GET", "/housing/user/testUserId", nil)
+ req.Header.Set("X-CSRF-Token", "test-token")
+
+ w := httptest.NewRecorder()
+ grpcError := status.Error(codes.Internal, "failed to get place per city")
+ mockClient.On("GetUserPlaces", mock.Anything, mock.Anything, mock.Anything).
+ Return(&gen.GetAllAdsResponseList{}, grpcError)
+
+ handler.GetUserPlaces(w, req)
+
+ result := w.Result()
+ defer func(Body io.ReadCloser) {
+ err := Body.Close()
+ if err != nil {
+ return
+ }
+ }(result.Body)
+
+ assert.Equal(t, http.StatusInternalServerError, result.StatusCode)
+
+ mockClient.AssertExpectations(t)
+}
+
+func TestAdHandler_GetUserPlaces_ConvertError(t *testing.T) {
+ require.NoError(t, logger.InitLoggers())
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
+
+ mockClient := new(mocks.MockGrpcClient)
+ utilsMock := &utils.MockUtils{}
+ handler := &AdHandler{client: mockClient, utils: utilsMock}
+
+ req := httptest.NewRequest("GET", "/housing/user/testUserId", nil)
+ req.Header.Set("X-CSRF-Token", "test-token")
+
+ w := httptest.NewRecorder()
+
+ mockClient.On("GetUserPlaces", mock.Anything, mock.Anything, mock.Anything).
+ Return(&gen.GetAllAdsResponseList{}, nil)
+
+ utilsMock.On("ConvertGetAllAdsResponseProtoToGo", mock.Anything).
+ Return([]domain.GetAllAdsListResponse{}, errors.New("conversion error"))
+
+ handler.GetUserPlaces(w, req)
+
+ result := w.Result()
+ defer func(Body io.ReadCloser) {
+ err := Body.Close()
+ if err != nil {
+ return
+ }
+ }(result.Body)
+
+ assert.Equal(t, http.StatusInternalServerError, result.StatusCode)
+
+ mockClient.AssertExpectations(t)
+ utilsMock.AssertExpectations(t)
+}
+
+func TestAdHandler_DeleteAdImage_Success(t *testing.T) {
+ require.NoError(t, logger.InitLoggers())
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
+
+ mockClient := new(mocks.MockGrpcClient)
+
+ handler := &AdHandler{client: mockClient}
+
+ req := httptest.NewRequest("DELETE", "/housing/{adId}/images/{imageId}", nil)
+ req.Header.Set("X-CSRF-Token", "test-token")
+ req.AddCookie(&http.Cookie{
+ Name: "session_id",
+ Value: "test-session-id",
})
- t.Run("missing CSRF token", func(t *testing.T) {
- req := httptest.NewRequest(http.MethodDelete, "/api/ads/"+adId+"/images/"+imageId, nil)
- w := httptest.NewRecorder()
- req = mux.SetURLVars(req, map[string]string{"adId": adId, "imageId": imageId})
+ w := httptest.NewRecorder()
+
+ mockClient.On("DeleteAdImage", mock.Anything, mock.Anything, mock.Anything).
+ Return(&gen.DeleteResponse{}, nil)
+
+ handler.DeleteAdImage(w, req)
+
+ resp := w.Result()
+ defer func(Body io.ReadCloser) {
+ err := Body.Close()
+ if err != nil {
+ return
+ }
+ }(resp.Body)
+
+ require.Equal(t, http.StatusOK, resp.StatusCode)
+ require.Contains(t, w.Body.String(), "\"message\":\"Successfully deleted ad image\"")
+
+ mockClient.AssertExpectations(t)
+}
+
+func TestAdHandler_DeleteAdImage_FailedToGetSessionID(t *testing.T) {
+ require.NoError(t, logger.InitLoggers())
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
+
+ handler := &AdHandler{}
+
+ req := httptest.NewRequest("DELETE", "/housing/{adId}/images/{imageId}", nil)
+ req.Header.Set("X-CSRF-Token", "test-token")
+
+ w := httptest.NewRecorder()
+
+ handler.DeleteAdImage(w, req)
+
+ resp := w.Result()
+ defer func(Body io.ReadCloser) {
+ err := Body.Close()
+ if err != nil {
+ return
+ }
+ }(resp.Body)
+
+ require.Equal(t, http.StatusInternalServerError, resp.StatusCode)
+ require.Contains(t, w.Body.String(), "failed to get session id from request cookie")
+}
+
+func TestAdHandler_DeleteAdImage_GrpcError(t *testing.T) {
+ require.NoError(t, logger.InitLoggers())
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
+
+ mockClient := new(mocks.MockGrpcClient)
+
+ handler := &AdHandler{client: mockClient}
+
+ req := httptest.NewRequest("DELETE", "/housing/{adId}/images/{imageId}", nil)
+ req.Header.Set("X-CSRF-Token", "test-token")
+ req.AddCookie(&http.Cookie{
+ Name: "session_id",
+ Value: "test-session-id",
+ })
+
+ w := httptest.NewRecorder()
+ grpcError := status.Error(codes.Internal, "failed to get place per city")
+ mockClient.On("DeleteAdImage", mock.Anything, mock.Anything, mock.Anything).
+ Return(&gen.DeleteResponse{}, grpcError)
+
+ handler.DeleteAdImage(w, req)
+
+ result := w.Result()
+ defer func(Body io.ReadCloser) {
+ err := Body.Close()
+ if err != nil {
+ return
+ }
+ }(result.Body)
+
+ assert.Equal(t, http.StatusInternalServerError, result.StatusCode)
+ require.Contains(t, w.Body.String(), "\"error\":\"failed to get place per city\"")
+
+ mockClient.AssertExpectations(t)
+}
+
+func TestAdHandler_AddToFavorites_Success(t *testing.T) {
+ require.NoError(t, logger.InitLoggers())
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
+
+ mockClient := new(mocks.MockGrpcClient)
+ handler := &AdHandler{client: mockClient}
+
+ req := httptest.NewRequest("POST", "/housing/{adId}/like", nil)
+ req.Header.Set("X-CSRF-Token", "test-token")
+ req.AddCookie(&http.Cookie{
+ Name: "session_id",
+ Value: "test-session-id",
+ })
+
+ w := httptest.NewRecorder()
+
+ mockClient.On("AddToFavorites", mock.Anything, mock.Anything, mock.Anything).
+ Return(&gen.AdResponse{}, nil)
+
+ handler.AddToFavorites(w, req)
+
+ resp := w.Result()
+ defer func(Body io.ReadCloser) {
+ err := Body.Close()
+ if err != nil {
+ return
+ }
+ }(resp.Body)
+
+ require.Equal(t, http.StatusOK, resp.StatusCode)
+ require.Contains(t, w.Body.String(), "\"message\":\"Successfully added to favorites\"")
+
+ mockClient.AssertExpectations(t)
+}
+
+func TestAdHandler_AddToFavorites_FailedToGetSessionID(t *testing.T) {
+ require.NoError(t, logger.InitLoggers())
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
+
+ handler := &AdHandler{}
+
+ req := httptest.NewRequest("POST", "/housing/{adId}/like", nil)
+ req.Header.Set("X-CSRF-Token", "test-token")
+
+ w := httptest.NewRecorder()
+
+ handler.AddToFavorites(w, req)
+
+ resp := w.Result()
+ defer func(Body io.ReadCloser) {
+ err := Body.Close()
+ if err != nil {
+ return
+ }
+ }(resp.Body)
+
+ require.Equal(t, http.StatusInternalServerError, resp.StatusCode)
+ require.Contains(t, w.Body.String(), "failed to get session id from request cookie")
+
+}
+
+func TestAdHandler_AddToFavorites_GrpcError(t *testing.T) {
+ require.NoError(t, logger.InitLoggers())
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
+
+ mockClient := new(mocks.MockGrpcClient)
+ handler := &AdHandler{client: mockClient}
+
+ req := httptest.NewRequest("POST", "/housing/{adId}/like", nil)
+ req.Header.Set("X-CSRF-Token", "test-token")
+ req.AddCookie(&http.Cookie{
+ Name: "session_id",
+ Value: "test-session-id",
+ })
+
+ w := httptest.NewRecorder()
+ grpcError := status.Error(codes.Internal, "failed to add to favorites")
+ mockClient.On("AddToFavorites", mock.Anything, mock.Anything, mock.Anything).
+ Return(&gen.AdResponse{}, grpcError)
+
+ handler.AddToFavorites(w, req)
+
+ result := w.Result()
+ defer func(Body io.ReadCloser) {
+ err := Body.Close()
+ if err != nil {
+ return
+ }
+ }(result.Body)
+
+ assert.Equal(t, http.StatusInternalServerError, result.StatusCode)
+ require.Contains(t, w.Body.String(), "\"error\":\"failed to add to favorites\"")
+
+ mockClient.AssertExpectations(t)
+}
+
+func TestAdHandler_DeleteFromFavorites_Success(t *testing.T) {
+ require.NoError(t, logger.InitLoggers())
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
- handler.DeleteAdImage(w, req)
+ mockClient := new(mocks.MockGrpcClient)
+ handler := &AdHandler{client: mockClient}
- assert.Equal(t, http.StatusUnauthorized, w.Result().StatusCode)
- assert.Contains(t, w.Body.String(), "Missing X-CSRF-Token header")
+ req := httptest.NewRequest("DELETE", "/housing/{adId}/dislike", nil)
+ req.Header.Set("X-CSRF-Token", "test-token")
+ req.AddCookie(&http.Cookie{
+ Name: "session_id",
+ Value: "test-session-id",
})
- t.Run("invalid JWT token", func(t *testing.T) {
- req := httptest.NewRequest(http.MethodDelete, "/api/ads/"+adId+"/images/"+imageId, nil)
- req.Header.Set("X-CSRF-Token", csrfToken)
- w := httptest.NewRecorder()
- req = mux.SetURLVars(req, map[string]string{"adId": adId, "imageId": imageId})
+ w := httptest.NewRecorder()
- mockJWT.MockValidate = func(tokenString string) (*middleware.JwtCsrfClaims, error) {
- return nil, errors.New("invalid token")
+ mockClient.On("DeleteFromFavorites", mock.Anything, mock.Anything, mock.Anything).
+ Return(&gen.AdResponse{}, nil)
+
+ handler.DeleteFromFavorites(w, req)
+
+ resp := w.Result()
+ defer func(Body io.ReadCloser) {
+ err := Body.Close()
+ if err != nil {
+ return
+ }
+ }(resp.Body)
+
+ require.Equal(t, http.StatusOK, resp.StatusCode)
+ require.Contains(t, w.Body.String(), "\"message\":\"Successfully deleted from favorites\"")
+
+ mockClient.AssertExpectations(t)
+}
+
+func TestAdHandler_DeleteFromFavorites_FailedToGetSessionID(t *testing.T) {
+ require.NoError(t, logger.InitLoggers())
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
+
+ handler := &AdHandler{}
+
+ req := httptest.NewRequest("POST", "/housing/{adId}/dislike", nil)
+ req.Header.Set("X-CSRF-Token", "test-token")
+
+ w := httptest.NewRecorder()
+
+ handler.DeleteFromFavorites(w, req)
+
+ resp := w.Result()
+ defer func(Body io.ReadCloser) {
+ err := Body.Close()
+ if err != nil {
+ return
+ }
+ }(resp.Body)
+
+ require.Equal(t, http.StatusInternalServerError, resp.StatusCode)
+ require.Contains(t, w.Body.String(), "failed to get session id from request cookie")
+
+}
+
+func TestAdHandler_DeleteFromFavorites_GrpcError(t *testing.T) {
+ require.NoError(t, logger.InitLoggers())
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
}
+ }()
- handler.DeleteAdImage(w, req)
+ mockClient := new(mocks.MockGrpcClient)
+ handler := &AdHandler{client: mockClient}
- assert.Equal(t, http.StatusUnauthorized, w.Result().StatusCode)
- assert.Contains(t, w.Body.String(), "Invalid token")
+ req := httptest.NewRequest("POST", "/housing/{adId}/dislike", nil)
+ req.Header.Set("X-CSRF-Token", "test-token")
+ req.AddCookie(&http.Cookie{
+ Name: "session_id",
+ Value: "test-session-id",
})
- t.Run("no active session", func(t *testing.T) {
- req := httptest.NewRequest(http.MethodDelete, "/api/ads/"+adId+"/images/"+imageId, nil)
- req.Header.Set("X-CSRF-Token", csrfToken)
- w := httptest.NewRecorder()
- req = mux.SetURLVars(req, map[string]string{"adId": adId, "imageId": imageId})
+ w := httptest.NewRecorder()
+ grpcError := status.Error(codes.Internal, "failed to delete ad from favorites")
+ mockClient.On("DeleteFromFavorites", mock.Anything, mock.Anything, mock.Anything).
+ Return(&gen.AdResponse{}, grpcError)
- mockJWT.MockValidate = func(tokenString string) (*middleware.JwtCsrfClaims, error) {
- if tokenString == "valid_token" {
- return &middleware.JwtCsrfClaims{}, nil
- }
- return nil, fmt.Errorf("invalid token")
+ handler.DeleteFromFavorites(w, req)
+
+ result := w.Result()
+ defer func(Body io.ReadCloser) {
+ err := Body.Close()
+ if err != nil {
+ return
}
- mockSession.MockGetUserID = func(ctx context.Context, r *http.Request) (string, error) {
- return "", errors.New("no active session")
+ }(result.Body)
+
+ assert.Equal(t, http.StatusInternalServerError, result.StatusCode)
+ require.Contains(t, w.Body.String(), "\"error\":\"failed to delete ad from favorites\"")
+
+ mockClient.AssertExpectations(t)
+}
+
+func TestAdHandler_GetUserFavorites_Success(t *testing.T) {
+ require.NoError(t, logger.InitLoggers())
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
}
+ }()
- handler.DeleteAdImage(w, req)
+ mockClient := new(mocks.MockGrpcClient)
+ mockUtils := &utils.MockUtils{}
+ handler := &AdHandler{client: mockClient, utils: mockUtils}
- assert.Equal(t, http.StatusUnauthorized, w.Result().StatusCode)
- assert.Contains(t, w.Body.String(), "no active session")
+ req := httptest.NewRequest("GET", "/users/{userId}/favorites", nil)
+ req.Header.Set("X-CSRF-Token", "test-token")
+ req.AddCookie(&http.Cookie{
+ Name: "session_id",
+ Value: "test-session-id",
})
- t.Run("ad use case error", func(t *testing.T) {
- req := httptest.NewRequest(http.MethodDelete, "/api/ads/"+adId+"/images/"+imageId, nil)
- req.Header.Set("X-CSRF-Token", csrfToken)
- w := httptest.NewRecorder()
- req = mux.SetURLVars(req, map[string]string{"adId": adId, "imageId": imageId})
+ w := httptest.NewRecorder()
+
+ mockClient.On("GetUserFavorites", mock.Anything, mock.Anything, mock.Anything).
+ Return(&gen.GetAllAdsResponseList{}, nil)
- mockJWT.MockValidate = func(tokenString string) (*middleware.JwtCsrfClaims, error) {
- if tokenString == "valid_token" {
- return &middleware.JwtCsrfClaims{}, nil
- }
- return nil, fmt.Errorf("invalid token")
+ mockUtils.On("ConvertGetAllAdsResponseProtoToGo", mock.Anything).
+ Return([]domain.GetAllAdsListResponse{}, nil)
+
+ handler.GetUserFavorites(w, req)
+
+ resp := w.Result()
+ defer func(Body io.ReadCloser) {
+ err := Body.Close()
+ if err != nil {
+ return
+ }
+ }(resp.Body)
+
+ require.Equal(t, http.StatusOK, resp.StatusCode)
+
+ mockClient.AssertExpectations(t)
+ mockUtils.AssertExpectations(t)
+}
+
+func TestAdHandler_GetUserFavorites_GrpcError(t *testing.T) {
+ require.NoError(t, logger.InitLoggers())
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
}
- mockSession.MockGetUserID = func(ctx context.Context, r *http.Request) (string, error) {
- return userId, nil
+ }()
+
+ mockClient := new(mocks.MockGrpcClient)
+ handler := &AdHandler{client: mockClient}
+
+ req := httptest.NewRequest("GET", "/users/{userId}/favorites", nil)
+ req.Header.Set("X-CSRF-Token", "test-token")
+ req.AddCookie(&http.Cookie{
+ Name: "session_id",
+ Value: "test-session-id",
+ })
+
+ w := httptest.NewRecorder()
+ grpcError := status.Error(codes.Internal, "failed to user favorites")
+ mockClient.On("GetUserFavorites", mock.Anything, mock.Anything, mock.Anything).
+ Return(&gen.GetAllAdsResponseList{}, grpcError)
+
+ handler.GetUserFavorites(w, req)
+
+ result := w.Result()
+ defer func(Body io.ReadCloser) {
+ err := Body.Close()
+ if err != nil {
+ return
}
- mockAdUseCase.MockDeleteAdImage = func(ctx context.Context, adId string, imageId int, userId string) error {
- return errors.New("delete error")
+ }(result.Body)
+
+ assert.Equal(t, http.StatusInternalServerError, result.StatusCode)
+
+ mockClient.AssertExpectations(t)
+}
+
+func TestAdHandler_GetUserFavorites_ConvertError(t *testing.T) {
+ require.NoError(t, logger.InitLoggers())
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
}
+ }()
- handler.DeleteAdImage(w, req)
+ mockClient := new(mocks.MockGrpcClient)
+ utilsMock := &utils.MockUtils{}
+ handler := &AdHandler{client: mockClient, utils: utilsMock}
- assert.Equal(t, http.StatusInternalServerError, w.Result().StatusCode)
- assert.Contains(t, w.Body.String(), "delete error")
+ req := httptest.NewRequest("GET", "/users/{userId}/favorites", nil)
+ req.Header.Set("X-CSRF-Token", "test-token")
+ req.AddCookie(&http.Cookie{
+ Name: "session_id",
+ Value: "test-session-id",
})
- t.Run("successful delete", func(t *testing.T) {
- req := httptest.NewRequest(http.MethodDelete, "/api/ads/"+adId+"/images/"+imageId, nil)
- req.Header.Set("X-CSRF-Token", csrfToken)
- w := httptest.NewRecorder()
- req = mux.SetURLVars(req, map[string]string{"adId": adId, "imageId": imageId})
+ w := httptest.NewRecorder()
+
+ mockClient.On("GetUserFavorites", mock.Anything, mock.Anything, mock.Anything).
+ Return(&gen.GetAllAdsResponseList{}, nil)
- mockJWT.MockValidate = func(tokenString string) (*middleware.JwtCsrfClaims, error) {
- if tokenString == "valid_token" {
- return &middleware.JwtCsrfClaims{}, nil
- }
- return nil, fmt.Errorf("invalid token")
+ utilsMock.On("ConvertGetAllAdsResponseProtoToGo", mock.Anything).
+ Return([]domain.GetAllAdsListResponse{}, errors.New("conversion error"))
+
+ handler.GetUserFavorites(w, req)
+
+ result := w.Result()
+ defer func(Body io.ReadCloser) {
+ err := Body.Close()
+ if err != nil {
+ return
}
- mockSession.MockGetUserID = func(ctx context.Context, r *http.Request) (string, error) {
- return userId, nil
+ }(result.Body)
+
+ assert.Equal(t, http.StatusInternalServerError, result.StatusCode)
+
+ mockClient.AssertExpectations(t)
+ utilsMock.AssertExpectations(t)
+}
+
+func TestAdHandler_GetUserFavorites_FailedToGetSessionID(t *testing.T) {
+ require.NoError(t, logger.InitLoggers())
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
}
- mockAdUseCase.MockDeleteAdImage = func(ctx context.Context, adId string, imageId int, userId string) error {
- return nil
+ }()
+
+ handler := &AdHandler{}
+
+ req := httptest.NewRequest("GET", "/users/{userId}/favorites", nil)
+ req.Header.Set("X-CSRF-Token", "test-token")
+
+ w := httptest.NewRecorder()
+
+ handler.GetUserFavorites(w, req)
+
+ resp := w.Result()
+ defer func(Body io.ReadCloser) {
+ err := Body.Close()
+ if err != nil {
+ return
+ }
+ }(resp.Body)
+
+ require.Equal(t, http.StatusInternalServerError, resp.StatusCode)
+ require.Contains(t, w.Body.String(), "failed to get session id from request cookie")
+}
+
+func TestAdHandler_UpdatePriorityWithPayment_Success(t *testing.T) {
+ require.NoError(t, logger.InitLoggers())
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
}
+ }()
- handler.DeleteAdImage(w, req)
+ mockClient := new(mocks.MockGrpcClient)
+ handler := &AdHandler{client: mockClient}
- assert.Equal(t, http.StatusOK, w.Result().StatusCode)
+ card := domain.PaymentInfo{
+ DonationAmount: "100",
+ }
+ cardData, _ := json.Marshal(card)
- var response map[string]string
- err := json.NewDecoder(w.Body).Decode(&response)
- assert.NoError(t, err)
- assert.Equal(t, "Delete image successfully", response["response"])
+ req := httptest.NewRequest("POST", "/housing/{adId}/payment", bytes.NewReader(cardData))
+ req.Header.Set("X-CSRF-Token", "test-token")
+ req.AddCookie(&http.Cookie{
+ Name: "session_id",
+ Value: "test-session-id",
})
+ w := httptest.NewRecorder()
+
+ mockClient.On("UpdatePriority", mock.Anything, mock.Anything, mock.Anything).
+ Return(&gen.AdResponse{}, nil)
+
+ handler.UpdatePriorityWithPayment(w, req)
+
+ resp := w.Result()
+ defer func(Body io.ReadCloser) {
+ err := Body.Close()
+ if err != nil {
+ return
+ }
+ }(resp.Body)
+
+ require.Equal(t, http.StatusOK, resp.StatusCode)
+ require.Contains(t, w.Body.String(), "\"message\":\"Successfully update ad priority\"")
+
+ mockClient.AssertExpectations(t)
}
+
+func TestAdHandler_UpdatePriorityWithPayment_DecodeError(t *testing.T) {
+ require.NoError(t, logger.InitLoggers())
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
+
+ mockClient := new(mocks.MockGrpcClient)
+ handler := &AdHandler{client: mockClient}
+
+ req := httptest.NewRequest("POST", "/housing/{adId}/payment", bytes.NewReader([]byte("invalid json")))
+ req.Header.Set("X-CSRF-Token", "test-token")
+ req.AddCookie(&http.Cookie{
+ Name: "session_id",
+ Value: "test-session-id",
+ })
+ w := httptest.NewRecorder()
+
+ handler.UpdatePriorityWithPayment(w, req)
+
+ resp := w.Result()
+ defer func(Body io.ReadCloser) {
+ err := Body.Close()
+ if err != nil {
+ return
+ }
+ }(resp.Body)
+
+ require.Equal(t, http.StatusInternalServerError, resp.StatusCode)
+
+ mockClient.AssertExpectations(t)
+}
+
+func TestAdHandler_UpdatePriorityWithPayment_FailedToGetSessionID(t *testing.T) {
+ require.NoError(t, logger.InitLoggers())
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
+
+ mockClient := new(mocks.MockGrpcClient)
+ handler := &AdHandler{client: mockClient}
+ card := domain.PaymentInfo{
+ DonationAmount: "100",
+ }
+ cardData, _ := json.Marshal(card)
+
+ req := httptest.NewRequest("POST", "/housing/{adId}/payment", bytes.NewReader(cardData))
+ req.Header.Set("X-CSRF-Token", "test-token")
+ w := httptest.NewRecorder()
+
+ handler.UpdatePriorityWithPayment(w, req)
+
+ resp := w.Result()
+ defer func(Body io.ReadCloser) {
+ err := Body.Close()
+ if err != nil {
+ return
+ }
+ }(resp.Body)
+
+ require.Equal(t, http.StatusInternalServerError, resp.StatusCode)
+ require.Contains(t, w.Body.String(), "failed to get session id from request cookie")
+}
+
+func TestAdHandler_UpdatePriorityWithPayment_GrpcError(t *testing.T) {
+ require.NoError(t, logger.InitLoggers())
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
+
+ mockClient := new(mocks.MockGrpcClient)
+ handler := &AdHandler{client: mockClient}
+
+ card := domain.PaymentInfo{
+ DonationAmount: "100",
+ }
+ cardData, _ := json.Marshal(card)
+
+ req := httptest.NewRequest("POST", "/housing/{adId}/payment", bytes.NewReader(cardData))
+ req.Header.Set("X-CSRF-Token", "test-token")
+ req.AddCookie(&http.Cookie{
+ Name: "session_id",
+ Value: "test-session-id",
+ })
+ w := httptest.NewRecorder()
+ grpcError := status.Error(codes.Internal, "failed to update ad priority")
+ mockClient.On("UpdatePriority", mock.Anything, mock.Anything, mock.Anything).
+ Return(&gen.AdResponse{}, grpcError)
+
+ handler.UpdatePriorityWithPayment(w, req)
+
+ result := w.Result()
+ defer func(Body io.ReadCloser) {
+ err := Body.Close()
+ if err != nil {
+ return
+ }
+ }(result.Body)
+
+ assert.Equal(t, http.StatusInternalServerError, result.StatusCode)
+
+ mockClient.AssertExpectations(t)
+}
\ No newline at end of file
diff --git a/internal/auth/controller/auth_controller.go b/internal/auth/controller/auth_controller.go
index 8205f86..c474e56 100644
--- a/internal/auth/controller/auth_controller.go
+++ b/internal/auth/controller/auth_controller.go
@@ -6,31 +6,34 @@ import (
"2024_2_FIGHT-CLUB/internal/service/metrics"
"2024_2_FIGHT-CLUB/internal/service/middleware"
"2024_2_FIGHT-CLUB/internal/service/session"
+ "2024_2_FIGHT-CLUB/internal/service/utils"
"2024_2_FIGHT-CLUB/microservices/auth_service/controller/gen"
- "encoding/json"
"errors"
- "github.com/gorilla/mux"
- "go.uber.org/zap"
- "google.golang.org/grpc/status"
- "google.golang.org/protobuf/types/known/timestamppb"
"io"
- "math"
"mime/multipart"
"net/http"
"time"
+
+ "github.com/gorilla/mux"
+ "github.com/mailru/easyjson"
+ "go.uber.org/zap"
+ "google.golang.org/grpc/status"
+ "google.golang.org/protobuf/types/known/timestamppb"
)
type AuthHandler struct {
client gen.AuthClient
sessionService session.InterfaceSession
jwtToken middleware.JwtTokenService
+ utils utils.UtilsInterface
}
-func NewAuthHandler(client gen.AuthClient, sessionService session.InterfaceSession, jwtToken middleware.JwtTokenService) *AuthHandler {
+func NewAuthHandler(client gen.AuthClient, sessionService session.InterfaceSession, jwtToken middleware.JwtTokenService, utils utils.UtilsInterface) *AuthHandler {
return &AuthHandler{
client: client,
sessionService: sessionService,
jwtToken: jwtToken,
+ utils: utils,
}
}
@@ -64,7 +67,7 @@ func (h *AuthHandler) RegisterUser(w http.ResponseWriter, r *http.Request) {
)
var creds domain.User
- if err := json.NewDecoder(r.Body).Decode(&creds); err != nil {
+ if err = easyjson.UnmarshalFromReader(r.Body, &creds); err != nil {
logger.AccessLogger.Error("Failed to decode request body",
zap.String("request_id", requestID),
zap.Error(err),
@@ -99,6 +102,8 @@ func (h *AuthHandler) RegisterUser(w http.ResponseWriter, r *http.Request) {
Name: "session_id",
Value: userSession,
Path: "/",
+ HttpOnly: true,
+ Secure: true,
SameSite: http.SameSiteStrictMode,
})
@@ -106,21 +111,22 @@ func (h *AuthHandler) RegisterUser(w http.ResponseWriter, r *http.Request) {
Name: "csrf_token",
Value: jwtToken,
Path: "/",
+ HttpOnly: false,
+ Secure: true,
SameSite: http.SameSiteStrictMode,
})
- body := map[string]interface{}{
- "session_id": userSession,
- "user": gen.User{
- Id: response.User.Id,
- Username: response.User.Username,
- Email: response.User.Email,
- },
+ body, err := h.utils.ConvertAuthResponseProtoToGo(response, userSession)
+ if err != nil {
+ logger.AccessLogger.Error("Failed to convert auth response",
+ zap.String("request_id", requestID),
+ zap.Error(err))
+ statusCode = h.handleError(w, err, requestID)
+ return
}
-
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
w.WriteHeader(http.StatusCreated)
- if err := json.NewEncoder(w).Encode(body); err != nil {
+ if _, err = easyjson.MarshalToWriter(body, w); err != nil {
logger.AccessLogger.Error("Failed to encode response",
zap.String("request_id", requestID),
zap.Error(err),
@@ -128,6 +134,7 @@ func (h *AuthHandler) RegisterUser(w http.ResponseWriter, r *http.Request) {
statusCode = h.handleError(w, err, requestID)
return
}
+
duration := time.Since(start).Seconds()
logger.AccessLogger.Info("Completed RegisterUser request",
zap.String("request_id", requestID),
@@ -166,7 +173,7 @@ func (h *AuthHandler) LoginUser(w http.ResponseWriter, r *http.Request) {
)
var creds domain.User
- if err := json.NewDecoder(r.Body).Decode(&creds); err != nil {
+ if err = easyjson.UnmarshalFromReader(r.Body, &creds); err != nil {
logger.AccessLogger.Error("Failed to decode request body",
zap.String("request_id", requestID),
zap.Error(err),
@@ -181,7 +188,8 @@ func (h *AuthHandler) LoginUser(w http.ResponseWriter, r *http.Request) {
zap.String("request_id", requestID),
zap.Error(errors.New("csrf_token already exists")),
)
- statusCode = h.handleError(w, errors.New("csrf_token already exists"), requestID)
+ err = errors.New("csrf_token already exists")
+ statusCode = h.handleError(w, err, requestID)
return
}
@@ -208,6 +216,8 @@ func (h *AuthHandler) LoginUser(w http.ResponseWriter, r *http.Request) {
Name: "session_id",
Value: userSession,
Path: "/",
+ HttpOnly: true,
+ Secure: true,
SameSite: http.SameSiteStrictMode,
})
@@ -215,21 +225,22 @@ func (h *AuthHandler) LoginUser(w http.ResponseWriter, r *http.Request) {
Name: "csrf_token",
Value: jwtToken,
Path: "/",
+ HttpOnly: false,
+ Secure: true,
SameSite: http.SameSiteStrictMode,
})
- body := map[string]interface{}{
- "session_id": userSession,
- "user": map[string]interface{}{
- "id": response.User.Id,
- "username": response.User.Username,
- "email": response.User.Email,
- },
+ body, err := h.utils.ConvertAuthResponseProtoToGo(response, userSession)
+ if err != nil {
+ logger.AccessLogger.Error("Failed to convert auth response",
+ zap.String("request_id", requestID),
+ zap.Error(err))
+ statusCode = h.handleError(w, err, requestID)
+ return
}
-
- w.Header().Set("Content-Type", "application/json")
+ w.Header().Set("Content-Type", "application/json; charset=UTF-8")
w.WriteHeader(http.StatusOK)
- if err := json.NewEncoder(w).Encode(body); err != nil {
+ if _, err = easyjson.MarshalToWriter(body, w); err != nil {
logger.AccessLogger.Error("Failed to encode response",
zap.String("request_id", requestID),
zap.Error(err),
@@ -285,7 +296,7 @@ func (h *AuthHandler) LogoutUser(w http.ResponseWriter, r *http.Request) {
}
authHeader := r.Header.Get("X-CSRF-Token")
- response, err := h.client.LogoutUser(ctx, &gen.LogoutRequest{
+ _, err = h.client.LogoutUser(ctx, &gen.LogoutRequest{
AuthHeader: authHeader,
SessionId: sessionID,
})
@@ -315,17 +326,19 @@ func (h *AuthHandler) LogoutUser(w http.ResponseWriter, r *http.Request) {
Name: "csrf_token",
Value: "",
Path: "/",
- HttpOnly: true,
+ HttpOnly: false,
Secure: true,
Expires: time.Unix(0, 0),
SameSite: http.SameSiteStrictMode,
})
- w.Header().Set("Content-Type", "application/json")
+ w.Header().Set("Content-Type", "application/json; charset=UTF-8")
w.WriteHeader(http.StatusOK)
- logoutResponse := map[string]string{"response": response.Response}
- if err := json.NewEncoder(w).Encode(logoutResponse); err != nil {
- logger.AccessLogger.Error("Failed to encode logout response",
+ logoutResponse := domain.ResponseMessage{
+ Message: "Successfully logged out",
+ }
+ if _, err = easyjson.MarshalToWriter(logoutResponse, w); err != nil {
+ logger.AccessLogger.Error("Failed to encode response",
zap.String("request_id", requestID),
zap.Error(err),
)
@@ -388,7 +401,7 @@ func (h *AuthHandler) PutUser(w http.ResponseWriter, r *http.Request) {
metadata := r.FormValue("metadata")
var creds domain.User
- if err := json.Unmarshal([]byte(metadata), &creds); err != nil {
+ if err = creds.UnmarshalJSON([]byte(metadata)); err != nil {
w.WriteHeader(http.StatusBadRequest)
logger.AccessLogger.Warn("Failed to parse metadata",
zap.String("request_id", requestID),
@@ -421,7 +434,7 @@ func (h *AuthHandler) PutUser(w http.ResponseWriter, r *http.Request) {
}
}
- response, err := h.client.PutUser(ctx, &gen.PutUserRequest{
+ _, err = h.client.PutUser(ctx, &gen.PutUserRequest{
Creds: &gen.Metadata{
Uuid: creds.UUID,
Username: creds.Username,
@@ -451,9 +464,12 @@ func (h *AuthHandler) PutUser(w http.ResponseWriter, r *http.Request) {
return
}
- w.Header().Set("Content-Type", "application/json")
+ w.Header().Set("Content-Type", "application/json; charset=UTF-8")
w.WriteHeader(http.StatusOK)
- if err := json.NewEncoder(w).Encode(response.Response); err != nil {
+ PutUserResponse := domain.ResponseMessage{
+ Message: "Successfully update user data",
+ }
+ if _, err = easyjson.MarshalToWriter(PutUserResponse, w); err != nil {
logger.AccessLogger.Error("Failed to encode update response",
zap.String("request_id", requestID),
zap.Error(err),
@@ -485,13 +501,14 @@ func (h *AuthHandler) GetUserById(w http.ResponseWriter, r *http.Request) {
clientIP = forwarded
}
defer func() {
+ sanitizedPath := metrics.SanitizeUserIdPath(r.URL.Path)
if statusCode == http.StatusOK {
- metrics.HttpRequestsTotal.WithLabelValues(r.Method, r.URL.Path, http.StatusText(statusCode), clientIP).Inc()
+ metrics.HttpRequestsTotal.WithLabelValues(r.Method, sanitizedPath, http.StatusText(statusCode), clientIP).Inc()
} else {
- metrics.HttpErrorsTotal.WithLabelValues(r.Method, r.URL.Path, http.StatusText(statusCode), err.Error(), clientIP).Inc()
+ metrics.HttpErrorsTotal.WithLabelValues(r.Method, sanitizedPath, http.StatusText(statusCode), err.Error(), clientIP).Inc()
}
duration := time.Since(start).Seconds()
- metrics.HttpRequestDuration.WithLabelValues(r.Method, r.URL.Path, clientIP).Observe(duration)
+ metrics.HttpRequestDuration.WithLabelValues(r.Method, sanitizedPath, clientIP).Observe(duration)
}()
logger.AccessLogger.Info("Received GetUserById request",
@@ -514,23 +531,18 @@ func (h *AuthHandler) GetUserById(w http.ResponseWriter, r *http.Request) {
return
}
- response := &domain.UserDataResponse{
- Uuid: user.User.Uuid,
- Username: user.User.Username,
- Email: user.User.Email,
- Name: user.User.Name,
- Score: math.Round(float64(user.User.Score)*10) / 10,
- Avatar: user.User.Avatar,
- Sex: user.User.Sex,
- GuestCount: int(user.User.GuestCount),
- Birthdate: (user.User.Birthdate).AsTime(),
- IsHost: user.User.IsHost,
+ response, err := h.utils.ConvertUserResponseProtoToGo(user.User)
+ if err != nil {
+ logger.AccessLogger.Error("Failed to convert user response",
+ zap.String("request_id", requestID),
+ zap.Error(err))
+ statusCode = h.handleError(w, err, requestID)
+ return
}
-
- w.Header().Set("Content-Type", "application/json")
+ w.Header().Set("Content-Type", "application/json; charset=UTF-8")
w.WriteHeader(http.StatusOK)
- if err := json.NewEncoder(w).Encode(response); err != nil {
- logger.AccessLogger.Error("Failed to encode user data",
+ if _, err = easyjson.MarshalToWriter(response, w); err != nil {
+ logger.AccessLogger.Error("Failed to encode getUserById response",
zap.String("request_id", requestID),
zap.Error(err),
)
@@ -588,28 +600,21 @@ func (h *AuthHandler) GetAllUsers(w http.ResponseWriter, r *http.Request) {
return
}
- var body []*domain.UserDataResponse
- for _, user := range users.Users {
- body = append(body, &domain.UserDataResponse{
- Uuid: user.Uuid,
- Username: user.Username,
- Email: user.Email,
- Name: user.Name,
- Score: math.Round(float64(user.Score)*10) / 10,
- Avatar: user.Avatar,
- Sex: user.Sex,
- GuestCount: int(user.GuestCount),
- Birthdate: (user.Birthdate).AsTime(),
- IsHost: user.IsHost,
- })
+ body, err := h.utils.ConvertUsersProtoToGo(users)
+ if err != nil {
+ logger.AccessLogger.Error("Failed to convert users proto",
+ zap.String("request_id", requestID),
+ zap.Error(err))
+ h.handleError(w, err, requestID)
+ return
}
- response := map[string]interface{}{
- "users": body,
+ response := domain.GetAllUsersResponse{
+ Users: body,
}
- w.Header().Set("Content-Type", "application/json")
+ w.Header().Set("Content-Type", "application/json; charset=UTF-8")
w.WriteHeader(http.StatusOK)
- if err := json.NewEncoder(w).Encode(response); err != nil {
- logger.AccessLogger.Error("Failed to encode users response",
+ if _, err = easyjson.MarshalToWriter(response, w); err != nil {
+ logger.AccessLogger.Error("Failed to encode getAllUsers response",
zap.String("request_id", requestID),
zap.Error(err),
)
@@ -678,10 +683,18 @@ func (h *AuthHandler) GetSessionData(w http.ResponseWriter, r *http.Request) {
return
}
- w.Header().Set("Content-Type", "application/json")
+ response, err := h.utils.ConvertSessionDataProtoToGo(sessionData)
+ if err != nil {
+ logger.AccessLogger.Error("Failed to convert session data",
+ zap.String("request_id", requestID),
+ zap.Error(err))
+ h.handleError(w, err, requestID)
+ return
+ }
+ w.Header().Set("Content-Type", "application/json; charset=UTF-8")
w.WriteHeader(http.StatusOK)
- if err := json.NewEncoder(w).Encode(sessionData); err != nil {
- logger.AccessLogger.Error("Failed to encode session data",
+ if _, err = easyjson.MarshalToWriter(response, w); err != nil {
+ logger.AccessLogger.Error("Failed to encode GetSessionData response",
zap.String("request_id", requestID),
zap.Error(err),
)
@@ -720,8 +733,6 @@ func (h *AuthHandler) RefreshCsrfToken(w http.ResponseWriter, r *http.Request) {
metrics.HttpRequestDuration.WithLabelValues(r.Method, r.URL.Path, clientIP).Observe(duration)
}()
- ctx = middleware.WithLogger(ctx, logger.AccessLogger)
-
logger.AccessLogger.Info("Received RefreshCsrfToken request",
zap.String("request_id", requestID),
zap.String("method", r.Method),
@@ -755,21 +766,227 @@ func (h *AuthHandler) RefreshCsrfToken(w http.ResponseWriter, r *http.Request) {
Name: "csrf_token",
Value: newCsrfToken.CsrfToken,
Path: "/",
+ HttpOnly: false,
+ Secure: true,
SameSite: http.SameSiteStrictMode,
})
+ w.Header().Set("Content-Type", "application/json; charset=UTF-8")
w.WriteHeader(http.StatusOK)
- err = json.NewEncoder(w).Encode(map[string]string{"csrf_token": newCsrfToken.CsrfToken})
+ response := domain.CSRFTokenResponse{
+ Token: newCsrfToken.CsrfToken,
+ }
+ if _, err = easyjson.MarshalToWriter(response, w); err != nil {
+ logger.AccessLogger.Error("Failed to encode getUserById response",
+ zap.String("request_id", requestID),
+ zap.Error(err),
+ )
+ statusCode = h.handleError(w, err, requestID)
+ return
+ }
+
+ duration := time.Since(start)
+ logger.AccessLogger.Info("Completed RefreshCsrfToken request",
+ zap.String("request_id", requestID),
+ zap.Duration("duration", duration),
+ zap.Int("status", http.StatusOK),
+ )
+}
+
+func (h *AuthHandler) UpdateUserRegion(w http.ResponseWriter, r *http.Request) {
+ start := time.Now()
+ requestID := middleware.GetRequestID(r.Context())
+ ctx, cancel := middleware.WithTimeout(r.Context())
+ defer cancel()
+ var err error
+ statusCode := http.StatusOK
+ clientIP := r.RemoteAddr
+ if realIP := r.Header.Get("X-Real-IP"); realIP != "" {
+ clientIP = realIP
+ } else if forwarded := r.Header.Get("X-Forwarded-For"); forwarded != "" {
+ clientIP = forwarded
+ }
+ defer func() {
+ if statusCode == http.StatusOK {
+ metrics.HttpRequestsTotal.WithLabelValues(r.Method, r.URL.Path, http.StatusText(statusCode), clientIP).Inc()
+ } else {
+ metrics.HttpErrorsTotal.WithLabelValues(r.Method, r.URL.Path, http.StatusText(statusCode), err.Error(), clientIP).Inc()
+ }
+ duration := time.Since(start).Seconds()
+ metrics.HttpRequestDuration.WithLabelValues(r.Method, r.URL.Path, clientIP).Observe(duration)
+ }()
+
+ logger.AccessLogger.Info("Received UpdateUserRegions request",
+ zap.String("request_id", requestID),
+ zap.String("method", r.Method),
+ zap.String("url", r.URL.String()),
+ )
+
+ var region domain.UpdateUserRegion
+ if err = easyjson.UnmarshalFromReader(r.Body, ®ion); err != nil {
+ logger.AccessLogger.Error("Failed to decode request body",
+ zap.String("request_id", requestID),
+ zap.Error(err),
+ )
+ statusCode = h.handleError(w, err, requestID)
+ return
+ }
+
+ sessionID, err := session.GetSessionId(r)
if err != nil {
- logger.AccessLogger.Error("Failed to encode CSRF token",
+ logger.AccessLogger.Error("Failed to get session ID",
zap.String("request_id", requestID),
zap.Error(err))
statusCode = h.handleError(w, err, requestID)
return
}
+ authHeader := r.Header.Get("X-CSRF-Token")
+ startVisitTime, err := time.Parse("2006-01-02", region.StartVisitedDate)
+ if err != nil {
+ logger.AccessLogger.Error("Failed to parse start visit date",
+ zap.String("request_id", requestID),
+ zap.Error(err))
+ statusCode = h.handleError(w, err, requestID)
+ return
+ }
+
+ endVisitTime, err := time.Parse("2006-01-02", region.EndVisitedDate)
+ if err != nil {
+ logger.AccessLogger.Error("Failed to parse end visit date",
+ zap.String("request_id", requestID),
+ zap.Error(err))
+ statusCode = h.handleError(w, err, requestID)
+ return
+ }
+
+ grpcRegion := &gen.UpdateUserRegionsRequest{
+ Region: region.RegionName,
+ StartVisitDate: timestamppb.New(startVisitTime),
+ EndVisitDate: timestamppb.New(endVisitTime),
+ AuthHeader: authHeader,
+ SessionId: sessionID,
+ }
+
+ _, err = h.client.UpdateUserRegions(ctx, grpcRegion)
+ if err != nil {
+ logger.AccessLogger.Error("Failed to update user regions",
+ zap.String("request_id", requestID),
+ zap.Error(err),
+ )
+ st, ok := status.FromError(err)
+ if ok {
+ statusCode = h.handleError(w, errors.New(st.Message()), requestID)
+ }
+ return
+ }
+
+ w.WriteHeader(http.StatusOK)
+ UpdateUserRegionsResponse := domain.ResponseMessage{
+ Message: "Successfully update user regions",
+ }
+ if _, err = easyjson.MarshalToWriter(UpdateUserRegionsResponse, w); err != nil {
+ logger.AccessLogger.Error("Failed to encode update response",
+ zap.String("request_id", requestID),
+ zap.Error(err),
+ )
+ statusCode = h.handleError(w, err, requestID)
+ return
+ }
+
duration := time.Since(start)
- logger.AccessLogger.Info("Completed RefreshCsrfToken request",
+ logger.AccessLogger.Info("Completed LoginUser request",
+ zap.String("request_id", requestID),
+ zap.Duration("duration", duration),
+ zap.Int("status", http.StatusOK),
+ )
+}
+
+func (h *AuthHandler) DeleteUserRegion(w http.ResponseWriter, r *http.Request) {
+ start := time.Now()
+ requestID := middleware.GetRequestID(r.Context())
+ ctx, cancel := middleware.WithTimeout(r.Context())
+ defer cancel()
+
+ var err error
+ statusCode := http.StatusOK
+ clientIP := r.RemoteAddr
+ if realIP := r.Header.Get("X-Real-IP"); realIP != "" {
+ clientIP = realIP
+ } else if forwarded := r.Header.Get("X-Forwarded-For"); forwarded != "" {
+ clientIP = forwarded
+ }
+ defer func() {
+ if statusCode == http.StatusOK {
+ metrics.HttpRequestsTotal.WithLabelValues(r.Method, r.URL.Path, http.StatusText(statusCode), clientIP).Inc()
+ } else {
+ metrics.HttpErrorsTotal.WithLabelValues(r.Method, r.URL.Path, http.StatusText(statusCode), err.Error(), clientIP).Inc()
+ }
+ duration := time.Since(start).Seconds()
+ metrics.HttpRequestDuration.WithLabelValues(r.Method, r.URL.Path, clientIP).Observe(duration)
+ }()
+
+ logger.AccessLogger.Info("Received DeleteUserRegion request",
+ zap.String("request_id", requestID),
+ zap.String("method", r.Method),
+ zap.String("url", r.URL.String()),
+ )
+
+ regionName := mux.Vars(r)["regionName"]
+
+ sessionID, err := session.GetSessionId(r)
+ if err != nil {
+ logger.AccessLogger.Error("Failed to get session ID",
+ zap.String("request_id", requestID),
+ zap.Error(err))
+ err = errors.New("failed to get session id from request cookie")
+ statusCode = h.handleError(w, err, requestID)
+ return
+ }
+
+ authHeader := r.Header.Get("X-CSRF-Token")
+ if authHeader == "" {
+ logger.AccessLogger.Error("Missing X-CSRF-Token header",
+ zap.String("request_id", requestID))
+ err = errors.New("missing X-CSRF-Token header")
+ statusCode = h.handleError(w, err, requestID)
+ return
+ }
+
+ grpcRequest := &gen.DeleteUserRegionsRequest{
+ Region: regionName,
+ AuthHeader: authHeader,
+ SessionId: sessionID,
+ }
+
+ _, err = h.client.DeleteUserRegions(ctx, grpcRequest)
+ if err != nil {
+ logger.AccessLogger.Error("Failed to delete user region via gRPC",
+ zap.String("request_id", requestID),
+ zap.Error(err),
+ )
+ st, ok := status.FromError(err)
+ if ok {
+ statusCode = h.handleError(w, errors.New(st.Message()), requestID)
+ }
+ return
+ }
+
+ w.WriteHeader(http.StatusOK)
+ deleteResponse := domain.ResponseMessage{
+ Message: "Successfully deleted user region",
+ }
+ if _, err = easyjson.MarshalToWriter(deleteResponse, w); err != nil {
+ logger.AccessLogger.Error("Failed to encode delete response",
+ zap.String("request_id", requestID),
+ zap.Error(err),
+ )
+ statusCode = h.handleError(w, err, requestID)
+ return
+ }
+
+ duration := time.Since(start)
+ logger.AccessLogger.Info("Completed DeleteUserRegion request",
zap.String("request_id", requestID),
zap.Duration("duration", duration),
zap.Int("status", http.StatusOK),
@@ -783,35 +1000,41 @@ func (h *AuthHandler) handleError(w http.ResponseWriter, err error, requestID st
)
w.Header().Set("Content-Type", "application/json")
- errorResponse := map[string]string{"error": err.Error()}
+ errorResponse := domain.ErrorResponse{
+ Error: err.Error(),
+ }
var statusCode int
switch err.Error() {
case "username, password, and email are required",
"username and password are required",
"invalid credentials",
"csrf_token already exists",
- "Input contains invalid characters",
- "Input exceeds character limit",
- "Invalid size, type or resolution of image",
+ "input contains invalid characters",
+ "input exceeds character limit",
+ "invalid size, type or resolution of image",
"invalid metadata JSON",
"missing X-CSRF-Token header",
"invalid JWT token",
"invalid type for id in session data",
- "invalid type for avatar in session data":
- w.WriteHeader(http.StatusBadRequest)
+ "invalid type for avatar in session data",
+ "token invalid",
+ "token expired",
+ "file type is not allowed, please use (png, jpg, jpeg) types",
+ "unsupported image format",
+ "image resolution exceeds maximum allowed size of 2000 x 2000":
statusCode = http.StatusBadRequest
case "user already exists",
"email already exists",
"session already exists",
- "already logged in":
- w.WriteHeader(http.StatusConflict)
+ "already logged in",
+ "username or email already exists":
statusCode = http.StatusConflict
case "no active session",
"session not found",
- "user ID not found in session":
- w.WriteHeader(http.StatusUnauthorized)
+ "user ID not found in session",
+ "failed to get session id from request cookie":
statusCode = http.StatusUnauthorized
case "user not found",
@@ -819,7 +1042,6 @@ func (h *AuthHandler) handleError(w http.ResponseWriter, err error, requestID st
"error fetching user by name",
"error fetching user by email",
"there is none user in db":
- w.WriteHeader(http.StatusNotFound)
statusCode = http.StatusNotFound
case "error creating user",
@@ -837,20 +1059,17 @@ func (h *AuthHandler) handleError(w http.ResponseWriter, err error, requestID st
"failed to get session data",
"failed to refresh csrf token",
"error generating random bytes for session ID",
- "failed to get session id from request cookie",
"token parse error",
- "token invalid",
- "token expired",
- "bad sign method":
- w.WriteHeader(http.StatusInternalServerError)
+ "bad sign method",
+ "could not decode image":
statusCode = http.StatusInternalServerError
default:
- w.WriteHeader(http.StatusInternalServerError)
statusCode = http.StatusInternalServerError
}
- if jsonErr := json.NewEncoder(w).Encode(errorResponse); jsonErr != nil {
+ w.WriteHeader(statusCode)
+ if _, jsonErr := easyjson.MarshalToWriter(&errorResponse, w); jsonErr != nil {
logger.AccessLogger.Error("Failed to encode error response",
zap.String("request_id", requestID),
zap.Error(jsonErr),
diff --git a/internal/auth/controller/auth_controller_test.go b/internal/auth/controller/auth_controller_test.go
index 8c7aad8..a3d7584 100644
--- a/internal/auth/controller/auth_controller_test.go
+++ b/internal/auth/controller/auth_controller_test.go
@@ -2,19 +2,25 @@ package controller
import (
"2024_2_FIGHT-CLUB/domain"
- "2024_2_FIGHT-CLUB/internal/auth/mocks"
"2024_2_FIGHT-CLUB/internal/service/logger"
- "2024_2_FIGHT-CLUB/internal/service/middleware"
+ "2024_2_FIGHT-CLUB/internal/service/utils"
+ "2024_2_FIGHT-CLUB/microservices/auth_service/controller/gen"
+ "2024_2_FIGHT-CLUB/microservices/auth_service/mocks"
"bytes"
"context"
- "encoding/json"
- "fmt"
+ "errors"
"github.com/gorilla/mux"
- "github.com/gorilla/sessions"
+ "github.com/mailru/easyjson"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
+ "google.golang.org/grpc/codes"
+ "google.golang.org/grpc/status"
+ "image"
+ "image/color"
+ "image/jpeg"
+ "image/png"
"io"
- "log"
"mime/multipart"
"net/http"
"net/http/httptest"
@@ -22,593 +28,1501 @@ import (
"time"
)
-func TestRegisterUser(t *testing.T) {
- if err := logger.InitLoggers(); err != nil {
- log.Fatalf("Failed to initialize loggers: %v", err)
+// GenerateImage создает изображение с указанным форматом и размером
+func GenerateImage(format string, width, height int) ([]byte, error) {
+ img := image.NewRGBA(image.Rect(0, 0, width, height))
+ // Заливаем белым цветом
+ white := color.RGBA{R: 255, G: 255, B: 255, A: 255}
+ for x := 0; x < width; x++ {
+ for y := 0; y < height; y++ {
+ img.Set(x, y, white)
+ }
+ }
+
+ var buf bytes.Buffer
+ switch format {
+ case "jpeg", "jpg":
+ err := jpeg.Encode(&buf, img, nil)
+ if err != nil {
+ return nil, err
+ }
+ case "png":
+ err := png.Encode(&buf, img)
+ if err != nil {
+ return nil, err
+ }
+ default:
+ return nil, errors.New("unsupported format")
}
- defer logger.SyncLoggers()
- mockAuthUseCase := &mocks.MockAuthUseCase{}
- mockSessionService := &mocks.MockServiceSession{}
- mockJwtTokenService := &mocks.MockJwtTokenService{}
+ return buf.Bytes(), nil
+}
- handler := &AuthHandler{
- authUseCase: mockAuthUseCase,
- sessionService: mockSessionService,
- jwtToken: mockJwtTokenService,
+// Тест для успешного выполнения RegisterUser
+func TestAuthHandler_RegisterUser_Success(t *testing.T) {
+ require.NoError(t, logger.InitLoggers())
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
+
+ // Мок gRPC клиента
+ mockGrpcClient := new(mocks.MockGrpcClient)
+ utilsMock := &utils.MockUtils{}
+ mockResponse := &gen.UserResponse{
+ SessionId: "session123",
+ Jwttoken: "token123",
+ User: &gen.User{
+ Id: "test_user_id",
+ Username: "test_user_name",
+ Email: "test@example.com",
+ },
+ }
+ userSession := "session123"
+ mockGrpcClient.On("RegisterUser", mock.Anything, mock.Anything, mock.Anything).Return(mockResponse, nil)
+ utilsMock.On("ConvertAuthResponseProtoToGo", mock.Anything, userSession).Return(domain.AuthResponse{
+ SessionId: userSession,
+ User: domain.AuthData{
+ Id: mockResponse.User.Id,
+ Email: mockResponse.User.Email,
+ Username: mockResponse.User.Username,
+ },
+ }, nil)
+ // Инициализация обработчика
+ authHandler := AuthHandler{
+ client: mockGrpcClient,
+ utils: utilsMock,
}
+ // Создание тела запроса
user := domain.User{
- Username: "testuser",
+ Username: "test_user_name",
Email: "test@example.com",
+ Name: "Test User",
Password: "password123",
- Name: "testuser",
}
+ body, _ := easyjson.Marshal(user)
- body, _ := json.Marshal(user)
- req := httptest.NewRequest("POST", "/api/auth/register", bytes.NewReader(body))
+ // Создание HTTP запроса
+ req := httptest.NewRequest(http.MethodPost, "/auth/register", bytes.NewBuffer(body))
req.Header.Set("Content-Type", "application/json")
+ req.Header.Set("X-Real-IP", "127.0.0.1")
+
w := httptest.NewRecorder()
- // Mock функции
- mockAuthUseCase.MockRegisterUser = func(ctx context.Context, user *domain.User) error {
- user.UUID = "test-uuid"
- return nil
- }
+ // Вызов обработчика
+ authHandler.RegisterUser(w, req)
+
+ // Проверка результата
+ result := w.Result()
+ defer func(Body io.ReadCloser) {
+ err := Body.Close()
+ if err != nil {
+ return
+ }
+ }(result.Body)
- mockSession := &sessions.Session{
- Values: map[interface{}]interface{}{
- "session_id": "session-id-123",
- },
- }
+ assert.Equal(t, http.StatusCreated, result.StatusCode)
+ mockGrpcClient.AssertExpectations(t)
+}
- mockSessionService.MockCreateSession = func(ctx context.Context, r *http.Request, w http.ResponseWriter, user *domain.User) (*sessions.Session, error) {
- return mockSession, nil
+// Тест на ошибку при декодировании JSON
+func TestAuthHandler_RegisterUser_DecodeError(t *testing.T) {
+ require.NoError(t, logger.InitLoggers())
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
+
+ authHandler := AuthHandler{}
+
+ // Некорректное тело запроса
+ req := httptest.NewRequest(http.MethodPost, "/auth/register", bytes.NewBuffer([]byte("{invalid_json}")))
+ w := httptest.NewRecorder()
+
+ // Вызов обработчика
+ authHandler.RegisterUser(w, req)
+
+ // Проверка результата
+ result := w.Result()
+ defer func(Body io.ReadCloser) {
+ err := Body.Close()
+ if err != nil {
+ return
+ }
+ }(result.Body)
+
+ assert.Equal(t, http.StatusInternalServerError, result.StatusCode)
+}
+
+// Тест на ошибку от gRPC клиента
+func TestAuthHandler_RegisterUser_GrpcError(t *testing.T) {
+ require.NoError(t, logger.InitLoggers())
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
+ grpcErr := status.Error(codes.Internal, "gRPC Error")
+ mockGrpcClient := new(mocks.MockGrpcClient)
+ mockGrpcClient.On("RegisterUser", mock.Anything, mock.Anything, mock.Anything).Return(&gen.UserResponse{}, grpcErr)
+
+ authHandler := AuthHandler{
+ client: mockGrpcClient,
}
- mockJwtTokenService.MockCreate = func(s *sessions.Session, tokenExpTime int64) (string, error) {
- return "fake-jwt-token", nil
+ user := domain.User{
+ Username: "testuser",
+ Email: "test@example.com",
+ Name: "Test User",
+ Password: "password123",
}
+ body, _ := easyjson.Marshal(user)
- // Тест успешного запроса
- handler.RegisterUser(w, req)
- res := w.Result()
- defer res.Body.Close()
+ req := httptest.NewRequest(http.MethodPost, "/auth/register", bytes.NewBuffer(body))
+ req.Header.Set("Content-Type", "application/json")
- assert.Equal(t, http.StatusCreated, res.StatusCode)
+ w := httptest.NewRecorder()
+ authHandler.RegisterUser(w, req)
- // Проверка наличия csrf_token в cookies
- cookies := res.Cookies()
- foundCsrf := false
- for _, cookie := range cookies {
- if cookie.Name == "csrf_token" && cookie.Value == "fake-jwt-token" {
- foundCsrf = true
- break
+ result := w.Result()
+ defer func(Body io.ReadCloser) {
+ err := Body.Close()
+ if err != nil {
+ return
}
- }
- assert.True(t, foundCsrf, "Expected csrf_token cookie")
+ }(result.Body)
- var response map[string]interface{}
- err := json.NewDecoder(res.Body).Decode(&response)
- assert.NoError(t, err)
- assert.Equal(t, "session-id-123", response["session_id"])
+ assert.Equal(t, http.StatusInternalServerError, result.StatusCode)
+ mockGrpcClient.AssertExpectations(t)
+}
- userData := response["user"].(map[string]interface{})
- assert.Equal(t, "test-uuid", userData["id"])
- assert.Equal(t, "testuser", userData["username"])
- assert.Equal(t, "test@example.com", userData["email"])
+// Тест на ошибку при конверсии ответа
+func TestAuthHandler_RegisterUser_ConvertError(t *testing.T) {
+ require.NoError(t, logger.InitLoggers())
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
+ mockResponse := &gen.UserResponse{
+ SessionId: "session123",
+ Jwttoken: "token123",
+ User: &gen.User{
+ Id: "test_user_id",
+ Username: "test_user_name",
+ Email: "test@example.com",
+ },
+ }
+ userSession := "session123"
+ utilsMock := &utils.MockUtils{}
+ mockGrpcClient := new(mocks.MockGrpcClient)
+ authHandler := AuthHandler{
+ client: mockGrpcClient,
+ utils: utilsMock,
+ }
+ mockGrpcClient.On("RegisterUser", mock.Anything, mock.Anything, mock.Anything).Return(mockResponse, nil)
+ // Переопределяем конвертацию ответа, чтобы она возвращала ошибку
+ utilsMock.On("ConvertAuthResponseProtoToGo", mockResponse, userSession).
+ Return(nil, errors.New("conversion error"))
- // Тест обработки ошибки при регистрации пользователя
- mockAuthUseCase.MockRegisterUser = func(ctx context.Context, user *domain.User) error {
- return fmt.Errorf("register error")
+ user := domain.User{
+ Username: "test_user_name",
+ Email: "test@example.com",
+ Name: "Test User",
+ Password: "password123",
}
- w = httptest.NewRecorder()
- handler.RegisterUser(w, req)
- assert.Equal(t, http.StatusInternalServerError, w.Code, "Expected status 500")
+ body, _ := easyjson.Marshal(user)
- // Тест обработки ошибки при создании сессии
- mockAuthUseCase.MockRegisterUser = func(ctx context.Context, user *domain.User) error {
- return nil
+ req := httptest.NewRequest(http.MethodPost, "/auth/register", bytes.NewBuffer(body))
+ req.Header.Set("Content-Type", "application/json")
+
+ w := httptest.NewRecorder()
+ authHandler.RegisterUser(w, req)
+
+ result := w.Result()
+ defer func(Body io.ReadCloser) {
+ err := Body.Close()
+ if err != nil {
+ return
+ }
+ }(result.Body)
+
+ assert.Equal(t, http.StatusInternalServerError, result.StatusCode)
+ mockGrpcClient.AssertExpectations(t)
+}
+
+func TestAuthHandler_LoginUser_Success(t *testing.T) {
+ require.NoError(t, logger.InitLoggers())
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
+
+ // Моки
+ mockGrpcClient := new(mocks.MockGrpcClient)
+ utilsMock := &utils.MockUtils{}
+
+ mockResponse := &gen.UserResponse{
+ SessionId: "session123",
+ Jwttoken: "token123",
+ User: &gen.User{
+ Id: "test_user_id",
+ Username: "test_user_name",
+ Email: "test@example.com",
+ },
}
- mockSessionService.MockCreateSession = func(ctx context.Context, r *http.Request, w http.ResponseWriter, user *domain.User) (*sessions.Session, error) {
- return nil, fmt.Errorf("error creating session")
+
+ // Настройка моков
+ mockGrpcClient.On("LoginUser", mock.Anything, mock.Anything, mock.Anything).Return(mockResponse, nil)
+ utilsMock.On("ConvertAuthResponseProtoToGo", mockResponse, "session123").Return(domain.AuthResponse{
+ SessionId: "session123",
+ User: domain.AuthData{
+ Id: "test_user_id",
+ Email: "test@example.com",
+ Username: "test_user_name",
+ },
+ }, nil)
+
+ // Инициализация обработчика
+ authHandler := AuthHandler{
+ client: mockGrpcClient,
+ utils: utilsMock,
}
- w = httptest.NewRecorder()
- handler.RegisterUser(w, req)
- assert.Equal(t, http.StatusInternalServerError, w.Code, "Expected status 500")
- // Тест обработки ошибки при создании JWT-токена
- mockSessionService.MockCreateSession = func(ctx context.Context, r *http.Request, w http.ResponseWriter, user *domain.User) (*sessions.Session, error) {
- return mockSession, nil
+ // Тело запроса
+ user := domain.User{
+ Username: "test_user",
+ Password: "password123",
}
- mockJwtTokenService.MockCreate = func(s *sessions.Session, tokenExpTime int64) (string, error) {
- return "", fmt.Errorf("error creating JWT token")
+ body, _ := easyjson.Marshal(user)
+
+ req := httptest.NewRequest(http.MethodPost, "/auth/login", bytes.NewBuffer(body))
+ req.Header.Set("Content-Type", "application/json")
+ req.Header.Set("X-Real-IP", "127.0.0.1")
+ w := httptest.NewRecorder()
+
+ // Вызов метода
+ authHandler.LoginUser(w, req)
+
+ // Проверка
+ result := w.Result()
+ defer func(Body io.ReadCloser) {
+ err := Body.Close()
+ if err != nil {
+ return
+ }
+ }(result.Body)
+
+ assert.Equal(t, http.StatusOK, result.StatusCode)
+ mockGrpcClient.AssertExpectations(t)
+ utilsMock.AssertExpectations(t)
+}
+
+func TestAuthHandler_LoginUser_DecodeError(t *testing.T) {
+ require.NoError(t, logger.InitLoggers())
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
+
+ authHandler := AuthHandler{}
+
+ // Некорректное тело запроса
+ req := httptest.NewRequest(http.MethodPost, "/auth/login", bytes.NewBuffer([]byte("invalid_json")))
+ w := httptest.NewRecorder()
+
+ authHandler.LoginUser(w, req)
+
+ result := w.Result()
+ defer func(Body io.ReadCloser) {
+ err := Body.Close()
+ if err != nil {
+ return
+ }
+ }(result.Body)
+
+ assert.Equal(t, http.StatusInternalServerError, result.StatusCode)
+}
+
+func TestAuthHandler_LoginUser_ExistingCsrfToken(t *testing.T) {
+ require.NoError(t, logger.InitLoggers())
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
+
+ authHandler := AuthHandler{}
+
+ user := domain.User{
+ Username: "test_user",
+ Password: "password123",
}
- w = httptest.NewRecorder()
- handler.RegisterUser(w, req)
- assert.Equal(t, http.StatusInternalServerError, w.Code, "Expected status 500")
+ body, _ := easyjson.Marshal(user)
+
+ // Запрос с существующим CSRF куки
+ req := httptest.NewRequest(http.MethodPost, "/auth/login", bytes.NewBuffer(body))
+ req.AddCookie(&http.Cookie{Name: "csrf_token", Value: "already_exists"})
+ w := httptest.NewRecorder()
+
+ authHandler.LoginUser(w, req)
+
+ result := w.Result()
+ defer func(Body io.ReadCloser) {
+ err := Body.Close()
+ if err != nil {
+ return
+ }
+ }(result.Body)
+
+ assert.Equal(t, http.StatusBadRequest, result.StatusCode)
}
-func TestLoginUser(t *testing.T) {
- if err := logger.InitLoggers(); err != nil {
- log.Fatalf("Failed to initialize loggers: %v", err)
+func TestAuthHandler_LoginUser_GrpcError(t *testing.T) {
+ require.NoError(t, logger.InitLoggers())
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
+
+ mockGrpcClient := new(mocks.MockGrpcClient)
+ grpcErr := status.Error(codes.Internal, "gRPC Error")
+
+ // Настройка мока
+ mockGrpcClient.On("LoginUser", mock.Anything, mock.Anything, mock.Anything).Return(&gen.UserResponse{}, grpcErr)
+
+ authHandler := AuthHandler{
+ client: mockGrpcClient,
}
- defer logger.SyncLoggers()
- mockAuthUseCase := &mocks.MockAuthUseCase{}
- mockSessionService := &mocks.MockServiceSession{}
- mockJwtToken := &mocks.MockJwtTokenService{}
- handler := NewAuthHandler(mockAuthUseCase, mockSessionService, mockJwtToken)
user := domain.User{
- Username: "testuser",
+ Username: "test_user",
Password: "password123",
}
- body, _ := json.Marshal(user)
- req := httptest.NewRequest("POST", "/api/auth/login", bytes.NewReader(body))
+ body, _ := easyjson.Marshal(user)
+
+ req := httptest.NewRequest(http.MethodPost, "/auth/login", bytes.NewBuffer(body))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
- // Настройка моков для успешного входа
- mockAuthUseCase.MockLoginUser = func(ctx context.Context, user *domain.User) (*domain.User, error) {
- if user.Username == "testuser" && user.Password == "password123" {
- return &domain.User{
- UUID: "test-uuid",
- Username: "testuser",
- Email: "test@example.com",
- }, nil
+ authHandler.LoginUser(w, req)
+
+ result := w.Result()
+ defer func(Body io.ReadCloser) {
+ err := Body.Close()
+ if err != nil {
+ return
}
- return nil, fmt.Errorf("invalid credentials")
- }
+ }(result.Body)
- mockSessionService.MockCreateSession = func(ctx context.Context, r *http.Request, w http.ResponseWriter, user *domain.User) (*sessions.Session, error) {
- session := &sessions.Session{
- Values: map[interface{}]interface{}{
- "session_id": "session-id-123",
- },
+ assert.Equal(t, http.StatusInternalServerError, result.StatusCode)
+ mockGrpcClient.AssertExpectations(t)
+}
+
+func TestAuthHandler_LoginUser_ConversionError(t *testing.T) {
+ require.NoError(t, logger.InitLoggers())
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
}
- return session, nil
+ }()
+
+ mockGrpcClient := new(mocks.MockGrpcClient)
+ utilsMock := &utils.MockUtils{}
+
+ mockResponse := &gen.UserResponse{
+ SessionId: "session123",
+ Jwttoken: "token123",
+ User: &gen.User{
+ Id: "test_user_id",
+ Username: "test_user",
+ Email: "test@example.com",
+ },
}
- mockJwtToken.MockCreate = func(session *sessions.Session, exp int64) (string, error) {
- return "fake-jwt-token", nil
+ // Настройка моков
+ mockGrpcClient.On("LoginUser", mock.Anything, mock.Anything, mock.Anything).Return(mockResponse, nil)
+ utilsMock.On("ConvertAuthResponseProtoToGo", mockResponse, "session123").
+ Return(domain.AuthResponse{}, errors.New("conversion error"))
+
+ authHandler := AuthHandler{
+ client: mockGrpcClient,
+ utils: utilsMock,
}
- // Вызов обработчика
- handler.LoginUser(w, req)
+ user := domain.User{
+ Username: "test_user",
+ Password: "password123",
+ }
+ body, _ := easyjson.Marshal(user)
- res := w.Result()
- defer res.Body.Close()
+ req := httptest.NewRequest(http.MethodPost, "/auth/login", bytes.NewBuffer(body))
+ req.Header.Set("Content-Type", "application/json")
+ w := httptest.NewRecorder()
- // Проверка кода статуса
- assert.Equal(t, http.StatusOK, res.StatusCode)
+ authHandler.LoginUser(w, req)
- // Проверка на наличие и значение csrf_token cookie
- cookies := res.Cookies()
- var csrfTokenCookie *http.Cookie
- for _, cookie := range cookies {
- if cookie.Name == "csrf_token" {
- csrfTokenCookie = cookie
- break
+ result := w.Result()
+ defer func(Body io.ReadCloser) {
+ err := Body.Close()
+ if err != nil {
+ return
}
+ }(result.Body)
+
+ assert.Equal(t, http.StatusInternalServerError, result.StatusCode)
+ mockGrpcClient.AssertExpectations(t)
+ utilsMock.AssertExpectations(t)
+}
+
+func TestAuthHandler_LogoutUser_Success(t *testing.T) {
+ require.NoError(t, logger.InitLoggers())
+ defer func() { _ = logger.SyncLoggers() }()
+
+ mockGrpcClient := new(mocks.MockGrpcClient)
+ mockSession := new(mocks.MockServiceSession)
+ mockResponse := &gen.LogoutUserResponse{}
+
+ // Настройка моков
+ mockSession.MockLogoutSession = func(ctx context.Context, sessionID string) error {
+ return nil
}
- require.NotNil(t, csrfTokenCookie, "Expected csrf_token cookie")
- assert.Equal(t, "fake-jwt-token", csrfTokenCookie.Value)
+ mockGrpcClient.On("LogoutUser", mock.Anything, mock.Anything, mock.Anything).Return(mockResponse, nil)
+
+ authHandler := AuthHandler{
+ client: mockGrpcClient,
+ sessionService: mockSession,
+ }
+
+ req := httptest.NewRequest(http.MethodPost, "/auth/logout", nil)
+ req.Header.Set("X-Real-IP", "127.0.0.1")
+ // Установка куков в запрос
+ req.AddCookie(&http.Cookie{
+ Name: "session_id",
+ Value: "test_session_id",
+ })
+ req.AddCookie(&http.Cookie{
+ Name: "csrf_token",
+ Value: "test_csrf_token",
+ })
- // Проверка структуры ответа
- var response map[string]interface{}
- err := json.NewDecoder(res.Body).Decode(&response)
- assert.NoError(t, err)
+ // Запись ответа
+ w := httptest.NewRecorder()
- assert.Equal(t, "session-id-123", response["session_id"])
+ authHandler.LogoutUser(w, req)
+
+ result := w.Result()
+ defer func(Body io.ReadCloser) {
+ err := Body.Close()
+ if err != nil {
+ return
+ }
+ }(result.Body)
- // Проверка данных пользователя в ответе
- userData := response["user"].(map[string]interface{})
- assert.Equal(t, "test-uuid", userData["id"])
- assert.Equal(t, "testuser", userData["username"])
- assert.Equal(t, "test@example.com", userData["email"])
+ assert.Equal(t, http.StatusOK, result.StatusCode)
+ mockGrpcClient.AssertExpectations(t)
- // Тестирование на случай неверных учетных данных
- mockAuthUseCase.MockLoginUser = func(ctx context.Context, user *domain.User) (*domain.User, error) {
- return nil, fmt.Errorf("invalid credentials")
+ cookies := result.Cookies()
+ require.Len(t, cookies, 2)
+ assert.Equal(t, "session_id", cookies[0].Name)
+ assert.Equal(t, "csrf_token", cookies[1].Name)
+ assert.Equal(t, "", cookies[0].Value)
+ assert.Equal(t, "", cookies[1].Value)
+}
+
+func TestAuthHandler_LogoutUser_SessionIDError(t *testing.T) {
+ require.NoError(t, logger.InitLoggers())
+ defer func() { _ = logger.SyncLoggers() }()
+
+ mockSession := new(mocks.MockServiceSession)
+
+ authHandler := AuthHandler{
+ sessionService: mockSession,
}
- w = httptest.NewRecorder()
- handler.LoginUser(w, req)
- assert.Equal(t, http.StatusInternalServerError, w.Code, "Expected status 500")
+ req := httptest.NewRequest(http.MethodPost, "/auth/logout", nil)
+ w := httptest.NewRecorder()
+
+ authHandler.LogoutUser(w, req)
+
+ result := w.Result()
+ defer func(Body io.ReadCloser) {
+ err := Body.Close()
+ if err != nil {
+ return
+ }
+ }(result.Body)
+
+ assert.Equal(t, http.StatusUnauthorized, result.StatusCode)
}
-func TestLogoutUser(t *testing.T) {
- if err := logger.InitLoggers(); err != nil {
- log.Fatalf("Failed to initialize loggers: %v", err)
+func TestAuthHandler_LogoutUser_GrpcError(t *testing.T) {
+ require.NoError(t, logger.InitLoggers())
+ defer func() { _ = logger.SyncLoggers() }()
+
+ mockGrpcClient := new(mocks.MockGrpcClient)
+ mockSession := new(mocks.MockServiceSession)
+ grpcErr := status.Error(codes.Internal, "gRPC Logout Error")
+
+ mockSession.MockLogoutSession = func(ctx context.Context, sessionID string) error {
+ return errors.New("session error")
}
- defer logger.SyncLoggers()
+ mockGrpcClient.On("LogoutUser", mock.Anything, mock.Anything, mock.Anything).Return(&gen.LogoutUserResponse{}, grpcErr)
- mockAuthUseCase := &mocks.MockAuthUseCase{}
- mockSessionService := &mocks.MockServiceSession{}
- mockJwtToken := &mocks.MockJwtTokenService{}
- handler := NewAuthHandler(mockAuthUseCase, mockSessionService, mockJwtToken)
+ authHandler := AuthHandler{
+ client: mockGrpcClient,
+ sessionService: mockSession,
+ }
- req := httptest.NewRequest("POST", "/api/auth/logout", nil)
- req.Header.Set("X-CSRF-Token", "Bearer valid-token")
+ req := httptest.NewRequest(http.MethodPost, "/auth/logout", nil)
+ // Установка куков в запрос
+ req.AddCookie(&http.Cookie{
+ Name: "session_id",
+ Value: "test_session_id",
+ })
+ req.AddCookie(&http.Cookie{
+ Name: "csrf_token",
+ Value: "test_csrf_token",
+ })
w := httptest.NewRecorder()
- // Настройка моков для успешного выхода
- mockJwtToken.MockValidate = func(tokenString string) (*middleware.JwtCsrfClaims, error) {
- if tokenString == "valid-token" {
- return &middleware.JwtCsrfClaims{}, nil
+ authHandler.LogoutUser(w, req)
+
+ result := w.Result()
+ defer func(Body io.ReadCloser) {
+ err := Body.Close()
+ if err != nil {
+ return
}
- return nil, fmt.Errorf("invalid token")
+ }(result.Body)
+
+ assert.Equal(t, http.StatusInternalServerError, result.StatusCode)
+ mockGrpcClient.AssertExpectations(t)
+}
+
+func TestAuthHandler_PutUser_Success(t *testing.T) {
+ require.NoError(t, logger.InitLoggers())
+ defer func() { _ = logger.SyncLoggers() }()
+
+ // Моки
+ mockGrpcClient := new(mocks.MockGrpcClient)
+ mockSession := new(mocks.MockServiceSession)
+
+ mockGrpcClient.On("PutUser", mock.Anything, mock.Anything, mock.Anything).Return(&gen.UpdateResponse{}, nil)
+
+ authHandler := AuthHandler{
+ client: mockGrpcClient,
+ sessionService: mockSession,
}
- mockSessionService.MockLogoutSession = func(ctx context.Context, r *http.Request, w http.ResponseWriter) error {
- return nil
+ // Тело запроса
+ body := new(bytes.Buffer)
+ writer := multipart.NewWriter(body)
+
+ _ = writer.WriteField("metadata", `{
+ "UUID": "123",
+ "Username": "test_user",
+ "Password": "password123",
+ "Email": "test@example.com",
+ "Name": "Test User",
+ "Score": 100,
+ "Sex": "M",
+ "GuestCount": 2,
+ "Birthdate": "2000-01-01T00:00:00Z",
+ "IsHost": true
+ }`)
+
+ part, _ := writer.CreateFormFile("avatar", "avatar.png")
+ _, err := part.Write([]byte("fake_image_data"))
+ if err != nil {
+ return
+ }
+
+ err = writer.Close()
+ if err != nil {
+ return
}
- handler.LogoutUser(w, req)
- res := w.Result()
- defer res.Body.Close()
- assert.Equal(t, http.StatusOK, res.StatusCode)
- var logoutResponse map[string]string
- err := json.NewDecoder(res.Body).Decode(&logoutResponse)
- assert.NoError(t, err)
- assert.Equal(t, "Logout successfully", logoutResponse["response"])
+ req := httptest.NewRequest(http.MethodPut, "/user", body)
+ req.Header.Set("Content-Type", writer.FormDataContentType())
+ req.Header.Set("X-CSRF-Token", "csrf_token")
+ req.Header.Set("X-Real-IP", "127.0.0.1")
+ req.AddCookie(&http.Cookie{
+ Name: "session_id",
+ Value: "test_session_id",
+ })
+ w := httptest.NewRecorder()
+
+ authHandler.PutUser(w, req)
- cookies := res.Cookies()
- var csrfTokenCookie *http.Cookie
- for _, cookie := range cookies {
- if cookie.Name == "csrf_token" {
- csrfTokenCookie = cookie
- break
+ result := w.Result()
+ defer func(Body io.ReadCloser) {
+ err = Body.Close()
+ if err != nil {
+ return
}
- }
- require.NotNil(t, csrfTokenCookie, "Expected csrf_token cookie")
- assert.Empty(t, csrfTokenCookie.Value)
- assert.True(t, csrfTokenCookie.Expires.Before(time.Now()), "Expected csrf_token cookie to be expired")
+ }(result.Body)
- req.Header.Set("X-CSRF-Token", "Bearer invalid-token")
- w = httptest.NewRecorder()
+ assert.Equal(t, http.StatusOK, result.StatusCode)
+ mockGrpcClient.AssertExpectations(t)
+}
- handler.LogoutUser(w, req)
+func TestAuthHandler_PutUser_SessionIDError(t *testing.T) {
+ require.NoError(t, logger.InitLoggers())
+ defer func() { _ = logger.SyncLoggers() }()
- res = w.Result()
- assert.Equal(t, http.StatusInternalServerError, res.StatusCode)
+ mockSession := new(mocks.MockServiceSession)
- req.Header.Set("X-CSRF-Token", "Bearer valid-token")
- mockSessionService.MockLogoutSession = func(ctx context.Context, r *http.Request, w http.ResponseWriter) error {
- return fmt.Errorf("logout error")
+ authHandler := AuthHandler{
+ sessionService: mockSession,
}
- w = httptest.NewRecorder()
- handler.LogoutUser(w, req)
+ req := httptest.NewRequest(http.MethodPut, "/user", nil)
+ w := httptest.NewRecorder()
+
+ authHandler.PutUser(w, req)
+
+ result := w.Result()
+ defer func(Body io.ReadCloser) {
+ err := Body.Close()
+ if err != nil {
+ return
+ }
+ }(result.Body)
- res = w.Result()
- assert.Equal(t, http.StatusInternalServerError, res.StatusCode)
+ assert.Equal(t, http.StatusUnauthorized, result.StatusCode)
}
-func TestPutUser(t *testing.T) {
- // Инициализация логгера
- if err := logger.InitLoggers(); err != nil {
- log.Fatalf("Failed to initialize loggers: %v", err)
+func TestAuthHandler_PutUser_ParseMultipartFormError(t *testing.T) {
+ require.NoError(t, logger.InitLoggers())
+ defer func() { _ = logger.SyncLoggers() }()
+
+ mockSession := new(mocks.MockServiceSession)
+
+ authHandler := AuthHandler{
+ sessionService: mockSession,
}
- defer logger.SyncLoggers()
- mockAuthUseCase := &mocks.MockAuthUseCase{}
- mockSessionService := &mocks.MockServiceSession{}
- mockJwtToken := &mocks.MockJwtTokenService{}
- handler := NewAuthHandler(mockAuthUseCase, mockSessionService, mockJwtToken)
+ req := httptest.NewRequest(http.MethodPut, "/user", bytes.NewBuffer([]byte("invalid_data")))
+ req.Header.Set("Content-Type", "multipart/form-data")
+ req.AddCookie(&http.Cookie{
+ Name: "session_id",
+ Value: "test_session_id",
+ })
+ w := httptest.NewRecorder()
+
+ authHandler.PutUser(w, req)
- var buf bytes.Buffer
- writer := multipart.NewWriter(&buf)
+ result := w.Result()
+ defer func(Body io.ReadCloser) {
+ err := Body.Close()
+ if err != nil {
+ return
+ }
+ }(result.Body)
- user := domain.User{
- Username: "updateduser",
- Email: "updated@example.com",
- Sex: "M",
- Name: "Robert Baron",
+ assert.Equal(t, http.StatusInternalServerError, result.StatusCode)
+}
+
+func TestAuthHandler_PutUser_MetadataParseError(t *testing.T) {
+ require.NoError(t, logger.InitLoggers())
+ defer func() { _ = logger.SyncLoggers() }()
+
+ mockSession := new(mocks.MockServiceSession)
+
+ authHandler := AuthHandler{
+ sessionService: mockSession,
}
- metadata, _ := json.Marshal(user)
- _ = writer.WriteField("metadata", string(metadata))
- writer.Close()
+ body := new(bytes.Buffer)
+ writer := multipart.NewWriter(body)
+ _ = writer.WriteField("metadata", "invalid_json")
+ err := writer.Close()
+ if err != nil {
+ return
+ }
- req := httptest.NewRequest("PUT", "/api/users/", &buf)
+ req := httptest.NewRequest(http.MethodPut, "/user", body)
req.Header.Set("Content-Type", writer.FormDataContentType())
- req.Header.Set("X-CSRF-Token", "Bearer valid-token")
+ req.AddCookie(&http.Cookie{
+ Name: "session_id",
+ Value: "test_session_id",
+ })
w := httptest.NewRecorder()
- mockJwtToken.MockValidate = func(tokenString string) (*middleware.JwtCsrfClaims, error) {
- if tokenString == "valid-token" {
- return &middleware.JwtCsrfClaims{}, nil
+ authHandler.PutUser(w, req)
+
+ result := w.Result()
+ defer func(Body io.ReadCloser) {
+ err = Body.Close()
+ if err != nil {
+ return
}
- return nil, fmt.Errorf("invalid token")
- }
+ }(result.Body)
+
+ assert.Equal(t, http.StatusBadRequest, result.StatusCode)
+}
+
+func TestAuthHandler_PutUser_GrpcError(t *testing.T) {
+ require.NoError(t, logger.InitLoggers())
+ defer func() { _ = logger.SyncLoggers() }()
- mockSessionService.MockGetUserID = func(ctx context.Context, r *http.Request) (string, error) {
- return "user123", nil
+ mockGrpcClient := new(mocks.MockGrpcClient)
+ mockSession := new(mocks.MockServiceSession)
+ grpcErr := status.Error(codes.Internal, "gRPC Error")
+ mockGrpcClient.On("PutUser", mock.Anything, mock.Anything, mock.Anything).Return(&gen.UpdateResponse{}, grpcErr)
+
+ authHandler := AuthHandler{
+ client: mockGrpcClient,
+ sessionService: mockSession,
}
- mockAuthUseCase.MockPutUser = func(ctx context.Context, creds *domain.User, userID string, avatar *multipart.FileHeader) error {
- return nil
+ body := new(bytes.Buffer)
+ writer := multipart.NewWriter(body)
+ _ = writer.WriteField("metadata", `{"Username": "test_user"}`)
+ err := writer.Close()
+ if err != nil {
+ return
}
- handler.PutUser(w, req)
+ req := httptest.NewRequest(http.MethodPut, "/user", body)
+ req.Header.Set("Content-Type", writer.FormDataContentType())
+ req.AddCookie(&http.Cookie{
+ Name: "session_id",
+ Value: "test_session_id",
+ })
+ w := httptest.NewRecorder()
- res := w.Result()
- assert.Equal(t, http.StatusOK, res.StatusCode)
+ authHandler.PutUser(w, req)
- mockAuthUseCase.MockPutUser = func(ctx context.Context, creds *domain.User, userID string, avatar *multipart.FileHeader) error {
- return fmt.Errorf("update error") // Ошибка обновления
- }
+ result := w.Result()
+ defer func(Body io.ReadCloser) {
+ err := Body.Close()
+ if err != nil {
+ return
+ }
+ }(result.Body)
- w = httptest.NewRecorder()
- handler.PutUser(w, req)
- res = w.Result()
- assert.Equal(t, http.StatusInternalServerError, res.StatusCode)
+ assert.Equal(t, http.StatusInternalServerError, result.StatusCode)
+ mockGrpcClient.AssertExpectations(t)
}
-func TestGetUserById(t *testing.T) {
- if err := logger.InitLoggers(); err != nil {
- t.Fatalf("Failed to initialize loggers: %v", err)
+func TestAuthHandler_GetUserById_Success(t *testing.T) {
+ require.NoError(t, logger.InitLoggers())
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
+
+ mockGrpcClient := new(mocks.MockGrpcClient)
+ utilsMock := &utils.MockUtils{}
+
+ mockResponse := &gen.GetUserByIdResponse{
+ User: &gen.MetadataOneUser{
+ Uuid: "test_uuid",
+ Username: "test_username",
+ Email: "test@example.com",
+ Name: "test_name",
+ Score: 2,
+ Avatar: "avatar.png",
+ Sex: "M",
+ GuestCount: 5,
+ Birthdate: nil,
+ IsHost: false,
+ },
}
- defer logger.SyncLoggers()
- mockAuthUseCase := &mocks.MockAuthUseCase{}
+ // Настройка моков
+ mockGrpcClient.On("GetUserById", mock.Anything, mock.Anything, mock.Anything).Return(mockResponse, nil)
+ utilsMock.On("ConvertUserResponseProtoToGo", mockResponse.User).Return(domain.UserDataResponse{
+ Uuid: "test_uuid",
+ Username: "test_username",
+ Email: "test@example.com",
+ Name: "test_name",
+ Score: 2,
+ Avatar: "avatar.png",
+ Sex: "M",
+ GuestCount: 5,
+ Birthdate: time.Time{},
+ IsHost: false,
+ }, nil)
+
+ authHandler := AuthHandler{
+ client: mockGrpcClient,
+ utils: utilsMock,
+ }
- handler := NewAuthHandler(mockAuthUseCase, nil, nil) // вторым параметром передаем nil, так как MockServiceSession больше не нужен
+ req := httptest.NewRequest(http.MethodGet, "/user/user123", nil)
+ req = mux.SetURLVars(req, map[string]string{"userId": "user123"})
+ req.Header.Set("X-Real-IP", "127.0.0.1")
+ req.AddCookie(&http.Cookie{
+ Name: "session_id",
+ Value: "test_session_id",
+ })
+ w := httptest.NewRecorder()
- router := mux.NewRouter()
- router.HandleFunc("/api/users/{userId}", handler.GetUserById)
+ authHandler.GetUserById(w, req)
- router.Use(middleware.RequestIDMiddleware)
+ result := w.Result()
+ defer func(Body io.ReadCloser) {
+ err := Body.Close()
+ if err != nil {
+ return
+ }
+ }(result.Body)
- // Тестовый пользователь (успешный случай)
- t.Run("Successful GetUserById", func(t *testing.T) {
- // Устанавливаем поведение мока для успешного вызова
- mockAuthUseCase.MockGetUserById = func(ctx context.Context, id string) (*domain.User, error) {
- return &domain.User{UUID: "test-uuid", Username: "testuser", Email: "test@example.com"}, nil
+ assert.Equal(t, http.StatusOK, result.StatusCode)
+ mockGrpcClient.AssertExpectations(t)
+ utilsMock.AssertExpectations(t)
+}
+
+func TestAuthHandler_GetUserById_GrpcError(t *testing.T) {
+ require.NoError(t, logger.InitLoggers())
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
}
+ }()
- req := httptest.NewRequest("GET", "/api/users/test-uuid", nil)
- w := httptest.NewRecorder()
+ mockGrpcClient := new(mocks.MockGrpcClient)
- router.ServeHTTP(w, req)
+ grpcErr := status.Error(codes.Internal, "gRPC error")
+ mockGrpcClient.On("GetUserById", mock.Anything, mock.Anything, mock.Anything).Return(&gen.GetUserByIdResponse{}, grpcErr)
- // Проверяем статус код
- res := w.Result()
- assert.Equal(t, http.StatusOK, res.StatusCode)
+ authHandler := AuthHandler{
+ client: mockGrpcClient,
+ }
- // Проверяем заголовок Content-Type
- assert.Equal(t, "application/json", res.Header.Get("Content-Type"))
+ req := httptest.NewRequest(http.MethodGet, "/user/user123", nil)
+ req = mux.SetURLVars(req, map[string]string{"userId": "user123"})
+ w := httptest.NewRecorder()
- // Декодируем ответ
- var response domain.User
- err := json.NewDecoder(res.Body).Decode(&response)
- assert.NoError(t, err)
+ authHandler.GetUserById(w, req)
- // Проверяем содержимое ответа
- assert.Equal(t, "test-uuid", response.UUID)
- assert.Equal(t, "testuser", response.Username)
- assert.Equal(t, "test@example.com", response.Email)
- })
+ result := w.Result()
+ defer func(Body io.ReadCloser) {
+ err := Body.Close()
+ if err != nil {
+ return
+ }
+ }(result.Body)
- // Тестовый пользователь (случай ошибки - пользователь не найден)
- t.Run("User Not Found", func(t *testing.T) {
- // Устанавливаем поведение мока для ошибки
- mockAuthUseCase.MockGetUserById = func(ctx context.Context, id string) (*domain.User, error) {
- return nil, fmt.Errorf("user not found")
+ assert.Equal(t, http.StatusInternalServerError, result.StatusCode)
+ mockGrpcClient.AssertExpectations(t)
+}
+
+func TestAuthHandler_GetUserById_ConversionError(t *testing.T) {
+ require.NoError(t, logger.InitLoggers())
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
}
+ }()
+
+ mockGrpcClient := new(mocks.MockGrpcClient)
+ utilsMock := &utils.MockUtils{}
+
+ mockResponse := &gen.GetUserByIdResponse{
+ User: &gen.MetadataOneUser{
+ Uuid: "test_uuid",
+ Username: "test_username",
+ Email: "test@example.com",
+ Name: "test_name",
+ Score: 2,
+ Avatar: "avatar.png",
+ Sex: "M",
+ GuestCount: 5,
+ Birthdate: nil,
+ IsHost: false,
+ },
+ }
- // Создаем новый тестовый запрос
- req := httptest.NewRequest("GET", "/api/users/non-existent-uuid", nil)
- w := httptest.NewRecorder()
+ // Моки
+ mockGrpcClient.On("GetUserById", mock.Anything, mock.Anything, mock.Anything).Return(mockResponse, nil)
+ utilsMock.On("ConvertUserResponseProtoToGo", mockResponse.User).
+ Return(domain.UserDataResponse{}, errors.New("conversion error"))
- // Выполняем запрос через маршрутизатор
- router.ServeHTTP(w, req)
+ authHandler := AuthHandler{
+ client: mockGrpcClient,
+ utils: utilsMock,
+ }
- // Проверяем статус код
- res := w.Result()
- assert.Equal(t, http.StatusNotFound, res.StatusCode)
+ req := httptest.NewRequest(http.MethodGet, "/user/user123", nil)
+ req = mux.SetURLVars(req, map[string]string{"userId": "user123"})
+ w := httptest.NewRecorder()
- // Проверяем содержание ответа (можно уточнить в зависимости от реализации h.handleError)
- var response map[string]string
- err := json.NewDecoder(res.Body).Decode(&response)
- assert.NoError(t, err)
- assert.Contains(t, response["error"], "user not found")
- })
+ authHandler.GetUserById(w, req)
+
+ result := w.Result()
+ defer func(Body io.ReadCloser) {
+ err := Body.Close()
+ if err != nil {
+ return
+ }
+ }(result.Body)
+
+ assert.Equal(t, http.StatusInternalServerError, result.StatusCode)
+ mockGrpcClient.AssertExpectations(t)
+ utilsMock.AssertExpectations(t)
}
-func TestGetAllUsers(t *testing.T) {
- if err := logger.InitLoggers(); err != nil {
- log.Fatalf("Failed to initialize loggers: %v", err)
+func TestAuthHandler_GetAllUsers_Success(t *testing.T) {
+ require.NoError(t, logger.InitLoggers())
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
+
+ // Моки
+ mockGrpcClient := new(mocks.MockGrpcClient)
+ utilsMock := &utils.MockUtils{}
+
+ mockUsers := &gen.AllUsersResponse{
+ Users: []*gen.MetadataOneUser{
+ {
+ Uuid: "test_uuid1",
+ Username: "test_username1",
+ Email: "test1@example.com",
+ Name: "test_name1",
+ Score: 2,
+ Avatar: "avatar1.png",
+ Sex: "M",
+ GuestCount: 5,
+ Birthdate: nil,
+ IsHost: false,
+ },
+ {
+ Uuid: "test_uuid2",
+ Username: "test_username2",
+ Email: "test2@example.com",
+ Name: "test_name2",
+ Score: 3,
+ Avatar: "avatar2.png",
+ Sex: "F",
+ GuestCount: 2,
+ Birthdate: nil,
+ IsHost: false,
+ },
+ },
+ }
+
+ convertedUsers := []*domain.UserDataResponse{
+ {
+ Uuid: "test_uuid1",
+ Username: "test_username1",
+ Email: "test1@example.com",
+ Name: "test_name1",
+ Score: 2,
+ Avatar: "avatar1.png",
+ Sex: "M",
+ GuestCount: 5,
+ Birthdate: time.Time{},
+ IsHost: false,
+ },
+ {
+ Uuid: "test_uuid2",
+ Username: "test_username2",
+ Email: "test2@example.com",
+ Name: "test_name2",
+ Score: 3,
+ Avatar: "avatar2.png",
+ Sex: "F",
+ GuestCount: 2,
+ Birthdate: time.Time{},
+ IsHost: false,
+ },
}
- defer logger.SyncLoggers()
- mockAuthUseCase := &mocks.MockAuthUseCase{}
- mockSessionService := &mocks.MockServiceSession{}
- mockJwtToken := &mocks.MockJwtTokenService{}
- handler := NewAuthHandler(mockAuthUseCase, mockSessionService, mockJwtToken)
+ mockGrpcClient.On("GetAllUsers", mock.Anything, mock.Anything, mock.Anything).Return(mockUsers, nil)
+ utilsMock.On("ConvertUsersProtoToGo", mockUsers).Return(convertedUsers, nil)
+
+ authHandler := AuthHandler{
+ client: mockGrpcClient,
+ utils: utilsMock,
+ }
- req := httptest.NewRequest("GET", "/users", nil)
+ req := httptest.NewRequest(http.MethodGet, "/users", nil)
+ req.Header.Set("X-Real-IP", "127.0.0.1")
w := httptest.NewRecorder()
- mockAuthUseCase.MockGetAllUser = func(ctx context.Context) ([]domain.User, error) {
- return []domain.User{
- {UUID: "test-uuid-1", Username: "user1", Email: "user1@example.com"},
- {UUID: "test-uuid-2", Username: "user2", Email: "user2@example.com"},
- }, nil
- }
+ // Вызов метода
+ authHandler.GetAllUsers(w, req)
- handler.GetAllUsers(w, req)
+ // Проверка
+ result := w.Result()
+ defer func(Body io.ReadCloser) {
+ err := Body.Close()
+ if err != nil {
+ return
+ }
+ }(result.Body)
- res := w.Result()
- assert.Equal(t, http.StatusOK, res.StatusCode)
+ assert.Equal(t, http.StatusOK, result.StatusCode)
+ mockGrpcClient.AssertExpectations(t)
+ utilsMock.AssertExpectations(t)
- var response map[string]interface{}
- err := json.NewDecoder(res.Body).Decode(&response)
- assert.NoError(t, err)
+ body, err := io.ReadAll(result.Body)
+ require.NoError(t, err)
- usersData := response["users"].([]interface{})
- assert.Len(t, usersData, 2)
+ var response domain.GetAllUsersResponse
+ err = easyjson.Unmarshal(body, &response)
+ require.NoError(t, err)
+ assert.Equal(t, convertedUsers, response.Users)
+}
- mockAuthUseCase.MockGetAllUser = func(ctx context.Context) ([]domain.User, error) {
- return nil, fmt.Errorf("error fetching users") // Ошибка при извлечении пользователей
+func TestAuthHandler_GetAllUsers_GrpcError(t *testing.T) {
+ require.NoError(t, logger.InitLoggers())
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
+
+ mockGrpcClient := new(mocks.MockGrpcClient)
+ grpcErr := status.Error(codes.Internal, "gRPC Error")
+
+ mockGrpcClient.On("GetAllUsers", mock.Anything, mock.Anything, mock.Anything).Return(&gen.AllUsersResponse{}, grpcErr)
+
+ authHandler := AuthHandler{
+ client: mockGrpcClient,
}
- w = httptest.NewRecorder()
- handler.GetAllUsers(w, req)
- res = w.Result()
- assert.Equal(t, http.StatusInternalServerError, res.StatusCode)
+ req := httptest.NewRequest(http.MethodGet, "/users", nil)
+ w := httptest.NewRecorder()
+
+ // Вызов метода
+ authHandler.GetAllUsers(w, req)
+
+ // Проверка
+ result := w.Result()
+ defer func(Body io.ReadCloser) {
+ err := Body.Close()
+ if err != nil {
+ return
+ }
+ }(result.Body)
+
+ assert.Equal(t, http.StatusInternalServerError, result.StatusCode)
+ mockGrpcClient.AssertExpectations(t)
}
-func TestGetSessionData(t *testing.T) {
- if err := logger.InitLoggers(); err != nil {
- log.Fatalf("Failed to initialize loggers: %v", err)
+func TestAuthHandler_GetAllUsers_ConversionError(t *testing.T) {
+ require.NoError(t, logger.InitLoggers())
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
+
+ mockGrpcClient := new(mocks.MockGrpcClient)
+ utilsMock := &utils.MockUtils{}
+
+ mockUsers := &gen.AllUsersResponse{
+ Users: []*gen.MetadataOneUser{
+ {
+ Uuid: "test_uuid1",
+ Username: "test_username1",
+ Email: "test1@example.com",
+ Name: "test_name1",
+ Score: 2,
+ Avatar: "avatar1.png",
+ Sex: "M",
+ GuestCount: 5,
+ Birthdate: nil,
+ IsHost: false,
+ },
+ {
+ Uuid: "test_uuid2",
+ Username: "test_username2",
+ Email: "test2@example.com",
+ Name: "test_name2",
+ Score: 3,
+ Avatar: "avatar2.png",
+ Sex: "F",
+ GuestCount: 2,
+ Birthdate: nil,
+ IsHost: false,
+ },
+ },
}
- defer logger.SyncLoggers()
- mockAuthUseCase := &mocks.MockAuthUseCase{}
- mockSessionService := &mocks.MockServiceSession{}
- mockJwtToken := &mocks.MockJwtTokenService{}
- handler := NewAuthHandler(mockAuthUseCase, mockSessionService, mockJwtToken)
+ mockGrpcClient.On("GetAllUsers", mock.Anything, mock.Anything, mock.Anything).Return(mockUsers, nil)
+ utilsMock.On("ConvertUsersProtoToGo", mockUsers).Return(nil, errors.New("conversion error"))
+
+ authHandler := AuthHandler{
+ client: mockGrpcClient,
+ utils: utilsMock,
+ }
- req := httptest.NewRequest("GET", "/session", nil)
+ req := httptest.NewRequest(http.MethodGet, "/users", nil)
w := httptest.NewRecorder()
- mockSessionService.MockGetSessionData = func(ctx context.Context, r *http.Request) (*map[string]interface{}, error) {
- return &map[string]interface{}{
- "id": "test-uuid",
- "avatar": "images/avatar.jpg",
- }, nil
- }
+ // Вызов метода
+ authHandler.GetAllUsers(w, req)
- handler.GetSessionData(w, req)
+ // Проверка
+ result := w.Result()
+ defer func(Body io.ReadCloser) {
+ err := Body.Close()
+ if err != nil {
+ return
+ }
+ }(result.Body)
- res := w.Result()
- assert.Equal(t, http.StatusOK, res.StatusCode)
+ assert.Equal(t, http.StatusInternalServerError, result.StatusCode)
+ mockGrpcClient.AssertExpectations(t)
+ utilsMock.AssertExpectations(t)
+}
- var response map[string]interface{}
- err := json.NewDecoder(res.Body).Decode(&response)
- assert.NoError(t, err)
+func TestAuthHandler_GetSessionData_Success(t *testing.T) {
+ require.NoError(t, logger.InitLoggers())
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
- assert.Equal(t, "test-uuid", response["id"])
- assert.Equal(t, "images/avatar.jpg", response["avatar"])
+ // Моки
+ mockGrpcClient := new(mocks.MockGrpcClient)
+ utilsMock := &utils.MockUtils{}
- mockSessionService.MockGetSessionData = func(ctx context.Context, r *http.Request) (*map[string]interface{}, error) {
- return nil, fmt.Errorf("session error")
+ sessionID := "test_session_id"
+ mockResponse := &gen.SessionDataResponse{
+ Id: "test_id",
+ Avatar: "test_avatar",
}
- w = httptest.NewRecorder()
- handler.GetSessionData(w, req)
- res = w.Result()
- assert.Equal(t, http.StatusInternalServerError, res.StatusCode)
-}
+ convertedResponse := domain.SessionData{
+ Id: "test_id",
+ Avatar: "test_avatar",
+ }
+
+ // Настройка моков
+ mockGrpcClient.On("GetSessionData", mock.Anything, &gen.GetSessionDataRequest{SessionId: sessionID}, mock.Anything).Return(mockResponse, nil)
+ utilsMock.On("ConvertSessionDataProtoToGo", mockResponse).Return(convertedResponse, nil)
+ sessionMock := &mocks.MockServiceSession{}
-func TestRefreshCsrfToken(t *testing.T) {
- if err := logger.InitLoggers(); err != nil {
- t.Fatalf("Failed to initialize loggers: %v", err)
+ authHandler := AuthHandler{
+ client: mockGrpcClient,
+ utils: utilsMock,
+ sessionService: sessionMock,
}
- defer logger.SyncLoggers()
- mockAuthUseCase := &mocks.MockAuthUseCase{}
- mockSessService := &mocks.MockServiceSession{}
- mockJWT := &mocks.MockJwtTokenService{}
+ // Запрос
+ req := httptest.NewRequest(http.MethodGet, "/auth/session", nil)
+ req.Header.Set("X-Real-IP", "127.0.0.1")
+ req.AddCookie(&http.Cookie{
+ Name: "session_id",
+ Value: "test_session_id",
+ })
+ w := httptest.NewRecorder()
- handler := NewAuthHandler(mockAuthUseCase, mockSessService, mockJWT)
+ // Вызов метода
+ authHandler.GetSessionData(w, req)
- router := mux.NewRouter()
- router.HandleFunc("/api/csrf/refresh", handler.RefreshCsrfToken)
- router.Use(middleware.RequestIDMiddleware) // Ваш собственный middleware для установки request_id
+ // Проверка
+ result := w.Result()
+ defer func(Body io.ReadCloser) {
+ err := Body.Close()
+ if err != nil {
+ return
+ }
+ }(result.Body)
- testRequestID := "test-request-id"
+ assert.Equal(t, http.StatusOK, result.StatusCode)
- router.Use(func(next http.Handler) http.Handler {
- return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- ctx := context.WithValue(r.Context(), "request_id", testRequestID)
- next.ServeHTTP(w, r.WithContext(ctx))
- })
- })
+ var response domain.SessionData
+ err := easyjson.UnmarshalFromReader(result.Body, &response)
+ require.NoError(t, err)
+ assert.Equal(t, convertedResponse, response)
- newCsrfToken := "new-csrf-token"
+ mockGrpcClient.AssertExpectations(t)
+ utilsMock.AssertExpectations(t)
+}
- mockSession := &sessions.Session{
- Values: map[interface{}]interface{}{
- "session_id": "session-id-123",
- },
+func TestAuthHandler_GetSessionData_SessionError(t *testing.T) {
+ require.NoError(t, logger.InitLoggers())
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
+
+ sessionMock := &mocks.MockServiceSession{}
+
+ authHandler := AuthHandler{
+ sessionService: sessionMock,
}
- t.Run("Successful RefreshCsrfToken", func(t *testing.T) {
- mockSessService.MockGetSession = func(ctx context.Context, r *http.Request) (*sessions.Session, error) {
- return mockSession, nil
+ // Запрос
+ req := httptest.NewRequest(http.MethodGet, "/auth/session", nil)
+ w := httptest.NewRecorder()
+
+ // Вызов метода
+ authHandler.GetSessionData(w, req)
+
+ // Проверка
+ result := w.Result()
+ defer func(Body io.ReadCloser) {
+ err := Body.Close()
+ if err != nil {
+ return
}
+ }(result.Body)
- mockJWT.MockCreate = func(s *sessions.Session, tokenExpTime int64) (string, error) {
- return newCsrfToken, nil
+ assert.Equal(t, http.StatusUnauthorized, result.StatusCode)
+}
+
+func TestAuthHandler_GetSessionData_GrpcError(t *testing.T) {
+ require.NoError(t, logger.InitLoggers())
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
}
+ }()
- req := httptest.NewRequest("POST", "/api/csrf/refresh", nil)
- w := httptest.NewRecorder()
+ mockGrpcClient := new(mocks.MockGrpcClient)
+ sessionMock := &mocks.MockServiceSession{}
+ sessionID := "test_session_id"
+ grpcErr := status.Error(codes.Internal, "gRPC error")
- router.ServeHTTP(w, req)
+ // Настройка моков
+ mockGrpcClient.On("GetSessionData", mock.Anything, &gen.GetSessionDataRequest{SessionId: sessionID}, mock.Anything).Return(&gen.SessionDataResponse{}, grpcErr)
- res := w.Result()
- assert.Equal(t, http.StatusOK, res.StatusCode)
+ authHandler := AuthHandler{
+ client: mockGrpcClient,
+ sessionService: sessionMock,
+ }
- cookies := res.Cookies()
- var csrfCookie *http.Cookie
- for _, cookie := range cookies {
- if cookie.Name == "csrf_token" {
- csrfCookie = cookie
- break
- }
+ // Запрос
+ req := httptest.NewRequest(http.MethodGet, "/auth/session", nil)
+ req.AddCookie(&http.Cookie{
+ Name: "session_id",
+ Value: "test_session_id",
+ })
+ w := httptest.NewRecorder()
+
+ // Вызов метода
+ authHandler.GetSessionData(w, req)
+
+ // Проверка
+ result := w.Result()
+ defer func(Body io.ReadCloser) {
+ err := Body.Close()
+ if err != nil {
+ return
}
- assert.NotNil(t, csrfCookie)
- assert.Equal(t, newCsrfToken, csrfCookie.Value)
- assert.Equal(t, "/", csrfCookie.Path)
- assert.Equal(t, http.SameSiteStrictMode, csrfCookie.SameSite)
+ }(result.Body)
- var response map[string]string
- err := json.NewDecoder(res.Body).Decode(&response)
- assert.NoError(t, err)
- assert.Equal(t, newCsrfToken, response["csrf_token"])
- })
+ assert.Equal(t, http.StatusInternalServerError, result.StatusCode)
+
+ mockGrpcClient.AssertExpectations(t)
+}
- t.Run("Failed to GetSession - Unauthorized", func(t *testing.T) {
- mockSessService.MockGetSession = func(ctx context.Context, r *http.Request) (*sessions.Session, error) {
- return nil, fmt.Errorf("session not found")
+func TestAuthHandler_GetSessionData_ConversionError(t *testing.T) {
+ require.NoError(t, logger.InitLoggers())
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
}
- req := httptest.NewRequest("POST", "/api/csrf/refresh", nil)
- w := httptest.NewRecorder()
+ }()
- router.ServeHTTP(w, req)
+ mockGrpcClient := new(mocks.MockGrpcClient)
+ utilsMock := &utils.MockUtils{}
+ sessionMock := &mocks.MockServiceSession{}
- res := w.Result()
- assert.Equal(t, http.StatusUnauthorized, res.StatusCode)
+ sessionID := "test_session_id"
+ mockResponse := &gen.SessionDataResponse{
+ Id: "test_id",
+ Avatar: "test_avatar",
+ }
+
+ // Настройка моков
+ mockGrpcClient.On("GetSessionData", mock.Anything, &gen.GetSessionDataRequest{SessionId: sessionID}, mock.Anything).Return(mockResponse, nil)
+ utilsMock.On("ConvertSessionDataProtoToGo", mockResponse).Return(nil, errors.New("conversion error"))
+
+ authHandler := AuthHandler{
+ client: mockGrpcClient,
+ utils: utilsMock,
+ sessionService: sessionMock,
+ }
- // Используем io.ReadAll для чтения тела ответа
- bodyBytes, err := io.ReadAll(res.Body)
- assert.NoError(t, err)
- assert.Equal(t, "Unauthorized\n", string(bodyBytes))
+ // Запрос
+ req := httptest.NewRequest(http.MethodGet, "/auth/session", nil)
+ req.AddCookie(&http.Cookie{
+ Name: "session_id",
+ Value: "test_session_id",
})
+ w := httptest.NewRecorder()
+
+ // Вызов метода
+ authHandler.GetSessionData(w, req)
- t.Run("Failed to Create CSRF Token - Internal Server Error", func(t *testing.T) {
- mockSessService.MockGetSession = func(ctx context.Context, r *http.Request) (*sessions.Session, error) {
- return mockSession, nil
+ // Проверка
+ result := w.Result()
+ defer func(Body io.ReadCloser) {
+ err := Body.Close()
+ if err != nil {
+ return
}
- mockJWT.MockCreate = func(s *sessions.Session, tokenExpTime int64) (string, error) {
- return "", fmt.Errorf("failed to create CSRF token")
+ }(result.Body)
+
+ assert.Equal(t, http.StatusInternalServerError, result.StatusCode)
+
+ mockGrpcClient.AssertExpectations(t)
+ utilsMock.AssertExpectations(t)
+}
+
+func TestAuthHandler_RefreshCsrfToken_Success(t *testing.T) {
+ require.NoError(t, logger.InitLoggers())
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
}
+ }()
- req := httptest.NewRequest("POST", "/api/csrf/refresh", nil)
- w := httptest.NewRecorder()
+ // Моки
+ mockGrpcClient := new(mocks.MockGrpcClient)
+ mockResponse := &gen.RefreshCsrfTokenResponse{
+ CsrfToken: "new_csrf_token",
+ }
- router.ServeHTTP(w, req)
+ // Настройка мока
+ mockGrpcClient.On("RefreshCsrfToken", mock.Anything, mock.Anything, mock.Anything).Return(mockResponse, nil)
- res := w.Result()
- assert.Equal(t, http.StatusInternalServerError, res.StatusCode)
+ authHandler := AuthHandler{
+ client: mockGrpcClient,
+ }
- // Проверяем тело ответа
- bodyBytes, err := io.ReadAll(res.Body)
- assert.NoError(t, err)
- assert.Equal(t, "Failed to create CSRF token\n", string(bodyBytes))
+ // Запрос с валидным sessionID
+ req := httptest.NewRequest(http.MethodPost, "/auth/refresh-csrf", nil)
+ req.Header.Set("X-Real-IP", "127.0.0.1")
+ req.AddCookie(&http.Cookie{
+ Name: "session_id",
+ Value: "test_session_id",
})
+ w := httptest.NewRecorder()
+
+ // Вызов метода
+ authHandler.RefreshCsrfToken(w, req)
+
+ // Проверка
+ result := w.Result()
+ defer func(Body io.ReadCloser) {
+ err := Body.Close()
+ if err != nil {
+ return
+ }
+ }(result.Body)
+
+ assert.Equal(t, http.StatusOK, result.StatusCode)
+
+ // Проверяем CSRF куки
+ cookies := result.Cookies()
+ require.Len(t, cookies, 1)
+ assert.Equal(t, "csrf_token", cookies[0].Name)
+ assert.Equal(t, "new_csrf_token", cookies[0].Value)
+
+ mockGrpcClient.AssertExpectations(t)
+}
+
+func TestAuthHandler_RefreshCsrfToken_NoSessionID(t *testing.T) {
+ require.NoError(t, logger.InitLoggers())
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
+
+ authHandler := AuthHandler{}
+
+ // Запрос без sessionID
+ req := httptest.NewRequest(http.MethodPost, "/auth/refresh-csrf", nil)
+ w := httptest.NewRecorder()
+
+ // Вызов метода
+ authHandler.RefreshCsrfToken(w, req)
+
+ // Проверка
+ result := w.Result()
+ defer func(Body io.ReadCloser) {
+ err := Body.Close()
+ if err != nil {
+ return
+ }
+ }(result.Body)
+
+ assert.Equal(t, http.StatusUnauthorized, result.StatusCode)
}
+
+func TestAuthHandler_RefreshCsrfToken_GrpcError(t *testing.T) {
+ require.NoError(t, logger.InitLoggers())
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
+
+ mockGrpcClient := new(mocks.MockGrpcClient)
+ grpcErr := status.Error(codes.Internal, "gRPC error")
+
+ // Настройка мока
+ mockGrpcClient.On("RefreshCsrfToken", mock.Anything, mock.Anything, mock.Anything).Return(&gen.RefreshCsrfTokenResponse{}, grpcErr)
+
+ authHandler := AuthHandler{
+ client: mockGrpcClient,
+ }
+
+ // Запрос с валидным sessionID
+ req := httptest.NewRequest(http.MethodPost, "/auth/refresh-csrf", nil)
+ req.AddCookie(&http.Cookie{
+ Name: "session_id",
+ Value: "test_session_id",
+ })
+ w := httptest.NewRecorder()
+
+ // Вызов метода
+ authHandler.RefreshCsrfToken(w, req)
+
+ // Проверка
+ result := w.Result()
+ defer func(Body io.ReadCloser) {
+ err := Body.Close()
+ if err != nil {
+ return
+ }
+ }(result.Body)
+
+ assert.Equal(t, http.StatusInternalServerError, result.StatusCode)
+ mockGrpcClient.AssertExpectations(t)
+}
\ No newline at end of file
diff --git a/internal/chat/controller/chat_controller.go b/internal/chat/controller/chat_controller.go
index 79739df..33017a9 100644
--- a/internal/chat/controller/chat_controller.go
+++ b/internal/chat/controller/chat_controller.go
@@ -8,12 +8,13 @@ import (
"2024_2_FIGHT-CLUB/internal/service/middleware"
"2024_2_FIGHT-CLUB/internal/service/session"
"context"
- "encoding/json"
"errors"
"github.com/gorilla/mux"
"github.com/gorilla/websocket"
+ "github.com/mailru/easyjson"
"go.uber.org/zap"
"net/http"
+ "sync"
"time"
)
@@ -34,16 +35,25 @@ func NewChatController(chatUseCase usecase.ChatUseCase, sessionService session.I
const (
socketBufferSize = 1024
messageBufferSize = 256
+ maxConnections = 100
+ messageRateLimit = 5
)
var (
upgrader = websocket.Upgrader{ReadBufferSize: socketBufferSize, WriteBufferSize: socketBufferSize, CheckOrigin: func(r *http.Request) bool { return true }}
mapUserConn = make(map[string]*Client)
+ connCounter = 0
+ mu sync.Mutex
)
func (cc *ChatHandler) SetConnection(w http.ResponseWriter, r *http.Request) {
start := time.Now()
requestID := middleware.GetRequestID(r.Context())
+ logger.AccessLogger.Info("Received SetConnection request",
+ zap.String("request_id", requestID),
+ zap.String("method", r.Method),
+ zap.String("url", r.URL.String()),
+ )
var err error
statusCode := http.StatusOK
clientIP := r.RemoteAddr
@@ -52,7 +62,22 @@ func (cc *ChatHandler) SetConnection(w http.ResponseWriter, r *http.Request) {
} else if forwarded := r.Header.Get("X-Forwarded-For"); forwarded != "" {
clientIP = forwarded
}
+
+ mu.Lock()
+ if connCounter >= maxConnections {
+ mu.Unlock()
+ err = errors.New("too many connections")
+ http.Error(w, "Too many connections", http.StatusTooManyRequests)
+ return
+ }
+ connCounter++
+ mu.Unlock()
+
defer func() {
+ mu.Lock()
+ connCounter--
+ mu.Unlock()
+
if statusCode == http.StatusOK {
metrics.HttpRequestsTotal.WithLabelValues(r.Method, r.URL.Path, http.StatusText(statusCode), clientIP).Inc()
} else {
@@ -61,39 +86,53 @@ func (cc *ChatHandler) SetConnection(w http.ResponseWriter, r *http.Request) {
duration := time.Since(start).Seconds()
metrics.HttpRequestDuration.WithLabelValues(r.Method, r.URL.Path, clientIP).Observe(duration)
}()
- logger.AccessLogger.Info("Received RegisterUser request",
- zap.String("request_id", requestID),
- zap.String("method", r.Method),
- zap.String("url", r.URL.String()),
- )
sess, err := session.GetSessionId(r)
if err != nil {
logger.AccessLogger.Info("Failed to get sessionId",
zap.String("request_id", requestID),
zap.Error(err))
+ cc.handleError(w, err, requestID)
+ return
+ }
+
+ UserID, err := cc.sessionService.GetUserID(r.Context(), sess)
+ if err != nil || UserID == "" {
+ logger.AccessLogger.Info("Unauthorized user",
+ zap.String("request_id", requestID),
+ zap.Error(err))
+ cc.handleError(w, err, requestID)
+ return
+ }
+
+ if _, ok := w.(http.Hijacker); !ok {
+ http.Error(w, "WebSocket upgrade not supported", http.StatusInternalServerError)
+ logger.AccessLogger.Error("ResponseWriter does not support Hijacker")
return
}
socket, err := upgrader.Upgrade(w, r, nil)
if err != nil {
+ cc.handleError(w, errors.New("failed to upgrade connection"), requestID)
logger.AccessLogger.Info("Failed to get socket",
zap.String("request_id", requestID),
zap.Error(err))
return
}
+
client := &Client{
Socket: socket,
Receive: make(chan *domain.Message, messageBufferSize),
ChatController: cc,
+ RateLimiter: NewRateLimiter(messageRateLimit, time.Second, 10*time.Second),
}
- UserID, err := cc.sessionService.GetUserID(r.Context(), sess)
mapUserConn[UserID] = client
defer func() {
delete(mapUserConn, UserID)
close(client.Receive)
}()
+
go client.Write()
go client.Read(UserID)
cc.SendChatMsg(r.Context(), requestID)
@@ -136,13 +175,17 @@ func (cc *ChatHandler) SendChatMsg(ctx context.Context, reqID string) {
zap.Int("status", http.StatusOK),
)
- return
}
func (cc *ChatHandler) GetAllChats(w http.ResponseWriter, r *http.Request) {
start := time.Now()
requestID := middleware.GetRequestID(r.Context())
ctx, cancel := middleware.WithTimeout(r.Context())
+ logger.AccessLogger.Info("Received GetAllChats request",
+ zap.String("request_id", requestID),
+ zap.String("method", r.Method),
+ zap.String("url", r.URL.String()),
+ )
defer cancel()
var err error
statusCode := http.StatusOK
@@ -161,11 +204,6 @@ func (cc *ChatHandler) GetAllChats(w http.ResponseWriter, r *http.Request) {
duration := time.Since(start).Seconds()
metrics.HttpRequestDuration.WithLabelValues(r.Method, r.URL.Path, clientIP).Observe(duration)
}()
- logger.AccessLogger.Info("Received RegisterUser request",
- zap.String("request_id", requestID),
- zap.String("method", r.Method),
- zap.String("url", r.URL.String()),
- )
var (
lastTimeQuery = r.URL.Query().Get("lastTime")
@@ -186,10 +224,11 @@ func (cc *ChatHandler) GetAllChats(w http.ResponseWriter, r *http.Request) {
}
sess, err := session.GetSessionId(r)
- if err != nil {
+ if err != nil || sess == "" {
logger.AccessLogger.Info("Failed to get sessionId",
zap.String("request_id", requestID),
zap.Error(err))
+ err = errors.New("session not found")
cc.handleError(w, err, requestID)
return
}
@@ -210,19 +249,18 @@ func (cc *ChatHandler) GetAllChats(w http.ResponseWriter, r *http.Request) {
return
}
- body := map[string]interface{}{
- "chats": chats,
+ body := domain.AllChats{
+ Chats: chats,
}
-
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
w.WriteHeader(http.StatusOK)
- if err := json.NewEncoder(w).Encode(body); err != nil {
- logger.AccessLogger.Error("Failed to encode response",
+ if _, err = easyjson.MarshalToWriter(&body, w); err != nil {
+ logger.AccessLogger.Warn("Failed to encode response",
zap.String("request_id", requestID),
- zap.Error(err),
- )
+ zap.Error(err))
return
}
+
duration := time.Since(start)
logger.AccessLogger.Info("Completed GetAllChats request",
zap.String("request_id", requestID),
@@ -262,7 +300,7 @@ func (cc *ChatHandler) GetChat(w http.ResponseWriter, r *http.Request) {
metrics.HttpRequestDuration.WithLabelValues(r.Method, r.URL.Path, clientIP).Observe(duration)
}()
- logger.AccessLogger.Info("Received RegisterUser request",
+ logger.AccessLogger.Info("Received GetChat request",
zap.String("request_id", requestID),
zap.String("method", r.Method),
zap.String("url", r.URL.String()),
@@ -282,10 +320,11 @@ func (cc *ChatHandler) GetChat(w http.ResponseWriter, r *http.Request) {
}
sess, err := session.GetSessionId(r)
- if err != nil {
+ if err != nil || sess == "" {
logger.AccessLogger.Info("Failed to get sessionId",
zap.String("request_id", requestID),
zap.Error(err))
+ err = errors.New("session not found")
cc.handleError(w, err, requestID)
return
}
@@ -306,18 +345,19 @@ func (cc *ChatHandler) GetChat(w http.ResponseWriter, r *http.Request) {
return
}
- body := map[string]interface{}{
- "chat": messages,
+ body := domain.AllMessages{
+ Chat: messages,
}
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
w.WriteHeader(http.StatusOK)
- if err := json.NewEncoder(w).Encode(body); err != nil {
- logger.AccessLogger.Error("Failed to encode response",
+ if _, err = easyjson.MarshalToWriter(&body, w); err != nil {
+ logger.AccessLogger.Warn("Failed to encode response",
zap.String("request_id", requestID),
zap.Error(err),
)
return
}
+
duration := time.Since(start)
logger.AccessLogger.Info("Completed GetAllChats request",
zap.String("request_id", requestID),
@@ -332,28 +372,26 @@ func (cc *ChatHandler) handleError(w http.ResponseWriter, err error, requestID s
zap.Error(err),
)
- w.Header().Set("Content-Type", "application/json")
- errorResponse := map[string]string{"error": err.Error()}
+ w.Header().Set("Content-Type", "application/json; charset=UTF-8")
+ errorResponse := domain.ErrorResponse{
+ Error: err.Error(),
+ }
var status int
switch err.Error() {
case "error fetching chats", "error fetching messages",
"failed to generate session id", "failed to save session", "error generating random bytes for session ID",
- "failed to delete session", "failed to get session id from request cookie":
- w.WriteHeader(http.StatusInternalServerError)
+ "failed to delete session", "failed to get session id from request cookie", "failed to upgrade connection":
status = http.StatusInternalServerError
case "error sending message",
"failed to parse lastTime":
- w.WriteHeader(http.StatusBadRequest)
status = http.StatusBadRequest
case "session not found", "user ID not found in session":
- w.WriteHeader(http.StatusUnauthorized)
status = http.StatusUnauthorized
default:
- w.WriteHeader(http.StatusInternalServerError)
status = http.StatusInternalServerError
}
-
- if jsonErr := json.NewEncoder(w).Encode(errorResponse); jsonErr != nil {
+ w.WriteHeader(status)
+ if _, jsonErr := easyjson.MarshalToWriter(&errorResponse, w); jsonErr != nil {
logger.AccessLogger.Error("Failed to encode error response",
zap.String("request_id", requestID),
zap.Error(jsonErr),
diff --git a/internal/chat/controller/client.go b/internal/chat/controller/client.go
index fb96c50..9e81e15 100644
--- a/internal/chat/controller/client.go
+++ b/internal/chat/controller/client.go
@@ -2,39 +2,197 @@ package controller
import (
"2024_2_FIGHT-CLUB/domain"
- "encoding/json"
+ "2024_2_FIGHT-CLUB/internal/service/logger"
+ "fmt"
"github.com/gorilla/websocket"
+ "github.com/mailru/easyjson"
+ "go.uber.org/zap"
+ "strings"
+ "sync"
"time"
)
+type RateLimiter struct {
+ mu sync.Mutex
+ tokens int // Текущее количество доступных токенов
+ maxTokens int // Максимальное количество токенов
+ interval time.Duration // Интервал пополнения токенов
+ lastFill time.Time // Последнее время пополнения токенов
+ blockUntil time.Time // Время, до которого пользователь заблокирован
+ blockTime time.Duration // Длительность блокировки
+}
+
+// NewRateLimiter создает RateLimiter с заданным лимитом, интервалом и временем блокировки.
+func NewRateLimiter(maxTokens int, refillInterval, blockTime time.Duration) *RateLimiter {
+ return &RateLimiter{
+ tokens: maxTokens,
+ maxTokens: maxTokens,
+ interval: refillInterval,
+ blockTime: blockTime,
+ lastFill: time.Now(),
+ }
+}
+
+// Allow проверяет, можно ли совершить действие.
+func (rl *RateLimiter) Allow() (bool, error) {
+ rl.mu.Lock()
+ defer rl.mu.Unlock()
+
+ now := time.Now()
+
+ // Проверяем, заблокирован ли пользователь
+ if now.Before(rl.blockUntil) {
+ return false, fmt.Errorf("user is blocked until %s", rl.blockUntil.Format(time.RFC3339))
+ }
+
+ // Пополняем токены
+ elapsed := now.Sub(rl.lastFill)
+ if elapsed > rl.interval {
+ newTokens := int(elapsed / rl.interval)
+ rl.tokens += newTokens
+ if rl.tokens > rl.maxTokens {
+ rl.tokens = rl.maxTokens
+ }
+ rl.lastFill = now
+ }
+
+ // Проверяем наличие токенов
+ if rl.tokens > 0 {
+ rl.tokens-- // Используем токен
+ return true, nil
+ }
+
+ // Если нет токенов, блокируем пользователя
+ rl.blockUntil = now.Add(rl.blockTime)
+ return false, fmt.Errorf("rate limit exceeded, user blocked for %s", rl.blockTime)
+}
+
type Client struct {
Socket *websocket.Conn
Receive chan *domain.Message
ChatController *ChatHandler
+ RateLimiter *RateLimiter
}
func (c *Client) Read(userID string) {
defer c.Socket.Close()
+
+ // Создаем таймер для отслеживания времени бездействия
+ idleTimeout := 20 * time.Minute
+ lastActive := time.Now()
+ closeOnIdle := make(chan struct{})
+
+ go func() {
+ // Закрываем соединение при исчерпании таймера
+ for {
+ select {
+ case <-closeOnIdle:
+ return
+ case <-time.After(10 * time.Second): // Проверяем тайм-аут каждые 10 секунд
+ if time.Since(lastActive) > idleTimeout {
+ logger.AccessLogger.Info("Connection closed due to inactivity",
+ zap.String("user_id", userID))
+ c.Socket.Close()
+ return
+ }
+ }
+ }
+ }()
+
for {
msg := &domain.Message{}
- _, jsonMessage, err := c.Socket.ReadMessage()
+
+ // Читаем JSON-сообщение из сокета
+ err := c.Socket.ReadJSON(&msg)
if err != nil {
- return
+ if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
+ logger.AccessLogger.Info("Unexpected socket closure",
+ zap.String("user_id", userID),
+ zap.Error(err))
+ }
+ break
}
- err = json.Unmarshal(jsonMessage, msg)
- if err != nil {
- return
+
+ // Проверяем, является ли сообщение пустым или состоит только из пробелов/переводов строк/табуляций
+ if strings.TrimSpace(msg.Content) == "" {
+ // Логируем и отправляем клиенту сообщение об ошибке
+ logger.AccessLogger.Warn("Invalid message content received (only whitespace characters)",
+ zap.String("user_id", userID))
+ errMsg := map[string]interface{}{
+ "response": "Invalid message content: only whitespace characters, tabs, or line breaks are not allowed.",
+ "sent": false,
+ }
+ if writeErr := c.Socket.WriteJSON(errMsg); writeErr != nil {
+ logger.AccessLogger.Error("Failed to send invalid message content error to client",
+ zap.String("user_id", userID),
+ zap.Error(writeErr))
+ }
+ continue
}
+
+ // Обновляем последнее время активности
+ lastActive = time.Now()
+
+ // Проверяем лимит сообщений
+ allowed, rateErr := c.RateLimiter.Allow()
+ if !allowed {
+ logger.AccessLogger.Error("Rate limit exceeded",
+ zap.String("user_id", userID),
+ zap.Error(rateErr))
+
+ // Сообщаем клиенту о блокировке
+ errMsg := map[string]interface{}{
+ "response": rateErr.Error(),
+ "sent": false,
+ }
+ if writeErr := c.Socket.WriteJSON(errMsg); writeErr != nil {
+ logger.AccessLogger.Error("Failed to send rate limit error to client",
+ zap.String("user_id", userID),
+ zap.Error(writeErr))
+ }
+ continue
+ }
+
+ // Устанавливаем SenderID на основе текущего пользователя
msg.SenderID = userID
- c.ChatController.Messages <- msg
+
+ // Отправляем сообщение в канал для обработки
+ select {
+ case c.ChatController.Messages <- msg:
+ // Возвращаем успешный ответ клиенту
+ successMsg := map[string]interface{}{
+ "response": "Message delivered successfully",
+ "sent": true,
+ }
+ if writeErr := c.Socket.WriteJSON(successMsg); writeErr != nil {
+ logger.AccessLogger.Error("Failed to send success message to client",
+ zap.String("user_id", userID),
+ zap.Error(writeErr))
+ }
+ default:
+ logger.AccessLogger.Warn("Message channel is full, dropping message",
+ zap.String("user_id", userID))
+ errMsg := map[string]interface{}{
+ "response": "Message channel is full. Please try again later.",
+ "sent": false,
+ }
+ if writeErr := c.Socket.WriteJSON(errMsg); writeErr != nil {
+ logger.AccessLogger.Error("Failed to send channel full error to client",
+ zap.String("user_id", userID),
+ zap.Error(writeErr))
+ }
+ }
}
+
+ // Завершаем горутину таймера при выходе из цикла
+ close(closeOnIdle)
}
func (c *Client) Write() {
defer c.Socket.Close()
for msg := range c.Receive {
msg.CreatedAt = time.Now()
- jsonForSend, err := json.Marshal(msg)
+ jsonForSend, err := easyjson.Marshal(msg)
if err != nil {
return
}
diff --git a/internal/chat/repository/chat_repository.go b/internal/chat/repository/chat_repository.go
index 52b2e1e..ff25fec 100644
--- a/internal/chat/repository/chat_repository.go
+++ b/internal/chat/repository/chat_repository.go
@@ -99,8 +99,7 @@ func (cr *Repo) GetMessages(ctx context.Context, userID1 string, userID2 string,
err = cr.db.
Where("(\"senderId\" = ? AND \"receiverId\" = ?) OR (\"senderId\" = ? AND \"receiverId\" = ?)", userID1, userID2, userID2, userID1).
Where("\"createdAt\" < ?", lastSentTime).
- Order("\"createdAt\" DESC").
- Limit(20).
+ Order("\"createdAt\" ASC").
Find(&messages).Error
if err != nil {
diff --git a/internal/cities/controller/cities_controller.go b/internal/cities/controller/cities_controller.go
index 35f05df..d98c8af 100644
--- a/internal/cities/controller/cities_controller.go
+++ b/internal/cities/controller/cities_controller.go
@@ -1,13 +1,15 @@
package controller
import (
+ "2024_2_FIGHT-CLUB/domain"
"2024_2_FIGHT-CLUB/internal/service/logger"
"2024_2_FIGHT-CLUB/internal/service/metrics"
"2024_2_FIGHT-CLUB/internal/service/middleware"
+ "2024_2_FIGHT-CLUB/internal/service/utils"
"2024_2_FIGHT-CLUB/microservices/city_service/controller/gen"
- "encoding/json"
"errors"
"github.com/gorilla/mux"
+ "github.com/mailru/easyjson"
"go.uber.org/zap"
"google.golang.org/grpc/status"
"net/http"
@@ -16,11 +18,13 @@ import (
type CityHandler struct {
client gen.CityServiceClient
+ utils utils.UtilsInterface
}
-func NewCityHandler(client gen.CityServiceClient) *CityHandler {
+func NewCityHandler(client gen.CityServiceClient, utils utils.UtilsInterface) *CityHandler {
return &CityHandler{
client: client,
+ utils: utils,
}
}
@@ -67,10 +71,20 @@ func (h *CityHandler) GetCities(w http.ResponseWriter, r *http.Request) {
}
return
}
-
+ payload, err := h.utils.ConvertAllCitiesProtoToGo(cities)
+ if err != nil {
+ logger.AccessLogger.Error("Failed to convert cities data",
+ zap.String("request_id", requestID),
+ zap.Error(err))
+ statusCode = h.handleError(w, err, requestID)
+ return
+ }
+ body := domain.AllCitiesResponse{
+ Cities: payload,
+ }
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
w.WriteHeader(http.StatusOK)
- if err := json.NewEncoder(w).Encode(cities); err != nil {
+ if _, err = easyjson.MarshalToWriter(body, w); err != nil {
logger.AccessLogger.Error("Failed to encode response",
zap.String("request_id", requestID),
zap.Error(err),
@@ -129,9 +143,20 @@ func (h *CityHandler) GetOneCity(w http.ResponseWriter, r *http.Request) {
}
return
}
+ payload, err := h.utils.ConvertOneCityProtoToGo(city.City)
+ if err != nil {
+ logger.AccessLogger.Error("Failed to convert city data",
+ zap.String("request_id", requestID),
+ zap.Error(err))
+ statusCode = h.handleError(w, err, requestID)
+ return
+ }
+ body := domain.OneCityResponse{
+ City: payload,
+ }
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
w.WriteHeader(http.StatusOK)
- if err := json.NewEncoder(w).Encode(city); err != nil {
+ if _, err = easyjson.MarshalToWriter(body, w); err != nil {
logger.AccessLogger.Error("Failed to encode response",
zap.String("request_id", requestID),
zap.Error(err))
@@ -152,28 +177,28 @@ func (h *CityHandler) handleError(w http.ResponseWriter, err error, requestID st
)
w.Header().Set("Content-Type", "application/json")
- errorResponse := map[string]string{"error": err.Error()}
- var status int
+ errorResponse := domain.ErrorResponse{
+ Error: err.Error(),
+ }
+ var statusCode int
switch err.Error() {
case "input contains invalid characters",
"input exceeds character limit":
- w.WriteHeader(http.StatusBadRequest)
- status = http.StatusBadRequest
+ statusCode = http.StatusBadRequest
case "error fetching all cities",
"error fetching city":
- w.WriteHeader(http.StatusInternalServerError)
- status = http.StatusInternalServerError
+ statusCode = http.StatusInternalServerError
default:
- w.WriteHeader(http.StatusInternalServerError)
- status = http.StatusInternalServerError
+ statusCode = http.StatusInternalServerError
}
- if jsonErr := json.NewEncoder(w).Encode(errorResponse); jsonErr != nil {
+ w.WriteHeader(statusCode)
+ if _, jsonErr := easyjson.MarshalToWriter(errorResponse, w); jsonErr != nil {
logger.AccessLogger.Error("Failed to encode error response",
zap.String("request_id", requestID),
zap.Error(jsonErr),
)
http.Error(w, jsonErr.Error(), http.StatusInternalServerError)
}
- return status
+ return statusCode
}
diff --git a/internal/cities/controller/cities_controller_test.go b/internal/cities/controller/cities_controller_test.go
index 72e417f..2edbcaa 100644
--- a/internal/cities/controller/cities_controller_test.go
+++ b/internal/cities/controller/cities_controller_test.go
@@ -3,155 +3,361 @@ package controller
import (
"2024_2_FIGHT-CLUB/domain"
"2024_2_FIGHT-CLUB/internal/service/logger"
+ "2024_2_FIGHT-CLUB/internal/service/utils"
+ "2024_2_FIGHT-CLUB/microservices/city_service/controller/gen"
"2024_2_FIGHT-CLUB/microservices/city_service/mocks"
- "context"
- "encoding/json"
"errors"
"github.com/gorilla/mux"
"github.com/stretchr/testify/assert"
- "log"
+ "github.com/stretchr/testify/mock"
+ "github.com/stretchr/testify/require"
+ "google.golang.org/grpc/codes"
+ "google.golang.org/grpc/status"
+ "io"
"net/http"
"net/http/httptest"
- "reflect"
"testing"
)
-func TestGetCitiesSuccess(t *testing.T) {
- if err := logger.InitLoggers(); err != nil {
- log.Fatalf("Failed to initialize loggers: %v", err)
+func TestCityHandler_GetCities_Success(t *testing.T) {
+ require.NoError(t, logger.InitLoggers())
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
+
+ // Моки
+ mockClient := new(mocks.MockGrpcClient)
+ mockUtils := &utils.MockUtils{}
+
+ mockResponse := &gen.GetCitiesResponse{
+ Cities: []*gen.City{
+ {
+ Id: 1,
+ Title: "Москва",
+ Entitle: "Moscow",
+ Description: "Some Desc",
+ Image: "test_image",
+ },
+ {
+ Id: 2,
+ Title: "Волгоград",
+ Entitle: "Volgograd",
+ Description: "Some Desc",
+ Image: "test_image",
+ },
+ },
}
- defer logger.SyncLoggers()
- mockUseCase := &mocks.MockCitiesUseCase{
- MockGetCities: func(ctx context.Context) ([]domain.City, error) {
- return []domain.City{
- {ID: 1, Title: "Moscow", EnTitle: "moscow", Description: "A large city in Russia."},
- }, nil
+
+ mockPayload := domain.AllCitiesResponse{
+ Cities: []*domain.City{
+ {
+ ID: 1,
+ Title: "Москва",
+ EnTitle: "Moscow",
+ Description: "Some Desc",
+ Image: "test_image",
+ },
+ {
+ ID: 2,
+ Title: "Волгоград",
+ EnTitle: "Volgograd",
+ Description: "Some Desc",
+ Image: "test_image",
+ },
},
}
- handler := NewCityHandler(mockUseCase)
+ // Настройка моков
+ mockClient.On("GetCities", mock.Anything, mock.Anything, mock.Anything).Return(mockResponse, nil)
+ mockUtils.On("ConvertAllCitiesProtoToGo", mockResponse).Return(mockPayload, nil)
- req, err := http.NewRequest("GET", "/api/cities", nil)
- if err != nil {
- t.Fatal(err)
+ cityHandler := CityHandler{
+ client: mockClient,
+ utils: mockUtils,
}
- rr := httptest.NewRecorder()
- handler.GetCities(rr, req)
+ req := httptest.NewRequest(http.MethodGet, "/cities", nil)
+ req.Header.Set("X-Real-IP", "127.0.0.1")
+ w := httptest.NewRecorder()
- if status := rr.Code; status != http.StatusOK {
- t.Errorf("expected status code %v, got %v", http.StatusOK, status)
- }
+ // Вызов метода
+ cityHandler.GetCities(w, req)
- expectedCities := []domain.City{
- {ID: 1, Title: "Moscow", EnTitle: "moscow", Description: "A large city in Russia."},
- }
- var responseData map[string][]domain.City
- if err := json.Unmarshal(rr.Body.Bytes(), &responseData); err != nil {
- t.Fatalf("failed to unmarshal response: %v", err)
- }
- if !reflect.DeepEqual(responseData["cities"], expectedCities) {
- t.Errorf("expected body %v, got %v", expectedCities, responseData["cities"])
- }
+ // Проверка
+ result := w.Result()
+ defer func(Body io.ReadCloser) {
+ err := Body.Close()
+ if err != nil {
+ return
+ }
+ }(result.Body)
+
+ body, _ := io.ReadAll(result.Body)
+ expectedResponse := `{"cities":[{"description":"Some Desc", "enTitle":"Moscow", "id":1, "image":"test_image", "title":"Москва"},{"description":"Some Desc", "enTitle":"Volgograd", "id":2, "image":"test_image", "title":"Волгоград"}]}`
+ assert.Equal(t, http.StatusOK, result.StatusCode)
+ assert.JSONEq(t, expectedResponse, string(body))
+
+ mockClient.AssertExpectations(t)
+ mockUtils.AssertExpectations(t)
}
-func TestGetCitiesFailure(t *testing.T) {
- if err := logger.InitLoggers(); err != nil {
- log.Fatalf("Failed to initialize loggers: %v", err)
+func TestCityHandler_GetCities_GrpcError(t *testing.T) {
+ require.NoError(t, logger.InitLoggers())
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
+
+ mockClient := new(mocks.MockGrpcClient)
+ mockUtils := &utils.MockUtils{}
+
+ grpcErr := status.Error(codes.Internal, "gRPC error")
+
+ // Настройка мока
+ mockClient.On("GetCities", mock.Anything, &gen.GetCitiesRequest{}, mock.Anything).Return(&gen.GetCitiesResponse{}, grpcErr)
+
+ cityHandler := CityHandler{
+ client: mockClient,
+ utils: mockUtils,
}
- defer logger.SyncLoggers()
- mockUseCase := &mocks.MockCitiesUseCase{
- MockGetCities: func(ctx context.Context) ([]domain.City, error) {
- return nil, errors.New("failed to retrieve cities")
+
+ req := httptest.NewRequest(http.MethodGet, "/cities", nil)
+ w := httptest.NewRecorder()
+
+ // Вызов метода
+ cityHandler.GetCities(w, req)
+
+ // Проверка
+ result := w.Result()
+ defer func(Body io.ReadCloser) {
+ err := Body.Close()
+ if err != nil {
+ return
+ }
+ }(result.Body)
+
+ assert.Equal(t, http.StatusInternalServerError, result.StatusCode)
+
+ mockClient.AssertExpectations(t)
+}
+
+func TestCityHandler_GetCities_ConversionError(t *testing.T) {
+ require.NoError(t, logger.InitLoggers())
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
+
+ mockClient := new(mocks.MockGrpcClient)
+ mockUtils := &utils.MockUtils{}
+
+ mockResponse := &gen.GetCitiesResponse{
+ Cities: []*gen.City{
+ {
+ Id: 1,
+ Title: "Москва",
+ Entitle: "Moscow",
+ Description: "Some Desc",
+ Image: "test_image",
+ },
+ {
+ Id: 2,
+ Title: "Волгоград",
+ Entitle: "Volgograd",
+ Description: "Some Desc",
+ Image: "test_image",
+ },
},
}
- handler := NewCityHandler(mockUseCase)
+ conversionErr := errors.New("conversion error")
+
+ // Настройка моков
+ mockClient.On("GetCities", mock.Anything, &gen.GetCitiesRequest{}, mock.Anything).Return(mockResponse, nil)
+ mockUtils.On("ConvertAllCitiesProtoToGo", mockResponse).Return(nil, conversionErr)
- req, err := http.NewRequest("GET", "/api/cities", nil)
- if err != nil {
- t.Fatal(err)
+ cityHandler := CityHandler{
+ client: mockClient,
+ utils: mockUtils,
}
- rr := httptest.NewRecorder()
- handler.GetCities(rr, req)
+ req := httptest.NewRequest(http.MethodGet, "/cities", nil)
+ w := httptest.NewRecorder()
- if status := rr.Code; status != http.StatusInternalServerError {
- t.Errorf("expected status code %v, got %v", http.StatusInternalServerError, status)
- }
+ // Вызов метода
+ cityHandler.GetCities(w, req)
- expectedError := map[string]string{"error": "failed to retrieve cities"}
- var actualError map[string]string
- if err := json.Unmarshal(rr.Body.Bytes(), &actualError); err != nil {
- t.Fatalf("failed to unmarshal response: %v", err)
- }
+ // Проверка
+ result := w.Result()
+ defer func(Body io.ReadCloser) {
+ err := Body.Close()
+ if err != nil {
+ return
+ }
+ }(result.Body)
- if !reflect.DeepEqual(actualError, expectedError) {
- t.Errorf("expected body %v, got %v", expectedError, actualError)
- }
+ assert.Equal(t, http.StatusInternalServerError, result.StatusCode)
+
+ mockClient.AssertExpectations(t)
+ mockUtils.AssertExpectations(t)
}
-func TestGetOneCitySuccess(t *testing.T) {
- if err := logger.InitLoggers(); err != nil {
- log.Fatalf("Failed to initialize loggers: %v", err)
- }
- defer logger.SyncLoggers()
- mockCity := domain.City{
- ID: 1,
- Title: "Test City",
- EnTitle: "test-city",
- Description: "This is a test city",
- Image: "image.jpg",
- }
+func TestCityHandler_GetOneCity_Success(t *testing.T) {
+ require.NoError(t, logger.InitLoggers())
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
+
+ // Моки
+ mockClient := new(mocks.MockGrpcClient)
+ mockUtils := new(utils.MockUtils)
- mockUseCase := &mocks.MockCitiesUseCase{
- MockGetOneCity: func(ctx context.Context, cityEnName string) (domain.City, error) {
- return mockCity, nil
+ mockCityResponse := &gen.GetOneCityResponse{
+ City: &gen.City{
+ Id: 1,
+ Title: "Москва",
+ Entitle: "Moscow",
+ Description: "Some Desc",
+ Image: "test_image",
},
}
- handler := NewCityHandler(mockUseCase)
+ mockClient.On("GetOneCity", mock.Anything, mock.Anything, mock.Anything).Return(mockCityResponse, nil)
+ mockUtils.On("ConvertOneCityProtoToGo", mockCityResponse.City).Return(domain.City{
+ ID: 1,
+ Title: "Москва",
+ EnTitle: "Moscow",
+ Description: "Some Desc",
+ Image: "test_image",
+ }, nil)
- req, err := http.NewRequest("GET", "/api/cities/test-city", nil)
- assert.NoError(t, err)
+ // Инициализация обработчика
+ cityHandler := CityHandler{
+ client: mockClient,
+ utils: mockUtils,
+ }
- req = mux.SetURLVars(req, map[string]string{"city": "test-city"})
- rr := httptest.NewRecorder()
+ // HTTP-запрос
+ req := httptest.NewRequest(http.MethodGet, "/city/test_city", nil)
+ req = mux.SetURLVars(req, map[string]string{"city": "test_city"})
+ w := httptest.NewRecorder()
- handler.GetOneCity(rr, req)
+ // Вызов метода
+ cityHandler.GetOneCity(w, req)
- assert.Equal(t, http.StatusOK, rr.Code)
+ // Проверка
+ result := w.Result()
+ defer func(Body io.ReadCloser) {
+ err := Body.Close()
+ if err != nil {
+ return
+ }
+ }(result.Body)
- var responseBody map[string]interface{}
- err = json.NewDecoder(rr.Body).Decode(&responseBody)
- assert.NoError(t, err)
- assert.Equal(t, mockCity.Title, responseBody["city"].(map[string]interface{})["title"])
+ assert.Equal(t, http.StatusOK, result.StatusCode)
+ mockClient.AssertExpectations(t)
+ mockUtils.AssertExpectations(t)
}
-// TestGetOneCityFailure tests the failure scenario of GetOneCity handler.
-func TestGetOneCityFailure(t *testing.T) {
- if err := logger.InitLoggers(); err != nil {
- log.Fatalf("Failed to initialize loggers: %v", err)
+func TestCityHandler_GetOneCity_GrpcError(t *testing.T) {
+ require.NoError(t, logger.InitLoggers())
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
+
+ // Моки
+ mockClient := new(mocks.MockGrpcClient)
+ grpcErr := status.Error(codes.Internal, "gRPC error")
+
+ mockClient.On("GetOneCity", mock.Anything, mock.Anything, mock.Anything).Return(&gen.GetOneCityResponse{}, grpcErr)
+
+ // Инициализация обработчика
+ cityHandler := CityHandler{
+ client: mockClient,
}
- defer logger.SyncLoggers()
- mockUseCase := &mocks.MockCitiesUseCase{
- MockGetOneCity: func(ctx context.Context, cityEnName string) (domain.City, error) {
- return domain.City{}, errors.New("city not found")
+
+ req := httptest.NewRequest(http.MethodGet, "/city/test_city", nil)
+ req = mux.SetURLVars(req, map[string]string{"city": "test_city"})
+ w := httptest.NewRecorder()
+
+ // Вызов метода
+ cityHandler.GetOneCity(w, req)
+
+ // Проверка
+ result := w.Result()
+ defer func(Body io.ReadCloser) {
+ err := Body.Close()
+ if err != nil {
+ return
+ }
+ }(result.Body)
+
+ assert.Equal(t, http.StatusInternalServerError, result.StatusCode)
+ mockClient.AssertExpectations(t)
+}
+
+func TestCityHandler_GetOneCity_ConversionError(t *testing.T) {
+ require.NoError(t, logger.InitLoggers())
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
+
+ // Моки
+ mockClient := new(mocks.MockGrpcClient)
+ mockUtils := new(utils.MockUtils)
+
+ mockCityResponse := &gen.GetOneCityResponse{
+ City: &gen.City{
+ Id: 1,
+ Title: "Москва",
+ Entitle: "Moscow",
+ Description: "Some Desc",
+ Image: "test_image",
},
}
- handler := NewCityHandler(mockUseCase)
+ mockClient.On("GetOneCity", mock.Anything, mock.Anything, mock.Anything).Return(mockCityResponse, nil)
+ mockUtils.On("ConvertOneCityProtoToGo", mockCityResponse.City).Return(nil, errors.New("conversion error"))
- req, err := http.NewRequest("GET", "/api/cities/test-city", nil)
- assert.NoError(t, err)
+ // Инициализация обработчика
+ cityHandler := CityHandler{
+ client: mockClient,
+ utils: mockUtils,
+ }
- req = mux.SetURLVars(req, map[string]string{"city": "test-city"})
- rr := httptest.NewRecorder()
+ req := httptest.NewRequest(http.MethodGet, "/city/test_city", nil)
+ req = mux.SetURLVars(req, map[string]string{"city": "test_city"})
+ w := httptest.NewRecorder()
- handler.GetOneCity(rr, req)
+ // Вызов метода
+ cityHandler.GetOneCity(w, req)
- assert.Equal(t, http.StatusInternalServerError, rr.Code)
+ // Проверка
+ result := w.Result()
+ defer func(Body io.ReadCloser) {
+ err := Body.Close()
+ if err != nil {
+ return
+ }
+ }(result.Body)
- var responseBody map[string]interface{}
- err = json.NewDecoder(rr.Body).Decode(&responseBody)
-}
+ assert.Equal(t, http.StatusInternalServerError, result.StatusCode)
+ mockClient.AssertExpectations(t)
+ mockUtils.AssertExpectations(t)
+}
\ No newline at end of file
diff --git a/internal/regions/controller/regions_controller.go b/internal/regions/controller/regions_controller.go
new file mode 100644
index 0000000..2049c51
--- /dev/null
+++ b/internal/regions/controller/regions_controller.go
@@ -0,0 +1,155 @@
+package controller
+
+import (
+ "2024_2_FIGHT-CLUB/domain"
+ "2024_2_FIGHT-CLUB/internal/regions/usecase"
+ "2024_2_FIGHT-CLUB/internal/service/logger"
+ "2024_2_FIGHT-CLUB/internal/service/metrics"
+ "2024_2_FIGHT-CLUB/internal/service/middleware"
+ "2024_2_FIGHT-CLUB/internal/service/session"
+ "github.com/gorilla/mux"
+ "github.com/mailru/easyjson"
+ "github.com/microcosm-cc/bluemonday"
+ "go.uber.org/zap"
+ "net/http"
+ "time"
+)
+
+type RegionHandler struct {
+ usecase usecase.RegionUsecase
+ sessionService session.InterfaceSession
+ jwtToken middleware.JwtTokenService
+}
+
+func NewRegionHandler(usecase usecase.RegionUsecase, sessionService session.InterfaceSession, jwtToken middleware.JwtTokenService) *RegionHandler {
+ return &RegionHandler{
+ usecase: usecase,
+ sessionService: sessionService,
+ jwtToken: jwtToken,
+ }
+}
+
+func (rh *RegionHandler) GetVisitedRegions(w http.ResponseWriter, r *http.Request) {
+ start := time.Now()
+ requestID := middleware.GetRequestID(r.Context())
+ ctx, cancel := middleware.WithTimeout(r.Context())
+
+ defer cancel()
+ var err error
+ statusCode := http.StatusOK
+ clientIP := r.RemoteAddr
+ if realIP := r.Header.Get("X-Real-IP"); realIP != "" {
+ clientIP = realIP
+ } else if forwarded := r.Header.Get("X-Forwarded-For"); forwarded != "" {
+ clientIP = forwarded
+ }
+ defer func() {
+ if statusCode == http.StatusOK {
+ metrics.HttpRequestsTotal.WithLabelValues(r.Method, r.URL.Path, http.StatusText(statusCode), clientIP).Inc()
+ } else {
+ metrics.HttpErrorsTotal.WithLabelValues(r.Method, r.URL.Path, http.StatusText(statusCode), err.Error(), clientIP).Inc()
+ }
+ duration := time.Since(start).Seconds()
+ metrics.HttpRequestDuration.WithLabelValues(r.Method, r.URL.Path, clientIP).Observe(duration)
+ }()
+ userId := mux.Vars(r)["userId"]
+ sanitizer := bluemonday.UGCPolicy()
+ userId = sanitizer.Sanitize(userId)
+
+ logger.AccessLogger.Info("Received GetVisitedRegions request",
+ zap.String("request_id", requestID),
+ zap.String("method", r.Method),
+ zap.String("url", r.URL.String()),
+ zap.String("query", r.URL.Query().Encode()),
+ )
+
+ w.Header().Set("Content-Type", "application/json; charset=UTF-8")
+
+ var regions domain.VisitedRegionsList
+
+ regions, err = rh.usecase.GetVisitedRegions(ctx, userId)
+ if err != nil {
+ logger.AccessLogger.Warn("Failed to get visited regions", zap.String("request_id", requestID), zap.Error(err))
+ statusCode = rh.handleError(w, err, requestID)
+ return
+ }
+
+ w.WriteHeader(http.StatusOK)
+ if _, err = easyjson.MarshalToWriter(®ions, w); err != nil {
+ logger.AccessLogger.Warn("Failed to encode response", zap.String("request_id", requestID), zap.Error(err))
+ statusCode = rh.handleError(w, err, requestID)
+ return
+ }
+ duration := time.Since(start)
+ logger.AccessLogger.Info("Completed GetVisitedRegions request",
+ zap.String("request_id", requestID),
+ zap.Duration("duration", duration),
+ zap.Int("status", http.StatusOK))
+}
+
+func (rh *RegionHandler) handleError(w http.ResponseWriter, err error, requestID string) int {
+ logger.AccessLogger.Error("Handling error",
+ zap.String("request_id", requestID),
+ zap.Error(err),
+ )
+
+ var statusCode int
+ w.Header().Set("Content-Type", "application/json; charset=UTF-8")
+ errorResponse := domain.ErrorResponse{
+ Error: err.Error(),
+ }
+
+ switch err.Error() {
+ case "input contains invalid characters",
+ "score out of range",
+ "input exceeds character limit":
+
+ statusCode = http.StatusBadRequest
+
+ case "host and user are the same",
+ "review already exist":
+ statusCode = http.StatusConflict
+
+ case "user not found",
+ "review not found",
+ "session not found",
+ "no reviews found":
+ statusCode = http.StatusNotFound
+
+ case "token invalid",
+ "token expired",
+ "bad sign method",
+ "missing X-CSRF-Token header",
+ "invalid JWT token":
+ statusCode = http.StatusUnauthorized
+
+ case "failed to generate session id",
+ "failed to save session",
+ "failed to delete session",
+ "error generating random bytes for session ID",
+ "failed to fetch reviews for host",
+ "failed to update host score",
+ "error creating review",
+ "error updating review",
+ "error finding review",
+ "error finding host",
+ "error updating host score",
+ "error fetching reviews",
+ "error fetching user by ID":
+ statusCode = http.StatusInternalServerError
+
+ default:
+ statusCode = http.StatusInternalServerError
+ }
+
+ w.WriteHeader(statusCode)
+ if _, jsonErr := easyjson.MarshalToWriter(&errorResponse, w); jsonErr != nil {
+ logger.AccessLogger.Error("Failed to encode error response",
+ zap.String("request_id", requestID),
+ zap.Error(jsonErr),
+ )
+ http.Error(w, jsonErr.Error(), http.StatusInternalServerError)
+ }
+
+ return statusCode
+}
diff --git a/internal/regions/repository/regions_repository.go b/internal/regions/repository/regions_repository.go
new file mode 100644
index 0000000..c0cbeb3
--- /dev/null
+++ b/internal/regions/repository/regions_repository.go
@@ -0,0 +1,64 @@
+package repository
+
+import (
+ "2024_2_FIGHT-CLUB/domain"
+ "2024_2_FIGHT-CLUB/internal/service/logger"
+ "2024_2_FIGHT-CLUB/internal/service/metrics"
+ "2024_2_FIGHT-CLUB/internal/service/middleware"
+ "context"
+ "errors"
+ "go.uber.org/zap"
+ "gorm.io/gorm"
+ "time"
+)
+
+type RegionRepository struct {
+ db *gorm.DB
+}
+
+func NewRegionRepository(db *gorm.DB) domain.RegionRepository {
+ return &RegionRepository{
+ db: db,
+ }
+}
+
+func (r *RegionRepository) GetVisitedRegions(ctx context.Context, userId string) ([]domain.VisitedRegions, error) {
+ start := time.Now()
+ requestID := middleware.GetRequestID(ctx)
+ logger.DBLogger.Info("GetVisitedRegions called", zap.String("request_id", requestID), zap.String("userID", userId))
+ var err error
+ defer func() {
+ if err != nil {
+ metrics.RepoErrorsTotal.WithLabelValues("GetVisitedRegions", "error", err.Error()).Inc()
+ } else {
+ metrics.RepoRequestTotal.WithLabelValues("GetVisitedRegions", "success").Inc()
+ }
+ duration := time.Since(start).Seconds()
+ metrics.RepoRequestDuration.WithLabelValues("GetVisitedRegions").Observe(duration)
+ }()
+ var user domain.User
+ var regions []domain.VisitedRegions
+ if err := r.db.Where("uuid = ?", userId).First(&user).Error; err != nil {
+ if errors.Is(err, gorm.ErrRecordNotFound) {
+ logger.DBLogger.Warn("User not found", zap.String("request_id", requestID), zap.String("userID", userId))
+ return nil, errors.New("user not found")
+ }
+ logger.DBLogger.Error("Error fetching user by ID", zap.String("request_id", requestID), zap.String("userID", userId), zap.Error(err))
+ return nil, errors.New("error fetching user by ID")
+ }
+
+ if err := r.db.Model(&domain.VisitedRegions{}).
+ Where("\"userId\"", userId).
+ Order("\"startVisitDate\" ASC").
+ Find(®ions).Error; err != nil {
+ if errors.Is(err, gorm.ErrRecordNotFound) {
+ logger.DBLogger.Warn("No regions found", zap.String("request_id", requestID), zap.String("userID", userId))
+ return nil, errors.New("no regions found")
+ }
+ logger.DBLogger.Error("Error fetching regions", zap.String("request_id", requestID), zap.String("userID", userId), zap.Error(err))
+ return nil, errors.New("error fetching regions")
+ }
+
+ logger.DBLogger.Info("Successfully fetched user by ID", zap.String("request_id", requestID), zap.String("userID", userId))
+ return regions, nil
+}
diff --git a/internal/regions/usecase/regions_usecase.go b/internal/regions/usecase/regions_usecase.go
new file mode 100644
index 0000000..a4d3c5a
--- /dev/null
+++ b/internal/regions/usecase/regions_usecase.go
@@ -0,0 +1,46 @@
+package usecase
+
+import (
+ "2024_2_FIGHT-CLUB/domain"
+ "2024_2_FIGHT-CLUB/internal/service/logger"
+ "2024_2_FIGHT-CLUB/internal/service/middleware"
+ "context"
+ "errors"
+ "go.uber.org/zap"
+ "regexp"
+)
+
+type RegionUsecase interface {
+ GetVisitedRegions(ctx context.Context, userId string) ([]domain.VisitedRegions, error)
+}
+
+type regionUsecase struct {
+ repository domain.RegionRepository
+}
+
+func NewRegionUsecase(repository domain.RegionRepository) RegionUsecase {
+ return ®ionUsecase{
+ repository: repository,
+ }
+}
+
+func (r *regionUsecase) GetVisitedRegions(ctx context.Context, userId string) ([]domain.VisitedRegions, error) {
+ requestID := middleware.GetRequestID(ctx)
+ const maxLen = 255
+ validCharPattern := regexp.MustCompile(`^[a-zA-Zа-яА-ЯёЁ0-9\s\-_]*$`)
+ if !validCharPattern.MatchString(userId) {
+ logger.AccessLogger.Warn("Input contains invalid characters", zap.String("request_id", requestID))
+ return nil, errors.New("input contains invalid characters")
+ }
+
+ if len(userId) > maxLen {
+ logger.AccessLogger.Warn("Input exceeds character limit", zap.String("request_id", requestID))
+ return nil, errors.New("input exceeds character limit")
+ }
+
+ reviews, err := r.repository.GetVisitedRegions(ctx, userId)
+ if err != nil {
+ return nil, err
+ }
+ return reviews, nil
+}
diff --git a/internal/reviews/contoller/reviews_controller.go b/internal/reviews/contoller/reviews_controller.go
index 7a304dd..b1782b0 100644
--- a/internal/reviews/contoller/reviews_controller.go
+++ b/internal/reviews/contoller/reviews_controller.go
@@ -7,9 +7,9 @@ import (
"2024_2_FIGHT-CLUB/internal/service/metrics"
"2024_2_FIGHT-CLUB/internal/service/middleware"
"2024_2_FIGHT-CLUB/internal/service/session"
- "encoding/json"
"errors"
"github.com/gorilla/mux"
+ "github.com/mailru/easyjson"
"github.com/microcosm-cc/bluemonday"
"go.uber.org/zap"
"net/http"
@@ -54,8 +54,6 @@ func (rh *ReviewHandler) CreateReview(w http.ResponseWriter, r *http.Request) {
metrics.HttpRequestDuration.WithLabelValues(r.Method, r.URL.Path, clientIP).Observe(duration)
}()
- ctx = middleware.WithLogger(ctx, logger.AccessLogger)
-
logger.AccessLogger.Info("Received CreateReview request",
zap.String("request_id", requestID),
zap.String("method", r.Method),
@@ -80,7 +78,8 @@ func (rh *ReviewHandler) CreateReview(w http.ResponseWriter, r *http.Request) {
zap.String("request_id", requestID),
zap.Error(errors.New("missing X-CSRF-Token header")),
)
- statusCode = rh.handleError(w, errors.New("missing X-CSRF-Token header"), requestID)
+ err = errors.New("missing X-CSRF-Token header")
+ statusCode = rh.handleError(w, err, requestID)
return
}
@@ -100,8 +99,7 @@ func (rh *ReviewHandler) CreateReview(w http.ResponseWriter, r *http.Request) {
}
var review domain.Review
-
- if err := json.NewDecoder(r.Body).Decode(&review); err != nil {
+ if err := easyjson.UnmarshalFromReader(r.Body, &review); err != nil {
logger.AccessLogger.Warn("Failed to unmarshal review", zap.String("request_id", requestID), zap.Error(err))
statusCode = rh.handleError(w, err, requestID)
return
@@ -119,11 +117,11 @@ func (rh *ReviewHandler) CreateReview(w http.ResponseWriter, r *http.Request) {
return
}
- body := map[string]interface{}{
- "review": review,
- }
w.WriteHeader(http.StatusCreated)
- if err := json.NewEncoder(w).Encode(body); err != nil {
+ body := domain.ReviewBody{
+ Review: review,
+ }
+ if _, err = easyjson.MarshalToWriter(&body, w); err != nil {
logger.AccessLogger.Warn("Failed to encode response", zap.String("request_id", requestID), zap.Error(err))
statusCode = rh.handleError(w, err, requestID)
return
@@ -162,7 +160,6 @@ func (rh *ReviewHandler) GetUserReviews(w http.ResponseWriter, r *http.Request)
}()
userId := mux.Vars(r)["userId"]
sanitizer := bluemonday.UGCPolicy()
- ctx = middleware.WithLogger(ctx, logger.AccessLogger)
userId = sanitizer.Sanitize(userId)
logger.AccessLogger.Info("Received GetUserReviews request",
@@ -174,7 +171,7 @@ func (rh *ReviewHandler) GetUserReviews(w http.ResponseWriter, r *http.Request)
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
- var reviews []domain.UserReviews
+ var reviews domain.UserReviewsList
reviews, err = rh.usecase.GetUserReviews(ctx, userId)
if err != nil {
@@ -184,7 +181,7 @@ func (rh *ReviewHandler) GetUserReviews(w http.ResponseWriter, r *http.Request)
}
w.WriteHeader(http.StatusOK)
- if err := json.NewEncoder(w).Encode(reviews); err != nil {
+ if _, err = easyjson.MarshalToWriter(&reviews, w); err != nil {
logger.AccessLogger.Warn("Failed to encode response", zap.String("request_id", requestID), zap.Error(err))
statusCode = rh.handleError(w, err, requestID)
return
@@ -201,7 +198,6 @@ func (rh *ReviewHandler) DeleteReview(w http.ResponseWriter, r *http.Request) {
start := time.Now()
requestID := middleware.GetRequestID(r.Context())
ctx, cancel := middleware.WithTimeout(r.Context())
- ctx = middleware.WithLogger(ctx, logger.AccessLogger)
defer cancel()
var err error
statusCode := http.StatusOK
@@ -246,7 +242,8 @@ func (rh *ReviewHandler) DeleteReview(w http.ResponseWriter, r *http.Request) {
zap.String("request_id", requestID),
zap.Error(errors.New("missing X-CSRF-Token header")),
)
- statusCode = rh.handleError(w, errors.New("missing X-CSRF-Token header"), requestID)
+ err = errors.New("missing X-CSRF-Token header")
+ statusCode = rh.handleError(w, err, requestID)
return
}
@@ -274,7 +271,8 @@ func (rh *ReviewHandler) DeleteReview(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
w.WriteHeader(http.StatusOK)
- if err := json.NewEncoder(w).Encode("response: deleted successfully"); err != nil {
+ response := domain.ResponseMessage{Message: "deleted successfully"}
+ if _, err = easyjson.MarshalToWriter(&response, w); err != nil {
logger.AccessLogger.Warn("Failed to encode response", zap.String("request_id", requestID), zap.Error(err))
statusCode = rh.handleError(w, err, requestID)
return
@@ -310,7 +308,6 @@ func (rh *ReviewHandler) UpdateReview(w http.ResponseWriter, r *http.Request) {
metrics.HttpRequestDuration.WithLabelValues(r.Method, r.URL.Path, clientIP).Observe(duration)
}()
sanitizer := bluemonday.UGCPolicy()
- ctx = middleware.WithLogger(ctx, logger.AccessLogger)
hostId := mux.Vars(r)["hostId"]
hostId = sanitizer.Sanitize(hostId)
@@ -337,7 +334,8 @@ func (rh *ReviewHandler) UpdateReview(w http.ResponseWriter, r *http.Request) {
zap.String("request_id", requestID),
zap.Error(errors.New("missing X-CSRF-Token header")),
)
- statusCode = rh.handleError(w, errors.New("missing X-CSRF-Token header"), requestID)
+ err = errors.New("missing X-CSRF-Token header")
+ statusCode = rh.handleError(w, err, requestID)
return
}
@@ -357,7 +355,7 @@ func (rh *ReviewHandler) UpdateReview(w http.ResponseWriter, r *http.Request) {
}
var updatedReview domain.Review
- if err := json.NewDecoder(r.Body).Decode(&updatedReview); err != nil {
+ if err := easyjson.UnmarshalFromReader(r.Body, &updatedReview); err != nil {
logger.AccessLogger.Warn("Failed to unmarshal review", zap.String("request_id", requestID), zap.Error(err))
statusCode = rh.handleError(w, err, requestID)
return
@@ -376,8 +374,9 @@ func (rh *ReviewHandler) UpdateReview(w http.ResponseWriter, r *http.Request) {
}
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
- w.WriteHeader(http.StatusCreated)
- if err := json.NewEncoder(w).Encode("response: updated successfully"); err != nil {
+ w.WriteHeader(http.StatusOK)
+ response := domain.ResponseMessage{Message: "updated successfully"}
+ if _, err = easyjson.MarshalToWriter(&response, w); err != nil {
logger.AccessLogger.Warn("Failed to encode response", zap.String("request_id", requestID), zap.Error(err))
statusCode = rh.handleError(w, err, requestID)
return
@@ -396,28 +395,28 @@ func (rh *ReviewHandler) handleError(w http.ResponseWriter, err error, requestID
zap.String("request_id", requestID),
zap.Error(err),
)
+
var statusCode int
- w.Header().Set("Content-Type", "application/json")
- errorResponse := map[string]string{"error": err.Error()}
+ w.Header().Set("Content-Type", "application/json; charset=UTF-8")
+ errorResponse := domain.ErrorResponse{
+ Error: err.Error(),
+ }
switch err.Error() {
case "input contains invalid characters",
"score out of range",
"input exceeds character limit":
- w.WriteHeader(http.StatusBadRequest)
statusCode = http.StatusBadRequest
case "host and user are the same",
"review already exist":
- w.WriteHeader(http.StatusConflict)
statusCode = http.StatusConflict
case "user not found",
"review not found",
"session not found",
"no reviews found":
- w.WriteHeader(http.StatusNotFound)
statusCode = http.StatusNotFound
case "token invalid",
@@ -425,7 +424,6 @@ func (rh *ReviewHandler) handleError(w http.ResponseWriter, err error, requestID
"bad sign method",
"missing X-CSRF-Token header",
"invalid JWT token":
- w.WriteHeader(http.StatusUnauthorized)
statusCode = http.StatusUnauthorized
case "failed to generate session id",
@@ -441,20 +439,20 @@ func (rh *ReviewHandler) handleError(w http.ResponseWriter, err error, requestID
"error updating host score",
"error fetching reviews",
"error fetching user by ID":
- w.WriteHeader(http.StatusInternalServerError)
statusCode = http.StatusInternalServerError
default:
- w.WriteHeader(http.StatusInternalServerError)
statusCode = http.StatusInternalServerError
}
- if jsonErr := json.NewEncoder(w).Encode(errorResponse); jsonErr != nil {
+ w.WriteHeader(statusCode)
+ if _, jsonErr := easyjson.MarshalToWriter(&errorResponse, w); jsonErr != nil {
logger.AccessLogger.Error("Failed to encode error response",
zap.String("request_id", requestID),
zap.Error(jsonErr),
)
http.Error(w, jsonErr.Error(), http.StatusInternalServerError)
}
+
return statusCode
}
diff --git a/internal/reviews/contoller/reviews_controller_test.go b/internal/reviews/contoller/reviews_controller_test.go
new file mode 100644
index 0000000..e2a7c4f
--- /dev/null
+++ b/internal/reviews/contoller/reviews_controller_test.go
@@ -0,0 +1,421 @@
+package contoller
+
+import (
+ "2024_2_FIGHT-CLUB/domain"
+ "2024_2_FIGHT-CLUB/internal/reviews/mocks"
+ "2024_2_FIGHT-CLUB/internal/service/logger"
+ "2024_2_FIGHT-CLUB/internal/service/middleware"
+ "bytes"
+ "context"
+ "errors"
+ "github.com/gorilla/mux"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+ "net/http"
+ "net/http/httptest"
+ "testing"
+)
+
+func TestCreateReview(t *testing.T) {
+ require.NoError(t, logger.InitLoggers())
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
+ mockJwtService := &mocks.MockJwtTokenService{}
+ mockSessionService := &mocks.MockServiceSession{}
+ mockReviewUsecase := &mocks.MockReviewsUsecase{}
+
+ handler := NewReviewHandler(mockReviewUsecase, mockSessionService, mockJwtService)
+
+ t.Run("Successful Review Creation", func(t *testing.T) {
+ mockJwtService.MockValidate = func(tokenString string, expectedSessionId string) (*middleware.JwtCsrfClaims, error) {
+ return &middleware.JwtCsrfClaims{}, nil
+ }
+ mockSessionService.MockGetUserID = func(ctx context.Context, sessionID string) (string, error) {
+ return "test-user-id", nil
+ }
+ mockReviewUsecase.MockCreateReview = func(ctx context.Context, review *domain.Review, userId string) error {
+ assert.Equal(t, "test-user-id", userId)
+ assert.Equal(t, "Test Review", review.Title)
+ return nil
+ }
+
+ reviewPayload := `{"title":"Test Review","text":"Review Content","host_id":"host1","user_id":"user1"}`
+ request := httptest.NewRequest(http.MethodPost, "/reviews", bytes.NewReader([]byte(reviewPayload)))
+ request.Header.Set("Cookie", "session_id=test-session-id")
+ request.Header.Set("X-CSRF-Token", "Bearer valid-token")
+
+ responseRecorder := httptest.NewRecorder()
+ handler.CreateReview(responseRecorder, request)
+
+ assert.Equal(t, http.StatusCreated, responseRecorder.Code)
+ })
+
+ t.Run("Missing CSRF Token", func(t *testing.T) {
+ reviewPayload := `{"title":"Test Review","text":"Review Content"}`
+ request := httptest.NewRequest(http.MethodPost, "/reviews", bytes.NewReader([]byte(reviewPayload)))
+ request.Header.Set("Cookie", "session_id=test-session-id")
+
+ responseRecorder := httptest.NewRecorder()
+ handler.CreateReview(responseRecorder, request)
+
+ assert.Equal(t, http.StatusUnauthorized, responseRecorder.Code)
+ })
+
+ t.Run("Invalid JWT Token", func(t *testing.T) {
+ mockJwtService.MockValidate = func(tokenString string, expectedSessionId string) (*middleware.JwtCsrfClaims, error) {
+ return nil, errors.New("invalid JWT token")
+ }
+
+ reviewPayload := `{"title":"Test Review","text":"Review Content"}`
+ request := httptest.NewRequest(http.MethodPost, "/reviews", bytes.NewReader([]byte(reviewPayload)))
+ request.Header.Set("Cookie", "session_id=test-session-id")
+ request.Header.Set("X-CSRF-Token", "Bearer invalid-token")
+
+ responseRecorder := httptest.NewRecorder()
+ handler.CreateReview(responseRecorder, request)
+
+ assert.Equal(t, http.StatusUnauthorized, responseRecorder.Code)
+ })
+
+ t.Run("Failed to Get User ID from Session", func(t *testing.T) {
+ mockJwtService.MockValidate = func(tokenString string, expectedSessionId string) (*middleware.JwtCsrfClaims, error) {
+ return &middleware.JwtCsrfClaims{}, nil
+ }
+ mockSessionService.MockGetUserID = func(ctx context.Context, sessionID string) (string, error) {
+ return "", errors.New("failed to get user ID")
+ }
+
+ reviewPayload := `{"title":"Test Review","text":"Review Content"}`
+ request := httptest.NewRequest(http.MethodPost, "/reviews", bytes.NewReader([]byte(reviewPayload)))
+ request.Header.Set("Cookie", "session_id=test-session-id")
+ request.Header.Set("X-CSRF-Token", "Bearer valid-token")
+
+ responseRecorder := httptest.NewRecorder()
+ handler.CreateReview(responseRecorder, request)
+
+ assert.Equal(t, http.StatusInternalServerError, responseRecorder.Code)
+ })
+ t.Run("Failed to Get User ID from Session", func(t *testing.T) {
+ mockJwtService.MockValidate = func(tokenString string, expectedSessionId string) (*middleware.JwtCsrfClaims, error) {
+ return &middleware.JwtCsrfClaims{}, nil
+ }
+ mockSessionService.MockGetUserID = func(ctx context.Context, sessionID string) (string, error) {
+ return "", errors.New("failed to get user ID")
+ }
+
+ reviewPayload := `{"title":"Test Review","text":"Review Content"}`
+ request := httptest.NewRequest(http.MethodPost, "/reviews", bytes.NewReader([]byte(reviewPayload)))
+ request.Header.Set("Cookie", "session_id=test-session-id")
+ request.Header.Set("X-CSRF-Token", "Bearer valid-token")
+
+ responseRecorder := httptest.NewRecorder()
+ handler.CreateReview(responseRecorder, request)
+
+ assert.Equal(t, http.StatusInternalServerError, responseRecorder.Code)
+ })
+}
+
+func TestGetUserReviews(t *testing.T) {
+ require.NoError(t, logger.InitLoggers())
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
+ mockJwtService := &mocks.MockJwtTokenService{}
+ mockSessionService := &mocks.MockServiceSession{}
+ mockReviewUsecase := &mocks.MockReviewsUsecase{}
+
+ handler := NewReviewHandler(mockReviewUsecase, mockSessionService, mockJwtService)
+
+ t.Run("Successful GetUserReviews", func(t *testing.T) {
+ mockReviewUsecase.MockGetUserReviews = func(ctx context.Context, userId string) ([]domain.UserReviews, error) {
+ assert.Equal(t, "", userId)
+ return []domain.UserReviews{
+ {
+ HostID: "host1",
+ Title: "Test Review 1",
+ },
+ {
+ HostID: "host2",
+ Title: "Test Review 2",
+ },
+ }, nil
+ }
+
+ request := httptest.NewRequest(http.MethodGet, "/reviews/{userId}", nil)
+ responseRecorder := httptest.NewRecorder()
+
+ handler.GetUserReviews(responseRecorder, request)
+
+ assert.Equal(t, http.StatusOK, responseRecorder.Code)
+ })
+
+ t.Run("Error from GetUserReviews Usecase", func(t *testing.T) {
+ mockReviewUsecase.MockGetUserReviews = func(ctx context.Context, userId string) ([]domain.UserReviews, error) {
+ assert.Equal(t, "", userId)
+ return nil, errors.New("database error")
+ }
+
+ request := httptest.NewRequest(http.MethodGet, "/reviews/{userId}", nil)
+ responseRecorder := httptest.NewRecorder()
+
+ handler.GetUserReviews(responseRecorder, request)
+
+ assert.Equal(t, http.StatusInternalServerError, responseRecorder.Code)
+ })
+}
+
+func TestDeleteReview(t *testing.T) {
+ require.NoError(t, logger.InitLoggers())
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
+ mockJwtService := &mocks.MockJwtTokenService{}
+ mockSessionService := &mocks.MockServiceSession{}
+ mockReviewUsecase := &mocks.MockReviewsUsecase{}
+ handler := NewReviewHandler(mockReviewUsecase, mockSessionService, mockJwtService)
+
+ t.Run("Successful DeleteReview", func(t *testing.T) {
+ mockJwtService.MockValidate = func(tokenString string, expectedSessionId string) (*middleware.JwtCsrfClaims, error) {
+ return &middleware.JwtCsrfClaims{}, nil
+ }
+ mockSessionService.MockGetUserID = func(ctx context.Context, sessionID string) (string, error) {
+ return "test-user-id", nil
+ }
+ mockReviewUsecase.MockDeleteReview = func(ctx context.Context, userId, hostId string) error {
+ assert.Equal(t, "test-user-id", userId)
+ assert.Equal(t, "test-host-id", hostId)
+ return nil
+ }
+
+ request := httptest.NewRequest(http.MethodDelete, "/reviews/{adId}", nil)
+ request = mux.SetURLVars(request, map[string]string{"hostId": "test-host-id"})
+ request.Header.Set("Cookie", "session_id=test-session-id")
+ request.Header.Set("X-CSRF-Token", "Bearer valid-token")
+
+ rr := httptest.NewRecorder()
+ handler.DeleteReview(rr, request)
+
+ assert.Equal(t, http.StatusOK, rr.Code)
+ assert.Contains(t, rr.Body.String(), "deleted successfully")
+ })
+
+ t.Run("Missing CSRF Token", func(t *testing.T) {
+ request := httptest.NewRequest(http.MethodDelete, "/reviews/{adId}", nil)
+ request = mux.SetURLVars(request, map[string]string{"hostId": "test-host-id"})
+ request.Header.Set("Cookie", "session_id=test-session-id")
+
+ rr := httptest.NewRecorder()
+ handler.DeleteReview(rr, request)
+
+ assert.Equal(t, http.StatusUnauthorized, rr.Code)
+ })
+
+ t.Run("Invalid JWT Token", func(t *testing.T) {
+ mockJwtService.MockValidate = func(tokenString string, expectedSessionId string) (*middleware.JwtCsrfClaims, error) {
+ return nil, errors.New("invalid JWT token")
+ }
+
+ request := httptest.NewRequest(http.MethodDelete, "/reviews/{adId}", nil)
+ request = mux.SetURLVars(request, map[string]string{"hostId": "test-host-id"})
+ request.Header.Set("Cookie", "session_id=test-session-id")
+ request.Header.Set("X-CSRF-Token", "Bearer invalid-token")
+
+ rr := httptest.NewRecorder()
+ handler.DeleteReview(rr, request)
+
+ assert.Equal(t, http.StatusUnauthorized, rr.Code)
+ })
+
+ t.Run("Session ID Extraction Error", func(t *testing.T) {
+ request := httptest.NewRequest(http.MethodDelete, "/reviews/{adId}", nil)
+ request = mux.SetURLVars(request, map[string]string{"hostId": "test-host-id"})
+
+ rr := httptest.NewRecorder()
+ handler.DeleteReview(rr, request)
+
+ assert.Equal(t, http.StatusInternalServerError, rr.Code)
+ })
+
+ t.Run("Error Getting UserID from Session", func(t *testing.T) {
+ mockJwtService.MockValidate = func(tokenString string, expectedSessionId string) (*middleware.JwtCsrfClaims, error) {
+ return &middleware.JwtCsrfClaims{}, nil
+ }
+ mockSessionService.MockGetUserID = func(ctx context.Context, sessionID string) (string, error) {
+ return "", errors.New("session error")
+ }
+
+ request := httptest.NewRequest(http.MethodDelete, "/reviews/{adId}", nil)
+ request = mux.SetURLVars(request, map[string]string{"hostId": "test-host-id"})
+ request.Header.Set("Cookie", "session_id=test-session-id")
+ request.Header.Set("X-CSRF-Token", "Bearer valid-token")
+
+ rr := httptest.NewRecorder()
+ handler.DeleteReview(rr, request)
+
+ assert.Equal(t, http.StatusInternalServerError, rr.Code)
+ })
+
+ t.Run("Error Deleting Review", func(t *testing.T) {
+ mockJwtService.MockValidate = func(tokenString string, expectedSessionId string) (*middleware.JwtCsrfClaims, error) {
+ return &middleware.JwtCsrfClaims{}, nil
+ }
+ mockSessionService.MockGetUserID = func(ctx context.Context, sessionID string) (string, error) {
+ return "test-user-id", nil
+ }
+ mockReviewUsecase.MockDeleteReview = func(ctx context.Context, userId, hostId string) error {
+ return errors.New("delete error")
+ }
+
+ request := httptest.NewRequest(http.MethodDelete, "/reviews/{hostId}", nil)
+ request = mux.SetURLVars(request, map[string]string{"hostId": "test-host-id"})
+ request.Header.Set("Cookie", "session_id=test-session-id")
+ request.Header.Set("X-CSRF-Token", "Bearer valid-token")
+
+ rr := httptest.NewRecorder()
+ handler.DeleteReview(rr, request)
+
+ assert.Equal(t, http.StatusInternalServerError, rr.Code)
+ })
+}
+
+func TestUpdateReview(t *testing.T) {
+ require.NoError(t, logger.InitLoggers())
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
+ mockJwtService := &mocks.MockJwtTokenService{}
+ mockSessionService := &mocks.MockServiceSession{}
+ mockReviewUsecase := &mocks.MockReviewsUsecase{}
+ handler := NewReviewHandler(mockReviewUsecase, mockSessionService, mockJwtService)
+
+ t.Run("Successful UpdateReview", func(t *testing.T) {
+ mockJwtService.MockValidate = func(tokenString string, sessionID string) (*middleware.JwtCsrfClaims, error) {
+ return &middleware.JwtCsrfClaims{}, nil
+ }
+ mockSessionService.MockGetUserID = func(ctx context.Context, sessionID string) (string, error) {
+ return "test-user-id", nil
+ }
+ mockReviewUsecase.MockUpdateReview = func(ctx context.Context, userID, hostID string, review *domain.Review) error {
+ assert.Equal(t, "test-host-id", hostID)
+ assert.Equal(t, "test-user-id", userID)
+ assert.Equal(t, "Test Title", review.Title)
+ return nil
+ }
+
+ updatedReview := `{"title":"Test Title","text":"Updated text"}`
+
+ request := httptest.NewRequest(http.MethodPut, "/reviews/{hostId}", bytes.NewBufferString(updatedReview))
+ request = mux.SetURLVars(request, map[string]string{"hostId": "test-host-id"})
+ request.Header.Set("X-CSRF-Token", "Bearer valid-token")
+ request.Header.Set("Cookie", "session_id=test-session-id")
+
+ rr := httptest.NewRecorder()
+ handler.UpdateReview(rr, request)
+
+ assert.Equal(t, http.StatusOK, rr.Code)
+ assert.Contains(t, rr.Body.String(), "updated successfully")
+ })
+
+ t.Run("Missing CSRF Token", func(t *testing.T) {
+ request := httptest.NewRequest(http.MethodPut, "/reviews/{hostId}", nil)
+ request = mux.SetURLVars(request, map[string]string{"hostId": "test-host-id"})
+ request.Header.Set("Cookie", "session_id=test-session-id")
+
+ rr := httptest.NewRecorder()
+ handler.UpdateReview(rr, request)
+
+ assert.Equal(t, http.StatusUnauthorized, rr.Code)
+ })
+
+ t.Run("Invalid JWT Token", func(t *testing.T) {
+ mockJwtService.MockValidate = func(tokenString string, sessionID string) (*middleware.JwtCsrfClaims, error) {
+ return nil, errors.New("invalid token")
+ }
+
+ request := httptest.NewRequest(http.MethodPut, "/reviews/{hostId}", nil)
+ request = mux.SetURLVars(request, map[string]string{"hostId": "test-host-id"})
+ request.Header.Set("X-CSRF-Token", "Bearer invalid-token")
+ request.Header.Set("Cookie", "session_id=test-session-id")
+
+ rr := httptest.NewRecorder()
+ handler.UpdateReview(rr, request)
+
+ assert.Equal(t, http.StatusUnauthorized, rr.Code)
+ })
+
+ t.Run("Session ID Error", func(t *testing.T) {
+ request := httptest.NewRequest(http.MethodPut, "/reviews/{hostId}", nil)
+ request = mux.SetURLVars(request, map[string]string{"hostId": "test-host-id"})
+
+ rr := httptest.NewRecorder()
+ handler.UpdateReview(rr, request)
+
+ assert.Equal(t, http.StatusInternalServerError, rr.Code)
+ })
+
+ t.Run("Failed to get UserID", func(t *testing.T) {
+ mockJwtService.MockValidate = func(tokenString string, sessionID string) (*middleware.JwtCsrfClaims, error) {
+ return &middleware.JwtCsrfClaims{}, nil
+ }
+ mockSessionService.MockGetUserID = func(ctx context.Context, sessionID string) (string, error) {
+ return "", errors.New("failed to get user ID")
+ }
+
+ request := httptest.NewRequest(http.MethodPut, "/reviews/{hostId}", nil)
+ request = mux.SetURLVars(request, map[string]string{"hostId": "test-host-id"})
+ request.Header.Set("X-CSRF-Token", "Bearer valid-token")
+ request.Header.Set("Cookie", "session_id=test-session-id")
+
+ rr := httptest.NewRecorder()
+ handler.UpdateReview(rr, request)
+
+ assert.Equal(t, http.StatusInternalServerError, rr.Code)
+ })
+
+ t.Run("Failed Unmarshal", func(t *testing.T) {
+ request := httptest.NewRequest(http.MethodPut, "/reviews/{hostId}", bytes.NewBufferString("invalid-json"))
+ request = mux.SetURLVars(request, map[string]string{"hostId": "test-host-id"})
+ request.Header.Set("X-CSRF-Token", "Bearer valid-token")
+ request.Header.Set("Cookie", "session_id=test-session-id")
+
+ rr := httptest.NewRecorder()
+ handler.UpdateReview(rr, request)
+
+ assert.Equal(t, http.StatusInternalServerError, rr.Code)
+ })
+
+ t.Run("UpdateReview Usecase Error", func(t *testing.T) {
+ mockJwtService.MockValidate = func(tokenString string, sessionID string) (*middleware.JwtCsrfClaims, error) {
+ return &middleware.JwtCsrfClaims{}, nil
+ }
+ mockSessionService.MockGetUserID = func(ctx context.Context, sessionID string) (string, error) {
+ return "test-user-id", nil
+ }
+ mockReviewUsecase.MockUpdateReview = func(ctx context.Context, userID, hostID string, review *domain.Review) error {
+ return errors.New("update error")
+ }
+
+ request := httptest.NewRequest(http.MethodPut, "/reviews/{hostId}", bytes.NewBufferString(`{"title":"Test"}`))
+ request = mux.SetURLVars(request, map[string]string{"hostId": "test-host-id"})
+ request.Header.Set("X-CSRF-Token", "Bearer valid-token")
+ request.Header.Set("Cookie", "session_id=test-session-id")
+
+ rr := httptest.NewRecorder()
+ handler.UpdateReview(rr, request)
+
+ assert.Equal(t, http.StatusInternalServerError, rr.Code)
+ })
+}
diff --git a/internal/reviews/mocks/mocks.go b/internal/reviews/mocks/mocks.go
new file mode 100644
index 0000000..fc6a3aa
--- /dev/null
+++ b/internal/reviews/mocks/mocks.go
@@ -0,0 +1,95 @@
+package mocks
+
+import (
+ "2024_2_FIGHT-CLUB/domain"
+ "2024_2_FIGHT-CLUB/internal/service/middleware"
+ "context"
+ "github.com/golang-jwt/jwt"
+)
+
+type MockJwtTokenService struct {
+ MockCreate func(session_id string, tokenExpTime int64) (string, error)
+ MockValidate func(tokenString string, expectedSessionId string) (*middleware.JwtCsrfClaims, error)
+ MockParseSecretGetter func(token *jwt.Token) (interface{}, error)
+}
+
+func (m *MockJwtTokenService) Create(session_id string, tokenExpTime int64) (string, error) {
+ return m.MockCreate(session_id, tokenExpTime)
+}
+
+func (m *MockJwtTokenService) Validate(tokenString string, expectedSessionId string) (*middleware.JwtCsrfClaims, error) {
+ return m.MockValidate(tokenString, expectedSessionId)
+}
+
+func (m *MockJwtTokenService) ParseSecretGetter(token *jwt.Token) (interface{}, error) {
+ return m.MockParseSecretGetter(token)
+}
+
+type MockServiceSession struct {
+ MockGetUserID func(ctx context.Context, sessionID string) (string, error)
+ MockLogoutSession func(ctx context.Context, sessionID string) error
+ MockCreateSession func(ctx context.Context, user *domain.User) (string, error)
+ MockGetSessionData func(ctx context.Context, sessionID string) (*domain.SessionData, error)
+}
+
+func (m *MockServiceSession) GetUserID(ctx context.Context, sessionID string) (string, error) {
+ return m.MockGetUserID(ctx, sessionID)
+}
+
+func (m *MockServiceSession) LogoutSession(ctx context.Context, sessionID string) error {
+ return m.MockLogoutSession(ctx, sessionID)
+}
+
+func (m *MockServiceSession) CreateSession(ctx context.Context, user *domain.User) (string, error) {
+ return m.MockCreateSession(ctx, user)
+}
+
+func (m *MockServiceSession) GetSessionData(ctx context.Context, sessionID string) (*domain.SessionData, error) {
+ return m.MockGetSessionData(ctx, sessionID)
+}
+
+type MockReviewsUsecase struct {
+ MockCreateReview func(ctx context.Context, review *domain.Review, userId string) error
+ MockGetUserReviews func(ctx context.Context, userId string) ([]domain.UserReviews, error)
+ MockUpdateReview func(ctx context.Context, userID, hostID string, updatedReview *domain.Review) error
+ MockDeleteReview func(ctx context.Context, userID, hostID string) error
+}
+
+func (m *MockReviewsUsecase) CreateReview(ctx context.Context, review *domain.Review, userId string) error {
+ return m.MockCreateReview(ctx, review, userId)
+}
+
+func (m *MockReviewsUsecase) GetUserReviews(ctx context.Context, userId string) ([]domain.UserReviews, error) {
+ return m.MockGetUserReviews(ctx, userId)
+}
+
+func (m *MockReviewsUsecase) UpdateReview(ctx context.Context, userID, hostID string, updatedReview *domain.Review) error {
+ return m.MockUpdateReview(ctx, userID, hostID, updatedReview)
+}
+
+func (m *MockReviewsUsecase) DeleteReview(ctx context.Context, userID, hostID string) error {
+ return m.MockDeleteReview(ctx, userID, hostID)
+}
+
+type MockReviewsRepository struct {
+ MockCreateReview func(ctx context.Context, review *domain.Review) error
+ MockGetUserReviews func(ctx context.Context, userID string) ([]domain.UserReviews, error)
+ MockDeleteReview func(ctx context.Context, userID, hostID string) error
+ MockUpdateReview func(ctx context.Context, userID, hostID string, updatedReview *domain.Review) error
+}
+
+func (m *MockReviewsRepository) CreateReview(ctx context.Context, review *domain.Review) error {
+ return m.MockCreateReview(ctx, review)
+}
+
+func (m *MockReviewsRepository) GetUserReviews(ctx context.Context, userID string) ([]domain.UserReviews, error) {
+ return m.MockGetUserReviews(ctx, userID)
+}
+
+func (m *MockReviewsRepository) UpdateReview(ctx context.Context, userID, hostID string, updatedReview *domain.Review) error {
+ return m.MockUpdateReview(ctx, userID, hostID, updatedReview)
+}
+
+func (m *MockReviewsRepository) DeleteReview(ctx context.Context, userID, hostID string) error {
+ return m.MockDeleteReview(ctx, userID, hostID)
+}
diff --git a/internal/reviews/repository/reviews_repository_test.go b/internal/reviews/repository/reviews_repository_test.go
new file mode 100644
index 0000000..50a4378
--- /dev/null
+++ b/internal/reviews/repository/reviews_repository_test.go
@@ -0,0 +1 @@
+package repository
diff --git a/internal/reviews/usecase/reviews_usecase_test.go b/internal/reviews/usecase/reviews_usecase_test.go
new file mode 100644
index 0000000..db2da35
--- /dev/null
+++ b/internal/reviews/usecase/reviews_usecase_test.go
@@ -0,0 +1,303 @@
+package usecase
+
+import (
+ "2024_2_FIGHT-CLUB/domain"
+ "2024_2_FIGHT-CLUB/internal/reviews/mocks"
+ "2024_2_FIGHT-CLUB/internal/service/logger"
+ "context"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+ "testing"
+)
+
+func TestCreateReview(t *testing.T) {
+ mockRepo := &mocks.MockReviewsRepository{}
+ reviewUsecase := NewReviewUsecase(mockRepo)
+
+ validReview := &domain.Review{
+ Title: "Great Place!",
+ Text: "I loved staying here. The hosts were wonderful.",
+ Rating: 5,
+ HostID: "host123",
+ }
+ mockRepo.MockCreateReview = func(ctx context.Context, review *domain.Review) error {
+ return nil
+ }
+
+ userID := "user123"
+ ctx := context.Background()
+ err := reviewUsecase.CreateReview(ctx, validReview, userID)
+ assert.NoError(t, err)
+}
+
+func TestCreateReview_InvalidInput(t *testing.T) {
+ require.NoError(t, logger.InitLoggers())
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
+ mockRepo := &mocks.MockReviewsRepository{}
+ reviewUsecase := NewReviewUsecase(mockRepo)
+
+ invalidReview := &domain.Review{
+ Title: "Bad Title#$%",
+ Text: "Bad Text%@#",
+ Rating: 10,
+ HostID: "host123",
+ }
+
+ userID := "user123"
+ ctx := context.Background()
+ err := reviewUsecase.CreateReview(ctx, invalidReview, userID)
+ assert.Error(t, err)
+}
+
+func TestUpdateReview(t *testing.T) {
+ require.NoError(t, logger.InitLoggers())
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
+ mockRepo := &mocks.MockReviewsRepository{}
+ reviewUsecase := NewReviewUsecase(mockRepo)
+
+ validReview := &domain.Review{
+ Title: "Updated Title",
+ Text: "Updated Text",
+ Rating: 4,
+ }
+ mockRepo.MockUpdateReview = func(ctx context.Context, userID, hostID string, updatedReview *domain.Review) error {
+ assert.Equal(t, "user123", userID)
+ assert.Equal(t, "host123", hostID)
+ assert.Equal(t, validReview.Title, updatedReview.Title)
+ assert.Equal(t, validReview.Text, updatedReview.Text)
+ assert.Equal(t, validReview.Rating, updatedReview.Rating)
+ return nil
+ }
+
+ ctx := context.Background()
+ err := reviewUsecase.UpdateReview(ctx, "user123", "host123", validReview)
+ assert.NoError(t, err)
+}
+
+func TestUpdateReview_InvalidInput(t *testing.T) {
+ require.NoError(t, logger.InitLoggers())
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
+ mockRepo := &mocks.MockReviewsRepository{}
+ reviewUsecase := NewReviewUsecase(mockRepo)
+
+ invalidReview := &domain.Review{
+ Title: "Invalid Title#$%",
+ Text: "Invalid Text%@#",
+ Rating: 0,
+ }
+
+ ctx := context.Background()
+ err := reviewUsecase.UpdateReview(ctx, "user123", "host123", invalidReview)
+ assert.Error(t, err)
+}
+
+func TestDeleteReview(t *testing.T) {
+ require.NoError(t, logger.InitLoggers())
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
+ mockRepo := &mocks.MockReviewsRepository{}
+ reviewUsecase := NewReviewUsecase(mockRepo)
+
+ mockRepo.MockDeleteReview = func(ctx context.Context, userID, hostID string) error {
+ assert.Equal(t, "user123", userID)
+ assert.Equal(t, "host123", hostID)
+ return nil
+ }
+
+ ctx := context.Background()
+ err := reviewUsecase.DeleteReview(ctx, "user123", "host123")
+ assert.NoError(t, err)
+}
+
+func TestDeleteReview_InvalidInput(t *testing.T) {
+ require.NoError(t, logger.InitLoggers())
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
+ mockRepo := &mocks.MockReviewsRepository{}
+ reviewUsecase := NewReviewUsecase(mockRepo)
+
+ ctx := context.Background()
+ err := reviewUsecase.DeleteReview(ctx, "user123", "!nv@lidH0stID")
+ assert.Error(t, err)
+}
+
+func TestGetUserReviews(t *testing.T) {
+ mockRepo := &mocks.MockReviewsRepository{}
+ reviewUsecase := NewReviewUsecase(mockRepo)
+
+ expectedReviews := []domain.UserReviews{
+ {ID: 1, Title: "Review 1", Text: "Text 1"},
+ {ID: 2, Title: "Review 2", Text: "Text 2"},
+ }
+ mockRepo.MockGetUserReviews = func(ctx context.Context, userID string) ([]domain.UserReviews, error) {
+ assert.Equal(t, "user123", userID)
+ return expectedReviews, nil
+ }
+
+ ctx := context.Background()
+ reviews, err := reviewUsecase.GetUserReviews(ctx, "user123")
+ assert.NoError(t, err)
+ assert.Equal(t, expectedReviews, reviews)
+}
+
+func TestGetUserReviews_InvalidInput(t *testing.T) {
+ require.NoError(t, logger.InitLoggers())
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
+ mockRepo := &mocks.MockReviewsRepository{}
+ reviewUsecase := NewReviewUsecase(mockRepo)
+
+ ctx := context.Background()
+ _, err := reviewUsecase.GetUserReviews(ctx, "user#123") // Invalid userID
+ assert.Error(t, err)
+}
+
+func TestUpdateReview_ScoreOutOfRange(t *testing.T) {
+ require.NoError(t, logger.InitLoggers())
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
+ mockRepo := &mocks.MockReviewsRepository{}
+ reviewUsecase := NewReviewUsecase(mockRepo)
+
+ outOfRangeReview := &domain.Review{
+ Title: "Valid Title",
+ Text: "Valid Text",
+ Rating: 6, // Вне допустимого диапазона [1, 5]
+ }
+
+ ctx := context.Background()
+ err := reviewUsecase.UpdateReview(ctx, "user123", "host123", outOfRangeReview)
+ assert.EqualError(t, err, "score out of range")
+}
+
+func TestUpdateReview_InputExceedsCharacterLimit(t *testing.T) {
+ require.NoError(t, logger.InitLoggers())
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
+ mockRepo := &mocks.MockReviewsRepository{}
+ reviewUsecase := NewReviewUsecase(mockRepo)
+
+ longText := make([]byte, 1001) // Больше 1000 символов
+ for i := range longText {
+ longText[i] = 'a'
+ }
+
+ longReview := &domain.Review{
+ Title: "Valid Title",
+ Text: string(longText), // Текст превышает лимит
+ Rating: 5,
+ }
+
+ ctx := context.Background()
+ err := reviewUsecase.UpdateReview(ctx, "user123", "host123", longReview)
+ assert.EqualError(t, err, "input exceeds character limit")
+}
+
+func TestCreateReview_ScoreOutOfRange(t *testing.T) {
+ require.NoError(t, logger.InitLoggers())
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
+ mockRepo := &mocks.MockReviewsRepository{}
+ reviewUsecase := NewReviewUsecase(mockRepo)
+
+ outOfRangeReview := &domain.Review{
+ Title: "Valid Title",
+ Text: "Valid Text",
+ Rating: 0, // Вне допустимого диапазона [1, 5]
+ HostID: "host123",
+ }
+
+ ctx := context.Background()
+ err := reviewUsecase.CreateReview(ctx, outOfRangeReview, "user123")
+ assert.EqualError(t, err, "score out of range")
+}
+
+func TestCreateReview_InputExceedsCharacterLimit(t *testing.T) {
+ require.NoError(t, logger.InitLoggers())
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
+ mockRepo := &mocks.MockReviewsRepository{}
+ reviewUsecase := NewReviewUsecase(mockRepo)
+
+ longTitle := make([]byte, 101) // Больше 100 символов
+ for i := range longTitle {
+ longTitle[i] = 'a'
+ }
+
+ longReview := &domain.Review{
+ Title: string(longTitle), // Название превышает лимит
+ Text: "Valid Text",
+ Rating: 5,
+ HostID: "host123",
+ }
+
+ ctx := context.Background()
+ err := reviewUsecase.CreateReview(ctx, longReview, "user123")
+ assert.EqualError(t, err, "input exceeds character limit")
+}
+
+func TestCreateReview_HostAndUserSame(t *testing.T) {
+ require.NoError(t, logger.InitLoggers())
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
+ mockRepo := &mocks.MockReviewsRepository{}
+ reviewUsecase := NewReviewUsecase(mockRepo)
+
+ review := &domain.Review{
+ Title: "Valid Title",
+ Text: "Valid Text",
+ Rating: 5,
+ HostID: "user123", // Хост совпадает с пользователем
+ }
+
+ ctx := context.Background()
+ err := reviewUsecase.CreateReview(ctx, review, "user123")
+ assert.EqualError(t, err, "host and user are the same")
+}
diff --git a/internal/service/metrics/metrics.go b/internal/service/metrics/metrics.go
index c997d45..01f39f3 100644
--- a/internal/service/metrics/metrics.go
+++ b/internal/service/metrics/metrics.go
@@ -2,6 +2,7 @@ package metrics
import (
"github.com/prometheus/client_golang/prometheus"
+ "regexp"
)
var (
@@ -99,3 +100,13 @@ func InitRepoMetric() {
prometheus.MustRegister(RepoRequestDuration)
prometheus.MustRegister(RepoErrorsTotal)
}
+
+func SanitizeUserIdPath(path string) string {
+ re := regexp.MustCompile(`[0-9a-fA-F-]{36}`)
+ return re.ReplaceAllString(path, "{userId}")
+}
+
+func SanitizeAdIdPath(path string) string {
+ re := regexp.MustCompile(`[0-9a-fA-F-]{36}`)
+ return re.ReplaceAllString(path, "{adId}")
+}
diff --git a/internal/service/middleware/middleware.go b/internal/service/middleware/middleware.go
index 9cf2a67..7b2f707 100644
--- a/internal/service/middleware/middleware.go
+++ b/internal/service/middleware/middleware.go
@@ -1,39 +1,37 @@
package middleware
import (
+ "2024_2_FIGHT-CLUB/domain"
"2024_2_FIGHT-CLUB/internal/service/dsn"
"2024_2_FIGHT-CLUB/internal/service/images"
+ "2024_2_FIGHT-CLUB/microservices/ads_service/controller/gen"
+ "bufio"
"context"
+ "errors"
"fmt"
"github.com/google/uuid"
- "go.uber.org/zap"
"golang.org/x/crypto/bcrypt"
"golang.org/x/time/rate"
+ "google.golang.org/grpc"
+ "google.golang.org/grpc/codes"
+ "google.golang.org/grpc/status"
"gorm.io/driver/postgres"
"gorm.io/gorm"
"log"
+ "net"
"net/http"
"os"
+ "runtime/debug"
"sync"
"time"
)
-type contextKey string
-
-const (
- loggerKey contextKey = "logger"
-)
-
const requestTimeout = 5 * time.Second
func WithTimeout(ctx context.Context) (context.Context, context.CancelFunc) {
return context.WithTimeout(ctx, requestTimeout)
}
-func WithLogger(ctx context.Context, logger *zap.Logger) context.Context {
- return context.WithValue(ctx, loggerKey, logger)
-}
-
type key int
const RequestIDKey key = 0
@@ -137,3 +135,105 @@ func CheckPassword(hashedPassword, password string) bool {
err := bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password))
return err == nil
}
+
+func ConvertRoomsToGRPC(rooms []domain.AdRoomsResponse) []*gen.AdRooms {
+ var grpcRooms []*gen.AdRooms
+ for _, room := range rooms {
+ grpcRooms = append(grpcRooms, &gen.AdRooms{
+ Type: room.Type,
+ SquareMeters: int32(room.SquareMeters),
+ })
+ }
+ return grpcRooms
+}
+
+func ConvertGRPCToRooms(grpc []*gen.AdRooms) []domain.AdRoomsResponse {
+ var Rooms []domain.AdRoomsResponse
+ for _, room := range grpc {
+ Rooms = append(Rooms, domain.AdRoomsResponse{
+ Type: room.Type,
+ SquareMeters: int(room.SquareMeters),
+ })
+ }
+ return Rooms
+}
+
+type responseWriterWrapper struct {
+ http.ResponseWriter
+ written bool
+}
+
+func (w *responseWriterWrapper) WriteHeader(statusCode int) {
+ if !w.written {
+ w.written = true
+ w.ResponseWriter.WriteHeader(statusCode)
+ }
+}
+
+func (w *responseWriterWrapper) Hijack() (net.Conn, *bufio.ReadWriter, error) {
+ if hijacker, ok := w.ResponseWriter.(http.Hijacker); ok {
+ return hijacker.Hijack()
+ }
+ return nil, nil, errors.New("Hijack not supported")
+}
+
+func RecoverWrap(h http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ wrappedWriter := &responseWriterWrapper{ResponseWriter: w}
+
+ defer func() {
+ if r := recover(); r != nil {
+ var err error
+ switch t := r.(type) {
+ case string:
+ err = errors.New(t)
+ case error:
+ err = t
+ default:
+ err = errors.New("Unknown error")
+ }
+ if !wrappedWriter.written {
+ http.Error(wrappedWriter, err.Error(), http.StatusInternalServerError)
+ }
+ }
+ }()
+ h.ServeHTTP(wrappedWriter, r)
+ })
+}
+
+func RecoveryInterceptor(
+ ctx context.Context,
+ req interface{},
+ info *grpc.UnaryServerInfo,
+ handler grpc.UnaryHandler,
+) (resp interface{}, err error) {
+ defer func() {
+ if r := recover(); r != nil {
+ fmt.Printf("Panic occurred: %v\n", r)
+ debug.PrintStack()
+ err = status.Errorf(codes.Internal, "internal server error: %v", r)
+ }
+ }()
+ return handler(ctx, req)
+}
+
+func ChainUnaryInterceptors(interceptors ...grpc.UnaryServerInterceptor) grpc.UnaryServerInterceptor {
+ return func(
+ ctx context.Context,
+ req interface{},
+ info *grpc.UnaryServerInfo,
+ handler grpc.UnaryHandler,
+ ) (interface{}, error) {
+ current := len(interceptors) - 1
+ var chain grpc.UnaryHandler
+ chain = func(currentCtx context.Context, currentReq interface{}) (interface{}, error) {
+ if current < 0 {
+ return handler(currentCtx, currentReq)
+ }
+ interceptor := interceptors[current]
+ current--
+ return interceptor(currentCtx, currentReq, info, chain)
+ }
+ return chain(ctx, req)
+ }
+}
diff --git a/internal/service/middleware/middleware_test.go b/internal/service/middleware/middleware_test.go
index 97b7d43..a080c53 100644
--- a/internal/service/middleware/middleware_test.go
+++ b/internal/service/middleware/middleware_test.go
@@ -92,7 +92,7 @@ func TestJwtToken_Validate(t *testing.T) {
// Валидация истёкшего токена
expiredTime := time.Now().Add(-1 * time.Hour).Unix()
- expiredToken, err := jwtService.Create(session, expiredTime)
+ expiredToken, _ := jwtService.Create(session, expiredTime)
_, err = jwtService.Validate(expiredToken, session)
assert.Error(t, err)
diff --git a/internal/service/router/router.go b/internal/service/router/router.go
index ce91e8c..6b2543b 100644
--- a/internal/service/router/router.go
+++ b/internal/service/router/router.go
@@ -5,12 +5,13 @@ import (
auth "2024_2_FIGHT-CLUB/internal/auth/controller"
chat "2024_2_FIGHT-CLUB/internal/chat/controller"
city "2024_2_FIGHT-CLUB/internal/cities/controller"
+ regions "2024_2_FIGHT-CLUB/internal/regions/controller"
review "2024_2_FIGHT-CLUB/internal/reviews/contoller"
"github.com/gorilla/mux"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
-func SetUpRoutes(authHandler *auth.AuthHandler, adsHandler *ads.AdHandler, cityHandler *city.CityHandler, chatHandler *chat.ChatHandler, reviewHandler *review.ReviewHandler) *mux.Router {
+func SetUpRoutes(authHandler *auth.AuthHandler, adsHandler *ads.AdHandler, cityHandler *city.CityHandler, chatHandler *chat.ChatHandler, reviewHandler *review.ReviewHandler, regionsHandler *regions.RegionHandler) *mux.Router {
router := mux.NewRouter()
api := "/api"
@@ -25,6 +26,8 @@ func SetUpRoutes(authHandler *auth.AuthHandler, adsHandler *ads.AdHandler, cityH
router.HandleFunc(api+"/session", authHandler.GetSessionData).Methods("GET") // Get session data
router.HandleFunc(api+"/users/{userId}/housing", adsHandler.GetUserPlaces).Methods("GET") // Get User Ads
router.HandleFunc(api+"/users/{userId}/favorites", adsHandler.GetUserFavorites).Methods("GET") // Get User Favorites
+ router.HandleFunc(api+"/users/regions", authHandler.UpdateUserRegion).Methods("POST")
+ router.HandleFunc(api+"/users/regions/{regionName}", authHandler.DeleteUserRegion).Methods("DELETE")
// Ad Management Routes
router.HandleFunc(api+"/housing", adsHandler.GetAllPlaces).Methods("GET") // Get all ads
router.HandleFunc(api+"/housing/{adId}", adsHandler.GetOnePlace).Methods("GET") // Get ad by ID
@@ -49,7 +52,10 @@ func SetUpRoutes(authHandler *auth.AuthHandler, adsHandler *ads.AdHandler, cityH
router.HandleFunc(api+"/reviews/{userId}", reviewHandler.GetUserReviews).Methods("GET")
router.HandleFunc(api+"/reviews/{hostId}", reviewHandler.DeleteReview).Methods("DELETE")
router.HandleFunc(api+"/reviews/{hostId}", reviewHandler.UpdateReview).Methods("PUT")
-
+ // Payment Management Routes
+ router.HandleFunc(api+"/housing/{adId}/payment", adsHandler.UpdatePriorityWithPayment).Methods("PUT")
+ // Regions Management Routes
+ router.HandleFunc(api+"/users/{userId}/regions", regionsHandler.GetVisitedRegions).Methods("GET")
router.Handle(api+"/metrics", promhttp.Handler())
return router
diff --git a/internal/service/session/redis_session.go b/internal/service/session/redis_session.go
index a5b5a6e..46cef09 100644
--- a/internal/service/session/redis_session.go
+++ b/internal/service/session/redis_session.go
@@ -1,17 +1,18 @@
package session
import (
+ "2024_2_FIGHT-CLUB/domain"
"context"
- "encoding/json"
"errors"
+ "github.com/mailru/easyjson"
"time"
"github.com/go-redis/redis/v8"
)
type RedisInterface interface {
- Get(ctx context.Context, sessionID string) (map[string]interface{}, error)
- Set(ctx context.Context, sessionID string, data map[string]interface{}, ttl time.Duration) error
+ Get(ctx context.Context, sessionID string) (domain.SessionData, error)
+ Set(ctx context.Context, sessionID string, data domain.SessionData, ttl time.Duration) error
Delete(ctx context.Context, sessionID string) error
}
@@ -23,25 +24,25 @@ func NewRedisSessionStore(client *redis.Client) *RedisSessionStore {
return &RedisSessionStore{client: client}
}
-func (r *RedisSessionStore) Get(ctx context.Context, sessionID string) (map[string]interface{}, error) {
+func (r *RedisSessionStore) Get(ctx context.Context, sessionID string) (domain.SessionData, error) {
data, err := r.client.Get(ctx, sessionID).Result()
if errors.Is(err, redis.Nil) {
- return nil, errors.New("session not found")
+ return domain.SessionData{}, errors.New("session not found")
}
if err != nil {
- return nil, err
+ return domain.SessionData{}, err
}
- var sessionData map[string]interface{}
- if err := json.Unmarshal([]byte(data), &sessionData); err != nil {
- return nil, err
+ var sessionData domain.SessionData
+ if err := easyjson.Unmarshal([]byte(data), &sessionData); err != nil {
+ return domain.SessionData{}, err
}
return sessionData, nil
}
-func (r *RedisSessionStore) Set(ctx context.Context, sessionID string, data map[string]interface{}, ttl time.Duration) error {
- jsonData, err := json.Marshal(data)
+func (r *RedisSessionStore) Set(ctx context.Context, sessionID string, data domain.SessionData, ttl time.Duration) error {
+ jsonData, err := easyjson.Marshal(data)
if err != nil {
return err
}
diff --git a/internal/service/session/session.go b/internal/service/session/session.go
index 77d9032..b8e4274 100644
--- a/internal/service/session/session.go
+++ b/internal/service/session/session.go
@@ -17,7 +17,7 @@ type InterfaceSession interface {
GetUserID(ctx context.Context, sessionID string) (string, error)
LogoutSession(ctx context.Context, sessionID string) error
CreateSession(ctx context.Context, user *domain.User) (string, error)
- GetSessionData(ctx context.Context, sessionID string) (*map[string]interface{}, error)
+ GetSessionData(ctx context.Context, sessionID string) (*domain.SessionData, error)
}
type ServiceSession struct {
@@ -43,11 +43,10 @@ func (s *ServiceSession) CreateSession(ctx context.Context, user *domain.User) (
}
// Данные для сессии
- sessionData := map[string]interface{}{
- "id": user.UUID,
- "avatar": user.Avatar,
+ sessionData := domain.SessionData{
+ Id: user.UUID,
+ Avatar: user.Avatar,
}
-
// Сохранение сессии в Redis
if err := s.store.Set(ctx, sessionID, sessionData, 24*time.Hour); err != nil {
logger.AccessLogger.Error("Failed to save session", zap.String("request_id", requestID), zap.Error(err))
@@ -69,18 +68,14 @@ func (s *ServiceSession) GetUserID(ctx context.Context, sessionID string) (strin
return "", errors.New("session not found")
}
- userID, ok := data["id"].(string)
- if !ok {
- logger.AccessLogger.Error("User ID not found or invalid type in session", zap.String("request_id", requestID))
- return "", errors.New("user ID not found in session")
- }
+ userID := data.Id
logger.AccessLogger.Info("Successfully retrieved user ID from session", zap.String("request_id", requestID), zap.String("userID", userID))
return userID, nil
}
// GetSessionData Получение данных сессии
-func (s *ServiceSession) GetSessionData(ctx context.Context, sessionID string) (*map[string]interface{}, error) {
+func (s *ServiceSession) GetSessionData(ctx context.Context, sessionID string) (*domain.SessionData, error) {
requestID := middleware.GetRequestID(ctx)
logger.AccessLogger.Info("GetSessionData called", zap.String("request_id", requestID))
diff --git a/internal/service/utils/convert.go b/internal/service/utils/convert.go
new file mode 100644
index 0000000..2cb4df3
--- /dev/null
+++ b/internal/service/utils/convert.go
@@ -0,0 +1,242 @@
+package utils
+
+import (
+ "2024_2_FIGHT-CLUB/domain"
+ adsGen "2024_2_FIGHT-CLUB/microservices/ads_service/controller/gen"
+ authGen "2024_2_FIGHT-CLUB/microservices/auth_service/controller/gen"
+ cityGen "2024_2_FIGHT-CLUB/microservices/city_service/controller/gen"
+ "errors"
+ "fmt"
+ "log"
+ "math"
+ "time"
+)
+
+type UtilsInterface interface {
+ ConvertGetAllAdsResponseProtoToGo(proto *adsGen.GetAllAdsResponseList) (domain.GetAllAdsListResponse, error)
+ ConvertAdProtoToGo(ad *adsGen.GetAllAdsResponse) (domain.GetAllAdsResponse, error)
+ ConvertAuthResponseProtoToGo(response *authGen.UserResponse, userSession string) (domain.AuthResponse, error)
+ ConvertUserResponseProtoToGo(user *authGen.MetadataOneUser) (domain.UserDataResponse, error)
+ ConvertUsersProtoToGo(users *authGen.AllUsersResponse) ([]*domain.UserDataResponse, error)
+ ConvertSessionDataProtoToGo(sessionData *authGen.SessionDataResponse) (domain.SessionData, error)
+ ConvertAllCitiesProtoToGo(cities *cityGen.GetCitiesResponse) ([]*domain.City, error)
+ ConvertOneCityProtoToGo(city *cityGen.City) (domain.City, error)
+}
+
+type Utils struct{}
+
+func NewUtilsInterface() UtilsInterface {
+ return &Utils{}
+}
+
+const layout = "2006-01-02"
+
+func parseDate(dateStr, adID, fieldName string) (time.Time, error) {
+ if dateStr == "" {
+ return time.Time{}, nil
+ }
+ parsedDate, err := time.Parse(layout, dateStr)
+ if err != nil {
+ log.Printf("Error parsing %s for ad %s: %v\n", fieldName, adID, err)
+ return time.Time{}, errors.New("error parsing date for ad")
+ }
+ return parsedDate, nil
+}
+
+func (u *Utils) ConvertGetAllAdsResponseProtoToGo(proto *adsGen.GetAllAdsResponseList) (domain.GetAllAdsListResponse, error) {
+ ads := make([]domain.GetAllAdsResponse, 0, len(proto.Housing))
+
+ for _, ad := range proto.Housing {
+ adConverted, err := u.ConvertAdProtoToGo(ad)
+ if err != nil {
+ return domain.GetAllAdsListResponse{}, err
+ }
+ ads = append(ads, adConverted)
+ }
+
+ return domain.GetAllAdsListResponse{Housing: ads}, nil
+}
+
+func (u *Utils) ConvertAdProtoToGo(ad *adsGen.GetAllAdsResponse) (domain.GetAllAdsResponse, error) {
+ if ad == nil {
+ return domain.GetAllAdsResponse{}, errors.New("ad is nil")
+ }
+
+ if ad.AdAuthor == nil {
+ return domain.GetAllAdsResponse{}, errors.New("adAuthor is nil")
+ }
+
+ parsedPublicationDate, err := parseDate(ad.PublicationDate, ad.Id, "PublicationDate")
+ if err != nil {
+ return domain.GetAllAdsResponse{}, err
+ }
+
+ parsedEndBoostDate, err := parseDate(ad.EndBoostDate, ad.Id, "EndBoostDate")
+ if err != nil {
+ return domain.GetAllAdsResponse{}, err
+ }
+
+ parsedDateTo, err := parseDate(ad.AdDateTo, ad.Id, "AdDateTo")
+ if err != nil {
+ return domain.GetAllAdsResponse{}, err
+ }
+
+ parsedDateFrom, err := parseDate(ad.AdDateFrom, ad.Id, "AdDateFrom")
+ if err != nil {
+ return domain.GetAllAdsResponse{}, err
+ }
+
+ parsedBirthDate, err := parseDate(ad.AdAuthor.BirthDate, ad.Id, "BirthDate")
+ if err != nil {
+ return domain.GetAllAdsResponse{}, err
+ }
+
+ // Преобразуем объявление
+ return domain.GetAllAdsResponse{
+ UUID: ad.Id,
+ CityID: int(ad.CityId),
+ AuthorUUID: ad.AuthorUUID,
+ Address: ad.Address,
+ PublicationDate: parsedPublicationDate,
+ Description: ad.Description,
+ RoomsNumber: int(ad.RoomsNumber),
+ ViewsCount: int(ad.ViewsCount),
+ SquareMeters: int(ad.SquareMeters),
+ Floor: int(ad.Floor),
+ BuildingType: ad.BuildingType,
+ HasBalcony: ad.HasBalcony,
+ HasElevator: ad.HasElevator,
+ HasGas: ad.HasGas,
+ LikesCount: int(ad.LikesCount),
+ Priority: int(ad.Priority),
+ EndBoostDate: parsedEndBoostDate,
+ CityName: ad.CityName,
+ AdDateFrom: parsedDateFrom,
+ AdDateTo: parsedDateTo,
+ IsFavorite: ad.IsFavorite,
+ AdAuthor: domain.UserResponce{
+ Rating: math.Round(float64(ad.AdAuthor.Rating)*10) / 10,
+ Avatar: ad.AdAuthor.Avatar,
+ Name: ad.AdAuthor.Name,
+ Sex: ad.AdAuthor.Sex,
+ Birthdate: parsedBirthDate,
+ GuestCount: int(ad.AdAuthor.GuestCount),
+ },
+ Images: u.convertImagesResponseProtoToGo(ad.Images),
+ Rooms: u.convertAdRoomsResponseProtoToGo(ad.Rooms),
+ }, nil
+}
+
+// Вспомогательные функции для конвертации массивов
+func (u *Utils) convertImagesResponseProtoToGo(protoImages []*adsGen.ImageResponse) []domain.ImageResponse {
+ images := make([]domain.ImageResponse, len(protoImages))
+ for i, img := range protoImages {
+ images[i] = domain.ImageResponse{
+ ID: int(img.Id),
+ ImagePath: img.Path,
+ }
+ }
+ return images
+}
+
+func (u *Utils) convertAdRoomsResponseProtoToGo(protoRooms []*adsGen.AdRooms) []domain.AdRoomsResponse {
+ rooms := make([]domain.AdRoomsResponse, len(protoRooms))
+ for i, room := range protoRooms {
+ rooms[i] = domain.AdRoomsResponse{
+ Type: room.Type,
+ SquareMeters: int(room.SquareMeters),
+ }
+ }
+ return rooms
+}
+
+func (u *Utils) ConvertAuthResponseProtoToGo(response *authGen.UserResponse, userSession string) (domain.AuthResponse, error) {
+ if response == nil || response.User == nil {
+ return domain.AuthResponse{}, errors.New("invalid response or user nil")
+ }
+
+ return domain.AuthResponse{
+ SessionId: userSession,
+ User: domain.AuthData{
+ Id: response.User.Id,
+ Username: response.User.Username,
+ Email: response.User.Email,
+ },
+ }, nil
+}
+
+func (u *Utils) ConvertUserResponseProtoToGo(user *authGen.MetadataOneUser) (domain.UserDataResponse, error) {
+ if user == nil {
+ return domain.UserDataResponse{}, errors.New("user response is nil")
+ }
+
+ return domain.UserDataResponse{
+ Uuid: user.Uuid,
+ Username: user.Username,
+ Email: user.Email,
+ Name: user.Name,
+ Score: math.Round(float64(user.Score)*10) / 10,
+ Avatar: user.Avatar,
+ Sex: user.Sex,
+ GuestCount: int(user.GuestCount),
+ Birthdate: user.Birthdate.AsTime(),
+ IsHost: user.IsHost,
+ }, nil
+}
+
+func (u *Utils) ConvertUsersProtoToGo(users *authGen.AllUsersResponse) ([]*domain.UserDataResponse, error) {
+ var body []*domain.UserDataResponse
+
+ for _, user := range users.Users {
+ userResponse, err := u.ConvertUserResponseProtoToGo(user)
+ if err != nil {
+ return nil, fmt.Errorf("error converting user %s: %v", user.Uuid, err)
+ }
+
+ body = append(body, &userResponse)
+ }
+
+ return body, nil
+}
+
+func (u *Utils) ConvertSessionDataProtoToGo(sessionData *authGen.SessionDataResponse) (domain.SessionData, error) {
+ if sessionData == nil {
+ return domain.SessionData{}, errors.New("sessionData is nil")
+ }
+
+ return domain.SessionData{
+ Id: sessionData.Id,
+ Avatar: sessionData.Avatar,
+ }, nil
+}
+
+func (u *Utils) ConvertAllCitiesProtoToGo(cities *cityGen.GetCitiesResponse) ([]*domain.City, error) {
+ if cities == nil || cities.Cities == nil {
+ return []*domain.City{}, errors.New("cities is nil")
+ }
+ var body []*domain.City
+ for _, city := range cities.Cities {
+ cityResponse, err := u.ConvertOneCityProtoToGo(city)
+ if err != nil {
+ return nil, errors.New("error convert city")
+ }
+
+ body = append(body, &cityResponse)
+ }
+
+ return body, nil
+}
+
+func (u *Utils) ConvertOneCityProtoToGo(city *cityGen.City) (domain.City, error) {
+ if city == nil {
+ return domain.City{}, errors.New("city response is nil")
+ }
+
+ return domain.City{
+ ID: int(city.Id),
+ Title: city.Title,
+ EnTitle: city.Entitle,
+ Description: city.Description,
+ Image: city.Image,
+ }, nil
+}
diff --git a/internal/service/utils/mocks_convert.go b/internal/service/utils/mocks_convert.go
new file mode 100644
index 0000000..df0d2d5
--- /dev/null
+++ b/internal/service/utils/mocks_convert.go
@@ -0,0 +1,79 @@
+package utils
+
+import (
+ "2024_2_FIGHT-CLUB/domain"
+ "2024_2_FIGHT-CLUB/microservices/ads_service/controller/gen"
+ authGen "2024_2_FIGHT-CLUB/microservices/auth_service/controller/gen"
+ cityGen "2024_2_FIGHT-CLUB/microservices/city_service/controller/gen"
+ "github.com/stretchr/testify/mock"
+)
+
+type MockUtils struct {
+ mock.Mock
+}
+
+func (m *MockUtils) ConvertGetAllAdsResponseProtoToGo(proto *gen.GetAllAdsResponseList) (domain.GetAllAdsListResponse, error) {
+ args := m.Called(proto)
+ if res, ok := args.Get(0).(domain.GetAllAdsListResponse); ok {
+ return res, args.Error(1)
+ }
+ return domain.GetAllAdsListResponse{}, args.Error(1)
+}
+
+func (m *MockUtils) ConvertAdProtoToGo(ad *gen.GetAllAdsResponse) (domain.GetAllAdsResponse, error) {
+ args := m.Called(ad)
+ if res, ok := args.Get(0).(domain.GetAllAdsResponse); ok {
+ return res, args.Error(1)
+ }
+ return domain.GetAllAdsResponse{}, args.Error(1)
+}
+
+func (m *MockUtils) ConvertAuthResponseProtoToGo(response *authGen.UserResponse, userSession string) (domain.AuthResponse, error) {
+ args := m.Called(response, userSession)
+ if res, ok := args.Get(0).(domain.AuthResponse); ok {
+ return res, args.Error(1)
+ }
+ return domain.AuthResponse{}, args.Error(1)
+}
+
+func (m *MockUtils) ConvertUserResponseProtoToGo(user *authGen.MetadataOneUser) (domain.UserDataResponse, error) {
+ args := m.Called(user)
+ if res, ok := args.Get(0).(domain.UserDataResponse); ok {
+ return res, args.Error(1)
+ }
+ return domain.UserDataResponse{}, args.Error(1)
+}
+
+func (m *MockUtils) ConvertUsersProtoToGo(users *authGen.AllUsersResponse) ([]*domain.UserDataResponse, error) {
+ args := m.Called(users)
+ if res, ok := args.Get(0).([]*domain.UserDataResponse); ok {
+ return res, args.Error(1)
+ }
+ return []*domain.UserDataResponse{}, args.Error(1)
+}
+
+func (m *MockUtils) ConvertSessionDataProtoToGo(sessionData *authGen.SessionDataResponse) (domain.SessionData, error) {
+ args := m.Called(sessionData)
+ if res, ok := args.Get(0).(domain.SessionData); ok {
+ return res, args.Error(1)
+ }
+ return domain.SessionData{}, args.Error(1)
+}
+
+func (m *MockUtils) ConvertAllCitiesProtoToGo(cities *cityGen.GetCitiesResponse) ([]*domain.City, error) {
+ args := m.Called(cities)
+ var trueRes []*domain.City
+ if res, ok := args.Get(0).(domain.AllCitiesResponse); ok {
+ trueRes = append(trueRes, res.Cities...)
+ return trueRes, args.Error(1)
+ }
+ return []*domain.City{}, args.Error(1)
+}
+
+func (m *MockUtils) ConvertOneCityProtoToGo(city *cityGen.City) (domain.City, error) {
+ args := m.Called(city)
+ if res, ok := args.Get(0).(domain.City); ok {
+ return res, args.Error(1)
+ }
+ return domain.City{}, args.Error(1)
+}
diff --git a/internal/service/validation/validation.go b/internal/service/validation/validation.go
index 5dbf96b..4aec767 100644
--- a/internal/service/validation/validation.go
+++ b/internal/service/validation/validation.go
@@ -33,9 +33,9 @@ func ValidateName(name string) bool {
}
func ValidateImages(files [][]byte, maxSize int64, allowedMimeTypes []string, maxWidth, maxHeight int) error {
- for i, file := range files {
+ for _, file := range files {
if err := ValidateImage(file, maxSize, allowedMimeTypes, maxWidth, maxHeight); err != nil {
- return fmt.Errorf("file at index %d is invalid: %w", i, err)
+ return err
}
}
return nil
@@ -56,7 +56,7 @@ func ValidateImage(file []byte, maxSize int64, allowedMimeTypes []string, maxWid
}
}
if !allowed {
- return fmt.Errorf("file type %s is not allowed", mimeType)
+ return errors.New("file type is not allowed, please use (png, jpg, jpeg) types")
}
var img image.Image
@@ -72,13 +72,13 @@ func ValidateImage(file []byte, maxSize int64, allowedMimeTypes []string, maxWid
return errors.New("unsupported image format")
}
if err != nil {
- return fmt.Errorf("could not decode image: %v", err)
+ return errors.New("could not decode image")
}
width := img.Bounds().Dx()
height := img.Bounds().Dy()
if width > maxWidth || height > maxHeight {
- return fmt.Errorf("image resolution exceeds maximum allowed size of %dx%d", maxWidth, maxHeight)
+ return errors.New("image resolution exceeds maximum allowed size of 2000 x 2000")
}
return nil
diff --git a/microservices/ads_service/Dockerfile b/microservices/ads_service/Dockerfile
new file mode 100644
index 0000000..6182c5f
--- /dev/null
+++ b/microservices/ads_service/Dockerfile
@@ -0,0 +1,24 @@
+# 1. Build it
+
+FROM golang:1.23.1 AS builder
+WORKDIR /app
+# Копируем go.mod и go.sum
+COPY go.mod go.sum ./
+RUN go mod download
+
+# This microservice uses other modules so we can't just copy only it
+# Therefore we need to copy the whole fucking project
+# I have wasted 3 hours of my life on this
+# COPY ./microservices/ads_service/ ./microservices/ads_service/
+COPY . .
+ENV CGO_ENABLED=0
+ENV GOOS=linux
+RUN go build -o /microservices/ads_service/cmd/ads_service ./microservices/ads_service/cmd/main.go
+
+
+# 2. Run it
+FROM alpine:latest
+# WORKDIR /microservices/ads_service
+COPY --from=builder ./microservices/ads_service/cmd/ads_service /app/ads_service
+COPY --from=builder ./app/ssl /ssl/
+CMD ["/app/ads_service"]
\ No newline at end of file
diff --git a/microservices/ads_service/cmd/main.go b/microservices/ads_service/cmd/main.go
index 43d2877..e5935d4 100644
--- a/microservices/ads_service/cmd/main.go
+++ b/microservices/ads_service/cmd/main.go
@@ -9,6 +9,7 @@ import (
generatedAds "2024_2_FIGHT-CLUB/microservices/ads_service/controller/gen"
adRepository "2024_2_FIGHT-CLUB/microservices/ads_service/repository"
adUseCase "2024_2_FIGHT-CLUB/microservices/ads_service/usecase"
+ "context"
"github.com/joho/godotenv"
"github.com/prometheus/client_golang/prometheus/promhttp"
"google.golang.org/grpc"
@@ -16,6 +17,7 @@ import (
"net"
"net/http"
"os"
+ "time"
)
func main() {
@@ -27,6 +29,8 @@ func main() {
db := middleware.DbConnect()
minioService := middleware.MinioConnect()
+ ctx, cancel := context.WithCancel(context.Background())
+ defer cancel()
// Инициализация метрик
metrics.InitMetrics()
metrics.InitRepoMetric()
@@ -58,9 +62,12 @@ func main() {
sessionService := session.NewSessionService(redisStore)
adsUseCase := adUseCase.NewAdUseCase(adsRepository, minioService)
adsServer := grpcAd.NewGrpcAdHandler(sessionService, adsUseCase, jwtToken)
-
+ adsUseCase.StartPriorityResetWorker(ctx, 24*time.Hour)
grpcServer := grpc.NewServer(
- grpc.UnaryInterceptor(middleware.UnaryMetricsInterceptor),
+ grpc.UnaryInterceptor(middleware.ChainUnaryInterceptors(
+ middleware.RecoveryInterceptor, // интерсептор для обработки паники
+ middleware.UnaryMetricsInterceptor, // интерсептор для метрик
+ )),
)
generatedAds.RegisterAdsServer(grpcServer, adsServer)
diff --git a/microservices/ads_service/controller/ads_grpc.go b/microservices/ads_service/controller/ads_grpc.go
index 179a465..4d5b2f0 100644
--- a/microservices/ads_service/controller/ads_grpc.go
+++ b/microservices/ads_service/controller/ads_grpc.go
@@ -106,7 +106,12 @@ func (adh *GrpcAdHandler) GetAllPlaces(ctx context.Context, in *gen.AdFilterRequ
DateTo: dateTo,
}
- places, err := adh.usecase.GetAllPlaces(ctx, filter)
+ userID, err := adh.sessionService.GetUserID(ctx, in.SessionId)
+ if err != nil {
+ logger.AccessLogger.Warn("No active session", zap.String("request_id", requestID))
+ }
+
+ places, err := adh.usecase.GetAllPlaces(ctx, filter, userID)
if err != nil {
logger.AccessLogger.Error("Failed to get places",
zap.Error(err),
@@ -123,19 +128,30 @@ func (adh *GrpcAdHandler) GetAllPlaces(ctx context.Context, in *gen.AdFilterRequ
PublicationDate: place.PublicationDate.Format(layout),
Description: place.Description,
RoomsNumber: int32(place.RoomsNumber),
- ViewsCount: int32Ptr(int32(place.ViewsCount)),
+ ViewsCount: int32(place.ViewsCount),
+ SquareMeters: int32(place.SquareMeters),
+ Floor: int32(place.Floor),
+ BuildingType: place.BuildingType,
+ HasBalcony: place.HasBalcony,
+ HasElevator: place.HasElevator,
+ HasGas: place.HasGas,
+ LikesCount: int32(place.LikesCount),
+ Priority: int32(place.Priority),
+ EndBoostDate: place.EndBoostDate.Format(layout),
CityName: place.CityName,
AdDateFrom: place.AdDateFrom.Format(layout),
AdDateTo: place.AdDateTo.Format(layout),
+ IsFavorite: place.IsFavorite,
AdAuthor: &gen.UserResponse{
- Rating: float32Ptr(float32(place.AdAuthor.Rating)),
+ Rating: float32(place.AdAuthor.Rating),
Avatar: place.AdAuthor.Avatar,
Name: place.AdAuthor.Name,
- GuestCount: int32Ptr(int32(place.AdAuthor.GuestCount)),
+ GuestCount: int32(place.AdAuthor.GuestCount),
Sex: place.AdAuthor.Sex,
BirthDate: place.AdAuthor.Birthdate.Format(layout),
},
Images: convertImagesToGRPC(place.Images),
+ Rooms: middleware.ConvertRoomsToGRPC(place.Rooms),
}
responseList.Housing = append(responseList.Housing, ad)
}
@@ -171,19 +187,30 @@ func (adh *GrpcAdHandler) GetOnePlace(ctx context.Context, in *gen.GetPlaceByIdR
PublicationDate: place.PublicationDate.Format(layout),
Description: place.Description,
RoomsNumber: int32(place.RoomsNumber),
- ViewsCount: int32Ptr(int32(place.ViewsCount)),
+ ViewsCount: int32(place.ViewsCount),
+ SquareMeters: int32(place.SquareMeters),
+ Floor: int32(place.Floor),
+ BuildingType: place.BuildingType,
+ HasBalcony: place.HasBalcony,
+ HasElevator: place.HasElevator,
+ HasGas: place.HasGas,
+ LikesCount: int32(place.LikesCount),
+ Priority: int32(place.Priority),
+ EndBoostDate: place.EndBoostDate.Format(layout),
CityName: place.CityName,
AdDateFrom: place.AdDateFrom.Format(layout),
AdDateTo: place.AdDateTo.Format(layout),
+ IsFavorite: place.IsFavorite,
AdAuthor: &gen.UserResponse{
- Rating: float32Ptr(float32(place.AdAuthor.Rating)),
+ Rating: float32(place.AdAuthor.Rating),
Avatar: place.AdAuthor.Avatar,
Name: place.AdAuthor.Name,
- GuestCount: int32Ptr(int32(place.AdAuthor.GuestCount)),
+ GuestCount: int32(place.AdAuthor.GuestCount),
Sex: place.AdAuthor.Sex,
BirthDate: place.AdAuthor.Birthdate.Format(layout),
},
Images: convertImagesToGRPC(place.Images),
+ Rooms: middleware.ConvertRoomsToGRPC(place.Rooms),
}, nil
}
@@ -206,6 +233,7 @@ func (adh *GrpcAdHandler) CreatePlace(ctx context.Context, in *gen.CreateAdReque
in.CityName = sanitizer.Sanitize(in.CityName)
in.Description = sanitizer.Sanitize(in.Description)
in.Address = sanitizer.Sanitize(in.Address)
+ in.BuildingType = sanitizer.Sanitize(in.BuildingType)
tokenString := in.AuthHeader[len("Bearer "):]
_, err := adh.jwtToken.Validate(tokenString, in.SessionID)
@@ -222,12 +250,19 @@ func (adh *GrpcAdHandler) CreatePlace(ctx context.Context, in *gen.CreateAdReque
var place domain.Ad
newPlace := domain.CreateAdRequest{
- CityName: in.CityName,
- Description: in.Description,
- Address: in.Address,
- RoomsNumber: int(in.RoomsNumber),
- DateFrom: (in.DateFrom).AsTime(),
- DateTo: (in.DateTo).AsTime(),
+ CityName: in.CityName,
+ Description: in.Description,
+ Address: in.Address,
+ RoomsNumber: int(in.RoomsNumber),
+ DateFrom: (in.DateFrom).AsTime(),
+ DateTo: (in.DateTo).AsTime(),
+ Rooms: middleware.ConvertGRPCToRooms(in.Rooms),
+ SquareMeters: int(in.SquareMeters),
+ Floor: int(in.Floor),
+ BuildingType: in.BuildingType,
+ HasBalcony: in.HasBalcony,
+ HasElevator: in.HasElevator,
+ HasGas: in.HasGas,
}
place.AuthorUUID = userID
@@ -260,6 +295,7 @@ func (adh *GrpcAdHandler) UpdatePlace(ctx context.Context, in *gen.UpdateAdReque
in.Description = sanitizer.Sanitize(in.Description)
in.Address = sanitizer.Sanitize(in.Address)
in.CityName = sanitizer.Sanitize(in.CityName)
+ in.BuildingType = sanitizer.Sanitize(in.BuildingType)
if in.AuthHeader == "" {
logger.AccessLogger.Warn("Missing X-CSRF-Token header",
@@ -282,12 +318,19 @@ func (adh *GrpcAdHandler) UpdatePlace(ctx context.Context, in *gen.UpdateAdReque
return nil, errors.New("no active session")
}
updatedPlace := domain.UpdateAdRequest{
- CityName: in.CityName,
- Description: in.Description,
- Address: in.Address,
- RoomsNumber: int(in.RoomsNumber),
- DateFrom: (in.DateFrom).AsTime(),
- DateTo: (in.DateTo).AsTime(),
+ CityName: in.CityName,
+ Description: in.Description,
+ Address: in.Address,
+ RoomsNumber: int(in.RoomsNumber),
+ DateFrom: (in.DateFrom).AsTime(),
+ DateTo: (in.DateTo).AsTime(),
+ Rooms: middleware.ConvertGRPCToRooms(in.Rooms),
+ SquareMeters: int(in.SquareMeters),
+ Floor: int(in.Floor),
+ BuildingType: in.BuildingType,
+ HasBalcony: in.HasBalcony,
+ HasElevator: in.HasElevator,
+ HasGas: in.HasGas,
}
var place domain.Ad
err = adh.usecase.UpdatePlace(ctx, &place, in.AdId, userID, in.Images, updatedPlace)
@@ -359,19 +402,30 @@ func (adh *GrpcAdHandler) GetPlacesPerCity(ctx context.Context, in *gen.GetPlace
PublicationDate: place.PublicationDate.Format(layout),
Description: place.Description,
RoomsNumber: int32(place.RoomsNumber),
- ViewsCount: int32Ptr(int32(place.ViewsCount)),
+ ViewsCount: int32(place.ViewsCount),
+ SquareMeters: int32(place.SquareMeters),
+ Floor: int32(place.Floor),
+ BuildingType: place.BuildingType,
+ HasBalcony: place.HasBalcony,
+ HasElevator: place.HasElevator,
+ HasGas: place.HasGas,
+ LikesCount: int32(place.LikesCount),
+ Priority: int32(place.Priority),
+ EndBoostDate: place.EndBoostDate.Format(layout),
CityName: place.CityName,
AdDateFrom: place.AdDateFrom.Format(layout),
AdDateTo: place.AdDateTo.Format(layout),
+ IsFavorite: place.IsFavorite,
AdAuthor: &gen.UserResponse{
- Rating: float32Ptr(float32(place.AdAuthor.Rating)),
+ Rating: float32(place.AdAuthor.Rating),
Avatar: place.AdAuthor.Avatar,
Name: place.AdAuthor.Name,
- GuestCount: int32Ptr(int32(place.AdAuthor.GuestCount)),
+ GuestCount: int32(place.AdAuthor.GuestCount),
Sex: place.AdAuthor.Sex,
BirthDate: place.AdAuthor.Birthdate.Format(layout),
},
Images: convertImagesToGRPC(place.Images),
+ Rooms: middleware.ConvertRoomsToGRPC(place.Rooms),
}
responseList.Housing = append(responseList.Housing, ad)
}
@@ -401,19 +455,29 @@ func (adh *GrpcAdHandler) GetUserPlaces(ctx context.Context, in *gen.GetUserPlac
PublicationDate: place.PublicationDate.Format(layout),
Description: place.Description,
RoomsNumber: int32(place.RoomsNumber),
- ViewsCount: int32Ptr(int32(place.ViewsCount)),
+ ViewsCount: int32(place.ViewsCount),
+ SquareMeters: int32(place.SquareMeters),
+ Floor: int32(place.Floor),
+ BuildingType: place.BuildingType,
+ HasBalcony: place.HasBalcony,
+ HasElevator: place.HasElevator,
+ HasGas: place.HasGas,
+ LikesCount: int32(place.LikesCount),
+ Priority: int32(place.Priority),
+ EndBoostDate: place.EndBoostDate.Format(layout),
CityName: place.CityName,
AdDateFrom: place.AdDateFrom.Format(layout),
AdDateTo: place.AdDateTo.Format(layout),
AdAuthor: &gen.UserResponse{
- Rating: float32Ptr(float32(place.AdAuthor.Rating)),
+ Rating: float32(place.AdAuthor.Rating),
Avatar: place.AdAuthor.Avatar,
Name: place.AdAuthor.Name,
- GuestCount: int32Ptr(int32(place.AdAuthor.GuestCount)),
+ GuestCount: int32(place.AdAuthor.GuestCount),
Sex: place.AdAuthor.Sex,
BirthDate: place.AdAuthor.Birthdate.Format(layout),
},
Images: convertImagesToGRPC(place.Images),
+ Rooms: middleware.ConvertRoomsToGRPC(place.Rooms),
}
responseList.Housing = append(responseList.Housing, ad)
}
@@ -544,21 +608,6 @@ func (adh *GrpcAdHandler) GetUserFavorites(ctx context.Context, in *gen.GetUserF
layout := "2006-01-02"
in.UserId = sanitizer.Sanitize(in.UserId)
- if in.AuthHeader == "" {
- logger.AccessLogger.Warn("Missing X-CSRF-Token header",
- zap.String("request_id", requestID),
- zap.Error(errors.New("missing X-CSRF-Token header")),
- )
- return nil, errors.New("missing X-CSRF-Token header")
- }
-
- tokenString := in.AuthHeader[len("Bearer "):]
- _, err := adh.jwtToken.Validate(tokenString, in.SessionID)
- if err != nil {
- logger.AccessLogger.Warn("Invalid JWT token", zap.String("request_id", requestID), zap.Error(err))
- return nil, errors.New("invalid JWT token")
- }
-
userID, err := adh.sessionService.GetUserID(ctx, in.SessionID)
if err != nil {
logger.AccessLogger.Warn("No active session", zap.String("request_id", requestID))
@@ -583,19 +632,29 @@ func (adh *GrpcAdHandler) GetUserFavorites(ctx context.Context, in *gen.GetUserF
PublicationDate: place.PublicationDate.Format(layout),
Description: place.Description,
RoomsNumber: int32(place.RoomsNumber),
- ViewsCount: int32Ptr(int32(place.ViewsCount)),
+ ViewsCount: int32(place.ViewsCount),
+ SquareMeters: int32(place.SquareMeters),
+ Floor: int32(place.Floor),
+ BuildingType: place.BuildingType,
+ HasBalcony: place.HasBalcony,
+ HasElevator: place.HasElevator,
+ HasGas: place.HasGas,
+ LikesCount: int32(place.LikesCount),
+ Priority: int32(place.Priority),
+ EndBoostDate: place.EndBoostDate.Format(layout),
CityName: place.CityName,
AdDateFrom: place.AdDateFrom.Format(layout),
AdDateTo: place.AdDateTo.Format(layout),
AdAuthor: &gen.UserResponse{
- Rating: float32Ptr(float32(place.AdAuthor.Rating)),
+ Rating: float32(place.AdAuthor.Rating),
Avatar: place.AdAuthor.Avatar,
Name: place.AdAuthor.Name,
- GuestCount: int32Ptr(int32(place.AdAuthor.GuestCount)),
+ GuestCount: int32(place.AdAuthor.GuestCount),
Sex: place.AdAuthor.Sex,
BirthDate: place.AdAuthor.Birthdate.Format(layout),
},
Images: convertImagesToGRPC(place.Images),
+ Rooms: middleware.ConvertRoomsToGRPC(place.Rooms),
}
responseList.Housing = append(responseList.Housing, ad)
}
@@ -604,12 +663,51 @@ func (adh *GrpcAdHandler) GetUserFavorites(ctx context.Context, in *gen.GetUserF
return &responseList, nil
}
-func float32Ptr(f float32) *float32 {
- return &f
-}
+func (adh *GrpcAdHandler) UpdatePriority(ctx context.Context, in *gen.UpdatePriorityRequest) (*gen.AdResponse, error) {
+ requestID := middleware.GetRequestID(ctx)
+ sanitizer := bluemonday.UGCPolicy()
+ logger.AccessLogger.Info("Received UpdatePriority request in microservice",
+ zap.String("request_id", requestID),
+ )
+ in.AdId = sanitizer.Sanitize(in.AdId)
+ in.Amount = sanitizer.Sanitize(in.Amount)
-func int32Ptr(i int32) *int32 {
- return &i
+ var amountInt int
+ if in.Amount != "" {
+ var err error
+ amountInt, err = strconv.Atoi(in.Amount)
+ if err != nil {
+ logger.AccessLogger.Error("Failed to parse amount as int", zap.String("request_id", requestID), zap.Error(err))
+ return nil, errors.New("amount is not int")
+ }
+ }
+
+ if in.AuthHeader == "" {
+ logger.AccessLogger.Warn("Missing X-CSRF-Token header",
+ zap.String("request_id", requestID),
+ zap.Error(errors.New("missing X-CSRF-Token header")),
+ )
+ return nil, errors.New("missing X-CSRF-Token header")
+ }
+
+ tokenString := in.AuthHeader[len("Bearer "):]
+ _, err := adh.jwtToken.Validate(tokenString, in.SessionID)
+ if err != nil {
+ logger.AccessLogger.Warn("Invalid JWT token", zap.String("request_id", requestID), zap.Error(err))
+ return nil, errors.New("invalid JWT token")
+ }
+
+ userId, err := adh.sessionService.GetUserID(ctx, in.SessionID)
+ if err != nil {
+ logger.AccessLogger.Warn("No active session", zap.String("request_id", requestID))
+ return nil, errors.New("no active session")
+ }
+ err = adh.usecase.UpdatePriority(ctx, in.AdId, userId, amountInt)
+ if err != nil {
+ logger.AccessLogger.Warn("Failed to update ad priority", zap.String("request_id", requestID), zap.Error(err))
+ return nil, err
+ }
+ return &gen.AdResponse{Response: "Successfully update ad priority"}, nil
}
func convertImagesToGRPC(images []domain.ImageResponse) []*gen.ImageResponse {
diff --git a/microservices/ads_service/controller/gen/ads.pb.go b/microservices/ads_service/controller/gen/ads.pb.go
index 27af9c4..c3c1b9a 100644
--- a/microservices/ads_service/controller/gen/ads.pb.go
+++ b/microservices/ads_service/controller/gen/ads.pb.go
@@ -127,16 +127,23 @@ type CreateAdRequest struct {
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
- CityName string `protobuf:"bytes,1,opt,name=cityName,proto3" json:"cityName,omitempty"`
- Address string `protobuf:"bytes,2,opt,name=address,proto3" json:"address,omitempty"`
- Description string `protobuf:"bytes,3,opt,name=description,proto3" json:"description,omitempty"`
- RoomsNumber int32 `protobuf:"varint,4,opt,name=roomsNumber,proto3" json:"roomsNumber,omitempty"`
- DateFrom *timestamppb.Timestamp `protobuf:"bytes,5,opt,name=dateFrom,proto3" json:"dateFrom,omitempty"`
- DateTo *timestamppb.Timestamp `protobuf:"bytes,6,opt,name=dateTo,proto3" json:"dateTo,omitempty"`
- Images [][]byte `protobuf:"bytes,7,rep,name=images,proto3" json:"images,omitempty"`
- AuthHeader string `protobuf:"bytes,8,opt,name=authHeader,proto3" json:"authHeader,omitempty"`
- SessionID string `protobuf:"bytes,9,opt,name=sessionID,proto3" json:"sessionID,omitempty"`
- AuthorID string `protobuf:"bytes,10,opt,name=authorID,proto3" json:"authorID,omitempty"`
+ CityName string `protobuf:"bytes,1,opt,name=cityName,proto3" json:"cityName,omitempty"`
+ Address string `protobuf:"bytes,2,opt,name=address,proto3" json:"address,omitempty"`
+ Description string `protobuf:"bytes,3,opt,name=description,proto3" json:"description,omitempty"`
+ RoomsNumber int32 `protobuf:"varint,4,opt,name=roomsNumber,proto3" json:"roomsNumber,omitempty"`
+ DateFrom *timestamppb.Timestamp `protobuf:"bytes,5,opt,name=dateFrom,proto3" json:"dateFrom,omitempty"`
+ DateTo *timestamppb.Timestamp `protobuf:"bytes,6,opt,name=dateTo,proto3" json:"dateTo,omitempty"`
+ Images [][]byte `protobuf:"bytes,7,rep,name=images,proto3" json:"images,omitempty"`
+ SquareMeters int32 `protobuf:"varint,8,opt,name=squareMeters,proto3" json:"squareMeters,omitempty"`
+ Floor int32 `protobuf:"varint,9,opt,name=floor,proto3" json:"floor,omitempty"`
+ BuildingType string `protobuf:"bytes,10,opt,name=buildingType,proto3" json:"buildingType,omitempty"`
+ HasBalcony bool `protobuf:"varint,11,opt,name=hasBalcony,proto3" json:"hasBalcony,omitempty"`
+ HasElevator bool `protobuf:"varint,12,opt,name=hasElevator,proto3" json:"hasElevator,omitempty"`
+ HasGas bool `protobuf:"varint,13,opt,name=hasGas,proto3" json:"hasGas,omitempty"`
+ AuthHeader string `protobuf:"bytes,14,opt,name=authHeader,proto3" json:"authHeader,omitempty"`
+ SessionID string `protobuf:"bytes,15,opt,name=sessionID,proto3" json:"sessionID,omitempty"`
+ AuthorID string `protobuf:"bytes,16,opt,name=authorID,proto3" json:"authorID,omitempty"`
+ Rooms []*AdRooms `protobuf:"bytes,17,rep,name=rooms,proto3" json:"rooms,omitempty"`
}
func (x *CreateAdRequest) Reset() {
@@ -218,6 +225,48 @@ func (x *CreateAdRequest) GetImages() [][]byte {
return nil
}
+func (x *CreateAdRequest) GetSquareMeters() int32 {
+ if x != nil {
+ return x.SquareMeters
+ }
+ return 0
+}
+
+func (x *CreateAdRequest) GetFloor() int32 {
+ if x != nil {
+ return x.Floor
+ }
+ return 0
+}
+
+func (x *CreateAdRequest) GetBuildingType() string {
+ if x != nil {
+ return x.BuildingType
+ }
+ return ""
+}
+
+func (x *CreateAdRequest) GetHasBalcony() bool {
+ if x != nil {
+ return x.HasBalcony
+ }
+ return false
+}
+
+func (x *CreateAdRequest) GetHasElevator() bool {
+ if x != nil {
+ return x.HasElevator
+ }
+ return false
+}
+
+func (x *CreateAdRequest) GetHasGas() bool {
+ if x != nil {
+ return x.HasGas
+ }
+ return false
+}
+
func (x *CreateAdRequest) GetAuthHeader() string {
if x != nil {
return x.AuthHeader
@@ -239,26 +288,93 @@ func (x *CreateAdRequest) GetAuthorID() string {
return ""
}
+func (x *CreateAdRequest) GetRooms() []*AdRooms {
+ if x != nil {
+ return x.Rooms
+ }
+ return nil
+}
+
+type AdRooms struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"`
+ SquareMeters int32 `protobuf:"varint,2,opt,name=squareMeters,proto3" json:"squareMeters,omitempty"`
+}
+
+func (x *AdRooms) Reset() {
+ *x = AdRooms{}
+ mi := &file_ads_proto_msgTypes[2]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *AdRooms) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*AdRooms) ProtoMessage() {}
+
+func (x *AdRooms) ProtoReflect() protoreflect.Message {
+ mi := &file_ads_proto_msgTypes[2]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use AdRooms.ProtoReflect.Descriptor instead.
+func (*AdRooms) Descriptor() ([]byte, []int) {
+ return file_ads_proto_rawDescGZIP(), []int{2}
+}
+
+func (x *AdRooms) GetType() string {
+ if x != nil {
+ return x.Type
+ }
+ return ""
+}
+
+func (x *AdRooms) GetSquareMeters() int32 {
+ if x != nil {
+ return x.SquareMeters
+ }
+ return 0
+}
+
type UpdateAdRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
- AdId string `protobuf:"bytes,1,opt,name=adId,proto3" json:"adId,omitempty"`
- CityName string `protobuf:"bytes,2,opt,name=cityName,proto3" json:"cityName,omitempty"`
- Address string `protobuf:"bytes,3,opt,name=address,proto3" json:"address,omitempty"`
- Description string `protobuf:"bytes,4,opt,name=description,proto3" json:"description,omitempty"`
- RoomsNumber int32 `protobuf:"varint,5,opt,name=roomsNumber,proto3" json:"roomsNumber,omitempty"`
- DateFrom *timestamppb.Timestamp `protobuf:"bytes,6,opt,name=dateFrom,proto3" json:"dateFrom,omitempty"`
- DateTo *timestamppb.Timestamp `protobuf:"bytes,7,opt,name=dateTo,proto3" json:"dateTo,omitempty"`
- Images [][]byte `protobuf:"bytes,8,rep,name=images,proto3" json:"images,omitempty"`
- AuthHeader string `protobuf:"bytes,9,opt,name=authHeader,proto3" json:"authHeader,omitempty"`
- SessionID string `protobuf:"bytes,10,opt,name=sessionID,proto3" json:"sessionID,omitempty"`
+ AdId string `protobuf:"bytes,1,opt,name=adId,proto3" json:"adId,omitempty"`
+ CityName string `protobuf:"bytes,2,opt,name=cityName,proto3" json:"cityName,omitempty"`
+ Address string `protobuf:"bytes,3,opt,name=address,proto3" json:"address,omitempty"`
+ Description string `protobuf:"bytes,4,opt,name=description,proto3" json:"description,omitempty"`
+ RoomsNumber int32 `protobuf:"varint,5,opt,name=roomsNumber,proto3" json:"roomsNumber,omitempty"`
+ DateFrom *timestamppb.Timestamp `protobuf:"bytes,6,opt,name=dateFrom,proto3" json:"dateFrom,omitempty"`
+ DateTo *timestamppb.Timestamp `protobuf:"bytes,7,opt,name=dateTo,proto3" json:"dateTo,omitempty"`
+ SquareMeters int32 `protobuf:"varint,8,opt,name=squareMeters,proto3" json:"squareMeters,omitempty"`
+ Floor int32 `protobuf:"varint,9,opt,name=floor,proto3" json:"floor,omitempty"`
+ BuildingType string `protobuf:"bytes,10,opt,name=buildingType,proto3" json:"buildingType,omitempty"`
+ HasBalcony bool `protobuf:"varint,11,opt,name=hasBalcony,proto3" json:"hasBalcony,omitempty"`
+ HasElevator bool `protobuf:"varint,12,opt,name=hasElevator,proto3" json:"hasElevator,omitempty"`
+ HasGas bool `protobuf:"varint,13,opt,name=hasGas,proto3" json:"hasGas,omitempty"`
+ Images [][]byte `protobuf:"bytes,14,rep,name=images,proto3" json:"images,omitempty"`
+ AuthHeader string `protobuf:"bytes,15,opt,name=authHeader,proto3" json:"authHeader,omitempty"`
+ SessionID string `protobuf:"bytes,16,opt,name=sessionID,proto3" json:"sessionID,omitempty"`
+ Rooms []*AdRooms `protobuf:"bytes,17,rep,name=rooms,proto3" json:"rooms,omitempty"`
}
func (x *UpdateAdRequest) Reset() {
*x = UpdateAdRequest{}
- mi := &file_ads_proto_msgTypes[2]
+ mi := &file_ads_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -270,7 +386,7 @@ func (x *UpdateAdRequest) String() string {
func (*UpdateAdRequest) ProtoMessage() {}
func (x *UpdateAdRequest) ProtoReflect() protoreflect.Message {
- mi := &file_ads_proto_msgTypes[2]
+ mi := &file_ads_proto_msgTypes[3]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -283,7 +399,7 @@ func (x *UpdateAdRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use UpdateAdRequest.ProtoReflect.Descriptor instead.
func (*UpdateAdRequest) Descriptor() ([]byte, []int) {
- return file_ads_proto_rawDescGZIP(), []int{2}
+ return file_ads_proto_rawDescGZIP(), []int{3}
}
func (x *UpdateAdRequest) GetAdId() string {
@@ -335,6 +451,48 @@ func (x *UpdateAdRequest) GetDateTo() *timestamppb.Timestamp {
return nil
}
+func (x *UpdateAdRequest) GetSquareMeters() int32 {
+ if x != nil {
+ return x.SquareMeters
+ }
+ return 0
+}
+
+func (x *UpdateAdRequest) GetFloor() int32 {
+ if x != nil {
+ return x.Floor
+ }
+ return 0
+}
+
+func (x *UpdateAdRequest) GetBuildingType() string {
+ if x != nil {
+ return x.BuildingType
+ }
+ return ""
+}
+
+func (x *UpdateAdRequest) GetHasBalcony() bool {
+ if x != nil {
+ return x.HasBalcony
+ }
+ return false
+}
+
+func (x *UpdateAdRequest) GetHasElevator() bool {
+ if x != nil {
+ return x.HasElevator
+ }
+ return false
+}
+
+func (x *UpdateAdRequest) GetHasGas() bool {
+ if x != nil {
+ return x.HasGas
+ }
+ return false
+}
+
func (x *UpdateAdRequest) GetImages() [][]byte {
if x != nil {
return x.Images
@@ -356,6 +514,13 @@ func (x *UpdateAdRequest) GetSessionID() string {
return ""
}
+func (x *UpdateAdRequest) GetRooms() []*AdRooms {
+ if x != nil {
+ return x.Rooms
+ }
+ return nil
+}
+
type DeletePlaceRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
@@ -368,7 +533,7 @@ type DeletePlaceRequest struct {
func (x *DeletePlaceRequest) Reset() {
*x = DeletePlaceRequest{}
- mi := &file_ads_proto_msgTypes[3]
+ mi := &file_ads_proto_msgTypes[4]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -380,7 +545,7 @@ func (x *DeletePlaceRequest) String() string {
func (*DeletePlaceRequest) ProtoMessage() {}
func (x *DeletePlaceRequest) ProtoReflect() protoreflect.Message {
- mi := &file_ads_proto_msgTypes[3]
+ mi := &file_ads_proto_msgTypes[4]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -393,7 +558,7 @@ func (x *DeletePlaceRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use DeletePlaceRequest.ProtoReflect.Descriptor instead.
func (*DeletePlaceRequest) Descriptor() ([]byte, []int) {
- return file_ads_proto_rawDescGZIP(), []int{3}
+ return file_ads_proto_rawDescGZIP(), []int{4}
}
func (x *DeletePlaceRequest) GetAdId() string {
@@ -429,7 +594,7 @@ type AddToFavoritesRequest struct {
func (x *AddToFavoritesRequest) Reset() {
*x = AddToFavoritesRequest{}
- mi := &file_ads_proto_msgTypes[4]
+ mi := &file_ads_proto_msgTypes[5]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -441,7 +606,7 @@ func (x *AddToFavoritesRequest) String() string {
func (*AddToFavoritesRequest) ProtoMessage() {}
func (x *AddToFavoritesRequest) ProtoReflect() protoreflect.Message {
- mi := &file_ads_proto_msgTypes[4]
+ mi := &file_ads_proto_msgTypes[5]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -454,7 +619,7 @@ func (x *AddToFavoritesRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use AddToFavoritesRequest.ProtoReflect.Descriptor instead.
func (*AddToFavoritesRequest) Descriptor() ([]byte, []int) {
- return file_ads_proto_rawDescGZIP(), []int{4}
+ return file_ads_proto_rawDescGZIP(), []int{5}
}
func (x *AddToFavoritesRequest) GetAdId() string {
@@ -490,7 +655,7 @@ type DeleteFromFavoritesRequest struct {
func (x *DeleteFromFavoritesRequest) Reset() {
*x = DeleteFromFavoritesRequest{}
- mi := &file_ads_proto_msgTypes[5]
+ mi := &file_ads_proto_msgTypes[6]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -502,7 +667,7 @@ func (x *DeleteFromFavoritesRequest) String() string {
func (*DeleteFromFavoritesRequest) ProtoMessage() {}
func (x *DeleteFromFavoritesRequest) ProtoReflect() protoreflect.Message {
- mi := &file_ads_proto_msgTypes[5]
+ mi := &file_ads_proto_msgTypes[6]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -515,7 +680,7 @@ func (x *DeleteFromFavoritesRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use DeleteFromFavoritesRequest.ProtoReflect.Descriptor instead.
func (*DeleteFromFavoritesRequest) Descriptor() ([]byte, []int) {
- return file_ads_proto_rawDescGZIP(), []int{5}
+ return file_ads_proto_rawDescGZIP(), []int{6}
}
func (x *DeleteFromFavoritesRequest) GetAdId() string {
@@ -544,14 +709,13 @@ type GetUserFavoritesRequest struct {
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
- UserId string `protobuf:"bytes,1,opt,name=userId,proto3" json:"userId,omitempty"`
- AuthHeader string `protobuf:"bytes,2,opt,name=authHeader,proto3" json:"authHeader,omitempty"`
- SessionID string `protobuf:"bytes,3,opt,name=sessionID,proto3" json:"sessionID,omitempty"`
+ UserId string `protobuf:"bytes,1,opt,name=userId,proto3" json:"userId,omitempty"`
+ SessionID string `protobuf:"bytes,2,opt,name=sessionID,proto3" json:"sessionID,omitempty"`
}
func (x *GetUserFavoritesRequest) Reset() {
*x = GetUserFavoritesRequest{}
- mi := &file_ads_proto_msgTypes[6]
+ mi := &file_ads_proto_msgTypes[7]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -563,7 +727,7 @@ func (x *GetUserFavoritesRequest) String() string {
func (*GetUserFavoritesRequest) ProtoMessage() {}
func (x *GetUserFavoritesRequest) ProtoReflect() protoreflect.Message {
- mi := &file_ads_proto_msgTypes[6]
+ mi := &file_ads_proto_msgTypes[7]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -576,7 +740,7 @@ func (x *GetUserFavoritesRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use GetUserFavoritesRequest.ProtoReflect.Descriptor instead.
func (*GetUserFavoritesRequest) Descriptor() ([]byte, []int) {
- return file_ads_proto_rawDescGZIP(), []int{6}
+ return file_ads_proto_rawDescGZIP(), []int{7}
}
func (x *GetUserFavoritesRequest) GetUserId() string {
@@ -586,13 +750,6 @@ func (x *GetUserFavoritesRequest) GetUserId() string {
return ""
}
-func (x *GetUserFavoritesRequest) GetAuthHeader() string {
- if x != nil {
- return x.AuthHeader
- }
- return ""
-}
-
func (x *GetUserFavoritesRequest) GetSessionID() string {
if x != nil {
return x.SessionID
@@ -613,7 +770,7 @@ type DeleteAdImageRequest struct {
func (x *DeleteAdImageRequest) Reset() {
*x = DeleteAdImageRequest{}
- mi := &file_ads_proto_msgTypes[7]
+ mi := &file_ads_proto_msgTypes[8]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -625,7 +782,7 @@ func (x *DeleteAdImageRequest) String() string {
func (*DeleteAdImageRequest) ProtoMessage() {}
func (x *DeleteAdImageRequest) ProtoReflect() protoreflect.Message {
- mi := &file_ads_proto_msgTypes[7]
+ mi := &file_ads_proto_msgTypes[8]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -638,7 +795,7 @@ func (x *DeleteAdImageRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use DeleteAdImageRequest.ProtoReflect.Descriptor instead.
func (*DeleteAdImageRequest) Descriptor() ([]byte, []int) {
- return file_ads_proto_rawDescGZIP(), []int{7}
+ return file_ads_proto_rawDescGZIP(), []int{8}
}
func (x *DeleteAdImageRequest) GetAdId() string {
@@ -679,7 +836,7 @@ type GetPlacesPerCityRequest struct {
func (x *GetPlacesPerCityRequest) Reset() {
*x = GetPlacesPerCityRequest{}
- mi := &file_ads_proto_msgTypes[8]
+ mi := &file_ads_proto_msgTypes[9]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -691,7 +848,7 @@ func (x *GetPlacesPerCityRequest) String() string {
func (*GetPlacesPerCityRequest) ProtoMessage() {}
func (x *GetPlacesPerCityRequest) ProtoReflect() protoreflect.Message {
- mi := &file_ads_proto_msgTypes[8]
+ mi := &file_ads_proto_msgTypes[9]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -704,7 +861,7 @@ func (x *GetPlacesPerCityRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use GetPlacesPerCityRequest.ProtoReflect.Descriptor instead.
func (*GetPlacesPerCityRequest) Descriptor() ([]byte, []int) {
- return file_ads_proto_rawDescGZIP(), []int{8}
+ return file_ads_proto_rawDescGZIP(), []int{9}
}
func (x *GetPlacesPerCityRequest) GetCityName() string {
@@ -724,7 +881,7 @@ type GetUserPlacesRequest struct {
func (x *GetUserPlacesRequest) Reset() {
*x = GetUserPlacesRequest{}
- mi := &file_ads_proto_msgTypes[9]
+ mi := &file_ads_proto_msgTypes[10]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -736,7 +893,7 @@ func (x *GetUserPlacesRequest) String() string {
func (*GetUserPlacesRequest) ProtoMessage() {}
func (x *GetUserPlacesRequest) ProtoReflect() protoreflect.Message {
- mi := &file_ads_proto_msgTypes[9]
+ mi := &file_ads_proto_msgTypes[10]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -749,7 +906,7 @@ func (x *GetUserPlacesRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use GetUserPlacesRequest.ProtoReflect.Descriptor instead.
func (*GetUserPlacesRequest) Descriptor() ([]byte, []int) {
- return file_ads_proto_rawDescGZIP(), []int{9}
+ return file_ads_proto_rawDescGZIP(), []int{10}
}
func (x *GetUserPlacesRequest) GetUserId() string {
@@ -773,11 +930,12 @@ type AdFilterRequest struct {
Offset string `protobuf:"bytes,7,opt,name=offset,proto3" json:"offset,omitempty"`
DateFrom string `protobuf:"bytes,8,opt,name=dateFrom,proto3" json:"dateFrom,omitempty"`
DateTo string `protobuf:"bytes,9,opt,name=dateTo,proto3" json:"dateTo,omitempty"`
+ SessionId string `protobuf:"bytes,10,opt,name=sessionId,proto3" json:"sessionId,omitempty"`
}
func (x *AdFilterRequest) Reset() {
*x = AdFilterRequest{}
- mi := &file_ads_proto_msgTypes[10]
+ mi := &file_ads_proto_msgTypes[11]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -789,7 +947,7 @@ func (x *AdFilterRequest) String() string {
func (*AdFilterRequest) ProtoMessage() {}
func (x *AdFilterRequest) ProtoReflect() protoreflect.Message {
- mi := &file_ads_proto_msgTypes[10]
+ mi := &file_ads_proto_msgTypes[11]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -802,7 +960,7 @@ func (x *AdFilterRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use AdFilterRequest.ProtoReflect.Descriptor instead.
func (*AdFilterRequest) Descriptor() ([]byte, []int) {
- return file_ads_proto_rawDescGZIP(), []int{10}
+ return file_ads_proto_rawDescGZIP(), []int{11}
}
func (x *AdFilterRequest) GetLocation() string {
@@ -868,6 +1026,13 @@ func (x *AdFilterRequest) GetDateTo() string {
return ""
}
+func (x *AdFilterRequest) GetSessionId() string {
+ if x != nil {
+ return x.SessionId
+ }
+ return ""
+}
+
type GetAllAdsResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
@@ -880,17 +1045,28 @@ type GetAllAdsResponse struct {
PublicationDate string `protobuf:"bytes,5,opt,name=publicationDate,proto3" json:"publicationDate,omitempty"`
Description string `protobuf:"bytes,6,opt,name=description,proto3" json:"description,omitempty"`
RoomsNumber int32 `protobuf:"varint,7,opt,name=roomsNumber,proto3" json:"roomsNumber,omitempty"`
- ViewsCount *int32 `protobuf:"varint,8,opt,name=viewsCount,proto3,oneof" json:"viewsCount,omitempty"`
- CityName string `protobuf:"bytes,9,opt,name=cityName,proto3" json:"cityName,omitempty"`
- AdDateFrom string `protobuf:"bytes,10,opt,name=adDateFrom,proto3" json:"adDateFrom,omitempty"`
- AdDateTo string `protobuf:"bytes,11,opt,name=adDateTo,proto3" json:"adDateTo,omitempty"`
- AdAuthor *UserResponse `protobuf:"bytes,12,opt,name=adAuthor,proto3" json:"adAuthor,omitempty"`
- Images []*ImageResponse `protobuf:"bytes,13,rep,name=images,proto3" json:"images,omitempty"`
+ ViewsCount int32 `protobuf:"varint,8,opt,name=viewsCount,proto3" json:"viewsCount,omitempty"`
+ SquareMeters int32 `protobuf:"varint,9,opt,name=squareMeters,proto3" json:"squareMeters,omitempty"`
+ Floor int32 `protobuf:"varint,10,opt,name=floor,proto3" json:"floor,omitempty"`
+ BuildingType string `protobuf:"bytes,11,opt,name=buildingType,proto3" json:"buildingType,omitempty"`
+ HasBalcony bool `protobuf:"varint,12,opt,name=hasBalcony,proto3" json:"hasBalcony,omitempty"`
+ HasElevator bool `protobuf:"varint,13,opt,name=hasElevator,proto3" json:"hasElevator,omitempty"`
+ HasGas bool `protobuf:"varint,14,opt,name=hasGas,proto3" json:"hasGas,omitempty"`
+ LikesCount int32 `protobuf:"varint,15,opt,name=LikesCount,proto3" json:"LikesCount,omitempty"`
+ Priority int32 `protobuf:"varint,16,opt,name=priority,proto3" json:"priority,omitempty"`
+ EndBoostDate string `protobuf:"bytes,17,opt,name=endBoostDate,proto3" json:"endBoostDate,omitempty"`
+ CityName string `protobuf:"bytes,18,opt,name=cityName,proto3" json:"cityName,omitempty"`
+ AdDateFrom string `protobuf:"bytes,19,opt,name=adDateFrom,proto3" json:"adDateFrom,omitempty"`
+ AdDateTo string `protobuf:"bytes,20,opt,name=adDateTo,proto3" json:"adDateTo,omitempty"`
+ IsFavorite bool `protobuf:"varint,21,opt,name=isFavorite,proto3" json:"isFavorite,omitempty"`
+ AdAuthor *UserResponse `protobuf:"bytes,22,opt,name=adAuthor,proto3" json:"adAuthor,omitempty"`
+ Images []*ImageResponse `protobuf:"bytes,23,rep,name=images,proto3" json:"images,omitempty"`
+ Rooms []*AdRooms `protobuf:"bytes,24,rep,name=rooms,proto3" json:"rooms,omitempty"`
}
func (x *GetAllAdsResponse) Reset() {
*x = GetAllAdsResponse{}
- mi := &file_ads_proto_msgTypes[11]
+ mi := &file_ads_proto_msgTypes[12]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -902,7 +1078,7 @@ func (x *GetAllAdsResponse) String() string {
func (*GetAllAdsResponse) ProtoMessage() {}
func (x *GetAllAdsResponse) ProtoReflect() protoreflect.Message {
- mi := &file_ads_proto_msgTypes[11]
+ mi := &file_ads_proto_msgTypes[12]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -915,7 +1091,7 @@ func (x *GetAllAdsResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use GetAllAdsResponse.ProtoReflect.Descriptor instead.
func (*GetAllAdsResponse) Descriptor() ([]byte, []int) {
- return file_ads_proto_rawDescGZIP(), []int{11}
+ return file_ads_proto_rawDescGZIP(), []int{12}
}
func (x *GetAllAdsResponse) GetId() string {
@@ -968,12 +1144,75 @@ func (x *GetAllAdsResponse) GetRoomsNumber() int32 {
}
func (x *GetAllAdsResponse) GetViewsCount() int32 {
- if x != nil && x.ViewsCount != nil {
- return *x.ViewsCount
+ if x != nil {
+ return x.ViewsCount
}
return 0
}
+func (x *GetAllAdsResponse) GetSquareMeters() int32 {
+ if x != nil {
+ return x.SquareMeters
+ }
+ return 0
+}
+
+func (x *GetAllAdsResponse) GetFloor() int32 {
+ if x != nil {
+ return x.Floor
+ }
+ return 0
+}
+
+func (x *GetAllAdsResponse) GetBuildingType() string {
+ if x != nil {
+ return x.BuildingType
+ }
+ return ""
+}
+
+func (x *GetAllAdsResponse) GetHasBalcony() bool {
+ if x != nil {
+ return x.HasBalcony
+ }
+ return false
+}
+
+func (x *GetAllAdsResponse) GetHasElevator() bool {
+ if x != nil {
+ return x.HasElevator
+ }
+ return false
+}
+
+func (x *GetAllAdsResponse) GetHasGas() bool {
+ if x != nil {
+ return x.HasGas
+ }
+ return false
+}
+
+func (x *GetAllAdsResponse) GetLikesCount() int32 {
+ if x != nil {
+ return x.LikesCount
+ }
+ return 0
+}
+
+func (x *GetAllAdsResponse) GetPriority() int32 {
+ if x != nil {
+ return x.Priority
+ }
+ return 0
+}
+
+func (x *GetAllAdsResponse) GetEndBoostDate() string {
+ if x != nil {
+ return x.EndBoostDate
+ }
+ return ""
+}
+
func (x *GetAllAdsResponse) GetCityName() string {
if x != nil {
return x.CityName
@@ -995,6 +1234,13 @@ func (x *GetAllAdsResponse) GetAdDateTo() string {
return ""
}
+func (x *GetAllAdsResponse) GetIsFavorite() bool {
+ if x != nil {
+ return x.IsFavorite
+ }
+ return false
+}
+
func (x *GetAllAdsResponse) GetAdAuthor() *UserResponse {
if x != nil {
return x.AdAuthor
@@ -1009,6 +1255,13 @@ func (x *GetAllAdsResponse) GetImages() []*ImageResponse {
return nil
}
+func (x *GetAllAdsResponse) GetRooms() []*AdRooms {
+ if x != nil {
+ return x.Rooms
+ }
+ return nil
+}
+
type GetAllAdsResponseList struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
@@ -1019,7 +1272,7 @@ type GetAllAdsResponseList struct {
func (x *GetAllAdsResponseList) Reset() {
*x = GetAllAdsResponseList{}
- mi := &file_ads_proto_msgTypes[12]
+ mi := &file_ads_proto_msgTypes[13]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -1031,7 +1284,7 @@ func (x *GetAllAdsResponseList) String() string {
func (*GetAllAdsResponseList) ProtoMessage() {}
func (x *GetAllAdsResponseList) ProtoReflect() protoreflect.Message {
- mi := &file_ads_proto_msgTypes[12]
+ mi := &file_ads_proto_msgTypes[13]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -1044,7 +1297,7 @@ func (x *GetAllAdsResponseList) ProtoReflect() protoreflect.Message {
// Deprecated: Use GetAllAdsResponseList.ProtoReflect.Descriptor instead.
func (*GetAllAdsResponseList) Descriptor() ([]byte, []int) {
- return file_ads_proto_rawDescGZIP(), []int{12}
+ return file_ads_proto_rawDescGZIP(), []int{13}
}
func (x *GetAllAdsResponseList) GetHousing() []*GetAllAdsResponse {
@@ -1065,7 +1318,7 @@ type GetPlaceByIdRequest struct {
func (x *GetPlaceByIdRequest) Reset() {
*x = GetPlaceByIdRequest{}
- mi := &file_ads_proto_msgTypes[13]
+ mi := &file_ads_proto_msgTypes[14]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -1077,7 +1330,7 @@ func (x *GetPlaceByIdRequest) String() string {
func (*GetPlaceByIdRequest) ProtoMessage() {}
func (x *GetPlaceByIdRequest) ProtoReflect() protoreflect.Message {
- mi := &file_ads_proto_msgTypes[13]
+ mi := &file_ads_proto_msgTypes[14]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -1090,7 +1343,7 @@ func (x *GetPlaceByIdRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use GetPlaceByIdRequest.ProtoReflect.Descriptor instead.
func (*GetPlaceByIdRequest) Descriptor() ([]byte, []int) {
- return file_ads_proto_rawDescGZIP(), []int{13}
+ return file_ads_proto_rawDescGZIP(), []int{14}
}
func (x *GetPlaceByIdRequest) GetAdId() string {
@@ -1117,7 +1370,7 @@ type AdResponse struct {
func (x *AdResponse) Reset() {
*x = AdResponse{}
- mi := &file_ads_proto_msgTypes[14]
+ mi := &file_ads_proto_msgTypes[15]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -1129,7 +1382,7 @@ func (x *AdResponse) String() string {
func (*AdResponse) ProtoMessage() {}
func (x *AdResponse) ProtoReflect() protoreflect.Message {
- mi := &file_ads_proto_msgTypes[14]
+ mi := &file_ads_proto_msgTypes[15]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -1142,7 +1395,7 @@ func (x *AdResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use AdResponse.ProtoReflect.Descriptor instead.
func (*AdResponse) Descriptor() ([]byte, []int) {
- return file_ads_proto_rawDescGZIP(), []int{14}
+ return file_ads_proto_rawDescGZIP(), []int{15}
}
func (x *AdResponse) GetResponse() string {
@@ -1162,7 +1415,7 @@ type DeleteResponse struct {
func (x *DeleteResponse) Reset() {
*x = DeleteResponse{}
- mi := &file_ads_proto_msgTypes[15]
+ mi := &file_ads_proto_msgTypes[16]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -1174,7 +1427,7 @@ func (x *DeleteResponse) String() string {
func (*DeleteResponse) ProtoMessage() {}
func (x *DeleteResponse) ProtoReflect() protoreflect.Message {
- mi := &file_ads_proto_msgTypes[15]
+ mi := &file_ads_proto_msgTypes[16]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -1187,7 +1440,7 @@ func (x *DeleteResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use DeleteResponse.ProtoReflect.Descriptor instead.
func (*DeleteResponse) Descriptor() ([]byte, []int) {
- return file_ads_proto_rawDescGZIP(), []int{15}
+ return file_ads_proto_rawDescGZIP(), []int{16}
}
func (x *DeleteResponse) GetResponse() string {
@@ -1208,7 +1461,7 @@ type ImageResponse struct {
func (x *ImageResponse) Reset() {
*x = ImageResponse{}
- mi := &file_ads_proto_msgTypes[16]
+ mi := &file_ads_proto_msgTypes[17]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -1220,7 +1473,7 @@ func (x *ImageResponse) String() string {
func (*ImageResponse) ProtoMessage() {}
func (x *ImageResponse) ProtoReflect() protoreflect.Message {
- mi := &file_ads_proto_msgTypes[16]
+ mi := &file_ads_proto_msgTypes[17]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -1233,7 +1486,7 @@ func (x *ImageResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use ImageResponse.ProtoReflect.Descriptor instead.
func (*ImageResponse) Descriptor() ([]byte, []int) {
- return file_ads_proto_rawDescGZIP(), []int{16}
+ return file_ads_proto_rawDescGZIP(), []int{17}
}
func (x *ImageResponse) GetId() int32 {
@@ -1255,17 +1508,17 @@ type UserResponse struct {
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
- Rating *float32 `protobuf:"fixed32,1,opt,name=rating,proto3,oneof" json:"rating,omitempty"`
- Avatar string `protobuf:"bytes,2,opt,name=avatar,proto3" json:"avatar,omitempty"`
- Name string `protobuf:"bytes,3,opt,name=name,proto3" json:"name,omitempty"`
- Sex string `protobuf:"bytes,4,opt,name=sex,proto3" json:"sex,omitempty"`
- BirthDate string `protobuf:"bytes,5,opt,name=birthDate,proto3" json:"birthDate,omitempty"`
- GuestCount *int32 `protobuf:"varint,6,opt,name=guestCount,proto3,oneof" json:"guestCount,omitempty"`
+ Rating float32 `protobuf:"fixed32,1,opt,name=rating,proto3" json:"rating,omitempty"`
+ Avatar string `protobuf:"bytes,2,opt,name=avatar,proto3" json:"avatar,omitempty"`
+ Name string `protobuf:"bytes,3,opt,name=name,proto3" json:"name,omitempty"`
+ Sex string `protobuf:"bytes,4,opt,name=sex,proto3" json:"sex,omitempty"`
+ BirthDate string `protobuf:"bytes,5,opt,name=birthDate,proto3" json:"birthDate,omitempty"`
+ GuestCount int32 `protobuf:"varint,6,opt,name=guestCount,proto3" json:"guestCount,omitempty"`
}
func (x *UserResponse) Reset() {
*x = UserResponse{}
- mi := &file_ads_proto_msgTypes[17]
+ mi := &file_ads_proto_msgTypes[18]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -1277,7 +1530,7 @@ func (x *UserResponse) String() string {
func (*UserResponse) ProtoMessage() {}
func (x *UserResponse) ProtoReflect() protoreflect.Message {
- mi := &file_ads_proto_msgTypes[17]
+ mi := &file_ads_proto_msgTypes[18]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -1290,12 +1543,12 @@ func (x *UserResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use UserResponse.ProtoReflect.Descriptor instead.
func (*UserResponse) Descriptor() ([]byte, []int) {
- return file_ads_proto_rawDescGZIP(), []int{17}
+ return file_ads_proto_rawDescGZIP(), []int{18}
}
func (x *UserResponse) GetRating() float32 {
- if x != nil && x.Rating != nil {
- return *x.Rating
+ if x != nil {
+ return x.Rating
}
return 0
}
@@ -1329,12 +1582,81 @@ func (x *UserResponse) GetBirthDate() string {
}
func (x *UserResponse) GetGuestCount() int32 {
- if x != nil && x.GuestCount != nil {
- return *x.GuestCount
+ if x != nil {
+ return x.GuestCount
}
return 0
}
+type UpdatePriorityRequest struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ AdId string `protobuf:"bytes,1,opt,name=adId,proto3" json:"adId,omitempty"`
+ AuthHeader string `protobuf:"bytes,2,opt,name=authHeader,proto3" json:"authHeader,omitempty"`
+ SessionID string `protobuf:"bytes,3,opt,name=sessionID,proto3" json:"sessionID,omitempty"`
+ Amount string `protobuf:"bytes,4,opt,name=Amount,proto3" json:"Amount,omitempty"`
+}
+
+func (x *UpdatePriorityRequest) Reset() {
+ *x = UpdatePriorityRequest{}
+ mi := &file_ads_proto_msgTypes[19]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *UpdatePriorityRequest) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*UpdatePriorityRequest) ProtoMessage() {}
+
+func (x *UpdatePriorityRequest) ProtoReflect() protoreflect.Message {
+ mi := &file_ads_proto_msgTypes[19]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use UpdatePriorityRequest.ProtoReflect.Descriptor instead.
+func (*UpdatePriorityRequest) Descriptor() ([]byte, []int) {
+ return file_ads_proto_rawDescGZIP(), []int{19}
+}
+
+func (x *UpdatePriorityRequest) GetAdId() string {
+ if x != nil {
+ return x.AdId
+ }
+ return ""
+}
+
+func (x *UpdatePriorityRequest) GetAuthHeader() string {
+ if x != nil {
+ return x.AuthHeader
+ }
+ return ""
+}
+
+func (x *UpdatePriorityRequest) GetSessionID() string {
+ if x != nil {
+ return x.SessionID
+ }
+ return ""
+}
+
+func (x *UpdatePriorityRequest) GetAmount() string {
+ if x != nil {
+ return x.Amount
+ }
+ return ""
+}
+
var File_ads_proto protoreflect.FileDescriptor
var file_ads_proto_rawDesc = []byte{
@@ -1356,7 +1678,7 @@ var file_ads_proto_rawDesc = []byte{
0x6f, 0x6d, 0x73, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x07, 0x20, 0x01, 0x28, 0x05, 0x52,
0x0b, 0x72, 0x6f, 0x6f, 0x6d, 0x73, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x1e, 0x0a, 0x0a,
0x76, 0x69, 0x65, 0x77, 0x73, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x05,
- 0x52, 0x0a, 0x76, 0x69, 0x65, 0x77, 0x73, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0xe9, 0x02, 0x0a,
+ 0x52, 0x0a, 0x76, 0x69, 0x65, 0x77, 0x73, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0xc5, 0x04, 0x0a,
0x0f, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x41, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x12, 0x1a, 0x0a, 0x08, 0x63, 0x69, 0x74, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01,
0x28, 0x09, 0x52, 0x08, 0x63, 0x69, 0x74, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07,
@@ -1373,199 +1695,261 @@ var file_ads_proto_rawDesc = []byte{
0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x06,
0x64, 0x61, 0x74, 0x65, 0x54, 0x6f, 0x12, 0x16, 0x0a, 0x06, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x73,
- 0x18, 0x07, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x06, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x73, 0x12, 0x1e,
- 0x0a, 0x0a, 0x61, 0x75, 0x74, 0x68, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x18, 0x08, 0x20, 0x01,
- 0x28, 0x09, 0x52, 0x0a, 0x61, 0x75, 0x74, 0x68, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x1c,
- 0x0a, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x18, 0x09, 0x20, 0x01, 0x28,
- 0x09, 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x12, 0x1a, 0x0a, 0x08,
- 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x49, 0x44, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08,
- 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x49, 0x44, 0x22, 0xe1, 0x02, 0x0a, 0x0f, 0x55, 0x70, 0x64,
- 0x61, 0x74, 0x65, 0x41, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04,
- 0x61, 0x64, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x61, 0x64, 0x49, 0x64,
- 0x12, 0x1a, 0x0a, 0x08, 0x63, 0x69, 0x74, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01,
- 0x28, 0x09, 0x52, 0x08, 0x63, 0x69, 0x74, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07,
- 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61,
- 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69,
- 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73,
- 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x20, 0x0a, 0x0b, 0x72, 0x6f, 0x6f, 0x6d,
- 0x73, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x72,
- 0x6f, 0x6f, 0x6d, 0x73, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x36, 0x0a, 0x08, 0x64, 0x61,
- 0x74, 0x65, 0x46, 0x72, 0x6f, 0x6d, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67,
- 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54,
- 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x08, 0x64, 0x61, 0x74, 0x65, 0x46, 0x72,
- 0x6f, 0x6d, 0x12, 0x32, 0x0a, 0x06, 0x64, 0x61, 0x74, 0x65, 0x54, 0x6f, 0x18, 0x07, 0x20, 0x01,
- 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74,
- 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x06,
- 0x64, 0x61, 0x74, 0x65, 0x54, 0x6f, 0x12, 0x16, 0x0a, 0x06, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x73,
- 0x18, 0x08, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x06, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x73, 0x12, 0x1e,
- 0x0a, 0x0a, 0x61, 0x75, 0x74, 0x68, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x18, 0x09, 0x20, 0x01,
- 0x28, 0x09, 0x52, 0x0a, 0x61, 0x75, 0x74, 0x68, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x1c,
- 0x0a, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x18, 0x0a, 0x20, 0x01, 0x28,
- 0x09, 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x22, 0x66, 0x0a, 0x12,
- 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x6c, 0x61, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65,
- 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x61, 0x64, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
- 0x52, 0x04, 0x61, 0x64, 0x49, 0x64, 0x12, 0x1e, 0x0a, 0x0a, 0x61, 0x75, 0x74, 0x68, 0x48, 0x65,
- 0x61, 0x64, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x61, 0x75, 0x74, 0x68,
+ 0x18, 0x07, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x06, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x73, 0x12, 0x22,
+ 0x0a, 0x0c, 0x73, 0x71, 0x75, 0x61, 0x72, 0x65, 0x4d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x18, 0x08,
+ 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x73, 0x71, 0x75, 0x61, 0x72, 0x65, 0x4d, 0x65, 0x74, 0x65,
+ 0x72, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x66, 0x6c, 0x6f, 0x6f, 0x72, 0x18, 0x09, 0x20, 0x01, 0x28,
+ 0x05, 0x52, 0x05, 0x66, 0x6c, 0x6f, 0x6f, 0x72, 0x12, 0x22, 0x0a, 0x0c, 0x62, 0x75, 0x69, 0x6c,
+ 0x64, 0x69, 0x6e, 0x67, 0x54, 0x79, 0x70, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c,
+ 0x62, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1e, 0x0a, 0x0a,
+ 0x68, 0x61, 0x73, 0x42, 0x61, 0x6c, 0x63, 0x6f, 0x6e, 0x79, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x08,
+ 0x52, 0x0a, 0x68, 0x61, 0x73, 0x42, 0x61, 0x6c, 0x63, 0x6f, 0x6e, 0x79, 0x12, 0x20, 0x0a, 0x0b,
+ 0x68, 0x61, 0x73, 0x45, 0x6c, 0x65, 0x76, 0x61, 0x74, 0x6f, 0x72, 0x18, 0x0c, 0x20, 0x01, 0x28,
+ 0x08, 0x52, 0x0b, 0x68, 0x61, 0x73, 0x45, 0x6c, 0x65, 0x76, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x16,
+ 0x0a, 0x06, 0x68, 0x61, 0x73, 0x47, 0x61, 0x73, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06,
+ 0x68, 0x61, 0x73, 0x47, 0x61, 0x73, 0x12, 0x1e, 0x0a, 0x0a, 0x61, 0x75, 0x74, 0x68, 0x48, 0x65,
+ 0x61, 0x64, 0x65, 0x72, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x61, 0x75, 0x74, 0x68,
0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f,
- 0x6e, 0x49, 0x44, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69,
- 0x6f, 0x6e, 0x49, 0x44, 0x22, 0x69, 0x0a, 0x15, 0x41, 0x64, 0x64, 0x54, 0x6f, 0x46, 0x61, 0x76,
- 0x6f, 0x72, 0x69, 0x74, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a,
- 0x04, 0x61, 0x64, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x61, 0x64, 0x49,
- 0x64, 0x12, 0x1e, 0x0a, 0x0a, 0x61, 0x75, 0x74, 0x68, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x18,
- 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x61, 0x75, 0x74, 0x68, 0x48, 0x65, 0x61, 0x64, 0x65,
- 0x72, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x18, 0x03,
- 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x22,
- 0x6e, 0x0a, 0x1a, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x46, 0x72, 0x6f, 0x6d, 0x46, 0x61, 0x76,
- 0x6f, 0x72, 0x69, 0x74, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a,
+ 0x6e, 0x49, 0x44, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69,
+ 0x6f, 0x6e, 0x49, 0x44, 0x12, 0x1a, 0x0a, 0x08, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x49, 0x44,
+ 0x18, 0x10, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x49, 0x44,
+ 0x12, 0x22, 0x0a, 0x05, 0x72, 0x6f, 0x6f, 0x6d, 0x73, 0x18, 0x11, 0x20, 0x03, 0x28, 0x0b, 0x32,
+ 0x0c, 0x2e, 0x61, 0x64, 0x73, 0x2e, 0x41, 0x64, 0x52, 0x6f, 0x6f, 0x6d, 0x73, 0x52, 0x05, 0x72,
+ 0x6f, 0x6f, 0x6d, 0x73, 0x22, 0x41, 0x0a, 0x07, 0x41, 0x64, 0x52, 0x6f, 0x6f, 0x6d, 0x73, 0x12,
+ 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74,
+ 0x79, 0x70, 0x65, 0x12, 0x22, 0x0a, 0x0c, 0x73, 0x71, 0x75, 0x61, 0x72, 0x65, 0x4d, 0x65, 0x74,
+ 0x65, 0x72, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x73, 0x71, 0x75, 0x61, 0x72,
+ 0x65, 0x4d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x22, 0xbd, 0x04, 0x0a, 0x0f, 0x55, 0x70, 0x64, 0x61,
+ 0x74, 0x65, 0x41, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x61,
+ 0x64, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x61, 0x64, 0x49, 0x64, 0x12,
+ 0x1a, 0x0a, 0x08, 0x63, 0x69, 0x74, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28,
+ 0x09, 0x52, 0x08, 0x63, 0x69, 0x74, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x61,
+ 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64,
+ 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70,
+ 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63,
+ 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x20, 0x0a, 0x0b, 0x72, 0x6f, 0x6f, 0x6d, 0x73,
+ 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x72, 0x6f,
+ 0x6f, 0x6d, 0x73, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x36, 0x0a, 0x08, 0x64, 0x61, 0x74,
+ 0x65, 0x46, 0x72, 0x6f, 0x6d, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f,
+ 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69,
+ 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x08, 0x64, 0x61, 0x74, 0x65, 0x46, 0x72, 0x6f,
+ 0x6d, 0x12, 0x32, 0x0a, 0x06, 0x64, 0x61, 0x74, 0x65, 0x54, 0x6f, 0x18, 0x07, 0x20, 0x01, 0x28,
+ 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
+ 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x06, 0x64,
+ 0x61, 0x74, 0x65, 0x54, 0x6f, 0x12, 0x22, 0x0a, 0x0c, 0x73, 0x71, 0x75, 0x61, 0x72, 0x65, 0x4d,
+ 0x65, 0x74, 0x65, 0x72, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x73, 0x71, 0x75,
+ 0x61, 0x72, 0x65, 0x4d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x66, 0x6c, 0x6f,
+ 0x6f, 0x72, 0x18, 0x09, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x66, 0x6c, 0x6f, 0x6f, 0x72, 0x12,
+ 0x22, 0x0a, 0x0c, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x54, 0x79, 0x70, 0x65, 0x18,
+ 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x54,
+ 0x79, 0x70, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x68, 0x61, 0x73, 0x42, 0x61, 0x6c, 0x63, 0x6f, 0x6e,
+ 0x79, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x68, 0x61, 0x73, 0x42, 0x61, 0x6c, 0x63,
+ 0x6f, 0x6e, 0x79, 0x12, 0x20, 0x0a, 0x0b, 0x68, 0x61, 0x73, 0x45, 0x6c, 0x65, 0x76, 0x61, 0x74,
+ 0x6f, 0x72, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x68, 0x61, 0x73, 0x45, 0x6c, 0x65,
+ 0x76, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x16, 0x0a, 0x06, 0x68, 0x61, 0x73, 0x47, 0x61, 0x73, 0x18,
+ 0x0d, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x68, 0x61, 0x73, 0x47, 0x61, 0x73, 0x12, 0x16, 0x0a,
+ 0x06, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x73, 0x18, 0x0e, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x06, 0x69,
+ 0x6d, 0x61, 0x67, 0x65, 0x73, 0x12, 0x1e, 0x0a, 0x0a, 0x61, 0x75, 0x74, 0x68, 0x48, 0x65, 0x61,
+ 0x64, 0x65, 0x72, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x61, 0x75, 0x74, 0x68, 0x48,
+ 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e,
+ 0x49, 0x44, 0x18, 0x10, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f,
+ 0x6e, 0x49, 0x44, 0x12, 0x22, 0x0a, 0x05, 0x72, 0x6f, 0x6f, 0x6d, 0x73, 0x18, 0x11, 0x20, 0x03,
+ 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x61, 0x64, 0x73, 0x2e, 0x41, 0x64, 0x52, 0x6f, 0x6f, 0x6d, 0x73,
+ 0x52, 0x05, 0x72, 0x6f, 0x6f, 0x6d, 0x73, 0x22, 0x66, 0x0a, 0x12, 0x44, 0x65, 0x6c, 0x65, 0x74,
+ 0x65, 0x50, 0x6c, 0x61, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a,
0x04, 0x61, 0x64, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x61, 0x64, 0x49,
0x64, 0x12, 0x1e, 0x0a, 0x0a, 0x61, 0x75, 0x74, 0x68, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x18,
0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x61, 0x75, 0x74, 0x68, 0x48, 0x65, 0x61, 0x64, 0x65,
0x72, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x18, 0x03,
0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x22,
- 0x6f, 0x0a, 0x17, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x46, 0x61, 0x76, 0x6f, 0x72, 0x69,
- 0x74, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x75, 0x73,
- 0x65, 0x72, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72,
+ 0x69, 0x0a, 0x15, 0x41, 0x64, 0x64, 0x54, 0x6f, 0x46, 0x61, 0x76, 0x6f, 0x72, 0x69, 0x74, 0x65,
+ 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x61, 0x64, 0x49, 0x64,
+ 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x61, 0x64, 0x49, 0x64, 0x12, 0x1e, 0x0a, 0x0a,
+ 0x61, 0x75, 0x74, 0x68, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09,
+ 0x52, 0x0a, 0x61, 0x75, 0x74, 0x68, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x1c, 0x0a, 0x09,
+ 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52,
+ 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x22, 0x6e, 0x0a, 0x1a, 0x44, 0x65,
+ 0x6c, 0x65, 0x74, 0x65, 0x46, 0x72, 0x6f, 0x6d, 0x46, 0x61, 0x76, 0x6f, 0x72, 0x69, 0x74, 0x65,
+ 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x61, 0x64, 0x49, 0x64,
+ 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x61, 0x64, 0x49, 0x64, 0x12, 0x1e, 0x0a, 0x0a,
+ 0x61, 0x75, 0x74, 0x68, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09,
+ 0x52, 0x0a, 0x61, 0x75, 0x74, 0x68, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x1c, 0x0a, 0x09,
+ 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52,
+ 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x22, 0x4f, 0x0a, 0x17, 0x47, 0x65,
+ 0x74, 0x55, 0x73, 0x65, 0x72, 0x46, 0x61, 0x76, 0x6f, 0x72, 0x69, 0x74, 0x65, 0x73, 0x52, 0x65,
+ 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x18,
+ 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1c, 0x0a,
+ 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09,
+ 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x22, 0x82, 0x01, 0x0a, 0x14,
+ 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x64, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71,
+ 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x61, 0x64, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01,
+ 0x28, 0x09, 0x52, 0x04, 0x61, 0x64, 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x69, 0x6d, 0x61, 0x67,
+ 0x65, 0x49, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x69, 0x6d, 0x61, 0x67, 0x65,
0x49, 0x64, 0x12, 0x1e, 0x0a, 0x0a, 0x61, 0x75, 0x74, 0x68, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72,
- 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x61, 0x75, 0x74, 0x68, 0x48, 0x65, 0x61, 0x64,
+ 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x61, 0x75, 0x74, 0x68, 0x48, 0x65, 0x61, 0x64,
0x65, 0x72, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x18,
- 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x44,
- 0x22, 0x82, 0x01, 0x0a, 0x14, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x64, 0x49, 0x6d, 0x61,
- 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x61, 0x64, 0x49,
- 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x61, 0x64, 0x49, 0x64, 0x12, 0x18, 0x0a,
- 0x07, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x49, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07,
- 0x69, 0x6d, 0x61, 0x67, 0x65, 0x49, 0x64, 0x12, 0x1e, 0x0a, 0x0a, 0x61, 0x75, 0x74, 0x68, 0x48,
- 0x65, 0x61, 0x64, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x61, 0x75, 0x74,
- 0x68, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69,
- 0x6f, 0x6e, 0x49, 0x44, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x65, 0x73, 0x73,
- 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x22, 0x35, 0x0a, 0x17, 0x47, 0x65, 0x74, 0x50, 0x6c, 0x61, 0x63,
- 0x65, 0x73, 0x50, 0x65, 0x72, 0x43, 0x69, 0x74, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
- 0x12, 0x1a, 0x0a, 0x08, 0x63, 0x69, 0x74, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01,
- 0x28, 0x09, 0x52, 0x08, 0x63, 0x69, 0x74, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x2e, 0x0a, 0x14,
- 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x50, 0x6c, 0x61, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71,
- 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x18, 0x01,
- 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x89, 0x02, 0x0a,
- 0x0f, 0x41, 0x64, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
- 0x12, 0x1a, 0x0a, 0x08, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01,
- 0x28, 0x09, 0x52, 0x08, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06,
- 0x72, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x61,
- 0x74, 0x69, 0x6e, 0x67, 0x12, 0x20, 0x0a, 0x0b, 0x6e, 0x65, 0x77, 0x54, 0x68, 0x69, 0x73, 0x57,
- 0x65, 0x65, 0x6b, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x6e, 0x65, 0x77, 0x54, 0x68,
- 0x69, 0x73, 0x57, 0x65, 0x65, 0x6b, 0x12, 0x1e, 0x0a, 0x0a, 0x68, 0x6f, 0x73, 0x74, 0x47, 0x65,
- 0x6e, 0x64, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x68, 0x6f, 0x73, 0x74,
- 0x47, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x12, 0x1e, 0x0a, 0x0a, 0x67, 0x75, 0x65, 0x73, 0x74, 0x43,
- 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x67, 0x75, 0x65, 0x73,
- 0x74, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18,
- 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x16, 0x0a, 0x06,
- 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6f, 0x66,
- 0x66, 0x73, 0x65, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x61, 0x74, 0x65, 0x46, 0x72, 0x6f, 0x6d,
- 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x64, 0x61, 0x74, 0x65, 0x46, 0x72, 0x6f, 0x6d,
- 0x12, 0x16, 0x0a, 0x06, 0x64, 0x61, 0x74, 0x65, 0x54, 0x6f, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09,
- 0x52, 0x06, 0x64, 0x61, 0x74, 0x65, 0x54, 0x6f, 0x22, 0xca, 0x03, 0x0a, 0x11, 0x47, 0x65, 0x74,
- 0x41, 0x6c, 0x6c, 0x41, 0x64, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x0e,
- 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x16,
- 0x0a, 0x06, 0x63, 0x69, 0x74, 0x79, 0x49, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06,
- 0x63, 0x69, 0x74, 0x79, 0x49, 0x64, 0x12, 0x1e, 0x0a, 0x0a, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72,
- 0x55, 0x55, 0x49, 0x44, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x61, 0x75, 0x74, 0x68,
- 0x6f, 0x72, 0x55, 0x55, 0x49, 0x44, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73,
- 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73,
- 0x12, 0x28, 0x0a, 0x0f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x44,
- 0x61, 0x74, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x70, 0x75, 0x62, 0x6c, 0x69,
- 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x61, 0x74, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65,
- 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52,
- 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x20, 0x0a, 0x0b,
- 0x72, 0x6f, 0x6f, 0x6d, 0x73, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x07, 0x20, 0x01, 0x28,
- 0x05, 0x52, 0x0b, 0x72, 0x6f, 0x6f, 0x6d, 0x73, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x23,
- 0x0a, 0x0a, 0x76, 0x69, 0x65, 0x77, 0x73, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x08, 0x20, 0x01,
- 0x28, 0x05, 0x48, 0x00, 0x52, 0x0a, 0x76, 0x69, 0x65, 0x77, 0x73, 0x43, 0x6f, 0x75, 0x6e, 0x74,
- 0x88, 0x01, 0x01, 0x12, 0x1a, 0x0a, 0x08, 0x63, 0x69, 0x74, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x18,
- 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x69, 0x74, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x12,
- 0x1e, 0x0a, 0x0a, 0x61, 0x64, 0x44, 0x61, 0x74, 0x65, 0x46, 0x72, 0x6f, 0x6d, 0x18, 0x0a, 0x20,
- 0x01, 0x28, 0x09, 0x52, 0x0a, 0x61, 0x64, 0x44, 0x61, 0x74, 0x65, 0x46, 0x72, 0x6f, 0x6d, 0x12,
- 0x1a, 0x0a, 0x08, 0x61, 0x64, 0x44, 0x61, 0x74, 0x65, 0x54, 0x6f, 0x18, 0x0b, 0x20, 0x01, 0x28,
- 0x09, 0x52, 0x08, 0x61, 0x64, 0x44, 0x61, 0x74, 0x65, 0x54, 0x6f, 0x12, 0x2d, 0x0a, 0x08, 0x61,
- 0x64, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e,
- 0x61, 0x64, 0x73, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
- 0x52, 0x08, 0x61, 0x64, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x12, 0x2a, 0x0a, 0x06, 0x69, 0x6d,
- 0x61, 0x67, 0x65, 0x73, 0x18, 0x0d, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x61, 0x64, 0x73,
- 0x2e, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x06,
- 0x69, 0x6d, 0x61, 0x67, 0x65, 0x73, 0x42, 0x0d, 0x0a, 0x0b, 0x5f, 0x76, 0x69, 0x65, 0x77, 0x73,
- 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x49, 0x0a, 0x15, 0x47, 0x65, 0x74, 0x41, 0x6c, 0x6c, 0x41,
- 0x64, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x30,
- 0x0a, 0x07, 0x68, 0x6f, 0x75, 0x73, 0x69, 0x6e, 0x67, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32,
- 0x16, 0x2e, 0x61, 0x64, 0x73, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x6c, 0x6c, 0x41, 0x64, 0x73, 0x52,
- 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x07, 0x68, 0x6f, 0x75, 0x73, 0x69, 0x6e, 0x67,
- 0x22, 0x4d, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x50, 0x6c, 0x61, 0x63, 0x65, 0x42, 0x79, 0x49, 0x64,
- 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x61, 0x64, 0x49, 0x64, 0x18,
- 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x61, 0x64, 0x49, 0x64, 0x12, 0x22, 0x0a, 0x0c, 0x69,
- 0x73, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28,
- 0x08, 0x52, 0x0c, 0x69, 0x73, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x22,
- 0x28, 0x0a, 0x0a, 0x41, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1a, 0x0a,
- 0x08, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
- 0x08, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x2c, 0x0a, 0x0e, 0x44, 0x65, 0x6c,
- 0x65, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x72,
- 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72,
- 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x33, 0x0a, 0x0d, 0x49, 0x6d, 0x61, 0x67, 0x65,
- 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01,
- 0x20, 0x01, 0x28, 0x05, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68,
- 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x22, 0xc6, 0x01, 0x0a,
- 0x0c, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1b, 0x0a,
- 0x06, 0x72, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x02, 0x48, 0x00, 0x52,
- 0x06, 0x72, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x88, 0x01, 0x01, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x76,
- 0x61, 0x74, 0x61, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x61, 0x76, 0x61, 0x74,
- 0x61, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09,
- 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x73, 0x65, 0x78, 0x18, 0x04, 0x20,
- 0x01, 0x28, 0x09, 0x52, 0x03, 0x73, 0x65, 0x78, 0x12, 0x1c, 0x0a, 0x09, 0x62, 0x69, 0x72, 0x74,
- 0x68, 0x44, 0x61, 0x74, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x62, 0x69, 0x72,
- 0x74, 0x68, 0x44, 0x61, 0x74, 0x65, 0x12, 0x23, 0x0a, 0x0a, 0x67, 0x75, 0x65, 0x73, 0x74, 0x43,
- 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x48, 0x01, 0x52, 0x0a, 0x67, 0x75,
- 0x65, 0x73, 0x74, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x88, 0x01, 0x01, 0x42, 0x09, 0x0a, 0x07, 0x5f,
- 0x72, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x42, 0x0d, 0x0a, 0x0b, 0x5f, 0x67, 0x75, 0x65, 0x73, 0x74,
- 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x32, 0xd6, 0x05, 0x0a, 0x03, 0x41, 0x64, 0x73, 0x12, 0x40, 0x0a,
- 0x0c, 0x47, 0x65, 0x74, 0x41, 0x6c, 0x6c, 0x50, 0x6c, 0x61, 0x63, 0x65, 0x73, 0x12, 0x14, 0x2e,
- 0x61, 0x64, 0x73, 0x2e, 0x41, 0x64, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75,
- 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x61, 0x64, 0x73, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x6c, 0x6c,
+ 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x44,
+ 0x22, 0x35, 0x0a, 0x17, 0x47, 0x65, 0x74, 0x50, 0x6c, 0x61, 0x63, 0x65, 0x73, 0x50, 0x65, 0x72,
+ 0x43, 0x69, 0x74, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x63,
+ 0x69, 0x74, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63,
+ 0x69, 0x74, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x2e, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x55, 0x73,
+ 0x65, 0x72, 0x50, 0x6c, 0x61, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12,
+ 0x16, 0x0a, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
+ 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0xa7, 0x02, 0x0a, 0x0f, 0x41, 0x64, 0x46, 0x69,
+ 0x6c, 0x74, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x6c,
+ 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6c,
+ 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x61, 0x74, 0x69, 0x6e,
+ 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x12,
+ 0x20, 0x0a, 0x0b, 0x6e, 0x65, 0x77, 0x54, 0x68, 0x69, 0x73, 0x57, 0x65, 0x65, 0x6b, 0x18, 0x03,
+ 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x6e, 0x65, 0x77, 0x54, 0x68, 0x69, 0x73, 0x57, 0x65, 0x65,
+ 0x6b, 0x12, 0x1e, 0x0a, 0x0a, 0x68, 0x6f, 0x73, 0x74, 0x47, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x18,
+ 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x68, 0x6f, 0x73, 0x74, 0x47, 0x65, 0x6e, 0x64, 0x65,
+ 0x72, 0x12, 0x1e, 0x0a, 0x0a, 0x67, 0x75, 0x65, 0x73, 0x74, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x18,
+ 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x67, 0x75, 0x65, 0x73, 0x74, 0x43, 0x6f, 0x75, 0x6e,
+ 0x74, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09,
+ 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65,
+ 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x12,
+ 0x1a, 0x0a, 0x08, 0x64, 0x61, 0x74, 0x65, 0x46, 0x72, 0x6f, 0x6d, 0x18, 0x08, 0x20, 0x01, 0x28,
+ 0x09, 0x52, 0x08, 0x64, 0x61, 0x74, 0x65, 0x46, 0x72, 0x6f, 0x6d, 0x12, 0x16, 0x0a, 0x06, 0x64,
+ 0x61, 0x74, 0x65, 0x54, 0x6f, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x61, 0x74,
+ 0x65, 0x54, 0x6f, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x64,
+ 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49,
+ 0x64, 0x22, 0x92, 0x06, 0x0a, 0x11, 0x47, 0x65, 0x74, 0x41, 0x6c, 0x6c, 0x41, 0x64, 0x73, 0x52,
+ 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20,
+ 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x63, 0x69, 0x74, 0x79, 0x49,
+ 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x63, 0x69, 0x74, 0x79, 0x49, 0x64, 0x12,
+ 0x1e, 0x0a, 0x0a, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x55, 0x55, 0x49, 0x44, 0x18, 0x03, 0x20,
+ 0x01, 0x28, 0x09, 0x52, 0x0a, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x55, 0x55, 0x49, 0x44, 0x12,
+ 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09,
+ 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x28, 0x0a, 0x0f, 0x70, 0x75, 0x62,
+ 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x61, 0x74, 0x65, 0x18, 0x05, 0x20, 0x01,
+ 0x28, 0x09, 0x52, 0x0f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x44,
+ 0x61, 0x74, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69,
+ 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69,
+ 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x20, 0x0a, 0x0b, 0x72, 0x6f, 0x6f, 0x6d, 0x73, 0x4e, 0x75,
+ 0x6d, 0x62, 0x65, 0x72, 0x18, 0x07, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x72, 0x6f, 0x6f, 0x6d,
+ 0x73, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x1e, 0x0a, 0x0a, 0x76, 0x69, 0x65, 0x77, 0x73,
+ 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x76, 0x69, 0x65,
+ 0x77, 0x73, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x22, 0x0a, 0x0c, 0x73, 0x71, 0x75, 0x61, 0x72,
+ 0x65, 0x4d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x73,
+ 0x71, 0x75, 0x61, 0x72, 0x65, 0x4d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x66,
+ 0x6c, 0x6f, 0x6f, 0x72, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x66, 0x6c, 0x6f, 0x6f,
+ 0x72, 0x12, 0x22, 0x0a, 0x0c, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x54, 0x79, 0x70,
+ 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e,
+ 0x67, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x68, 0x61, 0x73, 0x42, 0x61, 0x6c, 0x63,
+ 0x6f, 0x6e, 0x79, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x68, 0x61, 0x73, 0x42, 0x61,
+ 0x6c, 0x63, 0x6f, 0x6e, 0x79, 0x12, 0x20, 0x0a, 0x0b, 0x68, 0x61, 0x73, 0x45, 0x6c, 0x65, 0x76,
+ 0x61, 0x74, 0x6f, 0x72, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x68, 0x61, 0x73, 0x45,
+ 0x6c, 0x65, 0x76, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x16, 0x0a, 0x06, 0x68, 0x61, 0x73, 0x47, 0x61,
+ 0x73, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x68, 0x61, 0x73, 0x47, 0x61, 0x73, 0x12,
+ 0x1e, 0x0a, 0x0a, 0x4c, 0x69, 0x6b, 0x65, 0x73, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0f, 0x20,
+ 0x01, 0x28, 0x05, 0x52, 0x0a, 0x4c, 0x69, 0x6b, 0x65, 0x73, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12,
+ 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x18, 0x10, 0x20, 0x01, 0x28,
+ 0x05, 0x52, 0x08, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x12, 0x22, 0x0a, 0x0c, 0x65,
+ 0x6e, 0x64, 0x42, 0x6f, 0x6f, 0x73, 0x74, 0x44, 0x61, 0x74, 0x65, 0x18, 0x11, 0x20, 0x01, 0x28,
+ 0x09, 0x52, 0x0c, 0x65, 0x6e, 0x64, 0x42, 0x6f, 0x6f, 0x73, 0x74, 0x44, 0x61, 0x74, 0x65, 0x12,
+ 0x1a, 0x0a, 0x08, 0x63, 0x69, 0x74, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x12, 0x20, 0x01, 0x28,
+ 0x09, 0x52, 0x08, 0x63, 0x69, 0x74, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x61,
+ 0x64, 0x44, 0x61, 0x74, 0x65, 0x46, 0x72, 0x6f, 0x6d, 0x18, 0x13, 0x20, 0x01, 0x28, 0x09, 0x52,
+ 0x0a, 0x61, 0x64, 0x44, 0x61, 0x74, 0x65, 0x46, 0x72, 0x6f, 0x6d, 0x12, 0x1a, 0x0a, 0x08, 0x61,
+ 0x64, 0x44, 0x61, 0x74, 0x65, 0x54, 0x6f, 0x18, 0x14, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x61,
+ 0x64, 0x44, 0x61, 0x74, 0x65, 0x54, 0x6f, 0x12, 0x1e, 0x0a, 0x0a, 0x69, 0x73, 0x46, 0x61, 0x76,
+ 0x6f, 0x72, 0x69, 0x74, 0x65, 0x18, 0x15, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x69, 0x73, 0x46,
+ 0x61, 0x76, 0x6f, 0x72, 0x69, 0x74, 0x65, 0x12, 0x2d, 0x0a, 0x08, 0x61, 0x64, 0x41, 0x75, 0x74,
+ 0x68, 0x6f, 0x72, 0x18, 0x16, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x61, 0x64, 0x73, 0x2e,
+ 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x08, 0x61, 0x64,
+ 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x12, 0x2a, 0x0a, 0x06, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x73,
+ 0x18, 0x17, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x61, 0x64, 0x73, 0x2e, 0x49, 0x6d, 0x61,
+ 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x06, 0x69, 0x6d, 0x61, 0x67,
+ 0x65, 0x73, 0x12, 0x22, 0x0a, 0x05, 0x72, 0x6f, 0x6f, 0x6d, 0x73, 0x18, 0x18, 0x20, 0x03, 0x28,
+ 0x0b, 0x32, 0x0c, 0x2e, 0x61, 0x64, 0x73, 0x2e, 0x41, 0x64, 0x52, 0x6f, 0x6f, 0x6d, 0x73, 0x52,
+ 0x05, 0x72, 0x6f, 0x6f, 0x6d, 0x73, 0x22, 0x49, 0x0a, 0x15, 0x47, 0x65, 0x74, 0x41, 0x6c, 0x6c,
0x41, 0x64, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x12,
- 0x3f, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x4f, 0x6e, 0x65, 0x50, 0x6c, 0x61, 0x63, 0x65, 0x12, 0x18,
- 0x2e, 0x61, 0x64, 0x73, 0x2e, 0x47, 0x65, 0x74, 0x50, 0x6c, 0x61, 0x63, 0x65, 0x42, 0x79, 0x49,
- 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x61, 0x64, 0x73, 0x2e, 0x47,
- 0x65, 0x74, 0x41, 0x6c, 0x6c, 0x41, 0x64, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
- 0x12, 0x2c, 0x0a, 0x0b, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x6c, 0x61, 0x63, 0x65, 0x12,
- 0x14, 0x2e, 0x61, 0x64, 0x73, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x41, 0x64, 0x52, 0x65,
- 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x07, 0x2e, 0x61, 0x64, 0x73, 0x2e, 0x41, 0x64, 0x12, 0x34,
- 0x0a, 0x0b, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, 0x6c, 0x61, 0x63, 0x65, 0x12, 0x14, 0x2e,
- 0x61, 0x64, 0x73, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, 0x64, 0x52, 0x65, 0x71, 0x75,
- 0x65, 0x73, 0x74, 0x1a, 0x0f, 0x2e, 0x61, 0x64, 0x73, 0x2e, 0x41, 0x64, 0x52, 0x65, 0x73, 0x70,
- 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3b, 0x0a, 0x0b, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x6c,
- 0x61, 0x63, 0x65, 0x12, 0x17, 0x2e, 0x61, 0x64, 0x73, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65,
- 0x50, 0x6c, 0x61, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x61,
- 0x64, 0x73, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
- 0x65, 0x12, 0x4c, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x50, 0x6c, 0x61, 0x63, 0x65, 0x73, 0x50, 0x65,
- 0x72, 0x43, 0x69, 0x74, 0x79, 0x12, 0x1c, 0x2e, 0x61, 0x64, 0x73, 0x2e, 0x47, 0x65, 0x74, 0x50,
- 0x6c, 0x61, 0x63, 0x65, 0x73, 0x50, 0x65, 0x72, 0x43, 0x69, 0x74, 0x79, 0x52, 0x65, 0x71, 0x75,
+ 0x30, 0x0a, 0x07, 0x68, 0x6f, 0x75, 0x73, 0x69, 0x6e, 0x67, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b,
+ 0x32, 0x16, 0x2e, 0x61, 0x64, 0x73, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x6c, 0x6c, 0x41, 0x64, 0x73,
+ 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x07, 0x68, 0x6f, 0x75, 0x73, 0x69, 0x6e,
+ 0x67, 0x22, 0x4d, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x50, 0x6c, 0x61, 0x63, 0x65, 0x42, 0x79, 0x49,
+ 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x61, 0x64, 0x49, 0x64,
+ 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x61, 0x64, 0x49, 0x64, 0x12, 0x22, 0x0a, 0x0c,
+ 0x69, 0x73, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01,
+ 0x28, 0x08, 0x52, 0x0c, 0x69, 0x73, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64,
+ 0x22, 0x28, 0x0a, 0x0a, 0x41, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1a,
+ 0x0a, 0x08, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
+ 0x52, 0x08, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x2c, 0x0a, 0x0e, 0x44, 0x65,
+ 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1a, 0x0a, 0x08,
+ 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08,
+ 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x33, 0x0a, 0x0d, 0x49, 0x6d, 0x61, 0x67,
+ 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18,
+ 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74,
+ 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x22, 0xa2, 0x01,
+ 0x0a, 0x0c, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16,
+ 0x0a, 0x06, 0x72, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x02, 0x52, 0x06,
+ 0x72, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x76, 0x61, 0x74, 0x61, 0x72,
+ 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x61, 0x76, 0x61, 0x74, 0x61, 0x72, 0x12, 0x12,
+ 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61,
+ 0x6d, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x73, 0x65, 0x78, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52,
+ 0x03, 0x73, 0x65, 0x78, 0x12, 0x1c, 0x0a, 0x09, 0x62, 0x69, 0x72, 0x74, 0x68, 0x44, 0x61, 0x74,
+ 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x62, 0x69, 0x72, 0x74, 0x68, 0x44, 0x61,
+ 0x74, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x67, 0x75, 0x65, 0x73, 0x74, 0x43, 0x6f, 0x75, 0x6e, 0x74,
+ 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x67, 0x75, 0x65, 0x73, 0x74, 0x43, 0x6f, 0x75,
+ 0x6e, 0x74, 0x22, 0x81, 0x01, 0x0a, 0x15, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, 0x72, 0x69,
+ 0x6f, 0x72, 0x69, 0x74, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04,
+ 0x61, 0x64, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x61, 0x64, 0x49, 0x64,
+ 0x12, 0x1e, 0x0a, 0x0a, 0x61, 0x75, 0x74, 0x68, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x18, 0x02,
+ 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x61, 0x75, 0x74, 0x68, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72,
+ 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x18, 0x03, 0x20,
+ 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x12, 0x16,
+ 0x0a, 0x06, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06,
+ 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x32, 0x95, 0x06, 0x0a, 0x03, 0x41, 0x64, 0x73, 0x12, 0x40,
+ 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x41, 0x6c, 0x6c, 0x50, 0x6c, 0x61, 0x63, 0x65, 0x73, 0x12, 0x14,
+ 0x2e, 0x61, 0x64, 0x73, 0x2e, 0x41, 0x64, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x52, 0x65, 0x71,
+ 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x61, 0x64, 0x73, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x6c,
+ 0x6c, 0x41, 0x64, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x4c, 0x69, 0x73, 0x74,
+ 0x12, 0x3f, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x4f, 0x6e, 0x65, 0x50, 0x6c, 0x61, 0x63, 0x65, 0x12,
+ 0x18, 0x2e, 0x61, 0x64, 0x73, 0x2e, 0x47, 0x65, 0x74, 0x50, 0x6c, 0x61, 0x63, 0x65, 0x42, 0x79,
+ 0x49, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x61, 0x64, 0x73, 0x2e,
+ 0x47, 0x65, 0x74, 0x41, 0x6c, 0x6c, 0x41, 0x64, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
+ 0x65, 0x12, 0x2c, 0x0a, 0x0b, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x6c, 0x61, 0x63, 0x65,
+ 0x12, 0x14, 0x2e, 0x61, 0x64, 0x73, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x41, 0x64, 0x52,
+ 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x07, 0x2e, 0x61, 0x64, 0x73, 0x2e, 0x41, 0x64, 0x12,
+ 0x34, 0x0a, 0x0b, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, 0x6c, 0x61, 0x63, 0x65, 0x12, 0x14,
+ 0x2e, 0x61, 0x64, 0x73, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, 0x64, 0x52, 0x65, 0x71,
+ 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0f, 0x2e, 0x61, 0x64, 0x73, 0x2e, 0x41, 0x64, 0x52, 0x65, 0x73,
+ 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3b, 0x0a, 0x0b, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50,
+ 0x6c, 0x61, 0x63, 0x65, 0x12, 0x17, 0x2e, 0x61, 0x64, 0x73, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74,
+ 0x65, 0x50, 0x6c, 0x61, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e,
+ 0x61, 0x64, 0x73, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
+ 0x73, 0x65, 0x12, 0x4c, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x50, 0x6c, 0x61, 0x63, 0x65, 0x73, 0x50,
+ 0x65, 0x72, 0x43, 0x69, 0x74, 0x79, 0x12, 0x1c, 0x2e, 0x61, 0x64, 0x73, 0x2e, 0x47, 0x65, 0x74,
+ 0x50, 0x6c, 0x61, 0x63, 0x65, 0x73, 0x50, 0x65, 0x72, 0x43, 0x69, 0x74, 0x79, 0x52, 0x65, 0x71,
+ 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x61, 0x64, 0x73, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x6c,
+ 0x6c, 0x41, 0x64, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x4c, 0x69, 0x73, 0x74,
+ 0x12, 0x46, 0x0a, 0x0d, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x50, 0x6c, 0x61, 0x63, 0x65,
+ 0x73, 0x12, 0x19, 0x2e, 0x61, 0x64, 0x73, 0x2e, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x50,
+ 0x6c, 0x61, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x61,
+ 0x64, 0x73, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x6c, 0x6c, 0x41, 0x64, 0x73, 0x52, 0x65, 0x73, 0x70,
+ 0x6f, 0x6e, 0x73, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x3f, 0x0a, 0x0d, 0x44, 0x65, 0x6c, 0x65,
+ 0x74, 0x65, 0x41, 0x64, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x12, 0x19, 0x2e, 0x61, 0x64, 0x73, 0x2e,
+ 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x64, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71,
+ 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x61, 0x64, 0x73, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74,
+ 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3d, 0x0a, 0x0e, 0x41, 0x64, 0x64,
+ 0x54, 0x6f, 0x46, 0x61, 0x76, 0x6f, 0x72, 0x69, 0x74, 0x65, 0x73, 0x12, 0x1a, 0x2e, 0x61, 0x64,
+ 0x73, 0x2e, 0x41, 0x64, 0x64, 0x54, 0x6f, 0x46, 0x61, 0x76, 0x6f, 0x72, 0x69, 0x74, 0x65, 0x73,
+ 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0f, 0x2e, 0x61, 0x64, 0x73, 0x2e, 0x41, 0x64,
+ 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x47, 0x0a, 0x13, 0x44, 0x65, 0x6c, 0x65,
+ 0x74, 0x65, 0x46, 0x72, 0x6f, 0x6d, 0x46, 0x61, 0x76, 0x6f, 0x72, 0x69, 0x74, 0x65, 0x73, 0x12,
+ 0x1f, 0x2e, 0x61, 0x64, 0x73, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x46, 0x72, 0x6f, 0x6d,
+ 0x46, 0x61, 0x76, 0x6f, 0x72, 0x69, 0x74, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
+ 0x1a, 0x0f, 0x2e, 0x61, 0x64, 0x73, 0x2e, 0x41, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
+ 0x65, 0x12, 0x4c, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x46, 0x61, 0x76, 0x6f,
+ 0x72, 0x69, 0x74, 0x65, 0x73, 0x12, 0x1c, 0x2e, 0x61, 0x64, 0x73, 0x2e, 0x47, 0x65, 0x74, 0x55,
+ 0x73, 0x65, 0x72, 0x46, 0x61, 0x76, 0x6f, 0x72, 0x69, 0x74, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75,
0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x61, 0x64, 0x73, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x6c, 0x6c,
0x41, 0x64, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x12,
- 0x46, 0x0a, 0x0d, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x50, 0x6c, 0x61, 0x63, 0x65, 0x73,
- 0x12, 0x19, 0x2e, 0x61, 0x64, 0x73, 0x2e, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x50, 0x6c,
- 0x61, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x61, 0x64,
- 0x73, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x6c, 0x6c, 0x41, 0x64, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f,
- 0x6e, 0x73, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x3f, 0x0a, 0x0d, 0x44, 0x65, 0x6c, 0x65, 0x74,
- 0x65, 0x41, 0x64, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x12, 0x19, 0x2e, 0x61, 0x64, 0x73, 0x2e, 0x44,
- 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x64, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75,
- 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x61, 0x64, 0x73, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65,
- 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3d, 0x0a, 0x0e, 0x41, 0x64, 0x64, 0x54,
- 0x6f, 0x46, 0x61, 0x76, 0x6f, 0x72, 0x69, 0x74, 0x65, 0x73, 0x12, 0x1a, 0x2e, 0x61, 0x64, 0x73,
- 0x2e, 0x41, 0x64, 0x64, 0x54, 0x6f, 0x46, 0x61, 0x76, 0x6f, 0x72, 0x69, 0x74, 0x65, 0x73, 0x52,
- 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0f, 0x2e, 0x61, 0x64, 0x73, 0x2e, 0x41, 0x64, 0x52,
- 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x47, 0x0a, 0x13, 0x44, 0x65, 0x6c, 0x65, 0x74,
- 0x65, 0x46, 0x72, 0x6f, 0x6d, 0x46, 0x61, 0x76, 0x6f, 0x72, 0x69, 0x74, 0x65, 0x73, 0x12, 0x1f,
- 0x2e, 0x61, 0x64, 0x73, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x46, 0x72, 0x6f, 0x6d, 0x46,
- 0x61, 0x76, 0x6f, 0x72, 0x69, 0x74, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
- 0x0f, 0x2e, 0x61, 0x64, 0x73, 0x2e, 0x41, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
- 0x12, 0x4c, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x46, 0x61, 0x76, 0x6f, 0x72,
- 0x69, 0x74, 0x65, 0x73, 0x12, 0x1c, 0x2e, 0x61, 0x64, 0x73, 0x2e, 0x47, 0x65, 0x74, 0x55, 0x73,
- 0x65, 0x72, 0x46, 0x61, 0x76, 0x6f, 0x72, 0x69, 0x74, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65,
- 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x61, 0x64, 0x73, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x6c, 0x6c, 0x41,
- 0x64, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x42, 0x32,
+ 0x3d, 0x0a, 0x0e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74,
+ 0x79, 0x12, 0x1a, 0x2e, 0x61, 0x64, 0x73, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, 0x72,
+ 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0f, 0x2e,
+ 0x61, 0x64, 0x73, 0x2e, 0x41, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x32,
0x5a, 0x30, 0x2e, 0x2e, 0x2f, 0x6d, 0x69, 0x63, 0x72, 0x6f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63,
0x65, 0x73, 0x2f, 0x61, 0x64, 0x73, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2f, 0x63,
0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x3b, 0x67,
@@ -1584,63 +1968,70 @@ func file_ads_proto_rawDescGZIP() []byte {
return file_ads_proto_rawDescData
}
-var file_ads_proto_msgTypes = make([]protoimpl.MessageInfo, 18)
+var file_ads_proto_msgTypes = make([]protoimpl.MessageInfo, 20)
var file_ads_proto_goTypes = []any{
(*Ad)(nil), // 0: ads.Ad
(*CreateAdRequest)(nil), // 1: ads.CreateAdRequest
- (*UpdateAdRequest)(nil), // 2: ads.UpdateAdRequest
- (*DeletePlaceRequest)(nil), // 3: ads.DeletePlaceRequest
- (*AddToFavoritesRequest)(nil), // 4: ads.AddToFavoritesRequest
- (*DeleteFromFavoritesRequest)(nil), // 5: ads.DeleteFromFavoritesRequest
- (*GetUserFavoritesRequest)(nil), // 6: ads.GetUserFavoritesRequest
- (*DeleteAdImageRequest)(nil), // 7: ads.DeleteAdImageRequest
- (*GetPlacesPerCityRequest)(nil), // 8: ads.GetPlacesPerCityRequest
- (*GetUserPlacesRequest)(nil), // 9: ads.GetUserPlacesRequest
- (*AdFilterRequest)(nil), // 10: ads.AdFilterRequest
- (*GetAllAdsResponse)(nil), // 11: ads.GetAllAdsResponse
- (*GetAllAdsResponseList)(nil), // 12: ads.GetAllAdsResponseList
- (*GetPlaceByIdRequest)(nil), // 13: ads.GetPlaceByIdRequest
- (*AdResponse)(nil), // 14: ads.AdResponse
- (*DeleteResponse)(nil), // 15: ads.DeleteResponse
- (*ImageResponse)(nil), // 16: ads.ImageResponse
- (*UserResponse)(nil), // 17: ads.UserResponse
- (*timestamppb.Timestamp)(nil), // 18: google.protobuf.Timestamp
+ (*AdRooms)(nil), // 2: ads.AdRooms
+ (*UpdateAdRequest)(nil), // 3: ads.UpdateAdRequest
+ (*DeletePlaceRequest)(nil), // 4: ads.DeletePlaceRequest
+ (*AddToFavoritesRequest)(nil), // 5: ads.AddToFavoritesRequest
+ (*DeleteFromFavoritesRequest)(nil), // 6: ads.DeleteFromFavoritesRequest
+ (*GetUserFavoritesRequest)(nil), // 7: ads.GetUserFavoritesRequest
+ (*DeleteAdImageRequest)(nil), // 8: ads.DeleteAdImageRequest
+ (*GetPlacesPerCityRequest)(nil), // 9: ads.GetPlacesPerCityRequest
+ (*GetUserPlacesRequest)(nil), // 10: ads.GetUserPlacesRequest
+ (*AdFilterRequest)(nil), // 11: ads.AdFilterRequest
+ (*GetAllAdsResponse)(nil), // 12: ads.GetAllAdsResponse
+ (*GetAllAdsResponseList)(nil), // 13: ads.GetAllAdsResponseList
+ (*GetPlaceByIdRequest)(nil), // 14: ads.GetPlaceByIdRequest
+ (*AdResponse)(nil), // 15: ads.AdResponse
+ (*DeleteResponse)(nil), // 16: ads.DeleteResponse
+ (*ImageResponse)(nil), // 17: ads.ImageResponse
+ (*UserResponse)(nil), // 18: ads.UserResponse
+ (*UpdatePriorityRequest)(nil), // 19: ads.UpdatePriorityRequest
+ (*timestamppb.Timestamp)(nil), // 20: google.protobuf.Timestamp
}
var file_ads_proto_depIdxs = []int32{
- 18, // 0: ads.CreateAdRequest.dateFrom:type_name -> google.protobuf.Timestamp
- 18, // 1: ads.CreateAdRequest.dateTo:type_name -> google.protobuf.Timestamp
- 18, // 2: ads.UpdateAdRequest.dateFrom:type_name -> google.protobuf.Timestamp
- 18, // 3: ads.UpdateAdRequest.dateTo:type_name -> google.protobuf.Timestamp
- 17, // 4: ads.GetAllAdsResponse.adAuthor:type_name -> ads.UserResponse
- 16, // 5: ads.GetAllAdsResponse.images:type_name -> ads.ImageResponse
- 11, // 6: ads.GetAllAdsResponseList.housing:type_name -> ads.GetAllAdsResponse
- 10, // 7: ads.Ads.GetAllPlaces:input_type -> ads.AdFilterRequest
- 13, // 8: ads.Ads.GetOnePlace:input_type -> ads.GetPlaceByIdRequest
- 1, // 9: ads.Ads.CreatePlace:input_type -> ads.CreateAdRequest
- 2, // 10: ads.Ads.UpdatePlace:input_type -> ads.UpdateAdRequest
- 3, // 11: ads.Ads.DeletePlace:input_type -> ads.DeletePlaceRequest
- 8, // 12: ads.Ads.GetPlacesPerCity:input_type -> ads.GetPlacesPerCityRequest
- 9, // 13: ads.Ads.GetUserPlaces:input_type -> ads.GetUserPlacesRequest
- 7, // 14: ads.Ads.DeleteAdImage:input_type -> ads.DeleteAdImageRequest
- 4, // 15: ads.Ads.AddToFavorites:input_type -> ads.AddToFavoritesRequest
- 5, // 16: ads.Ads.DeleteFromFavorites:input_type -> ads.DeleteFromFavoritesRequest
- 6, // 17: ads.Ads.GetUserFavorites:input_type -> ads.GetUserFavoritesRequest
- 12, // 18: ads.Ads.GetAllPlaces:output_type -> ads.GetAllAdsResponseList
- 11, // 19: ads.Ads.GetOnePlace:output_type -> ads.GetAllAdsResponse
- 0, // 20: ads.Ads.CreatePlace:output_type -> ads.Ad
- 14, // 21: ads.Ads.UpdatePlace:output_type -> ads.AdResponse
- 15, // 22: ads.Ads.DeletePlace:output_type -> ads.DeleteResponse
- 12, // 23: ads.Ads.GetPlacesPerCity:output_type -> ads.GetAllAdsResponseList
- 12, // 24: ads.Ads.GetUserPlaces:output_type -> ads.GetAllAdsResponseList
- 15, // 25: ads.Ads.DeleteAdImage:output_type -> ads.DeleteResponse
- 14, // 26: ads.Ads.AddToFavorites:output_type -> ads.AdResponse
- 14, // 27: ads.Ads.DeleteFromFavorites:output_type -> ads.AdResponse
- 12, // 28: ads.Ads.GetUserFavorites:output_type -> ads.GetAllAdsResponseList
- 18, // [18:29] is the sub-list for method output_type
- 7, // [7:18] is the sub-list for method input_type
- 7, // [7:7] is the sub-list for extension type_name
- 7, // [7:7] is the sub-list for extension extendee
- 0, // [0:7] is the sub-list for field type_name
+ 20, // 0: ads.CreateAdRequest.dateFrom:type_name -> google.protobuf.Timestamp
+ 20, // 1: ads.CreateAdRequest.dateTo:type_name -> google.protobuf.Timestamp
+ 2, // 2: ads.CreateAdRequest.rooms:type_name -> ads.AdRooms
+ 20, // 3: ads.UpdateAdRequest.dateFrom:type_name -> google.protobuf.Timestamp
+ 20, // 4: ads.UpdateAdRequest.dateTo:type_name -> google.protobuf.Timestamp
+ 2, // 5: ads.UpdateAdRequest.rooms:type_name -> ads.AdRooms
+ 18, // 6: ads.GetAllAdsResponse.adAuthor:type_name -> ads.UserResponse
+ 17, // 7: ads.GetAllAdsResponse.images:type_name -> ads.ImageResponse
+ 2, // 8: ads.GetAllAdsResponse.rooms:type_name -> ads.AdRooms
+ 12, // 9: ads.GetAllAdsResponseList.housing:type_name -> ads.GetAllAdsResponse
+ 11, // 10: ads.Ads.GetAllPlaces:input_type -> ads.AdFilterRequest
+ 14, // 11: ads.Ads.GetOnePlace:input_type -> ads.GetPlaceByIdRequest
+ 1, // 12: ads.Ads.CreatePlace:input_type -> ads.CreateAdRequest
+ 3, // 13: ads.Ads.UpdatePlace:input_type -> ads.UpdateAdRequest
+ 4, // 14: ads.Ads.DeletePlace:input_type -> ads.DeletePlaceRequest
+ 9, // 15: ads.Ads.GetPlacesPerCity:input_type -> ads.GetPlacesPerCityRequest
+ 10, // 16: ads.Ads.GetUserPlaces:input_type -> ads.GetUserPlacesRequest
+ 8, // 17: ads.Ads.DeleteAdImage:input_type -> ads.DeleteAdImageRequest
+ 5, // 18: ads.Ads.AddToFavorites:input_type -> ads.AddToFavoritesRequest
+ 6, // 19: ads.Ads.DeleteFromFavorites:input_type -> ads.DeleteFromFavoritesRequest
+ 7, // 20: ads.Ads.GetUserFavorites:input_type -> ads.GetUserFavoritesRequest
+ 19, // 21: ads.Ads.UpdatePriority:input_type -> ads.UpdatePriorityRequest
+ 13, // 22: ads.Ads.GetAllPlaces:output_type -> ads.GetAllAdsResponseList
+ 12, // 23: ads.Ads.GetOnePlace:output_type -> ads.GetAllAdsResponse
+ 0, // 24: ads.Ads.CreatePlace:output_type -> ads.Ad
+ 15, // 25: ads.Ads.UpdatePlace:output_type -> ads.AdResponse
+ 16, // 26: ads.Ads.DeletePlace:output_type -> ads.DeleteResponse
+ 13, // 27: ads.Ads.GetPlacesPerCity:output_type -> ads.GetAllAdsResponseList
+ 13, // 28: ads.Ads.GetUserPlaces:output_type -> ads.GetAllAdsResponseList
+ 16, // 29: ads.Ads.DeleteAdImage:output_type -> ads.DeleteResponse
+ 15, // 30: ads.Ads.AddToFavorites:output_type -> ads.AdResponse
+ 15, // 31: ads.Ads.DeleteFromFavorites:output_type -> ads.AdResponse
+ 13, // 32: ads.Ads.GetUserFavorites:output_type -> ads.GetAllAdsResponseList
+ 15, // 33: ads.Ads.UpdatePriority:output_type -> ads.AdResponse
+ 22, // [22:34] is the sub-list for method output_type
+ 10, // [10:22] is the sub-list for method input_type
+ 10, // [10:10] is the sub-list for extension type_name
+ 10, // [10:10] is the sub-list for extension extendee
+ 0, // [0:10] is the sub-list for field type_name
}
func init() { file_ads_proto_init() }
@@ -1648,15 +2039,13 @@ func file_ads_proto_init() {
if File_ads_proto != nil {
return
}
- file_ads_proto_msgTypes[11].OneofWrappers = []any{}
- file_ads_proto_msgTypes[17].OneofWrappers = []any{}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_ads_proto_rawDesc,
NumEnums: 0,
- NumMessages: 18,
+ NumMessages: 20,
NumExtensions: 0,
NumServices: 1,
},
diff --git a/microservices/ads_service/controller/gen/ads_grpc.pb.go b/microservices/ads_service/controller/gen/ads_grpc.pb.go
index a5465e1..898ae84 100644
--- a/microservices/ads_service/controller/gen/ads_grpc.pb.go
+++ b/microservices/ads_service/controller/gen/ads_grpc.pb.go
@@ -30,6 +30,7 @@ const (
Ads_AddToFavorites_FullMethodName = "/ads.Ads/AddToFavorites"
Ads_DeleteFromFavorites_FullMethodName = "/ads.Ads/DeleteFromFavorites"
Ads_GetUserFavorites_FullMethodName = "/ads.Ads/GetUserFavorites"
+ Ads_UpdatePriority_FullMethodName = "/ads.Ads/UpdatePriority"
)
// AdsClient is the client API for Ads service.
@@ -47,6 +48,7 @@ type AdsClient interface {
AddToFavorites(ctx context.Context, in *AddToFavoritesRequest, opts ...grpc.CallOption) (*AdResponse, error)
DeleteFromFavorites(ctx context.Context, in *DeleteFromFavoritesRequest, opts ...grpc.CallOption) (*AdResponse, error)
GetUserFavorites(ctx context.Context, in *GetUserFavoritesRequest, opts ...grpc.CallOption) (*GetAllAdsResponseList, error)
+ UpdatePriority(ctx context.Context, in *UpdatePriorityRequest, opts ...grpc.CallOption) (*AdResponse, error)
}
type adsClient struct {
@@ -167,6 +169,16 @@ func (c *adsClient) GetUserFavorites(ctx context.Context, in *GetUserFavoritesRe
return out, nil
}
+func (c *adsClient) UpdatePriority(ctx context.Context, in *UpdatePriorityRequest, opts ...grpc.CallOption) (*AdResponse, error) {
+ cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
+ out := new(AdResponse)
+ err := c.cc.Invoke(ctx, Ads_UpdatePriority_FullMethodName, in, out, cOpts...)
+ if err != nil {
+ return nil, err
+ }
+ return out, nil
+}
+
// AdsServer is the server API for Ads service.
// All implementations must embed UnimplementedAdsServer
// for forward compatibility.
@@ -182,6 +194,7 @@ type AdsServer interface {
AddToFavorites(context.Context, *AddToFavoritesRequest) (*AdResponse, error)
DeleteFromFavorites(context.Context, *DeleteFromFavoritesRequest) (*AdResponse, error)
GetUserFavorites(context.Context, *GetUserFavoritesRequest) (*GetAllAdsResponseList, error)
+ UpdatePriority(context.Context, *UpdatePriorityRequest) (*AdResponse, error)
mustEmbedUnimplementedAdsServer()
}
@@ -225,6 +238,9 @@ func (UnimplementedAdsServer) DeleteFromFavorites(context.Context, *DeleteFromFa
func (UnimplementedAdsServer) GetUserFavorites(context.Context, *GetUserFavoritesRequest) (*GetAllAdsResponseList, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetUserFavorites not implemented")
}
+func (UnimplementedAdsServer) UpdatePriority(context.Context, *UpdatePriorityRequest) (*AdResponse, error) {
+ return nil, status.Errorf(codes.Unimplemented, "method UpdatePriority not implemented")
+}
func (UnimplementedAdsServer) mustEmbedUnimplementedAdsServer() {}
func (UnimplementedAdsServer) testEmbeddedByValue() {}
@@ -444,6 +460,24 @@ func _Ads_GetUserFavorites_Handler(srv interface{}, ctx context.Context, dec fun
return interceptor(ctx, in, info, handler)
}
+func _Ads_UpdatePriority_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+ in := new(UpdatePriorityRequest)
+ if err := dec(in); err != nil {
+ return nil, err
+ }
+ if interceptor == nil {
+ return srv.(AdsServer).UpdatePriority(ctx, in)
+ }
+ info := &grpc.UnaryServerInfo{
+ Server: srv,
+ FullMethod: Ads_UpdatePriority_FullMethodName,
+ }
+ handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+ return srv.(AdsServer).UpdatePriority(ctx, req.(*UpdatePriorityRequest))
+ }
+ return interceptor(ctx, in, info, handler)
+}
+
// Ads_ServiceDesc is the grpc.ServiceDesc for Ads service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
@@ -495,6 +529,10 @@ var Ads_ServiceDesc = grpc.ServiceDesc{
MethodName: "GetUserFavorites",
Handler: _Ads_GetUserFavorites_Handler,
},
+ {
+ MethodName: "UpdatePriority",
+ Handler: _Ads_UpdatePriority_Handler,
+ },
},
Streams: []grpc.StreamDesc{},
Metadata: "ads.proto",
diff --git a/microservices/ads_service/mocks/mocks.go b/microservices/ads_service/mocks/mocks.go
index c6db7d8..c7f4ae4 100644
--- a/microservices/ads_service/mocks/mocks.go
+++ b/microservices/ads_service/mocks/mocks.go
@@ -3,10 +3,12 @@ package mocks
import (
"2024_2_FIGHT-CLUB/domain"
"2024_2_FIGHT-CLUB/internal/service/middleware"
+ "2024_2_FIGHT-CLUB/microservices/ads_service/controller/gen"
"context"
"github.com/golang-jwt/jwt"
- "github.com/gorilla/sessions"
- "net/http"
+ "github.com/stretchr/testify/mock"
+ "google.golang.org/grpc"
+ "time"
)
type MockJwtTokenService struct {
@@ -31,8 +33,7 @@ type MockServiceSession struct {
MockGetUserID func(ctx context.Context, sessionID string) (string, error)
MockLogoutSession func(ctx context.Context, sessionID string) error
MockCreateSession func(ctx context.Context, user *domain.User) (string, error)
- MockGetSessionData func(ctx context.Context, sessionID string) (*map[string]interface{}, error)
- MockGetSession func(r *http.Request) (*sessions.Session, error)
+ MockGetSessionData func(ctx context.Context, sessionID string) (*domain.SessionData, error)
}
func (m *MockServiceSession) GetUserID(ctx context.Context, sessionID string) (string, error) {
@@ -47,31 +48,32 @@ func (m *MockServiceSession) CreateSession(ctx context.Context, user *domain.Use
return m.MockCreateSession(ctx, user)
}
-func (m *MockServiceSession) GetSessionData(ctx context.Context, sessionID string) (*map[string]interface{}, error) {
+func (m *MockServiceSession) GetSessionData(ctx context.Context, sessionID string) (*domain.SessionData, error) {
return m.MockGetSessionData(ctx, sessionID)
}
-func (m *MockServiceSession) GetSession(r *http.Request) (*sessions.Session, error) {
- return m.MockGetSession(r)
-}
-
type MockAdUseCase struct {
- MockGetAllPlaces func(ctx context.Context, filter domain.AdFilter) ([]domain.GetAllAdsResponse, error)
- MockGetOnePlace func(ctx context.Context, adId string, isAuthorized bool) (domain.GetAllAdsResponse, error)
- MockCreatePlace func(ctx context.Context, place *domain.Ad, fileHeader [][]byte, newPlace domain.CreateAdRequest, userId string) error
- MockUpdatePlace func(ctx context.Context, place *domain.Ad, adId string, userId string, fileHeader [][]byte, updatedPlace domain.UpdateAdRequest) error
- MockDeletePlace func(ctx context.Context, adId string, userId string) error
- MockGetPlacesPerCity func(ctx context.Context, city string) ([]domain.GetAllAdsResponse, error)
- MockGetUserPlaces func(ctx context.Context, userId string) ([]domain.GetAllAdsResponse, error)
- MockDeleteAdImage func(ctx context.Context, adId string, imageId string, userId string) error
+ MockGetAllPlaces func(ctx context.Context, filter domain.AdFilter, userId string) ([]domain.GetAllAdsResponse, error)
+ MockGetOnePlace func(ctx context.Context, adId string, isAuthorized bool) (domain.GetAllAdsResponse, error)
+ MockCreatePlace func(ctx context.Context, place *domain.Ad, fileHeader [][]byte, newPlace domain.CreateAdRequest, userId string) error
+ MockUpdatePlace func(ctx context.Context, place *domain.Ad, adId string, userId string, fileHeader [][]byte, updatedPlace domain.UpdateAdRequest) error
+ MockDeletePlace func(ctx context.Context, adId string, userId string) error
+ MockGetPlacesPerCity func(ctx context.Context, city string) ([]domain.GetAllAdsResponse, error)
+ MockGetUserPlaces func(ctx context.Context, userId string) ([]domain.GetAllAdsResponse, error)
+ MockDeleteAdImage func(ctx context.Context, adId string, imageId string, userId string) error
+ MockAddToFavorites func(ctx context.Context, adId string, userId string) error
+ MockDeleteFromFavorites func(ctx context.Context, adId string, userId string) error
+ MockGetUserFavorites func(ctx context.Context, userId string) ([]domain.GetAllAdsResponse, error)
+ MockUpdatePriority func(ctx context.Context, adId string, userId string, amount int) error
+ MockStartPriorityResetWorker func(ctx context.Context, tickerInterval time.Duration)
}
func (m *MockAdUseCase) DeleteAdImage(ctx context.Context, adId string, imageId string, userId string) error {
return m.MockDeleteAdImage(ctx, adId, imageId, userId)
}
-func (m *MockAdUseCase) GetAllPlaces(ctx context.Context, filter domain.AdFilter) ([]domain.GetAllAdsResponse, error) {
- return m.MockGetAllPlaces(ctx, filter)
+func (m *MockAdUseCase) GetAllPlaces(ctx context.Context, filter domain.AdFilter, userId string) ([]domain.GetAllAdsResponse, error) {
+ return m.MockGetAllPlaces(ctx, filter, userId)
}
func (m *MockAdUseCase) GetOnePlace(ctx context.Context, adId string, isAuthorized bool) (domain.GetAllAdsResponse, error) {
@@ -98,27 +100,53 @@ func (m *MockAdUseCase) GetUserPlaces(ctx context.Context, userId string) ([]dom
return m.MockGetUserPlaces(ctx, userId)
}
+func (m *MockAdUseCase) AddToFavorites(ctx context.Context, adId string, userId string) error {
+ return m.MockAddToFavorites(ctx, adId, userId)
+}
+
+func (m *MockAdUseCase) DeleteFromFavorites(ctx context.Context, adId string, userId string) error {
+ return m.MockDeleteFromFavorites(ctx, adId, userId)
+}
+
+func (m *MockAdUseCase) GetUserFavorites(ctx context.Context, userId string) ([]domain.GetAllAdsResponse, error) {
+ return m.MockGetUserFavorites(ctx, userId)
+}
+
+func (m *MockAdUseCase) UpdatePriority(ctx context.Context, adId string, userId string, amount int) error {
+ return m.MockUpdatePriority(ctx, adId, userId, amount)
+}
+
+func (m *MockAdUseCase) StartPriorityResetWorker(ctx context.Context, tickerInterval time.Duration) {
+ m.MockStartPriorityResetWorker(ctx, tickerInterval)
+}
+
type MockAdRepository struct {
- MockGetAllPlaces func(ctx context.Context, filter domain.AdFilter) ([]domain.GetAllAdsResponse, error)
- MockGetPlaceById func(ctx context.Context, adId string) (domain.GetAllAdsResponse, error)
- MockUpdateViewsCount func(ctx context.Context, ad domain.GetAllAdsResponse) (domain.GetAllAdsResponse, error)
- MockCreatePlace func(ctx context.Context, ad *domain.Ad, newAd domain.CreateAdRequest, userId string) error
- MockSavePlace func(ctx context.Context, ad *domain.Ad) error
- MockUpdatePlace func(ctx context.Context, ad *domain.Ad, adId string, userId string, updatedAd domain.UpdateAdRequest) error
- MockDeletePlace func(ctx context.Context, adId string, userId string) error
- MockGetPlacesPerCity func(ctx context.Context, city string) ([]domain.GetAllAdsResponse, error)
- MockSaveImages func(ctx context.Context, adUUID string, imagePaths []string) error
- MockGetAdImages func(ctx context.Context, adId string) ([]string, error)
- MockGetUserPlaces func(ctx context.Context, userId string) ([]domain.GetAllAdsResponse, error)
- MockDeleteAdImage func(ctx context.Context, adId string, imageId int, userId string) (string, error)
+ MockGetAllPlaces func(ctx context.Context, filter domain.AdFilter, userId string) ([]domain.GetAllAdsResponse, error)
+ MockGetPlaceById func(ctx context.Context, adId string) (domain.GetAllAdsResponse, error)
+ MockUpdateViewsCount func(ctx context.Context, ad domain.GetAllAdsResponse) (domain.GetAllAdsResponse, error)
+ MockCreatePlace func(ctx context.Context, ad *domain.Ad, newAd domain.CreateAdRequest, userId string) error
+ MockSavePlace func(ctx context.Context, ad *domain.Ad) error
+ MockUpdatePlace func(ctx context.Context, ad *domain.Ad, adId string, userId string, updatedAd domain.UpdateAdRequest) error
+ MockDeletePlace func(ctx context.Context, adId string, userId string) error
+ MockGetPlacesPerCity func(ctx context.Context, city string) ([]domain.GetAllAdsResponse, error)
+ MockSaveImages func(ctx context.Context, adUUID string, imagePaths []string) error
+ MockGetAdImages func(ctx context.Context, adId string) ([]string, error)
+ MockGetUserPlaces func(ctx context.Context, userId string) ([]domain.GetAllAdsResponse, error)
+ MockDeleteAdImage func(ctx context.Context, adId string, imageId int, userId string) (string, error)
+ MockAddToFavorites func(ctx context.Context, adId string, userId string) error
+ MockDeleteFromFavorites func(ctx context.Context, adId string, userId string) error
+ MockGetUserFavorites func(ctx context.Context, userId string) ([]domain.GetAllAdsResponse, error)
+ MockUpdateFavoritesCount func(ctx context.Context, adId string) error
+ MockUpdatePriority func(ctx context.Context, adId string, userId string, amount int) error
+ MockResetExpiredPriorities func(ctx context.Context) error
}
func (m *MockAdRepository) DeleteAdImage(ctx context.Context, adId string, imageId int, userId string) (string, error) {
return m.MockDeleteAdImage(ctx, adId, imageId, userId)
}
-func (m *MockAdRepository) GetAllPlaces(ctx context.Context, filter domain.AdFilter) ([]domain.GetAllAdsResponse, error) {
- return m.MockGetAllPlaces(ctx, filter)
+func (m *MockAdRepository) GetAllPlaces(ctx context.Context, filter domain.AdFilter, userId string) ([]domain.GetAllAdsResponse, error) {
+ return m.MockGetAllPlaces(ctx, filter, userId)
}
func (m *MockAdRepository) GetPlaceById(ctx context.Context, adId string) (domain.GetAllAdsResponse, error) {
@@ -161,6 +189,30 @@ func (m *MockAdRepository) GetUserPlaces(ctx context.Context, userId string) ([]
return m.MockGetUserPlaces(ctx, userId)
}
+func (m *MockAdRepository) AddToFavorites(ctx context.Context, adId string, userId string) error {
+ return m.MockAddToFavorites(ctx, adId, userId)
+}
+
+func (m *MockAdRepository) DeleteFromFavorites(ctx context.Context, adId string, userId string) error {
+ return m.MockDeleteFromFavorites(ctx, adId, userId)
+}
+
+func (m *MockAdRepository) GetUserFavorites(ctx context.Context, userId string) ([]domain.GetAllAdsResponse, error) {
+ return m.MockGetUserFavorites(ctx, userId)
+}
+
+func (m *MockAdRepository) UpdateFavoritesCount(ctx context.Context, adId string) error {
+ return m.MockUpdateFavoritesCount(ctx, adId)
+}
+
+func (m *MockAdRepository) UpdatePriority(ctx context.Context, adId string, userId string, amount int) error {
+ return m.MockUpdatePriority(ctx, adId, userId, amount)
+}
+
+func (m *MockAdRepository) ResetExpiredPriorities(ctx context.Context) error {
+ return m.MockResetExpiredPriorities(ctx)
+}
+
type MockMinioService struct {
UploadFileFunc func(file []byte, contentType, id string) (string, error)
DeleteFileFunc func(filePath string) error
@@ -173,3 +225,67 @@ func (m *MockMinioService) UploadFile(file []byte, contentType, id string) (stri
func (m *MockMinioService) DeleteFile(filePath string) error {
return m.DeleteFileFunc(filePath)
}
+
+type MockGrpcClient struct {
+ mock.Mock
+}
+
+func (m *MockGrpcClient) GetAllPlaces(ctx context.Context, in *gen.AdFilterRequest, opts ...grpc.CallOption) (*gen.GetAllAdsResponseList, error) {
+ args := m.Called(ctx, in, opts)
+ return args.Get(0).(*gen.GetAllAdsResponseList), args.Error(1)
+}
+
+func (m *MockGrpcClient) GetOnePlace(ctx context.Context, in *gen.GetPlaceByIdRequest, opts ...grpc.CallOption) (*gen.GetAllAdsResponse, error) {
+ args := m.Called(ctx, in, opts)
+ return args.Get(0).(*gen.GetAllAdsResponse), args.Error(1)
+}
+
+func (m *MockGrpcClient) CreatePlace(ctx context.Context, in *gen.CreateAdRequest, opts ...grpc.CallOption) (*gen.Ad, error) {
+ args := m.Called(ctx, in, opts)
+ return args.Get(0).(*gen.Ad), args.Error(1)
+}
+
+func (m *MockGrpcClient) UpdatePlace(ctx context.Context, in *gen.UpdateAdRequest, opts ...grpc.CallOption) (*gen.AdResponse, error) {
+ args := m.Called(ctx, in, opts)
+ return args.Get(0).(*gen.AdResponse), args.Error(1)
+}
+
+func (m *MockGrpcClient) DeletePlace(ctx context.Context, in *gen.DeletePlaceRequest, opts ...grpc.CallOption) (*gen.DeleteResponse, error) {
+ args := m.Called(ctx, in, opts)
+ return args.Get(0).(*gen.DeleteResponse), args.Error(1)
+}
+
+func (m *MockGrpcClient) GetPlacesPerCity(ctx context.Context, in *gen.GetPlacesPerCityRequest, opts ...grpc.CallOption) (*gen.GetAllAdsResponseList, error) {
+ args := m.Called(ctx, in, opts)
+ return args.Get(0).(*gen.GetAllAdsResponseList), args.Error(1)
+}
+
+func (m *MockGrpcClient) GetUserPlaces(ctx context.Context, in *gen.GetUserPlacesRequest, opts ...grpc.CallOption) (*gen.GetAllAdsResponseList, error) {
+ args := m.Called(ctx, in, opts)
+ return args.Get(0).(*gen.GetAllAdsResponseList), args.Error(1)
+}
+
+func (m *MockGrpcClient) DeleteAdImage(ctx context.Context, in *gen.DeleteAdImageRequest, opts ...grpc.CallOption) (*gen.DeleteResponse, error) {
+ args := m.Called(ctx, in, opts)
+ return args.Get(0).(*gen.DeleteResponse), args.Error(1)
+}
+
+func (m *MockGrpcClient) AddToFavorites(ctx context.Context, in *gen.AddToFavoritesRequest, opts ...grpc.CallOption) (*gen.AdResponse, error) {
+ args := m.Called(ctx, in, opts)
+ return args.Get(0).(*gen.AdResponse), args.Error(1)
+}
+
+func (m *MockGrpcClient) DeleteFromFavorites(ctx context.Context, in *gen.DeleteFromFavoritesRequest, opts ...grpc.CallOption) (*gen.AdResponse, error) {
+ args := m.Called(ctx, in, opts)
+ return args.Get(0).(*gen.AdResponse), args.Error(1)
+}
+
+func (m *MockGrpcClient) GetUserFavorites(ctx context.Context, in *gen.GetUserFavoritesRequest, opts ...grpc.CallOption) (*gen.GetAllAdsResponseList, error) {
+ args := m.Called(ctx, in, opts)
+ return args.Get(0).(*gen.GetAllAdsResponseList), args.Error(1)
+}
+
+func (m *MockGrpcClient) UpdatePriority(ctx context.Context, in *gen.UpdatePriorityRequest, opts ...grpc.CallOption) (*gen.AdResponse, error) {
+ args := m.Called(ctx, in, opts)
+ return args.Get(0).(*gen.AdResponse), args.Error(1)
+}
diff --git a/microservices/ads_service/repository/ads_repository.go b/microservices/ads_service/repository/ads_repository.go
index 9cfde30..2bb0201 100644
--- a/microservices/ads_service/repository/ads_repository.go
+++ b/microservices/ads_service/repository/ads_repository.go
@@ -23,7 +23,7 @@ func NewAdRepository(db *gorm.DB) domain.AdRepository {
}
}
-func (r *adRepository) GetAllPlaces(ctx context.Context, filter domain.AdFilter) ([]domain.GetAllAdsResponse, error) {
+func (r *adRepository) GetAllPlaces(ctx context.Context, filter domain.AdFilter, userId string) ([]domain.GetAllAdsResponse, error) {
start := time.Now()
requestID := middleware.GetRequestID(ctx)
logger.DBLogger.Info("GetAllPlaces called", zap.String("request_id", requestID))
@@ -100,14 +100,32 @@ func (r *adRepository) GetAllPlaces(ctx context.Context, filter domain.AdFilter)
query = query.Limit(filter.Limit)
}
- if err := query.Find(&ads).Error; err != nil {
+ if err := query.Order("priority DESC").Find(&ads).Error; err != nil {
logger.DBLogger.Error("Error fetching all places", zap.String("request_id", requestID), zap.Error(err))
- return nil, errors.New("error fetching all places")
+ return nil, errors.New("erro0r fetching all places")
+ }
+
+ var favoriteAdIds []string
+ if userId != "" {
+ if err := r.db.Model(&domain.Favorites{}).Where("\"userId\" = ?", userId).Pluck("\"adId\"", &favoriteAdIds).Error; err != nil {
+ logger.DBLogger.Error("Error fetching favorites", zap.String("request_id", requestID), zap.Error(err))
+ return nil, errors.New("error fetching favorites")
+ }
+ }
+
+ favoritesMap := make(map[string]bool)
+ for _, adId := range favoriteAdIds {
+ favoritesMap[adId] = true
}
for i, ad := range ads {
+ if _, ok := favoritesMap[ad.UUID]; ok {
+ ads[i].IsFavorite = true
+ }
+
var images []domain.Image
var user domain.User
+ var rooms []domain.AdRooms
err := r.db.Model(&domain.Image{}).Where("\"adId\" = ?", ad.UUID).Find(&images).Error
if err != nil {
logger.DBLogger.Error("Error fetching images for ad", zap.String("request_id", requestID), zap.Error(err))
@@ -119,6 +137,13 @@ func (r *adRepository) GetAllPlaces(ctx context.Context, filter domain.AdFilter)
logger.DBLogger.Error("Error fetching user", zap.String("request_id", requestID), zap.Error(err))
return nil, errors.New("error fetching user")
}
+
+ err = r.db.Model(&domain.AdRooms{}).Where("\"adId\" = ?", ad.UUID).Find(&rooms).Error
+ if err != nil {
+ logger.DBLogger.Error("Error fetching rooms for ad", zap.String("request_id", requestID), zap.Error(err))
+ return nil, errors.New("error fetching rooms for ad")
+ }
+
ads[i].AdAuthor.Name = user.Name
ads[i].AdAuthor.Avatar = user.Avatar
ads[i].AdAuthor.Rating = user.Score
@@ -131,6 +156,13 @@ func (r *adRepository) GetAllPlaces(ctx context.Context, filter domain.AdFilter)
ImagePath: img.ImageUrl,
})
}
+
+ for _, room := range rooms {
+ ads[i].Rooms = append(ads[i].Rooms, domain.AdRoomsResponse{
+ Type: room.Type,
+ SquareMeters: room.SquareMeters,
+ })
+ }
}
logger.DBLogger.Info("Successfully fetched all places", zap.String("request_id", requestID), zap.Int("count", len(ads)))
@@ -164,6 +196,7 @@ func (r *adRepository) GetPlaceById(ctx context.Context, adId string) (domain.Ge
return ad, errors.New("error fetching place")
}
+ var rooms []domain.AdRooms
var images []domain.Image
var user domain.User
err = r.db.Model(&domain.Image{}).Where("\"adId\" = ?", ad.UUID).Find(&images).Error
@@ -178,6 +211,11 @@ func (r *adRepository) GetPlaceById(ctx context.Context, adId string) (domain.Ge
return ad, errors.New("error fetching user")
}
+ err = r.db.Model(&domain.AdRooms{}).Where("\"adId\" = ?", ad.UUID).Find(&rooms).Error
+ if err != nil {
+ logger.DBLogger.Error("Error fetching rooms for ad", zap.String("request_id", requestID), zap.Error(err))
+ return ad, errors.New("error fetching rooms for ad")
+ }
ad.AdAuthor.Name = user.Name
ad.AdAuthor.Avatar = user.Avatar
ad.AdAuthor.Rating = user.Score
@@ -192,6 +230,13 @@ func (r *adRepository) GetPlaceById(ctx context.Context, adId string) (domain.Ge
})
}
+ for _, room := range rooms {
+ ad.Rooms = append(ad.Rooms, domain.AdRoomsResponse{
+ Type: room.Type,
+ SquareMeters: room.SquareMeters,
+ })
+ }
+
if err != nil {
logger.DBLogger.Error("Error fetching place views", zap.String("request_id", requestID), zap.Error(err))
return ad, errors.New("error fetching place views")
@@ -224,6 +269,38 @@ func (r *adRepository) UpdateViewsCount(ctx context.Context, ad domain.GetAllAds
return ad, nil
}
+func (r *adRepository) UpdateFavoritesCount(ctx context.Context, adId string) error {
+ start := time.Now()
+ requestID := middleware.GetRequestID(ctx)
+ logger.DBLogger.Info("UpdateLikesCount called", zap.String("adId", adId), zap.String("request_id", requestID))
+ var err error
+ defer func() {
+ if err != nil {
+ metrics.RepoErrorsTotal.WithLabelValues("GetPlaceById", "error", err.Error()).Inc()
+ } else {
+ metrics.RepoRequestTotal.WithLabelValues("GetPlaceById", "success").Inc()
+ }
+ duration := time.Since(start).Seconds()
+ metrics.RepoRequestDuration.WithLabelValues("GetPlaceById").Observe(duration)
+ }()
+
+ var count int64
+ err = r.db.Model(&domain.Favorites{}).Where("\"adId\" = ?", adId).Count(&count).Error
+ if err != nil {
+ logger.DBLogger.Error("Error counting favorites", zap.String("adId", adId), zap.String("request_id", requestID), zap.Error(err))
+ return errors.New("error counting favorites")
+ }
+
+ err = r.db.Model(&domain.Ad{}).Where("uuid = ?", adId).Update("\"likesCount\"", count).Error
+ if err != nil {
+ logger.DBLogger.Error("Error updating favorites count", zap.String("adId", adId), zap.String("request_id", requestID), zap.Error(err))
+ return errors.New("error updating favorites count")
+ }
+
+ logger.DBLogger.Info("Successfully updated likes count", zap.String("request_id", requestID))
+ return nil
+}
+
func (r *adRepository) CreatePlace(ctx context.Context, ad *domain.Ad, newAd domain.CreateAdRequest, userId string) error {
start := time.Now()
requestID := middleware.GetRequestID(ctx)
@@ -241,7 +318,6 @@ func (r *adRepository) CreatePlace(ctx context.Context, ad *domain.Ad, newAd dom
var city domain.City
var user domain.User
var date domain.AdAvailableDate
-
if err := r.db.Where("uuid = ?", userId).First(&user).Error; err != nil {
logger.DBLogger.Error("Error finding user", zap.String("userId", userId), zap.String("request_id", requestID), zap.Error(err))
return errors.New("error finding user")
@@ -258,7 +334,16 @@ func (r *adRepository) CreatePlace(ctx context.Context, ad *domain.Ad, newAd dom
}
ad.CityID = city.ID
ad.AuthorUUID = userId
- ad.PublicationDate = time.Now()
+ ad.PublicationDate = time.Now().Truncate(time.Second)
+ ad.Description = newAd.Description
+ ad.Address = newAd.Address
+ ad.RoomsNumber = newAd.RoomsNumber
+ ad.SquareMeters = newAd.SquareMeters
+ ad.Floor = newAd.Floor
+ ad.BuildingType = newAd.BuildingType
+ ad.HasBalcony = newAd.HasBalcony
+ ad.HasElevator = newAd.HasElevator
+ ad.HasGas = newAd.HasGas
if err := r.db.Create(ad).Error; err != nil {
logger.DBLogger.Error("Error creating place", zap.String("adId", ad.UUID), zap.String("request_id", requestID), zap.Error(err))
return errors.New("error creating place")
@@ -272,29 +357,18 @@ func (r *adRepository) CreatePlace(ctx context.Context, ad *domain.Ad, newAd dom
logger.DBLogger.Error("Error creating date", zap.String("adId", ad.UUID), zap.String("request_id", requestID), zap.Error(err))
return errors.New("error creating date")
}
- logger.DBLogger.Info("Successfully place", zap.String("adId", ad.UUID), zap.String("request_id", requestID))
- return nil
-}
-func (r *adRepository) SavePlace(ctx context.Context, ad *domain.Ad) error {
- start := time.Now()
- requestID := middleware.GetRequestID(ctx)
- logger.DBLogger.Info("SavePlace called", zap.String("adId", ad.UUID), zap.String("request_id", requestID))
- var err error
- defer func() {
- if err != nil {
- metrics.RepoErrorsTotal.WithLabelValues("SavePlace", "error", err.Error()).Inc()
- } else {
- metrics.RepoRequestTotal.WithLabelValues("SavePlace", "success").Inc()
+ for _, room := range newAd.Rooms {
+ var oneRoom domain.AdRooms
+ oneRoom.AdID = ad.UUID
+ oneRoom.Type = room.Type
+ oneRoom.SquareMeters = room.SquareMeters
+ if err := r.db.Create(&oneRoom).Error; err != nil {
+ logger.DBLogger.Error("Error creating room", zap.String("adId", ad.UUID), zap.String("request_id", requestID), zap.Error(err))
+ return errors.New("error creating room")
}
- duration := time.Since(start).Seconds()
- metrics.RepoRequestDuration.WithLabelValues("SavePlace").Observe(duration)
- }()
- if err := r.db.Save(ad).Error; err != nil {
- logger.DBLogger.Error("Error saving place", zap.String("adId", ad.UUID), zap.String("request_id", requestID), zap.Error(err))
- return errors.New("error saving place")
}
- logger.DBLogger.Info("Successfully saved place", zap.String("adId", ad.UUID), zap.String("request_id", requestID))
+ logger.DBLogger.Info("Successfully create place", zap.String("adId", ad.UUID), zap.String("request_id", requestID))
return nil
}
@@ -334,11 +408,31 @@ func (r *adRepository) UpdatePlace(ctx context.Context, ad *domain.Ad, adId stri
return errors.New("error finding city")
}
ad.CityID = city.ID
+ ad.Description = updatedPlace.Description
+ ad.Address = updatedPlace.Address
+ ad.RoomsNumber = updatedPlace.RoomsNumber
+ ad.SquareMeters = updatedPlace.SquareMeters
+ ad.Floor = updatedPlace.Floor
+ ad.BuildingType = updatedPlace.BuildingType
+ ad.HasBalcony = updatedPlace.HasBalcony
+ ad.HasElevator = updatedPlace.HasElevator
+ ad.HasGas = updatedPlace.HasGas
if err := r.db.Model(&oldAd).Updates(ad).Error; err != nil {
logger.DBLogger.Error("Error updating place", zap.String("adId", adId), zap.String("request_id", requestID), zap.Error(err))
return errors.New("error updating place")
}
-
+ if err := r.db.Model(&oldAd).Update("\"hasBalcony\"", ad.HasBalcony).Error; err != nil {
+ logger.DBLogger.Error("Error updating place", zap.String("adId", adId), zap.String("request_id", requestID), zap.Error(err))
+ return errors.New("error updating place")
+ }
+ if err := r.db.Model(&oldAd).Update("\"hasElevator\"", ad.HasElevator).Error; err != nil {
+ logger.DBLogger.Error("Error updating place", zap.String("adId", adId), zap.String("request_id", requestID), zap.Error(err))
+ return errors.New("error updating place")
+ }
+ if err := r.db.Model(&oldAd).Update("\"hasGas\"", ad.HasGas).Error; err != nil {
+ logger.DBLogger.Error("Error updating place", zap.String("adId", adId), zap.String("request_id", requestID), zap.Error(err))
+ return errors.New("error updating place")
+ }
oldDate.AvailableDateFrom = updatedPlace.DateFrom
oldDate.AvailableDateTo = updatedPlace.DateTo
@@ -347,6 +441,22 @@ func (r *adRepository) UpdatePlace(ctx context.Context, ad *domain.Ad, adId stri
return errors.New("error updating date")
}
+ if err := r.db.Model(&domain.AdRooms{}).Where("\"adId\" = ?", adId).Delete(&domain.AdRooms{}).Error; err != nil {
+ logger.DBLogger.Error("Error deleting rooms", zap.String("adId", adId), zap.String("request_id", requestID), zap.Error(err))
+ return errors.New("error deleting rooms")
+ }
+
+ for _, room := range updatedPlace.Rooms {
+ var oneRoom domain.AdRooms
+ oneRoom.AdID = adId
+ oneRoom.Type = room.Type
+ oneRoom.SquareMeters = room.SquareMeters
+ if err := r.db.Create(&oneRoom).Error; err != nil {
+ logger.DBLogger.Error("Error creating room", zap.String("adId", adId), zap.String("request_id", requestID), zap.Error(err))
+ return errors.New("error creating room")
+ }
+ }
+
logger.DBLogger.Info("Successfully updated place", zap.String("adId", adId), zap.String("request_id", requestID))
return nil
}
@@ -377,22 +487,22 @@ func (r *adRepository) DeletePlace(ctx context.Context, adId string, userId stri
}
if err := r.db.Where("\"adId\" = ?", adId).Delete(&domain.Image{}).Error; err != nil {
- logger.DBLogger.Error("Error deleting place", zap.String("adId", adId), zap.String("request_id", requestID), zap.Error(err))
+ logger.DBLogger.Error("Error deleting image", zap.String("adId", adId), zap.String("request_id", requestID), zap.Error(err))
return errors.New("error deleting place")
}
if err := r.db.Where("\"adId\" = ?", adId).Delete(&domain.AdPosition{}).Error; err != nil {
- logger.DBLogger.Error("Error deleting place", zap.String("adId", adId), zap.String("request_id", requestID), zap.Error(err))
+ logger.DBLogger.Error("Error deleting position", zap.String("adId", adId), zap.String("request_id", requestID), zap.Error(err))
return errors.New("error deleting place")
}
if err := r.db.Where("\"adId\" = ?", adId).Delete(&domain.AdAvailableDate{}).Error; err != nil {
- logger.DBLogger.Error("Error deleting place", zap.String("adId", adId), zap.String("request_id", requestID), zap.Error(err))
+ logger.DBLogger.Error("Error deleting dates", zap.String("adId", adId), zap.String("request_id", requestID), zap.Error(err))
return errors.New("error deleting place")
}
- if err := r.db.Where("\"adId\" = ?", adId).Delete(&domain.Request{}).Error; err != nil {
- logger.DBLogger.Error("Error deleting place", zap.String("adId", adId), zap.String("request_id", requestID), zap.Error(err))
+ if err := r.db.Where("\"adId\" = ?", adId).Delete(&domain.AdRooms{}).Error; err != nil {
+ logger.DBLogger.Error("Error deleting rooms", zap.String("adId", adId), zap.String("request_id", requestID), zap.Error(err))
return errors.New("error deleting place")
}
@@ -422,7 +532,7 @@ func (r *adRepository) GetPlacesPerCity(ctx context.Context, city string) ([]dom
var ads []domain.GetAllAdsResponse
query := r.db.Model(&domain.Ad{}).Joins("JOIN users ON ads.\"authorUUID\" = users.uuid").Joins("JOIN cities ON ads.\"cityId\" = cities.id").
Select("ads.*, cities.title as \"CityName\"").Where("cities.\"enTitle\" = ?", city)
- if err := query.Find(&ads).Error; err != nil {
+ if err := query.Order("priority DESC").Find(&ads).Error; err != nil {
logger.DBLogger.Error("Error fetching places per city", zap.String("city", city), zap.String("request_id", requestID), zap.Error(err))
return nil, errors.New("error fetching places per city")
}
@@ -430,6 +540,7 @@ func (r *adRepository) GetPlacesPerCity(ctx context.Context, city string) ([]dom
for i, ad := range ads {
var images []domain.Image
var user domain.User
+ var rooms []domain.AdRooms
err := r.db.Model(&domain.Image{}).Where("\"adId\" = ?", ad.UUID).Find(&images).Error
if err != nil {
logger.DBLogger.Error("Error fetching images for ad", zap.String("request_id", requestID), zap.Error(err))
@@ -441,19 +552,32 @@ func (r *adRepository) GetPlacesPerCity(ctx context.Context, city string) ([]dom
logger.DBLogger.Error("Error fetching user", zap.String("request_id", requestID), zap.Error(err))
return nil, errors.New("error fetching user")
}
+
+ err = r.db.Model(&domain.AdRooms{}).Where("\"adId\" = ?", ad.UUID).Find(&rooms).Error
+ if err != nil {
+ logger.DBLogger.Error("Error fetching rooms for ad", zap.String("request_id", requestID), zap.Error(err))
+ return nil, errors.New("error fetching rooms for ad")
+ }
+
ads[i].AdAuthor.Name = user.Name
ads[i].AdAuthor.Avatar = user.Avatar
ads[i].AdAuthor.Rating = user.Score
ads[i].AdAuthor.GuestCount = user.GuestCount
ads[i].AdAuthor.Sex = user.Sex
ads[i].AdAuthor.Birthdate = user.Birthdate
-
for _, img := range images {
ads[i].Images = append(ads[i].Images, domain.ImageResponse{
ID: img.ID,
ImagePath: img.ImageUrl,
})
}
+
+ for _, room := range rooms {
+ ads[i].Rooms = append(ads[i].Rooms, domain.AdRoomsResponse{
+ Type: room.Type,
+ SquareMeters: room.SquareMeters,
+ })
+ }
}
logger.DBLogger.Info("Successfully fetched places per city", zap.String("city", city), zap.Int("count", len(ads)), zap.String("request_id", requestID))
@@ -529,25 +653,39 @@ func (r *adRepository) GetUserPlaces(ctx context.Context, userId string) ([]doma
var ads []domain.GetAllAdsResponse
query := r.db.Model(&domain.Ad{}).Joins("JOIN users ON ads.\"authorUUID\" = users.uuid").Joins("JOIN cities ON ads.\"cityId\" = cities.id").
Select("ads.*, users.avatar, users.name, users.score as rating, cities.title as \"CityName\"").Where("users.uuid = ?", userId)
- if err := query.Find(&ads).Error; err != nil {
+ if err := query.Order("priority DESC").Find(&ads).Error; err != nil {
logger.DBLogger.Error("Error fetching user places", zap.String("city", userId), zap.String("request_id", requestID), zap.Error(err))
return nil, errors.New("error fetching user places")
}
for i, ad := range ads {
var images []domain.Image
+ var rooms []domain.AdRooms
err := r.db.Model(&domain.Image{}).Where("\"adId\" = ?", ad.UUID).Find(&images).Error
if err != nil {
logger.DBLogger.Error("Error fetching images for ad", zap.String("request_id", requestID), zap.Error(err))
return nil, errors.New("error fetching images for ad")
}
+ err = r.db.Model(&domain.AdRooms{}).Where("\"adId\" = ?", ad.UUID).Find(&rooms).Error
+ if err != nil {
+ logger.DBLogger.Error("Error fetching rooms for ad", zap.String("request_id", requestID), zap.Error(err))
+ return nil, errors.New("error fetching rooms for ad")
+ }
+
for _, img := range images {
ads[i].Images = append(ads[i].Images, domain.ImageResponse{
ID: img.ID,
ImagePath: img.ImageUrl,
})
}
+
+ for _, room := range rooms {
+ ads[i].Rooms = append(ads[i].Rooms, domain.AdRoomsResponse{
+ Type: room.Type,
+ SquareMeters: room.SquareMeters,
+ })
+ }
}
logger.DBLogger.Info("Successfully fetched user places", zap.String("city", userId), zap.Int("count", len(ads)), zap.String("request_id", requestID))
@@ -680,8 +818,10 @@ func (r *adRepository) GetUserFavorites(ctx context.Context, userId string) ([]d
query := r.db.Model(&domain.Ad{}).
Joins("JOIN favorites ON favorites.\"adId\" = ads.uuid").
+ Joins("JOIN cities ON ads.\"cityId\" = cities.id").
+ Joins("JOIN ad_available_dates ON ad_available_dates.\"adId\" = ads.uuid").
Where("favorites.\"userId\" = ?", userId).
- Select("ads.*, favorites.\"userId\" AS \"FavoriteUserId\"")
+ Select("ads.*, favorites.\"userId\" AS \"FavoriteUserId\", cities.title as \"CityName\", ad_available_dates.\"availableDateFrom\" as \"AdDateFrom\", ad_available_dates.\"availableDateTo\" as \"AdDateTo\"")
if err := query.Find(&ads).Error; err != nil {
logger.DBLogger.Error("Error fetching user favorites", zap.String("request_id", requestID), zap.Error(err))
@@ -691,6 +831,7 @@ func (r *adRepository) GetUserFavorites(ctx context.Context, userId string) ([]d
for i, ad := range ads {
var images []domain.Image
var user domain.User
+ var rooms []domain.AdRooms
err := r.db.Model(&domain.Image{}).Where("\"adId\" = ?", ad.UUID).Find(&images).Error
if err != nil {
logger.DBLogger.Error("Error fetching images for ad", zap.String("request_id", requestID), zap.Error(err))
@@ -702,6 +843,13 @@ func (r *adRepository) GetUserFavorites(ctx context.Context, userId string) ([]d
logger.DBLogger.Error("Error fetching user", zap.String("request_id", requestID), zap.Error(err))
return nil, errors.New("error fetching user")
}
+
+ err = r.db.Model(&domain.AdRooms{}).Where("\"adId\" = ?", ad.UUID).Find(&rooms).Error
+ if err != nil {
+ logger.DBLogger.Error("Error fetching rooms for ad", zap.String("request_id", requestID), zap.Error(err))
+ return nil, errors.New("error fetching rooms for ad")
+ }
+
ads[i].AdAuthor.Name = user.Name
ads[i].AdAuthor.Avatar = user.Avatar
ads[i].AdAuthor.Rating = user.Score
@@ -714,9 +862,69 @@ func (r *adRepository) GetUserFavorites(ctx context.Context, userId string) ([]d
ImagePath: img.ImageUrl,
})
}
+
+ for _, room := range rooms {
+ ads[i].Rooms = append(ads[i].Rooms, domain.AdRoomsResponse{
+ Type: room.Type,
+ SquareMeters: room.SquareMeters,
+ })
+ }
}
logger.DBLogger.Info("Successfully fetched user favorites", zap.String("request_id", requestID), zap.Int("count", len(ads)))
return ads, nil
}
+
+func (r *adRepository) UpdatePriority(ctx context.Context, adId string, userId string, amount int) error {
+ start := time.Now()
+ requestID := middleware.GetRequestID(ctx)
+ logger.DBLogger.Info("UpdatePriority called", zap.String("ad", adId), zap.String("request_id", requestID))
+ var err error
+ defer func() {
+ if err != nil {
+ metrics.RepoErrorsTotal.WithLabelValues("UpdatePriority", "error", err.Error()).Inc()
+ } else {
+ metrics.RepoRequestTotal.WithLabelValues("UpdatePriority", "success").Inc()
+ }
+ duration := time.Since(start).Seconds()
+ metrics.RepoRequestDuration.WithLabelValues("UpdatePriority").Observe(duration)
+ }()
+ var ad domain.Ad
+ if err := r.db.Where("uuid = ?", adId).First(&ad).Error; err != nil {
+ logger.DBLogger.Error("Error fetching ad", zap.String("request_id", requestID), zap.Error(err))
+ return errors.New("error fetching ad")
+ }
+ if userId != ad.AuthorUUID {
+ return errors.New("not owner of ad")
+ }
+ ad.Priority = amount
+ ad.EndBoostDate = time.Now().Add(24 * 7 * time.Hour)
+ if err := r.db.Model(&ad).Update("priority", ad.Priority).Error; err != nil {
+ logger.DBLogger.Error("Error updating priority", zap.String("request_id", requestID), zap.Error(err))
+ return errors.New("error updating priority")
+ }
+ if err := r.db.Model(&ad).Update("endBoostDate", ad.EndBoostDate).Error; err != nil {
+ logger.DBLogger.Error("Error updating priority", zap.String("request_id", requestID), zap.Error(err))
+ return errors.New("error updating priority")
+ }
+ return nil
+}
+
+func (r *adRepository) ResetExpiredPriorities(ctx context.Context) error {
+ requestID := middleware.GetRequestID(ctx)
+ logger.DBLogger.Info("ResetExpiredPriorities called", zap.String("request_id", requestID))
+
+ // Обновляем все объявления, у которых EndBoostDate в прошлом
+ result := r.db.Model(&domain.Ad{}).
+ Where("\"endBoostDate\" <= ?", time.Now()).
+ Updates(map[string]interface{}{"priority": 0, "\"endBoostDate\"": nil})
+
+ if result.Error != nil {
+ logger.DBLogger.Error("Error resetting expired priorities", zap.String("request_id", requestID), zap.Error(result.Error))
+ return errors.New("error resetting expired priorities")
+ }
+
+ logger.DBLogger.Info("Expired priorities reset successfully", zap.String("request_id", requestID), zap.Int64("rows_affected", result.RowsAffected))
+ return nil
+}
diff --git a/microservices/ads_service/repository/ads_repository_test.go b/microservices/ads_service/repository/ads_repository_test.go
index 0432efe..bab3a47 100644
--- a/microservices/ads_service/repository/ads_repository_test.go
+++ b/microservices/ads_service/repository/ads_repository_test.go
@@ -3,6 +3,7 @@ package repository
import (
"2024_2_FIGHT-CLUB/domain"
"2024_2_FIGHT-CLUB/internal/service/logger"
+ "2024_2_FIGHT-CLUB/internal/service/middleware"
ntype "2024_2_FIGHT-CLUB/internal/service/type"
"context"
"errors"
@@ -31,7 +32,12 @@ func TestGetAllPlaces(t *testing.T) {
if err := logger.InitLoggers(); err != nil {
log.Fatalf("Failed to initialize loggers: %v", err)
}
- defer logger.SyncLoggers()
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
db, mock, err := setupDBMock()
require.NoError(t, err)
@@ -46,6 +52,8 @@ func TestGetAllPlaces(t *testing.T) {
GuestCount: "",
}
+ userId := "12345"
+
// Фиксированная дата для теста
fixedDate := time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC)
@@ -63,6 +71,10 @@ func TestGetAllPlaces(t *testing.T) {
}).AddRow("some-uuid", 1, "author-uuid", "Some Address", fixedDate, "Some Description", 3, 0, "City Name", fixedDate, fixedDate)
mock.ExpectQuery(regexp.QuoteMeta(query)).WillReturnRows(adRows)
+ favoritesQuery := `SELECT "adId" FROM "favorites" WHERE "userId" = $1`
+ favoritesRows := sqlmock.NewRows([]string{"adId"}).AddRow("id1").AddRow("id2")
+ mock.ExpectQuery(regexp.QuoteMeta(favoritesQuery)).WillReturnRows(favoritesRows)
+
imagesQuery := `SELECT * FROM "images" WHERE "adId" = $1`
imageRows := sqlmock.NewRows([]string{"id", "adId", "imageUrl"}).
AddRow(1, "some-uuid", "images/image1.jpg").
@@ -75,7 +87,11 @@ func TestGetAllPlaces(t *testing.T) {
}).AddRow("author-uuid", "test_username", "some_password", "test@example.com", "Test User", 4.5, "avatar_url", "M", 2, fixedDate)
mock.ExpectQuery(regexp.QuoteMeta(userQuery)).WithArgs("author-uuid").WillReturnRows(userRows)
- ads, err := repo.GetAllPlaces(context.Background(), filter)
+ adQuery := `SELECT * FROM "ad_rooms" WHERE "adId" = $1`
+ addRows := sqlmock.NewRows([]string{"id", "adId", "type", "squaremeters"}).AddRow(1, "id1", "some-type", 12)
+ mock.ExpectQuery(regexp.QuoteMeta(adQuery)).WithArgs("some-uuid").WillReturnRows(addRows)
+
+ ads, err := repo.GetAllPlaces(context.Background(), filter, userId)
require.NoError(t, err)
assert.Len(t, ads, 1)
@@ -113,10 +129,17 @@ func TestGetAllPlaces_Failure(t *testing.T) {
if err := logger.InitLoggers(); err != nil {
log.Fatalf("Failed to initialize loggers: %v", err)
}
- defer logger.SyncLoggers()
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
db, mock, err := setupDBMock()
assert.Nil(t, err)
+ userId := "12345"
+
repo := NewAdRepository(db)
filter := domain.AdFilter{}
query := `
@@ -129,7 +152,7 @@ func TestGetAllPlaces_Failure(t *testing.T) {
mock.ExpectQuery(query).
WillReturnError(errors.New("db error"))
- ads, err := repo.GetAllPlaces(context.Background(), filter)
+ ads, err := repo.GetAllPlaces(context.Background(), filter, userId)
assert.Error(t, err)
assert.Nil(t, ads)
}
@@ -138,7 +161,12 @@ func TestGetPlaceById(t *testing.T) {
if err := logger.InitLoggers(); err != nil {
log.Fatalf("Failed to initialize loggers: %v", err)
}
- defer logger.SyncLoggers()
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
db, mock, err := setupDBMock()
assert.Nil(t, err)
@@ -174,6 +202,10 @@ func TestGetPlaceById(t *testing.T) {
}).AddRow("author-uuid", "test_username", "some_password", "test@example.com", "Test User", 4.5, "avatar_url", "M", 2, fixedDate)
mock.ExpectQuery(regexp.QuoteMeta(userQuery)).WithArgs("author-uuid").WillReturnRows(userRows)
+ adQuery := `SELECT * FROM "ad_rooms" WHERE "adId" = $1`
+ addRows := sqlmock.NewRows([]string{"id", "adId", "type", "squaremeters"}).AddRow(1, "id1", "some-type", 12)
+ mock.ExpectQuery(regexp.QuoteMeta(adQuery)).WithArgs("some-uuid").WillReturnRows(addRows)
+
ad, err := repo.GetPlaceById(context.Background(), "some-uuid")
require.NoError(t, err)
@@ -209,7 +241,12 @@ func TestGetPlaceById_Failure(t *testing.T) {
if err := logger.InitLoggers(); err != nil {
log.Fatalf("Failed to initialize loggers: %v", err)
}
- defer logger.SyncLoggers()
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
db, mock, err := setupDBMock()
assert.Nil(t, err)
@@ -224,11 +261,207 @@ func TestGetPlaceById_Failure(t *testing.T) {
assert.Empty(t, ad)
}
+func TestAdRepository_UpdateViewsCount_Success(t *testing.T) {
+ if err := logger.InitLoggers(); err != nil {
+ log.Fatalf("Failed to initialize loggers: %v", err)
+ }
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
+
+ db, mock, err := setupDBMock()
+ assert.Nil(t, err)
+ defer func(db *gorm.DB) {
+ _, err := db.DB()
+ if err != nil {
+ return
+ }
+ }(db)
+
+ repo := NewAdRepository(db)
+ ctx := context.Background()
+
+ ctx = context.WithValue(ctx, middleware.RequestIDKey, "test-request-id")
+
+ ad := domain.GetAllAdsResponse{
+ UUID: "ad-uuid-123",
+ ViewsCount: 5,
+ }
+
+ mock.ExpectBegin()
+ mock.ExpectExec(regexp.QuoteMeta(`UPDATE "ads" SET "uuid"=$1,"viewsCount"=$2 WHERE uuid = $3`)).
+ WithArgs(ad.UUID, ad.ViewsCount+1, ad.UUID). // Увеличение на 1
+ WillReturnResult(sqlmock.NewResult(1, 1))
+ mock.ExpectCommit()
+
+ updatedAd, err := repo.UpdateViewsCount(ctx, ad)
+
+ assert.NoError(t, err)
+ assert.Equal(t, ad.ViewsCount+1, updatedAd.ViewsCount)
+ assert.NoError(t, mock.ExpectationsWereMet())
+}
+
+func TestAdRepository_UpdateViewsCount_DBError(t *testing.T) {
+ if err := logger.InitLoggers(); err != nil {
+ log.Fatalf("Failed to initialize loggers: %v", err)
+ }
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
+
+ db, mock, err := setupDBMock()
+ assert.Nil(t, err)
+ defer func(db *gorm.DB) {
+ _, err := db.DB()
+ if err != nil {
+ return
+ }
+ }(db)
+
+ repo := NewAdRepository(db)
+ ctx := context.Background()
+
+ ctx = context.WithValue(ctx, middleware.RequestIDKey, "test-request-id")
+
+ ad := domain.GetAllAdsResponse{
+ UUID: "ad-uuid-123",
+ ViewsCount: 5,
+ }
+ updatedViewsCountFail := 5
+
+ mock.ExpectBegin()
+ mock.ExpectExec(regexp.QuoteMeta(`UPDATE "ads" SET "uuid"=$1,"viewsCount"=$2 WHERE uuid = $3`)).
+ WithArgs(ad.UUID, ad.ViewsCount+1, ad.UUID).
+ WillReturnError(errors.New("error updating views count"))
+ mock.ExpectRollback()
+
+ _, err = repo.UpdateViewsCount(ctx, ad)
+
+ assert.Error(t, err)
+ assert.Equal(t, "error updating views count", err.Error())
+ assert.Equal(t, ad.ViewsCount, updatedViewsCountFail)
+ assert.NoError(t, mock.ExpectationsWereMet())
+}
+
+func TestAdRepository_UpdateFavoritesCount_Success(t *testing.T) {
+ if err := logger.InitLoggers(); err != nil {
+ log.Fatalf("Failed to initialize loggers: %v", err)
+ }
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
+
+ db, mock, err := setupDBMock()
+ assert.Nil(t, err)
+
+ repo := NewAdRepository(db)
+ ctx := context.WithValue(context.Background(), middleware.RequestIDKey, "test-request-id")
+
+ adID := "ad-uuid-123"
+ favoritesCount := int64(10)
+
+ mock.ExpectQuery(regexp.QuoteMeta(`SELECT count(*) FROM "favorites" WHERE "adId" = $1`)).
+ WithArgs(adID).
+ WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(favoritesCount))
+
+ mock.ExpectBegin()
+ mock.ExpectExec(regexp.QuoteMeta(`UPDATE "ads" SET "likesCount"=$1 WHERE uuid = $2`)).
+ WithArgs(favoritesCount, adID).
+ WillReturnResult(sqlmock.NewResult(1, 1))
+ mock.ExpectCommit()
+
+ err = repo.UpdateFavoritesCount(ctx, adID)
+
+ assert.NoError(t, err)
+ assert.NoError(t, mock.ExpectationsWereMet())
+}
+
+func TestAdRepository_UpdateFavoritesCount_CountError(t *testing.T) {
+ if err := logger.InitLoggers(); err != nil {
+ log.Fatalf("Failed to initialize loggers: %v", err)
+ }
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
+
+ db, mock, err := setupDBMock()
+ assert.Nil(t, err)
+
+ repo := NewAdRepository(db)
+ ctx := context.WithValue(context.Background(), middleware.RequestIDKey, "test-request-id")
+
+ adID := "ad-uuid-123"
+
+ mock.ExpectQuery(regexp.QuoteMeta(`SELECT count(*) FROM "favorites" WHERE "adId" = $1`)).
+ WithArgs(adID).
+ WillReturnError(errors.New("error counting favorites"))
+
+ err = repo.UpdateFavoritesCount(ctx, adID)
+
+ assert.Error(t, err)
+ assert.Equal(t, "error counting favorites", err.Error())
+ assert.NoError(t, mock.ExpectationsWereMet())
+}
+
+func TestAdRepository_UpdateFavoritesCount_UpdateError(t *testing.T) {
+ if err := logger.InitLoggers(); err != nil {
+ log.Fatalf("Failed to initialize loggers: %v", err)
+ }
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
+
+ db, mock, err := setupDBMock()
+ assert.Nil(t, err)
+
+ repo := NewAdRepository(db)
+ ctx := context.WithValue(context.Background(), middleware.RequestIDKey, "test-request-id")
+
+ adID := "ad-uuid-123"
+ favoritesCount := int64(10)
+
+ mock.ExpectQuery(regexp.QuoteMeta(`SELECT count(*) FROM "favorites" WHERE "adId" = $1`)).
+ WithArgs(adID).
+ WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(favoritesCount))
+
+ mock.ExpectBegin()
+ mock.ExpectExec(regexp.QuoteMeta(`UPDATE "ads" SET "likesCount"=$1 WHERE uuid = $2`)).
+ WithArgs(favoritesCount, adID).
+ WillReturnError(errors.New("error updating favorites count"))
+ mock.ExpectRollback()
+
+ err = repo.UpdateFavoritesCount(ctx, adID)
+
+ assert.Error(t, err)
+ assert.Equal(t, "error updating favorites count", err.Error())
+ assert.NoError(t, mock.ExpectationsWereMet())
+}
+
func TestUpdatePlace(t *testing.T) {
if err := logger.InitLoggers(); err != nil {
log.Fatalf("Failed to initialize loggers: %v", err)
}
- defer logger.SyncLoggers()
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
db, mock, err := setupDBMock()
assert.Nil(t, err)
@@ -244,6 +477,9 @@ func TestUpdatePlace(t *testing.T) {
RoomsNumber: 3,
DateFrom: time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC),
DateTo: time.Date(2024, 12, 31, 0, 0, 0, 0, time.UTC),
+ HasBalcony: true,
+ HasGas: true,
+ HasElevator: true,
}
adRows := sqlmock.NewRows([]string{"uuid", "authorUUID", "cityId", "address", "roomsNumber"}).
@@ -261,16 +497,41 @@ func TestUpdatePlace(t *testing.T) {
WithArgs("Новый город", 1).WillReturnRows(cityRows)
mock.ExpectBegin()
- mock.ExpectExec(regexp.QuoteMeta(`UPDATE "ads" SET "cityId"=$1 WHERE "uuid" = $2`)).
- WithArgs(1, adId).
+ mock.ExpectExec(regexp.QuoteMeta(`UPDATE "ads" SET "cityId"=$1,"address"=$2,"description"=$3,"roomsNumber"=$4,"hasBalcony"=$5,"hasElevator"=$6,"hasGas"=$7 WHERE "uuid" = $8`)).
+ WithArgs(1, "New Address", "Updated Description", 3, true, true, true, adId).
WillReturnResult(sqlmock.NewResult(1, 1))
mock.ExpectCommit()
+
+ mock.ExpectBegin()
+ mock.ExpectExec(regexp.QuoteMeta(`UPDATE "ads" SET "hasBalcony"=$1 WHERE "uuid" = $2`)).
+ WithArgs(true, adId).
+ WillReturnResult(sqlmock.NewResult(1, 1))
+ mock.ExpectCommit()
+
+ mock.ExpectBegin()
+ mock.ExpectExec(regexp.QuoteMeta(`UPDATE "ads" SET "hasElevator"=$1 WHERE "uuid" = $2`)).
+ WithArgs(true, adId).
+ WillReturnResult(sqlmock.NewResult(1, 1))
+ mock.ExpectCommit()
+
+ mock.ExpectBegin()
+ mock.ExpectExec(regexp.QuoteMeta(`UPDATE "ads" SET "hasGas"=$1 WHERE "uuid" = $2`)).
+ WithArgs(true, adId).
+ WillReturnResult(sqlmock.NewResult(1, 1))
+ mock.ExpectCommit()
+
mock.ExpectBegin()
mock.ExpectExec(regexp.QuoteMeta(`UPDATE "ad_available_dates" SET "id"=$1,"adId"=$2,"availableDateFrom"=$3,"availableDateTo"=$4 WHERE "id" = $5`)).
WithArgs(1, adId, updatedRequest.DateFrom, updatedRequest.DateTo, 1).
WillReturnResult(sqlmock.NewResult(1, 1))
mock.ExpectCommit()
+ mock.ExpectBegin()
+ mock.ExpectExec(regexp.QuoteMeta(`DELETE FROM "ad_rooms" WHERE "adId" = $1`)).
+ WithArgs(adId).
+ WillReturnResult(sqlmock.NewResult(1, 1))
+ mock.ExpectCommit()
+
err = repo.UpdatePlace(context.Background(), &domain.Ad{}, adId, userId, updatedRequest)
assert.NoError(t, err)
@@ -282,7 +543,12 @@ func TestUpdatePlace_AdNotFound(t *testing.T) {
if err := logger.InitLoggers(); err != nil {
log.Fatalf("Failed to initialize loggers: %v", err)
}
- defer logger.SyncLoggers()
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
db, mock, err := setupDBMock()
assert.Nil(t, err)
@@ -313,7 +579,12 @@ func TestUpdatePlace_AdDateNotFound(t *testing.T) {
if err := logger.InitLoggers(); err != nil {
log.Fatalf("Failed to initialize loggers: %v", err)
}
- defer logger.SyncLoggers()
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
db, mock, err := setupDBMock()
assert.Nil(t, err)
@@ -346,7 +617,12 @@ func TestUpdatePlace_UserNotAuthorized(t *testing.T) {
if err := logger.InitLoggers(); err != nil {
log.Fatalf("Failed to initialize loggers: %v", err)
}
- defer logger.SyncLoggers()
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
db, mock, err := setupDBMock()
assert.Nil(t, err)
@@ -379,67 +655,16 @@ func TestUpdatePlace_UserNotAuthorized(t *testing.T) {
assert.NoError(t, err)
}
-func TestDeletePlace(t *testing.T) {
- if err := logger.InitLoggers(); err != nil {
- log.Fatalf("Failed to initialize loggers: %v", err)
- }
- defer logger.SyncLoggers()
-
- db, mock, err := setupDBMock()
- assert.Nil(t, err)
-
- adRepo := NewAdRepository(db)
- ctx := context.Background()
-
- adId := "1"
- userId := "123"
-
- mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "ads" WHERE uuid = $1 ORDER BY "ads"."uuid" LIMIT $2`)).
- WithArgs(adId, 1).
- WillReturnRows(sqlmock.NewRows([]string{"uuid", "address", "authorUUID"}).
- AddRow("1", "test_address", "123"))
-
- mock.ExpectBegin()
- mock.ExpectExec(regexp.QuoteMeta(`DELETE FROM "images" WHERE "adId" = $1`)).
- WithArgs(adId).
- WillReturnResult(sqlmock.NewResult(1, 1))
- mock.ExpectCommit()
-
- mock.ExpectBegin()
- mock.ExpectExec(regexp.QuoteMeta(`DELETE FROM "ad_positions" WHERE "adId" = $1`)).
- WithArgs(adId).
- WillReturnResult(sqlmock.NewResult(1, 1))
- mock.ExpectCommit()
-
- mock.ExpectBegin()
- mock.ExpectExec(regexp.QuoteMeta(`DELETE FROM "ad_available_dates" WHERE "adId" = $1`)).
- WithArgs(adId).
- WillReturnResult(sqlmock.NewResult(1, 1))
- mock.ExpectCommit()
-
- mock.ExpectBegin()
- mock.ExpectExec(regexp.QuoteMeta(`DELETE FROM "requests" WHERE "adId" = $1`)).
- WithArgs(adId).
- WillReturnResult(sqlmock.NewResult(1, 1))
- mock.ExpectCommit()
-
- mock.ExpectBegin()
- mock.ExpectExec(regexp.QuoteMeta(`DELETE FROM "ads" WHERE "ads"."uuid" = $1`)).
- WithArgs(adId).
- WillReturnResult(sqlmock.NewResult(1, 1))
- mock.ExpectCommit()
-
- err = adRepo.DeletePlace(ctx, adId, userId)
-
- assert.NoError(t, err)
- assert.NoError(t, mock.ExpectationsWereMet())
-}
-
func TestDeletePlace_Failure(t *testing.T) {
if err := logger.InitLoggers(); err != nil {
log.Fatalf("Failed to initialize loggers: %v", err)
}
- defer logger.SyncLoggers()
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
db, mock, err := setupDBMock()
assert.Nil(t, err)
@@ -466,7 +691,12 @@ func TestCreatePlace(t *testing.T) {
if err := logger.InitLoggers(); err != nil {
log.Fatalf("Failed to initialize loggers: %v", err)
}
- defer logger.SyncLoggers()
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
db, mock, err := setupDBMock()
require.NoError(t, err)
@@ -474,10 +704,18 @@ func TestCreatePlace(t *testing.T) {
repo := NewAdRepository(db)
ad := &domain.Ad{
- UUID: "some-ad-uuid",
- Description: "A great place",
- Address: "123 Main Street",
- RoomsNumber: 2,
+ UUID: "some-ad-uuid",
+ Description: "A great place",
+ Address: "123 Main Street",
+ RoomsNumber: 2,
+ SquareMeters: 15,
+ Floor: 2,
+ BuildingType: "house",
+ HasElevator: true,
+ HasGas: true,
+ HasBalcony: true,
+ LikesCount: 5,
+ Priority: 10,
}
newAd := domain.CreateAdRequest{
@@ -487,6 +725,18 @@ func TestCreatePlace(t *testing.T) {
RoomsNumber: 2,
DateFrom: time.Now(),
DateTo: time.Now().AddDate(0, 0, 7),
+ Rooms: []domain.AdRoomsResponse{
+ {
+ Type: "room",
+ SquareMeters: 14,
+ },
+ },
+ SquareMeters: 15,
+ Floor: 2,
+ BuildingType: "house",
+ HasElevator: true,
+ HasGas: true,
+ HasBalcony: true,
}
user := domain.User{
@@ -506,6 +756,13 @@ func TestCreatePlace(t *testing.T) {
AvailableDateTo: newAd.DateTo,
}
+ room := domain.AdRooms{
+ ID: 1,
+ AdID: "some-ad-uuid",
+ Type: "room",
+ SquareMeters: 14,
+ }
+
mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "users" WHERE uuid = $1 ORDER BY "users"."uuid" LIMIT $2`)).
WithArgs(user.UUID, 1).
WillReturnRows(sqlmock.NewRows([]string{"uuid", "isHost"}).
@@ -517,7 +774,7 @@ func TestCreatePlace(t *testing.T) {
AddRow(city.ID, city.Title))
mock.ExpectBegin()
- mock.ExpectQuery(regexp.QuoteMeta(`INSERT INTO "ads" ("cityId","authorUUID","address","publicationDate","description","roomsNumber","viewsCount","uuid") VALUES ($1,$2,$3,$4,$5,$6,$7,$8) RETURNING "uuid"`)).
+ mock.ExpectQuery(regexp.QuoteMeta(`INSERT INTO "ads" ("cityId","authorUUID","address","publicationDate","description","roomsNumber","viewsCount","squareMeters","floor","buildingType","hasBalcony","hasElevator","hasGas","likesCount","priority","endBoostDate","uuid") VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17) RETURNING "uuid"`)).
WithArgs(
city.ID, // cityId
user.UUID, // authorUUID
@@ -526,7 +783,16 @@ func TestCreatePlace(t *testing.T) {
ad.Description, // description
ad.RoomsNumber, // roomsNumber
0, // viewsCount
- ad.UUID, // uuid
+ ad.SquareMeters,
+ ad.Floor,
+ ad.BuildingType,
+ ad.HasBalcony,
+ ad.HasElevator,
+ ad.HasGas,
+ ad.LikesCount,
+ ad.Priority,
+ sqlmock.AnyArg(),
+ ad.UUID, // uuid
).
WillReturnRows(sqlmock.NewRows([]string{"uuid"}).AddRow(ad.UUID))
mock.ExpectCommit()
@@ -542,6 +808,16 @@ func TestCreatePlace(t *testing.T) {
WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(date.ID))
mock.ExpectCommit()
+ mock.ExpectBegin()
+ mock.ExpectQuery(regexp.QuoteMeta(`INSERT INTO "ad_rooms" ("adId","type","squareMeters") VALUES ($1,$2,$3) RETURNING "id"`)).
+ WithArgs(
+ room.AdID,
+ room.Type,
+ room.SquareMeters,
+ ).
+ WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(date.ID))
+ mock.ExpectCommit()
+
err = repo.CreatePlace(context.Background(), ad, newAd, user.UUID)
require.NoError(t, err)
@@ -556,7 +832,12 @@ func TestCreatePlace_CityNotFound(t *testing.T) {
if err := logger.InitLoggers(); err != nil {
log.Fatalf("Failed to initialize loggers: %v", err)
}
- defer logger.SyncLoggers()
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
db, mock, err := setupDBMock()
assert.Nil(t, err)
@@ -606,7 +887,12 @@ func TestCreatePlace_UserNotHost(t *testing.T) {
if err := logger.InitLoggers(); err != nil {
log.Fatalf("Failed to initialize loggers: %v", err)
}
- defer logger.SyncLoggers()
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
db, mock, err := setupDBMock()
assert.Nil(t, err)
@@ -649,93 +935,16 @@ func TestCreatePlace_UserNotHost(t *testing.T) {
}
}
-func TestSavePlace_Success(t *testing.T) {
- if err := logger.InitLoggers(); err != nil {
- log.Fatalf("Failed to initialize loggers: %v", err)
- }
- defer logger.SyncLoggers()
-
- db, mock, err := setupDBMock()
- assert.Nil(t, err)
-
- adRepo := NewAdRepository(db)
- ctx := context.Background()
-
- ad := &domain.Ad{
- UUID: "ad-uuid-123",
- CityID: 1,
- AuthorUUID: "author-uuid-456",
- Address: "123 Main St",
- Description: "Updated description",
- RoomsNumber: 4,
- ViewsCount: 2,
- PublicationDate: time.Now(),
- }
-
- // Mock update ad
- mock.ExpectBegin()
- mock.ExpectExec(regexp.QuoteMeta(`UPDATE "ads" SET "cityId"=$1,"authorUUID"=$2,"address"=$3,"publicationDate"=$4,"description"=$5,"roomsNumber"=$6,"viewsCount"=$7 WHERE "uuid" = $8`)).
- WithArgs(ad.CityID, ad.AuthorUUID, ad.Address, time.Now(), ad.Description, ad.RoomsNumber, ad.ViewsCount, ad.UUID).
- WillReturnResult(sqlmock.NewResult(1, 1))
- mock.ExpectCommit()
-
- err = adRepo.SavePlace(ctx, ad)
-
- if err != nil {
- t.Errorf("Expected no error, got %v", err)
- }
-
- if err := mock.ExpectationsWereMet(); err != nil {
- t.Errorf("Unmet expectations: %v", err)
- }
-}
-
-func TestSavePlace_Failure(t *testing.T) {
- if err := logger.InitLoggers(); err != nil {
- log.Fatalf("Failed to initialize loggers: %v", err)
- }
- defer logger.SyncLoggers()
-
- db, mock, err := setupDBMock()
- assert.Nil(t, err)
-
- adRepo := NewAdRepository(db)
- ctx := context.Background()
-
- ad := &domain.Ad{
- UUID: "ad-uuid-123",
- CityID: 1,
- AuthorUUID: "author-uuid-456",
- Address: "123 Main St",
- Description: "Updated description",
- RoomsNumber: 4,
- ViewsCount: 2,
- PublicationDate: time.Now(),
- }
-
- // Mock update ad with error
- mock.ExpectBegin()
- mock.ExpectExec(regexp.QuoteMeta(`UPDATE "ads" SET "cityId"=$1,"authorUUID"=$2,"address"=$3,"publicationDate"=$4,"description"=$5,"roomsNumber"=$6,"viewsCount"=$7 WHERE "uuid" = $8`)).
- WithArgs(ad.CityID, ad.AuthorUUID, ad.Address, time.Now(), ad.Description, ad.RoomsNumber, ad.ViewsCount, ad.UUID).
- WillReturnError(gorm.ErrInvalidData)
- mock.ExpectRollback()
-
- err = adRepo.SavePlace(ctx, ad)
-
- if err == nil {
- t.Errorf("Expected error, got none")
- }
-
- if err := mock.ExpectationsWereMet(); err != nil {
- t.Errorf("Unmet expectations: %v", err)
- }
-}
-
func TestGetPlacesPerCity_Success(t *testing.T) {
if err := logger.InitLoggers(); err != nil {
log.Fatalf("Failed to initialize loggers: %v", err)
}
- defer logger.SyncLoggers()
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
db, mock, err := setupDBMock()
assert.Nil(t, err)
@@ -767,6 +976,10 @@ func TestGetPlacesPerCity_Success(t *testing.T) {
AddRow("some-uuid", "test_username", "some_password", "test@example.com", "test_username")
mock.ExpectQuery(regexp.QuoteMeta(query2)).WillReturnRows(rows2)
+ adQuery := "SELECT * FROM \"ad_rooms\" WHERE \"adId\" = $1"
+ addRows := sqlmock.NewRows([]string{"id", "adId", "type", "squaremeters"}).AddRow(1, "id1", "some-type", 12)
+ mock.ExpectQuery(regexp.QuoteMeta(adQuery)).WithArgs("ad-uuid-123").WillReturnRows(addRows)
+
ads, err := adRepo.GetPlacesPerCity(ctx, city)
if err != nil {
@@ -786,7 +999,12 @@ func TestGetPlacesPerCity_Failure(t *testing.T) {
if err := logger.InitLoggers(); err != nil {
log.Fatalf("Failed to initialize loggers: %v", err)
}
- defer logger.SyncLoggers()
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
db, mock, err := setupDBMock()
assert.Nil(t, err)
@@ -820,7 +1038,12 @@ func TestSaveImages_Success(t *testing.T) {
if err := logger.InitLoggers(); err != nil {
log.Fatalf("Failed to initialize loggers: %v", err)
}
- defer logger.SyncLoggers()
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
db, mock, err := setupDBMock()
assert.Nil(t, err)
@@ -854,7 +1077,12 @@ func TestSaveImages_Failure(t *testing.T) {
if err := logger.InitLoggers(); err != nil {
log.Fatalf("Failed to initialize loggers: %v", err)
}
- defer logger.SyncLoggers()
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
db, mock, err := setupDBMock()
assert.Nil(t, err)
@@ -893,7 +1121,12 @@ func TestGetAdImages_Success(t *testing.T) {
if err := logger.InitLoggers(); err != nil {
log.Fatalf("Failed to initialize loggers: %v", err)
}
- defer logger.SyncLoggers()
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
db, mock, err := setupDBMock()
assert.Nil(t, err)
@@ -931,7 +1164,12 @@ func TestGetAdImages_Failure(t *testing.T) {
if err := logger.InitLoggers(); err != nil {
log.Fatalf("Failed to initialize loggers: %v", err)
}
- defer logger.SyncLoggers()
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
db, mock, err := setupDBMock()
assert.Nil(t, err)
@@ -965,7 +1203,12 @@ func TestGetUserPlaces_Success(t *testing.T) {
if err := logger.InitLoggers(); err != nil {
log.Fatalf("Failed to initialize loggers: %v", err)
}
- defer logger.SyncLoggers()
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
db, mock, err := setupDBMock()
assert.Nil(t, err)
@@ -997,6 +1240,10 @@ func TestGetUserPlaces_Success(t *testing.T) {
WithArgs("ad-uuid-123").
WillReturnRows(imageRows)
+ adQuery := "SELECT * FROM \"ad_rooms\" WHERE \"adId\" = $1"
+ addRows := sqlmock.NewRows([]string{"id", "adId", "type", "squaremeters"}).AddRow(1, "id1", "some-type", 12)
+ mock.ExpectQuery(regexp.QuoteMeta(adQuery)).WithArgs("ad-uuid-123").WillReturnRows(addRows)
+
ads, err := adRepo.GetUserPlaces(ctx, userId)
assert.NoError(t, err)
if err != nil {
@@ -1020,7 +1267,12 @@ func TestGetUserPlaces_Failure(t *testing.T) {
if err := logger.InitLoggers(); err != nil {
log.Fatalf("Failed to initialize loggers: %v", err)
}
- defer logger.SyncLoggers()
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
db, mock, err := setupDBMock()
assert.Nil(t, err)
@@ -1063,11 +1315,448 @@ func TestGetUserPlaces_Failure(t *testing.T) {
}
}
+func TestAdRepository_AddToFavorites_Success(t *testing.T) {
+ if err := logger.InitLoggers(); err != nil {
+ log.Fatalf("Failed to initialize loggers: %v", err)
+ }
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
+
+ db, mock, err := setupDBMock()
+ assert.Nil(t, err)
+
+ repo := NewAdRepository(db)
+ ctx := context.WithValue(context.Background(), middleware.RequestIDKey, "test-request-id")
+
+ adID := "ad-uuid-123"
+ userID := "user-uuid-456"
+
+ mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "ads" WHERE uuid = $1 ORDER BY "ads"."uuid" LIMIT $2`)).
+ WithArgs(adID, 1).
+ WillReturnRows(sqlmock.NewRows([]string{"uuid"}).AddRow(adID))
+
+ mock.ExpectBegin()
+ mock.ExpectExec(regexp.QuoteMeta(`INSERT INTO "favorites" ("adId","userId") VALUES ($1,$2)`)).
+ WithArgs(adID, userID).
+ WillReturnResult(sqlmock.NewResult(1, 1))
+ mock.ExpectCommit()
+
+ err = repo.AddToFavorites(ctx, adID, userID)
+
+ assert.NoError(t, err, "AddToFavorites should succeed without error")
+ assert.NoError(t, mock.ExpectationsWereMet(), "All database calls should be met")
+}
+
+func TestAdRepository_AddToFavorites_AdNotFound(t *testing.T) {
+ if err := logger.InitLoggers(); err != nil {
+ log.Fatalf("Failed to initialize loggers: %v", err)
+ }
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
+
+ db, mock, err := setupDBMock()
+ assert.Nil(t, err)
+
+ repo := NewAdRepository(db)
+ ctx := context.WithValue(context.Background(), middleware.RequestIDKey, "test-request-id")
+
+ adID := "ad-uuid-123"
+ userID := "user-uuid-456"
+
+ mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "ads" WHERE uuid = $1 ORDER BY "ads"."uuid" LIMIT $2`)).
+ WithArgs(adID, 1).
+ WillReturnError(gorm.ErrRecordNotFound)
+
+ err = repo.AddToFavorites(ctx, adID, userID)
+
+ assert.Error(t, err)
+ assert.Equal(t, "ad not found", err.Error())
+ assert.NoError(t, mock.ExpectationsWereMet(), "All database calls should be met")
+}
+
+func TestAdRepository_AddToFavorites_ErrorOnCreateFavorite(t *testing.T) {
+ if err := logger.InitLoggers(); err != nil {
+ log.Fatalf("Failed to initialize loggers: %v", err)
+ }
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
+
+ db, mock, err := setupDBMock()
+ assert.Nil(t, err)
+
+ repo := NewAdRepository(db)
+ ctx := context.WithValue(context.Background(), middleware.RequestIDKey, "test-request-id")
+
+ adID := "ad-uuid-123"
+ userID := "user-uuid-456"
+
+ mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "ads" WHERE uuid = $1 ORDER BY "ads"."uuid" LIMIT $2`)).
+ WithArgs(adID, 1).
+ WillReturnRows(sqlmock.NewRows([]string{"uuid"}).AddRow(adID))
+
+ mock.ExpectBegin()
+ mock.ExpectExec(regexp.QuoteMeta(`INSERT INTO "favorites" ("adId","userId") VALUES ($1,$2)`)).
+ WithArgs(adID, userID).
+ WillReturnError(errors.New("error creating favorite"))
+ mock.ExpectRollback()
+
+ err = repo.AddToFavorites(ctx, adID, userID)
+
+ assert.Error(t, err)
+ assert.Equal(t, "error create favorite", err.Error())
+ assert.NoError(t, mock.ExpectationsWereMet(), "All database calls should be met")
+}
+
+func TestAdRepository_DeleteFromFavorites_Success(t *testing.T) {
+ if err := logger.InitLoggers(); err != nil {
+ log.Fatalf("Failed to initialize loggers: %v", err)
+ }
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
+
+ db, mock, err := setupDBMock()
+ assert.Nil(t, err)
+
+ repo := NewAdRepository(db)
+ ctx := context.WithValue(context.Background(), middleware.RequestIDKey, "test-request-id")
+
+ adID := "ad-uuid-123"
+ userID := "user-uuid-456"
+
+ mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "ads" WHERE uuid = $1 ORDER BY "ads"."uuid" LIMIT $2`)).
+ WithArgs(adID, 1).
+ WillReturnRows(sqlmock.NewRows([]string{"uuid"}).AddRow(adID))
+
+ mock.ExpectBegin()
+ mock.ExpectExec(regexp.QuoteMeta(`DELETE FROM "favorites" WHERE ("favorites"."adId","favorites"."userId") IN (($1,$2))`)).
+ WithArgs(adID, userID).
+ WillReturnResult(sqlmock.NewResult(1, 1))
+ mock.ExpectCommit()
+
+ err = repo.DeleteFromFavorites(ctx, adID, userID)
+
+ assert.NoError(t, err)
+ assert.NoError(t, mock.ExpectationsWereMet())
+}
+
+func TestAdRepository_DeleteFromFavorites_AdNotFound(t *testing.T) {
+ if err := logger.InitLoggers(); err != nil {
+ log.Fatalf("Failed to initialize loggers: %v", err)
+ }
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
+
+ db, mock, err := setupDBMock()
+ assert.Nil(t, err)
+
+ repo := NewAdRepository(db)
+ ctx := context.WithValue(context.Background(), middleware.RequestIDKey, "test-request-id")
+
+ adID := "ad-uuid-123"
+ userID := "user-uuid-456"
+
+ mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "ads" WHERE uuid = $1 ORDER BY "ads"."uuid" LIMIT $2`)).
+ WithArgs(adID, 1).
+ WillReturnError(gorm.ErrRecordNotFound)
+
+ err = repo.DeleteFromFavorites(ctx, adID, userID)
+
+ assert.Error(t, err)
+ assert.Equal(t, "ad not found", err.Error())
+ assert.NoError(t, mock.ExpectationsWereMet())
+}
+
+func TestAdRepository_DeleteFromFavorites_DeleteError(t *testing.T) {
+ if err := logger.InitLoggers(); err != nil {
+ log.Fatalf("Failed to initialize loggers: %v", err)
+ }
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
+
+ db, mock, err := setupDBMock()
+ assert.Nil(t, err)
+
+ repo := NewAdRepository(db)
+ ctx := context.WithValue(context.Background(), middleware.RequestIDKey, "test-request-id")
+
+ adID := "ad-uuid-123"
+ userID := "user-uuid-456"
+
+ mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "ads" WHERE uuid = $1 ORDER BY "ads"."uuid" LIMIT $2`)).
+ WithArgs(adID, 1).
+ WillReturnRows(sqlmock.NewRows([]string{"uuid"}).AddRow(adID))
+
+ mock.ExpectBegin()
+ mock.ExpectExec(regexp.QuoteMeta(`DELETE FROM "favorites" WHERE ("favorites"."adId","favorites"."userId") IN (($1,$2))`)).
+ WithArgs(adID, userID).
+ WillReturnError(errors.New("delete error"))
+ mock.ExpectRollback()
+
+ err = repo.DeleteFromFavorites(ctx, adID, userID)
+
+ assert.Error(t, err)
+ assert.Equal(t, "error create favorite", err.Error())
+ assert.NoError(t, mock.ExpectationsWereMet())
+}
+
+func TestGetUserFavorites(t *testing.T) {
+ if err := logger.InitLoggers(); err != nil {
+ log.Fatalf("Failed to initialize loggers: %v", err)
+ }
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
+
+ db, mock, err := setupDBMock()
+ require.NoError(t, err)
+
+ repo := NewAdRepository(db)
+ userId := "user-uuid-123"
+
+ // Фиксированная дата для тестов
+ fixedDate := time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC)
+
+ // Основной запрос на выборку избранных объявлений
+ query := `
+ SELECT ads.*, favorites."userId" AS "FavoriteUserId", cities.title as "CityName", ad_available_dates."availableDateFrom" as "AdDateFrom", ad_available_dates."availableDateTo" as "AdDateTo"
+ FROM "ads"
+ JOIN favorites ON favorites."adId" = ads.uuid
+ JOIN cities ON ads."cityId" = cities.id
+ JOIN ad_available_dates ON ad_available_dates."adId" = ads.uuid
+ WHERE favorites."userId" = $1
+ `
+
+ adRows := sqlmock.NewRows([]string{
+ "uuid", "cityId", "authorUUID", "address", "publicationDate", "description", "roomsNumber", "viewsCount",
+ "FavoriteUserId", "CityName", "AdDateFrom", "AdDateTo",
+ }).
+ AddRow("ad-uuid-123", 1, "author-uuid-456", "Test Address", fixedDate, "Test Description", 3, 10,
+ "user-uuid-123", "CityName", fixedDate, fixedDate)
+
+ mock.ExpectQuery(regexp.QuoteMeta(query)).
+ WithArgs(userId).
+ WillReturnRows(adRows)
+
+ // Запрос на получение картинок
+ imagesQuery := `SELECT * FROM "images" WHERE "adId" = $1`
+ imageRows := sqlmock.NewRows([]string{"id", "adId", "imageUrl"}).
+ AddRow(1, "ad-uuid-123", "images/image1.jpg").
+ AddRow(2, "ad-uuid-123", "images/image2.jpg")
+ mock.ExpectQuery(regexp.QuoteMeta(imagesQuery)).
+ WithArgs("ad-uuid-123").
+ WillReturnRows(imageRows)
+
+ // Запрос на получение данных пользователя
+ userQuery := `SELECT * FROM "users" WHERE uuid = $1`
+ userRows := sqlmock.NewRows([]string{
+ "uuid", "name", "avatar", "score", "sex", "guestCount", "birthdate",
+ }).
+ AddRow("author-uuid-456", "Test User", "avatar_url", 4.8, "F", 5, fixedDate)
+ mock.ExpectQuery(regexp.QuoteMeta(userQuery)).
+ WithArgs("author-uuid-456").
+ WillReturnRows(userRows)
+
+ // Запрос на получение комнат
+ roomsQuery := `SELECT * FROM "ad_rooms" WHERE "adId" = $1`
+ roomRows := sqlmock.NewRows([]string{"id", "adId", "type", "squareMeters"}).
+ AddRow(1, "ad-uuid-123", "Bedroom", 25).
+ AddRow(2, "ad-uuid-123", "Living Room", 40)
+ mock.ExpectQuery(regexp.QuoteMeta(roomsQuery)).
+ WithArgs("ad-uuid-123").
+ WillReturnRows(roomRows)
+
+ // Вызов метода
+ ctx := context.WithValue(context.Background(), middleware.RequestIDKey, "test-request-id")
+ ads, err := repo.GetUserFavorites(ctx, userId)
+
+ // Проверка результата
+ require.NoError(t, err)
+ assert.Len(t, ads, 1)
+ assert.Equal(t, "ad-uuid-123", ads[0].UUID)
+ assert.Equal(t, "Test Address", ads[0].Address)
+ assert.Equal(t, "CityName", ads[0].CityName)
+ assert.Equal(t, fixedDate, ads[0].AdDateFrom)
+ assert.Equal(t, 3, ads[0].RoomsNumber)
+
+ assert.ElementsMatch(t, []domain.ImageResponse{
+ {ID: 1, ImagePath: "images/image1.jpg"},
+ {ID: 2, ImagePath: "images/image2.jpg"},
+ }, ads[0].Images)
+
+ assert.Equal(t, domain.UserResponce{
+ Name: "Test User",
+ Avatar: "avatar_url",
+ Rating: 4.8,
+ GuestCount: 5,
+ Sex: "F",
+ Birthdate: fixedDate,
+ }, ads[0].AdAuthor)
+
+ assert.ElementsMatch(t, []domain.AdRoomsResponse{
+ {Type: "Bedroom", SquareMeters: 25},
+ {Type: "Living Room", SquareMeters: 40},
+ }, ads[0].Rooms)
+
+ // Убедиться, что все ожидания выполнены
+ require.NoError(t, mock.ExpectationsWereMet())
+}
+
+func TestGetUserFavoritesFail(t *testing.T) {
+ if err := logger.InitLoggers(); err != nil {
+ log.Fatalf("Failed to initialize loggers: %v", err)
+ }
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
+ db, mock, err := setupDBMock()
+ assert.Nil(t, err)
+
+ userId := "12345"
+
+ repo := NewAdRepository(db)
+ query := `
+ SELECT ads.*, favorites."userId" AS "FavoriteUserId", cities.title as "CityName", ad_available_dates."availableDateFrom" as "AdDateFrom", ad_available_dates."availableDateTo" as "AdDateTo"
+ FROM "ads"
+ JOIN favorites ON favorites."adId" = ads.uuid
+ JOIN cities ON ads."cityId" = cities.id
+ JOIN ad_available_dates ON ad_available_dates."adId" = ads.uuid
+ WHERE favorites."userId" = $1
+ `
+ mock.ExpectQuery(regexp.QuoteMeta(query)).
+ WithArgs(userId).
+ WillReturnError(errors.New("db error"))
+
+ ctx := context.WithValue(context.Background(), middleware.RequestIDKey, "test-request-id")
+ ads, err := repo.GetUserFavorites(ctx, userId)
+ assert.Error(t, err)
+ assert.Nil(t, ads)
+}
+
+func TestUpdatePriority(t *testing.T) {
+ if err := logger.InitLoggers(); err != nil {
+ log.Fatalf("Failed to initialize loggers: %v", err)
+ }
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
+
+ db, mock, err := setupDBMock()
+ require.NoError(t, err)
+
+ repo := NewAdRepository(db)
+
+ adId := "ad-uuid-123"
+ userId := "user-uuid-456"
+ amount := 5
+ now := time.Now()
+
+ selectAdQuery := `SELECT * FROM "ads" WHERE uuid = $1 ORDER BY "ads"."uuid" LIMIT $2`
+ adRow := sqlmock.NewRows([]string{"uuid", "authorUUID", "priority", "endBoostDate"}).
+ AddRow(adId, userId, 1, now)
+
+ mock.ExpectQuery(regexp.QuoteMeta(selectAdQuery)).
+ WithArgs(adId, 1).
+ WillReturnRows(adRow)
+
+ mock.ExpectBegin()
+ updatePriorityQuery := `UPDATE "ads" SET "priority"=$1 WHERE "uuid" = $2`
+ mock.ExpectExec(regexp.QuoteMeta(updatePriorityQuery)).
+ WithArgs(amount, adId).
+ WillReturnResult(sqlmock.NewResult(1, 1))
+ mock.ExpectCommit()
+
+ mock.ExpectBegin()
+ updateBoostDateQuery := `UPDATE "ads" SET "endBoostDate"=$1 WHERE "uuid" = $2`
+ mock.ExpectExec(regexp.QuoteMeta(updateBoostDateQuery)).
+ WithArgs(sqlmock.AnyArg(), adId).
+ WillReturnResult(sqlmock.NewResult(1, 1))
+ mock.ExpectCommit()
+
+ ctx := context.WithValue(context.Background(), middleware.RequestIDKey, "test-request-id")
+ err = repo.UpdatePriority(ctx, adId, userId, amount)
+
+ require.NoError(t, err)
+
+ require.NoError(t, mock.ExpectationsWereMet())
+}
+
+func TestResetExpiredPriorities(t *testing.T) {
+ if err := logger.InitLoggers(); err != nil {
+ log.Fatalf("Failed to initialize loggers: %v", err)
+ }
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
+
+ db, mock, err := setupDBMock()
+ require.NoError(t, err)
+
+ repo := NewAdRepository(db)
+
+ updateQuery := `
+ UPDATE "ads" SET "endBoostDate"=$1,"priority"=$2 WHERE "endBoostDate" <= $3
+ `
+
+ mock.ExpectBegin()
+ mock.ExpectExec(regexp.QuoteMeta(updateQuery)).
+ WithArgs(nil, 0, sqlmock.AnyArg()).
+ WillReturnResult(sqlmock.NewResult(0, 5))
+ mock.ExpectCommit()
+
+ ctx := context.WithValue(context.Background(), middleware.RequestIDKey, "test-request-id")
+ err = repo.ResetExpiredPriorities(ctx)
+
+ require.NoError(t, err)
+
+ require.NoError(t, mock.ExpectationsWereMet())
+}
+
func TestDeleteAdImage(t *testing.T) {
if err := logger.InitLoggers(); err != nil {
log.Fatalf("Failed to initialize loggers: %v", err)
}
- defer logger.SyncLoggers()
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
db, mock, err := setupDBMock()
assert.Nil(t, err)
diff --git a/microservices/ads_service/usecase/ads_usecase.go b/microservices/ads_service/usecase/ads_usecase.go
index f2b5a96..86270be 100644
--- a/microservices/ads_service/usecase/ads_usecase.go
+++ b/microservices/ads_service/usecase/ads_usecase.go
@@ -12,13 +12,14 @@ import (
"net/http"
"regexp"
"strconv"
+ "time"
"context"
"errors"
)
type AdUseCase interface {
- GetAllPlaces(ctx context.Context, filter domain.AdFilter) ([]domain.GetAllAdsResponse, error)
+ GetAllPlaces(ctx context.Context, filter domain.AdFilter, userId string) ([]domain.GetAllAdsResponse, error)
GetOnePlace(ctx context.Context, adId string, isAuthorized bool) (domain.GetAllAdsResponse, error)
CreatePlace(ctx context.Context, place *domain.Ad, fileHeader [][]byte, newPlace domain.CreateAdRequest, userId string) error
UpdatePlace(ctx context.Context, place *domain.Ad, adId string, userId string, fileHeader [][]byte, updatedPlace domain.UpdateAdRequest) error
@@ -29,6 +30,8 @@ type AdUseCase interface {
AddToFavorites(ctx context.Context, adId string, userId string) error
DeleteFromFavorites(ctx context.Context, adId string, userId string) error
GetUserFavorites(ctx context.Context, userId string) ([]domain.GetAllAdsResponse, error)
+ UpdatePriority(ctx context.Context, adId string, userId string, amount int) error
+ StartPriorityResetWorker(ctx context.Context, tickerInterval time.Duration)
}
type adUseCase struct {
@@ -43,8 +46,8 @@ func NewAdUseCase(adRepository domain.AdRepository, minioService images.MinioSer
}
}
-func (uc *adUseCase) GetAllPlaces(ctx context.Context, filter domain.AdFilter) ([]domain.GetAllAdsResponse, error) {
- ads, err := uc.adRepository.GetAllPlaces(ctx, filter)
+func (uc *adUseCase) GetAllPlaces(ctx context.Context, filter domain.AdFilter, userId string) ([]domain.GetAllAdsResponse, error) {
+ ads, err := uc.adRepository.GetAllPlaces(ctx, filter, userId)
if err != nil {
return nil, err
}
@@ -81,7 +84,7 @@ func (uc *adUseCase) GetOnePlace(ctx context.Context, adId string, isAuthorized
}
func (uc *adUseCase) CreatePlace(ctx context.Context, place *domain.Ad, files [][]byte, newPlace domain.CreateAdRequest, userId string) error {
- const maxLen = 255
+ const maxLen = 1000
requestID := middleware.GetRequestID(ctx)
validCharPattern := regexp.MustCompile(`^[a-zA-Zа-яА-Я0-9@.,\s\-!?&;#()/$*^%+=|]*$`)
@@ -100,17 +103,14 @@ func (uc *adUseCase) CreatePlace(ctx context.Context, place *domain.Ad, files []
const minRooms, maxRooms = 1, 100
if newPlace.RoomsNumber < minRooms || newPlace.RoomsNumber > maxRooms {
logger.AccessLogger.Warn("RoomsNumber out of range", zap.String("request_id", requestID))
- return errors.New("RoomsNumber out of range")
+ return errors.New("roomsNumber out of range")
}
if err := validation.ValidateImages(files, 5<<20, []string{"image/jpeg", "image/png", "image/jpg"}, 2000, 2000); err != nil {
logger.AccessLogger.Warn("Invalid image", zap.String("request_id", requestID), zap.Error(err))
- return errors.New("invalid size, type or resolution of image")
+ return err
}
- place.Description = newPlace.Description
- place.Address = newPlace.Address
- place.RoomsNumber = newPlace.RoomsNumber
err := uc.adRepository.CreatePlace(ctx, place, newPlace, userId)
if err != nil {
return err
@@ -142,12 +142,12 @@ func (uc *adUseCase) CreatePlace(ctx context.Context, place *domain.Ad, files []
func (uc *adUseCase) UpdatePlace(ctx context.Context, place *domain.Ad, adId string, userId string, files [][]byte, updatedPlace domain.UpdateAdRequest) error {
requestID := middleware.GetRequestID(ctx)
- const maxLen = 255
+ const maxLen = 1000
if len(files) > 0 {
if err := validation.ValidateImages(files, 5<<20, []string{"image/jpeg", "image/png", "image/jpg"}, 2000, 2000); err != nil {
logger.AccessLogger.Warn("Invalid image", zap.String("request_id", requestID), zap.Error(err))
- return errors.New("invalid size, type or resolution of image")
+ return err
}
}
@@ -178,16 +178,14 @@ func (uc *adUseCase) UpdatePlace(ctx context.Context, place *domain.Ad, adId str
const minRooms, maxRooms = 1, 100
if updatedPlace.RoomsNumber < minRooms || updatedPlace.RoomsNumber > maxRooms {
logger.AccessLogger.Warn("RoomsNumber out of range", zap.String("request_id", requestID))
- return errors.New("RoomsNumber out of range")
+ return errors.New("roomsNumber out of range")
}
_, err := uc.adRepository.GetPlaceById(ctx, adId)
if err != nil {
return err
}
- place.Description = updatedPlace.Description
- place.Address = updatedPlace.Address
- place.RoomsNumber = updatedPlace.RoomsNumber
+
var newUploadedPaths ntype.StringArray
for _, file := range files {
@@ -327,61 +325,70 @@ func (uc *adUseCase) DeleteAdImage(ctx context.Context, adId string, imageId str
func (uc *adUseCase) AddToFavorites(ctx context.Context, adId string, userId string) error {
requestID := middleware.GetRequestID(ctx)
const maxLen = 255
- validCharPattern := regexp.MustCompile(`^[a-zA-Zа-яА-ЯёЁ0-9\s\-_]*$`)
- if !validCharPattern.MatchString(adId) {
- logger.AccessLogger.Warn("Input contains invalid characters", zap.String("request_id", requestID))
- return errors.New("input contains invalid characters")
- }
if len(adId) > maxLen {
logger.AccessLogger.Warn("Input exceeds character limit", zap.String("request_id", requestID))
return errors.New("input exceeds character limit")
}
+ validCharPattern := regexp.MustCompile(`^[a-zA-Zа-яА-ЯёЁ0-9\s\-_]*$`)
+ if !validCharPattern.MatchString(adId) {
+ logger.AccessLogger.Warn("Input contains invalid characters", zap.String("request_id", requestID))
+ return errors.New("input contains invalid characters")
+ }
+
err := uc.adRepository.AddToFavorites(ctx, adId, userId)
if err != nil {
return err
}
-
+ err = uc.adRepository.UpdateFavoritesCount(ctx, adId)
+ if err != nil {
+ return err
+ }
return nil
}
func (uc *adUseCase) DeleteFromFavorites(ctx context.Context, adId string, userId string) error {
requestID := middleware.GetRequestID(ctx)
const maxLen = 255
- validCharPattern := regexp.MustCompile(`^[a-zA-Zа-яА-ЯёЁ0-9\s\-_]*$`)
- if !validCharPattern.MatchString(adId) {
- logger.AccessLogger.Warn("Input contains invalid characters", zap.String("request_id", requestID))
- return errors.New("input contains invalid characters")
- }
if len(adId) > maxLen {
logger.AccessLogger.Warn("Input exceeds character limit", zap.String("request_id", requestID))
return errors.New("input exceeds character limit")
}
+ validCharPattern := regexp.MustCompile(`^[a-zA-Zа-яА-ЯёЁ0-9\s\-_]*$`)
+ if !validCharPattern.MatchString(adId) {
+ logger.AccessLogger.Warn("Input contains invalid characters", zap.String("request_id", requestID))
+ return errors.New("input contains invalid characters")
+ }
+
err := uc.adRepository.DeleteFromFavorites(ctx, adId, userId)
if err != nil {
return err
}
-
+ err = uc.adRepository.UpdateFavoritesCount(ctx, adId)
+ if err != nil {
+ return err
+ }
return nil
}
func (uc *adUseCase) GetUserFavorites(ctx context.Context, userId string) ([]domain.GetAllAdsResponse, error) {
requestID := middleware.GetRequestID(ctx)
const maxLen = 255
- validCharPattern := regexp.MustCompile(`^[a-zA-Zа-яА-ЯёЁ0-9\s\-_]*$`)
- if !validCharPattern.MatchString(userId) {
- logger.AccessLogger.Warn("Input contains invalid characters", zap.String("request_id", requestID))
- return nil, errors.New("input contains invalid characters")
- }
if len(userId) > maxLen {
logger.AccessLogger.Warn("Input exceeds character limit", zap.String("request_id", requestID))
return nil, errors.New("input exceeds character limit")
}
+ validCharPattern := regexp.MustCompile(`^[a-zA-Zа-яА-ЯёЁ0-9\s\-_]*$`)
+ if !validCharPattern.MatchString(userId) {
+ logger.AccessLogger.Warn("Input contains invalid characters", zap.String("request_id", requestID))
+ return nil, errors.New("input contains invalid characters")
+ }
+
places, err := uc.adRepository.GetUserFavorites(ctx, userId)
if err != nil {
return nil, err
@@ -389,3 +396,45 @@ func (uc *adUseCase) GetUserFavorites(ctx context.Context, userId string) ([]dom
return places, nil
}
+
+func (uc *adUseCase) UpdatePriority(ctx context.Context, adId string, userId string, amount int) error {
+ requestID := middleware.GetRequestID(ctx)
+ const maxLen = 255
+
+ if len(adId) > maxLen {
+ logger.AccessLogger.Warn("Input exceeds character limit", zap.String("request_id", requestID))
+ return errors.New("input exceeds character limit")
+ }
+
+ validCharPattern := regexp.MustCompile(`^[a-zA-Zа-яА-ЯёЁ0-9\s\-_]*$`)
+ if !validCharPattern.MatchString(adId) {
+ logger.AccessLogger.Warn("Input contains invalid characters", zap.String("request_id", requestID))
+ return errors.New("input contains invalid characters")
+ }
+
+ err := uc.adRepository.UpdatePriority(ctx, adId, userId, amount)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func (uc *adUseCase) StartPriorityResetWorker(ctx context.Context, tickerInterval time.Duration) {
+ go func() {
+ ticker := time.NewTicker(tickerInterval) // Интервал передаётся извне
+ defer ticker.Stop()
+
+ for {
+ select {
+ case <-ctx.Done():
+ logger.AccessLogger.Info("Priority reset worker stopped")
+ return
+ case <-ticker.C:
+ logger.AccessLogger.Info("Priority reset worker started")
+ if err := uc.adRepository.ResetExpiredPriorities(ctx); err != nil {
+ logger.AccessLogger.Error("Failed to reset expired priorities", zap.Error(err))
+ }
+ }
+ }
+ }()
+}
diff --git a/microservices/ads_service/usecase/ads_usecase_test.go b/microservices/ads_service/usecase/ads_usecase_test.go
index 5e7f961..be2a2bc 100644
--- a/microservices/ads_service/usecase/ads_usecase_test.go
+++ b/microservices/ads_service/usecase/ads_usecase_test.go
@@ -2,16 +2,19 @@ package usecase
import (
"2024_2_FIGHT-CLUB/domain"
+ "2024_2_FIGHT-CLUB/internal/service/logger"
"2024_2_FIGHT-CLUB/microservices/ads_service/mocks"
"bytes"
"context"
"errors"
"github.com/stretchr/testify/assert"
+ "go.uber.org/zap"
"image"
"image/color"
"image/jpeg"
"math/rand"
"testing"
+ "time"
)
func TestAdUseCase_GetAllPlaces(t *testing.T) {
@@ -22,13 +25,14 @@ func TestAdUseCase_GetAllPlaces(t *testing.T) {
expectedAds := []domain.GetAllAdsResponse{
{UUID: "1234", CityID: 1, AuthorUUID: "user123"},
}
- mockRepo.MockGetAllPlaces = func(ctx context.Context, filter domain.AdFilter) ([]domain.GetAllAdsResponse, error) {
+ mockRepo.MockGetAllPlaces = func(ctx context.Context, filter domain.AdFilter, userId string) ([]domain.GetAllAdsResponse, error) {
return expectedAds, nil
}
ctx := context.Background()
filter := domain.AdFilter{Location: "New York"}
- ads, err := useCase.GetAllPlaces(ctx, filter)
+ userId := "user123"
+ ads, err := useCase.GetAllPlaces(ctx, filter, userId)
assert.NoError(t, err)
assert.Equal(t, expectedAds, ads)
@@ -242,13 +246,14 @@ func TestAdUseCase_GetAllPlaces_Error(t *testing.T) {
mockMinioService := &mocks.MockMinioService{}
useCase := NewAdUseCase(mockRepo, mockMinioService)
- mockRepo.MockGetAllPlaces = func(ctx context.Context, filter domain.AdFilter) ([]domain.GetAllAdsResponse, error) {
+ mockRepo.MockGetAllPlaces = func(ctx context.Context, filter domain.AdFilter, userId string) ([]domain.GetAllAdsResponse, error) {
return nil, errors.New("database error")
}
ctx := context.Background()
filter := domain.AdFilter{Location: "New York"}
- ads, err := useCase.GetAllPlaces(ctx, filter)
+ userId := "user123"
+ ads, err := useCase.GetAllPlaces(ctx, filter, userId)
assert.Error(t, err)
assert.Nil(t, ads)
@@ -369,6 +374,9 @@ func TestAdUseCase_UpdatePlace_ErrorOnUploadImage(t *testing.T) {
mockMinioService := &mocks.MockMinioService{}
useCase := NewAdUseCase(mockRepo, mockMinioService)
fileHeaders, err := createValidFileHeaders(3)
+ if err != nil {
+ return
+ }
adID := "invalid_ad_id"
userID := "user456"
newAd := domain.Ad{}
@@ -430,6 +438,215 @@ func TestAdUseCase_DeletePlace_ErrorOnGet(t *testing.T) {
assert.Equal(t, "ad not found", err.Error())
}
+func TestAdUseCase_AddToFavorites(t *testing.T) {
+ setupLogger()
+ mockRepo := &mocks.MockAdRepository{}
+ mockMinioService := &mocks.MockMinioService{}
+ useCase := NewAdUseCase(mockRepo, mockMinioService)
+
+ ctx := context.Background()
+ validAdID := "ad123"
+ invalidAdID := "ad!@#"
+ overMaxLenID := string(make([]rune, 256))
+ userID := "user123"
+
+ mockRepo.MockAddToFavorites = func(ctx context.Context, adId, userId string) error {
+ return nil
+ }
+ mockRepo.MockUpdateFavoritesCount = func(ctx context.Context, adId string) error {
+ return nil
+ }
+
+ err := useCase.AddToFavorites(ctx, validAdID, userID)
+ assert.NoError(t, err)
+
+ err = useCase.AddToFavorites(ctx, invalidAdID, userID)
+ assert.Error(t, err)
+ assert.Equal(t, "input contains invalid characters", err.Error())
+
+ err = useCase.AddToFavorites(ctx, overMaxLenID, userID)
+ assert.Error(t, err)
+ assert.Equal(t, "input exceeds character limit", err.Error())
+
+ mockRepo.MockAddToFavorites = func(ctx context.Context, adId, userId string) error {
+ return errors.New("repository error")
+ }
+ err = useCase.AddToFavorites(ctx, validAdID, userID)
+ assert.Error(t, err)
+ assert.Equal(t, "repository error", err.Error())
+
+ mockRepo.MockAddToFavorites = func(ctx context.Context, adId, userId string) error {
+ return nil
+ }
+ mockRepo.MockUpdateFavoritesCount = func(ctx context.Context, adId string) error {
+ return errors.New("update count error")
+ }
+
+ err = useCase.AddToFavorites(ctx, validAdID, userID)
+ assert.Error(t, err)
+ assert.Equal(t, "update count error", err.Error())
+}
+
+func TestAdUseCase_DeleteFromFavorites(t *testing.T) {
+ setupLogger()
+ mockRepo := &mocks.MockAdRepository{}
+ mockMinioService := &mocks.MockMinioService{}
+ useCase := NewAdUseCase(mockRepo, mockMinioService)
+
+ ctx := context.Background()
+ validAdID := "ad123"
+ invalidAdID := "ad!@#"
+ overMaxLenID := string(make([]rune, 256))
+ userID := "user123"
+
+ mockRepo.MockDeleteFromFavorites = func(ctx context.Context, adId, userId string) error {
+ return nil
+ }
+ mockRepo.MockUpdateFavoritesCount = func(ctx context.Context, adId string) error {
+ return nil
+ }
+
+ err := useCase.DeleteFromFavorites(ctx, validAdID, userID)
+ assert.NoError(t, err)
+
+ err = useCase.DeleteFromFavorites(ctx, invalidAdID, userID)
+ assert.Error(t, err)
+ assert.Equal(t, "input contains invalid characters", err.Error())
+
+ err = useCase.DeleteFromFavorites(ctx, overMaxLenID, userID)
+ assert.Error(t, err)
+ assert.Equal(t, "input exceeds character limit", err.Error())
+
+ mockRepo.MockDeleteFromFavorites = func(ctx context.Context, adId, userId string) error {
+ return errors.New("repository error")
+ }
+ err = useCase.DeleteFromFavorites(ctx, validAdID, userID)
+ assert.Error(t, err)
+ assert.Equal(t, "repository error", err.Error())
+
+ mockRepo.MockDeleteFromFavorites = func(ctx context.Context, adId, userId string) error {
+ return nil
+ }
+ mockRepo.MockUpdateFavoritesCount = func(ctx context.Context, adId string) error {
+ return errors.New("update count error")
+ }
+
+ err = useCase.DeleteFromFavorites(ctx, validAdID, userID)
+ assert.Error(t, err)
+ assert.Equal(t, "update count error", err.Error())
+}
+
+func TestAdUseCase_GetUserFavorites(t *testing.T) {
+ setupLogger()
+ mockRepo := &mocks.MockAdRepository{}
+ mockMinioService := &mocks.MockMinioService{}
+ useCase := NewAdUseCase(mockRepo, mockMinioService)
+
+ ctx := context.Background()
+ validUserID := "user123"
+ invalidUserID := "user!@#"
+ overMaxLenUserID := string(make([]rune, 256))
+
+ expectedAds := []domain.GetAllAdsResponse{
+ {UUID: "ad123", AuthorUUID: "user123"},
+ }
+
+ mockRepo.MockGetUserFavorites = func(ctx context.Context, userId string) ([]domain.GetAllAdsResponse, error) {
+ return expectedAds, nil
+ }
+
+ ads, err := useCase.GetUserFavorites(ctx, validUserID)
+ assert.NoError(t, err)
+ assert.Equal(t, expectedAds, ads)
+
+ ads, err = useCase.GetUserFavorites(ctx, invalidUserID)
+ assert.Error(t, err)
+ assert.Nil(t, ads)
+ assert.Equal(t, "input contains invalid characters", err.Error())
+
+ ads, err = useCase.GetUserFavorites(ctx, overMaxLenUserID)
+ assert.Error(t, err)
+ assert.Nil(t, ads)
+ assert.Equal(t, "input exceeds character limit", err.Error())
+
+ mockRepo.MockGetUserFavorites = func(ctx context.Context, userId string) ([]domain.GetAllAdsResponse, error) {
+ return nil, errors.New("repository error")
+ }
+
+ ads, err = useCase.GetUserFavorites(ctx, validUserID)
+ assert.Error(t, err)
+ assert.Nil(t, ads)
+ assert.Equal(t, "repository error", err.Error())
+}
+
+func TestAdUseCase_UpdatePriority(t *testing.T) {
+ setupLogger()
+
+ mockRepo := &mocks.MockAdRepository{}
+ mockMinioService := &mocks.MockMinioService{}
+ useCase := NewAdUseCase(mockRepo, mockMinioService)
+
+ ctx := context.Background()
+
+ t.Run("Error: invalid characters in adId", func(t *testing.T) {
+ invalidAdID := "invalid#ID" // Недопустимый символ #
+ userID := "user123"
+ amount := 5
+
+ err := useCase.UpdatePriority(ctx, invalidAdID, userID, amount)
+ assert.Error(t, err)
+ assert.Equal(t, "input contains invalid characters", err.Error())
+ })
+
+ t.Run("Error: adId exceeds max length", func(t *testing.T) {
+ overMaxLenID := string(make([]rune, 256)) // Строка длиной 256 символов
+ userID := "user123"
+ amount := 5
+
+ err := useCase.UpdatePriority(ctx, overMaxLenID, userID, amount)
+ assert.Error(t, err)
+ assert.Equal(t, "input exceeds character limit", err.Error())
+ })
+
+ t.Run("Success: valid input", func(t *testing.T) {
+ validAdID := "ad123"
+ userID := "user123"
+ amount := 10
+
+ mockRepo.MockUpdatePriority = func(ctx context.Context, adId, userId string, amount int) error {
+ return nil
+ }
+
+ err := useCase.UpdatePriority(ctx, validAdID, userID, amount)
+ assert.NoError(t, err)
+ })
+}
+
+func TestAdUseCase_StartPriorityResetWorker(t *testing.T) {
+ setupLogger()
+
+ mockRepo := &mocks.MockAdRepository{}
+ mockMinioService := &mocks.MockMinioService{}
+ useCase := NewAdUseCase(mockRepo, mockMinioService)
+
+ ctx, cancel := context.WithCancel(context.Background())
+ defer cancel()
+
+ resetCalled := false
+
+ mockRepo.MockResetExpiredPriorities = func(ctx context.Context) error {
+ resetCalled = true
+ return nil
+ }
+
+ useCase.StartPriorityResetWorker(ctx, 10*time.Millisecond)
+
+ time.Sleep(50 * time.Millisecond)
+ cancel()
+
+ assert.True(t, resetCalled, "ResetExpiredPriorities should be called")
+}
+
func TestDeleteAdImage(t *testing.T) {
ctx := context.Background()
adId := "ad-uuid"
@@ -549,3 +766,7 @@ func createValidFileHeaders(numFiles int) ([][]byte, error) {
return fileHeaders, nil
}
+
+func setupLogger() {
+ logger.AccessLogger = zap.NewNop()
+}
diff --git a/microservices/auth_service/Dockerfile b/microservices/auth_service/Dockerfile
new file mode 100644
index 0000000..3f12505
--- /dev/null
+++ b/microservices/auth_service/Dockerfile
@@ -0,0 +1,23 @@
+# 1. Build it
+
+FROM golang:1.23.1 AS builder
+WORKDIR /app
+# Копируем go.mod и go.sum
+COPY go.mod go.sum ./
+RUN go mod download
+
+# This microservice uses other modules so we can't just copy only it
+# Therefore we need to copy the whole fucking project
+# I have wasted 3 hours of my life on this
+# COPY ./microservices/auth_service/ ./microservices/auth_service/
+COPY . .
+ENV CGO_ENABLED=0
+ENV GOOS=linux
+RUN go build -o /microservices/auth_service/cmd/auth_service ./microservices/auth_service/cmd/main.go
+
+
+# 2. Run it
+FROM alpine:latest
+# WORKDIR /microservices/auth_service
+COPY --from=builder ./microservices/auth_service/cmd/auth_service /app/auth_service
+CMD ["/app/auth_service"]
\ No newline at end of file
diff --git a/microservices/auth_service/cmd/main.go b/microservices/auth_service/cmd/main.go
index fad9de3..735c3c3 100644
--- a/microservices/auth_service/cmd/main.go
+++ b/microservices/auth_service/cmd/main.go
@@ -64,7 +64,10 @@ func main() {
authServer := grpcAuth.NewGrpcAuthHandler(auUseCase, sessionService, jwtToken)
grpcServer := grpc.NewServer(
- grpc.UnaryInterceptor(middleware.UnaryMetricsInterceptor),
+ grpc.UnaryInterceptor(middleware.ChainUnaryInterceptors(
+ middleware.RecoveryInterceptor, // интерсептор для обработки паники
+ middleware.UnaryMetricsInterceptor, // интерсептор для метрик
+ )),
)
generatedAuth.RegisterAuthServer(grpcServer, authServer)
diff --git a/microservices/auth_service/controller/auth_grpc.go b/microservices/auth_service/controller/auth_grpc.go
index 4b46a51..84a1682 100644
--- a/microservices/auth_service/controller/auth_grpc.go
+++ b/microservices/auth_service/controller/auth_grpc.go
@@ -172,7 +172,7 @@ func (h *GrpcAuthHandler) LogoutUser(ctx context.Context, in *gen.LogoutRequest)
func (h *GrpcAuthHandler) PutUser(ctx context.Context, in *gen.PutUserRequest) (*gen.UpdateResponse, error) {
requestID := middleware.GetRequestID(ctx)
sanitizer := bluemonday.UGCPolicy()
- logger.AccessLogger.Info("Received LoginUser request in microservice",
+ logger.AccessLogger.Info("Received PutUser request in microservice",
zap.String("request_id", requestID))
if in.AuthHeader == "" {
@@ -311,23 +311,114 @@ func (h *GrpcAuthHandler) GetSessionData(ctx context.Context, in *gen.GetSession
}
data := *sessionData
- id, ok := data["id"].(string)
- if !ok {
- logger.AccessLogger.Warn("Invalid type for id in session data",
- zap.String("request_id", requestID))
- return nil, errors.New("invalid type for id in session data")
+ id := data.Id
+ avatar := data.Avatar
+ return &gen.SessionDataResponse{
+ Id: id,
+ Avatar: avatar,
+ }, nil
+}
+
+func (h *GrpcAuthHandler) UpdateUserRegions(ctx context.Context, in *gen.UpdateUserRegionsRequest) (*gen.UpdateResponse, error) {
+ requestID := middleware.GetRequestID(ctx)
+ sanitizer := bluemonday.UGCPolicy()
+ logger.AccessLogger.Info("Received UpdateUserRegions request in microservice",
+ zap.String("request_id", requestID))
+
+ if in.AuthHeader == "" {
+ logger.AccessLogger.Warn("Failed to X-CSRF-Token header",
+ zap.String("request_id", requestID),
+ zap.Error(errors.New("missing X-CSRF-Token header")),
+ )
+ return nil, errors.New("missing X-CSRF-Token header")
}
- avatar, ok := data["avatar"].(string)
- if !ok {
- logger.AccessLogger.Warn("Invalid type for avatar in session data",
- zap.String("request_id", requestID))
- return nil, errors.New("invalid type for avatar in session data")
+ tokenString := in.AuthHeader[len("Bearer "):]
+ _, err := h.jwtToken.Validate(tokenString, in.SessionId)
+ if err != nil {
+ logger.AccessLogger.Warn("Invalid JWT token", zap.String("request_id", requestID), zap.Error(err))
+ return nil, errors.New("invalid JWT token")
}
- return &gen.SessionDataResponse{
- Id: id,
- Avatar: avatar,
+ userID, err := h.sessionService.GetUserID(ctx, in.SessionId)
+ if err != nil {
+ logger.AccessLogger.Warn("Failed to get user ID from session",
+ zap.String("request_id", requestID),
+ zap.Error(err),
+ )
+ return nil, errors.New("failed to get user ID")
+ }
+
+ var domainRegions domain.UpdateUserRegion
+ domainRegions.RegionName = sanitizer.Sanitize(in.Region)
+ startVisitedDate := ""
+ if in.StartVisitDate != nil {
+ startVisitedDate = in.StartVisitDate.AsTime().Format("2006-01-02")
+ }
+ domainRegions.StartVisitedDate = startVisitedDate
+ endVisitedDate := ""
+ if in.EndVisitDate != nil {
+ endVisitedDate = in.EndVisitDate.AsTime().Format("2006-01-02")
+ }
+ domainRegions.EndVisitedDate = endVisitedDate
+
+ err = h.usecase.UpdateUserRegions(ctx, domainRegions, userID)
+ if err != nil {
+ logger.AccessLogger.Warn("Failed to put user",
+ zap.String("request_id", requestID),
+ zap.Error(err))
+ return nil, err
+ }
+ return &gen.UpdateResponse{
+ Response: "Success",
+ }, nil
+}
+
+func (h *GrpcAuthHandler) DeleteUserRegions(ctx context.Context, in *gen.DeleteUserRegionsRequest) (*gen.UpdateResponse, error) {
+ requestID := middleware.GetRequestID(ctx)
+ logger.AccessLogger.Info("Received DeleteUserRegion request in microservice",
+ zap.String("request_id", requestID))
+
+ if in.AuthHeader == "" {
+ logger.AccessLogger.Warn("Failed to X-CSRF-Token header",
+ zap.String("request_id", requestID),
+ zap.Error(errors.New("missing X-CSRF-Token header")),
+ )
+ return nil, errors.New("missing X-CSRF-Token header")
+ }
+
+ tokenString := in.AuthHeader[len("Bearer "):]
+ _, err := h.jwtToken.Validate(tokenString, in.SessionId)
+ if err != nil {
+ logger.AccessLogger.Warn("Invalid JWT token", zap.String("request_id", requestID), zap.Error(err))
+ return nil, errors.New("invalid JWT token")
+ }
+
+ userID, err := h.sessionService.GetUserID(ctx, in.SessionId)
+ if err != nil {
+ logger.AccessLogger.Warn("Failed to get user ID from session",
+ zap.String("request_id", requestID),
+ zap.Error(err),
+ )
+ return nil, errors.New("failed to get user ID")
+ }
+
+ err = h.usecase.DeleteUserRegion(ctx, in.Region, userID)
+ if err != nil {
+ logger.AccessLogger.Warn("Failed to put user",
+ zap.String("request_id", requestID),
+ zap.Error(err))
+ return nil, err
+ }
+
+ logger.AccessLogger.Info("Successfully deleted user region",
+ zap.String("request_id", requestID),
+ zap.String("userId", userID),
+ zap.String("region", in.Region),
+ )
+
+ return &gen.UpdateResponse{
+ Response: "Region successfully deleted",
}, nil
}
diff --git a/microservices/auth_service/controller/gen/auth.pb.go b/microservices/auth_service/controller/gen/auth.pb.go
index 190c4a7..8dc180e 100644
--- a/microservices/auth_service/controller/gen/auth.pb.go
+++ b/microservices/auth_service/controller/gen/auth.pb.go
@@ -1078,6 +1078,144 @@ func (x *RefreshCsrfTokenResponse) GetCsrfToken() string {
return ""
}
+type DeleteUserRegionsRequest struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ Region string `protobuf:"bytes,1,opt,name=Region,proto3" json:"Region,omitempty"`
+ AuthHeader string `protobuf:"bytes,2,opt,name=authHeader,proto3" json:"authHeader,omitempty"`
+ SessionId string `protobuf:"bytes,3,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"`
+}
+
+func (x *DeleteUserRegionsRequest) Reset() {
+ *x = DeleteUserRegionsRequest{}
+ mi := &file_auth_proto_msgTypes[18]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *DeleteUserRegionsRequest) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*DeleteUserRegionsRequest) ProtoMessage() {}
+
+func (x *DeleteUserRegionsRequest) ProtoReflect() protoreflect.Message {
+ mi := &file_auth_proto_msgTypes[18]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use DeleteUserRegionsRequest.ProtoReflect.Descriptor instead.
+func (*DeleteUserRegionsRequest) Descriptor() ([]byte, []int) {
+ return file_auth_proto_rawDescGZIP(), []int{18}
+}
+
+func (x *DeleteUserRegionsRequest) GetRegion() string {
+ if x != nil {
+ return x.Region
+ }
+ return ""
+}
+
+func (x *DeleteUserRegionsRequest) GetAuthHeader() string {
+ if x != nil {
+ return x.AuthHeader
+ }
+ return ""
+}
+
+func (x *DeleteUserRegionsRequest) GetSessionId() string {
+ if x != nil {
+ return x.SessionId
+ }
+ return ""
+}
+
+type UpdateUserRegionsRequest struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ Region string `protobuf:"bytes,1,opt,name=Region,proto3" json:"Region,omitempty"`
+ StartVisitDate *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=StartVisitDate,proto3" json:"StartVisitDate,omitempty"`
+ EndVisitDate *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=EndVisitDate,proto3" json:"EndVisitDate,omitempty"`
+ AuthHeader string `protobuf:"bytes,4,opt,name=authHeader,proto3" json:"authHeader,omitempty"`
+ SessionId string `protobuf:"bytes,5,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"`
+}
+
+func (x *UpdateUserRegionsRequest) Reset() {
+ *x = UpdateUserRegionsRequest{}
+ mi := &file_auth_proto_msgTypes[19]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *UpdateUserRegionsRequest) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*UpdateUserRegionsRequest) ProtoMessage() {}
+
+func (x *UpdateUserRegionsRequest) ProtoReflect() protoreflect.Message {
+ mi := &file_auth_proto_msgTypes[19]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use UpdateUserRegionsRequest.ProtoReflect.Descriptor instead.
+func (*UpdateUserRegionsRequest) Descriptor() ([]byte, []int) {
+ return file_auth_proto_rawDescGZIP(), []int{19}
+}
+
+func (x *UpdateUserRegionsRequest) GetRegion() string {
+ if x != nil {
+ return x.Region
+ }
+ return ""
+}
+
+func (x *UpdateUserRegionsRequest) GetStartVisitDate() *timestamppb.Timestamp {
+ if x != nil {
+ return x.StartVisitDate
+ }
+ return nil
+}
+
+func (x *UpdateUserRegionsRequest) GetEndVisitDate() *timestamppb.Timestamp {
+ if x != nil {
+ return x.EndVisitDate
+ }
+ return nil
+}
+
+func (x *UpdateUserRegionsRequest) GetAuthHeader() string {
+ if x != nil {
+ return x.AuthHeader
+ }
+ return ""
+}
+
+func (x *UpdateUserRegionsRequest) GetSessionId() string {
+ if x != nil {
+ return x.SessionId
+ }
+ return ""
+}
+
var File_auth_proto protoreflect.FileDescriptor
var file_auth_proto_rawDesc = []byte{
@@ -1191,44 +1329,76 @@ var file_auth_proto_rawDesc = []byte{
0x61, 0x72, 0x22, 0x39, 0x0a, 0x18, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x43, 0x73, 0x72,
0x66, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1d,
0x0a, 0x0a, 0x63, 0x73, 0x72, 0x66, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01,
- 0x28, 0x09, 0x52, 0x09, 0x63, 0x73, 0x72, 0x66, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x32, 0x87, 0x04,
- 0x0a, 0x04, 0x41, 0x75, 0x74, 0x68, 0x12, 0x3d, 0x0a, 0x0c, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74,
- 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x12, 0x19, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x52, 0x65,
- 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
- 0x74, 0x1a, 0x12, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73,
- 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x37, 0x0a, 0x09, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x55, 0x73,
- 0x65, 0x72, 0x12, 0x16, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x55,
- 0x73, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x61, 0x75, 0x74,
- 0x68, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3b,
- 0x0a, 0x0a, 0x4c, 0x6f, 0x67, 0x6f, 0x75, 0x74, 0x55, 0x73, 0x65, 0x72, 0x12, 0x13, 0x2e, 0x61,
- 0x75, 0x74, 0x68, 0x2e, 0x6c, 0x6f, 0x67, 0x6f, 0x75, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
- 0x74, 0x1a, 0x18, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x4c, 0x6f, 0x67, 0x6f, 0x75, 0x74, 0x55,
- 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x35, 0x0a, 0x07, 0x50,
- 0x75, 0x74, 0x55, 0x73, 0x65, 0x72, 0x12, 0x14, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x50, 0x75,
- 0x74, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x61,
- 0x75, 0x74, 0x68, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
- 0x73, 0x65, 0x12, 0x42, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x42, 0x79, 0x49,
- 0x64, 0x12, 0x18, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72,
- 0x42, 0x79, 0x49, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x61, 0x75,
- 0x74, 0x68, 0x2e, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x42, 0x79, 0x49, 0x64, 0x52, 0x65,
- 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x32, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x41, 0x6c, 0x6c,
- 0x55, 0x73, 0x65, 0x72, 0x73, 0x12, 0x0b, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x45, 0x6d, 0x70,
- 0x74, 0x79, 0x1a, 0x16, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x41, 0x6c, 0x6c, 0x55, 0x73, 0x65,
- 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x48, 0x0a, 0x0e, 0x47, 0x65,
- 0x74, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x44, 0x61, 0x74, 0x61, 0x12, 0x1b, 0x2e, 0x61,
- 0x75, 0x74, 0x68, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x44, 0x61,
- 0x74, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x61, 0x75, 0x74, 0x68,
- 0x2e, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x44, 0x61, 0x74, 0x61, 0x52, 0x65, 0x73, 0x70,
- 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x51, 0x0a, 0x10, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x43,
- 0x73, 0x72, 0x66, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x1d, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x2e,
- 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x43, 0x73, 0x72, 0x66, 0x54, 0x6f, 0x6b, 0x65, 0x6e,
- 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x52,
- 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x43, 0x73, 0x72, 0x66, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52,
- 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x33, 0x5a, 0x31, 0x2e, 0x2e, 0x2f, 0x6d, 0x69,
- 0x63, 0x72, 0x6f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2f, 0x61, 0x75, 0x74, 0x68,
- 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c,
- 0x6c, 0x65, 0x72, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x3b, 0x67, 0x65, 0x6e, 0x62, 0x06, 0x70, 0x72,
- 0x6f, 0x74, 0x6f, 0x33,
+ 0x28, 0x09, 0x52, 0x09, 0x63, 0x73, 0x72, 0x66, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x71, 0x0a,
+ 0x18, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x67, 0x69, 0x6f,
+ 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x52, 0x65, 0x67,
+ 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x52, 0x65, 0x67, 0x69, 0x6f,
+ 0x6e, 0x12, 0x1e, 0x0a, 0x0a, 0x61, 0x75, 0x74, 0x68, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x18,
+ 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x61, 0x75, 0x74, 0x68, 0x48, 0x65, 0x61, 0x64, 0x65,
+ 0x72, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18,
+ 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x64,
+ 0x22, 0xf5, 0x01, 0x0a, 0x18, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x52,
+ 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a,
+ 0x06, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x52,
+ 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x12, 0x42, 0x0a, 0x0e, 0x53, 0x74, 0x61, 0x72, 0x74, 0x56, 0x69,
+ 0x73, 0x69, 0x74, 0x44, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e,
+ 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e,
+ 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0e, 0x53, 0x74, 0x61, 0x72, 0x74,
+ 0x56, 0x69, 0x73, 0x69, 0x74, 0x44, 0x61, 0x74, 0x65, 0x12, 0x3e, 0x0a, 0x0c, 0x45, 0x6e, 0x64,
+ 0x56, 0x69, 0x73, 0x69, 0x74, 0x44, 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32,
+ 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75,
+ 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0c, 0x45, 0x6e, 0x64,
+ 0x56, 0x69, 0x73, 0x69, 0x74, 0x44, 0x61, 0x74, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x61, 0x75, 0x74,
+ 0x68, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x61,
+ 0x75, 0x74, 0x68, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x65, 0x73,
+ 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73,
+ 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x32, 0x9d, 0x05, 0x0a, 0x04, 0x41, 0x75, 0x74,
+ 0x68, 0x12, 0x3d, 0x0a, 0x0c, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x55, 0x73, 0x65,
+ 0x72, 0x12, 0x19, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65,
+ 0x72, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x61,
+ 0x75, 0x74, 0x68, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
+ 0x12, 0x37, 0x0a, 0x09, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x55, 0x73, 0x65, 0x72, 0x12, 0x16, 0x2e,
+ 0x61, 0x75, 0x74, 0x68, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65,
+ 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x55, 0x73, 0x65,
+ 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3b, 0x0a, 0x0a, 0x4c, 0x6f, 0x67,
+ 0x6f, 0x75, 0x74, 0x55, 0x73, 0x65, 0x72, 0x12, 0x13, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x6c,
+ 0x6f, 0x67, 0x6f, 0x75, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x61,
+ 0x75, 0x74, 0x68, 0x2e, 0x4c, 0x6f, 0x67, 0x6f, 0x75, 0x74, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65,
+ 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x35, 0x0a, 0x07, 0x50, 0x75, 0x74, 0x55, 0x73, 0x65,
+ 0x72, 0x12, 0x14, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x50, 0x75, 0x74, 0x55, 0x73, 0x65, 0x72,
+ 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x55,
+ 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x42, 0x0a,
+ 0x0b, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x42, 0x79, 0x49, 0x64, 0x12, 0x18, 0x2e, 0x61,
+ 0x75, 0x74, 0x68, 0x2e, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x42, 0x79, 0x49, 0x64, 0x52,
+ 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x47, 0x65,
+ 0x74, 0x55, 0x73, 0x65, 0x72, 0x42, 0x79, 0x49, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
+ 0x65, 0x12, 0x32, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x41, 0x6c, 0x6c, 0x55, 0x73, 0x65, 0x72, 0x73,
+ 0x12, 0x0b, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x16, 0x2e,
+ 0x61, 0x75, 0x74, 0x68, 0x2e, 0x41, 0x6c, 0x6c, 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73,
+ 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x48, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x53, 0x65, 0x73, 0x73,
+ 0x69, 0x6f, 0x6e, 0x44, 0x61, 0x74, 0x61, 0x12, 0x1b, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x47,
+ 0x65, 0x74, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x44, 0x61, 0x74, 0x61, 0x52, 0x65, 0x71,
+ 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x53, 0x65, 0x73, 0x73,
+ 0x69, 0x6f, 0x6e, 0x44, 0x61, 0x74, 0x61, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12,
+ 0x51, 0x0a, 0x10, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x43, 0x73, 0x72, 0x66, 0x54, 0x6f,
+ 0x6b, 0x65, 0x6e, 0x12, 0x1d, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x52, 0x65, 0x66, 0x72, 0x65,
+ 0x73, 0x68, 0x43, 0x73, 0x72, 0x66, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65,
+ 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73,
+ 0x68, 0x43, 0x73, 0x72, 0x66, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
+ 0x73, 0x65, 0x12, 0x49, 0x0a, 0x11, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72,
+ 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1e, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x55,
+ 0x70, 0x64, 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x73,
+ 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x55,
+ 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x49, 0x0a,
+ 0x11, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x67, 0x69, 0x6f,
+ 0x6e, 0x73, 0x12, 0x1e, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65,
+ 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65,
+ 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65,
+ 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x33, 0x5a, 0x31, 0x2e, 0x2e, 0x2f, 0x6d,
+ 0x69, 0x63, 0x72, 0x6f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2f, 0x61, 0x75, 0x74,
+ 0x68, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f,
+ 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x3b, 0x67, 0x65, 0x6e, 0x62, 0x06, 0x70,
+ 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
@@ -1243,7 +1413,7 @@ func file_auth_proto_rawDescGZIP() []byte {
return file_auth_proto_rawDescData
}
-var file_auth_proto_msgTypes = make([]protoimpl.MessageInfo, 18)
+var file_auth_proto_msgTypes = make([]protoimpl.MessageInfo, 20)
var file_auth_proto_goTypes = []any{
(*RefreshCsrfTokenRequest)(nil), // 0: auth.RefreshCsrfTokenRequest
(*Metadata)(nil), // 1: auth.Metadata
@@ -1263,36 +1433,44 @@ var file_auth_proto_goTypes = []any{
(*GetUserByIdResponse)(nil), // 15: auth.GetUserByIdResponse
(*SessionDataResponse)(nil), // 16: auth.SessionDataResponse
(*RefreshCsrfTokenResponse)(nil), // 17: auth.RefreshCsrfTokenResponse
- (*timestamppb.Timestamp)(nil), // 18: google.protobuf.Timestamp
+ (*DeleteUserRegionsRequest)(nil), // 18: auth.DeleteUserRegionsRequest
+ (*UpdateUserRegionsRequest)(nil), // 19: auth.UpdateUserRegionsRequest
+ (*timestamppb.Timestamp)(nil), // 20: google.protobuf.Timestamp
}
var file_auth_proto_depIdxs = []int32{
- 18, // 0: auth.Metadata.birthdate:type_name -> google.protobuf.Timestamp
- 18, // 1: auth.MetadataOneUser.birthdate:type_name -> google.protobuf.Timestamp
+ 20, // 0: auth.Metadata.birthdate:type_name -> google.protobuf.Timestamp
+ 20, // 1: auth.MetadataOneUser.birthdate:type_name -> google.protobuf.Timestamp
1, // 2: auth.PutUserRequest.creds:type_name -> auth.Metadata
4, // 3: auth.UserResponse.user:type_name -> auth.User
2, // 4: auth.AllUsersResponse.users:type_name -> auth.MetadataOneUser
2, // 5: auth.GetUserByIdResponse.user:type_name -> auth.MetadataOneUser
- 5, // 6: auth.Auth.RegisterUser:input_type -> auth.RegisterUserRequest
- 6, // 7: auth.Auth.LoginUser:input_type -> auth.LoginUserRequest
- 3, // 8: auth.Auth.LogoutUser:input_type -> auth.logoutRequest
- 9, // 9: auth.Auth.PutUser:input_type -> auth.PutUserRequest
- 10, // 10: auth.Auth.GetUserById:input_type -> auth.GetUserByIdRequest
- 8, // 11: auth.Auth.GetAllUsers:input_type -> auth.Empty
- 7, // 12: auth.Auth.GetSessionData:input_type -> auth.GetSessionDataRequest
- 0, // 13: auth.Auth.RefreshCsrfToken:input_type -> auth.RefreshCsrfTokenRequest
- 11, // 14: auth.Auth.RegisterUser:output_type -> auth.UserResponse
- 11, // 15: auth.Auth.LoginUser:output_type -> auth.UserResponse
- 12, // 16: auth.Auth.LogoutUser:output_type -> auth.LogoutUserResponse
- 13, // 17: auth.Auth.PutUser:output_type -> auth.UpdateResponse
- 15, // 18: auth.Auth.GetUserById:output_type -> auth.GetUserByIdResponse
- 14, // 19: auth.Auth.GetAllUsers:output_type -> auth.AllUsersResponse
- 16, // 20: auth.Auth.GetSessionData:output_type -> auth.SessionDataResponse
- 17, // 21: auth.Auth.RefreshCsrfToken:output_type -> auth.RefreshCsrfTokenResponse
- 14, // [14:22] is the sub-list for method output_type
- 6, // [6:14] is the sub-list for method input_type
- 6, // [6:6] is the sub-list for extension type_name
- 6, // [6:6] is the sub-list for extension extendee
- 0, // [0:6] is the sub-list for field type_name
+ 20, // 6: auth.UpdateUserRegionsRequest.StartVisitDate:type_name -> google.protobuf.Timestamp
+ 20, // 7: auth.UpdateUserRegionsRequest.EndVisitDate:type_name -> google.protobuf.Timestamp
+ 5, // 8: auth.Auth.RegisterUser:input_type -> auth.RegisterUserRequest
+ 6, // 9: auth.Auth.LoginUser:input_type -> auth.LoginUserRequest
+ 3, // 10: auth.Auth.LogoutUser:input_type -> auth.logoutRequest
+ 9, // 11: auth.Auth.PutUser:input_type -> auth.PutUserRequest
+ 10, // 12: auth.Auth.GetUserById:input_type -> auth.GetUserByIdRequest
+ 8, // 13: auth.Auth.GetAllUsers:input_type -> auth.Empty
+ 7, // 14: auth.Auth.GetSessionData:input_type -> auth.GetSessionDataRequest
+ 0, // 15: auth.Auth.RefreshCsrfToken:input_type -> auth.RefreshCsrfTokenRequest
+ 19, // 16: auth.Auth.UpdateUserRegions:input_type -> auth.UpdateUserRegionsRequest
+ 18, // 17: auth.Auth.DeleteUserRegions:input_type -> auth.DeleteUserRegionsRequest
+ 11, // 18: auth.Auth.RegisterUser:output_type -> auth.UserResponse
+ 11, // 19: auth.Auth.LoginUser:output_type -> auth.UserResponse
+ 12, // 20: auth.Auth.LogoutUser:output_type -> auth.LogoutUserResponse
+ 13, // 21: auth.Auth.PutUser:output_type -> auth.UpdateResponse
+ 15, // 22: auth.Auth.GetUserById:output_type -> auth.GetUserByIdResponse
+ 14, // 23: auth.Auth.GetAllUsers:output_type -> auth.AllUsersResponse
+ 16, // 24: auth.Auth.GetSessionData:output_type -> auth.SessionDataResponse
+ 17, // 25: auth.Auth.RefreshCsrfToken:output_type -> auth.RefreshCsrfTokenResponse
+ 13, // 26: auth.Auth.UpdateUserRegions:output_type -> auth.UpdateResponse
+ 13, // 27: auth.Auth.DeleteUserRegions:output_type -> auth.UpdateResponse
+ 18, // [18:28] is the sub-list for method output_type
+ 8, // [8:18] is the sub-list for method input_type
+ 8, // [8:8] is the sub-list for extension type_name
+ 8, // [8:8] is the sub-list for extension extendee
+ 0, // [0:8] is the sub-list for field type_name
}
func init() { file_auth_proto_init() }
@@ -1306,7 +1484,7 @@ func file_auth_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_auth_proto_rawDesc,
NumEnums: 0,
- NumMessages: 18,
+ NumMessages: 20,
NumExtensions: 0,
NumServices: 1,
},
diff --git a/microservices/auth_service/controller/gen/auth_grpc.pb.go b/microservices/auth_service/controller/gen/auth_grpc.pb.go
index 9488a9a..e4760d2 100644
--- a/microservices/auth_service/controller/gen/auth_grpc.pb.go
+++ b/microservices/auth_service/controller/gen/auth_grpc.pb.go
@@ -19,14 +19,16 @@ import (
const _ = grpc.SupportPackageIsVersion9
const (
- Auth_RegisterUser_FullMethodName = "/auth.Auth/RegisterUser"
- Auth_LoginUser_FullMethodName = "/auth.Auth/LoginUser"
- Auth_LogoutUser_FullMethodName = "/auth.Auth/LogoutUser"
- Auth_PutUser_FullMethodName = "/auth.Auth/PutUser"
- Auth_GetUserById_FullMethodName = "/auth.Auth/GetUserById"
- Auth_GetAllUsers_FullMethodName = "/auth.Auth/GetAllUsers"
- Auth_GetSessionData_FullMethodName = "/auth.Auth/GetSessionData"
- Auth_RefreshCsrfToken_FullMethodName = "/auth.Auth/RefreshCsrfToken"
+ Auth_RegisterUser_FullMethodName = "/auth.Auth/RegisterUser"
+ Auth_LoginUser_FullMethodName = "/auth.Auth/LoginUser"
+ Auth_LogoutUser_FullMethodName = "/auth.Auth/LogoutUser"
+ Auth_PutUser_FullMethodName = "/auth.Auth/PutUser"
+ Auth_GetUserById_FullMethodName = "/auth.Auth/GetUserById"
+ Auth_GetAllUsers_FullMethodName = "/auth.Auth/GetAllUsers"
+ Auth_GetSessionData_FullMethodName = "/auth.Auth/GetSessionData"
+ Auth_RefreshCsrfToken_FullMethodName = "/auth.Auth/RefreshCsrfToken"
+ Auth_UpdateUserRegions_FullMethodName = "/auth.Auth/UpdateUserRegions"
+ Auth_DeleteUserRegions_FullMethodName = "/auth.Auth/DeleteUserRegions"
)
// AuthClient is the client API for Auth service.
@@ -41,6 +43,8 @@ type AuthClient interface {
GetAllUsers(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*AllUsersResponse, error)
GetSessionData(ctx context.Context, in *GetSessionDataRequest, opts ...grpc.CallOption) (*SessionDataResponse, error)
RefreshCsrfToken(ctx context.Context, in *RefreshCsrfTokenRequest, opts ...grpc.CallOption) (*RefreshCsrfTokenResponse, error)
+ UpdateUserRegions(ctx context.Context, in *UpdateUserRegionsRequest, opts ...grpc.CallOption) (*UpdateResponse, error)
+ DeleteUserRegions(ctx context.Context, in *DeleteUserRegionsRequest, opts ...grpc.CallOption) (*UpdateResponse, error)
}
type authClient struct {
@@ -131,6 +135,26 @@ func (c *authClient) RefreshCsrfToken(ctx context.Context, in *RefreshCsrfTokenR
return out, nil
}
+func (c *authClient) UpdateUserRegions(ctx context.Context, in *UpdateUserRegionsRequest, opts ...grpc.CallOption) (*UpdateResponse, error) {
+ cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
+ out := new(UpdateResponse)
+ err := c.cc.Invoke(ctx, Auth_UpdateUserRegions_FullMethodName, in, out, cOpts...)
+ if err != nil {
+ return nil, err
+ }
+ return out, nil
+}
+
+func (c *authClient) DeleteUserRegions(ctx context.Context, in *DeleteUserRegionsRequest, opts ...grpc.CallOption) (*UpdateResponse, error) {
+ cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
+ out := new(UpdateResponse)
+ err := c.cc.Invoke(ctx, Auth_DeleteUserRegions_FullMethodName, in, out, cOpts...)
+ if err != nil {
+ return nil, err
+ }
+ return out, nil
+}
+
// AuthServer is the server API for Auth service.
// All implementations must embed UnimplementedAuthServer
// for forward compatibility.
@@ -143,6 +167,8 @@ type AuthServer interface {
GetAllUsers(context.Context, *Empty) (*AllUsersResponse, error)
GetSessionData(context.Context, *GetSessionDataRequest) (*SessionDataResponse, error)
RefreshCsrfToken(context.Context, *RefreshCsrfTokenRequest) (*RefreshCsrfTokenResponse, error)
+ UpdateUserRegions(context.Context, *UpdateUserRegionsRequest) (*UpdateResponse, error)
+ DeleteUserRegions(context.Context, *DeleteUserRegionsRequest) (*UpdateResponse, error)
mustEmbedUnimplementedAuthServer()
}
@@ -177,6 +203,12 @@ func (UnimplementedAuthServer) GetSessionData(context.Context, *GetSessionDataRe
func (UnimplementedAuthServer) RefreshCsrfToken(context.Context, *RefreshCsrfTokenRequest) (*RefreshCsrfTokenResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method RefreshCsrfToken not implemented")
}
+func (UnimplementedAuthServer) UpdateUserRegions(context.Context, *UpdateUserRegionsRequest) (*UpdateResponse, error) {
+ return nil, status.Errorf(codes.Unimplemented, "method UpdateUserRegions not implemented")
+}
+func (UnimplementedAuthServer) DeleteUserRegions(context.Context, *DeleteUserRegionsRequest) (*UpdateResponse, error) {
+ return nil, status.Errorf(codes.Unimplemented, "method DeleteUserRegions not implemented")
+}
func (UnimplementedAuthServer) mustEmbedUnimplementedAuthServer() {}
func (UnimplementedAuthServer) testEmbeddedByValue() {}
@@ -342,6 +374,42 @@ func _Auth_RefreshCsrfToken_Handler(srv interface{}, ctx context.Context, dec fu
return interceptor(ctx, in, info, handler)
}
+func _Auth_UpdateUserRegions_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+ in := new(UpdateUserRegionsRequest)
+ if err := dec(in); err != nil {
+ return nil, err
+ }
+ if interceptor == nil {
+ return srv.(AuthServer).UpdateUserRegions(ctx, in)
+ }
+ info := &grpc.UnaryServerInfo{
+ Server: srv,
+ FullMethod: Auth_UpdateUserRegions_FullMethodName,
+ }
+ handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+ return srv.(AuthServer).UpdateUserRegions(ctx, req.(*UpdateUserRegionsRequest))
+ }
+ return interceptor(ctx, in, info, handler)
+}
+
+func _Auth_DeleteUserRegions_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+ in := new(DeleteUserRegionsRequest)
+ if err := dec(in); err != nil {
+ return nil, err
+ }
+ if interceptor == nil {
+ return srv.(AuthServer).DeleteUserRegions(ctx, in)
+ }
+ info := &grpc.UnaryServerInfo{
+ Server: srv,
+ FullMethod: Auth_DeleteUserRegions_FullMethodName,
+ }
+ handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+ return srv.(AuthServer).DeleteUserRegions(ctx, req.(*DeleteUserRegionsRequest))
+ }
+ return interceptor(ctx, in, info, handler)
+}
+
// Auth_ServiceDesc is the grpc.ServiceDesc for Auth service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
@@ -381,6 +449,14 @@ var Auth_ServiceDesc = grpc.ServiceDesc{
MethodName: "RefreshCsrfToken",
Handler: _Auth_RefreshCsrfToken_Handler,
},
+ {
+ MethodName: "UpdateUserRegions",
+ Handler: _Auth_UpdateUserRegions_Handler,
+ },
+ {
+ MethodName: "DeleteUserRegions",
+ Handler: _Auth_DeleteUserRegions_Handler,
+ },
},
Streams: []grpc.StreamDesc{},
Metadata: "auth.proto",
diff --git a/microservices/auth_service/mocks/mocks.go b/microservices/auth_service/mocks/mocks.go
index 655e59d..4696c3b 100644
--- a/microservices/auth_service/mocks/mocks.go
+++ b/microservices/auth_service/mocks/mocks.go
@@ -3,25 +3,25 @@ package mocks
import (
"2024_2_FIGHT-CLUB/domain"
"2024_2_FIGHT-CLUB/internal/service/middleware"
+ "2024_2_FIGHT-CLUB/microservices/auth_service/controller/gen"
"context"
"github.com/golang-jwt/jwt"
- "github.com/gorilla/sessions"
- "mime/multipart"
- "net/http"
+ "github.com/stretchr/testify/mock"
+ "google.golang.org/grpc"
)
type MockJwtTokenService struct {
- MockCreate func(s *sessions.Session, tokenExpTime int64) (string, error)
- MockValidate func(tokenString string) (*middleware.JwtCsrfClaims, error)
+ MockCreate func(session_id string, tokenExpTime int64) (string, error)
+ MockValidate func(tokenString string, expectedSessionId string) (*middleware.JwtCsrfClaims, error)
MockParseSecretGetter func(token *jwt.Token) (interface{}, error)
}
-func (m *MockJwtTokenService) Create(s *sessions.Session, tokenExpTime int64) (string, error) {
- return m.MockCreate(s, tokenExpTime)
+func (m *MockJwtTokenService) Create(session_id string, tokenExpTime int64) (string, error) {
+ return m.MockCreate(session_id, tokenExpTime)
}
-func (m *MockJwtTokenService) Validate(tokenString string) (*middleware.JwtCsrfClaims, error) {
- return m.MockValidate(tokenString)
+func (m *MockJwtTokenService) Validate(tokenString string, expectedSessionId string) (*middleware.JwtCsrfClaims, error) {
+ return m.MockValidate(tokenString, expectedSessionId)
}
func (m *MockJwtTokenService) ParseSecretGetter(token *jwt.Token) (interface{}, error) {
@@ -29,39 +29,36 @@ func (m *MockJwtTokenService) ParseSecretGetter(token *jwt.Token) (interface{},
}
type MockServiceSession struct {
- MockGetUserID func(ctx context.Context, r *http.Request) (string, error)
- MockLogoutSession func(ctx context.Context, r *http.Request, w http.ResponseWriter) error
- MockCreateSession func(ctx context.Context, r *http.Request, w http.ResponseWriter, user *domain.User) (*sessions.Session, error)
- MockGetSessionData func(ctx context.Context, r *http.Request) (*map[string]interface{}, error)
- MockGetSession func(ctx context.Context, r *http.Request) (*sessions.Session, error)
+ MockGetUserID func(ctx context.Context, sessionID string) (string, error)
+ MockLogoutSession func(ctx context.Context, sessionID string) error
+ MockCreateSession func(ctx context.Context, user *domain.User) (string, error)
+ MockGetSessionData func(ctx context.Context, sessionID string) (*domain.SessionData, error)
}
-func (m *MockServiceSession) GetUserID(ctx context.Context, r *http.Request) (string, error) {
- return m.MockGetUserID(ctx, r)
+func (m *MockServiceSession) GetUserID(ctx context.Context, sessionID string) (string, error) {
+ return m.MockGetUserID(ctx, sessionID)
}
-func (m *MockServiceSession) LogoutSession(ctx context.Context, r *http.Request, w http.ResponseWriter) error {
- return m.MockLogoutSession(ctx, r, w)
+func (m *MockServiceSession) LogoutSession(ctx context.Context, sessionID string) error {
+ return m.MockLogoutSession(ctx, sessionID)
}
-func (m *MockServiceSession) CreateSession(ctx context.Context, r *http.Request, w http.ResponseWriter, user *domain.User) (*sessions.Session, error) {
- return m.MockCreateSession(ctx, r, w, user)
+func (m *MockServiceSession) CreateSession(ctx context.Context, user *domain.User) (string, error) {
+ return m.MockCreateSession(ctx, user)
}
-func (m *MockServiceSession) GetSessionData(ctx context.Context, r *http.Request) (*map[string]interface{}, error) {
- return m.MockGetSessionData(ctx, r)
-}
-
-func (m *MockServiceSession) GetSession(ctx context.Context, r *http.Request) (*sessions.Session, error) {
- return m.MockGetSession(ctx, r)
+func (m *MockServiceSession) GetSessionData(ctx context.Context, sessionID string) (*domain.SessionData, error) {
+ return m.MockGetSessionData(ctx, sessionID)
}
type MockAuthUseCase struct {
- MockRegisterUser func(ctx context.Context, creds *domain.User) error
- MockLoginUser func(ctx context.Context, creds *domain.User) (*domain.User, error)
- MockPutUser func(ctx context.Context, creds *domain.User, userID string, avatar *multipart.FileHeader) error
- MockGetAllUser func(ctx context.Context) ([]domain.User, error)
- MockGetUserById func(ctx context.Context, userID string) (*domain.User, error)
+ MockRegisterUser func(ctx context.Context, creds *domain.User) error
+ MockLoginUser func(ctx context.Context, creds *domain.User) (*domain.User, error)
+ MockPutUser func(ctx context.Context, creds *domain.User, userID string, avatar []byte) error
+ MockGetAllUser func(ctx context.Context) ([]domain.User, error)
+ MockGetUserById func(ctx context.Context, userID string) (*domain.User, error)
+ MockUpdateUserRegions func(ctx context.Context, regions domain.UpdateUserRegion, userId string) error
+ MockDeleteUserRegion func(ctx context.Context, regionName string, userID string) error
}
func (m *MockAuthUseCase) RegisterUser(ctx context.Context, creds *domain.User) error {
@@ -72,7 +69,7 @@ func (m *MockAuthUseCase) LoginUser(ctx context.Context, creds *domain.User) (*d
return m.MockLoginUser(ctx, creds)
}
-func (m *MockAuthUseCase) PutUser(ctx context.Context, creds *domain.User, userID string, avatar *multipart.FileHeader) error {
+func (m *MockAuthUseCase) PutUser(ctx context.Context, creds *domain.User, userID string, avatar []byte) error {
return m.MockPutUser(ctx, creds, userID, avatar)
}
@@ -80,18 +77,28 @@ func (m *MockAuthUseCase) GetAllUser(ctx context.Context) ([]domain.User, error)
return m.MockGetAllUser(ctx)
}
+func (m *MockAuthUseCase) UpdateUserRegions(ctx context.Context, regions domain.UpdateUserRegion, userId string) error {
+ return m.MockUpdateUserRegions(ctx, regions, userId)
+}
+
+func (m *MockAuthUseCase) DeleteUserRegion(ctx context.Context, regionName string, userID string) error {
+ return m.MockDeleteUserRegion(ctx, regionName, userID)
+}
+
func (m *MockAuthUseCase) GetUserById(ctx context.Context, userID string) (*domain.User, error) {
return m.MockGetUserById(ctx, userID)
}
type MockAuthRepository struct {
- GetUserByNameFunc func(ctx context.Context, username string) (*domain.User, error)
- CreateUserFunc func(ctx context.Context, user *domain.User) error
- SaveUserFunc func(ctx context.Context, user *domain.User) error
- PutUserFunc func(ctx context.Context, user *domain.User, userID string) error
- GetAllUserFunc func(ctx context.Context) ([]domain.User, error)
- GetUserByIdFunc func(ctx context.Context, userID string) (*domain.User, error)
- MockGetUserByEmail func(ctx context.Context, email string) (*domain.User, error)
+ GetUserByNameFunc func(ctx context.Context, username string) (*domain.User, error)
+ CreateUserFunc func(ctx context.Context, user *domain.User) error
+ SaveUserFunc func(ctx context.Context, user *domain.User) error
+ PutUserFunc func(ctx context.Context, user *domain.User, userID string) error
+ GetAllUserFunc func(ctx context.Context) ([]domain.User, error)
+ GetUserByIdFunc func(ctx context.Context, userID string) (*domain.User, error)
+ MockGetUserByEmail func(ctx context.Context, email string) (*domain.User, error)
+ MockUpdateUserRegion func(ctx context.Context, region domain.UpdateUserRegion, userId string) error
+ MockDeleteUserRegion func(ctx context.Context, regionName string, userId string) error
}
func (m *MockAuthRepository) GetUserByEmail(ctx context.Context, email string) (*domain.User, error) {
@@ -122,6 +129,14 @@ func (m *MockAuthRepository) GetUserById(ctx context.Context, userID string) (*d
return m.GetUserByIdFunc(ctx, userID)
}
+func (m *MockAuthRepository) UpdateUserRegion(ctx context.Context, region domain.UpdateUserRegion, userId string) error {
+ return m.MockUpdateUserRegion(ctx, region, userId)
+}
+
+func (m *MockAuthRepository) DeleteUserRegion(ctx context.Context, regionName string, userId string) error {
+ return m.MockDeleteUserRegion(ctx, regionName, userId)
+}
+
type MockMinioService struct {
UploadFileFunc func(file []byte, contentType string, id string) (string, error)
DeleteFileFunc func(path string) error
@@ -134,3 +149,50 @@ func (m *MockMinioService) UploadFile(file []byte, contentType string, id string
func (m *MockMinioService) DeleteFile(path string) error {
return m.DeleteFileFunc(path)
}
+
+type MockGrpcClient struct {
+ mock.Mock
+}
+
+func (m *MockGrpcClient) RegisterUser(ctx context.Context, in *gen.RegisterUserRequest, opts ...grpc.CallOption) (*gen.UserResponse, error) {
+ args := m.Called(ctx, in, opts)
+ return args.Get(0).(*gen.UserResponse), args.Error(1)
+}
+func (m *MockGrpcClient) LoginUser(ctx context.Context, in *gen.LoginUserRequest, opts ...grpc.CallOption) (*gen.UserResponse, error) {
+ args := m.Called(ctx, in, opts)
+ return args.Get(0).(*gen.UserResponse), args.Error(1)
+}
+func (m *MockGrpcClient) LogoutUser(ctx context.Context, in *gen.LogoutRequest, opts ...grpc.CallOption) (*gen.LogoutUserResponse, error) {
+ args := m.Called(ctx, in, opts)
+ return args.Get(0).(*gen.LogoutUserResponse), args.Error(1)
+}
+func (m *MockGrpcClient) PutUser(ctx context.Context, in *gen.PutUserRequest, opts ...grpc.CallOption) (*gen.UpdateResponse, error) {
+ args := m.Called(ctx, in, opts)
+ return args.Get(0).(*gen.UpdateResponse), args.Error(1)
+}
+func (m *MockGrpcClient) GetUserById(ctx context.Context, in *gen.GetUserByIdRequest, opts ...grpc.CallOption) (*gen.GetUserByIdResponse, error) {
+ args := m.Called(ctx, in, opts)
+ return args.Get(0).(*gen.GetUserByIdResponse), args.Error(1)
+}
+func (m *MockGrpcClient) GetAllUsers(ctx context.Context, in *gen.Empty, opts ...grpc.CallOption) (*gen.AllUsersResponse, error) {
+ args := m.Called(ctx, in, opts)
+ return args.Get(0).(*gen.AllUsersResponse), args.Error(1)
+}
+func (m *MockGrpcClient) GetSessionData(ctx context.Context, in *gen.GetSessionDataRequest, opts ...grpc.CallOption) (*gen.SessionDataResponse, error) {
+ args := m.Called(ctx, in, opts)
+ return args.Get(0).(*gen.SessionDataResponse), args.Error(1)
+}
+func (m *MockGrpcClient) RefreshCsrfToken(ctx context.Context, in *gen.RefreshCsrfTokenRequest, opts ...grpc.CallOption) (*gen.RefreshCsrfTokenResponse, error) {
+ args := m.Called(ctx, in, opts)
+ return args.Get(0).(*gen.RefreshCsrfTokenResponse), args.Error(1)
+}
+
+func (m *MockGrpcClient) UpdateUserRegions(ctx context.Context, in *gen.UpdateUserRegionsRequest, opts ...grpc.CallOption) (*gen.UpdateResponse, error) {
+ args := m.Called(ctx, in, opts)
+ return args.Get(0).(*gen.UpdateResponse), args.Error(1)
+}
+
+func (m *MockGrpcClient) DeleteUserRegions(ctx context.Context, in *gen.DeleteUserRegionsRequest, opts ...grpc.CallOption) (*gen.UpdateResponse, error) {
+ args := m.Called(ctx, in, opts)
+ return args.Get(0).(*gen.UpdateResponse), args.Error(1)
+}
diff --git a/microservices/auth_service/repository/auth_repository.go b/microservices/auth_service/repository/auth_repository.go
index 7307681..b7dbd95 100644
--- a/microservices/auth_service/repository/auth_repository.go
+++ b/microservices/auth_service/repository/auth_repository.go
@@ -9,6 +9,7 @@ import (
"errors"
"go.uber.org/zap"
"gorm.io/gorm"
+ "strings"
"time"
)
@@ -82,12 +83,21 @@ func (r *authRepository) PutUser(ctx context.Context, creds *domain.User, userID
duration := time.Since(start).Seconds()
metrics.RepoRequestDuration.WithLabelValues("PutUser").Observe(duration)
}()
+
if err := r.db.Model(&domain.User{}).Where("UUID = ?", userID).Updates(creds).Error; err != nil {
+ if strings.Contains(err.Error(), "duplicate key value violates unique constraint") {
+ logger.DBLogger.Warn("Unique constraint violation", zap.String("request_id", requestID), zap.String("userID", userID), zap.Error(err))
+ return errors.New("username or email already exists")
+ }
logger.DBLogger.Error("Error updating user", zap.String("request_id", requestID), zap.String("userID", userID), zap.Error(err))
return errors.New("error updating user")
}
- //для булевых false
+
if err := r.db.Model(&domain.User{}).Where("UUID = ?", userID).Update("isHost", creds.IsHost).Error; err != nil {
+ if strings.Contains(err.Error(), "duplicate key value violates unique constraint") {
+ logger.DBLogger.Warn("Unique constraint violation on isHost", zap.String("request_id", requestID), zap.String("userID", userID), zap.Error(err))
+ return errors.New("username or email already exists")
+ }
logger.DBLogger.Error("Error updating user", zap.String("request_id", requestID), zap.String("userID", userID), zap.Error(err))
return errors.New("error updating user")
}
@@ -203,3 +213,92 @@ func (r *authRepository) GetUserByEmail(ctx context.Context, email string) (*dom
logger.DBLogger.Info("Successfully fetched user by email", zap.String("request_id", requestID), zap.String("email", email))
return &user, nil
}
+
+func (r *authRepository) UpdateUserRegion(ctx context.Context, region domain.UpdateUserRegion, userID string) error {
+ start := time.Now()
+ requestID := middleware.GetRequestID(ctx)
+ logger.DBLogger.Info("UpdateUserRegions called",
+ zap.String("request_id", requestID),
+ zap.String("userID", userID),
+ )
+
+ var err error
+ defer func() {
+ if err != nil {
+ metrics.RepoErrorsTotal.WithLabelValues("UpdateUserRegions", "error", err.Error()).Inc()
+ } else {
+ metrics.RepoRequestTotal.WithLabelValues("UpdateUserRegions", "success").Inc()
+ }
+ duration := time.Since(start).Seconds()
+ metrics.RepoRequestDuration.WithLabelValues("UpdateUserRegions").Observe(duration)
+ }()
+
+ var visitedRegion domain.VisitedRegions
+ visitedRegion.Name = region.RegionName
+ startVisitDate, parseErr := time.Parse("2006-01-02", region.StartVisitedDate)
+ if parseErr != nil {
+ logger.DBLogger.Error("Invalid date format", zap.String("request_id", requestID), zap.Error(parseErr))
+ return parseErr
+ }
+ visitedRegion.StartVisitDate = startVisitDate
+ endVisitDate, parseErr := time.Parse("2006-01-02", region.EndVisitedDate)
+ if parseErr != nil {
+ logger.DBLogger.Error("Invalid date format", zap.String("request_id", requestID), zap.Error(parseErr))
+ return parseErr
+ }
+ visitedRegion.EndVisitDate = endVisitDate
+ visitedRegion.UserID = userID
+ err = r.db.WithContext(ctx).Create(&visitedRegion).Error
+ if err != nil {
+ logger.DBLogger.Error("Failed to insert visited regions into database",
+ zap.String("request_id", requestID),
+ zap.Error(err),
+ )
+ return err
+ }
+
+ logger.DBLogger.Info("Successfully updated user regions",
+ zap.String("request_id", requestID),
+ zap.String("userID", userID),
+ )
+
+ return nil
+}
+
+func (r *authRepository) DeleteUserRegion(ctx context.Context, regionName string, userID string) error {
+ start := time.Now()
+ requestID := middleware.GetRequestID(ctx)
+ logger.DBLogger.Info("DeleteUserRegion called",
+ zap.String("request_id", requestID),
+ zap.String("userID", userID),
+ zap.String("regionName", regionName),
+ )
+
+ var err error
+ defer func() {
+ if err != nil {
+ metrics.RepoErrorsTotal.WithLabelValues("DeleteUserRegion", "error", err.Error()).Inc()
+ } else {
+ metrics.RepoRequestTotal.WithLabelValues("DeleteUserRegion", "success").Inc()
+ }
+ duration := time.Since(start).Seconds()
+ metrics.RepoRequestDuration.WithLabelValues("DeleteUserRegion").Observe(duration)
+ }()
+
+ err = r.db.WithContext(ctx).Where("name = ? AND \"userId\" = ?", regionName, userID).Delete(&domain.VisitedRegions{}).Error
+ if err != nil {
+ logger.DBLogger.Error("Failed to delete region from database",
+ zap.String("request_id", requestID),
+ zap.Error(err),
+ )
+ return err
+ }
+
+ logger.DBLogger.Info("Successfully deleted user region from database",
+ zap.String("request_id", requestID),
+ zap.String("userID", userID),
+ zap.String("regionName", regionName),
+ )
+
+ return nil
+}
diff --git a/microservices/auth_service/repository/auth_repository_test.go b/microservices/auth_service/repository/auth_repository_test.go
index eda0902..83f42bf 100644
--- a/microservices/auth_service/repository/auth_repository_test.go
+++ b/microservices/auth_service/repository/auth_repository_test.go
@@ -33,7 +33,12 @@ func TestCreateUser(t *testing.T) {
if err := logger.InitLoggers(); err != nil {
log.Fatalf("Failed to initialize loggers: %v", err)
}
- defer logger.SyncLoggers()
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
gormDB, mock := setupTestDB(t)
defer func() {
@@ -120,10 +125,20 @@ func TestCreateUser_Failure(t *testing.T) {
if err := logger.InitLoggers(); err != nil {
log.Fatalf("Failed to initialize loggers: %v", err)
}
- defer logger.SyncLoggers()
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
db, mock := setupTestDB(t)
- defer db.DB()
+ defer func(db *gorm.DB) {
+ _, err := db.DB()
+ if err != nil {
+ return
+ }
+ }(db)
repo := NewAuthRepository(db)
@@ -173,11 +188,21 @@ func TestSaveUser_Success(t *testing.T) {
if err := logger.InitLoggers(); err != nil {
log.Fatalf("Failed to initialize loggers: %v", err)
}
- defer logger.SyncLoggers()
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
// Настройка тестовой базы данных и sqlmock
db, mock := setupTestDB(t)
- defer db.DB()
+ defer func(db *gorm.DB) {
+ _, err := db.DB()
+ if err != nil {
+ return
+ }
+ }(db)
// Создание репозитория
repo := NewAuthRepository(db)
@@ -229,9 +254,19 @@ func TestSaveUser_Failure(t *testing.T) {
if err := logger.InitLoggers(); err != nil {
log.Fatalf("Failed to initialize loggers: %v", err)
}
- defer logger.SyncLoggers()
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
db, mock := setupTestDB(t)
- defer db.DB()
+ defer func(db *gorm.DB) {
+ _, err := db.DB()
+ if err != nil {
+ return
+ }
+ }(db)
repo := NewAuthRepository(db)
@@ -274,9 +309,19 @@ func TestGetUserById_Success(t *testing.T) {
if err := logger.InitLoggers(); err != nil {
log.Fatalf("Failed to initialize loggers: %v", err)
}
- defer logger.SyncLoggers()
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
db, mock := setupTestDB(t)
- defer db.DB()
+ defer func(db *gorm.DB) {
+ _, err := db.DB()
+ if err != nil {
+ return
+ }
+ }(db)
repo := NewAuthRepository(db)
@@ -297,9 +342,19 @@ func TestGetUserById_NotFound(t *testing.T) {
if err := logger.InitLoggers(); err != nil {
log.Fatalf("Failed to initialize loggers: %v", err)
}
- defer logger.SyncLoggers()
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
db, mock := setupTestDB(t)
- defer db.DB()
+ defer func(db *gorm.DB) {
+ _, err := db.DB()
+ if err != nil {
+ return
+ }
+ }(db)
repo := NewAuthRepository(db)
@@ -316,9 +371,19 @@ func TestGetAllUser_Success(t *testing.T) {
if err := logger.InitLoggers(); err != nil {
log.Fatalf("Failed to initialize loggers: %v", err)
}
- defer logger.SyncLoggers()
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
db, mock := setupTestDB(t)
- defer db.DB()
+ defer func(db *gorm.DB) {
+ _, err := db.DB()
+ if err != nil {
+ return
+ }
+ }(db)
repo := NewAuthRepository(db)
@@ -337,9 +402,19 @@ func TestGetAllUser_Failure(t *testing.T) {
if err := logger.InitLoggers(); err != nil {
log.Fatalf("Failed to initialize loggers: %v", err)
}
- defer logger.SyncLoggers()
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
db, mock := setupTestDB(t)
- defer db.DB()
+ defer func(db *gorm.DB) {
+ _, err := db.DB()
+ if err != nil {
+ return
+ }
+ }(db)
repo := NewAuthRepository(db)
@@ -353,9 +428,19 @@ func TestAuthRepository_GetUserByName(t *testing.T) {
if err := logger.InitLoggers(); err != nil {
log.Fatalf("Failed to initialize loggers: %v", err)
}
- defer logger.SyncLoggers()
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
db, mock := setupTestDB(t)
- defer db.DB()
+ defer func(db *gorm.DB) {
+ _, err := db.DB()
+ if err != nil {
+ return
+ }
+ }(db)
repo := NewAuthRepository(db)
@@ -409,11 +494,21 @@ func TestAuthRepository_PutUser(t *testing.T) {
if err := logger.InitLoggers(); err != nil {
log.Fatalf("Failed to initialize loggers: %v", err)
}
- defer logger.SyncLoggers()
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
// Настраиваем тестовую базу данных и sqlmock
db, mock := setupTestDB(t)
- defer db.DB()
+ defer func(db *gorm.DB) {
+ _, err := db.DB()
+ if err != nil {
+ return
+ }
+ }(db)
repo := NewAuthRepository(db)
@@ -492,6 +587,36 @@ func TestAuthRepository_PutUser(t *testing.T) {
assert.Equal(t, "error updating user", err.Error())
})
+ // Тест-кейс 3: Ошибка обновления пользователя
+ t.Run("Error on Update isHost", func(t *testing.T) {
+ mock.ExpectBegin()
+ mock.ExpectExec(regexp.QuoteMeta(`UPDATE "users" SET "username"=$1,"password"=$2,"email"=$3,"name"=$4,"score"=$5,"avatar"=$6,"sex"=$7,"guestCount"=$8,"birthDate"=$9,"isHost"=$10 WHERE UUID = $11`)).
+ WithArgs(
+ creds.Username,
+ creds.Password,
+ creds.Email,
+ creds.Name,
+ creds.Score,
+ creds.Avatar,
+ creds.Sex,
+ creds.GuestCount,
+ creds.Birthdate,
+ creds.IsHost,
+ userID,
+ ).WillReturnResult(sqlmock.NewResult(1, 1))
+ mock.ExpectCommit()
+ mock.ExpectBegin()
+ mock.ExpectExec(regexp.QuoteMeta(`UPDATE "users" SET "isHost"=$1 WHERE UUID = $2`)).
+ WithArgs(creds.IsHost, userID).
+ WillReturnError(errors.New("error updating user"))
+ mock.ExpectRollback()
+
+ err := repo.PutUser(ctx, creds, userID)
+
+ assert.Error(t, err)
+ assert.Equal(t, "error updating user", err.Error())
+ })
+
// Проверяем, что все ожидания выполнены
require.NoError(t, mock.ExpectationsWereMet())
}
@@ -500,12 +625,20 @@ func TestGetUserByEmail(t *testing.T) {
if err := logger.InitLoggers(); err != nil {
log.Fatalf("Failed to initialize loggers: %v", err)
}
- defer logger.SyncLoggers()
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
gormDB, mock := setupTestDB(t)
defer func() {
db, _ := gormDB.DB()
- db.Close()
+ err := db.Close()
+ if err != nil {
+ return
+ }
}()
authRepo := NewAuthRepository(gormDB)
diff --git a/microservices/auth_service/usecase/auth_usecase.go b/microservices/auth_service/usecase/auth_usecase.go
index 04b1428..8ac3133 100644
--- a/microservices/auth_service/usecase/auth_usecase.go
+++ b/microservices/auth_service/usecase/auth_usecase.go
@@ -7,8 +7,8 @@ import (
"2024_2_FIGHT-CLUB/internal/service/middleware"
"2024_2_FIGHT-CLUB/internal/service/validation"
"context"
- "encoding/json"
"errors"
+ "github.com/mailru/easyjson"
"go.uber.org/zap"
"net/http"
"regexp"
@@ -20,6 +20,8 @@ type AuthUseCase interface {
PutUser(ctx context.Context, creds *domain.User, userID string, avatar []byte) error
GetAllUser(ctx context.Context) ([]domain.User, error)
GetUserById(ctx context.Context, userID string) (*domain.User, error)
+ UpdateUserRegions(ctx context.Context, regions domain.UpdateUserRegion, userId string) error
+ DeleteUserRegion(ctx context.Context, regionName string, userID string) error
}
type authUseCase struct {
@@ -52,9 +54,9 @@ func (uc *authUseCase) RegisterUser(ctx context.Context, creds *domain.User) err
if creds.Username == "" || creds.Password == "" || creds.Email == "" {
return errors.New("username, password, and email are required")
}
- errorResponse := map[string]interface{}{
- "error": "Incorrect data forms",
- "wrongFields": []string{},
+ errorResponse := domain.WrongFieldErrorResponse{
+ Error: "Incorrect data forms",
+ WrongFields: make([]string, 0),
}
var wrongFields []string
if !validation.ValidateLogin(creds.Username) {
@@ -70,8 +72,8 @@ func (uc *authUseCase) RegisterUser(ctx context.Context, creds *domain.User) err
wrongFields = append(wrongFields, "name")
}
if len(wrongFields) > 0 {
- errorResponse["wrongFields"] = wrongFields
- errorResponseJSON, err := json.Marshal(errorResponse)
+ errorResponse.WrongFields = wrongFields
+ errorResponseJSON, err := easyjson.Marshal(errorResponse)
if err != nil {
return errors.New("failed to generate error response")
}
@@ -123,9 +125,9 @@ func (uc *authUseCase) LoginUser(ctx context.Context, creds *domain.User) (*doma
if creds.Username == "" || creds.Password == "" {
return nil, errors.New("username and password are required")
}
- errorResponse := map[string]interface{}{
- "error": "Incorrect data forms",
- "wrongFields": []string{},
+ errorResponse := domain.WrongFieldErrorResponse{
+ Error: "Incorrect data forms",
+ WrongFields: make([]string, 0),
}
var wrongFields []string
if !validation.ValidateLogin(creds.Username) {
@@ -135,8 +137,8 @@ func (uc *authUseCase) LoginUser(ctx context.Context, creds *domain.User) (*doma
wrongFields = append(wrongFields, "password")
}
if len(wrongFields) > 0 {
- errorResponse["wrongFields"] = wrongFields
- errorResponseJSON, err := json.Marshal(errorResponse)
+ errorResponse.WrongFields = wrongFields
+ errorResponseJSON, err := easyjson.Marshal(errorResponse)
if err != nil {
return nil, errors.New("failed to generate error response")
}
@@ -173,14 +175,14 @@ func (uc *authUseCase) PutUser(ctx context.Context, creds *domain.User, userID s
if avatar != nil {
if err := validation.ValidateImage(avatar, 5<<20, []string{"image/jpeg", "image/png", "image/jpg"}, 2000, 2000); err != nil {
logger.AccessLogger.Warn("Invalid size, type or resolution of image", zap.String("request_id", requestID), zap.Error(err))
- return errors.New("invalid size, type or resolution of image")
+ return err
}
}
var wrongFields []string
- errorResponse := map[string]interface{}{
- "error": "Incorrect data forms",
- "wrongFields": []string{},
+ errorResponse := domain.WrongFieldErrorResponse{
+ Error: "Incorrect data forms",
+ WrongFields: make([]string, 0),
}
if !validation.ValidateLogin(creds.Username) && len(creds.Username) > 0 {
wrongFields = append(wrongFields, "username")
@@ -195,8 +197,8 @@ func (uc *authUseCase) PutUser(ctx context.Context, creds *domain.User, userID s
wrongFields = append(wrongFields, "name")
}
if len(wrongFields) > 0 {
- errorResponse["wrongFields"] = wrongFields
- errorResponseJSON, err := json.Marshal(errorResponse)
+ errorResponse.WrongFields = wrongFields
+ errorResponseJSON, err := easyjson.Marshal(errorResponse)
if err != nil {
return errors.New("failed to generate error response")
}
@@ -208,6 +210,7 @@ func (uc *authUseCase) PutUser(ctx context.Context, creds *domain.User, userID s
uploadedPath, err := uc.minioService.UploadFile(avatar, contentType, "user/"+userID)
if err != nil {
+ logger.AccessLogger.Warn("Failed to upload file", zap.String("request_id", requestID), zap.Error(err))
return errors.New("failed to upload file")
}
@@ -253,3 +256,48 @@ func (uc *authUseCase) GetUserById(ctx context.Context, userID string) (*domain.
}
return user, nil
}
+
+func (uc *authUseCase) UpdateUserRegions(ctx context.Context, region domain.UpdateUserRegion, userId string) error {
+ requestID := middleware.GetRequestID(ctx)
+ const maxLen = 255
+ validCharPattern := regexp.MustCompile(`^[a-zA-Zа-яА-ЯёЁ0-9\s\-_]*$`)
+ if !validCharPattern.MatchString(userId) {
+ logger.AccessLogger.Warn("Input contains invalid characters", zap.String("request_id", requestID))
+ return errors.New("input contains invalid characters")
+ }
+
+ if len(userId) > maxLen {
+ logger.AccessLogger.Warn("Input exceeds character limit", zap.String("request_id", requestID))
+ return errors.New("input exceeds character limit")
+ }
+
+ err := uc.authRepository.UpdateUserRegion(ctx, region, userId)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func (u *authUseCase) DeleteUserRegion(ctx context.Context, regionName string, userID string) error {
+ logger.AccessLogger.Info("Processing DeleteUserRegion",
+ zap.String("userID", userID),
+ zap.String("regionName", regionName),
+ )
+
+ // Удаляем регион через репозиторий
+ err := u.authRepository.DeleteUserRegion(ctx, regionName, userID)
+ if err != nil {
+ logger.AccessLogger.Error("Failed to delete user region",
+ zap.String("userID", userID),
+ zap.String("regionName", regionName),
+ zap.Error(err),
+ )
+ return err
+ }
+
+ logger.AccessLogger.Info("Successfully deleted user region",
+ zap.String("userID", userID),
+ zap.String("regionName", regionName),
+ )
+ return nil
+}
diff --git a/microservices/auth_service/usecase/auth_usecase_test.go b/microservices/auth_service/usecase/auth_usecase_test.go
index c73a731..cd29e01 100644
--- a/microservices/auth_service/usecase/auth_usecase_test.go
+++ b/microservices/auth_service/usecase/auth_usecase_test.go
@@ -52,7 +52,12 @@ func TestRegisterUser(t *testing.T) {
if err := logger.InitLoggers(); err != nil {
log.Fatalf("Failed to initialize loggers: %v", err)
}
- defer logger.SyncLoggers()
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
mockAuthRepo := &mocks.MockAuthRepository{}
mockMinioService := &mocks.MockMinioService{}
@@ -253,7 +258,12 @@ func TestLoginUser(t *testing.T) {
if err := logger.InitLoggers(); err != nil {
log.Fatalf("Failed to initialize loggers: %v", err)
}
- defer logger.SyncLoggers()
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
mockAuthRepo := &mocks.MockAuthRepository{}
uc := NewAuthUseCase(mockAuthRepo, nil)
@@ -439,7 +449,12 @@ func TestPutUser(t *testing.T) {
if err := logger.InitLoggers(); err != nil {
log.Fatalf("Failed to initialize loggers: %v", err)
}
- defer logger.SyncLoggers()
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
mockAuthRepo := &mocks.MockAuthRepository{}
mockMinioService := &mocks.MockMinioService{}
@@ -530,7 +545,7 @@ func TestPutUser(t *testing.T) {
err := uc.PutUser(ctx, creds, userID, invalidAvatar)
assert.Error(t, err)
- assert.Contains(t, err.Error(), "invalid size, type or resolution of image")
+ assert.Contains(t, err.Error(), "image resolution exceeds maximum allowed size of 2000 x 2000")
})
// Тест-кейс 6: Ошибка загрузки аватара
@@ -633,7 +648,12 @@ func TestGetUserById(t *testing.T) {
if err := logger.InitLoggers(); err != nil {
log.Fatalf("Failed to initialize loggers: %v", err)
}
- defer logger.SyncLoggers()
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
mockAuthRepo := &mocks.MockAuthRepository{}
uc := NewAuthUseCase(mockAuthRepo, nil)
diff --git a/microservices/city_service/Dockerfile b/microservices/city_service/Dockerfile
new file mode 100644
index 0000000..cb288ee
--- /dev/null
+++ b/microservices/city_service/Dockerfile
@@ -0,0 +1,23 @@
+# 1. Build it
+
+FROM golang:1.23.1 AS builder
+WORKDIR /app
+# Копируем go.mod и go.sum
+COPY go.mod go.sum ./
+RUN go mod download
+
+# This microservice uses other modules so we can't just copy only it
+# Therefore we need to copy the whole fucking project
+# I have wasted 3 hours of my life on this
+# COPY ./microservices/city_service/ ./microservices/city_service/
+COPY . .
+ENV CGO_ENABLED=0
+ENV GOOS=linux
+RUN go build -o /microservices/city_service/cmd/city_service ./microservices/city_service/cmd/main.go
+
+
+# 2. Run it
+FROM alpine:latest
+# WORKDIR /microservices/city_service
+COPY --from=builder ./microservices/city_service/cmd/city_service /app/city_service
+CMD ["/app/city_service"]
\ No newline at end of file
diff --git a/microservices/city_service/cmd/main.go b/microservices/city_service/cmd/main.go
index 5ab62ba..a5b831c 100644
--- a/microservices/city_service/cmd/main.go
+++ b/microservices/city_service/cmd/main.go
@@ -54,7 +54,10 @@ func main() {
cityServer := grpcCity.NewGrpcCityHandler(citiesUseCase)
grpcServer := grpc.NewServer(
- grpc.UnaryInterceptor(middleware.UnaryMetricsInterceptor),
+ grpc.UnaryInterceptor(middleware.ChainUnaryInterceptors(
+ middleware.RecoveryInterceptor, // интерсептор для обработки паники
+ middleware.UnaryMetricsInterceptor, // интерсептор для метрик
+ )),
)
generatedCity.RegisterCityServiceServer(grpcServer, cityServer)
@@ -64,7 +67,7 @@ func main() {
log.Fatalf("Failed to listen on address: %s %v", os.Getenv("CITY_SERVICE_ADDRESS"), err)
}
- log.Printf("AuthService is running on address: %s", os.Getenv("CITY_SERVICE_ADDRESS"))
+ log.Printf("CityService is running on address: %s", os.Getenv("CITY_SERVICE_ADDRESS"))
if err := grpcServer.Serve(listener); err != nil {
log.Fatalf("Failed to serve gRPC server: %v", err)
}
diff --git a/microservices/city_service/mocks/mocks.go b/microservices/city_service/mocks/mocks.go
index 69400ae..b4f22a8 100644
--- a/microservices/city_service/mocks/mocks.go
+++ b/microservices/city_service/mocks/mocks.go
@@ -2,7 +2,10 @@ package mocks
import (
"2024_2_FIGHT-CLUB/domain"
+ "2024_2_FIGHT-CLUB/microservices/city_service/controller/gen"
"context"
+ "github.com/stretchr/testify/mock"
+ "google.golang.org/grpc"
)
type MockCitiesRepository struct {
@@ -30,3 +33,16 @@ func (m *MockCitiesUseCase) GetCities(ctx context.Context) ([]domain.City, error
func (m *MockCitiesUseCase) GetOneCity(ctx context.Context, cityEnName string) (domain.City, error) {
return m.MockGetOneCity(ctx, cityEnName)
}
+
+type MockGrpcClient struct {
+ mock.Mock
+}
+
+func (m *MockGrpcClient) GetCities(ctx context.Context, in *gen.GetCitiesRequest, opts ...grpc.CallOption) (*gen.GetCitiesResponse, error) {
+ args := m.Called(ctx, in, opts)
+ return args.Get(0).(*gen.GetCitiesResponse), args.Error(1)
+}
+func (m *MockGrpcClient) GetOneCity(ctx context.Context, in *gen.GetOneCityRequest, opts ...grpc.CallOption) (*gen.GetOneCityResponse, error) {
+ args := m.Called(ctx, in, opts)
+ return args.Get(0).(*gen.GetOneCityResponse), args.Error(1)
+}
diff --git a/microservices/city_service/repository/cities_repository_test.go b/microservices/city_service/repository/cities_repository_test.go
index 31183ba..0e0e3a6 100644
--- a/microservices/city_service/repository/cities_repository_test.go
+++ b/microservices/city_service/repository/cities_repository_test.go
@@ -4,6 +4,7 @@ import (
"2024_2_FIGHT-CLUB/domain"
"2024_2_FIGHT-CLUB/internal/service/logger"
"context"
+ "database/sql"
"errors"
"github.com/DATA-DOG/go-sqlmock"
"github.com/stretchr/testify/assert"
@@ -19,12 +20,22 @@ func TestGetCitiesSuccess(t *testing.T) {
if err := logger.InitLoggers(); err != nil {
log.Fatalf("Failed to initialize loggers: %v", err)
}
- defer logger.SyncLoggers()
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
db, mock, err := sqlmock.New()
if err != nil {
t.Fatalf("an error '%s' was encountered while creating SQLMock database connection", err)
}
- defer db.Close()
+ defer func(db *sql.DB) {
+ err := db.Close()
+ if err != nil {
+ return
+ }
+ }(db)
gormDB, err := gorm.Open(postgres.New(postgres.Config{
Conn: db,
@@ -58,12 +69,22 @@ func TestGetCitiesFailure(t *testing.T) {
if err := logger.InitLoggers(); err != nil {
log.Fatalf("Failed to initialize loggers: %v", err)
}
- defer logger.SyncLoggers()
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
db, mock, err := sqlmock.New()
if err != nil {
t.Fatalf("an error '%s' was encountered while creating SQLMock database connection", err)
}
- defer db.Close()
+ defer func(db *sql.DB) {
+ err := db.Close()
+ if err != nil {
+ return
+ }
+ }(db)
gormDB, err := gorm.Open(postgres.New(postgres.Config{
Conn: db,
@@ -93,12 +114,22 @@ func TestGetCityByEnName(t *testing.T) {
if err := logger.InitLoggers(); err != nil {
log.Fatalf("Failed to initialize loggers: %v", err)
}
- defer logger.SyncLoggers()
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
db, mock, err := sqlmock.New()
if err != nil {
t.Fatalf("an error '%s' was encountered while creating SQLMock database connection", err)
}
- defer db.Close()
+ defer func(db *sql.DB) {
+ err := db.Close()
+ if err != nil {
+ return
+ }
+ }(db)
gormDB, err := gorm.Open(postgres.New(postgres.Config{
Conn: db,
diff --git a/microservices/city_service/usecase/cities_usecase_test.go b/microservices/city_service/usecase/cities_usecase_test.go
index d32dbb5..242dfdf 100644
--- a/microservices/city_service/usecase/cities_usecase_test.go
+++ b/microservices/city_service/usecase/cities_usecase_test.go
@@ -17,7 +17,12 @@ func TestGetCitiesSuccess(t *testing.T) {
if err := logger.InitLoggers(); err != nil {
log.Fatalf("Failed to initialize loggers: %v", err)
}
- defer logger.SyncLoggers()
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
mockRepo := &mocks.MockCitiesRepository{
MockGetCities: func(ctx context.Context) ([]domain.City, error) {
return []domain.City{
@@ -46,7 +51,12 @@ func TestGetCitiesFailure(t *testing.T) {
if err := logger.InitLoggers(); err != nil {
log.Fatalf("Failed to initialize loggers: %v", err)
}
- defer logger.SyncLoggers()
+ defer func() {
+ err := logger.SyncLoggers()
+ if err != nil {
+ return
+ }
+ }()
mockRepo := &mocks.MockCitiesRepository{
MockGetCities: func(ctx context.Context) ([]domain.City, error) {
return nil, errors.New("failed to retrieve cities")
diff --git a/proto/ads.proto b/proto/ads.proto
index f1d7a52..c4a019e 100644
--- a/proto/ads.proto
+++ b/proto/ads.proto
@@ -17,6 +17,7 @@ service Ads {
rpc AddToFavorites (AddToFavoritesRequest) returns (AdResponse);
rpc DeleteFromFavorites (DeleteFromFavoritesRequest) returns (AdResponse);
rpc GetUserFavorites (GetUserFavoritesRequest) returns (GetAllAdsResponseList);
+ rpc UpdatePriority (UpdatePriorityRequest) returns (AdResponse);
}
message Ad {
@@ -38,9 +39,21 @@ message CreateAdRequest {
google.protobuf.Timestamp dateFrom = 5;
google.protobuf.Timestamp dateTo = 6;
repeated bytes images = 7;
- string authHeader = 8;
- string sessionID = 9;
- string authorID = 10;
+ int32 squareMeters = 8;
+ int32 floor = 9;
+ string buildingType = 10;
+ bool hasBalcony = 11;
+ bool hasElevator = 12;
+ bool hasGas = 13;
+ string authHeader = 14;
+ string sessionID = 15;
+ string authorID = 16;
+ repeated AdRooms rooms = 17;
+}
+
+message AdRooms {
+ string type = 1;
+ int32 squareMeters = 2;
}
message UpdateAdRequest {
@@ -51,9 +64,16 @@ message UpdateAdRequest {
int32 roomsNumber = 5;
google.protobuf.Timestamp dateFrom = 6;
google.protobuf.Timestamp dateTo = 7;
- repeated bytes images = 8;
- string authHeader = 9;
- string sessionID = 10;
+ int32 squareMeters = 8;
+ int32 floor = 9;
+ string buildingType = 10;
+ bool hasBalcony = 11;
+ bool hasElevator = 12;
+ bool hasGas = 13;
+ repeated bytes images = 14;
+ string authHeader = 15;
+ string sessionID = 16;
+ repeated AdRooms rooms = 17;
}
message DeletePlaceRequest {
@@ -76,8 +96,7 @@ message DeleteFromFavoritesRequest {
message GetUserFavoritesRequest {
string userId = 1;
- string authHeader = 2;
- string sessionID = 3;
+ string sessionID = 2;
}
message DeleteAdImageRequest {
@@ -105,6 +124,7 @@ message AdFilterRequest {
string offset = 7;
string dateFrom = 8;
string dateTo = 9;
+ string sessionId = 10;
}
message GetAllAdsResponse {
@@ -115,12 +135,23 @@ message GetAllAdsResponse {
string publicationDate = 5;
string description = 6;
int32 roomsNumber = 7;
- optional int32 viewsCount = 8;
- string cityName = 9;
- string adDateFrom = 10;
- string adDateTo = 11;
- UserResponse adAuthor = 12;
- repeated ImageResponse images = 13;
+ int32 viewsCount = 8;
+ int32 squareMeters = 9;
+ int32 floor = 10;
+ string buildingType = 11;
+ bool hasBalcony = 12;
+ bool hasElevator = 13;
+ bool hasGas = 14;
+ int32 LikesCount = 15;
+ int32 priority = 16;
+ string endBoostDate = 17;
+ string cityName = 18;
+ string adDateFrom = 19;
+ string adDateTo = 20;
+ bool isFavorite = 21;
+ UserResponse adAuthor = 22;
+ repeated ImageResponse images = 23;
+ repeated AdRooms rooms = 24;
}
message GetAllAdsResponseList {
@@ -146,10 +177,17 @@ message ImageResponse {
}
message UserResponse {
- optional float rating = 1;
+ float rating = 1;
string avatar = 2;
string name = 3;
string sex = 4;
string birthDate = 5;
- optional int32 guestCount = 6;
+ int32 guestCount = 6;
}
+
+message UpdatePriorityRequest {
+ string adId = 1;
+ string authHeader = 2;
+ string sessionID = 3;
+ string Amount = 4;
+}
\ No newline at end of file
diff --git a/proto/auth.proto b/proto/auth.proto
index c0c3390..6899a8f 100644
--- a/proto/auth.proto
+++ b/proto/auth.proto
@@ -12,6 +12,8 @@ service Auth {
rpc GetAllUsers (Empty) returns (AllUsersResponse);
rpc GetSessionData (GetSessionDataRequest) returns (SessionDataResponse);
rpc RefreshCsrfToken (RefreshCsrfTokenRequest) returns (RefreshCsrfTokenResponse);
+ rpc UpdateUserRegions (UpdateUserRegionsRequest) returns (UpdateResponse);
+ rpc DeleteUserRegions (DeleteUserRegionsRequest) returns (UpdateResponse);
}
message RefreshCsrfTokenRequest {
@@ -116,3 +118,17 @@ message RefreshCsrfTokenResponse{
string csrf_token = 1;
}
+message DeleteUserRegionsRequest {
+ string Region = 1;
+ string authHeader = 2;
+ string session_id = 3;
+}
+
+message UpdateUserRegionsRequest {
+ string Region = 1;
+ google.protobuf.Timestamp StartVisitDate = 2;
+ google.protobuf.Timestamp EndVisitDate = 3;
+ string authHeader = 4;
+ string session_id = 5;
+}
+
diff --git a/run.sh b/run.sh
new file mode 100644
index 0000000..48a0edd
--- /dev/null
+++ b/run.sh
@@ -0,0 +1 @@
+docker compose --env-file .env up -d