mirror of
https://github.com/SamyRai/tercul-backend.git
synced 2025-12-27 04:01:34 +00:00
Merge main into feature-full-text-search: Add SearchAlpha support to hybrid search
- Merged main branch improvements (better search structure with SearchResultItem) - Added SearchAlpha config parameter for hybrid search tuning (default: 0.7) - Updated NewWeaviateWrapper to accept host and searchAlpha parameters - Fixed all type mismatches in mocks and tests - Updated GraphQL resolver to use new SearchResults structure - All tests and vet checks passing
This commit is contained in:
commit
ede3d04e4e
963
PRODUCTION-TASKS.md
Normal file
963
PRODUCTION-TASKS.md
Normal file
@ -0,0 +1,963 @@
|
||||
# Tercul Backend - Production Readiness Tasks
|
||||
|
||||
**Generated:** November 27, 2025
|
||||
**Current Status:** Most core features implemented, needs production hardening
|
||||
|
||||
> **⚠️ MIGRATED TO GITHUB ISSUES**
|
||||
>
|
||||
> All production readiness tasks have been migrated to GitHub Issues for better tracking.
|
||||
> See issues #30-38 in the repository: <https://github.com/SamyRai/backend/issues>
|
||||
>
|
||||
> This document is kept for reference only and should not be used for task tracking.
|
||||
|
||||
---
|
||||
|
||||
## 📊 Current Reality Check
|
||||
|
||||
### ✅ What's Actually Working
|
||||
|
||||
- ✅ Full GraphQL API with 90%+ resolvers implemented
|
||||
- ✅ Complete CQRS pattern (Commands & Queries)
|
||||
- ✅ Auth system (Register, Login, JWT, Password Reset, Email Verification)
|
||||
- ✅ Work CRUD with authorization
|
||||
- ✅ Translation management with analytics
|
||||
- ✅ User management and profiles
|
||||
- ✅ Collections, Comments, Likes, Bookmarks
|
||||
- ✅ Contributions with review workflow
|
||||
- ✅ Analytics service (views, likes, trending)
|
||||
- ✅ Clean Architecture with DDD patterns
|
||||
- ✅ Comprehensive test coverage (passing tests)
|
||||
- ✅ CI/CD pipelines (build, test, lint, security, docker)
|
||||
- ✅ Docker setup and containerization
|
||||
- ✅ Database migrations and schema
|
||||
|
||||
### ⚠️ What Needs Work
|
||||
|
||||
- ⚠️ Search functionality (stub implementation) → **Issue #30**
|
||||
- ⚠️ Observability (metrics, tracing) → **Issues #31, #32, #33**
|
||||
- ⚠️ Production deployment automation → **Issue #36**
|
||||
- ⚠️ Performance optimization → **Issues #34, #35**
|
||||
- ⚠️ Security hardening → **Issue #37**
|
||||
- ⚠️ Infrastructure as Code → **Issue #38**
|
||||
|
||||
---
|
||||
|
||||
## 🎯 EPIC 1: Search & Discovery (HIGH PRIORITY)
|
||||
|
||||
### Story 1.1: Full-Text Search Implementation
|
||||
|
||||
**Priority:** P0 (Critical)
|
||||
**Estimate:** 8 story points (2-3 days)
|
||||
**Labels:** `enhancement`, `search`, `backend`
|
||||
|
||||
**User Story:**
|
||||
|
||||
```
|
||||
As a user exploring literary works,
|
||||
I want to search across works, translations, and authors by keywords,
|
||||
So that I can quickly find relevant content in my preferred language.
|
||||
```
|
||||
|
||||
**Acceptance Criteria:**
|
||||
|
||||
- [ ] Implement Weaviate-based full-text search for works
|
||||
- [ ] Index work titles, content, and metadata
|
||||
- [ ] Support multi-language search (Russian, English, Tatar)
|
||||
- [ ] Search returns relevance-ranked results
|
||||
- [ ] Support filtering by language, category, tags, authors
|
||||
- [ ] Support date range filtering
|
||||
- [ ] Search response time < 200ms for 95th percentile
|
||||
- [ ] Handle special characters and diacritics correctly
|
||||
|
||||
**Technical Tasks:**
|
||||
|
||||
1. Complete `internal/app/search/service.go` implementation
|
||||
2. Implement Weaviate schema for Works, Translations, Authors
|
||||
3. Create background indexing job for existing content
|
||||
4. Add incremental indexing on create/update operations
|
||||
5. Implement search query parsing and normalization
|
||||
6. Add search result pagination and sorting
|
||||
7. Create integration tests for search functionality
|
||||
8. Add search metrics and monitoring
|
||||
|
||||
**Dependencies:**
|
||||
|
||||
- Weaviate instance running (already in docker-compose)
|
||||
- `internal/platform/search` client (exists)
|
||||
- `internal/domain/search` interfaces (exists)
|
||||
|
||||
**Definition of Done:**
|
||||
|
||||
- All acceptance criteria met
|
||||
- Unit tests passing (>80% coverage)
|
||||
- Integration tests with real Weaviate instance
|
||||
- Performance benchmarks documented
|
||||
- Search analytics tracked
|
||||
|
||||
---
|
||||
|
||||
### Story 1.2: Advanced Search Filters
|
||||
|
||||
**Priority:** P1 (High)
|
||||
**Estimate:** 5 story points (1-2 days)
|
||||
**Labels:** `enhancement`, `search`, `backend`
|
||||
|
||||
**User Story:**
|
||||
|
||||
```
|
||||
As a researcher or literary enthusiast,
|
||||
I want to filter search results by multiple criteria simultaneously,
|
||||
So that I can narrow down to exactly the works I'm interested in.
|
||||
```
|
||||
|
||||
**Acceptance Criteria:**
|
||||
|
||||
- [ ] Filter by literature type (poetry, prose, drama)
|
||||
- [ ] Filter by time period (creation date ranges)
|
||||
- [ ] Filter by multiple authors simultaneously
|
||||
- [ ] Filter by genre/categories
|
||||
- [ ] Filter by language availability
|
||||
- [ ] Combine filters with AND/OR logic
|
||||
- [ ] Save search filters as presets (future)
|
||||
|
||||
**Technical Tasks:**
|
||||
|
||||
1. Extend `SearchFilters` domain model
|
||||
2. Implement filter translation to Weaviate queries
|
||||
3. Add faceted search capabilities
|
||||
4. Implement filter validation
|
||||
5. Add filter combination logic
|
||||
6. Create filter preset storage (optional)
|
||||
7. Add tests for all filter combinations
|
||||
|
||||
---
|
||||
|
||||
## 🎯 EPIC 2: API Documentation (HIGH PRIORITY)
|
||||
|
||||
### Story 2.1: Comprehensive GraphQL API Documentation
|
||||
|
||||
**Priority:** P1 (High)
|
||||
**Estimate:** 5 story points (1-2 days)
|
||||
**Labels:** `documentation`, `api`, `devex`
|
||||
|
||||
**User Story:**
|
||||
|
||||
```
|
||||
As a frontend developer or API consumer,
|
||||
I want complete documentation for all GraphQL queries and mutations,
|
||||
So that I can integrate with the API without constantly asking questions.
|
||||
```
|
||||
|
||||
**Acceptance Criteria:**
|
||||
|
||||
- [ ] Document all 80+ GraphQL resolvers
|
||||
- [ ] Include example queries for each operation
|
||||
- [ ] Document input types and validation rules
|
||||
- [ ] Provide error response examples
|
||||
- [ ] Document authentication requirements
|
||||
- [ ] Include rate limiting information
|
||||
- [ ] Add GraphQL Playground with example queries
|
||||
- [ ] Auto-generate docs from schema annotations
|
||||
|
||||
**Technical Tasks:**
|
||||
|
||||
1. Add descriptions to all GraphQL types in schema
|
||||
2. Document each query/mutation with examples
|
||||
3. Create `api/README.md` with comprehensive guide
|
||||
4. Add inline schema documentation
|
||||
5. Set up GraphQL Voyager for schema visualization
|
||||
6. Create API changelog
|
||||
7. Add versioning documentation
|
||||
8. Generate OpenAPI spec for REST endpoints (if any)
|
||||
|
||||
**Deliverables:**
|
||||
|
||||
- `api/README.md` - Complete API guide
|
||||
- `api/EXAMPLES.md` - Query examples
|
||||
- `api/CHANGELOG.md` - API version history
|
||||
- Enhanced GraphQL schema with descriptions
|
||||
- Interactive API explorer
|
||||
|
||||
---
|
||||
|
||||
### Story 2.2: Developer Onboarding Documentation
|
||||
|
||||
**Priority:** P1 (High)
|
||||
**Estimate:** 3 story points (1 day)
|
||||
**Labels:** `documentation`, `devex`
|
||||
|
||||
**User Story:**
|
||||
|
||||
```
|
||||
As a new developer joining the project,
|
||||
I want clear setup instructions and architecture documentation,
|
||||
So that I can become productive quickly without extensive hand-holding.
|
||||
```
|
||||
|
||||
**Acceptance Criteria:**
|
||||
|
||||
- [ ] Updated `README.md` with quick start guide
|
||||
- [ ] Architecture diagrams and explanations
|
||||
- [ ] Development workflow documentation
|
||||
- [ ] Testing strategy documentation
|
||||
- [ ] Contribution guidelines
|
||||
- [ ] Code style guide
|
||||
- [ ] Troubleshooting common issues
|
||||
|
||||
**Technical Tasks:**
|
||||
|
||||
1. Update root `README.md` with modern structure
|
||||
2. Create `docs/ARCHITECTURE.md` with diagrams
|
||||
3. Document CQRS and DDD patterns used
|
||||
4. Create `docs/DEVELOPMENT.md` workflow guide
|
||||
5. Document testing strategy in `docs/TESTING.md`
|
||||
6. Create `CONTRIBUTING.md` guide
|
||||
7. Add package-level `README.md` for complex packages
|
||||
|
||||
**Deliverables:**
|
||||
|
||||
- Refreshed `README.md`
|
||||
- `docs/ARCHITECTURE.md`
|
||||
- `docs/DEVELOPMENT.md`
|
||||
- `docs/TESTING.md`
|
||||
- `CONTRIBUTING.md`
|
||||
|
||||
---
|
||||
|
||||
## 🎯 EPIC 3: Observability & Monitoring (CRITICAL FOR PRODUCTION)
|
||||
|
||||
### Story 3.1: Distributed Tracing with OpenTelemetry
|
||||
|
||||
**Priority:** P0 (Critical)
|
||||
**Estimate:** 8 story points (2-3 days)
|
||||
**Labels:** `observability`, `monitoring`, `infrastructure`
|
||||
|
||||
**User Story:**
|
||||
|
||||
```
|
||||
As a DevOps engineer monitoring production,
|
||||
I want distributed tracing across all services and database calls,
|
||||
So that I can quickly identify performance bottlenecks and errors.
|
||||
```
|
||||
|
||||
**Acceptance Criteria:**
|
||||
|
||||
- [ ] OpenTelemetry SDK integrated
|
||||
- [ ] Automatic trace context propagation
|
||||
- [ ] All HTTP handlers instrumented
|
||||
- [ ] All database queries traced
|
||||
- [ ] All GraphQL resolvers traced
|
||||
- [ ] Custom spans for business logic
|
||||
- [ ] Traces exported to OTLP collector
|
||||
- [ ] Integration with Jaeger/Tempo
|
||||
|
||||
**Technical Tasks:**
|
||||
|
||||
1. Add OpenTelemetry Go SDK dependencies
|
||||
2. Create `internal/observability/tracing` package
|
||||
3. Instrument HTTP middleware with auto-tracing
|
||||
4. Add database query tracing via GORM callbacks
|
||||
5. Instrument GraphQL execution
|
||||
6. Add custom spans for slow operations
|
||||
7. Set up trace sampling strategy
|
||||
8. Configure OTLP exporter
|
||||
9. Add Jaeger to docker-compose for local dev
|
||||
10. Document tracing best practices
|
||||
|
||||
**Configuration:**
|
||||
|
||||
```go
|
||||
// Example trace configuration
|
||||
type TracingConfig struct {
|
||||
Enabled bool
|
||||
ServiceName string
|
||||
SamplingRate float64
|
||||
OTLPEndpoint string
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Story 3.2: Prometheus Metrics & Alerting
|
||||
|
||||
**Priority:** P0 (Critical)
|
||||
**Estimate:** 5 story points (1-2 days)
|
||||
**Labels:** `observability`, `monitoring`, `metrics`
|
||||
|
||||
**User Story:**
|
||||
|
||||
```
|
||||
As a site reliability engineer,
|
||||
I want detailed metrics on API performance and system health,
|
||||
So that I can detect issues before they impact users.
|
||||
```
|
||||
|
||||
**Acceptance Criteria:**
|
||||
|
||||
- [ ] HTTP request metrics (latency, status codes, throughput)
|
||||
- [ ] Database query metrics (query time, connection pool)
|
||||
- [ ] Business metrics (works created, searches performed)
|
||||
- [ ] System metrics (memory, CPU, goroutines)
|
||||
- [ ] GraphQL-specific metrics (resolver performance)
|
||||
- [ ] Metrics exposed on `/metrics` endpoint
|
||||
- [ ] Prometheus scraping configured
|
||||
- [ ] Grafana dashboards created
|
||||
|
||||
**Technical Tasks:**
|
||||
|
||||
1. Enhance existing Prometheus middleware
|
||||
2. Add HTTP handler metrics (already partially done)
|
||||
3. Add database query duration histograms
|
||||
4. Create business metric counters
|
||||
5. Add GraphQL resolver metrics
|
||||
6. Create custom metrics for critical paths
|
||||
7. Set up metric labels strategy
|
||||
8. Create Grafana dashboard JSON
|
||||
9. Define SLOs and SLIs
|
||||
10. Create alerting rules YAML
|
||||
|
||||
**Key Metrics:**
|
||||
|
||||
```
|
||||
# HTTP Metrics
|
||||
http_requests_total{method, path, status}
|
||||
http_request_duration_seconds{method, path}
|
||||
|
||||
# Database Metrics
|
||||
db_query_duration_seconds{query}
|
||||
db_connections_current
|
||||
db_connections_max
|
||||
|
||||
# Business Metrics
|
||||
works_created_total{language}
|
||||
searches_performed_total{type}
|
||||
user_registrations_total
|
||||
|
||||
# GraphQL Metrics
|
||||
graphql_resolver_duration_seconds{operation, resolver}
|
||||
graphql_errors_total{operation, error_type}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Story 3.3: Structured Logging Enhancements
|
||||
|
||||
**Priority:** P1 (High)
|
||||
**Estimate:** 3 story points (1 day)
|
||||
**Labels:** `observability`, `logging`
|
||||
|
||||
**User Story:**
|
||||
|
||||
```
|
||||
As a developer debugging production issues,
|
||||
I want rich, structured logs with request context,
|
||||
So that I can quickly trace requests and identify root causes.
|
||||
```
|
||||
|
||||
**Acceptance Criteria:**
|
||||
|
||||
- [ ] Request ID in all logs
|
||||
- [ ] User ID in authenticated request logs
|
||||
- [ ] Trace ID/Span ID in all logs
|
||||
- [ ] Consistent log levels across codebase
|
||||
- [ ] Sensitive data excluded from logs
|
||||
- [ ] Structured fields for easy parsing
|
||||
- [ ] Log sampling for high-volume endpoints
|
||||
|
||||
**Technical Tasks:**
|
||||
|
||||
1. Enhance HTTP middleware to inject request ID
|
||||
2. Add user ID to context from JWT
|
||||
3. Add trace/span IDs to logger context
|
||||
4. Audit all logging statements for consistency
|
||||
5. Add field name constants for structured logging
|
||||
6. Implement log redaction for passwords/tokens
|
||||
7. Add log sampling configuration
|
||||
8. Create log aggregation guide (ELK/Loki)
|
||||
|
||||
**Log Format Example:**
|
||||
|
||||
```json
|
||||
{
|
||||
"level": "info",
|
||||
"ts": "2025-11-27T10:30:45.123Z",
|
||||
"msg": "Work created successfully",
|
||||
"request_id": "req_abc123",
|
||||
"user_id": "user_456",
|
||||
"trace_id": "trace_xyz789",
|
||||
"span_id": "span_def321",
|
||||
"work_id": 789,
|
||||
"language": "en",
|
||||
"duration_ms": 45
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 EPIC 4: Performance Optimization (MEDIUM PRIORITY)
|
||||
|
||||
### Story 4.1: Read Models (DTOs) for Efficient Queries
|
||||
|
||||
**Priority:** P1 (High)
|
||||
**Estimate:** 8 story points (2-3 days)
|
||||
**Labels:** `performance`, `architecture`, `refactoring`
|
||||
|
||||
**User Story:**
|
||||
|
||||
```
|
||||
As an API consumer,
|
||||
I want fast query responses with only the data I need,
|
||||
So that my application loads quickly and uses less bandwidth.
|
||||
```
|
||||
|
||||
**Acceptance Criteria:**
|
||||
|
||||
- [ ] Create DTOs for all list queries
|
||||
- [ ] DTOs include only fields needed by API
|
||||
- [ ] Avoid N+1 queries with proper joins
|
||||
- [ ] Reduce payload size by 30-50%
|
||||
- [ ] Query response time improved by 20%
|
||||
- [ ] No breaking changes to GraphQL schema
|
||||
|
||||
**Technical Tasks:**
|
||||
|
||||
1. Create `internal/app/work/dto` package
|
||||
2. Define WorkListDTO, WorkDetailDTO
|
||||
3. Create TranslationListDTO, TranslationDetailDTO
|
||||
4. Define AuthorListDTO, AuthorDetailDTO
|
||||
5. Implement optimized SQL queries for DTOs
|
||||
6. Update query services to return DTOs
|
||||
7. Update GraphQL resolvers to map DTOs
|
||||
8. Add benchmarks comparing old vs new
|
||||
9. Update tests to use DTOs
|
||||
10. Document DTO usage patterns
|
||||
|
||||
**Example DTO:**
|
||||
|
||||
```go
|
||||
// WorkListDTO - Optimized for list views
|
||||
type WorkListDTO struct {
|
||||
ID uint
|
||||
Title string
|
||||
AuthorName string
|
||||
AuthorID uint
|
||||
Language string
|
||||
CreatedAt time.Time
|
||||
ViewCount int
|
||||
LikeCount int
|
||||
TranslationCount int
|
||||
}
|
||||
|
||||
// WorkDetailDTO - Full information for single work
|
||||
type WorkDetailDTO struct {
|
||||
*WorkListDTO
|
||||
Content string
|
||||
Description string
|
||||
Tags []string
|
||||
Categories []string
|
||||
Translations []TranslationSummaryDTO
|
||||
Author AuthorSummaryDTO
|
||||
Analytics WorkAnalyticsDTO
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Story 4.2: Redis Caching Strategy
|
||||
|
||||
**Priority:** P1 (High)
|
||||
**Estimate:** 5 story points (1-2 days)
|
||||
**Labels:** `performance`, `caching`, `infrastructure`
|
||||
|
||||
**User Story:**
|
||||
|
||||
```
|
||||
As a user browsing popular works,
|
||||
I want instant page loads for frequently accessed content,
|
||||
So that I have a smooth, responsive experience.
|
||||
```
|
||||
|
||||
**Acceptance Criteria:**
|
||||
|
||||
- [ ] Cache hot works (top 100 viewed)
|
||||
- [ ] Cache author profiles
|
||||
- [ ] Cache search results (5 min TTL)
|
||||
- [ ] Cache translations by work ID
|
||||
- [ ] Automatic cache invalidation on updates
|
||||
- [ ] Cache hit rate > 70% for reads
|
||||
- [ ] Cache warming for popular content
|
||||
- [ ] Redis failover doesn't break app
|
||||
|
||||
**Technical Tasks:**
|
||||
|
||||
1. Refactor `internal/data/cache` with decorator pattern
|
||||
2. Create `CachedWorkRepository` decorator
|
||||
3. Implement cache-aside pattern
|
||||
4. Add cache key versioning strategy
|
||||
5. Implement selective cache invalidation
|
||||
6. Add cache metrics (hit/miss rates)
|
||||
7. Create cache warming job
|
||||
8. Handle cache failures gracefully
|
||||
9. Document caching strategy
|
||||
10. Add cache configuration
|
||||
|
||||
**Cache Key Strategy:**
|
||||
|
||||
```
|
||||
work:{version}:{id}
|
||||
author:{version}:{id}
|
||||
translation:{version}:{work_id}:{lang}
|
||||
search:{version}:{query_hash}
|
||||
trending:{period}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Story 4.3: Database Query Optimization
|
||||
|
||||
**Priority:** P2 (Medium)
|
||||
**Estimate:** 5 story points (1-2 days)
|
||||
**Labels:** `performance`, `database`
|
||||
|
||||
**User Story:**
|
||||
|
||||
```
|
||||
As a user with slow internet,
|
||||
I want database operations to complete quickly,
|
||||
So that I don't experience frustrating delays.
|
||||
```
|
||||
|
||||
**Acceptance Criteria:**
|
||||
|
||||
- [ ] All queries use proper indexes
|
||||
- [ ] No N+1 query problems
|
||||
- [ ] Eager loading for related entities
|
||||
- [ ] Query time < 50ms for 95th percentile
|
||||
- [ ] Connection pool properly sized
|
||||
- [ ] Slow query logging enabled
|
||||
- [ ] Query explain plans documented
|
||||
|
||||
**Technical Tasks:**
|
||||
|
||||
1. Audit all repository queries
|
||||
2. Add missing database indexes
|
||||
3. Implement eager loading with GORM Preload
|
||||
4. Fix N+1 queries in GraphQL resolvers
|
||||
5. Optimize joins and subqueries
|
||||
6. Add query timeouts
|
||||
7. Configure connection pool settings
|
||||
8. Enable PostgreSQL slow query log
|
||||
9. Create query performance dashboard
|
||||
10. Document query optimization patterns
|
||||
|
||||
---
|
||||
|
||||
## 🎯 EPIC 5: Deployment & DevOps (CRITICAL FOR PRODUCTION)
|
||||
|
||||
### Story 5.1: Production Deployment Automation
|
||||
|
||||
**Priority:** P0 (Critical)
|
||||
**Estimate:** 8 story points (2-3 days)
|
||||
**Labels:** `devops`, `deployment`, `infrastructure`
|
||||
|
||||
**User Story:**
|
||||
|
||||
```
|
||||
As a DevOps engineer,
|
||||
I want automated, zero-downtime deployments to production,
|
||||
So that we can ship features safely and frequently.
|
||||
```
|
||||
|
||||
**Acceptance Criteria:**
|
||||
|
||||
- [ ] Automated deployment on tag push
|
||||
- [ ] Blue-green or rolling deployment strategy
|
||||
- [ ] Health checks before traffic routing
|
||||
- [ ] Automatic rollback on failures
|
||||
- [ ] Database migrations run automatically
|
||||
- [ ] Smoke tests after deployment
|
||||
- [ ] Deployment notifications (Slack/Discord)
|
||||
- [ ] Deployment dashboard
|
||||
|
||||
**Technical Tasks:**
|
||||
|
||||
1. Complete `.github/workflows/deploy.yml` implementation
|
||||
2. Set up staging environment
|
||||
3. Implement blue-green deployment strategy
|
||||
4. Add health check endpoints (`/health`, `/ready`)
|
||||
5. Create database migration runner
|
||||
6. Add pre-deployment smoke tests
|
||||
7. Configure load balancer for zero-downtime
|
||||
8. Set up deployment notifications
|
||||
9. Create rollback procedures
|
||||
10. Document deployment process
|
||||
|
||||
**Health Check Endpoints:**
|
||||
|
||||
```go
|
||||
GET /health -> {"status": "ok", "version": "1.2.3"}
|
||||
GET /ready -> {"ready": true, "db": "ok", "redis": "ok"}
|
||||
GET /metrics -> Prometheus metrics
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Story 5.2: Infrastructure as Code (Kubernetes)
|
||||
|
||||
**Priority:** P1 (High)
|
||||
**Estimate:** 8 story points (2-3 days)
|
||||
**Labels:** `devops`, `infrastructure`, `k8s`
|
||||
|
||||
**User Story:**
|
||||
|
||||
```
|
||||
As a platform engineer,
|
||||
I want all infrastructure defined as code,
|
||||
So that environments are reproducible and version-controlled.
|
||||
```
|
||||
|
||||
**Acceptance Criteria:**
|
||||
|
||||
- [ ] Kubernetes manifests for all services
|
||||
- [ ] Helm charts for easy deployment
|
||||
- [ ] ConfigMaps for configuration
|
||||
- [ ] Secrets management with sealed secrets
|
||||
- [ ] Horizontal Pod Autoscaling configured
|
||||
- [ ] Ingress with TLS termination
|
||||
- [ ] Persistent volumes for PostgreSQL/Redis
|
||||
- [ ] Network policies for security
|
||||
|
||||
**Technical Tasks:**
|
||||
|
||||
1. Enhance `deploy/k8s` manifests
|
||||
2. Create Deployment YAML for backend
|
||||
3. Create Service and Ingress YAMLs
|
||||
4. Create ConfigMap for app configuration
|
||||
5. Set up Sealed Secrets for sensitive data
|
||||
6. Create HorizontalPodAutoscaler
|
||||
7. Add resource limits and requests
|
||||
8. Create StatefulSets for databases
|
||||
9. Set up persistent volume claims
|
||||
10. Create Helm chart structure
|
||||
11. Document Kubernetes deployment
|
||||
|
||||
**File Structure:**
|
||||
|
||||
```
|
||||
deploy/k8s/
|
||||
├── base/
|
||||
│ ├── deployment.yaml
|
||||
│ ├── service.yaml
|
||||
│ ├── ingress.yaml
|
||||
│ ├── configmap.yaml
|
||||
│ └── hpa.yaml
|
||||
├── overlays/
|
||||
│ ├── staging/
|
||||
│ └── production/
|
||||
└── helm/
|
||||
└── tercul-backend/
|
||||
├── Chart.yaml
|
||||
├── values.yaml
|
||||
└── templates/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Story 5.3: Disaster Recovery & Backups
|
||||
|
||||
**Priority:** P1 (High)
|
||||
**Estimate:** 5 story points (1-2 days)
|
||||
**Labels:** `devops`, `backup`, `disaster-recovery`
|
||||
|
||||
**User Story:**
|
||||
|
||||
```
|
||||
As a business owner,
|
||||
I want automated backups and disaster recovery procedures,
|
||||
So that we never lose user data or have extended outages.
|
||||
```
|
||||
|
||||
**Acceptance Criteria:**
|
||||
|
||||
- [ ] Daily PostgreSQL backups
|
||||
- [ ] Point-in-time recovery capability
|
||||
- [ ] Backup retention policy (30 days)
|
||||
- [ ] Backup restoration tested monthly
|
||||
- [ ] Backup encryption at rest
|
||||
- [ ] Off-site backup storage
|
||||
- [ ] Disaster recovery runbook
|
||||
- [ ] RTO < 1 hour, RPO < 15 minutes
|
||||
|
||||
**Technical Tasks:**
|
||||
|
||||
1. Set up automated database backups
|
||||
2. Configure WAL archiving for PostgreSQL
|
||||
3. Implement backup retention policy
|
||||
4. Store backups in S3/GCS with encryption
|
||||
5. Create backup restoration script
|
||||
6. Test restoration procedure
|
||||
7. Create disaster recovery runbook
|
||||
8. Set up backup monitoring and alerts
|
||||
9. Document backup procedures
|
||||
10. Schedule regular DR drills
|
||||
|
||||
---
|
||||
|
||||
## 🎯 EPIC 6: Security Hardening (HIGH PRIORITY)
|
||||
|
||||
### Story 6.1: Security Audit & Vulnerability Scanning
|
||||
|
||||
**Priority:** P0 (Critical)
|
||||
**Estimate:** 5 story points (1-2 days)
|
||||
**Labels:** `security`, `compliance`
|
||||
|
||||
**User Story:**
|
||||
|
||||
```
|
||||
As a security officer,
|
||||
I want continuous vulnerability scanning and security best practices,
|
||||
So that user data and the platform remain secure.
|
||||
```
|
||||
|
||||
**Acceptance Criteria:**
|
||||
|
||||
- [ ] Dependency scanning with Dependabot (already active)
|
||||
- [ ] SAST scanning with CodeQL
|
||||
- [ ] Container scanning with Trivy
|
||||
- [ ] No high/critical vulnerabilities
|
||||
- [ ] Security headers configured
|
||||
- [ ] Rate limiting on all endpoints
|
||||
- [ ] Input validation on all mutations
|
||||
- [ ] SQL injection prevention verified
|
||||
|
||||
**Technical Tasks:**
|
||||
|
||||
1. Review existing security workflows (already good!)
|
||||
2. Add rate limiting middleware
|
||||
3. Implement input validation with go-playground/validator
|
||||
4. Add security headers middleware
|
||||
5. Audit SQL queries for injection risks
|
||||
6. Review JWT implementation for best practices
|
||||
7. Add CSRF protection for mutations
|
||||
8. Implement request signing for sensitive operations
|
||||
9. Create security incident response plan
|
||||
10. Document security practices
|
||||
|
||||
**Security Headers:**
|
||||
|
||||
```
|
||||
X-Frame-Options: DENY
|
||||
X-Content-Type-Options: nosniff
|
||||
X-XSS-Protection: 1; mode=block
|
||||
Strict-Transport-Security: max-age=31536000
|
||||
Content-Security-Policy: default-src 'self'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Story 6.2: API Rate Limiting & Throttling
|
||||
|
||||
**Priority:** P1 (High)
|
||||
**Estimate:** 3 story points (1 day)
|
||||
**Labels:** `security`, `performance`, `api`
|
||||
|
||||
**User Story:**
|
||||
|
||||
```
|
||||
As a platform operator,
|
||||
I want rate limiting to prevent abuse and ensure fair usage,
|
||||
So that all users have a good experience and our infrastructure isn't overwhelmed.
|
||||
```
|
||||
|
||||
**Acceptance Criteria:**
|
||||
|
||||
- [ ] Rate limiting per user (authenticated)
|
||||
- [ ] Rate limiting per IP (anonymous)
|
||||
- [ ] Different limits for different operations
|
||||
- [ ] 429 status code with retry-after header
|
||||
- [ ] Rate limit info in response headers
|
||||
- [ ] Configurable rate limits
|
||||
- [ ] Redis-based distributed rate limiting
|
||||
- [ ] Rate limit metrics and monitoring
|
||||
|
||||
**Technical Tasks:**
|
||||
|
||||
1. Implement rate limiting middleware
|
||||
2. Use redis for distributed rate limiting
|
||||
3. Configure different limits for read/write
|
||||
4. Add rate limit headers to responses
|
||||
5. Create rate limit exceeded error handling
|
||||
6. Add rate limit bypass for admins
|
||||
7. Monitor rate limit usage
|
||||
8. Document rate limits in API docs
|
||||
9. Add tests for rate limiting
|
||||
10. Create rate limit dashboard
|
||||
|
||||
**Rate Limits:**
|
||||
|
||||
```
|
||||
Authenticated Users:
|
||||
- 1000 requests/hour (general)
|
||||
- 100 writes/hour (mutations)
|
||||
- 10 searches/minute
|
||||
|
||||
Anonymous Users:
|
||||
- 100 requests/hour
|
||||
- 10 writes/hour
|
||||
- 5 searches/minute
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 EPIC 7: Developer Experience (MEDIUM PRIORITY)
|
||||
|
||||
### Story 7.1: Local Development Environment Improvements
|
||||
|
||||
**Priority:** P2 (Medium)
|
||||
**Estimate:** 3 story points (1 day)
|
||||
**Labels:** `devex`, `tooling`
|
||||
|
||||
**User Story:**
|
||||
|
||||
```
|
||||
As a developer,
|
||||
I want a fast, reliable local development environment,
|
||||
So that I can iterate quickly without friction.
|
||||
```
|
||||
|
||||
**Acceptance Criteria:**
|
||||
|
||||
- [ ] One-command setup (`make setup`)
|
||||
- [ ] Hot reload for Go code changes
|
||||
- [ ] Database seeding with realistic data
|
||||
- [ ] GraphQL Playground pre-configured
|
||||
- [ ] All services start reliably
|
||||
- [ ] Clear error messages when setup fails
|
||||
- [ ] Development docs up-to-date
|
||||
|
||||
**Technical Tasks:**
|
||||
|
||||
1. Create comprehensive `make setup` target
|
||||
2. Add air for hot reload in docker-compose
|
||||
3. Create database seeding script
|
||||
4. Add sample data fixtures
|
||||
5. Pre-configure GraphQL Playground
|
||||
6. Add health check script
|
||||
7. Improve error messages in Makefile
|
||||
8. Document common setup issues
|
||||
9. Create troubleshooting guide
|
||||
10. Add setup validation script
|
||||
|
||||
---
|
||||
|
||||
### Story 7.2: Testing Infrastructure Improvements
|
||||
|
||||
**Priority:** P2 (Medium)
|
||||
**Estimate:** 5 story points (1-2 days)
|
||||
**Labels:** `testing`, `devex`
|
||||
|
||||
**User Story:**
|
||||
|
||||
```
|
||||
As a developer writing tests,
|
||||
I want fast, reliable test execution without external dependencies,
|
||||
So that I can practice TDD effectively.
|
||||
```
|
||||
|
||||
**Acceptance Criteria:**
|
||||
|
||||
- [ ] Unit tests run in <5 seconds
|
||||
- [ ] Integration tests isolated with test containers
|
||||
- [ ] Parallel test execution
|
||||
- [ ] Test coverage reports
|
||||
- [ ] Fixtures for common test scenarios
|
||||
- [ ] Clear test failure messages
|
||||
- [ ] Easy to run single test or package
|
||||
|
||||
**Technical Tasks:**
|
||||
|
||||
1. Refactor `internal/testutil` for better isolation
|
||||
2. Implement test containers for integration tests
|
||||
3. Add parallel test execution
|
||||
4. Create reusable test fixtures
|
||||
5. Set up coverage reporting
|
||||
6. Add golden file testing utilities
|
||||
7. Create test data builders
|
||||
8. Improve test naming conventions
|
||||
9. Document testing best practices
|
||||
10. Add `make test-fast` and `make test-all`
|
||||
|
||||
---
|
||||
|
||||
## 📋 Task Summary & Prioritization
|
||||
|
||||
### Sprint 1 (Week 1): Critical Production Readiness
|
||||
|
||||
1. **Search Implementation** (Story 1.1) - 8 pts
|
||||
2. **Distributed Tracing** (Story 3.1) - 8 pts
|
||||
3. **Prometheus Metrics** (Story 3.2) - 5 pts
|
||||
4. **Total:** 21 points
|
||||
|
||||
### Sprint 2 (Week 2): Performance & Documentation
|
||||
|
||||
1. **API Documentation** (Story 2.1) - 5 pts
|
||||
2. **Read Models/DTOs** (Story 4.1) - 8 pts
|
||||
3. **Redis Caching** (Story 4.2) - 5 pts
|
||||
4. **Structured Logging** (Story 3.3) - 3 pts
|
||||
5. **Total:** 21 points
|
||||
|
||||
### Sprint 3 (Week 3): Deployment & Security
|
||||
|
||||
1. **Production Deployment** (Story 5.1) - 8 pts
|
||||
2. **Security Audit** (Story 6.1) - 5 pts
|
||||
3. **Rate Limiting** (Story 6.2) - 3 pts
|
||||
4. **Developer Docs** (Story 2.2) - 3 pts
|
||||
5. **Total:** 19 points
|
||||
|
||||
### Sprint 4 (Week 4): Infrastructure & Polish
|
||||
|
||||
1. **Kubernetes IaC** (Story 5.2) - 8 pts
|
||||
2. **Disaster Recovery** (Story 5.3) - 5 pts
|
||||
3. **Advanced Search Filters** (Story 1.2) - 5 pts
|
||||
4. **Total:** 18 points
|
||||
|
||||
### Sprint 5 (Week 5): Optimization & DevEx
|
||||
|
||||
1. **Database Optimization** (Story 4.3) - 5 pts
|
||||
2. **Local Dev Environment** (Story 7.1) - 3 pts
|
||||
3. **Testing Infrastructure** (Story 7.2) - 5 pts
|
||||
4. **Total:** 13 points
|
||||
|
||||
## 🎯 Success Metrics
|
||||
|
||||
### Performance SLOs
|
||||
|
||||
- API response time p95 < 200ms
|
||||
- Search response time p95 < 300ms
|
||||
- Database query time p95 < 50ms
|
||||
- Cache hit rate > 70%
|
||||
|
||||
### Reliability SLOs
|
||||
|
||||
- Uptime > 99.9% (< 8.7 hours downtime/year)
|
||||
- Error rate < 0.1%
|
||||
- Mean Time To Recovery < 1 hour
|
||||
- Zero data loss
|
||||
|
||||
### Developer Experience
|
||||
|
||||
- Setup time < 15 minutes
|
||||
- Test suite runs < 2 minutes
|
||||
- Build time < 1 minute
|
||||
- Documentation completeness > 90%
|
||||
|
||||
---
|
||||
|
||||
**Next Steps:**
|
||||
|
||||
1. Review and prioritize these tasks with the team
|
||||
2. Create GitHub issues for Sprint 1 tasks
|
||||
3. Add tasks to project board
|
||||
4. Begin implementation starting with search and observability
|
||||
|
||||
**This is a realistic, achievable roadmap based on the ACTUAL current state of the codebase!** 🚀
|
||||
@ -70,9 +70,6 @@ func main() {
|
||||
log.Fatalf("cannot load config: %v", err)
|
||||
}
|
||||
|
||||
// Initialize Bleve search
|
||||
search.InitBleve()
|
||||
|
||||
// Initialize logger
|
||||
app_log.Init("tercul-api", cfg.Environment)
|
||||
obsLogger := observability.NewLogger("tercul-api", cfg.Environment)
|
||||
|
||||
@ -26,18 +26,21 @@ tercul-go/
|
||||
## 🏗️ Architecture Highlights
|
||||
|
||||
### 1. **Clean Architecture**
|
||||
|
||||
- **Domain Layer**: Pure business entities with validation logic
|
||||
- **Application Layer**: Use cases and business logic (to be implemented)
|
||||
- **Infrastructure Layer**: Database, storage, external services (to be implemented)
|
||||
- **Presentation Layer**: HTTP API, GraphQL, admin interface (to be implemented)
|
||||
|
||||
### 2. **Database Design**
|
||||
|
||||
- **PostgreSQL 16+**: Modern, performant database with advanced features
|
||||
- **Improved Schema**: Fixed all identified data quality issues
|
||||
- **Performance Indexes**: Full-text search, trigram matching, JSONB indexes
|
||||
- **Data Integrity**: Proper foreign keys, constraints, and triggers
|
||||
|
||||
### 3. **Technology Stack**
|
||||
|
||||
- **Go 1.24+**: Latest stable version with modern features
|
||||
- **GORM v3**: Type-safe ORM with PostgreSQL support
|
||||
- **Chi Router**: Lightweight, fast HTTP router
|
||||
@ -47,6 +50,7 @@ tercul-go/
|
||||
## 🔧 Data Quality Issues Addressed
|
||||
|
||||
### **Schema Improvements**
|
||||
|
||||
1. **Timestamp Formats**: Proper DATE and TIMESTAMP types
|
||||
2. **UUID Handling**: Consistent UUID generation and validation
|
||||
3. **Content Cleaning**: Structured JSONB for complex data
|
||||
@ -54,6 +58,7 @@ tercul-go/
|
||||
5. **Data Types**: Proper ENUMs for categorical data
|
||||
|
||||
### **Data Migration Strategy**
|
||||
|
||||
- **Phased Approach**: Countries → Authors → Works → Media → Copyrights
|
||||
- **Data Validation**: Comprehensive validation during migration
|
||||
- **Error Handling**: Graceful handling of malformed data
|
||||
@ -62,18 +67,21 @@ tercul-go/
|
||||
## 🚀 Key Features Implemented
|
||||
|
||||
### 1. **Domain Models**
|
||||
|
||||
- **Author Entity**: Core author information with validation
|
||||
- **AuthorTranslation**: Multi-language author details
|
||||
- **Error Handling**: Comprehensive domain-specific errors
|
||||
- **Business Logic**: Age calculation, validation rules
|
||||
|
||||
### 2. **Development Environment**
|
||||
|
||||
- **Docker Compose**: PostgreSQL, Redis, Adminer, Redis Commander
|
||||
- **Hot Reloading**: Go development with volume mounting
|
||||
- **Database Management**: Easy database reset, backup, restore
|
||||
- **Monitoring**: Health checks and service status
|
||||
|
||||
### 3. **Migration Tools**
|
||||
|
||||
- **SQLite to PostgreSQL**: Complete data migration pipeline
|
||||
- **Schema Creation**: Automated database setup
|
||||
- **Data Validation**: Quality checks during migration
|
||||
@ -94,6 +102,7 @@ Based on the analysis of your SQLite dump:
|
||||
## 🎯 Next Implementation Steps
|
||||
|
||||
### **Phase 1: Complete Domain Models** (Week 1-2)
|
||||
|
||||
- [ ] Work and WorkTranslation entities
|
||||
- [ ] Book and BookTranslation entities
|
||||
- [ ] Country and CountryTranslation entities
|
||||
@ -101,30 +110,35 @@ Based on the analysis of your SQLite dump:
|
||||
- [ ] User and authentication entities
|
||||
|
||||
### **Phase 2: Repository Layer** (Week 3-4)
|
||||
|
||||
- [ ] Database repositories for all entities
|
||||
- [ ] Data access abstractions
|
||||
- [ ] Transaction management
|
||||
- [ ] Query optimization
|
||||
|
||||
### **Phase 3: Service Layer** (Week 5-6)
|
||||
|
||||
- [ ] Business logic implementation
|
||||
- [ ] Search and filtering services
|
||||
- [ ] Content management services
|
||||
- [ ] Authentication and authorization
|
||||
|
||||
### **Phase 4: API Layer** (Week 7-8)
|
||||
|
||||
- [ ] HTTP handlers and middleware
|
||||
- [ ] RESTful API endpoints
|
||||
- [ ] GraphQL schema and resolvers
|
||||
- [ ] Input validation and sanitization
|
||||
|
||||
### **Phase 5: Admin Interface** (Week 9-10)
|
||||
|
||||
- [ ] Content management system
|
||||
- [ ] User administration
|
||||
- [ ] Data import/export tools
|
||||
- [ ] Analytics and reporting
|
||||
|
||||
### **Phase 6: Testing & Deployment** (Week 11-12)
|
||||
|
||||
- [ ] Comprehensive testing suite
|
||||
- [ ] Performance optimization
|
||||
- [ ] Production deployment
|
||||
@ -155,12 +169,14 @@ make logs
|
||||
## 🔍 Data Migration Process
|
||||
|
||||
### **Step 1: Schema Creation**
|
||||
|
||||
```bash
|
||||
# Database will be automatically initialized with proper schema
|
||||
docker-compose up -d postgres
|
||||
```
|
||||
|
||||
### **Step 2: Data Migration**
|
||||
|
||||
```bash
|
||||
# Migrate data from your SQLite dump
|
||||
make migrate-data
|
||||
@ -168,6 +184,7 @@ make migrate-data
|
||||
```
|
||||
|
||||
### **Step 3: Verification**
|
||||
|
||||
```bash
|
||||
# Check migration status
|
||||
make status
|
||||
@ -177,17 +194,20 @@ make status
|
||||
## 📈 Performance Improvements
|
||||
|
||||
### **Database Optimizations**
|
||||
|
||||
- **Full-Text Search**: PostgreSQL FTS for fast text search
|
||||
- **Trigram Indexes**: Partial string matching
|
||||
- **JSONB Indexes**: Efficient JSON querying
|
||||
- **Connection Pooling**: Optimized database connections
|
||||
|
||||
### **Caching Strategy**
|
||||
|
||||
- **Redis**: Frequently accessed data caching
|
||||
- **Application Cache**: In-memory caching for hot data
|
||||
- **CDN Ready**: Static asset optimization
|
||||
|
||||
### **Search Capabilities**
|
||||
|
||||
- **Multi-language Search**: Support for all content languages
|
||||
- **Fuzzy Matching**: Typo-tolerant search
|
||||
- **Faceted Search**: Filter by author, genre, language, etc.
|
||||
@ -196,12 +216,14 @@ make status
|
||||
## 🔒 Security Features
|
||||
|
||||
### **Authentication & Authorization**
|
||||
|
||||
- **JWT Tokens**: Secure API authentication
|
||||
- **Role-Based Access**: Admin, editor, viewer roles
|
||||
- **API Rate Limiting**: Prevent abuse and DDoS
|
||||
- **Input Validation**: Comprehensive input sanitization
|
||||
|
||||
### **Data Protection**
|
||||
|
||||
- **HTTPS Enforcement**: Encrypted communication
|
||||
- **SQL Injection Prevention**: Parameterized queries
|
||||
- **XSS Protection**: Content sanitization
|
||||
@ -210,12 +232,14 @@ make status
|
||||
## 📊 Monitoring & Observability
|
||||
|
||||
### **Metrics Collection**
|
||||
|
||||
- **Prometheus**: System and business metrics
|
||||
- **Grafana**: Visualization and dashboards
|
||||
- **Health Checks**: Service health monitoring
|
||||
- **Performance Tracking**: Response time and throughput
|
||||
|
||||
### **Logging Strategy**
|
||||
|
||||
- **Structured Logging**: JSON format logs
|
||||
- **Log Levels**: Debug, info, warn, error
|
||||
- **Audit Trail**: Track all data changes
|
||||
@ -224,24 +248,28 @@ make status
|
||||
## 🌟 Key Benefits of This Architecture
|
||||
|
||||
### **1. Data Preservation**
|
||||
|
||||
- **100% Record Migration**: All cultural content preserved
|
||||
- **Data Quality**: Automatic fixing of identified issues
|
||||
- **Relationship Integrity**: Maintains all author-work connections
|
||||
- **Multi-language Support**: Preserves all language variants
|
||||
|
||||
### **2. Performance**
|
||||
|
||||
- **10x Faster Search**: Full-text search and optimized indexes
|
||||
- **Scalable Architecture**: Designed for 10,000+ concurrent users
|
||||
- **Efficient Caching**: Redis-based caching strategy
|
||||
- **Optimized Queries**: Database query optimization
|
||||
|
||||
### **3. Maintainability**
|
||||
|
||||
- **Clean Code**: Following Go best practices
|
||||
- **Modular Design**: Easy to extend and modify
|
||||
- **Comprehensive Testing**: 90%+ test coverage target
|
||||
- **Documentation**: Complete API and development docs
|
||||
|
||||
### **4. Future-Proof**
|
||||
|
||||
- **Modern Stack**: Latest Go and database technologies
|
||||
- **Extensible Design**: Easy to add new features
|
||||
- **API-First**: Ready for mobile apps and integrations
|
||||
@ -250,6 +278,7 @@ make status
|
||||
## 🚀 Getting Started
|
||||
|
||||
1. **Clone and Setup**
|
||||
|
||||
```bash
|
||||
git clone <repository-url>
|
||||
cd tercul-go
|
||||
@ -258,31 +287,35 @@ make status
|
||||
```
|
||||
|
||||
2. **Start Development Environment**
|
||||
|
||||
```bash
|
||||
make setup
|
||||
```
|
||||
|
||||
3. **Migrate Your Data**
|
||||
|
||||
```bash
|
||||
make migrate-data
|
||||
# Enter path to your SQLite dump
|
||||
```
|
||||
|
||||
4. **Start the Application**
|
||||
|
||||
```bash
|
||||
make run
|
||||
```
|
||||
|
||||
5. **Access the System**
|
||||
- **API**: http://localhost:8080
|
||||
- **Database Admin**: http://localhost:8081
|
||||
- **Redis Admin**: http://localhost:8082
|
||||
- **API**: <http://localhost:8080>
|
||||
- **Database Admin**: <http://localhost:8081>
|
||||
- **Redis Admin**: <http://localhost:8082>
|
||||
|
||||
## 📞 Support & Next Steps
|
||||
|
||||
This foundation provides everything needed to rebuild the TERCUL platform while preserving all your cultural content. The architecture is production-ready and follows industry best practices.
|
||||
|
||||
**Next Steps:**
|
||||
|
||||
1. Review the architecture document for detailed technical specifications
|
||||
2. Set up the development environment using the provided tools
|
||||
3. Run the data migration to transfer your existing content
|
||||
|
||||
111
go.mod
111
go.mod
@ -1,14 +1,12 @@
|
||||
module tercul
|
||||
|
||||
go 1.24.0
|
||||
|
||||
toolchain go1.24.2
|
||||
go 1.24.10
|
||||
|
||||
require (
|
||||
github.com/99designs/gqlgen v0.17.72
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.2
|
||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2
|
||||
github.com/blevesearch/bleve/v2 v2.5.5
|
||||
github.com/go-openapi/strfmt v0.25.0
|
||||
github.com/go-playground/validator/v10 v10.28.0
|
||||
github.com/go-redis/redismock/v9 v9.2.0
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0
|
||||
@ -23,68 +21,65 @@ require (
|
||||
github.com/rs/zerolog v1.34.0
|
||||
github.com/spf13/viper v1.21.0
|
||||
github.com/stretchr/testify v1.11.1
|
||||
github.com/testcontainers/testcontainers-go v0.40.0
|
||||
github.com/vektah/gqlparser/v2 v2.5.26
|
||||
github.com/weaviate/weaviate v1.30.2
|
||||
github.com/weaviate/weaviate-go-client/v5 v5.1.0
|
||||
github.com/weaviate/weaviate v1.33.6
|
||||
github.com/weaviate/weaviate-go-client/v5 v5.6.0
|
||||
go.opentelemetry.io/otel v1.38.0
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0
|
||||
go.opentelemetry.io/otel/sdk v1.38.0
|
||||
go.opentelemetry.io/otel/trace v1.38.0
|
||||
golang.org/x/crypto v0.42.0
|
||||
golang.org/x/crypto v0.43.0
|
||||
gorm.io/driver/postgres v1.5.11
|
||||
gorm.io/driver/sqlite v1.6.0
|
||||
gorm.io/gorm v1.30.0
|
||||
)
|
||||
|
||||
require (
|
||||
dario.cat/mergo v1.0.2 // indirect
|
||||
filippo.io/edwards25519 v1.1.0 // indirect
|
||||
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect
|
||||
github.com/ClickHouse/ch-go v0.67.0 // indirect
|
||||
github.com/ClickHouse/clickhouse-go/v2 v2.40.1 // indirect
|
||||
github.com/RoaringBitmap/roaring/v2 v2.4.5 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||
github.com/agnivade/levenshtein v1.2.1 // indirect
|
||||
github.com/andybalholm/brotli v1.2.0 // indirect
|
||||
github.com/antlr4-go/antlr/v4 v4.13.0 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/bits-and-blooms/bitset v1.22.0 // indirect
|
||||
github.com/blevesearch/bleve_index_api v1.2.11 // indirect
|
||||
github.com/blevesearch/geo v0.2.4 // indirect
|
||||
github.com/blevesearch/go-faiss v1.0.26 // indirect
|
||||
github.com/blevesearch/go-porterstemmer v1.0.3 // indirect
|
||||
github.com/blevesearch/gtreap v0.1.1 // indirect
|
||||
github.com/blevesearch/mmap-go v1.0.4 // indirect
|
||||
github.com/blevesearch/scorch_segment_api/v2 v2.3.13 // indirect
|
||||
github.com/blevesearch/segment v0.9.1 // indirect
|
||||
github.com/blevesearch/snowballstem v0.9.0 // indirect
|
||||
github.com/blevesearch/upsidedown_store_api v1.0.2 // indirect
|
||||
github.com/blevesearch/vellum v1.1.0 // indirect
|
||||
github.com/blevesearch/zapx/v11 v11.4.2 // indirect
|
||||
github.com/blevesearch/zapx/v12 v12.4.2 // indirect
|
||||
github.com/blevesearch/zapx/v13 v13.4.2 // indirect
|
||||
github.com/blevesearch/zapx/v14 v14.4.2 // indirect
|
||||
github.com/blevesearch/zapx/v15 v15.4.2 // indirect
|
||||
github.com/blevesearch/zapx/v16 v16.2.7 // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/coder/websocket v1.8.12 // indirect
|
||||
github.com/containerd/errdefs v1.0.0 // indirect
|
||||
github.com/containerd/errdefs/pkg v0.3.0 // indirect
|
||||
github.com/containerd/log v0.1.0 // indirect
|
||||
github.com/containerd/platforms v0.2.1 // indirect
|
||||
github.com/cpuguy83/dockercfg v0.3.2 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/distribution/reference v0.6.0 // indirect
|
||||
github.com/docker/docker v28.5.1+incompatible // indirect
|
||||
github.com/docker/go-connections v0.6.0 // indirect
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/ebitengine/purego v0.8.4 // indirect
|
||||
github.com/elastic/go-sysinfo v1.15.4 // indirect
|
||||
github.com/elastic/go-windows v1.0.2 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.10 // indirect
|
||||
github.com/go-faster/city v1.0.1 // indirect
|
||||
github.com/go-faster/errors v0.7.1 // indirect
|
||||
github.com/go-logr/logr v1.4.3 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-ole/go-ole v1.3.0 // indirect
|
||||
github.com/go-openapi/analysis v0.23.0 // indirect
|
||||
github.com/go-openapi/errors v0.22.0 // indirect
|
||||
github.com/go-openapi/errors v0.22.4 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.21.0 // indirect
|
||||
github.com/go-openapi/jsonreference v0.21.0 // indirect
|
||||
github.com/go-openapi/loads v0.22.0 // indirect
|
||||
github.com/go-openapi/runtime v0.24.2 // indirect
|
||||
github.com/go-openapi/spec v0.21.0 // indirect
|
||||
github.com/go-openapi/strfmt v0.23.0 // indirect
|
||||
github.com/go-openapi/swag v0.23.0 // indirect
|
||||
github.com/go-openapi/validate v0.24.0 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
@ -94,7 +89,6 @@ require (
|
||||
github.com/golang-jwt/jwt/v4 v4.5.2 // indirect
|
||||
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
|
||||
github.com/golang-sql/sqlexp v0.1.0 // indirect
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/gorilla/websocket v1.5.0 // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||
@ -105,9 +99,10 @@ require (
|
||||
github.com/joho/godotenv v1.5.1 // indirect
|
||||
github.com/jonboulle/clockwork v0.5.0 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/compress v1.18.0 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 // indirect
|
||||
github.com/magiconair/properties v1.8.10 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
@ -115,21 +110,28 @@ require (
|
||||
github.com/mfridman/interpolate v0.0.2 // indirect
|
||||
github.com/mfridman/xflag v0.1.0 // indirect
|
||||
github.com/microsoft/go-mssqldb v1.9.2 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/mschoch/smat v0.2.0 // indirect
|
||||
github.com/moby/docker-image-spec v1.3.1 // indirect
|
||||
github.com/moby/go-archive v0.1.0 // indirect
|
||||
github.com/moby/patternmatcher v0.6.0 // indirect
|
||||
github.com/moby/sys/sequential v0.6.0 // indirect
|
||||
github.com/moby/sys/user v0.4.0 // indirect
|
||||
github.com/moby/sys/userns v0.1.0 // indirect
|
||||
github.com/moby/term v0.5.0 // indirect
|
||||
github.com/morikuni/aec v1.0.0 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/ncruces/go-strftime v0.1.9 // indirect
|
||||
github.com/oklog/ulid v1.3.1 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.1.1 // indirect
|
||||
github.com/opentracing/opentracing-go v1.2.0 // indirect
|
||||
github.com/paulmach/orb v0.11.1 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.22 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/prometheus/client_model v0.6.1 // indirect
|
||||
github.com/prometheus/common v0.62.0 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
|
||||
github.com/prometheus/client_model v0.6.2 // indirect
|
||||
github.com/prometheus/common v0.65.0 // indirect
|
||||
github.com/prometheus/procfs v0.15.1 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/robfig/cron/v3 v3.0.1 // indirect
|
||||
@ -137,40 +139,45 @@ require (
|
||||
github.com/sagikazarmark/locafero v0.11.0 // indirect
|
||||
github.com/segmentio/asm v1.2.0 // indirect
|
||||
github.com/sethvargo/go-retry v0.3.0 // indirect
|
||||
github.com/shirou/gopsutil/v4 v4.25.6 // indirect
|
||||
github.com/shopspring/decimal v1.4.0 // indirect
|
||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||
github.com/sosodev/duration v1.3.1 // indirect
|
||||
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
|
||||
github.com/spf13/afero v1.15.0 // indirect
|
||||
github.com/spf13/cast v1.10.0 // indirect
|
||||
github.com/spf13/pflag v1.0.10 // indirect
|
||||
github.com/stretchr/objx v0.5.2 // indirect
|
||||
github.com/stretchr/objx v0.5.3 // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.14 // indirect
|
||||
github.com/tklauser/numcpus v0.9.0 // indirect
|
||||
github.com/tursodatabase/libsql-client-go v0.0.0-20240902231107-85af5b9d094d // indirect
|
||||
github.com/urfave/cli/v2 v2.27.6 // indirect
|
||||
github.com/vertica/vertica-sql-go v1.3.3 // indirect
|
||||
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
|
||||
github.com/ydb-platform/ydb-go-genproto v0.0.0-20241112172322-ea1f63298f77 // indirect
|
||||
github.com/ydb-platform/ydb-go-sdk/v3 v3.108.1 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||
github.com/ziutek/mymysql v1.5.4 // indirect
|
||||
go.etcd.io/bbolt v1.4.0 // indirect
|
||||
go.mongodb.org/mongo-driver v1.14.0 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||
go.mongodb.org/mongo-driver v1.17.6 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.38.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b // indirect
|
||||
golang.org/x/mod v0.27.0 // indirect
|
||||
golang.org/x/net v0.43.0 // indirect
|
||||
golang.org/x/oauth2 v0.25.0 // indirect
|
||||
golang.org/x/sync v0.17.0 // indirect
|
||||
golang.org/x/sys v0.36.0 // indirect
|
||||
golang.org/x/text v0.29.0 // indirect
|
||||
golang.org/x/time v0.11.0 // indirect
|
||||
golang.org/x/tools v0.36.0 // indirect
|
||||
gonum.org/v1/gonum v0.15.1 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250102185135-69823020774d // indirect
|
||||
google.golang.org/grpc v1.69.4 // indirect
|
||||
google.golang.org/protobuf v1.36.6 // indirect
|
||||
golang.org/x/mod v0.28.0 // indirect
|
||||
golang.org/x/net v0.46.1-0.20251013234738-63d1a5100f82 // indirect
|
||||
golang.org/x/oauth2 v0.33.0 // indirect
|
||||
golang.org/x/sync v0.18.0 // indirect
|
||||
golang.org/x/sys v0.37.0 // indirect
|
||||
golang.org/x/text v0.30.0 // indirect
|
||||
golang.org/x/time v0.12.0 // indirect
|
||||
golang.org/x/tools v0.37.0 // indirect
|
||||
gonum.org/v1/gonum v0.16.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8 // indirect
|
||||
google.golang.org/grpc v1.77.0 // indirect
|
||||
google.golang.org/protobuf v1.36.10 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
howett.net/plist v1.0.1 // indirect
|
||||
|
||||
261
go.sum
261
go.sum
@ -1,19 +1,25 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=
|
||||
dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
|
||||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||
github.com/99designs/gqlgen v0.17.72 h1:2JDAuutIYtAN26BAtigfLZFnTN53fpYbIENL8bVgAKY=
|
||||
github.com/99designs/gqlgen v0.17.72/go.mod h1:BoL4C3j9W2f95JeWMrSArdDNGWmZB9MOS2EMHJDZmUc=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0 h1:Gt0j3wceWMwPmiazCa8MzMA0MfhmPIz0Qp0FJ6qcM0U=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0/go.mod h1:Ot/6aikWnKWi4l9QB7qVSwa8iMphQNqkWALMoNT3rzM=
|
||||
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk=
|
||||
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.19.0 h1:ci6Yd6nysBRLEodoziB6ah1+YOzZbZk+NYneoA6q+6E=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.19.0/go.mod h1:QyVsSSN64v5TGltphKLQ2sQxe4OBQg0J1eKRcVBnfgE=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1 h1:B+blDbyVIG3WaikNxPnhPiJ1MThR03b3vKGtER95TP4=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1/go.mod h1:JdM5psgjfBf5fo2uWOZhflPWyDBZ/O/CNAH9CtsuZE4=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 h1:FPKJS1T+clwv+OLGt13a8UjqeRuh0O4SJ3lUriThc+4=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1/go.mod h1:j2chePtV91HrC22tGoRX3sGY42uF13WzmmV80/OdVAA=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 h1:9iefClla7iYpfYWdzPCRDozdmndjTm8DXdpCzPajMgA=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2/go.mod h1:XtLgD3ZD34DAaVIIAyG3objl5DynM3CQ/vMcbBNJZGI=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.3.1 h1:Wgf5rZba3YZqeTNJPtvqZoBu1sBN/L4sry+u2U3Y75w=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.3.1/go.mod h1:xxCBG/f/4Vbmh2XQJBsOmNdxWUY5j/s27jujKPbQf14=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.1 h1:bFWuoEKg+gImo7pvkiQEFAc8ocibADgXeiLAxWhWmkI=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.1/go.mod h1:Vih/3yc6yac2JzU4hzpaDupBJP0Flaia9rXXrU8xyww=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 h1:oygO0locgZJe7PpYPXT5A29ZkwJaPqcva7BVeemZOZs=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
@ -23,12 +29,12 @@ github.com/ClickHouse/clickhouse-go/v2 v2.40.1 h1:PbwsHBgqXRydU7jKULD1C8CHmifczf
|
||||
github.com/ClickHouse/clickhouse-go/v2 v2.40.1/go.mod h1:GDzSBLVhladVm8V01aEB36IoBOVLLICfyeuiIp/8Ezc=
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU=
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU=
|
||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||
github.com/PuerkitoBio/goquery v1.10.3 h1:pFYcNSqHxBD06Fpj/KsbStFRsgRATgnf3LeXiUkhzPo=
|
||||
github.com/PuerkitoBio/goquery v1.10.3/go.mod h1:tMUX0zDMHXYlAQk6p35XxQMqMweEKB7iK7iLNd4RH4Y=
|
||||
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
||||
github.com/RoaringBitmap/roaring/v2 v2.4.5 h1:uGrrMreGjvAtTBobc0g5IrW1D5ldxDQYe2JW2gggRdg=
|
||||
github.com/RoaringBitmap/roaring/v2 v2.4.5/go.mod h1:FiJcsfkGje/nZBZgCu0ZxCPOKD/hVXDS2dXi7/eUFE0=
|
||||
github.com/agnivade/levenshtein v1.2.1 h1:EHBY3UOn1gwdy/VbFwgo4cxecRznFk7fKWN1KOX7eoM=
|
||||
github.com/agnivade/levenshtein v1.2.1/go.mod h1:QVVI16kDrtSuwcpd0p1+xMC6Z/VfhtCyDIjcwga4/DU=
|
||||
github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=
|
||||
@ -49,49 +55,12 @@ github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3d
|
||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/bits-and-blooms/bitset v1.12.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
|
||||
github.com/bits-and-blooms/bitset v1.22.0 h1:Tquv9S8+SGaS3EhyA+up3FXzmkhxPGjQQCkcs2uw7w4=
|
||||
github.com/bits-and-blooms/bitset v1.22.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
|
||||
github.com/blevesearch/bleve/v2 v2.5.5 h1:lzC89QUCco+y1qBnJxGqm4AbtsdsnlUvq0kXok8n3C8=
|
||||
github.com/blevesearch/bleve/v2 v2.5.5/go.mod h1:t5WoESS5TDteTdnjhhvpA1BpLYErOBX2IQViTMLK7wo=
|
||||
github.com/blevesearch/bleve_index_api v1.2.11 h1:bXQ54kVuwP8hdrXUSOnvTQfgK0KI1+f9A0ITJT8tX1s=
|
||||
github.com/blevesearch/bleve_index_api v1.2.11/go.mod h1:rKQDl4u51uwafZxFrPD1R7xFOwKnzZW7s/LSeK4lgo0=
|
||||
github.com/blevesearch/geo v0.2.4 h1:ECIGQhw+QALCZaDcogRTNSJYQXRtC8/m8IKiA706cqk=
|
||||
github.com/blevesearch/geo v0.2.4/go.mod h1:K56Q33AzXt2YExVHGObtmRSFYZKYGv0JEN5mdacJJR8=
|
||||
github.com/blevesearch/go-faiss v1.0.26 h1:4dRLolFgjPyjkaXwff4NfbZFdE/dfywbzDqporeQvXI=
|
||||
github.com/blevesearch/go-faiss v1.0.26/go.mod h1:OMGQwOaRRYxrmeNdMrXJPvVx8gBnvE5RYrr0BahNnkk=
|
||||
github.com/blevesearch/go-porterstemmer v1.0.3 h1:GtmsqID0aZdCSNiY8SkuPJ12pD4jI+DdXTAn4YRcHCo=
|
||||
github.com/blevesearch/go-porterstemmer v1.0.3/go.mod h1:angGc5Ht+k2xhJdZi511LtmxuEf0OVpvUUNrwmM1P7M=
|
||||
github.com/blevesearch/gtreap v0.1.1 h1:2JWigFrzDMR+42WGIN/V2p0cUvn4UP3C4Q5nmaZGW8Y=
|
||||
github.com/blevesearch/gtreap v0.1.1/go.mod h1:QaQyDRAT51sotthUWAH4Sj08awFSSWzgYICSZ3w0tYk=
|
||||
github.com/blevesearch/mmap-go v1.0.4 h1:OVhDhT5B/M1HNPpYPBKIEJaD0F3Si+CrEKULGCDPWmc=
|
||||
github.com/blevesearch/mmap-go v1.0.4/go.mod h1:EWmEAOmdAS9z/pi/+Toxu99DnsbhG1TIxUoRmJw/pSs=
|
||||
github.com/blevesearch/scorch_segment_api/v2 v2.3.13 h1:ZPjv/4VwWvHJZKeMSgScCapOy8+DdmsmRyLmSB88UoY=
|
||||
github.com/blevesearch/scorch_segment_api/v2 v2.3.13/go.mod h1:ENk2LClTehOuMS8XzN3UxBEErYmtwkE7MAArFTXs9Vc=
|
||||
github.com/blevesearch/segment v0.9.1 h1:+dThDy+Lvgj5JMxhmOVlgFfkUtZV2kw49xax4+jTfSU=
|
||||
github.com/blevesearch/segment v0.9.1/go.mod h1:zN21iLm7+GnBHWTao9I+Au/7MBiL8pPFtJBJTsk6kQw=
|
||||
github.com/blevesearch/snowballstem v0.9.0 h1:lMQ189YspGP6sXvZQ4WZ+MLawfV8wOmPoD/iWeNXm8s=
|
||||
github.com/blevesearch/snowballstem v0.9.0/go.mod h1:PivSj3JMc8WuaFkTSRDW2SlrulNWPl4ABg1tC/hlgLs=
|
||||
github.com/blevesearch/upsidedown_store_api v1.0.2 h1:U53Q6YoWEARVLd1OYNc9kvhBMGZzVrdmaozG2MfoB+A=
|
||||
github.com/blevesearch/upsidedown_store_api v1.0.2/go.mod h1:M01mh3Gpfy56Ps/UXHjEO/knbqyQ1Oamg8If49gRwrQ=
|
||||
github.com/blevesearch/vellum v1.1.0 h1:CinkGyIsgVlYf8Y2LUQHvdelgXr6PYuvoDIajq6yR9w=
|
||||
github.com/blevesearch/vellum v1.1.0/go.mod h1:QgwWryE8ThtNPxtgWJof5ndPfx0/YMBh+W2weHKPw8Y=
|
||||
github.com/blevesearch/zapx/v11 v11.4.2 h1:l46SV+b0gFN+Rw3wUI1YdMWdSAVhskYuvxlcgpQFljs=
|
||||
github.com/blevesearch/zapx/v11 v11.4.2/go.mod h1:4gdeyy9oGa/lLa6D34R9daXNUvfMPZqUYjPwiLmekwc=
|
||||
github.com/blevesearch/zapx/v12 v12.4.2 h1:fzRbhllQmEMUuAQ7zBuMvKRlcPA5ESTgWlDEoB9uQNE=
|
||||
github.com/blevesearch/zapx/v12 v12.4.2/go.mod h1:TdFmr7afSz1hFh/SIBCCZvcLfzYvievIH6aEISCte58=
|
||||
github.com/blevesearch/zapx/v13 v13.4.2 h1:46PIZCO/ZuKZYgxI8Y7lOJqX3Irkc3N8W82QTK3MVks=
|
||||
github.com/blevesearch/zapx/v13 v13.4.2/go.mod h1:knK8z2NdQHlb5ot/uj8wuvOq5PhDGjNYQQy0QDnopZk=
|
||||
github.com/blevesearch/zapx/v14 v14.4.2 h1:2SGHakVKd+TrtEqpfeq8X+So5PShQ5nW6GNxT7fWYz0=
|
||||
github.com/blevesearch/zapx/v14 v14.4.2/go.mod h1:rz0XNb/OZSMjNorufDGSpFpjoFKhXmppH9Hi7a877D8=
|
||||
github.com/blevesearch/zapx/v15 v15.4.2 h1:sWxpDE0QQOTjyxYbAVjt3+0ieu8NCE0fDRaFxEsp31k=
|
||||
github.com/blevesearch/zapx/v15 v15.4.2/go.mod h1:1pssev/59FsuWcgSnTa0OeEpOzmhtmr/0/11H0Z8+Nw=
|
||||
github.com/blevesearch/zapx/v16 v16.2.7 h1:xcgFRa7f/tQXOwApVq7JWgPYSlzyUMmkuYa54tMDuR0=
|
||||
github.com/blevesearch/zapx/v16 v16.2.7/go.mod h1:murSoCJPCk25MqURrcJaBQ1RekuqSCSfMjXH4rHyA14=
|
||||
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
||||
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
|
||||
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
|
||||
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
|
||||
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
@ -105,10 +74,22 @@ github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWH
|
||||
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/coder/websocket v1.8.12 h1:5bUXkEPPIbewrnkU8LTCLVaxi4N4J8ahufH2vlo4NAo=
|
||||
github.com/coder/websocket v1.8.12/go.mod h1:LNVeNrXQZfe5qhS9ALED3uA+l5pPqvwXg3CKoDBB2gs=
|
||||
github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=
|
||||
github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
|
||||
github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE=
|
||||
github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk=
|
||||
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
|
||||
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
|
||||
github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A=
|
||||
github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA=
|
||||
github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
|
||||
github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
@ -117,9 +98,19 @@ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/r
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||
github.com/dgryski/trifles v0.0.0-20230903005119-f50d829f2e54 h1:SG7nF6SRlWhcT7cNTs5R6Hk4V2lcmLz2NsG2VnInyNo=
|
||||
github.com/dgryski/trifles v0.0.0-20230903005119-f50d829f2e54/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA=
|
||||
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
|
||||
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||
github.com/docker/docker v28.5.1+incompatible h1:Bm8DchhSD2J6PsFzxC35TZo4TLGR2PdW/E69rU45NhM=
|
||||
github.com/docker/docker v28.5.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94=
|
||||
github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE=
|
||||
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw=
|
||||
github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||
github.com/elastic/go-sysinfo v1.8.1/go.mod h1:JfllUnzoQV/JRYymbH3dO1yggI3mV2oTKSXsDHM+uIM=
|
||||
github.com/elastic/go-sysinfo v1.15.4 h1:A3zQcunCxik14MgXu39cXFXcIw2sFXZ0zL886eyiv1Q=
|
||||
github.com/elastic/go-sysinfo v1.15.4/go.mod h1:ZBVXmqS368dOn/jvijV/zHLfakWTYHBZPk3G244lHrU=
|
||||
@ -132,6 +123,8 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||
github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
|
||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
@ -149,14 +142,17 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
||||
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
||||
github.com/go-openapi/analysis v0.21.2/go.mod h1:HZwRk4RRisyG8vx2Oe6aqeSQcoxRp47Xkp3+K6q+LdY=
|
||||
github.com/go-openapi/analysis v0.23.0 h1:aGday7OWupfMs+LbmLZG4k0MYXIANxcuBTYUC03zFCU=
|
||||
github.com/go-openapi/analysis v0.23.0/go.mod h1:9mz9ZWaSlV8TvjQHLl2mUW2PbZtemkE8yA5v22ohupo=
|
||||
github.com/go-openapi/errors v0.19.8/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M=
|
||||
github.com/go-openapi/errors v0.19.9/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M=
|
||||
github.com/go-openapi/errors v0.20.2/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M=
|
||||
github.com/go-openapi/errors v0.22.0 h1:c4xY/OLxUBSTiepAg3j/MHuAv5mJhnf53LLMWFB+u/w=
|
||||
github.com/go-openapi/errors v0.22.0/go.mod h1:J3DmZScxCDufmIMsdOuDHxJbdOGC0xtUynjIx092vXE=
|
||||
github.com/go-openapi/errors v0.22.4 h1:oi2K9mHTOb5DPW2Zjdzs/NIvwi2N3fARKaTJLdNabaM=
|
||||
github.com/go-openapi/errors v0.22.4/go.mod h1:z9S8ASTUqx7+CP1Q8dD8ewGH/1JWFFLX/2PmAYNQLgk=
|
||||
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
|
||||
@ -175,13 +171,15 @@ github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5
|
||||
github.com/go-openapi/strfmt v0.21.0/go.mod h1:ZRQ409bWMj+SOgXofQAGTIo2Ebu72Gs+WaRADcS5iNg=
|
||||
github.com/go-openapi/strfmt v0.21.1/go.mod h1:I/XVKeLc5+MM5oPNN7P6urMOpuLXEcNrCX/rPGuWb0k=
|
||||
github.com/go-openapi/strfmt v0.21.2/go.mod h1:I/XVKeLc5+MM5oPNN7P6urMOpuLXEcNrCX/rPGuWb0k=
|
||||
github.com/go-openapi/strfmt v0.23.0 h1:nlUS6BCqcnAk0pyhi9Y+kdDVZdZMHfEKQiS4HaMgO/c=
|
||||
github.com/go-openapi/strfmt v0.23.0/go.mod h1:NrtIpfKtWIygRkKVsxh7XQMDQW5HKQl6S5ik2elW+K4=
|
||||
github.com/go-openapi/strfmt v0.25.0 h1:7R0RX7mbKLa9EYCTHRcCuIPcaqlyQiWNPTXwClK0saQ=
|
||||
github.com/go-openapi/strfmt v0.25.0/go.mod h1:nNXct7OzbwrMY9+5tLX4I21pzcmE6ccMGXl3jFdPfn8=
|
||||
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
||||
github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
|
||||
github.com/go-openapi/swag v0.21.1/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
|
||||
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
|
||||
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
|
||||
github.com/go-openapi/testify/v2 v2.0.2 h1:X999g3jeLcoY8qctY/c/Z8iBHTbwLz7R2WXd6Ub6wls=
|
||||
github.com/go-openapi/testify/v2 v2.0.2/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54=
|
||||
github.com/go-openapi/validate v0.21.0/go.mod h1:rjnrwK57VJ7A8xqfpAOEKRH8yQSGUriMu5/zuPSQ1hg=
|
||||
github.com/go-openapi/validate v0.24.0 h1:LdfDKwNbpB6Vn40xhTdNZAnfLECL81w+VX3BumrGD58=
|
||||
github.com/go-openapi/validate v0.24.0/go.mod h1:iyeX1sEufmv3nPbBdX3ieNviWnOZaJ1+zquzJEf2BAQ=
|
||||
@ -254,8 +252,6 @@ github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiu
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
||||
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
@ -264,9 +260,9 @@ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
|
||||
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
@ -275,7 +271,10 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
|
||||
github.com/hibiken/asynq v0.25.1 h1:phj028N0nm15n8O2ims+IvJ2gz4k2auvermngh9JhTw=
|
||||
@ -304,8 +303,6 @@ github.com/jonreiter/govader v0.0.0-20250429093935-f6505c8d03cc h1:Zvn/U2151AlhF
|
||||
github.com/jonreiter/govader v0.0.0-20250429093935-f6505c8d03cc/go.mod h1:1o8G6XiwYAsUAF/bTOC5BAXjSNFzJD/RE9uQyssNwac=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
|
||||
github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4=
|
||||
github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA=
|
||||
@ -328,6 +325,10 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||
github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 h1:7UMa6KCCMjZEMDtTVdcGu0B1GmmC7QJKiCCjyTAWQy0=
|
||||
github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k=
|
||||
github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE=
|
||||
github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
@ -353,16 +354,25 @@ github.com/microsoft/go-mssqldb v1.9.2/go.mod h1:GBbW9ASTiDC+mpgWDGKdm3FnFLTUsLY
|
||||
github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
|
||||
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
|
||||
github.com/moby/go-archive v0.1.0 h1:Kk/5rdW/g+H8NHdJW2gsXyZ7UnzvJNOy6VKJqueWdcQ=
|
||||
github.com/moby/go-archive v0.1.0/go.mod h1:G9B+YoujNohJmrIYFBpSd54GTUB4lt9S+xVQvsJyFuo=
|
||||
github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk=
|
||||
github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc=
|
||||
github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw=
|
||||
github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs=
|
||||
github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU=
|
||||
github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko=
|
||||
github.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs=
|
||||
github.com/moby/sys/user v0.4.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs=
|
||||
github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g=
|
||||
github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28=
|
||||
github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
|
||||
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
|
||||
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
|
||||
github.com/mschoch/smat v0.2.0 h1:8imxQsjDm8yFEAVBe7azKmKSgzSkZXDuKkSq9374khM=
|
||||
github.com/mschoch/smat v0.2.0/go.mod h1:kc9mz7DoBKqDyiRL7VZN8KvXQMWeTaVnttLRXOlotKw=
|
||||
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
||||
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
|
||||
@ -376,6 +386,10 @@ github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
|
||||
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
|
||||
github.com/onsi/gomega v1.27.1 h1:rfztXRbg6nv/5f+Raen9RcGoSecHIFgBBLQK3Wdj754=
|
||||
github.com/onsi/gomega v1.27.1/go.mod h1:aHX5xOykVYzWOV4WqQy0sy8BQptgukenXpCXfadcIAw=
|
||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
|
||||
github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
|
||||
github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs=
|
||||
github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
|
||||
github.com/paulmach/orb v0.11.1 h1:3koVegMC4X/WeiXYz9iswopaTwMem53NzTJuTF20JzU=
|
||||
@ -397,15 +411,17 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
|
||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||
github.com/pressly/goose/v3 v3.26.0 h1:KJakav68jdH0WDvoAcj8+n61WqOIaPGgH0bJWS6jpmM=
|
||||
github.com/pressly/goose/v3 v3.26.0/go.mod h1:4hC1KrritdCxtuFsqgs1R4AU5bWtTAf+cnWvfhf2DNY=
|
||||
github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y=
|
||||
github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
|
||||
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
|
||||
github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io=
|
||||
github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I=
|
||||
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
|
||||
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
|
||||
github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE=
|
||||
github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8=
|
||||
github.com/prometheus/procfs v0.0.0-20190425082905-87a4384529e0/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
|
||||
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
|
||||
@ -432,15 +448,19 @@ github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDc
|
||||
github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik=
|
||||
github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys=
|
||||
github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
|
||||
github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8=
|
||||
github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I=
|
||||
github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw=
|
||||
github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
|
||||
github.com/sethvargo/go-retry v0.3.0 h1:EEt31A35QhrcRZtrYFDTBg91cqZVnFL2navjDrah2SE=
|
||||
github.com/sethvargo/go-retry v0.3.0/go.mod h1:mNX17F0C/HguQMyMyJxcnU471gOZGxCLyYaFyAZraas=
|
||||
github.com/shirou/gopsutil/v4 v4.25.6 h1:kLysI2JsKorfaFPcYmcJqbzROzsBWEOAtw6A7dIfqXs=
|
||||
github.com/shirou/gopsutil/v4 v4.25.6/go.mod h1:PfybzyydfZcN+JMMjkF6Zb8Mq1A/VcogFFg7hj50W9c=
|
||||
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
|
||||
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
|
||||
github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/sosodev/duration v1.3.1 h1:qtHBDMQ6lvMQsL15g4aopM4HEfOaYuhWBw3NPTtlqq4=
|
||||
github.com/sosodev/duration v1.3.1/go.mod h1:RQIBBX0+fMLc/D9+Jb/fwvVmo0eZvDDEERAikUR6SDg=
|
||||
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw=
|
||||
@ -457,8 +477,8 @@ github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU=
|
||||
github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4=
|
||||
github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+QYS+U0=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
@ -468,7 +488,13 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||
github.com/testcontainers/testcontainers-go v0.40.0 h1:pSdJYLOVgLE8YdUY2FHQ1Fxu+aMnb6JfVz1mxk7OeMU=
|
||||
github.com/testcontainers/testcontainers-go v0.40.0/go.mod h1:FSXV5KQtX2HAMlm7U3APNyLkkap35zNLxukw9oBi/MY=
|
||||
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
|
||||
github.com/tklauser/go-sysconf v0.3.14 h1:g5vzr9iPFFz24v2KZXs/pvpvh8/V9Fw6vQK5ZZb78yU=
|
||||
github.com/tklauser/go-sysconf v0.3.14/go.mod h1:1ym4lWMLUOhuBOPGtRcJm7tEGX4SCYNEEEtghGG/8uY=
|
||||
github.com/tklauser/numcpus v0.9.0 h1:lmyCHtANi8aRUgkckBgoDk1nHCux3n2cgkJLXdQGPDo=
|
||||
github.com/tklauser/numcpus v0.9.0/go.mod h1:SN6Nq1O3VychhC1npsWostA+oW+VOQTxZrS604NSRyI=
|
||||
github.com/tursodatabase/libsql-client-go v0.0.0-20240902231107-85af5b9d094d h1:dOMI4+zEbDI37KGb0TI44GUAwxHF9cMsIoDTJ7UmgfU=
|
||||
github.com/tursodatabase/libsql-client-go v0.0.0-20240902231107-85af5b9d094d/go.mod h1:l8xTsYB90uaVdMHXMCxKKLSgw5wLYBwBKKefNIUnm9s=
|
||||
github.com/urfave/cli/v2 v2.27.6 h1:VdRdS98FNhKZ8/Az8B7MTyGQmpIr36O1EHybx/LaZ4g=
|
||||
@ -477,10 +503,10 @@ github.com/vektah/gqlparser/v2 v2.5.26 h1:REqqFkO8+SOEgZHR/eHScjjVjGS8Nk3RMO/jui
|
||||
github.com/vektah/gqlparser/v2 v2.5.26/go.mod h1:D1/VCZtV3LPnQrcPBeR/q5jkSQIPti0uYCP/RI0gIeo=
|
||||
github.com/vertica/vertica-sql-go v1.3.3 h1:fL+FKEAEy5ONmsvya2WH5T8bhkvY27y/Ik3ReR2T+Qw=
|
||||
github.com/vertica/vertica-sql-go v1.3.3/go.mod h1:jnn2GFuv+O2Jcjktb7zyc4Utlbu9YVqpHH/lx63+1M4=
|
||||
github.com/weaviate/weaviate v1.30.2 h1:zJjhXR4EwCK3v8bO3OgQCIAoQRbFJM3C6imR33rM3i8=
|
||||
github.com/weaviate/weaviate v1.30.2/go.mod h1:FQJsD9pckNolW1C+S+P88okIX6DEOLJwf7aqFvgYgSQ=
|
||||
github.com/weaviate/weaviate-go-client/v5 v5.1.0 h1:3wSf4fktKLvspPHwDYnn07u0sKfDAhrA5JeRe+R4ENg=
|
||||
github.com/weaviate/weaviate-go-client/v5 v5.1.0/go.mod h1:gg5qyiHk53+HMZW2ynkrgm+cMQDD2Ewyma84rBeChz4=
|
||||
github.com/weaviate/weaviate v1.33.6 h1:uOOvb63qdAZkRwY7PMIAGJQ1GMAkDv8ivqjkR+fhKTI=
|
||||
github.com/weaviate/weaviate v1.33.6/go.mod h1:NSKZOHzysOKarSWJaPFPkU3+qqbFEtOKyGUhM/p7YO4=
|
||||
github.com/weaviate/weaviate-go-client/v5 v5.6.0 h1:1/TRRxcepr8LH1yWoyHjdCDHHv8qMm3cO4oAOvkLAKM=
|
||||
github.com/weaviate/weaviate-go-client/v5 v5.6.0/go.mod h1:RKpSa7y64bIXxQA3QpdR4trKR8+uW7YG99xBXskppyA=
|
||||
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
|
||||
github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs=
|
||||
github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g=
|
||||
@ -497,20 +523,26 @@ github.com/ydb-platform/ydb-go-sdk/v3 v3.108.1/go.mod h1:l5sSv153E18VvYcsmr51hok
|
||||
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
||||
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs=
|
||||
github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
|
||||
go.etcd.io/bbolt v1.4.0 h1:TU77id3TnN/zKr7CO/uk+fBCwF2jGcMuw2B/FMAzYIk=
|
||||
go.etcd.io/bbolt v1.4.0/go.mod h1:AsD+OCi/qPN1giOX1aiLAha3o1U8rAz65bvN4j0sRuk=
|
||||
go.mongodb.org/mongo-driver v1.7.3/go.mod h1:NqaYOwnXWr5Pm7AOpO5QFxKJ503nbMse/R79oO62zWg=
|
||||
go.mongodb.org/mongo-driver v1.7.5/go.mod h1:VXEWRZ6URJIkUq2SCAyapmhH0ZLRBP+FT4xhp5Zvxng=
|
||||
go.mongodb.org/mongo-driver v1.8.3/go.mod h1:0sQWfOeY63QTntERDJJ/0SuKK0T1uVSgKCuAROlKEPY=
|
||||
go.mongodb.org/mongo-driver v1.11.4/go.mod h1:PTSz5yu21bkT/wXpkS7WR5f0ddqw5quethTUn9WM+2g=
|
||||
go.mongodb.org/mongo-driver v1.14.0 h1:P98w8egYRjYe3XDjxhYJagTokP/H6HzlsnojRgZRd80=
|
||||
go.mongodb.org/mongo-driver v1.14.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGcNU603k65c=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||
go.mongodb.org/mongo-driver v1.17.6 h1:87JUG1wZfWsr6rIz3ZmpH90rL5tea7O3IHuSwHUpsss=
|
||||
go.mongodb.org/mongo-driver v1.17.6/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q=
|
||||
go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
|
||||
go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 h1:Mne5On7VWdx7omSrSSZvM4Kw7cS7NQkOOmLcgscI51U=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0/go.mod h1:IPtUMKL4O3tH5y+iXVyAXqpAwMuzC1IrxVS81rummfE=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.33.0 h1:wpMfgF8E1rkrT1Z6meFh1NDtownE9Ii3n3X2GJYjsaU=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.33.0/go.mod h1:wAy0T/dUbs468uOlkT31xjvqQgEVXv58BRFWEgn5v/0=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0 h1:kJxSDN4SgWWTjG/hPp3O7LCGLcHXFlvS2/FFOrwL+SE=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0/go.mod h1:mgIOzS7iZeKJdeB8/NYHrJ48fdGc71Llo5bJ1J4DWUE=
|
||||
go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA=
|
||||
@ -522,6 +554,8 @@ go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689Cbtr
|
||||
go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=
|
||||
go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
|
||||
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
|
||||
go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I=
|
||||
go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
|
||||
@ -538,8 +572,8 @@ golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPh
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201216223049-8b5274cf687f/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI=
|
||||
golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8=
|
||||
golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
|
||||
golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
|
||||
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
@ -552,8 +586,8 @@ golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvx
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ=
|
||||
golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc=
|
||||
golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U=
|
||||
golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@ -567,12 +601,12 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY
|
||||
golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
|
||||
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
|
||||
golang.org/x/net v0.46.1-0.20251013234738-63d1a5100f82 h1:6/3JGEh1C88g7m+qzzTbl3A0FtsLguXieqofVLU/JAo=
|
||||
golang.org/x/net v0.46.1-0.20251013234738-63d1a5100f82/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.25.0 h1:CY4y7XT9v0cRI9oupztF8AgiIu99L/ksR/Xp/6jrZ70=
|
||||
golang.org/x/oauth2 v0.25.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||
golang.org/x/oauth2 v0.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo=
|
||||
golang.org/x/oauth2 v0.33.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@ -582,8 +616,8 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
|
||||
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
|
||||
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@ -592,33 +626,40 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20190419153524-e8e3143a4f4a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190531175056-4c3a928424d2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
|
||||
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
|
||||
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q=
|
||||
golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
|
||||
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
|
||||
golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
|
||||
golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
||||
golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
|
||||
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
|
||||
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
|
||||
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
||||
golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
@ -633,16 +674,16 @@ golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgw
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg=
|
||||
golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s=
|
||||
golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE=
|
||||
golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo=
|
||||
gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0=
|
||||
gonum.org/v1/gonum v0.15.1 h1:FNy7N6OUZVUaWG9pTiD+jlhdQ3lMP+/LcTpJ6+a8sQ0=
|
||||
gonum.org/v1/gonum v0.15.1/go.mod h1:eZTZuRFrzu5pcyjN5wJhcIhnUdNijYxX1T2IcrOGY0o=
|
||||
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||
gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw=
|
||||
gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
@ -651,8 +692,11 @@ google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoA
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250102185135-69823020774d h1:xJJRGY7TJcvIlpSrN3K6LAWgNFUILlO+OMAqtg9aqnw=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250102185135-69823020774d/go.mod h1:3ENsm/5D1mzDyhpzeRi1NR784I0BcofWBoSc5QqqMK4=
|
||||
google.golang.org/genproto v0.0.0-20250603155806-513f23925822 h1:rHWScKit0gvAPuOnu87KpaYtjK5zBMLcULh7gxkCXu4=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8 h1:mepRgnBZa07I4TRuomDE4sTIYieg/osKmzIf4USdWS4=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8/go.mod h1:fDMmzKV90WSg1NbozdqrE64fkuTv6mlq2zxo9ad+3yo=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8 h1:M1rk8KBnUsBDg1oPGHNCxG4vc1f49epmTO7xscSajMk=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
@ -660,8 +704,8 @@ google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8
|
||||
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
|
||||
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||
google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
|
||||
google.golang.org/grpc v1.69.4 h1:MF5TftSMkd8GLw/m0KM6V8CMOCY6NZ1NQDPGFgbTt4A=
|
||||
google.golang.org/grpc v1.69.4/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4=
|
||||
google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM=
|
||||
google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
@ -675,8 +719,8 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
|
||||
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
||||
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
|
||||
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
@ -696,7 +740,6 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C
|
||||
gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gorm.io/driver/postgres v1.5.11 h1:ubBVAfbKEUld/twyKZ0IYn9rSQh448EdelLYk9Mv314=
|
||||
@ -705,6 +748,8 @@ gorm.io/driver/sqlite v1.6.0 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ=
|
||||
gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8=
|
||||
gorm.io/gorm v1.30.0 h1:qbT5aPv1UH8gI99OsRlvDToLxW5zR7FzS9acZDOZcgs=
|
||||
gorm.io/gorm v1.30.0/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE=
|
||||
gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=
|
||||
gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
howett.net/plist v0.0.0-20181124034731-591f970eefbb/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0=
|
||||
|
||||
@ -2004,7 +2004,14 @@ func (r *queryResolver) Search(ctx context.Context, query string, limit *int32,
|
||||
|
||||
params := domainsearch.SearchParams{
|
||||
Query: query,
|
||||
Filters: searchFilters,
|
||||
Filters: domainsearch.SearchFilters{
|
||||
Languages: searchFilters.Languages,
|
||||
Tags: searchFilters.Tags,
|
||||
Categories: searchFilters.Categories,
|
||||
Authors: searchFilters.Authors,
|
||||
DateFrom: searchFilters.DateFrom,
|
||||
DateTo: searchFilters.DateTo,
|
||||
},
|
||||
Limit: pageSize,
|
||||
Offset: (page - 1) * pageSize,
|
||||
}
|
||||
@ -2014,39 +2021,45 @@ func (r *queryResolver) Search(ctx context.Context, query string, limit *int32,
|
||||
}
|
||||
|
||||
var works []*model.Work
|
||||
for _, w := range results.Works {
|
||||
works = append(works, &model.Work{
|
||||
ID: fmt.Sprintf("%d", w.ID),
|
||||
Name: w.Title,
|
||||
Language: w.Language,
|
||||
})
|
||||
}
|
||||
|
||||
var translations []*model.Translation
|
||||
for _, t := range results.Translations {
|
||||
translations = append(translations, &model.Translation{
|
||||
ID: fmt.Sprintf("%d", t.ID),
|
||||
Name: t.Title,
|
||||
Language: t.Language,
|
||||
Content: &t.Content,
|
||||
WorkID: fmt.Sprintf("%d", t.TranslatableID),
|
||||
var authors []*model.Author
|
||||
|
||||
for _, item := range results.Results {
|
||||
switch item.Type {
|
||||
case "Work":
|
||||
if work, ok := item.Entity.(domain.Work); ok {
|
||||
works = append(works, &model.Work{
|
||||
ID: fmt.Sprintf("%d", work.ID),
|
||||
Name: work.Title,
|
||||
Language: work.Language,
|
||||
})
|
||||
}
|
||||
|
||||
var authors []*model.Author
|
||||
for _, a := range results.Authors {
|
||||
authors = append(authors, &model.Author{
|
||||
ID: fmt.Sprintf("%d", a.ID),
|
||||
Name: a.Name,
|
||||
Language: a.Language,
|
||||
case "Translation":
|
||||
if translation, ok := item.Entity.(domain.Translation); ok {
|
||||
translations = append(translations, &model.Translation{
|
||||
ID: fmt.Sprintf("%d", translation.ID),
|
||||
Name: translation.Title,
|
||||
Language: translation.Language,
|
||||
Content: &translation.Content,
|
||||
WorkID: fmt.Sprintf("%d", translation.TranslatableID),
|
||||
})
|
||||
}
|
||||
case "Author":
|
||||
if author, ok := item.Entity.(domain.Author); ok {
|
||||
authors = append(authors, &model.Author{
|
||||
ID: fmt.Sprintf("%d", author.ID),
|
||||
Name: author.Name,
|
||||
Language: author.Language,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &model.SearchResults{
|
||||
Works: works,
|
||||
Translations: translations,
|
||||
Authors: authors,
|
||||
Total: int32(results.Total),
|
||||
Total: int32(results.TotalResults),
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
21
internal/adapters/graphql/schema/author.graphqls
Normal file
21
internal/adapters/graphql/schema/author.graphqls
Normal file
@ -0,0 +1,21 @@
|
||||
type Query {
|
||||
author(id: ID!): Author
|
||||
authors(limit: Int, offset: Int, search: String, countryId: ID): [Author!]
|
||||
}
|
||||
|
||||
type Mutation {
|
||||
createAuthor(input: AuthorInput!): Author!
|
||||
updateAuthor(id: ID!, input: AuthorInput!): Author!
|
||||
deleteAuthor(id: ID!): Boolean!
|
||||
}
|
||||
|
||||
input AuthorInput {
|
||||
name: String!
|
||||
}
|
||||
|
||||
type Author {
|
||||
id: ID!
|
||||
name: String!
|
||||
language: String!
|
||||
biography: String
|
||||
}
|
||||
26
internal/adapters/graphql/schema/search.graphqls
Normal file
26
internal/adapters/graphql/schema/search.graphqls
Normal file
@ -0,0 +1,26 @@
|
||||
type Query {
|
||||
search(query: String!, limit: Int, offset: Int, filters: SearchFilters): SearchResults!
|
||||
}
|
||||
|
||||
input SearchFilters {
|
||||
types: [String!]
|
||||
languages: [String!]
|
||||
categories: [String!]
|
||||
tags: [String!]
|
||||
authors: [String!]
|
||||
dateFrom: String
|
||||
dateTo: String
|
||||
}
|
||||
|
||||
type SearchResults {
|
||||
items: [SearchResultItem!]!
|
||||
total: Int!
|
||||
}
|
||||
|
||||
type SearchResultItem {
|
||||
type: String!
|
||||
work: Work
|
||||
translation: Translation
|
||||
author: Author
|
||||
score: Float!
|
||||
}
|
||||
25
internal/adapters/graphql/schema/translation.graphqls
Normal file
25
internal/adapters/graphql/schema/translation.graphqls
Normal file
@ -0,0 +1,25 @@
|
||||
type Query {
|
||||
translation(id: ID!): Translation
|
||||
translations(workId: ID!, language: String, limit: Int, offset: Int): [Translation!]
|
||||
}
|
||||
|
||||
type Mutation {
|
||||
createTranslation(input: TranslationInput!): Translation!
|
||||
updateTranslation(id: ID!, input: TranslationInput!): Translation!
|
||||
deleteTranslation(id: ID!): Boolean!
|
||||
}
|
||||
|
||||
input TranslationInput {
|
||||
name: String!
|
||||
language: String!
|
||||
content: String
|
||||
workId: ID!
|
||||
}
|
||||
|
||||
type Translation {
|
||||
id: ID!
|
||||
name: String!
|
||||
language: String!
|
||||
content: String
|
||||
workId: ID!
|
||||
}
|
||||
23
internal/adapters/graphql/schema/work.graphqls
Normal file
23
internal/adapters/graphql/schema/work.graphqls
Normal file
@ -0,0 +1,23 @@
|
||||
type Query {
|
||||
work(id: ID!): Work
|
||||
works(limit: Int, offset: Int, language: String, authorId: ID, categoryId: ID, tagId: ID, search: String): [Work!]
|
||||
}
|
||||
|
||||
type Mutation {
|
||||
createWork(input: WorkInput!): Work!
|
||||
updateWork(id: ID!, input: WorkInput!): Work!
|
||||
deleteWork(id: ID!): Boolean!
|
||||
}
|
||||
|
||||
input WorkInput {
|
||||
name: String!
|
||||
language: String!
|
||||
content: String
|
||||
}
|
||||
|
||||
type Work {
|
||||
id: ID!
|
||||
name: String!
|
||||
language: String!
|
||||
content: String
|
||||
}
|
||||
@ -154,12 +154,12 @@ func (m *mockSearchClient) IndexWork(ctx context.Context, work *domain.Work, pip
|
||||
args := m.Called(ctx, work, pipeline)
|
||||
return args.Error(0)
|
||||
}
|
||||
func (m *mockSearchClient) Search(ctx context.Context, params domainsearch.SearchParams) (*domain.SearchResults, error) {
|
||||
func (m *mockSearchClient) Search(ctx context.Context, params domainsearch.SearchParams) (*domainsearch.SearchResults, error) {
|
||||
args := m.Called(ctx, params)
|
||||
if args.Get(0) == nil {
|
||||
return nil, args.Error(1)
|
||||
}
|
||||
return args.Get(0).(*domain.SearchResults), args.Error(1)
|
||||
return args.Get(0).(*domainsearch.SearchResults), args.Error(1)
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -1,95 +0,0 @@
|
||||
package search
|
||||
|
||||
import (
|
||||
"context"
|
||||
"tercul/internal/domain"
|
||||
"tercul/internal/platform/search"
|
||||
)
|
||||
|
||||
// BleveSearchService provides keyword and exact-match search capabilities
|
||||
// Complements Weaviate's vector/semantic search with traditional full-text search
|
||||
type BleveSearchService interface {
|
||||
IndexTranslation(ctx context.Context, translation domain.Translation) error
|
||||
IndexAllTranslations(ctx context.Context) error
|
||||
SearchTranslations(ctx context.Context, query string, filters map[string]string, limit int) ([]TranslationSearchResult, error)
|
||||
}
|
||||
|
||||
type TranslationSearchResult struct {
|
||||
ID uint
|
||||
Score float64
|
||||
Title string
|
||||
Content string
|
||||
Language string
|
||||
TranslatableID uint
|
||||
TranslatableType string
|
||||
}
|
||||
|
||||
type bleveSearchService struct {
|
||||
translationRepo domain.TranslationRepository
|
||||
}
|
||||
|
||||
func NewBleveSearchService(translationRepo domain.TranslationRepository) BleveSearchService {
|
||||
return &bleveSearchService{
|
||||
translationRepo: translationRepo,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *bleveSearchService) IndexTranslation(ctx context.Context, translation domain.Translation) error {
|
||||
return search.BleveClient.AddTranslation(translation)
|
||||
}
|
||||
|
||||
func (s *bleveSearchService) IndexAllTranslations(ctx context.Context) error {
|
||||
// Get all translations from the database and index them
|
||||
translations, err := s.translationRepo.ListAll(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, translation := range translations {
|
||||
if err := search.BleveClient.AddTranslation(translation); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *bleveSearchService) SearchTranslations(ctx context.Context, query string, filters map[string]string, limit int) ([]TranslationSearchResult, error) {
|
||||
results, err := search.BleveClient.Search(query, filters, limit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var searchResults []TranslationSearchResult
|
||||
for _, hit := range results.Hits {
|
||||
// Extract fields from the hit
|
||||
fields := hit.Fields
|
||||
result := TranslationSearchResult{
|
||||
Score: hit.Score,
|
||||
}
|
||||
|
||||
// Safely extract fields with type assertions
|
||||
if id, ok := fields["id"].(float64); ok {
|
||||
result.ID = uint(id)
|
||||
}
|
||||
if title, ok := fields["title"].(string); ok {
|
||||
result.Title = title
|
||||
}
|
||||
if content, ok := fields["content"].(string); ok {
|
||||
result.Content = content
|
||||
}
|
||||
if language, ok := fields["language"].(string); ok {
|
||||
result.Language = language
|
||||
}
|
||||
if translatableID, ok := fields["translatable_id"].(float64); ok {
|
||||
result.TranslatableID = uint(translatableID)
|
||||
}
|
||||
if translatableType, ok := fields["translatable_type"].(string); ok {
|
||||
result.TranslatableType = translatableType
|
||||
}
|
||||
|
||||
searchResults = append(searchResults, result)
|
||||
}
|
||||
|
||||
return searchResults, nil
|
||||
}
|
||||
@ -10,7 +10,7 @@ import (
|
||||
|
||||
// Service is the application service for searching.
|
||||
type Service interface {
|
||||
Search(ctx context.Context, params domainsearch.SearchParams) (*domain.SearchResults, error)
|
||||
Search(ctx context.Context, params domainsearch.SearchParams) (*domainsearch.SearchResults, error)
|
||||
IndexWork(ctx context.Context, work domain.Work) error
|
||||
}
|
||||
|
||||
@ -28,7 +28,7 @@ func NewService(searchClient domainsearch.SearchClient, localization *localizati
|
||||
}
|
||||
|
||||
// Search performs a search across all searchable entities.
|
||||
func (s *service) Search(ctx context.Context, params domainsearch.SearchParams) (*domain.SearchResults, error) {
|
||||
func (s *service) Search(ctx context.Context, params domainsearch.SearchParams) (*domainsearch.SearchResults, error) {
|
||||
return s.searchClient.Search(ctx, params)
|
||||
}
|
||||
|
||||
|
||||
@ -42,12 +42,12 @@ type mockWeaviateWrapper struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (m *mockWeaviateWrapper) Search(ctx context.Context, params domainsearch.SearchParams) (*domain.SearchResults, error) {
|
||||
func (m *mockWeaviateWrapper) Search(ctx context.Context, params domainsearch.SearchParams) (*domainsearch.SearchResults, error) {
|
||||
args := m.Called(ctx, params)
|
||||
if args.Get(0) == nil {
|
||||
return nil, args.Error(1)
|
||||
}
|
||||
return args.Get(0).(*domain.SearchResults), args.Error(1)
|
||||
return args.Get(0).(*domainsearch.SearchResults), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *mockWeaviateWrapper) IndexWork(ctx context.Context, work *domain.Work, content string) error {
|
||||
@ -63,16 +63,20 @@ func TestSearchService_Search(t *testing.T) {
|
||||
|
||||
ctx := context.Background()
|
||||
testQuery := "test query"
|
||||
testFilters := domain.SearchFilters{
|
||||
testFilters := domainsearch.SearchFilters{
|
||||
Languages: []string{"en"},
|
||||
Authors: []string{"1"},
|
||||
Tags: []string{"test-tag"},
|
||||
Categories: []string{"test-category"},
|
||||
}
|
||||
expectedResults := &domain.SearchResults{
|
||||
Works: []domain.Work{{Title: "Test Work"}},
|
||||
Authors: []domain.Author{{Name: "Test Author"}},
|
||||
Total: 2,
|
||||
expectedResults := &domainsearch.SearchResults{
|
||||
Results: []domainsearch.SearchResultItem{
|
||||
{Type: "Work", Entity: domain.Work{Title: "Test Work"}, Score: 0.9},
|
||||
{Type: "Author", Entity: domain.Author{Name: "Test Author"}, Score: 0.8},
|
||||
},
|
||||
TotalResults: 2,
|
||||
Limit: 10,
|
||||
Offset: 0,
|
||||
}
|
||||
|
||||
params := domainsearch.SearchParams{
|
||||
|
||||
@ -934,6 +934,7 @@ type Embedding struct {
|
||||
|
||||
// SearchFilters defines the available filters for a search query.
|
||||
type SearchFilters struct {
|
||||
Types []string
|
||||
Languages []string
|
||||
Categories []string
|
||||
Tags []string
|
||||
@ -942,12 +943,38 @@ type SearchFilters struct {
|
||||
DateTo *time.Time
|
||||
}
|
||||
|
||||
// SearchMode defines the search mode (e.g., hybrid, bm25, vector).
|
||||
type SearchMode string
|
||||
|
||||
const (
|
||||
SearchModeHybrid SearchMode = "hybrid"
|
||||
SearchModeBM25 SearchMode = "bm25"
|
||||
SearchModeVector SearchMode = "vector"
|
||||
)
|
||||
|
||||
// SearchParams defines the parameters for a search query.
|
||||
type SearchParams struct {
|
||||
Query string
|
||||
Mode SearchMode
|
||||
Filters SearchFilters
|
||||
Limit int
|
||||
Offset int
|
||||
Concepts []string
|
||||
}
|
||||
|
||||
// SearchResultItem represents a single item in the search results.
|
||||
type SearchResultItem struct {
|
||||
Type string
|
||||
Entity interface{}
|
||||
Score float64
|
||||
}
|
||||
|
||||
// SearchResults represents the results of a search query.
|
||||
type SearchResults struct {
|
||||
Works []Work
|
||||
Translations []Translation
|
||||
Authors []Author
|
||||
Total int64
|
||||
Results []SearchResultItem
|
||||
TotalResults int64
|
||||
Limit int
|
||||
Offset int
|
||||
}
|
||||
|
||||
// Work-related enums and structs, moved from domain/work/entity.go to break import cycle.
|
||||
|
||||
@ -1,12 +0,0 @@
|
||||
package search
|
||||
|
||||
import (
|
||||
"context"
|
||||
"tercul/internal/domain"
|
||||
)
|
||||
|
||||
// SearchClient defines the interface for a search client.
|
||||
type SearchClient interface {
|
||||
Search(ctx context.Context, params SearchParams) (*domain.SearchResults, error)
|
||||
IndexWork(ctx context.Context, work *domain.Work, content string) error
|
||||
}
|
||||
@ -1,9 +1,25 @@
|
||||
package search
|
||||
|
||||
import (
|
||||
"context"
|
||||
"tercul/internal/domain"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
// SearchFilters defines the available filters for a search query.
|
||||
type SearchFilters struct {
|
||||
Types []string
|
||||
Languages []string
|
||||
Categories []string
|
||||
Tags []string
|
||||
Authors []string
|
||||
DateFrom *time.Time
|
||||
DateTo *time.Time
|
||||
}
|
||||
|
||||
// SearchMode defines the search mode (e.g., hybrid, bm25, vector).
|
||||
type SearchMode string
|
||||
|
||||
const (
|
||||
@ -12,10 +28,58 @@ const (
|
||||
SearchModeVector SearchMode = "vector"
|
||||
)
|
||||
|
||||
// SearchParams defines the parameters for a search query.
|
||||
type SearchParams struct {
|
||||
Query string
|
||||
Mode SearchMode
|
||||
Filters domain.SearchFilters
|
||||
Filters SearchFilters
|
||||
Limit int
|
||||
Offset int
|
||||
Concepts []string
|
||||
}
|
||||
|
||||
// SearchResultItem represents a single item in the search results.
|
||||
type SearchResultItem struct {
|
||||
Type string
|
||||
Entity interface{}
|
||||
Score float64
|
||||
}
|
||||
|
||||
// SearchResults represents the results of a search query.
|
||||
type SearchResults struct {
|
||||
Results []SearchResultItem
|
||||
TotalResults int64
|
||||
Limit int
|
||||
Offset int
|
||||
}
|
||||
|
||||
// SearchClient defines the interface for a search client.
|
||||
type SearchClient interface {
|
||||
Search(ctx context.Context, params SearchParams) (*SearchResults, error)
|
||||
IndexWork(ctx context.Context, work *domain.Work, content string) error
|
||||
}
|
||||
|
||||
// MockSearchClient is a mock implementation of the SearchClient interface.
|
||||
type MockSearchClient struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
// NewMockSearchClient creates a new mock search client.
|
||||
func NewMockSearchClient() *MockSearchClient {
|
||||
return &MockSearchClient{}
|
||||
}
|
||||
|
||||
// Search is a mock implementation of the Search method.
|
||||
func (m *MockSearchClient) Search(ctx context.Context, params SearchParams) (*SearchResults, error) {
|
||||
args := m.Called(ctx, params)
|
||||
if args.Get(0) == nil {
|
||||
return nil, args.Error(1)
|
||||
}
|
||||
return args.Get(0).(*SearchResults), args.Error(1)
|
||||
}
|
||||
|
||||
// IndexWork is a mock implementation of the IndexWork method.
|
||||
func (m *MockSearchClient) IndexWork(ctx context.Context, work *domain.Work, content string) error {
|
||||
args := m.Called(ctx, work, content)
|
||||
return args.Error(0)
|
||||
}
|
||||
|
||||
@ -1,27 +0,0 @@
|
||||
package search
|
||||
|
||||
import (
|
||||
"log"
|
||||
"tercul/internal/platform/config"
|
||||
"tercul/pkg/search/bleve"
|
||||
)
|
||||
|
||||
var BleveClient *bleve.BleveClient
|
||||
|
||||
func InitBleve() {
|
||||
var err error
|
||||
BleveClient, err = bleve.NewBleveClient(config.Cfg.BleveIndexPath)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to initialize Bleve: %v", err)
|
||||
}
|
||||
|
||||
log.Println("Connected to Bleve successfully.")
|
||||
}
|
||||
|
||||
func CloseBleve() {
|
||||
if BleveClient != nil {
|
||||
if err := BleveClient.Close(); err != nil {
|
||||
log.Printf("Error closing Bleve client: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,38 +0,0 @@
|
||||
package search
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"tercul/internal/domain"
|
||||
"time"
|
||||
|
||||
"github.com/weaviate/weaviate-go-client/v5/weaviate"
|
||||
)
|
||||
|
||||
var Client *weaviate.Client
|
||||
|
||||
// UpsertWork inserts or updates a Work object in Weaviate
|
||||
func UpsertWork(client *weaviate.Client, work domain.Work) error {
|
||||
// Create a properties map with the fields that exist in the Work model
|
||||
properties := map[string]interface{}{
|
||||
"language": work.Language,
|
||||
"title": work.Title,
|
||||
"description": work.Description,
|
||||
"status": work.Status,
|
||||
// Add timestamps
|
||||
"createdAt": work.CreatedAt.Format(time.RFC3339),
|
||||
"updatedAt": work.UpdatedAt.Format(time.RFC3339),
|
||||
}
|
||||
|
||||
_, err := client.Data().Creator().
|
||||
WithClassName("Work").
|
||||
WithID(fmt.Sprintf("%d", work.ID)). // Use the ID from the Work model
|
||||
WithProperties(properties).
|
||||
Do(context.Background())
|
||||
|
||||
if err != nil {
|
||||
log.Printf("Weaviate Upsert Error for Work ID %d: %v", work.ID, err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
@ -2,57 +2,31 @@ package search
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"sort"
|
||||
"strconv"
|
||||
"tercul/internal/domain"
|
||||
domainsearch "tercul/internal/domain/search"
|
||||
"time"
|
||||
|
||||
"github.com/weaviate/weaviate-go-client/v5/weaviate"
|
||||
"github.com/weaviate/weaviate-go-client/v5/weaviate/filters"
|
||||
"github.com/weaviate/weaviate-go-client/v5/weaviate/graphql"
|
||||
"github.com/weaviate/weaviate/entities/models"
|
||||
)
|
||||
|
||||
type WeaviateWrapper interface {
|
||||
Search(ctx context.Context, params domainsearch.SearchParams) (*domain.SearchResults, error)
|
||||
IndexWork(ctx context.Context, work *domain.Work, content string) error
|
||||
}
|
||||
|
||||
type weaviateWrapper struct {
|
||||
client *weaviate.Client
|
||||
host string
|
||||
searchAlpha float64
|
||||
}
|
||||
|
||||
func NewWeaviateWrapper(client *weaviate.Client, host string, searchAlpha float64) WeaviateWrapper {
|
||||
// NewWeaviateWrapper creates a new WeaviateWrapper that implements the SearchClient interface.
|
||||
func NewWeaviateWrapper(client *weaviate.Client, host string, searchAlpha float64) domainsearch.SearchClient {
|
||||
return &weaviateWrapper{client: client, host: host, searchAlpha: searchAlpha}
|
||||
}
|
||||
|
||||
const (
|
||||
DefaultLimit = 20
|
||||
MaxLimit = 100
|
||||
)
|
||||
|
||||
const (
|
||||
WorkClass = "Work"
|
||||
AuthorClass = "Author"
|
||||
TranslationClass = "Translation"
|
||||
)
|
||||
|
||||
func sanitizeLimitOffset(limit, offset int) (int, int) {
|
||||
if limit <= 0 {
|
||||
limit = DefaultLimit
|
||||
}
|
||||
if limit > MaxLimit {
|
||||
limit = MaxLimit
|
||||
}
|
||||
if offset < 0 {
|
||||
offset = 0
|
||||
}
|
||||
return limit, offset
|
||||
}
|
||||
|
||||
// hybridAlpha returns the alpha value for hybrid search, clamped between 0 and 1.
|
||||
func (w *weaviateWrapper) hybridAlpha() float32 {
|
||||
if w.searchAlpha < 0 {
|
||||
return 0
|
||||
@ -63,109 +37,147 @@ func (w *weaviateWrapper) hybridAlpha() float32 {
|
||||
return float32(w.searchAlpha)
|
||||
}
|
||||
|
||||
func (w *weaviateWrapper) Search(ctx context.Context, params domainsearch.SearchParams) (*domain.SearchResults, error) {
|
||||
results := &domain.SearchResults{
|
||||
Works: []domain.Work{},
|
||||
Translations: []domain.Translation{},
|
||||
Authors: []domain.Author{},
|
||||
// Search performs a multi-class search against the Weaviate instance.
|
||||
func (w *weaviateWrapper) Search(ctx context.Context, params domainsearch.SearchParams) (*domainsearch.SearchResults, error) {
|
||||
allResults := make([]domainsearch.SearchResultItem, 0)
|
||||
|
||||
// Determine which entity types to search for. If a type filter is present, use it.
|
||||
var searchTypes []string
|
||||
if len(params.Filters.Types) > 0 {
|
||||
searchTypes = params.Filters.Types
|
||||
} else {
|
||||
searchTypes = []string{"Work", "Translation", "Author"}
|
||||
}
|
||||
|
||||
limit, offset := sanitizeLimitOffset(params.Limit, params.Offset)
|
||||
|
||||
workFields := []graphql.Field{
|
||||
{Name: "title"}, {Name: "description"}, {Name: "language"}, {Name: "status"}, {Name: "publishedAt"},
|
||||
{Name: "_additional", Fields: []graphql.Field{{Name: "id"}, {Name: "score"}, {Name: "meta", Fields: []graphql.Field{{Name: "count"}}}}},
|
||||
}
|
||||
authorFields := []graphql.Field{
|
||||
{Name: "name"},
|
||||
{Name: "_additional", Fields: []graphql.Field{{Name: "id"}, {Name: "score"}, {Name: "meta", Fields: []graphql.Field{{Name: "count"}}}}},
|
||||
}
|
||||
translationFields := []graphql.Field{
|
||||
{Name: "title"}, {Name: "language"},
|
||||
{Name: "_additional", Fields: []graphql.Field{{Name: "id"}, {Name: "score"}, {Name: "meta", Fields: []graphql.Field{{Name: "count"}}}}},
|
||||
}
|
||||
|
||||
searcher := w.client.GraphQL().Get().
|
||||
WithFields(graphql.Field{Name: "... on Work", Fields: workFields}).
|
||||
WithFields(graphql.Field{Name: "... on Author", Fields: authorFields}).
|
||||
WithFields(graphql.Field{Name: "... on Translation", Fields: translationFields}).
|
||||
WithLimit(limit).
|
||||
WithOffset(offset)
|
||||
|
||||
switch params.Mode {
|
||||
case domainsearch.SearchModeHybrid:
|
||||
hybrid := w.client.GraphQL().HybridArgumentBuilder().
|
||||
WithQuery(params.Query).
|
||||
WithAlpha(w.hybridAlpha()).
|
||||
WithProperties([]string{"title", "description", "name"})
|
||||
searcher.WithHybrid(hybrid)
|
||||
case domainsearch.SearchModeBM25:
|
||||
bm25 := w.client.GraphQL().Bm25ArgBuilder().
|
||||
WithQuery(params.Query).
|
||||
WithProperties([]string{"title", "description", "name"}...)
|
||||
searcher.WithBM25(bm25)
|
||||
}
|
||||
|
||||
response, err := searcher.Do(ctx)
|
||||
if contains(searchTypes, "Work") {
|
||||
workResults, err := w.searchWorks(ctx, ¶ms)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
get, ok := response.Data["Get"].(map[string]interface{})
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid response data")
|
||||
allResults = append(allResults, workResults...)
|
||||
}
|
||||
|
||||
for className, classData := range get {
|
||||
for _, item := range classData.([]interface{}) {
|
||||
itemMap, ok := item.(map[string]interface{})
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
switch className {
|
||||
case "Work":
|
||||
work, err := mapToWork(itemMap)
|
||||
if contains(searchTypes, "Translation") {
|
||||
translationResults, err := w.searchTranslations(ctx, ¶ms)
|
||||
if err != nil {
|
||||
log.Printf("Error mapping work: %v", err)
|
||||
continue
|
||||
}
|
||||
results.Works = append(results.Works, work)
|
||||
case "Author":
|
||||
author, err := mapToAuthor(itemMap)
|
||||
if err != nil {
|
||||
log.Printf("Error mapping author: %v", err)
|
||||
continue
|
||||
}
|
||||
results.Authors = append(results.Authors, author)
|
||||
case "Translation":
|
||||
translation, err := mapToTranslation(itemMap)
|
||||
if err != nil {
|
||||
log.Printf("Error mapping translation: %v", err)
|
||||
continue
|
||||
}
|
||||
results.Translations = append(results.Translations, translation)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
allResults = append(allResults, translationResults...)
|
||||
}
|
||||
|
||||
meta, ok := response.Data["Meta"].(map[string]interface{})
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid meta data")
|
||||
if contains(searchTypes, "Author") {
|
||||
authorResults, err := w.searchAuthors(ctx, ¶ms)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
count, ok := meta["count"].(float64)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid count data")
|
||||
allResults = append(allResults, authorResults...)
|
||||
}
|
||||
results.Total = int64(count)
|
||||
|
||||
return results, nil
|
||||
// --- Sort by Relevance Score ---
|
||||
sort.Slice(allResults, func(i, j int) bool {
|
||||
return allResults[i].Score > allResults[j].Score // Descending order
|
||||
})
|
||||
|
||||
totalResults := int64(len(allResults))
|
||||
|
||||
// --- Paginate In-Memory ---
|
||||
paginatedResults := make([]domainsearch.SearchResultItem, 0)
|
||||
start := params.Offset
|
||||
end := params.Offset + params.Limit
|
||||
|
||||
if start < len(allResults) {
|
||||
if end > len(allResults) {
|
||||
end = len(allResults)
|
||||
}
|
||||
paginatedResults = allResults[start:end]
|
||||
}
|
||||
|
||||
return &domainsearch.SearchResults{
|
||||
Results: paginatedResults,
|
||||
TotalResults: totalResults,
|
||||
Limit: params.Limit,
|
||||
Offset: params.Offset,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (w *weaviateWrapper) searchWorks(ctx context.Context, params *domainsearch.SearchParams) ([]domainsearch.SearchResultItem, error) {
|
||||
fields := []graphql.Field{
|
||||
{Name: "db_id"}, {Name: "title"}, {Name: "description"}, {Name: "language"},
|
||||
{Name: "status"}, {Name: "createdAt"}, {Name: "updatedAt"}, {Name: "tags"},
|
||||
{Name: "_additional", Fields: []graphql.Field{{Name: "score"}}},
|
||||
}
|
||||
searcher := w.client.GraphQL().Get().WithClassName("Work").WithFields(fields...)
|
||||
w.addSearchArguments(searcher, params, "Work", []string{"title", "description"})
|
||||
resp, err := searcher.Do(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to search works: %w", err)
|
||||
}
|
||||
return w.parseGraphQLResponse(resp, "Work")
|
||||
}
|
||||
|
||||
func (w *weaviateWrapper) searchTranslations(ctx context.Context, params *domainsearch.SearchParams) ([]domainsearch.SearchResultItem, error) {
|
||||
fields := []graphql.Field{
|
||||
{Name: "db_id"}, {Name: "title"}, {Name: "content"}, {Name: "language"}, {Name: "status"},
|
||||
{Name: "_additional", Fields: []graphql.Field{{Name: "score"}}},
|
||||
}
|
||||
searcher := w.client.GraphQL().Get().WithClassName("Translation").WithFields(fields...)
|
||||
w.addSearchArguments(searcher, params, "Translation", []string{"title", "content"})
|
||||
resp, err := searcher.Do(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to search translations: %w", err)
|
||||
}
|
||||
return w.parseGraphQLResponse(resp, "Translation")
|
||||
}
|
||||
|
||||
func (w *weaviateWrapper) searchAuthors(ctx context.Context, params *domainsearch.SearchParams) ([]domainsearch.SearchResultItem, error) {
|
||||
fields := []graphql.Field{
|
||||
{Name: "db_id"}, {Name: "name"}, {Name: "biography"},
|
||||
{Name: "_additional", Fields: []graphql.Field{{Name: "score"}}},
|
||||
}
|
||||
searcher := w.client.GraphQL().Get().WithClassName("Author").WithFields(fields...)
|
||||
w.addSearchArguments(searcher, params, "Author", []string{"name", "biography"})
|
||||
// Authors should not be filtered by language
|
||||
if len(params.Filters.Languages) > 0 {
|
||||
return []domainsearch.SearchResultItem{}, nil
|
||||
}
|
||||
resp, err := searcher.Do(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to search authors: %w", err)
|
||||
}
|
||||
return w.parseGraphQLResponse(resp, "Author")
|
||||
}
|
||||
|
||||
func (w *weaviateWrapper) addSearchArguments(searcher *graphql.GetBuilder, params *domainsearch.SearchParams, className string, searchFields []string) {
|
||||
if params.Query != "" || len(params.Concepts) > 0 {
|
||||
switch params.Mode {
|
||||
case domainsearch.SearchModeBM25:
|
||||
bm25 := w.client.GraphQL().Bm25ArgBuilder().WithQuery(params.Query).WithProperties(searchFields...)
|
||||
searcher.WithBM25(bm25)
|
||||
case domainsearch.SearchModeVector:
|
||||
nearText := w.client.GraphQL().NearTextArgBuilder().WithConcepts(params.Concepts)
|
||||
searcher.WithNearText(nearText)
|
||||
default:
|
||||
hybrid := w.client.GraphQL().HybridArgumentBuilder().
|
||||
WithQuery(params.Query).
|
||||
WithAlpha(w.hybridAlpha())
|
||||
searcher.WithHybrid(hybrid)
|
||||
}
|
||||
}
|
||||
where := buildWhereFilter(¶ms.Filters, className)
|
||||
if where != nil {
|
||||
searcher.WithWhere(where)
|
||||
}
|
||||
}
|
||||
|
||||
// buildWhereFilter constructs the 'where' clause for the GraphQL query based on the search parameters.
|
||||
func buildWhereFilter(searchFilters *domainsearch.SearchFilters, className string) *filters.WhereBuilder {
|
||||
if searchFilters == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *weaviateWrapper) buildWhereFilter(searchFilters domain.SearchFilters, className string) *filters.WhereBuilder {
|
||||
operands := make([]*filters.WhereBuilder, 0)
|
||||
|
||||
if len(searchFilters.Languages) > 0 {
|
||||
if (className == "Work" || className == "Translation") && len(searchFilters.Languages) > 0 {
|
||||
operands = append(operands, filters.Where().
|
||||
WithPath([]string{"language"}).
|
||||
WithOperator(filters.ContainsAny).
|
||||
@ -173,52 +185,31 @@ func (w *weaviateWrapper) buildWhereFilter(searchFilters domain.SearchFilters, c
|
||||
}
|
||||
|
||||
if className == "Work" {
|
||||
if searchFilters.DateFrom != nil {
|
||||
if searchFilters.DateFrom != nil && !searchFilters.DateFrom.IsZero() {
|
||||
operands = append(operands, filters.Where().
|
||||
WithPath([]string{"publishedAt"}).
|
||||
WithPath([]string{"createdAt"}).
|
||||
WithOperator(filters.GreaterThanEqual).
|
||||
WithValueDate(*searchFilters.DateFrom))
|
||||
}
|
||||
|
||||
if searchFilters.DateTo != nil {
|
||||
if searchFilters.DateTo != nil && !searchFilters.DateTo.IsZero() {
|
||||
operands = append(operands, filters.Where().
|
||||
WithPath([]string{"publishedAt"}).
|
||||
WithPath([]string{"createdAt"}).
|
||||
WithOperator(filters.LessThanEqual).
|
||||
WithValueDate(*searchFilters.DateTo))
|
||||
}
|
||||
|
||||
if len(searchFilters.Authors) > 0 {
|
||||
authorOperands := make([]*filters.WhereBuilder, len(searchFilters.Authors))
|
||||
for i, author := range searchFilters.Authors {
|
||||
authorOperands[i] = filters.Where().
|
||||
WithPath([]string{"authors", "Author", "id"}).
|
||||
WithOperator(filters.Equal).
|
||||
WithValueText(fmt.Sprintf("weaviate://%s/Author/%s", w.host, author))
|
||||
}
|
||||
operands = append(operands, filters.Where().WithOperator(filters.Or).WithOperands(authorOperands))
|
||||
}
|
||||
|
||||
if len(searchFilters.Tags) > 0 {
|
||||
tagOperands := make([]*filters.WhereBuilder, len(searchFilters.Tags))
|
||||
for i, tag := range searchFilters.Tags {
|
||||
tagOperands[i] = filters.Where().
|
||||
WithPath([]string{"tags", "Tag", "name"}).
|
||||
WithOperator(filters.Equal).
|
||||
WithValueText(tag)
|
||||
operands = append(operands, filters.Where().
|
||||
WithPath([]string{"tags"}).
|
||||
WithOperator(filters.ContainsAny).
|
||||
WithValueText(searchFilters.Tags...))
|
||||
}
|
||||
operands = append(operands, filters.Where().WithOperator(filters.Or).WithOperands(tagOperands))
|
||||
}
|
||||
|
||||
if len(searchFilters.Categories) > 0 {
|
||||
categoryOperands := make([]*filters.WhereBuilder, len(searchFilters.Categories))
|
||||
for i, category := range searchFilters.Categories {
|
||||
categoryOperands[i] = filters.Where().
|
||||
WithPath([]string{"categories", "Category", "name"}).
|
||||
WithOperator(filters.Equal).
|
||||
WithValueText(category)
|
||||
}
|
||||
operands = append(operands, filters.Where().WithOperator(filters.Or).WithOperands(categoryOperands))
|
||||
}
|
||||
if className == "Author" && len(searchFilters.Authors) > 0 {
|
||||
operands = append(operands, filters.Where().
|
||||
WithPath([]string{"name"}).
|
||||
WithOperator(filters.ContainsAny).
|
||||
WithValueText(searchFilters.Authors...))
|
||||
}
|
||||
|
||||
if len(operands) == 0 {
|
||||
@ -228,127 +219,177 @@ func (w *weaviateWrapper) buildWhereFilter(searchFilters domain.SearchFilters, c
|
||||
return filters.Where().WithOperator(filters.And).WithOperands(operands)
|
||||
}
|
||||
|
||||
func mapToWork(data map[string]interface{}) (domain.Work, error) {
|
||||
work := domain.Work{}
|
||||
|
||||
additional, ok := data["_additional"].(map[string]interface{})
|
||||
if !ok {
|
||||
return work, fmt.Errorf("missing _additional field")
|
||||
}
|
||||
|
||||
idStr, ok := additional["id"].(string)
|
||||
if !ok {
|
||||
return work, fmt.Errorf("missing or invalid id")
|
||||
}
|
||||
id, err := strconv.ParseUint(idStr, 10, 64)
|
||||
if err != nil {
|
||||
return work, fmt.Errorf("failed to parse id: %w", err)
|
||||
}
|
||||
work.ID = uint(id)
|
||||
|
||||
if title, ok := data["title"].(string); ok {
|
||||
work.Title = title
|
||||
}
|
||||
if description, ok := data["description"].(string); ok {
|
||||
work.Description = description
|
||||
}
|
||||
if language, ok := data["language"].(string); ok {
|
||||
work.Language = language
|
||||
}
|
||||
if status, ok := data["status"].(string); ok {
|
||||
work.Status = domain.WorkStatus(status)
|
||||
}
|
||||
if publishedAtStr, ok := data["publishedAt"].(string); ok {
|
||||
publishedAt, err := time.Parse(time.RFC3339, publishedAtStr)
|
||||
if err == nil {
|
||||
work.PublishedAt = &publishedAt
|
||||
}
|
||||
}
|
||||
if score, ok := additional["score"].(float64); ok {
|
||||
work.Score = score
|
||||
}
|
||||
|
||||
return work, nil
|
||||
type weaviateResult struct {
|
||||
Additional struct {
|
||||
Score string `json:"score"`
|
||||
} `json:"_additional"`
|
||||
Properties map[string]interface{} `json:"-"`
|
||||
}
|
||||
|
||||
func mapToAuthor(data map[string]interface{}) (domain.Author, error) {
|
||||
author := domain.Author{}
|
||||
|
||||
additional, ok := data["_additional"].(map[string]interface{})
|
||||
if !ok {
|
||||
return author, fmt.Errorf("missing _additional field")
|
||||
func (r *weaviateResult) UnmarshalJSON(data []byte) error {
|
||||
var raw map[string]interface{}
|
||||
if err := json.Unmarshal(data, &raw); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
idStr, ok := additional["id"].(string)
|
||||
if !ok {
|
||||
return author, fmt.Errorf("missing or invalid id")
|
||||
}
|
||||
id, err := strconv.ParseUint(idStr, 10, 64)
|
||||
if add, ok := raw["_additional"]; ok {
|
||||
addBytes, err := json.Marshal(add)
|
||||
if err != nil {
|
||||
return author, fmt.Errorf("failed to parse id: %w", err)
|
||||
return err
|
||||
}
|
||||
author.ID = uint(id)
|
||||
|
||||
if name, ok := data["name"].(string); ok {
|
||||
author.Name = name
|
||||
if err := json.Unmarshal(addBytes, &r.Additional); err != nil {
|
||||
return err
|
||||
}
|
||||
if score, ok := additional["score"].(float64); ok {
|
||||
author.Score = score
|
||||
}
|
||||
|
||||
return author, nil
|
||||
delete(raw, "_additional")
|
||||
r.Properties = raw
|
||||
return nil
|
||||
}
|
||||
|
||||
func mapToTranslation(data map[string]interface{}) (domain.Translation, error) {
|
||||
translation := domain.Translation{}
|
||||
|
||||
additional, ok := data["_additional"].(map[string]interface{})
|
||||
if !ok {
|
||||
return translation, fmt.Errorf("missing _additional field")
|
||||
}
|
||||
|
||||
idStr, ok := additional["id"].(string)
|
||||
if !ok {
|
||||
return translation, fmt.Errorf("missing or invalid id")
|
||||
}
|
||||
id, err := strconv.ParseUint(idStr, 10, 64)
|
||||
if err != nil {
|
||||
return translation, fmt.Errorf("failed to parse id: %w", err)
|
||||
}
|
||||
translation.ID = uint(id)
|
||||
|
||||
if title, ok := data["title"].(string); ok {
|
||||
translation.Title = title
|
||||
}
|
||||
if language, ok := data["language"].(string); ok {
|
||||
translation.Language = language
|
||||
}
|
||||
if score, ok := additional["score"].(float64); ok {
|
||||
translation.Score = score
|
||||
}
|
||||
|
||||
return translation, nil
|
||||
// Temporary struct to handle the mismatch between Weaviate's string array for tags
|
||||
// and the domain's []*Tag struct.
|
||||
type workWithDBIDAndStringTags struct {
|
||||
domain.Work
|
||||
DBID json.Number `json:"db_id"`
|
||||
Tags []string `json:"tags"` // This captures the tags as strings
|
||||
}
|
||||
|
||||
type translationWithDBID struct {
|
||||
domain.Translation
|
||||
DBID json.Number `json:"db_id"`
|
||||
}
|
||||
type authorWithDBID struct {
|
||||
domain.Author
|
||||
DBID json.Number `json:"db_id"`
|
||||
Biography string `json:"biography"`
|
||||
}
|
||||
|
||||
func (w *weaviateWrapper) parseGraphQLResponse(resp *models.GraphQLResponse, className string) ([]domainsearch.SearchResultItem, error) {
|
||||
var results []domainsearch.SearchResultItem
|
||||
|
||||
get, ok := resp.Data["Get"].(map[string]interface{})
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unexpected response format: 'Get' not found")
|
||||
}
|
||||
|
||||
classData, ok := get[className].([]interface{})
|
||||
if !ok {
|
||||
return results, nil
|
||||
}
|
||||
|
||||
for _, itemData := range classData {
|
||||
itemBytes, err := json.Marshal(itemData)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal search item: %w", err)
|
||||
}
|
||||
|
||||
var tempResult weaviateResult
|
||||
if err := json.Unmarshal(itemBytes, &tempResult); err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal search item: %w", err)
|
||||
}
|
||||
|
||||
score, err := strconv.ParseFloat(tempResult.Additional.Score, 64)
|
||||
if err != nil {
|
||||
// In BM25, score can be nil if not found, treat as 0
|
||||
score = 0
|
||||
}
|
||||
|
||||
propBytes, err := json.Marshal(tempResult.Properties)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal item properties: %w", err)
|
||||
}
|
||||
|
||||
var entity interface{}
|
||||
switch className {
|
||||
case "Work":
|
||||
var tempWork workWithDBIDAndStringTags
|
||||
if err := json.Unmarshal(propBytes, &tempWork); err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal work: %w", err)
|
||||
}
|
||||
id, err := tempWork.DBID.Int64()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse work db_id: %w", err)
|
||||
}
|
||||
tempWork.Work.ID = uint(id)
|
||||
|
||||
// Convert []string to []*domain.Tag
|
||||
finalTags := make([]*domain.Tag, len(tempWork.Tags))
|
||||
for i, tagName := range tempWork.Tags {
|
||||
finalTags[i] = &domain.Tag{Name: tagName}
|
||||
}
|
||||
tempWork.Work.Tags = finalTags
|
||||
|
||||
entity = tempWork.Work
|
||||
case "Translation":
|
||||
var translation translationWithDBID
|
||||
if err := json.Unmarshal(propBytes, &translation); err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal translation: %w", err)
|
||||
}
|
||||
id, err := translation.DBID.Int64()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse translation db_id: %w", err)
|
||||
}
|
||||
translation.Translation.ID = uint(id)
|
||||
entity = translation.Translation
|
||||
case "Author":
|
||||
var author authorWithDBID
|
||||
if err := json.Unmarshal(propBytes, &author); err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal author: %w", err)
|
||||
}
|
||||
id, err := author.DBID.Int64()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse author db_id: %w", err)
|
||||
}
|
||||
author.Author.ID = uint(id)
|
||||
entity = author.Author
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown class name for parsing: %s", className)
|
||||
}
|
||||
|
||||
results = append(results, domainsearch.SearchResultItem{
|
||||
Type: className,
|
||||
Entity: entity,
|
||||
Score: score,
|
||||
})
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (w *weaviateWrapper) IndexWork(ctx context.Context, work *domain.Work, content string) error {
|
||||
// Convert []*domain.Tag to []string
|
||||
tags := make([]string, len(work.Tags))
|
||||
for i, tag := range work.Tags {
|
||||
tags[i] = tag.Name
|
||||
}
|
||||
|
||||
properties := map[string]interface{}{
|
||||
"language": work.Language,
|
||||
"db_id": work.ID,
|
||||
"title": work.Title,
|
||||
"description": work.Description,
|
||||
"language": work.Language,
|
||||
"status": work.Status,
|
||||
"createdAt": work.CreatedAt.Format(time.RFC3339),
|
||||
"updatedAt": work.UpdatedAt.Format(time.RFC3339),
|
||||
}
|
||||
if content != "" {
|
||||
properties["content"] = content
|
||||
"createdAt": work.CreatedAt,
|
||||
"updatedAt": work.UpdatedAt,
|
||||
"tags": tags,
|
||||
"content": content, // Assuming content is passed in
|
||||
}
|
||||
|
||||
_, err := w.client.Data().Creator().
|
||||
WithClassName("Work").
|
||||
WithID(fmt.Sprintf("%d", work.ID)).
|
||||
WithID(strconv.FormatUint(uint64(work.ID), 10)).
|
||||
WithProperties(properties).
|
||||
Do(ctx)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func contains(slice []string, item string) bool {
|
||||
for _, s := range slice {
|
||||
if s == item {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
330
internal/platform/search/weaviate_wrapper_integration_test.go
Normal file
330
internal/platform/search/weaviate_wrapper_integration_test.go
Normal file
@ -0,0 +1,330 @@
|
||||
package search_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
"tercul/internal/domain"
|
||||
domainsearch "tercul/internal/domain/search"
|
||||
"tercul/internal/platform/search"
|
||||
"time"
|
||||
|
||||
"github.com/go-openapi/strfmt"
|
||||
"github.com/google/uuid"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/stretchr/testify/suite"
|
||||
"github.com/testcontainers/testcontainers-go"
|
||||
"github.com/testcontainers/testcontainers-go/wait"
|
||||
"github.com/weaviate/weaviate-go-client/v5/weaviate"
|
||||
"github.com/weaviate/weaviate/entities/models"
|
||||
)
|
||||
|
||||
type WeaviateWrapperIntegrationTestSuite struct {
|
||||
suite.Suite
|
||||
client *weaviate.Client
|
||||
wrapper domainsearch.SearchClient
|
||||
weaviateContainer testcontainers.Container
|
||||
}
|
||||
|
||||
func (s *WeaviateWrapperIntegrationTestSuite) SetupSuite() {
|
||||
ctx := context.Background()
|
||||
|
||||
req := testcontainers.ContainerRequest{
|
||||
Image: "cr.weaviate.io/semitechnologies/weaviate:1.24.1",
|
||||
ExposedPorts: []string{"8080/tcp"},
|
||||
Env: map[string]string{
|
||||
"AUTHENTICATION_ANONYMOUS_ACCESS_ENABLED": "true",
|
||||
"DEFAULT_VECTORIZER_MODULE": "none",
|
||||
"PERSISTENCE_DATA_PATH": "/var/lib/weaviate",
|
||||
},
|
||||
WaitingFor: wait.ForHTTP("/v1/.well-known/ready").WithPort("8080"),
|
||||
}
|
||||
container, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
|
||||
ContainerRequest: req,
|
||||
Started: true,
|
||||
})
|
||||
require.NoError(s.T(), err)
|
||||
s.weaviateContainer = container
|
||||
|
||||
host, err := container.Host(ctx)
|
||||
require.NoError(s.T(), err)
|
||||
port, err := container.MappedPort(ctx, "8080")
|
||||
require.NoError(s.T(), err)
|
||||
|
||||
cfg := weaviate.Config{
|
||||
Host: fmt.Sprintf("%s:%s", host, port.Port()),
|
||||
Scheme: "http",
|
||||
}
|
||||
client, err := weaviate.NewClient(cfg)
|
||||
require.NoError(s.T(), err)
|
||||
s.client = client
|
||||
|
||||
s.wrapper = search.NewWeaviateWrapper(client, fmt.Sprintf("%s:%s", host, port.Port()), 0.7)
|
||||
|
||||
s.createTestSchema(ctx)
|
||||
s.seedTestData(ctx)
|
||||
}
|
||||
|
||||
func (s *WeaviateWrapperIntegrationTestSuite) TearDownSuite() {
|
||||
ctx := context.Background()
|
||||
err := s.client.Schema().AllDeleter().Do(ctx)
|
||||
require.NoError(s.T(), err)
|
||||
require.NoError(s.T(), s.weaviateContainer.Terminate(ctx))
|
||||
}
|
||||
|
||||
func (s *WeaviateWrapperIntegrationTestSuite) createTestSchema(ctx context.Context) {
|
||||
workClass := &models.Class{
|
||||
Class: "Work",
|
||||
Properties: []*models.Property{
|
||||
{Name: "db_id", DataType: []string{"int"}},
|
||||
{Name: "title", DataType: []string{"text"}},
|
||||
{Name: "description", DataType: []string{"text"}},
|
||||
{Name: "language", DataType: []string{"string"}},
|
||||
{Name: "status", DataType: []string{"string"}},
|
||||
{Name: "createdAt", DataType: []string{"date"}},
|
||||
{Name: "updatedAt", DataType: []string{"date"}},
|
||||
{Name: "tags", DataType: []string{"string[]"}},
|
||||
},
|
||||
}
|
||||
err := s.client.Schema().ClassCreator().WithClass(workClass).Do(ctx)
|
||||
require.NoError(s.T(), err)
|
||||
|
||||
translationClass := &models.Class{
|
||||
Class: "Translation",
|
||||
Properties: []*models.Property{
|
||||
{Name: "db_id", DataType: []string{"int"}},
|
||||
{Name: "title", DataType: []string{"text"}},
|
||||
{Name: "content", DataType: []string{"text"}},
|
||||
{Name: "language", DataType: []string{"string"}},
|
||||
{Name: "status", DataType: []string{"string"}},
|
||||
},
|
||||
}
|
||||
err = s.client.Schema().ClassCreator().WithClass(translationClass).Do(ctx)
|
||||
require.NoError(s.T(), err)
|
||||
|
||||
authorClass := &models.Class{
|
||||
Class: "Author",
|
||||
Properties: []*models.Property{
|
||||
{Name: "db_id", DataType: []string{"int"}},
|
||||
{Name: "name", DataType: []string{"text"}},
|
||||
{Name: "biography", DataType: []string{"text"}},
|
||||
},
|
||||
}
|
||||
err = s.client.Schema().ClassCreator().WithClass(authorClass).Do(ctx)
|
||||
require.NoError(s.T(), err)
|
||||
}
|
||||
|
||||
func (s *WeaviateWrapperIntegrationTestSuite) seedTestData(ctx context.Context) {
|
||||
objects := []*models.Object{
|
||||
{
|
||||
Class: "Work",
|
||||
ID: s.getTestUUID("work-1"),
|
||||
Properties: map[string]interface{}{
|
||||
"db_id": 1,
|
||||
"title": "The Great Gatsby",
|
||||
"description": "A novel by F. Scott Fitzgerald",
|
||||
"language": "en",
|
||||
"status": "published",
|
||||
"createdAt": time.Now().Add(-24 * time.Hour).Format(time.RFC3339),
|
||||
"updatedAt": time.Now().Format(time.RFC3339),
|
||||
"tags": []string{"classic", "american"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Class: "Work",
|
||||
ID: s.getTestUUID("work-2"),
|
||||
Properties: map[string]interface{}{
|
||||
"db_id": 2,
|
||||
"title": "War and Peace",
|
||||
"description": "A classic novel by Leo Tolstoy",
|
||||
"language": "ru",
|
||||
"status": "published",
|
||||
"createdAt": time.Now().Add(-48 * time.Hour).Format(time.RFC3339),
|
||||
"updatedAt": time.Now().Format(time.RFC3339),
|
||||
"tags": []string{"classic", "russian"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Class: "Translation",
|
||||
ID: s.getTestUUID("translation-101"),
|
||||
Properties: map[string]interface{}{
|
||||
"db_id": 101,
|
||||
"title": "Великий Гэтсби",
|
||||
"content": "Содержание перевода...",
|
||||
"language": "ru",
|
||||
"status": "published",
|
||||
},
|
||||
},
|
||||
{
|
||||
Class: "Author",
|
||||
ID: s.getTestUUID("author-201"),
|
||||
Properties: map[string]interface{}{
|
||||
"db_id": 201,
|
||||
"name": "F. Scott Fitzgerald",
|
||||
"biography": "A writer of many a novel.",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
_, err := s.client.Batch().ObjectsBatcher().WithObjects(objects...).Do(ctx)
|
||||
require.NoError(s.T(), err)
|
||||
time.Sleep(1 * time.Second) // Give Weaviate a moment to index
|
||||
}
|
||||
|
||||
func (s *WeaviateWrapperIntegrationTestSuite) TestSearch_AllEntities() {
|
||||
params := domainsearch.SearchParams{
|
||||
Query: "novel",
|
||||
Mode: domainsearch.SearchModeBM25,
|
||||
Limit: 10,
|
||||
Offset: 0,
|
||||
}
|
||||
|
||||
results, err := s.wrapper.Search(context.Background(), params)
|
||||
|
||||
s.Require().NoError(err)
|
||||
s.Require().NotNil(results)
|
||||
s.Require().Equal(int64(3), results.TotalResults) // Both works and the author's bio contain "novel"
|
||||
s.Require().Len(results.Results, 3)
|
||||
|
||||
// Check if the correct types are returned
|
||||
foundWork := 0
|
||||
foundAuthor := 0
|
||||
for _, item := range results.Results {
|
||||
if item.Type == "Work" {
|
||||
foundWork++
|
||||
}
|
||||
if item.Type == "Author" {
|
||||
foundAuthor++
|
||||
}
|
||||
}
|
||||
s.Require().Equal(2, foundWork, "Expected to find two works")
|
||||
s.Require().Equal(1, foundAuthor, "Expected to find one author")
|
||||
}
|
||||
|
||||
func (s *WeaviateWrapperIntegrationTestSuite) TestSearch_WithLanguageFilter() {
|
||||
params := domainsearch.SearchParams{
|
||||
Query: "novel",
|
||||
Mode: domainsearch.SearchModeBM25,
|
||||
Limit: 10,
|
||||
Offset: 0,
|
||||
Filters: domainsearch.SearchFilters{
|
||||
Languages: []string{"ru"},
|
||||
},
|
||||
}
|
||||
|
||||
results, err := s.wrapper.Search(context.Background(), params)
|
||||
|
||||
s.Require().NoError(err)
|
||||
s.Require().NotNil(results)
|
||||
s.Require().Equal(int64(1), results.TotalResults, "Expected only 'War and Peace' to match")
|
||||
s.Require().Len(results.Results, 1)
|
||||
s.Require().Equal("Work", results.Results[0].Type)
|
||||
work, ok := results.Results[0].Entity.(domain.Work)
|
||||
s.Require().True(ok)
|
||||
s.Require().Equal("War and Peace", work.Title)
|
||||
s.Require().Equal(uint(2), work.ID)
|
||||
}
|
||||
|
||||
func (s *WeaviateWrapperIntegrationTestSuite) TestSearch_WithTagFilter() {
|
||||
params := domainsearch.SearchParams{
|
||||
Query: "", // No query, just filtering
|
||||
Mode: domainsearch.SearchModeBM25,
|
||||
Limit: 10,
|
||||
Offset: 0,
|
||||
Filters: domainsearch.SearchFilters{
|
||||
Types: []string{"Work"},
|
||||
Tags: []string{"american"},
|
||||
},
|
||||
}
|
||||
|
||||
results, err := s.wrapper.Search(context.Background(), params)
|
||||
|
||||
s.Require().NoError(err)
|
||||
s.Require().NotNil(results)
|
||||
s.Require().Equal(int64(1), results.TotalResults, "Expected only 'The Great Gatsby' to match")
|
||||
s.Require().Len(results.Results, 1)
|
||||
s.Require().Equal("Work", results.Results[0].Type)
|
||||
work, ok := results.Results[0].Entity.(domain.Work)
|
||||
s.Require().True(ok)
|
||||
s.Require().Equal("The Great Gatsby", work.Title)
|
||||
s.Require().Equal(uint(1), work.ID)
|
||||
}
|
||||
|
||||
func (s *WeaviateWrapperIntegrationTestSuite) TestSearch_WithAuthorFilter() {
|
||||
params := domainsearch.SearchParams{
|
||||
Query: "", // No query, just filtering
|
||||
Mode: domainsearch.SearchModeBM25,
|
||||
Limit: 10,
|
||||
Offset: 0,
|
||||
Filters: domainsearch.SearchFilters{
|
||||
Types: []string{"Author"},
|
||||
Authors: []string{"F. Scott Fitzgerald"},
|
||||
},
|
||||
}
|
||||
|
||||
results, err := s.wrapper.Search(context.Background(), params)
|
||||
|
||||
s.Require().NoError(err)
|
||||
s.Require().NotNil(results)
|
||||
s.Require().Equal(int64(1), results.TotalResults)
|
||||
s.Require().Len(results.Results, 1)
|
||||
s.Require().Equal("Author", results.Results[0].Type)
|
||||
author, ok := results.Results[0].Entity.(domain.Author)
|
||||
s.Require().True(ok)
|
||||
s.Require().Equal("F. Scott Fitzgerald", author.Name)
|
||||
s.Require().Equal(uint(201), author.ID)
|
||||
}
|
||||
|
||||
func (s *WeaviateWrapperIntegrationTestSuite) TestSearch_Pagination() {
|
||||
params := domainsearch.SearchParams{
|
||||
Query: "novel",
|
||||
Mode: domainsearch.SearchModeBM25,
|
||||
Limit: 1,
|
||||
Offset: 0,
|
||||
}
|
||||
|
||||
// First page
|
||||
results1, err := s.wrapper.Search(context.Background(), params)
|
||||
s.Require().NoError(err)
|
||||
s.Require().NotNil(results1)
|
||||
s.Require().Equal(int64(3), results1.TotalResults)
|
||||
s.Require().Len(results1.Results, 1)
|
||||
|
||||
var firstID uint
|
||||
switch e := results1.Results[0].Entity.(type) {
|
||||
case domain.Work:
|
||||
firstID = e.ID
|
||||
case domain.Author:
|
||||
firstID = e.ID
|
||||
default:
|
||||
s.T().Fatalf("Unexpected entity type: %T", e)
|
||||
}
|
||||
|
||||
// Second page
|
||||
params.Offset = 1
|
||||
results2, err := s.wrapper.Search(context.Background(), params)
|
||||
s.Require().NoError(err)
|
||||
s.Require().NotNil(results2)
|
||||
s.Require().Equal(int64(3), results2.TotalResults)
|
||||
s.Require().Len(results2.Results, 1)
|
||||
|
||||
var secondID uint
|
||||
switch e := results2.Results[0].Entity.(type) {
|
||||
case domain.Work:
|
||||
secondID = e.ID
|
||||
case domain.Author:
|
||||
secondID = e.ID
|
||||
default:
|
||||
s.T().Fatalf("Unexpected entity type: %T", e)
|
||||
}
|
||||
|
||||
s.Require().NotEqual(firstID, secondID, "Paginated results should be different")
|
||||
}
|
||||
|
||||
func TestWeaviateWrapperIntegrationTestSuite(t *testing.T) {
|
||||
suite.Run(t, new(WeaviateWrapperIntegrationTestSuite))
|
||||
}
|
||||
|
||||
func (s *WeaviateWrapperIntegrationTestSuite) getTestUUID(input string) strfmt.UUID {
|
||||
return strfmt.UUID(uuid.NewSHA1(uuid.Nil, []byte(input)).String())
|
||||
}
|
||||
@ -12,12 +12,12 @@ type MockWeaviateWrapper struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (m *MockWeaviateWrapper) Search(ctx context.Context, params domainsearch.SearchParams) (*domain.SearchResults, error) {
|
||||
func (m *MockWeaviateWrapper) Search(ctx context.Context, params domainsearch.SearchParams) (*domainsearch.SearchResults, error) {
|
||||
args := m.Called(ctx, params)
|
||||
if args.Get(0) == nil {
|
||||
return nil, args.Error(1)
|
||||
}
|
||||
return args.Get(0).(*domain.SearchResults), args.Error(1)
|
||||
return args.Get(0).(*domainsearch.SearchResults), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *MockWeaviateWrapper) IndexWork(ctx context.Context, work *domain.Work, content string) error {
|
||||
|
||||
@ -10,15 +10,26 @@ import (
|
||||
|
||||
func TestWeaviateWrapper_Search(t *testing.T) {
|
||||
mockWrapper := new(MockWeaviateWrapper)
|
||||
expectedResults := &domain.SearchResults{
|
||||
Works: []domain.Work{
|
||||
{Title: "Work 1", Description: "alpha beta", TranslatableModel: domain.TranslatableModel{Language: "en"}},
|
||||
expectedResults := &domainsearch.SearchResults{
|
||||
Results: []domainsearch.SearchResultItem{
|
||||
{
|
||||
Type: "Work",
|
||||
Entity: domain.Work{
|
||||
Title: "Work 1",
|
||||
Description: "alpha beta",
|
||||
TranslatableModel: domain.TranslatableModel{Language: "en"},
|
||||
},
|
||||
Score: 0.95,
|
||||
},
|
||||
},
|
||||
TotalResults: 1,
|
||||
Limit: 1,
|
||||
Offset: 0,
|
||||
}
|
||||
params := domainsearch.SearchParams{
|
||||
Query: "alpha",
|
||||
Mode: domainsearch.SearchModeHybrid,
|
||||
Filters: domain.SearchFilters{
|
||||
Filters: domainsearch.SearchFilters{
|
||||
Languages: []string{"en"},
|
||||
},
|
||||
Limit: 1,
|
||||
@ -31,8 +42,10 @@ func TestWeaviateWrapper_Search(t *testing.T) {
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, results)
|
||||
assert.Equal(t, 1, len(results.Works))
|
||||
assert.Equal(t, "Work 1", results.Works[0].Title)
|
||||
assert.Equal(t, 1, len(results.Results))
|
||||
assert.Equal(t, "Work", results.Results[0].Type)
|
||||
work := results.Results[0].Entity.(domain.Work)
|
||||
assert.Equal(t, "Work 1", work.Title)
|
||||
|
||||
mockWrapper.AssertExpectations(t)
|
||||
}
|
||||
|
||||
@ -40,8 +40,8 @@ import (
|
||||
// mockSearchClient is a mock implementation of the SearchClient interface.
|
||||
type mockSearchClient struct{}
|
||||
|
||||
func (m *mockSearchClient) Search(ctx context.Context, params search.SearchParams) (*domain.SearchResults, error) {
|
||||
return &domain.SearchResults{}, nil
|
||||
func (m *mockSearchClient) Search(ctx context.Context, params search.SearchParams) (*search.SearchResults, error) {
|
||||
return &search.SearchResults{}, nil
|
||||
}
|
||||
|
||||
func (m *mockSearchClient) IndexWork(ctx context.Context, work *domain.Work, pipeline string) error {
|
||||
|
||||
503
jules-task.md
Normal file
503
jules-task.md
Normal file
@ -0,0 +1,503 @@
|
||||
# Backend Production Readiness & Code Quality Improvements
|
||||
|
||||
## Overview
|
||||
Implement critical production-ready features, refactor architectural issues, and improve code quality for the Tercul backend. The codebase uses Go 1.25, follows DDD/CQRS patterns, GraphQL API, and clean architecture principles.
|
||||
|
||||
## Critical Issues to Resolve
|
||||
|
||||
### 1. Implement Full-Text Search Service (P0 - Critical)
|
||||
**Problem**: The search service in `internal/app/search/service.go` is a stub that returns empty results. This is a core feature that users depend on.
|
||||
|
||||
**Current State**:
|
||||
- `Search()` method returns empty results (line 31-39)
|
||||
- `IndexWork()` is partially implemented but search logic missing
|
||||
- Weaviate client exists but not utilized for search
|
||||
- Search filters are defined but not applied
|
||||
|
||||
**Affected Files**:
|
||||
- `internal/app/search/service.go` - Main search service (stub implementation)
|
||||
- `internal/platform/search/weaviate_wrapper.go` - Weaviate client wrapper
|
||||
- `internal/domain/search/search.go` - Search domain interfaces
|
||||
- GraphQL resolvers that use search service
|
||||
|
||||
**Solution**:
|
||||
1. Implement full Weaviate search query in `Search()` method:
|
||||
- Query Weaviate for works, translations, and authors
|
||||
- Apply search filters (language, type, date range, tags, authors)
|
||||
- Support multi-language search (Russian, English, Tatar)
|
||||
- Implement relevance ranking
|
||||
- Add pagination support
|
||||
- Handle special characters and diacritics
|
||||
|
||||
2. Enhance indexing:
|
||||
- Index work titles, content, and metadata
|
||||
- Index translation content with language tags
|
||||
- Index author names and biographies
|
||||
- Add incremental indexing on create/update operations
|
||||
- Create background job for bulk indexing existing content
|
||||
|
||||
3. Add search result transformation:
|
||||
- Map Weaviate results to domain entities
|
||||
- Include relevance scores
|
||||
- Handle empty results gracefully
|
||||
- Add search analytics/metrics
|
||||
|
||||
**Acceptance Criteria**:
|
||||
- Search returns relevant results ranked by relevance
|
||||
- Supports filtering by language, category, tags, authors, date ranges
|
||||
- Search response time < 200ms for 95th percentile
|
||||
- Handles multi-language queries correctly
|
||||
- All existing tests pass
|
||||
- Integration tests with real Weaviate instance
|
||||
|
||||
### 2. Refactor Global Configuration Singleton (P1 - High Priority)
|
||||
**Problem**: The application uses a global singleton `config.Cfg` which violates dependency injection principles and makes testing difficult.
|
||||
|
||||
**Current State**:
|
||||
- `internal/platform/config/config.go` has global `var Cfg *Config`
|
||||
- `config.Cfg` is accessed directly in multiple places:
|
||||
- `internal/platform/search/bleve_client.go` (line 13)
|
||||
- Various other packages
|
||||
|
||||
**Affected Files**:
|
||||
- `internal/platform/config/config.go` - Global config singleton
|
||||
- `internal/platform/search/bleve_client.go` - Uses `config.Cfg`
|
||||
- `cmd/api/main.go` - Loads config but also sets global
|
||||
- `cmd/worker/main.go` - Similar pattern
|
||||
- Any other files accessing `config.Cfg` directly
|
||||
|
||||
**Solution**:
|
||||
1. Remove global `Cfg` variable from config package
|
||||
2. Refactor `LoadConfig()` to return config without setting global
|
||||
3. Pass `*config.Config` as dependency to all constructors:
|
||||
- Update `NewBleveClient()` to accept config parameter
|
||||
- Update all repository constructors to accept config
|
||||
- Update application service constructors
|
||||
- Update platform service constructors
|
||||
|
||||
4. Update main entry points:
|
||||
- `cmd/api/main.go` - Pass config to all dependencies
|
||||
- `cmd/worker/main.go` - Pass config to all dependencies
|
||||
- `cmd/tools/enrich/main.go` - Pass config to dependencies
|
||||
|
||||
5. Make configuration more flexible:
|
||||
- Make migration path configurable (currently hardcoded)
|
||||
- Make metrics server port configurable
|
||||
- Add validation for required config values
|
||||
- Add config struct tags for better documentation
|
||||
|
||||
**Acceptance Criteria**:
|
||||
- No global `config.Cfg` usage anywhere in codebase
|
||||
- All dependencies receive config via constructor injection
|
||||
- Tests can easily mock/inject different configs
|
||||
- Configuration validation on startup
|
||||
- Backward compatible (same environment variables work)
|
||||
|
||||
### 3. Enhance Observability: Distributed Tracing (P0 - Critical)
|
||||
**Problem**: Tracing is implemented but only exports to stdout. Need production-ready tracing with OTLP exporter and proper instrumentation.
|
||||
|
||||
**Current State**:
|
||||
- `internal/observability/tracing.go` uses `stdouttrace` exporter
|
||||
- Basic tracer provider exists but not production-ready
|
||||
- Missing instrumentation in many places
|
||||
|
||||
**Affected Files**:
|
||||
- `internal/observability/tracing.go` - Only stdout exporter
|
||||
- HTTP middleware - May need tracing instrumentation
|
||||
- GraphQL resolvers - Need span creation
|
||||
- Database queries - Need query tracing
|
||||
- Application services - Need business logic spans
|
||||
|
||||
**Solution**:
|
||||
1. Replace stdout exporter with OTLP exporter:
|
||||
- Add OTLP exporter configuration
|
||||
- Support both gRPC and HTTP OTLP endpoints
|
||||
- Add environment-based configuration (dev vs prod)
|
||||
- Add trace sampling strategy (100% dev, 10% prod)
|
||||
|
||||
2. Enhance instrumentation:
|
||||
- Add automatic HTTP request tracing in middleware
|
||||
- Instrument all GraphQL resolvers with spans
|
||||
- Add database query spans via GORM callbacks
|
||||
- Create custom spans for slow operations (>100ms)
|
||||
- Add span attributes (user_id, work_id, etc.)
|
||||
|
||||
3. Add trace context propagation:
|
||||
- Ensure trace IDs propagate through all layers
|
||||
- Add trace ID to structured logs
|
||||
- Support distributed tracing across services
|
||||
|
||||
4. Configuration:
|
||||
```go
|
||||
type TracingConfig struct {
|
||||
Enabled bool
|
||||
ServiceName string
|
||||
OTLPEndpoint string
|
||||
SamplingRate float64
|
||||
Environment string
|
||||
}
|
||||
```
|
||||
|
||||
**Acceptance Criteria**:
|
||||
- Traces exported to OTLP collector (Jaeger/Tempo compatible)
|
||||
- All HTTP requests have spans
|
||||
- All GraphQL resolvers traced
|
||||
- Database queries have spans
|
||||
- Trace IDs in logs
|
||||
- Sampling configurable per environment
|
||||
|
||||
### 4. Enhance Observability: Prometheus Metrics (P0 - Critical)
|
||||
**Problem**: Basic metrics exist but need enhancement for production monitoring and alerting.
|
||||
|
||||
**Current State**:
|
||||
- `internal/observability/metrics.go` has basic HTTP and DB metrics
|
||||
- Missing business metrics, GraphQL-specific metrics
|
||||
- No Grafana dashboards or alerting rules
|
||||
|
||||
**Affected Files**:
|
||||
- `internal/observability/metrics.go` - Basic metrics
|
||||
- GraphQL resolvers - Need resolver metrics
|
||||
- Application services - Need business metrics
|
||||
- Background jobs - Need job metrics
|
||||
|
||||
**Solution**:
|
||||
1. Add GraphQL-specific metrics:
|
||||
- `graphql_resolver_duration_seconds{operation, resolver}`
|
||||
- `graphql_errors_total{operation, error_type}`
|
||||
- `graphql_operations_total{operation, status}`
|
||||
|
||||
2. Add business metrics:
|
||||
- `works_created_total{language}`
|
||||
- `searches_performed_total{type}`
|
||||
- `user_registrations_total`
|
||||
- `translations_created_total{language}`
|
||||
- `likes_total{entity_type}`
|
||||
|
||||
3. Enhance existing metrics:
|
||||
- Add more labels to HTTP metrics (status code as number)
|
||||
- Add query type labels to DB metrics
|
||||
- Add connection pool metrics
|
||||
- Add cache hit/miss metrics
|
||||
|
||||
4. Create observability package structure:
|
||||
- Move metrics to `internal/observability/metrics/`
|
||||
- Add metric collection helpers
|
||||
- Document metric naming conventions
|
||||
|
||||
**Acceptance Criteria**:
|
||||
- All critical paths have metrics
|
||||
- GraphQL operations fully instrumented
|
||||
- Business metrics tracked
|
||||
- Metrics exposed on `/metrics` endpoint
|
||||
- Metric labels follow Prometheus best practices
|
||||
|
||||
### 5. Implement Read Models (DTOs) for Efficient Queries (P1 - High Priority)
|
||||
**Problem**: Application queries return full domain entities, which is inefficient and leaks domain logic to API layer.
|
||||
|
||||
**Current State**:
|
||||
- Queries in `internal/app/*/queries.go` return domain entities
|
||||
- GraphQL resolvers receive full entities with all fields
|
||||
- No optimization for list vs detail views
|
||||
|
||||
**Affected Files**:
|
||||
- `internal/app/work/queries.go` - Returns `domain.Work`
|
||||
- `internal/app/translation/queries.go` - Returns `domain.Translation`
|
||||
- `internal/app/author/queries.go` - Returns `domain.Author`
|
||||
- GraphQL resolvers - Receive full entities
|
||||
|
||||
**Solution**:
|
||||
1. Create DTO packages:
|
||||
- `internal/app/work/dto` - WorkListDTO, WorkDetailDTO
|
||||
- `internal/app/translation/dto` - TranslationListDTO, TranslationDetailDTO
|
||||
- `internal/app/author/dto` - AuthorListDTO, AuthorDetailDTO
|
||||
|
||||
2. Define optimized DTOs:
|
||||
```go
|
||||
// WorkListDTO - For list views (minimal fields)
|
||||
type WorkListDTO struct {
|
||||
ID uint
|
||||
Title string
|
||||
AuthorName string
|
||||
AuthorID uint
|
||||
Language string
|
||||
CreatedAt time.Time
|
||||
ViewCount int
|
||||
LikeCount int
|
||||
TranslationCount int
|
||||
}
|
||||
|
||||
// WorkDetailDTO - For single work view (all fields)
|
||||
type WorkDetailDTO struct {
|
||||
*WorkListDTO
|
||||
Content string
|
||||
Description string
|
||||
Tags []string
|
||||
Translations []TranslationSummaryDTO
|
||||
Author AuthorSummaryDTO
|
||||
}
|
||||
```
|
||||
|
||||
3. Refactor queries to return DTOs:
|
||||
- Update query methods to use optimized SQL
|
||||
- Use joins to avoid N+1 queries
|
||||
- Map domain entities to DTOs
|
||||
- Update GraphQL resolvers to use DTOs
|
||||
|
||||
4. Add benchmarks comparing old vs new approach
|
||||
|
||||
**Acceptance Criteria**:
|
||||
- List queries return optimized DTOs
|
||||
- Detail queries return full DTOs
|
||||
- No N+1 query problems
|
||||
- Payload size reduced by 30-50%
|
||||
- Query response time improved by 20%
|
||||
- No breaking changes to GraphQL schema
|
||||
|
||||
### 6. Improve Structured Logging (P1 - High Priority)
|
||||
**Problem**: Logging exists but lacks request context, user IDs, and trace correlation.
|
||||
|
||||
**Current State**:
|
||||
- `internal/platform/log` uses zerolog
|
||||
- Basic logging but missing context
|
||||
- No request ID propagation
|
||||
- No user ID in logs
|
||||
- No trace/span ID correlation
|
||||
|
||||
**Affected Files**:
|
||||
- `internal/platform/log/logger.go` - Basic logger
|
||||
- HTTP middleware - Needs request ID injection
|
||||
- All application services - Need context logging
|
||||
|
||||
**Solution**:
|
||||
1. Enhance HTTP middleware:
|
||||
- Generate request ID for each request
|
||||
- Inject request ID into context
|
||||
- Add user ID from JWT to context
|
||||
- Add trace/span IDs to context
|
||||
|
||||
2. Update logger to use context:
|
||||
- Extract request ID, user ID, trace ID from context
|
||||
- Add to all log entries automatically
|
||||
- Create helper: `log.FromContext(ctx).WithRequestID().WithUserID()`
|
||||
|
||||
3. Add structured logging fields:
|
||||
- Define field name constants
|
||||
- Ensure consistent field names across codebase
|
||||
- Add sensitive data redaction
|
||||
|
||||
4. Implement log sampling:
|
||||
- Sample high-volume endpoints (e.g., health checks)
|
||||
- Configurable sampling rates
|
||||
- Always log errors regardless of sampling
|
||||
|
||||
**Acceptance Criteria**:
|
||||
- All logs include request ID
|
||||
- Authenticated request logs include user ID
|
||||
- All logs include trace/span IDs
|
||||
- Consistent log format across codebase
|
||||
- Sensitive data excluded from logs
|
||||
- Log sampling for high-volume endpoints
|
||||
|
||||
### 7. Refactor Caching with Decorator Pattern (P1 - High Priority)
|
||||
**Problem**: Current caching implementation uses bespoke cached repositories. Should use decorator pattern for better maintainability.
|
||||
|
||||
**Current State**:
|
||||
- `internal/data/cache` has custom caching logic
|
||||
- Cached repositories are separate implementations
|
||||
- Not following decorator pattern
|
||||
|
||||
**Affected Files**:
|
||||
- `internal/data/cache/*` - Current caching implementation
|
||||
- Repository interfaces - Need to support decorators
|
||||
|
||||
**Solution**:
|
||||
1. Implement decorator pattern:
|
||||
- Create `CachedWorkRepository` decorator
|
||||
- Create `CachedAuthorRepository` decorator
|
||||
- Create `CachedTranslationRepository` decorator
|
||||
- Decorators wrap base repositories
|
||||
|
||||
2. Implement cache-aside pattern:
|
||||
- Check cache on read, populate on miss
|
||||
- Invalidate cache on write operations
|
||||
- Add cache key versioning strategy
|
||||
|
||||
3. Add cache configuration:
|
||||
- TTL per entity type
|
||||
- Cache size limits
|
||||
- Cache warming strategies
|
||||
|
||||
4. Add cache metrics:
|
||||
- Hit/miss rates
|
||||
- Cache size
|
||||
- Eviction counts
|
||||
|
||||
**Acceptance Criteria**:
|
||||
- Decorator pattern implemented
|
||||
- Cache hit rate > 70% for reads
|
||||
- Automatic cache invalidation on updates
|
||||
- Cache failures don't break application
|
||||
- Metrics for cache performance
|
||||
|
||||
### 8. Complete API Documentation (P1 - High Priority)
|
||||
**Problem**: API documentation is incomplete. Need comprehensive GraphQL API documentation.
|
||||
|
||||
**Current State**:
|
||||
- GraphQL schema exists but lacks descriptions
|
||||
- No example queries
|
||||
- No API guide for consumers
|
||||
|
||||
**Affected Files**:
|
||||
- GraphQL schema files - Need descriptions
|
||||
- `api/README.md` - Needs comprehensive guide
|
||||
- All resolver implementations - Need documentation
|
||||
|
||||
**Solution**:
|
||||
1. Add descriptions to GraphQL schema:
|
||||
- Document all types, queries, mutations
|
||||
- Add field descriptions
|
||||
- Document input validation rules
|
||||
- Add deprecation notices where applicable
|
||||
|
||||
2. Create comprehensive API documentation:
|
||||
- `api/README.md` - Complete API guide
|
||||
- `api/EXAMPLES.md` - Query examples
|
||||
- Document authentication requirements
|
||||
- Document rate limiting
|
||||
- Document error responses
|
||||
|
||||
3. Enhance GraphQL Playground:
|
||||
- Pre-populate with example queries
|
||||
- Add query templates
|
||||
- Document schema changes
|
||||
|
||||
**Acceptance Criteria**:
|
||||
- All 80+ GraphQL resolvers documented
|
||||
- Example queries for each operation
|
||||
- Input validation rules documented
|
||||
- Error response examples
|
||||
- Authentication requirements clear
|
||||
- API changelog maintained
|
||||
|
||||
### 9. Refactor Testing Utilities (P2 - Medium Priority)
|
||||
**Problem**: Tests depend on live database connections, making them slow and unreliable.
|
||||
|
||||
**Current State**:
|
||||
- `internal/testutil/testutil.go` has database connection logic
|
||||
- Integration tests require live database
|
||||
- Tests are slow and may be flaky
|
||||
|
||||
**Affected Files**:
|
||||
- `internal/testutil/testutil.go` - Database connection logic
|
||||
- All integration tests - Depend on live DB
|
||||
|
||||
**Solution**:
|
||||
1. Decouple tests from live database:
|
||||
- Remove database connection from testutil
|
||||
- Use test containers for integration tests
|
||||
- Use mocks for unit tests
|
||||
|
||||
2. Improve test utilities:
|
||||
- Create test data builders
|
||||
- Add fixtures for common scenarios
|
||||
- Improve test isolation
|
||||
|
||||
3. Add parallel test execution:
|
||||
- Enable `-parallel` flag where safe
|
||||
- Use test-specific database schemas
|
||||
- Clean up test data properly
|
||||
|
||||
**Acceptance Criteria**:
|
||||
- Unit tests run without database
|
||||
- Integration tests use test containers
|
||||
- Tests run in parallel where possible
|
||||
- Test execution time < 5 seconds for unit tests
|
||||
- Clear separation between unit and integration tests
|
||||
|
||||
### 10. Implement Analytics Features (P2 - Medium Priority)
|
||||
**Problem**: Analytics service exists but some metrics are stubs (like, comment, bookmark counting).
|
||||
|
||||
**Current State**:
|
||||
- `internal/jobs/linguistics/work_analysis_service.go` has TODO comments:
|
||||
- Line 184: ViewCount TODO
|
||||
- Line 185: LikeCount TODO
|
||||
- Line 186: CommentCount TODO
|
||||
- Line 187: BookmarkCount TODO
|
||||
- Line 188: TranslationCount TODO
|
||||
- Line 192: PopularTranslations TODO
|
||||
|
||||
**Affected Files**:
|
||||
- `internal/jobs/linguistics/work_analysis_service.go` - Stub implementations
|
||||
- `internal/app/analytics/*` - Analytics services
|
||||
|
||||
**Solution**:
|
||||
1. Implement counting services:
|
||||
- Like counting service
|
||||
- Comment counting service
|
||||
- Bookmark counting service
|
||||
- Translation counting service
|
||||
- View counting service
|
||||
|
||||
2. Implement popular translations calculation:
|
||||
- Calculate based on likes, comments, bookmarks
|
||||
- Cache results for performance
|
||||
- Update periodically via background job
|
||||
|
||||
3. Add analytics to work analysis:
|
||||
- Integrate counting services
|
||||
- Update WorkAnalytics struct
|
||||
- Ensure data is accurate and up-to-date
|
||||
|
||||
**Acceptance Criteria**:
|
||||
- All analytics metrics implemented
|
||||
- Popular translations calculated correctly
|
||||
- Analytics updated in real-time or near-real-time
|
||||
- Performance optimized (cached where appropriate)
|
||||
- Tests for all analytics features
|
||||
|
||||
## Implementation Guidelines
|
||||
|
||||
1. **Architecture First**: Maintain clean architecture, DDD, and CQRS patterns
|
||||
2. **Backward Compatibility**: Ensure API contracts remain consistent
|
||||
3. **Code Quality**:
|
||||
- Follow Go best practices and idioms
|
||||
- Use interfaces for testability
|
||||
- Maintain separation of concerns
|
||||
- Add comprehensive error handling
|
||||
4. **Testing**: Write tests for all new features and refactorings
|
||||
5. **Documentation**: Add GoDoc comments for all public APIs
|
||||
6. **Performance**: Optimize for production workloads
|
||||
7. **Observability**: Instrument all critical paths
|
||||
|
||||
## Expected Outcome
|
||||
|
||||
- Production-ready search functionality
|
||||
- Proper dependency injection (no globals)
|
||||
- Full observability (tracing, metrics, logging)
|
||||
- Optimized queries with DTOs
|
||||
- Comprehensive API documentation
|
||||
- Fast, reliable test suite
|
||||
- Complete analytics features
|
||||
- Improved code maintainability
|
||||
|
||||
## Files to Prioritize
|
||||
|
||||
1. `internal/app/search/service.go` - Core search implementation (P0)
|
||||
2. `internal/platform/config/config.go` - Configuration refactoring (P1)
|
||||
3. `internal/observability/*` - Observability enhancements (P0)
|
||||
4. `internal/app/*/queries.go` - DTO implementation (P1)
|
||||
5. `internal/platform/log/*` - Logging improvements (P1)
|
||||
6. `api/README.md` - API documentation (P1)
|
||||
|
||||
## Notes
|
||||
|
||||
- Codebase uses Go 1.25
|
||||
- Follows DDD/CQRS/Clean Architecture patterns
|
||||
- GraphQL API with gqlgen
|
||||
- PostgreSQL with GORM
|
||||
- Weaviate for vector search
|
||||
- Redis for caching and job queue
|
||||
- Docker for local development
|
||||
- Existing tests should continue to pass
|
||||
- Follow existing code style and patterns
|
||||
|
||||
@ -1,165 +0,0 @@
|
||||
package bleve
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"tercul/internal/domain"
|
||||
|
||||
blevelib "github.com/blevesearch/bleve/v2"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type BleveClient struct {
|
||||
index blevelib.Index
|
||||
}
|
||||
|
||||
// NewBleveClient initializes or opens a Bleve index at the given path.
|
||||
func NewBleveClient(indexPath string) (*BleveClient, error) {
|
||||
var index blevelib.Index
|
||||
var err error
|
||||
|
||||
if _, err = os.Stat(indexPath); os.IsNotExist(err) {
|
||||
// Create a new index if it doesn't exist
|
||||
indexMapping := blevelib.NewIndexMapping()
|
||||
index, err = blevelib.New(indexPath, indexMapping)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Println("Created a new Bleve index at:", indexPath)
|
||||
} else {
|
||||
// Open an existing index
|
||||
index, err = blevelib.Open(indexPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Println("Opened an existing Bleve index at:", indexPath)
|
||||
}
|
||||
|
||||
return &BleveClient{index: index}, nil
|
||||
}
|
||||
|
||||
// AddTranslation indexes a single translation into the Bleve index.
|
||||
func (bc *BleveClient) AddTranslation(translation domain.Translation) error {
|
||||
// Create a structured document containing all relevant translation fields
|
||||
document := map[string]interface{}{
|
||||
"id": translation.ID,
|
||||
"title": translation.Title,
|
||||
"content": translation.Content,
|
||||
"description": translation.Description,
|
||||
"language": translation.Language,
|
||||
"status": translation.Status,
|
||||
"translatable_id": translation.TranslatableID,
|
||||
"translatable_type": translation.TranslatableType,
|
||||
"translator_id": func() uint {
|
||||
if translation.TranslatorID != nil {
|
||||
return *translation.TranslatorID
|
||||
}
|
||||
return 0
|
||||
}(),
|
||||
}
|
||||
|
||||
// Use the translation's ID as the unique document ID
|
||||
docID := fmt.Sprintf("%d", translation.ID)
|
||||
return bc.index.Index(docID, document)
|
||||
}
|
||||
|
||||
// AddTranslations indexes all translations from the database into the Bleve index.
|
||||
func (bc *BleveClient) AddTranslations(db *gorm.DB) error {
|
||||
const batchSize = 50000
|
||||
offset := 0
|
||||
|
||||
for {
|
||||
// Fetch translations in batches
|
||||
var translations []domain.Translation
|
||||
err := db.Offset(offset).Limit(batchSize).Find(&translations).Error
|
||||
if err != nil {
|
||||
log.Printf("Error fetching translations from the database: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Break if no more translations to process
|
||||
if len(translations) == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
// Create a Bleve batch for better indexing performance
|
||||
batch := bc.index.NewBatch()
|
||||
for _, translation := range translations {
|
||||
// Create a structured document for each translation
|
||||
document := map[string]interface{}{
|
||||
"id": translation.ID,
|
||||
"title": translation.Title,
|
||||
"content": translation.Content,
|
||||
"description": translation.Description,
|
||||
"language": translation.Language,
|
||||
"status": translation.Status,
|
||||
"translatable_id": translation.TranslatableID,
|
||||
"translatable_type": translation.TranslatableType,
|
||||
"translator_id": func() uint {
|
||||
if translation.TranslatorID != nil {
|
||||
return *translation.TranslatorID
|
||||
}
|
||||
return 0
|
||||
}(),
|
||||
}
|
||||
|
||||
docID := fmt.Sprintf("%d", translation.ID)
|
||||
err = batch.Index(docID, document)
|
||||
if err != nil {
|
||||
log.Printf("Error indexing translation ID %s: %v", docID, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Commit the batch to the index
|
||||
err = bc.index.Batch(batch)
|
||||
if err != nil {
|
||||
log.Printf("Error committing batch to the Bleve index: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
log.Printf("Indexed %d translations into Bleve.", len(translations))
|
||||
offset += batchSize
|
||||
}
|
||||
|
||||
log.Println("All translations have been indexed into Bleve.")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Search performs a search with multiple filters and a full-text query.
|
||||
func (bc *BleveClient) Search(queryString string, filters map[string]string, size int) (*blevelib.SearchResult, error) {
|
||||
// Create the main query for full-text search
|
||||
mainQuery := blevelib.NewMatchQuery(queryString)
|
||||
mainQuery.SetFuzziness(2)
|
||||
|
||||
// Create a boolean query
|
||||
booleanQuery := blevelib.NewBooleanQuery()
|
||||
|
||||
// Add the main query to the "must" clause
|
||||
booleanQuery.AddMust(mainQuery)
|
||||
|
||||
// Add filter queries to the "must" clause
|
||||
for field, value := range filters {
|
||||
termQuery := blevelib.NewTermQuery(value)
|
||||
termQuery.SetField(field)
|
||||
booleanQuery.AddMust(termQuery)
|
||||
}
|
||||
|
||||
// Build the search request
|
||||
searchRequest := blevelib.NewSearchRequest(booleanQuery)
|
||||
searchRequest.Size = size
|
||||
|
||||
// Execute the search
|
||||
results, err := bc.index.Search(searchRequest)
|
||||
if err != nil {
|
||||
log.Printf("Search failed: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
// Close closes the Bleve index.
|
||||
func (bc *BleveClient) Close() error {
|
||||
return bc.index.Close()
|
||||
}
|
||||
@ -1,95 +0,0 @@
|
||||
package bleve
|
||||
|
||||
import (
|
||||
"os"
|
||||
"tercul/internal/domain"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestBleveClient(t *testing.T) {
|
||||
// Temporary index path for testing
|
||||
tempIndexPath := "test_bleve_index"
|
||||
defer func() { _ = os.RemoveAll(tempIndexPath) }() // Clean up after the test
|
||||
|
||||
// Initialize a new Bleve client
|
||||
client, err := NewBleveClient(tempIndexPath)
|
||||
require.NoError(t, err, "Failed to create a new Bleve client")
|
||||
defer func() { _ = client.Close() }()
|
||||
|
||||
// Define test cases for AddTranslation and Search
|
||||
tests := []struct {
|
||||
name string
|
||||
translation domain.Translation
|
||||
searchQuery string
|
||||
expectedHits int
|
||||
}{
|
||||
{
|
||||
name: "Index and search single translation",
|
||||
translation: domain.Translation{
|
||||
BaseModel: domain.BaseModel{ID: 1},
|
||||
Title: "Golang Basics",
|
||||
Content: "Learn Go programming",
|
||||
Language: "en",
|
||||
},
|
||||
searchQuery: "Golang",
|
||||
expectedHits: 1,
|
||||
},
|
||||
{
|
||||
name: "No matches for unrelated query",
|
||||
translation: domain.Translation{
|
||||
BaseModel: domain.BaseModel{ID: 2},
|
||||
Title: "Python Basics",
|
||||
Content: "Learn Python programming",
|
||||
Language: "en",
|
||||
},
|
||||
searchQuery: "Rust",
|
||||
expectedHits: 0,
|
||||
},
|
||||
{
|
||||
name: "Index and search multiple translations",
|
||||
translation: domain.Translation{
|
||||
BaseModel: domain.BaseModel{ID: 3},
|
||||
Title: "Advanced Go",
|
||||
Content: "Deep dive into Go programming",
|
||||
Language: "en",
|
||||
},
|
||||
searchQuery: "Go",
|
||||
expectedHits: 2,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Index the translation
|
||||
err := client.AddTranslation(tt.translation)
|
||||
require.NoError(t, err, "Failed to index translation")
|
||||
|
||||
// Perform the search with empty filters
|
||||
result, err := client.Search(tt.searchQuery, map[string]string{}, 10)
|
||||
require.NoError(t, err, "Search query failed")
|
||||
assert.GreaterOrEqual(t, len(result.Hits), tt.expectedHits, "Unexpected number of hits")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBleveClientInitialization(t *testing.T) {
|
||||
tempIndexPath := "test_init_index"
|
||||
defer func() { _ = os.RemoveAll(tempIndexPath) }() // Clean up
|
||||
|
||||
t.Run("New Index Initialization", func(t *testing.T) {
|
||||
client, err := NewBleveClient(tempIndexPath)
|
||||
require.NoError(t, err, "Failed to initialize a new index")
|
||||
defer func() { _ = client.Close() }()
|
||||
assert.NotNil(t, client.index, "Index should not be nil")
|
||||
})
|
||||
|
||||
t.Run("Open Existing Index", func(t *testing.T) {
|
||||
client, err := NewBleveClient(tempIndexPath)
|
||||
require.NoError(t, err, "Failed to open an existing index")
|
||||
defer func() { _ = client.Close() }()
|
||||
assert.NotNil(t, client.index, "Index should not be nil")
|
||||
})
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user