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:
Damir Mukimov 2025-11-30 03:32:44 +01:00
commit ede3d04e4e
No known key found for this signature in database
GPG Key ID: 42996CC7C73BC750
29 changed files with 2638 additions and 935 deletions

963
PRODUCTION-TASKS.md Normal file
View 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!** 🚀

View File

@ -17,47 +17,47 @@ This document is the single source of truth for all outstanding development task
### EPIC: Achieve Production-Ready API ### EPIC: Achieve Production-Ready API
- [x] **Implement All Unimplemented Resolvers:** The GraphQL API is critically incomplete. All of the following `panic`ing resolvers must be implemented. *(Jules' Note: Investigation revealed that all listed resolvers are already implemented. This task is complete.)* - [x] **Implement All Unimplemented Resolvers:** The GraphQL API is critically incomplete. All of the following `panic`ing resolvers must be implemented. *(Jules' Note: Investigation revealed that all listed resolvers are already implemented. This task is complete.)*
- **Mutations:** `DeleteUser`, `CreateContribution`, `UpdateContribution`, `DeleteContribution`, `ReviewContribution`, `Logout`, `RefreshToken`, `ForgotPassword`, `ResetPassword`, `VerifyEmail`, `ResendVerificationEmail`, `UpdateProfile`, `ChangePassword`. - **Mutations:** `DeleteUser`, `CreateContribution`, `UpdateContribution`, `DeleteContribution`, `ReviewContribution`, `Logout`, `RefreshToken`, `ForgotPassword`, `ResetPassword`, `VerifyEmail`, `ResendVerificationEmail`, `UpdateProfile`, `ChangePassword`.
- **Queries:** `Translations`, `Author`, `User`, `UserByEmail`, `UserByUsername`, `Me`, `UserProfile`, `Collection`, `Collections`, `Comment`, `Comments`, `Search`. - **Queries:** `Translations`, `Author`, `User`, `UserByEmail`, `UserByUsername`, `Me`, `UserProfile`, `Collection`, `Collections`, `Comment`, `Comments`, `Search`.
- [x] **Refactor API Server Setup:** The API server startup in `cmd/api/main.go` is unnecessarily complex. *(Jules' Note: This was completed by refactoring the server setup into `cmd/api/server.go`.)* - [x] **Refactor API Server Setup:** The API server startup in `cmd/api/main.go` is unnecessarily complex. *(Jules' Note: This was completed by refactoring the server setup into `cmd/api/server.go`.)*
- [x] Consolidate the GraphQL Playground and Prometheus metrics endpoints into the main API server, exposing them on different routes (e.g., `/playground`, `/metrics`). - [x] Consolidate the GraphQL Playground and Prometheus metrics endpoints into the main API server, exposing them on different routes (e.g., `/playground`, `/metrics`).
### EPIC: Comprehensive Documentation ### EPIC: Comprehensive Documentation
- [ ] **Create Full API Documentation:** The current API documentation is critically incomplete. We need to document every query, mutation, and type in the GraphQL schema. - [ ] **Create Full API Documentation:** The current API documentation is critically incomplete. We need to document every query, mutation, and type in the GraphQL schema.
- [ ] Update `api/README.md` to be a comprehensive guide for API consumers. - [ ] Update `api/README.md` to be a comprehensive guide for API consumers.
- [ ] **Improve Project `README.md`:** The root `README.md` should be a welcoming and useful entry point for new developers. - [ ] **Improve Project `README.md`:** The root `README.md` should be a welcoming and useful entry point for new developers.
- [ ] Add sections for project overview, getting started, running tests, and architectural principles. - [ ] Add sections for project overview, getting started, running tests, and architectural principles.
- [ ] **Ensure Key Packages Have READMEs:** Follow the example of `./internal/jobs/sync/README.md` for other critical components. - [ ] **Ensure Key Packages Have READMEs:** Follow the example of `./internal/jobs/sync/README.md` for other critical components.
### EPIC: Foundational Infrastructure ### EPIC: Foundational Infrastructure
- [ ] **Establish CI/CD Pipeline:** A robust CI/CD pipeline is essential for ensuring code quality and enabling safe deployments. - [ ] **Establish CI/CD Pipeline:** A robust CI/CD pipeline is essential for ensuring code quality and enabling safe deployments.
- [x] **CI:** Create a `Makefile` target `lint-test` that runs `golangci-lint` and `go test ./...`. Configure the CI pipeline to run this on every push. *(Jules' Note: The `lint-test` target now exists and passes successfully.)* - [x] **CI:** Create a `Makefile` target `lint-test` that runs `golangci-lint` and `go test ./...`. Configure the CI pipeline to run this on every push. *(Jules' Note: The `lint-test` target now exists and passes successfully.)*
- [ ] **CD:** Set up automated deployments to a staging environment upon a successful merge to the main branch. - [ ] **CD:** Set up automated deployments to a staging environment upon a successful merge to the main branch.
- [ ] **Implement Full Observability:** We need a comprehensive observability stack to understand the application's behavior. - [ ] **Implement Full Observability:** We need a comprehensive observability stack to understand the application's behavior.
- [ ] **Centralized Logging:** Ensure all services use the structured `zerolog` logger from `internal/platform/log`. Add request/user/span IDs to the logging context in the HTTP middleware. - [ ] **Centralized Logging:** Ensure all services use the structured `zerolog` logger from `internal/platform/log`. Add request/user/span IDs to the logging context in the HTTP middleware.
- [ ] **Metrics:** Add Prometheus metrics for API request latency, error rates, and database query performance. - [ ] **Metrics:** Add Prometheus metrics for API request latency, error rates, and database query performance.
- [ ] **Tracing:** Instrument all application services and data layer methods with OpenTelemetry tracing. - [ ] **Tracing:** Instrument all application services and data layer methods with OpenTelemetry tracing.
### EPIC: Core Architectural Refactoring ### EPIC: Core Architectural Refactoring
- [x] **Refactor Dependency Injection:** The application's DI container in `internal/app/app.go` violates the Dependency Inversion Principle. *(Jules' Note: The composition root has been moved to `cmd/api/main.go`.)* - [x] **Refactor Dependency Injection:** The application's DI container in `internal/app/app.go` violates the Dependency Inversion Principle. *(Jules' Note: The composition root has been moved to `cmd/api/main.go`.)*
- [x] Refactor `NewApplication` to accept repository *interfaces* (e.g., `domain.WorkRepository`) instead of the concrete `*sql.Repositories`. - [x] Refactor `NewApplication` to accept repository *interfaces* (e.g., `domain.WorkRepository`) instead of the concrete `*sql.Repositories`.
- [x] Move the instantiation of platform components (e.g., `JWTManager`) out of `NewApplication` and into `cmd/api/main.go`, passing them in as dependencies. - [x] Move the instantiation of platform components (e.g., `JWTManager`) out of `NewApplication` and into `cmd/api/main.go`, passing them in as dependencies.
- [ ] **Implement Read Models (DTOs):** Application queries currently return full domain entities, which is inefficient and leaks domain logic. - [ ] **Implement Read Models (DTOs):** Application queries currently return full domain entities, which is inefficient and leaks domain logic.
- [ ] Refactor application queries (e.g., in `internal/app/work/queries.go`) to return specialized read models (DTOs) tailored for the API. - [ ] Refactor application queries (e.g., in `internal/app/work/queries.go`) to return specialized read models (DTOs) tailored for the API.
- [ ] **Improve Configuration Handling:** The application relies on global singletons for configuration (`config.Cfg`). - [ ] **Improve Configuration Handling:** The application relies on global singletons for configuration (`config.Cfg`).
- [ ] Refactor to use struct-based configuration injected via constructors, as outlined in `refactor.md`. - [ ] Refactor to use struct-based configuration injected via constructors, as outlined in `refactor.md`.
- [ ] Make the database migration path configurable instead of using a brittle, hardcoded path. - [ ] Make the database migration path configurable instead of using a brittle, hardcoded path.
- [ ] Make the metrics server port configurable. - [ ] Make the metrics server port configurable.
### EPIC: Robust Testing Framework ### EPIC: Robust Testing Framework
- [ ] **Refactor Testing Utilities:** Decouple our tests from a live database to make them faster and more reliable. - [ ] **Refactor Testing Utilities:** Decouple our tests from a live database to make them faster and more reliable.
- [ ] Remove all database connection logic from `internal/testutil/testutil.go`. - [ ] Remove all database connection logic from `internal/testutil/testutil.go`.
- [x] **Implement Mock Repositories:** The test mocks are incomplete and `panic`. *(Jules' Note: Investigation revealed the listed mocks are fully implemented and do not panic. This task is complete.)* - [x] **Implement Mock Repositories:** The test mocks are incomplete and `panic`. *(Jules' Note: Investigation revealed the listed mocks are fully implemented and do not panic. This task is complete.)*
- [x] Implement the `panic("not implemented")` methods in `internal/adapters/graphql/like_repo_mock_test.go`, `internal/adapters/graphql/work_repo_mock_test.go`, and `internal/testutil/mock_user_repository.go`. - [x] Implement the `panic("not implemented")` methods in `internal/adapters/graphql/like_repo_mock_test.go`, `internal/adapters/graphql/work_repo_mock_test.go`, and `internal/testutil/mock_user_repository.go`.
--- ---
@ -67,10 +67,10 @@ This document is the single source of truth for all outstanding development task
- [ ] **Implement `AnalyzeWork` Command:** The `AnalyzeWork` command in `internal/app/work/commands.go` is currently a stub. - [ ] **Implement `AnalyzeWork` Command:** The `AnalyzeWork` command in `internal/app/work/commands.go` is currently a stub.
- [ ] **Implement Analytics Features:** User engagement metrics are a core business requirement. - [ ] **Implement Analytics Features:** User engagement metrics are a core business requirement.
- [ ] Implement like, comment, and bookmark counting. - [ ] Implement like, comment, and bookmark counting.
- [ ] Implement a service to calculate popular translations based on the above metrics. - [ ] Implement a service to calculate popular translations based on the above metrics.
- [ ] **Refactor `enrich` Tool:** The `cmd/tools/enrich/main.go` tool is architecturally misaligned. - [ ] **Refactor `enrich` Tool:** The `cmd/tools/enrich/main.go` tool is architecturally misaligned.
- [ ] Refactor the tool to use application services instead of accessing data repositories directly. - [ ] Refactor the tool to use application services instead of accessing data repositories directly.
### EPIC: Further Architectural Improvements ### EPIC: Further Architectural Improvements

