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:
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user