mirror of
https://github.com/SamyRai/turash.git
synced 2025-12-26 23:01:33 +00:00
809 lines
24 KiB
Go
809 lines
24 KiB
Go
package handler
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"strconv"
|
|
|
|
"bugulma/backend/internal/domain"
|
|
"bugulma/backend/internal/matching"
|
|
"bugulma/backend/internal/service"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"gorm.io/datatypes"
|
|
)
|
|
|
|
type OrganizationHandler struct {
|
|
orgService *service.OrganizationService
|
|
imageService *service.ImageService
|
|
resourceFlowService *service.ResourceFlowService
|
|
matchingService *matching.Service
|
|
proposalService *service.ProposalService
|
|
}
|
|
|
|
func NewOrganizationHandler(orgService *service.OrganizationService, imageService *service.ImageService, resourceFlowService *service.ResourceFlowService, matchingService *matching.Service, proposalService *service.ProposalService) *OrganizationHandler {
|
|
return &OrganizationHandler{
|
|
orgService: orgService,
|
|
imageService: imageService,
|
|
resourceFlowService: resourceFlowService,
|
|
matchingService: matchingService,
|
|
proposalService: proposalService,
|
|
}
|
|
}
|
|
|
|
// Helper methods for error responses
|
|
func (h *OrganizationHandler) errorResponse(c *gin.Context, status int, message string) {
|
|
c.JSON(status, gin.H{"error": message})
|
|
}
|
|
|
|
func (h *OrganizationHandler) internalError(c *gin.Context, err error) {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
}
|
|
|
|
func (h *OrganizationHandler) notFound(c *gin.Context, resource string) {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": resource + " not found"})
|
|
}
|
|
|
|
func (h *OrganizationHandler) badRequest(c *gin.Context, err error) {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
}
|
|
|
|
// Helper to parse limit query parameter with validation
|
|
func (h *OrganizationHandler) parseLimitQuery(c *gin.Context, defaultLimit, maxLimit int) int {
|
|
limitStr := c.DefaultQuery("limit", strconv.Itoa(defaultLimit))
|
|
limit, err := strconv.Atoi(limitStr)
|
|
if err != nil || limit < 1 {
|
|
return defaultLimit
|
|
}
|
|
if limit > maxLimit {
|
|
return maxLimit
|
|
}
|
|
return limit
|
|
}
|
|
|
|
// Helper to get organization by ID or return error response
|
|
func (h *OrganizationHandler) getOrgByIDOrError(c *gin.Context, id string) (*domain.Organization, bool) {
|
|
org, err := h.orgService.GetByID(c.Request.Context(), id)
|
|
if err != nil {
|
|
h.notFound(c, "Organization")
|
|
return nil, false
|
|
}
|
|
return org, true
|
|
}
|
|
|
|
// Helper to convert subtypes to string slice
|
|
func subtypesToStrings(subtypes []domain.OrganizationSubtype) []string {
|
|
result := make([]string, len(subtypes))
|
|
for i, st := range subtypes {
|
|
result[i] = string(st)
|
|
}
|
|
return result
|
|
}
|
|
|
|
type CreateOrganizationRequest struct {
|
|
// Required fields
|
|
Name string `json:"name" binding:"required"`
|
|
Sector string `json:"sector" binding:"required"`
|
|
Subtype string `json:"subtype" binding:"required"`
|
|
|
|
// Basic information
|
|
Description string `json:"description"`
|
|
LogoURL string `json:"logoUrl"`
|
|
GalleryImages []string `json:"galleryImages"`
|
|
Website string `json:"website"`
|
|
Address string `json:"address"`
|
|
Latitude float64 `json:"latitude"`
|
|
Longitude float64 `json:"longitude"`
|
|
|
|
// Business-specific fields
|
|
LegalForm string `json:"legalForm"`
|
|
PrimaryContactEmail string `json:"primaryContactEmail"`
|
|
PrimaryContactPhone string `json:"primaryContactPhone"`
|
|
IndustrialSector string `json:"industrialSector"`
|
|
CompanySize int `json:"companySize"`
|
|
YearsOperation int `json:"yearsOperation"`
|
|
SupplyChainRole string `json:"supplyChainRole"`
|
|
Certifications []string `json:"certifications"`
|
|
BusinessFocus []string `json:"businessFocus"`
|
|
StrategicVision string `json:"strategicVision"`
|
|
DriversBarriers string `json:"driversBarriers"`
|
|
ReadinessMaturity *int `json:"readinessMaturity"` // 1-5
|
|
TrustScore *float64 `json:"trustScore"` // 0.0-1.0
|
|
|
|
// Technical capabilities
|
|
TechnicalExpertise []string `json:"technicalExpertise"`
|
|
AvailableTechnology []string `json:"availableTechnology"`
|
|
ManagementSystems []string `json:"managementSystems"`
|
|
|
|
// Products and Services
|
|
SellsProducts []domain.ProductJSON `json:"sellsProducts"`
|
|
OffersServices []domain.ServiceJSON `json:"offersServices"`
|
|
NeedsServices []domain.ServiceNeedJSON `json:"needsServices"`
|
|
|
|
// Cultural/Historical building fields
|
|
YearBuilt string `json:"yearBuilt"`
|
|
BuilderOwner string `json:"builderOwner"`
|
|
Architect string `json:"architect"`
|
|
OriginalPurpose string `json:"originalPurpose"`
|
|
CurrentUse string `json:"currentUse"`
|
|
Style string `json:"style"`
|
|
Materials string `json:"materials"`
|
|
Storeys *int `json:"storeys"`
|
|
HeritageStatus string `json:"heritageStatus"`
|
|
|
|
// Metadata
|
|
Verified bool `json:"verified"`
|
|
Notes string `json:"notes"`
|
|
Sources []string `json:"sources"`
|
|
|
|
// Relationships
|
|
TrustNetwork []string `json:"trustNetwork"`
|
|
ExistingSymbioticRelationships []string `json:"existingSymbioticRelationships"`
|
|
}
|
|
|
|
func (h *OrganizationHandler) Create(c *gin.Context) {
|
|
var req CreateOrganizationRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
h.badRequest(c, err)
|
|
return
|
|
}
|
|
|
|
// Validate subtype
|
|
subtype := domain.OrganizationSubtype(req.Subtype)
|
|
if !domain.IsValidSubtype(subtype) {
|
|
h.errorResponse(c, http.StatusBadRequest, "invalid subtype: "+req.Subtype)
|
|
return
|
|
}
|
|
|
|
orgReq := service.CreateOrganizationRequest{
|
|
Name: req.Name,
|
|
Subtype: subtype,
|
|
Sector: domain.OrganizationSector(req.Sector),
|
|
Description: req.Description,
|
|
LogoURL: req.LogoURL,
|
|
GalleryImages: req.GalleryImages,
|
|
Website: req.Website,
|
|
Address: req.Address,
|
|
Latitude: req.Latitude,
|
|
Longitude: req.Longitude,
|
|
LegalForm: req.LegalForm,
|
|
PrimaryContactEmail: req.PrimaryContactEmail,
|
|
PrimaryContactPhone: req.PrimaryContactPhone,
|
|
IndustrialSector: req.IndustrialSector,
|
|
CompanySize: req.CompanySize,
|
|
YearsOperation: req.YearsOperation,
|
|
SupplyChainRole: domain.SupplyChainRole(req.SupplyChainRole),
|
|
Certifications: req.Certifications,
|
|
BusinessFocus: req.BusinessFocus,
|
|
StrategicVision: req.StrategicVision,
|
|
DriversBarriers: req.DriversBarriers,
|
|
ReadinessMaturity: req.ReadinessMaturity,
|
|
TrustScore: req.TrustScore,
|
|
TechnicalExpertise: req.TechnicalExpertise,
|
|
AvailableTechnology: req.AvailableTechnology,
|
|
ManagementSystems: req.ManagementSystems,
|
|
SellsProducts: req.SellsProducts,
|
|
OffersServices: req.OffersServices,
|
|
NeedsServices: req.NeedsServices,
|
|
YearBuilt: req.YearBuilt,
|
|
BuilderOwner: req.BuilderOwner,
|
|
Architect: req.Architect,
|
|
OriginalPurpose: req.OriginalPurpose,
|
|
CurrentUse: req.CurrentUse,
|
|
Style: req.Style,
|
|
Materials: req.Materials,
|
|
Storeys: req.Storeys,
|
|
HeritageStatus: req.HeritageStatus,
|
|
Verified: req.Verified,
|
|
Notes: req.Notes,
|
|
Sources: req.Sources,
|
|
TrustNetwork: req.TrustNetwork,
|
|
ExistingSymbioticRelationships: req.ExistingSymbioticRelationships,
|
|
}
|
|
|
|
org, err := h.orgService.Create(c.Request.Context(), orgReq)
|
|
if err != nil {
|
|
h.internalError(c, err)
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusCreated, org)
|
|
}
|
|
|
|
func (h *OrganizationHandler) GetByID(c *gin.Context) {
|
|
id := c.Param("id")
|
|
org, ok := h.getOrgByIDOrError(c, id)
|
|
if !ok {
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, org)
|
|
}
|
|
|
|
func (h *OrganizationHandler) GetAll(c *gin.Context) {
|
|
// Check for sector filter
|
|
sector := c.Query("sector")
|
|
if sector != "" {
|
|
orgs, err := h.orgService.GetBySector(c.Request.Context(), domain.OrganizationSector(sector))
|
|
if err != nil {
|
|
h.internalError(c, err)
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, orgs)
|
|
return
|
|
}
|
|
|
|
// No filter - return all organizations
|
|
orgs, err := h.orgService.GetAll(c.Request.Context())
|
|
if err != nil {
|
|
h.internalError(c, err)
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, orgs)
|
|
}
|
|
|
|
func (h *OrganizationHandler) Update(c *gin.Context) {
|
|
id := c.Param("id")
|
|
|
|
var req CreateOrganizationRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
h.badRequest(c, err)
|
|
return
|
|
}
|
|
|
|
org, ok := h.getOrgByIDOrError(c, id)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
// Validate subtype
|
|
subtype := domain.OrganizationSubtype(req.Subtype)
|
|
if !domain.IsValidSubtype(subtype) {
|
|
h.errorResponse(c, http.StatusBadRequest, "invalid subtype: "+req.Subtype)
|
|
return
|
|
}
|
|
|
|
// Update fields
|
|
org.Name = req.Name
|
|
org.Sector = domain.OrganizationSector(req.Sector)
|
|
org.Subtype = subtype
|
|
org.Description = req.Description
|
|
org.LogoURL = req.LogoURL
|
|
org.Website = req.Website
|
|
|
|
if err := h.orgService.Update(c.Request.Context(), org); err != nil {
|
|
h.internalError(c, err)
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, org)
|
|
}
|
|
|
|
func (h *OrganizationHandler) Delete(c *gin.Context) {
|
|
id := c.Param("id")
|
|
if err := h.orgService.Delete(c.Request.Context(), id); err != nil {
|
|
h.notFound(c, "Organization")
|
|
return
|
|
}
|
|
c.JSON(http.StatusNoContent, nil)
|
|
}
|
|
|
|
func (h *OrganizationHandler) GetBySubtype(c *gin.Context) {
|
|
subtype := c.Param("subtype")
|
|
orgs, err := h.orgService.GetBySubtype(c.Request.Context(), domain.OrganizationSubtype(subtype))
|
|
if err != nil {
|
|
h.internalError(c, err)
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, orgs)
|
|
}
|
|
|
|
func (h *OrganizationHandler) GetBySector(c *gin.Context) {
|
|
sectorParam := c.Param("sector")
|
|
sector := domain.OrganizationSector(sectorParam)
|
|
orgs, err := h.orgService.GetBySector(c.Request.Context(), sector)
|
|
if err != nil {
|
|
h.internalError(c, err)
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, orgs)
|
|
}
|
|
|
|
// GetSectorStats returns sector statistics (top sectors by organization count)
|
|
func (h *OrganizationHandler) GetSectorStats(c *gin.Context) {
|
|
limit := h.parseLimitQuery(c, 10, 50)
|
|
stats, err := h.orgService.GetSectorStats(c.Request.Context(), limit)
|
|
if err != nil {
|
|
h.internalError(c, err)
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{"sectors": stats})
|
|
}
|
|
|
|
// GetAllSubtypes returns all available organization subtypes
|
|
func (h *OrganizationHandler) GetAllSubtypes(c *gin.Context) {
|
|
subtypes := domain.GetAllSubtypes()
|
|
c.JSON(http.StatusOK, gin.H{"subtypes": subtypesToStrings(subtypes)})
|
|
}
|
|
|
|
// GetSubtypesBySector returns subtypes filtered by sector
|
|
func (h *OrganizationHandler) GetSubtypesBySector(c *gin.Context) {
|
|
sectorParam := c.Query("sector")
|
|
if sectorParam == "" {
|
|
h.GetAllSubtypes(c)
|
|
return
|
|
}
|
|
|
|
// Parse sector parameter as OrganizationSector enum
|
|
sector := domain.OrganizationSector(sectorParam)
|
|
subtypes := domain.GetSubtypesBySector(sector)
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"sector": sector,
|
|
"subtypes": subtypesToStrings(subtypes),
|
|
})
|
|
}
|
|
|
|
func (h *OrganizationHandler) GetByCertification(c *gin.Context) {
|
|
cert := c.Param("cert")
|
|
orgs, err := h.orgService.GetByCertification(c.Request.Context(), cert)
|
|
if err != nil {
|
|
h.internalError(c, err)
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, orgs)
|
|
}
|
|
|
|
// Search handles search requests for organizations
|
|
// Query parameters:
|
|
// - q: search query string (required)
|
|
// - limit: maximum number of results (optional, default: 50, max: 200)
|
|
func (h *OrganizationHandler) Search(c *gin.Context) {
|
|
query := c.Query("q")
|
|
if query == "" {
|
|
h.errorResponse(c, http.StatusBadRequest, "query parameter 'q' is required")
|
|
return
|
|
}
|
|
|
|
limit := h.parseLimitQuery(c, 50, 200)
|
|
orgs, err := h.orgService.Search(c.Request.Context(), query, limit)
|
|
if err != nil {
|
|
h.internalError(c, err)
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"organizations": orgs,
|
|
"count": len(orgs),
|
|
"total": len(orgs), // For now, we don't have total count from search
|
|
})
|
|
}
|
|
|
|
// SearchSuggestions handles autocomplete/suggestion requests
|
|
// Query parameters:
|
|
// - q: search query string (required)
|
|
// - limit: maximum number of suggestions (optional, default: 10, max: 50)
|
|
func (h *OrganizationHandler) SearchSuggestions(c *gin.Context) {
|
|
query := c.Query("q")
|
|
if query == "" {
|
|
c.JSON(http.StatusOK, []string{}) // Return empty array for empty query
|
|
return
|
|
}
|
|
|
|
limit := h.parseLimitQuery(c, 10, 50)
|
|
suggestions, err := h.orgService.SearchSuggestions(c.Request.Context(), query, limit)
|
|
if err != nil {
|
|
h.internalError(c, err)
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, suggestions)
|
|
}
|
|
|
|
func (h *OrganizationHandler) GetNearby(c *gin.Context) {
|
|
var query struct {
|
|
Latitude float64 `form:"lat" binding:"required"`
|
|
Longitude float64 `form:"lng" binding:"required"`
|
|
RadiusKm float64 `form:"radius" binding:"required"`
|
|
}
|
|
|
|
if err := c.ShouldBindQuery(&query); err != nil {
|
|
h.badRequest(c, err)
|
|
return
|
|
}
|
|
|
|
orgs, err := h.orgService.GetWithinRadius(c.Request.Context(), query.Latitude, query.Longitude, query.RadiusKm)
|
|
if err != nil {
|
|
h.internalError(c, err)
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, orgs)
|
|
}
|
|
|
|
// UploadLogo handles logo image uploads for organizations
|
|
func (h *OrganizationHandler) UploadLogo(c *gin.Context) {
|
|
orgID := c.Param("id")
|
|
uploadedImage, ok := h.handleImageUpload(c, orgID, "logo", "logos")
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
org, ok := h.getOrgByIDOrError(c, orgID)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
// Delete old logo if it exists
|
|
if org.LogoURL != "" {
|
|
h.imageService.DeleteImage(org.LogoURL)
|
|
}
|
|
|
|
org.LogoURL = uploadedImage.URL
|
|
if err := h.orgService.Update(c.Request.Context(), org); err != nil {
|
|
h.errorResponse(c, http.StatusInternalServerError, "Failed to update organization")
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"url": uploadedImage.URL,
|
|
"message": "Logo uploaded successfully",
|
|
})
|
|
}
|
|
|
|
// UploadGalleryImage handles gallery image uploads for organizations
|
|
func (h *OrganizationHandler) UploadGalleryImage(c *gin.Context) {
|
|
orgID := c.Param("id")
|
|
|
|
// Get the uploaded file
|
|
file, header, err := c.Request.FormFile("image")
|
|
if err != nil {
|
|
h.errorResponse(c, http.StatusBadRequest, "No image file provided")
|
|
return
|
|
}
|
|
defer file.Close()
|
|
|
|
// Save the image
|
|
uploadedImage, err := h.imageService.SaveImage(file, header, "gallery")
|
|
if err != nil {
|
|
h.errorResponse(c, http.StatusInternalServerError, fmt.Sprintf("Failed to save image: %v", err))
|
|
return
|
|
}
|
|
|
|
// Update the organization by adding the image to gallery
|
|
org, ok := h.getOrgByIDOrError(c, orgID)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
// Get current gallery images
|
|
galleryImages := []string{}
|
|
if len(org.GalleryImages) > 0 {
|
|
// Parse the JSON field
|
|
var currentImages []string
|
|
if err := json.Unmarshal(org.GalleryImages, ¤tImages); err == nil {
|
|
galleryImages = currentImages
|
|
}
|
|
}
|
|
|
|
// Add new image
|
|
galleryImages = append(galleryImages, uploadedImage.URL)
|
|
|
|
// Convert to JSON for storage
|
|
galleryJSON, _ := json.Marshal(galleryImages)
|
|
org.GalleryImages = datatypes.JSON(galleryJSON)
|
|
|
|
if err := h.orgService.Update(c.Request.Context(), org); err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update organization"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"url": uploadedImage.URL,
|
|
"galleryImages": galleryImages,
|
|
"message": "Gallery image uploaded successfully",
|
|
})
|
|
}
|
|
|
|
// DeleteGalleryImage removes a gallery image from an organization
|
|
func (h *OrganizationHandler) DeleteGalleryImage(c *gin.Context) {
|
|
orgID := c.Param("id")
|
|
imageURL := c.Query("url")
|
|
|
|
if imageURL == "" {
|
|
h.errorResponse(c, http.StatusBadRequest, "Image URL required")
|
|
return
|
|
}
|
|
|
|
// Get the organization
|
|
org, ok := h.getOrgByIDOrError(c, orgID)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
// Remove the image from gallery
|
|
galleryImages := []string{}
|
|
if len(org.GalleryImages) > 0 {
|
|
// Parse the JSON field
|
|
var currentImages []string
|
|
if err := json.Unmarshal(org.GalleryImages, ¤tImages); err == nil {
|
|
for _, img := range currentImages {
|
|
if img != imageURL {
|
|
galleryImages = append(galleryImages, img)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Delete the file from disk
|
|
if err := h.imageService.DeleteImage(imageURL); err != nil {
|
|
// Log error but continue with database update
|
|
fmt.Printf("Warning: failed to delete image file: %v\n", err)
|
|
}
|
|
|
|
// Convert to JSON for storage
|
|
galleryJSON, _ := json.Marshal(galleryImages)
|
|
org.GalleryImages = datatypes.JSON(galleryJSON)
|
|
if err := h.orgService.Update(c.Request.Context(), org); err != nil {
|
|
h.errorResponse(c, http.StatusInternalServerError, "Failed to update organization")
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"galleryImages": galleryImages,
|
|
"message": "Gallery image deleted successfully",
|
|
})
|
|
}
|
|
|
|
// GetSimilarOrganizations returns organizations similar to the given organization
|
|
func (h *OrganizationHandler) GetSimilarOrganizations(c *gin.Context) {
|
|
orgID := c.Param("id")
|
|
limit := h.parseLimitQuery(c, 5, 50)
|
|
|
|
org, ok := h.getOrgByIDOrError(c, orgID)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
// Get organizations by sector (primary similarity factor)
|
|
sectorOrgs, err := h.orgService.GetBySector(c.Request.Context(), org.Sector)
|
|
if err != nil {
|
|
h.errorResponse(c, http.StatusInternalServerError, "Failed to get similar organizations")
|
|
return
|
|
}
|
|
|
|
// Get resource flows for the organization to find complementary organizations
|
|
orgFlows, err := h.resourceFlowService.GetByOrganizationID(c.Request.Context(), orgID)
|
|
if err != nil {
|
|
// Continue without resource flow matching if it fails
|
|
orgFlows = []*domain.ResourceFlow{}
|
|
}
|
|
|
|
// Calculate similarity scores using service layer
|
|
similarOrgs, err := h.orgService.CalculateSimilarityScores(c.Request.Context(), orgID, sectorOrgs, orgFlows)
|
|
if err != nil {
|
|
h.errorResponse(c, http.StatusInternalServerError, "Failed to calculate similarity scores")
|
|
return
|
|
}
|
|
|
|
// Limit results
|
|
if len(similarOrgs) > limit {
|
|
similarOrgs = similarOrgs[:limit]
|
|
}
|
|
|
|
c.JSON(http.StatusOK, similarOrgs)
|
|
}
|
|
|
|
// GetOrganizationProposals returns proposals related to the organization
|
|
func (h *OrganizationHandler) GetOrganizationProposals(c *gin.Context) {
|
|
orgID := c.Param("id")
|
|
if h.proposalService == nil {
|
|
h.errorResponse(c, http.StatusServiceUnavailable, "Proposal service is not available")
|
|
return
|
|
}
|
|
|
|
proposals, err := h.proposalService.GetByOrganizationID(c.Request.Context(), orgID)
|
|
if err != nil {
|
|
h.errorResponse(c, http.StatusInternalServerError, "Failed to get organization proposals")
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, proposals)
|
|
}
|
|
|
|
// GetOrganizationResources returns resource flows for the organization
|
|
func (h *OrganizationHandler) GetOrganizationResources(c *gin.Context) {
|
|
orgID := c.Param("id")
|
|
resourceFlows, err := h.resourceFlowService.GetByOrganizationID(c.Request.Context(), orgID)
|
|
if err != nil {
|
|
h.errorResponse(c, http.StatusInternalServerError, "Failed to get organization resources")
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, resourceFlows)
|
|
}
|
|
|
|
// GetOrganizationProducts returns products for the organization
|
|
func (h *OrganizationHandler) GetOrganizationProducts(c *gin.Context) {
|
|
orgID := c.Param("id")
|
|
if h.matchingService == nil {
|
|
h.errorResponse(c, http.StatusServiceUnavailable, "Matching service is not available")
|
|
return
|
|
}
|
|
|
|
ctx := c.Request.Context()
|
|
products, err := h.matchingService.GetProductsByOrganization(ctx, orgID)
|
|
if err != nil {
|
|
h.errorResponse(c, http.StatusInternalServerError, "Failed to get organization products")
|
|
return
|
|
}
|
|
|
|
matches, err := h.convertItemsToDiscoveryMatches(ctx, products, "product")
|
|
if err != nil {
|
|
h.errorResponse(c, http.StatusInternalServerError, "Failed to convert products to matches")
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, matches)
|
|
}
|
|
|
|
// GetOrganizationServices returns services for the organization
|
|
func (h *OrganizationHandler) GetOrganizationServices(c *gin.Context) {
|
|
orgID := c.Param("id")
|
|
if h.matchingService == nil {
|
|
h.errorResponse(c, http.StatusServiceUnavailable, "Matching service is not available")
|
|
return
|
|
}
|
|
|
|
ctx := c.Request.Context()
|
|
services, err := h.matchingService.GetServicesByOrganization(ctx, orgID)
|
|
if err != nil {
|
|
h.errorResponse(c, http.StatusInternalServerError, "Failed to get organization services")
|
|
return
|
|
}
|
|
|
|
matches, err := h.convertItemsToDiscoveryMatches(ctx, services, "service")
|
|
if err != nil {
|
|
h.errorResponse(c, http.StatusInternalServerError, "Failed to convert services to matches")
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, matches)
|
|
}
|
|
|
|
// GetDirectMatches returns direct matches for the organization
|
|
func (h *OrganizationHandler) GetDirectMatches(c *gin.Context) {
|
|
orgID := c.Param("id")
|
|
if orgID == "" {
|
|
h.errorResponse(c, http.StatusBadRequest, "Organization ID is required")
|
|
return
|
|
}
|
|
|
|
ctx := c.Request.Context()
|
|
resourceFlows, err := h.resourceFlowService.GetByOrganizationID(ctx, orgID)
|
|
if err != nil {
|
|
h.errorResponse(c, http.StatusInternalServerError, fmt.Sprintf("Failed to get resource flows: %v", err))
|
|
return
|
|
}
|
|
|
|
providers, consumers, err := h.orgService.FindDirectMatches(ctx, orgID, resourceFlows, 10)
|
|
if err != nil {
|
|
h.errorResponse(c, http.StatusInternalServerError, "Failed to find direct matches")
|
|
return
|
|
}
|
|
|
|
response := service.DirectSymbiosisResponse{
|
|
Providers: providers,
|
|
Consumers: consumers,
|
|
}
|
|
|
|
c.JSON(http.StatusOK, response)
|
|
}
|
|
|
|
// convertItemsToDiscoveryMatches converts products or services to DiscoveryMatch format
|
|
func (h *OrganizationHandler) convertItemsToDiscoveryMatches(ctx context.Context, items interface{}, matchType string) ([]*matching.DiscoveryMatch, error) {
|
|
var matches []*matching.DiscoveryMatch
|
|
|
|
switch matchType {
|
|
case "product":
|
|
products, ok := items.([]*domain.Product)
|
|
if !ok {
|
|
return nil, fmt.Errorf("invalid products type")
|
|
}
|
|
for _, product := range products {
|
|
var org *domain.Organization
|
|
var site *domain.Site
|
|
if product.OrganizationID != "" {
|
|
org, _ = h.orgService.GetByID(ctx, product.OrganizationID)
|
|
}
|
|
// Note: Would need site service/repo access - for now, skip site
|
|
|
|
match := &matching.DiscoveryMatch{
|
|
Product: product,
|
|
MatchType: "product",
|
|
RelevanceScore: 1.0,
|
|
TextMatchScore: 1.0,
|
|
CategoryMatchScore: 1.0,
|
|
DistanceScore: 1.0,
|
|
PriceMatchScore: 1.0,
|
|
AvailabilityScore: 1.0,
|
|
Organization: org,
|
|
Site: site,
|
|
}
|
|
matches = append(matches, match)
|
|
}
|
|
case "service":
|
|
services, ok := items.([]*domain.Service)
|
|
if !ok {
|
|
return nil, fmt.Errorf("invalid services type")
|
|
}
|
|
for _, service := range services {
|
|
var org *domain.Organization
|
|
var site *domain.Site
|
|
if service.OrganizationID != "" {
|
|
org, _ = h.orgService.GetByID(ctx, service.OrganizationID)
|
|
}
|
|
// Note: Would need site service/repo access - for now, skip site
|
|
|
|
match := &matching.DiscoveryMatch{
|
|
Service: service,
|
|
MatchType: "service",
|
|
RelevanceScore: 1.0,
|
|
TextMatchScore: 1.0,
|
|
CategoryMatchScore: 1.0,
|
|
DistanceScore: 1.0,
|
|
PriceMatchScore: 1.0,
|
|
AvailabilityScore: 1.0,
|
|
Organization: org,
|
|
Site: site,
|
|
}
|
|
matches = append(matches, match)
|
|
}
|
|
default:
|
|
return nil, fmt.Errorf("unsupported match type: %s", matchType)
|
|
}
|
|
|
|
return matches, nil
|
|
}
|
|
|
|
// handleImageUpload handles common image upload logic
|
|
func (h *OrganizationHandler) handleImageUpload(c *gin.Context, orgID, formField, folderName string) (*service.UploadedImage, bool) {
|
|
file, header, err := c.Request.FormFile(formField)
|
|
if err != nil {
|
|
h.errorResponse(c, http.StatusBadRequest, "No "+formField+" file provided")
|
|
return nil, false
|
|
}
|
|
defer file.Close()
|
|
|
|
uploadedImage, err := h.imageService.SaveImage(file, header, folderName)
|
|
if err != nil {
|
|
h.errorResponse(c, http.StatusInternalServerError, fmt.Sprintf("Failed to save %s: %v", formField, err))
|
|
return nil, false
|
|
}
|
|
|
|
return uploadedImage, true
|
|
}
|
|
|
|
// GetUserOrganizations returns organizations associated with the current user
|
|
// For now, returns all organizations (can be enhanced with user-organization relationships later)
|
|
func (h *OrganizationHandler) GetUserOrganizations(c *gin.Context) {
|
|
ctx := c.Request.Context()
|
|
|
|
// Get user ID from context (set by middleware)
|
|
_, exists := c.Get("user_id")
|
|
if !exists {
|
|
h.errorResponse(c, http.StatusUnauthorized, "User not authenticated")
|
|
return
|
|
}
|
|
|
|
// TODO: In future, implement user-organization relationship table
|
|
// For now, return all organizations as a temporary solution
|
|
organizations, err := h.orgService.GetAll(ctx)
|
|
if err != nil {
|
|
h.errorResponse(c, http.StatusInternalServerError, fmt.Sprintf("Failed to get organizations: %v", err))
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, organizations)
|
|
}
|