a342da193e
- Updated feedback repository and service methods to use a consistent naming convention, changing `NewTenderRepository` and similar methods to `NewRepository`. - Refactored feedback handler methods to improve clarity by renaming methods such as `ListFeedback` to `Search` and `GetFeedback` to `Get`. - Enhanced feedback response structures to include detailed company and tender information, improving the clarity of feedback data returned to API consumers. - Updated Swagger documentation to reflect changes in feedback response structures and endpoint paths, ensuring accurate representation of the API for consumers.
313 lines
14 KiB
Go
313 lines
14 KiB
Go
package feedback
|
|
|
|
import (
|
|
"tm/internal/customer"
|
|
"tm/internal/user"
|
|
"tm/pkg/logger"
|
|
"tm/pkg/response"
|
|
|
|
"github.com/asaskevich/govalidator"
|
|
"github.com/labstack/echo/v4"
|
|
)
|
|
|
|
// Handler handles HTTP requests for feedback operations
|
|
type Handler struct {
|
|
service Service
|
|
logger logger.Logger
|
|
userHandler *user.Handler
|
|
customerHandler *customer.Handler
|
|
}
|
|
|
|
// NewHandler creates a new feedback handler
|
|
func NewHandler(
|
|
service Service,
|
|
userHandler *user.Handler,
|
|
customerHandler *customer.Handler,
|
|
logger logger.Logger,
|
|
) *Handler {
|
|
return &Handler{
|
|
service: service,
|
|
logger: logger,
|
|
userHandler: userHandler,
|
|
customerHandler: customerHandler,
|
|
}
|
|
}
|
|
|
|
// *************** ADMIN ***************
|
|
// ListFeedback retrieves feedback with pagination and filtering
|
|
// @Summary List feedback with filters
|
|
// @Description Retrieve paginated list of feedback with optional filtering by various criteria. This endpoint is for administrative use and provides comprehensive filtering options.
|
|
// @Tags Admin-Feedback
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param tender_id query string false "Filter by tender ID (MongoDB ObjectID format)"
|
|
// @Param customer_id query string false "Filter by customer ID (MongoDB ObjectID format)"
|
|
// @Param company_id query string false "Filter by company ID (MongoDB ObjectID format)"
|
|
// @Param feedback_type query string false "Filter by feedback type" Enums(like, dislike)
|
|
// @Param date_from query int64 false "Filter by creation date from (Unix timestamp in seconds)"
|
|
// @Param date_to query int64 false "Filter by creation date to (Unix timestamp in seconds)"
|
|
// @Param limit query int false "Number of items per page (default: 20, max: 100)" minimum(1) maximum(100)
|
|
// @Param offset query int false "Number of items to skip for pagination (default: 0)" minimum(0)
|
|
// @Success 200 {object} response.APIResponse{data=FeedbackListResponse} "Feedback list retrieved successfully"
|
|
// @Failure 400 {object} response.APIResponse{error=string,message=string} "Bad request - Invalid query parameters"
|
|
// @Failure 422 {object} response.APIResponse{error=string,message=string} "Validation error - Invalid filter criteria"
|
|
// @Failure 500 {object} response.APIResponse{error=string,message=string} "Internal server error"
|
|
// @Security BearerAuth
|
|
// @Router /admin/v1/feedback [get]
|
|
func (h *Handler) Search(c echo.Context) error {
|
|
form, err := response.Parse[SearchForm](c)
|
|
if err != nil {
|
|
return response.ValidationError(c, "Invalid request data", err.Error())
|
|
}
|
|
|
|
feedbackList, err := h.service.Search(c.Request().Context(), *form, response.NewPagination(c))
|
|
if err != nil {
|
|
return response.InternalServerError(c, "Failed to retrieve feedback list")
|
|
}
|
|
|
|
return response.Success(c, feedbackList.Feedback, "feedback list retrieved successfully")
|
|
}
|
|
|
|
// GetFeedbackByID retrieves feedback by ID with loaded related data
|
|
// @Summary Get feedback by ID
|
|
// @Description Retrieve feedback details by its unique identifier with loaded tender, customer, and company information. This endpoint provides comprehensive feedback data including related entity details.
|
|
// @Tags Admin-Feedback
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param id path string true "Feedback 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 feedback 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 /admin/v1/feedback/{id} [get]
|
|
func (h *Handler) Get(c echo.Context) error {
|
|
feedbackID := c.Param("id")
|
|
if feedbackID == "" {
|
|
return response.BadRequest(c, "Feedback ID is required", "Feedback ID parameter is missing")
|
|
}
|
|
|
|
feedback, err := h.service.Get(c.Request().Context(), feedbackID)
|
|
if err != nil {
|
|
if err == ErrFeedbackNotFound {
|
|
return response.NotFound(c, "Feedback not found")
|
|
}
|
|
if err == ErrFeedbackDeleted {
|
|
return response.NotFound(c, "Feedback has been deleted")
|
|
}
|
|
return response.InternalServerError(c, "Failed to retrieve feedback")
|
|
}
|
|
|
|
return response.Success(c, feedback, "feedback retrieved successfully")
|
|
}
|
|
|
|
// SoftDeleteFeedback marks feedback as deleted without removing it
|
|
// @Summary Soft delete feedback
|
|
// @Description Mark feedback as deleted without permanently removing it from the database. The feedback will be marked as deleted and hidden from normal queries but can be restored if needed.
|
|
// @Tags Admin-Feedback
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param id path string true "Feedback ID (MongoDB ObjectID format)" minlength(24) maxlength(24)
|
|
// @Success 200 {object} response.APIResponse{message=string} "Feedback soft deleted successfully"
|
|
// @Failure 400 {object} response.APIResponse{error=string,message=string} "Bad request - Invalid feedback ID format"
|
|
// @Failure 401 {object} response.APIResponse{error=string,message=string} "Unauthorized - User not authenticated"
|
|
// @Failure 403 {object} response.APIResponse{error=string,message=string} "Forbidden - User not authorized to delete this feedback"
|
|
// @Failure 404 {object} response.APIResponse{error=string,message=string} "Not found - Feedback not found"
|
|
// @Failure 500 {object} response.APIResponse{error=string,message=string} "Internal server error"
|
|
// @Security BearerAuth
|
|
// @Router /admin/v1/feedback/{id} [delete]
|
|
func (h *Handler) Delete(c echo.Context) error {
|
|
id := c.Param("id")
|
|
if id == "" {
|
|
return response.BadRequest(c, "Feedback ID is required", "")
|
|
}
|
|
|
|
// Call service
|
|
err := h.service.Delete(c.Request().Context(), id)
|
|
if err != nil {
|
|
switch err {
|
|
case ErrFeedbackNotFound:
|
|
return response.BadRequest(c, "Feedback not found", "")
|
|
case ErrUnauthorizedAccess:
|
|
return response.Forbidden(c, "You are not authorized to delete this feedback")
|
|
default:
|
|
return response.InternalServerError(c, "Failed to soft delete feedback")
|
|
}
|
|
}
|
|
|
|
return response.Success(c, nil, "Feedback soft deleted successfully")
|
|
}
|
|
|
|
// *************** PUBLIC ***************
|
|
// ToggleFeedback toggles between like and dislike for a tender
|
|
// @Summary Toggle feedback for a tender
|
|
// @Description Toggle between like and dislike for a tender, or create a like if no feedback exists. This endpoint allows users to express their preference for tenders and automatically handles the toggle logic.
|
|
// @Tags Feedback
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param feedback body ToggleFeedbackForm true "Feedback toggle request containing tender ID"
|
|
// @Success 200 {object} response.APIResponse{data=FeedbackResponse} "Feedback toggled successfully"
|
|
// @Failure 400 {object} response.APIResponse{error=string,message=string} "Bad request - Invalid input data or missing customer/company association"
|
|
// @Failure 401 {object} response.APIResponse{error=string,message=string} "Unauthorized - User not authenticated"
|
|
// @Failure 422 {object} response.APIResponse{error=string,message=string} "Validation error - Invalid request data format"
|
|
// @Failure 500 {object} response.APIResponse{error=string,message=string} "Internal server error"
|
|
// @Security BearerAuth
|
|
// @Router /api/v1/feedback [post]
|
|
func (h *Handler) Toggle(c echo.Context) error {
|
|
form, err := response.Parse[ToggleFeedbackForm](c)
|
|
if err != nil {
|
|
return response.ValidationError(c, "Invalid request data", err.Error())
|
|
}
|
|
|
|
// Validate form using govalidator
|
|
if _, err := govalidator.ValidateStruct(form); err != nil {
|
|
return response.ValidationError(c, "Validation failed", err.Error())
|
|
}
|
|
|
|
// Get customer ID from context
|
|
customerID, err := customer.GetCustomerIDFromContext(c)
|
|
if err != nil {
|
|
return response.BadRequest(c, "Customer ID required", "User must be associated with a customer")
|
|
}
|
|
|
|
// Get company ID from context
|
|
companyID, err := user.GetCompanyIDFromContext(c)
|
|
if err != nil {
|
|
return response.BadRequest(c, "Company ID required", "User must be associated with a company")
|
|
}
|
|
|
|
// Call service
|
|
result, err := h.service.Toggle(c.Request().Context(), *form, &customerID, &companyID)
|
|
if err != nil {
|
|
return response.InternalServerError(c, "Failed to toggle feedback")
|
|
}
|
|
|
|
return response.Success(c, result, "Feedback toggled successfully")
|
|
}
|
|
|
|
// ListFeedback retrieves feedback with pagination and filtering
|
|
// @Summary List feedback with filters
|
|
// @Description Retrieve paginated list of feedback with optional filtering by various criteria. This endpoint is for public use and filters results based on the authenticated user's company.
|
|
// @Tags Feedback
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param tender_id query string false "Filter by tender ID (MongoDB ObjectID format)"
|
|
// @Param customer_id query string false "Filter by customer ID (MongoDB ObjectID format)"
|
|
// @Param company_id query string false "Filter by company ID (MongoDB ObjectID format)"
|
|
// @Param feedback_type query string false "Filter by feedback type" Enums(like, dislike)
|
|
// @Param date_from query int64 false "Filter by creation date from (Unix timestamp in seconds)" minimum(0)
|
|
// @Param date_to query int64 false "Filter by creation date to (Unix timestamp in seconds)" minimum(0)
|
|
// @Param limit query int false "Number of items per page (default: 20, max: 100)" minimum(1) maximum(100)
|
|
// @Param offset query int false "Number of items to skip for pagination (default: 0)" minimum(0)
|
|
// @Success 200 {object} response.APIResponse{data=FeedbackListResponse} "Feedback list retrieved successfully"
|
|
// @Failure 400 {object} response.APIResponse{error=string,message=string} "Bad request - Invalid query parameters or missing company association"
|
|
// @Failure 422 {object} response.APIResponse{error=string,message=string} "Validation error - Invalid filter criteria"
|
|
// @Failure 500 {object} response.APIResponse{error=string,message=string} "Internal server error"
|
|
// @Security BearerAuth
|
|
// @Router /api/v1/feedback [get]
|
|
func (h *Handler) PublicList(c echo.Context) error {
|
|
companyID, err := user.GetCompanyIDFromContext(c)
|
|
if err != nil {
|
|
return response.BadRequest(c, "Company ID required", "User must be associated with a company")
|
|
}
|
|
|
|
form, err := response.Parse[SearchForm](c)
|
|
if err != nil {
|
|
return response.ValidationError(c, "Invalid request data", err.Error())
|
|
}
|
|
form.Company = &companyID
|
|
|
|
// Call service
|
|
result, err := h.service.Search(c.Request().Context(), *form, response.NewPagination(c))
|
|
if err != nil {
|
|
return response.InternalServerError(c, "Failed to list feedback")
|
|
}
|
|
|
|
return response.Success(c, result, "Feedback list retrieved successfully")
|
|
}
|
|
|
|
// PublicGetFeedback retrieves feedback by ID
|
|
// @Summary Get feedback by ID
|
|
// @Description Retrieve feedback details by its unique identifier. This endpoint provides basic feedback information for public users.
|
|
// @Tags Feedback
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param id path string true "Feedback 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 feedback 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/{id} [get]
|
|
func (h *Handler) PublicGet(c echo.Context) error {
|
|
feedbackID := c.Param("id")
|
|
if feedbackID == "" {
|
|
return response.BadRequest(c, "Feedback ID is required", "Feedback ID parameter is missing")
|
|
}
|
|
|
|
feedback, err := h.service.Get(c.Request().Context(), feedbackID)
|
|
if err != nil {
|
|
return response.BadRequest(c, "Failed to get feedback", "")
|
|
}
|
|
|
|
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) PublicGetByTenderID(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.GetByTenderID(c.Request().Context(), tenderID, companyID)
|
|
if err != nil {
|
|
return response.BadRequest(c, "Failed to get feedback", "")
|
|
}
|
|
|
|
return response.Success(c, feedback, "Feedback retrieved successfully")
|
|
}
|
|
|
|
// GetCompanyFeedbackStats retrieves feedback statistics for the authenticated user's company
|
|
// @Summary Get company feedback statistics
|
|
// @Description Retrieve comprehensive feedback statistics for the authenticated user's company including total likes, dislikes, and percentage calculations.
|
|
// @Tags Feedback
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Success 200 {object} response.APIResponse{data=StatsResponse} "Company feedback statistics retrieved successfully"
|
|
// @Failure 400 {object} response.APIResponse{error=string,message=string} "Bad request - Missing company association"
|
|
// @Failure 401 {object} response.APIResponse{error=string,message=string} "Unauthorized - User not authenticated"
|
|
// @Failure 500 {object} response.APIResponse{error=string,message=string} "Internal server error"
|
|
// @Security BearerAuth
|
|
// @Router /api/v1/feedback/stats/company [get]
|
|
func (h *Handler) GetCompanyStats(c echo.Context) error {
|
|
companyID, err := user.GetCompanyIDFromContext(c)
|
|
if err != nil {
|
|
return response.BadRequest(c, "Company ID required", "User must be associated with a company")
|
|
}
|
|
|
|
stats, err := h.service.CompanyStats(c.Request().Context(), companyID)
|
|
if err != nil {
|
|
return response.InternalServerError(c, "Failed to retrieve company stats")
|
|
}
|
|
|
|
return response.Success(c, stats, "Company stats retrieved successfully")
|
|
}
|