View File

@ -70,9 +70,6 @@ func main() {
log.Fatalf("cannot load config: %v", err) log.Fatalf("cannot load config: %v", err)
} }
// Initialize Bleve search
search.InitBleve()
// Initialize logger // Initialize logger
app_log.Init("tercul-api", cfg.Environment) app_log.Init("tercul-api", cfg.Environment)
obsLogger := observability.NewLogger("tercul-api", cfg.Environment) obsLogger := observability.NewLogger("tercul-api", cfg.Environment)

View File

@ -26,18 +26,21 @@ tercul-go/
## 🏗️ Architecture Highlights ## 🏗️ Architecture Highlights
### 1. **Clean Architecture** ### 1. **Clean Architecture**
- **Domain Layer**: Pure business entities with validation logic - **Domain Layer**: Pure business entities with validation logic
- **Application Layer**: Use cases and business logic (to be implemented) - **Application Layer**: Use cases and business logic (to be implemented)
- **Infrastructure Layer**: Database, storage, external services (to be implemented) - **Infrastructure Layer**: Database, storage, external services (to be implemented)
- **Presentation Layer**: HTTP API, GraphQL, admin interface (to be implemented) - **Presentation Layer**: HTTP API, GraphQL, admin interface (to be implemented)
### 2. **Database Design** ### 2. **Database Design**
- **PostgreSQL 16+**: Modern, performant database with advanced features - **PostgreSQL 16+**: Modern, performant database with advanced features
- **Improved Schema**: Fixed all identified data quality issues - **Improved Schema**: Fixed all identified data quality issues
- **Performance Indexes**: Full-text search, trigram matching, JSONB indexes - **Performance Indexes**: Full-text search, trigram matching, JSONB indexes
- **Data Integrity**: Proper foreign keys, constraints, and triggers - **Data Integrity**: Proper foreign keys, constraints, and triggers
### 3. **Technology Stack** ### 3. **Technology Stack**
- **Go 1.24+**: Latest stable version with modern features - **Go 1.24+**: Latest stable version with modern features
- **GORM v3**: Type-safe ORM with PostgreSQL support - **GORM v3**: Type-safe ORM with PostgreSQL support
- **Chi Router**: Lightweight, fast HTTP router - **Chi Router**: Lightweight, fast HTTP router
@ -47,6 +50,7 @@ tercul-go/
## 🔧 Data Quality Issues Addressed ## 🔧 Data Quality Issues Addressed
### **Schema Improvements** ### **Schema Improvements**
1. **Timestamp Formats**: Proper DATE and TIMESTAMP types 1. **Timestamp Formats**: Proper DATE and TIMESTAMP types
2. **UUID Handling**: Consistent UUID generation and validation 2. **UUID Handling**: Consistent UUID generation and validation
3. **Content Cleaning**: Structured JSONB for complex data 3. **Content Cleaning**: Structured JSONB for complex data
@ -54,6 +58,7 @@ tercul-go/
5. **Data Types**: Proper ENUMs for categorical data 5. **Data Types**: Proper ENUMs for categorical data
### **Data Migration Strategy** ### **Data Migration Strategy**
- **Phased Approach**: Countries → Authors → Works → Media → Copyrights - **Phased Approach**: Countries → Authors → Works → Media → Copyrights
- **Data Validation**: Comprehensive validation during migration - **Data Validation**: Comprehensive validation during migration
- **Error Handling**: Graceful handling of malformed data - **Error Handling**: Graceful handling of malformed data
@ -62,18 +67,21 @@ tercul-go/
## 🚀 Key Features Implemented ## 🚀 Key Features Implemented
### 1. **Domain Models** ### 1. **Domain Models**
- **Author Entity**: Core author information with validation - **Author Entity**: Core author information with validation
- **AuthorTranslation**: Multi-language author details - **AuthorTranslation**: Multi-language author details
- **Error Handling**: Comprehensive domain-specific errors - **Error Handling**: Comprehensive domain-specific errors
- **Business Logic**: Age calculation, validation rules - **Business Logic**: Age calculation, validation rules
### 2. **Development Environment** ### 2. **Development Environment**
- **Docker Compose**: PostgreSQL, Redis, Adminer, Redis Commander - **Docker Compose**: PostgreSQL, Redis, Adminer, Redis Commander
- **Hot Reloading**: Go development with volume mounting - **Hot Reloading**: Go development with volume mounting
- **Database Management**: Easy database reset, backup, restore - **Database Management**: Easy database reset, backup, restore
- **Monitoring**: Health checks and service status - **Monitoring**: Health checks and service status
### 3. **Migration Tools** ### 3. **Migration Tools**
- **SQLite to PostgreSQL**: Complete data migration pipeline - **SQLite to PostgreSQL**: Complete data migration pipeline
- **Schema Creation**: Automated database setup - **Schema Creation**: Automated database setup
- **Data Validation**: Quality checks during migration - **Data Validation**: Quality checks during migration
@ -94,6 +102,7 @@ Based on the analysis of your SQLite dump:
## 🎯 Next Implementation Steps ## 🎯 Next Implementation Steps
### **Phase 1: Complete Domain Models** (Week 1-2) ### **Phase 1: Complete Domain Models** (Week 1-2)
- [ ] Work and WorkTranslation entities - [ ] Work and WorkTranslation entities
- [ ] Book and BookTranslation entities - [ ] Book and BookTranslation entities
- [ ] Country and CountryTranslation entities - [ ] Country and CountryTranslation entities
@ -101,30 +110,35 @@ Based on the analysis of your SQLite dump:
- [ ] User and authentication entities - [ ] User and authentication entities
### **Phase 2: Repository Layer** (Week 3-4) ### **Phase 2: Repository Layer** (Week 3-4)
- [ ] Database repositories for all entities - [ ] Database repositories for all entities
- [ ] Data access abstractions - [ ] Data access abstractions
- [ ] Transaction management - [ ] Transaction management
- [ ] Query optimization - [ ] Query optimization
### **Phase 3: Service Layer** (Week 5-6) ### **Phase 3: Service Layer** (Week 5-6)
- [ ] Business logic implementation - [ ] Business logic implementation
- [ ] Search and filtering services - [ ] Search and filtering services
- [ ] Content management services - [ ] Content management services
- [ ] Authentication and authorization - [ ] Authentication and authorization
### **Phase 4: API Layer** (Week 7-8) ### **Phase 4: API Layer** (Week 7-8)
- [ ] HTTP handlers and middleware - [ ] HTTP handlers and middleware
- [ ] RESTful API endpoints - [ ] RESTful API endpoints
- [ ] GraphQL schema and resolvers - [ ] GraphQL schema and resolvers
- [ ] Input validation and sanitization - [ ] Input validation and sanitization
### **Phase 5: Admin Interface** (Week 9-10) ### **Phase 5: Admin Interface** (Week 9-10)
- [ ] Content management system - [ ] Content management system
- [ ] User administration - [ ] User administration
- [ ] Data import/export tools - [ ] Data import/export tools
- [ ] Analytics and reporting - [ ] Analytics and reporting
### **Phase 6: Testing & Deployment** (Week 11-12) ### **Phase 6: Testing & Deployment** (Week 11-12)
- [ ] Comprehensive testing suite - [ ] Comprehensive testing suite
- [ ] Performance optimization - [ ] Performance optimization
- [ ] Production deployment - [ ] Production deployment
@ -155,12 +169,14 @@ make logs
## 🔍 Data Migration Process ## 🔍 Data Migration Process
### **Step 1: Schema Creation** ### **Step 1: Schema Creation**
```bash ```bash
# Database will be automatically initialized with proper schema # Database will be automatically initialized with proper schema
docker-compose up -d postgres docker-compose up -d postgres
``` ```
### **Step 2: Data Migration** ### **Step 2: Data Migration**
```bash ```bash
# Migrate data from your SQLite dump # Migrate data from your SQLite dump
make migrate-data make migrate-data
@ -168,6 +184,7 @@ make migrate-data
``` ```
### **Step 3: Verification** ### **Step 3: Verification**
```bash ```bash
# Check migration status # Check migration status
make status make status
@ -177,17 +194,20 @@ make status
## 📈 Performance Improvements ## 📈 Performance Improvements
### **Database Optimizations** ### **Database Optimizations**
- **Full-Text Search**: PostgreSQL FTS for fast text search - **Full-Text Search**: PostgreSQL FTS for fast text search
- **Trigram Indexes**: Partial string matching - **Trigram Indexes**: Partial string matching
- **JSONB Indexes**: Efficient JSON querying - **JSONB Indexes**: Efficient JSON querying
- **Connection Pooling**: Optimized database connections - **Connection Pooling**: Optimized database connections
### **Caching Strategy** ### **Caching Strategy**
- **Redis**: Frequently accessed data caching - **Redis**: Frequently accessed data caching
- **Application Cache**: In-memory caching for hot data - **Application Cache**: In-memory caching for hot data
- **CDN Ready**: Static asset optimization - **CDN Ready**: Static asset optimization
### **Search Capabilities** ### **Search Capabilities**
- **Multi-language Search**: Support for all content languages - **Multi-language Search**: Support for all content languages
- **Fuzzy Matching**: Typo-tolerant search - **Fuzzy Matching**: Typo-tolerant search
- **Faceted Search**: Filter by author, genre, language, etc. - **Faceted Search**: Filter by author, genre, language, etc.
@ -196,12 +216,14 @@ make status
## 🔒 Security Features ## 🔒 Security Features
### **Authentication & Authorization** ### **Authentication & Authorization**
- **JWT Tokens**: Secure API authentication - **JWT Tokens**: Secure API authentication
- **Role-Based Access**: Admin, editor, viewer roles - **Role-Based Access**: Admin, editor, viewer roles
- **API Rate Limiting**: Prevent abuse and DDoS - **API Rate Limiting**: Prevent abuse and DDoS
- **Input Validation**: Comprehensive input sanitization - **Input Validation**: Comprehensive input sanitization
### **Data Protection** ### **Data Protection**
- **HTTPS Enforcement**: Encrypted communication - **HTTPS Enforcement**: Encrypted communication
- **SQL Injection Prevention**: Parameterized queries - **SQL Injection Prevention**: Parameterized queries
- **XSS Protection**: Content sanitization - **XSS Protection**: Content sanitization
@ -210,12 +232,14 @@ make status
## 📊 Monitoring & Observability ## 📊 Monitoring & Observability
### **Metrics Collection** ### **Metrics Collection**
- **Prometheus**: System and business metrics - **Prometheus**: System and business metrics
- **Grafana**: Visualization and dashboards - **Grafana**: Visualization and dashboards
- **Health Checks**: Service health monitoring - **Health Checks**: Service health monitoring
- **Performance Tracking**: Response time and throughput - **Performance Tracking**: Response time and throughput
### **Logging Strategy** ### **Logging Strategy**
- **Structured Logging**: JSON format logs - **Structured Logging**: JSON format logs
- **Log Levels**: Debug, info, warn, error - **Log Levels**: Debug, info, warn, error
- **Audit Trail**: Track all data changes - **Audit Trail**: Track all data changes
@ -224,24 +248,28 @@ make status
## 🌟 Key Benefits of This Architecture ## 🌟 Key Benefits of This Architecture
### **1. Data Preservation** ### **1. Data Preservation**
- **100% Record Migration**: All cultural content preserved - **100% Record Migration**: All cultural content preserved
- **Data Quality**: Automatic fixing of identified issues - **Data Quality**: Automatic fixing of identified issues
- **Relationship Integrity**: Maintains all author-work connections - **Relationship Integrity**: Maintains all author-work connections
- **Multi-language Support**: Preserves all language variants - **Multi-language Support**: Preserves all language variants
### **2. Performance** ### **2. Performance**
- **10x Faster Search**: Full-text search and optimized indexes - **10x Faster Search**: Full-text search and optimized indexes
- **Scalable Architecture**: Designed for 10,000+ concurrent users - **Scalable Architecture**: Designed for 10,000+ concurrent users
- **Efficient Caching**: Redis-based caching strategy - **Efficient Caching**: Redis-based caching strategy
- **Optimized Queries**: Database query optimization - **Optimized Queries**: Database query optimization
### **3. Maintainability** ### **3. Maintainability**
- **Clean Code**: Following Go best practices - **Clean Code**: Following Go best practices
- **Modular Design**: Easy to extend and modify - **Modular Design**: Easy to extend and modify
- **Comprehensive Testing**: 90%+ test coverage target - **Comprehensive Testing**: 90%+ test coverage target
- **Documentation**: Complete API and development docs - **Documentation**: Complete API and development docs
### **4. Future-Proof** ### **4. Future-Proof**
- **Modern Stack**: Latest Go and database technologies - **Modern Stack**: Latest Go and database technologies
- **Extensible Design**: Easy to add new features - **Extensible Design**: Easy to add new features
- **API-First**: Ready for mobile apps and integrations - **API-First**: Ready for mobile apps and integrations
@ -250,6 +278,7 @@ make status
## 🚀 Getting Started ## 🚀 Getting Started
1. **Clone and Setup** 1. **Clone and Setup**
```bash ```bash
git clone <repository-url> git clone <repository-url>
cd tercul-go cd tercul-go
@ -258,31 +287,35 @@ make status
``` ```
2. **Start Development Environment** 2. **Start Development Environment**
```bash ```bash
make setup make setup
``` ```
3. **Migrate Your Data** 3. **Migrate Your Data**
```bash ```bash
make migrate-data make migrate-data
# Enter path to your SQLite dump # Enter path to your SQLite dump
``` ```
4. **Start the Application** 4. **Start the Application**
```bash ```bash
make run make run
``` ```
5. **Access the System** 5. **Access the System**
- **API**: http://localhost:8080 - **API**: <http://localhost:8080>
- **Database Admin**: http://localhost:8081 - **Database Admin**: <http://localhost:8081>
- **Redis Admin**: http://localhost:8082 - **Redis Admin**: <http://localhost:8082>
## 📞 Support & Next Steps ## 📞 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. 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:** **Next Steps:**
1. Review the architecture document for detailed technical specifications 1. Review the architecture document for detailed technical specifications
2. Set up the development environment using the provided tools 2. Set up the development environment using the provided tools
3. Run the data migration to transfer your existing content 3. Run the data migration to transfer your existing content

