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.
This commit is contained in:
n.nakhostin
2025-08-17 13:29:44 +03:30
parent 4663a9781e
commit a8ef631e43
7 changed files with 329 additions and 8 deletions
+121 -3
View File
@@ -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": {
+121 -3
View File
@@ -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": {
+1
View File
@@ -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)
}
}
+4 -2
View File
@@ -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
+32
View File
@@ -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")
}
+16
View File
@@ -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{}
+34
View File
@@ -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
}