Refactor Feedback Module for Consistency and Clarity
- 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.
This commit is contained in:
@@ -76,7 +76,7 @@ func InitMongoDB(conf config.MongoConfig, log logger.Logger) *mongo.ConnectionMa
|
|||||||
// init TED scraper
|
// init TED scraper
|
||||||
func InitTEDScraper(config Config, mongoManager *mongo.ConnectionManager, appLogger logger.Logger) {
|
func InitTEDScraper(config Config, mongoManager *mongo.ConnectionManager, appLogger logger.Logger) {
|
||||||
// Initialize tender repository
|
// Initialize tender repository
|
||||||
tenderRepo := tender.NewTenderRepository(mongoManager, appLogger)
|
tenderRepo := tender.NewRepository(mongoManager, appLogger)
|
||||||
|
|
||||||
// Initialize TED scraper
|
// Initialize TED scraper
|
||||||
tedScraper := ted.NewTEDScraper(
|
tedScraper := ted.NewTEDScraper(
|
||||||
|
|||||||
+102
-22
@@ -5026,7 +5026,7 @@ const docTemplate = `{
|
|||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"data": {
|
"data": {
|
||||||
"$ref": "#/definitions/feedback.CompanyFeedbackStatsResponse"
|
"$ref": "#/definitions/feedback.StatsResponse"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -7675,27 +7675,6 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"feedback.CompanyFeedbackStatsResponse": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"company_id": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"last_updated": {
|
|
||||||
"description": "Unix timestamp",
|
|
||||||
"type": "integer"
|
|
||||||
},
|
|
||||||
"total_dislikes": {
|
|
||||||
"type": "integer"
|
|
||||||
},
|
|
||||||
"total_feedback": {
|
|
||||||
"type": "integer"
|
|
||||||
},
|
|
||||||
"total_likes": {
|
|
||||||
"type": "integer"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"feedback.FeedbackListResponse": {
|
"feedback.FeedbackListResponse": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -7713,6 +7692,9 @@ const docTemplate = `{
|
|||||||
"feedback.FeedbackResponse": {
|
"feedback.FeedbackResponse": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"company": {
|
||||||
|
"$ref": "#/definitions/feedback.companyResponse"
|
||||||
|
},
|
||||||
"company_id": {
|
"company_id": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
@@ -7729,6 +7711,9 @@ const docTemplate = `{
|
|||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"tender": {
|
"tender": {
|
||||||
|
"$ref": "#/definitions/feedback.tenderResponse"
|
||||||
|
},
|
||||||
|
"tender_id": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"updated_at": {
|
"updated_at": {
|
||||||
@@ -7747,6 +7732,29 @@ const docTemplate = `{
|
|||||||
"FeedbackTypeDislike"
|
"FeedbackTypeDislike"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"feedback.StatsResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"company": {
|
||||||
|
"$ref": "#/definitions/feedback.companyResponse"
|
||||||
|
},
|
||||||
|
"company_id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"last_updated": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"total_dislikes": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"total_feedback": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"total_likes": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"feedback.ToggleFeedbackForm": {
|
"feedback.ToggleFeedbackForm": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
@@ -7770,6 +7778,78 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"feedback.companyResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"feedback.organizationResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"feedback.tenderResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"buyer_organization": {
|
||||||
|
"$ref": "#/definitions/feedback.organizationResponse"
|
||||||
|
},
|
||||||
|
"country_code": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"currency": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"duration": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"duration_unit": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"estimated_value": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"notice_publication_id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"procedure_code": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"procurement_type_code": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"publication_date": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"submission_deadline": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"tender_deadline": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"inquiry.CreateInquiryForm": {
|
"inquiry.CreateInquiryForm": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|||||||
+102
-22
@@ -5020,7 +5020,7 @@
|
|||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"data": {
|
"data": {
|
||||||
"$ref": "#/definitions/feedback.CompanyFeedbackStatsResponse"
|
"$ref": "#/definitions/feedback.StatsResponse"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -7669,27 +7669,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"feedback.CompanyFeedbackStatsResponse": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"company_id": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"last_updated": {
|
|
||||||
"description": "Unix timestamp",
|
|
||||||
"type": "integer"
|
|
||||||
},
|
|
||||||
"total_dislikes": {
|
|
||||||
"type": "integer"
|
|
||||||
},
|
|
||||||
"total_feedback": {
|
|
||||||
"type": "integer"
|
|
||||||
},
|
|
||||||
"total_likes": {
|
|
||||||
"type": "integer"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"feedback.FeedbackListResponse": {
|
"feedback.FeedbackListResponse": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -7707,6 +7686,9 @@
|
|||||||
"feedback.FeedbackResponse": {
|
"feedback.FeedbackResponse": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"company": {
|
||||||
|
"$ref": "#/definitions/feedback.companyResponse"
|
||||||
|
},
|
||||||
"company_id": {
|
"company_id": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
@@ -7723,6 +7705,9 @@
|
|||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"tender": {
|
"tender": {
|
||||||
|
"$ref": "#/definitions/feedback.tenderResponse"
|
||||||
|
},
|
||||||
|
"tender_id": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"updated_at": {
|
"updated_at": {
|
||||||
@@ -7741,6 +7726,29 @@
|
|||||||
"FeedbackTypeDislike"
|
"FeedbackTypeDislike"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"feedback.StatsResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"company": {
|
||||||
|
"$ref": "#/definitions/feedback.companyResponse"
|
||||||
|
},
|
||||||
|
"company_id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"last_updated": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"total_dislikes": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"total_feedback": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"total_likes": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"feedback.ToggleFeedbackForm": {
|
"feedback.ToggleFeedbackForm": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
@@ -7764,6 +7772,78 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"feedback.companyResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"feedback.organizationResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"feedback.tenderResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"buyer_organization": {
|
||||||
|
"$ref": "#/definitions/feedback.organizationResponse"
|
||||||
|
},
|
||||||
|
"country_code": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"currency": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"duration": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"duration_unit": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"estimated_value": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"notice_publication_id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"procedure_code": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"procurement_type_code": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"publication_date": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"submission_deadline": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"tender_deadline": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"inquiry.CreateInquiryForm": {
|
"inquiry.CreateInquiryForm": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|||||||
+67
-15
@@ -512,20 +512,6 @@ definitions:
|
|||||||
example: reset_token_here
|
example: reset_token_here
|
||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
feedback.CompanyFeedbackStatsResponse:
|
|
||||||
properties:
|
|
||||||
company_id:
|
|
||||||
type: string
|
|
||||||
last_updated:
|
|
||||||
description: Unix timestamp
|
|
||||||
type: integer
|
|
||||||
total_dislikes:
|
|
||||||
type: integer
|
|
||||||
total_feedback:
|
|
||||||
type: integer
|
|
||||||
total_likes:
|
|
||||||
type: integer
|
|
||||||
type: object
|
|
||||||
feedback.FeedbackListResponse:
|
feedback.FeedbackListResponse:
|
||||||
properties:
|
properties:
|
||||||
feedback:
|
feedback:
|
||||||
@@ -537,6 +523,8 @@ definitions:
|
|||||||
type: object
|
type: object
|
||||||
feedback.FeedbackResponse:
|
feedback.FeedbackResponse:
|
||||||
properties:
|
properties:
|
||||||
|
company:
|
||||||
|
$ref: '#/definitions/feedback.companyResponse'
|
||||||
company_id:
|
company_id:
|
||||||
type: string
|
type: string
|
||||||
created_at:
|
created_at:
|
||||||
@@ -548,6 +536,8 @@ definitions:
|
|||||||
id:
|
id:
|
||||||
type: string
|
type: string
|
||||||
tender:
|
tender:
|
||||||
|
$ref: '#/definitions/feedback.tenderResponse'
|
||||||
|
tender_id:
|
||||||
type: string
|
type: string
|
||||||
updated_at:
|
updated_at:
|
||||||
type: integer
|
type: integer
|
||||||
@@ -560,6 +550,21 @@ definitions:
|
|||||||
x-enum-varnames:
|
x-enum-varnames:
|
||||||
- FeedbackTypeLike
|
- FeedbackTypeLike
|
||||||
- FeedbackTypeDislike
|
- FeedbackTypeDislike
|
||||||
|
feedback.StatsResponse:
|
||||||
|
properties:
|
||||||
|
company:
|
||||||
|
$ref: '#/definitions/feedback.companyResponse'
|
||||||
|
company_id:
|
||||||
|
type: string
|
||||||
|
last_updated:
|
||||||
|
type: integer
|
||||||
|
total_dislikes:
|
||||||
|
type: integer
|
||||||
|
total_feedback:
|
||||||
|
type: integer
|
||||||
|
total_likes:
|
||||||
|
type: integer
|
||||||
|
type: object
|
||||||
feedback.ToggleFeedbackForm:
|
feedback.ToggleFeedbackForm:
|
||||||
properties:
|
properties:
|
||||||
feedback_type:
|
feedback_type:
|
||||||
@@ -574,6 +579,53 @@ definitions:
|
|||||||
- feedback_type
|
- feedback_type
|
||||||
- tender_id
|
- tender_id
|
||||||
type: object
|
type: object
|
||||||
|
feedback.companyResponse:
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: string
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
feedback.organizationResponse:
|
||||||
|
properties:
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
feedback.tenderResponse:
|
||||||
|
properties:
|
||||||
|
buyer_organization:
|
||||||
|
$ref: '#/definitions/feedback.organizationResponse'
|
||||||
|
country_code:
|
||||||
|
type: string
|
||||||
|
currency:
|
||||||
|
type: string
|
||||||
|
description:
|
||||||
|
type: string
|
||||||
|
duration:
|
||||||
|
type: string
|
||||||
|
duration_unit:
|
||||||
|
type: string
|
||||||
|
estimated_value:
|
||||||
|
type: number
|
||||||
|
id:
|
||||||
|
type: string
|
||||||
|
notice_publication_id:
|
||||||
|
type: string
|
||||||
|
procedure_code:
|
||||||
|
type: string
|
||||||
|
procurement_type_code:
|
||||||
|
type: string
|
||||||
|
publication_date:
|
||||||
|
type: integer
|
||||||
|
status:
|
||||||
|
type: string
|
||||||
|
submission_deadline:
|
||||||
|
type: integer
|
||||||
|
tender_deadline:
|
||||||
|
type: integer
|
||||||
|
title:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
inquiry.CreateInquiryForm:
|
inquiry.CreateInquiryForm:
|
||||||
properties:
|
properties:
|
||||||
company_name:
|
company_name:
|
||||||
@@ -4508,7 +4560,7 @@ paths:
|
|||||||
- $ref: '#/definitions/response.APIResponse'
|
- $ref: '#/definitions/response.APIResponse'
|
||||||
- properties:
|
- properties:
|
||||||
data:
|
data:
|
||||||
$ref: '#/definitions/feedback.CompanyFeedbackStatsResponse'
|
$ref: '#/definitions/feedback.StatsResponse'
|
||||||
type: object
|
type: object
|
||||||
"400":
|
"400":
|
||||||
description: Bad request - Missing company association
|
description: Bad request - Missing company association
|
||||||
|
|||||||
+21
-21
@@ -134,14 +134,14 @@ func main() {
|
|||||||
customerAuthService := bootstrap.InitAuthorizationService(conf.CustomerAuth.JWT, redisClient, logger)
|
customerAuthService := bootstrap.InitAuthorizationService(conf.CustomerAuth.JWT, redisClient, logger)
|
||||||
|
|
||||||
// Initialize repositories with MongoDB connection manager
|
// Initialize repositories with MongoDB connection manager
|
||||||
userRepository := user.NewUserRepository(mongoManager, logger)
|
userRepository := user.NewRepository(mongoManager, logger)
|
||||||
customerRepository := customer.NewCustomerRepository(mongoManager, logger)
|
customerRepository := customer.NewRepository(mongoManager, logger)
|
||||||
companyRepository := company.NewCompanyRepository(mongoManager, logger)
|
companyRepository := company.NewRepository(mongoManager, logger)
|
||||||
categoryRepository := company_category.NewCategoryRepository(mongoManager, logger)
|
categoryRepository := company_category.NewRepository(mongoManager, logger)
|
||||||
tenderRepository := tender.NewTenderRepository(mongoManager, logger)
|
tenderRepository := tender.NewRepository(mongoManager, logger)
|
||||||
feedbackRepo := feedback.NewFeedbackRepository(mongoManager, logger)
|
feedbackRepo := feedback.NewRepository(mongoManager, logger)
|
||||||
tenderApprovalRepository := tender_approval.NewTenderApprovalRepository(mongoManager, logger)
|
tenderApprovalRepository := tender_approval.NewRepository(mongoManager, logger)
|
||||||
inquiryRepository := inquiry.NewInquiryRepository(mongoManager, logger)
|
inquiryRepository := inquiry.NewRepository(mongoManager, logger)
|
||||||
logger.Info("Repositories initialized successfully", map[string]interface{}{
|
logger.Info("Repositories initialized successfully", map[string]interface{}{
|
||||||
"repositories": []string{"customer", "user", "company", "category", "tender", "feedback", "tender_approval", "inquiry"},
|
"repositories": []string{"customer", "user", "company", "category", "tender", "feedback", "tender_approval", "inquiry"},
|
||||||
})
|
})
|
||||||
@@ -150,27 +150,27 @@ func main() {
|
|||||||
userValidator := user.NewValidationService()
|
userValidator := user.NewValidationService()
|
||||||
|
|
||||||
// Initialize services with repositories
|
// Initialize services with repositories
|
||||||
userService := user.NewUserService(userRepository, logger, userAuthService, userValidator, notificationSDK)
|
userService := user.NewService(userRepository, logger, userAuthService, userValidator, notificationSDK)
|
||||||
categoryService := company_category.NewCategoryService(categoryRepository, logger)
|
categoryService := company_category.NewService(categoryRepository, logger)
|
||||||
companyService := company.NewCompanyService(companyRepository, categoryService, logger)
|
companyService := company.NewService(companyRepository, categoryService, logger)
|
||||||
customerService := customer.New(customerRepository, logger, customerAuthService, companyService, redisClient, notificationSDK)
|
customerService := customer.NewService(customerRepository, logger, customerAuthService, companyService, redisClient, notificationSDK)
|
||||||
tenderService := tender.NewTenderService(tenderRepository, companyService, logger)
|
tenderService := tender.NewService(tenderRepository, companyService, logger)
|
||||||
feedbackService := feedback.NewFeedbackService(feedbackRepo, tenderService, logger)
|
feedbackService := feedback.NewService(feedbackRepo, tenderService, companyService, logger)
|
||||||
tenderApprovalService := tender_approval.NewTenderApprovalService(tenderApprovalRepository, tenderService, logger)
|
tenderApprovalService := tender_approval.NewService(tenderApprovalRepository, tenderService, logger)
|
||||||
inquiryService := inquiry.NewInquiryService(inquiryRepository, notificationSDK, logger)
|
inquiryService := inquiry.NewService(inquiryRepository, notificationSDK, logger)
|
||||||
flagService := assets.NewFlagService(logger, conf.Assets.FlagsPath)
|
flagService := assets.NewService(logger, conf.Assets.FlagsPath)
|
||||||
notificationService := notification.NewNotificationService(notificationSDK, userService, customerService, logger)
|
notificationService := notification.NewService(notificationSDK, userService, customerService, logger)
|
||||||
logger.Info("Services initialized successfully", map[string]interface{}{
|
logger.Info("Services initialized successfully", map[string]interface{}{
|
||||||
"services": []string{"customer", "user", "company", "category", "tender", "feedback", "tender_approval", "inquiry", "flag", "notification"},
|
"services": []string{"customer", "user", "company", "category", "tender", "feedback", "tender_approval", "inquiry", "flag", "notification"},
|
||||||
})
|
})
|
||||||
|
|
||||||
// Initialize handlers with services
|
// Initialize handlers with services
|
||||||
userHandler := user.NewUserHandler(userService, logger, userValidator, userAuthService)
|
userHandler := user.NewHandler(userService, logger, userValidator, userAuthService)
|
||||||
customerHandler := customer.NewHandler(customerService, userHandler, customerAuthService, logger)
|
customerHandler := customer.NewHandler(customerService, userHandler, customerAuthService, logger)
|
||||||
companyHandler := company.NewHandler(companyService, userHandler, logger)
|
companyHandler := company.NewHandler(companyService, userHandler, logger)
|
||||||
categoryHandler := company_category.NewHandler(categoryService, logger)
|
categoryHandler := company_category.NewHandler(categoryService, logger)
|
||||||
tenderHandler := tender.NewTenderHandler(tenderService, logger)
|
tenderHandler := tender.NewHandler(tenderService, logger)
|
||||||
feedbackHandler := feedback.NewFeedbackHandler(feedbackService, userHandler, customerHandler, logger)
|
feedbackHandler := feedback.NewHandler(feedbackService, userHandler, customerHandler, logger)
|
||||||
tenderApprovalHandler := tender_approval.NewHandler(tenderApprovalService, logger)
|
tenderApprovalHandler := tender_approval.NewHandler(tenderApprovalService, logger)
|
||||||
inquiryHandler := inquiry.NewHandler(inquiryService, logger)
|
inquiryHandler := inquiry.NewHandler(inquiryService, logger)
|
||||||
flagHandler := assets.NewHandler(flagService, logger)
|
flagHandler := assets.NewHandler(flagService, logger)
|
||||||
|
|||||||
@@ -99,9 +99,9 @@ func RegisterAdminRoutes(e *echo.Echo, userHandler *user.Handler, companyHandler
|
|||||||
feedbackGP := adminV1.Group("/feedback")
|
feedbackGP := adminV1.Group("/feedback")
|
||||||
{
|
{
|
||||||
feedbackGP.Use(userHandler.AuthMiddleware())
|
feedbackGP.Use(userHandler.AuthMiddleware())
|
||||||
feedbackGP.GET("", feedbackHandler.ListFeedback)
|
feedbackGP.GET("", feedbackHandler.Search)
|
||||||
feedbackGP.GET("/:id", feedbackHandler.GetFeedback)
|
feedbackGP.GET("/:id", feedbackHandler.Get)
|
||||||
feedbackGP.DELETE("/:id", feedbackHandler.DeleteFeedback)
|
feedbackGP.DELETE("/:id", feedbackHandler.Delete)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Admin Tender-Approvals Routes
|
// Admin Tender-Approvals Routes
|
||||||
@@ -175,11 +175,11 @@ func RegisterPublicRoutes(e *echo.Echo, customerHandler *customer.Handler, tende
|
|||||||
feedbackGP := v1.Group("/feedback")
|
feedbackGP := v1.Group("/feedback")
|
||||||
{
|
{
|
||||||
feedbackGP.Use(customerHandler.AuthMiddleware())
|
feedbackGP.Use(customerHandler.AuthMiddleware())
|
||||||
feedbackGP.POST("", feedbackHandler.ToggleFeedback)
|
feedbackGP.POST("", feedbackHandler.Toggle)
|
||||||
feedbackGP.GET("", feedbackHandler.PublicListFeedback)
|
feedbackGP.GET("", feedbackHandler.PublicList)
|
||||||
feedbackGP.GET("/tender/:tender_id", feedbackHandler.PublicGetFeedbackByTenderID)
|
feedbackGP.GET("/tender/:tender_id", feedbackHandler.PublicGetByTenderID)
|
||||||
feedbackGP.GET("/:id", feedbackHandler.PublicGetFeedback)
|
feedbackGP.GET("/:id", feedbackHandler.PublicGet)
|
||||||
feedbackGP.GET("/stats/company", feedbackHandler.GetCompanyFeedbackStats)
|
feedbackGP.GET("/stats/company", feedbackHandler.GetCompanyStats)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Public Tender Approvals Routes
|
// Public Tender Approvals Routes
|
||||||
|
|||||||
@@ -20,8 +20,8 @@ type flagService struct {
|
|||||||
flagsPath string
|
flagsPath string
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewFlagService creates a new flag service
|
// NewService creates a new flag service
|
||||||
func NewFlagService(logger logger.Logger, flagsPath string) Service {
|
func NewService(logger logger.Logger, flagsPath string) Service {
|
||||||
return &flagService{
|
return &flagService{
|
||||||
logger: logger,
|
logger: logger,
|
||||||
flagsPath: flagsPath,
|
flagsPath: flagsPath,
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ type companyRepository struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewCompanyRepository creates a new company repository
|
// NewCompanyRepository creates a new company repository
|
||||||
func NewCompanyRepository(mongoManager *orm.ConnectionManager, logger logger.Logger) Repository {
|
func NewRepository(mongoManager *orm.ConnectionManager, logger logger.Logger) Repository {
|
||||||
// Create indexes using the ORM's index management
|
// Create indexes using the ORM's index management
|
||||||
indexes := []orm.Index{
|
indexes := []orm.Index{
|
||||||
*orm.NewIndex("registration_number_idx", bson.D{{Key: "registration_number", Value: 1}}),
|
*orm.NewIndex("registration_number_idx", bson.D{{Key: "registration_number", Value: 1}}),
|
||||||
|
|||||||
@@ -36,8 +36,8 @@ type companyService struct {
|
|||||||
logger logger.Logger
|
logger logger.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewCompanyService creates a new company service
|
// NewService creates a new company service
|
||||||
func NewCompanyService(repository Repository, categoryService company_category.Service, logger logger.Logger) Service {
|
func NewService(repository Repository, categoryService company_category.Service, logger logger.Logger) Service {
|
||||||
return &companyService{
|
return &companyService{
|
||||||
repository: repository,
|
repository: repository,
|
||||||
categoryService: categoryService,
|
categoryService: categoryService,
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ type categoryRepository struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewCategoryRepository creates a new category repository
|
// NewCategoryRepository creates a new category repository
|
||||||
func NewCategoryRepository(mongoManager *orm.ConnectionManager, logger logger.Logger) Repository {
|
func NewRepository(mongoManager *orm.ConnectionManager, logger logger.Logger) Repository {
|
||||||
// Create indexes using the ORM's index management
|
// Create indexes using the ORM's index management
|
||||||
indexes := []orm.Index{
|
indexes := []orm.Index{
|
||||||
*orm.NewIndex("name_idx", bson.D{{Key: "name", Value: 1}}),
|
*orm.NewIndex("name_idx", bson.D{{Key: "name", Value: 1}}),
|
||||||
|
|||||||
@@ -30,8 +30,8 @@ type categoryService struct {
|
|||||||
logger logger.Logger
|
logger logger.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewCategoryService creates a new category service
|
// NewService creates a new category service
|
||||||
func NewCategoryService(repository Repository, logger logger.Logger) Service {
|
func NewService(repository Repository, logger logger.Logger) Service {
|
||||||
return &categoryService{
|
return &categoryService{
|
||||||
repository: repository,
|
repository: repository,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ func collection() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewCustomerRepository creates a new customer repository
|
// NewCustomerRepository creates a new customer repository
|
||||||
func NewCustomerRepository(mongoManager *orm.ConnectionManager, logger logger.Logger) Repository {
|
func NewRepository(mongoManager *orm.ConnectionManager, logger logger.Logger) Repository {
|
||||||
// Create indexes using the ORM's index management
|
// Create indexes using the ORM's index management
|
||||||
indexes := []orm.Index{
|
indexes := []orm.Index{
|
||||||
*orm.CreateUniqueIndex("username_idx", bson.D{{Key: "username", Value: 1}}),
|
*orm.CreateUniqueIndex("username_idx", bson.D{{Key: "username", Value: 1}}),
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ type customerService struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new customer service
|
// New creates a new customer service
|
||||||
func New(repository Repository, logger logger.Logger, authService authorization.AuthorizationService, companyService company.Service, redisClient redis.Client, notificationSDK notification.SDK) Service {
|
func NewService(repository Repository, logger logger.Logger, authService authorization.AuthorizationService, companyService company.Service, redisClient redis.Client, notificationSDK notification.SDK) Service {
|
||||||
return &customerService{
|
return &customerService{
|
||||||
repository: repository,
|
repository: repository,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
|
|||||||
+26
-26
@@ -1,8 +1,9 @@
|
|||||||
package feedback
|
package feedback
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"time"
|
|
||||||
"tm/pkg/mongo"
|
"tm/pkg/mongo"
|
||||||
|
|
||||||
|
"go.mongodb.org/mongo-driver/v2/bson"
|
||||||
)
|
)
|
||||||
|
|
||||||
// FeedbackType represents the type of feedback
|
// FeedbackType represents the type of feedback
|
||||||
@@ -22,31 +23,36 @@ type Feedback struct {
|
|||||||
FeedbackType FeedbackType `bson:"feedback_type" json:"feedback_type" validate:"required,oneof=like dislike"`
|
FeedbackType FeedbackType `bson:"feedback_type" json:"feedback_type" validate:"required,oneof=like dislike"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// FeedbackSummary represents aggregated feedback statistics for a tender
|
// SetID sets the feedback ID (implements IDSetter interface)
|
||||||
type FeedbackSummary struct {
|
func (f *Feedback) SetID(id string) {
|
||||||
TenderID string `bson:"tender_id" json:"tender_id"`
|
f.ID, _ = bson.ObjectIDFromHex(id)
|
||||||
TotalLikes int64 `bson:"total_likes" json:"total_likes"`
|
|
||||||
TotalDislikes int64 `bson:"total_dislikes" json:"total_dislikes"`
|
|
||||||
LastUpdated int64 `bson:"last_updated" json:"last_updated"` // Unix timestamp
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// FeedbackSearchCriteria represents search criteria for feedback
|
// GetID returns the feedback ID (implements IDGetter interface)
|
||||||
type FeedbackSearchCriteria struct {
|
|
||||||
TenderID *string `json:"tender_id,omitempty"`
|
|
||||||
CustomerID *string `json:"customer_id,omitempty"`
|
|
||||||
CompanyID *string `json:"company_id,omitempty"`
|
|
||||||
FeedbackType *FeedbackType `json:"feedback_type,omitempty"`
|
|
||||||
DateFrom *int64 `json:"date_from,omitempty"` // Unix timestamp
|
|
||||||
DateTo *int64 `json:"date_to,omitempty"` // Unix timestamp
|
|
||||||
Limit int `json:"limit,omitempty"`
|
|
||||||
Offset int `json:"offset,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetID returns the feedback ID
|
|
||||||
func (f *Feedback) GetID() string {
|
func (f *Feedback) GetID() string {
|
||||||
return f.ID.Hex()
|
return f.ID.Hex()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetCreatedAt sets the created timestamp (implements Timestamp interface)
|
||||||
|
func (f *Feedback) SetCreatedAt(timestamp int64) {
|
||||||
|
f.CreatedAt = timestamp
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetUpdatedAt sets the updated timestamp (implements Timestamp interface)
|
||||||
|
func (f *Feedback) SetUpdatedAt(timestamp int64) {
|
||||||
|
f.UpdatedAt = timestamp
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCreatedAt returns the created timestamp (implements Timestamp interface)
|
||||||
|
func (f *Feedback) GetCreatedAt() int64 {
|
||||||
|
return f.CreatedAt
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUpdatedAt returns the updated timestamp (implements Timestamp interface)
|
||||||
|
func (f *Feedback) GetUpdatedAt() int64 {
|
||||||
|
return f.UpdatedAt
|
||||||
|
}
|
||||||
|
|
||||||
// IsLike returns true if the feedback is a like
|
// IsLike returns true if the feedback is a like
|
||||||
func (f *Feedback) IsLike() bool {
|
func (f *Feedback) IsLike() bool {
|
||||||
return f.FeedbackType == FeedbackTypeLike
|
return f.FeedbackType == FeedbackTypeLike
|
||||||
@@ -56,9 +62,3 @@ func (f *Feedback) IsLike() bool {
|
|||||||
func (f *Feedback) IsDislike() bool {
|
func (f *Feedback) IsDislike() bool {
|
||||||
return f.FeedbackType == FeedbackTypeDislike
|
return f.FeedbackType == FeedbackTypeDislike
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateFeedbackType updates the feedback type and updated timestamp
|
|
||||||
func (f *Feedback) UpdateFeedbackType(feedbackType FeedbackType) {
|
|
||||||
f.FeedbackType = feedbackType
|
|
||||||
f.UpdatedAt = time.Now().Unix()
|
|
||||||
}
|
|
||||||
|
|||||||
+43
-85
@@ -1,61 +1,37 @@
|
|||||||
package feedback
|
package feedback
|
||||||
|
|
||||||
import (
|
import "tm/pkg/response"
|
||||||
"time"
|
|
||||||
"tm/pkg/response"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ListFeedbackForm represents the form for listing feedback
|
|
||||||
type ListFeedbackForm struct {
|
|
||||||
Feedbacks []FeedbackResponse `json:"feedbacks"`
|
|
||||||
DateFrom *int64 `json:"date_from,omitempty" validate:"omitempty,min=0"`
|
|
||||||
DateTo *int64 `json:"date_to,omitempty" validate:"omitempty,min=0"`
|
|
||||||
Limit int `json:"limit" validate:"required,min=1,max=100"`
|
|
||||||
Offset int `json:"offset" validate:"required,min=0"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ToggleFeedbackForm represents the form for toggling feedback (like/dislike)
|
// ToggleFeedbackForm represents the form for toggling feedback (like/dislike)
|
||||||
type ToggleFeedbackForm struct {
|
type ToggleFeedbackForm struct {
|
||||||
TenderID string `json:"tender_id" validate:"required"`
|
Tender string `json:"tender_id" validate:"required"`
|
||||||
FeedbackType FeedbackType `json:"feedback_type" validate:"required,oneof=like dislike"`
|
Type FeedbackType `json:"feedback_type" validate:"required,oneof=like dislike"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// FeedbackResponse represents the response for feedback operations
|
// SearchForm represents search criteria for feedback
|
||||||
type FeedbackResponse struct {
|
type SearchForm struct {
|
||||||
ID string `json:"id"`
|
Tender *string `json:"tender_id,omitempty" valid:"optional"`
|
||||||
FeedbackType FeedbackType `json:"feedback_type"`
|
Customer *string `json:"customer_id,omitempty" valid:"optional"`
|
||||||
UpdatedAt int64 `json:"updated_at"`
|
Company *string `json:"company_id,omitempty" valid:"optional"`
|
||||||
CreatedAt int64 `json:"created_at"`
|
Type *FeedbackType `json:"feedback_type,omitempty" valid:"optional,in(like|dislike)"`
|
||||||
TenderID string `json:"tender"`
|
DateFrom *int64 `json:"date_from,omitempty" valid:"optional"`
|
||||||
CustomerID *string `json:"customer_id"`
|
DateTo *int64 `json:"date_to,omitempty" valid:"optional"`
|
||||||
CompanyId *string `json:"company_id"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// FeedbackListResponse represents a paginated list of feedback
|
|
||||||
type FeedbackListResponse struct {
|
|
||||||
Feedback []*FeedbackResponse `json:"feedback"`
|
|
||||||
Meta *response.Meta `json:"meta"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// FeedbackListResponse represents a paginated list of feedback
|
|
||||||
type FeedbackListWithTenderResponse struct {
|
|
||||||
Feedback []*FeedbackWithTenderResponse `json:"feedback"`
|
|
||||||
Meta *response.Meta `json:"meta"`
|
|
||||||
}
|
|
||||||
type FeedbackWithTenderResponse struct {
|
|
||||||
ID string `json:"id"`
|
|
||||||
FeedbackType FeedbackType `json:"feedback_type"`
|
|
||||||
UpdatedAt int64 `json:"updated_at"`
|
|
||||||
CreatedAt int64 `json:"created_at"`
|
|
||||||
TenderID string `json:"tender_id"`
|
|
||||||
CustomerID *string `json:"customer_id"`
|
|
||||||
CompanyId *string `json:"company_id"`
|
|
||||||
Tender *tenderDetails `json:"tender"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// tenderDetails represents essential tender information for the response
|
|
||||||
type (
|
type (
|
||||||
tenderDetails struct {
|
FeedbackResponse struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
FeedbackType FeedbackType `json:"feedback_type"`
|
||||||
|
UpdatedAt int64 `json:"updated_at"`
|
||||||
|
CreatedAt int64 `json:"created_at"`
|
||||||
|
TenderID string `json:"tender_id"`
|
||||||
|
CustomerID *string `json:"customer_id"`
|
||||||
|
CompanyId *string `json:"company_id"`
|
||||||
|
Tender *tenderResponse `json:"tender"`
|
||||||
|
Company *companyResponse `json:"company"`
|
||||||
|
}
|
||||||
|
|
||||||
|
tenderResponse struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
NoticePublicationID string `json:"notice_publication_id"`
|
NoticePublicationID string `json:"notice_publication_id"`
|
||||||
PublicationDate int64 `json:"publication_date"`
|
PublicationDate int64 `json:"publication_date"`
|
||||||
@@ -73,39 +49,23 @@ type (
|
|||||||
BuyerOrganization *organizationResponse `json:"buyer_organization"`
|
BuyerOrganization *organizationResponse `json:"buyer_organization"`
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
}
|
}
|
||||||
|
|
||||||
organizationResponse struct {
|
organizationResponse struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
companyResponse struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
// CompanyFeedbackStatsResponse represents the response for company feedback statistics
|
type FeedbackListResponse struct {
|
||||||
type CompanyFeedbackStatsResponse struct {
|
Feedback []*FeedbackResponse `json:"feedback"`
|
||||||
CompanyID string `json:"company_id"`
|
Meta *response.Meta `json:"meta"`
|
||||||
TotalLikes int64 `json:"total_likes"`
|
|
||||||
TotalDislikes int64 `json:"total_dislikes"`
|
|
||||||
TotalFeedback int64 `json:"total_feedback"`
|
|
||||||
LastUpdated int64 `json:"last_updated"` // Unix timestamp
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetDateFromTime returns the date_from as time.Time
|
func (f *Feedback) ToResponse(tender *tenderResponse, company *companyResponse) *FeedbackResponse {
|
||||||
func (f *ListFeedbackForm) GetDateFromTime() *time.Time {
|
|
||||||
if f.DateFrom == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
t := time.Unix(*f.DateFrom, 0)
|
|
||||||
return &t
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetDateToTime returns the date_to as time.Time
|
|
||||||
func (f *ListFeedbackForm) GetDateToTime() *time.Time {
|
|
||||||
if f.DateTo == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
t := time.Unix(*f.DateTo, 0)
|
|
||||||
return &t
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *Feedback) ToResponse() *FeedbackResponse {
|
|
||||||
return &FeedbackResponse{
|
return &FeedbackResponse{
|
||||||
ID: f.ID.Hex(),
|
ID: f.ID.Hex(),
|
||||||
FeedbackType: f.FeedbackType,
|
FeedbackType: f.FeedbackType,
|
||||||
@@ -114,18 +74,16 @@ func (f *Feedback) ToResponse() *FeedbackResponse {
|
|||||||
CompanyId: f.CompanyID,
|
CompanyId: f.CompanyID,
|
||||||
UpdatedAt: f.UpdatedAt,
|
UpdatedAt: f.UpdatedAt,
|
||||||
CreatedAt: f.CreatedAt,
|
CreatedAt: f.CreatedAt,
|
||||||
|
Tender: tender,
|
||||||
|
Company: company,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Feedback) ToResponseWithTender(tender *tenderDetails) *FeedbackWithTenderResponse {
|
type StatsResponse struct {
|
||||||
return &FeedbackWithTenderResponse{
|
CompanyID string `json:"company_id"`
|
||||||
ID: f.ID.Hex(),
|
Company *companyResponse `json:"company"`
|
||||||
FeedbackType: f.FeedbackType,
|
TotalLikes int64 `json:"total_likes"`
|
||||||
TenderID: f.TenderID,
|
TotalDislikes int64 `json:"total_dislikes"`
|
||||||
CustomerID: f.CustomerID,
|
TotalFeedback int64 `json:"total_feedback"`
|
||||||
CompanyId: f.CompanyID,
|
LastUpdated int64 `json:"last_updated"`
|
||||||
UpdatedAt: f.UpdatedAt,
|
|
||||||
CreatedAt: f.CreatedAt,
|
|
||||||
Tender: tender,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+30
-124
@@ -1,7 +1,6 @@
|
|||||||
package feedback
|
package feedback
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strconv"
|
|
||||||
"tm/internal/customer"
|
"tm/internal/customer"
|
||||||
"tm/internal/user"
|
"tm/internal/user"
|
||||||
"tm/pkg/logger"
|
"tm/pkg/logger"
|
||||||
@@ -13,15 +12,15 @@ import (
|
|||||||
|
|
||||||
// Handler handles HTTP requests for feedback operations
|
// Handler handles HTTP requests for feedback operations
|
||||||
type Handler struct {
|
type Handler struct {
|
||||||
service FeedbackService
|
service Service
|
||||||
logger logger.Logger
|
logger logger.Logger
|
||||||
userHandler *user.Handler
|
userHandler *user.Handler
|
||||||
customerHandler *customer.Handler
|
customerHandler *customer.Handler
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewFeedbackHandler creates a new feedback handler
|
// NewHandler creates a new feedback handler
|
||||||
func NewFeedbackHandler(
|
func NewHandler(
|
||||||
service FeedbackService,
|
service Service,
|
||||||
userHandler *user.Handler,
|
userHandler *user.Handler,
|
||||||
customerHandler *customer.Handler,
|
customerHandler *customer.Handler,
|
||||||
logger logger.Logger,
|
logger logger.Logger,
|
||||||
@@ -55,65 +54,13 @@ func NewFeedbackHandler(
|
|||||||
// @Failure 500 {object} response.APIResponse{error=string,message=string} "Internal server error"
|
// @Failure 500 {object} response.APIResponse{error=string,message=string} "Internal server error"
|
||||||
// @Security BearerAuth
|
// @Security BearerAuth
|
||||||
// @Router /admin/v1/feedback [get]
|
// @Router /admin/v1/feedback [get]
|
||||||
func (h *Handler) ListFeedback(c echo.Context) error {
|
func (h *Handler) Search(c echo.Context) error {
|
||||||
// Parse query parameters
|
form, err := response.Parse[SearchForm](c)
|
||||||
limitStr := c.QueryParam("limit")
|
if err != nil {
|
||||||
offsetStr := c.QueryParam("offset")
|
return response.ValidationError(c, "Invalid request data", err.Error())
|
||||||
|
|
||||||
limit := 20 // default limit
|
|
||||||
offset := 0 // default offset
|
|
||||||
|
|
||||||
if l, err := strconv.Atoi(limitStr); err == nil && l > 0 && l <= 100 {
|
|
||||||
limit = l
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if o, err := strconv.Atoi(offsetStr); err == nil && o >= 0 {
|
feedbackList, err := h.service.Search(c.Request().Context(), *form, response.NewPagination(c))
|
||||||
offset = o
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build search criteria
|
|
||||||
criteria := FeedbackSearchCriteria{
|
|
||||||
Limit: limit,
|
|
||||||
Offset: offset,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse optional filters
|
|
||||||
if tenderID := c.QueryParam("tender_id"); tenderID != "" {
|
|
||||||
criteria.TenderID = &tenderID
|
|
||||||
}
|
|
||||||
|
|
||||||
if customerID := c.QueryParam("customer_id"); customerID != "" {
|
|
||||||
criteria.CustomerID = &customerID
|
|
||||||
}
|
|
||||||
|
|
||||||
if companyID := c.QueryParam("company_id"); companyID != "" {
|
|
||||||
criteria.CompanyID = &companyID
|
|
||||||
}
|
|
||||||
|
|
||||||
if feedbackType := c.QueryParam("feedback_type"); feedbackType != "" {
|
|
||||||
ft := FeedbackType(feedbackType)
|
|
||||||
criteria.FeedbackType = &ft
|
|
||||||
}
|
|
||||||
|
|
||||||
if dateFrom := c.QueryParam("date_from"); dateFrom != "" {
|
|
||||||
if df, err := strconv.ParseInt(dateFrom, 10, 64); err == nil {
|
|
||||||
criteria.DateFrom = &df
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if dateTo := c.QueryParam("date_to"); dateTo != "" {
|
|
||||||
if dt, err := strconv.ParseInt(dateTo, 10, 64); err == nil {
|
|
||||||
criteria.DateTo = &dt
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate date range
|
|
||||||
if criteria.DateFrom != nil && criteria.DateTo != nil && *criteria.DateFrom >= *criteria.DateTo {
|
|
||||||
return response.ValidationError(c, "Invalid date range", "date_from must be before date_to")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get enhanced feedback list with related data
|
|
||||||
feedbackList, err := h.service.ListFeedback(c.Request().Context(), criteria, limit, offset)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return response.InternalServerError(c, "Failed to retrieve feedback list")
|
return response.InternalServerError(c, "Failed to retrieve feedback list")
|
||||||
}
|
}
|
||||||
@@ -134,13 +81,13 @@ func (h *Handler) ListFeedback(c echo.Context) error {
|
|||||||
// @Failure 500 {object} response.APIResponse{error=string,message=string} "Internal server error"
|
// @Failure 500 {object} response.APIResponse{error=string,message=string} "Internal server error"
|
||||||
// @Security BearerAuth
|
// @Security BearerAuth
|
||||||
// @Router /admin/v1/feedback/{id} [get]
|
// @Router /admin/v1/feedback/{id} [get]
|
||||||
func (h *Handler) GetFeedback(c echo.Context) error {
|
func (h *Handler) Get(c echo.Context) error {
|
||||||
feedbackID := c.Param("id")
|
feedbackID := c.Param("id")
|
||||||
if feedbackID == "" {
|
if feedbackID == "" {
|
||||||
return response.BadRequest(c, "Feedback ID is required", "Feedback ID parameter is missing")
|
return response.BadRequest(c, "Feedback ID is required", "Feedback ID parameter is missing")
|
||||||
}
|
}
|
||||||
|
|
||||||
feedback, err := h.service.GetFeedback(c.Request().Context(), feedbackID)
|
feedback, err := h.service.Get(c.Request().Context(), feedbackID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == ErrFeedbackNotFound {
|
if err == ErrFeedbackNotFound {
|
||||||
return response.NotFound(c, "Feedback not found")
|
return response.NotFound(c, "Feedback not found")
|
||||||
@@ -169,14 +116,14 @@ func (h *Handler) GetFeedback(c echo.Context) error {
|
|||||||
// @Failure 500 {object} response.APIResponse{error=string,message=string} "Internal server error"
|
// @Failure 500 {object} response.APIResponse{error=string,message=string} "Internal server error"
|
||||||
// @Security BearerAuth
|
// @Security BearerAuth
|
||||||
// @Router /admin/v1/feedback/{id} [delete]
|
// @Router /admin/v1/feedback/{id} [delete]
|
||||||
func (h *Handler) DeleteFeedback(c echo.Context) error {
|
func (h *Handler) Delete(c echo.Context) error {
|
||||||
id := c.Param("id")
|
id := c.Param("id")
|
||||||
if id == "" {
|
if id == "" {
|
||||||
return response.BadRequest(c, "Feedback ID is required", "")
|
return response.BadRequest(c, "Feedback ID is required", "")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Call service
|
// Call service
|
||||||
err := h.service.DeleteFeedback(c.Request().Context(), id)
|
err := h.service.Delete(c.Request().Context(), id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
switch err {
|
switch err {
|
||||||
case ErrFeedbackNotFound:
|
case ErrFeedbackNotFound:
|
||||||
@@ -206,7 +153,7 @@ func (h *Handler) DeleteFeedback(c echo.Context) error {
|
|||||||
// @Failure 500 {object} response.APIResponse{error=string,message=string} "Internal server error"
|
// @Failure 500 {object} response.APIResponse{error=string,message=string} "Internal server error"
|
||||||
// @Security BearerAuth
|
// @Security BearerAuth
|
||||||
// @Router /api/v1/feedback [post]
|
// @Router /api/v1/feedback [post]
|
||||||
func (h *Handler) ToggleFeedback(c echo.Context) error {
|
func (h *Handler) Toggle(c echo.Context) error {
|
||||||
form, err := response.Parse[ToggleFeedbackForm](c)
|
form, err := response.Parse[ToggleFeedbackForm](c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return response.ValidationError(c, "Invalid request data", err.Error())
|
return response.ValidationError(c, "Invalid request data", err.Error())
|
||||||
@@ -230,7 +177,7 @@ func (h *Handler) ToggleFeedback(c echo.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Call service
|
// Call service
|
||||||
result, err := h.service.ToggleFeedback(c.Request().Context(), *form, &customerID, &companyID)
|
result, err := h.service.Toggle(c.Request().Context(), *form, &customerID, &companyID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return response.InternalServerError(c, "Failed to toggle feedback")
|
return response.InternalServerError(c, "Failed to toggle feedback")
|
||||||
}
|
}
|
||||||
@@ -258,61 +205,20 @@ func (h *Handler) ToggleFeedback(c echo.Context) error {
|
|||||||
// @Failure 500 {object} response.APIResponse{error=string,message=string} "Internal server error"
|
// @Failure 500 {object} response.APIResponse{error=string,message=string} "Internal server error"
|
||||||
// @Security BearerAuth
|
// @Security BearerAuth
|
||||||
// @Router /api/v1/feedback [get]
|
// @Router /api/v1/feedback [get]
|
||||||
func (h *Handler) PublicListFeedback(c echo.Context) error {
|
func (h *Handler) PublicList(c echo.Context) error {
|
||||||
// Parse query parameters
|
|
||||||
limitStr := c.QueryParam("limit")
|
|
||||||
offsetStr := c.QueryParam("offset")
|
|
||||||
|
|
||||||
limit := 20
|
|
||||||
offset := 0
|
|
||||||
|
|
||||||
if l, err := strconv.Atoi(limitStr); err == nil && l > 0 && l <= 100 {
|
|
||||||
limit = l
|
|
||||||
}
|
|
||||||
|
|
||||||
if o, err := strconv.Atoi(offsetStr); err == nil && o >= 0 {
|
|
||||||
offset = o
|
|
||||||
}
|
|
||||||
|
|
||||||
companyID, err := user.GetCompanyIDFromContext(c)
|
companyID, err := user.GetCompanyIDFromContext(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return response.BadRequest(c, "Company ID required", "User must be associated with a company")
|
return response.BadRequest(c, "Company ID required", "User must be associated with a company")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build search criteria
|
form, err := response.Parse[SearchForm](c)
|
||||||
criteria := FeedbackSearchCriteria{
|
if err != nil {
|
||||||
Limit: limit,
|
return response.ValidationError(c, "Invalid request data", err.Error())
|
||||||
Offset: offset,
|
|
||||||
CompanyID: &companyID,
|
|
||||||
}
|
|
||||||
|
|
||||||
if feedbackType := c.QueryParam("feedback_type"); feedbackType != "" {
|
|
||||||
ft := FeedbackType(feedbackType)
|
|
||||||
if ft != FeedbackTypeLike && ft != FeedbackTypeDislike {
|
|
||||||
return response.BadRequest(c, "Invalid feedback type", "Must be 'like' or 'dislike'")
|
|
||||||
}
|
|
||||||
criteria.FeedbackType = &ft
|
|
||||||
}
|
|
||||||
|
|
||||||
if dateFrom := c.QueryParam("date_from"); dateFrom != "" {
|
|
||||||
if df, err := strconv.ParseInt(dateFrom, 10, 64); err == nil && df >= 0 {
|
|
||||||
criteria.DateFrom = &df
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if dateTo := c.QueryParam("date_to"); dateTo != "" {
|
|
||||||
if dt, err := strconv.ParseInt(dateTo, 10, 64); err == nil && dt >= 0 {
|
|
||||||
criteria.DateTo = &dt
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate date range
|
|
||||||
if criteria.DateFrom != nil && criteria.DateTo != nil && *criteria.DateFrom >= *criteria.DateTo {
|
|
||||||
return response.ValidationError(c, "Invalid date range", "date_from must be before date_to")
|
|
||||||
}
|
}
|
||||||
|
form.Company = &companyID
|
||||||
|
|
||||||
// Call service
|
// Call service
|
||||||
result, err := h.service.ListFeedback(c.Request().Context(), criteria, limit, offset)
|
result, err := h.service.Search(c.Request().Context(), *form, response.NewPagination(c))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return response.InternalServerError(c, "Failed to list feedback")
|
return response.InternalServerError(c, "Failed to list feedback")
|
||||||
}
|
}
|
||||||
@@ -333,13 +239,13 @@ func (h *Handler) PublicListFeedback(c echo.Context) error {
|
|||||||
// @Failure 500 {object} response.APIResponse{error=string,message=string} "Internal server error"
|
// @Failure 500 {object} response.APIResponse{error=string,message=string} "Internal server error"
|
||||||
// @Security BearerAuth
|
// @Security BearerAuth
|
||||||
// @Router /api/v1/feedback/{id} [get]
|
// @Router /api/v1/feedback/{id} [get]
|
||||||
func (h *Handler) PublicGetFeedback(c echo.Context) error {
|
func (h *Handler) PublicGet(c echo.Context) error {
|
||||||
feedbackID := c.Param("id")
|
feedbackID := c.Param("id")
|
||||||
if feedbackID == "" {
|
if feedbackID == "" {
|
||||||
return response.BadRequest(c, "Feedback ID is required", "Feedback ID parameter is missing")
|
return response.BadRequest(c, "Feedback ID is required", "Feedback ID parameter is missing")
|
||||||
}
|
}
|
||||||
|
|
||||||
feedback, err := h.service.GetFeedback(c.Request().Context(), feedbackID)
|
feedback, err := h.service.Get(c.Request().Context(), feedbackID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return response.BadRequest(c, "Failed to get feedback", "")
|
return response.BadRequest(c, "Failed to get feedback", "")
|
||||||
}
|
}
|
||||||
@@ -360,7 +266,7 @@ func (h *Handler) PublicGetFeedback(c echo.Context) error {
|
|||||||
// @Failure 500 {object} response.APIResponse{error=string,message=string} "Internal server error"
|
// @Failure 500 {object} response.APIResponse{error=string,message=string} "Internal server error"
|
||||||
// @Security BearerAuth
|
// @Security BearerAuth
|
||||||
// @Router /api/v1/feedback/tender/{tender_id} [get]
|
// @Router /api/v1/feedback/tender/{tender_id} [get]
|
||||||
func (h *Handler) PublicGetFeedbackByTenderID(c echo.Context) error {
|
func (h *Handler) PublicGetByTenderID(c echo.Context) error {
|
||||||
tenderID := c.Param("tender_id")
|
tenderID := c.Param("tender_id")
|
||||||
if tenderID == "" {
|
if tenderID == "" {
|
||||||
return response.BadRequest(c, "Tender ID is required", "Tender ID parameter is missing")
|
return response.BadRequest(c, "Tender ID is required", "Tender ID parameter is missing")
|
||||||
@@ -371,7 +277,7 @@ func (h *Handler) PublicGetFeedbackByTenderID(c echo.Context) error {
|
|||||||
return response.BadRequest(c, "Company ID required", "User must be associated with a company")
|
return response.BadRequest(c, "Company ID required", "User must be associated with a company")
|
||||||
}
|
}
|
||||||
|
|
||||||
feedback, err := h.service.GetFeedbackByTenderID(c.Request().Context(), tenderID, companyID)
|
feedback, err := h.service.GetByTenderID(c.Request().Context(), tenderID, companyID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return response.BadRequest(c, "Failed to get feedback", "")
|
return response.BadRequest(c, "Failed to get feedback", "")
|
||||||
}
|
}
|
||||||
@@ -385,22 +291,22 @@ func (h *Handler) PublicGetFeedbackByTenderID(c echo.Context) error {
|
|||||||
// @Tags Feedback
|
// @Tags Feedback
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Success 200 {object} response.APIResponse{data=CompanyFeedbackStatsResponse} "Company feedback statistics retrieved successfully"
|
// @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 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 401 {object} response.APIResponse{error=string,message=string} "Unauthorized - User not authenticated"
|
||||||
// @Failure 500 {object} response.APIResponse{error=string,message=string} "Internal server error"
|
// @Failure 500 {object} response.APIResponse{error=string,message=string} "Internal server error"
|
||||||
// @Security BearerAuth
|
// @Security BearerAuth
|
||||||
// @Router /api/v1/feedback/stats/company [get]
|
// @Router /api/v1/feedback/stats/company [get]
|
||||||
func (h *Handler) GetCompanyFeedbackStats(c echo.Context) error {
|
func (h *Handler) GetCompanyStats(c echo.Context) error {
|
||||||
companyID, err := user.GetCompanyIDFromContext(c)
|
companyID, err := user.GetCompanyIDFromContext(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return response.BadRequest(c, "Company ID required", "User must be associated with a company")
|
return response.BadRequest(c, "Company ID required", "User must be associated with a company")
|
||||||
}
|
}
|
||||||
|
|
||||||
stats, err := h.service.CompanyFeedbackStats(c.Request().Context(), companyID)
|
stats, err := h.service.CompanyStats(c.Request().Context(), companyID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return response.InternalServerError(c, "Failed to retrieve company feedback statistics")
|
return response.InternalServerError(c, "Failed to retrieve company stats")
|
||||||
}
|
}
|
||||||
|
|
||||||
return response.Success(c, stats, "Company feedback statistics retrieved successfully")
|
return response.Success(c, stats, "Company stats retrieved successfully")
|
||||||
}
|
}
|
||||||
|
|||||||
+69
-277
@@ -4,10 +4,11 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"time"
|
"time"
|
||||||
"tm/pkg/logger"
|
"tm/pkg/logger"
|
||||||
"tm/pkg/mongo"
|
orm "tm/pkg/mongo"
|
||||||
|
"tm/pkg/response"
|
||||||
|
|
||||||
"go.mongodb.org/mongo-driver/v2/bson"
|
"go.mongodb.org/mongo-driver/v2/bson"
|
||||||
driver "go.mongodb.org/mongo-driver/v2/mongo"
|
"go.mongodb.org/mongo-driver/v2/mongo"
|
||||||
)
|
)
|
||||||
|
|
||||||
// FeedbackRepository interface defines feedback data access methods
|
// FeedbackRepository interface defines feedback data access methods
|
||||||
@@ -15,37 +16,33 @@ type Repository interface {
|
|||||||
// Basic CRUD operations
|
// Basic CRUD operations
|
||||||
Create(ctx context.Context, feedback *Feedback) error
|
Create(ctx context.Context, feedback *Feedback) error
|
||||||
Update(ctx context.Context, feedback *Feedback) error
|
Update(ctx context.Context, feedback *Feedback) error
|
||||||
|
Search(ctx context.Context, criteria SearchForm, pagination *response.Pagination) ([]Feedback, int64, error)
|
||||||
GetByID(ctx context.Context, id string) (*Feedback, error)
|
GetByID(ctx context.Context, id string) (*Feedback, error)
|
||||||
GetByCompanyID(ctx context.Context, companyID string, tender string) (*Feedback, error)
|
GetByCompanyID(ctx context.Context, companyID string, tender string) (*Feedback, error)
|
||||||
Delete(ctx context.Context, id string) error
|
|
||||||
List(ctx context.Context, criteria FeedbackSearchCriteria, limit, offset int) ([]*Feedback, int64, error)
|
|
||||||
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)
|
GetByTenderID(ctx context.Context, tenderID, companyID string) (*Feedback, error)
|
||||||
GetCompanyFeedbackStats(ctx context.Context, companyID string) (*CompanyFeedbackStatsResponse, error)
|
Delete(ctx context.Context, id string) error
|
||||||
|
CompanyStats(ctx context.Context, companyID string) (*StatsResponse, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// feedbackRepository implements FeedbackRepository interface using MongoDB ORM
|
// feedbackRepository implements FeedbackRepository interface using MongoDB ORM
|
||||||
type feedbackRepository struct {
|
type feedbackRepository struct {
|
||||||
repo mongo.Repository[Feedback]
|
repo orm.Repository[Feedback]
|
||||||
logger logger.Logger
|
logger logger.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewFeedbackRepository creates a new feedback repository
|
// NewFeedbackRepository creates a new feedback repository
|
||||||
func NewFeedbackRepository(mongoManager *mongo.ConnectionManager, logger logger.Logger) Repository {
|
func NewRepository(mongoManager *orm.ConnectionManager, logger logger.Logger) Repository {
|
||||||
// Create indexes for feedback collection
|
// Create indexes for feedback collection
|
||||||
feedbackIndexes := []mongo.Index{
|
feedbackIndexes := []orm.Index{
|
||||||
*mongo.NewIndex("tender_id_idx", bson.D{{Key: "tender_id", Value: 1}}),
|
*orm.NewIndex("tender_id_idx", bson.D{{Key: "tender_id", Value: 1}}),
|
||||||
*mongo.NewIndex("customer_id_idx", bson.D{{Key: "customer_id", Value: 1}}),
|
*orm.NewIndex("company_id_idx", bson.D{{Key: "company_id", Value: 1}}),
|
||||||
*mongo.NewIndex("company_id_idx", bson.D{{Key: "company_id", Value: 1}}),
|
*orm.NewIndex("feedback_type_idx", bson.D{{Key: "feedback_type", Value: 1}}),
|
||||||
*mongo.NewIndex("feedback_type_idx", bson.D{{Key: "feedback_type", Value: 1}}),
|
|
||||||
// Compound indexes for common queries
|
// Compound indexes for common queries
|
||||||
*mongo.NewIndex("tender_customer_idx", bson.D{
|
*orm.NewIndex("tender_company_idx", bson.D{
|
||||||
{Key: "tender_id", Value: 1},
|
{Key: "tender_id", Value: 1},
|
||||||
{Key: "customer_id", Value: 1},
|
{Key: "company_id", Value: 1},
|
||||||
}),
|
}),
|
||||||
*mongo.NewIndex("tender_type_idx", bson.D{
|
*orm.NewIndex("tender_type_idx", bson.D{
|
||||||
{Key: "tender_id", Value: 1},
|
{Key: "tender_id", Value: 1},
|
||||||
{Key: "feedback_type", Value: 1},
|
{Key: "feedback_type", Value: 1},
|
||||||
}),
|
}),
|
||||||
@@ -59,7 +56,7 @@ func NewFeedbackRepository(mongoManager *mongo.ConnectionManager, logger logger.
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create ORM repository
|
// Create ORM repository
|
||||||
repo := mongo.NewRepository[Feedback](mongoManager.GetCollection("feedback"), logger)
|
repo := orm.NewRepository[Feedback](mongoManager.GetCollection("feedback"), logger)
|
||||||
|
|
||||||
return &feedbackRepository{
|
return &feedbackRepository{
|
||||||
repo: repo,
|
repo: repo,
|
||||||
@@ -72,7 +69,6 @@ func (r *feedbackRepository) Create(ctx context.Context, feedback *Feedback) err
|
|||||||
// Set timestamps
|
// Set timestamps
|
||||||
now := time.Now().Unix()
|
now := time.Now().Unix()
|
||||||
feedback.CreatedAt = now
|
feedback.CreatedAt = now
|
||||||
feedback.UpdatedAt = now
|
|
||||||
|
|
||||||
err := r.repo.Create(ctx, feedback)
|
err := r.repo.Create(ctx, feedback)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -144,7 +140,24 @@ func (r *feedbackRepository) GetByCompanyID(ctx context.Context, companyID strin
|
|||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete permanently deletes feedback by ID
|
// GetByTenderID retrieves feedback by tender ID and company ID
|
||||||
|
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 and company ID", map[string]interface{}{
|
||||||
|
"tender_id": tenderID,
|
||||||
|
"company_id": companyID,
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete deletes a feedback by ID
|
||||||
func (r *feedbackRepository) Delete(ctx context.Context, id string) error {
|
func (r *feedbackRepository) Delete(ctx context.Context, id string) error {
|
||||||
err := r.repo.Delete(ctx, id)
|
err := r.repo.Delete(ctx, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -155,267 +168,46 @@ func (r *feedbackRepository) Delete(ctx context.Context, id string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
r.logger.Info("Feedback deleted successfully", map[string]interface{}{
|
|
||||||
"feedback_id": id,
|
|
||||||
})
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// List retrieves feedback with pagination and filtering
|
|
||||||
func (r *feedbackRepository) List(ctx context.Context, criteria FeedbackSearchCriteria, limit, offset int) ([]*Feedback, int64, error) {
|
|
||||||
filter := r.buildSearchFilter(criteria)
|
|
||||||
|
|
||||||
pagination := mongo.Pagination{
|
|
||||||
Limit: limit,
|
|
||||||
Skip: offset,
|
|
||||||
SortField: "created_at",
|
|
||||||
SortOrder: -1,
|
|
||||||
}
|
|
||||||
|
|
||||||
result, err := r.repo.FindAll(ctx, filter, pagination)
|
|
||||||
if err != nil {
|
|
||||||
r.logger.Error("Failed to list feedback", map[string]interface{}{
|
|
||||||
"error": err.Error(),
|
|
||||||
})
|
|
||||||
return nil, 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert []TenderFeedback to []*TenderFeedback
|
|
||||||
feedback := make([]*Feedback, len(result.Items))
|
|
||||||
for i := range result.Items {
|
|
||||||
feedback[i] = &result.Items[i]
|
|
||||||
}
|
|
||||||
|
|
||||||
return feedback, result.TotalCount, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetByFeedbackType retrieves feedback by type
|
|
||||||
func (r *feedbackRepository) GetByFeedbackType(ctx context.Context, feedbackType FeedbackType, limit, offset int) ([]*Feedback, int64, error) {
|
|
||||||
filter := bson.M{"feedback_type": feedbackType}
|
|
||||||
|
|
||||||
pagination := mongo.Pagination{
|
|
||||||
Limit: limit,
|
|
||||||
Skip: offset,
|
|
||||||
SortField: "created_at",
|
|
||||||
SortOrder: -1,
|
|
||||||
}
|
|
||||||
|
|
||||||
result, err := r.repo.FindAll(ctx, filter, pagination)
|
|
||||||
if err != nil {
|
|
||||||
r.logger.Error("Failed to get feedback by type", map[string]interface{}{
|
|
||||||
"feedback_type": feedbackType,
|
|
||||||
"error": err.Error(),
|
|
||||||
})
|
|
||||||
return nil, 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert []TenderFeedback to []*TenderFeedback
|
|
||||||
feedback := make([]*Feedback, len(result.Items))
|
|
||||||
for i := range result.Items {
|
|
||||||
feedback[i] = &result.Items[i]
|
|
||||||
}
|
|
||||||
|
|
||||||
return feedback, result.TotalCount, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Search searches feedback based on criteria
|
// Search searches feedback based on criteria
|
||||||
func (r *feedbackRepository) Search(ctx context.Context, criteria FeedbackSearchCriteria) ([]*Feedback, error) {
|
func (r *feedbackRepository) Search(ctx context.Context, criteria SearchForm, pagination *response.Pagination) ([]Feedback, int64, error) {
|
||||||
filter := r.buildSearchFilter(criteria)
|
filter := r.buildSearchFilter(criteria)
|
||||||
|
|
||||||
pagination := mongo.Pagination{
|
// Build sort
|
||||||
Limit: 100,
|
sort := "created_at"
|
||||||
SortField: "created_at",
|
if pagination.SortBy != "" {
|
||||||
SortOrder: -1,
|
sort = pagination.SortBy
|
||||||
}
|
}
|
||||||
|
|
||||||
result, err := r.repo.FindAll(ctx, filter, pagination)
|
sortOrder := "desc"
|
||||||
|
if pagination.SortOrder != "" {
|
||||||
|
sortOrder = pagination.SortOrder
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build pagination
|
||||||
|
paginationBuilder := orm.NewPaginationBuilder().
|
||||||
|
Limit(pagination.Limit).
|
||||||
|
Skip(pagination.Offset).
|
||||||
|
SortBy(sort, sortOrder).
|
||||||
|
Build()
|
||||||
|
|
||||||
|
// Use ORM to find users
|
||||||
|
result, err := r.repo.FindAll(ctx, filter, paginationBuilder)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
r.logger.Error("Failed to search feedback", map[string]interface{}{
|
r.logger.Error("Failed to list users", map[string]interface{}{
|
||||||
"error": err.Error(),
|
"error": err.Error(),
|
||||||
})
|
})
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert []TenderFeedback to []*TenderFeedback
|
|
||||||
feedback := make([]*Feedback, len(result.Items))
|
|
||||||
for i := range result.Items {
|
|
||||||
feedback[i] = &result.Items[i]
|
|
||||||
}
|
|
||||||
|
|
||||||
return feedback, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetFeedbackSummary calculates feedback summary for a tender
|
|
||||||
func (r *feedbackRepository) GetFeedbackSummary(ctx context.Context, tenderID string) (*FeedbackSummary, error) {
|
|
||||||
pipeline := driver.Pipeline{
|
|
||||||
{
|
|
||||||
{Key: "$match", Value: bson.M{
|
|
||||||
"tender_id": tenderID,
|
|
||||||
}},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
{Key: "$group", Value: bson.M{
|
|
||||||
"_id": nil,
|
|
||||||
"total_likes": bson.M{
|
|
||||||
"$sum": bson.M{
|
|
||||||
"$cond": []interface{}{
|
|
||||||
bson.M{"$eq": []string{"$feedback_type", string(FeedbackTypeLike)}},
|
|
||||||
1,
|
|
||||||
0,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"total_dislikes": bson.M{
|
|
||||||
"$sum": bson.M{
|
|
||||||
"$cond": []interface{}{
|
|
||||||
bson.M{"$eq": []string{"$feedback_type", string(FeedbackTypeDislike)}},
|
|
||||||
1,
|
|
||||||
0,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"total_comments": bson.M{
|
|
||||||
"$sum": bson.M{
|
|
||||||
"$cond": []interface{}{
|
|
||||||
bson.M{"$ne": []interface{}{"$comment", nil}},
|
|
||||||
1,
|
|
||||||
0,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"total_ratings": bson.M{
|
|
||||||
"$sum": bson.M{
|
|
||||||
"$cond": []interface{}{
|
|
||||||
bson.M{"$gt": []interface{}{"$rating", 0}},
|
|
||||||
1,
|
|
||||||
0,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"rating_sum": bson.M{
|
|
||||||
"$sum": bson.M{
|
|
||||||
"$cond": []interface{}{
|
|
||||||
bson.M{"$gt": []interface{}{"$rating", 0}},
|
|
||||||
"$rating",
|
|
||||||
0,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"last_updated": bson.M{"$max": "$updated_at"},
|
|
||||||
}},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
results, err := r.repo.Aggregate(ctx, pipeline)
|
|
||||||
if err != nil {
|
|
||||||
r.logger.Error("Failed to get feedback summary", map[string]interface{}{
|
|
||||||
"tender_id": tenderID,
|
|
||||||
"error": err.Error(),
|
|
||||||
})
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
summary := &FeedbackSummary{
|
|
||||||
TenderID: tenderID,
|
|
||||||
TotalLikes: 0,
|
|
||||||
TotalDislikes: 0,
|
|
||||||
LastUpdated: time.Now().Unix(),
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(results) > 0 {
|
|
||||||
result := results[0]
|
|
||||||
if totalLikes, ok := result["total_likes"].(int32); ok {
|
|
||||||
summary.TotalLikes = int64(totalLikes)
|
|
||||||
}
|
|
||||||
if totalDislikes, ok := result["total_dislikes"].(int32); ok {
|
|
||||||
summary.TotalDislikes = int64(totalDislikes)
|
|
||||||
}
|
|
||||||
if lastUpdated, ok := result["last_updated"].(int64); ok {
|
|
||||||
summary.LastUpdated = lastUpdated
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return summary, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetFeedbackCountByType gets feedback count grouped by type
|
|
||||||
func (r *feedbackRepository) GetFeedbackCountByType(ctx context.Context) (map[FeedbackType]int64, error) {
|
|
||||||
pipeline := driver.Pipeline{
|
|
||||||
{
|
|
||||||
{Key: "$group", Value: bson.M{
|
|
||||||
"_id": "$feedback_type",
|
|
||||||
"count": bson.M{"$sum": 1},
|
|
||||||
}},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
results, err := r.repo.Aggregate(ctx, pipeline)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
counts := make(map[FeedbackType]int64)
|
|
||||||
for _, result := range results {
|
|
||||||
if feedbackType, ok := result["_id"].(string); ok {
|
|
||||||
if count, ok := result["count"].(int32); ok {
|
|
||||||
counts[FeedbackType(feedbackType)] = int64(count)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return counts, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetCustomerFeedbackHistory gets the feedback history for a specific customer
|
|
||||||
func (r *feedbackRepository) GetCustomerFeedbackHistory(ctx context.Context, customerID string, limit, offset int) ([]*Feedback, int64, error) {
|
|
||||||
filter := bson.M{
|
|
||||||
"customer_id": customerID,
|
|
||||||
}
|
|
||||||
|
|
||||||
pagination := mongo.Pagination{
|
|
||||||
Limit: limit,
|
|
||||||
Skip: offset,
|
|
||||||
SortField: "created_at",
|
|
||||||
SortOrder: -1,
|
|
||||||
}
|
|
||||||
|
|
||||||
result, err := r.repo.FindAll(ctx, filter, pagination)
|
|
||||||
if err != nil {
|
|
||||||
r.logger.Error("Failed to get customer feedback history", map[string]interface{}{
|
|
||||||
"customer_id": customerID,
|
|
||||||
"error": err.Error(),
|
|
||||||
})
|
|
||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert []TenderFeedback to []*TenderFeedback
|
return result.Items, result.TotalCount, nil
|
||||||
feedback := make([]*Feedback, len(result.Items))
|
|
||||||
for i := range result.Items {
|
|
||||||
feedback[i] = &result.Items[i]
|
|
||||||
}
|
|
||||||
|
|
||||||
return feedback, result.TotalCount, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *feedbackRepository) GetByTenderID(ctx context.Context, tenderID, companyID string) (*Feedback, error) {
|
// Stats calculates feedback statistics for a company
|
||||||
filter := bson.M{"tender_id": tenderID, "company_id": companyID}
|
func (r *feedbackRepository) CompanyStats(ctx context.Context, companyID string) (*StatsResponse, error) {
|
||||||
|
pipeline := mongo.Pipeline{
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetCompanyFeedbackStats calculates feedback statistics for a company
|
|
||||||
func (r *feedbackRepository) GetCompanyFeedbackStats(ctx context.Context, companyID string) (*CompanyFeedbackStatsResponse, error) {
|
|
||||||
pipeline := driver.Pipeline{
|
|
||||||
{
|
{
|
||||||
{Key: "$match", Value: bson.M{
|
{Key: "$match", Value: bson.M{
|
||||||
"company_id": companyID,
|
"company_id": companyID,
|
||||||
@@ -452,14 +244,14 @@ func (r *feedbackRepository) GetCompanyFeedbackStats(ctx context.Context, compan
|
|||||||
|
|
||||||
results, err := r.repo.Aggregate(ctx, pipeline)
|
results, err := r.repo.Aggregate(ctx, pipeline)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
r.logger.Error("Failed to get company feedback stats", map[string]interface{}{
|
r.logger.Error("Failed to get feedback stats", map[string]interface{}{
|
||||||
"company_id": companyID,
|
"company_id": companyID,
|
||||||
"error": err.Error(),
|
"error": err.Error(),
|
||||||
})
|
})
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
stats := &CompanyFeedbackStatsResponse{
|
stats := &StatsResponse{
|
||||||
CompanyID: companyID,
|
CompanyID: companyID,
|
||||||
TotalFeedback: 0,
|
TotalFeedback: 0,
|
||||||
TotalLikes: 0,
|
TotalLikes: 0,
|
||||||
@@ -487,24 +279,24 @@ func (r *feedbackRepository) GetCompanyFeedbackStats(ctx context.Context, compan
|
|||||||
}
|
}
|
||||||
|
|
||||||
// buildSearchFilter builds MongoDB filter from search criteria
|
// buildSearchFilter builds MongoDB filter from search criteria
|
||||||
func (r *feedbackRepository) buildSearchFilter(criteria FeedbackSearchCriteria) bson.M {
|
func (r *feedbackRepository) buildSearchFilter(criteria SearchForm) bson.M {
|
||||||
filter := bson.M{}
|
filter := bson.M{}
|
||||||
|
|
||||||
// Always exclude deleted feedback unless specifically requested
|
// Always exclude deleted feedback unless specifically requested
|
||||||
if criteria.TenderID != nil {
|
if criteria.Tender != nil {
|
||||||
filter["tender_id"] = *criteria.TenderID
|
filter["tender_id"] = *criteria.Tender
|
||||||
}
|
}
|
||||||
|
|
||||||
if criteria.CustomerID != nil {
|
if criteria.Customer != nil {
|
||||||
filter["customer_id"] = *criteria.CustomerID
|
filter["customer_id"] = *criteria.Customer
|
||||||
}
|
}
|
||||||
|
|
||||||
if criteria.CompanyID != nil {
|
if criteria.Company != nil {
|
||||||
filter["company_id"] = *criteria.CompanyID
|
filter["company_id"] = *criteria.Company
|
||||||
}
|
}
|
||||||
|
|
||||||
if criteria.FeedbackType != nil {
|
if criteria.Type != nil {
|
||||||
filter["feedback_type"] = *criteria.FeedbackType
|
filter["feedback_type"] = *criteria.Type
|
||||||
}
|
}
|
||||||
|
|
||||||
if criteria.DateFrom != nil || criteria.DateTo != nil {
|
if criteria.DateFrom != nil || criteria.DateTo != nil {
|
||||||
|
|||||||
+163
-67
@@ -2,44 +2,48 @@ package feedback
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"tm/internal/company"
|
||||||
"tm/internal/tender"
|
"tm/internal/tender"
|
||||||
"tm/pkg/logger"
|
"tm/pkg/logger"
|
||||||
"tm/pkg/response"
|
"tm/pkg/response"
|
||||||
)
|
)
|
||||||
|
|
||||||
// FeedbackService interface defines feedback business logic methods
|
// FeedbackService interface defines feedback business logic methods
|
||||||
type FeedbackService interface {
|
type Service interface {
|
||||||
ToggleFeedback(ctx context.Context, req ToggleFeedbackForm, customerID *string, companyID *string) (*FeedbackResponse, error)
|
Toggle(ctx context.Context, req ToggleFeedbackForm, customerID *string, companyID *string) (*FeedbackResponse, error)
|
||||||
GetFeedback(ctx context.Context, id string) (*FeedbackResponse, error)
|
Search(ctx context.Context, criteria SearchForm, pagination *response.Pagination) (*FeedbackListResponse, error)
|
||||||
ListFeedback(ctx context.Context, criteria FeedbackSearchCriteria, limit, offset int) (*FeedbackListWithTenderResponse, error)
|
Get(ctx context.Context, id string) (*FeedbackResponse, error)
|
||||||
DeleteFeedback(ctx context.Context, id string) error
|
GetByTenderID(ctx context.Context, tenderID, companyID string) (*FeedbackResponse, error)
|
||||||
GetFeedbackByTenderID(ctx context.Context, tenderID, companyID string) (*FeedbackResponse, error)
|
Delete(ctx context.Context, id string) error
|
||||||
CompanyFeedbackStats(ctx context.Context, companyID string) (*CompanyFeedbackStatsResponse, error)
|
CompanyStats(ctx context.Context, companyID string) (*StatsResponse, error)
|
||||||
|
getDetails(ctx context.Context, tenderID, companyID string) (*tenderResponse, *companyResponse, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// feedbackService implements FeedbackService interface
|
// feedbackService implements FeedbackService interface
|
||||||
type feedbackService struct {
|
type feedbackService struct {
|
||||||
feedbackRepo Repository
|
feedbackRepo Repository
|
||||||
tenderService tender.Service
|
tenderService tender.Service
|
||||||
logger logger.Logger
|
companyService company.Service
|
||||||
|
logger logger.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewFeedbackService creates a new feedback service
|
// NewService creates a new feedback service
|
||||||
func NewFeedbackService(feedbackRepo Repository, tenderService tender.Service, logger logger.Logger) FeedbackService {
|
func NewService(feedbackRepo Repository, tenderService tender.Service, companyService company.Service, logger logger.Logger) Service {
|
||||||
return &feedbackService{
|
return &feedbackService{
|
||||||
feedbackRepo: feedbackRepo,
|
feedbackRepo: feedbackRepo,
|
||||||
tenderService: tenderService,
|
tenderService: tenderService,
|
||||||
logger: logger,
|
companyService: companyService,
|
||||||
|
logger: logger,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *feedbackService) ToggleFeedback(ctx context.Context, req ToggleFeedbackForm, customerID *string, companyID *string) (*FeedbackResponse, error) {
|
func (s *feedbackService) Toggle(ctx context.Context, req ToggleFeedbackForm, customerID *string, companyID *string) (*FeedbackResponse, error) {
|
||||||
existing, err := s.feedbackRepo.GetByCompanyID(ctx, *companyID, req.TenderID)
|
existing, err := s.feedbackRepo.GetByCompanyID(ctx, *companyID, req.Tender)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
feedback := &Feedback{
|
feedback := &Feedback{
|
||||||
TenderID: req.TenderID,
|
TenderID: req.Tender,
|
||||||
FeedbackType: req.FeedbackType,
|
FeedbackType: req.Type,
|
||||||
CompanyID: companyID,
|
CompanyID: companyID,
|
||||||
CustomerID: customerID,
|
CustomerID: customerID,
|
||||||
}
|
}
|
||||||
@@ -47,35 +51,53 @@ func (s *feedbackService) ToggleFeedback(ctx context.Context, req ToggleFeedback
|
|||||||
err = s.feedbackRepo.Create(ctx, feedback)
|
err = s.feedbackRepo.Create(ctx, feedback)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Error("Failed to create new feedback", map[string]interface{}{
|
s.logger.Error("Failed to create new feedback", map[string]interface{}{
|
||||||
"tender_id": req.TenderID,
|
"tender_id": req.Tender,
|
||||||
"company_id": *companyID,
|
"company_id": *companyID,
|
||||||
"error": err.Error(),
|
"error": err.Error(),
|
||||||
})
|
})
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return feedback.ToResponse(), nil
|
tender, company, err := s.getDetails(ctx, req.Tender, *companyID)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("Failed to get details", map[string]interface{}{
|
||||||
|
"tender_id": req.Tender,
|
||||||
|
"company_id": *companyID,
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return feedback.ToResponse(tender, company), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if existing.FeedbackType == req.FeedbackType {
|
if existing.FeedbackType == req.Type {
|
||||||
err = s.feedbackRepo.Delete(ctx, existing.GetID())
|
err = s.feedbackRepo.Delete(ctx, existing.GetID())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Error("Failed to update existing feedback", map[string]interface{}{
|
s.logger.Error("Failed to update existing feedback", map[string]interface{}{
|
||||||
"feedback_id": existing.GetID(),
|
"feedback_id": existing.GetID(),
|
||||||
"tender_id": req.TenderID,
|
"tender_id": req.Tender,
|
||||||
"company_id": *companyID,
|
"company_id": *companyID,
|
||||||
"error": err.Error(),
|
"error": err.Error(),
|
||||||
})
|
})
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if req.FeedbackType == FeedbackTypeLike {
|
if req.Type == FeedbackTypeLike {
|
||||||
existing.FeedbackType = "unlike"
|
existing.FeedbackType = "unlike"
|
||||||
} else {
|
} else {
|
||||||
existing.FeedbackType = "undislike"
|
existing.FeedbackType = "undislike"
|
||||||
}
|
}
|
||||||
|
|
||||||
return existing.ToResponse(), nil
|
tender, company, err := s.getDetails(ctx, req.Tender, *companyID)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("Failed to get details", map[string]interface{}{
|
||||||
|
"tender_id": req.Tender,
|
||||||
|
"company_id": *companyID,
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return existing.ToResponse(tender, company), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if existing.FeedbackType == FeedbackTypeLike {
|
if existing.FeedbackType == FeedbackTypeLike {
|
||||||
@@ -88,17 +110,26 @@ func (s *feedbackService) ToggleFeedback(ctx context.Context, req ToggleFeedback
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Error("Failed to update existing feedback", map[string]interface{}{
|
s.logger.Error("Failed to update existing feedback", map[string]interface{}{
|
||||||
"feedback_id": existing.GetID(),
|
"feedback_id": existing.GetID(),
|
||||||
"tender_id": req.TenderID,
|
"tender_id": req.Tender,
|
||||||
"company_id": *companyID,
|
"company_id": *companyID,
|
||||||
"error": err.Error(),
|
"error": err.Error(),
|
||||||
})
|
})
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return existing.ToResponse(), nil
|
tender, company, err := s.getDetails(ctx, req.Tender, *companyID)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("Failed to get details", map[string]interface{}{
|
||||||
|
"tender_id": req.Tender,
|
||||||
|
"company_id": *companyID,
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return existing.ToResponse(tender, company), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *feedbackService) GetFeedback(ctx context.Context, id string) (*FeedbackResponse, error) {
|
func (s *feedbackService) Get(ctx context.Context, id string) (*FeedbackResponse, error) {
|
||||||
feedback, err := s.feedbackRepo.GetByID(ctx, id)
|
feedback, err := s.feedbackRepo.GetByID(ctx, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Error("Failed to get feedback", map[string]interface{}{
|
s.logger.Error("Failed to get feedback", map[string]interface{}{
|
||||||
@@ -108,16 +139,25 @@ func (s *feedbackService) GetFeedback(ctx context.Context, id string) (*Feedback
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return feedback.ToResponse(), nil
|
tender, company, err := s.getDetails(ctx, feedback.TenderID, *feedback.CompanyID)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("Failed to get details", map[string]interface{}{
|
||||||
|
"tender_id": feedback.TenderID,
|
||||||
|
"company_id": *feedback.CompanyID,
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return feedback.ToResponse(tender, company), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *feedbackService) ListFeedback(ctx context.Context, criteria FeedbackSearchCriteria, limit, offset int) (*FeedbackListWithTenderResponse, error) {
|
func (s *feedbackService) Search(ctx context.Context, criteria SearchForm, pagination *response.Pagination) (*FeedbackListResponse, error) {
|
||||||
feedbacks, total, err := s.feedbackRepo.List(ctx, criteria, limit, offset)
|
feedbacks, total, err := s.feedbackRepo.Search(ctx, criteria, pagination)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Error("Failed to list feedback", map[string]interface{}{
|
s.logger.Error("Failed to list feedback", map[string]interface{}{
|
||||||
"criteria": criteria,
|
"criteria": criteria,
|
||||||
"limit": limit,
|
"limit": pagination.Limit,
|
||||||
"offset": offset,
|
"offset": pagination.Offset,
|
||||||
"error": err.Error(),
|
"error": err.Error(),
|
||||||
})
|
})
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -125,23 +165,56 @@ func (s *feedbackService) ListFeedback(ctx context.Context, criteria FeedbackSea
|
|||||||
|
|
||||||
meta := &response.Meta{
|
meta := &response.Meta{
|
||||||
Total: int(total),
|
Total: int(total),
|
||||||
Limit: limit,
|
Limit: pagination.Limit,
|
||||||
Offset: offset,
|
Offset: pagination.Offset,
|
||||||
Page: (offset / limit) + 1,
|
Page: (pagination.Offset / pagination.Limit) + 1,
|
||||||
Pages: int((total + int64(limit) - 1) / int64(limit)),
|
Pages: int((total + int64(pagination.Limit) - 1) / int64(pagination.Limit)),
|
||||||
}
|
}
|
||||||
|
|
||||||
result := make([]*FeedbackWithTenderResponse, len(feedbacks))
|
result := make([]*FeedbackResponse, len(feedbacks))
|
||||||
for i, feedback := range feedbacks {
|
for i, feedback := range feedbacks {
|
||||||
tender, err := s.tenderService.GetByID(ctx, feedback.TenderID)
|
tender, company, _ := s.getDetails(ctx, feedback.TenderID, *feedback.CompanyID)
|
||||||
if err != nil {
|
result[i] = feedback.ToResponse(tender, company)
|
||||||
s.logger.Error("Failed to get tender", map[string]interface{}{
|
}
|
||||||
"tender_id": feedback.TenderID,
|
|
||||||
"error": err.Error(),
|
return &FeedbackListResponse{
|
||||||
})
|
Feedback: result,
|
||||||
return nil, err
|
Meta: meta,
|
||||||
}
|
}, nil
|
||||||
resp := feedback.ToResponseWithTender(&tenderDetails{
|
}
|
||||||
|
|
||||||
|
func (s *feedbackService) Delete(ctx context.Context, id string) error {
|
||||||
|
return s.feedbackRepo.Delete(ctx, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *feedbackService) GetByTenderID(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
|
||||||
|
}
|
||||||
|
|
||||||
|
tender, err := s.tenderService.GetByID(ctx, feedback.TenderID)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("Failed to get tender", map[string]interface{}{
|
||||||
|
"tender_id": feedback.TenderID,
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
company, err := s.companyService.GetByID(ctx, *feedback.CompanyID)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("Failed to get company", map[string]interface{}{
|
||||||
|
"company_id": feedback.CompanyID,
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return feedback.ToResponse(
|
||||||
|
&tenderResponse{
|
||||||
ID: tender.ID,
|
ID: tender.ID,
|
||||||
NoticePublicationID: tender.NoticePublicationID,
|
NoticePublicationID: tender.NoticePublicationID,
|
||||||
PublicationDate: tender.PublicationDate,
|
PublicationDate: tender.PublicationDate,
|
||||||
@@ -158,46 +231,69 @@ func (s *feedbackService) ListFeedback(ctx context.Context, criteria FeedbackSea
|
|||||||
CountryCode: tender.CountryCode,
|
CountryCode: tender.CountryCode,
|
||||||
BuyerOrganization: &organizationResponse{Name: tender.BuyerOrganization.Name},
|
BuyerOrganization: &organizationResponse{Name: tender.BuyerOrganization.Name},
|
||||||
Status: string(tender.Status),
|
Status: string(tender.Status),
|
||||||
})
|
},
|
||||||
result[i] = resp
|
&companyResponse{
|
||||||
}
|
ID: company.ID,
|
||||||
return &FeedbackListWithTenderResponse{
|
Name: company.Name,
|
||||||
Feedback: result,
|
},
|
||||||
Meta: meta,
|
), nil
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *feedbackService) DeleteFeedback(ctx context.Context, id string) error {
|
func (s *feedbackService) getDetails(ctx context.Context, tenderID, companyID string) (*tenderResponse, *companyResponse, error) {
|
||||||
return s.feedbackRepo.Delete(ctx, id)
|
tender, err := s.tenderService.GetByID(ctx, tenderID)
|
||||||
}
|
|
||||||
|
|
||||||
func (s *feedbackService) GetFeedbackByTenderID(ctx context.Context, tenderID, companyID string) (*FeedbackResponse, error) {
|
|
||||||
feedback, err := s.feedbackRepo.GetByTenderID(ctx, tenderID, companyID)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Error("Failed to get feedback by tender ID", map[string]interface{}{
|
s.logger.Error("Failed to get tender", map[string]interface{}{
|
||||||
"tender_id": tenderID,
|
"tender_id": tenderID,
|
||||||
"error": err.Error(),
|
"error": err.Error(),
|
||||||
})
|
})
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
return feedback.ToResponse(), nil
|
|
||||||
|
company, err := s.companyService.GetByID(ctx, companyID)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("Failed to get company", map[string]interface{}{
|
||||||
|
"company_id": companyID,
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return &tenderResponse{
|
||||||
|
ID: tender.ID,
|
||||||
|
NoticePublicationID: tender.NoticePublicationID,
|
||||||
|
PublicationDate: tender.PublicationDate,
|
||||||
|
Title: tender.Title,
|
||||||
|
Description: tender.Description,
|
||||||
|
ProcurementTypeCode: tender.ProcurementTypeCode,
|
||||||
|
ProcedureCode: tender.ProcedureCode,
|
||||||
|
EstimatedValue: tender.EstimatedValue,
|
||||||
|
Currency: tender.Currency,
|
||||||
|
Duration: tender.Duration,
|
||||||
|
DurationUnit: tender.DurationUnit,
|
||||||
|
TenderDeadline: tender.TenderDeadline,
|
||||||
|
SubmissionDeadline: tender.SubmissionDeadline,
|
||||||
|
CountryCode: tender.CountryCode,
|
||||||
|
BuyerOrganization: &organizationResponse{Name: tender.BuyerOrganization.Name},
|
||||||
|
Status: string(tender.Status),
|
||||||
|
}, &companyResponse{
|
||||||
|
ID: company.ID,
|
||||||
|
Name: company.Name,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *feedbackService) CompanyFeedbackStats(ctx context.Context, companyID string) (*CompanyFeedbackStatsResponse, error) {
|
func (s *feedbackService) CompanyStats(ctx context.Context, companyID string) (*StatsResponse, error) {
|
||||||
s.logger.Info("Getting company feedback statistics", map[string]interface{}{
|
s.logger.Info("Getting company feedback statistics", map[string]interface{}{
|
||||||
"company_id": companyID,
|
"company_id": companyID,
|
||||||
})
|
})
|
||||||
|
|
||||||
stats, err := s.feedbackRepo.GetCompanyFeedbackStats(ctx, companyID)
|
stats, err := s.feedbackRepo.CompanyStats(ctx, companyID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Error("Failed to get company feedback statistics", map[string]interface{}{
|
s.logger.Error("Failed to get company stats", map[string]interface{}{
|
||||||
"company_id": companyID,
|
"company_id": companyID,
|
||||||
"error": err.Error(),
|
"error": err.Error(),
|
||||||
})
|
})
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
s.logger.Info("Company feedback statistics retrieved successfully", map[string]interface{}{
|
s.logger.Info("Company stats retrieved successfully", map[string]interface{}{
|
||||||
"company_id": companyID,
|
"company_id": companyID,
|
||||||
"total_feedback": stats.TotalFeedback,
|
"total_feedback": stats.TotalFeedback,
|
||||||
"total_likes": stats.TotalLikes,
|
"total_likes": stats.TotalLikes,
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ type inquiryRepository struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewInquiryRepository creates a new inquiry repository
|
// NewInquiryRepository creates a new inquiry repository
|
||||||
func NewInquiryRepository(mongoManager *orm.ConnectionManager, logger logger.Logger) Repository {
|
func NewRepository(mongoManager *orm.ConnectionManager, logger logger.Logger) Repository {
|
||||||
// Create indexes using the ORM's index management
|
// Create indexes using the ORM's index management
|
||||||
indexes := []orm.Index{
|
indexes := []orm.Index{
|
||||||
*orm.CreateTextIndex("search_idx", "full_name", "company_name", "work_email"),
|
*orm.CreateTextIndex("search_idx", "full_name", "company_name", "work_email"),
|
||||||
@@ -140,10 +140,10 @@ func (r *inquiryRepository) UpdateStatus(ctx context.Context, id string, status,
|
|||||||
}
|
}
|
||||||
|
|
||||||
r.logger.Info("Inquiry status updated successfully", map[string]interface{}{
|
r.logger.Info("Inquiry status updated successfully", map[string]interface{}{
|
||||||
"inquiry_id": id,
|
"inquiry_id": id,
|
||||||
"old_status": inquiry.StatusHistory[len(inquiry.StatusHistory)-2].Status, // Previous status
|
"old_status": inquiry.StatusHistory[len(inquiry.StatusHistory)-2].Status, // Previous status
|
||||||
"new_status": status,
|
"new_status": status,
|
||||||
"changed_by": changedBy,
|
"changed_by": changedBy,
|
||||||
})
|
})
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -25,8 +25,8 @@ type inquiryService struct {
|
|||||||
logger logger.Logger
|
logger logger.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewInquiryService creates a new inquiry service
|
// NewService creates a new inquiry service
|
||||||
func NewInquiryService(repository Repository, sdk notification.SDK, logger logger.Logger) Service {
|
func NewService(repository Repository, sdk notification.SDK, logger logger.Logger) Service {
|
||||||
return &inquiryService{
|
return &inquiryService{
|
||||||
repository: repository,
|
repository: repository,
|
||||||
sdk: sdk,
|
sdk: sdk,
|
||||||
|
|||||||
@@ -33,8 +33,8 @@ type notificationService struct {
|
|||||||
logger logger.Logger
|
logger logger.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewNotificationService creates a new notification service
|
// NewService creates a new notification service
|
||||||
func NewNotificationService(
|
func NewService(
|
||||||
sdk notification.SDK,
|
sdk notification.SDK,
|
||||||
userService user.Service,
|
userService user.Service,
|
||||||
customerService customer.Service,
|
customerService customer.Service,
|
||||||
|
|||||||
@@ -14,8 +14,8 @@ type TenderHandler struct {
|
|||||||
logger logger.Logger
|
logger logger.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewTenderHandler creates a new tender handler
|
// NewHandler creates a new tender handler
|
||||||
func NewTenderHandler(service Service, logger logger.Logger) *TenderHandler {
|
func NewHandler(service Service, logger logger.Logger) *TenderHandler {
|
||||||
return &TenderHandler{
|
return &TenderHandler{
|
||||||
service: service,
|
service: service,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ type tenderRepository struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewTenderRepository creates a new tender repository
|
// NewTenderRepository creates a new tender repository
|
||||||
func NewTenderRepository(mongoManager *orm.ConnectionManager, logger logger.Logger) TenderRepository {
|
func NewRepository(mongoManager *orm.ConnectionManager, logger logger.Logger) TenderRepository {
|
||||||
// Create indexes for tenders collection
|
// Create indexes for tenders collection
|
||||||
tenderIndexes := []orm.Index{
|
tenderIndexes := []orm.Index{
|
||||||
*orm.CreateUniqueIndex("notice_publication_id_idx", bson.D{{Key: "notice_publication_id", Value: 1}}),
|
*orm.CreateUniqueIndex("notice_publication_id_idx", bson.D{{Key: "notice_publication_id", Value: 1}}),
|
||||||
|
|||||||
@@ -30,8 +30,8 @@ type tenderService struct {
|
|||||||
logger logger.Logger
|
logger logger.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewTenderService creates a new tender service
|
// NewService creates a new tender service
|
||||||
func NewTenderService(repository TenderRepository, companyService company.Service, logger logger.Logger) Service {
|
func NewService(repository TenderRepository, companyService company.Service, logger logger.Logger) Service {
|
||||||
return &tenderService{
|
return &tenderService{
|
||||||
repository: repository,
|
repository: repository,
|
||||||
companyService: companyService,
|
companyService: companyService,
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ type tenderApprovalRepository struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewTenderApprovalRepository creates a new tender approval repository
|
// NewTenderApprovalRepository creates a new tender approval repository
|
||||||
func NewTenderApprovalRepository(mongoManager *mongo.ConnectionManager, logger logger.Logger) Repository {
|
func NewRepository(mongoManager *mongo.ConnectionManager, logger logger.Logger) Repository {
|
||||||
// Create indexes using the ORM's index management
|
// Create indexes using the ORM's index management
|
||||||
indexes := []mongo.Index{
|
indexes := []mongo.Index{
|
||||||
*mongo.CreateUniqueIndex("tender_company_idx", bson.D{{Key: "tender_id", Value: 1}, {Key: "company_id", Value: 1}}),
|
*mongo.CreateUniqueIndex("tender_company_idx", bson.D{{Key: "tender_id", Value: 1}, {Key: "company_id", Value: 1}}),
|
||||||
|
|||||||
@@ -55,8 +55,8 @@ type tenderApprovalService struct {
|
|||||||
logger logger.Logger
|
logger logger.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewTenderApprovalService creates a new tender approval service
|
// NewService creates a new tender approval service
|
||||||
func NewTenderApprovalService(repository Repository, tenderService tender.Service, logger logger.Logger) Service {
|
func NewService(repository Repository, tenderService tender.Service, logger logger.Logger) Service {
|
||||||
return &tenderApprovalService{
|
return &tenderApprovalService{
|
||||||
repository: repository,
|
repository: repository,
|
||||||
tenderService: tenderService,
|
tenderService: tenderService,
|
||||||
|
|||||||
@@ -17,8 +17,8 @@ type Handler struct {
|
|||||||
authService authorization.AuthorizationService
|
authService authorization.AuthorizationService
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewUserHandler creates a new user handler
|
// NewHandler creates a new user handler
|
||||||
func NewUserHandler(service Service, logger logger.Logger, validator ValidationService, authService authorization.AuthorizationService) *Handler {
|
func NewHandler(service Service, logger logger.Logger, validator ValidationService, authService authorization.AuthorizationService) *Handler {
|
||||||
return &Handler{
|
return &Handler{
|
||||||
service: service,
|
service: service,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ type userRepository struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewUserRepository creates a new user repository
|
// NewUserRepository creates a new user repository
|
||||||
func NewUserRepository(mongoManager *orm.ConnectionManager, logger logger.Logger) Repository {
|
func NewRepository(mongoManager *orm.ConnectionManager, logger logger.Logger) Repository {
|
||||||
// Create indexes using the ORM's index management
|
// Create indexes using the ORM's index management
|
||||||
indexes := []orm.Index{
|
indexes := []orm.Index{
|
||||||
*orm.CreateTextIndex("search_idx", "full_name", "email", "username"),
|
*orm.CreateTextIndex("search_idx", "full_name", "email", "username"),
|
||||||
|
|||||||
@@ -45,8 +45,8 @@ type userService struct {
|
|||||||
notification notification.SDK
|
notification notification.SDK
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewUserService creates a new user service
|
// NewService creates a new user service
|
||||||
func NewUserService(repository Repository, logger logger.Logger, authService authorization.AuthorizationService, validator ValidationService, notificationSDK notification.SDK) Service {
|
func NewService(repository Repository, logger logger.Logger, authService authorization.AuthorizationService, validator ValidationService, notificationSDK notification.SDK) Service {
|
||||||
return &userService{
|
return &userService{
|
||||||
repository: repository,
|
repository: repository,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
|
|||||||
Reference in New Issue
Block a user