turash/bugulma/backend/internal/repository/match_repository.go
Damir Mukimov 000eab4740
Major repository reorganization and missing backend endpoints implementation
Repository Structure:
- Move files from cluttered root directory into organized structure
- Create archive/ for archived data and scraper results
- Create bugulma/ for the complete application (frontend + backend)
- Create data/ for sample datasets and reference materials
- Create docs/ for comprehensive documentation structure
- Create scripts/ for utility scripts and API tools

Backend Implementation:
- Implement 3 missing backend endpoints identified in gap analysis:
  * GET /api/v1/organizations/{id}/matching/direct - Direct symbiosis matches
  * GET /api/v1/users/me/organizations - User organizations
  * POST /api/v1/proposals/{id}/status - Update proposal status
- Add complete proposal domain model, repository, and service layers
- Create database migration for proposals table
- Fix CLI server command registration issue

API Documentation:
- Add comprehensive proposals.md API documentation
- Update README.md with Users and Proposals API sections
- Document all request/response formats, error codes, and business rules

Code Quality:
- Follow existing Go backend architecture patterns
- Add proper error handling and validation
- Match frontend expected response schemas
- Maintain clean separation of concerns (handler -> service -> repository)
2025-11-25 06:01:16 +01:00

151 lines
5.2 KiB
Go

package repository
import (
"bugulma/backend/internal/domain"
"context"
"encoding/json"
"time"
"github.com/google/uuid"
"gorm.io/datatypes"
"gorm.io/gorm"
)
// MatchRepository implements domain.MatchRepository with GORM
type MatchRepository struct {
*BaseRepository[domain.Match]
}
// NewMatchRepository creates a new GORM-based match repository
func NewMatchRepository(db *gorm.DB) domain.MatchRepository {
return &MatchRepository{
BaseRepository: NewBaseRepository[domain.Match](db),
}
}
// GetByResourceID retrieves matches involving a specific resource flow
func (r *MatchRepository) GetByResourceID(ctx context.Context, resourceID string) ([]*domain.Match, error) {
return r.FindWhereWithContext(ctx, "source_resource_id = ? OR target_resource_id = ?", resourceID, resourceID)
}
// GetByOrganizationID retrieves matches involving a specific organization
func (r *MatchRepository) GetByOrganizationID(ctx context.Context, organizationID string) ([]*domain.Match, error) {
var matches []*domain.Match
query := `
SELECT m.* FROM matches m
JOIN resource_flows rf1 ON m.source_resource_id = rf1.id
JOIN resource_flows rf2 ON m.target_resource_id = rf2.id
WHERE rf1.organization_id = ? OR rf2.organization_id = ?
`
result := r.DB().WithContext(ctx).Raw(query, organizationID, organizationID).Scan(&matches)
if result.Error != nil {
return nil, result.Error
}
return matches, nil
}
// GetByStatus retrieves matches by status
func (r *MatchRepository) GetByStatus(ctx context.Context, status domain.MatchStatus) ([]*domain.Match, error) {
return r.FindWhereWithContext(ctx, "status = ?", status)
}
// GetWithNegotiationHistory retrieves a match with its complete negotiation history
func (r *MatchRepository) GetWithNegotiationHistory(matchID string) (*domain.Match, error) {
var match domain.Match
err := r.DB().Preload("NegotiationHistory").Where("id = ?", matchID).First(&match).Error
if err != nil {
return nil, err
}
return &match, nil
}
// GetTopMatches retrieves the top matches ordered by compatibility score
func (r *MatchRepository) GetTopMatches(ctx context.Context, limit int) ([]*domain.Match, error) {
var matches []*domain.Match
result := r.DB().WithContext(ctx).
Where("status = ?", domain.MatchStatusSuggested).
Order("compatibility_score DESC, economic_value DESC").
Limit(limit).
Find(&matches)
if result.Error != nil {
return nil, result.Error
}
return matches, nil
}
// CheckReservationConflicts checks if a resource has conflicting reservations
func (r *MatchRepository) CheckReservationConflicts(ctx context.Context, resourceID string) ([]*domain.Match, error) {
now := time.Now()
var matches []*domain.Match
result := r.DB().WithContext(ctx).Where(
"(source_resource_id = ? OR target_resource_id = ?) AND status IN (?, ?) AND (reserved_until IS NULL OR reserved_until > ?)",
resourceID, resourceID,
domain.MatchStatusReserved, domain.MatchStatusContracted,
now,
).Find(&matches)
if result.Error != nil {
return nil, result.Error
}
return matches, nil
}
// UpdateStatus updates the match status and adds a history entry
func (r *MatchRepository) UpdateStatus(ctx context.Context, matchID string, newStatus domain.MatchStatus, actor string, notes string) error {
return r.Transaction(func(tx *gorm.DB) error {
// Get the current match
var match domain.Match
if err := tx.WithContext(ctx).First(&match, "id = ?", matchID).Error; err != nil {
return err
}
oldStatus := match.Status
match.Status = newStatus
// Add negotiation history entry
negotiationEntry := &domain.NegotiationHistoryEntry{
ID: uuid.New().String(),
MatchID: matchID,
Timestamp: time.Now(),
ActorID: actor,
Action: "status_change",
Notes: notes,
}
// Set old/new values
oldValue, _ := json.Marshal(map[string]interface{}{"status": oldStatus})
newValue, _ := json.Marshal(map[string]interface{}{"status": newStatus})
negotiationEntry.OldValue = datatypes.JSON(oldValue)
negotiationEntry.NewValue = datatypes.JSON(newValue)
// Save negotiation history entry
if err := tx.WithContext(ctx).Create(negotiationEntry).Error; err != nil {
return err
}
// Save the match
return tx.WithContext(ctx).Save(&match).Error
})
}
// GetByOrganizationIDAndStatus retrieves matches for an organization with a specific status
func (r *MatchRepository) GetByOrganizationIDAndStatus(ctx context.Context, orgID string, status domain.MatchStatus) ([]*domain.Match, error) {
var matches []*domain.Match
err := r.DB().WithContext(ctx).Where("organization_id = ? AND status = ?", orgID, status).Find(&matches).Error
return matches, err
}
// GetExpiredReservations finds matches with expired reservations
func (r *MatchRepository) GetExpiredReservations() ([]*domain.Match, error) {
var matches []*domain.Match
now := time.Now()
err := r.DB().Where("status = ? AND reserved_until < ?", domain.MatchStatusReserved, now).Find(&matches).Error
return matches, err
}
// GetPendingNegotiations finds matches in negotiation status
func (r *MatchRepository) GetPendingNegotiations() ([]*domain.Match, error) {
var matches []*domain.Match
err := r.DB().Where("status = ?", domain.MatchStatusNegotiating).Find(&matches).Error
return matches, err
}