diff --git a/cmd/web/docs/docs.go b/cmd/web/docs/docs.go index a86c012..73b8867 100644 --- a/cmd/web/docs/docs.go +++ b/cmd/web/docs/docs.go @@ -1874,36 +1874,6 @@ const docTemplate = `{ "name": "company_id", "in": "query" }, - { - "type": "string", - "description": "Filter by industry", - "name": "industry", - "in": "query" - }, - { - "type": "boolean", - "description": "Filter by verification status", - "name": "is_verified", - "in": "query" - }, - { - "type": "boolean", - "description": "Filter by compliance status", - "name": "is_compliant", - "in": "query" - }, - { - "type": "string", - "description": "Filter by preferred language", - "name": "language", - "in": "query" - }, - { - "type": "string", - "description": "Filter by preferred currency", - "name": "currency", - "in": "query" - }, { "maximum": 100, "minimum": 1, @@ -2060,261 +2030,6 @@ const docTemplate = `{ } } }, - "/admin/v1/customers/company/{companyId}": { - "get": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Retrieve all customers associated with a specific company", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Admin-Customers" - ], - "summary": "Get customers by company ID", - "parameters": [ - { - "type": "string", - "description": "Company ID", - "name": "companyId", - "in": "path", - "required": true - }, - { - "type": "integer", - "description": "Number of customers to return (default: 10, max: 100)", - "name": "limit", - "in": "query" - }, - { - "type": "integer", - "description": "Number of customers to skip (default: 0)", - "name": "offset", - "in": "query" - } - ], - "responses": { - "200": { - "description": "Customers retrieved successfully", - "schema": { - "allOf": [ - { - "$ref": "#/definitions/response.APIResponse" - }, - { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/definitions/customer.CustomerResponse" - } - } - } - } - ] - } - }, - "400": { - "description": "Bad request - Invalid company ID", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - } - } - } - }, - "/admin/v1/customers/status/{status}": { - "get": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Retrieve customers filtered by their account status (active, inactive, suspended, or pending). Useful for administrative monitoring and management.", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Admin-Customers" - ], - "summary": "Get customers by status", - "parameters": [ - { - "enum": [ - "active", - "inactive", - "suspended", - "pending" - ], - "type": "string", - "description": "Customer status", - "name": "status", - "in": "path", - "required": true - }, - { - "maximum": 100, - "minimum": 1, - "type": "integer", - "default": 20, - "description": "Number of customers per page (1-100)", - "name": "limit", - "in": "query" - }, - { - "minimum": 0, - "type": "integer", - "default": 0, - "description": "Number of customers to skip for pagination", - "name": "offset", - "in": "query" - } - ], - "responses": { - "200": { - "description": "Customers retrieved successfully", - "schema": { - "allOf": [ - { - "$ref": "#/definitions/response.APIResponse" - }, - { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/definitions/customer.CustomerResponse" - } - }, - "meta": { - "$ref": "#/definitions/response.Meta" - } - } - } - ] - } - }, - "400": { - "description": "Bad request - Invalid customer status", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - } - } - } - }, - "/admin/v1/customers/type/{type}": { - "get": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Retrieve customers filtered by their type (individual, company, or government). Useful for administrative reporting and management.", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Admin-Customers" - ], - "summary": "Get customers by type", - "parameters": [ - { - "enum": [ - "individual", - "company", - "government" - ], - "type": "string", - "description": "Customer type", - "name": "type", - "in": "path", - "required": true - }, - { - "maximum": 100, - "minimum": 1, - "type": "integer", - "default": 20, - "description": "Number of customers per page (1-100)", - "name": "limit", - "in": "query" - }, - { - "minimum": 0, - "type": "integer", - "default": 0, - "description": "Number of customers to skip for pagination", - "name": "offset", - "in": "query" - } - ], - "responses": { - "200": { - "description": "Customers retrieved successfully", - "schema": { - "allOf": [ - { - "$ref": "#/definitions/response.APIResponse" - }, - { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/definitions/customer.CustomerResponse" - } - }, - "meta": { - "$ref": "#/definitions/response.Meta" - } - } - } - ] - } - }, - "400": { - "description": "Bad request - Invalid customer type", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - } - } - } - }, "/admin/v1/customers/{id}": { "get": { "security": [ @@ -2521,201 +2236,6 @@ const docTemplate = `{ } } }, - "/admin/v1/customers/{id}/activate": { - "post": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Activate a suspended customer account", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Admin-Customers" - ], - "summary": "Activate customer", - "parameters": [ - { - "type": "string", - "description": "Customer ID", - "name": "id", - "in": "path", - "required": true - } - ], - "responses": { - "200": { - "description": "Customer activated successfully", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "400": { - "description": "Bad request - Invalid customer ID", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "404": { - "description": "Not found - Customer not found", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - } - } - } - }, - "/admin/v1/customers/{id}/companies/assign": { - "post": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Assign one or more companies to a specific customer.", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Admin-Customers" - ], - "summary": "Assign companies to a customer", - "parameters": [ - { - "type": "string", - "description": "Customer ID", - "name": "id", - "in": "path", - "required": true - }, - { - "description": "List of company IDs to assign", - "name": "companies", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/customer.AssignCompaniesForm" - } - } - ], - "responses": { - "200": { - "description": "Companies assigned successfully", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "400": { - "description": "Bad request - Invalid input data", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "404": { - "description": "Not found - Customer not found", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "422": { - "description": "Validation error - Invalid request data", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - } - } - } - }, - "/admin/v1/customers/{id}/companies/remove": { - "post": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Remove one or more companies from a specific customer.", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Admin-Customers" - ], - "summary": "Remove companies from a customer", - "parameters": [ - { - "type": "string", - "description": "Customer ID", - "name": "id", - "in": "path", - "required": true - }, - { - "description": "List of company IDs to remove", - "name": "companies", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/customer.RemoveCompaniesForm" - } - } - ], - "responses": { - "200": { - "description": "Companies removed successfully", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "400": { - "description": "Bad request - Invalid input data", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "404": { - "description": "Not found - Customer not found", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "422": { - "description": "Validation error - Invalid request data", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - } - } - } - }, "/admin/v1/customers/{id}/status": { "patch": { "security": [ @@ -2748,7 +2268,7 @@ const docTemplate = `{ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/customer.UpdateCustomerStatusForm" + "$ref": "#/definitions/customer.UpdateStatusForm" } } ], @@ -2786,201 +2306,6 @@ const docTemplate = `{ } } }, - "/admin/v1/customers/{id}/suspend": { - "post": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Suspend a customer account with optional reason", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Admin-Customers" - ], - "summary": "Suspend customer", - "parameters": [ - { - "type": "string", - "description": "Customer ID", - "name": "id", - "in": "path", - "required": true - }, - { - "description": "Suspension information", - "name": "suspension", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/customer.SuspendCustomerForm" - } - } - ], - "responses": { - "200": { - "description": "Customer suspended successfully", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "400": { - "description": "Bad request - Invalid input data", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "404": { - "description": "Not found - Customer not found", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "422": { - "description": "Validation error - Invalid request data", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - } - } - } - }, - "/admin/v1/customers/{id}/verification": { - "patch": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Update customer verification and compliance status", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Admin-Customers" - ], - "summary": "Update customer verification status", - "parameters": [ - { - "type": "string", - "description": "Customer ID", - "name": "id", - "in": "path", - "required": true - }, - { - "description": "Verification status update", - "name": "verification", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/customer.UpdateCustomerVerificationForm" - } - } - ], - "responses": { - "200": { - "description": "Customer verification updated successfully", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "400": { - "description": "Bad request - Invalid input data", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "404": { - "description": "Not found - Customer not found", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "422": { - "description": "Validation error - Invalid request data", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - } - } - } - }, - "/admin/v1/customers/{id}/verify": { - "post": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Mark a customer as verified", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Admin-Customers" - ], - "summary": "Verify customer", - "parameters": [ - { - "type": "string", - "description": "Customer ID", - "name": "id", - "in": "path", - "required": true - } - ], - "responses": { - "200": { - "description": "Customer verified successfully", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "400": { - "description": "Bad request - Invalid customer ID", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "404": { - "description": "Not found - Customer not found", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - } - } - } - }, "/admin/v1/feedback": { "get": { "security": [ @@ -7008,70 +6333,6 @@ const docTemplate = `{ } } }, - "customer.Address": { - "type": "object", - "properties": { - "address_type": { - "description": "billing, shipping, etc.", - "type": "string" - }, - "city": { - "type": "string" - }, - "country": { - "type": "string" - }, - "is_default": { - "type": "boolean" - }, - "postal_code": { - "type": "string" - }, - "state": { - "type": "string" - }, - "street": { - "type": "string" - } - } - }, - "customer.AddressForm": { - "type": "object", - "properties": { - "address_type": { - "type": "string" - }, - "city": { - "type": "string" - }, - "country": { - "type": "string" - }, - "is_default": { - "type": "boolean" - }, - "postal_code": { - "type": "string" - }, - "state": { - "type": "string" - }, - "street": { - "type": "string" - } - } - }, - "customer.AssignCompaniesForm": { - "type": "object", - "properties": { - "company_ids": { - "type": "array", - "items": { - "type": "string" - } - } - } - }, "customer.AuthResponse": { "type": "object", "properties": { @@ -7100,150 +6361,38 @@ const docTemplate = `{ } } }, - "customer.ContactPerson": { - "type": "object", - "properties": { - "email": { - "type": "string" - }, - "first_name": { - "type": "string" - }, - "full_name": { - "type": "string" - }, - "is_primary": { - "type": "boolean" - }, - "last_name": { - "type": "string" - }, - "mobile": { - "type": "string" - }, - "phone": { - "type": "string" - }, - "position": { - "type": "string" - } - } - }, - "customer.ContactPersonForm": { - "type": "object", - "properties": { - "email": { - "type": "string" - }, - "first_name": { - "type": "string" - }, - "full_name": { - "type": "string" - }, - "is_primary": { - "type": "boolean" - }, - "last_name": { - "type": "string" - }, - "mobile": { - "type": "string" - }, - "phone": { - "type": "string" - }, - "position": { - "type": "string" - } - } - }, "customer.CreateCustomerForm": { "type": "object", "properties": { - "address": { - "description": "Address information", - "allOf": [ - { - "$ref": "#/definitions/customer.AddressForm" - } - ] - }, - "annual_revenue": { - "type": "number" - }, - "business_type": { - "description": "Business information", - "type": "string" - }, "company_ids": { - "description": "Company assignments", "type": "array", "items": { "type": "string" } }, - "contact_person": { - "description": "Contact person (for company customers)", - "allOf": [ - { - "$ref": "#/definitions/customer.ContactPersonForm" - } - ] - }, - "currency": { - "type": "string" - }, "email": { - "type": "string" - }, - "employee_count": { - "type": "integer" - }, - "first_name": { - "description": "Individual customer fields", - "type": "string" - }, - "founded_year": { - "type": "integer" + "type": "string", + "example": "user@opplens.com" }, "full_name": { - "type": "string" - }, - "industry": { - "type": "string" - }, - "language": { - "description": "Preferences and settings", - "type": "string" - }, - "last_name": { - "type": "string" - }, - "mobile": { - "type": "string" + "type": "string", + "example": "User" }, "password": { - "type": "string" + "type": "string", + "example": "User!1234" }, "phone": { - "type": "string" - }, - "registration_number": { - "description": "Company customer fields", - "type": "string" - }, - "tax_id": { - "type": "string" - }, - "timezone": { - "type": "string" + "type": "string", + "example": "+1234567890" }, "type": { - "type": "string" + "type": "string", + "example": "individual" }, "username": { - "type": "string" + "type": "string", + "example": "user" } } }, @@ -7256,115 +6405,56 @@ const docTemplate = `{ "$ref": "#/definitions/customer.CustomerResponse" } }, - "limit": { - "type": "integer" - }, - "offset": { - "type": "integer" - }, - "total": { - "type": "integer" - }, - "total_pages": { - "type": "integer" + "meta": { + "$ref": "#/definitions/response.Meta" } } }, "customer.CustomerResponse": { "type": "object", "properties": { - "address": { - "$ref": "#/definitions/customer.Address" - }, - "annual_revenue": { - "type": "number" - }, - "business_type": { - "type": "string" - }, "companies": { - "description": "Company relationships", "type": "array", "items": { "$ref": "#/definitions/customer.CompanySummary" } }, - "compliance_notes": { - "type": "string" + "companyIDs": { + "type": "array", + "items": { + "type": "string" + } }, - "contact_person": { - "$ref": "#/definitions/customer.ContactPerson" - }, - "created_at": { + "createdAt": { "type": "integer" }, - "created_by": { - "type": "string" - }, - "currency": { - "type": "string" - }, "email": { "type": "string" }, - "employee_count": { - "type": "integer" - }, - "first_name": { - "type": "string" - }, - "founded_year": { - "type": "integer" - }, - "full_name": { + "fullName": { "type": "string" }, "id": { "type": "string" }, - "industry": { - "type": "string" + "lastLoginAt": { + "type": "integer" }, - "is_compliant": { - "type": "boolean" - }, - "is_verified": { - "type": "boolean" - }, - "language": { - "type": "string" - }, - "last_name": { - "type": "string" - }, - "mobile": { + "password": { "type": "string" }, "phone": { "type": "string" }, - "registration_number": { - "description": "Company customer fields", - "type": "string" - }, "status": { "type": "string" }, - "tax_id": { - "type": "string" - }, - "timezone": { - "type": "string" - }, "type": { "type": "string" }, - "updated_at": { + "updatedAt": { "type": "integer" }, - "updated_by": { - "type": "string" - }, "username": { "type": "string" } @@ -7375,11 +6465,11 @@ const docTemplate = `{ "properties": { "password": { "type": "string", - "example": "Nima.1998" + "example": "App!1234" }, "username": { "type": "string", - "example": "nakhostin" + "example": "app" } } }, @@ -7391,133 +6481,51 @@ const docTemplate = `{ } } }, - "customer.RemoveCompaniesForm": { - "type": "object", - "properties": { - "company_ids": { - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "customer.SuspendCustomerForm": { - "type": "object", - "properties": { - "reason": { - "type": "string" - } - } - }, "customer.UpdateCustomerForm": { "type": "object", "properties": { - "address": { - "description": "Address information", - "allOf": [ - { - "$ref": "#/definitions/customer.AddressForm" - } - ] - }, - "annual_revenue": { - "type": "number" - }, - "business_type": { - "description": "Business information", - "type": "string" - }, "company_ids": { - "description": "Company assignments", "type": "array", "items": { "type": "string" } }, - "company_name": { - "description": "Company customer fields", - "type": "string" - }, - "contact_person": { - "description": "Contact person (for company customers)", - "allOf": [ - { - "$ref": "#/definitions/customer.ContactPersonForm" - } - ] - }, - "currency": { - "type": "string" - }, "email": { - "type": "string" - }, - "employee_count": { - "type": "integer" - }, - "first_name": { - "description": "Individual customer fields", - "type": "string" - }, - "founded_year": { - "type": "integer" + "type": "string", + "example": "user@opplens.com" }, "full_name": { - "type": "string" + "type": "string", + "example": "User" }, - "industry": { - "type": "string" - }, - "language": { - "description": "Preferences and settings", - "type": "string" - }, - "last_name": { - "type": "string" - }, - "mobile": { - "type": "string" + "password": { + "type": "string", + "example": "User!1234" }, "phone": { - "type": "string" - }, - "registration_number": { - "type": "string" - }, - "tax_id": { - "type": "string" - }, - "timezone": { - "type": "string" + "type": "string", + "example": "+1234567890" }, "type": { - "type": "string" + "type": "string", + "example": "individual" }, "username": { - "type": "string" + "type": "string", + "example": "user" } } }, - "customer.UpdateCustomerStatusForm": { + "customer.UpdateStatusForm": { "type": "object", "properties": { + "reason": { + "type": "string", + "example": "Customer is active" + }, "status": { - "type": "string" - } - } - }, - "customer.UpdateCustomerVerificationForm": { - "type": "object", - "properties": { - "compliance_notes": { - "type": "string" - }, - "is_compliant": { - "type": "boolean" - }, - "is_verified": { - "type": "boolean" + "type": "string", + "example": "active" } } }, diff --git a/cmd/web/docs/swagger.json b/cmd/web/docs/swagger.json index 80975a4..d154e43 100644 --- a/cmd/web/docs/swagger.json +++ b/cmd/web/docs/swagger.json @@ -1868,36 +1868,6 @@ "name": "company_id", "in": "query" }, - { - "type": "string", - "description": "Filter by industry", - "name": "industry", - "in": "query" - }, - { - "type": "boolean", - "description": "Filter by verification status", - "name": "is_verified", - "in": "query" - }, - { - "type": "boolean", - "description": "Filter by compliance status", - "name": "is_compliant", - "in": "query" - }, - { - "type": "string", - "description": "Filter by preferred language", - "name": "language", - "in": "query" - }, - { - "type": "string", - "description": "Filter by preferred currency", - "name": "currency", - "in": "query" - }, { "maximum": 100, "minimum": 1, @@ -2054,261 +2024,6 @@ } } }, - "/admin/v1/customers/company/{companyId}": { - "get": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Retrieve all customers associated with a specific company", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Admin-Customers" - ], - "summary": "Get customers by company ID", - "parameters": [ - { - "type": "string", - "description": "Company ID", - "name": "companyId", - "in": "path", - "required": true - }, - { - "type": "integer", - "description": "Number of customers to return (default: 10, max: 100)", - "name": "limit", - "in": "query" - }, - { - "type": "integer", - "description": "Number of customers to skip (default: 0)", - "name": "offset", - "in": "query" - } - ], - "responses": { - "200": { - "description": "Customers retrieved successfully", - "schema": { - "allOf": [ - { - "$ref": "#/definitions/response.APIResponse" - }, - { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/definitions/customer.CustomerResponse" - } - } - } - } - ] - } - }, - "400": { - "description": "Bad request - Invalid company ID", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - } - } - } - }, - "/admin/v1/customers/status/{status}": { - "get": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Retrieve customers filtered by their account status (active, inactive, suspended, or pending). Useful for administrative monitoring and management.", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Admin-Customers" - ], - "summary": "Get customers by status", - "parameters": [ - { - "enum": [ - "active", - "inactive", - "suspended", - "pending" - ], - "type": "string", - "description": "Customer status", - "name": "status", - "in": "path", - "required": true - }, - { - "maximum": 100, - "minimum": 1, - "type": "integer", - "default": 20, - "description": "Number of customers per page (1-100)", - "name": "limit", - "in": "query" - }, - { - "minimum": 0, - "type": "integer", - "default": 0, - "description": "Number of customers to skip for pagination", - "name": "offset", - "in": "query" - } - ], - "responses": { - "200": { - "description": "Customers retrieved successfully", - "schema": { - "allOf": [ - { - "$ref": "#/definitions/response.APIResponse" - }, - { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/definitions/customer.CustomerResponse" - } - }, - "meta": { - "$ref": "#/definitions/response.Meta" - } - } - } - ] - } - }, - "400": { - "description": "Bad request - Invalid customer status", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - } - } - } - }, - "/admin/v1/customers/type/{type}": { - "get": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Retrieve customers filtered by their type (individual, company, or government). Useful for administrative reporting and management.", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Admin-Customers" - ], - "summary": "Get customers by type", - "parameters": [ - { - "enum": [ - "individual", - "company", - "government" - ], - "type": "string", - "description": "Customer type", - "name": "type", - "in": "path", - "required": true - }, - { - "maximum": 100, - "minimum": 1, - "type": "integer", - "default": 20, - "description": "Number of customers per page (1-100)", - "name": "limit", - "in": "query" - }, - { - "minimum": 0, - "type": "integer", - "default": 0, - "description": "Number of customers to skip for pagination", - "name": "offset", - "in": "query" - } - ], - "responses": { - "200": { - "description": "Customers retrieved successfully", - "schema": { - "allOf": [ - { - "$ref": "#/definitions/response.APIResponse" - }, - { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/definitions/customer.CustomerResponse" - } - }, - "meta": { - "$ref": "#/definitions/response.Meta" - } - } - } - ] - } - }, - "400": { - "description": "Bad request - Invalid customer type", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - } - } - } - }, "/admin/v1/customers/{id}": { "get": { "security": [ @@ -2515,201 +2230,6 @@ } } }, - "/admin/v1/customers/{id}/activate": { - "post": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Activate a suspended customer account", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Admin-Customers" - ], - "summary": "Activate customer", - "parameters": [ - { - "type": "string", - "description": "Customer ID", - "name": "id", - "in": "path", - "required": true - } - ], - "responses": { - "200": { - "description": "Customer activated successfully", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "400": { - "description": "Bad request - Invalid customer ID", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "404": { - "description": "Not found - Customer not found", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - } - } - } - }, - "/admin/v1/customers/{id}/companies/assign": { - "post": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Assign one or more companies to a specific customer.", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Admin-Customers" - ], - "summary": "Assign companies to a customer", - "parameters": [ - { - "type": "string", - "description": "Customer ID", - "name": "id", - "in": "path", - "required": true - }, - { - "description": "List of company IDs to assign", - "name": "companies", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/customer.AssignCompaniesForm" - } - } - ], - "responses": { - "200": { - "description": "Companies assigned successfully", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "400": { - "description": "Bad request - Invalid input data", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "404": { - "description": "Not found - Customer not found", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "422": { - "description": "Validation error - Invalid request data", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - } - } - } - }, - "/admin/v1/customers/{id}/companies/remove": { - "post": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Remove one or more companies from a specific customer.", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Admin-Customers" - ], - "summary": "Remove companies from a customer", - "parameters": [ - { - "type": "string", - "description": "Customer ID", - "name": "id", - "in": "path", - "required": true - }, - { - "description": "List of company IDs to remove", - "name": "companies", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/customer.RemoveCompaniesForm" - } - } - ], - "responses": { - "200": { - "description": "Companies removed successfully", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "400": { - "description": "Bad request - Invalid input data", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "404": { - "description": "Not found - Customer not found", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "422": { - "description": "Validation error - Invalid request data", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - } - } - } - }, "/admin/v1/customers/{id}/status": { "patch": { "security": [ @@ -2742,7 +2262,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/customer.UpdateCustomerStatusForm" + "$ref": "#/definitions/customer.UpdateStatusForm" } } ], @@ -2780,201 +2300,6 @@ } } }, - "/admin/v1/customers/{id}/suspend": { - "post": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Suspend a customer account with optional reason", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Admin-Customers" - ], - "summary": "Suspend customer", - "parameters": [ - { - "type": "string", - "description": "Customer ID", - "name": "id", - "in": "path", - "required": true - }, - { - "description": "Suspension information", - "name": "suspension", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/customer.SuspendCustomerForm" - } - } - ], - "responses": { - "200": { - "description": "Customer suspended successfully", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "400": { - "description": "Bad request - Invalid input data", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "404": { - "description": "Not found - Customer not found", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "422": { - "description": "Validation error - Invalid request data", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - } - } - } - }, - "/admin/v1/customers/{id}/verification": { - "patch": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Update customer verification and compliance status", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Admin-Customers" - ], - "summary": "Update customer verification status", - "parameters": [ - { - "type": "string", - "description": "Customer ID", - "name": "id", - "in": "path", - "required": true - }, - { - "description": "Verification status update", - "name": "verification", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/customer.UpdateCustomerVerificationForm" - } - } - ], - "responses": { - "200": { - "description": "Customer verification updated successfully", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "400": { - "description": "Bad request - Invalid input data", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "404": { - "description": "Not found - Customer not found", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "422": { - "description": "Validation error - Invalid request data", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - } - } - } - }, - "/admin/v1/customers/{id}/verify": { - "post": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Mark a customer as verified", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Admin-Customers" - ], - "summary": "Verify customer", - "parameters": [ - { - "type": "string", - "description": "Customer ID", - "name": "id", - "in": "path", - "required": true - } - ], - "responses": { - "200": { - "description": "Customer verified successfully", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "400": { - "description": "Bad request - Invalid customer ID", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "404": { - "description": "Not found - Customer not found", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - } - } - } - }, "/admin/v1/feedback": { "get": { "security": [ @@ -7002,70 +6327,6 @@ } } }, - "customer.Address": { - "type": "object", - "properties": { - "address_type": { - "description": "billing, shipping, etc.", - "type": "string" - }, - "city": { - "type": "string" - }, - "country": { - "type": "string" - }, - "is_default": { - "type": "boolean" - }, - "postal_code": { - "type": "string" - }, - "state": { - "type": "string" - }, - "street": { - "type": "string" - } - } - }, - "customer.AddressForm": { - "type": "object", - "properties": { - "address_type": { - "type": "string" - }, - "city": { - "type": "string" - }, - "country": { - "type": "string" - }, - "is_default": { - "type": "boolean" - }, - "postal_code": { - "type": "string" - }, - "state": { - "type": "string" - }, - "street": { - "type": "string" - } - } - }, - "customer.AssignCompaniesForm": { - "type": "object", - "properties": { - "company_ids": { - "type": "array", - "items": { - "type": "string" - } - } - } - }, "customer.AuthResponse": { "type": "object", "properties": { @@ -7094,150 +6355,38 @@ } } }, - "customer.ContactPerson": { - "type": "object", - "properties": { - "email": { - "type": "string" - }, - "first_name": { - "type": "string" - }, - "full_name": { - "type": "string" - }, - "is_primary": { - "type": "boolean" - }, - "last_name": { - "type": "string" - }, - "mobile": { - "type": "string" - }, - "phone": { - "type": "string" - }, - "position": { - "type": "string" - } - } - }, - "customer.ContactPersonForm": { - "type": "object", - "properties": { - "email": { - "type": "string" - }, - "first_name": { - "type": "string" - }, - "full_name": { - "type": "string" - }, - "is_primary": { - "type": "boolean" - }, - "last_name": { - "type": "string" - }, - "mobile": { - "type": "string" - }, - "phone": { - "type": "string" - }, - "position": { - "type": "string" - } - } - }, "customer.CreateCustomerForm": { "type": "object", "properties": { - "address": { - "description": "Address information", - "allOf": [ - { - "$ref": "#/definitions/customer.AddressForm" - } - ] - }, - "annual_revenue": { - "type": "number" - }, - "business_type": { - "description": "Business information", - "type": "string" - }, "company_ids": { - "description": "Company assignments", "type": "array", "items": { "type": "string" } }, - "contact_person": { - "description": "Contact person (for company customers)", - "allOf": [ - { - "$ref": "#/definitions/customer.ContactPersonForm" - } - ] - }, - "currency": { - "type": "string" - }, "email": { - "type": "string" - }, - "employee_count": { - "type": "integer" - }, - "first_name": { - "description": "Individual customer fields", - "type": "string" - }, - "founded_year": { - "type": "integer" + "type": "string", + "example": "user@opplens.com" }, "full_name": { - "type": "string" - }, - "industry": { - "type": "string" - }, - "language": { - "description": "Preferences and settings", - "type": "string" - }, - "last_name": { - "type": "string" - }, - "mobile": { - "type": "string" + "type": "string", + "example": "User" }, "password": { - "type": "string" + "type": "string", + "example": "User!1234" }, "phone": { - "type": "string" - }, - "registration_number": { - "description": "Company customer fields", - "type": "string" - }, - "tax_id": { - "type": "string" - }, - "timezone": { - "type": "string" + "type": "string", + "example": "+1234567890" }, "type": { - "type": "string" + "type": "string", + "example": "individual" }, "username": { - "type": "string" + "type": "string", + "example": "user" } } }, @@ -7250,115 +6399,56 @@ "$ref": "#/definitions/customer.CustomerResponse" } }, - "limit": { - "type": "integer" - }, - "offset": { - "type": "integer" - }, - "total": { - "type": "integer" - }, - "total_pages": { - "type": "integer" + "meta": { + "$ref": "#/definitions/response.Meta" } } }, "customer.CustomerResponse": { "type": "object", "properties": { - "address": { - "$ref": "#/definitions/customer.Address" - }, - "annual_revenue": { - "type": "number" - }, - "business_type": { - "type": "string" - }, "companies": { - "description": "Company relationships", "type": "array", "items": { "$ref": "#/definitions/customer.CompanySummary" } }, - "compliance_notes": { - "type": "string" + "companyIDs": { + "type": "array", + "items": { + "type": "string" + } }, - "contact_person": { - "$ref": "#/definitions/customer.ContactPerson" - }, - "created_at": { + "createdAt": { "type": "integer" }, - "created_by": { - "type": "string" - }, - "currency": { - "type": "string" - }, "email": { "type": "string" }, - "employee_count": { - "type": "integer" - }, - "first_name": { - "type": "string" - }, - "founded_year": { - "type": "integer" - }, - "full_name": { + "fullName": { "type": "string" }, "id": { "type": "string" }, - "industry": { - "type": "string" + "lastLoginAt": { + "type": "integer" }, - "is_compliant": { - "type": "boolean" - }, - "is_verified": { - "type": "boolean" - }, - "language": { - "type": "string" - }, - "last_name": { - "type": "string" - }, - "mobile": { + "password": { "type": "string" }, "phone": { "type": "string" }, - "registration_number": { - "description": "Company customer fields", - "type": "string" - }, "status": { "type": "string" }, - "tax_id": { - "type": "string" - }, - "timezone": { - "type": "string" - }, "type": { "type": "string" }, - "updated_at": { + "updatedAt": { "type": "integer" }, - "updated_by": { - "type": "string" - }, "username": { "type": "string" } @@ -7369,11 +6459,11 @@ "properties": { "password": { "type": "string", - "example": "Nima.1998" + "example": "App!1234" }, "username": { "type": "string", - "example": "nakhostin" + "example": "app" } } }, @@ -7385,133 +6475,51 @@ } } }, - "customer.RemoveCompaniesForm": { - "type": "object", - "properties": { - "company_ids": { - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "customer.SuspendCustomerForm": { - "type": "object", - "properties": { - "reason": { - "type": "string" - } - } - }, "customer.UpdateCustomerForm": { "type": "object", "properties": { - "address": { - "description": "Address information", - "allOf": [ - { - "$ref": "#/definitions/customer.AddressForm" - } - ] - }, - "annual_revenue": { - "type": "number" - }, - "business_type": { - "description": "Business information", - "type": "string" - }, "company_ids": { - "description": "Company assignments", "type": "array", "items": { "type": "string" } }, - "company_name": { - "description": "Company customer fields", - "type": "string" - }, - "contact_person": { - "description": "Contact person (for company customers)", - "allOf": [ - { - "$ref": "#/definitions/customer.ContactPersonForm" - } - ] - }, - "currency": { - "type": "string" - }, "email": { - "type": "string" - }, - "employee_count": { - "type": "integer" - }, - "first_name": { - "description": "Individual customer fields", - "type": "string" - }, - "founded_year": { - "type": "integer" + "type": "string", + "example": "user@opplens.com" }, "full_name": { - "type": "string" + "type": "string", + "example": "User" }, - "industry": { - "type": "string" - }, - "language": { - "description": "Preferences and settings", - "type": "string" - }, - "last_name": { - "type": "string" - }, - "mobile": { - "type": "string" + "password": { + "type": "string", + "example": "User!1234" }, "phone": { - "type": "string" - }, - "registration_number": { - "type": "string" - }, - "tax_id": { - "type": "string" - }, - "timezone": { - "type": "string" + "type": "string", + "example": "+1234567890" }, "type": { - "type": "string" + "type": "string", + "example": "individual" }, "username": { - "type": "string" + "type": "string", + "example": "user" } } }, - "customer.UpdateCustomerStatusForm": { + "customer.UpdateStatusForm": { "type": "object", "properties": { + "reason": { + "type": "string", + "example": "Customer is active" + }, "status": { - "type": "string" - } - } - }, - "customer.UpdateCustomerVerificationForm": { - "type": "object", - "properties": { - "compliance_notes": { - "type": "string" - }, - "is_compliant": { - "type": "boolean" - }, - "is_verified": { - "type": "boolean" + "type": "string", + "example": "active" } } }, diff --git a/cmd/web/docs/swagger.yaml b/cmd/web/docs/swagger.yaml index a863855..889ca4d 100644 --- a/cmd/web/docs/swagger.yaml +++ b/cmd/web/docs/swagger.yaml @@ -435,48 +435,6 @@ definitions: is_verified: type: boolean type: object - customer.Address: - properties: - address_type: - description: billing, shipping, etc. - type: string - city: - type: string - country: - type: string - is_default: - type: boolean - postal_code: - type: string - state: - type: string - street: - type: string - type: object - customer.AddressForm: - properties: - address_type: - type: string - city: - type: string - country: - type: string - is_default: - type: boolean - postal_code: - type: string - state: - type: string - street: - type: string - type: object - customer.AssignCompaniesForm: - properties: - company_ids: - items: - type: string - type: array - type: object customer.AuthResponse: properties: access_token: @@ -495,100 +453,29 @@ definitions: name: type: string type: object - customer.ContactPerson: - properties: - email: - type: string - first_name: - type: string - full_name: - type: string - is_primary: - type: boolean - last_name: - type: string - mobile: - type: string - phone: - type: string - position: - type: string - type: object - customer.ContactPersonForm: - properties: - email: - type: string - first_name: - type: string - full_name: - type: string - is_primary: - type: boolean - last_name: - type: string - mobile: - type: string - phone: - type: string - position: - type: string - type: object customer.CreateCustomerForm: properties: - address: - allOf: - - $ref: '#/definitions/customer.AddressForm' - description: Address information - annual_revenue: - type: number - business_type: - description: Business information - type: string company_ids: - description: Company assignments items: type: string type: array - contact_person: - allOf: - - $ref: '#/definitions/customer.ContactPersonForm' - description: Contact person (for company customers) - currency: - type: string email: + example: user@opplens.com type: string - employee_count: - type: integer - first_name: - description: Individual customer fields - type: string - founded_year: - type: integer full_name: - type: string - industry: - type: string - language: - description: Preferences and settings - type: string - last_name: - type: string - mobile: + example: User type: string password: + example: User!1234 type: string phone: - type: string - registration_number: - description: Company customer fields - type: string - tax_id: - type: string - timezone: + example: "+1234567890" type: string type: + example: individual type: string username: + example: user type: string type: object customer.CustomerListResponse: @@ -597,89 +484,49 @@ definitions: items: $ref: '#/definitions/customer.CustomerResponse' type: array - limit: - type: integer - offset: - type: integer - total: - type: integer - total_pages: - type: integer + meta: + $ref: '#/definitions/response.Meta' type: object customer.CustomerResponse: properties: - address: - $ref: '#/definitions/customer.Address' - annual_revenue: - type: number - business_type: - type: string companies: - description: Company relationships items: $ref: '#/definitions/customer.CompanySummary' type: array - compliance_notes: - type: string - contact_person: - $ref: '#/definitions/customer.ContactPerson' - created_at: + companyIDs: + items: + type: string + type: array + createdAt: type: integer - created_by: - type: string - currency: - type: string email: type: string - employee_count: - type: integer - first_name: - type: string - founded_year: - type: integer - full_name: + fullName: type: string id: type: string - industry: - type: string - is_compliant: - type: boolean - is_verified: - type: boolean - language: - type: string - last_name: - type: string - mobile: + lastLoginAt: + type: integer + password: type: string phone: type: string - registration_number: - description: Company customer fields - type: string status: type: string - tax_id: - type: string - timezone: - type: string type: type: string - updated_at: + updatedAt: type: integer - updated_by: - type: string username: type: string type: object customer.LoginForm: properties: password: - example: Nima.1998 + example: App!1234 type: string username: - example: nakhostin + example: app type: string type: object customer.RefreshTokenForm: @@ -687,90 +534,40 @@ definitions: refresh_token: type: string type: object - customer.RemoveCompaniesForm: - properties: - company_ids: - items: - type: string - type: array - type: object - customer.SuspendCustomerForm: - properties: - reason: - type: string - type: object customer.UpdateCustomerForm: properties: - address: - allOf: - - $ref: '#/definitions/customer.AddressForm' - description: Address information - annual_revenue: - type: number - business_type: - description: Business information - type: string company_ids: - description: Company assignments items: type: string type: array - company_name: - description: Company customer fields - type: string - contact_person: - allOf: - - $ref: '#/definitions/customer.ContactPersonForm' - description: Contact person (for company customers) - currency: - type: string email: + example: user@opplens.com type: string - employee_count: - type: integer - first_name: - description: Individual customer fields - type: string - founded_year: - type: integer full_name: + example: User type: string - industry: - type: string - language: - description: Preferences and settings - type: string - last_name: - type: string - mobile: + password: + example: User!1234 type: string phone: - type: string - registration_number: - type: string - tax_id: - type: string - timezone: + example: "+1234567890" type: string type: + example: individual type: string username: + example: user type: string type: object - customer.UpdateCustomerStatusForm: + customer.UpdateStatusForm: properties: + reason: + example: Customer is active + type: string status: + example: active type: string type: object - customer.UpdateCustomerVerificationForm: - properties: - compliance_notes: - type: string - is_compliant: - type: boolean - is_verified: - type: boolean - type: object feedback.CompanyFeedbackStatsResponse: properties: company_id: @@ -2477,26 +2274,6 @@ paths: in: query name: company_id type: string - - description: Filter by industry - in: query - name: industry - type: string - - description: Filter by verification status - in: query - name: is_verified - type: boolean - - description: Filter by compliance status - in: query - name: is_compliant - type: boolean - - description: Filter by preferred language - in: query - name: language - type: string - - description: Filter by preferred currency - in: query - name: currency - type: string - default: 20 description: Number of customers per page (1-100) in: query @@ -2733,131 +2510,6 @@ paths: summary: Update customer information tags: - Admin-Customers - /admin/v1/customers/{id}/activate: - post: - consumes: - - application/json - description: Activate a suspended customer account - parameters: - - description: Customer ID - in: path - name: id - required: true - type: string - produces: - - application/json - responses: - "200": - description: Customer activated successfully - schema: - $ref: '#/definitions/response.APIResponse' - "400": - description: Bad request - Invalid customer ID - schema: - $ref: '#/definitions/response.APIResponse' - "404": - description: Not found - Customer not found - schema: - $ref: '#/definitions/response.APIResponse' - "500": - description: Internal server error - schema: - $ref: '#/definitions/response.APIResponse' - security: - - BearerAuth: [] - summary: Activate customer - tags: - - Admin-Customers - /admin/v1/customers/{id}/companies/assign: - post: - consumes: - - application/json - description: Assign one or more companies to a specific customer. - parameters: - - description: Customer ID - in: path - name: id - required: true - type: string - - description: List of company IDs to assign - in: body - name: companies - required: true - schema: - $ref: '#/definitions/customer.AssignCompaniesForm' - produces: - - application/json - responses: - "200": - description: Companies assigned successfully - schema: - $ref: '#/definitions/response.APIResponse' - "400": - description: Bad request - Invalid input data - schema: - $ref: '#/definitions/response.APIResponse' - "404": - description: Not found - Customer not found - schema: - $ref: '#/definitions/response.APIResponse' - "422": - description: Validation error - Invalid request data - schema: - $ref: '#/definitions/response.APIResponse' - "500": - description: Internal server error - schema: - $ref: '#/definitions/response.APIResponse' - security: - - BearerAuth: [] - summary: Assign companies to a customer - tags: - - Admin-Customers - /admin/v1/customers/{id}/companies/remove: - post: - consumes: - - application/json - description: Remove one or more companies from a specific customer. - parameters: - - description: Customer ID - in: path - name: id - required: true - type: string - - description: List of company IDs to remove - in: body - name: companies - required: true - schema: - $ref: '#/definitions/customer.RemoveCompaniesForm' - produces: - - application/json - responses: - "200": - description: Companies removed successfully - schema: - $ref: '#/definitions/response.APIResponse' - "400": - description: Bad request - Invalid input data - schema: - $ref: '#/definitions/response.APIResponse' - "404": - description: Not found - Customer not found - schema: - $ref: '#/definitions/response.APIResponse' - "422": - description: Validation error - Invalid request data - schema: - $ref: '#/definitions/response.APIResponse' - "500": - description: Internal server error - schema: - $ref: '#/definitions/response.APIResponse' - security: - - BearerAuth: [] - summary: Remove companies from a customer - tags: - - Admin-Customers /admin/v1/customers/{id}/status: patch: consumes: @@ -2874,7 +2526,7 @@ paths: name: status required: true schema: - $ref: '#/definitions/customer.UpdateCustomerStatusForm' + $ref: '#/definitions/customer.UpdateStatusForm' produces: - application/json responses: @@ -2903,294 +2555,6 @@ paths: summary: Update customer status tags: - Admin-Customers - /admin/v1/customers/{id}/suspend: - post: - consumes: - - application/json - description: Suspend a customer account with optional reason - parameters: - - description: Customer ID - in: path - name: id - required: true - type: string - - description: Suspension information - in: body - name: suspension - required: true - schema: - $ref: '#/definitions/customer.SuspendCustomerForm' - produces: - - application/json - responses: - "200": - description: Customer suspended successfully - schema: - $ref: '#/definitions/response.APIResponse' - "400": - description: Bad request - Invalid input data - schema: - $ref: '#/definitions/response.APIResponse' - "404": - description: Not found - Customer not found - schema: - $ref: '#/definitions/response.APIResponse' - "422": - description: Validation error - Invalid request data - schema: - $ref: '#/definitions/response.APIResponse' - "500": - description: Internal server error - schema: - $ref: '#/definitions/response.APIResponse' - security: - - BearerAuth: [] - summary: Suspend customer - tags: - - Admin-Customers - /admin/v1/customers/{id}/verification: - patch: - consumes: - - application/json - description: Update customer verification and compliance status - parameters: - - description: Customer ID - in: path - name: id - required: true - type: string - - description: Verification status update - in: body - name: verification - required: true - schema: - $ref: '#/definitions/customer.UpdateCustomerVerificationForm' - produces: - - application/json - responses: - "200": - description: Customer verification updated successfully - schema: - $ref: '#/definitions/response.APIResponse' - "400": - description: Bad request - Invalid input data - schema: - $ref: '#/definitions/response.APIResponse' - "404": - description: Not found - Customer not found - schema: - $ref: '#/definitions/response.APIResponse' - "422": - description: Validation error - Invalid request data - schema: - $ref: '#/definitions/response.APIResponse' - "500": - description: Internal server error - schema: - $ref: '#/definitions/response.APIResponse' - security: - - BearerAuth: [] - summary: Update customer verification status - tags: - - Admin-Customers - /admin/v1/customers/{id}/verify: - post: - consumes: - - application/json - description: Mark a customer as verified - parameters: - - description: Customer ID - in: path - name: id - required: true - type: string - produces: - - application/json - responses: - "200": - description: Customer verified successfully - schema: - $ref: '#/definitions/response.APIResponse' - "400": - description: Bad request - Invalid customer ID - schema: - $ref: '#/definitions/response.APIResponse' - "404": - description: Not found - Customer not found - schema: - $ref: '#/definitions/response.APIResponse' - "500": - description: Internal server error - schema: - $ref: '#/definitions/response.APIResponse' - security: - - BearerAuth: [] - summary: Verify customer - tags: - - Admin-Customers - /admin/v1/customers/company/{companyId}: - get: - consumes: - - application/json - description: Retrieve all customers associated with a specific company - parameters: - - description: Company ID - in: path - name: companyId - required: true - type: string - - description: 'Number of customers to return (default: 10, max: 100)' - in: query - name: limit - type: integer - - description: 'Number of customers to skip (default: 0)' - in: query - name: offset - type: integer - produces: - - application/json - responses: - "200": - description: Customers retrieved successfully - schema: - allOf: - - $ref: '#/definitions/response.APIResponse' - - properties: - data: - items: - $ref: '#/definitions/customer.CustomerResponse' - type: array - type: object - "400": - description: Bad request - Invalid company ID - schema: - $ref: '#/definitions/response.APIResponse' - "500": - description: Internal server error - schema: - $ref: '#/definitions/response.APIResponse' - security: - - BearerAuth: [] - summary: Get customers by company ID - tags: - - Admin-Customers - /admin/v1/customers/status/{status}: - get: - consumes: - - application/json - description: Retrieve customers filtered by their account status (active, inactive, - suspended, or pending). Useful for administrative monitoring and management. - parameters: - - description: Customer status - enum: - - active - - inactive - - suspended - - pending - in: path - name: status - required: true - type: string - - default: 20 - description: Number of customers per page (1-100) - in: query - maximum: 100 - minimum: 1 - name: limit - type: integer - - default: 0 - description: Number of customers to skip for pagination - in: query - minimum: 0 - name: offset - type: integer - produces: - - application/json - responses: - "200": - description: Customers retrieved successfully - schema: - allOf: - - $ref: '#/definitions/response.APIResponse' - - properties: - data: - items: - $ref: '#/definitions/customer.CustomerResponse' - type: array - meta: - $ref: '#/definitions/response.Meta' - type: object - "400": - description: Bad request - Invalid customer status - schema: - $ref: '#/definitions/response.APIResponse' - "500": - description: Internal server error - schema: - $ref: '#/definitions/response.APIResponse' - security: - - BearerAuth: [] - summary: Get customers by status - tags: - - Admin-Customers - /admin/v1/customers/type/{type}: - get: - consumes: - - application/json - description: Retrieve customers filtered by their type (individual, company, - or government). Useful for administrative reporting and management. - parameters: - - description: Customer type - enum: - - individual - - company - - government - in: path - name: type - required: true - type: string - - default: 20 - description: Number of customers per page (1-100) - in: query - maximum: 100 - minimum: 1 - name: limit - type: integer - - default: 0 - description: Number of customers to skip for pagination - in: query - minimum: 0 - name: offset - type: integer - produces: - - application/json - responses: - "200": - description: Customers retrieved successfully - schema: - allOf: - - $ref: '#/definitions/response.APIResponse' - - properties: - data: - items: - $ref: '#/definitions/customer.CustomerResponse' - type: array - meta: - $ref: '#/definitions/response.Meta' - type: object - "400": - description: Bad request - Invalid customer type - schema: - $ref: '#/definitions/response.APIResponse' - "500": - description: Internal server error - schema: - $ref: '#/definitions/response.APIResponse' - security: - - BearerAuth: [] - summary: Get customers by type - tags: - - Admin-Customers /admin/v1/feedback: get: consumes: diff --git a/cmd/web/router/routes.go b/cmd/web/router/routes.go index 80135d9..8045e38 100644 --- a/cmd/web/router/routes.go +++ b/cmd/web/router/routes.go @@ -71,20 +71,11 @@ func RegisterAdminRoutes(e *echo.Echo, userHandler *user.Handler, companyHandler { customersGP.Use(userHandler.AuthMiddleware()) customersGP.POST("", customerHandler.CreateCustomer) - customersGP.GET("", customerHandler.ListCustomers) + customersGP.GET("", customerHandler.Search) customersGP.GET("/:id", customerHandler.GetCustomerByID) customersGP.PUT("/:id", customerHandler.UpdateCustomer) customersGP.DELETE("/:id", customerHandler.DeleteCustomer) - customersGP.PATCH("/:id/status", customerHandler.UpdateCustomerStatus) - customersGP.PATCH("/:id/verification", customerHandler.UpdateCustomerVerification) - customersGP.POST("/:id/verify", customerHandler.VerifyCustomer) - customersGP.POST("/:id/suspend", customerHandler.SuspendCustomer) - customersGP.POST("/:id/activate", customerHandler.ActivateCustomer) - customersGP.GET("/company/:companyId", customerHandler.GetCustomersByCompanyID) - customersGP.GET("/type/:type", customerHandler.GetCustomersByType) - customersGP.GET("/status/:status", customerHandler.GetCustomersByStatus) - customersGP.POST("/:id/companies/assign", customerHandler.AssignCompaniesToCustomer) - customersGP.POST("/:id/companies/remove", customerHandler.RemoveCompaniesFromCustomer) + customersGP.PUT("/:id/status", customerHandler.UpdateStatus) } // Admin Tenders Routes diff --git a/internal/customer/entity.go b/internal/customer/entity.go index 756f143..1b30cb8 100644 --- a/internal/customer/entity.go +++ b/internal/customer/entity.go @@ -13,7 +13,6 @@ const ( CustomerStatusActive CustomerStatus = "active" CustomerStatusInactive CustomerStatus = "inactive" CustomerStatusSuspended CustomerStatus = "suspended" - CustomerStatusPending CustomerStatus = "pending" ) // CustomerType represents the type of customer @@ -29,52 +28,17 @@ const ( // A customer can have an associated company for login purposes type Customer struct { mongo.Model `bson:",inline"` - 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"` - LastName *string `bson:"last_name,omitempty" json:"last_name,omitempty"` - FullName *string `bson:"full_name,omitempty" json:"full_name,omitempty"` - Username string `bson:"username" json:"username"` // Username for authentication - Email string `bson:"email" json:"email"` - Password string `bson:"password" json:"-"` // Hashed password for authentication - Phone *string `bson:"phone,omitempty" json:"phone,omitempty"` - Mobile *string `bson:"mobile,omitempty" json:"mobile,omitempty"` - - // Company relationships - each customer can be assigned to multiple companies - CompanyIDs []string `bson:"company_ids,omitempty" json:"company_ids,omitempty"` - - // Company customer fields - 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"` - - // Address information - Address *Address `bson:"address,omitempty" json:"address,omitempty"` - - // Business information - BusinessType *string `bson:"business_type,omitempty" json:"business_type,omitempty"` - EmployeeCount *int `bson:"employee_count,omitempty" json:"employee_count,omitempty"` - AnnualRevenue *float64 `bson:"annual_revenue,omitempty" json:"annual_revenue,omitempty"` - FoundedYear *int `bson:"founded_year,omitempty" json:"founded_year,omitempty"` - - // Contact person (for company customers) - ContactPerson *ContactPerson `bson:"contact_person,omitempty" json:"contact_person,omitempty"` - - // Verification and compliance - IsVerified bool `bson:"is_verified" json:"is_verified"` - IsCompliant bool `bson:"is_compliant" json:"is_compliant"` - ComplianceNotes *string `bson:"compliance_notes,omitempty" json:"compliance_notes,omitempty"` - - // 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" - - // Audit fields - CreatedBy *string `bson:"created_by,omitempty" json:"created_by,omitempty"` - UpdatedBy *string `bson:"updated_by,omitempty" json:"updated_by,omitempty"` + FullName *string `bson:"full_name"` + Username string `bson:"username"` + Email string `bson:"email"` + Password string `bson:"password"` + Status CustomerStatus `bson:"status"` + Type CustomerType `bson:"type"` + Phone *string `bson:"phone"` + CompanyIDs []string `bson:"company_ids"` + LastLoginAt *int64 `bson:"last_login_at"` + UpdatedAt int64 `bson:"updated_at"` + CreatedAt int64 `bson:"created_at"` } // SetID sets the customer ID (implements IDSetter interface) @@ -87,49 +51,26 @@ func (c *Customer) GetID() string { return c.ID.Hex() } -// SetCreatedAt sets the created timestamp (implements Timestampable interface) +// SetCreatedAt sets the created timestamp (implements Timestamp interface) func (c *Customer) SetCreatedAt(timestamp int64) { c.CreatedAt = timestamp } -// SetUpdatedAt sets the updated timestamp (implements Timestampable interface) +// SetUpdatedAt sets the updated timestamp (implements Timestamp interface) func (c *Customer) SetUpdatedAt(timestamp int64) { c.UpdatedAt = timestamp } -// GetCreatedAt returns the created timestamp (implements Timestampable interface) +// GetCreatedAt returns the created timestamp (implements Timestamp interface) func (c *Customer) GetCreatedAt() int64 { return c.CreatedAt } -// GetUpdatedAt returns the updated timestamp (implements Timestampable interface) +// GetUpdatedAt returns the updated timestamp (implements Timestamp interface) func (c *Customer) GetUpdatedAt() int64 { return c.UpdatedAt } -// Address represents a customer's address -type Address struct { - Street string `bson:"street" json:"street"` - City string `bson:"city" json:"city"` - State string `bson:"state" json:"state"` - PostalCode string `bson:"postal_code" json:"postal_code"` - Country string `bson:"country" json:"country"` - AddressType string `bson:"address_type" json:"address_type"` // billing, shipping, etc. - IsDefault bool `bson:"is_default" json:"is_default"` -} - -// ContactPerson represents a contact person for company customers -type ContactPerson struct { - FirstName string `bson:"first_name" json:"first_name"` - LastName string `bson:"last_name" json:"last_name"` - FullName string `bson:"full_name" json:"full_name"` - Position string `bson:"position" json:"position"` - Email string `bson:"email" json:"email"` - Phone string `bson:"phone" json:"phone"` - Mobile *string `bson:"mobile,omitempty" json:"mobile,omitempty"` - IsPrimary bool `bson:"is_primary" json:"is_primary"` -} - // CompanySummary represents company summary data for customer responses type CompanySummary struct { ID string `json:"id"` @@ -138,81 +79,36 @@ type CompanySummary struct { // CustomerResponse represents the customer data sent in API responses type CustomerResponse struct { - ID string `json:"id"` - Type string `json:"type"` - Status string `json:"status"` - 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"` - - // Company relationships - Companies []*CompanySummary `json:"companies,omitempty"` - - // Company customer fields - RegistrationNumber *string `json:"registration_number,omitempty"` - TaxID *string `json:"tax_id"` - Industry *string `json:"industry"` - Address *Address `json:"address"` - BusinessType *string `json:"business_type"` - EmployeeCount *int `json:"employee_count"` - AnnualRevenue *float64 `json:"annual_revenue"` - FoundedYear *int `json:"founded_year"` - ContactPerson *ContactPerson `json:"contact_person"` - IsVerified bool `json:"is_verified"` - IsCompliant bool `json:"is_compliant"` - ComplianceNotes *string `json:"compliance_notes"` - Language string `json:"language"` - Username string `json:"username"` - Currency string `json:"currency"` - Timezone string `json:"timezone"` - CreatedAt int64 `json:"created_at"` - UpdatedAt int64 `json:"updated_at"` - CreatedBy *string `json:"created_by"` - UpdatedBy *string `json:"updated_by"` + ID string `bson:"id"` + FullName *string `bson:"full_name"` + Username string `bson:"username"` + Email string `bson:"email"` + Password string `bson:"password"` + Status string `bson:"status"` + Type string `bson:"type"` + Phone *string `bson:"phone"` + CompanyIDs []string `bson:"company_ids"` + UpdatedAt int64 `bson:"updated_at"` + CreatedAt int64 `bson:"created_at"` + LastLoginAt *int64 `bson:"last_login_at"` + Companies []*CompanySummary `json:"companies"` } // ToResponse converts Customer to CustomerResponse -func (c *Customer) ToResponse() *CustomerResponse { +func (c *Customer) ToResponse(companies []*CompanySummary) *CustomerResponse { return &CustomerResponse{ - ID: c.ID.Hex(), - Type: string(c.Type), - Status: string(c.Status), - FirstName: c.FirstName, - LastName: c.LastName, - FullName: c.FullName, - Username: c.Username, - Email: c.Email, - Phone: c.Phone, - Mobile: c.Mobile, - Companies: nil, // Will be populated by service layer - RegistrationNumber: c.RegistrationNumber, - TaxID: c.TaxID, - Industry: c.Industry, - Address: c.Address, - BusinessType: c.BusinessType, - EmployeeCount: c.EmployeeCount, - AnnualRevenue: c.AnnualRevenue, - FoundedYear: c.FoundedYear, - ContactPerson: c.ContactPerson, - IsVerified: c.IsVerified, - IsCompliant: c.IsCompliant, - ComplianceNotes: c.ComplianceNotes, - Language: c.Language, - Currency: c.Currency, - Timezone: c.Timezone, - CreatedAt: c.CreatedAt, - UpdatedAt: c.UpdatedAt, - CreatedBy: c.CreatedBy, - UpdatedBy: c.UpdatedBy, + ID: c.ID.Hex(), + FullName: c.FullName, + Username: c.Username, + Email: c.Email, + Password: c.Password, + Type: string(c.Type), + Status: string(c.Status), + Phone: c.Phone, + CompanyIDs: c.CompanyIDs, + UpdatedAt: c.UpdatedAt, + CreatedAt: c.CreatedAt, + LastLoginAt: c.LastLoginAt, + Companies: companies, } } - -// ToResponseWithCompanies converts Customer to CustomerResponse with company information -func (c *Customer) ToResponseWithCompanies(companies []*CompanySummary) *CustomerResponse { - response := c.ToResponse() - response.Companies = companies - return response -} diff --git a/internal/customer/form.go b/internal/customer/form.go index 8efbbbf..3a495fd 100644 --- a/internal/customer/form.go +++ b/internal/customer/form.go @@ -1,184 +1,52 @@ package customer +import "tm/pkg/response" + // CreateCustomerForm represents the form for creating a new customer type CreateCustomerForm struct { - Type string `json:"type" valid:"required,in(individual|company|government)"` - - // Company assignments + FullName *string `json:"full_name,omitempty" valid:"optional,length(2|100)" example:"User"` + Type string `json:"type" valid:"required,in(individual|company|government)" example:"individual"` CompanyIDs []string `json:"company_ids,omitempty" valid:"optional"` - - // Individual customer fields - 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)"` - - // Company customer fields - RegistrationNumber *string `json:"registration_number,omitempty" valid:"optional,length(5|50)"` - TaxID *string `json:"tax_id,omitempty" valid:"optional,length(5|50)"` - Industry *string `json:"industry,omitempty" valid:"optional,length(2|100)"` - - // Address information - Address *AddressForm `json:"address,omitempty"` - - // Business information - BusinessType *string `json:"business_type,omitempty" valid:"optional,length(2|100)"` - EmployeeCount *int `json:"employee_count,omitempty" valid:"optional,range(1|100000)"` - AnnualRevenue *float64 `json:"annual_revenue,omitempty" valid:"optional,range(0|999999999999)"` - FoundedYear *int `json:"founded_year,omitempty" valid:"optional,range(1800|2100)"` - - // Contact person (for company customers) - ContactPerson *ContactPersonForm `json:"contact_person,omitempty"` - - // Preferences and 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)"` - Timezone *string `json:"timezone,omitempty" valid:"optional,length(3|50)"` + Username string `json:"username" valid:"required,alphanum,length(3|30)" example:"user"` + Email string `json:"email" valid:"required,email" example:"user@opplens.com"` + Password string `json:"password" valid:"required,length(8|128)" example:"User!1234"` + Phone *string `json:"phone,omitempty" valid:"optional,length(10|20)" example:"+1234567890"` } // UpdateCustomerForm represents the form for updating a customer type UpdateCustomerForm struct { - Type *string `json:"type,omitempty" valid:"optional,in(individual|company|government)"` - - // Company assignments + FullName *string `json:"full_name,omitempty" valid:"optional,length(2|100)" example:"User"` + Type string `json:"type" valid:"required,in(individual|company|government)" example:"individual"` CompanyIDs []string `json:"company_ids,omitempty" valid:"optional"` + Username string `json:"username" valid:"required,alphanum,length(3|30)" example:"user"` + Email string `json:"email" valid:"required,email" example:"user@opplens.com"` + Password string `json:"password" valid:"required,length(8|128)" example:"User!1234"` + Phone *string `json:"phone,omitempty" valid:"optional,length(10|20)" example:"+1234567890"` +} - // Individual customer fields - 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,omitempty" valid:"optional,alphanum,length(3|30)"` - Email *string `json:"email,omitempty" valid:"optional,email"` - Phone *string `json:"phone,omitempty" valid:"optional,length(10|20)"` - Mobile *string `json:"mobile,omitempty" valid:"optional,length(10|20)"` - - // Company customer fields - CompanyName *string `json:"company_name,omitempty" valid:"optional,length(2|200)"` - RegistrationNumber *string `json:"registration_number,omitempty" valid:"optional,length(5|50)"` - TaxID *string `json:"tax_id,omitempty" valid:"optional,length(5|50)"` - Industry *string `json:"industry,omitempty" valid:"optional,length(2|100)"` - - // Address information - Address *AddressForm `json:"address,omitempty"` - - // Business information - BusinessType *string `json:"business_type,omitempty" valid:"optional,length(2|100)"` - EmployeeCount *int `json:"employee_count,omitempty" valid:"optional,range(1|100000)"` - AnnualRevenue *float64 `json:"annual_revenue,omitempty" valid:"optional,range(0|999999999999)"` - FoundedYear *int `json:"founded_year,omitempty" valid:"optional,range(1800|2100)"` - - // Contact person (for company customers) - ContactPerson *ContactPersonForm `json:"contact_person,omitempty"` - - // Preferences and 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)"` - Timezone *string `json:"timezone,omitempty" valid:"optional,length(3|50)"` +// CustomerStatusForm represents the form for suspending a customer +type UpdateStatusForm struct { + Status string `json:"status" valid:"required,in(active|inactive|suspended)" example:"active"` + Reason string `json:"reason" valid:"required,length(10|500)" example:"Customer is active"` } // ListCustomersForm represents the form for listing customers with filters -type ListCustomersForm struct { - Search *string `query:"search" valid:"optional"` - Type *string `query:"type" valid:"optional,in(individual|company|government)"` - Status *string `query:"status" valid:"optional,in(active|inactive|suspended|pending)"` - CompanyID *string `query:"company_id" valid:"optional,uuid"` - Industry *string `query:"industry" valid:"optional"` - IsVerified *bool `query:"is_verified" valid:"optional"` - IsCompliant *bool `query:"is_compliant" valid:"optional"` - Language *string `query:"language" valid:"optional"` - Currency *string `query:"currency" valid:"optional"` - Limit *int `query:"limit" valid:"optional,range(1|100)"` - Offset *int `query:"offset" valid:"optional,min(0)"` - SortBy *string `query:"sort_by" valid:"optional,in(email|company_name|created_at|updated_at|status)"` - SortOrder *string `query:"sort_order" valid:"optional,in(asc|desc)"` -} - -// UpdateCustomerStatusForm represents the form for updating customer status -type UpdateCustomerStatusForm struct { - Status string `json:"status" valid:"required,in(active|inactive|suspended|pending)"` -} - -// UpdateCustomerVerificationForm represents the form for updating customer verification status -type UpdateCustomerVerificationForm struct { - IsVerified bool `json:"is_verified"` - IsCompliant bool `json:"is_compliant"` - ComplianceNotes *string `json:"compliance_notes,omitempty" valid:"optional,length(0|1000)"` -} - -// SuspendCustomerForm represents the form for suspending a customer -type SuspendCustomerForm struct { - Reason string `json:"reason" valid:"required,length(10|500)"` -} - -// AddressForm represents the form for customer address -type AddressForm struct { - Street string `json:"street" valid:"required,length(5|200)"` - City string `json:"city" valid:"required,length(2|100)"` - State string `json:"state" valid:"required,length(2|100)"` - PostalCode string `json:"postal_code" valid:"required,length(3|20)"` - Country string `json:"country" valid:"required,length(2|100)"` - AddressType string `json:"address_type" valid:"required,in(billing|shipping|both)"` - IsDefault bool `json:"is_default"` -} - -// ContactPersonForm represents the form for contact person -type ContactPersonForm struct { - FirstName string `json:"first_name" valid:"required,length(2|50)"` - LastName string `json:"last_name" valid:"required,length(2|50)"` - FullName string `json:"full_name" valid:"required,length(2|100)"` - Position string `json:"position" valid:"required,length(2|100)"` - Email string `json:"email" valid:"required,email"` - Phone string `json:"phone" valid:"required,length(10|20)"` - Mobile *string `json:"mobile,omitempty" valid:"optional,length(10|20)"` - IsPrimary bool `json:"is_primary"` +type SearchCustomersForm struct { + Search *string `query:"search" valid:"optional"` + Type *string `query:"type" valid:"optional,in(individual|company|government)"` + Status *string `query:"status" valid:"optional,in(active|inactive|suspended)"` + CompanyID *string `query:"company_id" valid:"optional"` } // CustomerListResponse represents the response for listing customers type CustomerListResponse struct { - Customers []*CustomerResponse `json:"customers"` - Total int64 `json:"total"` - Limit int `json:"limit"` - Offset int `json:"offset"` - TotalPages int `json:"total_pages"` + Customers []*CustomerResponse `json:"customers"` + Meta *response.Meta `json:"meta"` } -// 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)"` - - // Company assignments - CompanyIDs []string `json:"company_ids,omitempty" valid:"optional"` - 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)"` - - // Company assignments - CompanyIDs []string `json:"company_ids,omitempty" valid:"optional"` - Industry *string `json:"industry,omitempty" valid:"optional,length(2|100)"` -} - -// Customer Authentication DTOs type LoginForm struct { - Username string `json:"username" valid:"required" example:"nakhostin"` - Password string `json:"password" valid:"required" example:"Nima.1998"` + Username string `json:"username" valid:"required" example:"app"` + Password string `json:"password" valid:"required" example:"App!1234"` } type RefreshTokenForm struct { @@ -191,20 +59,11 @@ type ChangePasswordForm struct { } type UpdateProfileForm 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)"` - Username *string `json:"username,omitempty" valid:"optional,alphanum,length(3|30)"` - 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)"` - 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)"` - Timezone *string `json:"timezone,omitempty" valid:"optional,length(3|50)"` + FullName *string `json:"full_name,omitempty" valid:"optional,length(2|100)"` + Username *string `json:"username,omitempty" valid:"optional,alphanum,length(3|30)"` + Phone *string `json:"phone,omitempty" valid:"optional,length(10|20)"` } -// Customer Authentication Response DTOs type AuthResponse struct { Customer *CustomerResponse `json:"customer"` AccessToken string `json:"access_token"` @@ -212,7 +71,6 @@ type AuthResponse struct { ExpiresAt int64 `json:"expires_at"` } -// Company assignment forms type AssignCompaniesForm struct { CompanyIDs []string `json:"company_ids" valid:"required"` } diff --git a/internal/customer/handler.go b/internal/customer/handler.go index 23a15df..ef40f71 100644 --- a/internal/customer/handler.go +++ b/internal/customer/handler.go @@ -1,7 +1,6 @@ package customer import ( - "strconv" "tm/internal/user" "tm/pkg/authorization" "tm/pkg/logger" @@ -48,13 +47,7 @@ func (h *Handler) CreateCustomer(c echo.Context) error { return response.ValidationError(c, "Invalid request data", err.Error()) } - // Get user ID from JWT token context - createdBy, err := user.GetUserIDFromContext(c) - if err != nil { - return response.Unauthorized(c, "User not authenticated") - } - - customer, err := h.service.CreateCustomer(c.Request().Context(), form, &createdBy) + customer, err := h.service.Register(c.Request().Context(), form) if err != nil { if err.Error() == "customer with this email already exists" || err.Error() == "company with this name already exists" || @@ -65,7 +58,7 @@ func (h *Handler) CreateCustomer(c echo.Context) error { return response.InternalServerError(c, "Failed to create customer") } - return response.Created(c, customer.ToResponse(), "Customer created successfully") + return response.Created(c, customer, "Customer created successfully") } // GetCustomerByID retrieves a customer by ID (Web Panel) @@ -82,9 +75,9 @@ func (h *Handler) CreateCustomer(c echo.Context) error { // @Security BearerAuth // @Router /admin/v1/customers/{id} [get] func (h *Handler) GetCustomerByID(c echo.Context) error { - idStr := c.Param("id") + id := c.Param("id") - customer, err := h.service.GetCustomerByIDWithCompanies(c.Request().Context(), idStr) + customer, err := h.service.GetByID(c.Request().Context(), id) if err != nil { if err.Error() == "customer not found" { return response.NotFound(c, "Customer not found") @@ -112,19 +105,15 @@ func (h *Handler) GetCustomerByID(c echo.Context) error { // @Security BearerAuth // @Router /admin/v1/customers/{id} [put] func (h *Handler) UpdateCustomer(c echo.Context) error { - idStr := c.Param("id") + id := c.Param("id") form, err := response.Parse[UpdateCustomerForm](c) if err != nil { return response.ValidationError(c, "Invalid request data", err.Error()) } // Get user ID from JWT token context - updatedBy, err := user.GetUserIDFromContext(c) - if err != nil { - return response.Unauthorized(c, "User not authenticated") - } - customer, err := h.service.UpdateCustomer(c.Request().Context(), idStr, form, &updatedBy) + customer, err := h.service.Update(c.Request().Context(), id, form) if err != nil { if err.Error() == "customer not found" { return response.NotFound(c, "Customer not found") @@ -138,7 +127,7 @@ func (h *Handler) UpdateCustomer(c echo.Context) error { return response.InternalServerError(c, "Failed to update customer") } - return response.Success(c, customer.ToResponse(), "Customer updated successfully") + return response.Success(c, customer, "Customer updated successfully") } // DeleteCustomer deletes a customer (Web Panel) @@ -155,15 +144,9 @@ func (h *Handler) UpdateCustomer(c echo.Context) error { // @Security BearerAuth // @Router /admin/v1/customers/{id} [delete] func (h *Handler) DeleteCustomer(c echo.Context) error { - idStr := c.Param("id") + id := c.Param("id") - // Get user ID from JWT token context - deletedBy, err := user.GetUserIDFromContext(c) - if err != nil { - return response.Unauthorized(c, "User not authenticated") - } - - err = h.service.DeleteCustomer(c.Request().Context(), idStr, &deletedBy) + err := h.service.Delete(c.Request().Context(), id) if err != nil { if err.Error() == "customer not found" { return response.NotFound(c, "Customer not found") @@ -186,11 +169,6 @@ func (h *Handler) DeleteCustomer(c echo.Context) error { // @Param type query string false "Filter by customer type" Enums(individual, company, government) // @Param status query string false "Filter by customer status" Enums(active, inactive, suspended, pending) // @Param company_id query string false "Filter by company ID" format(uuid) -// @Param industry query string false "Filter by industry" -// @Param is_verified query boolean false "Filter by verification status" -// @Param is_compliant query boolean false "Filter by compliance status" -// @Param language query string false "Filter by preferred language" -// @Param currency query string false "Filter by preferred currency" // @Param limit query integer false "Number of customers per page (1-100)" minimum(1) maximum(100) default(20) // @Param offset query integer false "Number of customers to skip for pagination" minimum(0) default(0) // @Param sort_by query string false "Field to sort by" Enums(email, company_name, created_at, updated_at, status) default(created_at) @@ -201,13 +179,15 @@ func (h *Handler) DeleteCustomer(c echo.Context) error { // @Failure 500 {object} response.APIResponse "Internal server error" // @Security BearerAuth // @Router /admin/v1/customers [get] -func (h *Handler) ListCustomers(c echo.Context) error { - form, err := response.Parse[ListCustomersForm](c) +func (h *Handler) Search(c echo.Context) error { + form, err := response.Parse[SearchCustomersForm](c) if err != nil { return response.ValidationError(c, "Invalid request data", err.Error()) } - result, err := h.service.ListCustomers(c.Request().Context(), form) + pagination := response.NewPagination(c) + + result, err := h.service.Search(c.Request().Context(), form, pagination) if err != nil { return response.InternalServerError(c, "Failed to list customers") } @@ -222,7 +202,7 @@ func (h *Handler) ListCustomers(c echo.Context) error { // @Accept json // @Produce json // @Param id path string true "Customer ID" -// @Param status body UpdateCustomerStatusForm true "New customer status" +// @Param status body UpdateStatusForm true "New customer status" // @Success 200 {object} response.APIResponse "Customer status updated successfully" // @Failure 400 {object} response.APIResponse "Bad request - Invalid input data" // @Failure 404 {object} response.APIResponse "Not found - Customer not found" @@ -230,24 +210,16 @@ func (h *Handler) ListCustomers(c echo.Context) error { // @Failure 500 {object} response.APIResponse "Internal server error" // @Security BearerAuth // @Router /admin/v1/customers/{id}/status [patch] -func (h *Handler) UpdateCustomerStatus(c echo.Context) error { - idStr := c.Param("id") - form, err := response.Parse[UpdateCustomerStatusForm](c) +func (h *Handler) UpdateStatus(c echo.Context) error { + id := c.Param("id") + + form, err := response.Parse[UpdateStatusForm](c) if err != nil { return response.ValidationError(c, "Invalid request data", err.Error()) } - // Get user ID from JWT token context - updatedBy, err := user.GetUserIDFromContext(c) + err = h.service.UpdateStatus(c.Request().Context(), id, form) if err != nil { - return response.Unauthorized(c, "User not authenticated") - } - - err = h.service.UpdateCustomerStatus(c.Request().Context(), idStr, form, &updatedBy) - if err != nil { - if err.Error() == "customer not found" { - return response.NotFound(c, "Customer not found") - } return response.InternalServerError(c, "Failed to update customer status") } @@ -256,403 +228,9 @@ func (h *Handler) UpdateCustomerStatus(c echo.Context) error { }, "Customer status updated successfully") } -// UpdateCustomerVerification updates customer verification status (Web Panel) -// @Summary Update customer verification status -// @Description Update customer verification and compliance status -// @Tags Admin-Customers -// @Accept json -// @Produce json -// @Param id path string true "Customer ID" -// @Param verification body UpdateCustomerVerificationForm true "Verification status update" -// @Success 200 {object} response.APIResponse "Customer verification updated successfully" -// @Failure 400 {object} response.APIResponse "Bad request - Invalid input data" -// @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" -// @Security BearerAuth -// @Router /admin/v1/customers/{id}/verification [patch] -func (h *Handler) UpdateCustomerVerification(c echo.Context) error { - idStr := c.Param("id") - form, err := response.Parse[UpdateCustomerVerificationForm](c) - if err != nil { - return response.ValidationError(c, "Invalid request data", err.Error()) - } - - // Get user ID from JWT token context - updatedBy, err := user.GetUserIDFromContext(c) - if err != nil { - return response.Unauthorized(c, "User not authenticated") - } - - err = h.service.UpdateCustomerVerification(c.Request().Context(), idStr, form, &updatedBy) - if err != nil { - if err.Error() == "customer not found" { - return response.NotFound(c, "Customer not found") - } - return response.InternalServerError(c, "Failed to update customer verification") - } - - return response.Success(c, map[string]interface{}{ - "message": "Customer verification updated successfully", - }, "Customer verification updated successfully") -} - -// VerifyCustomer verifies a customer (Web Panel) -// @Summary Verify customer -// @Description Mark a customer as verified -// @Tags Admin-Customers -// @Accept json -// @Produce json -// @Param id path string true "Customer ID" -// @Success 200 {object} response.APIResponse "Customer verified successfully" -// @Failure 400 {object} response.APIResponse "Bad request - Invalid customer ID" -// @Failure 404 {object} response.APIResponse "Not found - Customer not found" -// @Failure 500 {object} response.APIResponse "Internal server error" -// @Security BearerAuth -// @Router /admin/v1/customers/{id}/verify [post] -func (h *Handler) VerifyCustomer(c echo.Context) error { - idStr := c.Param("id") - - // Get user ID from JWT token context - verifiedBy, err := user.GetUserIDFromContext(c) - if err != nil { - return response.Unauthorized(c, "User not authenticated") - } - - err = h.service.VerifyCustomer(c.Request().Context(), idStr, &verifiedBy) - if err != nil { - if err.Error() == "customer not found" { - return response.NotFound(c, "Customer not found") - } - return response.InternalServerError(c, "Failed to verify customer") - } - - return response.Success(c, map[string]interface{}{ - "message": "Customer verified successfully", - }, "Customer verified successfully") -} - -// SuspendCustomer suspends a customer (Web Panel) -// @Summary Suspend customer -// @Description Suspend a customer account with optional reason -// @Tags Admin-Customers -// @Accept json -// @Produce json -// @Param id path string true "Customer ID" -// @Param suspension body SuspendCustomerForm true "Suspension information" -// @Success 200 {object} response.APIResponse "Customer suspended successfully" -// @Failure 400 {object} response.APIResponse "Bad request - Invalid input data" -// @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" -// @Security BearerAuth -// @Router /admin/v1/customers/{id}/suspend [post] -func (h *Handler) SuspendCustomer(c echo.Context) error { - idStr := c.Param("id") - form, err := response.Parse[SuspendCustomerForm](c) - if err != nil { - return response.ValidationError(c, "Invalid request data", err.Error()) - } - - // Get user ID from JWT token context - suspendedBy, err := user.GetUserIDFromContext(c) - if err != nil { - return response.Unauthorized(c, "User not authenticated") - } - - err = h.service.SuspendCustomer(c.Request().Context(), idStr, form.Reason, &suspendedBy) - if err != nil { - if err.Error() == "customer not found" { - return response.NotFound(c, "Customer not found") - } - return response.InternalServerError(c, "Failed to suspend customer") - } - - return response.Success(c, map[string]interface{}{ - "message": "Customer suspended successfully", - }, "Customer suspended successfully") -} - -// ActivateCustomer activates a customer (Web Panel) -// @Summary Activate customer -// @Description Activate a suspended customer account -// @Tags Admin-Customers -// @Accept json -// @Produce json -// @Param id path string true "Customer ID" -// @Success 200 {object} response.APIResponse "Customer activated successfully" -// @Failure 400 {object} response.APIResponse "Bad request - Invalid customer ID" -// @Failure 404 {object} response.APIResponse "Not found - Customer not found" -// @Failure 500 {object} response.APIResponse "Internal server error" -// @Security BearerAuth -// @Router /admin/v1/customers/{id}/activate [post] -func (h *Handler) ActivateCustomer(c echo.Context) error { - idStr := c.Param("id") - - // Get user ID from JWT token context - activatedBy, err := user.GetUserIDFromContext(c) - if err != nil { - return response.Unauthorized(c, "User not authenticated") - } - - err = h.service.ActivateCustomer(c.Request().Context(), idStr, &activatedBy) - if err != nil { - if err.Error() == "customer not found" { - return response.NotFound(c, "Customer not found") - } - return response.InternalServerError(c, "Failed to activate customer") - } - - return response.Success(c, map[string]interface{}{ - "message": "Customer activated successfully", - }, "Customer activated successfully") -} - -// GetCustomersByCompanyID retrieves customers by company ID (Web Panel) -// @Summary Get customers by company ID -// @Description Retrieve all customers associated with a specific company -// @Tags Admin-Customers -// @Accept json -// @Produce json -// @Param companyId path string true "Company ID" -// @Param limit query int false "Number of customers to return (default: 10, max: 100)" -// @Param offset query int false "Number of customers to skip (default: 0)" -// @Success 200 {object} response.APIResponse{data=[]CustomerResponse} "Customers retrieved successfully" -// @Failure 400 {object} response.APIResponse "Bad request - Invalid company ID" -// @Failure 500 {object} response.APIResponse "Internal server error" -// @Security BearerAuth -// @Router /admin/v1/customers/company/{companyId} [get] -func (h *Handler) GetCustomersByCompanyID(c echo.Context) error { - companyIDStr := c.Param("companyId") - - // Parse pagination parameters - limit := 10 // Default limit - offset := 0 // Default offset - - if limitStr := c.QueryParam("limit"); limitStr != "" { - if parsedLimit, err := strconv.Atoi(limitStr); err == nil && parsedLimit > 0 && parsedLimit <= 100 { - limit = parsedLimit - } - } - - if offsetStr := c.QueryParam("offset"); offsetStr != "" { - if parsedOffset, err := strconv.Atoi(offsetStr); err == nil && parsedOffset >= 0 { - offset = parsedOffset - } - } - - customers, total, err := h.service.GetCustomersByCompanyID(c.Request().Context(), companyIDStr, limit, offset) - if err != nil { - return response.InternalServerError(c, "Failed to get customers by company") - } - - // Convert to responses - var customerResponses []*CustomerResponse - for _, customer := range customers { - customerResponses = append(customerResponses, customer.ToResponse()) - } - - return response.Success(c, map[string]interface{}{ - "customers": customerResponses, - "total": total, - "limit": limit, - "offset": offset, - }, "Customers retrieved successfully") -} - -// GetCustomersByType retrieves customers by type -// @Summary Get customers by type -// @Description Retrieve customers filtered by their type (individual, company, or government). Useful for administrative reporting and management. -// @Tags Admin-Customers -// @Accept json -// @Produce json -// @Param type path string true "Customer type" Enums(individual, company, government) -// @Param limit query integer false "Number of customers per page (1-100)" minimum(1) maximum(100) default(20) -// @Param offset query integer false "Number of customers to skip for pagination" minimum(0) default(0) -// @Success 200 {object} response.APIResponse{data=[]CustomerResponse,meta=response.Meta} "Customers retrieved successfully" -// @Failure 400 {object} response.APIResponse "Bad request - Invalid customer type" -// @Failure 500 {object} response.APIResponse "Internal server error" -// @Security BearerAuth -// @Router /admin/v1/customers/type/{type} [get] -func (h *Handler) GetCustomersByType(c echo.Context) error { - customerTypeStr := c.Param("type") - customerType := CustomerType(customerTypeStr) - - // Validate customer type - if customerType != CustomerTypeIndividual && customerType != CustomerTypeCompany && customerType != CustomerTypeGovernment { - return response.BadRequest(c, "Invalid customer type", "Must be individual, company, or government") - } - - limit := 20 - if limitStr := c.QueryParam("limit"); limitStr != "" { - if parsedLimit, err := strconv.Atoi(limitStr); err == nil && parsedLimit > 0 && parsedLimit <= 100 { - limit = parsedLimit - } - } - - offset := 0 - if offsetStr := c.QueryParam("offset"); offsetStr != "" { - if parsedOffset, err := strconv.Atoi(offsetStr); err == nil && parsedOffset >= 0 { - offset = parsedOffset - } - } - - customers, total, err := h.service.GetCustomersByType(c.Request().Context(), customerType, limit, offset) - if err != nil { - return response.InternalServerError(c, "Failed to retrieve customers by type") - } - - var customerResponses []*CustomerResponse - for _, customer := range customers { - customerResponses = append(customerResponses, customer.ToResponse()) - } - - meta := &response.Meta{ - Total: int(total), - Limit: limit, - Offset: offset, - } - - return response.SuccessWithMeta(c, customerResponses, meta, "Customers retrieved successfully") -} - -// GetCustomersByStatus retrieves customers by status -// @Summary Get customers by status -// @Description Retrieve customers filtered by their account status (active, inactive, suspended, or pending). Useful for administrative monitoring and management. -// @Tags Admin-Customers -// @Accept json -// @Produce json -// @Param status path string true "Customer status" Enums(active, inactive, suspended, pending) -// @Param limit query integer false "Number of customers per page (1-100)" minimum(1) maximum(100) default(20) -// @Param offset query integer false "Number of customers to skip for pagination" minimum(0) default(0) -// @Success 200 {object} response.APIResponse{data=[]CustomerResponse,meta=response.Meta} "Customers retrieved successfully" -// @Failure 400 {object} response.APIResponse "Bad request - Invalid customer status" -// @Failure 500 {object} response.APIResponse "Internal server error" -// @Security BearerAuth -// @Router /admin/v1/customers/status/{status} [get] -func (h *Handler) GetCustomersByStatus(c echo.Context) error { - statusStr := c.Param("status") - status := CustomerStatus(statusStr) - - // Validate customer status - if status != CustomerStatusActive && status != CustomerStatusInactive && status != CustomerStatusSuspended && status != CustomerStatusPending { - return response.BadRequest(c, "Invalid customer status", "Must be active, inactive, suspended, or pending") - } - - limit := 20 - if limitStr := c.QueryParam("limit"); limitStr != "" { - if parsedLimit, err := strconv.Atoi(limitStr); err == nil && parsedLimit > 0 && parsedLimit <= 100 { - limit = parsedLimit - } - } - - offset := 0 - if offsetStr := c.QueryParam("offset"); offsetStr != "" { - if parsedOffset, err := strconv.Atoi(offsetStr); err == nil && parsedOffset >= 0 { - offset = parsedOffset - } - } - - customers, total, err := h.service.GetCustomersByStatus(c.Request().Context(), status, limit, offset) - if err != nil { - return response.InternalServerError(c, "Failed to retrieve customers by status") - } - - var customerResponses []*CustomerResponse - for _, customer := range customers { - customerResponses = append(customerResponses, customer.ToResponse()) - } - - meta := &response.Meta{ - Total: int(total), - Limit: limit, - Offset: offset, - } - - return response.SuccessWithMeta(c, customerResponses, meta, "Customers retrieved successfully") -} - -// AssignCompaniesToCustomer assigns companies to a customer (Web Panel) -// @Summary Assign companies to a customer -// @Description Assign one or more companies to a specific customer. -// @Tags Admin-Customers -// @Accept json -// @Produce json -// @Param id path string true "Customer ID" -// @Param companies body AssignCompaniesForm true "List of company IDs to assign" -// @Success 200 {object} response.APIResponse "Companies assigned successfully" -// @Failure 400 {object} response.APIResponse "Bad request - Invalid input data" -// @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" -// @Security BearerAuth -// @Router /admin/v1/customers/{id}/companies/assign [post] -func (h *Handler) AssignCompaniesToCustomer(c echo.Context) error { - idStr := c.Param("id") - form, err := response.Parse[AssignCompaniesForm](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.AssignCompaniesToCustomer(c.Request().Context(), idStr, form.CompanyIDs, &assignedBy) - if err != nil { - if err.Error() == "customer not found" { - return response.NotFound(c, "Customer not found") - } - return response.InternalServerError(c, "Failed to assign companies to customer") - } - - return response.Success(c, map[string]interface{}{ - "message": "Companies assigned successfully", - }, "Companies assigned successfully") -} - -// RemoveCompaniesFromCustomer removes companies from a customer (Web Panel) -// @Summary Remove companies from a customer -// @Description Remove one or more companies from a specific customer. -// @Tags Admin-Customers -// @Accept json -// @Produce json -// @Param id path string true "Customer ID" -// @Param companies body RemoveCompaniesForm true "List of company IDs to remove" -// @Success 200 {object} response.APIResponse "Companies removed successfully" -// @Failure 400 {object} response.APIResponse "Bad request - Invalid input data" -// @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" -// @Security BearerAuth -// @Router /admin/v1/customers/{id}/companies/remove [post] -func (h *Handler) RemoveCompaniesFromCustomer(c echo.Context) error { - idStr := c.Param("id") - form, err := response.Parse[RemoveCompaniesForm](c) - if err != nil { - return response.ValidationError(c, "Invalid request data", err.Error()) - } - - // Get user ID from JWT token context - removedBy, err := user.GetUserIDFromContext(c) - if err != nil { - return response.Unauthorized(c, "User not authenticated") - } - - err = h.service.RemoveCompaniesFromCustomer(c.Request().Context(), idStr, form.CompanyIDs, &removedBy) - if err != nil { - if err.Error() == "customer not found" { - return response.NotFound(c, "Customer not found") - } - return response.InternalServerError(c, "Failed to remove companies from customer") - } - - return response.Success(c, map[string]interface{}{ - "message": "Companies removed successfully", - }, "Companies removed successfully") -} +// ************************************************** +// * PROFILE HANDLERS * +// ************************************************** // Login handles customer authentication // @Summary Customer login diff --git a/internal/customer/repository.go b/internal/customer/repository.go index 99409e4..6997cda 100644 --- a/internal/customer/repository.go +++ b/internal/customer/repository.go @@ -5,61 +5,46 @@ import ( "errors" "time" "tm/pkg/logger" - mongopkg "tm/pkg/mongo" + orm "tm/pkg/mongo" + "tm/pkg/response" "go.mongodb.org/mongo-driver/bson" ) // Repository defines the interface for customer data operations type Repository interface { - Create(ctx context.Context, customer *Customer) error + Login(ctx context.Context, id string) error + Register(ctx context.Context, customer *Customer) error + Update(ctx context.Context, customer *Customer) error + UpdateStatus(ctx context.Context, id string, status CustomerStatus) error GetByID(ctx context.Context, id string) (*Customer, error) GetByEmail(ctx context.Context, email string) (*Customer, error) GetByUsername(ctx context.Context, username string) (*Customer, error) - GetByCompanyName(ctx context.Context, companyName string) (*Customer, error) - GetByRegistrationNumber(ctx context.Context, registrationNumber string) (*Customer, error) - GetByTaxID(ctx context.Context, taxID string) (*Customer, error) - GetByCompanyID(ctx context.Context, companyID string, limit, offset int) ([]*Customer, error) - Update(ctx context.Context, customer *Customer) error + GetByCompanyID(ctx context.Context, companyID string, pagination *response.Pagination) ([]*Customer, int64, error) + Search(ctx context.Context, form *SearchCustomersForm, pagination *response.Pagination) ([]Customer, int64, error) Delete(ctx context.Context, id string) error - List(ctx context.Context, limit, offset int) ([]*Customer, error) - Search(ctx context.Context, search string, customerType *string, status *string, companyID *string, industry *string, isVerified *bool, isCompliant *bool, language *string, currency *string, limit, offset int, sortBy, sortOrder string) ([]*Customer, error) - CountByCompanyID(ctx context.Context, companyID string) (int64, error) - CountByType(ctx context.Context, customerType CustomerType) (int64, error) - CountByStatus(ctx context.Context, status CustomerStatus) (int64, error) - UpdateStatus(ctx context.Context, id string, status CustomerStatus) error - UpdateVerification(ctx context.Context, id string, isVerified, isCompliant bool, complianceNotes *string) error } // customerRepository implements the Repository interface using the MongoDB ORM type customerRepository struct { - ormRepo mongopkg.Repository[Customer] + ormRepo orm.Repository[Customer] logger logger.Logger } +func collection() string { + return "customers" +} + // NewCustomerRepository creates a new customer repository -func NewCustomerRepository(mongoManager *mongopkg.ConnectionManager, logger logger.Logger) Repository { +func NewCustomerRepository(mongoManager *orm.ConnectionManager, logger logger.Logger) Repository { // Create indexes using the ORM's index management - indexes := []mongopkg.Index{ - *mongopkg.CreateUniqueIndex("email_idx", bson.D{{Key: "email", Value: 1}}), - *mongopkg.CreateUniqueIndex("username_idx", bson.D{{Key: "username", Value: 1}}), - *mongopkg.NewIndex("company_name_idx", bson.D{{Key: "company_name", Value: 1}}), - *mongopkg.NewIndex("registration_number_idx", bson.D{{Key: "registration_number", Value: 1}}), - *mongopkg.NewIndex("tax_id_idx", bson.D{{Key: "tax_id", Value: 1}}), - *mongopkg.NewIndex("company_ids_idx", bson.D{{Key: "company_ids", Value: 1}}), // Index for CompanyIDs array - *mongopkg.NewIndex("type_idx", bson.D{{Key: "type", Value: 1}}), - *mongopkg.NewIndex("status_idx", bson.D{{Key: "status", Value: 1}}), - *mongopkg.NewIndex("industry_idx", bson.D{{Key: "industry", Value: 1}}), - *mongopkg.NewIndex("is_verified_idx", bson.D{{Key: "is_verified", Value: 1}}), - *mongopkg.NewIndex("is_compliant_idx", bson.D{{Key: "is_compliant", Value: 1}}), - *mongopkg.NewIndex("language_idx", bson.D{{Key: "language", Value: 1}}), - *mongopkg.NewIndex("currency_idx", bson.D{{Key: "currency", Value: 1}}), - *mongopkg.NewIndex("created_at_idx", bson.D{{Key: "created_at", Value: -1}}), - *mongopkg.CreateTextIndex("search_idx", "first_name", "last_name", "full_name", "company_name", "email", "username"), + indexes := []orm.Index{ + *orm.CreateUniqueIndex("username_idx", bson.D{{Key: "username", Value: 1}}), + *orm.CreateTextIndex("search_idx", "full_name", "email", "username", "phone"), } // Create indexes - err := mongoManager.CreateIndexes("customers", indexes) + err := mongoManager.CreateIndexes(collection(), indexes) if err != nil { logger.Warn("Failed to create customer indexes", map[string]interface{}{ "error": err.Error(), @@ -67,7 +52,7 @@ func NewCustomerRepository(mongoManager *mongopkg.ConnectionManager, logger logg } // Create ORM repository - ormRepo := mongopkg.NewRepository[Customer](mongoManager.GetCollection("customers"), logger) + ormRepo := orm.NewRepository[Customer](mongoManager.GetCollection(collection()), logger) return &customerRepository{ ormRepo: ormRepo, @@ -75,17 +60,16 @@ func NewCustomerRepository(mongoManager *mongopkg.ConnectionManager, logger logg } } -// Create creates a new customer -func (r *customerRepository) Create(ctx context.Context, customer *Customer) error { +// Register creates a new customer +func (r *customerRepository) Register(ctx context.Context, customer *Customer) error { // Set created/updated timestamps using Unix timestamps now := time.Now().Unix() customer.SetCreatedAt(now) - customer.SetUpdatedAt(now) // Use ORM to create customer err := r.ormRepo.Create(ctx, customer) if err != nil { - r.logger.Error("Failed to create customer", map[string]interface{}{ + r.logger.Error("Failed to register customer", map[string]interface{}{ "error": err.Error(), "email": customer.Email, "username": customer.Username, @@ -93,7 +77,7 @@ func (r *customerRepository) Create(ctx context.Context, customer *Customer) err return err } - r.logger.Info("Customer created successfully", map[string]interface{}{ + r.logger.Info("Customer registered successfully", map[string]interface{}{ "customer_id": customer.ID, "email": customer.Email, "type": customer.Type, @@ -102,11 +86,40 @@ func (r *customerRepository) Create(ctx context.Context, customer *Customer) err return nil } +// Login updates the last login timestamp +func (r *customerRepository) Login(ctx context.Context, id string) error { + // Get customer first + customer, err := r.GetByID(ctx, id) + if err != nil { + return err + } + + // Update last login timestamp + customer.LastLoginAt = &[]int64{time.Now().Unix()}[0] + customer.SetUpdatedAt(time.Now().Unix()) + + // Update in database + err = r.ormRepo.Update(ctx, customer) + if err != nil { + r.logger.Error("Failed to update last login", map[string]interface{}{ + "error": err.Error(), + "customer_id": id, + }) + return err + } + + r.logger.Info("Last login updated successfully", map[string]interface{}{ + "customer_id": id, + }) + + return nil +} + // GetByID retrieves a customer by ID func (r *customerRepository) GetByID(ctx context.Context, id string) (*Customer, error) { customer, err := r.ormRepo.FindByID(ctx, id) if err != nil { - if errors.Is(err, mongopkg.ErrDocumentNotFound) { + if errors.Is(err, orm.ErrDocumentNotFound) { return nil, errors.New("customer not found") } r.logger.Error("Failed to get customer by ID", map[string]interface{}{ @@ -124,7 +137,7 @@ func (r *customerRepository) GetByEmail(ctx context.Context, email string) (*Cus filter := bson.M{"email": email} customer, err := r.ormRepo.FindOne(ctx, filter) if err != nil { - if errors.Is(err, mongopkg.ErrDocumentNotFound) { + if errors.Is(err, orm.ErrDocumentNotFound) { return nil, errors.New("customer not found") } r.logger.Error("Failed to get customer by email", map[string]interface{}{ @@ -142,7 +155,7 @@ func (r *customerRepository) GetByUsername(ctx context.Context, username string) filter := bson.M{"username": username} customer, err := r.ormRepo.FindOne(ctx, filter) if err != nil { - if errors.Is(err, mongopkg.ErrDocumentNotFound) { + if errors.Is(err, orm.ErrDocumentNotFound) { return nil, errors.New("customer not found") } r.logger.Error("Failed to get customer by username", map[string]interface{}{ @@ -155,67 +168,13 @@ func (r *customerRepository) GetByUsername(ctx context.Context, username string) return customer, nil } -// GetByCompanyName retrieves a customer by company name -func (r *customerRepository) GetByCompanyName(ctx context.Context, companyName string) (*Customer, error) { - filter := bson.M{"company_name": companyName} - customer, err := r.ormRepo.FindOne(ctx, filter) - if err != nil { - if errors.Is(err, mongopkg.ErrDocumentNotFound) { - return nil, errors.New("customer not found") - } - r.logger.Error("Failed to get customer by company name", map[string]interface{}{ - "error": err.Error(), - "company_name": companyName, - }) - return nil, err - } - - return customer, nil -} - -// GetByRegistrationNumber retrieves a customer by registration number -func (r *customerRepository) GetByRegistrationNumber(ctx context.Context, registrationNumber string) (*Customer, error) { - filter := bson.M{"registration_number": registrationNumber} - customer, err := r.ormRepo.FindOne(ctx, filter) - if err != nil { - if errors.Is(err, mongopkg.ErrDocumentNotFound) { - return nil, errors.New("customer not found") - } - r.logger.Error("Failed to get customer by registration number", map[string]interface{}{ - "error": err.Error(), - "registration_number": registrationNumber, - }) - return nil, err - } - - return customer, nil -} - -// GetByTaxID retrieves a customer by tax ID -func (r *customerRepository) GetByTaxID(ctx context.Context, taxID string) (*Customer, error) { - filter := bson.M{"tax_id": taxID} - customer, err := r.ormRepo.FindOne(ctx, filter) - if err != nil { - if errors.Is(err, mongopkg.ErrDocumentNotFound) { - return nil, errors.New("customer not found") - } - r.logger.Error("Failed to get customer by tax ID", map[string]interface{}{ - "error": err.Error(), - "tax_id": taxID, - }) - return nil, err - } - - return customer, nil -} - // GetByCompanyID retrieves customers by company ID with pagination -func (r *customerRepository) GetByCompanyID(ctx context.Context, companyID string, limit, offset int) ([]*Customer, error) { +func (r *customerRepository) GetByCompanyID(ctx context.Context, companyID string, pagination *response.Pagination) ([]*Customer, int64, error) { // Build pagination - pagination := mongopkg.NewPaginationBuilder(). - Limit(limit). - Skip(offset). - SortDesc("created_at"). + paginationBuilder := orm.NewPaginationBuilder(). + Limit(pagination.Limit). + Skip(pagination.Offset). + SortBy(pagination.SortBy, pagination.SortOrder). Build() // Filter by company ID and active status @@ -225,15 +184,15 @@ func (r *customerRepository) GetByCompanyID(ctx context.Context, companyID strin } // Use ORM to find customers - result, err := r.ormRepo.FindAll(ctx, filter, pagination) + result, err := r.ormRepo.FindAll(ctx, filter, paginationBuilder) if err != nil { r.logger.Error("Failed to get customers by company ID", map[string]interface{}{ "error": err.Error(), "company_id": companyID, - "limit": limit, - "offset": offset, + "limit": pagination.Limit, + "offset": pagination.Offset, }) - return nil, err + return nil, 0, err } // Convert []Customer to []*Customer @@ -242,7 +201,7 @@ func (r *customerRepository) GetByCompanyID(ctx context.Context, companyID strin customers[i] = &result.Items[i] } - return customers, nil + return customers, result.TotalCount, nil } // Update updates a customer @@ -268,20 +227,24 @@ func (r *customerRepository) Update(ctx context.Context, customer *Customer) err return nil } -// Delete deletes a customer (soft delete by setting status to inactive) +// Delete deletes a customer (hard delete) func (r *customerRepository) Delete(ctx context.Context, id string) error { // Get customer first - customer, err := r.GetByID(ctx, id) + _, err := r.GetByID(ctx, id) if err != nil { + if errors.Is(err, orm.ErrDocumentNotFound) { + return errors.New("customer not found") + } + r.logger.Error("Failed to delete customer", map[string]interface{}{ + "error": err.Error(), + "customer_id": id, + }) + return err } - // Update status to inactive - customer.Status = CustomerStatusInactive - customer.SetUpdatedAt(time.Now().Unix()) - // Update in database - err = r.ormRepo.Update(ctx, customer) + err = r.ormRepo.Delete(ctx, id) if err != nil { r.logger.Error("Failed to delete customer", map[string]interface{}{ "error": err.Error(), @@ -297,166 +260,44 @@ func (r *customerRepository) Delete(ctx context.Context, id string) error { return nil } -// List retrieves customers with pagination -func (r *customerRepository) List(ctx context.Context, limit, offset int) ([]*Customer, error) { - // Build pagination - pagination := mongopkg.NewPaginationBuilder(). - Limit(limit). - Skip(offset). - SortDesc("created_at"). - Build() - - // Only active customers by default - filter := bson.M{"status": bson.M{"$ne": CustomerStatusInactive}} - - // Use ORM to find customers - result, err := r.ormRepo.FindAll(ctx, filter, pagination) - if err != nil { - r.logger.Error("Failed to list customers", map[string]interface{}{ - "error": err.Error(), - "limit": limit, - "offset": offset, - }) - return nil, err - } - - // Convert []Customer to []*Customer - customers := make([]*Customer, len(result.Items)) - for i := range result.Items { - customers[i] = &result.Items[i] - } - - return customers, nil -} - // Search retrieves customers with search and filters -func (r *customerRepository) Search(ctx context.Context, search string, customerType *string, status *string, companyID *string, industry *string, isVerified *bool, isCompliant *bool, language *string, currency *string, limit, offset int, sortBy, sortOrder string) ([]*Customer, error) { +func (r *customerRepository) Search(ctx context.Context, form *SearchCustomersForm, pagination *response.Pagination) ([]Customer, int64, error) { // Build filter filter := bson.M{} - if search != "" { - filter["$text"] = bson.M{"$search": search} + if form.Search != nil { + filter["$text"] = bson.M{"$search": *form.Search} } - if customerType != nil { - filter["type"] = *customerType + if form.Type != nil { + filter["type"] = *form.Type } - if status != nil { - filter["status"] = *status + if form.Status != nil { + filter["status"] = *form.Status } - if companyID != nil { - filter["company_id"] = *companyID - } - - if industry != nil { - filter["industry"] = *industry - } - - if isVerified != nil { - filter["is_verified"] = *isVerified - } - - if isCompliant != nil { - filter["is_compliant"] = *isCompliant - } - - if language != nil { - filter["language"] = *language - } - - if currency != nil { - filter["currency"] = *currency + if form.CompanyID != nil { + filter["company_id"] = *form.CompanyID } // Build pagination - pagination := mongopkg.NewPaginationBuilder(). - Limit(limit). - Skip(offset) - - // Set sort - if sortBy != "" { - if sortOrder == "desc" { - pagination.SortDesc(sortBy) - } else { - pagination.SortAsc(sortBy) - } - } else { - pagination.SortDesc("created_at") // Default sort - } + paginationBuilder := orm.NewPaginationBuilder(). + Limit(pagination.Limit). + Skip(pagination.Offset). + SortBy(pagination.SortBy, pagination.SortOrder) // Use ORM to find customers - result, err := r.ormRepo.FindAll(ctx, filter, pagination.Build()) + result, err := r.ormRepo.FindAll(ctx, filter, paginationBuilder.Build()) if err != nil { r.logger.Error("Failed to search customers", map[string]interface{}{ "error": err.Error(), - "search": search, + "search": form.Search, }) - return nil, err + return nil, 0, err } - // Convert []Customer to []*Customer - customers := make([]*Customer, len(result.Items)) - for i := range result.Items { - customers[i] = &result.Items[i] - } - - return customers, nil -} - -// CountByCompanyID counts customers by company ID -func (r *customerRepository) CountByCompanyID(ctx context.Context, companyID string) (int64, error) { - filter := bson.M{ - "company_id": companyID, - "status": bson.M{"$ne": CustomerStatusInactive}, - } - - count, err := r.ormRepo.Count(ctx, filter) - if err != nil { - r.logger.Error("Failed to count customers by company ID", map[string]interface{}{ - "error": err.Error(), - "company_id": companyID, - }) - return 0, err - } - - return count, nil -} - -// CountByType counts customers by type -func (r *customerRepository) CountByType(ctx context.Context, customerType CustomerType) (int64, error) { - filter := bson.M{ - "type": customerType, - "status": bson.M{"$ne": CustomerStatusInactive}, - } - - count, err := r.ormRepo.Count(ctx, filter) - if err != nil { - r.logger.Error("Failed to count customers by type", map[string]interface{}{ - "error": err.Error(), - "customer_type": customerType, - }) - return 0, err - } - - return count, nil -} - -// CountByStatus counts customers by status -func (r *customerRepository) CountByStatus(ctx context.Context, status CustomerStatus) (int64, error) { - filter := bson.M{"status": status} - - count, err := r.ormRepo.Count(ctx, filter) - if err != nil { - r.logger.Error("Failed to count customers by status", map[string]interface{}{ - "error": err.Error(), - "status": status, - }) - return 0, err - } - - return count, nil + return result.Items, result.TotalCount, nil } // UpdateStatus updates customer status @@ -489,36 +330,3 @@ func (r *customerRepository) UpdateStatus(ctx context.Context, id string, status return nil } - -// UpdateVerification updates customer verification status -func (r *customerRepository) UpdateVerification(ctx context.Context, id string, isVerified, isCompliant bool, complianceNotes *string) error { - // Get customer first - customer, err := r.GetByID(ctx, id) - if err != nil { - return err - } - - // Update verification fields - customer.IsVerified = isVerified - customer.IsCompliant = isCompliant - customer.ComplianceNotes = complianceNotes - customer.SetUpdatedAt(time.Now().Unix()) - - // Update in database - err = r.ormRepo.Update(ctx, customer) - if err != nil { - r.logger.Error("Failed to update customer verification", map[string]interface{}{ - "error": err.Error(), - "customer_id": id, - }) - return err - } - - r.logger.Info("Customer verification updated successfully", map[string]interface{}{ - "customer_id": id, - "is_verified": isVerified, - "is_compliant": isCompliant, - }) - - return nil -} diff --git a/internal/customer/service.go b/internal/customer/service.go index 43e9fee..25acb04 100644 --- a/internal/customer/service.go +++ b/internal/customer/service.go @@ -7,6 +7,7 @@ import ( "tm/internal/company" "tm/pkg/logger" + "tm/pkg/response" "tm/pkg/authorization" @@ -17,41 +18,18 @@ import ( // Service defines business logic for customer operations type Service interface { // Core customer operations - CreateCustomer(ctx context.Context, form *CreateCustomerForm, createdBy *string) (*Customer, error) - GetCustomerByID(ctx context.Context, id string) (*Customer, error) - GetCustomerByIDWithCompanies(ctx context.Context, id string) (*CustomerResponse, error) - UpdateCustomer(ctx context.Context, id string, form *UpdateCustomerForm, updatedBy *string) (*Customer, error) - DeleteCustomer(ctx context.Context, id string, deletedBy *string) error - - // Customer listing and search - ListCustomers(ctx context.Context, form *ListCustomersForm) (*CustomerListResponse, error) - GetCustomersByCompanyID(ctx context.Context, companyID string, limit, offset int) ([]*Customer, int64, error) - GetCustomersByType(ctx context.Context, customerType CustomerType, limit, offset int) ([]*Customer, int64, error) - GetCustomersByStatus(ctx context.Context, status CustomerStatus, limit, offset int) ([]*Customer, int64, error) - - // Customer management - UpdateCustomerStatus(ctx context.Context, id string, form *UpdateCustomerStatusForm, updatedBy *string) error - UpdateCustomerVerification(ctx context.Context, id string, form *UpdateCustomerVerificationForm, updatedBy *string) error - - // Company assignments - AssignCompaniesToCustomer(ctx context.Context, customerID string, companyIDs []string, updatedBy *string) error - RemoveCompaniesFromCustomer(ctx context.Context, customerID string, companyIDs []string, updatedBy *string) error - - // Mobile-specific operations (simplified) - CreateCustomerMobile(ctx context.Context, form *CreateCustomerMobileForm, createdBy *string) (*Customer, error) - UpdateCustomerMobile(ctx context.Context, id string, form *UpdateCustomerMobileForm, updatedBy *string) (*Customer, error) - - // Business operations - VerifyCustomer(ctx context.Context, id string, verifiedBy *string) error - SuspendCustomer(ctx context.Context, id string, reason string, suspendedBy *string) error - ActivateCustomer(ctx context.Context, id string, activatedBy *string) error + Register(ctx context.Context, form *CreateCustomerForm) (*CustomerResponse, error) + GetByID(ctx context.Context, id string) (*CustomerResponse, error) + Search(ctx context.Context, form *SearchCustomersForm, pagination *response.Pagination) (*CustomerListResponse, error) + Update(ctx context.Context, id string, form *UpdateCustomerForm) (*CustomerResponse, error) + Delete(ctx context.Context, id string) error + UpdateStatus(ctx context.Context, id string, form *UpdateStatusForm) error // Authentication operations Login(ctx context.Context, form *LoginForm) (*AuthResponse, error) RefreshToken(ctx context.Context, refreshToken string) (*AuthResponse, error) - GetProfile(ctx context.Context, customerID string) (*Customer, error) - GetProfileWithCompanies(ctx context.Context, customerID string) (*CustomerResponse, error) - Logout(ctx context.Context, customerID string, accessToken string) error + GetProfileWithCompanies(ctx context.Context, id string) (*CustomerResponse, error) + Logout(ctx context.Context, id string, accessToken string) error } // customerService implements the Service interface @@ -72,34 +50,17 @@ func NewCustomerService(repository Repository, logger logger.Logger, authService } } -// CreateCustomer creates a new customer -func (s *customerService) CreateCustomer(ctx context.Context, form *CreateCustomerForm, createdBy *string) (*Customer, error) { +// Register creates a new customer +func (s *customerService) Register(ctx context.Context, form *CreateCustomerForm) (*CustomerResponse, error) { // Check if email already exists existingCustomer, _ := s.repository.GetByEmail(ctx, form.Email) if existingCustomer != nil { return nil, errors.New("customer with this email already exists") } - // Check if registration number already exists (for company customers) - if form.Type == string(CustomerTypeCompany) && form.RegistrationNumber != nil { - existingCustomer, _ = s.repository.GetByRegistrationNumber(ctx, *form.RegistrationNumber) - if existingCustomer != nil { - return nil, errors.New("company with this registration number already exists") - } - } - - // Check if tax ID already exists (for company customers) - if form.Type == string(CustomerTypeCompany) && form.TaxID != nil { - existingCustomer, _ = s.repository.GetByTaxID(ctx, *form.TaxID) - if existingCustomer != nil { - return nil, errors.New("company with this tax ID already exists") - } - } - - // Prepare company assignments + // Verify that all company IDs exist var companyIDs []string if len(form.CompanyIDs) > 0 { - // Verify that all company IDs exist for _, companyID := range form.CompanyIDs { _, err := s.companyService.GetCompanyByID(ctx, companyID) if err != nil { @@ -124,36 +85,18 @@ func (s *customerService) CreateCustomer(ctx context.Context, form *CreateCustom // Create customer customer := &Customer{ - Type: CustomerType(form.Type), - Status: CustomerStatusPending, - CompanyIDs: companyIDs, - FirstName: form.FirstName, - LastName: form.LastName, - FullName: form.FullName, - Username: form.Username, - Email: form.Email, - Password: string(hashedPassword), - Phone: form.Phone, - Mobile: form.Mobile, - RegistrationNumber: form.RegistrationNumber, - TaxID: form.TaxID, - Industry: form.Industry, - Address: s.convertAddressForm(form.Address), - BusinessType: form.BusinessType, - EmployeeCount: form.EmployeeCount, - AnnualRevenue: form.AnnualRevenue, - FoundedYear: form.FoundedYear, - ContactPerson: s.convertContactPersonForm(form.ContactPerson), - IsVerified: false, - IsCompliant: false, - Language: s.getDefaultLanguage(form.Language), - Currency: s.getDefaultCurrency(form.Currency), - Timezone: s.getDefaultTimezone(form.Timezone), - CreatedBy: createdBy, + Type: CustomerType(form.Type), + Status: CustomerStatusActive, + CompanyIDs: companyIDs, + FullName: form.FullName, + Username: form.Username, + Email: form.Email, + Password: string(hashedPassword), + Phone: form.Phone, } // Save to database - err = s.repository.Create(ctx, customer) + err = s.repository.Register(ctx, customer) if err != nil { s.logger.Error("Failed to create customer", map[string]interface{}{ "error": err.Error(), @@ -168,14 +111,13 @@ func (s *customerService) CreateCustomer(ctx context.Context, form *CreateCustom "email": customer.Email, "type": customer.Type, "company_ids": companyIDs, - "created_by": *createdBy, }) - return customer, nil + return customer.ToResponse(nil), nil } -// GetCustomerByID retrieves a customer by ID -func (s *customerService) GetCustomerByID(ctx context.Context, id string) (*Customer, error) { +// GetByID retrieves a customer by ID +func (s *customerService) GetByID(ctx context.Context, id string) (*CustomerResponse, error) { customer, err := s.repository.GetByID(ctx, id) if err != nil { s.logger.Error("Failed to get customer by ID", map[string]interface{}{ @@ -185,48 +127,56 @@ func (s *customerService) GetCustomerByID(ctx context.Context, id string) (*Cust return nil, err } + var companies []*CompanySummary + if len(customer.CompanyIDs) > 0 { + companies, err = s.loadCompaniesForCustomer(ctx, customer) + if err != nil { + s.logger.Error("Failed to load companies for customer", map[string]interface{}{ + "error": err.Error(), + "customer_id": customer.ID, + }) + } + } + s.logger.Info("Customer retrieved successfully", map[string]interface{}{ "customer_id": customer.ID, "email": customer.Email, }) - return customer, nil + return customer.ToResponse(companies), nil } -// GetCustomerByIDWithCompanies retrieves a customer by ID and loads associated companies -func (s *customerService) GetCustomerByIDWithCompanies(ctx context.Context, id string) (*CustomerResponse, error) { - customer, err := s.repository.GetByID(ctx, id) +// Search searches for customers +func (s *customerService) Search(ctx context.Context, form *SearchCustomersForm, pagination *response.Pagination) (*CustomerListResponse, error) { + customers, total, err := s.repository.Search(ctx, form, pagination) if err != nil { - s.logger.Error("Failed to get customer by ID with companies", map[string]interface{}{ - "error": err.Error(), - "customer_id": id, + s.logger.Error("Failed to search customers", map[string]interface{}{ + "error": err.Error(), }) - return nil, err + return nil, errors.New("failed to search customers") } - // Load companies for the customer - companies, err := s.loadCompaniesForCustomer(ctx, customer) - if err != nil { - s.logger.Error("Failed to load companies for customer", map[string]interface{}{ - "error": err.Error(), - "customer_id": customer.ID, - }) - // Continue without companies if loading fails - companies = []*CompanySummary{} + var customerResponses []*CustomerResponse + for _, customer := range customers { + companies, err := s.loadCompaniesForCustomer(ctx, &customer) + if err != nil { + s.logger.Error("Failed to load companies for customer", map[string]interface{}{ + "error": err.Error(), + "customer_id": customer.ID, + }) + } + + customerResponses = append(customerResponses, customer.ToResponse(companies)) } - customerResponse := customer.ToResponseWithCompanies(companies) - - s.logger.Info("Customer retrieved with companies successfully", map[string]interface{}{ - "customer_id": customer.ID, - "email": customer.Email, - }) - - return customerResponse, nil + return &CustomerListResponse{ + Customers: customerResponses, + Meta: pagination.Response(total), + }, nil } -// UpdateCustomer updates a customer -func (s *customerService) UpdateCustomer(ctx context.Context, id string, form *UpdateCustomerForm, updatedBy *string) (*Customer, error) { +// Update updates a customer +func (s *customerService) Update(ctx context.Context, id string, form *UpdateCustomerForm) (*CustomerResponse, error) { // Get existing customer customer, err := s.repository.GetByID(ctx, id) if err != nil { @@ -238,44 +188,21 @@ func (s *customerService) UpdateCustomer(ctx context.Context, id string, form *U } // Check if email already exists (if changing email) - if form.Email != nil && *form.Email != customer.Email { - existingCustomer, _ := s.repository.GetByEmail(ctx, *form.Email) + if form.Email != "" && form.Email != customer.Email { + existingCustomer, _ := s.repository.GetByEmail(ctx, form.Email) if existingCustomer != nil { return nil, errors.New("customer with this email already exists") } - customer.Email = *form.Email + customer.Email = form.Email } // Check if username already exists (if changing username) - if form.Username != nil && *form.Username != customer.Username { - existingCustomer, _ := s.repository.GetByUsername(ctx, *form.Username) + if form.Username != "" && form.Username != customer.Username { + existingCustomer, _ := s.repository.GetByUsername(ctx, form.Username) if existingCustomer != nil { return nil, errors.New("customer with this username already exists") } - customer.Username = *form.Username - } - - // Check if registration number already exists (if changing) - if form.RegistrationNumber != nil && *form.RegistrationNumber != *customer.RegistrationNumber { - existingCustomer, _ := s.repository.GetByRegistrationNumber(ctx, *form.RegistrationNumber) - if existingCustomer != nil { - return nil, errors.New("company with this registration number already exists") - } - customer.RegistrationNumber = form.RegistrationNumber - } - - // Check if tax ID already exists (if changing) - if form.TaxID != nil && *form.TaxID != *customer.TaxID { - existingCustomer, _ := s.repository.GetByTaxID(ctx, *form.TaxID) - if existingCustomer != nil { - return nil, errors.New("company with this tax ID already exists") - } - customer.TaxID = form.TaxID - } - - // Update fields if provided - if form.Type != nil { - customer.Type = CustomerType(*form.Type) + customer.Username = form.Username } // Handle company assignments @@ -296,14 +223,6 @@ func (s *customerService) UpdateCustomer(ctx context.Context, id string, form *U customer.CompanyIDs = validCompanyIDs } - if form.FirstName != nil { - customer.FirstName = form.FirstName - } - - if form.LastName != nil { - customer.LastName = form.LastName - } - if form.FullName != nil { customer.FullName = form.FullName } @@ -312,54 +231,6 @@ func (s *customerService) UpdateCustomer(ctx context.Context, id string, form *U customer.Phone = form.Phone } - if form.Mobile != nil { - customer.Mobile = form.Mobile - } - - if form.Industry != nil { - customer.Industry = form.Industry - } - - if form.Address != nil { - customer.Address = s.convertAddressForm(form.Address) - } - - if form.BusinessType != nil { - customer.BusinessType = form.BusinessType - } - - if form.EmployeeCount != nil { - customer.EmployeeCount = form.EmployeeCount - } - - if form.AnnualRevenue != nil { - customer.AnnualRevenue = form.AnnualRevenue - } - - if form.FoundedYear != nil { - customer.FoundedYear = form.FoundedYear - } - - if form.ContactPerson != nil { - customer.ContactPerson = s.convertContactPersonForm(form.ContactPerson) - } - - if form.Language != nil { - customer.Language = *form.Language - } - - if form.Currency != nil { - customer.Currency = *form.Currency - } - - if form.Timezone != nil { - customer.Timezone = *form.Timezone - } - - // Set updated by - customer.UpdatedBy = updatedBy - customer.UpdatedAt = time.Now().Unix() - // Update in database err = s.repository.Update(ctx, customer) if err != nil { @@ -373,23 +244,23 @@ func (s *customerService) UpdateCustomer(ctx context.Context, id string, form *U s.logger.Info("Customer updated successfully", map[string]interface{}{ "customer_id": customer.ID, "email": customer.Email, - "updated_by": *updatedBy, }) - return customer, nil + return customer.ToResponse(nil), nil } -// DeleteCustomer deletes a customer (soft delete) -func (s *customerService) DeleteCustomer(ctx context.Context, id string, deletedBy *string) error { +// Delete deletes a customer (soft delete) +func (s *customerService) Delete(ctx context.Context, id string) error { // Get customer to check if exists - customer, err := s.repository.GetByID(ctx, id) + _, err := s.repository.GetByID(ctx, id) if err != nil { + s.logger.Error("Failed to get customer for delete", map[string]interface{}{ + "error": err.Error(), + "customer_id": id, + }) return errors.New("customer not found") } - // Set updated by before deletion - customer.UpdatedBy = deletedBy - // Delete customer (soft delete) err = s.repository.Delete(ctx, id) if err != nil { @@ -402,154 +273,13 @@ func (s *customerService) DeleteCustomer(ctx context.Context, id string, deleted s.logger.Info("Customer deleted successfully", map[string]interface{}{ "customer_id": id, - "deleted_by": deletedBy, }) return nil } -// ListCustomers lists customers with search and filters -func (s *customerService) ListCustomers(ctx context.Context, form *ListCustomersForm) (*CustomerListResponse, error) { - // Set defaults - limit := 20 - if form.Limit != nil { - limit = *form.Limit - } - - offset := 0 - if form.Offset != nil { - offset = *form.Offset - } - - search := "" - if form.Search != nil { - search = *form.Search - } - - sortBy := "created_at" - if form.SortBy != nil { - sortBy = *form.SortBy - } - - sortOrder := "desc" - if form.SortOrder != nil { - sortOrder = *form.SortOrder - } - - // Parse company ID if provided - var companyID *string - if form.CompanyID != nil { - companyID = form.CompanyID - } - - // Get customers - customers, err := s.repository.Search(ctx, search, form.Type, form.Status, companyID, form.Industry, form.IsVerified, form.IsCompliant, form.Language, form.Currency, limit, offset, sortBy, sortOrder) - if err != nil { - s.logger.Error("Failed to list customers", map[string]interface{}{ - "error": err.Error(), - }) - return nil, errors.New("failed to list customers") - } - - // Convert to responses - var customerResponses []*CustomerResponse - for _, customer := range customers { - customerResponses = append(customerResponses, customer.ToResponse()) - } - - // TODO: Implement proper count for pagination metadata - total := int64(len(customers)) // This should be a separate count query - totalPages := (total + int64(limit) - 1) / int64(limit) - - return &CustomerListResponse{ - Customers: customerResponses, - Total: total, - Limit: limit, - Offset: offset, - TotalPages: int(totalPages), - }, nil -} - -// GetCustomersByCompanyID retrieves customers by company ID with pagination -func (s *customerService) GetCustomersByCompanyID(ctx context.Context, companyID string, limit, offset int) ([]*Customer, int64, error) { - customers, err := s.repository.GetByCompanyID(ctx, companyID, limit, offset) - if err != nil { - s.logger.Error("Failed to get customers by company ID", map[string]interface{}{ - "error": err.Error(), - "company_id": companyID, - }) - return nil, 0, errors.New("failed to get customers by company") - } - - // Get total count - total, err := s.repository.CountByCompanyID(ctx, companyID) - if err != nil { - s.logger.Error("Failed to count customers by company ID", map[string]interface{}{ - "error": err.Error(), - "company_id": companyID, - }) - return customers, 0, nil // Return customers even if count fails - } - - return customers, total, nil -} - -// GetCustomersByType retrieves customers by type with pagination -func (s *customerService) GetCustomersByType(ctx context.Context, customerType CustomerType, limit, offset int) ([]*Customer, int64, error) { - // This would need to be implemented in the repository - // For now, we'll use the search method - customerTypeStr := string(customerType) - customers, err := s.repository.Search(ctx, "", &customerTypeStr, nil, nil, nil, nil, nil, nil, nil, limit, offset, "created_at", "desc") - if err != nil { - s.logger.Error("Failed to get customers by type", map[string]interface{}{ - "error": err.Error(), - "customer_type": customerType, - }) - return nil, 0, errors.New("failed to get customers by type") - } - - // Get total count - total, err := s.repository.CountByType(ctx, customerType) - if err != nil { - s.logger.Error("Failed to count customers by type", map[string]interface{}{ - "error": err.Error(), - "customer_type": customerType, - }) - return customers, 0, nil - } - - return customers, total, nil -} - -// GetCustomersByStatus retrieves customers by status with pagination -func (s *customerService) GetCustomersByStatus(ctx context.Context, status CustomerStatus, limit, offset int) ([]*Customer, int64, error) { - // This would need to be implemented in the repository - // For now, we'll use the search method - statusStr := string(status) - customers, err := s.repository.Search(ctx, "", nil, &statusStr, nil, nil, nil, nil, nil, nil, limit, offset, "created_at", "desc") - if err != nil { - s.logger.Error("Failed to get customers by status", map[string]interface{}{ - "error": err.Error(), - "status": status, - }) - return nil, 0, errors.New("failed to get customers by status") - } - - // Get total count - total, err := s.repository.CountByStatus(ctx, status) - if err != nil { - s.logger.Error("Failed to count customers by status", map[string]interface{}{ - "error": err.Error(), - "status": status, - }) - return customers, 0, nil - } - - return customers, total, nil -} - -// UpdateCustomerStatus updates customer status -func (s *customerService) UpdateCustomerStatus(ctx context.Context, id string, form *UpdateCustomerStatusForm, updatedBy *string) error { +// UpdateStatus updates customer status +func (s *customerService) UpdateStatus(ctx context.Context, id string, form *UpdateStatusForm) error { // Get customer to check if exists _, err := s.repository.GetByID(ctx, id) if err != nil { @@ -571,42 +301,13 @@ func (s *customerService) UpdateCustomerStatus(ctx context.Context, id string, f s.logger.Info("Customer status updated successfully", map[string]interface{}{ "customer_id": id, "status": form.Status, - "updated_by": updatedBy, }) return nil } -// UpdateCustomerVerification updates customer verification status -func (s *customerService) UpdateCustomerVerification(ctx context.Context, id string, form *UpdateCustomerVerificationForm, updatedBy *string) error { - // Get customer to check if exists - _, err := s.repository.GetByID(ctx, id) - if err != nil { - return errors.New("customer not found") - } - - // Update verification - err = s.repository.UpdateVerification(ctx, id, form.IsVerified, form.IsCompliant, form.ComplianceNotes) - if err != nil { - s.logger.Error("Failed to update customer verification", map[string]interface{}{ - "error": err.Error(), - "customer_id": id, - }) - return errors.New("failed to update customer verification") - } - - s.logger.Info("Customer verification updated successfully", map[string]interface{}{ - "customer_id": id, - "is_verified": form.IsVerified, - "is_compliant": form.IsCompliant, - "updated_by": updatedBy, - }) - - return nil -} - -// AssignCompaniesToCustomer assigns companies to a customer -func (s *customerService) AssignCompaniesToCustomer(ctx context.Context, customerID string, companyIDs []string, updatedBy *string) error { +// AssignCompanies assigns companies to a customer +func (s *customerService) AssignCompanies(ctx context.Context, customerID string, companyIDs []string) error { // Get customer to check if exists customer, err := s.repository.GetByID(ctx, customerID) if err != nil { @@ -630,7 +331,6 @@ func (s *customerService) AssignCompaniesToCustomer(ctx context.Context, custome // Update customer's company IDs customer.CompanyIDs = append(customer.CompanyIDs, validCompanyIDs...) - customer.UpdatedBy = updatedBy customer.UpdatedAt = time.Now().Unix() err = s.repository.Update(ctx, customer) @@ -645,14 +345,13 @@ func (s *customerService) AssignCompaniesToCustomer(ctx context.Context, custome s.logger.Info("Companies assigned to customer successfully", map[string]interface{}{ "customer_id": customerID, "company_ids": validCompanyIDs, - "updated_by": updatedBy, }) return nil } // RemoveCompaniesFromCustomer removes companies from a customer -func (s *customerService) RemoveCompaniesFromCustomer(ctx context.Context, customerID string, companyIDs []string, updatedBy *string) error { +func (s *customerService) RemoveCompanies(ctx context.Context, customerID string, companyIDs []string) error { // Get customer to check if exists customer, err := s.repository.GetByID(ctx, customerID) if err != nil { @@ -676,7 +375,6 @@ func (s *customerService) RemoveCompaniesFromCustomer(ctx context.Context, custo // Update customer's company IDs customer.CompanyIDs = updatedCompanyIDs - customer.UpdatedBy = updatedBy customer.UpdatedAt = time.Now().Unix() err = s.repository.Update(ctx, customer) @@ -691,194 +389,13 @@ func (s *customerService) RemoveCompaniesFromCustomer(ctx context.Context, custo s.logger.Info("Companies removed from customer successfully", map[string]interface{}{ "customer_id": customerID, "company_ids": companyIDs, - "updated_by": updatedBy, }) return nil } -// CreateCustomerMobile creates a new customer via mobile app (simplified) -func (s *customerService) CreateCustomerMobile(ctx context.Context, form *CreateCustomerMobileForm, createdBy *string) (*Customer, error) { - // Check if email already exists - existingCustomer, _ := s.repository.GetByEmail(ctx, form.Email) - if existingCustomer != nil { - return nil, errors.New("customer with this email already exists") - } - - // Prepare company assignments - var companyIDs []string - if len(form.CompanyIDs) > 0 { - // Verify that all company IDs exist - for _, companyID := range form.CompanyIDs { - _, err := s.companyService.GetCompanyByID(ctx, companyID) - if err != nil { - s.logger.Error("Invalid company ID provided in mobile form", map[string]interface{}{ - "error": err.Error(), - "company_id": companyID, - }) - return nil, errors.New("invalid company ID: " + companyID) - } - } - companyIDs = form.CompanyIDs - } - - // Create customer with mobile form - customer := &Customer{ - Type: CustomerType(form.Type), - Status: CustomerStatusActive, - CompanyIDs: companyIDs, - FirstName: form.FirstName, - LastName: form.LastName, - FullName: form.FullName, - Username: form.Username, - Email: form.Email, - Password: "", // Will be set after hashing - Phone: form.Phone, - Mobile: form.Mobile, - Industry: form.Industry, - IsVerified: false, - IsCompliant: false, - Language: "en", - Currency: "USD", - Timezone: "UTC", - CreatedBy: createdBy, - } - - // Hash password - hashedPassword, err := bcrypt.GenerateFromPassword([]byte(form.Password), bcrypt.DefaultCost) - if err != nil { - s.logger.Error("Failed to hash password for mobile customer", map[string]interface{}{ - "error": err.Error(), - }) - return nil, errors.New("failed to process password") - } - customer.Password = string(hashedPassword) - - // Save to database - err = s.repository.Create(ctx, customer) - if err != nil { - s.logger.Error("Failed to create customer via mobile", map[string]interface{}{ - "error": err.Error(), - "email": form.Email, - "type": form.Type, - "created_by": createdBy, - }) - return nil, err - } - - s.logger.Info("Customer created via mobile successfully", map[string]interface{}{ - "customer_id": customer.ID, - "email": customer.Email, - "type": customer.Type, - "company_ids": companyIDs, - "created_by": *createdBy, - }) - - return customer, nil -} - -// UpdateCustomerMobile updates a customer via mobile app (simplified) -func (s *customerService) UpdateCustomerMobile(ctx context.Context, id string, form *UpdateCustomerMobileForm, updatedBy *string) (*Customer, error) { - // Get existing customer - customer, err := s.repository.GetByID(ctx, id) - if err != nil { - return nil, errors.New("customer not found") - } - - // Update fields if provided - if form.FirstName != nil { - customer.FirstName = form.FirstName - } - - if form.LastName != nil { - customer.LastName = form.LastName - } - - if form.FullName != nil { - customer.FullName = form.FullName - } - - if form.Phone != nil { - customer.Phone = form.Phone - } - - if form.Mobile != nil { - customer.Mobile = form.Mobile - } - - // Handle company assignments - if len(form.CompanyIDs) > 0 { - // Verify that all company IDs exist - var validCompanyIDs []string - for _, companyID := range form.CompanyIDs { - _, err := s.companyService.GetCompanyByID(ctx, companyID) - if err != nil { - s.logger.Error("Invalid company ID provided in mobile update", map[string]interface{}{ - "error": err.Error(), - "company_id": companyID, - }) - return nil, errors.New("invalid company ID: " + companyID) - } - validCompanyIDs = append(validCompanyIDs, companyID) - } - customer.CompanyIDs = validCompanyIDs - } - - if form.Industry != nil { - customer.Industry = form.Industry - } - - // Set updated by - customer.UpdatedBy = updatedBy - - // Update in database - err = s.repository.Update(ctx, customer) - if err != nil { - s.logger.Error("Failed to update customer via mobile", map[string]interface{}{ - "error": err.Error(), - "customer_id": id, - }) - return nil, errors.New("failed to update customer") - } - - s.logger.Info("Customer updated via mobile successfully", map[string]interface{}{ - "customer_id": customer.ID, - "email": customer.Email, - "company_ids": customer.CompanyIDs, - "updated_by": *updatedBy, - }) - - return customer, nil -} - -// VerifyCustomer verifies a customer -func (s *customerService) VerifyCustomer(ctx context.Context, id string, verifiedBy *string) error { - // Get customer to check if exists - customer, err := s.repository.GetByID(ctx, id) - if err != nil { - return errors.New("customer not found") - } - - // Update verification - err = s.repository.UpdateVerification(ctx, id, true, customer.IsCompliant, customer.ComplianceNotes) - if err != nil { - s.logger.Error("Failed to verify customer", map[string]interface{}{ - "error": err.Error(), - "customer_id": id, - }) - return errors.New("failed to verify customer") - } - - s.logger.Info("Customer verified successfully", map[string]interface{}{ - "customer_id": id, - "verified_by": verifiedBy, - }) - - return nil -} - -// SuspendCustomer suspends a customer -func (s *customerService) SuspendCustomer(ctx context.Context, id string, reason string, suspendedBy *string) error { +// Suspend suspends a customer +func (s *customerService) Suspend(ctx context.Context, id string, reason string) error { // Get customer to check if exists _, err := s.repository.GetByID(ctx, id) if err != nil { @@ -896,16 +413,15 @@ func (s *customerService) SuspendCustomer(ctx context.Context, id string, reason } s.logger.Info("Customer suspended successfully", map[string]interface{}{ - "customer_id": id, - "reason": reason, - "suspended_by": suspendedBy, + "customer_id": id, + "reason": reason, }) return nil } -// ActivateCustomer activates a customer -func (s *customerService) ActivateCustomer(ctx context.Context, id string, activatedBy *string) error { +// Activate activates a customer +func (s *customerService) Activate(ctx context.Context, id string) error { // Get customer to check if exists _, err := s.repository.GetByID(ctx, id) if err != nil { @@ -923,8 +439,7 @@ func (s *customerService) ActivateCustomer(ctx context.Context, id string, activ } s.logger.Info("Customer activated successfully", map[string]interface{}{ - "customer_id": id, - "activated_by": activatedBy, + "customer_id": id, }) return nil @@ -985,6 +500,22 @@ func (s *customerService) Login(ctx context.Context, form *LoginForm) (*AuthResp return nil, errors.New("failed to generate authentication tokens") } + err = s.repository.Login(ctx, customer.ID.Hex()) + if err != nil { + s.logger.Error("Failed to update customer last login", map[string]interface{}{ + "error": err.Error(), + "customer_id": customer.ID, + }) + } + + companies, err := s.loadCompaniesForCustomer(ctx, customer) + if err != nil { + s.logger.Error("Failed to load companies for customer", map[string]interface{}{ + "error": err.Error(), + "customer_id": customer.ID, + }) + } + s.logger.Info("Customer login successful", map[string]interface{}{ "username": form.Username, "customer_id": customer.ID, @@ -992,7 +523,7 @@ func (s *customerService) Login(ctx context.Context, form *LoginForm) (*AuthResp }) return &AuthResponse{ - Customer: customer.ToResponse(), + Customer: customer.ToResponse(companies), AccessToken: tokenPair.AccessToken, RefreshToken: tokenPair.RefreshToken, ExpiresAt: time.Now().Add(15 * time.Minute).Unix(), // 15 minutes from now @@ -1053,13 +584,21 @@ func (s *customerService) RefreshToken(ctx context.Context, refreshToken string) return nil, errors.New("customer account is inactive") } + companies, err := s.loadCompaniesForCustomer(ctx, customer) + if err != nil { + s.logger.Error("Failed to load companies for customer", map[string]interface{}{ + "error": err.Error(), + "customer_id": customer.ID, + }) + } + s.logger.Info("Access token refreshed successfully", map[string]interface{}{ "customer_id": customer.ID, "customer_type": string(customer.Type), }) return &AuthResponse{ - Customer: customer.ToResponse(), + Customer: customer.ToResponse(companies), AccessToken: newAccessToken, RefreshToken: refreshToken, // Return the same refresh token ExpiresAt: time.Now().Add(15 * time.Minute).Unix(), // 15 minutes from now @@ -1115,7 +654,7 @@ func (s *customerService) GetProfileWithCompanies(ctx context.Context, customerI companies = []*CompanySummary{} } - customerResponse := customer.ToResponseWithCompanies(companies) + customerResponse := customer.ToResponse(companies) s.logger.Info("Customer profile retrieved with companies successfully", map[string]interface{}{ "customer_id": customerID, @@ -1149,61 +688,6 @@ func (s *customerService) Logout(ctx context.Context, customerID string, accessT return nil } -// Helper methods for converting forms to domain objects -func (s *customerService) convertAddressForm(form *AddressForm) *Address { - if form == nil { - return nil - } - - return &Address{ - Street: form.Street, - City: form.City, - State: form.State, - PostalCode: form.PostalCode, - Country: form.Country, - AddressType: form.AddressType, - IsDefault: form.IsDefault, - } -} - -func (s *customerService) convertContactPersonForm(form *ContactPersonForm) *ContactPerson { - if form == nil { - return nil - } - - return &ContactPerson{ - FirstName: form.FirstName, - LastName: form.LastName, - FullName: form.FullName, - Position: form.Position, - Email: form.Email, - Phone: form.Phone, - Mobile: form.Mobile, - IsPrimary: form.IsPrimary, - } -} - -func (s *customerService) getDefaultLanguage(language *string) string { - if language != nil { - return *language - } - return "en" -} - -func (s *customerService) getDefaultCurrency(currency *string) string { - if currency != nil { - return *currency - } - return "USD" -} - -func (s *customerService) getDefaultTimezone(timezone *string) string { - if timezone != nil { - return *timezone - } - return "UTC" -} - // loadCompaniesForCustomer loads company summaries for a customer func (s *customerService) loadCompaniesForCustomer(ctx context.Context, customer *Customer) ([]*CompanySummary, error) { if len(customer.CompanyIDs) == 0 {