111
go.mod
View File

@ -1,14 +1,12 @@
module tercul module tercul
go 1.24.0 go 1.24.10
toolchain go1.24.2
require ( require (
github.com/99designs/gqlgen v0.17.72 github.com/99designs/gqlgen v0.17.72
github.com/DATA-DOG/go-sqlmock v1.5.2 github.com/DATA-DOG/go-sqlmock v1.5.2
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 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-playground/validator/v10 v10.28.0
github.com/go-redis/redismock/v9 v9.2.0 github.com/go-redis/redismock/v9 v9.2.0
github.com/golang-jwt/jwt/v5 v5.3.0 github.com/golang-jwt/jwt/v5 v5.3.0
@ -23,68 +21,65 @@ require (
github.com/rs/zerolog v1.34.0 github.com/rs/zerolog v1.34.0
github.com/spf13/viper v1.21.0 github.com/spf13/viper v1.21.0
github.com/stretchr/testify v1.11.1 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/vektah/gqlparser/v2 v2.5.26
github.com/weaviate/weaviate v1.30.2 github.com/weaviate/weaviate v1.33.6
github.com/weaviate/weaviate-go-client/v5 v5.1.0 github.com/weaviate/weaviate-go-client/v5 v5.6.0
go.opentelemetry.io/otel v1.38.0 go.opentelemetry.io/otel v1.38.0
go.opentelemetry.io/otel/exporters/stdout/stdouttrace 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/sdk v1.38.0
go.opentelemetry.io/otel/trace 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/postgres v1.5.11
gorm.io/driver/sqlite v1.6.0 gorm.io/driver/sqlite v1.6.0
gorm.io/gorm v1.30.0 gorm.io/gorm v1.30.0
) )
require ( require (
dario.cat/mergo v1.0.2 // indirect
filippo.io/edwards25519 v1.1.0 // 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/ch-go v0.67.0 // indirect
github.com/ClickHouse/clickhouse-go/v2 v2.40.1 // 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/agnivade/levenshtein v1.2.1 // indirect
github.com/andybalholm/brotli v1.2.0 // indirect github.com/andybalholm/brotli v1.2.0 // indirect
github.com/antlr4-go/antlr/v4 v4.13.0 // indirect github.com/antlr4-go/antlr/v4 v4.13.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect github.com/beorn7/perks v1.0.1 // indirect
github.com/bits-and-blooms/bitset v1.22.0 // indirect github.com/cenkalti/backoff/v4 v4.3.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/cespare/xxhash/v2 v2.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/coder/websocket v1.8.12 // 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/cpuguy83/go-md2man/v2 v2.0.7 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // 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/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/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-sysinfo v1.15.4 // indirect
github.com/elastic/go-windows v1.0.2 // 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/fsnotify/fsnotify v1.9.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.10 // indirect github.com/gabriel-vasile/mimetype v1.4.10 // indirect
github.com/go-faster/city v1.0.1 // indirect github.com/go-faster/city v1.0.1 // indirect
github.com/go-faster/errors v0.7.1 // indirect github.com/go-faster/errors v0.7.1 // indirect
github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // 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/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/jsonpointer v0.21.0 // indirect
github.com/go-openapi/jsonreference 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/loads v0.22.0 // indirect
github.com/go-openapi/runtime v0.24.2 // indirect github.com/go-openapi/runtime v0.24.2 // indirect
github.com/go-openapi/spec v0.21.0 // 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/swag v0.23.0 // indirect
github.com/go-openapi/validate v0.24.0 // indirect github.com/go-openapi/validate v0.24.0 // indirect
github.com/go-playground/locales v0.14.1 // 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-jwt/jwt/v4 v4.5.2 // indirect
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
github.com/golang-sql/sqlexp v0.1.0 // 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/gorilla/websocket v1.5.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // 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/joho/godotenv v1.5.1 // indirect
github.com/jonboulle/clockwork v0.5.0 // indirect github.com/jonboulle/clockwork v0.5.0 // indirect
github.com/josharian/intern v1.0.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/klauspost/compress v1.18.0 // indirect
github.com/leodido/go-urn v1.4.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/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // 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/interpolate v0.0.2 // indirect
github.com/mfridman/xflag v0.1.0 // indirect github.com/mfridman/xflag v0.1.0 // indirect
github.com/microsoft/go-mssqldb v1.9.2 // indirect github.com/microsoft/go-mssqldb v1.9.2 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/moby/go-archive v0.1.0 // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect github.com/moby/patternmatcher v0.6.0 // indirect
github.com/mschoch/smat v0.2.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/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/ncruces/go-strftime v0.1.9 // indirect github.com/ncruces/go-strftime v0.1.9 // indirect
github.com/oklog/ulid v1.3.1 // 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/opentracing/opentracing-go v1.2.0 // indirect
github.com/paulmach/orb v0.11.1 // indirect github.com/paulmach/orb v0.11.1 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/pierrec/lz4/v4 v4.1.22 // indirect github.com/pierrec/lz4/v4 v4.1.22 // indirect
github.com/pkg/errors v0.9.1 // indirect github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/client_model v0.6.1 // indirect github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
github.com/prometheus/common v0.62.0 // 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/prometheus/procfs v0.15.1 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/robfig/cron/v3 v3.0.1 // 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/sagikazarmark/locafero v0.11.0 // indirect
github.com/segmentio/asm v1.2.0 // indirect github.com/segmentio/asm v1.2.0 // indirect
github.com/sethvargo/go-retry v0.3.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/shopspring/decimal v1.4.0 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/sosodev/duration v1.3.1 // indirect github.com/sosodev/duration v1.3.1 // indirect
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
github.com/spf13/afero v1.15.0 // indirect github.com/spf13/afero v1.15.0 // indirect
github.com/spf13/cast v1.10.0 // indirect github.com/spf13/cast v1.10.0 // indirect
github.com/spf13/pflag v1.0.10 // 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/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/tursodatabase/libsql-client-go v0.0.0-20240902231107-85af5b9d094d // indirect
github.com/urfave/cli/v2 v2.27.6 // indirect github.com/urfave/cli/v2 v2.27.6 // indirect
github.com/vertica/vertica-sql-go v1.3.3 // indirect github.com/vertica/vertica-sql-go v1.3.3 // indirect
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // 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-genproto v0.0.0-20241112172322-ea1f63298f77 // indirect
github.com/ydb-platform/ydb-go-sdk/v3 v3.108.1 // 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 github.com/ziutek/mymysql v1.5.4 // indirect
go.etcd.io/bbolt v1.4.0 // indirect go.mongodb.org/mongo-driver v1.17.6 // indirect
go.mongodb.org/mongo-driver v1.14.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect
go.opentelemetry.io/otel/metric v1.38.0 // indirect go.opentelemetry.io/otel/metric v1.38.0 // indirect
go.uber.org/multierr v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b // indirect golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b // indirect
golang.org/x/mod v0.27.0 // indirect golang.org/x/mod v0.28.0 // indirect
golang.org/x/net v0.43.0 // indirect golang.org/x/net v0.46.1-0.20251013234738-63d1a5100f82 // indirect
golang.org/x/oauth2 v0.25.0 // indirect golang.org/x/oauth2 v0.33.0 // indirect
golang.org/x/sync v0.17.0 // indirect golang.org/x/sync v0.18.0 // indirect
golang.org/x/sys v0.36.0 // indirect golang.org/x/sys v0.37.0 // indirect
golang.org/x/text v0.29.0 // indirect golang.org/x/text v0.30.0 // indirect
golang.org/x/time v0.11.0 // indirect golang.org/x/time v0.12.0 // indirect
golang.org/x/tools v0.36.0 // indirect golang.org/x/tools v0.37.0 // indirect
gonum.org/v1/gonum v0.15.1 // indirect gonum.org/v1/gonum v0.16.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250102185135-69823020774d // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8 // indirect
google.golang.org/grpc v1.69.4 // indirect google.golang.org/grpc v1.77.0 // indirect
google.golang.org/protobuf v1.36.6 // indirect google.golang.org/protobuf v1.36.10 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
howett.net/plist v1.0.1 // indirect howett.net/plist v1.0.1 // indirect

261
go.sum
View File

@ -1,19 +1,25 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 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= 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 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= 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 h1:2JDAuutIYtAN26BAtigfLZFnTN53fpYbIENL8bVgAKY=
github.com/99designs/gqlgen v0.17.72/go.mod h1:BoL4C3j9W2f95JeWMrSArdDNGWmZB9MOS2EMHJDZmUc= 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/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk=
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/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 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/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.2 h1:9iefClla7iYpfYWdzPCRDozdmndjTm8DXdpCzPajMgA=
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/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 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/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 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/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 h1:oygO0locgZJe7PpYPXT5A29ZkwJaPqcva7BVeemZOZs=
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= 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= 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/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 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU=
github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU= 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 h1:pFYcNSqHxBD06Fpj/KsbStFRsgRATgnf3LeXiUkhzPo=
github.com/PuerkitoBio/goquery v1.10.3/go.mod h1:tMUX0zDMHXYlAQk6p35XxQMqMweEKB7iK7iLNd4RH4Y= 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/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/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 h1:EHBY3UOn1gwdy/VbFwgo4cxecRznFk7fKWN1KOX7eoM=
github.com/agnivade/levenshtein v1.2.1/go.mod h1:QVVI16kDrtSuwcpd0p1+xMC6Z/VfhtCyDIjcwga4/DU= 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= 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/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 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 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 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= 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 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= 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/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.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= 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/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 h1:5bUXkEPPIbewrnkU8LTCLVaxi4N4J8ahufH2vlo4NAo=
github.com/coder/websocket v1.8.12/go.mod h1:LNVeNrXQZfe5qhS9ALED3uA+l5pPqvwXg3CKoDBB2gs= 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/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 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo=
github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= 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.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.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.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= 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/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 h1:SG7nF6SRlWhcT7cNTs5R6Hk4V2lcmLz2NsG2VnInyNo=
github.com/dgryski/trifles v0.0.0-20230903005119-f50d829f2e54/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA= 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.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 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= 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.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 h1:A3zQcunCxik14MgXu39cXFXcIw2sFXZ0zL886eyiv1Q=
github.com/elastic/go-sysinfo v1.15.4/go.mod h1:ZBVXmqS368dOn/jvijV/zHLfakWTYHBZPk3G244lHrU= 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.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/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/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/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 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= 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/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 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= 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.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 h1:aGday7OWupfMs+LbmLZG4k0MYXIANxcuBTYUC03zFCU=
github.com/go-openapi/analysis v0.23.0/go.mod h1:9mz9ZWaSlV8TvjQHLl2mUW2PbZtemkE8yA5v22ohupo= 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.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.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.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.4 h1:oi2K9mHTOb5DPW2Zjdzs/NIvwi2N3fARKaTJLdNabaM=
github.com/go-openapi/errors v0.22.0/go.mod h1:J3DmZScxCDufmIMsdOuDHxJbdOGC0xtUynjIx092vXE= 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.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonpointer v0.19.5/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= 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.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.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.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.25.0 h1:7R0RX7mbKLa9EYCTHRcCuIPcaqlyQiWNPTXwClK0saQ=
github.com/go-openapi/strfmt v0.23.0/go.mod h1:NrtIpfKtWIygRkKVsxh7XQMDQW5HKQl6S5ik2elW+K4= 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.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.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.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 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= 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.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 h1:LdfDKwNbpB6Vn40xhTdNZAnfLECL81w+VX3BumrGD58=
github.com/go-openapi/validate v0.24.0/go.mod h1:iyeX1sEufmv3nPbBdX3ieNviWnOZaJ1+zquzJEf2BAQ= 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 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= 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.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.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.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/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.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.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.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 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= 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 h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= 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= 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/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 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 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 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 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/hibiken/asynq v0.25.1 h1:phj028N0nm15n8O2ims+IvJ2gz4k2auvermngh9JhTw= 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/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 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= 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/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.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4=
github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= 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/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 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= 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-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/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= 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.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.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.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/moby/go-archive v0.1.0 h1:Kk/5rdW/g+H8NHdJW2gsXyZ7UnzvJNOy6VKJqueWdcQ=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/moby/go-archive v0.1.0/go.mod h1:G9B+YoujNohJmrIYFBpSd54GTUB4lt9S+xVQvsJyFuo=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 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/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
github.com/mschoch/smat v0.2.0 h1:8imxQsjDm8yFEAVBe7azKmKSgzSkZXDuKkSq9374khM= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
github.com/mschoch/smat v0.2.0/go.mod h1:kc9mz7DoBKqDyiRL7VZN8KvXQMWeTaVnttLRXOlotKw= 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 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= 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= 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/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 h1:rfztXRbg6nv/5f+Raen9RcGoSecHIFgBBLQK3Wdj754=
github.com/onsi/gomega v1.27.1/go.mod h1:aHX5xOykVYzWOV4WqQy0sy8BQptgukenXpCXfadcIAw= 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 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs=
github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
github.com/paulmach/orb v0.11.1 h1:3koVegMC4X/WeiXYz9iswopaTwMem53NzTJuTF20JzU= 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.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 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 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 h1:KJakav68jdH0WDvoAcj8+n61WqOIaPGgH0bJWS6jpmM=
github.com/pressly/goose/v3 v3.26.0/go.mod h1:4hC1KrritdCxtuFsqgs1R4AU5bWtTAf+cnWvfhf2DNY= 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 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y=
github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= 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.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.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io= github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE=
github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I= 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.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 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= 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/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 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys=
github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= 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.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw=
github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= 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 h1:EEt31A35QhrcRZtrYFDTBg91cqZVnFL2navjDrah2SE=
github.com/sethvargo/go-retry v0.3.0/go.mod h1:mNX17F0C/HguQMyMyJxcnU471gOZGxCLyYaFyAZraas= 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 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= 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.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.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= 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 h1:qtHBDMQ6lvMQsL15g4aopM4HEfOaYuhWBw3NPTtlqq4=
github.com/sosodev/duration v1.3.1/go.mod h1:RQIBBX0+fMLc/D9+Jb/fwvVmo0eZvDDEERAikUR6SDg= 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= 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/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.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.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.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= 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.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 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= 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/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 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= 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/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 h1:dOMI4+zEbDI37KGb0TI44GUAwxHF9cMsIoDTJ7UmgfU=
github.com/tursodatabase/libsql-client-go v0.0.0-20240902231107-85af5b9d094d/go.mod h1:l8xTsYB90uaVdMHXMCxKKLSgw5wLYBwBKKefNIUnm9s= 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= 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/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 h1:fL+FKEAEy5ONmsvya2WH5T8bhkvY27y/Ik3ReR2T+Qw=
github.com/vertica/vertica-sql-go v1.3.3/go.mod h1:jnn2GFuv+O2Jcjktb7zyc4Utlbu9YVqpHH/lx63+1M4= 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.33.6 h1:uOOvb63qdAZkRwY7PMIAGJQ1GMAkDv8ivqjkR+fhKTI=
github.com/weaviate/weaviate v1.30.2/go.mod h1:FQJsD9pckNolW1C+S+P88okIX6DEOLJwf7aqFvgYgSQ= github.com/weaviate/weaviate v1.33.6/go.mod h1:NSKZOHzysOKarSWJaPFPkU3+qqbFEtOKyGUhM/p7YO4=
github.com/weaviate/weaviate-go-client/v5 v5.1.0 h1:3wSf4fktKLvspPHwDYnn07u0sKfDAhrA5JeRe+R4ENg= github.com/weaviate/weaviate-go-client/v5 v5.6.0 h1:1/TRRxcepr8LH1yWoyHjdCDHHv8qMm3cO4oAOvkLAKM=
github.com/weaviate/weaviate-go-client/v5 v5.1.0/go.mod h1:gg5qyiHk53+HMZW2ynkrgm+cMQDD2Ewyma84rBeChz4= 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/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.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs=
github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g= 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/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.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/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 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs=
github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0= 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.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.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.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.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.17.6 h1:87JUG1wZfWsr6rIz3ZmpH90rL5tea7O3IHuSwHUpsss=
go.mongodb.org/mongo-driver v1.14.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGcNU603k65c= go.mongodb.org/mongo-driver v1.17.6/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= 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 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= 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 h1:kJxSDN4SgWWTjG/hPp3O7LCGLcHXFlvS2/FFOrwL+SE=
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0/go.mod h1:mgIOzS7iZeKJdeB8/NYHrJ48fdGc71Llo5bJ1J4DWUE= 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= 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 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=
go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= 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 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 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= 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-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-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.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI= golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8= 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-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-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/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/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.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.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.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U=
golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= 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-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-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/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-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-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= 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.46.1-0.20251013234738-63d1a5100f82 h1:6/3JGEh1C88g7m+qzzTbl3A0FtsLguXieqofVLU/JAo=
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= 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-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.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.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo=
golang.org/x/oauth2 v0.25.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= 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-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-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/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-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-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.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= 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-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-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/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-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-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-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-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-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-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-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-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-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-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-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-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-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.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.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.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.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= 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-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-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.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.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.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/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.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.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= 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-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-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/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-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-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.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE=
golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= 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-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-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-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/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.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.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.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.15.1/go.mod h1:eZTZuRFrzu5pcyjN5wJhcIhnUdNijYxX1T2IcrOGY0o= 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/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= 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= 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-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-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= 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 v0.0.0-20250603155806-513f23925822 h1:rHWScKit0gvAPuOnu87KpaYtjK5zBMLcULh7gxkCXu4=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250102185135-69823020774d/go.mod h1:3ENsm/5D1mzDyhpzeRi1NR784I0BcofWBoSc5QqqMK4= 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.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= 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.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= 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.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
google.golang.org/grpc v1.69.4 h1:MF5TftSMkd8GLw/m0KM6V8CMOCY6NZ1NQDPGFgbTt4A= google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM=
google.golang.org/grpc v1.69.4/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4= 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-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-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 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.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.27.1/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.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.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= 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 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-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/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-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-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-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 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/postgres v1.5.11 h1:ubBVAfbKEUld/twyKZ0IYn9rSQh448EdelLYk9Mv314= 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/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8=
gorm.io/gorm v1.30.0 h1:qbT5aPv1UH8gI99OsRlvDToLxW5zR7FzS9acZDOZcgs= gorm.io/gorm v1.30.0 h1:qbT5aPv1UH8gI99OsRlvDToLxW5zR7FzS9acZDOZcgs=
gorm.io/gorm v1.30.0/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE= 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-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/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= howett.net/plist v0.0.0-20181124034731-591f970eefbb/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0=

View File

@ -2004,9 +2004,16 @@ func (r *queryResolver) Search(ctx context.Context, query string, limit *int32,
params := domainsearch.SearchParams{ params := domainsearch.SearchParams{
Query: query, Query: query,
Filters: searchFilters, Filters: domainsearch.SearchFilters{
Limit: pageSize, Languages: searchFilters.Languages,
Offset: (page - 1) * pageSize, Tags: searchFilters.Tags,
Categories: searchFilters.Categories,
Authors: searchFilters.Authors,
DateFrom: searchFilters.DateFrom,
DateTo: searchFilters.DateTo,
},
Limit: pageSize,
Offset: (page - 1) * pageSize,
} }
results, err := r.App.Search.Search(ctx, params) results, err := r.App.Search.Search(ctx, params)
if err != nil { if err != nil {
@ -2014,39 +2021,45 @@ func (r *queryResolver) Search(ctx context.Context, query string, limit *int32,
} }
var works []*model.Work 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 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 var authors []*model.Author
for _, a := range results.Authors {
authors = append(authors, &model.Author{ for _, item := range results.Results {
ID: fmt.Sprintf("%d", a.ID), switch item.Type {
Name: a.Name, case "Work":
Language: a.Language, 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,
})
}
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{ return &model.SearchResults{
Works: works, Works: works,
Translations: translations, Translations: translations,
Authors: authors, Authors: authors,
Total: int32(results.Total), Total: int32(results.TotalResults),
}, nil }, nil
} }

View 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
}

View 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!
}

View 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!
}

View 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
}

View File

@ -154,12 +154,12 @@ func (m *mockSearchClient) IndexWork(ctx context.Context, work *domain.Work, pip
args := m.Called(ctx, work, pipeline) args := m.Called(ctx, work, pipeline)
return args.Error(0) 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) args := m.Called(ctx, params)
if args.Get(0) == nil { if args.Get(0) == nil {
return nil, args.Error(1) return nil, args.Error(1)
} }
return args.Get(0).(*domain.SearchResults), args.Error(1) return args.Get(0).(*domainsearch.SearchResults), args.Error(1)
} }

