mirror of
https://github.com/SamyRai/turash.git
synced 2025-12-26 23:01:33 +00:00
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)
317 lines
10 KiB
Go
317 lines
10 KiB
Go
package service
|
|
|
|
import (
|
|
"bugulma/backend/internal/domain"
|
|
"bugulma/backend/internal/repository"
|
|
"context"
|
|
"encoding/json"
|
|
"log"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
"gorm.io/datatypes"
|
|
)
|
|
|
|
// OrganizationService handles business logic for organizations
|
|
type OrganizationService struct {
|
|
repo domain.OrganizationRepository
|
|
graphRepo *repository.GraphOrganizationRepository
|
|
}
|
|
|
|
// NewOrganizationService creates a new organization service
|
|
func NewOrganizationService(repo domain.OrganizationRepository, graphRepo *repository.GraphOrganizationRepository) *OrganizationService {
|
|
return &OrganizationService{
|
|
repo: repo,
|
|
graphRepo: graphRepo,
|
|
}
|
|
}
|
|
|
|
// SetGraphRepository sets the graph repository (for optional graph operations)
|
|
func (s *OrganizationService) SetGraphRepository(graphRepo *repository.GraphOrganizationRepository) {
|
|
s.graphRepo = graphRepo
|
|
}
|
|
|
|
// CreateOrganizationRequest represents the request to create an organization
|
|
type CreateOrganizationRequest struct {
|
|
// Required fields
|
|
Name string
|
|
Subtype domain.OrganizationSubtype
|
|
Sector string
|
|
|
|
// Basic information
|
|
Description string
|
|
LogoURL string
|
|
GalleryImages []string
|
|
Website string
|
|
Address string
|
|
Latitude float64
|
|
Longitude float64
|
|
|
|
// Business-specific fields
|
|
LegalForm string
|
|
PrimaryContactEmail string
|
|
PrimaryContactPhone string
|
|
IndustrialSector string
|
|
CompanySize int
|
|
YearsOperation int
|
|
SupplyChainRole domain.SupplyChainRole
|
|
Certifications []string
|
|
BusinessFocus []string
|
|
StrategicVision string
|
|
DriversBarriers string
|
|
ReadinessMaturity *int
|
|
TrustScore *float64
|
|
|
|
// Technical capabilities
|
|
TechnicalExpertise []string
|
|
AvailableTechnology []string
|
|
ManagementSystems []string
|
|
|
|
// Products and Services
|
|
SellsProducts []domain.ProductJSON
|
|
OffersServices []domain.ServiceJSON
|
|
NeedsServices []domain.ServiceNeedJSON
|
|
|
|
// Cultural/Historical building fields
|
|
YearBuilt string
|
|
BuilderOwner string
|
|
Architect string
|
|
OriginalPurpose string
|
|
CurrentUse string
|
|
Style string
|
|
Materials string
|
|
Storeys *int
|
|
HeritageStatus string
|
|
|
|
// Metadata
|
|
Verified bool
|
|
Notes string
|
|
Sources []string
|
|
|
|
// Relationships
|
|
TrustNetwork []string
|
|
ExistingSymbioticRelationships []string
|
|
}
|
|
|
|
// Create creates a new organization
|
|
func (s *OrganizationService) Create(ctx context.Context, req CreateOrganizationRequest) (*domain.Organization, error) {
|
|
// Marshal contact info
|
|
contactInfo := domain.PrimaryContact{
|
|
Email: req.PrimaryContactEmail,
|
|
Phone: req.PrimaryContactPhone,
|
|
}
|
|
contactJSON, _ := json.Marshal(contactInfo)
|
|
|
|
// Marshal arrays
|
|
certsJSON, _ := json.Marshal(req.Certifications)
|
|
focusJSON, _ := json.Marshal(req.BusinessFocus)
|
|
techExpertiseJSON, _ := json.Marshal(req.TechnicalExpertise)
|
|
technologyJSON, _ := json.Marshal(req.AvailableTechnology)
|
|
managementJSON, _ := json.Marshal(req.ManagementSystems)
|
|
trustNetJSON, _ := json.Marshal(req.TrustNetwork)
|
|
symbiosisJSON, _ := json.Marshal(req.ExistingSymbioticRelationships)
|
|
galleryImagesJSON, _ := json.Marshal(req.GalleryImages)
|
|
|
|
// Marshal complex JSON objects
|
|
productsJSON, _ := json.Marshal(req.SellsProducts)
|
|
servicesJSON, _ := json.Marshal(req.OffersServices)
|
|
needsJSON, _ := json.Marshal(req.NeedsServices)
|
|
sourcesJSON, _ := json.Marshal(req.Sources)
|
|
|
|
// Set defaults
|
|
readinessMaturity := 3
|
|
if req.ReadinessMaturity != nil {
|
|
readinessMaturity = *req.ReadinessMaturity
|
|
}
|
|
|
|
trustScore := 0.7
|
|
if req.TrustScore != nil {
|
|
trustScore = *req.TrustScore
|
|
}
|
|
|
|
storeys := 0
|
|
if req.Storeys != nil {
|
|
storeys = *req.Storeys
|
|
}
|
|
|
|
org := &domain.Organization{
|
|
ID: uuid.New().String(),
|
|
Name: req.Name,
|
|
Subtype: req.Subtype,
|
|
Sector: req.Sector,
|
|
Description: req.Description,
|
|
LogoURL: req.LogoURL,
|
|
GalleryImages: datatypes.JSON(galleryImagesJSON),
|
|
Website: req.Website,
|
|
Latitude: req.Latitude,
|
|
Longitude: req.Longitude,
|
|
LegalForm: req.LegalForm,
|
|
PrimaryContact: datatypes.JSON(contactJSON),
|
|
IndustrialSector: req.IndustrialSector,
|
|
CompanySize: req.CompanySize,
|
|
YearsOperation: req.YearsOperation,
|
|
SupplyChainRole: req.SupplyChainRole,
|
|
Certifications: datatypes.JSON(certsJSON),
|
|
BusinessFocus: datatypes.JSON(focusJSON),
|
|
StrategicVision: req.StrategicVision,
|
|
DriversBarriers: req.DriversBarriers,
|
|
ReadinessMaturity: readinessMaturity,
|
|
TrustScore: trustScore,
|
|
TechnicalExpertise: datatypes.JSON(techExpertiseJSON),
|
|
AvailableTechnology: datatypes.JSON(technologyJSON),
|
|
ManagementSystems: datatypes.JSON(managementJSON),
|
|
SellsProducts: datatypes.JSON(productsJSON),
|
|
OffersServices: datatypes.JSON(servicesJSON),
|
|
NeedsServices: datatypes.JSON(needsJSON),
|
|
YearBuilt: req.YearBuilt,
|
|
BuilderOwner: req.BuilderOwner,
|
|
Architect: req.Architect,
|
|
OriginalPurpose: req.OriginalPurpose,
|
|
CurrentUse: req.CurrentUse,
|
|
Style: req.Style,
|
|
Materials: req.Materials,
|
|
Storeys: storeys,
|
|
HeritageStatus: req.HeritageStatus,
|
|
Verified: req.Verified,
|
|
Notes: req.Notes,
|
|
Sources: datatypes.JSON(sourcesJSON),
|
|
TrustNetwork: datatypes.JSON(trustNetJSON),
|
|
ExistingSymbioticRelationships: datatypes.JSON(symbiosisJSON),
|
|
CreatedAt: time.Now(),
|
|
UpdatedAt: time.Now(),
|
|
}
|
|
|
|
if err := s.repo.Create(ctx, org); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if s.graphRepo != nil {
|
|
if err := s.graphRepo.SyncToGraph(ctx, org); err != nil {
|
|
log.Printf("Warning: Failed to sync organization %s to graph database: %v", org.ID, err)
|
|
}
|
|
}
|
|
|
|
return org, nil
|
|
}
|
|
|
|
// GetByID retrieves an organization by ID
|
|
func (s *OrganizationService) GetByID(ctx context.Context, id string) (*domain.Organization, error) {
|
|
return s.repo.GetByID(ctx, id)
|
|
}
|
|
|
|
// GetAll retrieves all organizations
|
|
func (s *OrganizationService) GetAll(ctx context.Context) ([]*domain.Organization, error) {
|
|
return s.repo.GetAll(ctx)
|
|
}
|
|
|
|
// GetBySubtype retrieves organizations by subtype
|
|
func (s *OrganizationService) GetBySubtype(ctx context.Context, subtype domain.OrganizationSubtype) ([]*domain.Organization, error) {
|
|
return s.repo.GetBySubtype(ctx, subtype)
|
|
}
|
|
|
|
// GetBySector retrieves organizations by sector
|
|
func (s *OrganizationService) GetBySector(ctx context.Context, sector string) ([]*domain.Organization, error) {
|
|
return s.repo.GetBySector(ctx, sector)
|
|
}
|
|
|
|
// Search performs fuzzy search on organizations
|
|
// limit: maximum number of results to return (default: 50)
|
|
func (s *OrganizationService) Search(ctx context.Context, query string, limit int) ([]*domain.Organization, error) {
|
|
if limit <= 0 {
|
|
limit = 50 // Default limit
|
|
}
|
|
if limit > 200 {
|
|
limit = 200 // Maximum limit
|
|
}
|
|
return s.repo.Search(ctx, query, limit)
|
|
}
|
|
|
|
// SearchSuggestions returns autocomplete suggestions for search queries
|
|
// limit: maximum number of suggestions to return (default: 10)
|
|
func (s *OrganizationService) SearchSuggestions(ctx context.Context, query string, limit int) ([]string, error) {
|
|
if limit <= 0 {
|
|
limit = 10 // Default limit
|
|
}
|
|
if limit > 50 {
|
|
limit = 50 // Maximum limit
|
|
}
|
|
return s.repo.SearchSuggestions(ctx, query, limit)
|
|
}
|
|
|
|
// GetByCertification retrieves organizations by certification
|
|
func (s *OrganizationService) GetByCertification(ctx context.Context, cert string) ([]*domain.Organization, error) {
|
|
return s.repo.GetByCertification(ctx, cert)
|
|
}
|
|
|
|
// GetWithinRadius retrieves organizations within a radius
|
|
func (s *OrganizationService) GetWithinRadius(ctx context.Context, lat, lng, radiusKm float64) ([]*domain.Organization, error) {
|
|
return s.repo.GetWithinRadius(ctx, lat, lng, radiusKm)
|
|
}
|
|
|
|
// Update updates an organization
|
|
func (s *OrganizationService) Update(ctx context.Context, org *domain.Organization) error {
|
|
org.UpdatedAt = time.Now()
|
|
if err := s.repo.Update(ctx, org); err != nil {
|
|
return err
|
|
}
|
|
if s.graphRepo != nil {
|
|
if err := s.graphRepo.SyncToGraph(ctx, org); err != nil {
|
|
// Log error
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Delete deletes an organization
|
|
func (s *OrganizationService) Delete(ctx context.Context, id string) error {
|
|
if s.graphRepo != nil {
|
|
if err := s.graphRepo.DeleteFromGraph(ctx, id); err != nil {
|
|
// Log error
|
|
}
|
|
}
|
|
return s.repo.Delete(ctx, id)
|
|
}
|
|
|
|
// AddProduct adds a product to an organization's offerings
|
|
func (s *OrganizationService) AddProduct(ctx context.Context, orgID string, product domain.ProductJSON) error {
|
|
org, err := s.repo.GetByID(ctx, orgID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var products []domain.ProductJSON
|
|
if org.SellsProducts != nil {
|
|
json.Unmarshal(org.SellsProducts, &products)
|
|
}
|
|
|
|
products = append(products, product)
|
|
productsJSON, _ := json.Marshal(products)
|
|
org.SellsProducts = datatypes.JSON(productsJSON)
|
|
|
|
return s.Update(ctx, org)
|
|
}
|
|
|
|
// AddService adds a service to an organization's offerings
|
|
func (s *OrganizationService) AddService(ctx context.Context, orgID string, service domain.ServiceJSON) error {
|
|
org, err := s.repo.GetByID(ctx, orgID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var services []domain.ServiceJSON
|
|
if org.OffersServices != nil {
|
|
json.Unmarshal(org.OffersServices, &services)
|
|
}
|
|
|
|
services = append(services, service)
|
|
servicesJSON, _ := json.Marshal(services)
|
|
org.OffersServices = datatypes.JSON(servicesJSON)
|
|
|
|
return s.Update(ctx, org)
|
|
}
|
|
|
|
// GetSectorStats returns the top sectors by organization count
|
|
func (s *OrganizationService) GetSectorStats(ctx context.Context, limit int) ([]domain.SectorStat, error) {
|
|
return s.repo.GetSectorStats(ctx, limit)
|
|
}
|