mirror of
https://github.com/SamyRai/tercul-backend.git
synced 2025-12-27 05:11:34 +00:00
Compare commits
2 Commits
ad749d9184
...
d50722dad5
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d50722dad5 | ||
|
|
6fdf0a97fd |
@ -6,7 +6,7 @@ tmp_dir = "tmp"
|
||||
|
||||
[build]
|
||||
# Just plain old shell command. You could use `make` as well.
|
||||
cmd = "go build -o ./tmp/tercul ."
|
||||
cmd = "go build -o ./tmp/tercul ./cmd/api"
|
||||
# Binary file yields from `cmd`.
|
||||
bin = "tmp/tercul"
|
||||
# Customize binary.
|
||||
@ -43,4 +43,4 @@ runner = "green"
|
||||
|
||||
[misc]
|
||||
# Delete tmp directory on exit
|
||||
clean_on_exit = true
|
||||
clean_on_exit = true
|
||||
|
||||
16
.github/dependabot.yml
vendored
16
.github/dependabot.yml
vendored
@ -1,16 +0,0 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
commit-message:
|
||||
prefix: "ci"
|
||||
include: "scope"
|
||||
- package-ecosystem: "gomod"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
commit-message:
|
||||
prefix: "deps"
|
||||
include: "scope"
|
||||
769
.github/workflows/README.md
vendored
769
.github/workflows/README.md
vendored
@ -1,769 +0,0 @@
|
||||
# GitHub Actions CI/CD Documentation
|
||||
|
||||
## Overview
|
||||
|
||||
This document describes the GitHub Actions CI/CD pipeline for the Tercul backend
|
||||
project, updated to 2025 best practices. The pipeline ensures code quality,
|
||||
security, and reliable deployments through automated testing, linting, security
|
||||
scanning, and containerized deployments.
|
||||
|
||||
### Quick Reference
|
||||
|
||||
| Workflow | File | Purpose | Triggers |
|
||||
|----------|------|---------|----------|
|
||||
| **Lint** | `lint.yml` | Code quality & style | Push/PR to main, develop |
|
||||
| **Test** | `test.yml` | Unit tests & compatibility | Push/PR to main, develop |
|
||||
| **Build** | `build.yml` | Binary compilation | Push/PR to main, develop |
|
||||
| **Security** | `security.yml` | CodeQL scanning | Push/PR to main + Weekly |
|
||||
| **Docker Build** | `docker-build.yml` | Container images | Push to main, Tags, PRs |
|
||||
| **Deploy** | `deploy.yml` | Production deployment | Tags (v*), Manual |
|
||||
|
||||
## Architecture
|
||||
|
||||
The CI/CD pipeline follows the **Single Responsibility Principle** with focused workflows:
|
||||
|
||||
1. **Lint** (`lint.yml`) - Code quality and style enforcement
|
||||
2. **Test** (`test.yml`) - Unit tests and compatibility matrix
|
||||
3. **Build** (`build.yml`) - Binary compilation and verification
|
||||
4. **Security** (`security.yml`) - CodeQL security scanning
|
||||
5. **Docker Build** (`docker-build.yml`) - Container image building and publishing
|
||||
6. **Deploy** (`deploy.yml`) - Production deployment orchestration
|
||||
|
||||
## Workflows
|
||||
|
||||
### Lint Workflow (`lint.yml`)
|
||||
|
||||
**Purpose**: Ensures code quality and consistent style across the codebase.
|
||||
|
||||
**Triggers**:
|
||||
|
||||
- Push to `main` and `develop` branches
|
||||
- Pull requests targeting `main` and `develop` branches
|
||||
|
||||
**Jobs**:
|
||||
|
||||
- `golangci-lint`: Go linting with golangci-lint
|
||||
- Checkout code
|
||||
- Setup Go 1.25 with caching
|
||||
- Install dependencies
|
||||
- Tidy modules (ensures go.mod/go.sum are clean)
|
||||
- Run linter with 5-minute timeout
|
||||
|
||||
**Configuration**:
|
||||
|
||||
- **Timeout**: 5 minutes
|
||||
- **Target**: All Go files (`./...`)
|
||||
- **Cache**: Enabled for faster runs
|
||||
|
||||
### Test Workflow (`test.yml`)
|
||||
|
||||
**Purpose**: Validates code functionality through comprehensive testing.
|
||||
|
||||
**Triggers**:
|
||||
|
||||
- Push to `main` and `develop` branches
|
||||
- Pull requests targeting `main` and `develop` branches
|
||||
|
||||
**Jobs**:
|
||||
|
||||
#### Unit Tests
|
||||
|
||||
- **Environment**: Ubuntu with PostgreSQL 15 and Redis 7
|
||||
- **Features**:
|
||||
|
||||
- Race detection enabled
|
||||
- Code coverage reporting (atomic mode)
|
||||
- HTML coverage report generation
|
||||
- Test result summaries in GitHub UI
|
||||
- 30-day artifact retention
|
||||
|
||||
**Services**:
|
||||
|
||||
- PostgreSQL 15 with health checks
|
||||
- Redis 7-alpine with health checks
|
||||
|
||||
#### Compatibility Matrix
|
||||
|
||||
- **Trigger**: Push to `main` branch only
|
||||
- **Strategy**: Tests across Go versions 1.22, 1.23, 1.24, 1.25
|
||||
- **Purpose**: Ensures compatibility with multiple Go versions
|
||||
- **Fail-fast**: Disabled (all versions tested even if one fails)
|
||||
|
||||
### Build Workflow (`build.yml`)
|
||||
|
||||
**Purpose**: Compiles the application binary and validates the build.
|
||||
|
||||
**Triggers**:
|
||||
|
||||
- Push to `main` and `develop` branches
|
||||
- Pull requests targeting `main` and `develop` branches
|
||||
|
||||
**Jobs**:
|
||||
|
||||
- `build-binary`: Binary compilation and verification
|
||||
- Dependency verification with `go mod verify`
|
||||
- Build to `bin/tercul-backend`
|
||||
- Binary validation test
|
||||
- Artifact upload (30-day retention)
|
||||
|
||||
**Permissions**:
|
||||
|
||||
- `contents: read` - Read repository code
|
||||
- `attestations: write` - Future SLSA attestation support
|
||||
- `id-token: write` - OIDC token for attestations
|
||||
|
||||
### Security Workflow (`security.yml`)
|
||||
|
||||
**Purpose**: Automated security vulnerability detection with CodeQL.
|
||||
|
||||
**Triggers**:
|
||||
|
||||
- Push to `main` branch
|
||||
- Pull requests targeting `main` branch
|
||||
- Scheduled: Every Monday at 14:20 UTC
|
||||
|
||||
**Jobs**:
|
||||
|
||||
- `codeql-analysis`: CodeQL security scanning for Go
|
||||
- Setup Go 1.25 (must run before CodeQL init)
|
||||
- Initialize CodeQL with Go language support
|
||||
- Build code for analysis
|
||||
- Perform security scan
|
||||
- Category: "backend-security" for tracking
|
||||
- Continues on error (warns if code scanning not enabled)
|
||||
|
||||
**Important Notes**:
|
||||
|
||||
- **Go Setup Order**: Go must be set up BEFORE CodeQL initialization to ensure version compatibility
|
||||
- **Code Scanning**: Must be enabled in repository settings (Settings > Security > Code scanning)
|
||||
- **Error Handling**: Workflow continues on CodeQL errors to allow scanning even if upload fails
|
||||
|
||||
**CodeQL Configuration**:
|
||||
|
||||
The workflow can be customized with additional query suites:
|
||||
|
||||
```yaml
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v3
|
||||
with:
|
||||
languages: go
|
||||
# Run security-extended suite for more comprehensive scanning
|
||||
queries: security-extended
|
||||
# Or use security-and-quality for maintainability checks
|
||||
# queries: security-and-quality
|
||||
```
|
||||
|
||||
**Available Query Suites**:
|
||||
|
||||
- `security-extended`: Default queries plus lower severity/precision queries
|
||||
- `security-and-quality`: Security queries plus maintainability and reliability
|
||||
|
||||
**Custom Query Packs**:
|
||||
|
||||
Add custom CodeQL query packs for specialized analysis:
|
||||
|
||||
```yaml
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v3
|
||||
with:
|
||||
languages: go
|
||||
packs: my-org/go-security-queries@1.0.0
|
||||
```
|
||||
|
||||
### Docker Build Workflow (`docker-build.yml`)
|
||||
|
||||
**Purpose**: Builds and publishes multi-architecture Docker images.
|
||||
|
||||
**Triggers**:
|
||||
|
||||
- Push to `main` branch
|
||||
- Tag pushes with `v*` pattern
|
||||
- Pull requests targeting `main` branch
|
||||
|
||||
**Jobs**:
|
||||
|
||||
- `build-image`: Multi-platform Docker image building
|
||||
|
||||
- Docker Buildx setup for multi-arch builds
|
||||
- Login to GitHub Container Registry
|
||||
- Metadata extraction for tags and labels
|
||||
- Build for AMD64 and ARM64 architectures
|
||||
- Push to registry (except for PRs)
|
||||
- Generate build provenance attestation
|
||||
|
||||
**Image Tagging Strategy**:
|
||||
|
||||
- `main` branch pushes → `main` + `sha-<hash>` tags
|
||||
- Tag pushes (`v1.2.3`) → `v1.2.3`, `1.2.3`, `1.2`, `sha-<hash>` tags
|
||||
- Pull requests → `pr-<number>` tag (build only, not pushed)
|
||||
|
||||
**Push Behavior**:
|
||||
|
||||
- **Pushes to main/tags**: Build and push to registry
|
||||
- **Pull requests**: Build only (validation, no push)
|
||||
|
||||
**Platforms**: linux/amd64, linux/arm64
|
||||
|
||||
### Deploy Workflow (`deploy.yml`)
|
||||
|
||||
**Purpose**: Production deployment orchestration to Docker Swarm.
|
||||
|
||||
**Triggers**:
|
||||
|
||||
- Tag pushes with `v*` pattern
|
||||
- Manual dispatch with version input
|
||||
|
||||
**Jobs**:
|
||||
|
||||
- `deploy-production`: Deployment to production environment
|
||||
|
||||
- Version extraction from tag or manual input
|
||||
- Docker Swarm service update (SSH-based deployment template)
|
||||
- Deployment summary with timestamp
|
||||
- Environment protection and tracking
|
||||
|
||||
**Environment**:
|
||||
|
||||
- Name: `production`
|
||||
- URL: Configurable production URL
|
||||
- Protection: Supports required reviewers and wait timers
|
||||
|
||||
**Manual Deployment**:
|
||||
|
||||
Deployments can be triggered manually from the Actions tab with a specific version.
|
||||
|
||||
**Required Secrets**:
|
||||
|
||||
- `SWARM_HOST`: Docker Swarm manager hostname/IP
|
||||
- `SWARM_SSH_KEY`: SSH private key for Swarm access
|
||||
|
||||
## Workflow Execution Order
|
||||
|
||||
**On Pull Request**:
|
||||
|
||||
1. Lint → Validates code style
|
||||
2. Test → Runs unit tests with coverage
|
||||
3. Build → Compiles binary
|
||||
4. Security → CodeQL analysis (main branch PRs only)
|
||||
5. Docker Build → Builds image (no push)
|
||||
|
||||
**On Push to main**:
|
||||
|
||||
1. Lint → Code quality check
|
||||
2. Test → Unit tests + compatibility matrix
|
||||
3. Build → Binary compilation
|
||||
4. Security → CodeQL scan
|
||||
5. Docker Build → Build and push image
|
||||
|
||||
**On Tag Push (v\*)**:
|
||||
|
||||
1. Docker Build → Build and push versioned image
|
||||
2. Deploy → Deploy to production
|
||||
|
||||
## Security Features
|
||||
|
||||
### Permissions Management
|
||||
|
||||
- **Principle of Least Privilege**: Each workflow has minimal required permissions
|
||||
- **GITHUB_TOKEN Restrictions**: Read-only by default, elevated only when necessary
|
||||
- **Workflow Separation**: Each workflow operates independently
|
||||
- **Attestation Permissions**: For build provenance and SLSA compliance
|
||||
|
||||
### Code Security
|
||||
|
||||
- **CodeQL Integration**: Automated security scanning for Go code
|
||||
- **Dependency Verification**: `go mod verify` ensures integrity
|
||||
- **Module Tidying**: `go mod tidy` prevents dependency drift
|
||||
|
||||
### Container Security
|
||||
|
||||
- **Multi-platform Builds**: Ensures compatibility and security across architectures
|
||||
- **Provenance Attestation**: Cryptographic proof of build integrity
|
||||
- **Registry Security**: GitHub Container Registry with token-based authentication
|
||||
|
||||
### Secrets Management
|
||||
|
||||
- **No Hardcoded Secrets**: All sensitive data uses GitHub secrets
|
||||
- **Environment Variables**: Proper isolation of configuration
|
||||
- **GITHUB_TOKEN**: Automatic authentication for package registries
|
||||
- **Granular Permissions**: Package-level access control
|
||||
|
||||
### Package Registry Security
|
||||
|
||||
- **GITHUB_TOKEN Authentication**: No personal access tokens required
|
||||
- **Automatic Permissions**: Packages inherit repository visibility
|
||||
- **Repository Scoped**: Packages linked to source repository
|
||||
- **Granular Access**: Fine-grained permissions per package
|
||||
- **Artifact Attestation**: Cryptographic proof of build provenance
|
||||
- **OIDC Support**: Token-based authentication without long-lived credentials
|
||||
|
||||
## Best Practices Implemented
|
||||
|
||||
### 2025 Updates
|
||||
|
||||
- **Semantic Versioning**: Actions pinned to major versions (e.g., `@v5`) instead of SHA
|
||||
- **Caching Optimization**: Go module and Docker layer caching
|
||||
- **Matrix Testing**: Cross-version compatibility validation
|
||||
- **Service Health Checks**: Database and Redis readiness verification
|
||||
- **Artifact Management**: Proper retention policies and naming
|
||||
|
||||
### Performance Optimizations
|
||||
|
||||
- **Dependency Caching**: Reduces setup time significantly
|
||||
- **Parallel Jobs**: Independent jobs run concurrently
|
||||
- **Conditional Execution**: Security scans only on main branch
|
||||
- **Artifact Upload**: Efficient storage and retrieval
|
||||
|
||||
### Reliability Features
|
||||
|
||||
- **Timeout Configuration**: Prevents hanging jobs
|
||||
- **Error Handling**: Proper exit codes and logging
|
||||
- **Health Checks**: Service readiness validation
|
||||
- **Retention Policies**: Balanced storage management
|
||||
|
||||
## Configuration Details
|
||||
|
||||
### Go Version
|
||||
|
||||
- Primary: Go 1.25
|
||||
- Matrix: Go 1.22, 1.23, 1.24, 1.25
|
||||
|
||||
### Services
|
||||
|
||||
- **PostgreSQL**: Version 15 with health checks
|
||||
- **Redis**: Version 7-alpine with ping health checks
|
||||
|
||||
### Tools
|
||||
|
||||
- **Linter**: golangci-lint latest with 5-minute timeout
|
||||
- **Testing**: `go test` with race detection and coverage
|
||||
- **Building**: `go build` with verbose output
|
||||
- **Security**: CodeQL for Go analysis
|
||||
|
||||
### Caching
|
||||
|
||||
- **Go Modules**: Automatic caching via setup-go action
|
||||
- **Docker Layers**: GitHub Actions cache with GHA type
|
||||
- **CodeQL Databases**: Stored in `${{ github.runner_temp }}/codeql_databases`
|
||||
|
||||
## Maintenance
|
||||
|
||||
### Dependabot Configuration
|
||||
|
||||
- **GitHub Actions**: Weekly updates with "ci" prefix
|
||||
- **Go Modules**: Weekly updates with "deps" prefix
|
||||
- **Automated PRs**: Keeps dependencies current and secure
|
||||
|
||||
### Monitoring
|
||||
|
||||
- **Workflow Runs**: GitHub Actions tab for execution monitoring
|
||||
- **Security Alerts**: Code scanning results and dependency alerts
|
||||
- **Coverage Reports**: Artifact downloads for test coverage analysis
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
#### Common Issues
|
||||
|
||||
1. **Cache Misses**: Clear caches if corruption suspected
|
||||
2. **Service Failures**: Check health check configurations
|
||||
3. **Permission Errors**: Verify GITHUB_TOKEN scopes
|
||||
4. **Timeout Issues**: Adjust timeout values in workflow configurations
|
||||
5. **Docker Push Failures**: Check package write permissions
|
||||
6. **Registry Authentication**: Ensure `packages: write` permission is set
|
||||
|
||||
#### Package Registry Issues
|
||||
|
||||
**Problem**: Cannot push to GitHub Container Registry
|
||||
|
||||
```yaml
|
||||
# Solution: Ensure proper permissions
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
id-token: write
|
||||
attestations: write
|
||||
```
|
||||
|
||||
**Problem**: Package not visible after push
|
||||
|
||||
- Check package visibility settings (public/private/internal)
|
||||
- Verify repository is linked to package
|
||||
- Ensure workflow completed successfully
|
||||
|
||||
**Problem**: Cannot pull package in workflow
|
||||
|
||||
```yaml
|
||||
# Solution: Login to registry first
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
```
|
||||
|
||||
#### Debugging
|
||||
|
||||
- **Manual Dispatch**: Use `workflow_dispatch` for testing
|
||||
- **Log Analysis**: Review step outputs for error details
|
||||
- **Artifact Inspection**: Download build artifacts for verification
|
||||
- **Package Logs**: Check package activity in GitHub UI
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
### Planned Features
|
||||
|
||||
- **SLSA Integration**: Enhanced build attestation
|
||||
- **Dependency Review**: Automated dependency vulnerability checking
|
||||
- **Performance Testing**: Load testing integration
|
||||
- **Multi-environment Deployment**: Staging and production separation
|
||||
|
||||
### Scalability Considerations
|
||||
|
||||
- **Self-hosted Runners**: For resource-intensive jobs
|
||||
- **Job Parallelization**: Further optimization of concurrent execution
|
||||
- **Cache Optimization**: Advanced caching strategies
|
||||
|
||||
## Docker Image Usage
|
||||
|
||||
### Pulling Images
|
||||
|
||||
Pull the latest image:
|
||||
|
||||
```bash
|
||||
docker pull ghcr.io/<owner>/<repository>:latest
|
||||
```
|
||||
|
||||
Pull a specific version:
|
||||
|
||||
```bash
|
||||
docker pull ghcr.io/<owner>/<repository>:1.2.3
|
||||
```
|
||||
|
||||
### Running Locally
|
||||
|
||||
Run the container:
|
||||
|
||||
```bash
|
||||
docker run -d \
|
||||
--name tercul-backend \
|
||||
-p 8080:8080 \
|
||||
-e DATABASE_URL="postgres://..." \
|
||||
ghcr.io/<owner>/<repository>:latest
|
||||
```
|
||||
|
||||
### Docker Swarm Deployment
|
||||
|
||||
Deploy as a stack:
|
||||
|
||||
```bash
|
||||
docker stack deploy -c docker-compose.yml tercul
|
||||
```
|
||||
|
||||
Update running service:
|
||||
|
||||
```bash
|
||||
docker service update \
|
||||
--image ghcr.io/<owner>/<repository>:1.2.3 \
|
||||
tercul_backend
|
||||
```
|
||||
|
||||
### Verifying Attestations
|
||||
|
||||
Verify build provenance:
|
||||
|
||||
```bash
|
||||
gh attestation verify \
|
||||
oci://ghcr.io/<owner>/<repository>:latest \
|
||||
--owner <owner>
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
When modifying workflows:
|
||||
|
||||
1. Test changes using `workflow_dispatch`
|
||||
2. Ensure backward compatibility
|
||||
3. Update this documentation
|
||||
4. Follow security best practices
|
||||
5. Use semantic versioning for action references
|
||||
6. Test Docker builds locally before pushing
|
||||
7. Verify package permissions after changes
|
||||
8. Review CodeQL alerts before merging PRs
|
||||
9. Update query packs regularly for latest security rules
|
||||
10. Test configuration changes with `workflow_dispatch`
|
||||
|
||||
## CodeQL Advanced Configuration
|
||||
|
||||
### Custom Configuration File
|
||||
|
||||
For complex CodeQL setups, use a configuration file (`.github/codeql/codeql-config.yml`):
|
||||
|
||||
```yaml
|
||||
name: "CodeQL Config"
|
||||
|
||||
# Disable default queries to run only custom queries
|
||||
disable-default-queries: false
|
||||
|
||||
# Specify query packs
|
||||
packs:
|
||||
- scope/go-security-pack
|
||||
- scope/go-compliance-pack@1.2.3
|
||||
|
||||
# Add custom queries
|
||||
queries:
|
||||
- uses: security-and-quality
|
||||
- uses: ./custom-queries
|
||||
|
||||
# Filter queries by severity
|
||||
query-filters:
|
||||
- exclude:
|
||||
problem.severity:
|
||||
- warning
|
||||
- recommendation
|
||||
- exclude:
|
||||
id: go/redundant-assignment
|
||||
|
||||
# Scan specific directories
|
||||
paths:
|
||||
- internal
|
||||
- cmd
|
||||
- pkg
|
||||
|
||||
paths-ignore:
|
||||
- "**/*_test.go"
|
||||
- vendor
|
||||
- "**/testdata/**"
|
||||
|
||||
# Extend threat model (preview)
|
||||
threat-models: local
|
||||
```
|
||||
|
||||
Reference the config in your workflow:
|
||||
|
||||
```yaml
|
||||
- uses: github/codeql-action/init@v3
|
||||
with:
|
||||
config-file: ./.github/codeql/codeql-config.yml
|
||||
```
|
||||
|
||||
### Inline Configuration
|
||||
|
||||
Alternatively, specify configuration inline:
|
||||
|
||||
```yaml
|
||||
- uses: github/codeql-action/init@v3
|
||||
with:
|
||||
languages: go
|
||||
config: |
|
||||
disable-default-queries: false
|
||||
queries:
|
||||
- uses: security-extended
|
||||
query-filters:
|
||||
- exclude:
|
||||
problem.severity:
|
||||
- recommendation
|
||||
```
|
||||
|
||||
### Scheduling CodeQL Scans
|
||||
|
||||
Run CodeQL on a schedule for regular security audits:
|
||||
|
||||
```yaml
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
schedule:
|
||||
# Run at 14:20 UTC every Monday
|
||||
- cron: '20 14 * * 1'
|
||||
```
|
||||
|
||||
### Avoiding Unnecessary Scans
|
||||
|
||||
Skip CodeQL for specific file changes:
|
||||
|
||||
```yaml
|
||||
on:
|
||||
pull_request:
|
||||
branches: [main]
|
||||
paths-ignore:
|
||||
- '**/*.md'
|
||||
- '**/*.txt'
|
||||
- 'docs/**'
|
||||
```
|
||||
|
||||
### Analysis Categories
|
||||
|
||||
Categorize multiple analyses in monorepos:
|
||||
|
||||
```yaml
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v3
|
||||
with:
|
||||
category: "backend-api"
|
||||
```
|
||||
|
||||
### External Query Packs
|
||||
|
||||
Use query packs from GitHub Enterprise Server:
|
||||
|
||||
```yaml
|
||||
- uses: github/codeql-action/init@v3
|
||||
with:
|
||||
registries: |
|
||||
- url: https://containers.GHEHOSTNAME/v2/
|
||||
packages:
|
||||
- my-company/*
|
||||
token: ${{ secrets.GHES_TOKEN }}
|
||||
packs: my-company/go-queries
|
||||
```
|
||||
|
||||
### Query Suite Examples
|
||||
|
||||
**Security-focused**:
|
||||
|
||||
```yaml
|
||||
queries:
|
||||
- uses: security-extended
|
||||
```
|
||||
|
||||
**Quality and security**:
|
||||
|
||||
```yaml
|
||||
queries:
|
||||
- uses: security-and-quality
|
||||
```
|
||||
|
||||
**Custom suite**:
|
||||
|
||||
```yaml
|
||||
queries:
|
||||
- uses: ./custom-queries/critical-security.qls
|
||||
```
|
||||
|
||||
## Advanced Workflow Techniques
|
||||
|
||||
### Workflow Commands
|
||||
|
||||
GitHub Actions supports workflow commands for advanced functionality:
|
||||
|
||||
#### Debug Logging
|
||||
|
||||
Enable detailed debugging with the `ACTIONS_STEP_DEBUG` secret:
|
||||
|
||||
```yaml
|
||||
- name: Debug step
|
||||
run: echo "::debug::Detailed debugging information"
|
||||
```
|
||||
|
||||
#### Annotations
|
||||
|
||||
Create annotations for notices, warnings, and errors:
|
||||
|
||||
```yaml
|
||||
# Notice annotation
|
||||
- run: echo "::notice file=app.go,line=10::Consider refactoring"
|
||||
|
||||
# Warning annotation
|
||||
- run: echo "::warning file=main.go,line=5,col=10::Deprecated function"
|
||||
|
||||
# Error annotation
|
||||
- run: echo "::error file=handler.go,line=20,title=Build Error::Missing import"
|
||||
```
|
||||
|
||||
#### Grouping Log Lines
|
||||
|
||||
Organize logs with collapsible groups:
|
||||
|
||||
```yaml
|
||||
- name: Build application
|
||||
run: |
|
||||
echo "::group::Compiling Go code"
|
||||
go build -v ./...
|
||||
echo "::endgroup::"
|
||||
```
|
||||
|
||||
#### Masking Secrets
|
||||
|
||||
Prevent sensitive values from appearing in logs:
|
||||
|
||||
```yaml
|
||||
- name: Generate token
|
||||
run: |
|
||||
TOKEN=$(generate_token)
|
||||
echo "::add-mask::$TOKEN"
|
||||
echo "TOKEN=$TOKEN" >> $GITHUB_ENV
|
||||
```
|
||||
|
||||
#### Job Summaries
|
||||
|
||||
Add Markdown summaries to workflow runs:
|
||||
|
||||
```yaml
|
||||
- name: Test summary
|
||||
run: |
|
||||
echo "### Test Results :white_check_mark:" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- Total: 150 tests" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- Passed: 148" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- Failed: 2" >> $GITHUB_STEP_SUMMARY
|
||||
```
|
||||
|
||||
### Environment Files
|
||||
|
||||
Use environment files for dynamic configuration:
|
||||
|
||||
```yaml
|
||||
# Set environment variable for subsequent steps
|
||||
- name: Set build info
|
||||
run: |
|
||||
echo "BUILD_TIME=$(date +'%Y-%m-%d %H:%M:%S')" >> $GITHUB_ENV
|
||||
echo "COMMIT_SHA=${GITHUB_SHA:0:7}" >> $GITHUB_ENV
|
||||
|
||||
# Use in later steps
|
||||
- name: Deploy
|
||||
run: echo "Deploying $COMMIT_SHA built at $BUILD_TIME"
|
||||
```
|
||||
|
||||
### Output Parameters
|
||||
|
||||
Share data between steps:
|
||||
|
||||
```yaml
|
||||
- name: Calculate version
|
||||
id: version
|
||||
run: echo "VERSION=1.2.3" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Use version
|
||||
run: echo "Building version ${{ steps.version.outputs.VERSION }}"
|
||||
```
|
||||
|
||||
### Multiline Values
|
||||
|
||||
Handle multiline strings safely:
|
||||
|
||||
```yaml
|
||||
- name: Store API response
|
||||
run: |
|
||||
{
|
||||
echo 'API_RESPONSE<<EOF'
|
||||
curl https://api.example.com/data
|
||||
echo EOF
|
||||
} >> $GITHUB_ENV
|
||||
```
|
||||
|
||||
## References
|
||||
|
||||
- [GitHub Actions Documentation](https://docs.github.com/en/actions)
|
||||
- [Go CI/CD Best Practices](https://github.com/golang/go/wiki/Go-Release-Cycle)
|
||||
- [Security Hardening Guide](https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions)
|
||||
- [Dependency Caching](https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows)
|
||||
- [Workflow Commands Reference](https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions)
|
||||
- [Publishing Packages with Actions](https://docs.github.com/en/packages/managing-github-packages-using-github-actions-workflows/publishing-and-installing-a-package-with-github-actions)
|
||||
- [GitHub Container Registry](https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-container-registry)
|
||||
- [Artifact Attestations](https://docs.github.com/en/actions/security-guides/using-artifact-attestations-to-establish-provenance-for-builds)
|
||||
- [CodeQL Configuration Reference](https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning)
|
||||
- [CodeQL Query Suites](https://docs.github.com/en/code-security/code-scanning/managing-your-code-scanning-configuration/codeql-query-suites)
|
||||
- [CodeQL CLI Reference](https://docs.github.com/en/code-security/codeql-cli)
|
||||
46
.github/workflows/build.yml
vendored
46
.github/workflows/build.yml
vendored
@ -1,46 +0,0 @@
|
||||
name: Build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main, develop]
|
||||
pull_request:
|
||||
branches: [main, develop]
|
||||
|
||||
jobs:
|
||||
build-binary:
|
||||
name: Build Binary
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
attestations: write
|
||||
id-token: write
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: "1.25"
|
||||
cache: true
|
||||
|
||||
- name: Install dependencies
|
||||
run: go mod download
|
||||
|
||||
- name: Verify dependencies
|
||||
run: go mod verify
|
||||
|
||||
- name: Build application
|
||||
run: |
|
||||
go build -v -o bin/tercul-backend ./cmd/api
|
||||
ls -la bin/
|
||||
|
||||
- name: Test binary
|
||||
run: ./bin/tercul-backend --help || echo "Binary built successfully"
|
||||
|
||||
- name: Upload build artifacts
|
||||
uses: actions/upload-artifact@v5
|
||||
with:
|
||||
name: tercul-backend-binary
|
||||
path: bin/
|
||||
retention-days: 30
|
||||
60
.github/workflows/deploy.yml
vendored
60
.github/workflows/deploy.yml
vendored
@ -1,60 +0,0 @@
|
||||
name: Deploy
|
||||
|
||||
on:
|
||||
push:
|
||||
tags: ["v*"]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: "Version to deploy (e.g., v1.2.3)"
|
||||
required: true
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
deploy-production:
|
||||
name: Deploy to Production
|
||||
runs-on: ubuntu-latest
|
||||
environment:
|
||||
name: production
|
||||
url: https://tercul.example.com
|
||||
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Extract version
|
||||
id: version
|
||||
run: |
|
||||
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
||||
echo "VERSION=${{ inputs.version }}" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Deploy to Docker Swarm
|
||||
env:
|
||||
SWARM_HOST: ${{ secrets.SWARM_HOST }}
|
||||
SWARM_SSH_KEY: ${{ secrets.SWARM_SSH_KEY }}
|
||||
IMAGE_TAG: ${{ steps.version.outputs.VERSION }}
|
||||
run: |
|
||||
# Uncomment and configure for actual Docker Swarm deployment
|
||||
# echo "$SWARM_SSH_KEY" > swarm_key
|
||||
# chmod 600 swarm_key
|
||||
# ssh -i swarm_key -o StrictHostKeyChecking=no \
|
||||
# deploy@$SWARM_HOST \
|
||||
# "docker service update \
|
||||
# --image ghcr.io/${{ github.repository }}:${IMAGE_TAG} \
|
||||
# tercul-backend"
|
||||
# rm swarm_key
|
||||
|
||||
echo "Deploying version ${{ steps.version.outputs.VERSION }} to production"
|
||||
echo "Image: ghcr.io/${{ github.repository }}:${IMAGE_TAG}"
|
||||
|
||||
- name: Deployment summary
|
||||
run: |
|
||||
echo "### Deployment Complete :rocket:" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **Version**: ${{ steps.version.outputs.VERSION }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **Image**: ghcr.io/${{ github.repository }}:${{ steps.version.outputs.VERSION }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **Environment**: Production" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **Deployed at**: $(date -u +'%Y-%m-%d %H:%M:%S UTC')" >> $GITHUB_STEP_SUMMARY
|
||||
65
.github/workflows/docker-build.yml
vendored
65
.github/workflows/docker-build.yml
vendored
@ -1,65 +0,0 @@
|
||||
name: Docker Build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
tags: ["v*"]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
build-image:
|
||||
name: Build Docker Image
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
attestations: write
|
||||
id-token: write
|
||||
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Extract metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ghcr.io/${{ github.repository }}
|
||||
tags: |
|
||||
type=ref,event=branch
|
||||
type=ref,event=pr
|
||||
type=ref,event=tag
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=sha,format=long
|
||||
|
||||
- name: Build and push
|
||||
id: push
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
platforms: linux/amd64,linux/arm64
|
||||
|
||||
- name: Generate artifact attestation
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: actions/attest-build-provenance@v3
|
||||
with:
|
||||
subject-name: ghcr.io/${{ github.repository}}
|
||||
subject-digest: ${{ steps.push.outputs.digest }}
|
||||
push-to-registry: true
|
||||
33
.github/workflows/lint.yml
vendored
33
.github/workflows/lint.yml
vendored
@ -1,33 +0,0 @@
|
||||
name: Lint
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main, develop]
|
||||
pull_request:
|
||||
branches: [main, develop]
|
||||
|
||||
jobs:
|
||||
golangci-lint:
|
||||
name: Go Lint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: "1.25"
|
||||
cache: true
|
||||
|
||||
- name: Install dependencies
|
||||
run: go mod download
|
||||
|
||||
- name: Tidy modules
|
||||
run: go mod tidy
|
||||
|
||||
- name: Run golangci-lint
|
||||
uses: golangci/golangci-lint-action@v6
|
||||
with:
|
||||
version: latest
|
||||
args: --timeout=5m ./...
|
||||
70
.github/workflows/security.yml
vendored
70
.github/workflows/security.yml
vendored
@ -1,70 +0,0 @@
|
||||
name: Security
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
schedule:
|
||||
# Run CodeQL scan every Monday at 14:20 UTC
|
||||
- cron: "20 14 * * 1"
|
||||
|
||||
jobs:
|
||||
codeql-analysis:
|
||||
name: CodeQL Security Scan
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
security-events: write
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: "1.25"
|
||||
cache: true
|
||||
|
||||
- name: Verify Go installation
|
||||
run: |
|
||||
echo "Go version: $(go version)"
|
||||
echo "Go path: $(which go)"
|
||||
echo "GOROOT: $GOROOT"
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v3
|
||||
with:
|
||||
languages: go
|
||||
# CodeQL will use the Go version installed by setup-go above
|
||||
# Optionally use security-extended for more comprehensive scanning
|
||||
# queries: security-extended
|
||||
|
||||
- name: Install dependencies
|
||||
run: go mod download
|
||||
|
||||
- name: Build for analysis
|
||||
run: go build -v ./...
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
id: codeql-analysis
|
||||
uses: github/codeql-action/analyze@v3
|
||||
with:
|
||||
category: "backend-security"
|
||||
continue-on-error: true
|
||||
|
||||
- name: Check CodeQL Results
|
||||
if: steps.codeql-analysis.outcome == 'failure'
|
||||
run: |
|
||||
echo "⚠️ CodeQL analysis completed with warnings/errors"
|
||||
echo "This may be due to:"
|
||||
echo " 1. Code scanning not enabled in repository settings"
|
||||
echo " 2. Security alerts that need review"
|
||||
echo ""
|
||||
echo "To enable code scanning:"
|
||||
echo " Go to Settings > Security > Code security and analysis"
|
||||
echo " Click 'Set up' under Code scanning"
|
||||
echo ""
|
||||
echo "Analysis results are still available in the workflow artifacts."
|
||||
116
.github/workflows/test.yml
vendored
116
.github/workflows/test.yml
vendored
@ -1,116 +0,0 @@
|
||||
name: Test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main, develop]
|
||||
pull_request:
|
||||
branches: [main, develop]
|
||||
|
||||
jobs:
|
||||
unit-tests:
|
||||
name: Unit Tests
|
||||
runs-on: ubuntu-latest
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:15
|
||||
env:
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: postgres
|
||||
POSTGRES_DB: testdb
|
||||
options: >-
|
||||
--health-cmd pg_isready
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
ports:
|
||||
- 5432:5432
|
||||
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
ports:
|
||||
- 6379:6379
|
||||
options: --health-cmd "redis-cli ping" --health-interval 10s --health-timeout 5s --health-retries 5
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: "1.25"
|
||||
cache: true
|
||||
|
||||
- name: Install dependencies
|
||||
run: go mod download
|
||||
|
||||
- name: Run tests with coverage
|
||||
run: |
|
||||
go test -v -race -coverprofile=coverage.out -covermode=atomic ./...
|
||||
go tool cover -html=coverage.out -o coverage.html
|
||||
|
||||
- name: Generate test summary
|
||||
if: always()
|
||||
run: |
|
||||
echo "### Test Results :test_tube:" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **Coverage**: See artifact for detailed report" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **Race Detection**: Enabled" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **Go Version**: 1.25" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
- name: Upload coverage reports
|
||||
uses: actions/upload-artifact@v5
|
||||
with:
|
||||
name: coverage-report
|
||||
path: |
|
||||
coverage.out
|
||||
coverage.html
|
||||
retention-days: 30
|
||||
|
||||
compatibility-matrix:
|
||||
name: Go ${{ matrix.go-version }} Compatibility
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
go-version: ["1.22", "1.23", "1.24", "1.25"]
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:15
|
||||
env:
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: postgres
|
||||
POSTGRES_DB: testdb
|
||||
options: >-
|
||||
--health-cmd pg_isready
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
ports:
|
||||
- 5432:5432
|
||||
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
ports:
|
||||
- 6379:6379
|
||||
options: --health-cmd "redis-cli ping" --health-interval 10s --health-timeout 5s --health-retries 5
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Setup Go ${{ matrix.go-version }}
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
cache: true
|
||||
|
||||
- name: Install dependencies
|
||||
run: go mod download
|
||||
|
||||
- name: Run tests
|
||||
run: go test -v -race ./...
|
||||
|
||||
- name: Build
|
||||
run: go build -v ./...
|
||||
@ -1,4 +1,4 @@
|
||||
FROM golang:1.24-alpine AS builder
|
||||
FROM golang:1.25-alpine AS builder
|
||||
|
||||
# Install git and required dependencies
|
||||
RUN apk add --no-cache git build-base
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
FROM golang:1.24 AS development
|
||||
FROM golang:1.25 AS development
|
||||
|
||||
# Install Air for hot reloading (using the updated repository)
|
||||
RUN go install github.com/air-verse/air@latest
|
||||
RUN go install github.com/air-verse/air@v1.52.3
|
||||
|
||||
# Set working directory
|
||||
WORKDIR /app
|
||||
@ -19,4 +19,4 @@ COPY . .
|
||||
EXPOSE 8080
|
||||
|
||||
# Command to run the application with Air for hot reloading
|
||||
CMD ["air"]
|
||||
CMD ["air"]
|
||||
|
||||
15
Makefile
15
Makefile
@ -1,7 +1,18 @@
|
||||
.PHONY: lint-test
|
||||
.PHONY: lint-test dev dev-deps dev-down
|
||||
|
||||
DOCKER_COMPOSE := $(shell command -v docker-compose >/dev/null 2>&1 && echo docker-compose || echo "docker compose")
|
||||
|
||||
dev-deps:
|
||||
$(DOCKER_COMPOSE) up -d
|
||||
|
||||
dev-down:
|
||||
$(DOCKER_COMPOSE) down
|
||||
|
||||
dev: dev-deps
|
||||
go run ./cmd/cli serve
|
||||
|
||||
lint-test:
|
||||
@echo "Running linter..."
|
||||
golangci-lint run --timeout=5m
|
||||
@echo "Running tests..."
|
||||
go test ./...
|
||||
go test ./...
|
||||
|
||||
@ -29,6 +29,7 @@ import (
|
||||
"tercul/internal/app/translation"
|
||||
"tercul/internal/app/user"
|
||||
"tercul/internal/app/work"
|
||||
datacache "tercul/internal/data/cache"
|
||||
dbsql "tercul/internal/data/sql"
|
||||
"tercul/internal/jobs/linguistics"
|
||||
"tercul/internal/observability"
|
||||
@ -125,13 +126,36 @@ func main() {
|
||||
// Create repositories
|
||||
repos := dbsql.NewRepositories(database, cfg)
|
||||
|
||||
// Initialize Redis cache once (shared by APQ, optional repo caching, and linguistics)
|
||||
redisCache, cacheErr := cache.NewDefaultRedisCache(cfg)
|
||||
|
||||
// Create linguistics dependencies
|
||||
analysisRepo := linguistics.NewGORMAnalysisRepository(database)
|
||||
sentimentProvider, sErr := linguistics.NewGoVADERSentimentProvider()
|
||||
if sErr != nil {
|
||||
app_log.Fatal(sErr, "Failed to create sentiment provider")
|
||||
}
|
||||
|
||||
workAnalyticsDeps := linguistics.WorkAnalyticsDeps{
|
||||
StatsRepo: repos.Analytics,
|
||||
LikeCounter: repos.Like,
|
||||
CommentCounter: repos.Comment,
|
||||
BookmarkCounter: repos.Bookmark,
|
||||
TranslationCount: repos.Translation,
|
||||
TranslationList: repos.Translation,
|
||||
}
|
||||
|
||||
linguisticsFactory := linguistics.NewLinguisticsFactory(
|
||||
cfg,
|
||||
database,
|
||||
redisCache,
|
||||
2,
|
||||
true,
|
||||
sentimentProvider,
|
||||
workAnalyticsDeps,
|
||||
)
|
||||
|
||||
analysisRepo := linguisticsFactory.GetAnalysisRepository()
|
||||
|
||||
// Create platform components
|
||||
jwtManager := platform_auth.NewJWTManager(cfg)
|
||||
|
||||
@ -181,12 +205,18 @@ func main() {
|
||||
App: application,
|
||||
}
|
||||
|
||||
// Initialize Redis Cache for APQ
|
||||
redisCache, cacheErr := cache.NewDefaultRedisCache(cfg)
|
||||
var queryCache gql.Cache[string]
|
||||
if cacheErr != nil {
|
||||
app_log.Warn("Redis cache initialization failed, APQ disabled: " + cacheErr.Error())
|
||||
} else {
|
||||
// Optional repository caching (opt-in)
|
||||
if os.Getenv("REPO_CACHE_ENABLED") == "true" {
|
||||
repos.Work = datacache.NewCachedWorkRepository(repos.Work, redisCache, nil)
|
||||
repos.Author = datacache.NewCachedAuthorRepository(repos.Author, redisCache, nil)
|
||||
repos.Translation = datacache.NewCachedTranslationRepository(repos.Translation, redisCache, nil)
|
||||
app_log.Info("Repository caching enabled")
|
||||
}
|
||||
|
||||
queryCache = &cache.GraphQLCacheAdapter{RedisCache: redisCache}
|
||||
app_log.Info("Redis cache initialized for APQ")
|
||||
}
|
||||
|
||||
@ -1 +1 @@
|
||||
{"last_processed_id":3,"total_processed":3,"last_updated":"2025-11-30T21:59:16.811419372Z"}
|
||||
{"last_processed_id":3,"total_processed":3,"last_updated":"2025-12-26T15:37:36.561092+01:00"}
|
||||
@ -6,7 +6,6 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"tercul/internal/data/sql"
|
||||
@ -30,7 +29,7 @@ const (
|
||||
)
|
||||
|
||||
type checkpoint struct {
|
||||
LastProcessedID uint `json:"last_processed_id"`
|
||||
LastProcessedID string `json:"last_processed_id"`
|
||||
TotalProcessed int `json:"total_processed"`
|
||||
LastUpdated time.Time `json:"last_updated"`
|
||||
}
|
||||
@ -104,7 +103,7 @@ Example:
|
||||
if resume {
|
||||
cp = loadCheckpoint()
|
||||
if cp != nil {
|
||||
logger.Info(fmt.Sprintf("Resuming from checkpoint: last_id=%d, total_processed=%d", cp.LastProcessedID, cp.TotalProcessed))
|
||||
logger.Info(fmt.Sprintf("Resuming from checkpoint: last_id=%s, total_processed=%d", cp.LastProcessedID, cp.TotalProcessed))
|
||||
}
|
||||
}
|
||||
|
||||
@ -268,10 +267,10 @@ func migrateTranslations(
|
||||
logger.Info(fmt.Sprintf("Found %d translations", totalTranslations))
|
||||
|
||||
// Filter translations if resuming from checkpoint
|
||||
if cp != nil && cp.LastProcessedID > 0 {
|
||||
if cp != nil && cp.LastProcessedID != "" {
|
||||
filtered := make([]domain.Translation, 0, len(translations))
|
||||
for _, t := range translations {
|
||||
if t.ID > cp.LastProcessedID {
|
||||
if t.ID.String() > cp.LastProcessedID {
|
||||
filtered = append(filtered, t)
|
||||
}
|
||||
}
|
||||
@ -282,11 +281,11 @@ func migrateTranslations(
|
||||
|
||||
// Process translations in batches
|
||||
batch := make([]domain.Translation, 0, batchSize)
|
||||
lastProcessedID := uint(0)
|
||||
lastProcessedID := ""
|
||||
|
||||
for i, translation := range translations {
|
||||
batch = append(batch, translation)
|
||||
lastProcessedID = translation.ID
|
||||
lastProcessedID = translation.ID.String()
|
||||
|
||||
// Process batch when it reaches the batch size or at the end
|
||||
if len(batch) >= batchSize || i == len(translations)-1 {
|
||||
@ -326,7 +325,7 @@ func indexBatch(index bleve.Index, translations []domain.Translation) error {
|
||||
batch := index.NewBatch()
|
||||
for _, t := range translations {
|
||||
doc := map[string]interface{}{
|
||||
"id": strconv.FormatUint(uint64(t.ID), 10),
|
||||
"id": t.ID.String(),
|
||||
"title": t.Title,
|
||||
"content": t.Content,
|
||||
"description": t.Description,
|
||||
|
||||
@ -38,11 +38,11 @@ func TestMigrateTranslations_LargeBatch(t *testing.T) {
|
||||
translations := make([]domain.Translation, 100)
|
||||
for i := 0; i < 100; i++ {
|
||||
translations[i] = domain.Translation{
|
||||
BaseModel: domain.BaseModel{ID: uint(i + 1)},
|
||||
Title: "Test Translation",
|
||||
Content: "Content",
|
||||
Language: "en",
|
||||
Status: domain.TranslationStatusPublished,
|
||||
BaseModel: domain.BaseModel{ID: uint(i + 1)},
|
||||
Title: "Test Translation",
|
||||
Content: "Content",
|
||||
Language: "en",
|
||||
Status: domain.TranslationStatusPublished,
|
||||
TranslatableID: uint(i + 1),
|
||||
TranslatableType: "works",
|
||||
}
|
||||
@ -104,11 +104,11 @@ func TestIndexBatch_WithTranslatorID(t *testing.T) {
|
||||
translatorID := uint(123)
|
||||
translations := []domain.Translation{
|
||||
{
|
||||
BaseModel: domain.BaseModel{ID: 1},
|
||||
Title: "Test",
|
||||
Content: "Content",
|
||||
Language: "en",
|
||||
Status: domain.TranslationStatusPublished,
|
||||
BaseModel: domain.BaseModel{ID: 1},
|
||||
Title: "Test",
|
||||
Content: "Content",
|
||||
Language: "en",
|
||||
Status: domain.TranslationStatusPublished,
|
||||
TranslatableID: 100,
|
||||
TranslatableType: "works",
|
||||
TranslatorID: &translatorID,
|
||||
@ -129,4 +129,3 @@ func TestCheckpoint_InvalidJSON(t *testing.T) {
|
||||
// This would require mocking file system, but for now we test the happy path
|
||||
// Invalid JSON handling is tested implicitly through file operations
|
||||
}
|
||||
|
||||
|
||||
@ -31,7 +31,7 @@ func (m *mockTranslationRepository) ListAll(ctx context.Context) ([]domain.Trans
|
||||
}
|
||||
|
||||
// Implement other required methods with minimal implementations
|
||||
func (m *mockTranslationRepository) GetByID(ctx context.Context, id uint) (*domain.Translation, error) {
|
||||
func (m *mockTranslationRepository) GetByID(ctx context.Context, id uuid.UUID) (*domain.Translation, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (m *mockTranslationRepository) Create(ctx context.Context, entity *domain.Translation) error {
|
||||
|
||||
@ -114,4 +114,3 @@ func TestRootCommand(t *testing.T) {
|
||||
assert.NotEmpty(t, cmd.Short)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -3,7 +3,6 @@ package commands
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"tercul/cmd/cli/internal/bootstrap"
|
||||
"tercul/internal/enrichment"
|
||||
@ -11,6 +10,7 @@ import (
|
||||
"tercul/internal/platform/db"
|
||||
"tercul/internal/platform/log"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@ -33,7 +33,7 @@ Example:
|
||||
return fmt.Errorf("both --type and --id are required")
|
||||
}
|
||||
|
||||
entityIDUint, err := strconv.ParseUint(entityID, 10, 64)
|
||||
entityIDUUID, err := uuid.Parse(entityID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid entity ID: %w", err)
|
||||
}
|
||||
@ -71,11 +71,11 @@ Example:
|
||||
|
||||
// Fetch, enrich, and save the entity
|
||||
ctx := context.Background()
|
||||
log.Info(fmt.Sprintf("Enriching %s with ID %d", entityType, entityIDUint))
|
||||
log.Info(fmt.Sprintf("Enriching %s with ID %s", entityType, entityIDUUID.String()))
|
||||
|
||||
switch entityType {
|
||||
case "author":
|
||||
author, err := deps.Repos.Author.GetByID(ctx, uint(entityIDUint))
|
||||
author, err := deps.Repos.Author.GetByID(ctx, entityIDUUID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get author: %w", err)
|
||||
}
|
||||
|
||||
@ -6,7 +6,10 @@ import (
|
||||
"syscall"
|
||||
|
||||
"tercul/cmd/cli/internal/bootstrap"
|
||||
dbsql "tercul/internal/data/sql"
|
||||
"tercul/internal/jobs/linguistics"
|
||||
"tercul/internal/jobs/sync"
|
||||
"tercul/internal/platform/cache"
|
||||
"tercul/internal/platform/config"
|
||||
"tercul/internal/platform/db"
|
||||
app_log "tercul/internal/platform/log"
|
||||
@ -73,16 +76,47 @@ func NewWorkerCommand() *cobra.Command {
|
||||
},
|
||||
)
|
||||
|
||||
repos := dbsql.NewRepositories(database, cfg)
|
||||
|
||||
redisCache, cacheErr := cache.NewDefaultRedisCache(cfg)
|
||||
if cacheErr != nil {
|
||||
app_log.Warn("Redis cache initialization failed for linguistics: " + cacheErr.Error())
|
||||
}
|
||||
|
||||
sentimentProvider, spErr := linguistics.NewGoVADERSentimentProvider()
|
||||
if spErr != nil {
|
||||
return spErr
|
||||
}
|
||||
|
||||
workAnalyticsDeps := linguistics.WorkAnalyticsDeps{
|
||||
StatsRepo: repos.Analytics,
|
||||
LikeCounter: repos.Like,
|
||||
CommentCounter: repos.Comment,
|
||||
BookmarkCounter: repos.Bookmark,
|
||||
TranslationCount: repos.Translation,
|
||||
TranslationList: repos.Translation,
|
||||
}
|
||||
|
||||
linguisticsFactory := linguistics.NewLinguisticsFactory(
|
||||
cfg,
|
||||
database,
|
||||
redisCache,
|
||||
2,
|
||||
true,
|
||||
sentimentProvider,
|
||||
workAnalyticsDeps,
|
||||
)
|
||||
|
||||
// Create SyncJob with all dependencies
|
||||
syncJob := sync.NewSyncJob(database, asynqClient, cfg, weaviateClient)
|
||||
linguisticJob := linguistics.NewLinguisticSyncJob(database, linguisticsFactory.GetAnalyzer(), asynqClient)
|
||||
|
||||
// Create a new ServeMux for routing jobs
|
||||
mux := asynq.NewServeMux()
|
||||
|
||||
// Register all job handlers
|
||||
sync.RegisterQueueHandlers(mux, syncJob)
|
||||
// Placeholder for other job handlers that might be added in the future
|
||||
// linguistics.RegisterLinguisticHandlers(mux, linguisticJob)
|
||||
linguistics.RegisterLinguisticHandlers(mux, linguisticJob)
|
||||
// trending.RegisterTrendingHandlers(mux, analyticsService)
|
||||
|
||||
// Start the server in a goroutine
|
||||
|
||||
@ -48,7 +48,7 @@ type Dependencies struct {
|
||||
Repos *dbsql.Repositories
|
||||
Application *app.Application
|
||||
JWTManager *platform_auth.JWTManager
|
||||
AnalysisRepo *linguistics.GORMAnalysisRepository
|
||||
AnalysisRepo linguistics.AnalysisRepository
|
||||
SentimentProvider *linguistics.GoVADERSentimentProvider
|
||||
}
|
||||
|
||||
@ -61,12 +61,32 @@ func Bootstrap(cfg *config.Config, database *gorm.DB, weaviateClient *weaviate.C
|
||||
repos := dbsql.NewRepositories(database, cfg)
|
||||
|
||||
// Create linguistics dependencies
|
||||
analysisRepo := linguistics.NewGORMAnalysisRepository(database)
|
||||
sentimentProvider, err := linguistics.NewGoVADERSentimentProvider()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
workAnalyticsDeps := linguistics.WorkAnalyticsDeps{
|
||||
StatsRepo: repos.Analytics,
|
||||
LikeCounter: repos.Like,
|
||||
CommentCounter: repos.Comment,
|
||||
BookmarkCounter: repos.Bookmark,
|
||||
TranslationCount: repos.Translation,
|
||||
TranslationList: repos.Translation,
|
||||
}
|
||||
|
||||
linguisticsFactory := linguistics.NewLinguisticsFactory(
|
||||
cfg,
|
||||
database,
|
||||
nil, // optional cache
|
||||
2,
|
||||
false,
|
||||
sentimentProvider,
|
||||
workAnalyticsDeps,
|
||||
)
|
||||
|
||||
analysisRepo := linguisticsFactory.GetAnalysisRepository()
|
||||
|
||||
// Create platform components
|
||||
jwtManager := platform_auth.NewJWTManager(cfg)
|
||||
|
||||
|
||||
@ -109,4 +109,3 @@ func TestBootstrapWithMetrics(t *testing.T) {
|
||||
assert.NotNil(t, deps)
|
||||
assert.NotNil(t, deps.Application)
|
||||
}
|
||||
|
||||
|
||||
@ -5,12 +5,13 @@ import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"tercul/internal/data/sql"
|
||||
"tercul/internal/enrichment"
|
||||
"tercul/internal/platform/config"
|
||||
"tercul/internal/platform/db"
|
||||
"tercul/internal/platform/log"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
func main() {
|
||||
@ -24,7 +25,7 @@ func main() {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
entityID, err := strconv.ParseUint(*entityIDStr, 10, 64)
|
||||
entityID, err := uuid.Parse(*entityIDStr)
|
||||
if err != nil {
|
||||
fmt.Printf("Invalid entity ID: %v\n", err)
|
||||
os.Exit(1)
|
||||
@ -55,7 +56,7 @@ func main() {
|
||||
|
||||
switch *entityType {
|
||||
case "author":
|
||||
author, err := repos.Author.GetByID(ctx, uint(entityID))
|
||||
author, err := repos.Author.GetByID(ctx, entityID)
|
||||
if err != nil {
|
||||
log.Fatal(err, "Failed to get author")
|
||||
}
|
||||
|
||||
@ -5,7 +5,11 @@ import (
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
dbsql "tercul/internal/data/sql"
|
||||
"tercul/internal/jobs/linguistics"
|
||||
"tercul/internal/jobs/sync"
|
||||
"tercul/internal/platform/cache"
|
||||
"tercul/internal/platform/config"
|
||||
"tercul/internal/platform/db"
|
||||
app_log "tercul/internal/platform/log"
|
||||
@ -70,13 +74,44 @@ func main() {
|
||||
// Create SyncJob with all dependencies
|
||||
syncJob := sync.NewSyncJob(database, asynqClient, cfg, weaviateClient)
|
||||
|
||||
repos := dbsql.NewRepositories(database, cfg)
|
||||
|
||||
redisCache, cacheErr := cache.NewDefaultRedisCache(cfg)
|
||||
if cacheErr != nil {
|
||||
app_log.Warn("Redis cache initialization failed for linguistics: " + cacheErr.Error())
|
||||
}
|
||||
|
||||
sentimentProvider, spErr := linguistics.NewGoVADERSentimentProvider()
|
||||
if spErr != nil {
|
||||
app_log.Fatal(spErr, "Failed to create sentiment provider")
|
||||
}
|
||||
|
||||
workAnalyticsDeps := linguistics.WorkAnalyticsDeps{
|
||||
StatsRepo: repos.Analytics,
|
||||
LikeCounter: repos.Like,
|
||||
CommentCounter: repos.Comment,
|
||||
BookmarkCounter: repos.Bookmark,
|
||||
TranslationCount: repos.Translation,
|
||||
TranslationList: repos.Translation,
|
||||
}
|
||||
|
||||
linguisticsFactory := linguistics.NewLinguisticsFactory(
|
||||
cfg,
|
||||
database,
|
||||
redisCache,
|
||||
2,
|
||||
true,
|
||||
sentimentProvider,
|
||||
workAnalyticsDeps,
|
||||
)
|
||||
linguisticJob := linguistics.NewLinguisticSyncJob(database, linguisticsFactory.GetAnalyzer(), asynqClient)
|
||||
|
||||
// Create a new ServeMux for routing jobs
|
||||
mux := asynq.NewServeMux()
|
||||
|
||||
// Register all job handlers
|
||||
sync.RegisterQueueHandlers(mux, syncJob)
|
||||
// Placeholder for other job handlers that might be added in the future
|
||||
// linguistics.RegisterLinguisticHandlers(mux, linguisticJob)
|
||||
linguistics.RegisterLinguisticHandlers(mux, linguisticJob)
|
||||
// trending.RegisterTrendingHandlers(mux, analyticsService)
|
||||
|
||||
// Start the server in a goroutine
|
||||
|
||||
@ -1,43 +0,0 @@
|
||||
{
|
||||
"contentTypeSlug": "blog",
|
||||
"title": "The Future of Artificial Intelligence",
|
||||
"slug": "future-of-ai",
|
||||
"status": "published",
|
||||
"content": {
|
||||
"excerpt": "A deep dive into the future of artificial intelligence, exploring its potential impact on society, industry, and our daily lives.",
|
||||
"content": "<p>Artificial intelligence (AI) is no longer a concept confined to science fiction. It's a powerful force that's reshaping our world in countless ways. From the algorithms that power our social media feeds to the sophisticated systems that drive autonomous vehicles, AI is already here. But what does the future hold for this transformative technology?</p><p>In this post, we'll explore some of the most exciting advancements on the horizon, including the rise of general AI, the potential for AI-driven scientific discovery, and the ethical considerations that we must address as we move forward.</p>",
|
||||
"publishDate": "2024-09-15",
|
||||
"author": "Dr. Evelyn Reed",
|
||||
"tags": ["AI", "Machine Learning", "Technology"],
|
||||
"meta_title": "The Future of AI: A Comprehensive Overview",
|
||||
"meta_description": "Learn about the future of artificial intelligence and its potential impact on our world."
|
||||
},
|
||||
"languageCode": "en-US",
|
||||
"isDefault": true,
|
||||
"id": "post-1",
|
||||
"translation_group_id": "tg-future-of-ai",
|
||||
"lifecycle": {
|
||||
"state": "published",
|
||||
"published_at": "2024-09-15T10:00:00Z",
|
||||
"timezone": "UTC"
|
||||
},
|
||||
"seo": {
|
||||
"canonical": "https://example.com/blog/future-of-ai",
|
||||
"og_title": "The Future of Artificial Intelligence",
|
||||
"og_description": "A deep dive into the future of AI.",
|
||||
"twitter_card": "summary_large_image"
|
||||
},
|
||||
"taxonomy": {
|
||||
"categories": ["Technology", "Science"],
|
||||
"featured": true
|
||||
},
|
||||
"relations": {
|
||||
"related_posts": ["post-2"]
|
||||
},
|
||||
"assets": {
|
||||
"hero_image": {
|
||||
"url": "https://example.com/images/ai-future.jpg",
|
||||
"alt": "An abstract image representing artificial intelligence."
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,43 +0,0 @@
|
||||
{
|
||||
"contentTypeSlug": "blog",
|
||||
"title": "A Guide to Sustainable Living",
|
||||
"slug": "guide-to-sustainable-living",
|
||||
"status": "published",
|
||||
"content": {
|
||||
"excerpt": "Discover practical tips and simple changes you can make to live a more sustainable and eco-friendly lifestyle.",
|
||||
"content": "<p>Living sustainably doesn't have to be complicated. It's about making conscious choices that reduce your environmental impact. In this guide, we'll cover everything from reducing your plastic consumption to creating a more energy-efficient home.</p><p>We'll also explore the benefits of a plant-based diet and how you can support local, sustainable businesses in your community.</p>",
|
||||
"publishDate": "2024-09-18",
|
||||
"author": "Liam Carter",
|
||||
"tags": ["Sustainability", "Eco-Friendly", "Lifestyle"],
|
||||
"meta_title": "Your Ultimate Guide to Sustainable Living",
|
||||
"meta_description": "Learn how to live a more sustainable lifestyle with our comprehensive guide."
|
||||
},
|
||||
"languageCode": "en-US",
|
||||
"isDefault": true,
|
||||
"id": "post-2",
|
||||
"translation_group_id": "tg-sustainable-living",
|
||||
"lifecycle": {
|
||||
"state": "published",
|
||||
"published_at": "2024-09-18T10:00:00Z",
|
||||
"timezone": "UTC"
|
||||
},
|
||||
"seo": {
|
||||
"canonical": "https://example.com/blog/guide-to-sustainable-living",
|
||||
"og_title": "A Guide to Sustainable Living",
|
||||
"og_description": "Discover practical tips for a more sustainable lifestyle.",
|
||||
"twitter_card": "summary"
|
||||
},
|
||||
"taxonomy": {
|
||||
"categories": ["Lifestyle", "Environment"],
|
||||
"featured": false
|
||||
},
|
||||
"relations": {
|
||||
"related_posts": ["post-1", "post-3"]
|
||||
},
|
||||
"assets": {
|
||||
"hero_image": {
|
||||
"url": "https://example.com/images/sustainable-living.jpg",
|
||||
"alt": "A person holding a reusable water bottle in a lush green environment."
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,43 +0,0 @@
|
||||
{
|
||||
"contentTypeSlug": "blog",
|
||||
"title": "The Art of Mindful Meditation",
|
||||
"slug": "art-of-mindful-meditation",
|
||||
"status": "published",
|
||||
"content": {
|
||||
"excerpt": "Learn the basics of mindful meditation and how it can help you reduce stress, improve focus, and cultivate a sense of inner peace.",
|
||||
"content": "<p>In our fast-paced world, it's easy to get caught up in the chaos. Mindful meditation offers a powerful tool to ground yourself in the present moment and find a sense of calm amidst the noise.</p><p>This post will guide you through the fundamental principles of mindfulness and provide simple exercises to help you start your meditation practice.</p>",
|
||||
"publishDate": "2024-09-22",
|
||||
"author": "Isabella Rossi",
|
||||
"tags": ["Mindfulness", "Meditation", "Wellness"],
|
||||
"meta_title": "A Beginner's Guide to Mindful Meditation",
|
||||
"meta_description": "Start your journey with mindful meditation and discover its many benefits."
|
||||
},
|
||||
"languageCode": "en-US",
|
||||
"isDefault": true,
|
||||
"id": "post-3",
|
||||
"translation_group_id": "tg-mindful-meditation",
|
||||
"lifecycle": {
|
||||
"state": "published",
|
||||
"published_at": "2024-09-22T10:00:00Z",
|
||||
"timezone": "UTC"
|
||||
},
|
||||
"seo": {
|
||||
"canonical": "https://example.com/blog/art-of-mindful-meditation",
|
||||
"og_title": "The Art of Mindful Meditation",
|
||||
"og_description": "Learn the basics of mindful meditation.",
|
||||
"twitter_card": "summary_large_image"
|
||||
},
|
||||
"taxonomy": {
|
||||
"categories": ["Wellness", "Lifestyle"],
|
||||
"featured": true
|
||||
},
|
||||
"relations": {
|
||||
"related_posts": ["post-2", "post-4"]
|
||||
},
|
||||
"assets": {
|
||||
"hero_image": {
|
||||
"url": "https://example.com/images/meditation.jpg",
|
||||
"alt": "A person meditating peacefully in a serene setting."
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,43 +0,0 @@
|
||||
{
|
||||
"contentTypeSlug": "blog",
|
||||
"title": "Exploring the Wonders of the Cosmos",
|
||||
"slug": "exploring-the-cosmos",
|
||||
"status": "published",
|
||||
"content": {
|
||||
"excerpt": "Join us on a journey through the cosmos as we explore distant galaxies, mysterious black holes, and the search for extraterrestrial life.",
|
||||
"content": "<p>The universe is a vast and mysterious place, filled with wonders that we are only just beginning to understand. From the birth of stars to the formation of galaxies, the cosmos is a story of epic proportions.</p><p>In this post, we'll take a look at some of the most awe-inspiring discoveries in modern astronomy and consider the big questions that continue to drive our exploration of space.</p>",
|
||||
"publishDate": "2024-09-25",
|
||||
"author": "Dr. Kenji Tanaka",
|
||||
"tags": ["Astronomy", "Space", "Science"],
|
||||
"meta_title": "A Journey Through the Cosmos",
|
||||
"meta_description": "Explore the wonders of the universe with our guide to modern astronomy."
|
||||
},
|
||||
"languageCode": "en-US",
|
||||
"isDefault": true,
|
||||
"id": "post-4",
|
||||
"translation_group_id": "tg-exploring-the-cosmos",
|
||||
"lifecycle": {
|
||||
"state": "published",
|
||||
"published_at": "2024-09-25T10:00:00Z",
|
||||
"timezone": "UTC"
|
||||
},
|
||||
"seo": {
|
||||
"canonical": "https://example.com/blog/exploring-the-cosmos",
|
||||
"og_title": "Exploring the Wonders of the Cosmos",
|
||||
"og_description": "A journey through the cosmos.",
|
||||
"twitter_card": "summary"
|
||||
},
|
||||
"taxonomy": {
|
||||
"categories": ["Science", "Astronomy"],
|
||||
"featured": false
|
||||
},
|
||||
"relations": {
|
||||
"related_posts": ["post-1", "post-5"]
|
||||
},
|
||||
"assets": {
|
||||
"hero_image": {
|
||||
"url": "https://example.com/images/cosmos.jpg",
|
||||
"alt": "A stunning image of a spiral galaxy."
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,43 +0,0 @@
|
||||
{
|
||||
"contentTypeSlug": "blog",
|
||||
"title": "The Rise of Remote Work",
|
||||
"slug": "rise-of-remote-work",
|
||||
"status": "published",
|
||||
"content": {
|
||||
"excerpt": "Remote work is here to stay. In this post, we'll explore the benefits and challenges of working from home and how to create a productive and healthy remote work environment.",
|
||||
"content": "<p>The way we work has been fundamentally transformed in recent years. Remote work has gone from a niche perk to a mainstream reality for millions of people around the world.</p><p>This shift has brought with it a host of new opportunities and challenges. We'll discuss how to stay focused and motivated while working from home, how to maintain a healthy work-life balance, and how companies can build strong remote teams.</p>",
|
||||
"publishDate": "2024-09-28",
|
||||
"author": "Chloe Davis",
|
||||
"tags": ["Remote Work", "Productivity", "Future of Work"],
|
||||
"meta_title": "Navigating the World of Remote Work",
|
||||
"meta_description": "Learn how to thrive in a remote work environment."
|
||||
},
|
||||
"languageCode": "en-US",
|
||||
"isDefault": true,
|
||||
"id": "post-5",
|
||||
"translation_group_id": "tg-remote-work",
|
||||
"lifecycle": {
|
||||
"state": "published",
|
||||
"published_at": "2024-09-28T10:00:00Z",
|
||||
"timezone": "UTC"
|
||||
},
|
||||
"seo": {
|
||||
"canonical": "https://example.com/blog/rise-of-remote-work",
|
||||
"og_title": "The Rise of Remote Work",
|
||||
"og_description": "The benefits and challenges of working from home.",
|
||||
"twitter_card": "summary_large_image"
|
||||
},
|
||||
"taxonomy": {
|
||||
"categories": ["Work", "Productivity"],
|
||||
"featured": true
|
||||
},
|
||||
"relations": {
|
||||
"related_posts": ["post-2", "post-4"]
|
||||
},
|
||||
"assets": {
|
||||
"hero_image": {
|
||||
"url": "https://example.com/images/remote-work.jpg",
|
||||
"alt": "A person working on a laptop in a comfortable home office setting."
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -14,7 +14,8 @@ services:
|
||||
- DB_PASSWORD=postgres
|
||||
- DB_NAME=tercul
|
||||
- REDIS_ADDR=redis:6379
|
||||
- WEAVIATE_HOST=http://weaviate:8080
|
||||
- WEAVIATE_HOST=weaviate:8080
|
||||
- WEAVIATE_SCHEME=http
|
||||
depends_on:
|
||||
- postgres
|
||||
- redis
|
||||
|
||||
2
go.mod
2
go.mod
@ -1,6 +1,6 @@
|
||||
module tercul
|
||||
|
||||
go 1.24.10
|
||||
go 1.25.3
|
||||
|
||||
require (
|
||||
github.com/99designs/gqlgen v0.17.72
|
||||
|
||||
@ -73,4 +73,4 @@ func (m *mockAnalyticsService) GetTrendingWorks(ctx context.Context, timePeriod
|
||||
return nil, args.Error(1)
|
||||
}
|
||||
return args.Get(0).([]*domain.Work), args.Error(1)
|
||||
}
|
||||
}
|
||||
|
||||
@ -46,4 +46,4 @@ func NewErrorPresenter() graphql.ErrorPresenterFunc {
|
||||
|
||||
return gqlErr
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,9 +1,13 @@
|
||||
package graphql
|
||||
|
||||
import "context"
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// resolveWorkContent uses the Work service to fetch preferred content for a work.
|
||||
func (r *queryResolver) resolveWorkContent(ctx context.Context, workID uint, preferredLanguage string) *string {
|
||||
func (r *queryResolver) resolveWorkContent(ctx context.Context, workID uuid.UUID, preferredLanguage string) *string {
|
||||
if r.App.Work == nil || r.App.Work.Queries == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -17,7 +17,7 @@ func (m *mockLikeRepository) Create(ctx context.Context, entity *domain.Like) er
|
||||
args := m.Called(ctx, entity)
|
||||
return args.Error(0)
|
||||
}
|
||||
func (m *mockLikeRepository) GetByID(ctx context.Context, id uint) (*domain.Like, error) {
|
||||
func (m *mockLikeRepository) GetByID(ctx context.Context, id uuid.UUID) (*domain.Like, error) {
|
||||
args := m.Called(ctx, id)
|
||||
if args.Get(0) == nil {
|
||||
return nil, args.Error(1)
|
||||
@ -135,4 +135,4 @@ func (m *mockLikeRepository) Exists(ctx context.Context, id uint) (bool, error)
|
||||
func (m *mockLikeRepository) BeginTx(ctx context.Context) (*gorm.DB, error) { return nil, nil }
|
||||
func (m *mockLikeRepository) WithTx(ctx context.Context, fn func(tx *gorm.DB) error) error {
|
||||
return fn(nil)
|
||||
}
|
||||
}
|
||||
|
||||
@ -116,4 +116,4 @@ func (s *LikeResolversUnitSuite) TestDeleteLike() {
|
||||
|
||||
// Verify that the repository's Delete method was called
|
||||
s.mockLikeRepo.AssertCalled(s.T(), "Delete", mock.Anything, uint(likeIDUint))
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,7 +8,7 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"tercul/internal/adapters/graphql/model"
|
||||
"tercul/internal/app/auth"
|
||||
"tercul/internal/app/author"
|
||||
@ -25,8 +25,27 @@ import (
|
||||
platform_auth "tercul/internal/platform/auth"
|
||||
"tercul/internal/platform/log"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
func toModelUserRole(role domain.UserRole) model.UserRole {
|
||||
switch strings.ToLower(string(role)) {
|
||||
case "reader":
|
||||
return model.UserRoleReader
|
||||
case "contributor":
|
||||
return model.UserRoleContributor
|
||||
case "reviewer":
|
||||
return model.UserRoleReviewer
|
||||
case "editor":
|
||||
return model.UserRoleEditor
|
||||
case "admin":
|
||||
return model.UserRoleAdmin
|
||||
default:
|
||||
return model.UserRoleReader
|
||||
}
|
||||
}
|
||||
|
||||
// Register is the resolver for the register field.
|
||||
func (r *mutationResolver) Register(ctx context.Context, input model.RegisterInput) (*model.AuthPayload, error) {
|
||||
// Convert GraphQL input to service input
|
||||
@ -54,7 +73,7 @@ func (r *mutationResolver) Register(ctx context.Context, input model.RegisterInp
|
||||
FirstName: &authResponse.User.FirstName,
|
||||
LastName: &authResponse.User.LastName,
|
||||
DisplayName: &authResponse.User.DisplayName,
|
||||
Role: model.UserRole(authResponse.User.Role),
|
||||
Role: toModelUserRole(authResponse.User.Role),
|
||||
Verified: authResponse.User.Verified,
|
||||
Active: authResponse.User.Active,
|
||||
},
|
||||
@ -85,7 +104,7 @@ func (r *mutationResolver) Login(ctx context.Context, input model.LoginInput) (*
|
||||
FirstName: &authResponse.User.FirstName,
|
||||
LastName: &authResponse.User.LastName,
|
||||
DisplayName: &authResponse.User.DisplayName,
|
||||
Role: model.UserRole(authResponse.User.Role),
|
||||
Role: toModelUserRole(authResponse.User.Role),
|
||||
Verified: authResponse.User.Verified,
|
||||
Active: authResponse.User.Active,
|
||||
},
|
||||
@ -126,7 +145,7 @@ func (r *mutationResolver) CreateWork(ctx context.Context, input model.WorkInput
|
||||
|
||||
// Convert to GraphQL model
|
||||
return &model.Work{
|
||||
ID: fmt.Sprintf("%d", createdWork.ID),
|
||||
ID: createdWork.ID.String(),
|
||||
Name: createdWork.Title,
|
||||
Language: createdWork.Language,
|
||||
Content: input.Content,
|
||||
@ -139,7 +158,7 @@ func (r *mutationResolver) UpdateWork(ctx context.Context, id string, input mode
|
||||
return nil, err
|
||||
}
|
||||
|
||||
workID, err := strconv.ParseUint(id, 10, 32)
|
||||
workID, err := uuid.Parse(id)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: invalid work ID", domain.ErrValidation)
|
||||
}
|
||||
@ -147,7 +166,7 @@ func (r *mutationResolver) UpdateWork(ctx context.Context, id string, input mode
|
||||
// Create domain model
|
||||
workModel := &domain.Work{
|
||||
TranslatableModel: domain.TranslatableModel{
|
||||
BaseModel: domain.BaseModel{ID: uint(workID)},
|
||||
BaseModel: domain.BaseModel{ID: workID},
|
||||
Language: input.Language,
|
||||
},
|
||||
Title: input.Name,
|
||||
@ -170,12 +189,12 @@ func (r *mutationResolver) UpdateWork(ctx context.Context, id string, input mode
|
||||
|
||||
// DeleteWork is the resolver for the deleteWork field.
|
||||
func (r *mutationResolver) DeleteWork(ctx context.Context, id string) (bool, error) {
|
||||
workID, err := strconv.ParseUint(id, 10, 32)
|
||||
workID, err := uuid.Parse(id)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("%w: invalid work ID", domain.ErrValidation)
|
||||
}
|
||||
|
||||
err = r.App.Work.Commands.DeleteWork(ctx, uint(workID))
|
||||
err = r.App.Work.Commands.DeleteWork(ctx, workID)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
@ -189,7 +208,7 @@ func (r *mutationResolver) CreateTranslation(ctx context.Context, input model.Tr
|
||||
return nil, err
|
||||
}
|
||||
|
||||
workID, err := strconv.ParseUint(input.WorkID, 10, 32)
|
||||
workID, err := uuid.Parse(input.WorkID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: invalid work ID", domain.ErrValidation)
|
||||
}
|
||||
@ -203,7 +222,7 @@ func (r *mutationResolver) CreateTranslation(ctx context.Context, input model.Tr
|
||||
Title: input.Name,
|
||||
Content: content,
|
||||
Language: input.Language,
|
||||
TranslatableID: uint(workID),
|
||||
TranslatableID: workID,
|
||||
TranslatableType: "works",
|
||||
}
|
||||
createdTranslation, err := r.App.Translation.Commands.CreateOrUpdateTranslation(ctx, createInput)
|
||||
@ -212,7 +231,7 @@ func (r *mutationResolver) CreateTranslation(ctx context.Context, input model.Tr
|
||||
}
|
||||
|
||||
go func() {
|
||||
if err := r.App.Analytics.IncrementWorkTranslationCount(context.Background(), uint(workID)); err != nil {
|
||||
if err := r.App.Analytics.IncrementWorkTranslationCount(context.Background(), workID); err != nil {
|
||||
log.Error(err, "failed to increment work translation count")
|
||||
}
|
||||
}()
|
||||
@ -232,7 +251,7 @@ func (r *mutationResolver) UpdateTranslation(ctx context.Context, id string, inp
|
||||
return nil, err
|
||||
}
|
||||
|
||||
workID, err := strconv.ParseUint(input.WorkID, 10, 32)
|
||||
workID, err := uuid.Parse(input.WorkID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: invalid work ID", domain.ErrValidation)
|
||||
}
|
||||
@ -246,7 +265,7 @@ func (r *mutationResolver) UpdateTranslation(ctx context.Context, id string, inp
|
||||
Title: input.Name,
|
||||
Content: content,
|
||||
Language: input.Language,
|
||||
TranslatableID: uint(workID),
|
||||
TranslatableID: workID,
|
||||
TranslatableType: "works",
|
||||
}
|
||||
updatedTranslation, err := r.App.Translation.Commands.CreateOrUpdateTranslation(ctx, updateInput)
|
||||
@ -265,12 +284,12 @@ func (r *mutationResolver) UpdateTranslation(ctx context.Context, id string, inp
|
||||
|
||||
// DeleteTranslation is the resolver for the deleteTranslation field.
|
||||
func (r *mutationResolver) DeleteTranslation(ctx context.Context, id string) (bool, error) {
|
||||
translationID, err := strconv.ParseUint(id, 10, 32)
|
||||
translationID, err := uuid.Parse(id)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("invalid translation ID: %v", err)
|
||||
}
|
||||
|
||||
err = r.App.Translation.Commands.DeleteTranslation(ctx, uint(translationID))
|
||||
err = r.App.Translation.Commands.DeleteTranslation(ctx, translationID)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
@ -311,13 +330,13 @@ func (r *mutationResolver) UpdateBook(ctx context.Context, id string, input mode
|
||||
return nil, err
|
||||
}
|
||||
|
||||
bookID, err := strconv.ParseUint(id, 10, 32)
|
||||
bookID, err := uuid.Parse(id)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: invalid book ID", domain.ErrValidation)
|
||||
}
|
||||
|
||||
updateInput := book.UpdateBookInput{
|
||||
ID: uint(bookID),
|
||||
ID: bookID,
|
||||
Title: &input.Name,
|
||||
Description: input.Description,
|
||||
Language: &input.Language,
|
||||
@ -340,12 +359,12 @@ func (r *mutationResolver) UpdateBook(ctx context.Context, id string, input mode
|
||||
|
||||
// DeleteBook is the resolver for the deleteBook field.
|
||||
func (r *mutationResolver) DeleteBook(ctx context.Context, id string) (bool, error) {
|
||||
bookID, err := strconv.ParseUint(id, 10, 32)
|
||||
bookID, err := uuid.Parse(id)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("%w: invalid book ID", domain.ErrValidation)
|
||||
}
|
||||
|
||||
err = r.App.Book.Commands.DeleteBook(ctx, uint(bookID))
|
||||
err = r.App.Book.Commands.DeleteBook(ctx, bookID)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
@ -378,13 +397,13 @@ func (r *mutationResolver) UpdateAuthor(ctx context.Context, id string, input mo
|
||||
if err := Validate(input); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
authorID, err := strconv.ParseUint(id, 10, 32)
|
||||
authorID, err := uuid.Parse(id)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid author ID: %v", err)
|
||||
}
|
||||
|
||||
updateInput := author.UpdateAuthorInput{
|
||||
ID: uint(authorID),
|
||||
ID: authorID,
|
||||
Name: input.Name,
|
||||
}
|
||||
updatedAuthor, err := r.App.Author.Commands.UpdateAuthor(ctx, updateInput)
|
||||
@ -401,12 +420,12 @@ func (r *mutationResolver) UpdateAuthor(ctx context.Context, id string, input mo
|
||||
|
||||
// DeleteAuthor is the resolver for the deleteAuthor field.
|
||||
func (r *mutationResolver) DeleteAuthor(ctx context.Context, id string) (bool, error) {
|
||||
authorID, err := strconv.ParseUint(id, 10, 32)
|
||||
authorID, err := uuid.Parse(id)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("invalid author ID: %v", err)
|
||||
}
|
||||
|
||||
err = r.App.Author.Commands.DeleteAuthor(ctx, uint(authorID))
|
||||
err = r.App.Author.Commands.DeleteAuthor(ctx, authorID)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
@ -420,13 +439,13 @@ func (r *mutationResolver) UpdateUser(ctx context.Context, id string, input mode
|
||||
return nil, err
|
||||
}
|
||||
|
||||
userID, err := strconv.ParseUint(id, 10, 32)
|
||||
userID, err := uuid.Parse(id)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid user ID: %v", err)
|
||||
}
|
||||
|
||||
updateInput := user.UpdateUserInput{
|
||||
ID: uint(userID),
|
||||
ID: userID,
|
||||
Username: input.Username,
|
||||
Email: input.Email,
|
||||
Password: input.Password,
|
||||
@ -445,28 +464,25 @@ func (r *mutationResolver) UpdateUser(ctx context.Context, id string, input mode
|
||||
}
|
||||
|
||||
if input.CountryID != nil {
|
||||
countryID, err := strconv.ParseUint(*input.CountryID, 10, 32)
|
||||
countryID, err := uuid.Parse(*input.CountryID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid country ID: %v", err)
|
||||
}
|
||||
uid := uint(countryID)
|
||||
updateInput.CountryID = &uid
|
||||
updateInput.CountryID = &countryID
|
||||
}
|
||||
if input.CityID != nil {
|
||||
cityID, err := strconv.ParseUint(*input.CityID, 10, 32)
|
||||
cityID, err := uuid.Parse(*input.CityID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid city ID: %v", err)
|
||||
}
|
||||
uid := uint(cityID)
|
||||
updateInput.CityID = &uid
|
||||
updateInput.CityID = &cityID
|
||||
}
|
||||
if input.AddressID != nil {
|
||||
addressID, err := strconv.ParseUint(*input.AddressID, 10, 32)
|
||||
addressID, err := uuid.Parse(*input.AddressID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid address ID: %v", err)
|
||||
}
|
||||
uid := uint(addressID)
|
||||
updateInput.AddressID = &uid
|
||||
updateInput.AddressID = &addressID
|
||||
}
|
||||
|
||||
updatedUser, err := r.App.User.Commands.UpdateUser(ctx, updateInput)
|
||||
@ -483,7 +499,7 @@ func (r *mutationResolver) UpdateUser(ctx context.Context, id string, input mode
|
||||
DisplayName: &updatedUser.DisplayName,
|
||||
Bio: &updatedUser.Bio,
|
||||
AvatarURL: &updatedUser.AvatarURL,
|
||||
Role: model.UserRole(updatedUser.Role),
|
||||
Role: toModelUserRole(updatedUser.Role),
|
||||
Verified: updatedUser.Verified,
|
||||
Active: updatedUser.Active,
|
||||
}, nil
|
||||
@ -491,12 +507,12 @@ func (r *mutationResolver) UpdateUser(ctx context.Context, id string, input mode
|
||||
|
||||
// DeleteUser is the resolver for the deleteUser field.
|
||||
func (r *mutationResolver) DeleteUser(ctx context.Context, id string) (bool, error) {
|
||||
userID, err := strconv.ParseUint(id, 10, 32)
|
||||
userID, err := uuid.Parse(id)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("%w: invalid user ID", domain.ErrValidation)
|
||||
}
|
||||
|
||||
err = r.App.User.Commands.DeleteUser(ctx, uint(userID))
|
||||
err = r.App.User.Commands.DeleteUser(ctx, userID)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
@ -540,13 +556,13 @@ func (r *mutationResolver) UpdateCollection(ctx context.Context, id string, inpu
|
||||
return nil, fmt.Errorf("unauthorized")
|
||||
}
|
||||
|
||||
collectionID, err := strconv.ParseUint(id, 10, 32)
|
||||
collectionID, err := uuid.Parse(id)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid collection ID: %v", err)
|
||||
}
|
||||
|
||||
updateInput := collection.UpdateCollectionInput{
|
||||
ID: uint(collectionID),
|
||||
ID: collectionID,
|
||||
Name: input.Name,
|
||||
UserID: userID,
|
||||
}
|
||||
@ -575,12 +591,12 @@ func (r *mutationResolver) DeleteCollection(ctx context.Context, id string) (boo
|
||||
return false, fmt.Errorf("unauthorized")
|
||||
}
|
||||
|
||||
collectionID, err := strconv.ParseUint(id, 10, 32)
|
||||
collectionID, err := uuid.Parse(id)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("invalid collection ID: %v", err)
|
||||
}
|
||||
|
||||
err = r.App.Collection.Commands.DeleteCollection(ctx, uint(collectionID), userID)
|
||||
err = r.App.Collection.Commands.DeleteCollection(ctx, collectionID, userID)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
@ -595,18 +611,18 @@ func (r *mutationResolver) AddWorkToCollection(ctx context.Context, collectionID
|
||||
return nil, fmt.Errorf("unauthorized")
|
||||
}
|
||||
|
||||
collID, err := strconv.ParseUint(collectionID, 10, 32)
|
||||
collID, err := uuid.Parse(collectionID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid collection ID: %v", err)
|
||||
}
|
||||
wID, err := strconv.ParseUint(workID, 10, 32)
|
||||
wID, err := uuid.Parse(workID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid work ID: %v", err)
|
||||
}
|
||||
|
||||
addInput := collection.AddWorkToCollectionInput{
|
||||
CollectionID: uint(collID),
|
||||
WorkID: uint(wID),
|
||||
CollectionID: collID,
|
||||
WorkID: wID,
|
||||
UserID: userID,
|
||||
}
|
||||
err = r.App.Collection.Commands.AddWorkToCollection(ctx, addInput)
|
||||
@ -614,7 +630,7 @@ func (r *mutationResolver) AddWorkToCollection(ctx context.Context, collectionID
|
||||
return nil, err
|
||||
}
|
||||
|
||||
updatedCollection, err := r.App.Collection.Queries.Collection(ctx, uint(collID))
|
||||
updatedCollection, err := r.App.Collection.Queries.Collection(ctx, collID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -633,18 +649,18 @@ func (r *mutationResolver) RemoveWorkFromCollection(ctx context.Context, collect
|
||||
return nil, fmt.Errorf("unauthorized")
|
||||
}
|
||||
|
||||
collID, err := strconv.ParseUint(collectionID, 10, 32)
|
||||
collID, err := uuid.Parse(collectionID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid collection ID: %v", err)
|
||||
}
|
||||
wID, err := strconv.ParseUint(workID, 10, 32)
|
||||
wID, err := uuid.Parse(workID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid work ID: %v", err)
|
||||
}
|
||||
|
||||
removeInput := collection.RemoveWorkFromCollectionInput{
|
||||
CollectionID: uint(collID),
|
||||
WorkID: uint(wID),
|
||||
CollectionID: collID,
|
||||
WorkID: wID,
|
||||
UserID: userID,
|
||||
}
|
||||
err = r.App.Collection.Commands.RemoveWorkFromCollection(ctx, removeInput)
|
||||
@ -652,7 +668,7 @@ func (r *mutationResolver) RemoveWorkFromCollection(ctx context.Context, collect
|
||||
return nil, err
|
||||
}
|
||||
|
||||
updatedCollection, err := r.App.Collection.Queries.Collection(ctx, uint(collID))
|
||||
updatedCollection, err := r.App.Collection.Queries.Collection(ctx, collID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -680,28 +696,25 @@ func (r *mutationResolver) CreateComment(ctx context.Context, input model.Commen
|
||||
UserID: userID,
|
||||
}
|
||||
if input.WorkID != nil {
|
||||
workID, err := strconv.ParseUint(*input.WorkID, 10, 32)
|
||||
workID, err := uuid.Parse(*input.WorkID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid work ID: %v", err)
|
||||
}
|
||||
wID := uint(workID)
|
||||
createInput.WorkID = &wID
|
||||
createInput.WorkID = &workID
|
||||
}
|
||||
if input.TranslationID != nil {
|
||||
translationID, err := strconv.ParseUint(*input.TranslationID, 10, 32)
|
||||
translationID, err := uuid.Parse(*input.TranslationID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid translation ID: %v", err)
|
||||
}
|
||||
tID := uint(translationID)
|
||||
createInput.TranslationID = &tID
|
||||
createInput.TranslationID = &translationID
|
||||
}
|
||||
if input.ParentCommentID != nil {
|
||||
parentCommentID, err := strconv.ParseUint(*input.ParentCommentID, 10, 32)
|
||||
parentCommentID, err := uuid.Parse(*input.ParentCommentID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid parent comment ID: %v", err)
|
||||
}
|
||||
pID := uint(parentCommentID)
|
||||
createInput.ParentID = &pID
|
||||
createInput.ParentID = &parentCommentID
|
||||
}
|
||||
|
||||
createdComment, err := r.App.Comment.Commands.CreateComment(ctx, createInput)
|
||||
@ -736,12 +749,12 @@ func (r *mutationResolver) UpdateComment(ctx context.Context, id string, input m
|
||||
return nil, fmt.Errorf("unauthorized")
|
||||
}
|
||||
|
||||
commentID, err := strconv.ParseUint(id, 10, 32)
|
||||
commentID, err := uuid.Parse(id)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid comment ID: %v", err)
|
||||
}
|
||||
|
||||
commentModel, err := r.App.Comment.Queries.Comment(ctx, uint(commentID))
|
||||
commentModel, err := r.App.Comment.Queries.Comment(ctx, commentID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -754,7 +767,7 @@ func (r *mutationResolver) UpdateComment(ctx context.Context, id string, input m
|
||||
}
|
||||
|
||||
updateInput := comment.UpdateCommentInput{
|
||||
ID: uint(commentID),
|
||||
ID: commentID,
|
||||
Text: input.Text,
|
||||
}
|
||||
updatedComment, err := r.App.Comment.Commands.UpdateComment(ctx, updateInput)
|
||||
@ -778,12 +791,12 @@ func (r *mutationResolver) DeleteComment(ctx context.Context, id string) (bool,
|
||||
return false, fmt.Errorf("unauthorized")
|
||||
}
|
||||
|
||||
commentID, err := strconv.ParseUint(id, 10, 32)
|
||||
commentID, err := uuid.Parse(id)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("invalid comment ID: %v", err)
|
||||
}
|
||||
|
||||
comment, err := r.App.Comment.Queries.Comment(ctx, uint(commentID))
|
||||
comment, err := r.App.Comment.Queries.Comment(ctx, commentID)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
@ -795,7 +808,7 @@ func (r *mutationResolver) DeleteComment(ctx context.Context, id string) (bool,
|
||||
return false, fmt.Errorf("unauthorized")
|
||||
}
|
||||
|
||||
err = r.App.Comment.Commands.DeleteComment(ctx, uint(commentID))
|
||||
err = r.App.Comment.Commands.DeleteComment(ctx, commentID)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
@ -821,28 +834,25 @@ func (r *mutationResolver) CreateLike(ctx context.Context, input model.LikeInput
|
||||
UserID: userID,
|
||||
}
|
||||
if input.WorkID != nil {
|
||||
workID, err := strconv.ParseUint(*input.WorkID, 10, 32)
|
||||
workID, err := uuid.Parse(*input.WorkID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid work ID: %v", err)
|
||||
}
|
||||
wID := uint(workID)
|
||||
createInput.WorkID = &wID
|
||||
createInput.WorkID = &workID
|
||||
}
|
||||
if input.TranslationID != nil {
|
||||
translationID, err := strconv.ParseUint(*input.TranslationID, 10, 32)
|
||||
translationID, err := uuid.Parse(*input.TranslationID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid translation ID: %v", err)
|
||||
}
|
||||
tID := uint(translationID)
|
||||
createInput.TranslationID = &tID
|
||||
createInput.TranslationID = &translationID
|
||||
}
|
||||
if input.CommentID != nil {
|
||||
commentID, err := strconv.ParseUint(*input.CommentID, 10, 32)
|
||||
commentID, err := uuid.Parse(*input.CommentID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid comment ID: %v", err)
|
||||
}
|
||||
cID := uint(commentID)
|
||||
createInput.CommentID = &cID
|
||||
createInput.CommentID = &commentID
|
||||
}
|
||||
|
||||
createdLike, err := r.App.Like.Commands.CreateLike(ctx, createInput)
|
||||
@ -874,12 +884,12 @@ func (r *mutationResolver) DeleteLike(ctx context.Context, id string) (bool, err
|
||||
return false, fmt.Errorf("unauthorized")
|
||||
}
|
||||
|
||||
likeID, err := strconv.ParseUint(id, 10, 32)
|
||||
likeID, err := uuid.Parse(id)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("invalid like ID: %v", err)
|
||||
}
|
||||
|
||||
like, err := r.App.Like.Queries.Like(ctx, uint(likeID))
|
||||
like, err := r.App.Like.Queries.Like(ctx, likeID)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
@ -891,7 +901,7 @@ func (r *mutationResolver) DeleteLike(ctx context.Context, id string) (bool, err
|
||||
return false, fmt.Errorf("unauthorized")
|
||||
}
|
||||
|
||||
err = r.App.Like.Commands.DeleteLike(ctx, uint(likeID))
|
||||
err = r.App.Like.Commands.DeleteLike(ctx, likeID)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
@ -906,14 +916,14 @@ func (r *mutationResolver) CreateBookmark(ctx context.Context, input model.Bookm
|
||||
return nil, fmt.Errorf("unauthorized")
|
||||
}
|
||||
|
||||
workID, err := strconv.ParseUint(input.WorkID, 10, 32)
|
||||
workID, err := uuid.Parse(input.WorkID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid work ID: %v", err)
|
||||
}
|
||||
|
||||
createInput := bookmark.CreateBookmarkInput{
|
||||
UserID: userID,
|
||||
WorkID: uint(workID),
|
||||
WorkID: workID,
|
||||
}
|
||||
if input.Name != nil {
|
||||
createInput.Name = *input.Name
|
||||
@ -924,7 +934,7 @@ func (r *mutationResolver) CreateBookmark(ctx context.Context, input model.Bookm
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := r.App.Analytics.IncrementWorkBookmarks(ctx, uint(workID)); err != nil {
|
||||
if err := r.App.Analytics.IncrementWorkBookmarks(ctx, workID); err != nil {
|
||||
log.FromContext(ctx).Error(err, "failed to increment work bookmarks")
|
||||
}
|
||||
|
||||
@ -943,12 +953,12 @@ func (r *mutationResolver) DeleteBookmark(ctx context.Context, id string) (bool,
|
||||
return false, fmt.Errorf("unauthorized")
|
||||
}
|
||||
|
||||
bookmarkID, err := strconv.ParseUint(id, 10, 32)
|
||||
bookmarkID, err := uuid.Parse(id)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("invalid bookmark ID: %v", err)
|
||||
}
|
||||
|
||||
bookmark, err := r.App.Bookmark.Queries.Bookmark(ctx, uint(bookmarkID))
|
||||
bookmark, err := r.App.Bookmark.Queries.Bookmark(ctx, bookmarkID)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
@ -960,7 +970,7 @@ func (r *mutationResolver) DeleteBookmark(ctx context.Context, id string) (bool,
|
||||
return false, fmt.Errorf("unauthorized")
|
||||
}
|
||||
|
||||
err = r.App.Bookmark.Commands.DeleteBookmark(ctx, uint(bookmarkID))
|
||||
err = r.App.Bookmark.Commands.DeleteBookmark(ctx, bookmarkID)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
@ -980,21 +990,19 @@ func (r *mutationResolver) CreateContribution(ctx context.Context, input model.C
|
||||
}
|
||||
|
||||
if input.WorkID != nil {
|
||||
workID, err := strconv.ParseUint(*input.WorkID, 10, 32)
|
||||
workID, err := uuid.Parse(*input.WorkID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid work ID: %v", err)
|
||||
}
|
||||
wID := uint(workID)
|
||||
createInput.WorkID = &wID
|
||||
createInput.WorkID = &workID
|
||||
}
|
||||
|
||||
if input.TranslationID != nil {
|
||||
translationID, err := strconv.ParseUint(*input.TranslationID, 10, 32)
|
||||
translationID, err := uuid.Parse(*input.TranslationID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid translation ID: %v", err)
|
||||
}
|
||||
tID := uint(translationID)
|
||||
createInput.TranslationID = &tID
|
||||
createInput.TranslationID = &translationID
|
||||
}
|
||||
|
||||
if input.Status != nil {
|
||||
@ -1025,13 +1033,13 @@ func (r *mutationResolver) UpdateContribution(ctx context.Context, id string, in
|
||||
return nil, domain.ErrUnauthorized
|
||||
}
|
||||
|
||||
contributionID, err := strconv.ParseUint(id, 10, 32)
|
||||
contributionID, err := uuid.Parse(id)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: invalid contribution ID", domain.ErrValidation)
|
||||
}
|
||||
|
||||
updateInput := contribution.UpdateContributionInput{
|
||||
ID: uint(contributionID),
|
||||
ID: contributionID,
|
||||
UserID: userID,
|
||||
Name: &input.Name,
|
||||
}
|
||||
@ -1063,12 +1071,12 @@ func (r *mutationResolver) DeleteContribution(ctx context.Context, id string) (b
|
||||
return false, domain.ErrUnauthorized
|
||||
}
|
||||
|
||||
contributionID, err := strconv.ParseUint(id, 10, 32)
|
||||
contributionID, err := uuid.Parse(id)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("%w: invalid contribution ID", domain.ErrValidation)
|
||||
}
|
||||
|
||||
err = r.App.Contribution.Commands.DeleteContribution(ctx, uint(contributionID), userID)
|
||||
err = r.App.Contribution.Commands.DeleteContribution(ctx, contributionID, userID)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
@ -1078,13 +1086,13 @@ func (r *mutationResolver) DeleteContribution(ctx context.Context, id string) (b
|
||||
|
||||
// ReviewContribution is the resolver for the reviewContribution field.
|
||||
func (r *mutationResolver) ReviewContribution(ctx context.Context, id string, status model.ContributionStatus, feedback *string) (*model.Contribution, error) {
|
||||
contributionID, err := strconv.ParseUint(id, 10, 32)
|
||||
contributionID, err := uuid.Parse(id)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: invalid contribution ID", domain.ErrValidation)
|
||||
}
|
||||
|
||||
reviewInput := contribution.ReviewContributionInput{
|
||||
ID: uint(contributionID),
|
||||
ID: contributionID,
|
||||
Status: status.String(),
|
||||
Feedback: feedback,
|
||||
}
|
||||
@ -1129,7 +1137,7 @@ func (r *mutationResolver) RefreshToken(ctx context.Context) (*model.AuthPayload
|
||||
FirstName: &authResponse.User.FirstName,
|
||||
LastName: &authResponse.User.LastName,
|
||||
DisplayName: &authResponse.User.DisplayName,
|
||||
Role: model.UserRole(authResponse.User.Role),
|
||||
Role: toModelUserRole(authResponse.User.Role),
|
||||
Verified: authResponse.User.Verified,
|
||||
Active: authResponse.User.Active,
|
||||
},
|
||||
@ -1193,28 +1201,25 @@ func (r *mutationResolver) UpdateProfile(ctx context.Context, input model.UserIn
|
||||
}
|
||||
|
||||
if input.CountryID != nil {
|
||||
countryID, err := strconv.ParseUint(*input.CountryID, 10, 32)
|
||||
countryID, err := uuid.Parse(*input.CountryID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid country ID: %v", err)
|
||||
}
|
||||
uid := uint(countryID)
|
||||
updateInput.CountryID = &uid
|
||||
updateInput.CountryID = &countryID
|
||||
}
|
||||
if input.CityID != nil {
|
||||
cityID, err := strconv.ParseUint(*input.CityID, 10, 32)
|
||||
cityID, err := uuid.Parse(*input.CityID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid city ID: %v", err)
|
||||
}
|
||||
uid := uint(cityID)
|
||||
updateInput.CityID = &uid
|
||||
updateInput.CityID = &cityID
|
||||
}
|
||||
if input.AddressID != nil {
|
||||
addressID, err := strconv.ParseUint(*input.AddressID, 10, 32)
|
||||
addressID, err := uuid.Parse(*input.AddressID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid address ID: %v", err)
|
||||
}
|
||||
uid := uint(addressID)
|
||||
updateInput.AddressID = &uid
|
||||
updateInput.AddressID = &addressID
|
||||
}
|
||||
|
||||
updatedUser, err := r.App.User.Commands.UpdateUser(ctx, updateInput)
|
||||
@ -1231,7 +1236,7 @@ func (r *mutationResolver) UpdateProfile(ctx context.Context, input model.UserIn
|
||||
DisplayName: &updatedUser.DisplayName,
|
||||
Bio: &updatedUser.Bio,
|
||||
AvatarURL: &updatedUser.AvatarURL,
|
||||
Role: model.UserRole(updatedUser.Role),
|
||||
Role: toModelUserRole(updatedUser.Role),
|
||||
Verified: updatedUser.Verified,
|
||||
Active: updatedUser.Active,
|
||||
}, nil
|
||||
@ -1260,12 +1265,12 @@ func (r *mutationResolver) ChangePassword(ctx context.Context, currentPassword s
|
||||
|
||||
// Work is the resolver for the work field.
|
||||
func (r *queryResolver) Work(ctx context.Context, id string) (*model.Work, error) {
|
||||
workID, err := strconv.ParseUint(id, 10, 32)
|
||||
workID, err := uuid.Parse(id)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid work ID: %v", err)
|
||||
}
|
||||
|
||||
workDTO, err := r.App.Work.Queries.GetWorkByID(ctx, uint(workID))
|
||||
workDTO, err := r.App.Work.Queries.GetWorkByID(ctx, workID)
|
||||
if err != nil {
|
||||
if errors.Is(err, domain.ErrEntityNotFound) {
|
||||
return nil, nil
|
||||
@ -1274,7 +1279,7 @@ func (r *queryResolver) Work(ctx context.Context, id string) (*model.Work, error
|
||||
}
|
||||
|
||||
go func() {
|
||||
if err := r.App.Analytics.IncrementWorkViews(context.Background(), uint(workID)); err != nil {
|
||||
if err := r.App.Analytics.IncrementWorkViews(context.Background(), workID); err != nil {
|
||||
log.Error(err, "failed to increment work views")
|
||||
}
|
||||
}()
|
||||
@ -1320,12 +1325,12 @@ func (r *queryResolver) Works(ctx context.Context, limit *int32, offset *int32,
|
||||
|
||||
// Translation is the resolver for the translation field.
|
||||
func (r *queryResolver) Translation(ctx context.Context, id string) (*model.Translation, error) {
|
||||
translationID, err := strconv.ParseUint(id, 10, 32)
|
||||
translationID, err := uuid.Parse(id)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid translation ID: %v", err)
|
||||
}
|
||||
|
||||
translationDTO, err := r.App.Translation.Queries.Translation(ctx, uint(translationID))
|
||||
translationDTO, err := r.App.Translation.Queries.Translation(ctx, translationID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -1334,7 +1339,7 @@ func (r *queryResolver) Translation(ctx context.Context, id string) (*model.Tran
|
||||
}
|
||||
|
||||
go func() {
|
||||
if err := r.App.Analytics.IncrementTranslationViews(context.Background(), uint(translationID)); err != nil {
|
||||
if err := r.App.Analytics.IncrementTranslationViews(context.Background(), translationID); err != nil {
|
||||
log.Error(err, "failed to increment translation views")
|
||||
}
|
||||
}()
|
||||
@ -1350,7 +1355,7 @@ func (r *queryResolver) Translation(ctx context.Context, id string) (*model.Tran
|
||||
|
||||
// Translations is the resolver for the translations field.
|
||||
func (r *queryResolver) Translations(ctx context.Context, workID string, language *string, limit *int32, offset *int32) ([]*model.Translation, error) {
|
||||
wID, err := strconv.ParseUint(workID, 10, 32)
|
||||
wID, err := uuid.Parse(workID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: invalid work ID", domain.ErrValidation)
|
||||
}
|
||||
@ -1364,7 +1369,7 @@ func (r *queryResolver) Translations(ctx context.Context, workID string, languag
|
||||
page = int(*offset)/pageSize + 1
|
||||
}
|
||||
|
||||
paginatedResult, err := r.App.Translation.Queries.ListTranslations(ctx, uint(wID), language, page, pageSize)
|
||||
paginatedResult, err := r.App.Translation.Queries.ListTranslations(ctx, wID, language, page, pageSize)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -1384,12 +1389,12 @@ func (r *queryResolver) Translations(ctx context.Context, workID string, languag
|
||||
|
||||
// Book is the resolver for the book field.
|
||||
func (r *queryResolver) Book(ctx context.Context, id string) (*model.Book, error) {
|
||||
bookID, err := strconv.ParseUint(id, 10, 32)
|
||||
bookID, err := uuid.Parse(id)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: invalid book ID", domain.ErrValidation)
|
||||
}
|
||||
|
||||
bookRecord, err := r.App.Book.Queries.Book(ctx, uint(bookID))
|
||||
bookRecord, err := r.App.Book.Queries.Book(ctx, bookID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -1429,12 +1434,12 @@ func (r *queryResolver) Books(ctx context.Context, limit *int32, offset *int32)
|
||||
|
||||
// Author is the resolver for the author field.
|
||||
func (r *queryResolver) Author(ctx context.Context, id string) (*model.Author, error) {
|
||||
authorID, err := strconv.ParseUint(id, 10, 32)
|
||||
authorID, err := uuid.Parse(id)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: invalid author ID", domain.ErrValidation)
|
||||
}
|
||||
|
||||
authorRecord, err := r.App.Author.Queries.Author(ctx, uint(authorID))
|
||||
authorRecord, err := r.App.Author.Queries.Author(ctx, authorID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -1463,18 +1468,17 @@ func (r *queryResolver) Author(ctx context.Context, id string) (*model.Author, e
|
||||
func (r *queryResolver) Authors(ctx context.Context, limit *int32, offset *int32, search *string, countryID *string) ([]*model.Author, error) {
|
||||
var authors []*domain.Author
|
||||
var err error
|
||||
var countryIDUint *uint
|
||||
var countryIDUUID *uuid.UUID
|
||||
|
||||
if countryID != nil {
|
||||
parsedID, err := strconv.ParseUint(*countryID, 10, 32)
|
||||
parsedID, err := uuid.Parse(*countryID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
uid := uint(parsedID)
|
||||
countryIDUint = &uid
|
||||
countryIDUUID = &parsedID
|
||||
}
|
||||
|
||||
authors, err = r.App.Author.Queries.Authors(ctx, countryIDUint)
|
||||
authors, err = r.App.Author.Queries.Authors(ctx, countryIDUUID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -1503,12 +1507,12 @@ func (r *queryResolver) Authors(ctx context.Context, limit *int32, offset *int32
|
||||
|
||||
// User is the resolver for the user field.
|
||||
func (r *queryResolver) User(ctx context.Context, id string) (*model.User, error) {
|
||||
userID, err := strconv.ParseUint(id, 10, 32)
|
||||
userID, err := uuid.Parse(id)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: invalid user ID", domain.ErrValidation)
|
||||
}
|
||||
|
||||
userRecord, err := r.App.User.Queries.User(ctx, uint(userID))
|
||||
userRecord, err := r.App.User.Queries.User(ctx, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -1525,7 +1529,7 @@ func (r *queryResolver) User(ctx context.Context, id string) (*model.User, error
|
||||
DisplayName: &userRecord.DisplayName,
|
||||
Bio: &userRecord.Bio,
|
||||
AvatarURL: &userRecord.AvatarURL,
|
||||
Role: model.UserRole(userRecord.Role),
|
||||
Role: toModelUserRole(userRecord.Role),
|
||||
Verified: userRecord.Verified,
|
||||
Active: userRecord.Active,
|
||||
}, nil
|
||||
@ -1550,7 +1554,7 @@ func (r *queryResolver) UserByEmail(ctx context.Context, email string) (*model.U
|
||||
DisplayName: &userRecord.DisplayName,
|
||||
Bio: &userRecord.Bio,
|
||||
AvatarURL: &userRecord.AvatarURL,
|
||||
Role: model.UserRole(userRecord.Role),
|
||||
Role: toModelUserRole(userRecord.Role),
|
||||
Verified: userRecord.Verified,
|
||||
Active: userRecord.Active,
|
||||
}, nil
|
||||
@ -1575,7 +1579,7 @@ func (r *queryResolver) UserByUsername(ctx context.Context, username string) (*m
|
||||
DisplayName: &userRecord.DisplayName,
|
||||
Bio: &userRecord.Bio,
|
||||
AvatarURL: &userRecord.AvatarURL,
|
||||
Role: model.UserRole(userRecord.Role),
|
||||
Role: toModelUserRole(userRecord.Role),
|
||||
Verified: userRecord.Verified,
|
||||
Active: userRecord.Active,
|
||||
}, nil
|
||||
@ -1664,7 +1668,7 @@ func (r *queryResolver) Me(ctx context.Context) (*model.User, error) {
|
||||
DisplayName: &userRecord.DisplayName,
|
||||
Bio: &userRecord.Bio,
|
||||
AvatarURL: &userRecord.AvatarURL,
|
||||
Role: model.UserRole(userRecord.Role),
|
||||
Role: toModelUserRole(userRecord.Role),
|
||||
Verified: userRecord.Verified,
|
||||
Active: userRecord.Active,
|
||||
}, nil
|
||||
@ -1672,12 +1676,12 @@ func (r *queryResolver) Me(ctx context.Context) (*model.User, error) {
|
||||
|
||||
// UserProfile is the resolver for the userProfile field.
|
||||
func (r *queryResolver) UserProfile(ctx context.Context, userID string) (*model.UserProfile, error) {
|
||||
uID, err := strconv.ParseUint(userID, 10, 32)
|
||||
uID, err := uuid.Parse(userID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: invalid user ID", domain.ErrValidation)
|
||||
}
|
||||
|
||||
profile, err := r.App.User.Queries.UserProfile(ctx, uint(uID))
|
||||
profile, err := r.App.User.Queries.UserProfile(ctx, uID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -1685,12 +1689,12 @@ func (r *queryResolver) UserProfile(ctx context.Context, userID string) (*model.
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
user, err := r.App.User.Queries.User(ctx, uint(uID))
|
||||
user, err := r.App.User.Queries.User(ctx, uID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if user == nil {
|
||||
return nil, fmt.Errorf("user not found for profile %d", profile.ID)
|
||||
return nil, fmt.Errorf("user not found for profile %s", profile.ID.String())
|
||||
}
|
||||
|
||||
return &model.UserProfile{
|
||||
@ -1705,7 +1709,7 @@ func (r *queryResolver) UserProfile(ctx context.Context, userID string) (*model.
|
||||
DisplayName: &user.DisplayName,
|
||||
Bio: &user.Bio,
|
||||
AvatarURL: &user.AvatarURL,
|
||||
Role: model.UserRole(user.Role),
|
||||
Role: toModelUserRole(user.Role),
|
||||
Verified: user.Verified,
|
||||
Active: user.Active,
|
||||
},
|
||||
@ -1720,12 +1724,12 @@ func (r *queryResolver) UserProfile(ctx context.Context, userID string) (*model.
|
||||
|
||||
// Collection is the resolver for the collection field.
|
||||
func (r *queryResolver) Collection(ctx context.Context, id string) (*model.Collection, error) {
|
||||
collID, err := strconv.ParseUint(id, 10, 32)
|
||||
collID, err := uuid.Parse(id)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: invalid collection ID", domain.ErrValidation)
|
||||
}
|
||||
|
||||
collectionRecord, err := r.App.Collection.Queries.Collection(ctx, uint(collID))
|
||||
collectionRecord, err := r.App.Collection.Queries.Collection(ctx, collID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -1733,7 +1737,7 @@ func (r *queryResolver) Collection(ctx context.Context, id string) (*model.Colle
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
workRecords, err := r.App.Work.Queries.ListByCollectionID(ctx, uint(collID))
|
||||
workRecords, err := r.App.Work.Queries.ListByCollectionID(ctx, collID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -1766,11 +1770,11 @@ func (r *queryResolver) Collections(ctx context.Context, userID *string, limit *
|
||||
var err error
|
||||
|
||||
if userID != nil {
|
||||
uID, idErr := strconv.ParseUint(*userID, 10, 32)
|
||||
uID, idErr := uuid.Parse(*userID)
|
||||
if idErr != nil {
|
||||
return nil, fmt.Errorf("%w: invalid user ID", domain.ErrValidation)
|
||||
}
|
||||
collectionRecords, err = r.App.Collection.Queries.CollectionsByUserID(ctx, uint(uID))
|
||||
collectionRecords, err = r.App.Collection.Queries.CollectionsByUserID(ctx, uID)
|
||||
} else {
|
||||
collectionRecords, err = r.App.Collection.Queries.PublicCollections(ctx)
|
||||
}
|
||||
@ -1811,18 +1815,18 @@ func (r *queryResolver) Collections(ctx context.Context, userID *string, limit *
|
||||
|
||||
// Tag is the resolver for the tag field.
|
||||
func (r *queryResolver) Tag(ctx context.Context, id string) (*model.Tag, error) {
|
||||
tagID, err := strconv.ParseUint(id, 10, 32)
|
||||
tagID, err := uuid.Parse(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tag, err := r.App.Tag.Queries.Tag(ctx, uint(tagID))
|
||||
tag, err := r.App.Tag.Queries.Tag(ctx, tagID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &model.Tag{
|
||||
ID: fmt.Sprintf("%d", tag.ID),
|
||||
ID: tag.ID.String(),
|
||||
Name: tag.Name,
|
||||
}, nil
|
||||
}
|
||||
@ -1847,12 +1851,12 @@ func (r *queryResolver) Tags(ctx context.Context, limit *int32, offset *int32) (
|
||||
|
||||
// Category is the resolver for the category field.
|
||||
func (r *queryResolver) Category(ctx context.Context, id string) (*model.Category, error) {
|
||||
categoryID, err := strconv.ParseUint(id, 10, 32)
|
||||
categoryID, err := uuid.Parse(id)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid category ID: %v", err)
|
||||
}
|
||||
|
||||
category, err := r.App.Category.Queries.Category(ctx, uint(categoryID))
|
||||
category, err := r.App.Category.Queries.Category(ctx, categoryID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -1861,7 +1865,7 @@ func (r *queryResolver) Category(ctx context.Context, id string) (*model.Categor
|
||||
}
|
||||
|
||||
return &model.Category{
|
||||
ID: fmt.Sprintf("%d", category.ID),
|
||||
ID: category.ID.String(),
|
||||
Name: category.Name,
|
||||
}, nil
|
||||
}
|
||||
@ -1886,12 +1890,12 @@ func (r *queryResolver) Categories(ctx context.Context, limit *int32, offset *in
|
||||
|
||||
// Comment is the resolver for the comment field.
|
||||
func (r *queryResolver) Comment(ctx context.Context, id string) (*model.Comment, error) {
|
||||
cID, err := strconv.ParseUint(id, 10, 32)
|
||||
cID, err := uuid.Parse(id)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: invalid comment ID", domain.ErrValidation)
|
||||
}
|
||||
|
||||
commentRecord, err := r.App.Comment.Queries.Comment(ctx, uint(cID))
|
||||
commentRecord, err := r.App.Comment.Queries.Comment(ctx, cID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -1900,10 +1904,10 @@ func (r *queryResolver) Comment(ctx context.Context, id string) (*model.Comment,
|
||||
}
|
||||
|
||||
return &model.Comment{
|
||||
ID: fmt.Sprintf("%d", commentRecord.ID),
|
||||
ID: commentRecord.ID.String(),
|
||||
Text: commentRecord.Text,
|
||||
User: &model.User{
|
||||
ID: fmt.Sprintf("%d", commentRecord.UserID),
|
||||
ID: commentRecord.UserID.String(),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
@ -1914,23 +1918,23 @@ func (r *queryResolver) Comments(ctx context.Context, workID *string, translatio
|
||||
var err error
|
||||
|
||||
if workID != nil {
|
||||
wID, idErr := strconv.ParseUint(*workID, 10, 32)
|
||||
wID, idErr := uuid.Parse(*workID)
|
||||
if idErr != nil {
|
||||
return nil, fmt.Errorf("%w: invalid work ID", domain.ErrValidation)
|
||||
}
|
||||
commentRecords, err = r.App.Comment.Queries.CommentsByWorkID(ctx, uint(wID))
|
||||
commentRecords, err = r.App.Comment.Queries.CommentsByWorkID(ctx, wID)
|
||||
} else if translationID != nil {
|
||||
tID, idErr := strconv.ParseUint(*translationID, 10, 32)
|
||||
tID, idErr := uuid.Parse(*translationID)
|
||||
if idErr != nil {
|
||||
return nil, fmt.Errorf("%w: invalid translation ID", domain.ErrValidation)
|
||||
}
|
||||
commentRecords, err = r.App.Comment.Queries.CommentsByTranslationID(ctx, uint(tID))
|
||||
commentRecords, err = r.App.Comment.Queries.CommentsByTranslationID(ctx, tID)
|
||||
} else if userID != nil {
|
||||
uID, idErr := strconv.ParseUint(*userID, 10, 32)
|
||||
uID, idErr := uuid.Parse(*userID)
|
||||
if idErr != nil {
|
||||
return nil, fmt.Errorf("%w: invalid user ID", domain.ErrValidation)
|
||||
}
|
||||
commentRecords, err = r.App.Comment.Queries.CommentsByUserID(ctx, uint(uID))
|
||||
commentRecords, err = r.App.Comment.Queries.CommentsByUserID(ctx, uID)
|
||||
} else {
|
||||
commentRecords, err = r.App.Comment.Queries.Comments(ctx)
|
||||
}
|
||||
@ -2003,7 +2007,7 @@ func (r *queryResolver) Search(ctx context.Context, query string, limit *int32,
|
||||
}
|
||||
|
||||
params := domainsearch.SearchParams{
|
||||
Query: query,
|
||||
Query: query,
|
||||
Filters: domainsearch.SearchFilters{
|
||||
Languages: searchFilters.Languages,
|
||||
Tags: searchFilters.Tags,
|
||||
|
||||
@ -3,13 +3,13 @@ package graphql
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
"tercul/internal/adapters/graphql/model"
|
||||
"tercul/internal/app"
|
||||
"tercul/internal/app/authz"
|
||||
"tercul/internal/app/user"
|
||||
"tercul/internal/domain"
|
||||
"tercul/internal/adapters/graphql/model"
|
||||
platform_auth "tercul/internal/platform/auth"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/suite"
|
||||
@ -20,7 +20,7 @@ import (
|
||||
type mockUserRepositoryForUserResolver struct{ mock.Mock }
|
||||
|
||||
// Implement domain.UserRepository
|
||||
func (m *mockUserRepositoryForUserResolver) GetByID(ctx context.Context, id uint) (*domain.User, error) {
|
||||
func (m *mockUserRepositoryForUserResolver) GetByID(ctx context.Context, id uuid.UUID) (*domain.User, error) {
|
||||
args := m.Called(ctx, id)
|
||||
if args.Get(0) == nil {
|
||||
return nil, args.Error(1)
|
||||
@ -106,7 +106,7 @@ func (m *mockUserRepositoryForUserResolver) WithTx(ctx context.Context, fn func(
|
||||
type mockUserProfileRepository struct{ mock.Mock }
|
||||
|
||||
// Implement domain.UserProfileRepository
|
||||
func (m *mockUserProfileRepository) GetByUserID(ctx context.Context, userID uint) (*domain.UserProfile, error) {
|
||||
func (m *mockUserProfileRepository) GetByUserID(ctx context.Context, userID uuid.UUID) (*domain.UserProfile, error) {
|
||||
args := m.Called(ctx, userID)
|
||||
if args.Get(0) == nil {
|
||||
return nil, args.Error(1)
|
||||
@ -121,7 +121,7 @@ func (m *mockUserProfileRepository) Create(ctx context.Context, entity *domain.U
|
||||
func (m *mockUserProfileRepository) CreateInTx(ctx context.Context, tx *gorm.DB, entity *domain.UserProfile) error {
|
||||
return nil
|
||||
}
|
||||
func (m *mockUserProfileRepository) GetByID(ctx context.Context, id uint) (*domain.UserProfile, error) {
|
||||
func (m *mockUserProfileRepository) GetByID(ctx context.Context, id uuid.UUID) (*domain.UserProfile, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (m *mockUserProfileRepository) GetByIDWithOptions(ctx context.Context, id uint, options *domain.QueryOptions) (*domain.UserProfile, error) {
|
||||
@ -634,4 +634,4 @@ func (s *UserResolversUnitSuite) TestDeleteUserMutation() {
|
||||
_, err := s.resolver.Mutation().DeleteUser(ctx, "invalid")
|
||||
s.Require().Error(err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -30,4 +30,4 @@ func Validate(s interface{}) error {
|
||||
|
||||
// For other unexpected errors, like invalid validation input.
|
||||
return fmt.Errorf("unexpected error during validation: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
@ -20,7 +20,7 @@ func (m *mockWorkRepository) Create(ctx context.Context, entity *domain.Work) er
|
||||
func (m *mockWorkRepository) CreateInTx(ctx context.Context, tx *gorm.DB, entity *domain.Work) error {
|
||||
return m.Create(ctx, entity)
|
||||
}
|
||||
func (m *mockWorkRepository) GetByID(ctx context.Context, id uint) (*domain.Work, error) {
|
||||
func (m *mockWorkRepository) GetByID(ctx context.Context, id uuid.UUID) (*domain.Work, error) {
|
||||
args := m.Called(ctx, id)
|
||||
if args.Get(0) == nil {
|
||||
return nil, args.Error(1)
|
||||
@ -153,4 +153,4 @@ func (m *mockWorkRepository) ListByCollectionID(ctx context.Context, collectionI
|
||||
return nil, args.Error(1)
|
||||
}
|
||||
return args.Get(0).([]domain.Work), args.Error(1)
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,7 +2,6 @@ package graphql
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"tercul/internal/adapters/graphql/model"
|
||||
"tercul/internal/app"
|
||||
"tercul/internal/app/authz"
|
||||
@ -11,6 +10,7 @@ import (
|
||||
"tercul/internal/domain"
|
||||
domainsearch "tercul/internal/domain/search"
|
||||
platform_auth "tercul/internal/platform/auth"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/mock"
|
||||
@ -26,7 +26,7 @@ func (m *mockWorkRepository) Create(ctx context.Context, work *domain.Work) erro
|
||||
work.ID = 1
|
||||
return args.Error(0)
|
||||
}
|
||||
func (m *mockWorkRepository) GetByID(ctx context.Context, id uint) (*domain.Work, error) {
|
||||
func (m *mockWorkRepository) GetByID(ctx context.Context, id uuid.UUID) (*domain.Work, error) {
|
||||
args := m.Called(ctx, id)
|
||||
if args.Get(0) == nil {
|
||||
return nil, args.Error(1)
|
||||
@ -52,10 +52,18 @@ func (m *mockWorkRepository) List(ctx context.Context, page, pageSize int) (*dom
|
||||
}
|
||||
return args.Get(0).(*domain.PaginatedResult[domain.Work]), args.Error(1)
|
||||
}
|
||||
func (m *mockWorkRepository) FindByTitle(ctx context.Context, title string) ([]domain.Work, error) { return nil, nil }
|
||||
func (m *mockWorkRepository) FindByAuthor(ctx context.Context, authorID uint) ([]domain.Work, error) { return nil, nil }
|
||||
func (m *mockWorkRepository) FindByCategory(ctx context.Context, categoryID uint) ([]domain.Work, error) { return nil, nil }
|
||||
func (m *mockWorkRepository) FindByLanguage(ctx context.Context, language string, page, pageSize int) (*domain.PaginatedResult[domain.Work], error) { return nil, nil }
|
||||
func (m *mockWorkRepository) FindByTitle(ctx context.Context, title string) ([]domain.Work, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (m *mockWorkRepository) FindByAuthor(ctx context.Context, authorID uint) ([]domain.Work, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (m *mockWorkRepository) FindByCategory(ctx context.Context, categoryID uint) ([]domain.Work, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (m *mockWorkRepository) FindByLanguage(ctx context.Context, language string, page, pageSize int) (*domain.PaginatedResult[domain.Work], error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (m *mockWorkRepository) GetWithTranslations(ctx context.Context, id uint) (*domain.Work, error) {
|
||||
args := m.Called(ctx, id)
|
||||
if args.Get(0) == nil {
|
||||
@ -63,24 +71,47 @@ func (m *mockWorkRepository) GetWithTranslations(ctx context.Context, id uint) (
|
||||
}
|
||||
return args.Get(0).(*domain.Work), args.Error(1)
|
||||
}
|
||||
func (m *mockWorkRepository) GetWithAssociations(ctx context.Context, id uint) (*domain.Work, error) { return nil, nil }
|
||||
func (m *mockWorkRepository) GetWithAssociationsInTx(ctx context.Context, tx *gorm.DB, id uint) (*domain.Work, error) { return nil, nil }
|
||||
func (m *mockWorkRepository) ListWithTranslations(ctx context.Context, page, pageSize int) (*domain.PaginatedResult[domain.Work], error) { return nil, nil }
|
||||
func (m *mockWorkRepository) ListByCollectionID(ctx context.Context, collectionID uint) ([]domain.Work, error) { return nil, nil }
|
||||
func (m *mockWorkRepository) CreateInTx(ctx context.Context, tx *gorm.DB, entity *domain.Work) error { return nil }
|
||||
func (m *mockWorkRepository) GetByIDWithOptions(ctx context.Context, id uint, options *domain.QueryOptions) (*domain.Work, error) { return nil, nil }
|
||||
func (m *mockWorkRepository) UpdateInTx(ctx context.Context, tx *gorm.DB, entity *domain.Work) error { return nil }
|
||||
func (m *mockWorkRepository) GetWithAssociations(ctx context.Context, id uint) (*domain.Work, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (m *mockWorkRepository) GetWithAssociationsInTx(ctx context.Context, tx *gorm.DB, id uint) (*domain.Work, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (m *mockWorkRepository) ListWithTranslations(ctx context.Context, page, pageSize int) (*domain.PaginatedResult[domain.Work], error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (m *mockWorkRepository) ListByCollectionID(ctx context.Context, collectionID uint) ([]domain.Work, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (m *mockWorkRepository) CreateInTx(ctx context.Context, tx *gorm.DB, entity *domain.Work) error {
|
||||
return nil
|
||||
}
|
||||
func (m *mockWorkRepository) GetByIDWithOptions(ctx context.Context, id uint, options *domain.QueryOptions) (*domain.Work, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (m *mockWorkRepository) UpdateInTx(ctx context.Context, tx *gorm.DB, entity *domain.Work) error {
|
||||
return nil
|
||||
}
|
||||
func (m *mockWorkRepository) DeleteInTx(ctx context.Context, tx *gorm.DB, id uint) error { return nil }
|
||||
func (m *mockWorkRepository) ListWithOptions(ctx context.Context, options *domain.QueryOptions) ([]domain.Work, error) { return nil, nil }
|
||||
func (m *mockWorkRepository) ListWithOptions(ctx context.Context, options *domain.QueryOptions) ([]domain.Work, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (m *mockWorkRepository) ListAll(ctx context.Context) ([]domain.Work, error) { return nil, nil }
|
||||
func (m *mockWorkRepository) Count(ctx context.Context) (int64, error) { return 0, nil }
|
||||
func (m *mockWorkRepository) CountWithOptions(ctx context.Context, options *domain.QueryOptions) (int64, error) { return 0, nil }
|
||||
func (m *mockWorkRepository) FindWithPreload(ctx context.Context, preloads []string, id uint) (*domain.Work, error) { return nil, nil }
|
||||
func (m *mockWorkRepository) GetAllForSync(ctx context.Context, batchSize, offset int) ([]domain.Work, error) { return nil, nil }
|
||||
func (m *mockWorkRepository) Count(ctx context.Context) (int64, error) { return 0, nil }
|
||||
func (m *mockWorkRepository) CountWithOptions(ctx context.Context, options *domain.QueryOptions) (int64, error) {
|
||||
return 0, nil
|
||||
}
|
||||
func (m *mockWorkRepository) FindWithPreload(ctx context.Context, preloads []string, id uint) (*domain.Work, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (m *mockWorkRepository) GetAllForSync(ctx context.Context, batchSize, offset int) ([]domain.Work, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (m *mockWorkRepository) Exists(ctx context.Context, id uint) (bool, error) { return false, nil }
|
||||
func (m *mockWorkRepository) BeginTx(ctx context.Context) (*gorm.DB, error) { return nil, nil }
|
||||
func (m *mockWorkRepository) WithTx(ctx context.Context, fn func(tx *gorm.DB) error) error { return nil }
|
||||
|
||||
func (m *mockWorkRepository) BeginTx(ctx context.Context) (*gorm.DB, error) { return nil, nil }
|
||||
func (m *mockWorkRepository) WithTx(ctx context.Context, fn func(tx *gorm.DB) error) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type mockAuthorRepository struct{ mock.Mock }
|
||||
|
||||
@ -96,57 +127,111 @@ func (m *mockAuthorRepository) Create(ctx context.Context, author *domain.Author
|
||||
author.ID = 1
|
||||
return args.Error(0)
|
||||
}
|
||||
func (m *mockAuthorRepository) GetByID(ctx context.Context, id uint) (*domain.Author, error) { return nil, nil }
|
||||
func (m *mockAuthorRepository) ListByWorkID(ctx context.Context, workID uint) ([]domain.Author, error) { return nil, nil }
|
||||
func (m *mockAuthorRepository) ListByBookID(ctx context.Context, bookID uint) ([]domain.Author, error) { return nil, nil }
|
||||
func (m *mockAuthorRepository) ListByCountryID(ctx context.Context, countryID uint) ([]domain.Author, error) { return nil, nil }
|
||||
func (m *mockAuthorRepository) GetWithTranslations(ctx context.Context, id uint) (*domain.Author, error) { return nil, nil }
|
||||
func (m *mockAuthorRepository) CreateInTx(ctx context.Context, tx *gorm.DB, entity *domain.Author) error { return nil }
|
||||
func (m *mockAuthorRepository) GetByIDWithOptions(ctx context.Context, id uint, options *domain.QueryOptions) (*domain.Author, error) { return nil, nil }
|
||||
func (m *mockAuthorRepository) GetByID(ctx context.Context, id uuid.UUID) (*domain.Author, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (m *mockAuthorRepository) ListByWorkID(ctx context.Context, workID uint) ([]domain.Author, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (m *mockAuthorRepository) ListByBookID(ctx context.Context, bookID uint) ([]domain.Author, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (m *mockAuthorRepository) ListByCountryID(ctx context.Context, countryID uint) ([]domain.Author, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (m *mockAuthorRepository) GetWithTranslations(ctx context.Context, id uint) (*domain.Author, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (m *mockAuthorRepository) CreateInTx(ctx context.Context, tx *gorm.DB, entity *domain.Author) error {
|
||||
return nil
|
||||
}
|
||||
func (m *mockAuthorRepository) GetByIDWithOptions(ctx context.Context, id uint, options *domain.QueryOptions) (*domain.Author, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (m *mockAuthorRepository) Update(ctx context.Context, entity *domain.Author) error { return nil }
|
||||
func (m *mockAuthorRepository) UpdateInTx(ctx context.Context, tx *gorm.DB, entity *domain.Author) error { return nil }
|
||||
func (m *mockAuthorRepository) UpdateInTx(ctx context.Context, tx *gorm.DB, entity *domain.Author) error {
|
||||
return nil
|
||||
}
|
||||
func (m *mockAuthorRepository) Delete(ctx context.Context, id uint) error { return nil }
|
||||
func (m *mockAuthorRepository) DeleteInTx(ctx context.Context, tx *gorm.DB, id uint) error { return nil }
|
||||
func (m *mockAuthorRepository) List(ctx context.Context, page, pageSize int) (*domain.PaginatedResult[domain.Author], error) { return nil, nil }
|
||||
func (m *mockAuthorRepository) ListWithOptions(ctx context.Context, options *domain.QueryOptions) ([]domain.Author, error) { return nil, nil }
|
||||
func (m *mockAuthorRepository) DeleteInTx(ctx context.Context, tx *gorm.DB, id uint) error {
|
||||
return nil
|
||||
}
|
||||
func (m *mockAuthorRepository) List(ctx context.Context, page, pageSize int) (*domain.PaginatedResult[domain.Author], error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (m *mockAuthorRepository) ListWithOptions(ctx context.Context, options *domain.QueryOptions) ([]domain.Author, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (m *mockAuthorRepository) ListAll(ctx context.Context) ([]domain.Author, error) { return nil, nil }
|
||||
func (m *mockAuthorRepository) Count(ctx context.Context) (int64, error) { return 0, nil }
|
||||
func (m *mockAuthorRepository) CountWithOptions(ctx context.Context, options *domain.QueryOptions) (int64, error) { return 0, nil }
|
||||
func (m *mockAuthorRepository) FindWithPreload(ctx context.Context, preloads []string, id uint) (*domain.Author, error) { return nil, nil }
|
||||
func (m *mockAuthorRepository) GetAllForSync(ctx context.Context, batchSize, offset int) ([]domain.Author, error) { return nil, nil }
|
||||
func (m *mockAuthorRepository) Count(ctx context.Context) (int64, error) { return 0, nil }
|
||||
func (m *mockAuthorRepository) CountWithOptions(ctx context.Context, options *domain.QueryOptions) (int64, error) {
|
||||
return 0, nil
|
||||
}
|
||||
func (m *mockAuthorRepository) FindWithPreload(ctx context.Context, preloads []string, id uint) (*domain.Author, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (m *mockAuthorRepository) GetAllForSync(ctx context.Context, batchSize, offset int) ([]domain.Author, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (m *mockAuthorRepository) Exists(ctx context.Context, id uint) (bool, error) { return false, nil }
|
||||
func (m *mockAuthorRepository) BeginTx(ctx context.Context) (*gorm.DB, error) { return nil, nil }
|
||||
func (m *mockAuthorRepository) WithTx(ctx context.Context, fn func(tx *gorm.DB) error) error { return nil }
|
||||
func (m *mockAuthorRepository) BeginTx(ctx context.Context) (*gorm.DB, error) { return nil, nil }
|
||||
func (m *mockAuthorRepository) WithTx(ctx context.Context, fn func(tx *gorm.DB) error) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type mockUserRepository struct{ mock.Mock }
|
||||
|
||||
func (m *mockUserRepository) GetByID(ctx context.Context, id uint) (*domain.User, error) {
|
||||
func (m *mockUserRepository) GetByID(ctx context.Context, id uuid.UUID) (*domain.User, error) {
|
||||
args := m.Called(ctx, id)
|
||||
if args.Get(0) == nil {
|
||||
return nil, args.Error(1)
|
||||
}
|
||||
return args.Get(0).(*domain.User), args.Error(1)
|
||||
}
|
||||
func (m *mockUserRepository) FindByUsername(ctx context.Context, username string) (*domain.User, error) { return nil, nil }
|
||||
func (m *mockUserRepository) FindByEmail(ctx context.Context, email string) (*domain.User, error) { return nil, nil }
|
||||
func (m *mockUserRepository) ListByRole(ctx context.Context, role domain.UserRole) ([]domain.User, error) { return nil, nil }
|
||||
func (m *mockUserRepository) FindByUsername(ctx context.Context, username string) (*domain.User, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (m *mockUserRepository) FindByEmail(ctx context.Context, email string) (*domain.User, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (m *mockUserRepository) ListByRole(ctx context.Context, role domain.UserRole) ([]domain.User, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (m *mockUserRepository) Create(ctx context.Context, entity *domain.User) error { return nil }
|
||||
func (m *mockUserRepository) CreateInTx(ctx context.Context, tx *gorm.DB, entity *domain.User) error { return nil }
|
||||
func (m *mockUserRepository) GetByIDWithOptions(ctx context.Context, id uint, options *domain.QueryOptions) (*domain.User, error) { return nil, nil }
|
||||
func (m *mockUserRepository) CreateInTx(ctx context.Context, tx *gorm.DB, entity *domain.User) error {
|
||||
return nil
|
||||
}
|
||||
func (m *mockUserRepository) GetByIDWithOptions(ctx context.Context, id uint, options *domain.QueryOptions) (*domain.User, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (m *mockUserRepository) Update(ctx context.Context, entity *domain.User) error { return nil }
|
||||
func (m *mockUserRepository) UpdateInTx(ctx context.Context, tx *gorm.DB, entity *domain.User) error { return nil }
|
||||
func (m *mockUserRepository) Delete(ctx context.Context, id uint) error { return nil }
|
||||
func (m *mockUserRepository) UpdateInTx(ctx context.Context, tx *gorm.DB, entity *domain.User) error {
|
||||
return nil
|
||||
}
|
||||
func (m *mockUserRepository) Delete(ctx context.Context, id uint) error { return nil }
|
||||
func (m *mockUserRepository) DeleteInTx(ctx context.Context, tx *gorm.DB, id uint) error { return nil }
|
||||
func (m *mockUserRepository) List(ctx context.Context, page, pageSize int) (*domain.PaginatedResult[domain.User], error) { return nil, nil }
|
||||
func (m *mockUserRepository) ListWithOptions(ctx context.Context, options *domain.QueryOptions) ([]domain.User, error) { return nil, nil }
|
||||
func (m *mockUserRepository) List(ctx context.Context, page, pageSize int) (*domain.PaginatedResult[domain.User], error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (m *mockUserRepository) ListWithOptions(ctx context.Context, options *domain.QueryOptions) ([]domain.User, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (m *mockUserRepository) ListAll(ctx context.Context) ([]domain.User, error) { return nil, nil }
|
||||
func (m *mockUserRepository) Count(ctx context.Context) (int64, error) { return 0, nil }
|
||||
func (m *mockUserRepository) CountWithOptions(ctx context.Context, options *domain.QueryOptions) (int64, error) { return 0, nil }
|
||||
func (m *mockUserRepository) FindWithPreload(ctx context.Context, preloads []string, id uint) (*domain.User, error) { return nil, nil }
|
||||
func (m *mockUserRepository) GetAllForSync(ctx context.Context, batchSize, offset int) ([]domain.User, error) { return nil, nil }
|
||||
func (m *mockUserRepository) Count(ctx context.Context) (int64, error) { return 0, nil }
|
||||
func (m *mockUserRepository) CountWithOptions(ctx context.Context, options *domain.QueryOptions) (int64, error) {
|
||||
return 0, nil
|
||||
}
|
||||
func (m *mockUserRepository) FindWithPreload(ctx context.Context, preloads []string, id uint) (*domain.User, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (m *mockUserRepository) GetAllForSync(ctx context.Context, batchSize, offset int) ([]domain.User, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (m *mockUserRepository) Exists(ctx context.Context, id uint) (bool, error) { return false, nil }
|
||||
func (m *mockUserRepository) BeginTx(ctx context.Context) (*gorm.DB, error) { return nil, nil }
|
||||
func (m *mockUserRepository) WithTx(ctx context.Context, fn func(tx *gorm.DB) error) error { return nil }
|
||||
func (m *mockUserRepository) BeginTx(ctx context.Context) (*gorm.DB, error) { return nil, nil }
|
||||
func (m *mockUserRepository) WithTx(ctx context.Context, fn func(tx *gorm.DB) error) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type mockSearchClient struct{ mock.Mock }
|
||||
|
||||
@ -162,7 +247,6 @@ func (m *mockSearchClient) Search(ctx context.Context, params domainsearch.Searc
|
||||
return args.Get(0).(*domainsearch.SearchResults), args.Error(1)
|
||||
}
|
||||
|
||||
|
||||
type mockAnalyticsService struct{ mock.Mock }
|
||||
|
||||
func (m *mockAnalyticsService) IncrementWorkTranslationCount(ctx context.Context, workID uint) error {
|
||||
@ -174,26 +258,62 @@ func (m *mockAnalyticsService) IncrementWorkViews(ctx context.Context, workID ui
|
||||
return args.Error(0)
|
||||
}
|
||||
func (m *mockAnalyticsService) IncrementWorkLikes(ctx context.Context, workID uint) error { return nil }
|
||||
func (m *mockAnalyticsService) IncrementWorkComments(ctx context.Context, workID uint) error { return nil }
|
||||
func (m *mockAnalyticsService) IncrementWorkBookmarks(ctx context.Context, workID uint) error { return nil }
|
||||
func (m *mockAnalyticsService) IncrementWorkShares(ctx context.Context, workID uint) error { return nil }
|
||||
func (m *mockAnalyticsService) IncrementTranslationViews(ctx context.Context, translationID uint) error { return nil }
|
||||
func (m *mockAnalyticsService) IncrementTranslationLikes(ctx context.Context, translationID uint) error { return nil }
|
||||
func (m *mockAnalyticsService) IncrementWorkComments(ctx context.Context, workID uint) error {
|
||||
return nil
|
||||
}
|
||||
func (m *mockAnalyticsService) IncrementWorkBookmarks(ctx context.Context, workID uint) error {
|
||||
return nil
|
||||
}
|
||||
func (m *mockAnalyticsService) IncrementWorkShares(ctx context.Context, workID uint) error {
|
||||
return nil
|
||||
}
|
||||
func (m *mockAnalyticsService) IncrementTranslationViews(ctx context.Context, translationID uint) error {
|
||||
return nil
|
||||
}
|
||||
func (m *mockAnalyticsService) IncrementTranslationLikes(ctx context.Context, translationID uint) error {
|
||||
return nil
|
||||
}
|
||||
func (m *mockAnalyticsService) DecrementWorkLikes(ctx context.Context, workID uint) error { return nil }
|
||||
func (m *mockAnalyticsService) DecrementTranslationLikes(ctx context.Context, translationID uint) error { return nil }
|
||||
func (m *mockAnalyticsService) IncrementTranslationComments(ctx context.Context, translationID uint) error { return nil }
|
||||
func (m *mockAnalyticsService) IncrementTranslationShares(ctx context.Context, translationID uint) error { return nil }
|
||||
func (m *mockAnalyticsService) GetOrCreateWorkStats(ctx context.Context, workID uint) (*domain.WorkStats, error) { return nil, nil }
|
||||
func (m *mockAnalyticsService) GetOrCreateTranslationStats(ctx context.Context, translationID uint) (*domain.TranslationStats, error) { return nil, nil }
|
||||
func (m *mockAnalyticsService) UpdateWorkReadingTime(ctx context.Context, workID uint) error { return nil }
|
||||
func (m *mockAnalyticsService) UpdateWorkComplexity(ctx context.Context, workID uint) error { return nil }
|
||||
func (m *mockAnalyticsService) UpdateWorkSentiment(ctx context.Context, workID uint) error { return nil }
|
||||
func (m *mockAnalyticsService) UpdateTranslationReadingTime(ctx context.Context, translationID uint) error { return nil }
|
||||
func (m *mockAnalyticsService) UpdateTranslationSentiment(ctx context.Context, translationID uint) error { return nil }
|
||||
func (m *mockAnalyticsService) UpdateUserEngagement(ctx context.Context, userID uint, eventType string) error { return nil }
|
||||
func (m *mockAnalyticsService) DecrementTranslationLikes(ctx context.Context, translationID uint) error {
|
||||
return nil
|
||||
}
|
||||
func (m *mockAnalyticsService) IncrementTranslationComments(ctx context.Context, translationID uint) error {
|
||||
return nil
|
||||
}
|
||||
func (m *mockAnalyticsService) IncrementTranslationShares(ctx context.Context, translationID uint) error {
|
||||
return nil
|
||||
}
|
||||
func (m *mockAnalyticsService) GetOrCreateWorkStats(ctx context.Context, workID uint) (*domain.WorkStats, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (m *mockAnalyticsService) GetOrCreateTranslationStats(ctx context.Context, translationID uint) (*domain.TranslationStats, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (m *mockAnalyticsService) UpdateWorkReadingTime(ctx context.Context, workID uint) error {
|
||||
return nil
|
||||
}
|
||||
func (m *mockAnalyticsService) UpdateWorkComplexity(ctx context.Context, workID uint) error {
|
||||
return nil
|
||||
}
|
||||
func (m *mockAnalyticsService) UpdateWorkSentiment(ctx context.Context, workID uint) error {
|
||||
return nil
|
||||
}
|
||||
func (m *mockAnalyticsService) UpdateTranslationReadingTime(ctx context.Context, translationID uint) error {
|
||||
return nil
|
||||
}
|
||||
func (m *mockAnalyticsService) UpdateTranslationSentiment(ctx context.Context, translationID uint) error {
|
||||
return nil
|
||||
}
|
||||
func (m *mockAnalyticsService) UpdateUserEngagement(ctx context.Context, userID uint, eventType string) error {
|
||||
return nil
|
||||
}
|
||||
func (m *mockAnalyticsService) UpdateTrending(ctx context.Context) error { return nil }
|
||||
func (m *mockAnalyticsService) GetTrendingWorks(ctx context.Context, timePeriod string, limit int) ([]*domain.Work, error) { return nil, nil }
|
||||
func (m *mockAnalyticsService) UpdateWorkStats(ctx context.Context, workID uint, stats domain.WorkStats) error { return nil }
|
||||
func (m *mockAnalyticsService) GetTrendingWorks(ctx context.Context, timePeriod string, limit int) ([]*domain.Work, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (m *mockAnalyticsService) UpdateWorkStats(ctx context.Context, workID uint, stats domain.WorkStats) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type mockTranslationRepository struct{ mock.Mock }
|
||||
|
||||
@ -201,41 +321,80 @@ func (m *mockTranslationRepository) Upsert(ctx context.Context, translation *dom
|
||||
args := m.Called(ctx, translation)
|
||||
return args.Error(0)
|
||||
}
|
||||
func (m *mockTranslationRepository) GetByID(ctx context.Context, id uint) (*domain.Translation, error) { return nil, nil }
|
||||
func (m *mockTranslationRepository) ListByWorkID(ctx context.Context, workID uint) ([]domain.Translation, error) { return nil, nil }
|
||||
func (m *mockTranslationRepository) ListByWorkIDPaginated(ctx context.Context, workID uint, language *string, page, pageSize int) (*domain.PaginatedResult[domain.Translation], error) { return nil, nil }
|
||||
func (m *mockTranslationRepository) ListByEntity(ctx context.Context, entityType string, entityID uint) ([]domain.Translation, error) { return nil, nil }
|
||||
func (m *mockTranslationRepository) ListByTranslatorID(ctx context.Context, translatorID uint) ([]domain.Translation, error) { return nil, nil }
|
||||
func (m *mockTranslationRepository) ListByStatus(ctx context.Context, status domain.TranslationStatus) ([]domain.Translation, error) { return nil, nil }
|
||||
func (m *mockTranslationRepository) Create(ctx context.Context, entity *domain.Translation) error { return nil }
|
||||
func (m *mockTranslationRepository) CreateInTx(ctx context.Context, tx *gorm.DB, entity *domain.Translation) error { return nil }
|
||||
func (m *mockTranslationRepository) GetByIDWithOptions(ctx context.Context, id uint, options *domain.QueryOptions) (*domain.Translation, error) { return nil, nil }
|
||||
func (m *mockTranslationRepository) Update(ctx context.Context, entity *domain.Translation) error { return nil }
|
||||
func (m *mockTranslationRepository) UpdateInTx(ctx context.Context, tx *gorm.DB, entity *domain.Translation) error { return nil }
|
||||
func (m *mockTranslationRepository) GetByID(ctx context.Context, id uuid.UUID) (*domain.Translation, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (m *mockTranslationRepository) ListByWorkID(ctx context.Context, workID uint) ([]domain.Translation, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (m *mockTranslationRepository) ListByWorkIDPaginated(ctx context.Context, workID uint, language *string, page, pageSize int) (*domain.PaginatedResult[domain.Translation], error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (m *mockTranslationRepository) ListByEntity(ctx context.Context, entityType string, entityID uint) ([]domain.Translation, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (m *mockTranslationRepository) ListByTranslatorID(ctx context.Context, translatorID uint) ([]domain.Translation, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (m *mockTranslationRepository) ListByStatus(ctx context.Context, status domain.TranslationStatus) ([]domain.Translation, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (m *mockTranslationRepository) Create(ctx context.Context, entity *domain.Translation) error {
|
||||
return nil
|
||||
}
|
||||
func (m *mockTranslationRepository) CreateInTx(ctx context.Context, tx *gorm.DB, entity *domain.Translation) error {
|
||||
return nil
|
||||
}
|
||||
func (m *mockTranslationRepository) GetByIDWithOptions(ctx context.Context, id uint, options *domain.QueryOptions) (*domain.Translation, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (m *mockTranslationRepository) Update(ctx context.Context, entity *domain.Translation) error {
|
||||
return nil
|
||||
}
|
||||
func (m *mockTranslationRepository) UpdateInTx(ctx context.Context, tx *gorm.DB, entity *domain.Translation) error {
|
||||
return nil
|
||||
}
|
||||
func (m *mockTranslationRepository) Delete(ctx context.Context, id uint) error { return nil }
|
||||
func (m *mockTranslationRepository) DeleteInTx(ctx context.Context, tx *gorm.DB, id uint) error { return nil }
|
||||
func (m *mockTranslationRepository) List(ctx context.Context, page, pageSize int) (*domain.PaginatedResult[domain.Translation], error) { return nil, nil }
|
||||
func (m *mockTranslationRepository) ListWithOptions(ctx context.Context, options *domain.QueryOptions) ([]domain.Translation, error) { return nil, nil }
|
||||
func (m *mockTranslationRepository) ListAll(ctx context.Context) ([]domain.Translation, error) { return nil, nil }
|
||||
func (m *mockTranslationRepository) DeleteInTx(ctx context.Context, tx *gorm.DB, id uint) error {
|
||||
return nil
|
||||
}
|
||||
func (m *mockTranslationRepository) List(ctx context.Context, page, pageSize int) (*domain.PaginatedResult[domain.Translation], error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (m *mockTranslationRepository) ListWithOptions(ctx context.Context, options *domain.QueryOptions) ([]domain.Translation, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (m *mockTranslationRepository) ListAll(ctx context.Context) ([]domain.Translation, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (m *mockTranslationRepository) Count(ctx context.Context) (int64, error) { return 0, nil }
|
||||
func (m *mockTranslationRepository) CountWithOptions(ctx context.Context, options *domain.QueryOptions) (int64, error) { return 0, nil }
|
||||
func (m *mockTranslationRepository) FindWithPreload(ctx context.Context, preloads []string, id uint) (*domain.Translation, error) { return nil, nil }
|
||||
func (m *mockTranslationRepository) GetAllForSync(ctx context.Context, batchSize, offset int) ([]domain.Translation, error) { return nil, nil }
|
||||
func (m *mockTranslationRepository) Exists(ctx context.Context, id uint) (bool, error) { return false, nil }
|
||||
func (m *mockTranslationRepository) CountWithOptions(ctx context.Context, options *domain.QueryOptions) (int64, error) {
|
||||
return 0, nil
|
||||
}
|
||||
func (m *mockTranslationRepository) FindWithPreload(ctx context.Context, preloads []string, id uint) (*domain.Translation, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (m *mockTranslationRepository) GetAllForSync(ctx context.Context, batchSize, offset int) ([]domain.Translation, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (m *mockTranslationRepository) Exists(ctx context.Context, id uint) (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
func (m *mockTranslationRepository) BeginTx(ctx context.Context) (*gorm.DB, error) { return nil, nil }
|
||||
func (m *mockTranslationRepository) WithTx(ctx context.Context, fn func(tx *gorm.DB) error) error { return nil }
|
||||
|
||||
func (m *mockTranslationRepository) WithTx(ctx context.Context, fn func(tx *gorm.DB) error) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// WorkResolversUnitSuite is a unit test suite for the work resolvers.
|
||||
type WorkResolversUnitSuite struct {
|
||||
suite.Suite
|
||||
resolver *Resolver
|
||||
mockWorkRepo *mockWorkRepository
|
||||
mockAuthorRepo *mockAuthorRepository
|
||||
mockUserRepo *mockUserRepository
|
||||
mockTranslationRepo *mockTranslationRepository
|
||||
mockSearchClient *mockSearchClient
|
||||
mockAnalyticsSvc *mockAnalyticsService
|
||||
resolver *Resolver
|
||||
mockWorkRepo *mockWorkRepository
|
||||
mockAuthorRepo *mockAuthorRepository
|
||||
mockUserRepo *mockUserRepository
|
||||
mockTranslationRepo *mockTranslationRepository
|
||||
mockSearchClient *mockSearchClient
|
||||
mockAnalyticsSvc *mockAnalyticsService
|
||||
}
|
||||
|
||||
func (s *WorkResolversUnitSuite) SetupTest() {
|
||||
@ -453,4 +612,4 @@ func (s *WorkResolversUnitSuite) TestWorksQuery_Unit() {
|
||||
_, err := s.resolver.Query().Works(ctx, &limit, &offset, nil, nil, nil, nil, nil)
|
||||
s.Require().NoError(err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -1 +0,0 @@
|
||||
# This file is created to ensure the directory structure is in place.
|
||||
@ -4,18 +4,20 @@ import (
|
||||
"context"
|
||||
"tercul/internal/domain"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// AnalyticsRepository defines the data access layer for analytics.
|
||||
type Repository interface {
|
||||
IncrementWorkCounter(ctx context.Context, workID uint, field string, value int) error
|
||||
IncrementTranslationCounter(ctx context.Context, translationID uint, field string, value int) error
|
||||
UpdateWorkStats(ctx context.Context, workID uint, stats domain.WorkStats) error
|
||||
UpdateTranslationStats(ctx context.Context, translationID uint, stats domain.TranslationStats) error
|
||||
GetOrCreateWorkStats(ctx context.Context, workID uint) (*domain.WorkStats, error)
|
||||
GetOrCreateTranslationStats(ctx context.Context, translationID uint) (*domain.TranslationStats, error)
|
||||
GetOrCreateUserEngagement(ctx context.Context, userID uint, date time.Time) (*domain.UserEngagement, error)
|
||||
IncrementWorkCounter(ctx context.Context, workID uuid.UUID, field string, value int) error
|
||||
IncrementTranslationCounter(ctx context.Context, translationID uuid.UUID, field string, value int) error
|
||||
UpdateWorkStats(ctx context.Context, workID uuid.UUID, stats domain.WorkStats) error
|
||||
UpdateTranslationStats(ctx context.Context, translationID uuid.UUID, stats domain.TranslationStats) error
|
||||
GetOrCreateWorkStats(ctx context.Context, workID uuid.UUID) (*domain.WorkStats, error)
|
||||
GetOrCreateTranslationStats(ctx context.Context, translationID uuid.UUID) (*domain.TranslationStats, error)
|
||||
GetOrCreateUserEngagement(ctx context.Context, userID uuid.UUID, date time.Time) (*domain.UserEngagement, error)
|
||||
UpdateUserEngagement(ctx context.Context, userEngagement *domain.UserEngagement) error
|
||||
UpdateTrendingWorks(ctx context.Context, timePeriod string, trending []*domain.Trending) error
|
||||
GetTrendingWorks(ctx context.Context, timePeriod string, limit int) ([]*domain.Work, error)
|
||||
}
|
||||
}
|
||||
|
||||
@ -13,34 +13,36 @@ import (
|
||||
|
||||
"go.opentelemetry.io/otel"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type Service interface {
|
||||
IncrementWorkViews(ctx context.Context, workID uint) error
|
||||
IncrementWorkLikes(ctx context.Context, workID uint) error
|
||||
IncrementWorkComments(ctx context.Context, workID uint) error
|
||||
IncrementWorkBookmarks(ctx context.Context, workID uint) error
|
||||
IncrementWorkShares(ctx context.Context, workID uint) error
|
||||
IncrementWorkTranslationCount(ctx context.Context, workID uint) error
|
||||
IncrementTranslationViews(ctx context.Context, translationID uint) error
|
||||
IncrementTranslationLikes(ctx context.Context, translationID uint) error
|
||||
DecrementWorkLikes(ctx context.Context, workID uint) error
|
||||
DecrementTranslationLikes(ctx context.Context, translationID uint) error
|
||||
IncrementTranslationComments(ctx context.Context, translationID uint) error
|
||||
IncrementTranslationShares(ctx context.Context, translationID uint) error
|
||||
GetOrCreateWorkStats(ctx context.Context, workID uint) (*domain.WorkStats, error)
|
||||
GetOrCreateTranslationStats(ctx context.Context, translationID uint) (*domain.TranslationStats, error)
|
||||
IncrementWorkViews(ctx context.Context, workID uuid.UUID) error
|
||||
IncrementWorkLikes(ctx context.Context, workID uuid.UUID) error
|
||||
IncrementWorkComments(ctx context.Context, workID uuid.UUID) error
|
||||
IncrementWorkBookmarks(ctx context.Context, workID uuid.UUID) error
|
||||
IncrementWorkShares(ctx context.Context, workID uuid.UUID) error
|
||||
IncrementWorkTranslationCount(ctx context.Context, workID uuid.UUID) error
|
||||
IncrementTranslationViews(ctx context.Context, translationID uuid.UUID) error
|
||||
IncrementTranslationLikes(ctx context.Context, translationID uuid.UUID) error
|
||||
DecrementWorkLikes(ctx context.Context, workID uuid.UUID) error
|
||||
DecrementTranslationLikes(ctx context.Context, translationID uuid.UUID) error
|
||||
IncrementTranslationComments(ctx context.Context, translationID uuid.UUID) error
|
||||
IncrementTranslationShares(ctx context.Context, translationID uuid.UUID) error
|
||||
GetOrCreateWorkStats(ctx context.Context, workID uuid.UUID) (*domain.WorkStats, error)
|
||||
GetOrCreateTranslationStats(ctx context.Context, translationID uuid.UUID) (*domain.TranslationStats, error)
|
||||
|
||||
UpdateWorkReadingTime(ctx context.Context, workID uint) error
|
||||
UpdateWorkComplexity(ctx context.Context, workID uint) error
|
||||
UpdateWorkSentiment(ctx context.Context, workID uint) error
|
||||
UpdateTranslationReadingTime(ctx context.Context, translationID uint) error
|
||||
UpdateTranslationSentiment(ctx context.Context, translationID uint) error
|
||||
UpdateWorkReadingTime(ctx context.Context, workID uuid.UUID) error
|
||||
UpdateWorkComplexity(ctx context.Context, workID uuid.UUID) error
|
||||
UpdateWorkSentiment(ctx context.Context, workID uuid.UUID) error
|
||||
UpdateTranslationReadingTime(ctx context.Context, translationID uuid.UUID) error
|
||||
UpdateTranslationSentiment(ctx context.Context, translationID uuid.UUID) error
|
||||
|
||||
UpdateUserEngagement(ctx context.Context, userID uint, eventType string) error
|
||||
UpdateUserEngagement(ctx context.Context, userID uuid.UUID, eventType string) error
|
||||
UpdateTrending(ctx context.Context) error
|
||||
GetTrendingWorks(ctx context.Context, timePeriod string, limit int) ([]*domain.Work, error)
|
||||
UpdateWorkStats(ctx context.Context, workID uint, stats domain.WorkStats) error
|
||||
UpdateWorkStats(ctx context.Context, workID uuid.UUID, stats domain.WorkStats) error
|
||||
}
|
||||
|
||||
type service struct {
|
||||
@ -63,91 +65,91 @@ func NewService(repo Repository, analysisRepo linguistics.AnalysisRepository, tr
|
||||
}
|
||||
}
|
||||
|
||||
func (s *service) IncrementWorkViews(ctx context.Context, workID uint) error {
|
||||
func (s *service) IncrementWorkViews(ctx context.Context, workID uuid.UUID) error {
|
||||
ctx, span := s.tracer.Start(ctx, "IncrementWorkViews")
|
||||
defer span.End()
|
||||
return s.repo.IncrementWorkCounter(ctx, workID, "views", 1)
|
||||
}
|
||||
|
||||
func (s *service) IncrementWorkLikes(ctx context.Context, workID uint) error {
|
||||
func (s *service) IncrementWorkLikes(ctx context.Context, workID uuid.UUID) error {
|
||||
ctx, span := s.tracer.Start(ctx, "IncrementWorkLikes")
|
||||
defer span.End()
|
||||
return s.repo.IncrementWorkCounter(ctx, workID, "likes", 1)
|
||||
}
|
||||
|
||||
func (s *service) DecrementWorkLikes(ctx context.Context, workID uint) error {
|
||||
func (s *service) DecrementWorkLikes(ctx context.Context, workID uuid.UUID) error {
|
||||
ctx, span := s.tracer.Start(ctx, "DecrementWorkLikes")
|
||||
defer span.End()
|
||||
return s.repo.IncrementWorkCounter(ctx, workID, "likes", -1)
|
||||
}
|
||||
|
||||
func (s *service) IncrementWorkComments(ctx context.Context, workID uint) error {
|
||||
func (s *service) IncrementWorkComments(ctx context.Context, workID uuid.UUID) error {
|
||||
ctx, span := s.tracer.Start(ctx, "IncrementWorkComments")
|
||||
defer span.End()
|
||||
return s.repo.IncrementWorkCounter(ctx, workID, "comments", 1)
|
||||
}
|
||||
|
||||
func (s *service) IncrementWorkBookmarks(ctx context.Context, workID uint) error {
|
||||
func (s *service) IncrementWorkBookmarks(ctx context.Context, workID uuid.UUID) error {
|
||||
ctx, span := s.tracer.Start(ctx, "IncrementWorkBookmarks")
|
||||
defer span.End()
|
||||
return s.repo.IncrementWorkCounter(ctx, workID, "bookmarks", 1)
|
||||
}
|
||||
|
||||
func (s *service) IncrementWorkShares(ctx context.Context, workID uint) error {
|
||||
func (s *service) IncrementWorkShares(ctx context.Context, workID uuid.UUID) error {
|
||||
ctx, span := s.tracer.Start(ctx, "IncrementWorkShares")
|
||||
defer span.End()
|
||||
return s.repo.IncrementWorkCounter(ctx, workID, "shares", 1)
|
||||
}
|
||||
|
||||
func (s *service) IncrementWorkTranslationCount(ctx context.Context, workID uint) error {
|
||||
func (s *service) IncrementWorkTranslationCount(ctx context.Context, workID uuid.UUID) error {
|
||||
ctx, span := s.tracer.Start(ctx, "IncrementWorkTranslationCount")
|
||||
defer span.End()
|
||||
return s.repo.IncrementWorkCounter(ctx, workID, "translation_count", 1)
|
||||
}
|
||||
|
||||
func (s *service) IncrementTranslationViews(ctx context.Context, translationID uint) error {
|
||||
func (s *service) IncrementTranslationViews(ctx context.Context, translationID uuid.UUID) error {
|
||||
ctx, span := s.tracer.Start(ctx, "IncrementTranslationViews")
|
||||
defer span.End()
|
||||
return s.repo.IncrementTranslationCounter(ctx, translationID, "views", 1)
|
||||
}
|
||||
|
||||
func (s *service) IncrementTranslationLikes(ctx context.Context, translationID uint) error {
|
||||
func (s *service) IncrementTranslationLikes(ctx context.Context, translationID uuid.UUID) error {
|
||||
ctx, span := s.tracer.Start(ctx, "IncrementTranslationLikes")
|
||||
defer span.End()
|
||||
return s.repo.IncrementTranslationCounter(ctx, translationID, "likes", 1)
|
||||
}
|
||||
|
||||
func (s *service) DecrementTranslationLikes(ctx context.Context, translationID uint) error {
|
||||
func (s *service) DecrementTranslationLikes(ctx context.Context, translationID uuid.UUID) error {
|
||||
ctx, span := s.tracer.Start(ctx, "DecrementTranslationLikes")
|
||||
defer span.End()
|
||||
return s.repo.IncrementTranslationCounter(ctx, translationID, "likes", -1)
|
||||
}
|
||||
|
||||
func (s *service) IncrementTranslationComments(ctx context.Context, translationID uint) error {
|
||||
func (s *service) IncrementTranslationComments(ctx context.Context, translationID uuid.UUID) error {
|
||||
ctx, span := s.tracer.Start(ctx, "IncrementTranslationComments")
|
||||
defer span.End()
|
||||
return s.repo.IncrementTranslationCounter(ctx, translationID, "comments", 1)
|
||||
}
|
||||
|
||||
func (s *service) IncrementTranslationShares(ctx context.Context, translationID uint) error {
|
||||
func (s *service) IncrementTranslationShares(ctx context.Context, translationID uuid.UUID) error {
|
||||
ctx, span := s.tracer.Start(ctx, "IncrementTranslationShares")
|
||||
defer span.End()
|
||||
return s.repo.IncrementTranslationCounter(ctx, translationID, "shares", 1)
|
||||
}
|
||||
|
||||
func (s *service) GetOrCreateWorkStats(ctx context.Context, workID uint) (*domain.WorkStats, error) {
|
||||
func (s *service) GetOrCreateWorkStats(ctx context.Context, workID uuid.UUID) (*domain.WorkStats, error) {
|
||||
ctx, span := s.tracer.Start(ctx, "GetOrCreateWorkStats")
|
||||
defer span.End()
|
||||
return s.repo.GetOrCreateWorkStats(ctx, workID)
|
||||
}
|
||||
|
||||
func (s *service) GetOrCreateTranslationStats(ctx context.Context, translationID uint) (*domain.TranslationStats, error) {
|
||||
func (s *service) GetOrCreateTranslationStats(ctx context.Context, translationID uuid.UUID) (*domain.TranslationStats, error) {
|
||||
ctx, span := s.tracer.Start(ctx, "GetOrCreateTranslationStats")
|
||||
defer span.End()
|
||||
return s.repo.GetOrCreateTranslationStats(ctx, translationID)
|
||||
}
|
||||
|
||||
func (s *service) UpdateWorkReadingTime(ctx context.Context, workID uint) error {
|
||||
func (s *service) UpdateWorkReadingTime(ctx context.Context, workID uuid.UUID) error {
|
||||
ctx, span := s.tracer.Start(ctx, "UpdateWorkReadingTime")
|
||||
defer span.End()
|
||||
stats, err := s.repo.GetOrCreateWorkStats(ctx, workID)
|
||||
@ -174,7 +176,7 @@ func (s *service) UpdateWorkReadingTime(ctx context.Context, workID uint) error
|
||||
return s.repo.UpdateWorkStats(ctx, workID, *stats)
|
||||
}
|
||||
|
||||
func (s *service) UpdateWorkComplexity(ctx context.Context, workID uint) error {
|
||||
func (s *service) UpdateWorkComplexity(ctx context.Context, workID uuid.UUID) error {
|
||||
ctx, span := s.tracer.Start(ctx, "UpdateWorkComplexity")
|
||||
defer span.End()
|
||||
logger := log.FromContext(ctx).With("workID", workID)
|
||||
@ -198,7 +200,7 @@ func (s *service) UpdateWorkComplexity(ctx context.Context, workID uint) error {
|
||||
return s.repo.UpdateWorkStats(ctx, workID, *stats)
|
||||
}
|
||||
|
||||
func (s *service) UpdateWorkSentiment(ctx context.Context, workID uint) error {
|
||||
func (s *service) UpdateWorkSentiment(ctx context.Context, workID uuid.UUID) error {
|
||||
ctx, span := s.tracer.Start(ctx, "UpdateWorkSentiment")
|
||||
defer span.End()
|
||||
logger := log.FromContext(ctx).With("workID", workID)
|
||||
@ -227,7 +229,7 @@ func (s *service) UpdateWorkSentiment(ctx context.Context, workID uint) error {
|
||||
return s.repo.UpdateWorkStats(ctx, workID, *stats)
|
||||
}
|
||||
|
||||
func (s *service) UpdateTranslationReadingTime(ctx context.Context, translationID uint) error {
|
||||
func (s *service) UpdateTranslationReadingTime(ctx context.Context, translationID uuid.UUID) error {
|
||||
ctx, span := s.tracer.Start(ctx, "UpdateTranslationReadingTime")
|
||||
defer span.End()
|
||||
stats, err := s.repo.GetOrCreateTranslationStats(ctx, translationID)
|
||||
@ -255,7 +257,7 @@ func (s *service) UpdateTranslationReadingTime(ctx context.Context, translationI
|
||||
return s.repo.UpdateTranslationStats(ctx, translationID, *stats)
|
||||
}
|
||||
|
||||
func (s *service) UpdateTranslationSentiment(ctx context.Context, translationID uint) error {
|
||||
func (s *service) UpdateTranslationSentiment(ctx context.Context, translationID uuid.UUID) error {
|
||||
ctx, span := s.tracer.Start(ctx, "UpdateTranslationSentiment")
|
||||
defer span.End()
|
||||
stats, err := s.repo.GetOrCreateTranslationStats(ctx, translationID)
|
||||
@ -282,7 +284,7 @@ func (s *service) UpdateTranslationSentiment(ctx context.Context, translationID
|
||||
return s.repo.UpdateTranslationStats(ctx, translationID, *stats)
|
||||
}
|
||||
|
||||
func (s *service) UpdateUserEngagement(ctx context.Context, userID uint, eventType string) error {
|
||||
func (s *service) UpdateUserEngagement(ctx context.Context, userID uuid.UUID, eventType string) error {
|
||||
ctx, span := s.tracer.Start(ctx, "UpdateUserEngagement")
|
||||
defer span.End()
|
||||
today := time.Now().UTC().Truncate(24 * time.Hour)
|
||||
@ -315,7 +317,7 @@ func (s *service) GetTrendingWorks(ctx context.Context, timePeriod string, limit
|
||||
return s.repo.GetTrendingWorks(ctx, timePeriod, limit)
|
||||
}
|
||||
|
||||
func (s *service) UpdateWorkStats(ctx context.Context, workID uint, stats domain.WorkStats) error {
|
||||
func (s *service) UpdateWorkStats(ctx context.Context, workID uuid.UUID, stats domain.WorkStats) error {
|
||||
ctx, span := s.tracer.Start(ctx, "UpdateWorkStats")
|
||||
defer span.End()
|
||||
return s.repo.UpdateWorkStats(ctx, workID, stats)
|
||||
|
||||
@ -3,13 +3,13 @@ package analytics_test
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"testing"
|
||||
"tercul/internal/app/analytics"
|
||||
"tercul/internal/data/sql"
|
||||
"tercul/internal/domain"
|
||||
"tercul/internal/jobs/linguistics"
|
||||
"tercul/internal/platform/config"
|
||||
"tercul/internal/testutil"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
@ -271,4 +271,4 @@ func (s *AnalyticsServiceTestSuite) TestUpdateTrending() {
|
||||
// TestAnalyticsService runs the full test suite.
|
||||
func TestAnalyticsService(t *testing.T) {
|
||||
suite.Run(t, new(AnalyticsServiceTestSuite))
|
||||
}
|
||||
}
|
||||
|
||||
@ -80,4 +80,4 @@ func NewApplication(
|
||||
Search: searchSvc,
|
||||
Analytics: analyticsSvc,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -11,6 +11,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/asaskevich/govalidator"
|
||||
"github.com/google/uuid"
|
||||
"go.opentelemetry.io/otel"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
@ -38,9 +39,9 @@ type RegisterInput struct {
|
||||
|
||||
// AuthResponse represents authentication response
|
||||
type AuthResponse struct {
|
||||
Token string `json:"token"`
|
||||
Token string `json:"token"`
|
||||
User *domain.User `json:"user"`
|
||||
ExpiresAt time.Time `json:"expires_at"`
|
||||
ExpiresAt time.Time `json:"expires_at"`
|
||||
}
|
||||
|
||||
// AuthCommands contains the command handlers for authentication.
|
||||
@ -178,7 +179,7 @@ func (c *AuthCommands) ResendVerificationEmail(ctx context.Context, email string
|
||||
|
||||
// ChangePasswordInput represents the input for changing a password.
|
||||
type ChangePasswordInput struct {
|
||||
UserID uint
|
||||
UserID uuid.UUID
|
||||
CurrentPassword string
|
||||
NewPassword string
|
||||
}
|
||||
|
||||
@ -9,7 +9,6 @@ import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
|
||||
type AuthCommandsSuite struct {
|
||||
suite.Suite
|
||||
userRepo *mockUserRepository
|
||||
|
||||
@ -10,12 +10,12 @@ import (
|
||||
|
||||
// mockUserRepository is a local mock for the UserRepository interface.
|
||||
type mockUserRepository struct {
|
||||
users map[uint]domain.User
|
||||
users map[uint]domain.User
|
||||
findByEmailFunc func(ctx context.Context, email string) (*domain.User, error)
|
||||
findByUsernameFunc func(ctx context.Context, username string) (*domain.User, error)
|
||||
createFunc func(ctx context.Context, user *domain.User) error
|
||||
updateFunc func(ctx context.Context, user *domain.User) error
|
||||
getByIDFunc func(ctx context.Context, id uint) (*domain.User, error)
|
||||
createFunc func(ctx context.Context, user *domain.User) error
|
||||
updateFunc func(ctx context.Context, user *domain.User) error
|
||||
getByIDFunc func(ctx context.Context, id uint) (*domain.User, error)
|
||||
}
|
||||
|
||||
func newMockUserRepository() *mockUserRepository {
|
||||
@ -71,7 +71,7 @@ func (m *mockUserRepository) Update(ctx context.Context, user *domain.User) erro
|
||||
return errors.New("user not found")
|
||||
}
|
||||
|
||||
func (m *mockUserRepository) GetByID(ctx context.Context, id uint) (*domain.User, error) {
|
||||
func (m *mockUserRepository) GetByID(ctx context.Context, id uuid.UUID) (*domain.User, error) {
|
||||
if m.getByIDFunc != nil {
|
||||
return m.getByIDFunc(ctx, id)
|
||||
}
|
||||
@ -85,8 +85,8 @@ func (m *mockUserRepository) GetByID(ctx context.Context, id uint) (*domain.User
|
||||
func (m *mockUserRepository) List(ctx context.Context, page, pageSize int) (*domain.PaginatedResult[domain.User], error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (m *mockUserRepository) ListAll(ctx context.Context) ([]domain.User, error) { return nil, nil }
|
||||
func (m *mockUserRepository) Count(ctx context.Context) (int64, error) { return 0, nil }
|
||||
func (m *mockUserRepository) ListAll(ctx context.Context) ([]domain.User, error) { return nil, nil }
|
||||
func (m *mockUserRepository) Count(ctx context.Context) (int64, error) { return 0, nil }
|
||||
func (m *mockUserRepository) ListByRole(ctx context.Context, role domain.UserRole) ([]domain.User, error) {
|
||||
return nil, nil
|
||||
}
|
||||
@ -114,7 +114,7 @@ func (m *mockUserRepository) GetAllForSync(ctx context.Context, batchSize, offse
|
||||
return nil, nil
|
||||
}
|
||||
func (m *mockUserRepository) Exists(ctx context.Context, id uint) (bool, error) { return false, nil }
|
||||
func (m *mockUserRepository) BeginTx(ctx context.Context) (*gorm.DB, error) { return nil, nil }
|
||||
func (m *mockUserRepository) BeginTx(ctx context.Context) (*gorm.DB, error) { return nil, nil }
|
||||
func (m *mockUserRepository) WithTx(ctx context.Context, fn func(tx *gorm.DB) error) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -3,6 +3,8 @@ package author
|
||||
import (
|
||||
"context"
|
||||
"tercul/internal/domain"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// AuthorCommands contains the command handlers for the author aggregate.
|
||||
@ -34,7 +36,7 @@ func (c *AuthorCommands) CreateAuthor(ctx context.Context, input CreateAuthorInp
|
||||
|
||||
// UpdateAuthorInput represents the input for updating an existing author.
|
||||
type UpdateAuthorInput struct {
|
||||
ID uint
|
||||
ID uuid.UUID
|
||||
Name string
|
||||
}
|
||||
|
||||
@ -53,6 +55,6 @@ func (c *AuthorCommands) UpdateAuthor(ctx context.Context, input UpdateAuthorInp
|
||||
}
|
||||
|
||||
// DeleteAuthor deletes an author by ID.
|
||||
func (c *AuthorCommands) DeleteAuthor(ctx context.Context, id uint) error {
|
||||
func (c *AuthorCommands) DeleteAuthor(ctx context.Context, id uuid.UUID) error {
|
||||
return c.repo.Delete(ctx, id)
|
||||
}
|
||||
|
||||
@ -3,6 +3,8 @@ package author
|
||||
import (
|
||||
"context"
|
||||
"tercul/internal/domain"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// AuthorQueries contains the query handlers for the author aggregate.
|
||||
@ -16,12 +18,12 @@ func NewAuthorQueries(repo domain.AuthorRepository) *AuthorQueries {
|
||||
}
|
||||
|
||||
// Author returns an author by ID.
|
||||
func (q *AuthorQueries) Author(ctx context.Context, id uint) (*domain.Author, error) {
|
||||
func (q *AuthorQueries) Author(ctx context.Context, id uuid.UUID) (*domain.Author, error) {
|
||||
return q.repo.GetByID(ctx, id)
|
||||
}
|
||||
|
||||
// Authors returns all authors, with optional filtering by country.
|
||||
func (q *AuthorQueries) Authors(ctx context.Context, countryID *uint) ([]*domain.Author, error) {
|
||||
func (q *AuthorQueries) Authors(ctx context.Context, countryID *uuid.UUID) ([]*domain.Author, error) {
|
||||
var authors []domain.Author
|
||||
var err error
|
||||
|
||||
@ -43,6 +45,6 @@ func (q *AuthorQueries) Authors(ctx context.Context, countryID *uint) ([]*domain
|
||||
}
|
||||
|
||||
// AuthorWithTranslations returns an author by ID with its translations.
|
||||
func (q *AuthorQueries) AuthorWithTranslations(ctx context.Context, id uint) (*domain.Author, error) {
|
||||
func (q *AuthorQueries) AuthorWithTranslations(ctx context.Context, id uuid.UUID) (*domain.Author, error) {
|
||||
return q.repo.GetWithTranslations(ctx, id)
|
||||
}
|
||||
|
||||
@ -4,6 +4,8 @@ import (
|
||||
"context"
|
||||
"tercul/internal/domain"
|
||||
platform_auth "tercul/internal/platform/auth"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// Service provides authorization checks for the application.
|
||||
@ -26,7 +28,7 @@ func NewService(workRepo domain.WorkRepository, authorRepo domain.AuthorReposito
|
||||
|
||||
// CanEditWork checks if a user has permission to edit a work.
|
||||
// For now, we'll implement a simple rule: only an admin or the work's author can edit it.
|
||||
func (s *Service) CanEditWork(ctx context.Context, userID uint, work *domain.Work) (bool, error) {
|
||||
func (s *Service) CanEditWork(ctx context.Context, userID uuid.UUID, work *domain.Work) (bool, error) {
|
||||
claims, ok := platform_auth.GetClaimsFromContext(ctx)
|
||||
if !ok {
|
||||
return false, domain.ErrUnauthorized
|
||||
@ -61,7 +63,7 @@ func (s *Service) CanEditWork(ctx context.Context, userID uint, work *domain.Wor
|
||||
}
|
||||
|
||||
// CanDeleteWork checks if a user has permission to delete a work.
|
||||
func (s *Service) CanDeleteWork(ctx context.Context, userID uint, work *domain.Work) (bool, error) {
|
||||
func (s *Service) CanDeleteWork(ctx context.Context, userID uuid.UUID, work *domain.Work) (bool, error) {
|
||||
claims, ok := platform_auth.GetClaimsFromContext(ctx)
|
||||
if !ok {
|
||||
return false, domain.ErrUnauthorized
|
||||
@ -96,7 +98,7 @@ func (s *Service) CanDeleteWork(ctx context.Context, userID uint, work *domain.W
|
||||
}
|
||||
|
||||
// CanEditEntity checks if a user has permission to edit a specific translatable entity.
|
||||
func (s *Service) CanEditEntity(ctx context.Context, userID uint, translatableType string, translatableID uint) (bool, error) {
|
||||
func (s *Service) CanEditEntity(ctx context.Context, userID uuid.UUID, translatableType string, translatableID uuid.UUID) (bool, error) {
|
||||
switch translatableType {
|
||||
case "works":
|
||||
// For works, we can reuse the CanEditWork logic.
|
||||
@ -114,7 +116,7 @@ func (s *Service) CanEditEntity(ctx context.Context, userID uint, translatableTy
|
||||
}
|
||||
|
||||
// CanDeleteTranslation checks if a user can delete a translation.
|
||||
func (s *Service) CanDeleteTranslation(ctx context.Context, userID uint, translationID uint) (bool, error) {
|
||||
func (s *Service) CanDeleteTranslation(ctx context.Context, userID uuid.UUID, translationID uuid.UUID) (bool, error) {
|
||||
claims, ok := platform_auth.GetClaimsFromContext(ctx)
|
||||
if !ok {
|
||||
return false, domain.ErrUnauthorized
|
||||
@ -157,7 +159,7 @@ func (s *Service) CanCreateTranslation(ctx context.Context) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (s *Service) CanEditTranslation(ctx context.Context, userID uint, translationID uint) (bool, error) {
|
||||
func (s *Service) CanEditTranslation(ctx context.Context, userID uuid.UUID, translationID uuid.UUID) (bool, error) {
|
||||
claims, ok := platform_auth.GetClaimsFromContext(ctx)
|
||||
if !ok {
|
||||
return false, domain.ErrUnauthorized
|
||||
@ -211,7 +213,7 @@ func (s *Service) CanDeleteBook(ctx context.Context) (bool, error) {
|
||||
return false, domain.ErrForbidden
|
||||
}
|
||||
|
||||
func (s *Service) CanUpdateUser(ctx context.Context, actorID, targetUserID uint) (bool, error) {
|
||||
func (s *Service) CanUpdateUser(ctx context.Context, actorID, targetUserID uuid.UUID) (bool, error) {
|
||||
claims, ok := platform_auth.GetClaimsFromContext(ctx)
|
||||
if !ok {
|
||||
return false, domain.ErrUnauthorized
|
||||
@ -232,7 +234,7 @@ func (s *Service) CanUpdateUser(ctx context.Context, actorID, targetUserID uint)
|
||||
|
||||
// CanDeleteComment checks if a user has permission to delete a comment.
|
||||
// For now, we'll implement a simple rule: only an admin or the comment's author can delete it.
|
||||
func (s *Service) CanDeleteComment(ctx context.Context, userID uint, comment *domain.Comment) (bool, error) {
|
||||
func (s *Service) CanDeleteComment(ctx context.Context, userID uuid.UUID, comment *domain.Comment) (bool, error) {
|
||||
claims, ok := platform_auth.GetClaimsFromContext(ctx)
|
||||
if !ok {
|
||||
return false, domain.ErrUnauthorized
|
||||
@ -249,4 +251,4 @@ func (s *Service) CanDeleteComment(ctx context.Context, userID uint, comment *do
|
||||
}
|
||||
|
||||
return false, domain.ErrForbidden
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,6 +4,8 @@ import (
|
||||
"context"
|
||||
"tercul/internal/app/authz"
|
||||
"tercul/internal/domain"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// BookCommands contains the command handlers for the book aggregate.
|
||||
@ -26,7 +28,7 @@ type CreateBookInput struct {
|
||||
Description string
|
||||
Language string
|
||||
ISBN *string
|
||||
AuthorIDs []uint
|
||||
AuthorIDs []uuid.UUID
|
||||
}
|
||||
|
||||
// CreateBook creates a new book.
|
||||
@ -62,12 +64,12 @@ func (c *BookCommands) CreateBook(ctx context.Context, input CreateBookInput) (*
|
||||
|
||||
// UpdateBookInput represents the input for updating an existing book.
|
||||
type UpdateBookInput struct {
|
||||
ID uint
|
||||
ID uuid.UUID
|
||||
Title *string
|
||||
Description *string
|
||||
Language *string
|
||||
ISBN *string
|
||||
AuthorIDs []uint
|
||||
AuthorIDs []uuid.UUID
|
||||
}
|
||||
|
||||
// UpdateBook updates an existing book.
|
||||
@ -106,7 +108,7 @@ func (c *BookCommands) UpdateBook(ctx context.Context, input UpdateBookInput) (*
|
||||
}
|
||||
|
||||
// DeleteBook deletes a book by ID.
|
||||
func (c *BookCommands) DeleteBook(ctx context.Context, id uint) error {
|
||||
func (c *BookCommands) DeleteBook(ctx context.Context, id uuid.UUID) error {
|
||||
can, err := c.authzSvc.CanDeleteBook(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -115,4 +117,4 @@ func (c *BookCommands) DeleteBook(ctx context.Context, id uint) error {
|
||||
return domain.ErrForbidden
|
||||
}
|
||||
return c.repo.Delete(ctx, id)
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,6 +3,8 @@ package book
|
||||
import (
|
||||
"context"
|
||||
"tercul/internal/domain"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// BookQueries contains the query handlers for the book aggregate.
|
||||
@ -16,11 +18,11 @@ func NewBookQueries(repo domain.BookRepository) *BookQueries {
|
||||
}
|
||||
|
||||
// Book retrieves a book by its ID.
|
||||
func (q *BookQueries) Book(ctx context.Context, id uint) (*domain.Book, error) {
|
||||
func (q *BookQueries) Book(ctx context.Context, id uuid.UUID) (*domain.Book, error) {
|
||||
return q.repo.GetByID(ctx, id)
|
||||
}
|
||||
|
||||
// Books retrieves a list of all books.
|
||||
func (q *BookQueries) Books(ctx context.Context) ([]domain.Book, error) {
|
||||
return q.repo.ListAll(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
@ -17,4 +17,4 @@ func NewService(repo domain.BookRepository, authzSvc *authz.Service) *Service {
|
||||
Commands: NewBookCommands(repo, authzSvc),
|
||||
Queries: NewBookQueries(repo),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,6 +5,8 @@ import (
|
||||
"tercul/internal/app/analytics"
|
||||
"tercul/internal/domain"
|
||||
"tercul/internal/platform/log"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// BookmarkCommands contains the command handlers for the bookmark aggregate.
|
||||
@ -24,8 +26,8 @@ func NewBookmarkCommands(repo domain.BookmarkRepository, analyticsSvc analytics.
|
||||
// CreateBookmarkInput represents the input for creating a new bookmark.
|
||||
type CreateBookmarkInput struct {
|
||||
Name string
|
||||
UserID uint
|
||||
WorkID uint
|
||||
UserID uuid.UUID
|
||||
WorkID uuid.UUID
|
||||
Notes string
|
||||
}
|
||||
|
||||
@ -55,7 +57,7 @@ func (c *BookmarkCommands) CreateBookmark(ctx context.Context, input CreateBookm
|
||||
|
||||
// UpdateBookmarkInput represents the input for updating an existing bookmark.
|
||||
type UpdateBookmarkInput struct {
|
||||
ID uint
|
||||
ID uuid.UUID
|
||||
Name string
|
||||
Notes string
|
||||
}
|
||||
@ -76,6 +78,6 @@ func (c *BookmarkCommands) UpdateBookmark(ctx context.Context, input UpdateBookm
|
||||
}
|
||||
|
||||
// DeleteBookmark deletes a bookmark by ID.
|
||||
func (c *BookmarkCommands) DeleteBookmark(ctx context.Context, id uint) error {
|
||||
func (c *BookmarkCommands) DeleteBookmark(ctx context.Context, id uuid.UUID) error {
|
||||
return c.repo.Delete(ctx, id)
|
||||
}
|
||||
|
||||
@ -3,6 +3,8 @@ package bookmark
|
||||
import (
|
||||
"context"
|
||||
"tercul/internal/domain"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// BookmarkQueries contains the query handlers for the bookmark aggregate.
|
||||
@ -16,16 +18,16 @@ func NewBookmarkQueries(repo domain.BookmarkRepository) *BookmarkQueries {
|
||||
}
|
||||
|
||||
// Bookmark returns a bookmark by ID.
|
||||
func (q *BookmarkQueries) Bookmark(ctx context.Context, id uint) (*domain.Bookmark, error) {
|
||||
func (q *BookmarkQueries) Bookmark(ctx context.Context, id uuid.UUID) (*domain.Bookmark, error) {
|
||||
return q.repo.GetByID(ctx, id)
|
||||
}
|
||||
|
||||
// BookmarksByUserID returns all bookmarks for a user.
|
||||
func (q *BookmarkQueries) BookmarksByUserID(ctx context.Context, userID uint) ([]domain.Bookmark, error) {
|
||||
func (q *BookmarkQueries) BookmarksByUserID(ctx context.Context, userID uuid.UUID) ([]domain.Bookmark, error) {
|
||||
return q.repo.ListByUserID(ctx, userID)
|
||||
}
|
||||
|
||||
// BookmarksByWorkID returns all bookmarks for a work.
|
||||
func (q *BookmarkQueries) BookmarksByWorkID(ctx context.Context, workID uint) ([]domain.Bookmark, error) {
|
||||
func (q *BookmarkQueries) BookmarksByWorkID(ctx context.Context, workID uuid.UUID) ([]domain.Bookmark, error) {
|
||||
return q.repo.ListByWorkID(ctx, workID)
|
||||
}
|
||||
|
||||
@ -3,6 +3,8 @@ package category
|
||||
import (
|
||||
"context"
|
||||
"tercul/internal/domain"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// CategoryCommands contains the command handlers for the category aggregate.
|
||||
@ -19,7 +21,7 @@ func NewCategoryCommands(repo domain.CategoryRepository) *CategoryCommands {
|
||||
type CreateCategoryInput struct {
|
||||
Name string
|
||||
Description string
|
||||
ParentID *uint
|
||||
ParentID *uuid.UUID
|
||||
}
|
||||
|
||||
// CreateCategory creates a new category.
|
||||
@ -38,10 +40,10 @@ func (c *CategoryCommands) CreateCategory(ctx context.Context, input CreateCateg
|
||||
|
||||
// UpdateCategoryInput represents the input for updating an existing category.
|
||||
type UpdateCategoryInput struct {
|
||||
ID uint
|
||||
ID uuid.UUID
|
||||
Name string
|
||||
Description string
|
||||
ParentID *uint
|
||||
ParentID *uuid.UUID
|
||||
}
|
||||
|
||||
// UpdateCategory updates an existing category.
|
||||
@ -61,6 +63,6 @@ func (c *CategoryCommands) UpdateCategory(ctx context.Context, input UpdateCateg
|
||||
}
|
||||
|
||||
// DeleteCategory deletes a category by ID.
|
||||
func (c *CategoryCommands) DeleteCategory(ctx context.Context, id uint) error {
|
||||
func (c *CategoryCommands) DeleteCategory(ctx context.Context, id uuid.UUID) error {
|
||||
return c.repo.Delete(ctx, id)
|
||||
}
|
||||
|
||||
@ -3,6 +3,8 @@ package category
|
||||
import (
|
||||
"context"
|
||||
"tercul/internal/domain"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// CategoryQueries contains the query handlers for the category aggregate.
|
||||
@ -16,7 +18,7 @@ func NewCategoryQueries(repo domain.CategoryRepository) *CategoryQueries {
|
||||
}
|
||||
|
||||
// Category returns a category by ID.
|
||||
func (q *CategoryQueries) Category(ctx context.Context, id uint) (*domain.Category, error) {
|
||||
func (q *CategoryQueries) Category(ctx context.Context, id uuid.UUID) (*domain.Category, error) {
|
||||
return q.repo.GetByID(ctx, id)
|
||||
}
|
||||
|
||||
@ -26,12 +28,12 @@ func (q *CategoryQueries) CategoryByName(ctx context.Context, name string) (*dom
|
||||
}
|
||||
|
||||
// CategoriesByWorkID returns all categories for a work.
|
||||
func (q *CategoryQueries) CategoriesByWorkID(ctx context.Context, workID uint) ([]domain.Category, error) {
|
||||
func (q *CategoryQueries) CategoriesByWorkID(ctx context.Context, workID uuid.UUID) ([]domain.Category, error) {
|
||||
return q.repo.ListByWorkID(ctx, workID)
|
||||
}
|
||||
|
||||
// CategoriesByParentID returns all categories for a parent.
|
||||
func (q *CategoryQueries) CategoriesByParentID(ctx context.Context, parentID *uint) ([]domain.Category, error) {
|
||||
func (q *CategoryQueries) CategoriesByParentID(ctx context.Context, parentID *uuid.UUID) ([]domain.Category, error) {
|
||||
return q.repo.ListByParentID(ctx, parentID)
|
||||
}
|
||||
|
||||
|
||||
@ -4,6 +4,8 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"tercul/internal/domain"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// CollectionCommands contains the command handlers for the collection aggregate.
|
||||
@ -20,7 +22,7 @@ func NewCollectionCommands(repo domain.CollectionRepository) *CollectionCommands
|
||||
type CreateCollectionInput struct {
|
||||
Name string
|
||||
Description string
|
||||
UserID uint
|
||||
UserID uuid.UUID
|
||||
IsPublic bool
|
||||
CoverImageURL string
|
||||
}
|
||||
@ -43,12 +45,12 @@ func (c *CollectionCommands) CreateCollection(ctx context.Context, input CreateC
|
||||
|
||||
// UpdateCollectionInput represents the input for updating an existing collection.
|
||||
type UpdateCollectionInput struct {
|
||||
ID uint
|
||||
ID uuid.UUID
|
||||
Name string
|
||||
Description string
|
||||
IsPublic bool
|
||||
CoverImageURL string
|
||||
UserID uint
|
||||
UserID uuid.UUID
|
||||
}
|
||||
|
||||
// UpdateCollection updates an existing collection.
|
||||
@ -58,7 +60,7 @@ func (c *CollectionCommands) UpdateCollection(ctx context.Context, input UpdateC
|
||||
return nil, err
|
||||
}
|
||||
if collection.UserID != input.UserID {
|
||||
return nil, fmt.Errorf("unauthorized: user %d cannot update collection %d", input.UserID, input.ID)
|
||||
return nil, fmt.Errorf("unauthorized: user %s cannot update collection %s", input.UserID, input.ID)
|
||||
}
|
||||
collection.Name = input.Name
|
||||
collection.Description = input.Description
|
||||
@ -72,22 +74,22 @@ func (c *CollectionCommands) UpdateCollection(ctx context.Context, input UpdateC
|
||||
}
|
||||
|
||||
// DeleteCollection deletes a collection by ID.
|
||||
func (c *CollectionCommands) DeleteCollection(ctx context.Context, id uint, userID uint) error {
|
||||
func (c *CollectionCommands) DeleteCollection(ctx context.Context, id uuid.UUID, userID uuid.UUID) error {
|
||||
collection, err := c.repo.GetByID(ctx, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if collection.UserID != userID {
|
||||
return fmt.Errorf("unauthorized: user %d cannot delete collection %d", userID, id)
|
||||
return fmt.Errorf("unauthorized: user %s cannot delete collection %s", userID, id)
|
||||
}
|
||||
return c.repo.Delete(ctx, id)
|
||||
}
|
||||
|
||||
// AddWorkToCollectionInput represents the input for adding a work to a collection.
|
||||
type AddWorkToCollectionInput struct {
|
||||
CollectionID uint
|
||||
WorkID uint
|
||||
UserID uint
|
||||
CollectionID uuid.UUID
|
||||
WorkID uuid.UUID
|
||||
UserID uuid.UUID
|
||||
}
|
||||
|
||||
// AddWorkToCollection adds a work to a collection.
|
||||
@ -97,16 +99,16 @@ func (c *CollectionCommands) AddWorkToCollection(ctx context.Context, input AddW
|
||||
return err
|
||||
}
|
||||
if collection.UserID != input.UserID {
|
||||
return fmt.Errorf("unauthorized: user %d cannot add work to collection %d", input.UserID, input.CollectionID)
|
||||
return fmt.Errorf("unauthorized: user %s cannot add work to collection %s", input.UserID, input.CollectionID)
|
||||
}
|
||||
return c.repo.AddWorkToCollection(ctx, input.CollectionID, input.WorkID)
|
||||
}
|
||||
|
||||
// RemoveWorkFromCollectionInput represents the input for removing a work from a collection.
|
||||
type RemoveWorkFromCollectionInput struct {
|
||||
CollectionID uint
|
||||
WorkID uint
|
||||
UserID uint
|
||||
CollectionID uuid.UUID
|
||||
WorkID uuid.UUID
|
||||
UserID uuid.UUID
|
||||
}
|
||||
|
||||
// RemoveWorkFromCollection removes a work from a collection.
|
||||
@ -116,7 +118,7 @@ func (c *CollectionCommands) RemoveWorkFromCollection(ctx context.Context, input
|
||||
return err
|
||||
}
|
||||
if collection.UserID != input.UserID {
|
||||
return fmt.Errorf("unauthorized: user %d cannot remove work from collection %d", input.UserID, input.CollectionID)
|
||||
return fmt.Errorf("unauthorized: user %s cannot remove work from collection %s", input.UserID, input.CollectionID)
|
||||
}
|
||||
return c.repo.RemoveWorkFromCollection(ctx, input.CollectionID, input.WorkID)
|
||||
}
|
||||
|
||||
@ -3,6 +3,8 @@ package collection
|
||||
import (
|
||||
"context"
|
||||
"tercul/internal/domain"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// CollectionQueries contains the query handlers for the collection aggregate.
|
||||
@ -16,12 +18,12 @@ func NewCollectionQueries(repo domain.CollectionRepository) *CollectionQueries {
|
||||
}
|
||||
|
||||
// Collection returns a collection by ID.
|
||||
func (q *CollectionQueries) Collection(ctx context.Context, id uint) (*domain.Collection, error) {
|
||||
func (q *CollectionQueries) Collection(ctx context.Context, id uuid.UUID) (*domain.Collection, error) {
|
||||
return q.repo.GetByID(ctx, id)
|
||||
}
|
||||
|
||||
// CollectionsByUserID returns all collections for a user.
|
||||
func (q *CollectionQueries) CollectionsByUserID(ctx context.Context, userID uint) ([]domain.Collection, error) {
|
||||
func (q *CollectionQueries) CollectionsByUserID(ctx context.Context, userID uuid.UUID) ([]domain.Collection, error) {
|
||||
return q.repo.ListByUserID(ctx, userID)
|
||||
}
|
||||
|
||||
@ -31,7 +33,7 @@ func (q *CollectionQueries) PublicCollections(ctx context.Context) ([]domain.Col
|
||||
}
|
||||
|
||||
// CollectionsByWorkID returns all collections for a work.
|
||||
func (q *CollectionQueries) CollectionsByWorkID(ctx context.Context, workID uint) ([]domain.Collection, error) {
|
||||
func (q *CollectionQueries) CollectionsByWorkID(ctx context.Context, workID uuid.UUID) ([]domain.Collection, error) {
|
||||
return q.repo.ListByWorkID(ctx, workID)
|
||||
}
|
||||
|
||||
|
||||
@ -9,6 +9,8 @@ import (
|
||||
"tercul/internal/domain"
|
||||
platform_auth "tercul/internal/platform/auth"
|
||||
"tercul/internal/platform/log"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// CommentCommands contains the command handlers for the comment aggregate.
|
||||
@ -30,10 +32,10 @@ func NewCommentCommands(repo domain.CommentRepository, authzSvc *authz.Service,
|
||||
// CreateCommentInput represents the input for creating a new comment.
|
||||
type CreateCommentInput struct {
|
||||
Text string
|
||||
UserID uint
|
||||
WorkID *uint
|
||||
TranslationID *uint
|
||||
ParentID *uint
|
||||
UserID uuid.UUID
|
||||
WorkID *uuid.UUID
|
||||
TranslationID *uuid.UUID
|
||||
ParentID *uuid.UUID
|
||||
}
|
||||
|
||||
// CreateComment creates a new comment.
|
||||
@ -72,7 +74,7 @@ func (c *CommentCommands) CreateComment(ctx context.Context, input CreateComment
|
||||
|
||||
// UpdateCommentInput represents the input for updating an existing comment.
|
||||
type UpdateCommentInput struct {
|
||||
ID uint
|
||||
ID uuid.UUID
|
||||
Text string
|
||||
}
|
||||
|
||||
@ -86,7 +88,7 @@ func (c *CommentCommands) UpdateComment(ctx context.Context, input UpdateComment
|
||||
comment, err := c.repo.GetByID(ctx, input.ID)
|
||||
if err != nil {
|
||||
if errors.Is(err, domain.ErrEntityNotFound) {
|
||||
return nil, fmt.Errorf("%w: comment with id %d not found", domain.ErrEntityNotFound, input.ID)
|
||||
return nil, fmt.Errorf("%w: comment with id %s not found", domain.ErrEntityNotFound, input.ID)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
@ -108,7 +110,7 @@ func (c *CommentCommands) UpdateComment(ctx context.Context, input UpdateComment
|
||||
}
|
||||
|
||||
// DeleteComment deletes a comment by ID after an authorization check.
|
||||
func (c *CommentCommands) DeleteComment(ctx context.Context, id uint) error {
|
||||
func (c *CommentCommands) DeleteComment(ctx context.Context, id uuid.UUID) error {
|
||||
userID, ok := platform_auth.GetUserIDFromContext(ctx)
|
||||
if !ok {
|
||||
return domain.ErrUnauthorized
|
||||
@ -117,7 +119,7 @@ func (c *CommentCommands) DeleteComment(ctx context.Context, id uint) error {
|
||||
comment, err := c.repo.GetByID(ctx, id)
|
||||
if err != nil {
|
||||
if errors.Is(err, domain.ErrEntityNotFound) {
|
||||
return fmt.Errorf("%w: comment with id %d not found", domain.ErrEntityNotFound, id)
|
||||
return fmt.Errorf("%w: comment with id %s not found", domain.ErrEntityNotFound, id)
|
||||
}
|
||||
return err
|
||||
}
|
||||
@ -131,4 +133,4 @@ func (c *CommentCommands) DeleteComment(ctx context.Context, id uint) error {
|
||||
}
|
||||
|
||||
return c.repo.Delete(ctx, id)
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,6 +3,8 @@ package comment
|
||||
import (
|
||||
"context"
|
||||
"tercul/internal/domain"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// CommentQueries contains the query handlers for the comment aggregate.
|
||||
@ -16,27 +18,27 @@ func NewCommentQueries(repo domain.CommentRepository) *CommentQueries {
|
||||
}
|
||||
|
||||
// Comment returns a comment by ID.
|
||||
func (q *CommentQueries) Comment(ctx context.Context, id uint) (*domain.Comment, error) {
|
||||
func (q *CommentQueries) Comment(ctx context.Context, id uuid.UUID) (*domain.Comment, error) {
|
||||
return q.repo.GetByID(ctx, id)
|
||||
}
|
||||
|
||||
// CommentsByUserID returns all comments for a user.
|
||||
func (q *CommentQueries) CommentsByUserID(ctx context.Context, userID uint) ([]domain.Comment, error) {
|
||||
func (q *CommentQueries) CommentsByUserID(ctx context.Context, userID uuid.UUID) ([]domain.Comment, error) {
|
||||
return q.repo.ListByUserID(ctx, userID)
|
||||
}
|
||||
|
||||
// CommentsByWorkID returns all comments for a work.
|
||||
func (q *CommentQueries) CommentsByWorkID(ctx context.Context, workID uint) ([]domain.Comment, error) {
|
||||
func (q *CommentQueries) CommentsByWorkID(ctx context.Context, workID uuid.UUID) ([]domain.Comment, error) {
|
||||
return q.repo.ListByWorkID(ctx, workID)
|
||||
}
|
||||
|
||||
// CommentsByTranslationID returns all comments for a translation.
|
||||
func (q *CommentQueries) CommentsByTranslationID(ctx context.Context, translationID uint) ([]domain.Comment, error) {
|
||||
func (q *CommentQueries) CommentsByTranslationID(ctx context.Context, translationID uuid.UUID) ([]domain.Comment, error) {
|
||||
return q.repo.ListByTranslationID(ctx, translationID)
|
||||
}
|
||||
|
||||
// CommentsByParentID returns all comments for a parent.
|
||||
func (q *CommentQueries) CommentsByParentID(ctx context.Context, parentID uint) ([]domain.Comment, error) {
|
||||
func (q *CommentQueries) CommentsByParentID(ctx context.Context, parentID uuid.UUID) ([]domain.Comment, error) {
|
||||
return q.repo.ListByParentID(ctx, parentID)
|
||||
}
|
||||
|
||||
|
||||
@ -5,6 +5,8 @@ import (
|
||||
"tercul/internal/app/authz"
|
||||
"tercul/internal/domain"
|
||||
platform_auth "tercul/internal/platform/auth"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// Commands contains the command handlers for the contribution aggregate.
|
||||
@ -25,8 +27,8 @@ func NewCommands(repo domain.ContributionRepository, authzSvc *authz.Service) *C
|
||||
type CreateContributionInput struct {
|
||||
Name string
|
||||
Status string
|
||||
WorkID *uint
|
||||
TranslationID *uint
|
||||
WorkID *uuid.UUID
|
||||
TranslationID *uuid.UUID
|
||||
}
|
||||
|
||||
// CreateContribution creates a new contribution.
|
||||
@ -56,8 +58,8 @@ func (c *Commands) CreateContribution(ctx context.Context, input CreateContribut
|
||||
|
||||
// UpdateContributionInput represents the input for updating a contribution.
|
||||
type UpdateContributionInput struct {
|
||||
ID uint
|
||||
UserID uint
|
||||
ID uuid.UUID
|
||||
UserID uuid.UUID
|
||||
Name *string
|
||||
Status *string
|
||||
}
|
||||
@ -89,7 +91,7 @@ func (c *Commands) UpdateContribution(ctx context.Context, input UpdateContribut
|
||||
}
|
||||
|
||||
// DeleteContribution deletes a contribution.
|
||||
func (c *Commands) DeleteContribution(ctx context.Context, contributionID uint, userID uint) error {
|
||||
func (c *Commands) DeleteContribution(ctx context.Context, contributionID uuid.UUID, userID uuid.UUID) error {
|
||||
contribution, err := c.repo.GetByID(ctx, contributionID)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -105,7 +107,7 @@ func (c *Commands) DeleteContribution(ctx context.Context, contributionID uint,
|
||||
|
||||
// ReviewContributionInput represents the input for reviewing a contribution.
|
||||
type ReviewContributionInput struct {
|
||||
ID uint
|
||||
ID uuid.UUID
|
||||
Status string
|
||||
Feedback *string
|
||||
}
|
||||
@ -135,4 +137,4 @@ func (c *Commands) ReviewContribution(ctx context.Context, input ReviewContribut
|
||||
}
|
||||
|
||||
return contribution, nil
|
||||
}
|
||||
}
|
||||
|
||||
@ -11,4 +11,4 @@ func NewService(commands *Commands) *Service {
|
||||
return &Service{
|
||||
Commands: commands,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,6 +5,8 @@ import (
|
||||
"errors"
|
||||
"tercul/internal/domain"
|
||||
"tercul/internal/platform/log"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// CopyrightCommands contains the command handlers for copyright.
|
||||
@ -37,8 +39,8 @@ func (c *CopyrightCommands) UpdateCopyright(ctx context.Context, copyright *doma
|
||||
if copyright == nil {
|
||||
return errors.New("copyright cannot be nil")
|
||||
}
|
||||
if copyright.ID == 0 {
|
||||
return errors.New("copyright ID cannot be zero")
|
||||
if copyright.ID == uuid.Nil {
|
||||
return errors.New("copyright ID cannot be nil")
|
||||
}
|
||||
if copyright.Name == "" {
|
||||
return errors.New("copyright name cannot be empty")
|
||||
@ -51,8 +53,8 @@ func (c *CopyrightCommands) UpdateCopyright(ctx context.Context, copyright *doma
|
||||
}
|
||||
|
||||
// DeleteCopyright deletes a copyright.
|
||||
func (c *CopyrightCommands) DeleteCopyright(ctx context.Context, id uint) error {
|
||||
if id == 0 {
|
||||
func (c *CopyrightCommands) DeleteCopyright(ctx context.Context, id uuid.UUID) error {
|
||||
if id == uuid.Nil {
|
||||
return errors.New("invalid copyright ID")
|
||||
}
|
||||
log.FromContext(ctx).With("id", id).Debug("Deleting copyright")
|
||||
@ -60,8 +62,8 @@ func (c *CopyrightCommands) DeleteCopyright(ctx context.Context, id uint) error
|
||||
}
|
||||
|
||||
// AddCopyrightToWork adds a copyright to a work.
|
||||
func (c *CopyrightCommands) AddCopyrightToWork(ctx context.Context, workID uint, copyrightID uint) error {
|
||||
if workID == 0 || copyrightID == 0 {
|
||||
func (c *CopyrightCommands) AddCopyrightToWork(ctx context.Context, workID uuid.UUID, copyrightID uuid.UUID) error {
|
||||
if workID == uuid.Nil || copyrightID == uuid.Nil {
|
||||
return errors.New("invalid work ID or copyright ID")
|
||||
}
|
||||
log.FromContext(ctx).With("work_id", workID).With("copyright_id", copyrightID).Debug("Adding copyright to work")
|
||||
@ -69,8 +71,8 @@ func (c *CopyrightCommands) AddCopyrightToWork(ctx context.Context, workID uint,
|
||||
}
|
||||
|
||||
// RemoveCopyrightFromWork removes a copyright from a work.
|
||||
func (c *CopyrightCommands) RemoveCopyrightFromWork(ctx context.Context, workID uint, copyrightID uint) error {
|
||||
if workID == 0 || copyrightID == 0 {
|
||||
func (c *CopyrightCommands) RemoveCopyrightFromWork(ctx context.Context, workID uuid.UUID, copyrightID uuid.UUID) error {
|
||||
if workID == uuid.Nil || copyrightID == uuid.Nil {
|
||||
return errors.New("invalid work ID or copyright ID")
|
||||
}
|
||||
log.FromContext(ctx).With("work_id", workID).With("copyright_id", copyrightID).Debug("Removing copyright from work")
|
||||
@ -78,8 +80,8 @@ func (c *CopyrightCommands) RemoveCopyrightFromWork(ctx context.Context, workID
|
||||
}
|
||||
|
||||
// AddCopyrightToAuthor adds a copyright to an author.
|
||||
func (c *CopyrightCommands) AddCopyrightToAuthor(ctx context.Context, authorID uint, copyrightID uint) error {
|
||||
if authorID == 0 || copyrightID == 0 {
|
||||
func (c *CopyrightCommands) AddCopyrightToAuthor(ctx context.Context, authorID uuid.UUID, copyrightID uuid.UUID) error {
|
||||
if authorID == uuid.Nil || copyrightID == uuid.Nil {
|
||||
return errors.New("invalid author ID or copyright ID")
|
||||
}
|
||||
log.FromContext(ctx).With("author_id", authorID).With("copyright_id", copyrightID).Debug("Adding copyright to author")
|
||||
@ -87,8 +89,8 @@ func (c *CopyrightCommands) AddCopyrightToAuthor(ctx context.Context, authorID u
|
||||
}
|
||||
|
||||
// RemoveCopyrightFromAuthor removes a copyright from an author.
|
||||
func (c *CopyrightCommands) RemoveCopyrightFromAuthor(ctx context.Context, authorID uint, copyrightID uint) error {
|
||||
if authorID == 0 || copyrightID == 0 {
|
||||
func (c *CopyrightCommands) RemoveCopyrightFromAuthor(ctx context.Context, authorID uuid.UUID, copyrightID uuid.UUID) error {
|
||||
if authorID == uuid.Nil || copyrightID == uuid.Nil {
|
||||
return errors.New("invalid author ID or copyright ID")
|
||||
}
|
||||
log.FromContext(ctx).With("author_id", authorID).With("copyright_id", copyrightID).Debug("Removing copyright from author")
|
||||
@ -96,8 +98,8 @@ func (c *CopyrightCommands) RemoveCopyrightFromAuthor(ctx context.Context, autho
|
||||
}
|
||||
|
||||
// AddCopyrightToBook adds a copyright to a book.
|
||||
func (c *CopyrightCommands) AddCopyrightToBook(ctx context.Context, bookID uint, copyrightID uint) error {
|
||||
if bookID == 0 || copyrightID == 0 {
|
||||
func (c *CopyrightCommands) AddCopyrightToBook(ctx context.Context, bookID uuid.UUID, copyrightID uuid.UUID) error {
|
||||
if bookID == uuid.Nil || copyrightID == uuid.Nil {
|
||||
return errors.New("invalid book ID or copyright ID")
|
||||
}
|
||||
log.FromContext(ctx).With("book_id", bookID).With("copyright_id", copyrightID).Debug("Adding copyright to book")
|
||||
@ -105,8 +107,8 @@ func (c *CopyrightCommands) AddCopyrightToBook(ctx context.Context, bookID uint,
|
||||
}
|
||||
|
||||
// RemoveCopyrightFromBook removes a copyright from a book.
|
||||
func (c *CopyrightCommands) RemoveCopyrightFromBook(ctx context.Context, bookID uint, copyrightID uint) error {
|
||||
if bookID == 0 || copyrightID == 0 {
|
||||
func (c *CopyrightCommands) RemoveCopyrightFromBook(ctx context.Context, bookID uuid.UUID, copyrightID uuid.UUID) error {
|
||||
if bookID == uuid.Nil || copyrightID == uuid.Nil {
|
||||
return errors.New("invalid book ID or copyright ID")
|
||||
}
|
||||
log.FromContext(ctx).With("book_id", bookID).With("copyright_id", copyrightID).Debug("Removing copyright from book")
|
||||
@ -114,8 +116,8 @@ func (c *CopyrightCommands) RemoveCopyrightFromBook(ctx context.Context, bookID
|
||||
}
|
||||
|
||||
// AddCopyrightToPublisher adds a copyright to a publisher.
|
||||
func (c *CopyrightCommands) AddCopyrightToPublisher(ctx context.Context, publisherID uint, copyrightID uint) error {
|
||||
if publisherID == 0 || copyrightID == 0 {
|
||||
func (c *CopyrightCommands) AddCopyrightToPublisher(ctx context.Context, publisherID uuid.UUID, copyrightID uuid.UUID) error {
|
||||
if publisherID == uuid.Nil || copyrightID == uuid.Nil {
|
||||
return errors.New("invalid publisher ID or copyright ID")
|
||||
}
|
||||
log.FromContext(ctx).With("publisher_id", publisherID).With("copyright_id", copyrightID).Debug("Adding copyright to publisher")
|
||||
@ -123,8 +125,8 @@ func (c *CopyrightCommands) AddCopyrightToPublisher(ctx context.Context, publish
|
||||
}
|
||||
|
||||
// RemoveCopyrightFromPublisher removes a copyright from a publisher.
|
||||
func (c *CopyrightCommands) RemoveCopyrightFromPublisher(ctx context.Context, publisherID uint, copyrightID uint) error {
|
||||
if publisherID == 0 || copyrightID == 0 {
|
||||
func (c *CopyrightCommands) RemoveCopyrightFromPublisher(ctx context.Context, publisherID uuid.UUID, copyrightID uuid.UUID) error {
|
||||
if publisherID == uuid.Nil || copyrightID == uuid.Nil {
|
||||
return errors.New("invalid publisher ID or copyright ID")
|
||||
}
|
||||
log.FromContext(ctx).With("publisher_id", publisherID).With("copyright_id", copyrightID).Debug("Removing copyright from publisher")
|
||||
@ -132,8 +134,8 @@ func (c *CopyrightCommands) RemoveCopyrightFromPublisher(ctx context.Context, pu
|
||||
}
|
||||
|
||||
// AddCopyrightToSource adds a copyright to a source.
|
||||
func (c *CopyrightCommands) AddCopyrightToSource(ctx context.Context, sourceID uint, copyrightID uint) error {
|
||||
if sourceID == 0 || copyrightID == 0 {
|
||||
func (c *CopyrightCommands) AddCopyrightToSource(ctx context.Context, sourceID uuid.UUID, copyrightID uuid.UUID) error {
|
||||
if sourceID == uuid.Nil || copyrightID == uuid.Nil {
|
||||
return errors.New("invalid source ID or copyright ID")
|
||||
}
|
||||
log.FromContext(ctx).With("source_id", sourceID).With("copyright_id", copyrightID).Debug("Adding copyright to source")
|
||||
@ -141,8 +143,8 @@ func (c *CopyrightCommands) AddCopyrightToSource(ctx context.Context, sourceID u
|
||||
}
|
||||
|
||||
// RemoveCopyrightFromSource removes a copyright from a source.
|
||||
func (c *CopyrightCommands) RemoveCopyrightFromSource(ctx context.Context, sourceID uint, copyrightID uint) error {
|
||||
if sourceID == 0 || copyrightID == 0 {
|
||||
func (c *CopyrightCommands) RemoveCopyrightFromSource(ctx context.Context, sourceID uuid.UUID, copyrightID uuid.UUID) error {
|
||||
if sourceID == uuid.Nil || copyrightID == uuid.Nil {
|
||||
return errors.New("invalid source ID or copyright ID")
|
||||
}
|
||||
log.FromContext(ctx).With("source_id", sourceID).With("copyright_id", copyrightID).Debug("Removing copyright from source")
|
||||
@ -154,8 +156,8 @@ func (c *CopyrightCommands) AddTranslation(ctx context.Context, translation *dom
|
||||
if translation == nil {
|
||||
return errors.New("translation cannot be nil")
|
||||
}
|
||||
if translation.CopyrightID == 0 {
|
||||
return errors.New("copyright ID cannot be zero")
|
||||
if translation.CopyrightID == uuid.Nil {
|
||||
return errors.New("copyright ID cannot be nil")
|
||||
}
|
||||
if translation.LanguageCode == "" {
|
||||
return errors.New("language code cannot be empty")
|
||||
|
||||
@ -4,10 +4,10 @@ package copyright_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"tercul/internal/app/copyright"
|
||||
"tercul/internal/domain"
|
||||
"tercul/internal/testutil"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
@ -7,24 +7,24 @@ import (
|
||||
)
|
||||
|
||||
type mockCopyrightRepository struct {
|
||||
createFunc func(ctx context.Context, copyright *domain.Copyright) error
|
||||
updateFunc func(ctx context.Context, copyright *domain.Copyright) error
|
||||
deleteFunc func(ctx context.Context, id uint) error
|
||||
addCopyrightToWorkFunc func(ctx context.Context, workID uint, copyrightID uint) error
|
||||
removeCopyrightFromWorkFunc func(ctx context.Context, workID uint, copyrightID uint) error
|
||||
addCopyrightToAuthorFunc func(ctx context.Context, authorID uint, copyrightID uint) error
|
||||
removeCopyrightFromAuthorFunc func(ctx context.Context, authorID uint, copyrightID uint) error
|
||||
addCopyrightToBookFunc func(ctx context.Context, bookID uint, copyrightID uint) error
|
||||
removeCopyrightFromBookFunc func(ctx context.Context, bookID uint, copyrightID uint) error
|
||||
addCopyrightToPublisherFunc func(ctx context.Context, publisherID uint, copyrightID uint) error
|
||||
createFunc func(ctx context.Context, copyright *domain.Copyright) error
|
||||
updateFunc func(ctx context.Context, copyright *domain.Copyright) error
|
||||
deleteFunc func(ctx context.Context, id uint) error
|
||||
addCopyrightToWorkFunc func(ctx context.Context, workID uint, copyrightID uint) error
|
||||
removeCopyrightFromWorkFunc func(ctx context.Context, workID uint, copyrightID uint) error
|
||||
addCopyrightToAuthorFunc func(ctx context.Context, authorID uint, copyrightID uint) error
|
||||
removeCopyrightFromAuthorFunc func(ctx context.Context, authorID uint, copyrightID uint) error
|
||||
addCopyrightToBookFunc func(ctx context.Context, bookID uint, copyrightID uint) error
|
||||
removeCopyrightFromBookFunc func(ctx context.Context, bookID uint, copyrightID uint) error
|
||||
addCopyrightToPublisherFunc func(ctx context.Context, publisherID uint, copyrightID uint) error
|
||||
removeCopyrightFromPublisherFunc func(ctx context.Context, publisherID uint, copyrightID uint) error
|
||||
addCopyrightToSourceFunc func(ctx context.Context, sourceID uint, copyrightID uint) error
|
||||
removeCopyrightFromSourceFunc func(ctx context.Context, sourceID uint, copyrightID uint) error
|
||||
addTranslationFunc func(ctx context.Context, translation *domain.CopyrightTranslation) error
|
||||
getByIDFunc func(ctx context.Context, id uint) (*domain.Copyright, error)
|
||||
listAllFunc func(ctx context.Context) ([]domain.Copyright, error)
|
||||
getTranslationsFunc func(ctx context.Context, copyrightID uint) ([]domain.CopyrightTranslation, error)
|
||||
getTranslationByLanguageFunc func(ctx context.Context, copyrightID uint, languageCode string) (*domain.CopyrightTranslation, error)
|
||||
addCopyrightToSourceFunc func(ctx context.Context, sourceID uint, copyrightID uint) error
|
||||
removeCopyrightFromSourceFunc func(ctx context.Context, sourceID uint, copyrightID uint) error
|
||||
addTranslationFunc func(ctx context.Context, translation *domain.CopyrightTranslation) error
|
||||
getByIDFunc func(ctx context.Context, id uint) (*domain.Copyright, error)
|
||||
listAllFunc func(ctx context.Context) ([]domain.Copyright, error)
|
||||
getTranslationsFunc func(ctx context.Context, copyrightID uint) ([]domain.CopyrightTranslation, error)
|
||||
getTranslationByLanguageFunc func(ctx context.Context, copyrightID uint, languageCode string) (*domain.CopyrightTranslation, error)
|
||||
}
|
||||
|
||||
func (m *mockCopyrightRepository) Create(ctx context.Context, copyright *domain.Copyright) error {
|
||||
@ -111,7 +111,7 @@ func (m *mockCopyrightRepository) AddTranslation(ctx context.Context, translatio
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (m *mockCopyrightRepository) GetByID(ctx context.Context, id uint) (*domain.Copyright, error) {
|
||||
func (m *mockCopyrightRepository) GetByID(ctx context.Context, id uuid.UUID) (*domain.Copyright, error) {
|
||||
if m.getByIDFunc != nil {
|
||||
return m.getByIDFunc(ctx, id)
|
||||
}
|
||||
@ -165,7 +165,9 @@ func (m *mockCopyrightRepository) FindWithPreload(ctx context.Context, preloads
|
||||
func (m *mockCopyrightRepository) GetAllForSync(ctx context.Context, batchSize, offset int) ([]domain.Copyright, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (m *mockCopyrightRepository) Exists(ctx context.Context, id uint) (bool, error) { return false, nil }
|
||||
func (m *mockCopyrightRepository) Exists(ctx context.Context, id uint) (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
func (m *mockCopyrightRepository) BeginTx(ctx context.Context) (*gorm.DB, error) { return nil, nil }
|
||||
func (m *mockCopyrightRepository) WithTx(ctx context.Context, fn func(tx *gorm.DB) error) error {
|
||||
return nil
|
||||
@ -182,40 +184,48 @@ func (m *mockWorkRepository) GetByIDWithOptions(ctx context.Context, id uint, op
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
type mockAuthorRepository struct {
|
||||
domain.AuthorRepository
|
||||
getByIDWithOptionsFunc func(ctx context.Context, id uint, options *domain.QueryOptions) (*domain.Author, error)
|
||||
}
|
||||
|
||||
func (m *mockAuthorRepository) GetByIDWithOptions(ctx context.Context, id uint, options *domain.QueryOptions) (*domain.Author, error) {
|
||||
if m.getByIDWithOptionsFunc != nil {
|
||||
return m.getByIDWithOptionsFunc(ctx, id, options)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
type mockBookRepository struct {
|
||||
domain.BookRepository
|
||||
getByIDWithOptionsFunc func(ctx context.Context, id uint, options *domain.QueryOptions) (*domain.Book, error)
|
||||
}
|
||||
|
||||
func (m *mockBookRepository) GetByIDWithOptions(ctx context.Context, id uint, options *domain.QueryOptions) (*domain.Book, error) {
|
||||
if m.getByIDWithOptionsFunc != nil {
|
||||
return m.getByIDWithOptionsFunc(ctx, id, options)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
type mockPublisherRepository struct {
|
||||
domain.PublisherRepository
|
||||
getByIDWithOptionsFunc func(ctx context.Context, id uint, options *domain.QueryOptions) (*domain.Publisher, error)
|
||||
}
|
||||
|
||||
func (m *mockPublisherRepository) GetByIDWithOptions(ctx context.Context, id uint, options *domain.QueryOptions) (*domain.Publisher, error) {
|
||||
if m.getByIDWithOptionsFunc != nil {
|
||||
return m.getByIDWithOptionsFunc(ctx, id, options)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
type mockSourceRepository struct {
|
||||
domain.SourceRepository
|
||||
getByIDWithOptionsFunc func(ctx context.Context, id uint, options *domain.QueryOptions) (*domain.Source, error)
|
||||
}
|
||||
|
||||
func (m *mockSourceRepository) GetByIDWithOptions(ctx context.Context, id uint, options *domain.QueryOptions) (*domain.Source, error) {
|
||||
if m.getByIDWithOptionsFunc != nil {
|
||||
return m.getByIDWithOptionsFunc(ctx, id, options)
|
||||
|
||||
@ -5,6 +5,8 @@ import (
|
||||
"errors"
|
||||
"tercul/internal/domain"
|
||||
"tercul/internal/platform/log"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// CopyrightQueries contains the query handlers for copyright.
|
||||
@ -23,8 +25,8 @@ func NewCopyrightQueries(repo domain.CopyrightRepository, workRepo domain.WorkRe
|
||||
}
|
||||
|
||||
// GetCopyrightByID retrieves a copyright by ID.
|
||||
func (q *CopyrightQueries) GetCopyrightByID(ctx context.Context, id uint) (*domain.Copyright, error) {
|
||||
if id == 0 {
|
||||
func (q *CopyrightQueries) GetCopyrightByID(ctx context.Context, id uuid.UUID) (*domain.Copyright, error) {
|
||||
if id == uuid.Nil {
|
||||
return nil, errors.New("invalid copyright ID")
|
||||
}
|
||||
log.FromContext(ctx).With("id", id).Debug("Getting copyright by ID")
|
||||
@ -40,7 +42,7 @@ func (q *CopyrightQueries) ListCopyrights(ctx context.Context) ([]domain.Copyrig
|
||||
}
|
||||
|
||||
// GetCopyrightsForWork gets all copyrights for a specific work.
|
||||
func (q *CopyrightQueries) GetCopyrightsForWork(ctx context.Context, workID uint) ([]*domain.Copyright, error) {
|
||||
func (q *CopyrightQueries) GetCopyrightsForWork(ctx context.Context, workID uuid.UUID) ([]*domain.Copyright, error) {
|
||||
log.FromContext(ctx).With("work_id", workID).Debug("Getting copyrights for work")
|
||||
workRecord, err := q.workRepo.GetByIDWithOptions(ctx, workID, &domain.QueryOptions{Preloads: []string{"Copyrights"}})
|
||||
if err != nil {
|
||||
@ -50,7 +52,7 @@ func (q *CopyrightQueries) GetCopyrightsForWork(ctx context.Context, workID uint
|
||||
}
|
||||
|
||||
// GetCopyrightsForAuthor gets all copyrights for a specific author.
|
||||
func (q *CopyrightQueries) GetCopyrightsForAuthor(ctx context.Context, authorID uint) ([]*domain.Copyright, error) {
|
||||
func (q *CopyrightQueries) GetCopyrightsForAuthor(ctx context.Context, authorID uuid.UUID) ([]*domain.Copyright, error) {
|
||||
log.FromContext(ctx).With("author_id", authorID).Debug("Getting copyrights for author")
|
||||
author, err := q.authorRepo.GetByIDWithOptions(ctx, authorID, &domain.QueryOptions{Preloads: []string{"Copyrights"}})
|
||||
if err != nil {
|
||||
@ -60,7 +62,7 @@ func (q *CopyrightQueries) GetCopyrightsForAuthor(ctx context.Context, authorID
|
||||
}
|
||||
|
||||
// GetCopyrightsForBook gets all copyrights for a specific book.
|
||||
func (q *CopyrightQueries) GetCopyrightsForBook(ctx context.Context, bookID uint) ([]*domain.Copyright, error) {
|
||||
func (q *CopyrightQueries) GetCopyrightsForBook(ctx context.Context, bookID uuid.UUID) ([]*domain.Copyright, error) {
|
||||
log.FromContext(ctx).With("book_id", bookID).Debug("Getting copyrights for book")
|
||||
book, err := q.bookRepo.GetByIDWithOptions(ctx, bookID, &domain.QueryOptions{Preloads: []string{"Copyrights"}})
|
||||
if err != nil {
|
||||
@ -70,7 +72,7 @@ func (q *CopyrightQueries) GetCopyrightsForBook(ctx context.Context, bookID uint
|
||||
}
|
||||
|
||||
// GetCopyrightsForPublisher gets all copyrights for a specific publisher.
|
||||
func (q *CopyrightQueries) GetCopyrightsForPublisher(ctx context.Context, publisherID uint) ([]*domain.Copyright, error) {
|
||||
func (q *CopyrightQueries) GetCopyrightsForPublisher(ctx context.Context, publisherID uuid.UUID) ([]*domain.Copyright, error) {
|
||||
log.FromContext(ctx).With("publisher_id", publisherID).Debug("Getting copyrights for publisher")
|
||||
publisher, err := q.publisherRepo.GetByIDWithOptions(ctx, publisherID, &domain.QueryOptions{Preloads: []string{"Copyrights"}})
|
||||
if err != nil {
|
||||
@ -80,7 +82,7 @@ func (q *CopyrightQueries) GetCopyrightsForPublisher(ctx context.Context, publis
|
||||
}
|
||||
|
||||
// GetCopyrightsForSource gets all copyrights for a specific source.
|
||||
func (q *CopyrightQueries) GetCopyrightsForSource(ctx context.Context, sourceID uint) ([]*domain.Copyright, error) {
|
||||
func (q *CopyrightQueries) GetCopyrightsForSource(ctx context.Context, sourceID uuid.UUID) ([]*domain.Copyright, error) {
|
||||
log.FromContext(ctx).With("source_id", sourceID).Debug("Getting copyrights for source")
|
||||
source, err := q.sourceRepo.GetByIDWithOptions(ctx, sourceID, &domain.QueryOptions{Preloads: []string{"Copyrights"}})
|
||||
if err != nil {
|
||||
@ -90,8 +92,8 @@ func (q *CopyrightQueries) GetCopyrightsForSource(ctx context.Context, sourceID
|
||||
}
|
||||
|
||||
// GetTranslations gets all translations for a copyright.
|
||||
func (q *CopyrightQueries) GetTranslations(ctx context.Context, copyrightID uint) ([]domain.CopyrightTranslation, error) {
|
||||
if copyrightID == 0 {
|
||||
func (q *CopyrightQueries) GetTranslations(ctx context.Context, copyrightID uuid.UUID) ([]domain.CopyrightTranslation, error) {
|
||||
if copyrightID == uuid.Nil {
|
||||
return nil, errors.New("invalid copyright ID")
|
||||
}
|
||||
log.FromContext(ctx).With("copyright_id", copyrightID).Debug("Getting translations for copyright")
|
||||
@ -99,8 +101,8 @@ func (q *CopyrightQueries) GetTranslations(ctx context.Context, copyrightID uint
|
||||
}
|
||||
|
||||
// GetTranslationByLanguage gets a specific translation by language code.
|
||||
func (q *CopyrightQueries) GetTranslationByLanguage(ctx context.Context, copyrightID uint, languageCode string) (*domain.CopyrightTranslation, error) {
|
||||
if copyrightID == 0 {
|
||||
func (q *CopyrightQueries) GetTranslationByLanguage(ctx context.Context, copyrightID uuid.UUID, languageCode string) (*domain.CopyrightTranslation, error) {
|
||||
if copyrightID == uuid.Nil {
|
||||
return nil, errors.New("invalid copyright ID")
|
||||
}
|
||||
if languageCode == "" {
|
||||
|
||||
@ -6,6 +6,8 @@ import (
|
||||
"tercul/internal/app/analytics"
|
||||
"tercul/internal/domain"
|
||||
"tercul/internal/platform/log"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// LikeCommands contains the command handlers for the like aggregate.
|
||||
@ -24,10 +26,10 @@ func NewLikeCommands(repo domain.LikeRepository, analyticsSvc analytics.Service)
|
||||
|
||||
// CreateLikeInput represents the input for creating a new like.
|
||||
type CreateLikeInput struct {
|
||||
UserID uint
|
||||
WorkID *uint
|
||||
TranslationID *uint
|
||||
CommentID *uint
|
||||
UserID uuid.UUID
|
||||
WorkID *uuid.UUID
|
||||
TranslationID *uuid.UUID
|
||||
CommentID *uuid.UUID
|
||||
}
|
||||
|
||||
// CreateLike creates a new like and increments the relevant counter.
|
||||
@ -69,7 +71,7 @@ func (c *LikeCommands) CreateLike(ctx context.Context, input CreateLikeInput) (*
|
||||
}
|
||||
|
||||
// DeleteLike deletes a like by ID and decrements the relevant counter.
|
||||
func (c *LikeCommands) DeleteLike(ctx context.Context, id uint) error {
|
||||
func (c *LikeCommands) DeleteLike(ctx context.Context, id uuid.UUID) error {
|
||||
// First, get the like to determine what it was attached to.
|
||||
like, err := c.repo.GetByID(ctx, id)
|
||||
if err != nil {
|
||||
|
||||
@ -3,6 +3,8 @@ package like
|
||||
import (
|
||||
"context"
|
||||
"tercul/internal/domain"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// LikeQueries contains the query handlers for the like aggregate.
|
||||
@ -16,27 +18,27 @@ func NewLikeQueries(repo domain.LikeRepository) *LikeQueries {
|
||||
}
|
||||
|
||||
// Like returns a like by ID.
|
||||
func (q *LikeQueries) Like(ctx context.Context, id uint) (*domain.Like, error) {
|
||||
func (q *LikeQueries) Like(ctx context.Context, id uuid.UUID) (*domain.Like, error) {
|
||||
return q.repo.GetByID(ctx, id)
|
||||
}
|
||||
|
||||
// LikesByUserID returns all likes for a user.
|
||||
func (q *LikeQueries) LikesByUserID(ctx context.Context, userID uint) ([]domain.Like, error) {
|
||||
func (q *LikeQueries) LikesByUserID(ctx context.Context, userID uuid.UUID) ([]domain.Like, error) {
|
||||
return q.repo.ListByUserID(ctx, userID)
|
||||
}
|
||||
|
||||
// LikesByWorkID returns all likes for a work.
|
||||
func (q *LikeQueries) LikesByWorkID(ctx context.Context, workID uint) ([]domain.Like, error) {
|
||||
func (q *LikeQueries) LikesByWorkID(ctx context.Context, workID uuid.UUID) ([]domain.Like, error) {
|
||||
return q.repo.ListByWorkID(ctx, workID)
|
||||
}
|
||||
|
||||
// LikesByTranslationID returns all likes for a translation.
|
||||
func (q *LikeQueries) LikesByTranslationID(ctx context.Context, translationID uint) ([]domain.Like, error) {
|
||||
func (q *LikeQueries) LikesByTranslationID(ctx context.Context, translationID uuid.UUID) ([]domain.Like, error) {
|
||||
return q.repo.ListByTranslationID(ctx, translationID)
|
||||
}
|
||||
|
||||
// LikesByCommentID returns all likes for a comment.
|
||||
func (q *LikeQueries) LikesByCommentID(ctx context.Context, commentID uint) ([]domain.Like, error) {
|
||||
func (q *LikeQueries) LikesByCommentID(ctx context.Context, commentID uuid.UUID) ([]domain.Like, error) {
|
||||
return q.repo.ListByCommentID(ctx, commentID)
|
||||
}
|
||||
|
||||
|
||||
@ -10,4 +10,4 @@ type LocalizationCommands struct {
|
||||
// NewLocalizationCommands creates a new LocalizationCommands handler.
|
||||
func NewLocalizationCommands(repo domain.LocalizationRepository) *LocalizationCommands {
|
||||
return &LocalizationCommands{repo: repo}
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,6 +3,8 @@ package localization
|
||||
import (
|
||||
"context"
|
||||
"tercul/internal/domain"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// LocalizationQueries contains the query handlers for the localization aggregate.
|
||||
@ -26,11 +28,11 @@ func (q *LocalizationQueries) GetTranslations(ctx context.Context, keys []string
|
||||
}
|
||||
|
||||
// GetAuthorBiography returns the biography of an author in a specific language.
|
||||
func (q *LocalizationQueries) GetAuthorBiography(ctx context.Context, authorID uint, language string) (string, error) {
|
||||
func (q *LocalizationQueries) GetAuthorBiography(ctx context.Context, authorID uuid.UUID, language string) (string, error) {
|
||||
return q.repo.GetAuthorBiography(ctx, authorID, language)
|
||||
}
|
||||
|
||||
// GetWorkContent returns the content of a work in a specific language.
|
||||
func (q *LocalizationQueries) GetWorkContent(ctx context.Context, workID uint, language string) (string, error) {
|
||||
func (q *LocalizationQueries) GetWorkContent(ctx context.Context, workID uuid.UUID, language string) (string, error) {
|
||||
return q.repo.GetWorkContent(ctx, workID, language)
|
||||
}
|
||||
}
|
||||
|
||||
@ -14,4 +14,4 @@ func NewService(repo domain.LocalizationRepository) *Service {
|
||||
Commands: NewLocalizationCommands(repo),
|
||||
Queries: NewLocalizationQueries(repo),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -90,4 +90,4 @@ func TestLocalizationService_GetAuthorBiography(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, expectedBiography, biography)
|
||||
repo.AssertExpectations(t)
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,6 +5,8 @@ import (
|
||||
"errors"
|
||||
"tercul/internal/domain"
|
||||
"tercul/internal/platform/log"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// MonetizationCommands contains the command handlers for monetization.
|
||||
@ -18,8 +20,8 @@ func NewMonetizationCommands(repo domain.MonetizationRepository) *MonetizationCo
|
||||
}
|
||||
|
||||
// AddMonetizationToWork adds a monetization to a work.
|
||||
func (c *MonetizationCommands) AddMonetizationToWork(ctx context.Context, workID uint, monetizationID uint) error {
|
||||
if workID == 0 || monetizationID == 0 {
|
||||
func (c *MonetizationCommands) AddMonetizationToWork(ctx context.Context, workID uuid.UUID, monetizationID uuid.UUID) error {
|
||||
if workID == uuid.Nil || monetizationID == uuid.Nil {
|
||||
return errors.New("invalid work ID or monetization ID")
|
||||
}
|
||||
log.FromContext(ctx).With("work_id", workID).With("monetization_id", monetizationID).Debug("Adding monetization to work")
|
||||
@ -27,72 +29,72 @@ func (c *MonetizationCommands) AddMonetizationToWork(ctx context.Context, workID
|
||||
}
|
||||
|
||||
// RemoveMonetizationFromWork removes a monetization from a work.
|
||||
func (c *MonetizationCommands) RemoveMonetizationFromWork(ctx context.Context, workID uint, monetizationID uint) error {
|
||||
if workID == 0 || monetizationID == 0 {
|
||||
func (c *MonetizationCommands) RemoveMonetizationFromWork(ctx context.Context, workID uuid.UUID, monetizationID uuid.UUID) error {
|
||||
if workID == uuid.Nil || monetizationID == uuid.Nil {
|
||||
return errors.New("invalid work ID or monetization ID")
|
||||
}
|
||||
log.FromContext(ctx).With("work_id", workID).With("monetization_id", monetizationID).Debug("Removing monetization from work")
|
||||
return c.repo.RemoveMonetizationFromWork(ctx, workID, monetizationID)
|
||||
}
|
||||
|
||||
func (c *MonetizationCommands) AddMonetizationToAuthor(ctx context.Context, authorID uint, monetizationID uint) error {
|
||||
if authorID == 0 || monetizationID == 0 {
|
||||
func (c *MonetizationCommands) AddMonetizationToAuthor(ctx context.Context, authorID uuid.UUID, monetizationID uuid.UUID) error {
|
||||
if authorID == uuid.Nil || monetizationID == uuid.Nil {
|
||||
return errors.New("invalid author ID or monetization ID")
|
||||
}
|
||||
log.FromContext(ctx).With("author_id", authorID).With("monetization_id", monetizationID).Debug("Adding monetization to author")
|
||||
return c.repo.AddMonetizationToAuthor(ctx, authorID, monetizationID)
|
||||
}
|
||||
|
||||
func (c *MonetizationCommands) RemoveMonetizationFromAuthor(ctx context.Context, authorID uint, monetizationID uint) error {
|
||||
if authorID == 0 || monetizationID == 0 {
|
||||
func (c *MonetizationCommands) RemoveMonetizationFromAuthor(ctx context.Context, authorID uuid.UUID, monetizationID uuid.UUID) error {
|
||||
if authorID == uuid.Nil || monetizationID == uuid.Nil {
|
||||
return errors.New("invalid author ID or monetization ID")
|
||||
}
|
||||
log.FromContext(ctx).With("author_id", authorID).With("monetization_id", monetizationID).Debug("Removing monetization from author")
|
||||
return c.repo.RemoveMonetizationFromAuthor(ctx, authorID, monetizationID)
|
||||
}
|
||||
|
||||
func (c *MonetizationCommands) AddMonetizationToBook(ctx context.Context, bookID uint, monetizationID uint) error {
|
||||
if bookID == 0 || monetizationID == 0 {
|
||||
func (c *MonetizationCommands) AddMonetizationToBook(ctx context.Context, bookID uuid.UUID, monetizationID uuid.UUID) error {
|
||||
if bookID == uuid.Nil || monetizationID == uuid.Nil {
|
||||
return errors.New("invalid book ID or monetization ID")
|
||||
}
|
||||
log.FromContext(ctx).With("book_id", bookID).With("monetization_id", monetizationID).Debug("Adding monetization to book")
|
||||
return c.repo.AddMonetizationToBook(ctx, bookID, monetizationID)
|
||||
}
|
||||
|
||||
func (c *MonetizationCommands) RemoveMonetizationFromBook(ctx context.Context, bookID uint, monetizationID uint) error {
|
||||
if bookID == 0 || monetizationID == 0 {
|
||||
func (c *MonetizationCommands) RemoveMonetizationFromBook(ctx context.Context, bookID uuid.UUID, monetizationID uuid.UUID) error {
|
||||
if bookID == uuid.Nil || monetizationID == uuid.Nil {
|
||||
return errors.New("invalid book ID or monetization ID")
|
||||
}
|
||||
log.FromContext(ctx).With("book_id", bookID).With("monetization_id", monetizationID).Debug("Removing monetization from book")
|
||||
return c.repo.RemoveMonetizationFromBook(ctx, bookID, monetizationID)
|
||||
}
|
||||
|
||||
func (c *MonetizationCommands) AddMonetizationToPublisher(ctx context.Context, publisherID uint, monetizationID uint) error {
|
||||
if publisherID == 0 || monetizationID == 0 {
|
||||
func (c *MonetizationCommands) AddMonetizationToPublisher(ctx context.Context, publisherID uuid.UUID, monetizationID uuid.UUID) error {
|
||||
if publisherID == uuid.Nil || monetizationID == uuid.Nil {
|
||||
return errors.New("invalid publisher ID or monetization ID")
|
||||
}
|
||||
log.FromContext(ctx).With("publisher_id", publisherID).With("monetization_id", monetizationID).Debug("Adding monetization to publisher")
|
||||
return c.repo.AddMonetizationToPublisher(ctx, publisherID, monetizationID)
|
||||
}
|
||||
|
||||
func (c *MonetizationCommands) RemoveMonetizationFromPublisher(ctx context.Context, publisherID uint, monetizationID uint) error {
|
||||
if publisherID == 0 || monetizationID == 0 {
|
||||
func (c *MonetizationCommands) RemoveMonetizationFromPublisher(ctx context.Context, publisherID uuid.UUID, monetizationID uuid.UUID) error {
|
||||
if publisherID == uuid.Nil || monetizationID == uuid.Nil {
|
||||
return errors.New("invalid publisher ID or monetization ID")
|
||||
}
|
||||
log.FromContext(ctx).With("publisher_id", publisherID).With("monetization_id", monetizationID).Debug("Removing monetization from publisher")
|
||||
return c.repo.RemoveMonetizationFromPublisher(ctx, publisherID, monetizationID)
|
||||
}
|
||||
|
||||
func (c *MonetizationCommands) AddMonetizationToSource(ctx context.Context, sourceID uint, monetizationID uint) error {
|
||||
if sourceID == 0 || monetizationID == 0 {
|
||||
func (c *MonetizationCommands) AddMonetizationToSource(ctx context.Context, sourceID uuid.UUID, monetizationID uuid.UUID) error {
|
||||
if sourceID == uuid.Nil || monetizationID == uuid.Nil {
|
||||
return errors.New("invalid source ID or monetization ID")
|
||||
}
|
||||
log.FromContext(ctx).With("source_id", sourceID).With("monetization_id", monetizationID).Debug("Adding monetization to source")
|
||||
return c.repo.AddMonetizationToSource(ctx, sourceID, monetizationID)
|
||||
}
|
||||
|
||||
func (c *MonetizationCommands) RemoveMonetizationFromSource(ctx context.Context, sourceID uint, monetizationID uint) error {
|
||||
if sourceID == 0 || monetizationID == 0 {
|
||||
func (c *MonetizationCommands) RemoveMonetizationFromSource(ctx context.Context, sourceID uuid.UUID, monetizationID uuid.UUID) error {
|
||||
if sourceID == uuid.Nil || monetizationID == uuid.Nil {
|
||||
return errors.New("invalid source ID or monetization ID")
|
||||
}
|
||||
log.FromContext(ctx).With("source_id", sourceID).With("monetization_id", monetizationID).Debug("Removing monetization from source")
|
||||
|
||||
@ -4,10 +4,10 @@ package monetization_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"tercul/internal/app/monetization"
|
||||
"tercul/internal/domain"
|
||||
"tercul/internal/testutil"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
@ -7,21 +7,21 @@ import (
|
||||
|
||||
type mockMonetizationRepository struct {
|
||||
domain.MonetizationRepository
|
||||
addMonetizationToWorkFunc func(ctx context.Context, workID uint, monetizationID uint) error
|
||||
removeMonetizationFromWorkFunc func(ctx context.Context, workID uint, monetizationID uint) error
|
||||
addMonetizationToAuthorFunc func(ctx context.Context, authorID uint, monetizationID uint) error
|
||||
removeMonetizationFromAuthorFunc func(ctx context.Context, authorID uint, monetizationID uint) error
|
||||
addMonetizationToBookFunc func(ctx context.Context, bookID uint, monetizationID uint) error
|
||||
removeMonetizationFromBookFunc func(ctx context.Context, bookID uint, monetizationID uint) error
|
||||
addMonetizationToPublisherFunc func(ctx context.Context, publisherID uint, monetizationID uint) error
|
||||
addMonetizationToWorkFunc func(ctx context.Context, workID uint, monetizationID uint) error
|
||||
removeMonetizationFromWorkFunc func(ctx context.Context, workID uint, monetizationID uint) error
|
||||
addMonetizationToAuthorFunc func(ctx context.Context, authorID uint, monetizationID uint) error
|
||||
removeMonetizationFromAuthorFunc func(ctx context.Context, authorID uint, monetizationID uint) error
|
||||
addMonetizationToBookFunc func(ctx context.Context, bookID uint, monetizationID uint) error
|
||||
removeMonetizationFromBookFunc func(ctx context.Context, bookID uint, monetizationID uint) error
|
||||
addMonetizationToPublisherFunc func(ctx context.Context, publisherID uint, monetizationID uint) error
|
||||
removeMonetizationFromPublisherFunc func(ctx context.Context, publisherID uint, monetizationID uint) error
|
||||
addMonetizationToSourceFunc func(ctx context.Context, sourceID uint, monetizationID uint) error
|
||||
removeMonetizationFromSourceFunc func(ctx context.Context, sourceID uint, monetizationID uint) error
|
||||
getByIDFunc func(ctx context.Context, id uint) (*domain.Monetization, error)
|
||||
listAllFunc func(ctx context.Context) ([]domain.Monetization, error)
|
||||
addMonetizationToSourceFunc func(ctx context.Context, sourceID uint, monetizationID uint) error
|
||||
removeMonetizationFromSourceFunc func(ctx context.Context, sourceID uint, monetizationID uint) error
|
||||
getByIDFunc func(ctx context.Context, id uint) (*domain.Monetization, error)
|
||||
listAllFunc func(ctx context.Context) ([]domain.Monetization, error)
|
||||
}
|
||||
|
||||
func (m *mockMonetizationRepository) GetByID(ctx context.Context, id uint) (*domain.Monetization, error) {
|
||||
func (m *mockMonetizationRepository) GetByID(ctx context.Context, id uuid.UUID) (*domain.Monetization, error) {
|
||||
if m.getByIDFunc != nil {
|
||||
return m.getByIDFunc(ctx, id)
|
||||
}
|
||||
@ -107,40 +107,48 @@ func (m *mockWorkRepository) GetByIDWithOptions(ctx context.Context, id uint, op
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
type mockAuthorRepository struct {
|
||||
domain.AuthorRepository
|
||||
getByIDWithOptionsFunc func(ctx context.Context, id uint, options *domain.QueryOptions) (*domain.Author, error)
|
||||
}
|
||||
|
||||
func (m *mockAuthorRepository) GetByIDWithOptions(ctx context.Context, id uint, options *domain.QueryOptions) (*domain.Author, error) {
|
||||
if m.getByIDWithOptionsFunc != nil {
|
||||
return m.getByIDWithOptionsFunc(ctx, id, options)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
type mockBookRepository struct {
|
||||
domain.BookRepository
|
||||
getByIDWithOptionsFunc func(ctx context.Context, id uint, options *domain.QueryOptions) (*domain.Book, error)
|
||||
}
|
||||
|
||||
func (m *mockBookRepository) GetByIDWithOptions(ctx context.Context, id uint, options *domain.QueryOptions) (*domain.Book, error) {
|
||||
if m.getByIDWithOptionsFunc != nil {
|
||||
return m.getByIDWithOptionsFunc(ctx, id, options)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
type mockPublisherRepository struct {
|
||||
domain.PublisherRepository
|
||||
getByIDWithOptionsFunc func(ctx context.Context, id uint, options *domain.QueryOptions) (*domain.Publisher, error)
|
||||
}
|
||||
|
||||
func (m *mockPublisherRepository) GetByIDWithOptions(ctx context.Context, id uint, options *domain.QueryOptions) (*domain.Publisher, error) {
|
||||
if m.getByIDWithOptionsFunc != nil {
|
||||
return m.getByIDWithOptionsFunc(ctx, id, options)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
type mockSourceRepository struct {
|
||||
domain.SourceRepository
|
||||
getByIDWithOptionsFunc func(ctx context.Context, id uint, options *domain.QueryOptions) (*domain.Source, error)
|
||||
}
|
||||
|
||||
func (m *mockSourceRepository) GetByIDWithOptions(ctx context.Context, id uint, options *domain.QueryOptions) (*domain.Source, error) {
|
||||
if m.getByIDWithOptionsFunc != nil {
|
||||
return m.getByIDWithOptionsFunc(ctx, id, options)
|
||||
|
||||
@ -5,6 +5,8 @@ import (
|
||||
"errors"
|
||||
"tercul/internal/domain"
|
||||
"tercul/internal/platform/log"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// MonetizationQueries contains the query handlers for monetization.
|
||||
@ -23,8 +25,8 @@ func NewMonetizationQueries(repo domain.MonetizationRepository, workRepo domain.
|
||||
}
|
||||
|
||||
// GetMonetizationByID retrieves a monetization by ID.
|
||||
func (q *MonetizationQueries) GetMonetizationByID(ctx context.Context, id uint) (*domain.Monetization, error) {
|
||||
if id == 0 {
|
||||
func (q *MonetizationQueries) GetMonetizationByID(ctx context.Context, id uuid.UUID) (*domain.Monetization, error) {
|
||||
if id == uuid.Nil {
|
||||
return nil, errors.New("invalid monetization ID")
|
||||
}
|
||||
log.FromContext(ctx).With("id", id).Debug("Getting monetization by ID")
|
||||
@ -37,7 +39,7 @@ func (q *MonetizationQueries) ListMonetizations(ctx context.Context) ([]domain.M
|
||||
return q.repo.ListAll(ctx)
|
||||
}
|
||||
|
||||
func (q *MonetizationQueries) GetMonetizationsForWork(ctx context.Context, workID uint) ([]*domain.Monetization, error) {
|
||||
func (q *MonetizationQueries) GetMonetizationsForWork(ctx context.Context, workID uuid.UUID) ([]*domain.Monetization, error) {
|
||||
log.FromContext(ctx).With("work_id", workID).Debug("Getting monetizations for work")
|
||||
workRecord, err := q.workRepo.GetByIDWithOptions(ctx, workID, &domain.QueryOptions{Preloads: []string{"Monetizations"}})
|
||||
if err != nil {
|
||||
@ -46,7 +48,7 @@ func (q *MonetizationQueries) GetMonetizationsForWork(ctx context.Context, workI
|
||||
return workRecord.Monetizations, nil
|
||||
}
|
||||
|
||||
func (q *MonetizationQueries) GetMonetizationsForAuthor(ctx context.Context, authorID uint) ([]*domain.Monetization, error) {
|
||||
func (q *MonetizationQueries) GetMonetizationsForAuthor(ctx context.Context, authorID uuid.UUID) ([]*domain.Monetization, error) {
|
||||
log.FromContext(ctx).With("author_id", authorID).Debug("Getting monetizations for author")
|
||||
author, err := q.authorRepo.GetByIDWithOptions(ctx, authorID, &domain.QueryOptions{Preloads: []string{"Monetizations"}})
|
||||
if err != nil {
|
||||
@ -55,7 +57,7 @@ func (q *MonetizationQueries) GetMonetizationsForAuthor(ctx context.Context, aut
|
||||
return author.Monetizations, nil
|
||||
}
|
||||
|
||||
func (q *MonetizationQueries) GetMonetizationsForBook(ctx context.Context, bookID uint) ([]*domain.Monetization, error) {
|
||||
func (q *MonetizationQueries) GetMonetizationsForBook(ctx context.Context, bookID uuid.UUID) ([]*domain.Monetization, error) {
|
||||
log.FromContext(ctx).With("book_id", bookID).Debug("Getting monetizations for book")
|
||||
book, err := q.bookRepo.GetByIDWithOptions(ctx, bookID, &domain.QueryOptions{Preloads: []string{"Monetizations"}})
|
||||
if err != nil {
|
||||
@ -64,7 +66,7 @@ func (q *MonetizationQueries) GetMonetizationsForBook(ctx context.Context, bookI
|
||||
return book.Monetizations, nil
|
||||
}
|
||||
|
||||
func (q *MonetizationQueries) GetMonetizationsForPublisher(ctx context.Context, publisherID uint) ([]*domain.Monetization, error) {
|
||||
func (q *MonetizationQueries) GetMonetizationsForPublisher(ctx context.Context, publisherID uuid.UUID) ([]*domain.Monetization, error) {
|
||||
log.FromContext(ctx).With("publisher_id", publisherID).Debug("Getting monetizations for publisher")
|
||||
publisher, err := q.publisherRepo.GetByIDWithOptions(ctx, publisherID, &domain.QueryOptions{Preloads: []string{"Monetizations"}})
|
||||
if err != nil {
|
||||
@ -73,7 +75,7 @@ func (q *MonetizationQueries) GetMonetizationsForPublisher(ctx context.Context,
|
||||
return publisher.Monetizations, nil
|
||||
}
|
||||
|
||||
func (q *MonetizationQueries) GetMonetizationsForSource(ctx context.Context, sourceID uint) ([]*domain.Monetization, error) {
|
||||
func (q *MonetizationQueries) GetMonetizationsForSource(ctx context.Context, sourceID uuid.UUID) ([]*domain.Monetization, error) {
|
||||
log.FromContext(ctx).With("source_id", sourceID).Debug("Getting monetizations for source")
|
||||
source, err := q.sourceRepo.GetByIDWithOptions(ctx, sourceID, &domain.QueryOptions{Preloads: []string{"Monetizations"}})
|
||||
if err != nil {
|
||||
|
||||
@ -52,4 +52,4 @@ func (s *service) IndexWork(ctx context.Context, work domain.Work) error {
|
||||
}
|
||||
logger.Info("Successfully indexed work")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@ -83,7 +83,7 @@ func TestSearchService_Search(t *testing.T) {
|
||||
Query: testQuery,
|
||||
Filters: testFilters,
|
||||
Limit: 10,
|
||||
Offset: 0,
|
||||
Offset: 0,
|
||||
}
|
||||
weaviateWrapper.On("Search", ctx, params).Return(expectedResults, nil)
|
||||
|
||||
|
||||
@ -3,6 +3,8 @@ package tag
|
||||
import (
|
||||
"context"
|
||||
"tercul/internal/domain"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// TagCommands contains the command handlers for the tag aggregate.
|
||||
@ -36,7 +38,7 @@ func (c *TagCommands) CreateTag(ctx context.Context, input CreateTagInput) (*dom
|
||||
|
||||
// UpdateTagInput represents the input for updating an existing tag.
|
||||
type UpdateTagInput struct {
|
||||
ID uint
|
||||
ID uuid.UUID
|
||||
Name string
|
||||
Description string
|
||||
}
|
||||
@ -57,6 +59,6 @@ func (c *TagCommands) UpdateTag(ctx context.Context, input UpdateTagInput) (*dom
|
||||
}
|
||||
|
||||
// DeleteTag deletes a tag by ID.
|
||||
func (c *TagCommands) DeleteTag(ctx context.Context, id uint) error {
|
||||
func (c *TagCommands) DeleteTag(ctx context.Context, id uuid.UUID) error {
|
||||
return c.repo.Delete(ctx, id)
|
||||
}
|
||||
|
||||
@ -3,6 +3,8 @@ package tag
|
||||
import (
|
||||
"context"
|
||||
"tercul/internal/domain"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// TagQueries contains the query handlers for the tag aggregate.
|
||||
@ -16,7 +18,7 @@ func NewTagQueries(repo domain.TagRepository) *TagQueries {
|
||||
}
|
||||
|
||||
// Tag returns a tag by ID.
|
||||
func (q *TagQueries) Tag(ctx context.Context, id uint) (*domain.Tag, error) {
|
||||
func (q *TagQueries) Tag(ctx context.Context, id uuid.UUID) (*domain.Tag, error) {
|
||||
return q.repo.GetByID(ctx, id)
|
||||
}
|
||||
|
||||
@ -26,7 +28,7 @@ func (q *TagQueries) TagByName(ctx context.Context, name string) (*domain.Tag, e
|
||||
}
|
||||
|
||||
// TagsByWorkID returns all tags for a work.
|
||||
func (q *TagQueries) TagsByWorkID(ctx context.Context, workID uint) ([]domain.Tag, error) {
|
||||
func (q *TagQueries) TagsByWorkID(ctx context.Context, workID uuid.UUID) ([]domain.Tag, error) {
|
||||
return q.repo.ListByWorkID(ctx, workID)
|
||||
}
|
||||
|
||||
|
||||
@ -7,6 +7,7 @@ import (
|
||||
"tercul/internal/domain"
|
||||
platform_auth "tercul/internal/platform/auth"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"go.opentelemetry.io/otel"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
@ -34,9 +35,9 @@ type CreateOrUpdateTranslationInput struct {
|
||||
Description string
|
||||
Language string
|
||||
Status domain.TranslationStatus
|
||||
TranslatableID uint
|
||||
TranslatableID uuid.UUID
|
||||
TranslatableType string
|
||||
TranslatorID *uint
|
||||
TranslatorID *uuid.UUID
|
||||
IsOriginalLanguage bool
|
||||
}
|
||||
|
||||
@ -49,8 +50,8 @@ func (c *TranslationCommands) CreateOrUpdateTranslation(ctx context.Context, inp
|
||||
if input.Language == "" {
|
||||
return nil, fmt.Errorf("%w: language cannot be empty", domain.ErrValidation)
|
||||
}
|
||||
if input.TranslatableID == 0 {
|
||||
return nil, fmt.Errorf("%w: translatable ID cannot be zero", domain.ErrValidation)
|
||||
if input.TranslatableID == uuid.Nil {
|
||||
return nil, fmt.Errorf("%w: translatable ID cannot be nil", domain.ErrValidation)
|
||||
}
|
||||
if input.TranslatableType == "" {
|
||||
return nil, fmt.Errorf("%w: translatable type cannot be empty", domain.ErrValidation)
|
||||
@ -70,7 +71,7 @@ func (c *TranslationCommands) CreateOrUpdateTranslation(ctx context.Context, inp
|
||||
return nil, domain.ErrForbidden
|
||||
}
|
||||
|
||||
var translatorID uint
|
||||
var translatorID uuid.UUID
|
||||
if input.TranslatorID != nil {
|
||||
translatorID = *input.TranslatorID
|
||||
} else {
|
||||
@ -97,7 +98,7 @@ func (c *TranslationCommands) CreateOrUpdateTranslation(ctx context.Context, inp
|
||||
}
|
||||
|
||||
// DeleteTranslation deletes a translation by ID.
|
||||
func (c *TranslationCommands) DeleteTranslation(ctx context.Context, id uint) error {
|
||||
func (c *TranslationCommands) DeleteTranslation(ctx context.Context, id uuid.UUID) error {
|
||||
ctx, span := c.tracer.Start(ctx, "DeleteTranslation")
|
||||
defer span.End()
|
||||
|
||||
|
||||
@ -27,7 +27,7 @@ func (m *mockAuthorRepository) CreateInTx(ctx context.Context, tx *gorm.DB, enti
|
||||
args := m.Called(ctx, tx, entity)
|
||||
return args.Error(0)
|
||||
}
|
||||
func (m *mockAuthorRepository) GetByID(ctx context.Context, id uint) (*domain.Author, error) {
|
||||
func (m *mockAuthorRepository) GetByID(ctx context.Context, id uuid.UUID) (*domain.Author, error) {
|
||||
args := m.Called(ctx, id)
|
||||
if args.Get(0) == nil {
|
||||
return nil, args.Error(1)
|
||||
@ -281,4 +281,4 @@ func (s *TranslationCommandsTestSuite) TestCreateOrUpdateTranslation() {
|
||||
|
||||
func TestTranslationCommands(t *testing.T) {
|
||||
suite.Run(t, new(TranslationCommandsTestSuite))
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,10 +1,12 @@
|
||||
package translation
|
||||
|
||||
import "github.com/google/uuid"
|
||||
|
||||
// TranslationDTO is a read model for a translation.
|
||||
type TranslationDTO struct {
|
||||
ID uint
|
||||
ID uuid.UUID
|
||||
Title string
|
||||
Language string
|
||||
Content string
|
||||
TranslatableID uint
|
||||
}
|
||||
TranslatableID uuid.UUID
|
||||
}
|
||||
|
||||
@ -2,8 +2,8 @@ package translation
|
||||
|
||||
import (
|
||||
"context"
|
||||
"tercul/internal/domain"
|
||||
"gorm.io/gorm"
|
||||
"tercul/internal/domain"
|
||||
)
|
||||
|
||||
type mockTranslationRepository struct {
|
||||
@ -12,7 +12,7 @@ type mockTranslationRepository struct {
|
||||
listByWorkIDPaginatedFunc func(ctx context.Context, workID uint, language *string, page, pageSize int) (*domain.PaginatedResult[domain.Translation], error)
|
||||
}
|
||||
|
||||
func (m *mockTranslationRepository) GetByID(ctx context.Context, id uint) (*domain.Translation, error) {
|
||||
func (m *mockTranslationRepository) GetByID(ctx context.Context, id uuid.UUID) (*domain.Translation, error) {
|
||||
if m.getByIDFunc != nil {
|
||||
return m.getByIDFunc(ctx, id)
|
||||
}
|
||||
@ -92,4 +92,4 @@ func (m *mockTranslationRepository) ListByStatus(ctx context.Context, status dom
|
||||
}
|
||||
func (m *mockTranslationRepository) Upsert(ctx context.Context, translation *domain.Translation) error {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"tercul/internal/domain"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"go.opentelemetry.io/otel"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
@ -23,7 +24,7 @@ func NewTranslationQueries(repo domain.TranslationRepository) *TranslationQuerie
|
||||
}
|
||||
|
||||
// Translation returns a translation by ID.
|
||||
func (q *TranslationQueries) Translation(ctx context.Context, id uint) (*TranslationDTO, error) {
|
||||
func (q *TranslationQueries) Translation(ctx context.Context, id uuid.UUID) (*TranslationDTO, error) {
|
||||
ctx, span := q.tracer.Start(ctx, "Translation")
|
||||
defer span.End()
|
||||
|
||||
@ -45,21 +46,21 @@ func (q *TranslationQueries) Translation(ctx context.Context, id uint) (*Transla
|
||||
}
|
||||
|
||||
// TranslationsByWorkID returns all translations for a work.
|
||||
func (q *TranslationQueries) TranslationsByWorkID(ctx context.Context, workID uint) ([]domain.Translation, error) {
|
||||
func (q *TranslationQueries) TranslationsByWorkID(ctx context.Context, workID uuid.UUID) ([]domain.Translation, error) {
|
||||
ctx, span := q.tracer.Start(ctx, "TranslationsByWorkID")
|
||||
defer span.End()
|
||||
return q.repo.ListByWorkID(ctx, workID)
|
||||
}
|
||||
|
||||
// TranslationsByEntity returns all translations for an entity.
|
||||
func (q *TranslationQueries) TranslationsByEntity(ctx context.Context, entityType string, entityID uint) ([]domain.Translation, error) {
|
||||
func (q *TranslationQueries) TranslationsByEntity(ctx context.Context, entityType string, entityID uuid.UUID) ([]domain.Translation, error) {
|
||||
ctx, span := q.tracer.Start(ctx, "TranslationsByEntity")
|
||||
defer span.End()
|
||||
return q.repo.ListByEntity(ctx, entityType, entityID)
|
||||
}
|
||||
|
||||
// TranslationsByTranslatorID returns all translations for a translator.
|
||||
func (q *TranslationQueries) TranslationsByTranslatorID(ctx context.Context, translatorID uint) ([]domain.Translation, error) {
|
||||
func (q *TranslationQueries) TranslationsByTranslatorID(ctx context.Context, translatorID uuid.UUID) ([]domain.Translation, error) {
|
||||
ctx, span := q.tracer.Start(ctx, "TranslationsByTranslatorID")
|
||||
defer span.End()
|
||||
return q.repo.ListByTranslatorID(ctx, translatorID)
|
||||
@ -80,7 +81,7 @@ func (q *TranslationQueries) Translations(ctx context.Context) ([]domain.Transla
|
||||
}
|
||||
|
||||
// ListTranslations returns a paginated list of translations for a work, with optional language filtering.
|
||||
func (q *TranslationQueries) ListTranslations(ctx context.Context, workID uint, language *string, page, pageSize int) (*domain.PaginatedResult[TranslationDTO], error) {
|
||||
func (q *TranslationQueries) ListTranslations(ctx context.Context, workID uuid.UUID, language *string, page, pageSize int) (*domain.PaginatedResult[TranslationDTO], error) {
|
||||
ctx, span := q.tracer.Start(ctx, "ListTranslations")
|
||||
defer span.End()
|
||||
|
||||
|
||||
@ -76,4 +76,4 @@ func (s *TranslationQueriesSuite) TestListTranslations_Success() {
|
||||
TotalPages: 1,
|
||||
}
|
||||
assert.Equal(s.T(), expectedDTOs, paginatedDTOs)
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,6 +7,8 @@ import (
|
||||
"tercul/internal/app/authz"
|
||||
"tercul/internal/domain"
|
||||
platform_auth "tercul/internal/platform/auth"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// UserCommands contains the command handlers for the user aggregate.
|
||||
@ -52,7 +54,7 @@ func (c *UserCommands) CreateUser(ctx context.Context, input CreateUserInput) (*
|
||||
|
||||
// UpdateUserInput represents the input for updating an existing user.
|
||||
type UpdateUserInput struct {
|
||||
ID uint
|
||||
ID uuid.UUID
|
||||
Username *string
|
||||
Email *string
|
||||
Password *string
|
||||
@ -64,12 +66,13 @@ type UpdateUserInput struct {
|
||||
Role *domain.UserRole
|
||||
Verified *bool
|
||||
Active *bool
|
||||
CountryID *uint
|
||||
CityID *uint
|
||||
AddressID *uint
|
||||
CountryID *uuid.UUID
|
||||
CityID *uuid.UUID
|
||||
AddressID *uuid.UUID
|
||||
}
|
||||
|
||||
// UpdateUser updates an existing user.
|
||||
//
|
||||
//nolint:gocyclo // Complex update logic
|
||||
func (c *UserCommands) UpdateUser(ctx context.Context, input UpdateUserInput) (*domain.User, error) {
|
||||
actorID, ok := platform_auth.GetUserIDFromContext(ctx)
|
||||
@ -147,7 +150,7 @@ func (c *UserCommands) UpdateUser(ctx context.Context, input UpdateUserInput) (*
|
||||
}
|
||||
|
||||
// DeleteUser deletes a user by ID.
|
||||
func (c *UserCommands) DeleteUser(ctx context.Context, id uint) error {
|
||||
func (c *UserCommands) DeleteUser(ctx context.Context, id uuid.UUID) error {
|
||||
actorID, ok := platform_auth.GetUserIDFromContext(ctx)
|
||||
if !ok {
|
||||
return domain.ErrUnauthorized
|
||||
@ -162,4 +165,4 @@ func (c *UserCommands) DeleteUser(ctx context.Context, id uint) error {
|
||||
}
|
||||
|
||||
return c.repo.Delete(ctx, id)
|
||||
}
|
||||
}
|
||||
|
||||
@ -311,4 +311,4 @@ func (s *UserCommandsSuite) TestDeleteUser_NotFound() {
|
||||
assert.Error(s.T(), err)
|
||||
assert.ErrorIs(s.T(), err, domain.ErrEntityNotFound)
|
||||
s.repo.AssertExpectations(s.T())
|
||||
}
|
||||
}
|
||||
|
||||
@ -23,7 +23,7 @@ func (m *mockUserRepository) CreateInTx(ctx context.Context, tx *gorm.DB, entity
|
||||
return args.Error(0)
|
||||
}
|
||||
|
||||
func (m *mockUserRepository) GetByID(ctx context.Context, id uint) (*domain.User, error) {
|
||||
func (m *mockUserRepository) GetByID(ctx context.Context, id uuid.UUID) (*domain.User, error) {
|
||||
args := m.Called(ctx, id)
|
||||
if args.Get(0) == nil {
|
||||
return nil, args.Error(1)
|
||||
@ -149,4 +149,4 @@ func (m *mockUserRepository) ListByRole(ctx context.Context, role domain.UserRol
|
||||
return nil, args.Error(1)
|
||||
}
|
||||
return args.Get(0).([]domain.User), args.Error(1)
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,6 +3,8 @@ package user
|
||||
import (
|
||||
"context"
|
||||
"tercul/internal/domain"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// UserQueries contains the query handlers for the user aggregate.
|
||||
@ -17,7 +19,7 @@ func NewUserQueries(repo domain.UserRepository, profileRepo domain.UserProfileRe
|
||||
}
|
||||
|
||||
// User returns a user by ID.
|
||||
func (q *UserQueries) User(ctx context.Context, id uint) (*domain.User, error) {
|
||||
func (q *UserQueries) User(ctx context.Context, id uuid.UUID) (*domain.User, error) {
|
||||
return q.repo.GetByID(ctx, id)
|
||||
}
|
||||
|
||||
@ -42,6 +44,6 @@ func (q *UserQueries) Users(ctx context.Context) ([]domain.User, error) {
|
||||
}
|
||||
|
||||
// UserProfile returns a user profile by user ID.
|
||||
func (q *UserQueries) UserProfile(ctx context.Context, userID uint) (*domain.UserProfile, error) {
|
||||
func (q *UserQueries) UserProfile(ctx context.Context, userID uuid.UUID) (*domain.UserProfile, error) {
|
||||
return q.profileRepo.GetByUserID(ctx, userID)
|
||||
}
|
||||
|
||||
@ -18,7 +18,7 @@ type mockUserProfileRepository struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (m *mockUserProfileRepository) GetByUserID(ctx context.Context, userID uint) (*domain.UserProfile, error) {
|
||||
func (m *mockUserProfileRepository) GetByUserID(ctx context.Context, userID uuid.UUID) (*domain.UserProfile, error) {
|
||||
args := m.Called(ctx, userID)
|
||||
if args.Get(0) == nil {
|
||||
return nil, args.Error(1)
|
||||
@ -36,7 +36,7 @@ func (m *mockUserProfileRepository) CreateInTx(ctx context.Context, tx *gorm.DB,
|
||||
return args.Error(0)
|
||||
}
|
||||
|
||||
func (m *mockUserProfileRepository) GetByID(ctx context.Context, id uint) (*domain.UserProfile, error) {
|
||||
func (m *mockUserProfileRepository) GetByID(ctx context.Context, id uuid.UUID) (*domain.UserProfile, error) {
|
||||
args := m.Called(ctx, id)
|
||||
if args.Get(0) == nil {
|
||||
return nil, args.Error(1)
|
||||
@ -275,4 +275,4 @@ func TestNewService(t *testing.T) {
|
||||
assert.NotNil(t, service)
|
||||
assert.NotNil(t, service.Commands)
|
||||
assert.NotNil(t, service.Queries)
|
||||
}
|
||||
}
|
||||
|
||||
@ -14,6 +14,8 @@ import (
|
||||
"go.opentelemetry.io/otel"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// WorkCommands contains the command handlers for the work aggregate.
|
||||
@ -108,8 +110,8 @@ func (c *WorkCommands) UpdateWork(ctx context.Context, work *domain.Work) error
|
||||
if work == nil {
|
||||
return fmt.Errorf("%w: work cannot be nil", domain.ErrValidation)
|
||||
}
|
||||
if work.ID == 0 {
|
||||
return fmt.Errorf("%w: work ID cannot be zero", domain.ErrValidation)
|
||||
if work.ID == uuid.Nil {
|
||||
return fmt.Errorf("%w: work ID cannot be nil", domain.ErrValidation)
|
||||
}
|
||||
|
||||
userID, ok := platform_auth.GetUserIDFromContext(ctx)
|
||||
@ -149,10 +151,10 @@ func (c *WorkCommands) UpdateWork(ctx context.Context, work *domain.Work) error
|
||||
}
|
||||
|
||||
// DeleteWork deletes a work by ID after performing an authorization check.
|
||||
func (c *WorkCommands) DeleteWork(ctx context.Context, id uint) error {
|
||||
func (c *WorkCommands) DeleteWork(ctx context.Context, id uuid.UUID) error {
|
||||
ctx, span := c.tracer.Start(ctx, "DeleteWork")
|
||||
defer span.End()
|
||||
if id == 0 {
|
||||
if id == uuid.Nil {
|
||||
return fmt.Errorf("%w: invalid work ID", domain.ErrValidation)
|
||||
}
|
||||
|
||||
@ -181,7 +183,7 @@ func (c *WorkCommands) DeleteWork(ctx context.Context, id uint) error {
|
||||
}
|
||||
|
||||
// AnalyzeWork performs linguistic analysis on a work and its translations.
|
||||
func (c *WorkCommands) AnalyzeWork(ctx context.Context, workID uint) error {
|
||||
func (c *WorkCommands) AnalyzeWork(ctx context.Context, workID uuid.UUID) error {
|
||||
ctx, span := c.tracer.Start(ctx, "AnalyzeWork")
|
||||
defer span.End()
|
||||
logger := log.FromContext(ctx).With("workID", workID)
|
||||
@ -220,8 +222,9 @@ func (c *WorkCommands) AnalyzeWork(ctx context.Context, workID uint) error {
|
||||
}
|
||||
|
||||
// MergeWork merges two works, moving all associations from the source to the target and deleting the source.
|
||||
//
|
||||
//nolint:gocyclo // Complex merge logic
|
||||
func (c *WorkCommands) MergeWork(ctx context.Context, sourceID, targetID uint) error {
|
||||
func (c *WorkCommands) MergeWork(ctx context.Context, sourceID, targetID uuid.UUID) error {
|
||||
ctx, span := c.tracer.Start(ctx, "MergeWork")
|
||||
defer span.End()
|
||||
if sourceID == targetID {
|
||||
@ -331,7 +334,7 @@ func (c *WorkCommands) MergeWork(ctx context.Context, sourceID, targetID uint) e
|
||||
return nil
|
||||
}
|
||||
|
||||
func mergeWorkStats(tx *gorm.DB, sourceWorkID, targetWorkID uint) error {
|
||||
func mergeWorkStats(tx *gorm.DB, sourceWorkID, targetWorkID uuid.UUID) error {
|
||||
var sourceStats domain.WorkStats
|
||||
err := tx.Where("work_id = ?", sourceWorkID).First(&sourceStats).Error
|
||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
@ -352,7 +355,7 @@ func mergeWorkStats(tx *gorm.DB, sourceWorkID, targetWorkID uint) error {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
// If target has no stats, create a new stats record for it.
|
||||
newStats := sourceStats
|
||||
newStats.ID = 0
|
||||
newStats.ID = uuid.Nil
|
||||
newStats.WorkID = targetWorkID
|
||||
if err = tx.Create(&newStats).Error; err != nil {
|
||||
return fmt.Errorf("failed to create new target stats: %w", err)
|
||||
@ -373,4 +376,4 @@ func mergeWorkStats(tx *gorm.DB, sourceWorkID, targetWorkID uint) error {
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,8 +1,10 @@
|
||||
package work
|
||||
|
||||
import "github.com/google/uuid"
|
||||
|
||||
// WorkDTO is a read model for a work, containing only the data needed for API responses.
|
||||
type WorkDTO struct {
|
||||
ID uint
|
||||
ID uuid.UUID
|
||||
Title string
|
||||
Language string
|
||||
}
|
||||
}
|
||||
|
||||
@ -21,7 +21,7 @@ func (m *mockWorkRepository) CreateInTx(ctx context.Context, tx *gorm.DB, entity
|
||||
args := m.Called(ctx, tx, entity)
|
||||
return args.Error(0)
|
||||
}
|
||||
func (m *mockWorkRepository) GetByID(ctx context.Context, id uint) (*domain.Work, error) {
|
||||
func (m *mockWorkRepository) GetByID(ctx context.Context, id uuid.UUID) (*domain.Work, error) {
|
||||
args := m.Called(ctx, id)
|
||||
if args.Get(0) == nil {
|
||||
return nil, args.Error(1)
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
package work
|
||||
|
||||
// This file is intentionally left empty.
|
||||
// Mocks are defined in main_test.go to avoid redeclaration errors.
|
||||
// Mocks are defined in main_test.go to avoid redeclaration errors.
|
||||
|
||||
@ -7,6 +7,8 @@ import (
|
||||
|
||||
"go.opentelemetry.io/otel"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// WorkQueries contains the query handlers for the work aggregate.
|
||||
@ -24,10 +26,10 @@ func NewWorkQueries(repo domain.WorkRepository) *WorkQueries {
|
||||
}
|
||||
|
||||
// GetWorkByID retrieves a work by ID.
|
||||
func (q *WorkQueries) GetWorkByID(ctx context.Context, id uint) (*WorkDTO, error) {
|
||||
func (q *WorkQueries) GetWorkByID(ctx context.Context, id uuid.UUID) (*WorkDTO, error) {
|
||||
ctx, span := q.tracer.Start(ctx, "GetWorkByID")
|
||||
defer span.End()
|
||||
if id == 0 {
|
||||
if id == uuid.Nil {
|
||||
return nil, errors.New("invalid work ID")
|
||||
}
|
||||
|
||||
@ -75,10 +77,10 @@ func (q *WorkQueries) ListWorks(ctx context.Context, page, pageSize int) (*domai
|
||||
}
|
||||
|
||||
// GetWorkWithTranslations retrieves a work with its translations.
|
||||
func (q *WorkQueries) GetWorkWithTranslations(ctx context.Context, id uint) (*domain.Work, error) {
|
||||
func (q *WorkQueries) GetWorkWithTranslations(ctx context.Context, id uuid.UUID) (*domain.Work, error) {
|
||||
ctx, span := q.tracer.Start(ctx, "GetWorkWithTranslations")
|
||||
defer span.End()
|
||||
if id == 0 {
|
||||
if id == uuid.Nil {
|
||||
return nil, errors.New("invalid work ID")
|
||||
}
|
||||
return q.repo.GetWithTranslations(ctx, id)
|
||||
@ -95,20 +97,20 @@ func (q *WorkQueries) FindWorksByTitle(ctx context.Context, title string) ([]dom
|
||||
}
|
||||
|
||||
// FindWorksByAuthor finds works by author ID.
|
||||
func (q *WorkQueries) FindWorksByAuthor(ctx context.Context, authorID uint) ([]domain.Work, error) {
|
||||
func (q *WorkQueries) FindWorksByAuthor(ctx context.Context, authorID uuid.UUID) ([]domain.Work, error) {
|
||||
ctx, span := q.tracer.Start(ctx, "FindWorksByAuthor")
|
||||
defer span.End()
|
||||
if authorID == 0 {
|
||||
if authorID == uuid.Nil {
|
||||
return nil, errors.New("invalid author ID")
|
||||
}
|
||||
return q.repo.FindByAuthor(ctx, authorID)
|
||||
}
|
||||
|
||||
// FindWorksByCategory finds works by category ID.
|
||||
func (q *WorkQueries) FindWorksByCategory(ctx context.Context, categoryID uint) ([]domain.Work, error) {
|
||||
func (q *WorkQueries) FindWorksByCategory(ctx context.Context, categoryID uuid.UUID) ([]domain.Work, error) {
|
||||
ctx, span := q.tracer.Start(ctx, "FindWorksByCategory")
|
||||
defer span.End()
|
||||
if categoryID == 0 {
|
||||
if categoryID == uuid.Nil {
|
||||
return nil, errors.New("invalid category ID")
|
||||
}
|
||||
return q.repo.FindByCategory(ctx, categoryID)
|
||||
@ -125,10 +127,10 @@ func (q *WorkQueries) FindWorksByLanguage(ctx context.Context, language string,
|
||||
}
|
||||
|
||||
// ListByCollectionID finds works by collection ID.
|
||||
func (q *WorkQueries) ListByCollectionID(ctx context.Context, collectionID uint) ([]domain.Work, error) {
|
||||
func (q *WorkQueries) ListByCollectionID(ctx context.Context, collectionID uuid.UUID) ([]domain.Work, error) {
|
||||
ctx, span := q.tracer.Start(ctx, "ListByCollectionID")
|
||||
defer span.End()
|
||||
if collectionID == 0 {
|
||||
if collectionID == uuid.Nil {
|
||||
return nil, errors.New("invalid collection ID")
|
||||
}
|
||||
return q.repo.ListByCollectionID(ctx, collectionID)
|
||||
|
||||
@ -159,4 +159,4 @@ func (s *WorkQueriesSuite) TestFindWorksByLanguage_Empty() {
|
||||
w, err := s.queries.FindWorksByLanguage(context.Background(), "", 1, 10)
|
||||
assert.Error(s.T(), err)
|
||||
assert.Nil(s.T(), w)
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user