diff --git a/cmd/web/docs/docs.go b/cmd/web/docs/docs.go index 754bb6c..2c47e2e 100644 --- a/cmd/web/docs/docs.go +++ b/cmd/web/docs/docs.go @@ -24,830 +24,6 @@ const docTemplate = `{ "host": "{{.Host}}", "basePath": "{{.BasePath}}", "paths": { - "/admin/customers": { - "get": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Retrieve a paginated list of customers (admin only)", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "customers" - ], - "summary": "List customers", - "parameters": [ - { - "type": "integer", - "default": 1, - "description": "Page number", - "name": "page", - "in": "query" - }, - { - "type": "integer", - "default": 20, - "description": "Number of items per page", - "name": "limit", - "in": "query" - }, - { - "type": "string", - "description": "Search term", - "name": "search", - "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", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - } - } - } - }, - "/admin/customers/device-tokens": { - "get": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Retrieve all device tokens (admin only)", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "customers" - ], - "summary": "Get all device tokens", - "responses": { - "200": { - "description": "Device tokens retrieved successfully", - "schema": { - "allOf": [ - { - "$ref": "#/definitions/response.APIResponse" - }, - { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "type": "string" - } - } - } - } - ] - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - } - } - } - }, - "/admin/customers/register": { - "post": { - "description": "Register a new customer with the provided information", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "customers" - ], - "summary": "Register a new customer", - "parameters": [ - { - "description": "Customer registration information", - "name": "customer", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/customer.RegisterCustomerForm" - } - } - ], - "responses": { - "201": { - "description": "Customer registered successfully", - "schema": { - "allOf": [ - { - "$ref": "#/definitions/response.APIResponse" - }, - { - "type": "object", - "properties": { - "data": { - "$ref": "#/definitions/customer.CustomerResponse" - } - } - } - ] - } - }, - "400": { - "description": "Bad request", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "409": { - "description": "Customer already exists", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - } - } - } - }, - "/admin/customers/{id}": { - "get": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Retrieve customer information by ID (admin only)", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "customers" - ], - "summary": "Get customer by ID", - "parameters": [ - { - "type": "string", - "description": "Customer ID", - "name": "id", - "in": "path", - "required": true - } - ], - "responses": { - "200": { - "description": "Customer retrieved successfully", - "schema": { - "allOf": [ - { - "$ref": "#/definitions/response.APIResponse" - }, - { - "type": "object", - "properties": { - "data": { - "$ref": "#/definitions/customer.CustomerResponse" - } - } - } - ] - } - }, - "400": { - "description": "Bad request", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "404": { - "description": "Customer not found", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - } - } - } - }, - "/admin/customers/{id}/status": { - "put": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Update customer status (admin only)", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "customers" - ], - "summary": "Update customer status", - "parameters": [ - { - "type": "string", - "description": "Customer ID", - "name": "id", - "in": "path", - "required": true - }, - { - "description": "Status update information", - "name": "status", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/customer.UpdateCustomerStatusForm" - } - } - ], - "responses": { - "200": { - "description": "Customer status updated successfully", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "400": { - "description": "Bad request", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "404": { - "description": "Customer not found", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - } - } - } - }, - "/customers/change-password": { - "put": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Change the authenticated customer's password", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "customers" - ], - "summary": "Change customer password", - "parameters": [ - { - "description": "Password change information", - "name": "password", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/customer.ChangePasswordForm" - } - } - ], - "responses": { - "200": { - "description": "Password changed successfully", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "400": { - "description": "Bad request", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - } - } - } - }, - "/customers/device-token": { - "post": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Add a device token for push notifications", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "customers" - ], - "summary": "Add device token", - "parameters": [ - { - "description": "Device token information", - "name": "device_token", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/customer.AddDeviceTokenForm" - } - } - ], - "responses": { - "200": { - "description": "Device token added successfully", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "400": { - "description": "Bad request", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - } - } - }, - "delete": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Remove a device token for push notifications", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "customers" - ], - "summary": "Remove device token", - "parameters": [ - { - "description": "Device token information", - "name": "device_token", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/customer.RemoveDeviceTokenForm" - } - } - ], - "responses": { - "200": { - "description": "Device token removed successfully", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "400": { - "description": "Bad request", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - } - } - } - }, - "/customers/login": { - "post": { - "description": "Authenticate customer with email and password", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "customers" - ], - "summary": "Customer login", - "parameters": [ - { - "description": "Login credentials", - "name": "credentials", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/customer.LoginForm" - } - } - ], - "responses": { - "200": { - "description": "Login successful", - "schema": { - "allOf": [ - { - "$ref": "#/definitions/response.APIResponse" - }, - { - "type": "object", - "properties": { - "data": { - "$ref": "#/definitions/customer.AuthResponse" - } - } - } - ] - } - }, - "400": { - "description": "Bad request", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "401": { - "description": "Invalid credentials", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - } - } - } - }, - "/customers/logout": { - "post": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Logout customer and invalidate device token", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "customers" - ], - "summary": "Customer logout", - "parameters": [ - { - "description": "Logout request with device token", - "name": "logout", - "in": "body", - "required": true, - "schema": { - "type": "object" - } - } - ], - "responses": { - "200": { - "description": "Logged out successfully", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "400": { - "description": "Bad request", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - } - } - } - }, - "/customers/profile": { - "get": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Retrieve the authenticated customer's profile information", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "customers" - ], - "summary": "Get customer profile", - "responses": { - "200": { - "description": "Profile retrieved successfully", - "schema": { - "allOf": [ - { - "$ref": "#/definitions/response.APIResponse" - }, - { - "type": "object", - "properties": { - "data": { - "$ref": "#/definitions/customer.CustomerResponse" - } - } - } - ] - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "404": { - "description": "Customer not found", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - } - } - }, - "put": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Update the authenticated customer's profile information", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "customers" - ], - "summary": "Update customer profile", - "parameters": [ - { - "description": "Profile update information", - "name": "profile", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/customer.UpdateCustomerForm" - } - } - ], - "responses": { - "200": { - "description": "Profile updated successfully", - "schema": { - "allOf": [ - { - "$ref": "#/definitions/response.APIResponse" - }, - { - "type": "object", - "properties": { - "data": { - "$ref": "#/definitions/customer.CustomerResponse" - } - } - } - ] - } - }, - "400": { - "description": "Bad request", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - } - } - } - }, - "/customers/refresh-token": { - "post": { - "description": "Refresh the access token using a refresh token", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "customers" - ], - "summary": "Refresh access token", - "parameters": [ - { - "description": "Refresh token information", - "name": "token", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/customer.RefreshTokenForm" - } - } - ], - "responses": { - "200": { - "description": "Token refreshed successfully", - "schema": { - "allOf": [ - { - "$ref": "#/definitions/response.APIResponse" - }, - { - "type": "object", - "properties": { - "data": { - "$ref": "#/definitions/customer.AuthResponse" - } - } - } - ] - } - }, - "400": { - "description": "Bad request", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "401": { - "description": "Invalid refresh token", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - } - } - } - }, "/users/admin": { "get": { "security": [ @@ -1961,233 +1137,6 @@ const docTemplate = `{ } }, "definitions": { - "customer.AddDeviceTokenForm": { - "type": "object", - "properties": { - "device_token": { - "type": "string" - }, - "device_type": { - "type": "string" - } - } - }, - "customer.AuthResponse": { - "type": "object", - "properties": { - "access_token": { - "type": "string" - }, - "customer": { - "$ref": "#/definitions/customer.CustomerResponse" - }, - "expires_at": { - "type": "integer" - }, - "refresh_token": { - "type": "string" - } - } - }, - "customer.ChangePasswordForm": { - "type": "object", - "properties": { - "new_password": { - "type": "string" - }, - "old_password": { - "type": "string" - } - } - }, - "customer.CustomerResponse": { - "type": "object", - "properties": { - "birthdate": { - "type": "integer" - }, - "company_id": { - "type": "string" - }, - "created_at": { - "type": "integer" - }, - "device_tokens": { - "type": "array", - "items": { - "$ref": "#/definitions/customer.DeviceToken" - } - }, - "email": { - "type": "string" - }, - "full_name": { - "type": "string" - }, - "gender": { - "type": "string" - }, - "id": { - "type": "string" - }, - "is_verified": { - "type": "boolean" - }, - "last_login_at": { - "type": "integer" - }, - "mobile": { - "type": "string" - }, - "national_id": { - "type": "string" - }, - "profile_image": { - "type": "string" - }, - "status": { - "type": "string" - }, - "updated_at": { - "type": "integer" - }, - "username": { - "type": "string" - } - } - }, - "customer.DeviceToken": { - "type": "object", - "properties": { - "createdAt": { - "description": "Unix timestamp", - "type": "integer" - }, - "deviceType": { - "$ref": "#/definitions/customer.DeviceType" - }, - "token": { - "type": "string" - }, - "updatedAt": { - "description": "Unix timestamp", - "type": "integer" - } - } - }, - "customer.DeviceType": { - "type": "string", - "enum": [ - "android", - "ios" - ], - "x-enum-varnames": [ - "DeviceTypeAndroid", - "DeviceTypeIOS" - ] - }, - "customer.LoginForm": { - "type": "object", - "properties": { - "email_or_mobile": { - "type": "string" - }, - "password": { - "type": "string" - } - } - }, - "customer.RefreshTokenForm": { - "type": "object", - "properties": { - "refresh_token": { - "type": "string" - } - } - }, - "customer.RegisterCustomerForm": { - "type": "object", - "properties": { - "birthdate": { - "type": "integer" - }, - "company_id": { - "type": "string" - }, - "email": { - "type": "string" - }, - "full_name": { - "type": "string" - }, - "gender": { - "type": "string" - }, - "mobile": { - "type": "string" - }, - "national_id": { - "type": "string" - }, - "password": { - "type": "string" - }, - "profile_image": { - "type": "string" - }, - "username": { - "type": "string" - } - } - }, - "customer.RemoveDeviceTokenForm": { - "type": "object", - "properties": { - "device_token": { - "type": "string" - } - } - }, - "customer.UpdateCustomerForm": { - "type": "object", - "properties": { - "birthdate": { - "type": "integer" - }, - "email": { - "type": "string" - }, - "full_name": { - "type": "string" - }, - "gender": { - "type": "string" - }, - "mobile": { - "type": "string" - }, - "national_id": { - "type": "string" - }, - "profile_image": { - "type": "string" - }, - "status": { - "type": "string" - }, - "username": { - "type": "string" - } - } - }, - "customer.UpdateCustomerStatusForm": { - "type": "object", - "properties": { - "status": { - "type": "string" - } - } - }, "response.APIError": { "type": "object", "properties": { @@ -2306,10 +1255,10 @@ const docTemplate = `{ "user.LoginForm": { "type": "object", "properties": { - "email_or_username": { + "password": { "type": "string" }, - "password": { + "username": { "type": "string" } } diff --git a/cmd/web/docs/swagger.json b/cmd/web/docs/swagger.json index b913ea6..ffdb6eb 100644 --- a/cmd/web/docs/swagger.json +++ b/cmd/web/docs/swagger.json @@ -18,830 +18,6 @@ "host": "localhost:8081", "basePath": "/api/v1", "paths": { - "/admin/customers": { - "get": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Retrieve a paginated list of customers (admin only)", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "customers" - ], - "summary": "List customers", - "parameters": [ - { - "type": "integer", - "default": 1, - "description": "Page number", - "name": "page", - "in": "query" - }, - { - "type": "integer", - "default": 20, - "description": "Number of items per page", - "name": "limit", - "in": "query" - }, - { - "type": "string", - "description": "Search term", - "name": "search", - "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", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - } - } - } - }, - "/admin/customers/device-tokens": { - "get": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Retrieve all device tokens (admin only)", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "customers" - ], - "summary": "Get all device tokens", - "responses": { - "200": { - "description": "Device tokens retrieved successfully", - "schema": { - "allOf": [ - { - "$ref": "#/definitions/response.APIResponse" - }, - { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "type": "string" - } - } - } - } - ] - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - } - } - } - }, - "/admin/customers/register": { - "post": { - "description": "Register a new customer with the provided information", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "customers" - ], - "summary": "Register a new customer", - "parameters": [ - { - "description": "Customer registration information", - "name": "customer", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/customer.RegisterCustomerForm" - } - } - ], - "responses": { - "201": { - "description": "Customer registered successfully", - "schema": { - "allOf": [ - { - "$ref": "#/definitions/response.APIResponse" - }, - { - "type": "object", - "properties": { - "data": { - "$ref": "#/definitions/customer.CustomerResponse" - } - } - } - ] - } - }, - "400": { - "description": "Bad request", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "409": { - "description": "Customer already exists", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - } - } - } - }, - "/admin/customers/{id}": { - "get": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Retrieve customer information by ID (admin only)", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "customers" - ], - "summary": "Get customer by ID", - "parameters": [ - { - "type": "string", - "description": "Customer ID", - "name": "id", - "in": "path", - "required": true - } - ], - "responses": { - "200": { - "description": "Customer retrieved successfully", - "schema": { - "allOf": [ - { - "$ref": "#/definitions/response.APIResponse" - }, - { - "type": "object", - "properties": { - "data": { - "$ref": "#/definitions/customer.CustomerResponse" - } - } - } - ] - } - }, - "400": { - "description": "Bad request", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "404": { - "description": "Customer not found", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - } - } - } - }, - "/admin/customers/{id}/status": { - "put": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Update customer status (admin only)", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "customers" - ], - "summary": "Update customer status", - "parameters": [ - { - "type": "string", - "description": "Customer ID", - "name": "id", - "in": "path", - "required": true - }, - { - "description": "Status update information", - "name": "status", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/customer.UpdateCustomerStatusForm" - } - } - ], - "responses": { - "200": { - "description": "Customer status updated successfully", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "400": { - "description": "Bad request", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "404": { - "description": "Customer not found", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - } - } - } - }, - "/customers/change-password": { - "put": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Change the authenticated customer's password", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "customers" - ], - "summary": "Change customer password", - "parameters": [ - { - "description": "Password change information", - "name": "password", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/customer.ChangePasswordForm" - } - } - ], - "responses": { - "200": { - "description": "Password changed successfully", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "400": { - "description": "Bad request", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - } - } - } - }, - "/customers/device-token": { - "post": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Add a device token for push notifications", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "customers" - ], - "summary": "Add device token", - "parameters": [ - { - "description": "Device token information", - "name": "device_token", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/customer.AddDeviceTokenForm" - } - } - ], - "responses": { - "200": { - "description": "Device token added successfully", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "400": { - "description": "Bad request", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - } - } - }, - "delete": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Remove a device token for push notifications", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "customers" - ], - "summary": "Remove device token", - "parameters": [ - { - "description": "Device token information", - "name": "device_token", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/customer.RemoveDeviceTokenForm" - } - } - ], - "responses": { - "200": { - "description": "Device token removed successfully", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "400": { - "description": "Bad request", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - } - } - } - }, - "/customers/login": { - "post": { - "description": "Authenticate customer with email and password", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "customers" - ], - "summary": "Customer login", - "parameters": [ - { - "description": "Login credentials", - "name": "credentials", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/customer.LoginForm" - } - } - ], - "responses": { - "200": { - "description": "Login successful", - "schema": { - "allOf": [ - { - "$ref": "#/definitions/response.APIResponse" - }, - { - "type": "object", - "properties": { - "data": { - "$ref": "#/definitions/customer.AuthResponse" - } - } - } - ] - } - }, - "400": { - "description": "Bad request", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "401": { - "description": "Invalid credentials", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - } - } - } - }, - "/customers/logout": { - "post": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Logout customer and invalidate device token", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "customers" - ], - "summary": "Customer logout", - "parameters": [ - { - "description": "Logout request with device token", - "name": "logout", - "in": "body", - "required": true, - "schema": { - "type": "object" - } - } - ], - "responses": { - "200": { - "description": "Logged out successfully", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "400": { - "description": "Bad request", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - } - } - } - }, - "/customers/profile": { - "get": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Retrieve the authenticated customer's profile information", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "customers" - ], - "summary": "Get customer profile", - "responses": { - "200": { - "description": "Profile retrieved successfully", - "schema": { - "allOf": [ - { - "$ref": "#/definitions/response.APIResponse" - }, - { - "type": "object", - "properties": { - "data": { - "$ref": "#/definitions/customer.CustomerResponse" - } - } - } - ] - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "404": { - "description": "Customer not found", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - } - } - }, - "put": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Update the authenticated customer's profile information", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "customers" - ], - "summary": "Update customer profile", - "parameters": [ - { - "description": "Profile update information", - "name": "profile", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/customer.UpdateCustomerForm" - } - } - ], - "responses": { - "200": { - "description": "Profile updated successfully", - "schema": { - "allOf": [ - { - "$ref": "#/definitions/response.APIResponse" - }, - { - "type": "object", - "properties": { - "data": { - "$ref": "#/definitions/customer.CustomerResponse" - } - } - } - ] - } - }, - "400": { - "description": "Bad request", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - } - } - } - }, - "/customers/refresh-token": { - "post": { - "description": "Refresh the access token using a refresh token", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "customers" - ], - "summary": "Refresh access token", - "parameters": [ - { - "description": "Refresh token information", - "name": "token", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/customer.RefreshTokenForm" - } - } - ], - "responses": { - "200": { - "description": "Token refreshed successfully", - "schema": { - "allOf": [ - { - "$ref": "#/definitions/response.APIResponse" - }, - { - "type": "object", - "properties": { - "data": { - "$ref": "#/definitions/customer.AuthResponse" - } - } - } - ] - } - }, - "400": { - "description": "Bad request", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "401": { - "description": "Invalid refresh token", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - } - } - } - }, "/users/admin": { "get": { "security": [ @@ -1955,233 +1131,6 @@ } }, "definitions": { - "customer.AddDeviceTokenForm": { - "type": "object", - "properties": { - "device_token": { - "type": "string" - }, - "device_type": { - "type": "string" - } - } - }, - "customer.AuthResponse": { - "type": "object", - "properties": { - "access_token": { - "type": "string" - }, - "customer": { - "$ref": "#/definitions/customer.CustomerResponse" - }, - "expires_at": { - "type": "integer" - }, - "refresh_token": { - "type": "string" - } - } - }, - "customer.ChangePasswordForm": { - "type": "object", - "properties": { - "new_password": { - "type": "string" - }, - "old_password": { - "type": "string" - } - } - }, - "customer.CustomerResponse": { - "type": "object", - "properties": { - "birthdate": { - "type": "integer" - }, - "company_id": { - "type": "string" - }, - "created_at": { - "type": "integer" - }, - "device_tokens": { - "type": "array", - "items": { - "$ref": "#/definitions/customer.DeviceToken" - } - }, - "email": { - "type": "string" - }, - "full_name": { - "type": "string" - }, - "gender": { - "type": "string" - }, - "id": { - "type": "string" - }, - "is_verified": { - "type": "boolean" - }, - "last_login_at": { - "type": "integer" - }, - "mobile": { - "type": "string" - }, - "national_id": { - "type": "string" - }, - "profile_image": { - "type": "string" - }, - "status": { - "type": "string" - }, - "updated_at": { - "type": "integer" - }, - "username": { - "type": "string" - } - } - }, - "customer.DeviceToken": { - "type": "object", - "properties": { - "createdAt": { - "description": "Unix timestamp", - "type": "integer" - }, - "deviceType": { - "$ref": "#/definitions/customer.DeviceType" - }, - "token": { - "type": "string" - }, - "updatedAt": { - "description": "Unix timestamp", - "type": "integer" - } - } - }, - "customer.DeviceType": { - "type": "string", - "enum": [ - "android", - "ios" - ], - "x-enum-varnames": [ - "DeviceTypeAndroid", - "DeviceTypeIOS" - ] - }, - "customer.LoginForm": { - "type": "object", - "properties": { - "email_or_mobile": { - "type": "string" - }, - "password": { - "type": "string" - } - } - }, - "customer.RefreshTokenForm": { - "type": "object", - "properties": { - "refresh_token": { - "type": "string" - } - } - }, - "customer.RegisterCustomerForm": { - "type": "object", - "properties": { - "birthdate": { - "type": "integer" - }, - "company_id": { - "type": "string" - }, - "email": { - "type": "string" - }, - "full_name": { - "type": "string" - }, - "gender": { - "type": "string" - }, - "mobile": { - "type": "string" - }, - "national_id": { - "type": "string" - }, - "password": { - "type": "string" - }, - "profile_image": { - "type": "string" - }, - "username": { - "type": "string" - } - } - }, - "customer.RemoveDeviceTokenForm": { - "type": "object", - "properties": { - "device_token": { - "type": "string" - } - } - }, - "customer.UpdateCustomerForm": { - "type": "object", - "properties": { - "birthdate": { - "type": "integer" - }, - "email": { - "type": "string" - }, - "full_name": { - "type": "string" - }, - "gender": { - "type": "string" - }, - "mobile": { - "type": "string" - }, - "national_id": { - "type": "string" - }, - "profile_image": { - "type": "string" - }, - "status": { - "type": "string" - }, - "username": { - "type": "string" - } - } - }, - "customer.UpdateCustomerStatusForm": { - "type": "object", - "properties": { - "status": { - "type": "string" - } - } - }, "response.APIError": { "type": "object", "properties": { @@ -2300,10 +1249,10 @@ "user.LoginForm": { "type": "object", "properties": { - "email_or_username": { + "password": { "type": "string" }, - "password": { + "username": { "type": "string" } } diff --git a/cmd/web/docs/swagger.yaml b/cmd/web/docs/swagger.yaml index 4a9df92..d53749b 100644 --- a/cmd/web/docs/swagger.yaml +++ b/cmd/web/docs/swagger.yaml @@ -1,154 +1,5 @@ basePath: /api/v1 definitions: - customer.AddDeviceTokenForm: - properties: - device_token: - type: string - device_type: - type: string - type: object - customer.AuthResponse: - properties: - access_token: - type: string - customer: - $ref: '#/definitions/customer.CustomerResponse' - expires_at: - type: integer - refresh_token: - type: string - type: object - customer.ChangePasswordForm: - properties: - new_password: - type: string - old_password: - type: string - type: object - customer.CustomerResponse: - properties: - birthdate: - type: integer - company_id: - type: string - created_at: - type: integer - device_tokens: - items: - $ref: '#/definitions/customer.DeviceToken' - type: array - email: - type: string - full_name: - type: string - gender: - type: string - id: - type: string - is_verified: - type: boolean - last_login_at: - type: integer - mobile: - type: string - national_id: - type: string - profile_image: - type: string - status: - type: string - updated_at: - type: integer - username: - type: string - type: object - customer.DeviceToken: - properties: - createdAt: - description: Unix timestamp - type: integer - deviceType: - $ref: '#/definitions/customer.DeviceType' - token: - type: string - updatedAt: - description: Unix timestamp - type: integer - type: object - customer.DeviceType: - enum: - - android - - ios - type: string - x-enum-varnames: - - DeviceTypeAndroid - - DeviceTypeIOS - customer.LoginForm: - properties: - email_or_mobile: - type: string - password: - type: string - type: object - customer.RefreshTokenForm: - properties: - refresh_token: - type: string - type: object - customer.RegisterCustomerForm: - properties: - birthdate: - type: integer - company_id: - type: string - email: - type: string - full_name: - type: string - gender: - type: string - mobile: - type: string - national_id: - type: string - password: - type: string - profile_image: - type: string - username: - type: string - type: object - customer.RemoveDeviceTokenForm: - properties: - device_token: - type: string - type: object - customer.UpdateCustomerForm: - properties: - birthdate: - type: integer - email: - type: string - full_name: - type: string - gender: - type: string - mobile: - type: string - national_id: - type: string - profile_image: - type: string - status: - type: string - username: - type: string - type: object - customer.UpdateCustomerStatusForm: - properties: - status: - type: string - type: object response.APIError: properties: code: @@ -226,10 +77,10 @@ definitions: type: object user.LoginForm: properties: - email_or_username: - type: string password: type: string + username: + type: string type: object user.RefreshTokenForm: properties: @@ -340,512 +191,6 @@ info: title: Tender Management API version: 1.0.0 paths: - /admin/customers: - get: - consumes: - - application/json - description: Retrieve a paginated list of customers (admin only) - parameters: - - default: 1 - description: Page number - in: query - name: page - type: integer - - default: 20 - description: Number of items per page - in: query - name: limit - type: integer - - description: Search term - in: query - name: search - type: string - 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 - schema: - $ref: '#/definitions/response.APIResponse' - "401": - description: Unauthorized - schema: - $ref: '#/definitions/response.APIResponse' - "500": - description: Internal server error - schema: - $ref: '#/definitions/response.APIResponse' - security: - - BearerAuth: [] - summary: List customers - tags: - - customers - /admin/customers/{id}: - get: - consumes: - - application/json - description: Retrieve customer information by ID (admin only) - parameters: - - description: Customer ID - in: path - name: id - required: true - type: string - produces: - - application/json - responses: - "200": - description: Customer retrieved successfully - schema: - allOf: - - $ref: '#/definitions/response.APIResponse' - - properties: - data: - $ref: '#/definitions/customer.CustomerResponse' - type: object - "400": - description: Bad request - schema: - $ref: '#/definitions/response.APIResponse' - "401": - description: Unauthorized - schema: - $ref: '#/definitions/response.APIResponse' - "404": - description: Customer not found - schema: - $ref: '#/definitions/response.APIResponse' - "500": - description: Internal server error - schema: - $ref: '#/definitions/response.APIResponse' - security: - - BearerAuth: [] - summary: Get customer by ID - tags: - - customers - /admin/customers/{id}/status: - put: - consumes: - - application/json - description: Update customer status (admin only) - parameters: - - description: Customer ID - in: path - name: id - required: true - type: string - - description: Status update information - in: body - name: status - required: true - schema: - $ref: '#/definitions/customer.UpdateCustomerStatusForm' - produces: - - application/json - responses: - "200": - description: Customer status updated successfully - schema: - $ref: '#/definitions/response.APIResponse' - "400": - description: Bad request - schema: - $ref: '#/definitions/response.APIResponse' - "401": - description: Unauthorized - schema: - $ref: '#/definitions/response.APIResponse' - "404": - description: Customer not found - schema: - $ref: '#/definitions/response.APIResponse' - "500": - description: Internal server error - schema: - $ref: '#/definitions/response.APIResponse' - security: - - BearerAuth: [] - summary: Update customer status - tags: - - customers - /admin/customers/device-tokens: - get: - consumes: - - application/json - description: Retrieve all device tokens (admin only) - produces: - - application/json - responses: - "200": - description: Device tokens retrieved successfully - schema: - allOf: - - $ref: '#/definitions/response.APIResponse' - - properties: - data: - items: - type: string - type: array - type: object - "401": - description: Unauthorized - schema: - $ref: '#/definitions/response.APIResponse' - "500": - description: Internal server error - schema: - $ref: '#/definitions/response.APIResponse' - security: - - BearerAuth: [] - summary: Get all device tokens - tags: - - customers - /admin/customers/register: - post: - consumes: - - application/json - description: Register a new customer with the provided information - parameters: - - description: Customer registration information - in: body - name: customer - required: true - schema: - $ref: '#/definitions/customer.RegisterCustomerForm' - produces: - - application/json - responses: - "201": - description: Customer registered successfully - schema: - allOf: - - $ref: '#/definitions/response.APIResponse' - - properties: - data: - $ref: '#/definitions/customer.CustomerResponse' - type: object - "400": - description: Bad request - schema: - $ref: '#/definitions/response.APIResponse' - "409": - description: Customer already exists - schema: - $ref: '#/definitions/response.APIResponse' - "500": - description: Internal server error - schema: - $ref: '#/definitions/response.APIResponse' - summary: Register a new customer - tags: - - customers - /customers/change-password: - put: - consumes: - - application/json - description: Change the authenticated customer's password - parameters: - - description: Password change information - in: body - name: password - required: true - schema: - $ref: '#/definitions/customer.ChangePasswordForm' - produces: - - application/json - responses: - "200": - description: Password changed successfully - schema: - $ref: '#/definitions/response.APIResponse' - "400": - description: Bad request - schema: - $ref: '#/definitions/response.APIResponse' - "401": - description: Unauthorized - schema: - $ref: '#/definitions/response.APIResponse' - "500": - description: Internal server error - schema: - $ref: '#/definitions/response.APIResponse' - security: - - BearerAuth: [] - summary: Change customer password - tags: - - customers - /customers/device-token: - delete: - consumes: - - application/json - description: Remove a device token for push notifications - parameters: - - description: Device token information - in: body - name: device_token - required: true - schema: - $ref: '#/definitions/customer.RemoveDeviceTokenForm' - produces: - - application/json - responses: - "200": - description: Device token removed successfully - schema: - $ref: '#/definitions/response.APIResponse' - "400": - description: Bad request - schema: - $ref: '#/definitions/response.APIResponse' - "401": - description: Unauthorized - schema: - $ref: '#/definitions/response.APIResponse' - "500": - description: Internal server error - schema: - $ref: '#/definitions/response.APIResponse' - security: - - BearerAuth: [] - summary: Remove device token - tags: - - customers - post: - consumes: - - application/json - description: Add a device token for push notifications - parameters: - - description: Device token information - in: body - name: device_token - required: true - schema: - $ref: '#/definitions/customer.AddDeviceTokenForm' - produces: - - application/json - responses: - "200": - description: Device token added successfully - schema: - $ref: '#/definitions/response.APIResponse' - "400": - description: Bad request - schema: - $ref: '#/definitions/response.APIResponse' - "401": - description: Unauthorized - schema: - $ref: '#/definitions/response.APIResponse' - "500": - description: Internal server error - schema: - $ref: '#/definitions/response.APIResponse' - security: - - BearerAuth: [] - summary: Add device token - tags: - - customers - /customers/login: - post: - consumes: - - application/json - description: Authenticate customer with email and password - parameters: - - description: Login credentials - in: body - name: credentials - required: true - schema: - $ref: '#/definitions/customer.LoginForm' - produces: - - application/json - responses: - "200": - description: Login successful - schema: - allOf: - - $ref: '#/definitions/response.APIResponse' - - properties: - data: - $ref: '#/definitions/customer.AuthResponse' - type: object - "400": - description: Bad request - schema: - $ref: '#/definitions/response.APIResponse' - "401": - description: Invalid credentials - schema: - $ref: '#/definitions/response.APIResponse' - "500": - description: Internal server error - schema: - $ref: '#/definitions/response.APIResponse' - summary: Customer login - tags: - - customers - /customers/logout: - post: - consumes: - - application/json - description: Logout customer and invalidate device token - parameters: - - description: Logout request with device token - in: body - name: logout - required: true - schema: - type: object - produces: - - application/json - responses: - "200": - description: Logged out successfully - schema: - $ref: '#/definitions/response.APIResponse' - "400": - description: Bad request - schema: - $ref: '#/definitions/response.APIResponse' - "401": - description: Unauthorized - schema: - $ref: '#/definitions/response.APIResponse' - "500": - description: Internal server error - schema: - $ref: '#/definitions/response.APIResponse' - security: - - BearerAuth: [] - summary: Customer logout - tags: - - customers - /customers/profile: - get: - consumes: - - application/json - description: Retrieve the authenticated customer's profile information - produces: - - application/json - responses: - "200": - description: Profile retrieved successfully - schema: - allOf: - - $ref: '#/definitions/response.APIResponse' - - properties: - data: - $ref: '#/definitions/customer.CustomerResponse' - type: object - "401": - description: Unauthorized - schema: - $ref: '#/definitions/response.APIResponse' - "404": - description: Customer not found - schema: - $ref: '#/definitions/response.APIResponse' - "500": - description: Internal server error - schema: - $ref: '#/definitions/response.APIResponse' - security: - - BearerAuth: [] - summary: Get customer profile - tags: - - customers - put: - consumes: - - application/json - description: Update the authenticated customer's profile information - parameters: - - description: Profile update information - in: body - name: profile - required: true - schema: - $ref: '#/definitions/customer.UpdateCustomerForm' - produces: - - application/json - responses: - "200": - description: Profile updated successfully - schema: - allOf: - - $ref: '#/definitions/response.APIResponse' - - properties: - data: - $ref: '#/definitions/customer.CustomerResponse' - type: object - "400": - description: Bad request - schema: - $ref: '#/definitions/response.APIResponse' - "401": - description: Unauthorized - schema: - $ref: '#/definitions/response.APIResponse' - "500": - description: Internal server error - schema: - $ref: '#/definitions/response.APIResponse' - security: - - BearerAuth: [] - summary: Update customer profile - tags: - - customers - /customers/refresh-token: - post: - consumes: - - application/json - description: Refresh the access token using a refresh token - parameters: - - description: Refresh token information - in: body - name: token - required: true - schema: - $ref: '#/definitions/customer.RefreshTokenForm' - produces: - - application/json - responses: - "200": - description: Token refreshed successfully - schema: - allOf: - - $ref: '#/definitions/response.APIResponse' - - properties: - data: - $ref: '#/definitions/customer.AuthResponse' - type: object - "400": - description: Bad request - schema: - $ref: '#/definitions/response.APIResponse' - "401": - description: Invalid refresh token - schema: - $ref: '#/definitions/response.APIResponse' - "500": - description: Internal server error - schema: - $ref: '#/definitions/response.APIResponse' - summary: Refresh access token - tags: - - customers /users/admin: get: consumes: diff --git a/cmd/web/main.go b/cmd/web/main.go index 97e43c4..cdf8b31 100644 --- a/cmd/web/main.go +++ b/cmd/web/main.go @@ -56,7 +56,6 @@ import ( "fmt" "net/http" "time" - "tm/internal/customer" "tm/internal/user" _ "tm/cmd/web/docs" // This is generated by swag @@ -92,8 +91,8 @@ func main() { // Initialize authorization service authService := initAuthorizationService(conf.Auth.JWT, redisClient, logger) + // Initialize repositories with MongoDB connection manager - customerRepository := customer.NewCustomerRepository(mongoManager, logger) userRepository := user.NewUserRepository(mongoManager, logger) logger.Info("Repositories initialized successfully", map[string]interface{}{ @@ -104,7 +103,6 @@ func main() { userValidator := user.NewValidationService() // Initialize services with repositories - customerService := customer.NewCustomerService(customerRepository, logger) userService := user.NewUserService(userRepository, logger, authService, userValidator) logger.Info("Services initialized successfully", map[string]interface{}{ @@ -112,7 +110,6 @@ func main() { }) // Initialize handlers with services - customerHandler := customer.NewHandler(customerService) userHandler := user.NewUserHandler(userService, logger, userValidator, authService) logger.Info("Handlers initialized successfully", map[string]interface{}{ @@ -123,7 +120,6 @@ func main() { e := initHTTPServer(conf, logger) // Register routes - customerHandler.RegisterRoutes(e) userHandler.RegisterRoutes(e) // Start server diff --git a/internal/customer/README.md b/internal/customer/README.md deleted file mode 100644 index 6a4e153..0000000 --- a/internal/customer/README.md +++ /dev/null @@ -1,554 +0,0 @@ -# Customer Management Module - -This module implements a complete User Management system for the Tender Management System, allowing administrators to register users who can later access the mobile application. - -## Features - -### 1. User Registration (by Admin) -- **Fields**: Full name, username, email, mobile number, password (hashed), national ID (optional), gender (enum), birthdate (optional), profile image (optional), user status (active/inactive), registration date -- **Uniqueness**: Ensures uniqueness for email, mobile, and username -- **Validation**: Comprehensive input validation using govalidator - -### 2. Authentication for Mobile Users -- **Login**: Secure login via email or mobile number + password -- **JWT Tokens**: Access and refresh token generation -- **Password Security**: bcrypt hashing for secure password storage - -### 3. Device Token Management -- **Storage**: Saves device_token and device_type (Android/iOS) when user logs in -- **Updates**: Updates token if user logs in from new device or re-installs app -- **Cleanup**: Endpoint to delete token on logout - -### 4. User Profile Management -- **View/Edit**: Personal info (name, email, mobile, etc.) -- **Password Change**: Secure password change endpoint -- **Profile Picture**: Upload/update profile picture support - -### 5. Push Notification Ready -- **Multiple Tokens**: Stores multiple device tokens per user -- **User Association**: Tokens tied to correct user ID -- **Admin Access**: API to get all active device tokens for admin push campaigns - -### 6. Admin Panel APIs -- **List/Search/Filter**: Users by name, mobile, email, registration date, and status -- **Status Management**: Activate/deactivate users -- **Role Support**: Framework for role assignment (user, moderator, etc.) - -### 7. Security & Best Practices -- **Input Validation**: Comprehensive validation and sanitization -- **Rate Limiting**: Framework for authentication endpoint rate limiting -- **Password Security**: bcrypt hashing with configurable cost -- **HTTP Status Codes**: Proper status codes and error messages -- **Activity Logging**: User activities (login, logout, profile update) - -## Architecture - -### Clean Architecture Implementation -- **Domain Layer**: Business entities, aggregates, interfaces, and core business rules -- **Service Layer**: Business logic and use cases -- **Handler Layer**: HTTP controllers and request/response handling -- **Repository Layer**: Data access implementations -- **Infrastructure Layer**: External dependencies (DB, cache, queues, config) - -### File Structure -``` -internal/customer/ -├── entity.go # Core domain entities and types -├── form.go # Request/response forms with validation -├── repository.go # Repository interface and implementation -├── service.go # Business logic and use cases -├── handler.go # HTTP controllers and request/response handling -├── validation.go # Custom validation rules -└── README.md # This documentation -``` - -## Database Schema - -### Customer Collection -```javascript -{ - "_id": "uuid", - "full_name": "string", - "username": "string (unique)", - "email": "string (unique)", - "mobile": "string (unique)", - "password": "string (hashed)", - "national_id": "string (optional)", - "gender": "enum (male|female|other) (optional)", - "birthdate": "int64 (unix timestamp) (optional)", - "profile_image": "string (url) (optional)", - "status": "enum (active|inactive)", - "company_id": "uuid (optional)", - "is_verified": "boolean", - "device_tokens": [ - { - "token": "string", - "device_type": "enum (android|ios)", - "created_at": "int64 (unix timestamp)", - "updated_at": "int64 (unix timestamp)" - } - ], - "last_login_at": "int64 (unix timestamp) (optional)", - "created_at": "int64 (unix timestamp)", - "updated_at": "int64 (unix timestamp)" -} -``` - -### Indexes -- `email` (unique) -- `username` (unique) -- `mobile` (unique) -- `company_id` -- `status` -- `gender` -- `created_at` (descending) -- Text index on `full_name`, `email`, `mobile`, `username` - -## API Endpoints - -### Public Endpoints (No Authentication Required) - -#### 1. Register Customer -```http -POST /api/customers/register -Content-Type: application/json - -{ - "full_name": "John Doe", - "username": "johndoe", - "email": "john@example.com", - "mobile": "+1234567890", - "password": "securepassword123", - "national_id": "123456789", - "gender": "male", - "birthdate": 631152000, - "profile_image": "https://example.com/avatar.jpg", - "company_id": "uuid-optional" -} -``` - -**Response:** -```json -{ - "success": true, - "message": "Customer registered successfully", - "data": { - "id": "uuid", - "full_name": "John Doe", - "username": "johndoe", - "email": "john@example.com", - "mobile": "+1234567890", - "national_id": "123456789", - "gender": "male", - "birthdate": 631152000, - "profile_image": "https://example.com/avatar.jpg", - "status": "active", - "company_id": "uuid", - "is_verified": false, - "device_tokens": [], - "created_at": 1703123456, - "updated_at": 1703123456 - } -} -``` - -#### 2. Login -```http -POST /api/customers/login -Content-Type: application/json - -{ - "email_or_mobile": "john@example.com", - "password": "securepassword123" -} -``` - -**Response:** -```json -{ - "success": true, - "message": "Login successful", - "data": { - "customer": { - "id": "uuid", - "full_name": "John Doe", - "username": "johndoe", - "email": "john@example.com", - "mobile": "+1234567890", - "status": "active", - "is_verified": false, - "device_tokens": [], - "last_login_at": 1703123456, - "created_at": 1703123456, - "updated_at": 1703123456 - }, - "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", - "refresh_token": "refresh_token_here", - "expires_at": 1703127056 - } -} -``` - -#### 3. Refresh Token -```http -POST /api/customers/refresh-token -Content-Type: application/json - -{ - "refresh_token": "refresh_token_here" -} -``` - -### Protected Endpoints (Authentication Required) - -#### 4. Get Profile -```http -GET /api/customers/profile -Authorization: Bearer -X-Customer-ID: -``` - -#### 5. Update Profile -```http -PUT /api/customers/profile -Authorization: Bearer -X-Customer-ID: -Content-Type: application/json - -{ - "full_name": "John Updated Doe", - "email": "john.updated@example.com", - "mobile": "+1234567891", - "gender": "male", - "birthdate": 631152000, - "profile_image": "https://example.com/new-avatar.jpg" -} -``` - -#### 6. Change Password -```http -PUT /api/customers/change-password -Authorization: Bearer -X-Customer-ID: -Content-Type: application/json - -{ - "old_password": "currentpassword", - "new_password": "newsecurepassword123" -} -``` - -#### 7. Add Device Token -```http -POST /api/customers/device-token -Authorization: Bearer -X-Customer-ID: -Content-Type: application/json - -{ - "device_token": "fcm_token_here", - "device_type": "android" -} -``` - -#### 8. Remove Device Token -```http -DELETE /api/customers/device-token -Authorization: Bearer -X-Customer-ID: -Content-Type: application/json - -{ - "device_token": "fcm_token_here" -} -``` - -#### 9. Logout -```http -POST /api/customers/logout -Authorization: Bearer -X-Customer-ID: -Content-Type: application/json - -{ - "device_token": "fcm_token_here" -} -``` - -### Admin Endpoints (Admin Authentication Required) - -#### 10. List Customers -```http -GET /api/admin/customers?search=john&status=active&gender=male&limit=20&offset=0&sort_by=created_at&sort_order=desc -Authorization: Bearer -``` - -**Response:** -```json -{ - "success": true, - "message": "Customers retrieved successfully", - "data": [ - { - "id": "uuid", - "full_name": "John Doe", - "username": "johndoe", - "email": "john@example.com", - "mobile": "+1234567890", - "status": "active", - "is_verified": false, - "device_tokens": [], - "last_login_at": 1703123456, - "created_at": 1703123456, - "updated_at": 1703123456 - } - ], - "meta": { - "total": 100, - "limit": 20, - "offset": 0, - "page": 1, - "pages": 5 - } -} -``` - -#### 11. Get Customer by ID -```http -GET /api/admin/customers/{customer_id} -Authorization: Bearer -``` - -#### 12. Update Customer Status -```http -PUT /api/admin/customers/{customer_id}/status -Authorization: Bearer -Content-Type: application/json - -{ - "status": "inactive" -} -``` - -#### 13. Get All Device Tokens -```http -GET /api/admin/customers/device-tokens -Authorization: Bearer -``` - -**Response:** -```json -{ - "success": true, - "message": "Device tokens retrieved successfully", - "data": [ - { - "token": "fcm_token_1", - "device_type": "android", - "created_at": 1703123456, - "updated_at": 1703123456 - }, - { - "token": "fcm_token_2", - "device_type": "ios", - "created_at": 1703123457, - "updated_at": 1703123457 - } - ] -} -``` - -## Validation Rules - -### Registration Form -- `full_name`: Required, 2-100 characters -- `username`: Required, alphanumeric, 3-30 characters, unique -- `email`: Required, valid email format, unique -- `mobile`: Required, 10-15 characters, unique -- `password`: Required, 8-128 characters -- `national_id`: Optional, 5-20 characters -- `gender`: Optional, must be "male", "female", or "other" -- `birthdate`: Optional, valid Unix timestamp -- `profile_image`: Optional, valid URL -- `company_id`: Optional, valid UUID - -### Login Form -- `email_or_mobile`: Required -- `password`: Required - -### Update Form -- All fields optional with same validation rules as registration -- Uniqueness checks only if field is being updated - -### Device Token Form -- `device_token`: Required, 32-255 characters -- `device_type`: Required, must be "android" or "ios" - -### List Customers Form -- `search`: Optional, text search -- `status`: Optional, must be "active" or "inactive" -- `gender`: Optional, must be "male", "female", or "other" -- `company_id`: Optional, valid UUID -- `limit`: Optional, 1-100 -- `offset`: Optional, minimum 0 -- `sort_by`: Optional, must be "full_name", "email", "mobile", "created_at", or "last_login_at" -- `sort_order`: Optional, must be "asc" or "desc" - -## Error Handling - -### Standard Error Response Format -```json -{ - "success": false, - "message": "Error message", - "error": { - "code": "ERROR_CODE", - "message": "Detailed error message", - "details": "Additional error details" - } -} -``` - -### Common Error Codes -- `BAD_REQUEST`: Invalid request format or parameters -- `VALIDATION_ERROR`: Input validation failed -- `UNAUTHORIZED`: Authentication required or failed -- `FORBIDDEN`: Insufficient permissions -- `NOT_FOUND`: Resource not found -- `CONFLICT`: Resource already exists -- `INTERNAL_SERVER_ERROR`: Server error - -### HTTP Status Codes -- `200 OK`: Success -- `201 Created`: Resource created successfully -- `400 Bad Request`: Invalid request -- `401 Unauthorized`: Authentication required -- `403 Forbidden`: Insufficient permissions -- `404 Not Found`: Resource not found -- `409 Conflict`: Resource conflict -- `422 Unprocessable Entity`: Validation error -- `500 Internal Server Error`: Server error - -## Security Features - -### Password Security -- bcrypt hashing with configurable cost -- Minimum 8 characters required -- Maximum 128 characters allowed - -### Input Validation -- Comprehensive validation using govalidator -- Custom validators for Unix timestamps -- SQL injection prevention through parameterized queries -- XSS prevention through input sanitization - -### Authentication -- JWT-based token system -- Access and refresh token separation -- Token expiration handling -- Secure token generation - -### Data Protection -- Passwords never serialized in responses -- Sensitive data logging prevention -- Input sanitization and validation - -## Usage Examples - -### Register a New Customer (Admin) -```bash -curl -X POST http://localhost:8081/api/customers/register \ - -H "Content-Type: application/json" \ - -d '{ - "full_name": "John Doe", - "username": "johndoe", - "email": "john@example.com", - "mobile": "+1234567890", - "password": "securepassword123", - "gender": "male", - "birthdate": 631152000 - }' -``` - -### Login as Customer -```bash -curl -X POST http://localhost:8081/api/customers/login \ - -H "Content-Type: application/json" \ - -d '{ - "email_or_mobile": "john@example.com", - "password": "securepassword123" - }' -``` - -### Update Profile -```bash -curl -X PUT http://localhost:8081/api/customers/profile \ - -H "Content-Type: application/json" \ - -H "Authorization: Bearer " \ - -H "X-Customer-ID: " \ - -d '{ - "full_name": "John Updated Doe", - "email": "john.updated@example.com" - }' -``` - -### List Customers (Admin) -```bash -curl -X GET "http://localhost:8081/api/admin/customers?search=john&status=active&limit=10" \ - -H "Authorization: Bearer " -``` - -## Future Enhancements - -### Planned Features -1. **JWT Implementation**: Proper JWT token generation and validation -2. **Email Verification**: Email verification system -3. **Password Reset**: Forgot password functionality -4. **Role-Based Access Control**: User roles and permissions -5. **Rate Limiting**: API rate limiting implementation -6. **Audit Logging**: Comprehensive audit trail -7. **Bulk Operations**: Bulk user import/export -8. **Advanced Search**: Full-text search with filters -9. **File Upload**: Profile picture upload handling -10. **Two-Factor Authentication**: 2FA support - -### Technical Improvements -1. **Caching**: Redis caching for frequently accessed data -2. **Background Jobs**: Async processing for heavy operations -3. **Monitoring**: Health checks and metrics -4. **Testing**: Comprehensive unit and integration tests -5. **Documentation**: OpenAPI/Swagger documentation -6. **Performance**: Query optimization and indexing -7. **Security**: Additional security measures -8. **Scalability**: Horizontal scaling support - -## Dependencies - -### Required Packages -- `github.com/google/uuid`: UUID generation -- `github.com/labstack/echo/v4`: HTTP framework -- `github.com/asaskevich/govalidator`: Input validation -- `golang.org/x/crypto/bcrypt`: Password hashing -- `go.mongodb.org/mongo-driver`: MongoDB driver - -### Configuration -- MongoDB connection string -- JWT secret key -- Password hashing cost -- Token expiration times -- Rate limiting settings - -## Contributing - -When contributing to this module: - -1. Follow the existing code structure and patterns -2. Add comprehensive validation for new fields -3. Include proper error handling and logging -4. Write unit tests for new functionality -5. Update documentation for new endpoints -6. Follow security best practices -7. Use Unix timestamps for all time fields -8. Implement proper pagination for list endpoints -9. Add appropriate indexes for new fields -10. Follow the flat domain structure pattern \ No newline at end of file diff --git a/internal/customer/aggregate.go b/internal/customer/aggregate.go deleted file mode 100644 index f77155d..0000000 --- a/internal/customer/aggregate.go +++ /dev/null @@ -1,45 +0,0 @@ -package customer - -import ( - "github.com/google/uuid" -) - -type CustomerAggregate struct { - ID uuid.UUID `json:"_id"` - FullName string `json:"full_name"` - Username string `json:"username"` - Mobile string `json:"mobile"` - Email string `json:"email"` - Status string `json:"status"` - IsVerified bool `json:"is_verified"` - LastLoginAt *int64 `json:"last_login_at,omitempty"` // Unix timestamp - CreatedAt int64 `json:"created_at"` // Unix timestamp - UpdatedAt int64 `json:"updated_at"` // Unix timestamp -} - -func NewCustomerAggregate(c Customer) CustomerAggregate { - customer := CustomerAggregate{ - ID: c.ID, - FullName: c.FullName, - Username: c.Username, - Email: c.Email, - Mobile: c.Mobile, - Status: string(c.Status), - IsVerified: c.IsVerified, - CreatedAt: c.CreatedAt, - UpdatedAt: c.UpdatedAt, - } - if c.LastLoginAt != nil { - customer.LastLoginAt = c.LastLoginAt - } - - return customer -} - -// AuthResponse defines authentication response (kept for backward compatibility) -type AuthorizationResponse struct { - Customer CustomerAggregate `json:"customer"` - AccessToken string `json:"access_token"` - RefreshToken string `json:"refresh_token"` - ExpiresIn int64 `json:"expires_in"` -} diff --git a/internal/customer/entity.go b/internal/customer/entity.go deleted file mode 100644 index 9463e74..0000000 --- a/internal/customer/entity.go +++ /dev/null @@ -1,59 +0,0 @@ -package customer - -import ( - "github.com/google/uuid" -) - -// Gender represents user gender -type Gender string - -const ( - GenderMale Gender = "male" - GenderFemale Gender = "female" - GenderOther Gender = "other" -) - -// CustomerStatus represents customer account status -type CustomerStatus string - -const ( - CustomerStatusActive CustomerStatus = "active" - CustomerStatusInactive CustomerStatus = "inactive" -) - -// DeviceType represents the type of device -type DeviceType string - -const ( - DeviceTypeAndroid DeviceType = "android" - DeviceTypeIOS DeviceType = "ios" -) - -// DeviceToken represents a device token for push notifications -type DeviceToken struct { - Token string `bson:"token"` - DeviceType DeviceType `bson:"device_type"` - CreatedAt int64 `bson:"created_at"` // Unix timestamp - UpdatedAt int64 `bson:"updated_at"` // Unix timestamp -} - -// Customer represents a mobile app user (customer) -type Customer struct { - ID uuid.UUID `bson:"_id"` - FullName string `bson:"full_name"` - Username string `bson:"username"` - Email string `bson:"email"` - Mobile string `bson:"mobile"` - Password string `bson:"password"` // Never serialize password - NationalID *string `bson:"national_id,omitempty"` - Gender *Gender `bson:"gender,omitempty"` - Birthdate *int64 `bson:"birthdate,omitempty"` // Unix timestamp - ProfileImage *string `bson:"profile_image,omitempty"` - Status CustomerStatus `bson:"status"` - CompanyID *uuid.UUID `bson:"company_id,omitempty"` - IsVerified bool `bson:"is_verified"` - DeviceTokens []DeviceToken `bson:"device_tokens"` // For push notifications - LastLoginAt *int64 `bson:"last_login_at,omitempty"` // Unix timestamp - CreatedAt int64 `bson:"created_at"` // Unix timestamp - UpdatedAt int64 `bson:"updated_at"` // Unix timestamp -} diff --git a/internal/customer/form.go b/internal/customer/form.go deleted file mode 100644 index afda35b..0000000 --- a/internal/customer/form.go +++ /dev/null @@ -1,134 +0,0 @@ -package customer - -// Customer Registration DTOs -type RegisterCustomerForm struct { - FullName string `json:"full_name" valid:"required,length(2|100)"` - Username string `json:"username" valid:"required,alphanum,length(3|30)"` - Email string `json:"email" valid:"required,email"` - Mobile string `json:"mobile" valid:"required,length(10|15)"` - Password string `json:"password" valid:"required,length(8|128)"` - NationalID *string `json:"national_id,omitempty" valid:"optional,length(5|20)"` - Gender *string `json:"gender,omitempty" valid:"optional,in(male|female|other)"` - Birthdate *int64 `json:"birthdate,omitempty" valid:"optional,unix_timestamp"` - ProfileImage *string `json:"profile_image,omitempty" valid:"optional,url"` - CompanyID *string `json:"company_id,omitempty" valid:"optional,uuid"` -} - -type UpdateCustomerForm struct { - 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"` - Mobile *string `json:"mobile,omitempty" valid:"optional,length(10|15)"` - NationalID *string `json:"national_id,omitempty" valid:"optional,length(5|20)"` - Gender *string `json:"gender,omitempty" valid:"optional,in(male|female|other)"` - Birthdate *int64 `json:"birthdate,omitempty" valid:"optional,unix_timestamp"` - ProfileImage *string `json:"profile_image,omitempty" valid:"optional,url"` - Status *string `json:"status,omitempty" valid:"optional,in(active|inactive)"` -} - -// Customer Authentication DTOs -type LoginForm struct { - EmailOrMobile string `json:"email_or_mobile" valid:"required"` - Password string `json:"password" valid:"required"` -} - -type RefreshTokenForm struct { - RefreshToken string `json:"refresh_token" valid:"required"` -} - -type ChangePasswordForm struct { - OldPassword string `json:"old_password" valid:"required"` - NewPassword string `json:"new_password" valid:"required,length(8|128)"` -} - -// Device Token DTOs -type AddDeviceTokenForm struct { - DeviceToken string `json:"device_token" valid:"required,length(32|255)"` - DeviceType string `json:"device_type" valid:"required,in(android|ios)"` -} - -type RemoveDeviceTokenForm struct { - DeviceToken string `json:"device_token" valid:"required"` -} - -// Admin DTOs -type ListCustomersForm struct { - Search *string `query:"search" valid:"optional"` - Status *string `query:"status" valid:"optional,in(active|inactive)"` - Gender *string `query:"gender" valid:"optional,in(male|female|other)"` - CompanyID *string `query:"company_id" valid:"optional,uuid"` - 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(full_name|email|mobile|created_at|last_login_at)"` - SortOrder *string `query:"sort_order" valid:"optional,in(asc|desc)"` -} - -type UpdateCustomerStatusForm struct { - Status string `json:"status" valid:"required,in(active|inactive)"` -} - -// Response DTOs -type CustomerResponse struct { - ID string `json:"id"` - FullName string `json:"full_name"` - Username string `json:"username"` - Email string `json:"email"` - Mobile string `json:"mobile"` - NationalID *string `json:"national_id,omitempty"` - Gender *string `json:"gender,omitempty"` - Birthdate *int64 `json:"birthdate,omitempty"` - ProfileImage *string `json:"profile_image,omitempty"` - Status string `json:"status"` - CompanyID *string `json:"company_id,omitempty"` - IsVerified bool `json:"is_verified"` - DeviceTokens []DeviceToken `json:"device_tokens"` - LastLoginAt *int64 `json:"last_login_at,omitempty"` - CreatedAt int64 `json:"created_at"` - UpdatedAt int64 `json:"updated_at"` -} - -type AuthResponse struct { - Customer *CustomerResponse `json:"customer"` - AccessToken string `json:"access_token"` - RefreshToken string `json:"refresh_token"` - ExpiresAt int64 `json:"expires_at"` -} - -type DeviceTokenResponse struct { - Token string `json:"token"` - DeviceType string `json:"device_type"` - CreatedAt int64 `json:"created_at"` - UpdatedAt int64 `json:"updated_at"` -} - -// Helper function to convert Customer to CustomerResponse -func (c *Customer) ToResponse() *CustomerResponse { - gender := "" - if c.Gender != nil { - gender = string(*c.Gender) - } - - companyID := "" - if c.CompanyID != nil { - companyID = c.CompanyID.String() - } - - return &CustomerResponse{ - ID: c.ID.String(), - FullName: c.FullName, - Username: c.Username, - Email: c.Email, - Mobile: c.Mobile, - NationalID: c.NationalID, - Gender: &gender, - Birthdate: c.Birthdate, - ProfileImage: c.ProfileImage, - Status: string(c.Status), - CompanyID: &companyID, - IsVerified: c.IsVerified, - DeviceTokens: c.DeviceTokens, - LastLoginAt: c.LastLoginAt, - CreatedAt: c.CreatedAt, - UpdatedAt: c.UpdatedAt, - } -} diff --git a/internal/customer/handler.go b/internal/customer/handler.go deleted file mode 100644 index 2bc7012..0000000 --- a/internal/customer/handler.go +++ /dev/null @@ -1,571 +0,0 @@ -package customer - -import ( - "net/http" - "tm/pkg/response" - - "github.com/asaskevich/govalidator" - "github.com/google/uuid" - "github.com/labstack/echo/v4" -) - -// Handler handles HTTP requests for customer operations -type Handler struct { - service Service -} - -// NewHandler creates a new customer handler -func NewHandler(service Service) *Handler { - return &Handler{ - service: service, - } -} - -// RegisterRoutes registers all customer routes -func (h *Handler) RegisterRoutes(e *echo.Echo) { - - v1 := e.Group("/api/v1") - - v1.POST("/customers/register", h.RegisterCustomer) - // Public routes (no authentication required) - v1.POST("/customers/login", h.Login) - v1.POST("/customers/refresh-token", h.RefreshToken) - - // Protected routes (authentication required) - customers := v1.Group("/customers") - customers.Use(h.authMiddleware) // TODO: Implement auth middleware - customers.GET("/profile", h.GetProfile) - customers.PUT("/profile", h.UpdateProfile) - customers.PUT("/change-password", h.ChangePassword) - customers.POST("/device-token", h.AddDeviceToken) - customers.DELETE("/device-token", h.RemoveDeviceToken) - customers.POST("/logout", h.Logout) - - // Admin routes (admin authentication required) - admin := v1.Group("/admin/customers") - admin.Use(h.adminAuthMiddleware) // TODO: Implement admin auth middleware - admin.POST("/register", h.RegisterCustomer) - admin.GET("", h.ListCustomers) - admin.GET("/:id", h.GetCustomerByID) - admin.PUT("/:id/status", h.UpdateCustomerStatus) - admin.GET("/device-tokens", h.GetAllDeviceTokens) -} - -// RegisterCustomer handles customer registration -// @Summary Register a new customer -// @Description Register a new customer with the provided information -// @Tags customers -// @Accept json -// @Produce json -// @Param customer body RegisterCustomerForm true "Customer registration information" -// @Success 201 {object} response.APIResponse{data=customer.CustomerResponse} "Customer registered successfully" -// @Failure 400 {object} response.APIResponse "Bad request" -// @Failure 409 {object} response.APIResponse "Customer already exists" -// @Failure 500 {object} response.APIResponse "Internal server error" -// @Router /admin/customers/register [post] -func (h *Handler) RegisterCustomer(c echo.Context) error { - var form RegisterCustomerForm - - if err := c.Bind(&form); err != nil { - return response.BadRequest(c, "Invalid request body", err.Error()) - } - - // Validate form - if _, err := govalidator.ValidateStruct(form); err != nil { - return response.ValidationError(c, "Validation failed", err.Error()) - } - - // Register customer - customer, err := h.service.RegisterCustomer(c.Request().Context(), &form) - if err != nil { - return response.Conflict(c, err.Error()) - } - - return response.Created(c, customer.ToResponse(), "Customer registered successfully") -} - -// Login handles customer authentication -// @Summary Customer login -// @Description Authenticate customer with email and password -// @Tags customers -// @Accept json -// @Produce json -// @Param credentials body LoginForm true "Login credentials" -// @Success 200 {object} response.APIResponse{data=customer.AuthResponse} "Login successful" -// @Failure 400 {object} response.APIResponse "Bad request" -// @Failure 401 {object} response.APIResponse "Invalid credentials" -// @Failure 500 {object} response.APIResponse "Internal server error" -// @Router /customers/login [post] -func (h *Handler) Login(c echo.Context) error { - var form LoginForm - - if err := c.Bind(&form); err != nil { - return response.BadRequest(c, "Invalid request body", err.Error()) - } - - // Validate form - if _, err := govalidator.ValidateStruct(form); err != nil { - return response.ValidationError(c, "Validation failed", err.Error()) - } - - // Authenticate customer - authResponse, err := h.service.Login(c.Request().Context(), &form) - if err != nil { - return response.Unauthorized(c, err.Error()) - } - - return response.Success(c, authResponse, "Login successful") -} - -// RefreshToken handles token refresh -// @Summary Refresh access token -// @Description Refresh the access token using a refresh token -// @Tags customers -// @Accept json -// @Produce json -// @Param token body RefreshTokenForm true "Refresh token information" -// @Success 200 {object} response.APIResponse{data=customer.AuthResponse} "Token refreshed successfully" -// @Failure 400 {object} response.APIResponse "Bad request" -// @Failure 401 {object} response.APIResponse "Invalid refresh token" -// @Failure 500 {object} response.APIResponse "Internal server error" -// @Router /customers/refresh-token [post] -func (h *Handler) RefreshToken(c echo.Context) error { - var form RefreshTokenForm - - if err := c.Bind(&form); err != nil { - return response.BadRequest(c, "Invalid request body", err.Error()) - } - - // Validate form - if _, err := govalidator.ValidateStruct(form); err != nil { - return response.ValidationError(c, "Validation failed", err.Error()) - } - - // Refresh token - authResponse, err := h.service.RefreshToken(c.Request().Context(), form.RefreshToken) - if err != nil { - return response.Unauthorized(c, err.Error()) - } - - return response.Success(c, authResponse, "Token refreshed successfully") -} - -// GetProfile retrieves customer profile -// @Summary Get customer profile -// @Description Retrieve the authenticated customer's profile information -// @Tags customers -// @Accept json -// @Produce json -// @Security BearerAuth -// @Success 200 {object} response.APIResponse{data=customer.CustomerResponse} "Profile retrieved successfully" -// @Failure 401 {object} response.APIResponse "Unauthorized" -// @Failure 404 {object} response.APIResponse "Customer not found" -// @Failure 500 {object} response.APIResponse "Internal server error" -// @Router /customers/profile [get] -func (h *Handler) GetProfile(c echo.Context) error { - // Get customer ID from context (set by auth middleware) - customerID, err := h.getCustomerIDFromContext(c) - if err != nil { - return response.Unauthorized(c, "Invalid authentication") - } - - // Get customer - customer, err := h.service.GetCustomerByID(c.Request().Context(), customerID) - if err != nil { - return response.NotFound(c, "Customer not found") - } - - return response.Success(c, customer.ToResponse(), "Profile retrieved successfully") -} - -// UpdateProfile updates customer profile -// @Summary Update customer profile -// @Description Update the authenticated customer's profile information -// @Tags customers -// @Accept json -// @Produce json -// @Security BearerAuth -// @Param profile body UpdateCustomerForm true "Profile update information" -// @Success 200 {object} response.APIResponse{data=customer.CustomerResponse} "Profile updated successfully" -// @Failure 400 {object} response.APIResponse "Bad request" -// @Failure 401 {object} response.APIResponse "Unauthorized" -// @Failure 500 {object} response.APIResponse "Internal server error" -// @Router /customers/profile [put] -func (h *Handler) UpdateProfile(c echo.Context) error { - // Get customer ID from context - customerID, err := h.getCustomerIDFromContext(c) - if err != nil { - return response.Unauthorized(c, "Invalid authentication") - } - - var form UpdateCustomerForm - - if err := c.Bind(&form); err != nil { - return response.BadRequest(c, "Invalid request body", err.Error()) - } - - // Validate form - if _, err := govalidator.ValidateStruct(form); err != nil { - return response.ValidationError(c, "Validation failed", err.Error()) - } - - // Update customer - customer, err := h.service.UpdateCustomer(c.Request().Context(), customerID, &form) - if err != nil { - return response.BadRequest(c, err.Error(), "") - } - - return response.Success(c, customer.ToResponse(), "Profile updated successfully") -} - -// ChangePassword changes customer password -// @Summary Change customer password -// @Description Change the authenticated customer's password -// @Tags customers -// @Accept json -// @Produce json -// @Security BearerAuth -// @Param password body ChangePasswordForm true "Password change information" -// @Success 200 {object} response.APIResponse "Password changed successfully" -// @Failure 400 {object} response.APIResponse "Bad request" -// @Failure 401 {object} response.APIResponse "Unauthorized" -// @Failure 500 {object} response.APIResponse "Internal server error" -// @Router /customers/change-password [put] -func (h *Handler) ChangePassword(c echo.Context) error { - // Get customer ID from context - customerID, err := h.getCustomerIDFromContext(c) - if err != nil { - return response.Unauthorized(c, "Invalid authentication") - } - - var form ChangePasswordForm - - if err := c.Bind(&form); err != nil { - return response.BadRequest(c, "Invalid request body", err.Error()) - } - - // Validate form - if _, err := govalidator.ValidateStruct(form); err != nil { - return response.ValidationError(c, "Validation failed", err.Error()) - } - - // Change password - err = h.service.ChangePassword(c.Request().Context(), customerID, &form) - if err != nil { - return response.BadRequest(c, err.Error(), "") - } - - return response.Success(c, nil, "Password changed successfully") -} - -// AddDeviceToken adds a device token for push notifications -// @Summary Add device token -// @Description Add a device token for push notifications -// @Tags customers -// @Accept json -// @Produce json -// @Security BearerAuth -// @Param device_token body AddDeviceTokenForm true "Device token information" -// @Success 200 {object} response.APIResponse "Device token added successfully" -// @Failure 400 {object} response.APIResponse "Bad request" -// @Failure 401 {object} response.APIResponse "Unauthorized" -// @Failure 500 {object} response.APIResponse "Internal server error" -// @Router /customers/device-token [post] -func (h *Handler) AddDeviceToken(c echo.Context) error { - // Get customer ID from context - customerID, err := h.getCustomerIDFromContext(c) - if err != nil { - return response.Unauthorized(c, "Invalid authentication") - } - - var form AddDeviceTokenForm - - if err := c.Bind(&form); err != nil { - return response.BadRequest(c, "Invalid request body", err.Error()) - } - - // Validate form - if _, err := govalidator.ValidateStruct(form); err != nil { - return response.ValidationError(c, "Validation failed", err.Error()) - } - - // Add device token - err = h.service.AddDeviceToken(c.Request().Context(), customerID, &form) - if err != nil { - return response.BadRequest(c, err.Error(), "") - } - - return response.Success(c, nil, "Device token added successfully") -} - -// RemoveDeviceToken removes a device token -// @Summary Remove device token -// @Description Remove a device token for push notifications -// @Tags customers -// @Accept json -// @Produce json -// @Security BearerAuth -// @Param device_token body RemoveDeviceTokenForm true "Device token information" -// @Success 200 {object} response.APIResponse "Device token removed successfully" -// @Failure 400 {object} response.APIResponse "Bad request" -// @Failure 401 {object} response.APIResponse "Unauthorized" -// @Failure 500 {object} response.APIResponse "Internal server error" -// @Router /customers/device-token [delete] -func (h *Handler) RemoveDeviceToken(c echo.Context) error { - // Get customer ID from context - customerID, err := h.getCustomerIDFromContext(c) - if err != nil { - return response.Unauthorized(c, "Invalid authentication") - } - - var form RemoveDeviceTokenForm - - if err := c.Bind(&form); err != nil { - return response.BadRequest(c, "Invalid request body", err.Error()) - } - - // Validate form - if _, err := govalidator.ValidateStruct(form); err != nil { - return response.ValidationError(c, "Validation failed", err.Error()) - } - - // Remove device token - err = h.service.RemoveDeviceToken(c.Request().Context(), customerID, &form) - if err != nil { - return response.BadRequest(c, err.Error(), "") - } - - return response.Success(c, nil, "Device token removed successfully") -} - -// Logout handles customer logout -// @Summary Customer logout -// @Description Logout customer and invalidate device token -// @Tags customers -// @Accept json -// @Produce json -// @Security BearerAuth -// @Param logout body object true "Logout request with device token" -// @Success 200 {object} response.APIResponse "Logged out successfully" -// @Failure 400 {object} response.APIResponse "Bad request" -// @Failure 401 {object} response.APIResponse "Unauthorized" -// @Failure 500 {object} response.APIResponse "Internal server error" -// @Router /customers/logout [post] -func (h *Handler) Logout(c echo.Context) error { - // Get customer ID from context - customerID, err := h.getCustomerIDFromContext(c) - if err != nil { - return response.Unauthorized(c, "Invalid authentication") - } - - // Get device token from request body - var req struct { - DeviceToken string `json:"device_token" valid:"required"` - } - - if err := c.Bind(&req); err != nil { - return response.BadRequest(c, "Invalid request body", err.Error()) - } - - // Validate request - if _, err := govalidator.ValidateStruct(req); err != nil { - return response.ValidationError(c, "Validation failed", err.Error()) - } - - // Logout - err = h.service.Logout(c.Request().Context(), customerID, req.DeviceToken) - if err != nil { - return response.BadRequest(c, err.Error(), "") - } - - return response.Success(c, nil, "Logged out successfully") -} - -// ListCustomers lists customers (admin only) -// @Summary List customers -// @Description Retrieve a paginated list of customers (admin only) -// @Tags customers -// @Accept json -// @Produce json -// @Security BearerAuth -// @Param page query int false "Page number" default(1) -// @Param limit query int false "Number of items per page" default(20) -// @Param search query string false "Search term" -// @Success 200 {object} response.APIResponse{data=[]customer.CustomerResponse} "Customers retrieved successfully" -// @Failure 400 {object} response.APIResponse "Bad request" -// @Failure 401 {object} response.APIResponse "Unauthorized" -// @Failure 500 {object} response.APIResponse "Internal server error" -// @Router /admin/customers [get] -func (h *Handler) ListCustomers(c echo.Context) error { - var form ListCustomersForm - - // Bind query parameters - if err := c.Bind(&form); err != nil { - return response.BadRequest(c, "Invalid query parameters", err.Error()) - } - - // Validate form - if _, err := govalidator.ValidateStruct(form); err != nil { - return response.ValidationError(c, "Validation failed", err.Error()) - } - - // List customers - customers, total, err := h.service.ListCustomers(c.Request().Context(), &form) - if err != nil { - return response.InternalServerError(c, err.Error()) - } - - // Convert to responses - var responses []*CustomerResponse - for _, customer := range customers { - responses = append(responses, customer.ToResponse()) - } - - // Create metadata - meta := &response.Meta{ - Total: total, - Limit: 20, // Default limit - Offset: 0, // Default offset - } - - if form.Limit != nil { - meta.Limit = *form.Limit - } - if form.Offset != nil { - meta.Offset = *form.Offset - } - - return response.SuccessWithMeta(c, responses, meta, "Customers retrieved successfully") -} - -// GetCustomerByID retrieves a customer by ID (admin only) -// @Summary Get customer by ID -// @Description Retrieve customer information by ID (admin only) -// @Tags customers -// @Accept json -// @Produce json -// @Security BearerAuth -// @Param id path string true "Customer ID" -// @Success 200 {object} response.APIResponse{data=customer.CustomerResponse} "Customer retrieved successfully" -// @Failure 400 {object} response.APIResponse "Bad request" -// @Failure 401 {object} response.APIResponse "Unauthorized" -// @Failure 404 {object} response.APIResponse "Customer not found" -// @Failure 500 {object} response.APIResponse "Internal server error" -// @Router /admin/customers/{id} [get] -func (h *Handler) GetCustomerByID(c echo.Context) error { - // Parse customer ID - customerIDStr := c.Param("id") - customerID, err := uuid.Parse(customerIDStr) - if err != nil { - return response.BadRequest(c, "Invalid customer ID", err.Error()) - } - - // Get customer - customer, err := h.service.GetCustomerByID(c.Request().Context(), customerID) - if err != nil { - return response.NotFound(c, "Customer not found") - } - - return response.Success(c, customer.ToResponse(), "Customer retrieved successfully") -} - -// UpdateCustomerStatus updates customer status (admin only) -// @Summary Update customer status -// @Description Update customer status (admin only) -// @Tags customers -// @Accept json -// @Produce json -// @Security BearerAuth -// @Param id path string true "Customer ID" -// @Param status body UpdateCustomerStatusForm true "Status update information" -// @Success 200 {object} response.APIResponse "Customer status updated successfully" -// @Failure 400 {object} response.APIResponse "Bad request" -// @Failure 401 {object} response.APIResponse "Unauthorized" -// @Failure 404 {object} response.APIResponse "Customer not found" -// @Failure 500 {object} response.APIResponse "Internal server error" -// @Router /admin/customers/{id}/status [put] -func (h *Handler) UpdateCustomerStatus(c echo.Context) error { - // Parse customer ID - customerIDStr := c.Param("id") - customerID, err := uuid.Parse(customerIDStr) - if err != nil { - return response.BadRequest(c, "Invalid customer ID", err.Error()) - } - - var form UpdateCustomerStatusForm - - if err := c.Bind(&form); err != nil { - return response.BadRequest(c, "Invalid request body", err.Error()) - } - - // Validate form - if _, err := govalidator.ValidateStruct(form); err != nil { - return response.ValidationError(c, "Validation failed", err.Error()) - } - - // Update status - err = h.service.UpdateCustomerStatus(c.Request().Context(), customerID, &form) - if err != nil { - return response.BadRequest(c, err.Error(), "") - } - - return response.Success(c, nil, "Customer status updated successfully") -} - -// GetAllDeviceTokens retrieves all device tokens (admin only) -// @Summary Get all device tokens -// @Description Retrieve all device tokens (admin only) -// @Tags customers -// @Accept json -// @Produce json -// @Security BearerAuth -// @Success 200 {object} response.APIResponse{data=[]string} "Device tokens retrieved successfully" -// @Failure 401 {object} response.APIResponse "Unauthorized" -// @Failure 500 {object} response.APIResponse "Internal server error" -// @Router /admin/customers/device-tokens [get] -func (h *Handler) GetAllDeviceTokens(c echo.Context) error { - // Get all device tokens - tokens, err := h.service.GetAllDeviceTokens(c.Request().Context()) - if err != nil { - return response.InternalServerError(c, err.Error()) - } - - return response.Success(c, tokens, "Device tokens retrieved successfully") -} - -// Helper methods - -// getCustomerIDFromContext extracts customer ID from context -func (h *Handler) getCustomerIDFromContext(c echo.Context) (uuid.UUID, error) { - // TODO: Implement proper JWT token validation and extraction - // For now, we'll use a header-based approach - customerIDStr := c.Request().Header.Get("X-Customer-ID") - if customerIDStr == "" { - return uuid.Nil, echo.NewHTTPError(http.StatusUnauthorized, "Missing customer ID") - } - - customerID, err := uuid.Parse(customerIDStr) - if err != nil { - return uuid.Nil, echo.NewHTTPError(http.StatusUnauthorized, "Invalid customer ID") - } - - return customerID, nil -} - -// authMiddleware is a placeholder for authentication middleware -func (h *Handler) authMiddleware(next echo.HandlerFunc) echo.HandlerFunc { - return func(c echo.Context) error { - // TODO: Implement proper JWT token validation - // For now, we'll just pass through - return next(c) - } -} - -// adminAuthMiddleware is a placeholder for admin authentication middleware -func (h *Handler) adminAuthMiddleware(next echo.HandlerFunc) echo.HandlerFunc { - return func(c echo.Context) error { - // TODO: Implement proper admin authentication - // For now, we'll just pass through - return next(c) - } -} diff --git a/internal/customer/repository.go b/internal/customer/repository.go deleted file mode 100644 index 4992a81..0000000 --- a/internal/customer/repository.go +++ /dev/null @@ -1,554 +0,0 @@ -package customer - -import ( - "context" - "errors" - "time" - "tm/pkg/logger" - mongopkg "tm/pkg/mongo" - - "github.com/google/uuid" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" -) - -// Repository defines methods for customer (mobile user) data access -type Repository interface { - Create(ctx context.Context, customer *Customer) error - GetByID(ctx context.Context, id uuid.UUID) (*Customer, error) - GetByEmail(ctx context.Context, email string) (*Customer, error) - GetByUsername(ctx context.Context, username string) (*Customer, error) - GetByMobile(ctx context.Context, mobile string) (*Customer, error) - Update(ctx context.Context, customer *Customer) error - Delete(ctx context.Context, id uuid.UUID) error - List(ctx context.Context, limit, offset int) ([]*Customer, error) - Search(ctx context.Context, search string, status *string, gender *string, companyID *uuid.UUID, limit, offset int, sortBy, sortOrder string) ([]*Customer, error) - GetByCompanyID(ctx context.Context, companyID uuid.UUID, limit, offset int) ([]*Customer, error) - AddDeviceToken(ctx context.Context, id uuid.UUID, deviceToken DeviceToken) error - RemoveDeviceToken(ctx context.Context, id uuid.UUID, token string) error - UpdateLastLogin(ctx context.Context, id uuid.UUID) error - GetAllDeviceTokens(ctx context.Context) ([]DeviceToken, error) -} - -// customerRepository implements the Repository interface -type customerRepository struct { - collection *mongo.Collection - logger logger.Logger -} - -// NewCustomerRepository creates a new customer repository -func NewCustomerRepository(mongoManager *mongopkg.ConnectionManager, logger logger.Logger) Repository { - collection := mongoManager.GetCollection("customers") - - // Create indexes - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() - - // Email index (unique) - emailIndex := mongo.IndexModel{ - Keys: bson.D{{Key: "email", Value: 1}}, - Options: options.Index().SetUnique(true), - } - - // Username index (unique) - usernameIndex := mongo.IndexModel{ - Keys: bson.D{{Key: "username", Value: 1}}, - Options: options.Index().SetUnique(true), - } - - // Mobile index (unique) - mobileIndex := mongo.IndexModel{ - Keys: bson.D{{Key: "mobile", Value: 1}}, - Options: options.Index().SetUnique(true), - } - - // Company ID index - companyIndex := mongo.IndexModel{ - Keys: bson.D{{Key: "company_id", Value: 1}}, - } - - // Status index - statusIndex := mongo.IndexModel{ - Keys: bson.D{{Key: "status", Value: 1}}, - } - - // Gender index - genderIndex := mongo.IndexModel{ - Keys: bson.D{{Key: "gender", Value: 1}}, - } - - // Created at index - createdAtIndex := mongo.IndexModel{ - Keys: bson.D{{Key: "created_at", Value: -1}}, - } - - // Full text search index - textIndex := mongo.IndexModel{ - Keys: bson.D{ - {Key: "full_name", Value: "text"}, - {Key: "email", Value: "text"}, - {Key: "mobile", Value: "text"}, - {Key: "username", Value: "text"}, - }, - } - - _, err := collection.Indexes().CreateMany(ctx, []mongo.IndexModel{ - emailIndex, - usernameIndex, - mobileIndex, - companyIndex, - statusIndex, - genderIndex, - createdAtIndex, - textIndex, - }) - - if err != nil { - logger.Warn("Failed to create customer indexes", map[string]interface{}{ - "error": err.Error(), - }) - } - - return &customerRepository{ - collection: collection, - logger: logger, - } -} - -// Create creates a new customer -func (r *customerRepository) Create(ctx context.Context, customer *Customer) error { - // Set created/updated timestamps using Unix timestamps - now := time.Now().Unix() - customer.CreatedAt = now - customer.UpdatedAt = now - - // Insert customer - _, err := r.collection.InsertOne(ctx, customer) - if err != nil { - if mongo.IsDuplicateKeyError(err) { - return errors.New("customer already exists") - } - r.logger.Error("Failed to create customer", map[string]interface{}{ - "error": err.Error(), - "email": customer.Email, - "customer_id": customer.ID.String(), - }) - return err - } - - r.logger.Info("Customer created successfully", map[string]interface{}{ - "customer_id": customer.ID.String(), - "email": customer.Email, - }) - - return nil -} - -// GetByID retrieves a customer by ID -func (r *customerRepository) GetByID(ctx context.Context, id uuid.UUID) (*Customer, error) { - var customer Customer - - filter := bson.M{"_id": id} - err := r.collection.FindOne(ctx, filter).Decode(&customer) - - if err != nil { - if err == mongo.ErrNoDocuments { - return nil, errors.New("customer not found") - } - r.logger.Error("Failed to get customer by ID", map[string]interface{}{ - "error": err.Error(), - "customer_id": id.String(), - }) - return nil, err - } - - return &customer, nil -} - -// GetByEmail retrieves a customer by email -func (r *customerRepository) GetByEmail(ctx context.Context, email string) (*Customer, error) { - var customer Customer - - filter := bson.M{"email": email} - err := r.collection.FindOne(ctx, filter).Decode(&customer) - - if err != nil { - if err == mongo.ErrNoDocuments { - return nil, errors.New("customer not found") - } - r.logger.Error("Failed to get customer by email", map[string]interface{}{ - "error": err.Error(), - "email": email, - }) - return nil, err - } - - return &customer, nil -} - -// GetByUsername retrieves a customer by username -func (r *customerRepository) GetByUsername(ctx context.Context, username string) (*Customer, error) { - var customer Customer - - filter := bson.M{"username": username} - err := r.collection.FindOne(ctx, filter).Decode(&customer) - - if err != nil { - if err == mongo.ErrNoDocuments { - return nil, errors.New("customer not found") - } - r.logger.Error("Failed to get customer by username", map[string]interface{}{ - "error": err.Error(), - "username": username, - }) - return nil, err - } - - return &customer, nil -} - -// GetByMobile retrieves a customer by mobile -func (r *customerRepository) GetByMobile(ctx context.Context, mobile string) (*Customer, error) { - var customer Customer - - filter := bson.M{"mobile": mobile} - err := r.collection.FindOne(ctx, filter).Decode(&customer) - - if err != nil { - if err == mongo.ErrNoDocuments { - return nil, errors.New("customer not found") - } - r.logger.Error("Failed to get customer by mobile", map[string]interface{}{ - "error": err.Error(), - "mobile": mobile, - }) - return nil, err - } - - return &customer, nil -} - -// Update updates a customer -func (r *customerRepository) Update(ctx context.Context, customer *Customer) error { - // Set updated timestamp using Unix timestamp - customer.UpdatedAt = time.Now().Unix() - - filter := bson.M{"_id": customer.ID} - update := bson.M{"$set": customer} - - result, err := r.collection.UpdateOne(ctx, filter, update) - if err != nil { - r.logger.Error("Failed to update customer", map[string]interface{}{ - "error": err.Error(), - "customer_id": customer.ID.String(), - }) - return err - } - - if result.MatchedCount == 0 { - return errors.New("customer not found") - } - - r.logger.Info("Customer updated successfully", map[string]interface{}{ - "customer_id": customer.ID.String(), - "email": customer.Email, - }) - - return nil -} - -// Delete deletes a customer (soft delete by setting is_active to false) -func (r *customerRepository) Delete(ctx context.Context, id uuid.UUID) error { - filter := bson.M{"_id": id} - update := bson.M{ - "$set": bson.M{ - "is_active": false, - "updated_at": time.Now().Unix(), - }, - } - - result, err := r.collection.UpdateOne(ctx, filter, update) - if err != nil { - r.logger.Error("Failed to delete customer", map[string]interface{}{ - "error": err.Error(), - "customer_id": id.String(), - }) - return err - } - - if result.MatchedCount == 0 { - return errors.New("customer not found") - } - - r.logger.Info("Customer deleted successfully", map[string]interface{}{ - "customer_id": id.String(), - }) - - return nil -} - -// List retrieves customers with pagination -func (r *customerRepository) List(ctx context.Context, limit, offset int) ([]*Customer, error) { - var customers []*Customer - - // Build options - opts := options.Find() - opts.SetLimit(int64(limit)) - opts.SetSkip(int64(offset)) - opts.SetSort(bson.D{{Key: "created_at", Value: -1}}) // Sort by created_at desc - - // Only active customers by default - filter := bson.M{"status": "active"} - - cursor, err := r.collection.Find(ctx, filter, opts) - if err != nil { - r.logger.Error("Failed to list customers", map[string]interface{}{ - "error": err.Error(), - "limit": limit, - "offset": offset, - }) - return nil, err - } - defer cursor.Close(ctx) - - if err = cursor.All(ctx, &customers); err != nil { - r.logger.Error("Failed to decode customers", map[string]interface{}{ - "error": err.Error(), - }) - return nil, err - } - - return customers, nil -} - -// Search retrieves customers with search and filters -func (r *customerRepository) Search(ctx context.Context, search string, status *string, gender *string, companyID *uuid.UUID, limit, offset int, sortBy, sortOrder string) ([]*Customer, error) { - var customers []*Customer - - // Build filter - filter := bson.M{} - - if search != "" { - filter["$text"] = bson.M{"$search": search} - } - - if status != nil { - filter["status"] = *status - } - - if gender != nil { - filter["gender"] = *gender - } - - if companyID != nil { - filter["company_id"] = *companyID - } - - // Build options - opts := options.Find() - opts.SetLimit(int64(limit)) - opts.SetSkip(int64(offset)) - - // Set sort - if sortBy != "" { - sortValue := 1 - if sortOrder == "desc" { - sortValue = -1 - } - opts.SetSort(bson.D{{Key: sortBy, Value: sortValue}}) - } else { - opts.SetSort(bson.D{{Key: "created_at", Value: -1}}) // Default sort - } - - cursor, err := r.collection.Find(ctx, filter, opts) - if err != nil { - r.logger.Error("Failed to search customers", map[string]interface{}{ - "error": err.Error(), - "search": search, - }) - return nil, err - } - defer cursor.Close(ctx) - - if err = cursor.All(ctx, &customers); err != nil { - r.logger.Error("Failed to decode customers from search", map[string]interface{}{ - "error": err.Error(), - }) - return nil, err - } - - return customers, nil -} - -// GetByCompanyID retrieves customers by company ID with pagination -func (r *customerRepository) GetByCompanyID(ctx context.Context, companyID uuid.UUID, limit, offset int) ([]*Customer, error) { - var customers []*Customer - - // Build options - opts := options.Find() - opts.SetLimit(int64(limit)) - opts.SetSkip(int64(offset)) - opts.SetSort(bson.D{{Key: "created_at", Value: -1}}) // Sort by created_at desc - - // Filter by company ID and active status - filter := bson.M{ - "company_id": companyID, - "status": "active", - } - - cursor, err := r.collection.Find(ctx, filter, opts) - if err != nil { - r.logger.Error("Failed to get customers by company ID", map[string]interface{}{ - "error": err.Error(), - "company_id": companyID.String(), - "limit": limit, - "offset": offset, - }) - return nil, err - } - defer cursor.Close(ctx) - - if err = cursor.All(ctx, &customers); err != nil { - r.logger.Error("Failed to decode customers by company", map[string]interface{}{ - "error": err.Error(), - "company_id": companyID.String(), - }) - return nil, err - } - - return customers, nil -} - -// AddDeviceToken adds a device token for push notifications -func (r *customerRepository) AddDeviceToken(ctx context.Context, id uuid.UUID, deviceToken DeviceToken) error { - // Set timestamps - now := time.Now().Unix() - deviceToken.CreatedAt = now - deviceToken.UpdatedAt = now - - filter := bson.M{"_id": id} - update := bson.M{ - "$push": bson.M{"device_tokens": deviceToken}, // Use $push to add new token - "$set": bson.M{"updated_at": now}, - } - - result, err := r.collection.UpdateOne(ctx, filter, update) - if err != nil { - r.logger.Error("Failed to add device token", map[string]interface{}{ - "error": err.Error(), - "customer_id": id.String(), - "token": deviceToken.Token, - }) - return err - } - - if result.MatchedCount == 0 { - return errors.New("customer not found") - } - - r.logger.Info("Device token added successfully", map[string]interface{}{ - "customer_id": id.String(), - "token": deviceToken.Token, - "device_type": deviceToken.DeviceType, - }) - - return nil -} - -// RemoveDeviceToken removes a device token -func (r *customerRepository) RemoveDeviceToken(ctx context.Context, id uuid.UUID, token string) error { - filter := bson.M{"_id": id} - update := bson.M{ - "$pull": bson.M{"device_tokens": bson.M{"token": token}}, // Use $pull to remove the token - "$set": bson.M{"updated_at": time.Now().Unix()}, - } - - result, err := r.collection.UpdateOne(ctx, filter, update) - if err != nil { - r.logger.Error("Failed to remove device token", map[string]interface{}{ - "error": err.Error(), - "customer_id": id.String(), - "token": token, - }) - return err - } - - if result.MatchedCount == 0 { - return errors.New("customer not found") - } - - r.logger.Info("Device token removed successfully", map[string]interface{}{ - "customer_id": id.String(), - "token": token, - }) - - return nil -} - -// UpdateLastLogin updates the last login timestamp -func (r *customerRepository) UpdateLastLogin(ctx context.Context, id uuid.UUID) error { - filter := bson.M{"_id": id} - update := bson.M{ - "$set": bson.M{ - "last_login_at": time.Now().Unix(), - "updated_at": time.Now().Unix(), - }, - } - - result, err := r.collection.UpdateOne(ctx, filter, update) - if err != nil { - r.logger.Error("Failed to update last login", map[string]interface{}{ - "error": err.Error(), - "customer_id": id.String(), - }) - return err - } - - if result.MatchedCount == 0 { - return errors.New("customer not found") - } - - r.logger.Info("Last login updated successfully", map[string]interface{}{ - "customer_id": id.String(), - }) - - return nil -} - -// GetAllDeviceTokens retrieves all device tokens for push notifications -func (r *customerRepository) GetAllDeviceTokens(ctx context.Context) ([]DeviceToken, error) { - var customers []*Customer - - // Get all active customers - filter := bson.M{"status": "active"} - opts := options.Find().SetProjection(bson.M{"device_tokens": 1}) - - cursor, err := r.collection.Find(ctx, filter, opts) - if err != nil { - r.logger.Error("Failed to get all device tokens", map[string]interface{}{ - "error": err.Error(), - }) - return nil, err - } - defer cursor.Close(ctx) - - if err = cursor.All(ctx, &customers); err != nil { - r.logger.Error("Failed to decode customers for device tokens", map[string]interface{}{ - "error": err.Error(), - }) - return nil, err - } - - // Collect all device tokens - var allTokens []DeviceToken - for _, customer := range customers { - allTokens = append(allTokens, customer.DeviceTokens...) - } - - r.logger.Info("Retrieved all device tokens", map[string]interface{}{ - "total_tokens": len(allTokens), - }) - - return allTokens, nil -} diff --git a/internal/customer/service.go b/internal/customer/service.go deleted file mode 100644 index 7161d4c..0000000 --- a/internal/customer/service.go +++ /dev/null @@ -1,523 +0,0 @@ -package customer - -import ( - "context" - "crypto/rand" - "encoding/hex" - "errors" - "strings" - "time" - "tm/pkg/logger" - - "github.com/google/uuid" - "golang.org/x/crypto/bcrypt" -) - -// Service defines business logic for customer operations -type Service interface { - RegisterCustomer(ctx context.Context, form *RegisterCustomerForm) (*Customer, error) - Login(ctx context.Context, form *LoginForm) (*AuthResponse, error) - RefreshToken(ctx context.Context, refreshToken string) (*AuthResponse, error) - ChangePassword(ctx context.Context, customerID uuid.UUID, form *ChangePasswordForm) error - GetCustomerByID(ctx context.Context, id uuid.UUID) (*Customer, error) - UpdateCustomer(ctx context.Context, id uuid.UUID, form *UpdateCustomerForm) (*Customer, error) - AddDeviceToken(ctx context.Context, customerID uuid.UUID, form *AddDeviceTokenForm) error - RemoveDeviceToken(ctx context.Context, customerID uuid.UUID, form *RemoveDeviceTokenForm) error - ListCustomers(ctx context.Context, form *ListCustomersForm) ([]*Customer, int, error) - UpdateCustomerStatus(ctx context.Context, id uuid.UUID, form *UpdateCustomerStatusForm) error - GetAllDeviceTokens(ctx context.Context) ([]DeviceToken, error) - Logout(ctx context.Context, customerID uuid.UUID, deviceToken string) error -} - -// customerService implements the Service interface -type customerService struct { - repository Repository - logger logger.Logger -} - -// NewCustomerService creates a new customer service -func NewCustomerService(repository Repository, logger logger.Logger) Service { - return &customerService{ - repository: repository, - logger: logger, - } -} - -// RegisterCustomer registers a new customer -func (s *customerService) RegisterCustomer(ctx context.Context, form *RegisterCustomerForm) (*Customer, error) { - // Check if email already exists - existingCustomer, _ := s.repository.GetByEmail(ctx, form.Email) - if existingCustomer != nil { - return nil, errors.New("email already exists") - } - - // Check if username already exists - existingCustomer, _ = s.repository.GetByUsername(ctx, form.Username) - if existingCustomer != nil { - return nil, errors.New("username already exists") - } - - // Check if mobile already exists - existingCustomer, _ = s.repository.GetByMobile(ctx, form.Mobile) - if existingCustomer != nil { - return nil, errors.New("mobile number already exists") - } - - // Hash password - hashedPassword, err := bcrypt.GenerateFromPassword([]byte(form.Password), bcrypt.DefaultCost) - if err != nil { - s.logger.Error("Failed to hash password", map[string]interface{}{ - "error": err.Error(), - }) - return nil, errors.New("failed to process password") - } - - // Parse optional fields - var companyID *uuid.UUID - if form.CompanyID != nil { - parsedID, err := uuid.Parse(*form.CompanyID) - if err != nil { - return nil, errors.New("invalid company ID") - } - companyID = &parsedID - } - - var gender *Gender - if form.Gender != nil { - g := Gender(*form.Gender) - gender = &g - } - - // Create customer - customer := &Customer{ - ID: uuid.New(), - FullName: form.FullName, - Username: form.Username, - Email: form.Email, - Mobile: form.Mobile, - Password: string(hashedPassword), - NationalID: form.NationalID, - Gender: gender, - Birthdate: form.Birthdate, - ProfileImage: form.ProfileImage, - Status: CustomerStatusActive, - CompanyID: companyID, - IsVerified: false, - DeviceTokens: []DeviceToken{}, - } - - // Save to database - err = s.repository.Create(ctx, customer) - if err != nil { - s.logger.Error("Failed to create customer", map[string]interface{}{ - "error": err.Error(), - "email": form.Email, - "username": form.Username, - }) - return nil, err - } - - s.logger.Info("Customer registered successfully", map[string]interface{}{ - "customer_id": customer.ID.String(), - "email": customer.Email, - "username": customer.Username, - }) - - return customer, nil -} - -// Login authenticates a customer -func (s *customerService) Login(ctx context.Context, form *LoginForm) (*AuthResponse, error) { - // Find customer by email or mobile - var customer *Customer - var err error - - if strings.Contains(form.EmailOrMobile, "@") { - customer, err = s.repository.GetByEmail(ctx, form.EmailOrMobile) - } else { - customer, err = s.repository.GetByMobile(ctx, form.EmailOrMobile) - } - - if err != nil { - s.logger.Warn("Login attempt with invalid credentials", map[string]interface{}{ - "email_or_mobile": form.EmailOrMobile, - "error": err.Error(), - }) - return nil, errors.New("invalid credentials") - } - - // Check if customer is active - if customer.Status != CustomerStatusActive { - return nil, errors.New("account is inactive") - } - - // Verify password - err = bcrypt.CompareHashAndPassword([]byte(customer.Password), []byte(form.Password)) - if err != nil { - s.logger.Warn("Login attempt with wrong password", map[string]interface{}{ - "customer_id": customer.ID.String(), - "email": customer.Email, - }) - return nil, errors.New("invalid credentials") - } - - // Update last login - err = s.repository.UpdateLastLogin(ctx, customer.ID) - if err != nil { - s.logger.Error("Failed to update last login", map[string]interface{}{ - "error": err.Error(), - "customer_id": customer.ID.String(), - }) - } - - // Generate tokens - accessToken, refreshToken, expiresAt, err := s.generateTokens(customer.ID) - if err != nil { - s.logger.Error("Failed to generate tokens", map[string]interface{}{ - "error": err.Error(), - "customer_id": customer.ID.String(), - }) - return nil, errors.New("failed to generate authentication tokens") - } - - s.logger.Info("Customer logged in successfully", map[string]interface{}{ - "customer_id": customer.ID.String(), - "email": customer.Email, - }) - - return &AuthResponse{ - Customer: customer.ToResponse(), - AccessToken: accessToken, - RefreshToken: refreshToken, - ExpiresAt: expiresAt, - }, nil -} - -// RefreshToken refreshes the access token -func (s *customerService) RefreshToken(ctx context.Context, refreshToken string) (*AuthResponse, error) { - // TODO: Implement JWT refresh token validation - // For now, we'll return an error - return nil, errors.New("refresh token functionality not implemented") -} - -// ChangePassword changes customer password -func (s *customerService) ChangePassword(ctx context.Context, customerID uuid.UUID, form *ChangePasswordForm) error { - // Get customer - customer, err := s.repository.GetByID(ctx, customerID) - if err != nil { - return errors.New("customer not found") - } - - // Verify old password - err = bcrypt.CompareHashAndPassword([]byte(customer.Password), []byte(form.OldPassword)) - if err != nil { - return errors.New("invalid old password") - } - - // Hash new password - hashedPassword, err := bcrypt.GenerateFromPassword([]byte(form.NewPassword), bcrypt.DefaultCost) - if err != nil { - s.logger.Error("Failed to hash new password", map[string]interface{}{ - "error": err.Error(), - "customer_id": customerID.String(), - }) - return errors.New("failed to process new password") - } - - // Update password - customer.Password = string(hashedPassword) - err = s.repository.Update(ctx, customer) - if err != nil { - s.logger.Error("Failed to update password", map[string]interface{}{ - "error": err.Error(), - "customer_id": customerID.String(), - }) - return errors.New("failed to update password") - } - - s.logger.Info("Password changed successfully", map[string]interface{}{ - "customer_id": customerID.String(), - }) - - return nil -} - -// GetCustomerByID retrieves a customer by ID -func (s *customerService) GetCustomerByID(ctx context.Context, id uuid.UUID) (*Customer, error) { - customer, err := s.repository.GetByID(ctx, id) - if err != nil { - return nil, err - } - - return customer, nil -} - -// UpdateCustomer updates customer information -func (s *customerService) UpdateCustomer(ctx context.Context, id uuid.UUID, form *UpdateCustomerForm) (*Customer, error) { - // Get customer - customer, err := s.repository.GetByID(ctx, id) - if err != nil { - return nil, errors.New("customer not found") - } - - // Update fields if provided - if form.FullName != nil { - customer.FullName = *form.FullName - } - - if form.Username != nil { - // Check if username already exists - existingCustomer, _ := s.repository.GetByUsername(ctx, *form.Username) - if existingCustomer != nil && existingCustomer.ID != id { - return nil, errors.New("username already exists") - } - customer.Username = *form.Username - } - - if form.Email != nil { - // Check if email already exists - existingCustomer, _ := s.repository.GetByEmail(ctx, *form.Email) - if existingCustomer != nil && existingCustomer.ID != id { - return nil, errors.New("email already exists") - } - customer.Email = *form.Email - } - - if form.Mobile != nil { - // Check if mobile already exists - existingCustomer, _ := s.repository.GetByMobile(ctx, *form.Mobile) - if existingCustomer != nil && existingCustomer.ID != id { - return nil, errors.New("mobile number already exists") - } - customer.Mobile = *form.Mobile - } - - if form.NationalID != nil { - customer.NationalID = form.NationalID - } - - if form.Gender != nil { - g := Gender(*form.Gender) - customer.Gender = &g - } - - if form.Birthdate != nil { - customer.Birthdate = form.Birthdate - } - - if form.ProfileImage != nil { - customer.ProfileImage = form.ProfileImage - } - - if form.Status != nil { - customer.Status = CustomerStatus(*form.Status) - } - - // Update in database - err = s.repository.Update(ctx, customer) - if err != nil { - s.logger.Error("Failed to update customer", map[string]interface{}{ - "error": err.Error(), - "customer_id": id.String(), - }) - return nil, errors.New("failed to update customer") - } - - s.logger.Info("Customer updated successfully", map[string]interface{}{ - "customer_id": id.String(), - }) - - return customer, nil -} - -// AddDeviceToken adds a device token for push notifications -func (s *customerService) AddDeviceToken(ctx context.Context, customerID uuid.UUID, form *AddDeviceTokenForm) error { - // Verify customer exists - _, err := s.repository.GetByID(ctx, customerID) - if err != nil { - return errors.New("customer not found") - } - - // Create device token - deviceToken := DeviceToken{ - Token: form.DeviceToken, - DeviceType: DeviceType(form.DeviceType), - } - - // Add to database - err = s.repository.AddDeviceToken(ctx, customerID, deviceToken) - if err != nil { - s.logger.Error("Failed to add device token", map[string]interface{}{ - "error": err.Error(), - "customer_id": customerID.String(), - "token": form.DeviceToken, - }) - return errors.New("failed to add device token") - } - - s.logger.Info("Device token added successfully", map[string]interface{}{ - "customer_id": customerID.String(), - "token": form.DeviceToken, - "device_type": form.DeviceType, - }) - - return nil -} - -// RemoveDeviceToken removes a device token -func (s *customerService) RemoveDeviceToken(ctx context.Context, customerID uuid.UUID, form *RemoveDeviceTokenForm) error { - // Remove from database - err := s.repository.RemoveDeviceToken(ctx, customerID, form.DeviceToken) - if err != nil { - s.logger.Error("Failed to remove device token", map[string]interface{}{ - "error": err.Error(), - "customer_id": customerID.String(), - "token": form.DeviceToken, - }) - return errors.New("failed to remove device token") - } - - s.logger.Info("Device token removed successfully", map[string]interface{}{ - "customer_id": customerID.String(), - "token": form.DeviceToken, - }) - - return nil -} - -// ListCustomers lists customers with search and filters -func (s *customerService) ListCustomers(ctx context.Context, form *ListCustomersForm) ([]*Customer, int, 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 - } - - // Get customers - customers, err := s.repository.Search(ctx, search, form.Status, form.Gender, nil, limit, offset, sortBy, sortOrder) - if err != nil { - s.logger.Error("Failed to list customers", map[string]interface{}{ - "error": err.Error(), - }) - return nil, 0, errors.New("failed to list customers") - } - - // TODO: Implement count for pagination metadata - total := len(customers) // This should be a separate count query - - return customers, total, nil -} - -// UpdateCustomerStatus updates customer status -func (s *customerService) UpdateCustomerStatus(ctx context.Context, id uuid.UUID, form *UpdateCustomerStatusForm) error { - // Get customer - customer, err := s.repository.GetByID(ctx, id) - if err != nil { - return errors.New("customer not found") - } - - // Update status - customer.Status = CustomerStatus(form.Status) - - // Update in database - err = s.repository.Update(ctx, customer) - if err != nil { - s.logger.Error("Failed to update customer status", map[string]interface{}{ - "error": err.Error(), - "customer_id": id.String(), - "status": form.Status, - }) - return errors.New("failed to update customer status") - } - - s.logger.Info("Customer status updated successfully", map[string]interface{}{ - "customer_id": id.String(), - "status": form.Status, - }) - - return nil -} - -// GetAllDeviceTokens retrieves all device tokens for push notifications -func (s *customerService) GetAllDeviceTokens(ctx context.Context) ([]DeviceToken, error) { - tokens, err := s.repository.GetAllDeviceTokens(ctx) - if err != nil { - s.logger.Error("Failed to get all device tokens", map[string]interface{}{ - "error": err.Error(), - }) - return nil, errors.New("failed to get device tokens") - } - - return tokens, nil -} - -// Logout removes device token and logs the action -func (s *customerService) Logout(ctx context.Context, customerID uuid.UUID, deviceToken string) error { - // Remove device token - err := s.repository.RemoveDeviceToken(ctx, customerID, deviceToken) - if err != nil { - s.logger.Error("Failed to remove device token on logout", map[string]interface{}{ - "error": err.Error(), - "customer_id": customerID.String(), - "token": deviceToken, - }) - return errors.New("failed to logout") - } - - s.logger.Info("Customer logged out successfully", map[string]interface{}{ - "customer_id": customerID.String(), - "token": deviceToken, - }) - - return nil -} - -// generateTokens generates access and refresh tokens -func (s *customerService) generateTokens(customerID uuid.UUID) (string, string, int64, error) { - // Generate access token (simple implementation for now) - accessToken, err := s.generateRandomToken(32) - if err != nil { - return "", "", 0, err - } - - // Generate refresh token - refreshToken, err := s.generateRandomToken(64) - if err != nil { - return "", "", 0, err - } - - // Set expiration (1 hour from now) - expiresAt := time.Now().Add(1 * time.Hour).Unix() - - return accessToken, refreshToken, expiresAt, nil -} - -// generateRandomToken generates a random token -func (s *customerService) generateRandomToken(length int) (string, error) { - bytes := make([]byte, length) - _, err := rand.Read(bytes) - if err != nil { - return "", err - } - return hex.EncodeToString(bytes), nil -} diff --git a/internal/customer/validation.go b/internal/customer/validation.go deleted file mode 100644 index 6368720..0000000 --- a/internal/customer/validation.go +++ /dev/null @@ -1,32 +0,0 @@ -package customer - -import ( - "time" - - "github.com/asaskevich/govalidator" -) - -// init registers custom validators -func init() { - // Register custom validators - govalidator.CustomTypeTagMap.Set("unix_timestamp", govalidator.CustomTypeValidator(func(i interface{}, context interface{}) bool { - switch v := i.(type) { - case int64: - // Check if timestamp is reasonable (not too old, not in the future) - now := time.Now().Unix() - minTimestamp := now - (100 * 365 * 24 * 60 * 60) // 100 years ago - maxTimestamp := now + (10 * 365 * 24 * 60 * 60) // 10 years in future - return v >= minTimestamp && v <= maxTimestamp - case *int64: - if v == nil { - return true // nil is valid for optional fields - } - now := time.Now().Unix() - minTimestamp := now - (100 * 365 * 24 * 60 * 60) // 100 years ago - maxTimestamp := now + (10 * 365 * 24 * 60 * 60) // 10 years in future - return *v >= minTimestamp && *v <= maxTimestamp - default: - return false - } - })) -} diff --git a/internal/user/handler.go b/internal/user/handler.go index cff4b8e..457d8ea 100644 --- a/internal/user/handler.go +++ b/internal/user/handler.go @@ -31,40 +31,34 @@ func NewUserHandler(service Service, logger logger.Logger, validator ValidationS // RegisterRoutes registers user routes func (h *Handler) RegisterRoutes(e *echo.Echo) { - v1 := e.Group("/api/v1") + v1 := e.Group("/admin/v1") - userGroup := v1.Group("/users") - { - // Public routes - userGroup.POST("/login", h.Login) - userGroup.POST("/refresh-token", h.RefreshToken) - userGroup.POST("/reset-password", h.ResetPassword) + // Public routes (no authentication required) + authorizationGP := v1.Group("") + authorizationGP.POST("/login", h.Login) + authorizationGP.POST("/refresh-token", h.RefreshToken) + authorizationGP.POST("/reset-password", h.ResetPassword) - // Protected routes (require authentication) - authGroup := userGroup.Group("") - // authGroup.Use(h.AuthMiddleware()) - { - authGroup.GET("/profile", h.GetProfile) - authGroup.PUT("/profile", h.UpdateProfile) - authGroup.PUT("/change-password", h.ChangePassword) - authGroup.POST("/logout", h.Logout) + // Protected routes (require authentication) + profileGP := v1.Group("") + profileGP.Use(h.AuthMiddleware()) + profileGP.GET("/profile", h.GetProfile) + profileGP.PUT("/profile", h.UpdateProfile) + profileGP.PUT("/change-password", h.ChangePassword) + profileGP.DELETE("/logout", h.Logout) - // Admin routes - adminGroup := authGroup.Group("/admin") - // adminGroup.Use(h.AdminMiddleware()) - { - adminGroup.POST("", h.CreateUser) - adminGroup.GET("", h.ListUsers) - adminGroup.GET("/:id", h.GetUserByID) - adminGroup.PUT("/:id", h.UpdateUser) - adminGroup.DELETE("/:id", h.DeleteUser) - adminGroup.PUT("/:id/status", h.UpdateUserStatus) - adminGroup.PUT("/:id/role", h.UpdateUserRole) - adminGroup.GET("/company/:company_id", h.GetUsersByCompanyID) - adminGroup.GET("/role/:role", h.GetUsersByRole) - } - } - } + // Admin routes (require admin role) + adminGroup := v1.Group("/users") + adminGroup.Use(h.AuthMiddleware(), h.AdminMiddleware()) + adminGroup.POST("", h.CreateUser) + adminGroup.GET("", h.ListUsers) + adminGroup.GET("/:id", h.GetUserByID) + adminGroup.PUT("/:id", h.UpdateUser) + adminGroup.DELETE("/:id", h.DeleteUser) + adminGroup.PUT("/:id/status", h.UpdateUserStatus) + adminGroup.PUT("/:id/role", h.UpdateUserRole) + adminGroup.GET("/company/:company_id", h.GetUsersByCompanyID) + adminGroup.GET("/role/:role", h.GetUsersByRole) } // Login handles user login @@ -188,8 +182,11 @@ func (h *Handler) GetProfile(c echo.Context) error { // @Failure 500 {object} response.APIResponse // @Router /users/profile [put] func (h *Handler) UpdateProfile(c echo.Context) error { - // TODO: Extract user ID from JWT token - userID := uuid.New() // Placeholder - should be extracted from JWT + // Extract user ID from JWT token context + userID, err := GetUserIDFromContext(c) + if err != nil { + return response.Unauthorized(c, "User not authenticated") + } form, err := response.Parse[UpdateUserForm](c) if err != nil { @@ -218,8 +215,10 @@ func (h *Handler) UpdateProfile(c echo.Context) error { // @Failure 500 {object} response.APIResponse // @Router /users/change-password [put] func (h *Handler) ChangePassword(c echo.Context) error { - // TODO: Extract user ID from JWT token - userID := uuid.New() // Placeholder - should be extracted from JWT + userID, err := GetUserIDFromContext(c) + if err != nil { + return response.Unauthorized(c, "User not authenticated") + } form, err := response.Parse[ChangePasswordForm](c) if err != nil { @@ -397,8 +396,11 @@ func (h *Handler) GetUserByID(c echo.Context) error { // @Failure 500 {object} response.APIResponse // @Router /users/admin/{id} [put] func (h *Handler) UpdateUser(c echo.Context) error { - // TODO: Get current user ID from JWT token - currentUserID := uuid.New() // Placeholder + // Extract user ID from JWT token context + currentUserID, err := GetUserIDFromContext(c) + if err != nil { + return response.Unauthorized(c, "User not authenticated") + } idStr := c.Param("id") userID, err := uuid.Parse(idStr) @@ -441,8 +443,11 @@ func (h *Handler) UpdateUser(c echo.Context) error { // @Failure 500 {object} response.APIResponse // @Router /users/admin/{id} [delete] func (h *Handler) DeleteUser(c echo.Context) error { - // TODO: Get current user ID from JWT token - currentUserID := uuid.New() // Placeholder + // Extract user ID from JWT token context + currentUserID, err := GetUserIDFromContext(c) + if err != nil { + return response.Unauthorized(c, "User not authenticated") + } idStr := c.Param("id") userID, err := uuid.Parse(idStr) @@ -477,8 +482,10 @@ func (h *Handler) DeleteUser(c echo.Context) error { // @Failure 500 {object} response.APIResponse // @Router /users/admin/{id}/status [put] func (h *Handler) UpdateUserStatus(c echo.Context) error { - // TODO: Get current user ID from JWT token - currentUserID := uuid.New() // Placeholder + currentUserID, err := GetUserIDFromContext(c) + if err != nil { + return response.Unauthorized(c, "User not authenticated") + } idStr := c.Param("id") userID, err := uuid.Parse(idStr) @@ -524,8 +531,11 @@ func (h *Handler) UpdateUserStatus(c echo.Context) error { // @Failure 500 {object} response.APIResponse // @Router /users/admin/{id}/role [put] func (h *Handler) UpdateUserRole(c echo.Context) error { - // TODO: Get current user ID from JWT token - currentUserID := uuid.New() // Placeholder + // Extract user ID from JWT token context + currentUserID, err := GetUserIDFromContext(c) + if err != nil { + return response.Unauthorized(c, "User not authenticated") + } idStr := c.Param("id") userID, err := uuid.Parse(idStr)