Skip to content
Snippets Groups Projects
Commit 7fb3d747 authored by Dasan Ibrahim's avatar Dasan Ibrahim
Browse files

Implementation of the business logic for the project management operation

parent 70ce32ab
No related branches found
No related tags found
1 merge request!4merge dev to main
package project
import (
"context"
"fmt"
"log"
"github.com/google/uuid"
"go.mongodb.org/mongo-driver/bson/primitive"
)
// ProjectService contains the business logic for project management operations
// This service layer sits between the web controllers and the data repository
// It enforces business rules, handles validation, and coordinates data operations
type ProjectService struct {
repo ProjectRepository // Repository interface for data access operations
}
// NewProjectService creates and initializes a new ProjectService instance
// The service requires a repository implementation to handle data persistence
func NewProjectService(repo ProjectRepository) *ProjectService {
log.Println("INFO: ProjectService initialized")
return &ProjectService{repo: repo}
}
// CreateProject handles the creation of new projects with business logic validation
// This method ensures project uniqueness, validates business rules, and creates the project
func (s *ProjectService) CreateProject(ctx context.Context, toCreate *ProjectToCreate) (*Project, error) {
log.Printf("INFO: Creating project with ID '%s' and name '%s'", toCreate.ProjectID, toCreate.Name)
// Business Rule 1: Project IDs must be unique across the entire system
// Check if a project with this ID already exists before creating a new one
log.Printf("DEBUG: Checking if project ID '%s' already exists", toCreate.ProjectID)
existing, err := s.repo.FindByProjectID(ctx, toCreate.ProjectID)
if err == nil && existing != nil {
// If we found an existing project, the creation should fail
log.Printf("WARN: Project creation failed - ID '%s' already exists", toCreate.ProjectID)
return nil, fmt.Errorf("project with ID '%s' already exists", toCreate.ProjectID)
}
// Business Rule 2: Project end date must be after the start date
// This prevents creating projects with invalid date ranges
if toCreate.EndDate.Before(toCreate.StartDate) {
log.Printf("WARN: Project creation failed - end date (%s) before start date (%s)",
toCreate.EndDate.Format("2006-01-02"), toCreate.StartDate.Format("2006-01-02"))
return nil, fmt.Errorf("end date must be after start date")
}
log.Printf("DEBUG: Project validation passed, creating in repository")
// All business rules passed, delegate to repository for actual creation
project, err := s.repo.Create(ctx, toCreate)
if err != nil {
log.Printf("ERROR: Failed to create project '%s' in repository: %v", toCreate.ProjectID, err)
return nil, err
}
log.Printf("INFO: Project '%s' created successfully with MongoDB ID %s",
project.ProjectID, project.ID.Hex())
return project, nil
}
// GetProjectsByUser retrieves all projects belonging to a specific user
// This method provides a simple interface for fetching user-specific projects
func (s *ProjectService) GetProjectsByUser(ctx context.Context, userID uuid.UUID) ([]*Project, error) {
log.Printf("INFO: Getting projects for user %s", userID)
// Delegate to repository for data retrieval
projects, err := s.repo.FindByCreatedBy(ctx, userID)
if err != nil {
log.Printf("ERROR: Failed to get projects for user %s: %v", userID, err)
return nil, err
}
log.Printf("INFO: Found %d projects for user %s", len(projects), userID)
// Debug logging: Show project names for troubleshooting
if len(projects) > 0 {
log.Printf("DEBUG: Projects found: %v", func() []string {
names := make([]string, len(projects))
for i, p := range projects {
names[i] = p.Name
}
return names
}())
}
return projects, nil
}
// GetProjectWithRequirements retrieves a project along with all its associated requirements
// This method is optimized for displaying project detail pages where both
// project information and requirements are needed simultaneously
func (s *ProjectService) GetProjectWithRequirements(ctx context.Context, id primitive.ObjectID) (*ProjectWithRequirements, error) {
log.Printf("INFO: Getting project with requirements for ID %s", id.Hex())
// Use the repository method that efficiently loads project and requirements together
// This is more efficient than making separate calls for project and requirements
projectWithReqs, err := s.repo.FindWithRequirements(ctx, id)
if err != nil {
log.Printf("ERROR: Failed to get project with requirements for ID %s: %v", id.Hex(), err)
return nil, err
}
log.Printf("INFO: Loaded project '%s' with %d requirements",
projectWithReqs.Project.Name, projectWithReqs.RequirementCount)
return projectWithReqs, nil
}
// UpdateProject handles project updates with business rule validation
// This method ensures that updates maintain data integrity and business rules
func (s *ProjectService) UpdateProject(ctx context.Context, update *ProjectToUpdate) (*Project, error) {
log.Printf("INFO: Updating project %s with name '%s'", update.ID.Hex(), update.Name)
// Business Rule: End date must still be after start date after the update
// This validation is applied to updates just like it is for creation
if update.EndDate.Before(update.StartDate) {
log.Printf("WARN: Project update failed - end date (%s) before start date (%s)",
update.EndDate.Format("2006-01-02"), update.StartDate.Format("2006-01-02"))
return nil, fmt.Errorf("end date must be after start date")
}
log.Printf("DEBUG: Project update validation passed, updating in repository")
// Delegate to repository for the actual update operation
project, err := s.repo.Update(ctx, update)
if err != nil {
log.Printf("ERROR: Failed to update project %s: %v", update.ID.Hex(), err)
return nil, err
}
log.Printf("INFO: Project '%s' updated successfully", project.Name)
return project, nil
}
// DeleteProject handles the complete removal of a project and all associated data
// This method ensures that all related requirements are also deleted to maintain data consistency
func (s *ProjectService) DeleteProject(ctx context.Context, id primitive.ObjectID) error {
log.Printf("INFO: Deleting project with ID %s", id.Hex())
// The repository handles the cascading deletion of requirements
// This ensures that no orphaned requirements remain in the database
err := s.repo.Delete(ctx, id)
if err != nil {
log.Printf("ERROR: Failed to delete project %s: %v", id.Hex(), err)
return err
}
log.Printf("INFO: Project %s and all its requirements deleted successfully", id.Hex())
return nil
}
// SearchProjects provides flexible project search functionality
// This method handles both empty queries (return all projects) and text-based searches
func (s *ProjectService) SearchProjects(ctx context.Context, query string, userID uuid.UUID) ([]*Project, error) {
// Handle empty search queries by returning all projects for the user
// This provides a consistent interface regardless of whether a search term is provided
if query == "" {
log.Printf("INFO: Empty search query, returning all projects for user %s", userID)
return s.repo.FindByCreatedBy(ctx, userID)
}
log.Printf("INFO: Searching projects for user %s with query '%s'", userID, query)
// Delegate to repository for the actual search implementation
// The repository handles the specifics of text searching across project fields
projects, err := s.repo.Search(ctx, query, userID)
if err != nil {
log.Printf("ERROR: Search failed for query '%s' and user %s: %v", query, userID, err)
return nil, err
}
log.Printf("INFO: Search completed - found %d projects for query '%s'", len(projects), query)
return projects, nil
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment