From a8ef631e43464960f82d4010e71a4711e5007a41 Mon Sep 17 00:00:00 2001 From: "n.nakhostin" Date: Sun, 17 Aug 2025 13:29:44 +0330 Subject: [PATCH] Add Feedback Retrieval Endpoint for Tender ID - Introduced a new API endpoint to retrieve feedback details by tender ID, enhancing the feedback management system for public users. - Updated Swagger documentation to accurately reflect the new endpoint, including detailed descriptions, parameters, and response formats. - Implemented necessary service and repository methods to support the new functionality, ensuring adherence to Clean Architecture principles. - Modified existing routes to include the new feedback retrieval functionality, maintaining a clear separation of concerns in the handler layer. - Enhanced feedback types to include 'unlike' and 'undislike' options for better user interaction. --- cmd/web/docs/docs.go | 124 +++++++++++++++++++++++++++++++- cmd/web/docs/swagger.json | 124 +++++++++++++++++++++++++++++++- cmd/web/router/routes.go | 1 + internal/feedback/entity.go | 6 +- internal/feedback/handler.go | 32 +++++++++ internal/feedback/repository.go | 16 +++++ internal/feedback/service.go | 34 +++++++++ 7 files changed, 329 insertions(+), 8 deletions(-) diff --git a/cmd/web/docs/docs.go b/cmd/web/docs/docs.go index 0162c32..c321011 100644 --- a/cmd/web/docs/docs.go +++ b/cmd/web/docs/docs.go @@ -4940,6 +4940,120 @@ const docTemplate = `{ } } }, + "/api/v1/feedback/tender/{tender_id}": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Retrieve feedback details by tender ID. This endpoint provides basic feedback information for public users.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Feedback" + ], + "summary": "Get feedback by tender ID", + "parameters": [ + { + "maxLength": 24, + "minLength": 24, + "type": "string", + "description": "Tender ID (MongoDB ObjectID format)", + "name": "tender_id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "Feedback retrieved successfully", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.APIResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/feedback.FeedbackResponse" + } + } + } + ] + } + }, + "400": { + "description": "Bad request - Invalid tender ID format", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.APIResponse" + }, + { + "type": "object", + "properties": { + "error": { + "type": "string" + }, + "message": { + "type": "string" + } + } + } + ] + } + }, + "404": { + "description": "Feedback not found", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.APIResponse" + }, + { + "type": "object", + "properties": { + "error": { + "type": "string" + }, + "message": { + "type": "string" + } + } + } + ] + } + }, + "500": { + "description": "Internal server error", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.APIResponse" + }, + { + "type": "object", + "properties": { + "error": { + "type": "string" + }, + "message": { + "type": "string" + } + } + } + ] + } + } + } + } + }, "/api/v1/feedback/{id}": { "get": { "security": [ @@ -5374,7 +5488,7 @@ const docTemplate = `{ } } }, - "/api/v1/tenders/read/{id}": { + "/api/v1/tenders/details/{id}": { "get": { "security": [ { @@ -6683,11 +6797,15 @@ const docTemplate = `{ "type": "string", "enum": [ "like", - "dislike" + "dislike", + "unlike", + "undislike" ], "x-enum-varnames": [ "FeedbackTypeLike", - "FeedbackTypeDislike" + "FeedbackTypeDislike", + "FeedbackTypeUnLike", + "FeedbackTypeUnDislike" ] }, "feedback.ToggleFeedbackForm": { diff --git a/cmd/web/docs/swagger.json b/cmd/web/docs/swagger.json index fd3a269..282c1ae 100644 --- a/cmd/web/docs/swagger.json +++ b/cmd/web/docs/swagger.json @@ -4934,6 +4934,120 @@ } } }, + "/api/v1/feedback/tender/{tender_id}": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Retrieve feedback details by tender ID. This endpoint provides basic feedback information for public users.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Feedback" + ], + "summary": "Get feedback by tender ID", + "parameters": [ + { + "maxLength": 24, + "minLength": 24, + "type": "string", + "description": "Tender ID (MongoDB ObjectID format)", + "name": "tender_id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "Feedback retrieved successfully", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.APIResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/feedback.FeedbackResponse" + } + } + } + ] + } + }, + "400": { + "description": "Bad request - Invalid tender ID format", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.APIResponse" + }, + { + "type": "object", + "properties": { + "error": { + "type": "string" + }, + "message": { + "type": "string" + } + } + } + ] + } + }, + "404": { + "description": "Feedback not found", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.APIResponse" + }, + { + "type": "object", + "properties": { + "error": { + "type": "string" + }, + "message": { + "type": "string" + } + } + } + ] + } + }, + "500": { + "description": "Internal server error", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.APIResponse" + }, + { + "type": "object", + "properties": { + "error": { + "type": "string" + }, + "message": { + "type": "string" + } + } + } + ] + } + } + } + } + }, "/api/v1/feedback/{id}": { "get": { "security": [ @@ -5368,7 +5482,7 @@ } } }, - "/api/v1/tenders/read/{id}": { + "/api/v1/tenders/details/{id}": { "get": { "security": [ { @@ -6677,11 +6791,15 @@ "type": "string", "enum": [ "like", - "dislike" + "dislike", + "unlike", + "undislike" ], "x-enum-varnames": [ "FeedbackTypeLike", - "FeedbackTypeDislike" + "FeedbackTypeDislike", + "FeedbackTypeUnLike", + "FeedbackTypeUnDislike" ] }, "feedback.ToggleFeedbackForm": { diff --git a/cmd/web/router/routes.go b/cmd/web/router/routes.go index 9032192..cc244d7 100644 --- a/cmd/web/router/routes.go +++ b/cmd/web/router/routes.go @@ -138,6 +138,7 @@ func RegisterPublicRoutes(e *echo.Echo, customerHandler *customer.Handler, tende feedbackGP.Use(customerHandler.AuthMiddleware()) feedbackGP.POST("", feedbackHandler.ToggleFeedback) feedbackGP.GET("", feedbackHandler.PublicListFeedback) + feedbackGP.GET("/tender/:tender_id", feedbackHandler.PublicGetFeedbackByTenderID) feedbackGP.GET("/:id", feedbackHandler.PublicGetFeedback) } } diff --git a/internal/feedback/entity.go b/internal/feedback/entity.go index 3691b5f..d9d25a1 100644 --- a/internal/feedback/entity.go +++ b/internal/feedback/entity.go @@ -10,8 +10,10 @@ import ( type FeedbackType string const ( - FeedbackTypeLike FeedbackType = "like" - FeedbackTypeDislike FeedbackType = "dislike" + FeedbackTypeLike FeedbackType = "like" + FeedbackTypeDislike FeedbackType = "dislike" + FeedbackTypeUnLike FeedbackType = "unlike" + FeedbackTypeUnDislike FeedbackType = "undislike" ) // Feedback represents feedback given to a tender by a user diff --git a/internal/feedback/handler.go b/internal/feedback/handler.go index 441a4d6..69d02d4 100644 --- a/internal/feedback/handler.go +++ b/internal/feedback/handler.go @@ -346,3 +346,35 @@ func (h *Handler) PublicGetFeedback(c echo.Context) error { return response.Success(c, feedback, "Feedback retrieved successfully") } + +// PublicGetFeedbackByTenderID retrieves feedback by tender ID +// @Summary Get feedback by tender ID +// @Description Retrieve feedback details by tender ID. This endpoint provides basic feedback information for public users. +// @Tags Feedback +// @Accept json +// @Produce json +// @Param tender_id path string true "Tender ID (MongoDB ObjectID format)" minlength(24) maxlength(24) +// @Success 200 {object} response.APIResponse{data=FeedbackResponse} "Feedback retrieved successfully" +// @Failure 400 {object} response.APIResponse{error=string,message=string} "Bad request - Invalid tender ID format" +// @Failure 404 {object} response.APIResponse{error=string,message=string} "Feedback not found" +// @Failure 500 {object} response.APIResponse{error=string,message=string} "Internal server error" +// @Security BearerAuth +// @Router /api/v1/feedback/tender/{tender_id} [get] +func (h *Handler) PublicGetFeedbackByTenderID(c echo.Context) error { + tenderID := c.Param("tender_id") + if tenderID == "" { + return response.BadRequest(c, "Tender ID is required", "Tender ID parameter is missing") + } + + companyID, err := user.GetCompanyIDFromContext(c) + if err != nil { + return response.BadRequest(c, "Company ID required", "User must be associated with a company") + } + + feedback, err := h.service.GetFeedbackByTenderID(c.Request().Context(), tenderID, companyID) + if err != nil { + return response.BadRequest(c, "Failed to get feedback", "") + } + + return response.Success(c, feedback, "Feedback retrieved successfully") +} diff --git a/internal/feedback/repository.go b/internal/feedback/repository.go index f949541..1c6267f 100644 --- a/internal/feedback/repository.go +++ b/internal/feedback/repository.go @@ -22,6 +22,7 @@ type Repository interface { Search(ctx context.Context, criteria FeedbackSearchCriteria) ([]*Feedback, error) GetFeedbackSummary(ctx context.Context, tenderID string) (*FeedbackSummary, error) GetFeedbackCountByType(ctx context.Context) (map[FeedbackType]int64, error) + GetByTenderID(ctx context.Context, tenderID, companyID string) (*Feedback, error) } // feedbackRepository implements FeedbackRepository interface using MongoDB ORM @@ -396,6 +397,21 @@ func (r *feedbackRepository) GetCustomerFeedbackHistory(ctx context.Context, cus return feedback, result.TotalCount, nil } +func (r *feedbackRepository) GetByTenderID(ctx context.Context, tenderID, companyID string) (*Feedback, error) { + filter := bson.M{"tender_id": tenderID, "company_id": companyID} + + result, err := r.repo.FindOne(ctx, filter) + if err != nil { + r.logger.Error("Failed to get feedback by tender ID", map[string]interface{}{ + "tender_id": tenderID, + "error": err.Error(), + }) + return nil, err + } + + return result, nil +} + // buildSearchFilter builds MongoDB filter from search criteria func (r *feedbackRepository) buildSearchFilter(criteria FeedbackSearchCriteria) bson.M { filter := bson.M{} diff --git a/internal/feedback/service.go b/internal/feedback/service.go index c1afeaf..dffdc0f 100644 --- a/internal/feedback/service.go +++ b/internal/feedback/service.go @@ -12,6 +12,7 @@ type FeedbackService interface { GetFeedback(ctx context.Context, id string) (*FeedbackResponse, error) ListFeedback(ctx context.Context, criteria FeedbackSearchCriteria, limit, offset int) (*FeedbackListResponse, error) DeleteFeedback(ctx context.Context, id string) error + GetFeedbackByTenderID(ctx context.Context, tenderID, companyID string) (*FeedbackResponse, error) } // feedbackService implements FeedbackService interface @@ -52,6 +53,27 @@ func (s *feedbackService) ToggleFeedback(ctx context.Context, req ToggleFeedback return feedback.ToResponse(), nil } + if existing.FeedbackType == req.FeedbackType { + if existing.FeedbackType == FeedbackTypeLike { + existing.FeedbackType = FeedbackTypeUnLike + } else { + existing.FeedbackType = FeedbackTypeUnDislike + } + + err = s.feedbackRepo.Delete(ctx, existing.GetID()) + if err != nil { + s.logger.Error("Failed to update existing feedback", map[string]interface{}{ + "feedback_id": existing.GetID(), + "tender_id": req.TenderID, + "company_id": *companyID, + "error": err.Error(), + }) + return nil, err + } + + return existing.ToResponse(), nil + } + if existing.FeedbackType == FeedbackTypeLike { existing.FeedbackType = FeedbackTypeDislike } else { @@ -118,3 +140,15 @@ func (s *feedbackService) ListFeedback(ctx context.Context, criteria FeedbackSea func (s *feedbackService) DeleteFeedback(ctx context.Context, id string) error { return s.feedbackRepo.Delete(ctx, id) } + +func (s *feedbackService) GetFeedbackByTenderID(ctx context.Context, tenderID, companyID string) (*FeedbackResponse, error) { + feedback, err := s.feedbackRepo.GetByTenderID(ctx, tenderID, companyID) + if err != nil { + s.logger.Error("Failed to get feedback by tender ID", map[string]interface{}{ + "tender_id": tenderID, + "error": err.Error(), + }) + return nil, err + } + return feedback.ToResponse(), nil +}