profile keyword manual update and recommended tenders
This commit is contained in:
@@ -181,6 +181,11 @@ type ResetPasswordForm struct {
|
||||
NewPassword string `json:"new_password" valid:"required,length(5|128)" example:"NewPass!123"`
|
||||
}
|
||||
|
||||
// UpdateProfileKeywordsForm represents the form for updating profile keywords
|
||||
type UpdateProfileKeywordsForm struct {
|
||||
Keywords []string `json:"keywords" valid:"required"`
|
||||
}
|
||||
|
||||
// RequestResetPasswordResponse represents the response for password reset request
|
||||
type RequestResetPasswordResponse struct {
|
||||
Message string `json:"message" example:"Password reset code sent to your email"`
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package customer
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"tm/internal/user"
|
||||
"tm/pkg/authorization"
|
||||
"tm/pkg/logger"
|
||||
@@ -57,6 +58,9 @@ func (h *Handler) CreateCustomer(c echo.Context) error {
|
||||
err.Error() == "company with this tax ID already exists" {
|
||||
return response.Conflict(c, err.Error())
|
||||
}
|
||||
if strings.HasPrefix(err.Error(), "invalid company ID") || err.Error() == "invalid username format" {
|
||||
return response.BadRequest(c, err.Error(), "")
|
||||
}
|
||||
return response.InternalServerError(c, "Failed to create customer")
|
||||
}
|
||||
|
||||
@@ -397,6 +401,43 @@ func (h *Handler) GetProfile(c echo.Context) error {
|
||||
return response.Success(c, resp, "Profile retrieved successfully")
|
||||
}
|
||||
|
||||
// UpdateProfileKeywords updates customer profile keywords (Mobile)
|
||||
// @Summary Update customer profile keywords
|
||||
// @Description Update profile keywords manually and trigger asynchronous GLM enhancement based on customer company information (including country).
|
||||
// @Tags Authorization
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security BearerAuth
|
||||
// @Param request body UpdateProfileKeywordsForm true "Manual keywords"
|
||||
// @Success 200 {object} response.APIResponse{data=CustomerResponse} "Profile keywords update started"
|
||||
// @Failure 400 {object} response.APIResponse "Bad request - Invalid input data"
|
||||
// @Failure 401 {object} response.APIResponse "Unauthorized - Invalid or missing token"
|
||||
// @Failure 404 {object} response.APIResponse "Not found - Customer not found"
|
||||
// @Failure 422 {object} response.APIResponse "Validation error - Invalid request data"
|
||||
// @Failure 500 {object} response.APIResponse "Internal server error"
|
||||
// @Router /api/v1/profile/keywords [patch]
|
||||
func (h *Handler) UpdateProfileKeywords(c echo.Context) error {
|
||||
customerID, err := GetCustomerIDFromContext(c)
|
||||
if err != nil {
|
||||
return response.Unauthorized(c, "Customer not authenticated")
|
||||
}
|
||||
|
||||
form, err := response.Parse[UpdateProfileKeywordsForm](c)
|
||||
if err != nil {
|
||||
return response.ValidationError(c, "Invalid request data", err.Error())
|
||||
}
|
||||
|
||||
resp, err := h.service.UpdateProfileKeywords(c.Request().Context(), customerID, form.Keywords)
|
||||
if err != nil {
|
||||
if err.Error() == "customer not found" {
|
||||
return response.NotFound(c, "Customer not found")
|
||||
}
|
||||
return response.InternalServerError(c, "Failed to update profile keywords")
|
||||
}
|
||||
|
||||
return response.Success(c, resp, "Profile keywords update started")
|
||||
}
|
||||
|
||||
// Logout handles customer logout (Mobile)
|
||||
// @Summary Customer logout
|
||||
// @Description Logout customer and invalidate access token
|
||||
|
||||
@@ -41,6 +41,7 @@ type Service interface {
|
||||
Login(ctx context.Context, form *LoginForm) (*AuthResponse, error)
|
||||
RefreshToken(ctx context.Context, refreshToken string) (*AuthResponse, error)
|
||||
GetProfileWithCompanies(ctx context.Context, id string) (*CustomerResponse, error)
|
||||
UpdateProfileKeywords(ctx context.Context, customerID string, keywords []string) (*CustomerResponse, error)
|
||||
Logout(ctx context.Context, id string, accessToken string) error
|
||||
|
||||
// Forgot password operations
|
||||
@@ -96,6 +97,9 @@ func (s *customerService) Register(ctx context.Context, form *CreateCustomerForm
|
||||
var Companies []string
|
||||
if len(form.Companies) > 0 {
|
||||
for _, companyID := range form.Companies {
|
||||
if _, err := bson.ObjectIDFromHex(companyID); err != nil {
|
||||
return nil, errors.New("invalid company ID format: " + companyID)
|
||||
}
|
||||
_, err := s.companyService.GetByID(ctx, companyID)
|
||||
if err != nil {
|
||||
s.logger.Error("Invalid company ID provided", map[string]interface{}{
|
||||
@@ -405,62 +409,7 @@ func (s *customerService) Update(ctx context.Context, id string, form *UpdateCus
|
||||
customer.KeywordsStatus = KeywordsStatusInProgress
|
||||
|
||||
// Save customer with in_progress status first, then process asynchronously
|
||||
customerID := customer.ID.Hex()
|
||||
go func() {
|
||||
ctx := context.Background()
|
||||
// Get fresh customer data for processing
|
||||
cust, err := s.repository.GetByID(ctx, customerID)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to get customer for keyword processing", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"customer_id": customerID,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// If GLM SDK is not available, keep manual keywords and set status to ready
|
||||
if s.glmSDK == nil {
|
||||
s.logger.Info("GLM SDK not available, keeping manual keywords", map[string]interface{}{
|
||||
"customer_id": customerID,
|
||||
})
|
||||
cust.KeywordsStatus = KeywordsStatusReady
|
||||
} else {
|
||||
// Regenerate keywords based on updated customer info
|
||||
keywords, err := s.generateKeywords(ctx, cust)
|
||||
if err != nil {
|
||||
s.logger.Warn("Failed to process keywords after manual update", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"customer_id": customerID,
|
||||
})
|
||||
// Keep manual keywords but set status to failed
|
||||
cust.KeywordsStatus = KeywordsStatusFailed
|
||||
} else if len(keywords) > 0 {
|
||||
// Update with AI-generated keywords
|
||||
cust.Keywords = keywords
|
||||
cust.KeywordsStatus = KeywordsStatusReady
|
||||
} else {
|
||||
// Empty keywords from GLM, keep manual keywords and set status to ready
|
||||
s.logger.Warn("GLM returned empty keywords, keeping manual keywords", map[string]interface{}{
|
||||
"customer_id": customerID,
|
||||
})
|
||||
cust.KeywordsStatus = KeywordsStatusReady
|
||||
}
|
||||
}
|
||||
|
||||
// Update customer with new status
|
||||
err = s.repository.Update(ctx, cust)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to update customer keywords status", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"customer_id": customerID,
|
||||
})
|
||||
} else {
|
||||
s.logger.Info("Keywords processed successfully", map[string]interface{}{
|
||||
"customer_id": customerID,
|
||||
"status": cust.KeywordsStatus,
|
||||
})
|
||||
}
|
||||
}()
|
||||
s.processKeywordsAsync(customer.ID.Hex())
|
||||
} else if infoChanged || companiesChanged {
|
||||
// Customer info changed, regenerate keywords
|
||||
customer.KeywordsStatus = KeywordsStatusInProgress
|
||||
@@ -907,6 +856,42 @@ func (s *customerService) GetProfileWithCompanies(ctx context.Context, customerI
|
||||
return customerResponse, nil
|
||||
}
|
||||
|
||||
// UpdateProfileKeywords updates customer profile keywords and triggers AI enhancement asynchronously.
|
||||
func (s *customerService) UpdateProfileKeywords(ctx context.Context, customerID string, keywords []string) (*CustomerResponse, error) {
|
||||
customer, err := s.repository.GetByID(ctx, customerID)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to get customer for profile keyword update", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"customer_id": customerID,
|
||||
})
|
||||
return nil, errors.New("customer not found")
|
||||
}
|
||||
|
||||
customer.Keywords = keywords
|
||||
customer.KeywordsStatus = KeywordsStatusInProgress
|
||||
|
||||
if err := s.repository.Update(ctx, customer); err != nil {
|
||||
s.logger.Error("Failed to save profile keywords", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"customer_id": customerID,
|
||||
})
|
||||
return nil, errors.New("failed to update profile keywords")
|
||||
}
|
||||
|
||||
s.processKeywordsAsync(customerID)
|
||||
|
||||
companies, err := s.loadCompaniesForCustomer(ctx, customer)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to load companies after profile keyword update", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"customer_id": customerID,
|
||||
})
|
||||
companies = []*CompanySummary{}
|
||||
}
|
||||
|
||||
return customer.ToResponse(companies), nil
|
||||
}
|
||||
|
||||
// Logout handles customer logout
|
||||
func (s *customerService) Logout(ctx context.Context, customerID string, accessToken string) error {
|
||||
s.logger.Info("Customer logout", map[string]interface{}{
|
||||
@@ -1398,6 +1383,12 @@ func (s *customerService) generateKeywords(ctx context.Context, customer *Custom
|
||||
if comp.Industry != "" {
|
||||
customerInfo.WriteString(fmt.Sprintf(" (Industry: %s)", comp.Industry))
|
||||
}
|
||||
if comp.Address != nil && comp.Address.Country != "" {
|
||||
customerInfo.WriteString(fmt.Sprintf(" (Country: %s)", comp.Address.Country))
|
||||
}
|
||||
if comp.Tags != nil && len(comp.Tags.Keywords) > 0 {
|
||||
customerInfo.WriteString(fmt.Sprintf(" (Company Keywords: %s)", strings.Join(comp.Tags.Keywords, ", ")))
|
||||
}
|
||||
}
|
||||
}
|
||||
customerInfo.WriteString("\n")
|
||||
@@ -1452,3 +1443,54 @@ Return ONLY a comma-separated list of keywords, no explanations or additional te
|
||||
|
||||
return keywords, nil
|
||||
}
|
||||
|
||||
func (s *customerService) processKeywordsAsync(customerID string) {
|
||||
go func() {
|
||||
ctx := context.Background()
|
||||
cust, err := s.repository.GetByID(ctx, customerID)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to get customer for keyword processing", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"customer_id": customerID,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if s.glmSDK == nil {
|
||||
s.logger.Info("GLM SDK not available, keeping manual keywords", map[string]interface{}{
|
||||
"customer_id": customerID,
|
||||
})
|
||||
cust.KeywordsStatus = KeywordsStatusReady
|
||||
} else {
|
||||
keywords, err := s.generateKeywords(ctx, cust)
|
||||
if err != nil {
|
||||
s.logger.Warn("Failed to process keywords after manual update", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"customer_id": customerID,
|
||||
})
|
||||
cust.KeywordsStatus = KeywordsStatusFailed
|
||||
} else if len(keywords) > 0 {
|
||||
cust.Keywords = keywords
|
||||
cust.KeywordsStatus = KeywordsStatusReady
|
||||
} else {
|
||||
s.logger.Warn("GLM returned empty keywords, keeping manual keywords", map[string]interface{}{
|
||||
"customer_id": customerID,
|
||||
})
|
||||
cust.KeywordsStatus = KeywordsStatusReady
|
||||
}
|
||||
}
|
||||
|
||||
err = s.repository.Update(ctx, cust)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to update customer keywords status", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"customer_id": customerID,
|
||||
})
|
||||
} else {
|
||||
s.logger.Info("Keywords processed successfully", map[string]interface{}{
|
||||
"customer_id": customerID,
|
||||
"status": cust.KeywordsStatus,
|
||||
})
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
@@ -234,6 +234,16 @@ func (h *TenderHandler) GetPublicTenders(c echo.Context) error {
|
||||
return response.InternalServerError(c, "Failed to retrieve tenders")
|
||||
}
|
||||
|
||||
if result != nil {
|
||||
filtered := make([]TenderResponse, 0, len(result.Tenders))
|
||||
for _, t := range result.Tenders {
|
||||
if t.TenderDeadline != 0 {
|
||||
filtered = append(filtered, t)
|
||||
}
|
||||
}
|
||||
result.Tenders = filtered
|
||||
result.Metadata.Total = int(len(filtered))
|
||||
}
|
||||
return response.SuccessWithMeta(c, result, result.Metadata, "Tenders retrieved successfully")
|
||||
}
|
||||
|
||||
@@ -277,6 +287,11 @@ func (h *TenderHandler) RecommendTenders(c echo.Context) error {
|
||||
return response.ValidationError(c, err.Error(), "")
|
||||
}
|
||||
|
||||
result, err := h.service.Recommend(c.Request().Context(), form, response.NewPagination(c))
|
||||
if err != nil {
|
||||
return response.InternalServerError(c, "Failed to retrieve tenders")
|
||||
}
|
||||
|
||||
form.Status = []string{string(TenderStatusActive)}
|
||||
|
||||
var companyID *string
|
||||
@@ -291,11 +306,16 @@ func (h *TenderHandler) RecommendTenders(c echo.Context) error {
|
||||
}
|
||||
form.CustomerID = customerID
|
||||
|
||||
result, err := h.service.Recommend(c.Request().Context(), form, response.NewPagination(c))
|
||||
if err != nil {
|
||||
return response.InternalServerError(c, "Failed to retrieve tenders")
|
||||
if result != nil {
|
||||
filtered := make([]TenderResponse, 0, len(result.Tenders))
|
||||
for _, t := range result.Tenders {
|
||||
if t.TenderDeadline != 0 {
|
||||
filtered = append(filtered, t)
|
||||
}
|
||||
}
|
||||
result.Tenders = filtered
|
||||
result.Metadata.Total = int(len(filtered))
|
||||
}
|
||||
|
||||
return response.SuccessWithMeta(c, result, result.Metadata, "Tenders retrieved successfully")
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user