mirror of
https://github.com/SamyRai/turash.git
synced 2025-12-26 23:01:33 +00:00
254 lines
7.4 KiB
Go
254 lines
7.4 KiB
Go
package repository
|
|
|
|
import (
|
|
"bugulma/backend/internal/domain"
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
|
|
"github.com/neo4j/neo4j-go-driver/v5/neo4j"
|
|
)
|
|
|
|
// GraphServiceRepository manages Service nodes in Neo4j
|
|
type GraphServiceRepository struct {
|
|
driver neo4j.DriverWithContext
|
|
database string
|
|
}
|
|
|
|
// NewGraphServiceRepository creates a new graph service repository
|
|
func NewGraphServiceRepository(driver neo4j.DriverWithContext, dbName string) *GraphServiceRepository {
|
|
return &GraphServiceRepository{
|
|
driver: driver,
|
|
database: dbName,
|
|
}
|
|
}
|
|
|
|
// SyncToGraph syncs a service to the graph database
|
|
func (r *GraphServiceRepository) SyncToGraph(ctx context.Context, service *domain.Service) error {
|
|
session := r.driver.NewSession(ctx, neo4j.SessionConfig{
|
|
AccessMode: neo4j.AccessModeWrite,
|
|
DatabaseName: r.database,
|
|
})
|
|
defer session.Close(ctx)
|
|
|
|
// Marshal JSONB fields to JSON strings for Neo4j
|
|
certificationsJSON, _ := json.Marshal(service.Certifications)
|
|
specializationsJSON, _ := json.Marshal(service.Specializations)
|
|
sourcesJSON, _ := json.Marshal(service.Sources)
|
|
tagsJSON, _ := json.Marshal(service.Tags)
|
|
availabilityScheduleJSON, _ := json.Marshal(service.AvailabilitySchedule)
|
|
|
|
_, err := session.ExecuteWrite(ctx, func(tx neo4j.ManagedTransaction) (interface{}, error) {
|
|
cypher := `
|
|
MERGE (s:Service {id: $id})
|
|
SET s.type = $type,
|
|
s.domain = $domain,
|
|
s.description = $description,
|
|
s.on_site = $on_site,
|
|
s.hourly_rate = $hourly_rate,
|
|
s.service_area_km = $service_area_km,
|
|
s.certifications = $certifications,
|
|
s.response_time = $response_time,
|
|
s.warranty = $warranty,
|
|
s.specializations = $specializations,
|
|
s.availability = $availability,
|
|
s.sources = $sources,
|
|
s.search_keywords = $search_keywords,
|
|
s.tags = $tags,
|
|
s.availability_status = $availability_status,
|
|
s.availability_schedule = $availability_schedule,
|
|
s.site_id = $site_id,
|
|
s.created_at = datetime($created_at),
|
|
s.updated_at = datetime($updated_at)
|
|
WITH s
|
|
MATCH (o:Organization {id: $organization_id})
|
|
MERGE (o)-[:OFFERS]->(s)
|
|
WITH s, o
|
|
OPTIONAL MATCH (st:Site {id: $site_id})
|
|
FOREACH (x IN CASE WHEN st IS NOT NULL THEN [1] ELSE [] END |
|
|
MERGE (st)-[:HOSTS]->(s)
|
|
)
|
|
RETURN s.id
|
|
`
|
|
|
|
var siteID interface{}
|
|
if service.SiteID != nil {
|
|
siteID = *service.SiteID
|
|
}
|
|
|
|
var availabilitySchedule interface{}
|
|
if service.AvailabilitySchedule != nil {
|
|
availabilitySchedule = string(availabilityScheduleJSON)
|
|
}
|
|
|
|
params := map[string]interface{}{
|
|
"id": service.ID,
|
|
"type": string(service.Type),
|
|
"domain": service.Domain,
|
|
"description": service.Description,
|
|
"on_site": service.OnSite,
|
|
"hourly_rate": service.HourlyRate,
|
|
"service_area_km": service.ServiceAreaKm,
|
|
"certifications": string(certificationsJSON),
|
|
"response_time": service.ResponseTime,
|
|
"warranty": service.Warranty,
|
|
"specializations": string(specializationsJSON),
|
|
"availability": service.Availability,
|
|
"sources": string(sourcesJSON),
|
|
"search_keywords": service.SearchKeywords,
|
|
"tags": string(tagsJSON),
|
|
"availability_status": service.AvailabilityStatus,
|
|
"availability_schedule": availabilitySchedule,
|
|
"site_id": siteID,
|
|
"created_at": service.CreatedAt.Format("2006-01-02T15:04:05Z"),
|
|
"updated_at": service.UpdatedAt.Format("2006-01-02T15:04:05Z"),
|
|
"organization_id": service.OrganizationID,
|
|
}
|
|
|
|
result, err := tx.Run(ctx, cypher, params)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to sync service to graph: %w", err)
|
|
}
|
|
|
|
if result.Next(ctx) {
|
|
return result.Record().Values[0], nil
|
|
}
|
|
|
|
return nil, fmt.Errorf("no result returned from service sync")
|
|
})
|
|
|
|
return err
|
|
}
|
|
|
|
// DeleteFromGraph removes a service from the graph database
|
|
func (r *GraphServiceRepository) DeleteFromGraph(ctx context.Context, serviceID string) error {
|
|
session := r.driver.NewSession(ctx, neo4j.SessionConfig{
|
|
AccessMode: neo4j.AccessModeWrite,
|
|
DatabaseName: r.database,
|
|
})
|
|
defer session.Close(ctx)
|
|
|
|
_, err := session.ExecuteWrite(ctx, func(tx neo4j.ManagedTransaction) (interface{}, error) {
|
|
cypher := `
|
|
MATCH (s:Service {id: $id})
|
|
DETACH DELETE s
|
|
`
|
|
|
|
_, err := tx.Run(ctx, cypher, map[string]interface{}{
|
|
"id": serviceID,
|
|
})
|
|
return nil, err
|
|
})
|
|
|
|
return err
|
|
}
|
|
|
|
// FindMatchingServices finds services that could match a given need
|
|
func (r *GraphServiceRepository) FindMatchingServices(ctx context.Context, serviceType domain.ServiceType, domainName string, maxHourlyRate float64) ([]map[string]interface{}, error) {
|
|
session := r.driver.NewSession(ctx, neo4j.SessionConfig{
|
|
AccessMode: neo4j.AccessModeRead,
|
|
DatabaseName: r.database,
|
|
})
|
|
defer session.Close(ctx)
|
|
|
|
result, err := session.ExecuteRead(ctx, func(tx neo4j.ManagedTransaction) (interface{}, error) {
|
|
cypher := `
|
|
MATCH (s:Service)
|
|
WHERE s.type = $service_type
|
|
AND s.domain CONTAINS $domain_name
|
|
AND (s.hourly_rate <= $max_hourly_rate OR s.hourly_rate IS NULL)
|
|
RETURN s {
|
|
.*,
|
|
organization: [(s)<-[:OFFERS]-(o:Organization) | o {id: o.id, name: o.name}][0]
|
|
} as service
|
|
ORDER BY s.hourly_rate ASC
|
|
LIMIT 50
|
|
`
|
|
|
|
result, err := tx.Run(ctx, cypher, map[string]interface{}{
|
|
"service_type": string(serviceType),
|
|
"domain_name": domainName,
|
|
"max_hourly_rate": maxHourlyRate,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var services []map[string]interface{}
|
|
for result.Next(ctx) {
|
|
record := result.Record()
|
|
if service, ok := record.Get("service"); ok {
|
|
if serviceMap, ok := service.(map[string]interface{}); ok {
|
|
services = append(services, serviceMap)
|
|
}
|
|
}
|
|
}
|
|
|
|
return services, nil
|
|
})
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if services, ok := result.([]map[string]interface{}); ok {
|
|
return services, nil
|
|
}
|
|
|
|
return []map[string]interface{}{}, nil
|
|
}
|
|
|
|
// FindServiceProviders finds organizations that offer services in a specific domain within a geographic area
|
|
func (r *GraphServiceRepository) FindServiceProviders(ctx context.Context, domainName string, maxDistanceKm float64) ([]map[string]interface{}, error) {
|
|
session := r.driver.NewSession(ctx, neo4j.SessionConfig{
|
|
AccessMode: neo4j.AccessModeRead,
|
|
DatabaseName: r.database,
|
|
})
|
|
defer session.Close(ctx)
|
|
|
|
result, err := session.ExecuteRead(ctx, func(tx neo4j.ManagedTransaction) (interface{}, error) {
|
|
cypher := `
|
|
MATCH (o:Organization)-[:OFFERS]->(s:Service)
|
|
WHERE s.domain CONTAINS $domain_name
|
|
AND s.service_area_km >= $max_distance_km
|
|
RETURN o {
|
|
id: o.id,
|
|
name: o.name,
|
|
services: [(o)-[:OFFERS]->(s) WHERE s.domain CONTAINS $domain_name | s {.*}][..10]
|
|
} as organization
|
|
ORDER BY o.name
|
|
LIMIT 25
|
|
`
|
|
|
|
result, err := tx.Run(ctx, cypher, map[string]interface{}{
|
|
"domain_name": domainName,
|
|
"max_distance_km": maxDistanceKm,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var organizations []map[string]interface{}
|
|
for result.Next(ctx) {
|
|
record := result.Record()
|
|
if org, ok := record.Get("organization"); ok {
|
|
if orgMap, ok := org.(map[string]interface{}); ok {
|
|
organizations = append(organizations, orgMap)
|
|
}
|
|
}
|
|
}
|
|
|
|
return organizations, nil
|
|
})
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if organizations, ok := result.([]map[string]interface{}); ok {
|
|
return organizations, nil
|
|
}
|
|
|
|
return []map[string]interface{}{}, nil
|
|
}
|