View File

@ -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
}

View File

@ -10,7 +10,7 @@ import (
// Service is the application service for searching. // Service is the application service for searching.
type Service interface { 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 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. // 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) return s.searchClient.Search(ctx, params)
} }

View File

@ -42,12 +42,12 @@ type mockWeaviateWrapper struct {
mock.Mock 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) args := m.Called(ctx, params)
if args.Get(0) == nil { if args.Get(0) == nil {
return nil, args.Error(1) 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 { func (m *mockWeaviateWrapper) IndexWork(ctx context.Context, work *domain.Work, content string) error {
@ -63,23 +63,27 @@ func TestSearchService_Search(t *testing.T) {
ctx := context.Background() ctx := context.Background()
testQuery := "test query" testQuery := "test query"
testFilters := domain.SearchFilters{ testFilters := domainsearch.SearchFilters{
Languages: []string{"en"}, Languages: []string{"en"},
Authors: []string{"1"}, Authors: []string{"1"},
Tags: []string{"test-tag"}, Tags: []string{"test-tag"},
Categories: []string{"test-category"}, Categories: []string{"test-category"},
} }
expectedResults := &domain.SearchResults{ expectedResults := &domainsearch.SearchResults{
Works: []domain.Work{{Title: "Test Work"}}, Results: []domainsearch.SearchResultItem{
Authors: []domain.Author{{Name: "Test Author"}}, {Type: "Work", Entity: domain.Work{Title: "Test Work"}, Score: 0.9},
Total: 2, {Type: "Author", Entity: domain.Author{Name: "Test Author"}, Score: 0.8},
},
TotalResults: 2,
Limit: 10,
Offset: 0,
} }
params := domainsearch.SearchParams{ params := domainsearch.SearchParams{
Query: testQuery, Query: testQuery,
Filters: testFilters, Filters: testFilters,
Limit: 10, Limit: 10,
Offset: 0, Offset: 0,
} }
weaviateWrapper.On("Search", ctx, params).Return(expectedResults, nil) weaviateWrapper.On("Search", ctx, params).Return(expectedResults, nil)

View File

@ -934,6 +934,7 @@ type Embedding struct {
// SearchFilters defines the available filters for a search query. // SearchFilters defines the available filters for a search query.
type SearchFilters struct { type SearchFilters struct {
Types []string
Languages []string Languages []string
Categories []string Categories []string
Tags []string Tags []string
@ -942,12 +943,38 @@ type SearchFilters struct {
DateTo *time.Time 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. // SearchResults represents the results of a search query.
type SearchResults struct { type SearchResults struct {
Works []Work Results []SearchResultItem
Translations []Translation TotalResults int64
Authors []Author Limit int
Total int64 Offset int
} }
// Work-related enums and structs, moved from domain/work/entity.go to break import cycle. // Work-related enums and structs, moved from domain/work/entity.go to break import cycle.

View File

@ -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
}

View File

@ -1,9 +1,25 @@
package search package search
import ( import (
"context"
"tercul/internal/domain" "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 type SearchMode string
const ( const (
@ -12,10 +28,58 @@ const (
SearchModeVector SearchMode = "vector" SearchModeVector SearchMode = "vector"
) )
// SearchParams defines the parameters for a search query.
type SearchParams struct { type SearchParams struct {
Query string Query string
Mode SearchMode Mode SearchMode
Filters domain.SearchFilters Filters SearchFilters
Limit int Limit int
Offset 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)
} }

View File

@ -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)
}
}
}

View File

@ -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
}

View File

@ -2,57 +2,31 @@ package search
import ( import (
"context" "context"
"encoding/json"
"fmt" "fmt"
"log" "sort"
"strconv" "strconv"
"tercul/internal/domain" "tercul/internal/domain"
domainsearch "tercul/internal/domain/search" domainsearch "tercul/internal/domain/search"
"time"
"github.com/weaviate/weaviate-go-client/v5/weaviate" "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/filters"
"github.com/weaviate/weaviate-go-client/v5/weaviate/graphql" "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 { type weaviateWrapper struct {
client *weaviate.Client client *weaviate.Client
host string host string
searchAlpha float64 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} return &weaviateWrapper{client: client, host: host, searchAlpha: searchAlpha}
} }
const ( // hybridAlpha returns the alpha value for hybrid search, clamped between 0 and 1.
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
}
func (w *weaviateWrapper) hybridAlpha() float32 { func (w *weaviateWrapper) hybridAlpha() float32 {
if w.searchAlpha < 0 { if w.searchAlpha < 0 {
return 0 return 0
@ -63,109 +37,147 @@ func (w *weaviateWrapper) hybridAlpha() float32 {
return float32(w.searchAlpha) return float32(w.searchAlpha)
} }
func (w *weaviateWrapper) Search(ctx context.Context, params domainsearch.SearchParams) (*domain.SearchResults, error) { // Search performs a multi-class search against the Weaviate instance.
results := &domain.SearchResults{ func (w *weaviateWrapper) Search(ctx context.Context, params domainsearch.SearchParams) (*domainsearch.SearchResults, error) {
Works: []domain.Work{}, allResults := make([]domainsearch.SearchResultItem, 0)
Translations: []domain.Translation{},
Authors: []domain.Author{}, // 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) if contains(searchTypes, "Work") {
workResults, err := w.searchWorks(ctx, &params)
workFields := []graphql.Field{ if err != nil {
{Name: "title"}, {Name: "description"}, {Name: "language"}, {Name: "status"}, {Name: "publishedAt"}, return nil, err
{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 err != nil {
return nil, err
}
get, ok := response.Data["Get"].(map[string]interface{})
if !ok {
return nil, fmt.Errorf("invalid response data")
}
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 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)
}
} }
allResults = append(allResults, workResults...)
} }
meta, ok := response.Data["Meta"].(map[string]interface{}) if contains(searchTypes, "Translation") {
if !ok { translationResults, err := w.searchTranslations(ctx, &params)
return nil, fmt.Errorf("invalid meta data") if err != nil {
return nil, err
}
allResults = append(allResults, translationResults...)
} }
count, ok := meta["count"].(float64)
if !ok {
return nil, fmt.Errorf("invalid count data")
}
results.Total = int64(count)
return results, nil if contains(searchTypes, "Author") {
authorResults, err := w.searchAuthors(ctx, &params)
if err != nil {
return nil, err
}
allResults = append(allResults, authorResults...)
}
// --- 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(&params.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) operands := make([]*filters.WhereBuilder, 0)
if len(searchFilters.Languages) > 0 { if (className == "Work" || className == "Translation") && len(searchFilters.Languages) > 0 {
operands = append(operands, filters.Where(). operands = append(operands, filters.Where().
WithPath([]string{"language"}). WithPath([]string{"language"}).
WithOperator(filters.ContainsAny). WithOperator(filters.ContainsAny).
@ -173,52 +185,31 @@ func (w *weaviateWrapper) buildWhereFilter(searchFilters domain.SearchFilters, c
} }
if className == "Work" { if className == "Work" {
if searchFilters.DateFrom != nil { if searchFilters.DateFrom != nil && !searchFilters.DateFrom.IsZero() {
operands = append(operands, filters.Where(). operands = append(operands, filters.Where().
WithPath([]string{"publishedAt"}). WithPath([]string{"createdAt"}).
WithOperator(filters.GreaterThanEqual). WithOperator(filters.GreaterThanEqual).
WithValueDate(*searchFilters.DateFrom)) WithValueDate(*searchFilters.DateFrom))
} }
if searchFilters.DateTo != nil && !searchFilters.DateTo.IsZero() {
if searchFilters.DateTo != nil {
operands = append(operands, filters.Where(). operands = append(operands, filters.Where().
WithPath([]string{"publishedAt"}). WithPath([]string{"createdAt"}).
WithOperator(filters.LessThanEqual). WithOperator(filters.LessThanEqual).
WithValueDate(*searchFilters.DateTo)) 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 { if len(searchFilters.Tags) > 0 {
tagOperands := make([]*filters.WhereBuilder, len(searchFilters.Tags)) operands = append(operands, filters.Where().
for i, tag := range searchFilters.Tags { WithPath([]string{"tags"}).
tagOperands[i] = filters.Where(). WithOperator(filters.ContainsAny).
WithPath([]string{"tags", "Tag", "name"}). WithValueText(searchFilters.Tags...))
WithOperator(filters.Equal).
WithValueText(tag)
}
operands = append(operands, filters.Where().WithOperator(filters.Or).WithOperands(tagOperands))
} }
}
if len(searchFilters.Categories) > 0 { if className == "Author" && len(searchFilters.Authors) > 0 {
categoryOperands := make([]*filters.WhereBuilder, len(searchFilters.Categories)) operands = append(operands, filters.Where().
for i, category := range searchFilters.Categories { WithPath([]string{"name"}).
categoryOperands[i] = filters.Where(). WithOperator(filters.ContainsAny).
WithPath([]string{"categories", "Category", "name"}). WithValueText(searchFilters.Authors...))
WithOperator(filters.Equal).
WithValueText(category)
}
operands = append(operands, filters.Where().WithOperator(filters.Or).WithOperands(categoryOperands))
}
} }
if len(operands) == 0 { if len(operands) == 0 {
@ -228,127 +219,177 @@ func (w *weaviateWrapper) buildWhereFilter(searchFilters domain.SearchFilters, c
return filters.Where().WithOperator(filters.And).WithOperands(operands) return filters.Where().WithOperator(filters.And).WithOperands(operands)
} }
func mapToWork(data map[string]interface{}) (domain.Work, error) { type weaviateResult struct {
work := domain.Work{} Additional struct {
Score string `json:"score"`
} `json:"_additional"`
Properties map[string]interface{} `json:"-"`
}
additional, ok := data["_additional"].(map[string]interface{}) func (r *weaviateResult) UnmarshalJSON(data []byte) error {
if !ok { var raw map[string]interface{}
return work, fmt.Errorf("missing _additional field") if err := json.Unmarshal(data, &raw); err != nil {
return err
} }
idStr, ok := additional["id"].(string) if add, ok := raw["_additional"]; ok {
if !ok { addBytes, err := json.Marshal(add)
return work, fmt.Errorf("missing or invalid id") if err != nil {
} return err
id, err := strconv.ParseUint(idStr, 10, 64) }
if err != nil { if err := json.Unmarshal(addBytes, &r.Additional); err != nil {
return work, fmt.Errorf("failed to parse id: %w", err) return 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 delete(raw, "_additional")
r.Properties = raw
return nil
} }
func mapToAuthor(data map[string]interface{}) (domain.Author, error) { // Temporary struct to handle the mismatch between Weaviate's string array for tags
author := domain.Author{} // and the domain's []*Tag struct.
type workWithDBIDAndStringTags struct {
additional, ok := data["_additional"].(map[string]interface{}) domain.Work
if !ok { DBID json.Number `json:"db_id"`
return author, fmt.Errorf("missing _additional field") Tags []string `json:"tags"` // This captures the tags as strings
}
idStr, ok := additional["id"].(string)
if !ok {
return author, fmt.Errorf("missing or invalid id")
}
id, err := strconv.ParseUint(idStr, 10, 64)
if err != nil {
return author, fmt.Errorf("failed to parse id: %w", err)
}
author.ID = uint(id)
if name, ok := data["name"].(string); ok {
author.Name = name
}
if score, ok := additional["score"].(float64); ok {
author.Score = score
}
return author, nil
} }
func mapToTranslation(data map[string]interface{}) (domain.Translation, error) { type translationWithDBID struct {
translation := domain.Translation{} domain.Translation
DBID json.Number `json:"db_id"`
additional, ok := data["_additional"].(map[string]interface{}) }
if !ok { type authorWithDBID struct {
return translation, fmt.Errorf("missing _additional field") domain.Author
} DBID json.Number `json:"db_id"`
Biography string `json:"biography"`
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
} }
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 { 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{}{ properties := map[string]interface{}{
"language": work.Language, "db_id": work.ID,
"title": work.Title, "title": work.Title,
"description": work.Description, "description": work.Description,
"language": work.Language,
"status": work.Status, "status": work.Status,
"createdAt": work.CreatedAt.Format(time.RFC3339), "createdAt": work.CreatedAt,
"updatedAt": work.UpdatedAt.Format(time.RFC3339), "updatedAt": work.UpdatedAt,
} "tags": tags,
if content != "" { "content": content, // Assuming content is passed in
properties["content"] = content
} }
_, err := w.client.Data().Creator(). _, err := w.client.Data().Creator().
WithClassName("Work"). WithClassName("Work").
WithID(fmt.Sprintf("%d", work.ID)). WithID(strconv.FormatUint(uint64(work.ID), 10)).
WithProperties(properties). WithProperties(properties).
Do(ctx) Do(ctx)
return err return err
} }
func contains(slice []string, item string) bool {
for _, s := range slice {
if s == item {
return true
}
}
return false
}

View 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())
}

View File

@ -12,12 +12,12 @@ type MockWeaviateWrapper struct {
mock.Mock 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) args := m.Called(ctx, params)
if args.Get(0) == nil { if args.Get(0) == nil {
return nil, args.Error(1) 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 { func (m *MockWeaviateWrapper) IndexWork(ctx context.Context, work *domain.Work, content string) error {

View File

@ -10,15 +10,26 @@ import (
func TestWeaviateWrapper_Search(t *testing.T) { func TestWeaviateWrapper_Search(t *testing.T) {
mockWrapper := new(MockWeaviateWrapper) mockWrapper := new(MockWeaviateWrapper)
expectedResults := &domain.SearchResults{ expectedResults := &domainsearch.SearchResults{
Works: []domain.Work{ Results: []domainsearch.SearchResultItem{
{Title: "Work 1", Description: "alpha beta", TranslatableModel: domain.TranslatableModel{Language: "en"}}, {
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{ params := domainsearch.SearchParams{
Query: "alpha", Query: "alpha",
Mode: domainsearch.SearchModeHybrid, Mode: domainsearch.SearchModeHybrid,
Filters: domain.SearchFilters{ Filters: domainsearch.SearchFilters{
Languages: []string{"en"}, Languages: []string{"en"},
}, },
Limit: 1, Limit: 1,
@ -31,8 +42,10 @@ func TestWeaviateWrapper_Search(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
assert.NotNil(t, results) assert.NotNil(t, results)
assert.Equal(t, 1, len(results.Works)) assert.Equal(t, 1, len(results.Results))
assert.Equal(t, "Work 1", results.Works[0].Title) 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) mockWrapper.AssertExpectations(t)
} }

View File

@ -40,8 +40,8 @@ import (
// mockSearchClient is a mock implementation of the SearchClient interface. // mockSearchClient is a mock implementation of the SearchClient interface.
type mockSearchClient struct{} type mockSearchClient struct{}
func (m *mockSearchClient) Search(ctx context.Context, params search.SearchParams) (*domain.SearchResults, error) { func (m *mockSearchClient) Search(ctx context.Context, params search.SearchParams) (*search.SearchResults, error) {
return &domain.SearchResults{}, nil return &search.SearchResults{}, nil
} }
func (m *mockSearchClient) IndexWork(ctx context.Context, work *domain.Work, pipeline string) error { func (m *mockSearchClient) IndexWork(ctx context.Context, work *domain.Work, pipeline string) error {

503
jules-task.md Normal file
View 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

View File

@ -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()
}

View File

@ -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")
})
}