Make contractor residence optional with visibility rules
- Make residence_id nullable in contractor model - Add created_by_id field to track contractor creator - Update access control: personal contractors visible only to creator, residence contractors visible to all residence users - Add database migration for schema changes - Update admin panel DTOs and handlers for optional residence - Fix test utilities for new model structure 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -41,12 +41,8 @@ func (s *ContractorService) GetContractor(contractorID, userID uint) (*responses
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Check access via residence
|
||||
hasAccess, err := s.residenceRepo.HasAccess(contractor.ResidenceID, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !hasAccess {
|
||||
// Check access
|
||||
if !s.hasContractorAccess(contractor, userID) {
|
||||
return nil, ErrContractorAccessDenied
|
||||
}
|
||||
|
||||
@@ -54,6 +50,24 @@ func (s *ContractorService) GetContractor(contractorID, userID uint) (*responses
|
||||
return &resp, nil
|
||||
}
|
||||
|
||||
// hasContractorAccess checks if user has access to a contractor
|
||||
// Access rules:
|
||||
// - If contractor has no residence: only the creator has access
|
||||
// - If contractor has a residence: all users with access to that residence
|
||||
func (s *ContractorService) hasContractorAccess(contractor *models.Contractor, userID uint) bool {
|
||||
if contractor.ResidenceID == nil {
|
||||
// Personal contractor - only creator has access
|
||||
return contractor.CreatedByID == userID
|
||||
}
|
||||
|
||||
// Residence contractor - check residence access
|
||||
hasAccess, err := s.residenceRepo.HasAccess(*contractor.ResidenceID, userID)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return hasAccess
|
||||
}
|
||||
|
||||
// ListContractors lists all contractors accessible to a user
|
||||
func (s *ContractorService) ListContractors(userID uint) ([]responses.ContractorResponse, error) {
|
||||
residences, err := s.residenceRepo.FindByUser(userID)
|
||||
@@ -66,11 +80,8 @@ func (s *ContractorService) ListContractors(userID uint) ([]responses.Contractor
|
||||
residenceIDs[i] = r.ID
|
||||
}
|
||||
|
||||
if len(residenceIDs) == 0 {
|
||||
return []responses.ContractorResponse{}, nil
|
||||
}
|
||||
|
||||
contractors, err := s.contractorRepo.FindByUser(residenceIDs)
|
||||
// FindByUser now handles both personal and residence contractors
|
||||
contractors, err := s.contractorRepo.FindByUser(userID, residenceIDs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -80,13 +91,15 @@ func (s *ContractorService) ListContractors(userID uint) ([]responses.Contractor
|
||||
|
||||
// CreateContractor creates a new contractor
|
||||
func (s *ContractorService) CreateContractor(req *requests.CreateContractorRequest, userID uint) (*responses.ContractorResponse, error) {
|
||||
// Check residence access
|
||||
hasAccess, err := s.residenceRepo.HasAccess(req.ResidenceID, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !hasAccess {
|
||||
return nil, ErrResidenceAccessDenied
|
||||
// If residence is provided, check access
|
||||
if req.ResidenceID != nil {
|
||||
hasAccess, err := s.residenceRepo.HasAccess(*req.ResidenceID, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !hasAccess {
|
||||
return nil, ErrResidenceAccessDenied
|
||||
}
|
||||
}
|
||||
|
||||
isFavorite := false
|
||||
@@ -124,9 +137,9 @@ func (s *ContractorService) CreateContractor(req *requests.CreateContractorReque
|
||||
}
|
||||
|
||||
// Reload with relations
|
||||
contractor, err = s.contractorRepo.FindByID(contractor.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
contractor, reloadErr := s.contractorRepo.FindByID(contractor.ID)
|
||||
if reloadErr != nil {
|
||||
return nil, reloadErr
|
||||
}
|
||||
|
||||
resp := responses.NewContractorResponse(contractor)
|
||||
@@ -144,11 +157,7 @@ func (s *ContractorService) UpdateContractor(contractorID, userID uint, req *req
|
||||
}
|
||||
|
||||
// Check access
|
||||
hasAccess, err := s.residenceRepo.HasAccess(contractor.ResidenceID, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !hasAccess {
|
||||
if !s.hasContractorAccess(contractor, userID) {
|
||||
return nil, ErrContractorAccessDenied
|
||||
}
|
||||
|
||||
@@ -222,11 +231,7 @@ func (s *ContractorService) DeleteContractor(contractorID, userID uint) error {
|
||||
}
|
||||
|
||||
// Check access
|
||||
hasAccess, err := s.residenceRepo.HasAccess(contractor.ResidenceID, userID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !hasAccess {
|
||||
if !s.hasContractorAccess(contractor, userID) {
|
||||
return ErrContractorAccessDenied
|
||||
}
|
||||
|
||||
@@ -244,11 +249,7 @@ func (s *ContractorService) ToggleFavorite(contractorID, userID uint) (*response
|
||||
}
|
||||
|
||||
// Check access
|
||||
hasAccess, err := s.residenceRepo.HasAccess(contractor.ResidenceID, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !hasAccess {
|
||||
if !s.hasContractorAccess(contractor, userID) {
|
||||
return nil, ErrContractorAccessDenied
|
||||
}
|
||||
|
||||
@@ -278,11 +279,7 @@ func (s *ContractorService) GetContractorTasks(contractorID, userID uint) ([]res
|
||||
}
|
||||
|
||||
// Check access
|
||||
hasAccess, err := s.residenceRepo.HasAccess(contractor.ResidenceID, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !hasAccess {
|
||||
if !s.hasContractorAccess(contractor, userID) {
|
||||
return nil, ErrContractorAccessDenied
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user