From 566fa0757491f286e6c29e1fb195e5923fed7d75 Mon Sep 17 00:00:00 2001 From: "n.nakhostin" Date: Mon, 11 Aug 2025 16:24:28 +0330 Subject: [PATCH] Update API documentation to reflect customer authorization changes - Changed tags from "Customers-Mobile" to "Customers-Authorization" in Swagger documentation for customer logout and profile retrieval endpoints. - Updated the Company entity to replace CustomerID with OwnerCustomerID to better represent ownership. - Removed obsolete customer assignment methods and related forms from the company domain, streamlining the codebase. - Adjusted customer forms to include CompanyID instead of CompanyName for better consistency in customer management. - Enhanced Swagger documentation to accurately reflect the new structure and authorization details for customer-related endpoints. --- cmd/web/docs/docs.go | 4 +- cmd/web/docs/swagger.json | 4 +- cmd/web/docs/swagger.yaml | 4 +- internal/company/entity.go | 8 ++-- internal/company/form.go | 3 -- internal/company/handler.go | 82 ---------------------------------- internal/company/repository.go | 62 ------------------------- internal/company/service.go | 70 ----------------------------- internal/customer/entity.go | 18 ++++---- internal/customer/form.go | 36 +++++++-------- internal/customer/handler.go | 4 +- internal/customer/service.go | 16 ++----- 12 files changed, 41 insertions(+), 270 deletions(-) diff --git a/cmd/web/docs/docs.go b/cmd/web/docs/docs.go index d9580e5..0e69ada 100644 --- a/cmd/web/docs/docs.go +++ b/cmd/web/docs/docs.go @@ -3914,7 +3914,7 @@ const docTemplate = `{ "application/json" ], "tags": [ - "Customers-Mobile" + "Customers-Authorization" ], "summary": "Customer logout", "responses": { @@ -3954,7 +3954,7 @@ const docTemplate = `{ "application/json" ], "tags": [ - "Customers-Mobile" + "Customers-Authorization" ], "summary": "Get customer profile", "responses": { diff --git a/cmd/web/docs/swagger.json b/cmd/web/docs/swagger.json index 8d5e8cf..b48a5c0 100644 --- a/cmd/web/docs/swagger.json +++ b/cmd/web/docs/swagger.json @@ -3907,7 +3907,7 @@ "application/json" ], "tags": [ - "Customers-Mobile" + "Customers-Authorization" ], "summary": "Customer logout", "responses": { @@ -3947,7 +3947,7 @@ "application/json" ], "tags": [ - "Customers-Mobile" + "Customers-Authorization" ], "summary": "Get customer profile", "responses": { diff --git a/cmd/web/docs/swagger.yaml b/cmd/web/docs/swagger.yaml index 58c3e45..dd3aaa9 100644 --- a/cmd/web/docs/swagger.yaml +++ b/cmd/web/docs/swagger.yaml @@ -3413,7 +3413,7 @@ paths: - BearerAuth: [] summary: Customer logout tags: - - Customers-Mobile + - Customers-Authorization /api/v1/profile: get: consumes: @@ -3447,7 +3447,7 @@ paths: - BearerAuth: [] summary: Get customer profile tags: - - Customers-Mobile + - Customers-Authorization /api/v1/refresh-token: post: consumes: diff --git a/internal/company/entity.go b/internal/company/entity.go index 268a173..f38d74b 100644 --- a/internal/company/entity.go +++ b/internal/company/entity.go @@ -63,8 +63,8 @@ type Company struct { Currency string `bson:"currency" json:"currency"` // Default: "USD" Timezone string `bson:"timezone" json:"timezone"` // Default: "UTC" - // Customer assignment (for login credentials) - CustomerID *string `bson:"customer_id,omitempty" json:"customer_id,omitempty"` + // Company ownership - which customer owns this company + OwnerCustomerID *string `bson:"owner_customer_id,omitempty" json:"owner_customer_id,omitempty"` // Audit fields CreatedBy *string `bson:"created_by,omitempty" json:"created_by,omitempty"` @@ -165,7 +165,7 @@ type CompanyResponse struct { Language string `json:"language"` Currency string `json:"currency"` Timezone string `json:"timezone"` - CustomerID *string `json:"customer_id,omitempty"` + OwnerCustomerID *string `json:"owner_customer_id,omitempty"` CreatedAt int64 `json:"created_at"` UpdatedAt int64 `json:"updated_at"` CreatedBy *string `json:"created_by,omitempty"` @@ -198,7 +198,7 @@ func (c *Company) ToResponse() *CompanyResponse { Language: c.Language, Currency: c.Currency, Timezone: c.Timezone, - CustomerID: c.CustomerID, + OwnerCustomerID: c.OwnerCustomerID, CreatedAt: c.CreatedAt, UpdatedAt: c.UpdatedAt, CreatedBy: c.CreatedBy, diff --git a/internal/company/form.go b/internal/company/form.go index 4b2a534..a3b95b4 100644 --- a/internal/company/form.go +++ b/internal/company/form.go @@ -61,9 +61,6 @@ type UpdateCompanyForm struct { // Tags for categorization and search Tags *CompanyTagsForm `json:"tags,omitempty"` - // Customer assignment (for login credentials) - CustomerID *string `json:"customer_id,omitempty" valid:"optional,uuid"` - // Settings Language *string `json:"language,omitempty" valid:"optional,in(en|ar|fr|es|de|zh|ja|ko)"` Currency *string `json:"currency,omitempty" valid:"optional,in(USD|EUR|GBP|JPY|CAD|AUD|CHF|CNY|INR|BRL)"` diff --git a/internal/company/handler.go b/internal/company/handler.go index ec73f71..b40f133 100644 --- a/internal/company/handler.go +++ b/internal/company/handler.go @@ -38,8 +38,6 @@ func (h *Handler) RegisterRoutes(e *echo.Echo) { adminV1.GET("/search", h.SearchCompanies) adminV1.PATCH("/:id/status", h.UpdateCompanyStatus) adminV1.PATCH("/:id/verification", h.UpdateCompanyVerification) - adminV1.POST("/:id/assign-customer", h.AssignCustomer) - adminV1.POST("/:id/unassign-customer", h.UnassignCustomer) adminV1.PUT("/:id/tags", h.UpdateCompanyTags) adminV1.POST("/:id/tags/add", h.AddTags) adminV1.POST("/:id/tags/remove", h.RemoveTags) @@ -370,86 +368,6 @@ func (h *Handler) UpdateCompanyVerification(c echo.Context) error { }, "Company verification updated successfully") } -// AssignCustomer assigns a customer to a company (Web Panel) -// @Summary Assign customer to company -// @Description Assign a customer to a company for login credentials -// @Tags Companies-Admin -// @Accept json -// @Produce json -// @Param id path string true "Company ID" -// @Param assignment body AssignCustomerForm true "Customer assignment information" -// @Success 200 {object} response.APIResponse "Customer assigned successfully" -// @Failure 400 {object} response.APIResponse "Bad request - Invalid input data" -// @Failure 404 {object} response.APIResponse "Not found - Company not found" -// @Failure 409 {object} response.APIResponse "Conflict - Customer already assigned" -// @Failure 422 {object} response.APIResponse "Validation error - Invalid request data" -// @Failure 500 {object} response.APIResponse "Internal server error" -// @Security BearerAuth -// @Router /admin/v1/companies/{id}/assign-customer [post] -func (h *Handler) AssignCustomer(c echo.Context) error { - id := c.Param("id") - form, err := response.Parse[AssignCustomerForm](c) - if err != nil { - return response.ValidationError(c, "Invalid request data", err.Error()) - } - - // Get user ID from JWT token context - assignedBy, err := user.GetUserIDFromContext(c) - if err != nil { - return response.Unauthorized(c, "User not authenticated") - } - - err = h.service.AssignCustomer(c.Request().Context(), id, form, &assignedBy) - if err != nil { - if err.Error() == "company not found" { - return response.NotFound(c, "Company not found") - } - if err.Error() == "customer is already assigned to another company" { - return response.Conflict(c, err.Error()) - } - return response.InternalServerError(c, "Failed to assign customer") - } - - return response.Success(c, map[string]interface{}{ - "message": "Customer assigned successfully", - }, "Customer assigned successfully") -} - -// UnassignCustomer unassigns a customer from a company (Web Panel) -// @Summary Unassign customer from company -// @Description Remove customer assignment from a company -// @Tags Companies-Admin -// @Accept json -// @Produce json -// @Param id path string true "Company ID" -// @Success 200 {object} response.APIResponse "Customer unassigned successfully" -// @Failure 400 {object} response.APIResponse "Bad request - Invalid company ID" -// @Failure 404 {object} response.APIResponse "Not found - Company not found" -// @Failure 500 {object} response.APIResponse "Internal server error" -// @Security BearerAuth -// @Router /admin/v1/companies/{id}/unassign-customer [post] -func (h *Handler) UnassignCustomer(c echo.Context) error { - id := c.Param("id") - - // Get user ID from JWT token context - unassignedBy, err := user.GetUserIDFromContext(c) - if err != nil { - return response.Unauthorized(c, "User not authenticated") - } - - err = h.service.UnassignCustomer(c.Request().Context(), id, &unassignedBy) - if err != nil { - if err.Error() == "company not found" { - return response.NotFound(c, "Company not found") - } - return response.InternalServerError(c, "Failed to unassign customer") - } - - return response.Success(c, map[string]interface{}{ - "message": "Customer unassigned successfully", - }, "Customer unassigned successfully") -} - // UpdateCompanyTags updates company tags (Web Panel) // @Summary Update company tags // @Description Update company tags (CPV, categories, keywords, specializations, certifications) diff --git a/internal/company/repository.go b/internal/company/repository.go index 9ac1188..053df69 100644 --- a/internal/company/repository.go +++ b/internal/company/repository.go @@ -30,8 +30,6 @@ type Repository interface { CountWithCustomer(ctx context.Context) (int64, error) UpdateStatus(ctx context.Context, id string, status CompanyStatus) error UpdateVerification(ctx context.Context, id string, isVerified, isCompliant bool, complianceNotes *string) error - AssignCustomer(ctx context.Context, id string, customerID string) error - UnassignCustomer(ctx context.Context, id string) error UpdateTags(ctx context.Context, id string, tags *CompanyTags) error AddTags(ctx context.Context, id string, cpvCodes []string, categories []string, keywords []string, specializations []string, certifications []string) error RemoveTags(ctx context.Context, id string, cpvCodes []string, categories []string, keywords []string, specializations []string, certifications []string) error @@ -591,66 +589,6 @@ func (r *companyRepository) UpdateVerification(ctx context.Context, id string, i return nil } -// AssignCustomer assigns a customer to a company -func (r *companyRepository) AssignCustomer(ctx context.Context, id string, customerID string) error { - // Get company first - company, err := r.GetByID(ctx, id) - if err != nil { - return err - } - - // Assign customer - company.CustomerID = &customerID - company.SetUpdatedAt(time.Now().Unix()) - - // Update in database - err = r.ormRepo.Update(ctx, company) - if err != nil { - r.logger.Error("Failed to assign customer to company", map[string]interface{}{ - "error": err.Error(), - "company_id": id, - "customer_id": customerID, - }) - return err - } - - r.logger.Info("Customer assigned to company successfully", map[string]interface{}{ - "company_id": id, - "customer_id": customerID, - }) - - return nil -} - -// UnassignCustomer unassigns a customer from a company -func (r *companyRepository) UnassignCustomer(ctx context.Context, id string) error { - // Get company first - company, err := r.GetByID(ctx, id) - if err != nil { - return err - } - - // Unassign customer - company.CustomerID = nil - company.SetUpdatedAt(time.Now().Unix()) - - // Update in database - err = r.ormRepo.Update(ctx, company) - if err != nil { - r.logger.Error("Failed to unassign customer from company", map[string]interface{}{ - "error": err.Error(), - "company_id": id, - }) - return err - } - - r.logger.Info("Customer unassigned from company successfully", map[string]interface{}{ - "company_id": id, - }) - - return nil -} - // UpdateTags updates company tags func (r *companyRepository) UpdateTags(ctx context.Context, id string, tags *CompanyTags) error { // Get company first diff --git a/internal/company/service.go b/internal/company/service.go index 95f46fb..9b55dac 100644 --- a/internal/company/service.go +++ b/internal/company/service.go @@ -25,11 +25,6 @@ type Service interface { UpdateCompanyStatus(ctx context.Context, id string, form *UpdateCompanyStatusForm, updatedBy *string) error UpdateCompanyVerification(ctx context.Context, id string, form *UpdateCompanyVerificationForm, updatedBy *string) error - // Customer assignment - AssignCustomer(ctx context.Context, id string, form *AssignCustomerForm, assignedBy *string) error - UnassignCustomer(ctx context.Context, id string, unassignedBy *string) error - GetCompanyByCustomerID(ctx context.Context, customerID string) (*Company, error) - // Tag management UpdateCompanyTags(ctx context.Context, id string, form *UpdateCompanyTagsForm, updatedBy *string) error AddTags(ctx context.Context, id string, form *AddTagsForm, updatedBy *string) error @@ -101,7 +96,6 @@ func (s *companyService) CreateCompany(ctx context.Context, form *CreateCompanyF Phone: form.Phone, Email: form.Email, Tags: s.convertCompanyTagsForm(form.Tags), - CustomerID: form.CustomerID, IsVerified: false, IsCompliant: false, Language: s.getDefaultLanguage(form.Language), @@ -238,10 +232,6 @@ func (s *companyService) UpdateCompany(ctx context.Context, id string, form *Upd company.Tags = s.convertCompanyTagsForm(form.Tags) } - if form.CustomerID != nil { - company.CustomerID = form.CustomerID - } - if form.Language != nil { company.Language = *form.Language } @@ -479,66 +469,6 @@ func (s *companyService) UpdateCompanyVerification(ctx context.Context, id strin return nil } -// AssignCustomer assigns a customer to a company -func (s *companyService) AssignCustomer(ctx context.Context, id string, form *AssignCustomerForm, assignedBy *string) error { - // Get company to check if exists - _, err := s.repository.GetByID(ctx, id) - if err != nil { - return errors.New("company not found") - } - - // Check if customer is already assigned to another company - existingCompany, _ := s.repository.GetByCustomerID(ctx, form.CustomerID) - if existingCompany != nil { - return errors.New("customer is already assigned to another company") - } - - // Assign customer - err = s.repository.AssignCustomer(ctx, id, form.CustomerID) - if err != nil { - s.logger.Error("Failed to assign customer to company", map[string]interface{}{ - "error": err.Error(), - "company_id": id, - "customer_id": form.CustomerID, - }) - return errors.New("failed to assign customer to company") - } - - s.logger.Info("Customer assigned to company successfully", map[string]interface{}{ - "company_id": id, - "customer_id": form.CustomerID, - "assigned_by": assignedBy, - }) - - return nil -} - -// UnassignCustomer unassigns a customer from a company -func (s *companyService) UnassignCustomer(ctx context.Context, id string, unassignedBy *string) error { - // Get company to check if exists - _, err := s.repository.GetByID(ctx, id) - if err != nil { - return errors.New("company not found") - } - - // Unassign customer - err = s.repository.UnassignCustomer(ctx, id) - if err != nil { - s.logger.Error("Failed to unassign customer from company", map[string]interface{}{ - "error": err.Error(), - "company_id": id, - }) - return errors.New("failed to unassign customer from company") - } - - s.logger.Info("Customer unassigned from company successfully", map[string]interface{}{ - "company_id": id, - "unassigned_by": unassignedBy, - }) - - return nil -} - // GetCompanyByCustomerID retrieves a company by customer ID func (s *companyService) GetCompanyByCustomerID(ctx context.Context, customerID string) (*Company, error) { company, err := s.repository.GetByCustomerID(ctx, customerID) diff --git a/internal/customer/entity.go b/internal/customer/entity.go index 755c687..ce865eb 100644 --- a/internal/customer/entity.go +++ b/internal/customer/entity.go @@ -24,11 +24,11 @@ const ( ) // Customer represents a customer in the tender management system +// A customer can have an associated company for login purposes type Customer struct { mongo.Model - Type CustomerType `bson:"type" json:"type"` - Status CustomerStatus `bson:"status" json:"status"` - CompanyID *string `bson:"company_id,omitempty" json:"company_id,omitempty"` + Type CustomerType `bson:"type" json:"type"` + Status CustomerStatus `bson:"status" json:"status"` // Individual customer fields FirstName *string `bson:"first_name,omitempty" json:"first_name,omitempty"` @@ -40,8 +40,8 @@ type Customer struct { Phone *string `bson:"phone,omitempty" json:"phone,omitempty"` Mobile *string `bson:"mobile,omitempty" json:"mobile,omitempty"` - // Company customer fields - CompanyName *string `bson:"company_name,omitempty" json:"company_name,omitempty"` + // Company customer fields (for when customer represents a company) + CompanyID *string `bson:"company_id,omitempty" json:"company_id,omitempty"` RegistrationNumber *string `bson:"registration_number,omitempty" json:"registration_number,omitempty"` TaxID *string `bson:"tax_id,omitempty" json:"tax_id,omitempty"` Industry *string `bson:"industry,omitempty" json:"industry,omitempty"` @@ -63,7 +63,7 @@ type Customer struct { IsCompliant bool `bson:"is_compliant" json:"is_compliant"` ComplianceNotes *string `bson:"compliance_notes,omitempty" json:"compliance_notes,omitempty"` - // Preferences and settings + // Settings Language string `bson:"language" json:"language"` // Default: "en" Currency string `bson:"currency" json:"currency"` // Default: "USD" Timezone string `bson:"timezone" json:"timezone"` // Default: "UTC" @@ -131,14 +131,13 @@ type CustomerResponse struct { ID string `json:"id"` Type string `json:"type"` Status string `json:"status"` - CompanyID *string `json:"company_id,omitempty"` FirstName *string `json:"first_name,omitempty"` LastName *string `json:"last_name,omitempty"` FullName *string `json:"full_name,omitempty"` Email string `json:"email"` Phone *string `json:"phone,omitempty"` Mobile *string `json:"mobile,omitempty"` - CompanyName *string `json:"company_name,omitempty"` + CompanyID *string `json:"company_id,omitempty"` RegistrationNumber *string `json:"registration_number,omitempty"` TaxID *string `json:"tax_id,omitempty"` Industry *string `json:"industry,omitempty"` @@ -167,7 +166,6 @@ func (c *Customer) ToResponse() *CustomerResponse { ID: c.ID, Type: string(c.Type), Status: string(c.Status), - CompanyID: c.CompanyID, FirstName: c.FirstName, LastName: c.LastName, FullName: c.FullName, @@ -175,7 +173,7 @@ func (c *Customer) ToResponse() *CustomerResponse { Email: c.Email, Phone: c.Phone, Mobile: c.Mobile, - CompanyName: c.CompanyName, + CompanyID: c.CompanyID, RegistrationNumber: c.RegistrationNumber, TaxID: c.TaxID, Industry: c.Industry, diff --git a/internal/customer/form.go b/internal/customer/form.go index c0e9bd1..f823e37 100644 --- a/internal/customer/form.go +++ b/internal/customer/form.go @@ -145,27 +145,27 @@ type CustomerListResponse struct { // Mobile-specific forms (simplified for mobile app) type CreateCustomerMobileForm struct { - Type string `json:"type" valid:"required,in(individual|company)"` - FirstName *string `json:"first_name,omitempty" valid:"optional,length(2|50)"` - LastName *string `json:"last_name,omitempty" valid:"optional,length(2|50)"` - FullName *string `json:"full_name,omitempty" valid:"optional,length(2|100)"` - Username string `json:"username" valid:"required,alphanum,length(3|30)"` - Email string `json:"email" valid:"required,email"` - Password string `json:"password" valid:"required,length(8|128)"` - Phone *string `json:"phone,omitempty" valid:"optional,length(10|20)"` - Mobile *string `json:"mobile,omitempty" valid:"optional,length(10|20)"` - CompanyName *string `json:"company_name,omitempty" valid:"optional,length(2|200)"` - Industry *string `json:"industry,omitempty" valid:"optional,length(2|100)"` + Type string `json:"type" valid:"required,in(individual|company)"` + FirstName *string `json:"first_name,omitempty" valid:"optional,length(2|50)"` + LastName *string `json:"last_name,omitempty" valid:"optional,length(2|50)"` + FullName *string `json:"full_name,omitempty" valid:"optional,length(2|100)"` + Username string `json:"username" valid:"required,alphanum,length(3|30)"` + Email string `json:"email" valid:"required,email"` + Password string `json:"password" valid:"required,length(8|128)"` + Phone *string `json:"phone,omitempty" valid:"optional,length(10|20)"` + Mobile *string `json:"mobile,omitempty" valid:"optional,length(10|20)"` + CompanyID *string `json:"company_id,omitempty" valid:"optional,uuid"` + Industry *string `json:"industry,omitempty" valid:"optional,length(2|100)"` } type UpdateCustomerMobileForm struct { - FirstName *string `json:"first_name,omitempty" valid:"optional,length(2|50)"` - LastName *string `json:"last_name,omitempty" valid:"optional,length(2|50)"` - FullName *string `json:"full_name,omitempty" valid:"optional,length(2|100)"` - Phone *string `json:"phone,omitempty" valid:"optional,length(10|20)"` - Mobile *string `json:"mobile,omitempty" valid:"optional,length(10|20)"` - CompanyName *string `json:"company_name,omitempty" valid:"optional,length(2|200)"` - Industry *string `json:"industry,omitempty" valid:"optional,length(2|100)"` + FirstName *string `json:"first_name,omitempty" valid:"optional,length(2|50)"` + LastName *string `json:"last_name,omitempty" valid:"optional,length(2|50)"` + FullName *string `json:"full_name,omitempty" valid:"optional,length(2|100)"` + Phone *string `json:"phone,omitempty" valid:"optional,length(10|20)"` + Mobile *string `json:"mobile,omitempty" valid:"optional,length(10|20)"` + CompanyID *string `json:"company_id,omitempty" valid:"optional,uuid"` + Industry *string `json:"industry,omitempty" valid:"optional,length(2|100)"` } // Customer Authentication DTOs diff --git a/internal/customer/handler.go b/internal/customer/handler.go index 6542f68..442d676 100644 --- a/internal/customer/handler.go +++ b/internal/customer/handler.go @@ -672,7 +672,7 @@ func (h *Handler) RefreshToken(c echo.Context) error { // GetProfile retrieves customer profile information (Mobile) // @Summary Get customer profile // @Description Retrieve current customer profile information -// @Tags Customers-Mobile +// @Tags Customers-Authorization // @Accept json // @Produce json // @Security BearerAuth @@ -702,7 +702,7 @@ func (h *Handler) GetProfile(c echo.Context) error { // Logout handles customer logout (Mobile) // @Summary Customer logout // @Description Logout customer and invalidate access token -// @Tags Customers-Mobile +// @Tags Customers-Authorization // @Accept json // @Produce json // @Security BearerAuth diff --git a/internal/customer/service.go b/internal/customer/service.go index f7491ad..e7cd865 100644 --- a/internal/customer/service.go +++ b/internal/customer/service.go @@ -123,7 +123,6 @@ func (s *customerService) CreateCustomer(ctx context.Context, form *CreateCustom Password: string(hashedPassword), Phone: form.Phone, Mobile: form.Mobile, - CompanyName: form.CompanyName, RegistrationNumber: form.RegistrationNumber, TaxID: form.TaxID, Industry: form.Industry, @@ -211,15 +210,6 @@ func (s *customerService) UpdateCustomer(ctx context.Context, id string, form *U customer.Username = *form.Username } - // Check if company name already exists (if changing company name) - if form.CompanyName != nil && *form.CompanyName != *customer.CompanyName { - existingCustomer, _ := s.repository.GetByCompanyName(ctx, *form.CompanyName) - if existingCustomer != nil { - return nil, errors.New("company with this name already exists") - } - customer.CompanyName = form.CompanyName - } - // Check if registration number already exists (if changing) if form.RegistrationNumber != nil && *form.RegistrationNumber != *customer.RegistrationNumber { existingCustomer, _ := s.repository.GetByRegistrationNumber(ctx, *form.RegistrationNumber) @@ -576,7 +566,7 @@ func (s *customerService) CreateCustomerMobile(ctx context.Context, form *Create Password: "", // Will be set after hashing Phone: form.Phone, Mobile: form.Mobile, - CompanyName: form.CompanyName, + CompanyID: form.CompanyID, Industry: form.Industry, IsVerified: false, IsCompliant: false, @@ -647,8 +637,8 @@ func (s *customerService) UpdateCustomerMobile(ctx context.Context, id string, f customer.Mobile = form.Mobile } - if form.CompanyName != nil { - customer.CompanyName = form.CompanyName + if form.CompanyID != nil { + customer.CompanyID = form.CompanyID } if form.Industry != nil {