Add Company Category Management Functionality and Update API Documentation
- Introduced a new company category management feature, including CRUD operations for categories in both admin and public API endpoints. - Implemented category entity, service, repository, and handler layers following clean architecture principles. - Added new API routes for searching, retrieving, updating, deleting, and toggling publish status of categories, enhancing the overall functionality of the system. - Updated Swagger and YAML documentation to include detailed descriptions and examples for the new category endpoints, ensuring clarity for API consumers. - Enhanced error handling for duplicate categories and validation, improving the robustness of the category management process. - Updated existing routes to integrate category handling, ensuring a seamless user experience across the application.
This commit is contained in:
@@ -904,6 +904,454 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/admin/v1/company-categories": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"BearerAuth": []
|
||||
}
|
||||
],
|
||||
"description": "Search categories with filtering capabilities",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Admin-Company-Categories"
|
||||
],
|
||||
"summary": "Search categories",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Search query",
|
||||
"name": "q",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"description": "Published status filter",
|
||||
"name": "published",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"default": 20,
|
||||
"description": "Limit",
|
||||
"name": "limit",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"default": 0,
|
||||
"description": "Offset",
|
||||
"name": "offset",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"enum": [
|
||||
"name",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
"published_at"
|
||||
],
|
||||
"type": "string",
|
||||
"description": "Sort by field",
|
||||
"name": "sort_by",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"enum": [
|
||||
"asc",
|
||||
"desc"
|
||||
],
|
||||
"type": "string",
|
||||
"description": "Sort order",
|
||||
"name": "sort_order",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Categories search completed",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/definitions/company_category.CategoryListResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad request - Invalid query parameters",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation error - Invalid query parameters",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal server error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"BearerAuth": []
|
||||
}
|
||||
],
|
||||
"description": "Create a new category with name, description, and thumbnail",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Admin-Company-Categories"
|
||||
],
|
||||
"summary": "Create a new category",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "Category information",
|
||||
"name": "category",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/company_category.CategoryForm"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "Category created successfully",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/definitions/company_category.CategoryResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad request - Invalid input data",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
},
|
||||
"409": {
|
||||
"description": "Conflict - Category with this name already exists",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation error - Invalid request data",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal server error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/admin/v1/company-categories/{id}": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"BearerAuth": []
|
||||
}
|
||||
],
|
||||
"description": "Retrieve detailed category information by category ID",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Admin-Company-Categories"
|
||||
],
|
||||
"summary": "Get category by ID",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Category ID",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Category retrieved successfully",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/definitions/company_category.CategoryResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad request - Invalid category ID",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Not found - Category not found",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal server error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"put": {
|
||||
"security": [
|
||||
{
|
||||
"BearerAuth": []
|
||||
}
|
||||
],
|
||||
"description": "Update category information including name, description, and thumbnail",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Admin-Company-Categories"
|
||||
],
|
||||
"summary": "Update category information",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Category ID",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"description": "Category update information",
|
||||
"name": "category",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/company_category.CategoryForm"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Category updated successfully",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/definitions/company_category.CategoryResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad request - Invalid input data",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Not found - Category not found",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
},
|
||||
"409": {
|
||||
"description": "Conflict - Category with this name already exists",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation error - Invalid request data",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal server error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
"security": [
|
||||
{
|
||||
"BearerAuth": []
|
||||
}
|
||||
],
|
||||
"description": "Delete a category permanently",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Admin-Company-Categories"
|
||||
],
|
||||
"summary": "Delete category",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Category ID",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Category deleted successfully",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad request - Invalid category ID",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Not found - Category not found",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal server error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/admin/v1/company-categories/{id}/publish": {
|
||||
"patch": {
|
||||
"security": [
|
||||
{
|
||||
"BearerAuth": []
|
||||
}
|
||||
],
|
||||
"description": "Toggle category publish/unpublish status",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Admin-Company-Categories"
|
||||
],
|
||||
"summary": "Toggle category publish status",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Category ID",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Category publish status updated successfully",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad request - Invalid input data",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Not found - Category not found",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation error - Invalid request data",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal server error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/admin/v1/customers": {
|
||||
"get": {
|
||||
"security": [
|
||||
@@ -5487,6 +5935,63 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"company_category.CategoryForm": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"description": {
|
||||
"type": "string",
|
||||
"example": "Technology related companies"
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"example": "Technology"
|
||||
},
|
||||
"thumbnail": {
|
||||
"type": "string",
|
||||
"example": "https://example.com/thumbnail.jpg"
|
||||
}
|
||||
}
|
||||
},
|
||||
"company_category.CategoryListResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"categories": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/company_category.CategoryResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"company_category.CategoryResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"created_at": {
|
||||
"type": "integer"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"published": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"published_at": {
|
||||
"type": "integer"
|
||||
},
|
||||
"thumbnail": {
|
||||
"type": "string"
|
||||
},
|
||||
"updated_at": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"customer.AuthResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -6552,6 +7057,10 @@ const docTemplate = `{
|
||||
"description": "Administrative company management operations for web panel including CRUD operations, tag management, verification, status updates, comprehensive search and filtering, and statistical reporting",
|
||||
"name": "Admin-Companies"
|
||||
},
|
||||
{
|
||||
"description": "Administrative company category management operations for web panel including CRUD operations, publish/unpublish status, and comprehensive filtering with pagination",
|
||||
"name": "Admin-Company-Categories"
|
||||
},
|
||||
{
|
||||
"description": "Administrative tender management operations for web panel including CRUD operations, search, statistics, and comprehensive filtering with pagination",
|
||||
"name": "Admin-Tenders"
|
||||
|
||||
@@ -898,6 +898,454 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/admin/v1/company-categories": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"BearerAuth": []
|
||||
}
|
||||
],
|
||||
"description": "Search categories with filtering capabilities",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Admin-Company-Categories"
|
||||
],
|
||||
"summary": "Search categories",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Search query",
|
||||
"name": "q",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"description": "Published status filter",
|
||||
"name": "published",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"default": 20,
|
||||
"description": "Limit",
|
||||
"name": "limit",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"default": 0,
|
||||
"description": "Offset",
|
||||
"name": "offset",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"enum": [
|
||||
"name",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
"published_at"
|
||||
],
|
||||
"type": "string",
|
||||
"description": "Sort by field",
|
||||
"name": "sort_by",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"enum": [
|
||||
"asc",
|
||||
"desc"
|
||||
],
|
||||
"type": "string",
|
||||
"description": "Sort order",
|
||||
"name": "sort_order",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Categories search completed",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/definitions/company_category.CategoryListResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad request - Invalid query parameters",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation error - Invalid query parameters",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal server error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"BearerAuth": []
|
||||
}
|
||||
],
|
||||
"description": "Create a new category with name, description, and thumbnail",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Admin-Company-Categories"
|
||||
],
|
||||
"summary": "Create a new category",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "Category information",
|
||||
"name": "category",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/company_category.CategoryForm"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "Category created successfully",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/definitions/company_category.CategoryResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad request - Invalid input data",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
},
|
||||
"409": {
|
||||
"description": "Conflict - Category with this name already exists",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation error - Invalid request data",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal server error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/admin/v1/company-categories/{id}": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"BearerAuth": []
|
||||
}
|
||||
],
|
||||
"description": "Retrieve detailed category information by category ID",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Admin-Company-Categories"
|
||||
],
|
||||
"summary": "Get category by ID",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Category ID",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Category retrieved successfully",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/definitions/company_category.CategoryResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad request - Invalid category ID",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Not found - Category not found",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal server error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"put": {
|
||||
"security": [
|
||||
{
|
||||
"BearerAuth": []
|
||||
}
|
||||
],
|
||||
"description": "Update category information including name, description, and thumbnail",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Admin-Company-Categories"
|
||||
],
|
||||
"summary": "Update category information",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Category ID",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"description": "Category update information",
|
||||
"name": "category",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/company_category.CategoryForm"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Category updated successfully",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/definitions/company_category.CategoryResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad request - Invalid input data",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Not found - Category not found",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
},
|
||||
"409": {
|
||||
"description": "Conflict - Category with this name already exists",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation error - Invalid request data",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal server error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
"security": [
|
||||
{
|
||||
"BearerAuth": []
|
||||
}
|
||||
],
|
||||
"description": "Delete a category permanently",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Admin-Company-Categories"
|
||||
],
|
||||
"summary": "Delete category",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Category ID",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Category deleted successfully",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad request - Invalid category ID",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Not found - Category not found",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal server error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/admin/v1/company-categories/{id}/publish": {
|
||||
"patch": {
|
||||
"security": [
|
||||
{
|
||||
"BearerAuth": []
|
||||
}
|
||||
],
|
||||
"description": "Toggle category publish/unpublish status",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Admin-Company-Categories"
|
||||
],
|
||||
"summary": "Toggle category publish status",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Category ID",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Category publish status updated successfully",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad request - Invalid input data",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Not found - Category not found",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation error - Invalid request data",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal server error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/admin/v1/customers": {
|
||||
"get": {
|
||||
"security": [
|
||||
@@ -5481,6 +5929,63 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"company_category.CategoryForm": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"description": {
|
||||
"type": "string",
|
||||
"example": "Technology related companies"
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"example": "Technology"
|
||||
},
|
||||
"thumbnail": {
|
||||
"type": "string",
|
||||
"example": "https://example.com/thumbnail.jpg"
|
||||
}
|
||||
}
|
||||
},
|
||||
"company_category.CategoryListResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"categories": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/company_category.CategoryResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"company_category.CategoryResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"created_at": {
|
||||
"type": "integer"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"published": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"published_at": {
|
||||
"type": "integer"
|
||||
},
|
||||
"thumbnail": {
|
||||
"type": "string"
|
||||
},
|
||||
"updated_at": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"customer.AuthResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -6546,6 +7051,10 @@
|
||||
"description": "Administrative company management operations for web panel including CRUD operations, tag management, verification, status updates, comprehensive search and filtering, and statistical reporting",
|
||||
"name": "Admin-Companies"
|
||||
},
|
||||
{
|
||||
"description": "Administrative company category management operations for web panel including CRUD operations, publish/unpublish status, and comprehensive filtering with pagination",
|
||||
"name": "Admin-Company-Categories"
|
||||
},
|
||||
{
|
||||
"description": "Administrative tender management operations for web panel including CRUD operations, search, statistics, and comprehensive filtering with pagination",
|
||||
"name": "Admin-Tenders"
|
||||
|
||||
@@ -263,6 +263,44 @@ definitions:
|
||||
is_verified:
|
||||
type: boolean
|
||||
type: object
|
||||
company_category.CategoryForm:
|
||||
properties:
|
||||
description:
|
||||
example: Technology related companies
|
||||
type: string
|
||||
name:
|
||||
example: Technology
|
||||
type: string
|
||||
thumbnail:
|
||||
example: https://example.com/thumbnail.jpg
|
||||
type: string
|
||||
type: object
|
||||
company_category.CategoryListResponse:
|
||||
properties:
|
||||
categories:
|
||||
items:
|
||||
$ref: '#/definitions/company_category.CategoryResponse'
|
||||
type: array
|
||||
type: object
|
||||
company_category.CategoryResponse:
|
||||
properties:
|
||||
created_at:
|
||||
type: integer
|
||||
description:
|
||||
type: string
|
||||
id:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
published:
|
||||
type: boolean
|
||||
published_at:
|
||||
type: integer
|
||||
thumbnail:
|
||||
type: string
|
||||
updated_at:
|
||||
type: integer
|
||||
type: object
|
||||
customer.AuthResponse:
|
||||
properties:
|
||||
access_token:
|
||||
@@ -1545,6 +1583,285 @@ paths:
|
||||
summary: Update company verification status
|
||||
tags:
|
||||
- Admin-Companies
|
||||
/admin/v1/company-categories:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Search categories with filtering capabilities
|
||||
parameters:
|
||||
- description: Search query
|
||||
in: query
|
||||
name: q
|
||||
type: string
|
||||
- description: Published status filter
|
||||
in: query
|
||||
name: published
|
||||
type: boolean
|
||||
- default: 20
|
||||
description: Limit
|
||||
in: query
|
||||
name: limit
|
||||
type: integer
|
||||
- default: 0
|
||||
description: Offset
|
||||
in: query
|
||||
name: offset
|
||||
type: integer
|
||||
- description: Sort by field
|
||||
enum:
|
||||
- name
|
||||
- created_at
|
||||
- updated_at
|
||||
- published_at
|
||||
in: query
|
||||
name: sort_by
|
||||
type: string
|
||||
- description: Sort order
|
||||
enum:
|
||||
- asc
|
||||
- desc
|
||||
in: query
|
||||
name: sort_order
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: Categories search completed
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: '#/definitions/response.APIResponse'
|
||||
- properties:
|
||||
data:
|
||||
$ref: '#/definitions/company_category.CategoryListResponse'
|
||||
type: object
|
||||
"400":
|
||||
description: Bad request - Invalid query parameters
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
"422":
|
||||
description: Validation error - Invalid query parameters
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
"500":
|
||||
description: Internal server error
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
security:
|
||||
- BearerAuth: []
|
||||
summary: Search categories
|
||||
tags:
|
||||
- Admin-Company-Categories
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Create a new category with name, description, and thumbnail
|
||||
parameters:
|
||||
- description: Category information
|
||||
in: body
|
||||
name: category
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/company_category.CategoryForm'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"201":
|
||||
description: Category created successfully
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: '#/definitions/response.APIResponse'
|
||||
- properties:
|
||||
data:
|
||||
$ref: '#/definitions/company_category.CategoryResponse'
|
||||
type: object
|
||||
"400":
|
||||
description: Bad request - Invalid input data
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
"409":
|
||||
description: Conflict - Category with this name already exists
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
"422":
|
||||
description: Validation error - Invalid request data
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
"500":
|
||||
description: Internal server error
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
security:
|
||||
- BearerAuth: []
|
||||
summary: Create a new category
|
||||
tags:
|
||||
- Admin-Company-Categories
|
||||
/admin/v1/company-categories/{id}:
|
||||
delete:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Delete a category permanently
|
||||
parameters:
|
||||
- description: Category ID
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: Category deleted successfully
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
"400":
|
||||
description: Bad request - Invalid category ID
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
"404":
|
||||
description: Not found - Category not found
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
"500":
|
||||
description: Internal server error
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
security:
|
||||
- BearerAuth: []
|
||||
summary: Delete category
|
||||
tags:
|
||||
- Admin-Company-Categories
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Retrieve detailed category information by category ID
|
||||
parameters:
|
||||
- description: Category ID
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: Category retrieved successfully
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: '#/definitions/response.APIResponse'
|
||||
- properties:
|
||||
data:
|
||||
$ref: '#/definitions/company_category.CategoryResponse'
|
||||
type: object
|
||||
"400":
|
||||
description: Bad request - Invalid category ID
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
"404":
|
||||
description: Not found - Category not found
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
"500":
|
||||
description: Internal server error
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
security:
|
||||
- BearerAuth: []
|
||||
summary: Get category by ID
|
||||
tags:
|
||||
- Admin-Company-Categories
|
||||
put:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Update category information including name, description, and thumbnail
|
||||
parameters:
|
||||
- description: Category ID
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
type: string
|
||||
- description: Category update information
|
||||
in: body
|
||||
name: category
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/company_category.CategoryForm'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: Category updated successfully
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: '#/definitions/response.APIResponse'
|
||||
- properties:
|
||||
data:
|
||||
$ref: '#/definitions/company_category.CategoryResponse'
|
||||
type: object
|
||||
"400":
|
||||
description: Bad request - Invalid input data
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
"404":
|
||||
description: Not found - Category not found
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
"409":
|
||||
description: Conflict - Category with this name already exists
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
"422":
|
||||
description: Validation error - Invalid request data
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
"500":
|
||||
description: Internal server error
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
security:
|
||||
- BearerAuth: []
|
||||
summary: Update category information
|
||||
tags:
|
||||
- Admin-Company-Categories
|
||||
/admin/v1/company-categories/{id}/publish:
|
||||
patch:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Toggle category publish/unpublish status
|
||||
parameters:
|
||||
- description: Category ID
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: Category publish status updated successfully
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
"400":
|
||||
description: Bad request - Invalid input data
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
"404":
|
||||
description: Not found - Category not found
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
"422":
|
||||
description: Validation error - Invalid request data
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
"500":
|
||||
description: Internal server error
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
security:
|
||||
- BearerAuth: []
|
||||
summary: Toggle category publish status
|
||||
tags:
|
||||
- Admin-Company-Categories
|
||||
/admin/v1/customers:
|
||||
get:
|
||||
consumes:
|
||||
@@ -4185,6 +4502,10 @@ tags:
|
||||
CRUD operations, tag management, verification, status updates, comprehensive search
|
||||
and filtering, and statistical reporting
|
||||
name: Admin-Companies
|
||||
- description: Administrative company category management operations for web panel
|
||||
including CRUD operations, publish/unpublish status, and comprehensive filtering
|
||||
with pagination
|
||||
name: Admin-Company-Categories
|
||||
- description: Administrative tender management operations for web panel including
|
||||
CRUD operations, search, statistics, and comprehensive filtering with pagination
|
||||
name: Admin-Tenders
|
||||
|
||||
+12
-5
@@ -35,6 +35,9 @@ package main
|
||||
// @tag.name Admin-Companies
|
||||
// @tag.description Administrative company management operations for web panel including CRUD operations, tag management, verification, status updates, comprehensive search and filtering, and statistical reporting
|
||||
|
||||
// @tag.name Admin-Company-Categories
|
||||
// @tag.description Administrative company category management operations for web panel including CRUD operations, publish/unpublish status, and comprehensive filtering with pagination
|
||||
|
||||
// @tag.name Admin-Tenders
|
||||
// @tag.description Administrative tender management operations for web panel including CRUD operations, search, statistics, and comprehensive filtering with pagination
|
||||
|
||||
@@ -71,6 +74,7 @@ import (
|
||||
"net/http"
|
||||
"time"
|
||||
"tm/internal/company"
|
||||
"tm/internal/company_category"
|
||||
"tm/internal/customer"
|
||||
"tm/internal/feedback"
|
||||
"tm/internal/inquiry"
|
||||
@@ -119,12 +123,13 @@ func main() {
|
||||
userRepository := user.NewUserRepository(mongoManager, logger)
|
||||
customerRepository := customer.NewCustomerRepository(mongoManager, logger)
|
||||
companyRepository := company.NewCompanyRepository(mongoManager, logger)
|
||||
categoryRepository := company_category.NewCategoryRepository(mongoManager, logger)
|
||||
tenderRepository := tender.NewTenderRepository(mongoManager, logger)
|
||||
feedbackRepo := feedback.NewFeedbackRepository(mongoManager, logger)
|
||||
tenderApprovalRepository := tender_approval.NewTenderApprovalRepository(mongoManager, logger)
|
||||
inquiryRepository := inquiry.NewInquiryRepository(mongoManager, logger)
|
||||
logger.Info("Repositories initialized successfully", map[string]interface{}{
|
||||
"repositories": []string{"customer", "user", "company", "tender", "feedback", "tender_approval"},
|
||||
"repositories": []string{"customer", "user", "company", "category", "tender", "feedback", "tender_approval"},
|
||||
})
|
||||
|
||||
// Initialize validation service
|
||||
@@ -133,33 +138,35 @@ func main() {
|
||||
// Initialize services with repositories
|
||||
userService := user.NewUserService(userRepository, logger, userAuthService, userValidator)
|
||||
companyService := company.NewCompanyService(companyRepository, logger)
|
||||
categoryService := company_category.NewCategoryService(categoryRepository, logger)
|
||||
customerService := customer.NewCustomerService(customerRepository, logger, customerAuthService, companyService)
|
||||
tenderService := tender.NewTenderService(tenderRepository, companyService, logger)
|
||||
feedbackService := feedback.NewFeedbackService(feedbackRepo, tenderService, logger)
|
||||
tenderApprovalService := tender_approval.NewTenderApprovalService(tenderApprovalRepository, tenderService, logger)
|
||||
inquiryService := inquiry.NewInquiryService(inquiryRepository, logger)
|
||||
logger.Info("Services initialized successfully", map[string]interface{}{
|
||||
"services": []string{"customer", "user", "company", "tender", "feedback", "tender_approval"},
|
||||
"services": []string{"customer", "user", "company", "category", "tender", "feedback", "tender_approval"},
|
||||
})
|
||||
|
||||
// Initialize handlers with services
|
||||
userHandler := user.NewUserHandler(userService, logger, userValidator, userAuthService)
|
||||
customerHandler := customer.NewHandler(customerService, userHandler, customerAuthService, logger)
|
||||
companyHandler := company.NewHandler(companyService, userHandler, logger)
|
||||
categoryHandler := company_category.NewHandler(categoryService, logger)
|
||||
tenderHandler := tender.NewTenderHandler(tenderService, logger)
|
||||
feedbackHandler := feedback.NewFeedbackHandler(feedbackService, userHandler, customerHandler, logger)
|
||||
tenderApprovalHandler := tender_approval.NewHandler(tenderApprovalService, logger)
|
||||
inquiryHandler := inquiry.NewHandler(inquiryService, logger)
|
||||
logger.Info("Handlers initialized successfully", map[string]interface{}{
|
||||
"handlers": []string{"customer", "user", "company", "tender", "feedback", "tender_approval"},
|
||||
"handlers": []string{"customer", "user", "company", "category", "tender", "feedback", "tender_approval"},
|
||||
})
|
||||
|
||||
// Initialize HTTP server
|
||||
e := bootstrap.InitHTTPServer(conf, logger)
|
||||
|
||||
// Register routes
|
||||
router.RegisterAdminRoutes(e, userHandler, companyHandler, customerHandler, tenderHandler, feedbackHandler, tenderApprovalHandler, inquiryHandler)
|
||||
router.RegisterPublicRoutes(e, customerHandler, tenderHandler, feedbackHandler, tenderApprovalHandler, companyHandler, inquiryHandler)
|
||||
router.RegisterAdminRoutes(e, userHandler, companyHandler, customerHandler, tenderHandler, feedbackHandler, tenderApprovalHandler, inquiryHandler, categoryHandler)
|
||||
router.RegisterPublicRoutes(e, customerHandler, tenderHandler, feedbackHandler, tenderApprovalHandler, companyHandler, inquiryHandler, categoryHandler)
|
||||
|
||||
// Start server
|
||||
serverAddr := fmt.Sprintf("%s:%d", conf.Server.Host, conf.Server.Port)
|
||||
|
||||
@@ -2,6 +2,7 @@ package router
|
||||
|
||||
import (
|
||||
"tm/internal/company"
|
||||
"tm/internal/company_category"
|
||||
"tm/internal/customer"
|
||||
"tm/internal/feedback"
|
||||
"tm/internal/inquiry"
|
||||
@@ -12,7 +13,7 @@ import (
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
func RegisterAdminRoutes(e *echo.Echo, userHandler *user.Handler, companyHandler *company.Handler, customerHandler *customer.Handler, tenderHandler *tender.TenderHandler, feedbackHandler *feedback.Handler, tenderApprovalHandler *tender_approval.Handler, inquiryHandler *inquiry.Handler) {
|
||||
func RegisterAdminRoutes(e *echo.Echo, userHandler *user.Handler, companyHandler *company.Handler, customerHandler *customer.Handler, tenderHandler *tender.TenderHandler, feedbackHandler *feedback.Handler, tenderApprovalHandler *tender_approval.Handler, inquiryHandler *inquiry.Handler, categoryHandler *company_category.Handler) {
|
||||
adminV1 := e.Group("/admin/v1")
|
||||
|
||||
// Admin Users Routes
|
||||
@@ -56,6 +57,18 @@ func RegisterAdminRoutes(e *echo.Echo, userHandler *user.Handler, companyHandler
|
||||
companiesGP.PATCH("/:id/verification", companyHandler.UpdateVerification)
|
||||
}
|
||||
|
||||
// Admin Categories Routes
|
||||
categoriesGP := adminV1.Group("/company-categories")
|
||||
{
|
||||
categoriesGP.Use(userHandler.AuthMiddleware())
|
||||
categoriesGP.POST("", categoryHandler.Create)
|
||||
categoriesGP.GET("", categoryHandler.Search)
|
||||
categoriesGP.GET("/:id", categoryHandler.Get)
|
||||
categoriesGP.PUT("/:id", categoryHandler.Update)
|
||||
categoriesGP.DELETE("/:id", categoryHandler.Delete)
|
||||
categoriesGP.PATCH("/:id/publish", categoryHandler.TogglePublish)
|
||||
}
|
||||
|
||||
// Admin Customers Routes
|
||||
customersGP := adminV1.Group("/customers")
|
||||
{
|
||||
@@ -109,7 +122,7 @@ func RegisterAdminRoutes(e *echo.Echo, userHandler *user.Handler, companyHandler
|
||||
}
|
||||
}
|
||||
|
||||
func RegisterPublicRoutes(e *echo.Echo, customerHandler *customer.Handler, tenderHandler *tender.TenderHandler, feedbackHandler *feedback.Handler, tenderApprovalHandler *tender_approval.Handler, companyHandler *company.Handler, inquiryHandler *inquiry.Handler) {
|
||||
func RegisterPublicRoutes(e *echo.Echo, customerHandler *customer.Handler, tenderHandler *tender.TenderHandler, feedbackHandler *feedback.Handler, tenderApprovalHandler *tender_approval.Handler, companyHandler *company.Handler, inquiryHandler *inquiry.Handler, categoryHandler *company_category.Handler) {
|
||||
v1 := e.Group("/api/v1")
|
||||
|
||||
customerGP := v1.Group("/profile")
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
package company_category
|
||||
|
||||
import (
|
||||
"tm/pkg/mongo"
|
||||
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
)
|
||||
|
||||
type Category struct {
|
||||
mongo.Model `bson:",inline"`
|
||||
Name string `bson:"name" json:"name"`
|
||||
Description *string `bson:"description" json:"description"`
|
||||
Thumbnail *string `bson:"thumbnail" json:"thumbnail"`
|
||||
Published bool `bson:"published" json:"published"`
|
||||
PublishedAt *int64 `bson:"published_at" json:"published_at"`
|
||||
}
|
||||
|
||||
// SetID sets the category ID (implements IDSetter interface)
|
||||
func (c *Category) SetID(id string) {
|
||||
c.ID, _ = primitive.ObjectIDFromHex(id)
|
||||
}
|
||||
|
||||
// GetID returns the category ID (implements IDGetter interface)
|
||||
func (c *Category) GetID() string {
|
||||
return c.ID.Hex()
|
||||
}
|
||||
|
||||
// SetCreatedAt sets the created timestamp (implements Timestamp interface)
|
||||
func (c *Category) SetCreatedAt(timestamp int64) {
|
||||
c.CreatedAt = timestamp
|
||||
}
|
||||
|
||||
// SetUpdatedAt sets the updated timestamp (implements Timestamp interface)
|
||||
func (c *Category) SetUpdatedAt(timestamp int64) {
|
||||
c.UpdatedAt = timestamp
|
||||
}
|
||||
|
||||
// GetCreatedAt returns the created timestamp (implements Timestamp interface)
|
||||
func (c *Category) GetCreatedAt() int64 {
|
||||
return c.CreatedAt
|
||||
}
|
||||
|
||||
// GetUpdatedAt returns the updated timestamp (implements Timestamp interface)
|
||||
func (c *Category) GetUpdatedAt() int64 {
|
||||
return c.UpdatedAt
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package company_category
|
||||
|
||||
import "tm/pkg/response"
|
||||
|
||||
// CategoryForm represents the form for creating/updating a category
|
||||
type CategoryForm struct {
|
||||
Name string `json:"name" valid:"required,length(2|100)" example:"Technology"`
|
||||
Description *string `json:"description,omitempty" valid:"optional,length(10|500)" example:"Technology related companies"`
|
||||
Thumbnail *string `json:"thumbnail,omitempty" valid:"optional,url" example:"https://example.com/thumbnail.jpg"`
|
||||
}
|
||||
|
||||
// SearchForm represents the form for searching categories with filters
|
||||
type SearchForm struct {
|
||||
Search *string `query:"q" valid:"optional"`
|
||||
Published *bool `query:"published" valid:"optional"`
|
||||
Limit *int `query:"limit" valid:"optional,range(1|100)"`
|
||||
Offset *int `query:"offset" valid:"optional,min(0)"`
|
||||
SortBy *string `query:"sort_by" valid:"optional,in(name|created_at|updated_at|published_at)"`
|
||||
SortOrder *string `query:"sort_order" valid:"optional,in(asc|desc)"`
|
||||
}
|
||||
|
||||
// CategoryListResponse represents the response for listing categories
|
||||
type CategoryListResponse struct {
|
||||
Categories []*CategoryResponse `json:"categories"`
|
||||
Meta *response.Meta `json:"-"`
|
||||
}
|
||||
|
||||
// CategoryResponse represents the category data sent in API responses
|
||||
type CategoryResponse struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description *string `json:"description,omitempty"`
|
||||
Thumbnail *string `json:"thumbnail,omitempty"`
|
||||
Published bool `json:"published"`
|
||||
PublishedAt *int64 `json:"published_at,omitempty"`
|
||||
CreatedAt int64 `json:"created_at"`
|
||||
UpdatedAt int64 `json:"updated_at"`
|
||||
}
|
||||
|
||||
// ToResponse converts Category to CategoryResponse
|
||||
func (c *Category) ToResponse() *CategoryResponse {
|
||||
return &CategoryResponse{
|
||||
ID: c.ID.Hex(),
|
||||
Name: c.Name,
|
||||
Description: c.Description,
|
||||
Thumbnail: c.Thumbnail,
|
||||
Published: c.Published,
|
||||
PublishedAt: c.PublishedAt,
|
||||
CreatedAt: c.CreatedAt,
|
||||
UpdatedAt: c.UpdatedAt,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,213 @@
|
||||
package company_category
|
||||
|
||||
import (
|
||||
"tm/pkg/logger"
|
||||
"tm/pkg/response"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
// Handler handles HTTP requests for category operations
|
||||
type Handler struct {
|
||||
service Service
|
||||
logger logger.Logger
|
||||
}
|
||||
|
||||
// NewHandler creates a new category handler
|
||||
func NewHandler(service Service, logger logger.Logger) *Handler {
|
||||
return &Handler{
|
||||
service: service,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// Create creates a new category (Web Panel)
|
||||
// @Summary Create a new category
|
||||
// @Description Create a new category with name, description, and thumbnail
|
||||
// @Tags Admin-Company-Categories
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param category body CategoryForm true "Category information"
|
||||
// @Success 201 {object} response.APIResponse{data=CategoryResponse} "Category created successfully"
|
||||
// @Failure 400 {object} response.APIResponse "Bad request - Invalid input data"
|
||||
// @Failure 409 {object} response.APIResponse "Conflict - Category with this name already exists"
|
||||
// @Failure 422 {object} response.APIResponse "Validation error - Invalid request data"
|
||||
// @Failure 500 {object} response.APIResponse "Internal server error"
|
||||
// @Security BearerAuth
|
||||
// @Router /admin/v1/company-categories [post]
|
||||
func (h *Handler) Create(c echo.Context) error {
|
||||
form, err := response.Parse[CategoryForm](c)
|
||||
if err != nil {
|
||||
return response.ValidationError(c, "Invalid request data", err.Error())
|
||||
}
|
||||
|
||||
category, err := h.service.Create(c.Request().Context(), form)
|
||||
if err != nil {
|
||||
if err.Error() == "category with this name already exists" {
|
||||
return response.Conflict(c, err.Error())
|
||||
}
|
||||
return response.InternalServerError(c, "Failed to create category")
|
||||
}
|
||||
|
||||
return response.Created(c, category, "Category created successfully")
|
||||
}
|
||||
|
||||
// GetCategory retrieves a category by ID (Web Panel)
|
||||
// @Summary Get category by ID
|
||||
// @Description Retrieve detailed category information by category ID
|
||||
// @Tags Admin-Company-Categories
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path string true "Category ID"
|
||||
// @Success 200 {object} response.APIResponse{data=CategoryResponse} "Category retrieved successfully"
|
||||
// @Failure 400 {object} response.APIResponse "Bad request - Invalid category ID"
|
||||
// @Failure 404 {object} response.APIResponse "Not found - Category not found"
|
||||
// @Failure 500 {object} response.APIResponse "Internal server error"
|
||||
// @Security BearerAuth
|
||||
// @Router /admin/v1/company-categories/{id} [get]
|
||||
func (h *Handler) Get(c echo.Context) error {
|
||||
id := c.Param("id")
|
||||
|
||||
category, err := h.service.GetByID(c.Request().Context(), id)
|
||||
if err != nil {
|
||||
if err.Error() == "category not found" {
|
||||
return response.NotFound(c, "Category not found")
|
||||
}
|
||||
return response.InternalServerError(c, "Failed to get category")
|
||||
}
|
||||
|
||||
return response.Success(c, category, "Category retrieved successfully")
|
||||
}
|
||||
|
||||
// Update updates a category (Web Panel)
|
||||
// @Summary Update category information
|
||||
// @Description Update category information including name, description, and thumbnail
|
||||
// @Tags Admin-Company-Categories
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path string true "Category ID"
|
||||
// @Param category body CategoryForm true "Category update information"
|
||||
// @Success 200 {object} response.APIResponse{data=CategoryResponse} "Category updated successfully"
|
||||
// @Failure 400 {object} response.APIResponse "Bad request - Invalid input data"
|
||||
// @Failure 404 {object} response.APIResponse "Not found - Category not found"
|
||||
// @Failure 409 {object} response.APIResponse "Conflict - Category with this name already exists"
|
||||
// @Failure 422 {object} response.APIResponse "Validation error - Invalid request data"
|
||||
// @Failure 500 {object} response.APIResponse "Internal server error"
|
||||
// @Security BearerAuth
|
||||
// @Router /admin/v1/company-categories/{id} [put]
|
||||
func (h *Handler) Update(c echo.Context) error {
|
||||
id := c.Param("id")
|
||||
form, err := response.Parse[CategoryForm](c)
|
||||
if err != nil {
|
||||
return response.ValidationError(c, "Invalid request data", err.Error())
|
||||
}
|
||||
|
||||
category, err := h.service.Update(c.Request().Context(), id, form)
|
||||
if err != nil {
|
||||
if err.Error() == "category not found" {
|
||||
return response.NotFound(c, "Category not found")
|
||||
}
|
||||
if err.Error() == "category with this name already exists" {
|
||||
return response.Conflict(c, err.Error())
|
||||
}
|
||||
return response.InternalServerError(c, "Failed to update category")
|
||||
}
|
||||
|
||||
return response.Success(c, category, "Category updated successfully")
|
||||
}
|
||||
|
||||
// Delete deletes a category (Web Panel)
|
||||
// @Summary Delete category
|
||||
// @Description Delete a category permanently
|
||||
// @Tags Admin-Company-Categories
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path string true "Category ID"
|
||||
// @Success 200 {object} response.APIResponse "Category deleted successfully"
|
||||
// @Failure 400 {object} response.APIResponse "Bad request - Invalid category ID"
|
||||
// @Failure 404 {object} response.APIResponse "Not found - Category not found"
|
||||
// @Failure 500 {object} response.APIResponse "Internal server error"
|
||||
// @Security BearerAuth
|
||||
// @Router /admin/v1/company-categories/{id} [delete]
|
||||
func (h *Handler) Delete(c echo.Context) error {
|
||||
id := c.Param("id")
|
||||
|
||||
err := h.service.Delete(c.Request().Context(), id)
|
||||
if err != nil {
|
||||
if err.Error() == "category not found" {
|
||||
return response.NotFound(c, "Category not found")
|
||||
}
|
||||
return response.InternalServerError(c, "Failed to delete category")
|
||||
}
|
||||
|
||||
return response.Success(c, map[string]interface{}{
|
||||
"message": "Category deleted successfully",
|
||||
}, "Category deleted successfully")
|
||||
}
|
||||
|
||||
// Search searches categories with filters (Web Panel)
|
||||
// @Summary Search categories
|
||||
// @Description Search categories with filtering capabilities
|
||||
// @Tags Admin-Company-Categories
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param q query string false "Search query"
|
||||
// @Param published query boolean false "Published status filter"
|
||||
// @Param limit query integer false "Limit" default(20)
|
||||
// @Param offset query integer false "Offset" default(0)
|
||||
// @Param sort_by query string false "Sort by field" Enums(name,created_at,updated_at,published_at)
|
||||
// @Param sort_order query string false "Sort order" Enums(asc,desc)
|
||||
// @Success 200 {object} response.APIResponse{data=CategoryListResponse} "Categories search completed"
|
||||
// @Failure 400 {object} response.APIResponse "Bad request - Invalid query parameters"
|
||||
// @Failure 422 {object} response.APIResponse "Validation error - Invalid query parameters"
|
||||
// @Failure 500 {object} response.APIResponse "Internal server error"
|
||||
// @Security BearerAuth
|
||||
// @Router /admin/v1/company-categories [get]
|
||||
func (h *Handler) Search(c echo.Context) error {
|
||||
form, err := response.Parse[SearchForm](c)
|
||||
if err != nil {
|
||||
return response.ValidationError(c, "Invalid request data", err.Error())
|
||||
}
|
||||
|
||||
result, err := h.service.SearchCategories(c.Request().Context(), form, response.NewPagination(c))
|
||||
if err != nil {
|
||||
return response.InternalServerError(c, "Failed to search categories")
|
||||
}
|
||||
|
||||
return response.SuccessWithMeta(c, result, result.Meta, "Categories retrieved successfully")
|
||||
}
|
||||
|
||||
// TogglePublish toggles category publish status (Web Panel)
|
||||
// @Summary Toggle category publish status
|
||||
// @Description Toggle category publish/unpublish status
|
||||
// @Tags Admin-Company-Categories
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path string true "Category ID"
|
||||
// @Success 200 {object} response.APIResponse "Category publish status updated successfully"
|
||||
// @Failure 400 {object} response.APIResponse "Bad request - Invalid input data"
|
||||
// @Failure 404 {object} response.APIResponse "Not found - Category not found"
|
||||
// @Failure 422 {object} response.APIResponse "Validation error - Invalid request data"
|
||||
// @Failure 500 {object} response.APIResponse "Internal server error"
|
||||
// @Security BearerAuth
|
||||
// @Router /admin/v1/company-categories/{id}/publish [patch]
|
||||
func (h *Handler) TogglePublish(c echo.Context) error {
|
||||
id := c.Param("id")
|
||||
|
||||
published, err := h.service.TogglePublish(c.Request().Context(), id)
|
||||
if err != nil {
|
||||
if err.Error() == "category not found" {
|
||||
return response.NotFound(c, "Category not found")
|
||||
}
|
||||
return response.InternalServerError(c, "Failed to toggle category publish status")
|
||||
}
|
||||
|
||||
return response.Success(
|
||||
c,
|
||||
map[string]interface{}{
|
||||
"message": "Category publish status updated successfully",
|
||||
"published": published,
|
||||
},
|
||||
"Category publish status updated successfully",
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,255 @@
|
||||
package company_category
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"time"
|
||||
"tm/pkg/logger"
|
||||
orm "tm/pkg/mongo"
|
||||
"tm/pkg/response"
|
||||
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
)
|
||||
|
||||
// Repository defines the interface for category data operations
|
||||
type Repository interface {
|
||||
Create(ctx context.Context, category *Category) error
|
||||
Update(ctx context.Context, category *Category) error
|
||||
GetByID(ctx context.Context, id string) (*Category, error)
|
||||
GetByIDs(ctx context.Context, ids []string) ([]Category, error)
|
||||
GetByName(ctx context.Context, name string) (*Category, error)
|
||||
Search(ctx context.Context, form *SearchForm, pagination *response.Pagination) ([]Category, int64, error)
|
||||
Delete(ctx context.Context, id string) error
|
||||
TogglePublish(ctx context.Context, id string) (bool, error)
|
||||
}
|
||||
|
||||
// categoryRepository implements the Repository interface using the MongoDB ORM
|
||||
type categoryRepository struct {
|
||||
ormRepo orm.Repository[Category]
|
||||
logger logger.Logger
|
||||
}
|
||||
|
||||
// NewCategoryRepository creates a new category repository
|
||||
func NewCategoryRepository(mongoManager *orm.ConnectionManager, logger logger.Logger) Repository {
|
||||
// Create indexes using the ORM's index management
|
||||
indexes := []orm.Index{
|
||||
*orm.NewIndex("name_idx", bson.D{{Key: "name", Value: 1}}),
|
||||
*orm.NewIndex("published_idx", bson.D{{Key: "published", Value: 1}}),
|
||||
}
|
||||
|
||||
// Create indexes
|
||||
err := mongoManager.CreateIndexes("company_categories", indexes)
|
||||
if err != nil {
|
||||
logger.Warn("Failed to create category indexes", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
// Create ORM repository
|
||||
ormRepo := orm.NewRepository[Category](mongoManager.GetCollection("company_categories"), logger)
|
||||
|
||||
return &categoryRepository{
|
||||
ormRepo: ormRepo,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// Create creates a new category
|
||||
func (r *categoryRepository) Create(ctx context.Context, category *Category) error {
|
||||
// Set created/updated timestamps using Unix timestamps
|
||||
now := time.Now().Unix()
|
||||
category.SetCreatedAt(now)
|
||||
|
||||
// Use ORM to create category
|
||||
err := r.ormRepo.Create(ctx, category)
|
||||
if err != nil {
|
||||
r.logger.Error("Failed to create category", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"name": category.Name,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
r.logger.Info("Category created successfully", map[string]interface{}{
|
||||
"category_id": category.ID,
|
||||
"name": category.Name,
|
||||
"published": category.Published,
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetByID retrieves a category by ID
|
||||
func (r *categoryRepository) GetByID(ctx context.Context, id string) (*Category, error) {
|
||||
category, err := r.ormRepo.FindByID(ctx, id)
|
||||
if err != nil {
|
||||
if errors.Is(err, orm.ErrDocumentNotFound) {
|
||||
return nil, errors.New("category not found")
|
||||
}
|
||||
r.logger.Error("Failed to get category by ID", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"category_id": id,
|
||||
})
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return category, nil
|
||||
}
|
||||
|
||||
// GetByIDs retrieves categories by IDs
|
||||
func (r *categoryRepository) GetByIDs(ctx context.Context, ids []string) ([]Category, error) {
|
||||
filter := bson.M{"_id": bson.M{"$in": ids}}
|
||||
|
||||
paginationBuilder := orm.NewPaginationBuilder().
|
||||
Build()
|
||||
|
||||
result, err := r.ormRepo.FindAll(ctx, filter, paginationBuilder)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return result.Items, nil
|
||||
}
|
||||
|
||||
// GetByName retrieves a category by name
|
||||
func (r *categoryRepository) GetByName(ctx context.Context, name string) (*Category, error) {
|
||||
category, err := r.ormRepo.FindOne(ctx, bson.M{"name": name})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return category, nil
|
||||
}
|
||||
|
||||
// Update updates a category
|
||||
func (r *categoryRepository) Update(ctx context.Context, category *Category) error {
|
||||
// Set updated timestamp using Unix timestamp
|
||||
category.SetUpdatedAt(time.Now().Unix())
|
||||
|
||||
// Use ORM to update category
|
||||
err := r.ormRepo.Update(ctx, category)
|
||||
if err != nil {
|
||||
r.logger.Error("Failed to update category", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"category_id": category.ID,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
r.logger.Info("Category updated successfully", map[string]interface{}{
|
||||
"category_id": category.ID,
|
||||
"name": category.Name,
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete deletes a category
|
||||
func (r *categoryRepository) Delete(ctx context.Context, id string) error {
|
||||
// Get category first
|
||||
_, err := r.GetByID(ctx, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Delete from database
|
||||
err = r.ormRepo.Delete(ctx, id)
|
||||
if err != nil {
|
||||
r.logger.Error("Failed to delete category", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"category_id": id,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
r.logger.Info("Category deleted successfully", map[string]interface{}{
|
||||
"category_id": id,
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Search retrieves categories with search and filters
|
||||
func (r *categoryRepository) Search(ctx context.Context, form *SearchForm, pagination *response.Pagination) ([]Category, int64, error) {
|
||||
// Build filter
|
||||
filter := bson.M{}
|
||||
|
||||
if form.Search != nil && *form.Search != "" {
|
||||
// Use regex search for case-insensitive partial matching
|
||||
searchRegex := bson.M{
|
||||
"$regex": *form.Search,
|
||||
"$options": "i", // case-insensitive
|
||||
}
|
||||
filter["$or"] = []bson.M{
|
||||
{"name": searchRegex},
|
||||
{"description": searchRegex},
|
||||
}
|
||||
}
|
||||
|
||||
if form.Published != nil {
|
||||
filter["published"] = *form.Published
|
||||
}
|
||||
|
||||
// Build sort
|
||||
sort := "created_at"
|
||||
if pagination.SortBy != "" {
|
||||
sort = pagination.SortBy
|
||||
}
|
||||
|
||||
sortOrder := "desc"
|
||||
if pagination.SortOrder != "" {
|
||||
sortOrder = pagination.SortOrder
|
||||
}
|
||||
|
||||
// Build pagination
|
||||
paginationBuilder := orm.NewPaginationBuilder().
|
||||
Limit(pagination.Limit).
|
||||
Skip(pagination.Offset).
|
||||
SortBy(sort, sortOrder).
|
||||
Build()
|
||||
|
||||
// Use ORM to find categories
|
||||
result, err := r.ormRepo.FindAll(ctx, filter, paginationBuilder)
|
||||
if err != nil {
|
||||
r.logger.Error("Failed to search categories", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
})
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
return result.Items, result.TotalCount, nil
|
||||
}
|
||||
|
||||
// TogglePublish toggles category publish status
|
||||
func (r *categoryRepository) TogglePublish(ctx context.Context, id string) (bool, error) {
|
||||
// Get category first
|
||||
category, err := r.GetByID(ctx, id)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// Update publish status
|
||||
category.Published = !category.Published
|
||||
now := time.Now().Unix()
|
||||
category.SetUpdatedAt(now)
|
||||
|
||||
// Set published_at timestamp if publishing
|
||||
category.PublishedAt = &now
|
||||
|
||||
// Update in database
|
||||
err = r.ormRepo.Update(ctx, category)
|
||||
if err != nil {
|
||||
r.logger.Error("Failed to toggle category publish status", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"category_id": id,
|
||||
"published": category.Published,
|
||||
})
|
||||
return false, err
|
||||
}
|
||||
|
||||
r.logger.Info("Category publish status updated successfully", map[string]interface{}{
|
||||
"category_id": id,
|
||||
"published": category.Published,
|
||||
})
|
||||
|
||||
return category.Published, nil
|
||||
}
|
||||
@@ -0,0 +1,224 @@
|
||||
package company_category
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"tm/pkg/logger"
|
||||
"tm/pkg/response"
|
||||
)
|
||||
|
||||
// Service defines business logic for category operations
|
||||
type Service interface {
|
||||
// Core category operations
|
||||
Create(ctx context.Context, form *CategoryForm) (*CategoryResponse, error)
|
||||
Update(ctx context.Context, id string, form *CategoryForm) (*CategoryResponse, error)
|
||||
GetByID(ctx context.Context, id string) (*CategoryResponse, error)
|
||||
GetByIDs(ctx context.Context, ids []string) ([]*CategoryResponse, error)
|
||||
Delete(ctx context.Context, id string) error
|
||||
|
||||
// Category listing and search
|
||||
SearchCategories(ctx context.Context, form *SearchForm, pagination *response.Pagination) (*CategoryListResponse, error)
|
||||
|
||||
// Category management
|
||||
TogglePublish(ctx context.Context, id string) (bool, error)
|
||||
}
|
||||
|
||||
// categoryService implements the Service interface
|
||||
type categoryService struct {
|
||||
repository Repository
|
||||
logger logger.Logger
|
||||
}
|
||||
|
||||
// NewCategoryService creates a new category service
|
||||
func NewCategoryService(repository Repository, logger logger.Logger) Service {
|
||||
return &categoryService{
|
||||
repository: repository,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// Create creates a new category
|
||||
func (s *categoryService) Create(ctx context.Context, form *CategoryForm) (*CategoryResponse, error) {
|
||||
// Check if category name already exists
|
||||
existingCategory, _ := s.repository.GetByName(ctx, form.Name)
|
||||
if existingCategory != nil {
|
||||
return nil, errors.New("category with this name already exists")
|
||||
}
|
||||
|
||||
// Create category
|
||||
category := &Category{
|
||||
Name: form.Name,
|
||||
Description: form.Description,
|
||||
Thumbnail: form.Thumbnail,
|
||||
Published: false, // Default to unpublished
|
||||
}
|
||||
|
||||
// Save to database
|
||||
err := s.repository.Create(ctx, category)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to create category", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"name": form.Name,
|
||||
})
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s.logger.Info("Category created successfully", map[string]interface{}{
|
||||
"category_id": category.ID,
|
||||
"name": category.Name,
|
||||
"published": category.Published,
|
||||
})
|
||||
|
||||
return category.ToResponse(), nil
|
||||
}
|
||||
|
||||
// GetByIDs retrieves categories by IDs
|
||||
func (s *categoryService) GetByIDs(ctx context.Context, ids []string) ([]*CategoryResponse, error) {
|
||||
categories, err := s.repository.GetByIDs(ctx, ids)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var categoryResponses []*CategoryResponse
|
||||
for _, category := range categories {
|
||||
categoryResponses = append(categoryResponses, category.ToResponse())
|
||||
}
|
||||
|
||||
return categoryResponses, nil
|
||||
}
|
||||
|
||||
// GetByID retrieves a category by ID
|
||||
func (s *categoryService) GetByID(ctx context.Context, id string) (*CategoryResponse, error) {
|
||||
category, err := s.repository.GetByID(ctx, id)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to get category by ID", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"category_id": id,
|
||||
})
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s.logger.Info("Category retrieved successfully", map[string]interface{}{
|
||||
"category_id": category.ID,
|
||||
"name": category.Name,
|
||||
})
|
||||
|
||||
return category.ToResponse(), nil
|
||||
}
|
||||
|
||||
// Update updates a category
|
||||
func (s *categoryService) Update(ctx context.Context, id string, form *CategoryForm) (*CategoryResponse, error) {
|
||||
// Get existing category
|
||||
category, err := s.repository.GetByID(ctx, id)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to get category for update", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"category_id": id,
|
||||
})
|
||||
return nil, errors.New("category not found")
|
||||
}
|
||||
|
||||
// Check if name already exists (if changing name)
|
||||
if form.Name != "" && form.Name != category.Name {
|
||||
existingCategory, _ := s.repository.GetByName(ctx, form.Name)
|
||||
if existingCategory != nil {
|
||||
return nil, errors.New("category with this name already exists")
|
||||
}
|
||||
category.Name = form.Name
|
||||
}
|
||||
|
||||
// Update fields if provided
|
||||
if form.Description != nil {
|
||||
category.Description = form.Description
|
||||
}
|
||||
|
||||
if form.Thumbnail != nil {
|
||||
category.Thumbnail = form.Thumbnail
|
||||
}
|
||||
|
||||
// Update in database
|
||||
err = s.repository.Update(ctx, category)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to update category", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"category_id": id,
|
||||
})
|
||||
return nil, errors.New("failed to update category")
|
||||
}
|
||||
|
||||
s.logger.Info("Category updated successfully", map[string]interface{}{
|
||||
"category_id": category.ID,
|
||||
"name": category.Name,
|
||||
})
|
||||
|
||||
return category.ToResponse(), nil
|
||||
}
|
||||
|
||||
// Delete deletes a category
|
||||
func (s *categoryService) Delete(ctx context.Context, id string) error {
|
||||
// Delete category
|
||||
err := s.repository.Delete(ctx, id)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to delete category", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"category_id": id,
|
||||
})
|
||||
return errors.New("failed to delete category")
|
||||
}
|
||||
|
||||
s.logger.Info("Category deleted successfully", map[string]interface{}{
|
||||
"category_id": id,
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SearchCategories searches categories with filters
|
||||
func (s *categoryService) SearchCategories(ctx context.Context, form *SearchForm, pagination *response.Pagination) (*CategoryListResponse, error) {
|
||||
// Get categories using search
|
||||
categories, total, err := s.repository.Search(ctx, form, pagination)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to search categories", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
})
|
||||
return nil, errors.New("failed to search categories")
|
||||
}
|
||||
|
||||
// Convert to responses
|
||||
var categoryResponses []*CategoryResponse
|
||||
for _, category := range categories {
|
||||
categoryResponses = append(categoryResponses, category.ToResponse())
|
||||
}
|
||||
|
||||
return &CategoryListResponse{
|
||||
Categories: categoryResponses,
|
||||
Meta: pagination.Response(total),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// TogglePublish toggles category publish status
|
||||
func (s *categoryService) TogglePublish(ctx context.Context, id string) (bool, error) {
|
||||
// Get category to check if exists
|
||||
_, err := s.repository.GetByID(ctx, id)
|
||||
if err != nil {
|
||||
return false, errors.New("category not found")
|
||||
}
|
||||
|
||||
// Toggle publish status
|
||||
published, err := s.repository.TogglePublish(ctx, id)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to toggle category publish status", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"category_id": id,
|
||||
})
|
||||
return false, errors.New("failed to toggle category publish status")
|
||||
}
|
||||
|
||||
s.logger.Info("Category publish status updated successfully", map[string]interface{}{
|
||||
"category_id": id,
|
||||
"published": published,
|
||||
})
|
||||
|
||||
return published, nil
|
||||
}
|
||||
Reference in New Issue
Block a user