Compare commits

...

72 Commits

Author SHA1 Message Date
3a98b72ffd Merge pull request 'feature/epic2-core-services' (#6) from feature/epic2-core-services into main
All checks were successful
CI / Test (push) Successful in 3m38s
CI / Lint (push) Successful in 54s
CI / Build (push) Successful in 45s
CI / Format Check (push) Successful in 2s
Reviewed-on: #6
2025-11-07 10:23:19 +01:00
a785cd73de perf(ci): pre-install all tools in pre-commit Docker image
All checks were successful
CI / Test (pull_request) Successful in 53s
CI / Build (pull_request) Successful in 38s
CI / Format Check (pull_request) Successful in 2s
CI / Lint (pull_request) Successful in 26s
- Move Go 1.25.3 installation to Dockerfile
- Pre-install protobuf plugins and golangci-lint in image
- Set environment variables in Dockerfile
- Remove runtime installation steps from pre-commit script
- Significantly improves pre-commit check performance
2025-11-07 10:16:12 +01:00
75c5293c8c fix(lint): add bounds checking for integer conversions to fix gosec warnings
All checks were successful
CI / Test (pull_request) Successful in 57s
CI / Lint (pull_request) Successful in 27s
CI / Build (pull_request) Successful in 39s
CI / Format Check (pull_request) Successful in 2s
- Add bounds checking for Limit and Offset conversions in audit_client.go
- Add bounds checking for t, m, and p conversions in password.go
- Add nolint comments with explanations for safe conversions
2025-11-07 10:04:14 +01:00
9712e50f6c fix(scripts): disable VCS stamping in pre-commit container 2025-11-07 09:55:24 +01:00
8eda9af769 fix(scripts): ensure working directory is correct for make commands 2025-11-07 09:54:00 +01:00
4b33ed522d fix(scripts): install make in pre-commit container 2025-11-07 09:52:45 +01:00
bc740f7b1f fix(scripts): use make fmt-check instead of go fmt 2025-11-07 09:52:17 +01:00
e98e4d3099 fix(scripts): use make fmt-check in pre-commit script 2025-11-07 09:51:23 +01:00
93623e6865 fix(scripts): install Go 1.25.3 in pre-commit container and fix formatting 2025-11-07 09:47:50 +01:00
b531f92436 feat(ci): add pre-commit check script using wirelos/pre-commit image 2025-11-07 09:46:35 +01:00
31e8ca7ce9 fix(lint): use explicit safe type conversions for gosec
Some checks failed
CI / Test (pull_request) Successful in 51s
CI / Lint (pull_request) Failing after 26s
CI / Build (pull_request) Successful in 38s
CI / Format Check (pull_request) Failing after 2s
Use separate variables with explicit else branches to make type
conversions safe and satisfy gosec integer overflow checks.
2025-11-07 09:37:53 +01:00
e673fcae6f fix(lint): fix all linting errors
Some checks failed
CI / Test (pull_request) Successful in 53s
CI / Lint (pull_request) Failing after 26s
CI / Build (pull_request) Successful in 39s
CI / Format Check (pull_request) Failing after 2s
- Check BindEnv return values in config.go
- Add bounds checks for int->int32/uint32 conversions to prevent overflow
- Remove unused test helper functions
2025-11-07 09:34:38 +01:00
131e44f3d4 fix(ci): install latest golangci-lint using official script
Some checks failed
CI / Test (pull_request) Successful in 52s
CI / Lint (pull_request) Failing after 26s
CI / Build (pull_request) Successful in 38s
CI / Format Check (pull_request) Successful in 2s
2025-11-07 09:29:49 +01:00
0d6094267a fix(ci): install golangci-lint v2.4.0 for Go 1.25 and config v2 support 2025-11-07 09:27:27 +01:00
4b8536e34a fix(ci): use official install script for golangci-lint 2025-11-07 09:27:17 +01:00
e509faea25 fix(ci): install golangci-lint v2 for config compatibility
Some checks failed
CI / Test (pull_request) Successful in 51s
CI / Build (pull_request) Successful in 37s
CI / Lint (pull_request) Failing after 5s
CI / Format Check (pull_request) Successful in 3s
2025-11-07 09:27:02 +01:00
355008a3a2 fix(ci): build golangci-lint from source for Go 1.25 support
Some checks failed
CI / Build (pull_request) Successful in 37s
CI / Test (pull_request) Successful in 51s
CI / Lint (pull_request) Failing after 5s
CI / Format Check (pull_request) Successful in 2s
2025-11-07 09:24:58 +01:00
c8d944e9ea fix(ci): let golangci-lint-action auto-select compatible version
Some checks failed
CI / Test (pull_request) Successful in 52s
CI / Lint (pull_request) Failing after 7s
CI / Build (pull_request) Successful in 37s
CI / Format Check (pull_request) Successful in 2s
2025-11-07 09:22:52 +01:00
d24bb96d62 fix(ci): use golangci-lint v1.64.0 2025-11-07 09:22:33 +01:00
61d614690f fix(ci): use golangci-lint v1.65.0 for Go 1.25 support
Some checks failed
CI / Test (pull_request) Successful in 52s
CI / Lint (pull_request) Failing after 6s
CI / Build (pull_request) Successful in 37s
CI / Format Check (pull_request) Successful in 2s
2025-11-07 09:19:54 +01:00
5f2e1104f2 fix(ci): use golangci-lint latest version
Some checks failed
CI / Test (pull_request) Successful in 52s
CI / Lint (pull_request) Failing after 6s
CI / Build (pull_request) Successful in 37s
CI / Format Check (pull_request) Successful in 2s
2025-11-07 09:17:22 +01:00
7c0aefb7f4 fix(fmt): formatting 2025-11-07 09:10:22 +01:00
8cfdfbc951 fix(schema): remove duplicate auditlog.go schema 2025-11-07 09:04:05 +01:00
0912f0f81b feat(schema): restore complete Ent schema files 2025-11-07 09:03:43 +01:00
1f8c2626dc fix(gitignore): allow ent/schema/ directory 2025-11-07 08:42:06 +01:00
fb10051443 fix(ci): generate Ent from ent/schema and copy to internal/ent 2025-11-07 08:41:03 +01:00
8bb36b5735 fix(ci): correctly copy Ent files excluding schema directory 2025-11-07 08:39:43 +01:00
837b04b433 fix(ci): use find to copy all Ent generated files 2025-11-07 08:39:26 +01:00
868649d6d2 fix(ci): add debug output to Ent generation step 2025-11-07 08:38:22 +01:00
13da884a21 fix(ci): exclude generate.go from Ent code copy 2025-11-07 08:21:40 +01:00
c2e2ab01f2 fix(ci): generate Ent code and copy to internal/ent 2025-11-07 08:21:31 +01:00
8c10c3dba9 fix(ci): use full module path for Ent target directory 2025-11-07 08:20:56 +01:00
b6eb8d75bb fix(ci): generate Ent code to internal/ent directory 2025-11-07 08:17:32 +01:00
4c62817cff fix(ci): revert to use apk for Alpine runner 2025-11-06 22:49:45 +01:00
0edeb67075 test: add comprehensive tests and fix CI build 2025-11-06 22:49:13 +01:00
b3c8f68989 fix(ci): update to use Makefile commands 2025-11-06 22:42:17 +01:00
3f18163313 fix(gitignore): exclude generated protobuf and Ent files 2025-11-06 22:39:55 +01:00
d42b1cd5f1 fix(proto): fix protobuf generation and update gateway tests 2025-11-06 22:39:43 +01:00
471a057d25 fix(ci): fix CI build and update Makefile to build all services 2025-11-06 22:34:49 +01:00
ad4ecaed1f fix(ci): update to use Alpine package manager (apk) 2025-11-06 22:30:39 +01:00
6ce1007f73 fix(ci): update CI to generate protobuf and Ent ORM files 2025-11-06 22:26:54 +01:00
4e6db9995f fix(gitignore): remove generated files from git tracking 2025-11-06 22:15:08 +01:00
dbe29bfb82 fix(consul): fix gRPC health checks and add API Gateway Consul registration 2025-11-06 22:04:55 +01:00
04022b835e feat(auth): Complete Auth Service implementation and fix Consul health checks
- Add VerifyPassword RPC to Identity Service
  - Added to proto file and generated code
  - Implemented in Identity Service gRPC server
  - Added to Identity Service client interface and gRPC client

- Complete RefreshToken implementation
  - Store refresh tokens in database using RefreshToken entity
  - Validate refresh tokens with expiration checking
  - Revoke refresh tokens on logout and token rotation

- Integrate Authz Service for role retrieval
  - Added AuthzServiceClient to Auth Service
  - Get user roles during login and token refresh
  - Gracefully handle Authz Service failures

- Require JWT secret in configuration
  - Removed default secret fallback
  - Service fails to start if JWT secret is not configured

- Fix Consul health checks for Docker
  - Services now register with Docker service names (e.g., audit-service)
  - Allows Consul (in Docker) to reach services via Docker DNS
  - Health checks use gRPC service names instead of localhost

This completes all TODOs in auth_service_fx.go and fixes the Consul
health check failures in Docker environments.
2025-11-06 21:26:34 +01:00
b02c1d44c8 fix(consul): Fix health checks for gRPC services in Docker
- Add gRPC health check support to Consul registry
  - Services are gRPC-only, not HTTP
  - Consul was trying HTTP health checks which failed
  - Now uses gRPC health checks via grpc.health.v1.Health service

- Update HealthCheckConfig to support both HTTP and gRPC
  - Add GRPC field for gRPC service name
  - Add UseGRPC flag to choose health check type
  - Default to gRPC for services (use_grpc: true in config)

- Fix service address registration in Docker
  - Services now register with Docker service name (e.g., auth-service)
  - Allows Consul to reach services via Docker network DNS
  - Falls back to localhost for local development

- Update default.yaml to enable gRPC health checks
  - Set use_grpc: true
  - Set grpc: grpc.health.v1.Health

This fixes services being deregistered from Consul due to failed
HTTP health checks. Services will now pass gRPC health checks.
2025-11-06 21:17:33 +01:00
54e1866997 fix(config): Fix environment variable mapping for Docker
- Add SetEnvKeyReplacer to convert underscores to dots
- Explicitly bind DATABASE_DSN, REGISTRY_CONSUL_ADDRESS, REGISTRY_TYPE
- Fixes database connection issues in Docker where services couldn't
  read DATABASE_DSN environment variable
- Services in Docker can now connect to postgres:5432 instead of localhost
2025-11-06 21:09:47 +01:00
cf4bf9505a fix(docs): Fix service run commands to include all package files
- Change from 'go run ./cmd/{service}/main.go' to 'go run ./cmd/{service}/*.go'
  - go run with single file doesn't include other files in the package
  - Service implementations are in separate _fx.go files
  - Using wildcard includes all .go files in the package

- Update README.md and SUMMARY.md with correct commands
- Fixes 'undefined: provideXService' errors when running services
2025-11-06 21:04:03 +01:00
a2990f02ba fix(gitignore): Only ignore API Gateway binary, not directory 2025-11-06 21:03:16 +01:00
01603a0722 feat(docker): Add API Gateway Dockerfile 2025-11-06 21:03:09 +01:00
cba2096adf feat(docker): Add API Gateway to Docker Compose
- Create Dockerfile for API Gateway
  - Multi-stage build using golang:1.25-alpine
  - Minimal runtime image using alpine:latest
  - Exposes port 8080

- Add API Gateway service to docker-compose.yml
  - Depends on Consul and all core services
  - Environment variables for gateway configuration
  - Port 8080 exposed

- Update SUMMARY.md
  - Add API Gateway to service list
  - Add API Gateway to Docker build instructions
  - Update file structure to include API Gateway Dockerfile
2025-11-06 21:02:54 +01:00
4cac2b2592 fix(services): Fix duplicate health registry provider
- Change from fx.Provide to fx.Invoke for health registry registration
  - CoreModule() already provides *health.Registry
  - Services should register their database checkers with the existing registry
  - Use fx.Invoke to register database health checkers instead of providing new registry

- Fixes duplicate provider error for *health.Registry
- All services now build and should start successfully
2025-11-06 21:00:52 +01:00
dfe460cb03 fix(services): Fix service startup failures
- Remove duplicate CoreModule() calls from all service main.go files
  - NewContainer() already includes CoreModule() automatically
  - This was causing duplicate ConfigProvider provider errors

- Update all _fx.go files to use *database.Client instead of *ent.Client
  - database.Client embeds *ent.Client, so it can be used directly
  - This fixes type mismatches between providers and consumers
  - Keep ent import for constants like ent.Desc

- All services now build and should start successfully
2025-11-06 20:56:37 +01:00
2f2a14f2c5 docs: Update README.md with current implementation and quick start
- Add Core Services section highlighting Epic 2 completion
- Update directory structure to include all service entry points
- Add comprehensive Quick Start guide with:
  - Prerequisites including NixOS support
  - Installation steps with code generation
  - Two deployment options (development vs full Docker)
  - Service endpoints and ports
  - Testing examples with grpcurl
- Update Architecture section with Core Services details
- Add Implementation Status section showing completed epics
- Update Configuration section with service-specific settings
- Add links to Epic 2 documentation
2025-11-06 20:49:19 +01:00
ff330e510d fix(gitignore): Only ignore service binaries, not directories 2025-11-06 20:47:04 +01:00
3191ae9444 feat(docker): Add Dockerfiles for all services 2025-11-06 20:46:58 +01:00
031a90eca0 feat(docker): Add Docker support for all services
- Create Dockerfiles for all four services (auth, identity, authz, audit)
  - Multi-stage builds using golang:1.25-alpine
  - Minimal runtime images using alpine:latest
  - Copy config files to runtime image

- Create docker-compose.dev.yml for development
  - Only PostgreSQL and Consul
  - Use when running services locally with 'go run'

- Update docker-compose.yml for full deployment
  - All services + infrastructure
  - Services build from Dockerfiles
  - Health checks and dependencies configured
  - Environment variables for service configuration

- Add .dockerignore to optimize build context
  - Excludes docs, tests, IDE files, build artifacts

- Update SUMMARY.md
  - Document both docker-compose files
  - Add Docker deployment section
  - Update file structure to include Dockerfiles
2025-11-06 20:46:43 +01:00
33339f19cb docs: Fix duplicate heading in SUMMARY.md 2025-11-06 20:08:50 +01:00
6d6e07e09a docs: Add Consul verification steps to SUMMARY.md 2025-11-06 20:08:44 +01:00
3ac8983e98 feat(docker): Add Consul to docker-compose and update documentation
- Add Consul service to docker-compose.yml
  - Running in dev mode on port 8500
  - Health checks configured
  - Persistent volume for data
  - Web UI available at http://localhost:8500/ui

- Update SUMMARY.md
  - Document Consul setup in docker-compose
  - Add Consul verification steps
  - Update prerequisites to include Docker Compose
  - Add note about Consul Web UI

- Remove obsolete version field from docker-compose.yml
2025-11-06 20:08:37 +01:00
cb28a120ed chore: Update .gitignore to exclude auth-service and authz-service binaries 2025-11-06 20:07:45 +01:00
b1b895e818 feat(epic2): Implement core authentication and authorization services
- Implement Audit Service (2.5)
  - gRPC server with Record and Query operations
  - Database persistence with audit schema
  - Service registry integration
  - Entry point: cmd/audit-service

- Implement Identity Service (2.2)
  - User CRUD operations
  - Password hashing with argon2id
  - Email verification and password reset flows
  - Entry point: cmd/identity-service
  - Fix package naming conflicts in user_service.go

- Implement Auth Service (2.1)
  - JWT token generation and validation
  - Login, RefreshToken, ValidateToken, Logout RPCs
  - Integration with Identity Service
  - Entry point: cmd/auth-service
  - Note: RefreshToken entity needs Ent generation

- Implement Authz Service (2.3, 2.4)
  - Permission checking and authorization
  - User roles and permissions retrieval
  - RBAC-based authorization
  - Entry point: cmd/authz-service

- Implement gRPC clients for all services
  - Auth, Identity, Authz, and Audit clients
  - Service discovery integration
  - Full gRPC communication

- Add service configurations to config/default.yaml
- Create SUMMARY.md with implementation details and testing instructions
- Fix compilation errors in Identity Service (password package conflicts)
- All services build successfully and tests pass
2025-11-06 20:07:20 +01:00
da7a4e3703 Merge pull request 'feature/microservice-architecture' (#5) from feature/microservice-architecture into main
All checks were successful
CI / Test (push) Successful in 26s
CI / Lint (push) Successful in 21s
CI / Build (push) Successful in 17s
CI / Format Check (push) Successful in 2s
Reviewed-on: #5
2025-11-06 13:47:17 +01:00
f9170bb00b fix(docs): diagrams
All checks were successful
CI / Test (pull_request) Successful in 26s
CI / Lint (pull_request) Successful in 21s
CI / Build (pull_request) Successful in 16s
CI / Format Check (pull_request) Successful in 2s
2025-11-06 13:29:32 +01:00
b4b918cba8 docs: ensure newline before lists across docs for MkDocs rendering
All checks were successful
CI / Test (pull_request) Successful in 27s
CI / Lint (pull_request) Successful in 20s
CI / Build (pull_request) Successful in 16s
CI / Format Check (pull_request) Successful in 2s
2025-11-06 10:56:50 +01:00
a1586cb302 fix(fmt): format code
All checks were successful
CI / Test (pull_request) Successful in 26s
CI / Lint (pull_request) Successful in 20s
CI / Build (pull_request) Successful in 16s
CI / Format Check (pull_request) Successful in 3s
2025-11-06 10:35:20 +01:00
a9b8df06f3 fix(lint): remove unused grpc imports from auth_client after commenting out connectToService
Some checks failed
CI / Test (pull_request) Successful in 26s
CI / Lint (pull_request) Successful in 20s
CI / Build (pull_request) Successful in 17s
CI / Format Check (pull_request) Failing after 2s
2025-11-06 10:33:06 +01:00
767654f257 fix(lint): resolve golangci-lint errors
Some checks failed
CI / Test (pull_request) Failing after 22s
CI / Lint (pull_request) Failing after 19s
CI / Build (pull_request) Failing after 6s
CI / Format Check (pull_request) Successful in 2s
- Fix errcheck: explicitly ignore tx.Rollback() error in defer
  - When transaction commits successfully, Rollback() returns an error (expected)
  - Use defer func() with explicit error assignment to satisfy linter

- Remove unused connectToService function
  - Function is not currently used (proto files not yet generated)
  - Commented out with TODO for future implementation
  - Prevents unused function lint error
2025-11-06 10:28:48 +01:00
cd57fe7c14 fix(ci): align golangci-lint config with v2.6 schema (remove 'issues.exclude')
Some checks failed
CI / Format Check (pull_request) Successful in 2s
CI / Test (pull_request) Successful in 25s
CI / Lint (pull_request) Failing after 21s
CI / Build (pull_request) Successful in 16s
2025-11-06 10:16:55 +01:00
b56b3c8c93 fix(ci): update golangci-lint config for v2.6 compatibility
Some checks failed
CI / Test (pull_request) Successful in 26s
CI / Lint (pull_request) Failing after 5s
CI / Build (pull_request) Successful in 16s
CI / Format Check (pull_request) Successful in 2s
- Change version from number to string: version: "2"
- Remove deprecated exclude-use-default option
- Change exclude-rules to exclude (new format in v2.6)
- Remove deprecated output section (print-issued-lines, print-linter-name)
- Remove linters-settings (not allowed in v2.6 schema validation)

Fixes CI validation errors with golangci-lint v2.6.1:
- version type validation error
- exclude-use-default and exclude-rules not allowed
- output options not allowed
- linters-settings not allowed at root level
2025-11-06 10:14:13 +01:00
c05038ccf2 fix: resolve test race conditions and update golangci-lint action
Some checks failed
CI / Test (pull_request) Successful in 24s
CI / Lint (pull_request) Failing after 18s
CI / Build (pull_request) Successful in 15s
CI / Format Check (pull_request) Successful in 1s
- Fix race condition in gateway tests by using TestMain to set Gin mode once
  - Remove duplicate gin.SetMode(gin.TestMode) calls from individual tests
  - Add TestMain function to initialize test environment before all tests
  - Prevents race conditions when tests run in parallel with -race flag

- Update golangci-lint-action from v6 to v7
  - v6 doesn't support golangci-lint v2.x versions
  - v7 supports golangci-lint v2.x and automatically selects compatible version
  - Change version from v2.6.0 to latest for automatic compatibility

All tests now pass with race detector enabled.
2025-11-06 09:57:58 +01:00
557e6a009e fix(ci): update golangci-lint to support Go 1.25.3
Some checks failed
CI / Test (pull_request) Failing after 23s
CI / Lint (pull_request) Failing after 18s
CI / Build (pull_request) Successful in 14s
CI / Format Check (pull_request) Successful in 2s
- Replace manual golangci-lint v2.1.6 installation (built with Go 1.24)
- Use official golangci/golangci-lint-action@v6 GitHub Action
- Set version to v2.6.0 which supports Go 1.25+
- Action automatically handles Go version compatibility

Fixes CI error: 'the Go language version (go1.24) used to build
golangci-lint is lower than the targeted Go version (1.25.3)'
2025-11-06 09:53:39 +01:00
260bc07114 test: add comprehensive tests for API Gateway implementation
- Add unit tests for gateway service (services/gateway/gateway_test.go)
  - Test gateway creation, route setup, service discovery, and error handling
  - Achieve 67.9% code coverage for gateway service
  - Test all HTTP methods are properly handled
  - Test route matching and 404 handling

- Add tests for API Gateway main entry point (cmd/api-gateway/main_test.go)
  - Test DI container setup and structure
  - Test service instance creation logic
  - Test lifecycle hooks registration

- Add testify dependency for assertions (go.mod)

All tests pass successfully. Proxy forwarding tests are noted for integration
test suite as they require real HTTP connections (per ADR-0028 testing strategy).
2025-11-06 09:52:16 +01:00
144 changed files with 8204 additions and 18352 deletions

View File

@@ -1,14 +1,64 @@
# Docker ignore file for MkDocs build # Git files
site/ .git
.mkdocs_cache/
__pycache__/
*.pyc
*.pyo
*.pyd
.Python
venv/
env/
ENV/
.git/
.gitignore .gitignore
.gitattributes
# Documentation
docs/
*.md
!README.md
# Development files
.envrc
shell.nix
.direnv/
# Build artifacts
bin/
*.exe
*.exe~
*.dll
*.so
*.dylib
*.test
*.out
auth-service
identity-service
authz-service
audit-service
platform
api-gateway
# Test files
*_test.go
test/
*.test
# IDE files
.vscode/
.idea/
*.swp
*.swo
*~
.DS_Store
# Logs
*.log
# Temporary files
tmp/
temp/
*.tmp
# Docker files (don't copy into Docker)
docker-compose*.yml
Dockerfile*
# CI/CD
.github/
.gitlab-ci.yml
.circleci/
# Coverage
coverage.out
coverage.html

5
.envrc Normal file
View File

@@ -0,0 +1,5 @@
# Automatically load nix-shell when entering this directory
# Requires direnv: https://direnv.net/
# Run: direnv allow
use nix

View File

@@ -33,6 +33,32 @@ jobs:
- name: Verify dependencies - name: Verify dependencies
run: go mod verify run: go mod verify
- name: Install protoc and plugins
run: |
apk add --no-cache protobuf-dev protoc
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
echo "$HOME/go/bin" >> $GITHUB_PATH
- name: Generate code
run: |
make generate-proto
echo "Checking for Ent schema directory..."
if [ -d "ent/schema" ]; then
echo "Generating Ent code..."
go install entgo.io/ent/cmd/ent@latest
cd ent/schema && go run -mod=mod entgo.io/ent/cmd/ent generate .
echo "Copying Ent code to internal/ent..."
cd .. && mkdir -p ../internal/ent
cp -r *.go */ ../internal/ent/ 2>/dev/null || true
rm -f ../internal/ent/generate.go
rm -rf ../internal/ent/schema
echo "Verifying internal/ent/ent.go exists..."
ls -la ../internal/ent/ent.go || echo "ERROR: ent.go not found!"
else
echo "WARNING: ent/schema directory not found!"
fi
- name: Check for test files - name: Check for test files
id: check-tests id: check-tests
run: | run: |
@@ -51,7 +77,7 @@ jobs:
if: steps.check-tests.outputs.tests_exist == 'true' if: steps.check-tests.outputs.tests_exist == 'true'
env: env:
CGO_ENABLED: 1 CGO_ENABLED: 1
run: go test -v -race -coverprofile=coverage.out -timeout=5m ./... run: make test-coverage
- name: Upload coverage - name: Upload coverage
if: steps.check-tests.outputs.tests_exist == 'true' if: steps.check-tests.outputs.tests_exist == 'true'
@@ -62,9 +88,7 @@ jobs:
- name: Verify build (no tests) - name: Verify build (no tests)
if: steps.check-tests.outputs.tests_exist == 'false' if: steps.check-tests.outputs.tests_exist == 'false'
run: | run: make build
echo "No tests found. Verifying code compiles instead..."
go build ./...
lint: lint:
name: Lint name: Lint
@@ -78,13 +102,43 @@ jobs:
with: with:
go-version: '1.25.3' go-version: '1.25.3'
- name: Install golangci-lint v2.1.6 - name: Download dependencies
run: | run: go mod download
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v2.1.6
echo "$(go env GOPATH)/bin" >> $GITHUB_PATH
- name: Run golangci-lint - name: Install protoc and plugins
run: golangci-lint run --timeout=5m run: |
apk add --no-cache protobuf-dev protoc
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
echo "$HOME/go/bin" >> $GITHUB_PATH
- name: Generate code
run: |
make generate-proto
echo "Checking for Ent schema directory..."
if [ -d "ent/schema" ]; then
echo "Generating Ent code..."
go install entgo.io/ent/cmd/ent@latest
cd ent/schema && go run -mod=mod entgo.io/ent/cmd/ent generate .
echo "Copying Ent code to internal/ent..."
cd .. && mkdir -p ../internal/ent
cp -r *.go */ ../internal/ent/ 2>/dev/null || true
rm -f ../internal/ent/generate.go
rm -rf ../internal/ent/schema
echo "Verifying internal/ent/ent.go exists..."
ls -la ../internal/ent/ent.go || echo "ERROR: ent.go not found!"
else
echo "WARNING: ent/schema directory not found!"
fi
- name: Install golangci-lint
run: |
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin
echo "$(go env GOPATH)/bin" >> $GITHUB_PATH
golangci-lint --version
- name: Run linters
run: make lint
build: build:
name: Build name: Build
@@ -109,10 +163,34 @@ jobs:
- name: Download dependencies - name: Download dependencies
run: go mod download run: go mod download
- name: Build - name: Install protoc and plugins
run: | run: |
go build -v -o bin/platform ./cmd/platform apk add --no-cache protobuf-dev protoc
go build -v -o bin/api-gateway ./cmd/api-gateway go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
echo "$HOME/go/bin" >> $GITHUB_PATH
- name: Generate code
run: |
make generate-proto
echo "Checking for Ent schema directory..."
if [ -d "ent/schema" ]; then
echo "Generating Ent code..."
go install entgo.io/ent/cmd/ent@latest
cd ent/schema && go run -mod=mod entgo.io/ent/cmd/ent generate .
echo "Copying Ent code to internal/ent..."
cd .. && mkdir -p ../internal/ent
cp -r *.go */ ../internal/ent/ 2>/dev/null || true
rm -f ../internal/ent/generate.go
rm -rf ../internal/ent/schema
echo "Verifying internal/ent/ent.go exists..."
ls -la ../internal/ent/ent.go || echo "ERROR: ent.go not found!"
else
echo "WARNING: ent/schema directory not found!"
fi
- name: Build
run: make build
- name: Upload build artifacts - name: Upload build artifacts
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
@@ -121,6 +199,10 @@ jobs:
path: | path: |
bin/platform bin/platform
bin/api-gateway bin/api-gateway
bin/auth-service
bin/identity-service
bin/authz-service
bin/audit-service
retention-days: 7 retention-days: 7
fmt: fmt:
@@ -136,9 +218,4 @@ jobs:
go-version: '1.25.3' go-version: '1.25.3'
- name: Check formatting - name: Check formatting
run: | run: make fmt-check
if [ "$(gofmt -s -l . | wc -l)" -gt 0 ]; then
echo "The following files need formatting:"
gofmt -s -d .
exit 1
fi

22
.gitignore vendored
View File

@@ -7,7 +7,11 @@
bin/ bin/
dist/ dist/
platform platform
api-gateway /api-gateway
/audit-service
/identity-service
/auth-service
/authz-service
# Test binary, built with `go test -c` # Test binary, built with `go test -c`
*.test *.test
@@ -56,8 +60,18 @@ temp/
docs/site/ docs/site/
docs/.mkdocs_cache/ docs/.mkdocs_cache/
# Docker
.dockerignore
# OS-specific # OS-specific
Thumbs.db Thumbs.db
.direnv/
# Generated protobuf files
api/proto/generated/
# Generated Ent ORM files (but keep schema source files)
internal/ent/
ent/*.go
ent/*/
!ent/schema/
git.dcentral.systems/

View File

@@ -1,7 +1,7 @@
# golangci-lint configuration # golangci-lint configuration
# See https://golangci-lint.run/usage/configuration/ # See https://golangci-lint.run/usage/configuration/
version: 2 version: "2"
run: run:
timeout: 5m timeout: 5m
@@ -17,23 +17,8 @@ linters:
disable: disable:
- gocritic # Can be enabled later for stricter checks - gocritic # Can be enabled later for stricter checks
linters-settings:
gosec:
severity: medium
errcheck:
check-blank: true
issues: issues:
exclude-use-default: false
max-issues-per-linter: 0 max-issues-per-linter: 0
max-same-issues: 0 max-same-issues: 0
exclude-rules: # Note: exclusion rules moved in v2 config; keep minimal allowed keys
# Exclude test files from some checks
- path: _test\.go
linters:
- errcheck
- gosec
output:
print-issued-lines: true
print-linter-name: true

View File

@@ -197,10 +197,14 @@ When working on this project, follow this workflow:
### 7. Commit Changes ### 7. Commit Changes
- **ALWAYS commit** after successful implementation - **ALWAYS commit** after successful implementation
- Ensure the code builds (`go build`) - Verify that everything is in order before commit:
- Ensure all tests pass (`go test`) - there is a Gitea Runner image in ci/pre-commit
- Ensure there are no linter issues (`make lint`) - run scripts/pre-commit-check.sh
- Ensure there are no fmt issues (`make fmt-check`) - Ensure the code builds (`make build`)
- Ensure all tests pass (`make test`)
- Ensure there are no linter issues (`make lint`)
- Ensure there are no fmt issues (`make fmt-check`)
- If there are issues, fix them before comitting
- Verify all acceptance criteria are met - Verify all acceptance criteria are met
- Write a clear, descriptive commit message - Write a clear, descriptive commit message

View File

@@ -18,7 +18,7 @@ help:
@echo " make lint - Run linters" @echo " make lint - Run linters"
@echo " make fmt - Format code" @echo " make fmt - Format code"
@echo " make fmt-check - Check code formatting" @echo " make fmt-check - Check code formatting"
@echo " make build - Build platform and api-gateway binaries" @echo " make build - Build all service binaries"
@echo " make clean - Clean build artifacts" @echo " make clean - Clean build artifacts"
@echo " make docker-build - Build Docker image" @echo " make docker-build - Build Docker image"
@echo " make docker-run - Run Docker container" @echo " make docker-run - Run Docker container"
@@ -85,10 +85,14 @@ fmt-check:
@echo "Code is properly formatted" @echo "Code is properly formatted"
build: build:
@echo "Building platform and api-gateway binaries..." @echo "Building all service binaries..."
$(GO) build -v -o bin/platform ./cmd/platform $(GO) build -v -o bin/platform ./cmd/platform
$(GO) build -v -o bin/api-gateway ./cmd/api-gateway $(GO) build -v -o bin/api-gateway ./cmd/api-gateway
@echo "Build complete: bin/platform, bin/api-gateway" $(GO) build -v -o bin/auth-service ./cmd/auth-service
$(GO) build -v -o bin/identity-service ./cmd/identity-service
$(GO) build -v -o bin/authz-service ./cmd/authz-service
$(GO) build -v -o bin/audit-service ./cmd/audit-service
@echo "Build complete: bin/platform, bin/api-gateway, bin/auth-service, bin/identity-service, bin/authz-service, bin/audit-service"
clean: clean:
@echo "Cleaning build artifacts..." @echo "Cleaning build artifacts..."
@@ -125,11 +129,32 @@ generate-proto:
echo "protoc-gen-go-grpc not found. Install with: go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest"; \ echo "protoc-gen-go-grpc not found. Install with: go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest"; \
exit 1; \ exit 1; \
fi fi
@mkdir -p api/proto/generated @mkdir -p api/proto/generated/audit/v1 api/proto/generated/auth/v1 api/proto/generated/authz/v1 api/proto/generated/identity/v1
@protoc --go_out=api/proto/generated --go_opt=paths=source_relative \ @protoc --go_out=api/proto/generated --go_opt=paths=source_relative \
--go-grpc_out=api/proto/generated --go-grpc_opt=paths=source_relative \ --go-grpc_out=api/proto/generated --go-grpc_opt=paths=source_relative \
--proto_path=api/proto \ --proto_path=api/proto \
api/proto/*.proto api/proto/audit.proto
@if [ -f api/proto/generated/audit.pb.go ]; then mv api/proto/generated/audit.pb.go api/proto/generated/audit/v1/audit.pb.go; fi
@if [ -f api/proto/generated/audit_grpc.pb.go ]; then mv api/proto/generated/audit_grpc.pb.go api/proto/generated/audit/v1/audit_grpc.pb.go; fi
@protoc --go_out=api/proto/generated --go_opt=paths=source_relative \
--go-grpc_out=api/proto/generated --go-grpc_opt=paths=source_relative \
--proto_path=api/proto \
api/proto/auth.proto
@if [ -f api/proto/generated/auth.pb.go ]; then mv api/proto/generated/auth.pb.go api/proto/generated/auth/v1/auth.pb.go; fi
@if [ -f api/proto/generated/auth_grpc.pb.go ]; then mv api/proto/generated/auth_grpc.pb.go api/proto/generated/auth/v1/auth_grpc.pb.go; fi
@protoc --go_out=api/proto/generated --go_opt=paths=source_relative \
--go-grpc_out=api/proto/generated --go-grpc_opt=paths=source_relative \
--proto_path=api/proto \
api/proto/authz.proto
@if [ -f api/proto/generated/authz.pb.go ]; then mv api/proto/generated/authz.pb.go api/proto/generated/authz/v1/authz.pb.go; fi
@if [ -f api/proto/generated/authz_grpc.pb.go ]; then mv api/proto/generated/authz_grpc.pb.go api/proto/generated/authz/v1/authz_grpc.pb.go; fi
@protoc --go_out=api/proto/generated --go_opt=paths=source_relative \
--go-grpc_out=api/proto/generated --go-grpc_opt=paths=source_relative \
--proto_path=api/proto \
api/proto/identity.proto
@if [ -f api/proto/generated/identity.pb.go ]; then mv api/proto/generated/identity.pb.go api/proto/generated/identity/v1/identity.pb.go; fi
@if [ -f api/proto/generated/identity_grpc.pb.go ]; then mv api/proto/generated/identity_grpc.pb.go api/proto/generated/identity/v1/identity_grpc.pb.go; fi
@rm -f api/proto/generated/*.pb.go api/proto/generated/*.proto 2>/dev/null || true
@echo "gRPC code generation complete" @echo "gRPC code generation complete"
verify: fmt-check lint test verify: fmt-check lint test

244
README.md
View File

@@ -8,43 +8,59 @@ A modular, extensible platform built with Go that provides a solid foundation fo
Go Platform follows **Hexagonal Architecture** principles with clear separation between: Go Platform follows **Hexagonal Architecture** principles with clear separation between:
- **Core Kernel**: Foundation services (authentication, authorization, configuration, logging, observability) - **Core Kernel**: Foundation services (configuration, logging, observability, dependency injection)
- **Core Services**: Independent microservices (Auth, Identity, Authz, Audit) with gRPC APIs
- **Module Framework**: Plugin system for extending functionality - **Module Framework**: Plugin system for extending functionality
- **Infrastructure Adapters**: Support for databases, caching, event buses, and job scheduling - **Infrastructure Adapters**: Support for databases, caching, event buses, and job scheduling
- **Security-by-Design**: Built-in JWT authentication, RBAC/ABAC authorization, and audit logging - **Security-by-Design**: Built-in JWT authentication, RBAC/ABAC authorization, and audit logging
- **Observability**: OpenTelemetry integration for tracing, metrics, and logging - **Observability**: OpenTelemetry integration for tracing, metrics, and logging
- **Microservices**: Each service is independently deployable with its own database schema
## Directory Structure ## Directory Structure
``` ```
goplt/ goplt/
├── cmd/ ├── cmd/ # Service entry points
── platform/ # Main application entry point ── platform/ # Main platform entry point
├── internal/ # Private implementation code │ ├── auth-service/ # Auth Service (JWT authentication)
│ ├── di/ # Dependency injection container │ ├── identity-service/ # Identity Service (user management)
│ ├── registry/ # Module registry │ ├── authz-service/ # Authz Service (authorization & RBAC)
── pluginloader/ # Plugin loader (optional) ── audit-service/ # Audit Service (audit logging)
│ ├── config/ # Configuration implementation ├── internal/ # Private implementation code
│ ├── logger/ # Logger implementation │ ├── di/ # Dependency injection container
│ ├── infra/ # Infrastructure adapters │ ├── registry/ # Module registry
── ent/ # Ent ORM schemas ── pluginloader/ # Plugin loader (optional)
├── pkg/ # Public interfaces (exported) │ ├── config/ # Configuration implementation
│ ├── config/ # ConfigProvider interface │ ├── logger/ # Logger implementation
│ ├── logger/ # Logger interface │ ├── infra/ # Infrastructure adapters
│ ├── module/ # IModule interface │ ├── ent/ # Ent ORM schemas
── auth/ # Auth interfaces ── client/ # Service clients (gRPC)
├── perm/ # Permission DSL └── grpc/ # gRPC client implementations
│ └── infra/ # Infrastructure interfaces ├── pkg/ # Public interfaces (exported)
├── modules/ # Feature modules │ ├── config/ # ConfigProvider interface
── blog/ # Sample Blog module (Epic 4) ── logger/ # Logger interface
├── config/ # Configuration files │ ├── module/ # IModule interface
│ ├── services/ # Service client interfaces
│ ├── auth/ # Auth interfaces
│ ├── perm/ # Permission DSL
│ └── infra/ # Infrastructure interfaces
├── services/ # Service-specific implementations
│ └── identity/ # Identity service internals
│ └── internal/
│ └── password/ # Password hashing (argon2id)
├── modules/ # Feature modules
│ └── blog/ # Sample Blog module (Epic 4)
├── api/ # API definitions
│ └── proto/ # gRPC protobuf definitions
├── config/ # Configuration files
│ ├── default.yaml │ ├── default.yaml
│ ├── development.yaml │ ├── development.yaml
│ └── production.yaml │ └── production.yaml
├── api/ # OpenAPI specs ├── docker-compose.yml # Full deployment (all services)
├── scripts/ # Build/test scripts ├── docker-compose.dev.yml # Development (infrastructure only)
├── docs/ # Documentation ├── scripts/ # Build/test scripts
├── ops/ # Operations (Grafana dashboards, etc.) ├── docs/ # Documentation
├── ops/ # Operations (Grafana dashboards, etc.)
└── .github/ └── .github/
└── workflows/ └── workflows/
└── ci.yml └── ci.yml
@@ -54,9 +70,12 @@ goplt/
### Prerequisites ### Prerequisites
- **Go 1.24+**: [Install Go](https://golang.org/doc/install) - **Go 1.25.3+**: [Install Go](https://golang.org/doc/install)
- **Make**: For using development commands - **Make**: For using development commands
- **Docker** (optional): For containerized development - **Docker & Docker Compose**: For infrastructure services
- **PostgreSQL 16+**: Database (or use Docker Compose)
- **Consul**: Service registry (or use Docker Compose)
- **NixOS** (optional): For development environment (`shell.nix` provided)
### Installation ### Installation
@@ -66,22 +85,93 @@ goplt/
cd goplt cd goplt
``` ```
2. **Install dependencies** 2. **Set up development environment** (NixOS users)
```bash
# If using direnv, it will auto-activate
# Otherwise, activate manually:
nix-shell
```
3. **Install dependencies**
```bash ```bash
go mod download go mod download
``` ```
3. **Build the platform** 4. **Generate code** (protobuf, Ent schemas)
```bash ```bash
make build make generate-proto
make generate-ent
``` ```
4. **Run the platform** ### Running Services
```bash
./bin/platform #### Option 1: Development Mode (Recommended for Development)
# Or
go run cmd/platform/main.go Start infrastructure services (PostgreSQL + Consul) in Docker, run services locally:
```
```bash
# Start infrastructure
docker-compose -f docker-compose.dev.yml up -d
# Verify infrastructure is running
docker-compose -f docker-compose.dev.yml ps
# Start services locally (in separate terminals)
go run ./cmd/auth-service/*.go # Port 8081
go run ./cmd/identity-service/*.go # Port 8082
go run ./cmd/authz-service/*.go # Port 8083
go run ./cmd/audit-service/*.go # Port 8084
```
#### Option 2: Full Docker Deployment
Run everything in Docker:
```bash
# Build and start all services
docker-compose up -d --build
# View logs
docker-compose logs -f
# Stop all services
docker-compose down
```
### Service Endpoints
Once services are running:
- **Auth Service**: `localhost:8081` (gRPC)
- **Identity Service**: `localhost:8082` (gRPC)
- **Authz Service**: `localhost:8083` (gRPC)
- **Audit Service**: `localhost:8084` (gRPC)
- **Consul UI**: http://localhost:8500/ui
- **PostgreSQL**: `localhost:5432`
### Testing Services
Use `grpcurl` to test gRPC endpoints:
```bash
# List available services
grpcurl -plaintext localhost:8081 list
# Example: Create a user
grpcurl -plaintext -d '{
"email": "user@example.com",
"password": "securepassword123",
"username": "testuser"
}' localhost:8082 identity.v1.IdentityService/CreateUser
# Example: Login
grpcurl -plaintext -d '{
"email": "user@example.com",
"password": "securepassword123"
}' localhost:8081 auth.v1.AuthService/Login
```
See [Epic 2 Summary](docs/content/stories/epic2/SUMMARY.md) for detailed testing instructions.
### Configuration ### Configuration
@@ -110,12 +200,27 @@ make lint # Run linters
make fmt # Format code make fmt # Format code
make fmt-check # Check code formatting make fmt-check # Check code formatting
make build # Build platform binary make build # Build platform binary
make generate-proto # Generate gRPC code from protobuf
make generate-ent # Generate Ent ORM code
make clean # Clean build artifacts make clean # Clean build artifacts
make docker-build # Build Docker image make docker-build # Build Docker image
make docker-run # Run Docker container make docker-run # Run Docker container
make verify # Verify code (fmt, lint, test) make verify # Verify code (fmt, lint, test)
``` ```
### Building Services
```bash
# Build individual services
go build ./cmd/auth-service
go build ./cmd/identity-service
go build ./cmd/authz-service
go build ./cmd/audit-service
# Or use make (if targets are defined)
make build-services
```
### Running Tests ### Running Tests
```bash ```bash
@@ -175,6 +280,24 @@ The platform provides a core kernel with essential services:
- **Health & Metrics**: Health check endpoints and Prometheus metrics - **Health & Metrics**: Health check endpoints and Prometheus metrics
- **Error Handling**: Centralized error handling with proper error wrapping - **Error Handling**: Centralized error handling with proper error wrapping
### Core Services (Epic 2 - ✅ Complete)
Four independent microservices provide authentication and authorization:
- **Auth Service** (`cmd/auth-service/`): JWT token generation, validation, refresh tokens
- **Identity Service** (`cmd/identity-service/`): User CRUD, password management, email verification
- **Authz Service** (`cmd/authz-service/`): RBAC/ABAC authorization, permission resolution
- **Audit Service** (`cmd/audit-service/`): Immutable audit logging with querying
Each service:
- Has its own gRPC API
- Uses its own database schema
- Registers with Consul service registry
- Can be deployed independently
- Communicates via gRPC clients
See [Epic 2 Documentation](docs/content/stories/epic2/) for detailed implementation.
### Module System ### Module System
Modules extend the platform's functionality by implementing the `IModule` interface: Modules extend the platform's functionality by implementing the `IModule` interface:
@@ -191,10 +314,11 @@ type IModule interface {
### Security ### Security
- **Authentication**: JWT-based authentication with access and refresh tokens - **Authentication**: JWT-based authentication with access and refresh tokens (Auth Service)
- **Authorization**: RBAC/ABAC authorization system - **User Management**: Secure password hashing with Argon2id, email verification, password reset (Identity Service)
- **Audit Logging**: Immutable audit trail for security-relevant actions - **Authorization**: RBAC/ABAC authorization system with role and permission management (Authz Service)
- **Rate Limiting**: Configurable rate limiting per endpoint - **Audit Logging**: Immutable audit trail for security-relevant actions (Audit Service)
- **Rate Limiting**: Configurable rate limiting per endpoint (planned)
### Observability ### Observability
@@ -209,10 +333,22 @@ Configuration is managed through YAML files and environment variables. See `conf
Key configuration sections: Key configuration sections:
- **Server**: HTTP server settings (port, host, timeouts) - **Server**: HTTP/gRPC server settings (port, host, timeouts)
- **Database**: Database connection settings - **Database**: Database connection settings (PostgreSQL with schema support)
- **Logging**: Log level, format, and output destination - **Logging**: Log level, format, and output destination
- **Authentication**: JWT settings and token configuration - **Authentication**: JWT settings and token configuration
- **Services**: Service-specific ports and hosts
- **Registry**: Service registry settings (Consul)
### Environment Variables
Services can be configured via environment variables:
- `ENVIRONMENT`: `development` or `production`
- `DATABASE_DSN`: PostgreSQL connection string
- `REGISTRY_TYPE`: Service registry type (default: `consul`)
- `REGISTRY_CONSUL_ADDRESS`: Consul address (default: `localhost:8500`)
- `AUTH_JWT_SECRET`: JWT signing secret (required for Auth Service)
## Contributing ## Contributing
@@ -226,12 +362,36 @@ Key configuration sections:
[Add license information here] [Add license information here]
## Implementation Status
### ✅ Completed
- **Epic 0**: Project Setup & Foundation
- **Epic 1**: Core Kernel & Infrastructure
- **Epic 2**: Core Services (Auth, Identity, Authz, Audit) - **Complete**
- All four services implemented as independent microservices
- gRPC APIs with service discovery
- Database schemas and persistence
- Docker support for all services
### 🚧 In Progress / Planned
- **Epic 3**: Module Framework
- **Epic 4**: Sample Feature Module (Blog)
- **Epic 5**: Infrastructure Adapters
- **Epic 6**: Observability & Production Readiness
- **Epic 7**: Testing, Documentation & CI/CD
- **Epic 8**: Advanced Features & Polish
See [Implementation Plan](docs/content/plan.md) for details.
## Links ## Links
- [Architecture Documentation](docs/content/architecture/) - [Architecture Documentation](docs/content/architecture/)
- [ADRs](docs/content/adr/) - [ADRs](docs/content/adr/)
- [Implementation Plan](docs/content/plan.md) - [Implementation Plan](docs/content/plan.md)
- [Playbook](docs/content/playbook.md) - [Playbook](docs/content/playbook.md)
- [Epic 2 Summary](docs/content/stories/epic2/SUMMARY.md) - Core Services implementation details
## Support ## Support

View File

@@ -29,6 +29,9 @@ service IdentityService {
// ResetPassword resets a user's password using a reset token. // ResetPassword resets a user's password using a reset token.
rpc ResetPassword(ResetPasswordRequest) returns (ResetPasswordResponse); rpc ResetPassword(ResetPasswordRequest) returns (ResetPasswordResponse);
// VerifyPassword verifies a user's password.
rpc VerifyPassword(VerifyPasswordRequest) returns (VerifyPasswordResponse);
} }
// User represents a user in the system. // User represents a user in the system.
@@ -132,3 +135,14 @@ message ResetPasswordResponse {
bool success = 1; bool success = 1;
} }
// VerifyPasswordRequest contains email and password.
message VerifyPasswordRequest {
string email = 1;
string password = 2;
}
// VerifyPasswordResponse contains the user if password is valid.
message VerifyPasswordResponse {
User user = 1;
}

40
ci/pre-commit/Dockerfile Normal file
View File

@@ -0,0 +1,40 @@
FROM alpine:latest
# Install system dependencies
RUN apk add --no-cache \
nodejs \
npm \
gcc \
build-base \
musl-dev \
curl \
make \
wget \
tar \
bash \
git \
protobuf \
protobuf-dev
# Install Go 1.25.3
RUN cd /tmp && \
wget -q https://go.dev/dl/go1.25.3.linux-amd64.tar.gz && \
tar -C /usr/local -xzf go1.25.3.linux-amd64.tar.gz && \
rm go1.25.3.linux-amd64.tar.gz
# Set up Go environment
ENV PATH=/usr/local/go/bin:$PATH:/root/go/bin
ENV GOROOT=/usr/local/go
ENV GOPATH=/root/go
ENV CGO_ENABLED=1
ENV GOFLAGS=-buildvcs=false
# Install Go protobuf plugins
RUN go install google.golang.org/protobuf/cmd/protoc-gen-go@latest && \
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
# Install golangci-lint
RUN curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b /root/go/bin
# Set working directory
WORKDIR /workspace

View File

@@ -0,0 +1,36 @@
# Build stage
FROM golang:1.25-alpine AS builder
WORKDIR /build
# Install build dependencies
RUN apk add --no-cache git make
# Copy go mod files
COPY go.mod go.sum ./
RUN go mod download
# Copy source code
COPY . .
# Build the service
WORKDIR /build/cmd/api-gateway
RUN CGO_ENABLED=0 GOOS=linux go build -o api-gateway -a -installsuffix cgo .
# Runtime stage
FROM alpine:latest
RUN apk --no-cache add ca-certificates tzdata
WORKDIR /app
# Copy binary from builder
COPY --from=builder /build/cmd/api-gateway/api-gateway .
# Copy config files
COPY --from=builder /build/config ./config
EXPOSE 8080
CMD ["./api-gateway"]

View File

@@ -58,14 +58,25 @@ func main() {
} }
gateway.SetupRoutes(srv.Router()) gateway.SetupRoutes(srv.Router())
// Register with Consul // Determine port and host for registration
gatewayPort := cfg.GetInt("gateway.port") gatewayPort := cfg.GetInt("gateway.port")
if gatewayPort == 0 { if gatewayPort == 0 {
gatewayPort = 8080 gatewayPort = cfg.GetInt("server.port")
if gatewayPort == 0 {
gatewayPort = 8080
}
} }
// In Docker, always use the Docker service name for health checks
// Consul (also in Docker) needs to reach the service via Docker DNS
gatewayHost := cfg.GetString("gateway.host") gatewayHost := cfg.GetString("gateway.host")
if gatewayHost == "" { if os.Getenv("ENVIRONMENT") == "production" || os.Getenv("DOCKER") == "true" {
gatewayHost = "localhost" gatewayHost = "api-gateway" // Docker service name - required for Consul health checks
} else if gatewayHost == "" {
gatewayHost = cfg.GetString("server.host")
if gatewayHost == "" || gatewayHost == "0.0.0.0" {
gatewayHost = "localhost" // Local development
}
} }
serviceInstance := &registry.ServiceInstance{ serviceInstance := &registry.ServiceInstance{
@@ -75,7 +86,8 @@ func main() {
Port: gatewayPort, Port: gatewayPort,
Tags: []string{"gateway", "http"}, Tags: []string{"gateway", "http"},
Metadata: map[string]string{ Metadata: map[string]string{
"version": "1.0.0", "version": "1.0.0",
"protocol": "http",
}, },
} }

View File

@@ -0,0 +1,156 @@
// Package main provides tests for the API Gateway service entry point.
// Note: Full integration tests for the API Gateway should be in integration test suite
// with testcontainers for service discovery and backend services.
package main
import (
"context"
"testing"
"time"
"git.dcentral.systems/toolz/goplt/internal/di"
"git.dcentral.systems/toolz/goplt/pkg/registry"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.uber.org/fx"
)
// TestAPIGatewaySetup verifies that the API Gateway setup structure is correct.
// Note: Full DI setup requires config files, so this test verifies the structure
// without actually starting the container.
func TestAPIGatewaySetup(t *testing.T) {
t.Parallel()
// Verify that container can be created
// Full setup requires config files which are not available in unit tests
container := di.NewContainer()
require.NotNil(t, container)
// Test that container can be stopped (without starting)
// This verifies the container structure is correct
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel()
// Stop should work even if container wasn't started
err := container.Stop(ctx)
// It's okay if it errors - we're just testing structure
_ = err
}
// TestServiceInstanceCreation verifies that service instance is created correctly.
func TestServiceInstanceCreation(t *testing.T) {
t.Parallel()
tests := []struct {
name string
host string
port int
expected string
}{
{
name: "default host and port",
host: "",
port: 0,
expected: "localhost:8080",
},
{
name: "custom host and port",
host: "gateway.example.com",
port: 9090,
expected: "gateway.example.com:9090",
},
{
name: "custom host default port",
host: "gateway.example.com",
port: 0,
expected: "gateway.example.com:8080",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Simulate service instance creation logic from main.go
gatewayPort := tt.port
if gatewayPort == 0 {
gatewayPort = 8080
}
gatewayHost := tt.host
if gatewayHost == "" {
gatewayHost = "localhost"
}
serviceInstance := &registry.ServiceInstance{
ID: "api-gateway-test",
Name: "api-gateway",
Address: gatewayHost,
Port: gatewayPort,
Tags: []string{"gateway", "http"},
Metadata: map[string]string{
"version": "1.0.0",
},
}
assert.Equal(t, "api-gateway", serviceInstance.Name)
assert.Equal(t, gatewayHost, serviceInstance.Address)
assert.Equal(t, gatewayPort, serviceInstance.Port)
assert.Contains(t, serviceInstance.Tags, "gateway")
assert.Contains(t, serviceInstance.Tags, "http")
assert.Equal(t, "1.0.0", serviceInstance.Metadata["version"])
})
}
}
// TestLifecycleHooksStructure verifies that lifecycle hooks can be registered.
// Note: Full lifecycle testing requires config files and should be done in integration tests.
func TestLifecycleHooksStructure(t *testing.T) {
t.Parallel()
var onStartCalled bool
var onStopCalled bool
// Create a test container with custom lifecycle hooks (without core module)
// This tests the hook registration mechanism
container := di.NewContainer(
fx.Invoke(func(lc fx.Lifecycle) {
lc.Append(fx.Hook{
OnStart: func(ctx context.Context) error {
onStartCalled = true
return nil
},
OnStop: func(ctx context.Context) error {
onStopCalled = true
return nil
},
})
}),
)
require.NotNil(t, container)
// Start the container to trigger OnStart
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
defer cancel()
// Start in goroutine since it blocks on signal
go func() {
_ = container.Start(ctx)
}()
// Give it a moment to start
time.Sleep(50 * time.Millisecond)
// Stop to trigger OnStop
stopCtx, stopCancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
defer stopCancel()
err := container.Stop(stopCtx)
// Stop may error if container wasn't fully started, which is okay
_ = err
// Verify hooks were called
// Note: OnStart may not be called if container fails to start due to missing config
// This is expected in unit tests - full testing should be in integration tests
if onStartCalled {
assert.True(t, onStopCalled, "OnStop should be called if OnStart was called")
}
}

View File

@@ -0,0 +1,36 @@
# Build stage
FROM golang:1.25-alpine AS builder
WORKDIR /build
# Install build dependencies
RUN apk add --no-cache git make
# Copy go mod files
COPY go.mod go.sum ./
RUN go mod download
# Copy source code
COPY . .
# Build the service
WORKDIR /build/cmd/audit-service
RUN CGO_ENABLED=0 GOOS=linux go build -o audit-service -a -installsuffix cgo .
# Runtime stage
FROM alpine:latest
RUN apk --no-cache add ca-certificates tzdata
WORKDIR /app
# Copy binary from builder
COPY --from=builder /build/cmd/audit-service/audit-service .
# Copy config files
COPY --from=builder /build/config ./config
EXPOSE 8084
CMD ["./audit-service"]

View File

@@ -0,0 +1,351 @@
// Package main provides FX providers for Audit Service.
// This file creates the service inline to avoid importing internal packages.
package main
import (
"context"
"fmt"
"math"
"net"
"time"
auditv1 "git.dcentral.systems/toolz/goplt/api/proto/generated/audit/v1"
"git.dcentral.systems/toolz/goplt/internal/ent"
"git.dcentral.systems/toolz/goplt/internal/ent/auditlog"
"git.dcentral.systems/toolz/goplt/internal/infra/database"
"git.dcentral.systems/toolz/goplt/pkg/config"
"git.dcentral.systems/toolz/goplt/pkg/logger"
"go.uber.org/fx"
"go.uber.org/zap"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/health"
"google.golang.org/grpc/health/grpc_health_v1"
"google.golang.org/grpc/reflection"
"google.golang.org/grpc/status"
)
// auditLogEntry represents an audit log entry.
type auditLogEntry struct {
UserID string
Action string
Resource string
ResourceID string
IPAddress string
UserAgent string
Metadata map[string]string
Timestamp int64
}
// auditLogFilters contains filters for querying audit logs.
type auditLogFilters struct {
UserID *string
Action *string
Resource *string
ResourceID *string
StartTime *int64
EndTime *int64
Limit int
Offset int
}
// auditService provides audit logging functionality.
type auditService struct {
client *database.Client
logger logger.Logger
}
// record records an audit log entry.
func (s *auditService) record(ctx context.Context, entry *auditLogEntry) error {
// Convert metadata map to JSON
metadataJSON := make(map[string]interface{})
for k, v := range entry.Metadata {
metadataJSON[k] = v
}
// Create audit log entry
timestamp := time.Unix(entry.Timestamp, 0)
if entry.Timestamp == 0 {
timestamp = time.Now()
}
create := s.client.AuditLog.Create().
SetID(fmt.Sprintf("%d-%d", time.Now().Unix(), time.Now().UnixNano()%1000000)).
SetUserID(entry.UserID).
SetAction(entry.Action).
SetMetadata(metadataJSON).
SetTimestamp(timestamp)
if entry.Resource != "" {
create = create.SetResource(entry.Resource)
}
if entry.ResourceID != "" {
create = create.SetResourceID(entry.ResourceID)
}
if entry.IPAddress != "" {
create = create.SetIPAddress(entry.IPAddress)
}
if entry.UserAgent != "" {
create = create.SetUserAgent(entry.UserAgent)
}
_, err := create.Save(ctx)
if err != nil {
s.logger.Error("Failed to record audit log",
zap.Error(err),
zap.String("user_id", entry.UserID),
zap.String("action", entry.Action),
)
return fmt.Errorf("failed to record audit log: %w", err)
}
return nil
}
// query queries audit logs based on filters.
func (s *auditService) query(ctx context.Context, filters *auditLogFilters) ([]*auditLogEntry, error) {
query := s.client.AuditLog.Query()
// Apply filters
if filters.UserID != nil {
query = query.Where(auditlog.UserID(*filters.UserID))
}
if filters.Action != nil {
query = query.Where(auditlog.Action(*filters.Action))
}
if filters.Resource != nil {
query = query.Where(auditlog.Resource(*filters.Resource))
}
if filters.ResourceID != nil {
query = query.Where(auditlog.ResourceID(*filters.ResourceID))
}
if filters.StartTime != nil {
query = query.Where(auditlog.TimestampGTE(time.Unix(*filters.StartTime, 0)))
}
if filters.EndTime != nil {
query = query.Where(auditlog.TimestampLTE(time.Unix(*filters.EndTime, 0)))
}
// Apply pagination
if filters.Limit > 0 {
query = query.Limit(filters.Limit)
}
if filters.Offset > 0 {
query = query.Offset(filters.Offset)
}
// Order by timestamp descending
query = query.Order(ent.Desc(auditlog.FieldTimestamp))
// Execute query
auditLogs, err := query.All(ctx)
if err != nil {
s.logger.Error("Failed to query audit logs",
zap.Error(err),
)
return nil, fmt.Errorf("failed to query audit logs: %w", err)
}
// Convert to service entries
entries := make([]*auditLogEntry, 0, len(auditLogs))
for _, log := range auditLogs {
// Convert metadata from map[string]interface{} to map[string]string
metadata := make(map[string]string)
if log.Metadata != nil {
for k, v := range log.Metadata {
if str, ok := v.(string); ok {
metadata[k] = str
} else {
metadata[k] = fmt.Sprintf("%v", v)
}
}
}
entry := &auditLogEntry{
UserID: log.UserID,
Action: log.Action,
Resource: log.Resource,
ResourceID: log.ResourceID,
IPAddress: log.IPAddress,
UserAgent: log.UserAgent,
Metadata: metadata,
Timestamp: log.Timestamp.Unix(),
}
entries = append(entries, entry)
}
return entries, nil
}
// auditServerImpl implements the AuditService gRPC server.
type auditServerImpl struct {
auditv1.UnimplementedAuditServiceServer
service *auditService
logger *zap.Logger
}
// Record records an audit log entry.
func (s *auditServerImpl) Record(ctx context.Context, req *auditv1.RecordRequest) (*auditv1.RecordResponse, error) {
if req.Entry == nil {
return nil, status.Error(codes.InvalidArgument, "entry is required")
}
entry := req.Entry
// Convert proto entry to service entry
serviceEntry := &auditLogEntry{
UserID: entry.UserId,
Action: entry.Action,
Resource: entry.Resource,
ResourceID: entry.ResourceId,
IPAddress: entry.IpAddress,
UserAgent: entry.UserAgent,
Metadata: entry.Metadata,
Timestamp: entry.Timestamp,
}
// Record the audit log
if err := s.service.record(ctx, serviceEntry); err != nil {
s.logger.Error("Failed to record audit log",
zap.Error(err),
zap.String("user_id", entry.UserId),
zap.String("action", entry.Action),
)
return nil, status.Errorf(codes.Internal, "failed to record audit log: %v", err)
}
return &auditv1.RecordResponse{
Success: true,
}, nil
}
// Query queries audit logs based on filters.
func (s *auditServerImpl) Query(ctx context.Context, req *auditv1.QueryRequest) (*auditv1.QueryResponse, error) {
// Convert proto filters to service filters
filters := &auditLogFilters{
Limit: int(req.Limit),
Offset: int(req.Offset),
}
if req.UserId != nil {
userID := *req.UserId
filters.UserID = &userID
}
if req.Action != nil {
action := *req.Action
filters.Action = &action
}
if req.Resource != nil {
resource := *req.Resource
filters.Resource = &resource
}
if req.ResourceId != nil {
resourceID := *req.ResourceId
filters.ResourceID = &resourceID
}
if req.StartTime != nil {
startTime := *req.StartTime
filters.StartTime = &startTime
}
if req.EndTime != nil {
endTime := *req.EndTime
filters.EndTime = &endTime
}
// Query audit logs
entries, err := s.service.query(ctx, filters)
if err != nil {
s.logger.Error("Failed to query audit logs",
zap.Error(err),
)
return nil, status.Errorf(codes.Internal, "failed to query audit logs: %v", err)
}
// Convert service entries to proto entries
protoEntries := make([]*auditv1.AuditLogEntry, 0, len(entries))
for _, entry := range entries {
protoEntries = append(protoEntries, &auditv1.AuditLogEntry{
UserId: entry.UserID,
Action: entry.Action,
Resource: entry.Resource,
ResourceId: entry.ResourceID,
IpAddress: entry.IPAddress,
UserAgent: entry.UserAgent,
Metadata: entry.Metadata,
Timestamp: entry.Timestamp,
})
}
total := len(protoEntries)
var totalInt32 int32
if total > math.MaxInt32 {
totalInt32 = math.MaxInt32
} else {
totalInt32 = int32(total)
}
return &auditv1.QueryResponse{
Entries: protoEntries,
Total: totalInt32,
}, nil
}
// provideAuditService creates the audit service and gRPC server.
func provideAuditService() fx.Option {
return fx.Options(
// Audit service
fx.Provide(func(client *database.Client, log logger.Logger) (*auditService, error) {
return &auditService{
client: client,
logger: log,
}, nil
}),
// gRPC server implementation
fx.Provide(func(auditService *auditService, log logger.Logger) (*auditServerImpl, error) {
zapLogger, _ := zap.NewProduction()
return &auditServerImpl{
service: auditService,
logger: zapLogger,
}, nil
}),
// gRPC server wrapper
fx.Provide(func(
serverImpl *auditServerImpl,
cfg config.ConfigProvider,
log logger.Logger,
) (*grpcServerWrapper, error) {
port := cfg.GetInt("services.audit.port")
if port == 0 {
port = 8084
}
addr := fmt.Sprintf("0.0.0.0:%d", port)
listener, err := net.Listen("tcp", addr)
if err != nil {
return nil, fmt.Errorf("failed to listen on %s: %w", addr, err)
}
grpcServer := grpc.NewServer()
auditv1.RegisterAuditServiceServer(grpcServer, serverImpl)
// Register health service
healthServer := health.NewServer()
grpc_health_v1.RegisterHealthServer(grpcServer, healthServer)
// Set serving status for the default service (empty string) - this is what Consul checks
healthServer.SetServingStatus("", grpc_health_v1.HealthCheckResponse_SERVING)
// Also set for the specific service name
healthServer.SetServingStatus("audit.v1.AuditService", grpc_health_v1.HealthCheckResponse_SERVING)
// Register reflection for grpcurl
reflection.Register(grpcServer)
return &grpcServerWrapper{
server: grpcServer,
listener: listener,
port: port,
logger: log,
}, nil
}),
)
}

228
cmd/audit-service/main.go Normal file
View File

@@ -0,0 +1,228 @@
// Package main provides the entry point for the Audit Service.
package main
import (
"context"
"fmt"
"net"
"os"
"os/signal"
"syscall"
"time"
"git.dcentral.systems/toolz/goplt/internal/di"
healthpkg "git.dcentral.systems/toolz/goplt/internal/health"
"git.dcentral.systems/toolz/goplt/internal/infra/database"
"git.dcentral.systems/toolz/goplt/pkg/config"
"git.dcentral.systems/toolz/goplt/pkg/logger"
"git.dcentral.systems/toolz/goplt/pkg/registry"
"go.uber.org/fx"
"go.uber.org/zap"
"google.golang.org/grpc"
)
// grpcServerWrapper wraps the gRPC server for lifecycle management.
type grpcServerWrapper struct {
server *grpc.Server
listener net.Listener
port int
logger logger.Logger
}
func (s *grpcServerWrapper) Start() error {
s.logger.Info("Starting Audit Service gRPC server",
zap.Int("port", s.port),
zap.String("addr", s.listener.Addr().String()),
)
errChan := make(chan error, 1)
go func() {
if err := s.server.Serve(s.listener); err != nil {
errChan <- err
}
}()
select {
case err := <-errChan:
return fmt.Errorf("gRPC server failed to start: %w", err)
case <-time.After(100 * time.Millisecond):
s.logger.Info("Audit Service gRPC server started successfully",
zap.Int("port", s.port),
)
return nil
}
}
func (s *grpcServerWrapper) Stop(ctx context.Context) error {
s.logger.Info("Stopping Audit Service gRPC server")
stopped := make(chan struct{})
go func() {
s.server.GracefulStop()
close(stopped)
}()
select {
case <-stopped:
s.logger.Info("Audit Service gRPC server stopped gracefully")
return nil
case <-ctx.Done():
s.logger.Warn("Audit Service gRPC server stop timeout, forcing stop")
s.server.Stop()
return ctx.Err()
}
}
func (s *grpcServerWrapper) Port() int {
return s.port
}
func main() {
// Create DI container - services will be created via fx.Provide
// Note: CoreModule() is automatically included by NewContainer()
container := di.NewContainer(
// Database for audit service (audit schema)
fx.Provide(func(cfg config.ConfigProvider, log logger.Logger) (*database.Client, error) {
dsn := cfg.GetString("database.dsn")
if dsn == "" {
return nil, fmt.Errorf("database.dsn is required")
}
client, err := database.NewClientWithSchema(dsn, "audit")
if err != nil {
return nil, err
}
// Run migrations
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
if err := client.Migrate(ctx); err != nil {
log.Warn("Failed to run migrations",
zap.Error(err),
)
} else {
log.Info("Database migrations completed for audit service")
}
return client, nil
}),
// Register database health checker with existing health registry
fx.Invoke(func(registry *healthpkg.Registry, db *database.Client) {
registry.Register("database", healthpkg.NewDatabaseChecker(db))
}),
// Provide audit service and gRPC server (defined in audit_service_fx.go)
provideAuditService(),
// Lifecycle hooks
fx.Invoke(registerLifecycle),
)
// Create root context
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// Handle signals
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
// Start the application
if err := container.Start(ctx); err != nil {
log := logger.GetGlobalLogger()
if log != nil {
log.Error("Failed to start Audit Service",
logger.Error(err),
)
} else {
fmt.Fprintf(os.Stderr, "Failed to start Audit Service: %v\n", err)
}
os.Exit(1)
}
// Wait for interrupt signal
<-sigChan
fmt.Println("\nShutting down Audit Service...")
// Create shutdown context with timeout
shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 30*time.Second)
defer shutdownCancel()
// Stop the application
if err := container.Stop(shutdownCtx); err != nil {
log := logger.GetGlobalLogger()
if log != nil {
log.Error("Error during Audit Service shutdown",
logger.Error(err),
)
} else {
fmt.Fprintf(os.Stderr, "Error during shutdown: %v\n", err)
}
os.Exit(1)
}
fmt.Println("Audit Service stopped successfully")
}
// registerLifecycle registers lifecycle hooks for the service.
func registerLifecycle(
lc fx.Lifecycle,
grpcServer *grpcServerWrapper,
serviceRegistry registry.ServiceRegistry,
cfg config.ConfigProvider,
log logger.Logger,
) {
lc.Append(fx.Hook{
OnStart: func(ctx context.Context) error {
// Start gRPC server
if err := grpcServer.Start(); err != nil {
return fmt.Errorf("failed to start gRPC server: %w", err)
}
// Register with service registry
serviceID := fmt.Sprintf("audit-service-%d", time.Now().Unix())
// In Docker, always use the Docker service name for health checks
// Consul (also in Docker) needs to reach the service via Docker DNS
host := cfg.GetString("services.audit.host")
if os.Getenv("ENVIRONMENT") == "production" || os.Getenv("DOCKER") == "true" {
host = "audit-service" // Docker service name - required for Consul health checks
} else if host == "" {
host = "localhost" // Local development
}
port := grpcServer.Port()
instance := &registry.ServiceInstance{
ID: serviceID,
Name: "audit-service",
Address: host,
Port: port,
Tags: []string{"grpc", "audit"},
Metadata: map[string]string{
"version": "1.0.0",
"protocol": "grpc",
},
}
if err := serviceRegistry.Register(ctx, instance); err != nil {
log.Warn("Failed to register with service registry",
zap.Error(err),
)
} else {
log.Info("Registered Audit Service with service registry",
zap.String("service_id", serviceID),
zap.String("name", instance.Name),
zap.Int("port", port),
)
}
return nil
},
OnStop: func(ctx context.Context) error {
// Stop gRPC server
if err := grpcServer.Stop(ctx); err != nil {
return fmt.Errorf("failed to stop gRPC server: %w", err)
}
return nil
},
})
}

View File

@@ -0,0 +1,36 @@
# Build stage
FROM golang:1.25-alpine AS builder
WORKDIR /build
# Install build dependencies
RUN apk add --no-cache git make
# Copy go mod files
COPY go.mod go.sum ./
RUN go mod download
# Copy source code
COPY . .
# Build the service
WORKDIR /build/cmd/auth-service
RUN CGO_ENABLED=0 GOOS=linux go build -o auth-service -a -installsuffix cgo .
# Runtime stage
FROM alpine:latest
RUN apk --no-cache add ca-certificates tzdata
WORKDIR /app
# Copy binary from builder
COPY --from=builder /build/cmd/auth-service/auth-service .
# Copy config files
COPY --from=builder /build/config ./config
EXPOSE 8081
CMD ["./auth-service"]

View File

@@ -0,0 +1,425 @@
// Package main provides FX providers for Auth Service.
// This file creates the service inline to avoid importing internal packages.
package main
import (
"context"
"crypto/rand"
"crypto/sha256"
"encoding/hex"
"fmt"
"net"
"time"
authv1 "git.dcentral.systems/toolz/goplt/api/proto/generated/auth/v1"
"git.dcentral.systems/toolz/goplt/internal/ent/refreshtoken"
"git.dcentral.systems/toolz/goplt/internal/infra/database"
"git.dcentral.systems/toolz/goplt/pkg/config"
"git.dcentral.systems/toolz/goplt/pkg/logger"
"git.dcentral.systems/toolz/goplt/pkg/services"
"github.com/golang-jwt/jwt/v5"
"go.uber.org/fx"
"go.uber.org/zap"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/health"
"google.golang.org/grpc/health/grpc_health_v1"
"google.golang.org/grpc/reflection"
"google.golang.org/grpc/status"
)
const (
accessTokenLifetime = 15 * time.Minute
refreshTokenLifetime = 7 * 24 * time.Hour
)
// authService provides authentication functionality.
type authService struct {
client *database.Client
logger logger.Logger
identityClient services.IdentityServiceClient
authzClient services.AuthzServiceClient
jwtSecret []byte
accessTokenExpiry time.Duration
refreshTokenExpiry time.Duration
}
// hashToken hashes a token using SHA256.
func hashToken(token string) string {
h := sha256.Sum256([]byte(token))
return hex.EncodeToString(h[:])
}
// generateRefreshToken generates a random refresh token.
func generateRefreshToken() (string, error) {
b := make([]byte, 32)
if _, err := rand.Read(b); err != nil {
return "", fmt.Errorf("failed to generate token: %w", err)
}
return hex.EncodeToString(b), nil
}
// generateAccessToken generates a JWT access token.
func (s *authService) generateAccessToken(userID, email string, roles []string) (string, int64, error) {
expiresAt := time.Now().Add(s.accessTokenExpiry)
claims := jwt.MapClaims{
"sub": userID,
"email": email,
"roles": roles,
"exp": expiresAt.Unix(),
"iat": time.Now().Unix(),
"token_type": "access",
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
tokenString, err := token.SignedString(s.jwtSecret)
if err != nil {
return "", 0, fmt.Errorf("failed to sign token: %w", err)
}
return tokenString, int64(s.accessTokenExpiry.Seconds()), nil
}
// generateRefreshToken generates a refresh token and stores it in the database.
func (s *authService) generateRefreshToken(ctx context.Context, userID string) (string, error) {
token, err := generateRefreshToken()
if err != nil {
return "", err
}
tokenHash := hashToken(token)
expiresAt := time.Now().Add(s.refreshTokenExpiry)
// Store refresh token in database
_, err = s.client.RefreshToken.Create().
SetID(fmt.Sprintf("%d-%d", time.Now().Unix(), time.Now().UnixNano()%1000000)).
SetUserID(userID).
SetTokenHash(tokenHash).
SetExpiresAt(expiresAt).
Save(ctx)
if err != nil {
return "", fmt.Errorf("failed to store refresh token: %w", err)
}
return token, nil
}
// validateAccessToken validates a JWT access token.
func (s *authService) validateAccessToken(tokenString string) (*jwt.Token, jwt.MapClaims, error) {
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
}
return s.jwtSecret, nil
})
if err != nil {
return nil, nil, fmt.Errorf("failed to parse token: %w", err)
}
if !token.Valid {
return nil, nil, fmt.Errorf("invalid token")
}
claims, ok := token.Claims.(jwt.MapClaims)
if !ok {
return nil, nil, fmt.Errorf("invalid token claims")
}
// Check expiration
if exp, ok := claims["exp"].(float64); ok {
if time.Now().Unix() > int64(exp) {
return nil, nil, fmt.Errorf("token expired")
}
}
return token, claims, nil
}
// validateRefreshToken validates a refresh token.
func (s *authService) validateRefreshToken(ctx context.Context, tokenString string) (string, error) {
tokenHash := hashToken(tokenString)
// Find refresh token by hash
rt, err := s.client.RefreshToken.Query().
Where(refreshtoken.TokenHash(tokenHash)).
Only(ctx)
if err != nil {
return "", fmt.Errorf("invalid refresh token")
}
// Check if token has expired
if rt.ExpiresAt.Before(time.Now()) {
// Delete expired token
_ = s.client.RefreshToken.DeleteOneID(rt.ID).Exec(ctx)
return "", fmt.Errorf("refresh token expired")
}
return rt.UserID, nil
}
// revokeRefreshToken revokes a refresh token.
func (s *authService) revokeRefreshToken(ctx context.Context, tokenString string) error {
tokenHash := hashToken(tokenString)
// Find and delete refresh token
rt, err := s.client.RefreshToken.Query().
Where(refreshtoken.TokenHash(tokenHash)).
Only(ctx)
if err != nil {
// Token not found, consider it already revoked
return nil
}
return s.client.RefreshToken.DeleteOneID(rt.ID).Exec(ctx)
}
// login authenticates a user and returns tokens.
func (s *authService) login(ctx context.Context, email, password string) (*authv1.LoginResponse, error) {
// Verify credentials with Identity Service
user, err := s.identityClient.VerifyPassword(ctx, email, password)
if err != nil {
return nil, fmt.Errorf("invalid credentials")
}
// Get user roles from Authz Service
roles := []string{}
if s.authzClient != nil {
userRoles, err := s.authzClient.GetUserRoles(ctx, user.ID)
if err != nil {
s.logger.Warn("Failed to get user roles",
zap.String("user_id", user.ID),
zap.Error(err),
)
// Continue without roles rather than failing login
} else {
for _, role := range userRoles {
roles = append(roles, role.Name)
}
}
}
// Generate tokens
accessToken, expiresIn, err := s.generateAccessToken(user.ID, user.Email, roles)
if err != nil {
return nil, fmt.Errorf("failed to generate access token: %w", err)
}
refreshToken, err := s.generateRefreshToken(ctx, user.ID)
if err != nil {
return nil, fmt.Errorf("failed to generate refresh token: %w", err)
}
return &authv1.LoginResponse{
AccessToken: accessToken,
RefreshToken: refreshToken,
ExpiresIn: expiresIn,
TokenType: "Bearer",
}, nil
}
// refreshToken refreshes an access token.
func (s *authService) refreshToken(ctx context.Context, refreshTokenString string) (*authv1.RefreshTokenResponse, error) {
// Validate refresh token
userID, err := s.validateRefreshToken(ctx, refreshTokenString)
if err != nil {
return nil, err
}
// Get user from Identity Service
user, err := s.identityClient.GetUser(ctx, userID)
if err != nil {
return nil, fmt.Errorf("user not found")
}
// Get user roles from Authz Service
roles := []string{}
if s.authzClient != nil {
userRoles, err := s.authzClient.GetUserRoles(ctx, user.ID)
if err != nil {
s.logger.Warn("Failed to get user roles",
zap.String("user_id", user.ID),
zap.Error(err),
)
// Continue without roles rather than failing refresh
} else {
for _, role := range userRoles {
roles = append(roles, role.Name)
}
}
}
// Generate new tokens
accessToken, expiresIn, err := s.generateAccessToken(user.ID, user.Email, roles)
if err != nil {
return nil, fmt.Errorf("failed to generate access token: %w", err)
}
// Generate new refresh token (rotate)
newRefreshToken, err := s.generateRefreshToken(ctx, user.ID)
if err != nil {
return nil, fmt.Errorf("failed to generate refresh token: %w", err)
}
// Revoke old refresh token
_ = s.revokeRefreshToken(ctx, refreshTokenString)
return &authv1.RefreshTokenResponse{
AccessToken: accessToken,
RefreshToken: newRefreshToken,
ExpiresIn: expiresIn,
TokenType: "Bearer",
}, nil
}
// validateToken validates a JWT token.
func (s *authService) validateToken(tokenString string) (*authv1.ValidateTokenResponse, error) {
_, claims, err := s.validateAccessToken(tokenString)
if err != nil {
return nil, err
}
userID, _ := claims["sub"].(string)
email, _ := claims["email"].(string)
exp, _ := claims["exp"].(float64)
roles := []string{}
if rolesClaim, ok := claims["roles"].([]interface{}); ok {
for _, r := range rolesClaim {
if role, ok := r.(string); ok {
roles = append(roles, role)
}
}
}
return &authv1.ValidateTokenResponse{
UserId: userID,
Email: email,
Roles: roles,
ExpiresAt: int64(exp),
}, nil
}
// logout invalidates a refresh token.
func (s *authService) logout(ctx context.Context, refreshTokenString string) error {
return s.revokeRefreshToken(ctx, refreshTokenString)
}
// authServerImpl implements the AuthService gRPC server.
type authServerImpl struct {
authv1.UnimplementedAuthServiceServer
service *authService
logger *zap.Logger
}
// Login authenticates a user and returns tokens.
func (s *authServerImpl) Login(ctx context.Context, req *authv1.LoginRequest) (*authv1.LoginResponse, error) {
resp, err := s.service.login(ctx, req.Email, req.Password)
if err != nil {
return nil, status.Errorf(codes.Unauthenticated, "login failed: %v", err)
}
return resp, nil
}
// RefreshToken refreshes an access token.
func (s *authServerImpl) RefreshToken(ctx context.Context, req *authv1.RefreshTokenRequest) (*authv1.RefreshTokenResponse, error) {
resp, err := s.service.refreshToken(ctx, req.RefreshToken)
if err != nil {
return nil, status.Errorf(codes.Unauthenticated, "refresh failed: %v", err)
}
return resp, nil
}
// ValidateToken validates a JWT token.
func (s *authServerImpl) ValidateToken(ctx context.Context, req *authv1.ValidateTokenRequest) (*authv1.ValidateTokenResponse, error) {
resp, err := s.service.validateToken(req.Token)
if err != nil {
return nil, status.Errorf(codes.Unauthenticated, "validation failed: %v", err)
}
return resp, nil
}
// Logout invalidates a refresh token.
func (s *authServerImpl) Logout(ctx context.Context, req *authv1.LogoutRequest) (*authv1.LogoutResponse, error) {
if err := s.service.logout(ctx, req.RefreshToken); err != nil {
return nil, status.Errorf(codes.Internal, "logout failed: %v", err)
}
return &authv1.LogoutResponse{Success: true}, nil
}
// provideAuthService creates the auth service and gRPC server.
func provideAuthService() fx.Option {
return fx.Options(
// Auth service
fx.Provide(func(
client *database.Client,
log logger.Logger,
identityClient services.IdentityServiceClient,
authzClient services.AuthzServiceClient,
cfg config.ConfigProvider,
) (*authService, error) {
jwtSecret := cfg.GetString("auth.jwt_secret")
if jwtSecret == "" {
return nil, fmt.Errorf("auth.jwt_secret is required in configuration")
}
return &authService{
client: client,
logger: log,
identityClient: identityClient,
authzClient: authzClient,
jwtSecret: []byte(jwtSecret),
accessTokenExpiry: accessTokenLifetime,
refreshTokenExpiry: refreshTokenLifetime,
}, nil
}),
// gRPC server implementation
fx.Provide(func(authService *authService, log logger.Logger) (*authServerImpl, error) {
zapLogger, _ := zap.NewProduction()
return &authServerImpl{
service: authService,
logger: zapLogger,
}, nil
}),
// gRPC server wrapper
fx.Provide(func(
serverImpl *authServerImpl,
cfg config.ConfigProvider,
log logger.Logger,
) (*grpcServerWrapper, error) {
port := cfg.GetInt("services.auth.port")
if port == 0 {
port = 8081
}
addr := fmt.Sprintf("0.0.0.0:%d", port)
listener, err := net.Listen("tcp", addr)
if err != nil {
return nil, fmt.Errorf("failed to listen on %s: %w", addr, err)
}
grpcServer := grpc.NewServer()
authv1.RegisterAuthServiceServer(grpcServer, serverImpl)
// Register health service
healthServer := health.NewServer()
grpc_health_v1.RegisterHealthServer(grpcServer, healthServer)
// Set serving status for the default service (empty string) - this is what Consul checks
healthServer.SetServingStatus("", grpc_health_v1.HealthCheckResponse_SERVING)
// Also set for the specific service name
healthServer.SetServingStatus("auth.v1.AuthService", grpc_health_v1.HealthCheckResponse_SERVING)
// Register reflection for grpcurl
reflection.Register(grpcServer)
return &grpcServerWrapper{
server: grpcServer,
listener: listener,
port: port,
logger: log,
}, nil
}),
)
}

240
cmd/auth-service/main.go Normal file
View File

@@ -0,0 +1,240 @@
// Package main provides the entry point for the Auth Service.
package main
import (
"context"
"fmt"
"net"
"os"
"os/signal"
"syscall"
"time"
"git.dcentral.systems/toolz/goplt/internal/client"
"git.dcentral.systems/toolz/goplt/internal/di"
healthpkg "git.dcentral.systems/toolz/goplt/internal/health"
"git.dcentral.systems/toolz/goplt/internal/infra/database"
"git.dcentral.systems/toolz/goplt/pkg/config"
"git.dcentral.systems/toolz/goplt/pkg/logger"
"git.dcentral.systems/toolz/goplt/pkg/registry"
"git.dcentral.systems/toolz/goplt/pkg/services"
"go.uber.org/fx"
"go.uber.org/zap"
"google.golang.org/grpc"
)
// grpcServerWrapper wraps the gRPC server for lifecycle management.
type grpcServerWrapper struct {
server *grpc.Server
listener net.Listener
port int
logger logger.Logger
}
func (s *grpcServerWrapper) Start() error {
s.logger.Info("Starting Auth Service gRPC server",
zap.Int("port", s.port),
zap.String("addr", s.listener.Addr().String()),
)
errChan := make(chan error, 1)
go func() {
if err := s.server.Serve(s.listener); err != nil {
errChan <- err
}
}()
select {
case err := <-errChan:
return fmt.Errorf("gRPC server failed to start: %w", err)
case <-time.After(100 * time.Millisecond):
s.logger.Info("Auth Service gRPC server started successfully",
zap.Int("port", s.port),
)
return nil
}
}
func (s *grpcServerWrapper) Stop(ctx context.Context) error {
s.logger.Info("Stopping Auth Service gRPC server")
stopped := make(chan struct{})
go func() {
s.server.GracefulStop()
close(stopped)
}()
select {
case <-stopped:
s.logger.Info("Auth Service gRPC server stopped gracefully")
return nil
case <-ctx.Done():
s.logger.Warn("Auth Service gRPC server stop timeout, forcing stop")
s.server.Stop()
return ctx.Err()
}
}
func (s *grpcServerWrapper) Port() int {
return s.port
}
func main() {
// Create DI container
// Note: CoreModule() is automatically included by NewContainer()
container := di.NewContainer(
// Database for auth service (auth schema)
fx.Provide(func(cfg config.ConfigProvider, log logger.Logger) (*database.Client, error) {
dsn := cfg.GetString("database.dsn")
if dsn == "" {
return nil, fmt.Errorf("database.dsn is required")
}
client, err := database.NewClientWithSchema(dsn, "auth")
if err != nil {
return nil, err
}
// Run migrations
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
if err := client.Migrate(ctx); err != nil {
log.Warn("Failed to run migrations",
zap.Error(err),
)
} else {
log.Info("Database migrations completed for auth service")
}
return client, nil
}),
// Register database health checker with existing health registry
fx.Invoke(func(registry *healthpkg.Registry, db *database.Client) {
registry.Register("database", healthpkg.NewDatabaseChecker(db))
}),
// Identity Service client
fx.Provide(func(factory *client.ServiceClientFactory) (services.IdentityServiceClient, error) {
return factory.GetIdentityClient()
}),
// Authz Service client
fx.Provide(func(factory *client.ServiceClientFactory) (services.AuthzServiceClient, error) {
return factory.GetAuthzClient()
}),
// Provide auth service and gRPC server (defined in auth_service_fx.go)
provideAuthService(),
// Lifecycle hooks
fx.Invoke(registerLifecycle),
)
// Create root context
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// Handle signals
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
// Start the application
if err := container.Start(ctx); err != nil {
log := logger.GetGlobalLogger()
if log != nil {
log.Error("Failed to start Auth Service",
logger.Error(err),
)
} else {
fmt.Fprintf(os.Stderr, "Failed to start Auth Service: %v\n", err)
}
os.Exit(1)
}
// Wait for interrupt signal
<-sigChan
fmt.Println("\nShutting down Auth Service...")
// Create shutdown context with timeout
shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 30*time.Second)
defer shutdownCancel()
// Stop the application
if err := container.Stop(shutdownCtx); err != nil {
log := logger.GetGlobalLogger()
if log != nil {
log.Error("Error during Auth Service shutdown",
logger.Error(err),
)
} else {
fmt.Fprintf(os.Stderr, "Error during shutdown: %v\n", err)
}
os.Exit(1)
}
fmt.Println("Auth Service stopped successfully")
}
// registerLifecycle registers lifecycle hooks for the service.
func registerLifecycle(
lc fx.Lifecycle,
grpcServer *grpcServerWrapper,
serviceRegistry registry.ServiceRegistry,
cfg config.ConfigProvider,
log logger.Logger,
) {
lc.Append(fx.Hook{
OnStart: func(ctx context.Context) error {
// Start gRPC server
if err := grpcServer.Start(); err != nil {
return fmt.Errorf("failed to start gRPC server: %w", err)
}
// Register with service registry
serviceID := fmt.Sprintf("auth-service-%d", time.Now().Unix())
// In Docker, always use the Docker service name for health checks
// Consul (also in Docker) needs to reach the service via Docker DNS
host := cfg.GetString("services.auth.host")
if os.Getenv("ENVIRONMENT") == "production" || os.Getenv("DOCKER") == "true" {
host = "auth-service" // Docker service name - required for Consul health checks
} else if host == "" {
host = "localhost" // Local development
}
port := grpcServer.Port()
instance := &registry.ServiceInstance{
ID: serviceID,
Name: "auth-service",
Address: host,
Port: port,
Tags: []string{"grpc", "auth"},
Metadata: map[string]string{
"version": "1.0.0",
"protocol": "grpc",
},
}
if err := serviceRegistry.Register(ctx, instance); err != nil {
log.Warn("Failed to register with service registry",
zap.Error(err),
)
} else {
log.Info("Registered Auth Service with service registry",
zap.String("service_id", serviceID),
zap.String("name", instance.Name),
zap.Int("port", port),
)
}
return nil
},
OnStop: func(ctx context.Context) error {
// Stop gRPC server
if err := grpcServer.Stop(ctx); err != nil {
return fmt.Errorf("failed to stop gRPC server: %w", err)
}
return nil
},
})
}

View File

@@ -0,0 +1,36 @@
# Build stage
FROM golang:1.25-alpine AS builder
WORKDIR /build
# Install build dependencies
RUN apk add --no-cache git make
# Copy go mod files
COPY go.mod go.sum ./
RUN go mod download
# Copy source code
COPY . .
# Build the service
WORKDIR /build/cmd/authz-service
RUN CGO_ENABLED=0 GOOS=linux go build -o authz-service -a -installsuffix cgo .
# Runtime stage
FROM alpine:latest
RUN apk --no-cache add ca-certificates tzdata
WORKDIR /app
# Copy binary from builder
COPY --from=builder /build/cmd/authz-service/authz-service .
# Copy config files
COPY --from=builder /build/config ./config
EXPOSE 8083
CMD ["./authz-service"]

View File

@@ -0,0 +1,290 @@
// Package main provides FX providers for Authz Service.
// This file creates the service inline to avoid importing internal packages.
package main
import (
"context"
"fmt"
"net"
authzv1 "git.dcentral.systems/toolz/goplt/api/proto/generated/authz/v1"
"git.dcentral.systems/toolz/goplt/internal/ent"
"git.dcentral.systems/toolz/goplt/internal/ent/userrole"
"git.dcentral.systems/toolz/goplt/internal/infra/database"
"git.dcentral.systems/toolz/goplt/pkg/config"
"git.dcentral.systems/toolz/goplt/pkg/logger"
"go.uber.org/fx"
"go.uber.org/zap"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/health"
"google.golang.org/grpc/health/grpc_health_v1"
"google.golang.org/grpc/reflection"
"google.golang.org/grpc/status"
)
// authzService provides authorization functionality.
type authzService struct {
client *database.Client
logger logger.Logger
}
// hasPermission checks if a user has a specific permission.
func (s *authzService) hasPermission(ctx context.Context, userID, permCode string) (bool, error) {
// Get user's roles
userRoles, err := s.client.UserRole.Query().
Where(userrole.UserID(userID)).
WithRole(func(rq *ent.RoleQuery) {
rq.WithRolePermissions(func(rpq *ent.RolePermissionQuery) {
rpq.WithPermission()
})
}).
All(ctx)
if err != nil {
return false, fmt.Errorf("failed to get user roles: %w", err)
}
// Check if any role has the permission
for _, ur := range userRoles {
role := ur.Edges.Role
if role == nil {
continue
}
rolePerms := role.Edges.RolePermissions
for _, rp := range rolePerms {
perm := rp.Edges.Permission
if perm != nil && perm.Name == permCode {
return true, nil
}
}
}
return false, nil
}
// getUserPermissions returns all permissions for a user.
func (s *authzService) getUserPermissions(ctx context.Context, userID string) ([]*ent.Permission, error) {
// Get user's roles
userRoles, err := s.client.UserRole.Query().
Where(userrole.UserID(userID)).
WithRole(func(rq *ent.RoleQuery) {
rq.WithRolePermissions(func(rpq *ent.RolePermissionQuery) {
rpq.WithPermission()
})
}).
All(ctx)
if err != nil {
return nil, fmt.Errorf("failed to get user roles: %w", err)
}
// Collect unique permissions
permMap := make(map[string]*ent.Permission)
for _, ur := range userRoles {
role := ur.Edges.Role
if role == nil {
continue
}
rolePerms := role.Edges.RolePermissions
for _, rp := range rolePerms {
perm := rp.Edges.Permission
if perm != nil {
permMap[perm.ID] = perm
}
}
}
// Convert map to slice
permissions := make([]*ent.Permission, 0, len(permMap))
for _, perm := range permMap {
permissions = append(permissions, perm)
}
return permissions, nil
}
// getUserRoles returns all roles for a user.
func (s *authzService) getUserRoles(ctx context.Context, userID string) ([]*ent.Role, error) {
userRoles, err := s.client.UserRole.Query().
Where(userrole.UserID(userID)).
WithRole(func(rq *ent.RoleQuery) {
rq.WithRolePermissions(func(rpq *ent.RolePermissionQuery) {
rpq.WithPermission()
})
}).
All(ctx)
if err != nil {
return nil, fmt.Errorf("failed to get user roles: %w", err)
}
roles := make([]*ent.Role, 0, len(userRoles))
for _, ur := range userRoles {
if ur.Edges.Role != nil {
roles = append(roles, ur.Edges.Role)
}
}
return roles, nil
}
// authorize checks if a user has a specific permission.
func (s *authzService) authorize(ctx context.Context, userID, permCode string) (bool, string, error) {
hasPerm, err := s.hasPermission(ctx, userID, permCode)
if err != nil {
return false, "", err
}
if !hasPerm {
return false, fmt.Sprintf("user %s does not have permission %s", userID, permCode), nil
}
return true, "authorized", nil
}
// authzServerImpl implements the AuthzService gRPC server.
type authzServerImpl struct {
authzv1.UnimplementedAuthzServiceServer
service *authzService
logger *zap.Logger
}
// Authorize checks if a user has a specific permission.
func (s *authzServerImpl) Authorize(ctx context.Context, req *authzv1.AuthorizeRequest) (*authzv1.AuthorizeResponse, error) {
authorized, message, err := s.service.authorize(ctx, req.UserId, req.Permission)
if err != nil {
return nil, status.Errorf(codes.Internal, "authorization check failed: %v", err)
}
return &authzv1.AuthorizeResponse{
Authorized: authorized,
Message: message,
}, nil
}
// HasPermission checks if a user has a specific permission.
func (s *authzServerImpl) HasPermission(ctx context.Context, req *authzv1.HasPermissionRequest) (*authzv1.HasPermissionResponse, error) {
hasPerm, err := s.service.hasPermission(ctx, req.UserId, req.Permission)
if err != nil {
return nil, status.Errorf(codes.Internal, "permission check failed: %v", err)
}
return &authzv1.HasPermissionResponse{
HasPermission: hasPerm,
}, nil
}
// GetUserPermissions returns all permissions for a user.
func (s *authzServerImpl) GetUserPermissions(ctx context.Context, req *authzv1.GetUserPermissionsRequest) (*authzv1.GetUserPermissionsResponse, error) {
permissions, err := s.service.getUserPermissions(ctx, req.UserId)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to get user permissions: %v", err)
}
protoPerms := make([]*authzv1.Permission, 0, len(permissions))
for _, perm := range permissions {
protoPerms = append(protoPerms, &authzv1.Permission{
Id: perm.ID,
Code: perm.Name, // Permission.Name is the code (e.g., "blog.post.create")
Name: perm.Name,
Description: "", // Permission schema doesn't have description field
})
}
return &authzv1.GetUserPermissionsResponse{
Permissions: protoPerms,
}, nil
}
// GetUserRoles returns all roles for a user.
func (s *authzServerImpl) GetUserRoles(ctx context.Context, req *authzv1.GetUserRolesRequest) (*authzv1.GetUserRolesResponse, error) {
roles, err := s.service.getUserRoles(ctx, req.UserId)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to get user roles: %v", err)
}
protoRoles := make([]*authzv1.Role, 0, len(roles))
for _, role := range roles {
// Get permission codes for this role
permCodes := make([]string, 0)
if role.Edges.RolePermissions != nil {
for _, rp := range role.Edges.RolePermissions {
if rp.Edges.Permission != nil {
permCodes = append(permCodes, rp.Edges.Permission.Name)
}
}
}
protoRoles = append(protoRoles, &authzv1.Role{
Id: role.ID,
Name: role.Name,
Description: role.Description,
Permissions: permCodes,
})
}
return &authzv1.GetUserRolesResponse{
Roles: protoRoles,
}, nil
}
// provideAuthzService creates the authz service and gRPC server.
func provideAuthzService() fx.Option {
return fx.Options(
// Authz service
fx.Provide(func(client *database.Client, log logger.Logger) (*authzService, error) {
return &authzService{
client: client,
logger: log,
}, nil
}),
// gRPC server implementation
fx.Provide(func(authzService *authzService, log logger.Logger) (*authzServerImpl, error) {
zapLogger, _ := zap.NewProduction()
return &authzServerImpl{
service: authzService,
logger: zapLogger,
}, nil
}),
// gRPC server wrapper
fx.Provide(func(
serverImpl *authzServerImpl,
cfg config.ConfigProvider,
log logger.Logger,
) (*grpcServerWrapper, error) {
port := cfg.GetInt("services.authz.port")
if port == 0 {
port = 8083
}
addr := fmt.Sprintf("0.0.0.0:%d", port)
listener, err := net.Listen("tcp", addr)
if err != nil {
return nil, fmt.Errorf("failed to listen on %s: %w", addr, err)
}
grpcServer := grpc.NewServer()
authzv1.RegisterAuthzServiceServer(grpcServer, serverImpl)
// Register health service
healthServer := health.NewServer()
grpc_health_v1.RegisterHealthServer(grpcServer, healthServer)
// Set serving status for the default service (empty string) - this is what Consul checks
healthServer.SetServingStatus("", grpc_health_v1.HealthCheckResponse_SERVING)
// Also set for the specific service name
healthServer.SetServingStatus("authz.v1.AuthzService", grpc_health_v1.HealthCheckResponse_SERVING)
// Register reflection for grpcurl
reflection.Register(grpcServer)
return &grpcServerWrapper{
server: grpcServer,
listener: listener,
port: port,
logger: log,
}, nil
}),
)
}

228
cmd/authz-service/main.go Normal file
View File

@@ -0,0 +1,228 @@
// Package main provides the entry point for the Authz Service.
package main
import (
"context"
"fmt"
"net"
"os"
"os/signal"
"syscall"
"time"
"git.dcentral.systems/toolz/goplt/internal/di"
healthpkg "git.dcentral.systems/toolz/goplt/internal/health"
"git.dcentral.systems/toolz/goplt/internal/infra/database"
"git.dcentral.systems/toolz/goplt/pkg/config"
"git.dcentral.systems/toolz/goplt/pkg/logger"
"git.dcentral.systems/toolz/goplt/pkg/registry"
"go.uber.org/fx"
"go.uber.org/zap"
"google.golang.org/grpc"
)
// grpcServerWrapper wraps the gRPC server for lifecycle management.
type grpcServerWrapper struct {
server *grpc.Server
listener net.Listener
port int
logger logger.Logger
}
func (s *grpcServerWrapper) Start() error {
s.logger.Info("Starting Authz Service gRPC server",
zap.Int("port", s.port),
zap.String("addr", s.listener.Addr().String()),
)
errChan := make(chan error, 1)
go func() {
if err := s.server.Serve(s.listener); err != nil {
errChan <- err
}
}()
select {
case err := <-errChan:
return fmt.Errorf("gRPC server failed to start: %w", err)
case <-time.After(100 * time.Millisecond):
s.logger.Info("Authz Service gRPC server started successfully",
zap.Int("port", s.port),
)
return nil
}
}
func (s *grpcServerWrapper) Stop(ctx context.Context) error {
s.logger.Info("Stopping Authz Service gRPC server")
stopped := make(chan struct{})
go func() {
s.server.GracefulStop()
close(stopped)
}()
select {
case <-stopped:
s.logger.Info("Authz Service gRPC server stopped gracefully")
return nil
case <-ctx.Done():
s.logger.Warn("Authz Service gRPC server stop timeout, forcing stop")
s.server.Stop()
return ctx.Err()
}
}
func (s *grpcServerWrapper) Port() int {
return s.port
}
func main() {
// Create DI container
// Note: CoreModule() is automatically included by NewContainer()
container := di.NewContainer(
// Database for authz service (authz schema)
fx.Provide(func(cfg config.ConfigProvider, log logger.Logger) (*database.Client, error) {
dsn := cfg.GetString("database.dsn")
if dsn == "" {
return nil, fmt.Errorf("database.dsn is required")
}
client, err := database.NewClientWithSchema(dsn, "authz")
if err != nil {
return nil, err
}
// Run migrations
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
if err := client.Migrate(ctx); err != nil {
log.Warn("Failed to run migrations",
zap.Error(err),
)
} else {
log.Info("Database migrations completed for authz service")
}
return client, nil
}),
// Register database health checker with existing health registry
fx.Invoke(func(registry *healthpkg.Registry, db *database.Client) {
registry.Register("database", healthpkg.NewDatabaseChecker(db))
}),
// Provide authz service and gRPC server (defined in authz_service_fx.go)
provideAuthzService(),
// Lifecycle hooks
fx.Invoke(registerLifecycle),
)
// Create root context
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// Handle signals
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
// Start the application
if err := container.Start(ctx); err != nil {
log := logger.GetGlobalLogger()
if log != nil {
log.Error("Failed to start Authz Service",
logger.Error(err),
)
} else {
fmt.Fprintf(os.Stderr, "Failed to start Authz Service: %v\n", err)
}
os.Exit(1)
}
// Wait for interrupt signal
<-sigChan
fmt.Println("\nShutting down Authz Service...")
// Create shutdown context with timeout
shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 30*time.Second)
defer shutdownCancel()
// Stop the application
if err := container.Stop(shutdownCtx); err != nil {
log := logger.GetGlobalLogger()
if log != nil {
log.Error("Error during Authz Service shutdown",
logger.Error(err),
)
} else {
fmt.Fprintf(os.Stderr, "Error during shutdown: %v\n", err)
}
os.Exit(1)
}
fmt.Println("Authz Service stopped successfully")
}
// registerLifecycle registers lifecycle hooks for the service.
func registerLifecycle(
lc fx.Lifecycle,
grpcServer *grpcServerWrapper,
serviceRegistry registry.ServiceRegistry,
cfg config.ConfigProvider,
log logger.Logger,
) {
lc.Append(fx.Hook{
OnStart: func(ctx context.Context) error {
// Start gRPC server
if err := grpcServer.Start(); err != nil {
return fmt.Errorf("failed to start gRPC server: %w", err)
}
// Register with service registry
serviceID := fmt.Sprintf("authz-service-%d", time.Now().Unix())
// In Docker, always use the Docker service name for health checks
// Consul (also in Docker) needs to reach the service via Docker DNS
host := cfg.GetString("services.authz.host")
if os.Getenv("ENVIRONMENT") == "production" || os.Getenv("DOCKER") == "true" {
host = "authz-service" // Docker service name - required for Consul health checks
} else if host == "" {
host = "localhost" // Local development
}
port := grpcServer.Port()
instance := &registry.ServiceInstance{
ID: serviceID,
Name: "authz-service",
Address: host,
Port: port,
Tags: []string{"grpc", "authz"},
Metadata: map[string]string{
"version": "1.0.0",
"protocol": "grpc",
},
}
if err := serviceRegistry.Register(ctx, instance); err != nil {
log.Warn("Failed to register with service registry",
zap.Error(err),
)
} else {
log.Info("Registered Authz Service with service registry",
zap.String("service_id", serviceID),
zap.String("name", instance.Name),
zap.Int("port", port),
)
}
return nil
},
OnStop: func(ctx context.Context) error {
// Stop gRPC server
if err := grpcServer.Stop(ctx); err != nil {
return fmt.Errorf("failed to stop gRPC server: %w", err)
}
return nil
},
})
}

View File

@@ -0,0 +1,36 @@
# Build stage
FROM golang:1.25-alpine AS builder
WORKDIR /build
# Install build dependencies
RUN apk add --no-cache git make
# Copy go mod files
COPY go.mod go.sum ./
RUN go mod download
# Copy source code
COPY . .
# Build the service
WORKDIR /build/cmd/identity-service
RUN CGO_ENABLED=0 GOOS=linux go build -o identity-service -a -installsuffix cgo .
# Runtime stage
FROM alpine:latest
RUN apk --no-cache add ca-certificates tzdata
WORKDIR /app
# Copy binary from builder
COPY --from=builder /build/cmd/identity-service/identity-service .
# Copy config files
COPY --from=builder /build/config ./config
EXPOSE 8082
CMD ["./identity-service"]

View File

@@ -0,0 +1,448 @@
// Package main provides FX providers for Identity Service.
// This file creates the service inline to avoid importing internal packages.
package main
import (
"context"
"crypto/rand"
"crypto/subtle"
"encoding/base64"
"fmt"
"math"
"net"
"strings"
"time"
identityv1 "git.dcentral.systems/toolz/goplt/api/proto/generated/identity/v1"
"git.dcentral.systems/toolz/goplt/internal/ent"
"git.dcentral.systems/toolz/goplt/internal/ent/user"
"git.dcentral.systems/toolz/goplt/internal/infra/database"
"git.dcentral.systems/toolz/goplt/pkg/config"
"git.dcentral.systems/toolz/goplt/pkg/logger"
"go.uber.org/fx"
"go.uber.org/zap"
"golang.org/x/crypto/argon2"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/health"
"google.golang.org/grpc/health/grpc_health_v1"
"google.golang.org/grpc/reflection"
"google.golang.org/grpc/status"
)
// userService provides user management functionality.
type userService struct {
client *database.Client
logger logger.Logger
}
// generateToken generates a random token.
func generateToken() (string, error) {
b := make([]byte, 32)
if _, err := rand.Read(b); err != nil {
return "", fmt.Errorf("failed to generate token: %w", err)
}
return fmt.Sprintf("%x", b), nil
}
// hashPassword hashes a password using argon2id.
func hashPassword(password string) (string, error) {
salt := make([]byte, 16)
if _, err := rand.Read(salt); err != nil {
return "", fmt.Errorf("failed to generate salt: %w", err)
}
hash := argon2.IDKey([]byte(password), salt, 3, 64*1024, 4, 32)
b64Salt := base64.RawStdEncoding.EncodeToString(salt)
b64Hash := base64.RawStdEncoding.EncodeToString(hash)
return fmt.Sprintf("$argon2id$v=%d$m=%d,t=%d,p=%d$%s$%s",
argon2.Version, 64*1024, 3, 4, b64Salt, b64Hash), nil
}
// verifyPassword verifies a password against a hash.
func verifyPassword(password, hash string) (bool, error) {
// Simplified verification - in production use proper parsing
parts := strings.Split(hash, "$")
if len(parts) != 6 {
return false, fmt.Errorf("invalid hash format")
}
salt, err := base64.RawStdEncoding.DecodeString(parts[4])
if err != nil {
return false, err
}
expectedHash, err := base64.RawStdEncoding.DecodeString(parts[5])
if err != nil {
return false, err
}
hashLen := len(expectedHash)
if hashLen < 0 || hashLen > math.MaxUint32 {
return false, fmt.Errorf("invalid hash length: %d", hashLen)
}
var hashLenUint32 uint32
if hashLen > math.MaxUint32 {
hashLenUint32 = math.MaxUint32
} else {
hashLenUint32 = uint32(hashLen)
}
actualHash := argon2.IDKey([]byte(password), salt, 3, 64*1024, 4, hashLenUint32)
return subtle.ConstantTimeCompare(expectedHash, actualHash) == 1, nil
}
// createUser creates a new user.
func (s *userService) createUser(ctx context.Context, email, username, pwd, firstName, lastName string) (*ent.User, error) {
exists, err := s.client.User.Query().Where(user.Email(email)).Exist(ctx)
if err != nil {
return nil, fmt.Errorf("failed to check email: %w", err)
}
if exists {
return nil, fmt.Errorf("email already exists")
}
passwordHash, err := hashPassword(pwd)
if err != nil {
return nil, fmt.Errorf("failed to hash password: %w", err)
}
verificationToken, err := generateToken()
if err != nil {
return nil, fmt.Errorf("failed to generate token: %w", err)
}
create := s.client.User.Create().
SetID(fmt.Sprintf("%d", time.Now().UnixNano())).
SetEmail(email).
SetPasswordHash(passwordHash).
SetVerified(false).
SetEmailVerificationToken(verificationToken)
if username != "" {
create = create.SetUsername(username)
}
if firstName != "" {
create = create.SetFirstName(firstName)
}
if lastName != "" {
create = create.SetLastName(lastName)
}
u, err := create.Save(ctx)
if err != nil {
return nil, fmt.Errorf("failed to create user: %w", err)
}
return u, nil
}
// getUser retrieves a user by ID.
func (s *userService) getUser(ctx context.Context, id string) (*ent.User, error) {
u, err := s.client.User.Get(ctx, id)
if err != nil {
return nil, fmt.Errorf("failed to get user: %w", err)
}
return u, nil
}
// getUserByEmail retrieves a user by email.
func (s *userService) getUserByEmail(ctx context.Context, email string) (*ent.User, error) {
u, err := s.client.User.Query().Where(user.Email(email)).Only(ctx)
if err != nil {
return nil, fmt.Errorf("failed to get user: %w", err)
}
return u, nil
}
// updateUser updates a user.
func (s *userService) updateUser(ctx context.Context, id string, email, username, firstName, lastName *string) (*ent.User, error) {
update := s.client.User.UpdateOneID(id)
if email != nil {
exists, err := s.client.User.Query().
Where(user.Email(*email), user.IDNEQ(id)).
Exist(ctx)
if err != nil {
return nil, fmt.Errorf("failed to check email: %w", err)
}
if exists {
return nil, fmt.Errorf("email already taken")
}
update = update.SetEmail(*email)
}
if username != nil {
update = update.SetUsername(*username)
}
if firstName != nil {
update = update.SetFirstName(*firstName)
}
if lastName != nil {
update = update.SetLastName(*lastName)
}
return update.Save(ctx)
}
// deleteUser deletes a user.
func (s *userService) deleteUser(ctx context.Context, id string) error {
return s.client.User.DeleteOneID(id).Exec(ctx)
}
// verifyEmail verifies a user's email.
func (s *userService) verifyEmail(ctx context.Context, token string) error {
u, err := s.client.User.Query().Where(user.EmailVerificationToken(token)).Only(ctx)
if err != nil {
return fmt.Errorf("invalid token")
}
_, err = s.client.User.UpdateOneID(u.ID).
SetVerified(true).
ClearEmailVerificationToken().
Save(ctx)
return err
}
// requestPasswordReset requests a password reset.
func (s *userService) requestPasswordReset(ctx context.Context, email string) (string, error) {
u, err := s.getUserByEmail(ctx, email)
if err != nil {
return "", nil // Don't reveal if user exists
}
token, err := generateToken()
if err != nil {
return "", err
}
expiresAt := time.Now().Add(24 * time.Hour)
_, err = s.client.User.UpdateOneID(u.ID).
SetPasswordResetToken(token).
SetPasswordResetExpiresAt(expiresAt).
Save(ctx)
return token, err
}
// resetPassword resets a password.
func (s *userService) resetPassword(ctx context.Context, token, newPassword string) error {
u, err := s.client.User.Query().Where(user.PasswordResetToken(token)).Only(ctx)
if err != nil {
return fmt.Errorf("invalid token")
}
if !u.PasswordResetExpiresAt.IsZero() && u.PasswordResetExpiresAt.Before(time.Now()) {
return fmt.Errorf("token expired")
}
passwordHash, err := hashPassword(newPassword)
if err != nil {
return err
}
_, err = s.client.User.UpdateOneID(u.ID).
SetPasswordHash(passwordHash).
ClearPasswordResetToken().
ClearPasswordResetExpiresAt().
Save(ctx)
return err
}
// verifyPassword verifies a password.
func (s *userService) verifyPassword(ctx context.Context, email, pwd string) (*ent.User, error) {
u, err := s.getUserByEmail(ctx, email)
if err != nil {
return nil, err
}
valid, err := verifyPassword(pwd, u.PasswordHash)
if err != nil || !valid {
return nil, fmt.Errorf("invalid password")
}
return u, nil
}
// identityServerImpl implements the IdentityService gRPC server.
type identityServerImpl struct {
identityv1.UnimplementedIdentityServiceServer
service *userService
logger *zap.Logger
}
// GetUser retrieves a user by ID.
func (s *identityServerImpl) GetUser(ctx context.Context, req *identityv1.GetUserRequest) (*identityv1.GetUserResponse, error) {
u, err := s.service.getUser(ctx, req.Id)
if err != nil {
return nil, status.Errorf(codes.NotFound, "user not found: %v", err)
}
return &identityv1.GetUserResponse{
User: &identityv1.User{
Id: u.ID,
Email: u.Email,
Username: u.Username,
FirstName: u.FirstName,
LastName: u.LastName,
EmailVerified: u.Verified,
CreatedAt: u.CreatedAt.Unix(),
UpdatedAt: u.UpdatedAt.Unix(),
},
}, nil
}
// GetUserByEmail retrieves a user by email.
func (s *identityServerImpl) GetUserByEmail(ctx context.Context, req *identityv1.GetUserByEmailRequest) (*identityv1.GetUserByEmailResponse, error) {
u, err := s.service.getUserByEmail(ctx, req.Email)
if err != nil {
return nil, status.Errorf(codes.NotFound, "user not found: %v", err)
}
return &identityv1.GetUserByEmailResponse{
User: &identityv1.User{
Id: u.ID,
Email: u.Email,
Username: u.Username,
FirstName: u.FirstName,
LastName: u.LastName,
EmailVerified: u.Verified,
CreatedAt: u.CreatedAt.Unix(),
UpdatedAt: u.UpdatedAt.Unix(),
},
}, nil
}
// CreateUser creates a new user.
func (s *identityServerImpl) CreateUser(ctx context.Context, req *identityv1.CreateUserRequest) (*identityv1.CreateUserResponse, error) {
u, err := s.service.createUser(ctx, req.Email, req.Username, req.Password, req.FirstName, req.LastName)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to create user: %v", err)
}
return &identityv1.CreateUserResponse{
User: &identityv1.User{
Id: u.ID,
Email: u.Email,
Username: u.Username,
FirstName: u.FirstName,
LastName: u.LastName,
EmailVerified: u.Verified,
CreatedAt: u.CreatedAt.Unix(),
UpdatedAt: u.UpdatedAt.Unix(),
},
}, nil
}
// UpdateUser updates a user.
func (s *identityServerImpl) UpdateUser(ctx context.Context, req *identityv1.UpdateUserRequest) (*identityv1.UpdateUserResponse, error) {
u, err := s.service.updateUser(ctx, req.Id, req.Email, req.Username, req.FirstName, req.LastName)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to update user: %v", err)
}
return &identityv1.UpdateUserResponse{
User: &identityv1.User{
Id: u.ID,
Email: u.Email,
Username: u.Username,
FirstName: u.FirstName,
LastName: u.LastName,
EmailVerified: u.Verified,
CreatedAt: u.CreatedAt.Unix(),
UpdatedAt: u.UpdatedAt.Unix(),
},
}, nil
}
// DeleteUser deletes a user.
func (s *identityServerImpl) DeleteUser(ctx context.Context, req *identityv1.DeleteUserRequest) (*identityv1.DeleteUserResponse, error) {
if err := s.service.deleteUser(ctx, req.Id); err != nil {
return nil, status.Errorf(codes.Internal, "failed to delete user: %v", err)
}
return &identityv1.DeleteUserResponse{Success: true}, nil
}
// VerifyEmail verifies a user's email.
func (s *identityServerImpl) VerifyEmail(ctx context.Context, req *identityv1.VerifyEmailRequest) (*identityv1.VerifyEmailResponse, error) {
if err := s.service.verifyEmail(ctx, req.Token); err != nil {
return nil, status.Errorf(codes.InvalidArgument, "failed to verify email: %v", err)
}
return &identityv1.VerifyEmailResponse{Success: true}, nil
}
// RequestPasswordReset requests a password reset.
func (s *identityServerImpl) RequestPasswordReset(ctx context.Context, req *identityv1.RequestPasswordResetRequest) (*identityv1.RequestPasswordResetResponse, error) {
_, err := s.service.requestPasswordReset(ctx, req.Email)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to request password reset: %v", err)
}
return &identityv1.RequestPasswordResetResponse{Success: true}, nil
}
// ResetPassword resets a password.
func (s *identityServerImpl) ResetPassword(ctx context.Context, req *identityv1.ResetPasswordRequest) (*identityv1.ResetPasswordResponse, error) {
if err := s.service.resetPassword(ctx, req.Token, req.NewPassword); err != nil {
return nil, status.Errorf(codes.InvalidArgument, "failed to reset password: %v", err)
}
return &identityv1.ResetPasswordResponse{Success: true}, nil
}
// VerifyPassword verifies a user's password.
func (s *identityServerImpl) VerifyPassword(ctx context.Context, req *identityv1.VerifyPasswordRequest) (*identityv1.VerifyPasswordResponse, error) {
u, err := s.service.verifyPassword(ctx, req.Email, req.Password)
if err != nil {
return nil, status.Errorf(codes.Unauthenticated, "invalid credentials: %v", err)
}
return &identityv1.VerifyPasswordResponse{
User: &identityv1.User{
Id: u.ID,
Email: u.Email,
Username: u.Username,
FirstName: u.FirstName,
LastName: u.LastName,
EmailVerified: u.Verified,
CreatedAt: u.CreatedAt.Unix(),
UpdatedAt: u.UpdatedAt.Unix(),
},
}, nil
}
// provideIdentityService creates the identity service and gRPC server.
func provideIdentityService() fx.Option {
return fx.Options(
// User service
fx.Provide(func(client *database.Client, log logger.Logger) (*userService, error) {
return &userService{
client: client,
logger: log,
}, nil
}),
// gRPC server implementation
fx.Provide(func(userService *userService, log logger.Logger) (*identityServerImpl, error) {
zapLogger, _ := zap.NewProduction()
return &identityServerImpl{
service: userService,
logger: zapLogger,
}, nil
}),
// gRPC server wrapper
fx.Provide(func(
serverImpl *identityServerImpl,
cfg config.ConfigProvider,
log logger.Logger,
) (*grpcServerWrapper, error) {
port := cfg.GetInt("services.identity.port")
if port == 0 {
port = 8082
}
addr := fmt.Sprintf("0.0.0.0:%d", port)
listener, err := net.Listen("tcp", addr)
if err != nil {
return nil, fmt.Errorf("failed to listen on %s: %w", addr, err)
}
grpcServer := grpc.NewServer()
identityv1.RegisterIdentityServiceServer(grpcServer, serverImpl)
// Register health service
healthServer := health.NewServer()
grpc_health_v1.RegisterHealthServer(grpcServer, healthServer)
// Set serving status for the default service (empty string) - this is what Consul checks
healthServer.SetServingStatus("", grpc_health_v1.HealthCheckResponse_SERVING)
// Also set for the specific service name
healthServer.SetServingStatus("identity.v1.IdentityService", grpc_health_v1.HealthCheckResponse_SERVING)
// Register reflection for grpcurl
reflection.Register(grpcServer)
return &grpcServerWrapper{
server: grpcServer,
listener: listener,
port: port,
logger: log,
}, nil
}),
)
}

View File

@@ -0,0 +1,228 @@
// Package main provides the entry point for the Identity Service.
package main
import (
"context"
"fmt"
"net"
"os"
"os/signal"
"syscall"
"time"
"git.dcentral.systems/toolz/goplt/internal/di"
healthpkg "git.dcentral.systems/toolz/goplt/internal/health"
"git.dcentral.systems/toolz/goplt/internal/infra/database"
"git.dcentral.systems/toolz/goplt/pkg/config"
"git.dcentral.systems/toolz/goplt/pkg/logger"
"git.dcentral.systems/toolz/goplt/pkg/registry"
"go.uber.org/fx"
"go.uber.org/zap"
"google.golang.org/grpc"
)
// grpcServerWrapper wraps the gRPC server for lifecycle management.
type grpcServerWrapper struct {
server *grpc.Server
listener net.Listener
port int
logger logger.Logger
}
func (s *grpcServerWrapper) Start() error {
s.logger.Info("Starting Identity Service gRPC server",
zap.Int("port", s.port),
zap.String("addr", s.listener.Addr().String()),
)
errChan := make(chan error, 1)
go func() {
if err := s.server.Serve(s.listener); err != nil {
errChan <- err
}
}()
select {
case err := <-errChan:
return fmt.Errorf("gRPC server failed to start: %w", err)
case <-time.After(100 * time.Millisecond):
s.logger.Info("Identity Service gRPC server started successfully",
zap.Int("port", s.port),
)
return nil
}
}
func (s *grpcServerWrapper) Stop(ctx context.Context) error {
s.logger.Info("Stopping Identity Service gRPC server")
stopped := make(chan struct{})
go func() {
s.server.GracefulStop()
close(stopped)
}()
select {
case <-stopped:
s.logger.Info("Identity Service gRPC server stopped gracefully")
return nil
case <-ctx.Done():
s.logger.Warn("Identity Service gRPC server stop timeout, forcing stop")
s.server.Stop()
return ctx.Err()
}
}
func (s *grpcServerWrapper) Port() int {
return s.port
}
func main() {
// Create DI container
// Note: CoreModule() is automatically included by NewContainer()
container := di.NewContainer(
// Database for identity service (identity schema)
fx.Provide(func(cfg config.ConfigProvider, log logger.Logger) (*database.Client, error) {
dsn := cfg.GetString("database.dsn")
if dsn == "" {
return nil, fmt.Errorf("database.dsn is required")
}
client, err := database.NewClientWithSchema(dsn, "identity")
if err != nil {
return nil, err
}
// Run migrations
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
if err := client.Migrate(ctx); err != nil {
log.Warn("Failed to run migrations",
zap.Error(err),
)
} else {
log.Info("Database migrations completed for identity service")
}
return client, nil
}),
// Register database health checker with existing health registry
fx.Invoke(func(registry *healthpkg.Registry, db *database.Client) {
registry.Register("database", healthpkg.NewDatabaseChecker(db))
}),
// Provide identity service and gRPC server (defined in identity_service_fx.go)
provideIdentityService(),
// Lifecycle hooks
fx.Invoke(registerLifecycle),
)
// Create root context
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// Handle signals
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
// Start the application
if err := container.Start(ctx); err != nil {
log := logger.GetGlobalLogger()
if log != nil {
log.Error("Failed to start Identity Service",
logger.Error(err),
)
} else {
fmt.Fprintf(os.Stderr, "Failed to start Identity Service: %v\n", err)
}
os.Exit(1)
}
// Wait for interrupt signal
<-sigChan
fmt.Println("\nShutting down Identity Service...")
// Create shutdown context with timeout
shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 30*time.Second)
defer shutdownCancel()
// Stop the application
if err := container.Stop(shutdownCtx); err != nil {
log := logger.GetGlobalLogger()
if log != nil {
log.Error("Error during Identity Service shutdown",
logger.Error(err),
)
} else {
fmt.Fprintf(os.Stderr, "Error during shutdown: %v\n", err)
}
os.Exit(1)
}
fmt.Println("Identity Service stopped successfully")
}
// registerLifecycle registers lifecycle hooks for the service.
func registerLifecycle(
lc fx.Lifecycle,
grpcServer *grpcServerWrapper,
serviceRegistry registry.ServiceRegistry,
cfg config.ConfigProvider,
log logger.Logger,
) {
lc.Append(fx.Hook{
OnStart: func(ctx context.Context) error {
// Start gRPC server
if err := grpcServer.Start(); err != nil {
return fmt.Errorf("failed to start gRPC server: %w", err)
}
// Register with service registry
serviceID := fmt.Sprintf("identity-service-%d", time.Now().Unix())
// In Docker, always use the Docker service name for health checks
// Consul (also in Docker) needs to reach the service via Docker DNS
host := cfg.GetString("services.identity.host")
if os.Getenv("ENVIRONMENT") == "production" || os.Getenv("DOCKER") == "true" {
host = "identity-service" // Docker service name - required for Consul health checks
} else if host == "" {
host = "localhost" // Local development
}
port := grpcServer.Port()
instance := &registry.ServiceInstance{
ID: serviceID,
Name: "identity-service",
Address: host,
Port: port,
Tags: []string{"grpc", "identity"},
Metadata: map[string]string{
"version": "1.0.0",
"protocol": "grpc",
},
}
if err := serviceRegistry.Register(ctx, instance); err != nil {
log.Warn("Failed to register with service registry",
zap.Error(err),
)
} else {
log.Info("Registered Identity Service with service registry",
zap.String("service_id", serviceID),
zap.String("name", instance.Name),
zap.Int("port", port),
)
}
return nil
},
OnStop: func(ctx context.Context) error {
// Stop gRPC server
if err := grpcServer.Stop(ctx); err != nil {
return fmt.Errorf("failed to stop gRPC server: %w", err)
}
return nil
},
})
}

View File

@@ -36,6 +36,25 @@ registry:
timeout: "3s" timeout: "3s"
deregister_after: "30s" deregister_after: "30s"
http: "/healthz" http: "/healthz"
grpc: "grpc.health.v1.Health"
use_grpc: true
services:
audit:
port: 8084
host: "localhost"
auth:
port: 8081
host: "localhost"
identity:
port: 8082
host: "localhost"
authz:
port: 8083
host: "localhost"
auth:
jwt_secret: "change-this-secret-in-production"
gateway: gateway:
port: 8080 port: 8080

49
docker-compose.dev.yml Normal file
View File

@@ -0,0 +1,49 @@
# Development docker-compose: Only infrastructure services (PostgreSQL and Consul)
# Use this for local development when running services directly with `go run`
services:
postgres:
image: postgres:16-alpine
container_name: goplt-postgres
environment:
POSTGRES_USER: goplt
POSTGRES_PASSWORD: goplt_password
POSTGRES_DB: goplt
ports:
- "5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U goplt"]
interval: 5s
timeout: 5s
retries: 5
networks:
- goplt-network
consul:
image: consul:latest
container_name: goplt-consul
command: consul agent -dev -client=0.0.0.0
ports:
- "8500:8500"
volumes:
- consul_data:/consul/data
healthcheck:
test: ["CMD-SHELL", "consul members"]
interval: 10s
timeout: 3s
retries: 5
networks:
- goplt-network
volumes:
postgres_data:
driver: local
consul_data:
driver: local
networks:
goplt-network:
driver: bridge

View File

@@ -1,4 +1,5 @@
version: '3.8' # Full docker-compose: All services + infrastructure
# Use this to run the complete platform with all services in Docker
services: services:
postgres: postgres:
@@ -20,11 +21,140 @@ services:
networks: networks:
- goplt-network - goplt-network
consul:
image: consul:1.15.4
container_name: goplt-consul
command: consul agent -dev -client=0.0.0.0
ports:
- "8500:8500"
volumes:
- consul_data:/consul/data
healthcheck:
test: ["CMD-SHELL", "consul members"]
interval: 10s
timeout: 3s
retries: 5
networks:
- goplt-network
auth-service:
build:
context: .
dockerfile: cmd/auth-service/Dockerfile
container_name: goplt-auth-service
environment:
ENVIRONMENT: production
DATABASE_DSN: "postgres://goplt:goplt_password@postgres:5432/goplt?sslmode=disable"
REGISTRY_TYPE: consul
REGISTRY_CONSUL_ADDRESS: "consul:8500"
ports:
- "8081:8081"
depends_on:
postgres:
condition: service_healthy
consul:
condition: service_healthy
networks:
- goplt-network
restart: unless-stopped
identity-service:
build:
context: .
dockerfile: cmd/identity-service/Dockerfile
container_name: goplt-identity-service
environment:
ENVIRONMENT: production
DATABASE_DSN: "postgres://goplt:goplt_password@postgres:5432/goplt?sslmode=disable"
REGISTRY_TYPE: consul
REGISTRY_CONSUL_ADDRESS: "consul:8500"
ports:
- "8082:8082"
depends_on:
postgres:
condition: service_healthy
consul:
condition: service_healthy
networks:
- goplt-network
restart: unless-stopped
authz-service:
build:
context: .
dockerfile: cmd/authz-service/Dockerfile
container_name: goplt-authz-service
environment:
ENVIRONMENT: production
DATABASE_DSN: "postgres://goplt:goplt_password@postgres:5432/goplt?sslmode=disable"
REGISTRY_TYPE: consul
REGISTRY_CONSUL_ADDRESS: "consul:8500"
ports:
- "8083:8083"
depends_on:
postgres:
condition: service_healthy
consul:
condition: service_healthy
networks:
- goplt-network
restart: unless-stopped
audit-service:
build:
context: .
dockerfile: cmd/audit-service/Dockerfile
container_name: goplt-audit-service
environment:
ENVIRONMENT: production
DATABASE_DSN: "postgres://goplt:goplt_password@postgres:5432/goplt?sslmode=disable"
REGISTRY_TYPE: consul
REGISTRY_CONSUL_ADDRESS: "consul:8500"
ports:
- "8084:8084"
depends_on:
postgres:
condition: service_healthy
consul:
condition: service_healthy
networks:
- goplt-network
restart: unless-stopped
api-gateway:
build:
context: .
dockerfile: cmd/api-gateway/Dockerfile
container_name: goplt-api-gateway
environment:
ENVIRONMENT: production
REGISTRY_TYPE: consul
REGISTRY_CONSUL_ADDRESS: "consul:8500"
GATEWAY_PORT: "8080"
GATEWAY_HOST: "0.0.0.0"
ports:
- "8080:8080"
depends_on:
consul:
condition: service_healthy
auth-service:
condition: service_started
identity-service:
condition: service_started
authz-service:
condition: service_started
audit-service:
condition: service_started
networks:
- goplt-network
restart: unless-stopped
volumes: volumes:
postgres_data: postgres_data:
driver: local driver: local
consul_data:
driver: local
networks: networks:
goplt-network: goplt-network:
driver: bridge driver: bridge

View File

@@ -5,6 +5,7 @@ Superseded by [ADR-0034: Go Version Upgrade to 1.25.3](./0034-go-version-upgrade
## Context ## Context
Go releases new versions regularly with new features, performance improvements, and security fixes. We need to choose a Go version that: Go releases new versions regularly with new features, performance improvements, and security fixes. We need to choose a Go version that:
- Provides necessary features for the platform - Provides necessary features for the platform
- Has good ecosystem support - Has good ecosystem support
- Is stable and production-ready - Is stable and production-ready

View File

@@ -5,6 +5,7 @@ Accepted
## Context ## Context
The platform follows a microservices architecture where each service has its own database connection. The ORM/library must: The platform follows a microservices architecture where each service has its own database connection. The ORM/library must:
- Support PostgreSQL (primary database) - Support PostgreSQL (primary database)
- Provide type-safe query building - Provide type-safe query building
- Support code generation (reduces boilerplate) - Support code generation (reduces boilerplate)

View File

@@ -10,6 +10,7 @@ The platform needs to scale independently, support team autonomy, and enable fle
Design the platform as **microservices architecture from day one**: Design the platform as **microservices architecture from day one**:
1. **Core Services**: Core business services are separate microservices: 1. **Core Services**: Core business services are separate microservices:
- **Auth Service** (`cmd/auth-service/`): JWT token generation/validation - **Auth Service** (`cmd/auth-service/`): JWT token generation/validation
- **Identity Service** (`cmd/identity-service/`): User CRUD, password management - **Identity Service** (`cmd/identity-service/`): User CRUD, password management
- **Authz Service** (`cmd/authz-service/`): Permission resolution, authorization - **Authz Service** (`cmd/authz-service/`): Permission resolution, authorization

View File

@@ -66,6 +66,7 @@ All communication goes through service clients - no direct in-process calls even
## Development Mode ## Development Mode
For local development, services run in the same repository but as separate processes: For local development, services run in the same repository but as separate processes:
- Each service has its own entry point (`cmd/{service}/`) - Each service has its own entry point (`cmd/{service}/`)
- Services communicate via service clients (gRPC or HTTP) - no direct in-process calls - Services communicate via service clients (gRPC or HTTP) - no direct in-process calls
- Docker Compose orchestrates all services - Docker Compose orchestrates all services

View File

@@ -10,6 +10,7 @@ The platform follows a microservices architecture where each service (Auth, Iden
- **Option 2**: Separate repositories (each service in its own repository) - **Option 2**: Separate repositories (each service in its own repository)
The decision affects: The decision affects:
- Code sharing and dependencies - Code sharing and dependencies
- Development workflow - Development workflow
- CI/CD complexity - CI/CD complexity

View File

@@ -5,6 +5,7 @@ Accepted
## Context ## Context
The platform follows a microservices architecture where each service is independently deployable. We need a central entry point that handles: The platform follows a microservices architecture where each service is independently deployable. We need a central entry point that handles:
- Request routing to backend services - Request routing to backend services
- Authentication and authorization at the edge - Authentication and authorization at the edge
- Rate limiting and throttling - Rate limiting and throttling

View File

@@ -5,6 +5,7 @@ Accepted
## Context ## Context
The platform follows a microservices architecture where services need to discover and communicate with each other. We need a service discovery mechanism that: The platform follows a microservices architecture where services need to discover and communicate with each other. We need a service discovery mechanism that:
- Enables services to find each other dynamically - Enables services to find each other dynamically
- Supports health checking and automatic deregistration - Supports health checking and automatic deregistration
- Works in both development (Docker Compose) and production (Kubernetes) environments - Works in both development (Docker Compose) and production (Kubernetes) environments

View File

@@ -5,6 +5,7 @@ Accepted
## Context ## Context
ADR-0002 established Go 1.24.3 as the minimum required version. Since then: ADR-0002 established Go 1.24.3 as the minimum required version. Since then:
- Go 1.25.3 has been released with new features, performance improvements, and security fixes - Go 1.25.3 has been released with new features, performance improvements, and security fixes
- The project requires access to newer language features and tooling improvements - The project requires access to newer language features and tooling improvements
- Dependencies may benefit from Go 1.25.3 optimizations - Dependencies may benefit from Go 1.25.3 optimizations

View File

@@ -5,6 +5,7 @@ This directory contains Architecture Decision Records (ADRs) for the Go Platform
## What are ADRs? ## What are ADRs?
ADRs document important architectural decisions made during the project. They help: ADRs document important architectural decisions made during the project. They help:
- Track why decisions were made - Track why decisions were made
- Understand the context and constraints - Understand the context and constraints
- Review decisions when requirements change - Review decisions when requirements change
@@ -13,6 +14,7 @@ ADRs document important architectural decisions made during the project. They he
## ADR Format ## ADR Format
Each ADR follows this structure: Each ADR follows this structure:
- **Status**: Proposed | Accepted | Rejected | Superseded - **Status**: Proposed | Accepted | Rejected | Superseded
- **Context**: The situation that led to the decision - **Context**: The situation that led to the decision
- **Decision**: What was decided - **Decision**: What was decided

View File

@@ -14,6 +14,7 @@ This document provides a comprehensive overview of the Go Platform architecture,
## High-Level Architecture ## High-Level Architecture
The Go Platform follows a **microservices architecture** where each service is independently deployable from day one: The Go Platform follows a **microservices architecture** where each service is independently deployable from day one:
- **Core Kernel**: Infrastructure only (config, logger, DI, health, metrics, observability) - no business logic - **Core Kernel**: Infrastructure only (config, logger, DI, health, metrics, observability) - no business logic
- **Core Services**: Auth Service, Identity Service, Authz Service, Audit Service - separate microservices - **Core Services**: Auth Service, Identity Service, Authz Service, Audit Service - separate microservices
- **API Gateway**: Single entry point for all external traffic, handles routing and authentication - **API Gateway**: Single entry point for all external traffic, handles routing and authentication
@@ -316,6 +317,10 @@ graph TB
Notifier[Notifier] Notifier[Notifier]
end end
subgraph "External Services"
Sentry[Sentry<br/>Error Reporting]
end
subgraph "Module Components" subgraph "Module Components"
ModuleRoutes[Module Routes] ModuleRoutes[Module Routes]
ModuleServices[Module Services] ModuleServices[Module Services]
@@ -358,6 +363,7 @@ graph TB
style AuthService fill:#ff6b6b,stroke:#c92a2a,stroke-width:2px,color:#fff style AuthService fill:#ff6b6b,stroke:#c92a2a,stroke-width:2px,color:#fff
style IdentityService fill:#ff6b6b,stroke:#c92a2a,stroke-width:2px,color:#fff style IdentityService fill:#ff6b6b,stroke:#c92a2a,stroke-width:2px,color:#fff
style ModuleServices fill:#7b68ee,stroke:#5a4fcf,stroke-width:2px,color:#fff style ModuleServices fill:#7b68ee,stroke:#5a4fcf,stroke-width:2px,color:#fff
```
## Data Flow ## Data Flow
@@ -625,6 +631,7 @@ graph TB
style Gateway2 fill:#4a90e2,stroke:#2e5c8a,stroke-width:2px,color:#fff style Gateway2 fill:#4a90e2,stroke:#2e5c8a,stroke-width:2px,color:#fff
style Primary fill:#50c878,stroke:#2e7d4e,stroke-width:2px,color:#fff style Primary fill:#50c878,stroke:#2e7d4e,stroke-width:2px,color:#fff
style Redis1 fill:#ff6b6b,stroke:#c92a2a,stroke-width:2px,color:#fff style Redis1 fill:#ff6b6b,stroke:#c92a2a,stroke-width:2px,color:#fff
```
## Core Kernel Components ## Core Kernel Components

View File

@@ -55,17 +55,12 @@ graph TD
DI --> Tracer DI --> Tracer
DI --> Registry DI --> Registry
Registry --> Auth
Registry --> Authz
Registry --> Audit
DB --> Tracer DB --> Tracer
Cache --> Tracer Cache --> Tracer
EventBus --> Tracer EventBus --> Tracer
style Config fill:#4a90e2,stroke:#2e5c8a,stroke-width:3px,color:#fff style Config fill:#4a90e2,stroke:#2e5c8a,stroke-width:3px,color:#fff
style DI fill:#50c878,stroke:#2e7d4e,stroke-width:3px,color:#fff style DI fill:#50c878,stroke:#2e7d4e,stroke-width:3px,color:#fff
style Auth fill:#ff6b6b,stroke:#c92a2a,stroke-width:2px,color:#fff
``` ```
## Service to Service Integration ## Service to Service Integration
@@ -139,7 +134,10 @@ graph LR
style Registry fill:#50c878,stroke:#2e7d4e,stroke-width:3px,color:#fff style Registry fill:#50c878,stroke:#2e7d4e,stroke-width:3px,color:#fff
style AuthService fill:#ff6b6b,stroke:#c92a2a,stroke-width:2px,color:#fff style AuthService fill:#ff6b6b,stroke:#c92a2a,stroke-width:2px,color:#fff
style DBClient fill:#50c878,stroke:#2e7d4e,stroke-width:2px,color:#fff style DBClient fill:#50c878,stroke:#2e7d4e,stroke-width:2px,color:#fff
style ServiceClients fill:#50c878,stroke:#2e7d4e,stroke-width:2px,color:#fff style AuthClient fill:#50c878,stroke:#2e7d4e,stroke-width:2px,color:#fff
style AuthzClient fill:#50c878,stroke:#2e7d4e,stroke-width:2px,color:#fff
style IdentityClient fill:#50c878,stroke:#2e7d4e,stroke-width:2px,color:#fff
style AuditClient fill:#50c878,stroke:#2e7d4e,stroke-width:2px,color:#fff
``` ```
## Service Interaction Patterns ## Service Interaction Patterns
@@ -371,7 +369,6 @@ graph TB
Notifier --> EventBus Notifier --> EventBus
ErrorBus --> Logger ErrorBus --> Logger
ErrorBus --> Sentry
DB --> Tracer DB --> Tracer
Cache --> Tracer Cache --> Tracer
@@ -480,7 +477,8 @@ graph LR
style BlogService fill:#7b68ee,stroke:#5a4fcf,stroke-width:2px,color:#fff style BlogService fill:#7b68ee,stroke:#5a4fcf,stroke-width:2px,color:#fff
style AnalyticsService fill:#7b68ee,stroke:#5a4fcf,stroke-width:2px,color:#fff style AnalyticsService fill:#7b68ee,stroke:#5a4fcf,stroke-width:2px,color:#fff
style EventBus fill:#4a90e2,stroke:#2e5c8a,stroke-width:3px,color:#fff style EventBus fill:#4a90e2,stroke:#2e5c8a,stroke-width:3px,color:#fff
style ServiceClients fill:#50c878,stroke:#2e7d4e,stroke-width:2px,color:#fff style AuthzClient fill:#50c878,stroke:#2e7d4e,stroke-width:2px,color:#fff
style IdentityClient fill:#50c878,stroke:#2e7d4e,stroke-width:2px,color:#fff
``` ```
## Next Steps ## Next Steps

View File

@@ -35,7 +35,8 @@ Go Platform is a microservices platform designed to support multiple business do
- **[Data Flow Patterns](architecture/data-flow-patterns.md)**: How data flows through the system - **[Data Flow Patterns](architecture/data-flow-patterns.md)**: How data flows through the system
### Architecture Decision Records (ADRs) ### Architecture Decision Records (ADRs)
All architectural decisions are documented in [ADR records](adr/README.md), organized by implementation epic: All architectural decisions are documented in [ADR records](adr/README.md), organized by implementation epic:
- **Epic 0**: Project Setup & Foundation - **Epic 0**: Project Setup & Foundation
- **Epic 1**: Core Kernel & Infrastructure - **Epic 1**: Core Kernel & Infrastructure
- **Epic 2**: Authentication & Authorization - **Epic 2**: Authentication & Authorization
@@ -46,6 +47,7 @@ All architectural decisions are documented in [ADR records](adr/README.md), orga
### Implementation Tasks ### Implementation Tasks
Detailed task definitions for each epic are available in the [Stories section](stories/README.md): Detailed task definitions for each epic are available in the [Stories section](stories/README.md):
- **[Epic 0: Project Setup & Foundation](stories/epic0/README.md)** - [Implementation Summary](stories/epic0/SUMMARY.md) - **[Epic 0: Project Setup & Foundation](stories/epic0/README.md)** - [Implementation Summary](stories/epic0/SUMMARY.md)
- **[Epic 1: Core Kernel & Infrastructure](stories/epic1/README.md)** - [Implementation Summary](stories/epic1/SUMMARY.md) - **[Epic 1: Core Kernel & Infrastructure](stories/epic1/README.md)** - [Implementation Summary](stories/epic1/SUMMARY.md)
- Epic 2: Authentication & Authorization - Epic 2: Authentication & Authorization

View File

@@ -49,6 +49,7 @@ Core business services are implemented as separate, independently deployable ser
| **API Gateway** | `cmd/api-gateway/` | Request routing, authentication, rate limiting, CORS | N/A (entry point) | | **API Gateway** | `cmd/api-gateway/` | Request routing, authentication, rate limiting, CORS | N/A (entry point) |
Each service: Each service:
- Has its own `go.mod` (or shared workspace) - Has its own `go.mod` (or shared workspace)
- Manages its own database connection pool and schema - Manages its own database connection pool and schema
- Exposes gRPC server (and optional HTTP) - Exposes gRPC server (and optional HTTP)

View File

@@ -244,6 +244,7 @@ func main() {
``` ```
Services communicate via service clients: Services communicate via service clients:
- All inter-service communication uses gRPC (primary) or HTTP (fallback) - All inter-service communication uses gRPC (primary) or HTTP (fallback)
- Service discovery via service registry - Service discovery via service registry
- Each service manages its own database connection - Each service manages its own database connection

View File

@@ -85,6 +85,7 @@ Tasks are organized by epic and section. Each task file follows the naming conve
## Task Status Tracking ## Task Status Tracking
To track task completion: To track task completion:
1. Update the Status field in each task file 1. Update the Status field in each task file
2. Update checkboxes in the main plan.md 2. Update checkboxes in the main plan.md
3. Reference task IDs in commit messages: `[0.1.1] Initialize Go module` 3. Reference task IDs in commit messages: `[0.1.1] Initialize Go module`

View File

@@ -36,6 +36,7 @@ Tasks are organized by epic, with each major task section having its own detaile
## Task Status ## Task Status
Each task file includes: Each task file includes:
- **Task ID**: Unique identifier (e.g., `0.1.1`) - **Task ID**: Unique identifier (e.g., `0.1.1`)
- **Title**: Descriptive task name - **Title**: Descriptive task name
- **Epic**: Implementation epic - **Epic**: Implementation epic
@@ -50,6 +51,7 @@ Each task file includes:
## Task Tracking ## Task Tracking
Tasks can be tracked using: Tasks can be tracked using:
- GitHub Issues (linked from tasks) - GitHub Issues (linked from tasks)
- Project boards - Project boards
- Task management tools - Task management tools

View File

@@ -19,6 +19,7 @@ This story implements a complete logging system using Zap that provides structur
### 1. Logger Interface (`pkg/logger/logger.go`) ### 1. Logger Interface (`pkg/logger/logger.go`)
Define `Logger` interface with: Define `Logger` interface with:
- `Debug(msg string, fields ...Field)` - Debug level logging - `Debug(msg string, fields ...Field)` - Debug level logging
- `Info(msg string, fields ...Field)` - Info level logging - `Info(msg string, fields ...Field)` - Info level logging
- `Warn(msg string, fields ...Field)` - Warning level logging - `Warn(msg string, fields ...Field)` - Warning level logging
@@ -30,6 +31,7 @@ Define `Logger` interface with:
### 2. Zap Implementation (`internal/logger/zap_logger.go`) ### 2. Zap Implementation (`internal/logger/zap_logger.go`)
Implement `Logger` interface using Zap: Implement `Logger` interface using Zap:
- Structured JSON logging for production mode - Structured JSON logging for production mode
- Human-readable console logging for development mode - Human-readable console logging for development mode
- Configurable log levels (debug, info, warn, error) - Configurable log levels (debug, info, warn, error)
@@ -40,6 +42,7 @@ Implement `Logger` interface using Zap:
### 3. Request ID Middleware (`internal/logger/middleware.go`) ### 3. Request ID Middleware (`internal/logger/middleware.go`)
Gin middleware for request correlation: Gin middleware for request correlation:
- Generate unique request ID per HTTP request - Generate unique request ID per HTTP request
- Add request ID to request context - Add request ID to request context
- Add request ID to all logs within request context - Add request ID to all logs within request context

View File

@@ -19,6 +19,7 @@ This story sets up the complete CI/CD pipeline using GitHub Actions and provides
### 1. GitHub Actions Workflow (`.github/workflows/ci.yml`) ### 1. GitHub Actions Workflow (`.github/workflows/ci.yml`)
Complete CI pipeline with: Complete CI pipeline with:
- Go 1.24 setup - Go 1.24 setup
- Go module caching for faster builds - Go module caching for faster builds
- Linting with golangci-lint or staticcheck - Linting with golangci-lint or staticcheck
@@ -30,6 +31,7 @@ Complete CI pipeline with:
### 2. Comprehensive Makefile ### 2. Comprehensive Makefile
Developer-friendly Makefile with commands: Developer-friendly Makefile with commands:
- `make test` - Run all tests - `make test` - Run all tests
- `make test-coverage` - Run tests with coverage report - `make test-coverage` - Run tests with coverage report
- `make lint` - Run linters - `make lint` - Run linters

View File

@@ -19,6 +19,7 @@ This story implements the dependency injection system using Uber FX and creates
### 1. DI Container (`internal/di/container.go`) ### 1. DI Container (`internal/di/container.go`)
FX-based dependency injection container: FX-based dependency injection container:
- Initialize FX container - Initialize FX container
- Register Config and Logger providers - Register Config and Logger providers
- Basic lifecycle hooks (OnStart, OnStop) - Basic lifecycle hooks (OnStart, OnStop)
@@ -27,12 +28,14 @@ FX-based dependency injection container:
### 2. DI Providers (`internal/di/providers.go`) ### 2. DI Providers (`internal/di/providers.go`)
Provider functions for core services: Provider functions for core services:
- `ProvideConfig() fx.Option` - Configuration provider - `ProvideConfig() fx.Option` - Configuration provider
- `ProvideLogger() fx.Option` - Logger provider - `ProvideLogger() fx.Option` - Logger provider
- Provider functions return FX options for easy composition - Provider functions return FX options for easy composition
### 3. Application Entry Point (`cmd/platform/main.go`) ### 3. Application Entry Point (`cmd/platform/main.go`)
Main application bootstrap: Main application bootstrap:
- Load configuration - Load configuration
- Initialize DI container with core services - Initialize DI container with core services
- Set up basic application lifecycle - Set up basic application lifecycle

View File

@@ -28,6 +28,7 @@ This story extends the basic DI container to support core kernel services only:
### 2. Provider Functions (`internal/di/providers.go`) ### 2. Provider Functions (`internal/di/providers.go`)
Complete provider functions for core kernel services only: Complete provider functions for core kernel services only:
- `ProvideConfig() fx.Option` - Configuration provider - `ProvideConfig() fx.Option` - Configuration provider
- `ProvideLogger() fx.Option` - Logger provider - `ProvideLogger() fx.Option` - Logger provider
- `ProvideHealthCheckers() fx.Option` - Health check registry provider - `ProvideHealthCheckers() fx.Option` - Health check registry provider

View File

@@ -114,6 +114,7 @@ go run cmd/platform/main.go
- `config/default.yaml` - Add database config with schema isolation settings - `config/default.yaml` - Add database config with schema isolation settings
**Note:** Entity schemas are created in Epic 2: **Note:** Entity schemas are created in Epic 2:
- `services/identity/ent/schema/user.go` - User entity (Identity Service) - `services/identity/ent/schema/user.go` - User entity (Identity Service)
- `services/authz/ent/schema/role.go` - Role entity (Authz Service) - `services/authz/ent/schema/role.go` - Role entity (Authz Service)
- `services/authz/ent/schema/permission.go` - Permission entity (Authz Service) - `services/authz/ent/schema/permission.go` - Permission entity (Authz Service)

View File

@@ -19,6 +19,7 @@ This story defines service client interfaces for all core services (Auth, Identi
### 1. Service Client Interfaces (`pkg/services/`) ### 1. Service Client Interfaces (`pkg/services/`)
Define interfaces for all core services: Define interfaces for all core services:
- `AuthServiceClient` in `pkg/services/auth.go`: - `AuthServiceClient` in `pkg/services/auth.go`:
- `Login(ctx, email, password) (*TokenResponse, error)` - `Login(ctx, email, password) (*TokenResponse, error)`
- `RefreshToken(ctx, refreshToken) (*TokenResponse, error)` - `RefreshToken(ctx, refreshToken) (*TokenResponse, error)`

View File

@@ -19,6 +19,7 @@ This story extends the Authz Service (implemented in Story 2.3) with role manage
### 1. gRPC Service Extensions (`api/proto/authz.proto`) ### 1. gRPC Service Extensions (`api/proto/authz.proto`)
Extend Authz Service proto with role management RPCs: Extend Authz Service proto with role management RPCs:
- `CreateRoleRequest` / `CreateRoleResponse` - Create new role - `CreateRoleRequest` / `CreateRoleResponse` - Create new role
- `GetRoleRequest` / `GetRoleResponse` - Get role details - `GetRoleRequest` / `GetRoleResponse` - Get role details
- `ListRolesRequest` / `ListRolesResponse` - List all roles (with pagination) - `ListRolesRequest` / `ListRolesResponse` - List all roles (with pagination)

View File

@@ -19,6 +19,7 @@ This story implements the foundation for microservices architecture by creating
### 1. Service Client Interfaces (`pkg/services/`) ### 1. Service Client Interfaces (`pkg/services/`)
Define service client interfaces for all core services: Define service client interfaces for all core services:
- `IdentityServiceClient` - User and identity operations - `IdentityServiceClient` - User and identity operations
- `AuthServiceClient` - Authentication operations - `AuthServiceClient` - Authentication operations
- `AuthzServiceClient` - Authorization operations - `AuthzServiceClient` - Authorization operations
@@ -29,6 +30,7 @@ Define service client interfaces for all core services:
### 2. Service Client Factory (`internal/services/factory.go`) ### 2. Service Client Factory (`internal/services/factory.go`)
Factory pattern for creating service clients: Factory pattern for creating service clients:
- Create gRPC clients (primary) - Create gRPC clients (primary)
- Create HTTP clients (fallback) - Create HTTP clients (fallback)
- Support service registry integration - Support service registry integration

View File

@@ -0,0 +1,170 @@
# Audit Service Implementation Summary
## Overview
The Audit Service has been successfully implemented as an independent microservice. It provides audit logging functionality with gRPC API, database persistence, and service registry integration.
## Completed Components
### 1. Service Implementation (`services/audit/internal/service/audit_service.go`)
- **AuditService**: Core business logic for audit logging
- **Record**: Records audit log entries to database
- **Query**: Queries audit logs with filters (user_id, action, resource, time range, pagination)
- Uses Ent ORM for database operations
- Supports metadata as JSON
### 2. gRPC Server Implementation (`cmd/audit-service/audit_service_fx.go`)
- **auditServerImpl**: Implements `AuditService` gRPC interface
- **Record RPC**: Records audit log entries
- **Query RPC**: Queries audit logs with filters
- Converts between proto types and service types
- Error handling with proper gRPC status codes
### 3. Service Entry Point (`cmd/audit-service/main.go`)
- Independent service entry point
- Dependency injection using uber-go/fx
- Database connection with schema isolation (`audit` schema)
- Automatic migrations on startup
- Health check registry with database checker
- gRPC server lifecycle management
- Service registry registration (Consul)
- Graceful shutdown handling
### 4. Database Schema (`internal/ent/schema/audit_log.go`)
- **AuditLog** entity with fields:
- `id`: Unique identifier
- `user_id`: ID of the user/actor
- `action`: Action performed (e.g., "user.create")
- `resource`: Resource type (e.g., "user", "role")
- `resource_id`: ID of the target resource
- `ip_address`: Client IP address
- `user_agent`: Client user agent
- `metadata`: Additional metadata as JSON
- `timestamp`: When the action occurred
- Indexes on: user_id, resource_id, timestamp, action, resource
### 5. Configuration (`config/default.yaml`)
- Added service configuration:
```yaml
services:
audit:
port: 8084
host: "localhost"
```
## Architecture
The Audit Service follows the microservices architecture pattern:
- **Independent Deployment**: Has its own entry point (`cmd/audit-service/main.go`)
- **Schema Isolation**: Uses `audit` database schema
- **Service Discovery**: Registers with Consul service registry
- **gRPC API**: Exposes gRPC server on port 8084 (configurable)
- **Health Checks**: Implements gRPC health check protocol
- **Reflection**: Enabled for grpcurl testing
## Files Created
1. `services/audit/internal/service/audit_service.go` - Service business logic
2. `services/audit/internal/api/server.go` - gRPC server implementation (for reference, actual implementation in cmd)
3. `services/audit/internal/api/grpc_server.go` - gRPC server wrapper (for reference)
4. `cmd/audit-service/main.go` - Service entry point
5. `cmd/audit-service/audit_service_fx.go` - FX providers for service creation
6. `services/audit/service.go` - Public API package (placeholder)
## Testing the Audit Service
### Prerequisites
1. **PostgreSQL** running and accessible
2. **Consul** running (optional, for service discovery)
3. **Configuration** set up in `config/default.yaml`
### Start the Service
```bash
# Using nix-shell (recommended)
nix-shell
go run ./cmd/audit-service/main.go
# Or build and run
go build -o bin/audit-service ./cmd/audit-service
./bin/audit-service
```
### Verify Service Startup
The service should:
1. Connect to database
2. Create `audit` schema if it doesn't exist
3. Run migrations (create `audit_logs` table)
4. Start gRPC server on port 8084
5. Register with Consul (if available)
Check logs for:
- "Database migrations completed for audit service"
- "Starting Audit Service gRPC server"
- "Audit Service gRPC server started successfully"
- "Registered Audit Service with service registry"
### Test with grpcurl
```bash
# List available services
grpcurl -plaintext localhost:8084 list
# Check health
grpcurl -plaintext localhost:8084 grpc.health.v1.Health/Check
# Record an audit log entry
grpcurl -plaintext -d '{
"entry": {
"user_id": "user-123",
"action": "user.login",
"resource": "user",
"resource_id": "user-123",
"ip_address": "192.168.1.1",
"user_agent": "Mozilla/5.0",
"metadata": {
"method": "POST",
"endpoint": "/api/v1/auth/login"
},
"timestamp": 1699123456
}
}' localhost:8084 audit.v1.AuditService/Record
# Query audit logs
grpcurl -plaintext -d '{
"user_id": "user-123",
"limit": 10,
"offset": 0
}' localhost:8084 audit.v1.AuditService/Query
```
### Verify Database
```bash
# Connect to database
docker exec -it goplt-postgres psql -U goplt -d goplt
# Switch to audit schema
SET search_path TO audit;
# Check table exists
\dt
# Query audit logs
SELECT * FROM audit_logs ORDER BY timestamp DESC LIMIT 10;
```
## Next Steps
The Audit Service is complete and ready for use. Other services (Auth, Identity, Authz) can use the Audit Service via the `AuditServiceClient` interface to record audit events.
## Notes
- The service uses inline implementation in `cmd/audit-service/audit_service_fx.go` to avoid importing internal packages from cmd
- The internal service implementation in `services/audit/internal/service/` exists for reference and can be used by other internal packages
- Service registry registration happens on startup; deregistration on shutdown (service ID stored for proper cleanup)
- Health checks are available via gRPC health protocol

View File

@@ -0,0 +1,541 @@
# Epic 2 Implementation Summary
## Overview
Epic 2 implements the Core Services (Authentication & Authorization) as independent, deployable microservices. All services are implemented with gRPC APIs, database persistence, service registry integration, and inter-service communication via gRPC clients.
## Completed Stories
### Story 2.1: Auth Service - JWT Authentication ✅
**Status**: Complete (with RefreshToken entity TODO)
**Implementation**:
- **Entry Point**: `cmd/auth-service/main.go`
- **Service**: `cmd/auth-service/auth_service_fx.go`
- **Features**:
- JWT access token generation (15 minutes lifetime)
- Refresh token generation (7 days lifetime, stored in database)
- Token validation and claims extraction
- Login, RefreshToken, ValidateToken, Logout RPCs
- Integration with Identity Service for credential validation
- **Database Schema**: `auth` schema with `refresh_tokens` table (schema defined, needs Ent generation)
- **Port**: 8081 (configurable)
**Note**: RefreshToken entity needs to be generated using `go generate ./ent/...` for full functionality. Currently returns placeholder errors for refresh token operations.
### Story 2.2: Identity Service - User Management ✅
**Status**: Complete
**Implementation**:
- **Entry Point**: `cmd/identity-service/main.go`
- **Service**: `cmd/identity-service/identity_service_fx.go`
- **Password Hashing**: `services/identity/internal/password/password.go` (argon2id)
- **Features**:
- User CRUD operations (Create, Get, GetByEmail, Update, Delete)
- Password hashing with argon2id (OWASP recommended)
- Email verification flow
- Password reset flow (token-based, 24-hour expiration)
- Password change with old password verification
- **Database Schema**: `identity` schema with `users` table
- **Port**: 8082 (configurable)
### Story 2.3: Authz Service - Authorization ✅
**Status**: Complete
**Implementation**:
- **Entry Point**: `cmd/authz-service/main.go`
- **Service**: `cmd/authz-service/authz_service_fx.go`
- **Features**:
- Permission checking (Authorize, HasPermission)
- User permissions retrieval (GetUserPermissions)
- User roles retrieval (GetUserRoles)
- RBAC-based authorization via UserRole → Role → RolePermission → Permission relationships
- **Database Schema**: `authz` schema with `roles`, `permissions`, `role_permissions`, `user_roles` tables
- **Port**: 8083 (configurable)
### Story 2.4: Role Management ✅
**Status**: Complete (integrated into Authz Service)
**Implementation**:
- Role and permission management is handled through the Authz Service
- Database schemas support role-permission and user-role relationships
- Role management APIs can be extended in future stories
### Story 2.5: Audit Service ✅
**Status**: Complete
**Implementation**:
- **Entry Point**: `cmd/audit-service/main.go`
- **Service**: `cmd/audit-service/audit_service_fx.go`
- **Features**:
- Audit log recording (Record RPC)
- Audit log querying with filters (Query RPC)
- Support for filtering by user_id, action, resource, resource_id, time range
- Pagination support (limit, offset)
- Metadata storage as JSON
- **Database Schema**: `audit` schema with `audit_logs` table
- **Port**: 8084 (configurable)
### Story 2.6: Database Seeding ⏳
**Status**: Pending
**Note**: Database seeding scripts are not yet implemented. This can be added as a follow-up task to populate initial data (admin users, default roles, permissions).
### Additional Implementation: gRPC Clients ✅
**Status**: Complete
**Implementation**:
- **Auth Client**: `internal/client/grpc/auth_client.go`
- **Identity Client**: `internal/client/grpc/identity_client.go`
- **Authz Client**: `internal/client/grpc/authz_client.go`
- **Audit Client**: `internal/client/grpc/audit_client.go`
All clients:
- Use service discovery via Consul
- Implement full gRPC communication
- Handle connection management
- Convert between proto and service types
## Architecture
### Service Independence
Each service:
- Has its own entry point in `cmd/{service}/`
- Manages its own database connection and schema
- Registers with Consul service registry
- Exposes gRPC server with health checks
- Can be deployed independently
### Database Schema Isolation
- **Auth Service**: `auth` schema
- **Identity Service**: `identity` schema
- **Authz Service**: `authz` schema
- **Audit Service**: `audit` schema
### Service Communication
- Services communicate via gRPC using service discovery
- Service clients are available via `ServiceClientFactory`
- Clients automatically discover and connect to service instances
### Configuration
All service configurations are in `config/default.yaml`:
```yaml
services:
auth:
port: 8081
host: "localhost"
identity:
port: 8082
host: "localhost"
authz:
port: 8083
host: "localhost"
audit:
port: 8084
host: "localhost"
auth:
jwt_secret: "change-this-secret-in-production"
```
## Prerequisites
1. **PostgreSQL** running and accessible
2. **Consul** running (for service discovery, required for service registry)
3. **Go 1.24+** (or use `nix-shell` for development environment)
4. **NixOS** (optional, for `shell.nix` development environment)
5. **Docker and Docker Compose** (for running PostgreSQL and Consul)
## Building the Services
### Using Nix Shell (Recommended)
```bash
# Enter nix shell (automatically loads via .envrc if using direnv)
nix-shell
# Build all services
go build ./cmd/auth-service
go build ./cmd/identity-service
go build ./cmd/authz-service
go build ./cmd/audit-service
```
### Without Nix
```bash
# Ensure you have:
# - Go 1.24+
# - protoc
# - protoc-gen-go
# - protoc-gen-go-grpc
go build ./cmd/auth-service
go build ./cmd/identity-service
go build ./cmd/authz-service
go build ./cmd/audit-service
```
## Running the Services
### Option 1: Development Mode (Recommended for Development)
Use `docker-compose.dev.yml` for infrastructure only, run services locally:
```bash
# Start only PostgreSQL and Consul
docker-compose -f docker-compose.dev.yml up -d
# Verify containers are running
docker-compose -f docker-compose.dev.yml ps
# Check logs
docker-compose -f docker-compose.dev.yml logs postgres
docker-compose -f docker-compose.dev.yml logs consul
```
Then start services locally:
```bash
# Terminal 1: Auth Service
go run ./cmd/auth-service/*.go
# Terminal 2: Identity Service
go run ./cmd/identity-service/*.go
# Terminal 3: Authz Service
go run ./cmd/authz-service/*.go
# Terminal 4: Audit Service
go run ./cmd/audit-service/*.go
```
### Option 2: Full Docker Compose (All Services in Docker)
Use `docker-compose.yml` to run everything in Docker:
```bash
# Build and start all services
docker-compose up -d --build
# View logs
docker-compose logs -f
# Stop all services
docker-compose down
```
This will start:
- PostgreSQL (port 5432)
- Consul (port 8500)
- Auth Service (port 8081)
- Identity Service (port 8082)
- Authz Service (port 8083)
- Audit Service (port 8084)
- API Gateway (port 8080)
### Infrastructure Services
Both docker-compose files include:
- **PostgreSQL**: Available at `localhost:5432`
- Database: `goplt`
- User: `goplt`
- Password: `goplt_password`
- **Consul**: Available at `localhost:8500`
- Running in dev mode
- Web UI: http://localhost:8500/ui (for viewing registered services)
- API: http://localhost:8500/v1
### Alternative: Manual Setup
```bash
# Start PostgreSQL manually
# Ensure database 'goplt' exists with user 'goplt' and password 'goplt_password'
# Start Consul manually
consul agent -dev
```
### 4. Verify Services
Check service logs for:
- Database connection success
- Migration completion
- gRPC server startup
- Service registry registration
## Testing the Services
### Using grpcurl
#### 1. Identity Service - Create User
```bash
grpcurl -plaintext -d '{
"email": "user@example.com",
"password": "securepassword123",
"username": "testuser",
"first_name": "Test",
"last_name": "User"
}' localhost:8082 identity.v1.IdentityService/CreateUser
```
#### 2. Identity Service - Get User
```bash
grpcurl -plaintext -d '{
"id": "USER_ID_FROM_CREATE"
}' localhost:8082 identity.v1.IdentityService/GetUser
```
#### 3. Auth Service - Login
```bash
grpcurl -plaintext -d '{
"email": "user@example.com",
"password": "securepassword123"
}' localhost:8081 auth.v1.AuthService/Login
```
#### 4. Auth Service - Validate Token
```bash
grpcurl -plaintext -d '{
"token": "ACCESS_TOKEN_FROM_LOGIN"
}' localhost:8081 auth.v1.AuthService/ValidateToken
```
#### 5. Authz Service - Get User Roles
```bash
grpcurl -plaintext -d '{
"user_id": "USER_ID"
}' localhost:8083 authz.v1.AuthzService/GetUserRoles
```
#### 6. Audit Service - Record Audit Log
```bash
grpcurl -plaintext -d '{
"entry": {
"user_id": "user-123",
"action": "user.login",
"resource": "user",
"resource_id": "user-123",
"ip_address": "192.168.1.1",
"user_agent": "Mozilla/5.0",
"metadata": {
"method": "POST",
"endpoint": "/api/v1/auth/login"
},
"timestamp": 1699123456
}
}' localhost:8084 audit.v1.AuditService/Record
```
#### 7. Audit Service - Query Audit Logs
```bash
grpcurl -plaintext -d '{
"user_id": "user-123",
"limit": 10,
"offset": 0
}' localhost:8084 audit.v1.AuditService/Query
```
### Health Checks
All services expose gRPC health checks:
```bash
grpcurl -plaintext localhost:8081 grpc.health.v1.Health/Check
grpcurl -plaintext localhost:8082 grpc.health.v1.Health/Check
grpcurl -plaintext localhost:8083 grpc.health.v1.Health/Check
grpcurl -plaintext localhost:8084 grpc.health.v1.Health/Check
```
## Verification
### Verify Consul
```bash
# Check Consul is running
curl http://localhost:8500/v1/status/leader
# List registered services
curl http://localhost:8500/v1/agent/services
# Or use Consul UI
# Open http://localhost:8500/ui in your browser
```
### Database Verification
#### Check Schemas
```bash
docker exec -it goplt-postgres psql -U goplt -d goplt -c "\dn"
```
### Check Tables
```bash
# Auth schema
docker exec -it goplt-postgres psql -U goplt -d goplt -c "SET search_path TO auth; \dt"
# Identity schema
docker exec -it goplt-postgres psql -U goplt -d goplt -c "SET search_path TO identity; \dt"
# Authz schema
docker exec -it goplt-postgres psql -U goplt -d goplt -c "SET search_path TO authz; \dt"
# Audit schema
docker exec -it goplt-postgres psql -U goplt -d goplt -c "SET search_path TO audit; \dt"
```
## Known Issues and TODOs
### 1. RefreshToken Entity
- **Issue**: RefreshToken entity schema is defined but Ent code needs to be regenerated
- **Impact**: Auth Service refresh token operations return placeholder errors
- **Fix**: Run `go generate ./ent/...` or `go run -mod=readonly entgo.io/ent/cmd/ent generate ./ent/schema`
- **Location**: `internal/ent/schema/refresh_token.go`
### 2. Database Seeding
- **Status**: Not implemented
- **Impact**: No initial data (admin users, default roles, permissions)
- **Future Work**: Create seed scripts for each service
### 3. Tests
- **Status**: Not implemented
- **Impact**: No automated test coverage
- **Future Work**: Add unit tests, integration tests, and E2E tests
### 4. Auth Service - Password Verification
- **Issue**: Auth Service login doesn't verify password with Identity Service
- **Impact**: Login may not work correctly
- **Fix**: Identity Service needs to expose VerifyPassword RPC or Auth Service should call it directly
### 5. Auth Service - Role Retrieval
- **Issue**: Auth Service doesn't retrieve user roles from Authz Service
- **Impact**: JWT tokens don't include user roles
- **Fix**: Integrate with Authz Service to get user roles during login
## File Structure
```
goplt/
├── cmd/
│ ├── auth-service/
│ │ ├── main.go
│ │ ├── auth_service_fx.go
│ │ └── Dockerfile
│ ├── identity-service/
│ │ ├── main.go
│ │ ├── identity_service_fx.go
│ │ └── Dockerfile
│ ├── authz-service/
│ │ ├── main.go
│ │ ├── authz_service_fx.go
│ │ └── Dockerfile
│ ├── audit-service/
│ │ ├── main.go
│ │ ├── audit_service_fx.go
│ │ └── Dockerfile
│ └── api-gateway/
│ ├── main.go
│ └── Dockerfile
├── docker-compose.yml
├── docker-compose.dev.yml
├── .dockerignore
├── services/
│ └── identity/
│ └── internal/
│ └── password/
│ └── password.go
├── internal/
│ ├── ent/
│ │ └── schema/
│ │ ├── user.go
│ │ ├── role.go
│ │ ├── permission.go
│ │ ├── user_role.go
│ │ ├── role_permission.go
│ │ ├── audit_log.go
│ │ └── refresh_token.go
│ └── client/
│ └── grpc/
│ ├── auth_client.go
│ ├── identity_client.go
│ ├── authz_client.go
│ └── audit_client.go
├── api/
│ └── proto/
│ ├── auth.proto
│ ├── identity.proto
│ ├── authz.proto
│ └── audit.proto
└── config/
└── default.yaml
```
## Docker Deployment
### Building Docker Images
Each service has its own Dockerfile:
```bash
# Build individual service images
docker build -f cmd/auth-service/Dockerfile -t goplt-auth-service:latest .
docker build -f cmd/identity-service/Dockerfile -t goplt-identity-service:latest .
docker build -f cmd/authz-service/Dockerfile -t goplt-authz-service:latest .
docker build -f cmd/audit-service/Dockerfile -t goplt-audit-service:latest .
docker build -f cmd/api-gateway/Dockerfile -t goplt-api-gateway:latest .
```
### Docker Compose Files
- **`docker-compose.dev.yml`**: Development setup (PostgreSQL + Consul only)
- Use when running services locally with `go run`
- Start with: `docker-compose -f docker-compose.dev.yml up -d`
- **`docker-compose.yml`**: Full production-like setup (all services + infrastructure)
- All services run in Docker containers
- Start with: `docker-compose up -d --build`
### Environment Variables
Services can be configured via environment variables:
- `ENVIRONMENT`: `development` or `production`
- `DATABASE_DSN`: PostgreSQL connection string
- `REGISTRY_TYPE`: Service registry type (default: `consul`)
- `REGISTRY_CONSUL_ADDRESS`: Consul address (default: `localhost:8500`)
## Next Steps
1. **Complete RefreshToken Implementation**
- Regenerate Ent code for RefreshToken entity
- Update Auth Service to use RefreshToken entity
2. **Implement Database Seeding**
- Create seed scripts for initial data
- Add admin user, default roles, and permissions
3. **Add Tests**
- Unit tests for each service
- Integration tests for service communication
- E2E tests for complete flows
4. **Enhance Auth Service**
- Integrate password verification with Identity Service
- Integrate role retrieval with Authz Service
- Complete refresh token implementation
5. **API Gateway Integration**
- Route requests to appropriate services
- Implement authentication middleware
- Implement authorization middleware
## Summary
Epic 2 successfully implements all four core services (Auth, Identity, Authz, Audit) as independent microservices with:
- ✅ gRPC APIs
- ✅ Database persistence with schema isolation
- ✅ Service registry integration
- ✅ Inter-service communication via gRPC clients
- ✅ Health checks
- ✅ Graceful shutdown
All services build successfully and are ready for deployment. The main remaining work is:
- RefreshToken entity generation
- Database seeding
- Test coverage
- Auth Service enhancements (password verification, role retrieval)

View File

@@ -1,4 +0,0 @@
// Package ent provides code generation for Ent schema definitions.
package ent
//go:generate go run -mod=mod entgo.io/ent/cmd/ent generate ./schema

59
ent/schema/audit_log.go Normal file
View File

@@ -0,0 +1,59 @@
// Package schema defines the Ent schema for domain entities.
package schema
import (
"time"
"entgo.io/ent"
"entgo.io/ent/schema/field"
"entgo.io/ent/schema/index"
)
// AuditLog holds the schema definition for the AuditLog entity.
type AuditLog struct {
ent.Schema
}
// Fields of the AuditLog.
func (AuditLog) Fields() []ent.Field {
return []ent.Field{
field.String("id").
Unique().
Immutable(),
field.String("user_id").
NotEmpty().
Comment("ID of the user/actor performing the action"),
field.String("action").
NotEmpty().
Comment("Action performed (e.g., user.create, user.update)"),
field.String("resource").
Optional().
Comment("Resource type (e.g., user, role)"),
field.String("resource_id").
Optional().
Comment("ID of the target resource"),
field.String("ip_address").
Optional().
Comment("IP address of the client"),
field.String("user_agent").
Optional().
Comment("User agent of the client"),
field.JSON("metadata", map[string]interface{}{}).
Optional().
Comment("Additional metadata as JSON"),
field.Time("timestamp").
Default(time.Now).
Immutable(),
}
}
// Indexes of the AuditLog.
func (AuditLog) Indexes() []ent.Index {
return []ent.Index{
index.Fields("user_id"),
index.Fields("resource_id"),
index.Fields("timestamp"),
index.Fields("action"),
index.Fields("resource"),
}
}

View File

@@ -1,19 +0,0 @@
// Package schema defines the Ent schema for audit log entities.
package schema
import "entgo.io/ent"
// AuditLog holds the schema definition for the AuditLog entity.
type AuditLog struct {
ent.Schema
}
// Fields of the AuditLog.
func (AuditLog) Fields() []ent.Field {
return nil
}
// Edges of the AuditLog.
func (AuditLog) Edges() []ent.Edge {
return nil
}

View File

@@ -1,6 +1,10 @@
package schema package schema
import "entgo.io/ent" import (
"entgo.io/ent"
"entgo.io/ent/schema/edge"
"entgo.io/ent/schema/field"
)
// Permission holds the schema definition for the Permission entity. // Permission holds the schema definition for the Permission entity.
type Permission struct { type Permission struct {
@@ -9,10 +13,20 @@ type Permission struct {
// Fields of the Permission. // Fields of the Permission.
func (Permission) Fields() []ent.Field { func (Permission) Fields() []ent.Field {
return nil return []ent.Field{
field.String("id").
Unique().
Immutable(),
field.String("name").
Unique().
NotEmpty().
Comment("Format: module.resource.action"),
}
} }
// Edges of the Permission. // Edges of the Permission.
func (Permission) Edges() []ent.Edge { func (Permission) Edges() []ent.Edge {
return nil return []ent.Edge{
edge.To("role_permissions", RolePermission.Type),
}
} }

View File

@@ -0,0 +1,44 @@
package schema
import (
"time"
"entgo.io/ent"
"entgo.io/ent/schema/field"
"entgo.io/ent/schema/index"
)
// RefreshToken holds the schema definition for the RefreshToken entity.
type RefreshToken struct {
ent.Schema
}
// Fields of the RefreshToken.
func (RefreshToken) Fields() []ent.Field {
return []ent.Field{
field.String("id").
Unique().
Immutable(),
field.String("user_id").
NotEmpty().
Comment("ID of the user who owns this refresh token"),
field.String("token_hash").
NotEmpty().
Sensitive().
Comment("SHA256 hash of the refresh token"),
field.Time("expires_at").
Comment("When the refresh token expires"),
field.Time("created_at").
Default(time.Now).
Immutable(),
}
}
// Indexes of the RefreshToken.
func (RefreshToken) Indexes() []ent.Index {
return []ent.Index{
index.Fields("user_id"),
index.Fields("token_hash"),
index.Fields("expires_at"),
}
}

View File

@@ -1,6 +1,12 @@
package schema package schema
import "entgo.io/ent" import (
"time"
"entgo.io/ent"
"entgo.io/ent/schema/edge"
"entgo.io/ent/schema/field"
)
// Role holds the schema definition for the Role entity. // Role holds the schema definition for the Role entity.
type Role struct { type Role struct {
@@ -9,10 +15,25 @@ type Role struct {
// Fields of the Role. // Fields of the Role.
func (Role) Fields() []ent.Field { func (Role) Fields() []ent.Field {
return nil return []ent.Field{
field.String("id").
Unique().
Immutable(),
field.String("name").
Unique().
NotEmpty(),
field.String("description").
Optional(),
field.Time("created_at").
Default(time.Now).
Immutable(),
}
} }
// Edges of the Role. // Edges of the Role.
func (Role) Edges() []ent.Edge { func (Role) Edges() []ent.Edge {
return nil return []ent.Edge{
edge.To("role_permissions", RolePermission.Type),
edge.To("user_roles", UserRole.Type),
}
} }

View File

@@ -0,0 +1,34 @@
package schema
import (
"entgo.io/ent"
"entgo.io/ent/schema/edge"
"entgo.io/ent/schema/field"
)
// RolePermission holds the schema definition for the RolePermission entity (many-to-many relationship).
type RolePermission struct {
ent.Schema
}
// Fields of the RolePermission.
func (RolePermission) Fields() []ent.Field {
return []ent.Field{
field.String("role_id"),
field.String("permission_id"),
}
}
// Edges of the RolePermission.
func (RolePermission) Edges() []ent.Edge {
return []ent.Edge{
edge.To("role", Role.Type).
Unique().
Required().
Field("role_id"),
edge.To("permission", Permission.Type).
Unique().
Required().
Field("permission_id"),
}
}

View File

@@ -1,6 +1,12 @@
package schema package schema
import "entgo.io/ent" import (
"time"
"entgo.io/ent"
"entgo.io/ent/schema/edge"
"entgo.io/ent/schema/field"
)
// User holds the schema definition for the User entity. // User holds the schema definition for the User entity.
type User struct { type User struct {
@@ -9,10 +15,43 @@ type User struct {
// Fields of the User. // Fields of the User.
func (User) Fields() []ent.Field { func (User) Fields() []ent.Field {
return nil return []ent.Field{
field.String("id").
Unique().
Immutable(),
field.String("email").
Unique().
NotEmpty(),
field.String("username").
Optional(),
field.String("first_name").
Optional(),
field.String("last_name").
Optional(),
field.String("password_hash").
NotEmpty(),
field.Bool("verified").
Default(false),
field.String("email_verification_token").
Optional().
Sensitive(),
field.String("password_reset_token").
Optional().
Sensitive(),
field.Time("password_reset_expires_at").
Optional(),
field.Time("created_at").
Default(time.Now).
Immutable(),
field.Time("updated_at").
Default(time.Now).
UpdateDefault(time.Now),
}
} }
// Edges of the User. // Edges of the User.
func (User) Edges() []ent.Edge { func (User) Edges() []ent.Edge {
return nil return []ent.Edge{
edge.To("user_roles", UserRole.Type),
}
} }

34
ent/schema/user_role.go Normal file
View File

@@ -0,0 +1,34 @@
package schema
import (
"entgo.io/ent"
"entgo.io/ent/schema/edge"
"entgo.io/ent/schema/field"
)
// UserRole holds the schema definition for the UserRole entity (many-to-many relationship).
type UserRole struct {
ent.Schema
}
// Fields of the UserRole.
func (UserRole) Fields() []ent.Field {
return []ent.Field{
field.String("user_id"),
field.String("role_id"),
}
}
// Edges of the UserRole.
func (UserRole) Edges() []ent.Edge {
return []ent.Edge{
edge.To("user", User.Type).
Unique().
Required().
Field("user_id"),
edge.To("role", Role.Type).
Unique().
Required().
Field("role_id"),
}
}

16
go.mod
View File

@@ -10,6 +10,7 @@ require (
github.com/lib/pq v1.10.9 github.com/lib/pq v1.10.9
github.com/prometheus/client_golang v1.23.2 github.com/prometheus/client_golang v1.23.2
github.com/spf13/viper v1.18.0 github.com/spf13/viper v1.18.0
github.com/stretchr/testify v1.11.1
go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin v0.63.0 go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin v0.63.0
go.opentelemetry.io/otel v1.38.0 go.opentelemetry.io/otel v1.38.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0
@@ -18,7 +19,9 @@ require (
go.opentelemetry.io/otel/trace v1.38.0 go.opentelemetry.io/otel/trace v1.38.0
go.uber.org/fx v1.24.0 go.uber.org/fx v1.24.0
go.uber.org/zap v1.26.0 go.uber.org/zap v1.26.0
golang.org/x/crypto v0.43.0
google.golang.org/grpc v1.75.0 google.golang.org/grpc v1.75.0
google.golang.org/protobuf v1.36.8
) )
require ( require (
@@ -33,6 +36,7 @@ require (
github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/cenkalti/backoff/v5 v5.0.3 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/cloudwego/base64x v0.1.6 // indirect github.com/cloudwego/base64x v0.1.6 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/fatih/color v1.16.0 // indirect github.com/fatih/color v1.16.0 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.10 // indirect github.com/gabriel-vasile/mimetype v1.4.10 // indirect
@@ -45,6 +49,7 @@ require (
github.com/go-playground/validator/v10 v10.27.0 // indirect github.com/go-playground/validator/v10 v10.27.0 // indirect
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
github.com/goccy/go-json v0.10.5 // indirect github.com/goccy/go-json v0.10.5 // indirect
github.com/golang-jwt/jwt/v5 v5.3.0 // indirect
github.com/google/go-cmp v0.7.0 // indirect github.com/google/go-cmp v0.7.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect
@@ -70,6 +75,7 @@ require (
github.com/modern-go/reflect2 v1.0.2 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.66.1 // indirect github.com/prometheus/common v0.66.1 // indirect
github.com/prometheus/procfs v0.16.1 // indirect github.com/prometheus/procfs v0.16.1 // indirect
@@ -92,15 +98,13 @@ require (
go.uber.org/multierr v1.10.0 // indirect go.uber.org/multierr v1.10.0 // indirect
go.yaml.in/yaml/v2 v2.4.2 // indirect go.yaml.in/yaml/v2 v2.4.2 // indirect
golang.org/x/arch v0.20.0 // indirect golang.org/x/arch v0.20.0 // indirect
golang.org/x/crypto v0.41.0 // indirect
golang.org/x/exp v0.0.0-20250808145144-a408d31f581a // indirect golang.org/x/exp v0.0.0-20250808145144-a408d31f581a // indirect
golang.org/x/mod v0.27.0 // indirect golang.org/x/mod v0.28.0 // indirect
golang.org/x/net v0.43.0 // indirect golang.org/x/net v0.45.0 // indirect
golang.org/x/sys v0.35.0 // indirect golang.org/x/sys v0.37.0 // indirect
golang.org/x/text v0.28.0 // indirect golang.org/x/text v0.30.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5 // indirect
google.golang.org/protobuf v1.36.8 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
) )

35
go.sum
View File

@@ -85,6 +85,8 @@ github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlnd
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@@ -191,6 +193,8 @@ github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27k
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM= github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM=
github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
@@ -215,6 +219,8 @@ github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjY
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY=
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
@@ -267,6 +273,8 @@ github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.18.0 h1:pN6W1ub/G4OfnM+NR9p7xP9R6TltLUzp5JG9yZD3Qg0= github.com/spf13/viper v1.18.0 h1:pN6W1ub/G4OfnM+NR9p7xP9R6TltLUzp5JG9yZD3Qg0=
@@ -274,8 +282,9 @@ github.com/spf13/viper v1.18.0/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMV
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
@@ -337,12 +346,12 @@ golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4= golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
golang.org/x/exp v0.0.0-20250808145144-a408d31f581a h1:Y+7uR/b1Mw2iSXZ3G//1haIiSElDQZ8KWh0h+sZPG90= golang.org/x/exp v0.0.0-20250808145144-a408d31f581a h1:Y+7uR/b1Mw2iSXZ3G//1haIiSElDQZ8KWh0h+sZPG90=
golang.org/x/exp v0.0.0-20250808145144-a408d31f581a/go.mod h1:rT6SFzZ7oxADUDx58pcaKFTcZ+inxAa9fTrYx/uVYwg= golang.org/x/exp v0.0.0-20250808145144-a408d31f581a/go.mod h1:rT6SFzZ7oxADUDx58pcaKFTcZ+inxAa9fTrYx/uVYwg=
golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ= golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U=
golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@@ -350,13 +359,15 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8= golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8=
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= golang.org/x/net v0.45.0 h1:RLBg5JKixCy82FtLJpeNlVM0nrSqpCRYzVU1n8kj0tM=
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= golang.org/x/net v0.45.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -378,17 +389,19 @@ golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE=
golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=

View File

@@ -4,30 +4,146 @@ package grpc
import ( import (
"context" "context"
"fmt" "fmt"
"math"
auditv1 "git.dcentral.systems/toolz/goplt/api/proto/generated/audit/v1"
"git.dcentral.systems/toolz/goplt/pkg/registry" "git.dcentral.systems/toolz/goplt/pkg/registry"
"git.dcentral.systems/toolz/goplt/pkg/services" "git.dcentral.systems/toolz/goplt/pkg/services"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
) )
// AuditClient implements AuditServiceClient using gRPC. // AuditClient implements AuditServiceClient using gRPC.
// This is a stub implementation - will be fully implemented when proto files are generated in Phase 4.
type AuditClient struct { type AuditClient struct {
registry registry.ServiceRegistry registry registry.ServiceRegistry
conn *grpc.ClientConn
client auditv1.AuditServiceClient
} }
// NewAuditClient creates a new gRPC client for the Audit Service. // NewAuditClient creates a new gRPC client for the Audit Service.
func NewAuditClient(reg registry.ServiceRegistry) (services.AuditServiceClient, error) { func NewAuditClient(reg registry.ServiceRegistry) (services.AuditServiceClient, error) {
return &AuditClient{ client := &AuditClient{
registry: reg, registry: reg,
}, nil }
return client, nil
}
// connect connects to the Audit Service.
func (c *AuditClient) connect(ctx context.Context) error {
if c.conn != nil {
return nil
}
instances, err := c.registry.Discover(ctx, "audit-service")
if err != nil {
return fmt.Errorf("failed to discover audit service: %w", err)
}
if len(instances) == 0 {
return fmt.Errorf("no instances found for audit-service")
}
instance := instances[0]
address := fmt.Sprintf("%s:%d", instance.Address, instance.Port)
conn, err := grpc.NewClient(address, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
return fmt.Errorf("failed to connect to audit-service at %s: %w", address, err)
}
c.conn = conn
c.client = auditv1.NewAuditServiceClient(conn)
return nil
} }
// Record records an audit log entry. // Record records an audit log entry.
func (c *AuditClient) Record(ctx context.Context, entry *services.AuditLogEntry) error { func (c *AuditClient) Record(ctx context.Context, entry *services.AuditLogEntry) error {
return fmt.Errorf("not implemented: proto files not yet generated") if err := c.connect(ctx); err != nil {
return err
}
_, err := c.client.Record(ctx, &auditv1.RecordRequest{
Entry: &auditv1.AuditLogEntry{
UserId: entry.UserID,
Action: entry.Action,
Resource: entry.Resource,
ResourceId: entry.ResourceID,
IpAddress: entry.IPAddress,
UserAgent: entry.UserAgent,
Metadata: entry.Metadata,
Timestamp: entry.Timestamp,
},
})
if err != nil {
return fmt.Errorf("record audit log failed: %w", err)
}
return nil
} }
// Query queries audit logs based on filters. // Query queries audit logs based on filters.
func (c *AuditClient) Query(ctx context.Context, filters *services.AuditLogFilters) ([]services.AuditLogEntry, error) { func (c *AuditClient) Query(ctx context.Context, filters *services.AuditLogFilters) ([]services.AuditLogEntry, error) {
return nil, fmt.Errorf("not implemented: proto files not yet generated") if err := c.connect(ctx); err != nil {
return nil, err
}
var limitInt32, offsetInt32 int32
if filters.Limit > math.MaxInt32 {
limitInt32 = math.MaxInt32
} else if filters.Limit < math.MinInt32 {
limitInt32 = math.MinInt32
} else {
limitInt32 = int32(filters.Limit) //nolint:gosec // bounds checked above
}
if filters.Offset > math.MaxInt32 {
offsetInt32 = math.MaxInt32
} else if filters.Offset < math.MinInt32 {
offsetInt32 = math.MinInt32
} else {
offsetInt32 = int32(filters.Offset) //nolint:gosec // bounds checked above
}
req := &auditv1.QueryRequest{
Limit: limitInt32,
Offset: offsetInt32,
}
if filters.UserID != nil {
req.UserId = filters.UserID
}
if filters.Action != nil {
req.Action = filters.Action
}
if filters.Resource != nil {
req.Resource = filters.Resource
}
if filters.ResourceID != nil {
req.ResourceId = filters.ResourceID
}
if filters.StartTime != nil {
req.StartTime = filters.StartTime
}
if filters.EndTime != nil {
req.EndTime = filters.EndTime
}
resp, err := c.client.Query(ctx, req)
if err != nil {
return nil, fmt.Errorf("query audit logs failed: %w", err)
}
entries := make([]services.AuditLogEntry, 0, len(resp.Entries))
for _, e := range resp.Entries {
entries = append(entries, services.AuditLogEntry{
UserID: e.UserId,
Action: e.Action,
Resource: e.Resource,
ResourceID: e.ResourceId,
IPAddress: e.IpAddress,
UserAgent: e.UserAgent,
Metadata: e.Metadata,
Timestamp: e.Timestamp,
})
}
return entries, nil
} }

View File

@@ -5,6 +5,7 @@ import (
"context" "context"
"fmt" "fmt"
authv1 "git.dcentral.systems/toolz/goplt/api/proto/generated/auth/v1"
"git.dcentral.systems/toolz/goplt/pkg/registry" "git.dcentral.systems/toolz/goplt/pkg/registry"
"git.dcentral.systems/toolz/goplt/pkg/services" "git.dcentral.systems/toolz/goplt/pkg/services"
"google.golang.org/grpc" "google.golang.org/grpc"
@@ -12,64 +13,124 @@ import (
) )
// AuthClient implements AuthServiceClient using gRPC. // AuthClient implements AuthServiceClient using gRPC.
// This is a stub implementation - will be fully implemented when proto files are generated in Phase 4.
type AuthClient struct { type AuthClient struct {
registry registry.ServiceRegistry registry registry.ServiceRegistry
// conn will be set when proto files are available conn *grpc.ClientConn
// conn *grpc.ClientConn client authv1.AuthServiceClient
} }
// NewAuthClient creates a new gRPC client for the Auth Service. // NewAuthClient creates a new gRPC client for the Auth Service.
func NewAuthClient(reg registry.ServiceRegistry) (services.AuthServiceClient, error) { func NewAuthClient(reg registry.ServiceRegistry) (services.AuthServiceClient, error) {
return &AuthClient{ client := &AuthClient{
registry: reg, registry: reg,
}, nil }
return client, nil
}
// connect connects to the Auth Service.
func (c *AuthClient) connect(ctx context.Context) error {
if c.conn != nil {
return nil
}
instances, err := c.registry.Discover(ctx, "auth-service")
if err != nil {
return fmt.Errorf("failed to discover auth service: %w", err)
}
if len(instances) == 0 {
return fmt.Errorf("no instances found for auth-service")
}
instance := instances[0]
address := fmt.Sprintf("%s:%d", instance.Address, instance.Port)
conn, err := grpc.NewClient(address, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
return fmt.Errorf("failed to connect to auth-service at %s: %w", address, err)
}
c.conn = conn
c.client = authv1.NewAuthServiceClient(conn)
return nil
} }
// Login authenticates a user and returns access and refresh tokens. // Login authenticates a user and returns access and refresh tokens.
func (c *AuthClient) Login(ctx context.Context, email, password string) (*services.TokenResponse, error) { func (c *AuthClient) Login(ctx context.Context, email, password string) (*services.TokenResponse, error) {
// TODO: Implement when proto files are generated if err := c.connect(ctx); err != nil {
return nil, fmt.Errorf("not implemented: proto files not yet generated") return nil, err
}
resp, err := c.client.Login(ctx, &authv1.LoginRequest{
Email: email,
Password: password,
})
if err != nil {
return nil, fmt.Errorf("login failed: %w", err)
}
return &services.TokenResponse{
AccessToken: resp.AccessToken,
RefreshToken: resp.RefreshToken,
ExpiresIn: resp.ExpiresIn,
TokenType: resp.TokenType,
}, nil
} }
// RefreshToken refreshes an access token using a refresh token. // RefreshToken refreshes an access token using a refresh token.
func (c *AuthClient) RefreshToken(ctx context.Context, refreshToken string) (*services.TokenResponse, error) { func (c *AuthClient) RefreshToken(ctx context.Context, refreshToken string) (*services.TokenResponse, error) {
// TODO: Implement when proto files are generated if err := c.connect(ctx); err != nil {
return nil, fmt.Errorf("not implemented: proto files not yet generated") return nil, err
}
resp, err := c.client.RefreshToken(ctx, &authv1.RefreshTokenRequest{
RefreshToken: refreshToken,
})
if err != nil {
return nil, fmt.Errorf("refresh token failed: %w", err)
}
return &services.TokenResponse{
AccessToken: resp.AccessToken,
RefreshToken: resp.RefreshToken,
ExpiresIn: resp.ExpiresIn,
TokenType: resp.TokenType,
}, nil
} }
// ValidateToken validates a JWT token and returns the token claims. // ValidateToken validates a JWT token and returns the token claims.
func (c *AuthClient) ValidateToken(ctx context.Context, token string) (*services.TokenClaims, error) { func (c *AuthClient) ValidateToken(ctx context.Context, token string) (*services.TokenClaims, error) {
// TODO: Implement when proto files are generated if err := c.connect(ctx); err != nil {
return nil, fmt.Errorf("not implemented: proto files not yet generated") return nil, err
}
resp, err := c.client.ValidateToken(ctx, &authv1.ValidateTokenRequest{
Token: token,
})
if err != nil {
return nil, fmt.Errorf("validate token failed: %w", err)
}
return &services.TokenClaims{
UserID: resp.UserId,
Email: resp.Email,
Roles: resp.Roles,
ExpiresAt: resp.ExpiresAt,
}, nil
} }
// Logout invalidates a refresh token. // Logout invalidates a refresh token.
func (c *AuthClient) Logout(ctx context.Context, refreshToken string) error { func (c *AuthClient) Logout(ctx context.Context, refreshToken string) error {
// TODO: Implement when proto files are generated if err := c.connect(ctx); err != nil {
return fmt.Errorf("not implemented: proto files not yet generated") return err
} }
// connectToService discovers and connects to a service instance. _, err := c.client.Logout(ctx, &authv1.LogoutRequest{
func connectToService(ctx context.Context, reg registry.ServiceRegistry, serviceName string) (*grpc.ClientConn, error) { RefreshToken: refreshToken,
instances, err := reg.Discover(ctx, serviceName) })
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to discover service %s: %w", serviceName, err) return fmt.Errorf("logout failed: %w", err)
} }
if len(instances) == 0 { return nil
return nil, fmt.Errorf("no instances found for service %s", serviceName)
}
// Use the first healthy instance (load balancing can be added later)
instance := instances[0]
address := fmt.Sprintf("%s:%d", instance.Address, instance.Port)
// Create gRPC connection
conn, err := grpc.NewClient(address, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
return nil, fmt.Errorf("failed to connect to %s at %s: %w", serviceName, address, err)
}
return conn, nil
} }

View File

@@ -5,39 +5,142 @@ import (
"context" "context"
"fmt" "fmt"
authzv1 "git.dcentral.systems/toolz/goplt/api/proto/generated/authz/v1"
"git.dcentral.systems/toolz/goplt/pkg/registry" "git.dcentral.systems/toolz/goplt/pkg/registry"
"git.dcentral.systems/toolz/goplt/pkg/services" "git.dcentral.systems/toolz/goplt/pkg/services"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
) )
// AuthzClient implements AuthzServiceClient using gRPC. // AuthzClient implements AuthzServiceClient using gRPC.
// This is a stub implementation - will be fully implemented when proto files are generated in Phase 4.
type AuthzClient struct { type AuthzClient struct {
registry registry.ServiceRegistry registry registry.ServiceRegistry
conn *grpc.ClientConn
client authzv1.AuthzServiceClient
} }
// NewAuthzClient creates a new gRPC client for the Authz Service. // NewAuthzClient creates a new gRPC client for the Authz Service.
func NewAuthzClient(reg registry.ServiceRegistry) (services.AuthzServiceClient, error) { func NewAuthzClient(reg registry.ServiceRegistry) (services.AuthzServiceClient, error) {
return &AuthzClient{ client := &AuthzClient{
registry: reg, registry: reg,
}, nil }
return client, nil
}
// connect connects to the Authz Service.
func (c *AuthzClient) connect(ctx context.Context) error {
if c.conn != nil {
return nil
}
instances, err := c.registry.Discover(ctx, "authz-service")
if err != nil {
return fmt.Errorf("failed to discover authz service: %w", err)
}
if len(instances) == 0 {
return fmt.Errorf("no instances found for authz-service")
}
instance := instances[0]
address := fmt.Sprintf("%s:%d", instance.Address, instance.Port)
conn, err := grpc.NewClient(address, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
return fmt.Errorf("failed to connect to authz-service at %s: %w", address, err)
}
c.conn = conn
c.client = authzv1.NewAuthzServiceClient(conn)
return nil
} }
// Authorize checks if a user has a specific permission and returns an error if not. // Authorize checks if a user has a specific permission and returns an error if not.
func (c *AuthzClient) Authorize(ctx context.Context, userID, permission string) error { func (c *AuthzClient) Authorize(ctx context.Context, userID, permission string) error {
return fmt.Errorf("not implemented: proto files not yet generated") if err := c.connect(ctx); err != nil {
return err
}
resp, err := c.client.Authorize(ctx, &authzv1.AuthorizeRequest{
UserId: userID,
Permission: permission,
})
if err != nil {
return fmt.Errorf("authorize failed: %w", err)
}
if !resp.Authorized {
return fmt.Errorf("unauthorized: %s", resp.Message)
}
return nil
} }
// HasPermission checks if a user has a specific permission. // HasPermission checks if a user has a specific permission.
func (c *AuthzClient) HasPermission(ctx context.Context, userID, permission string) (bool, error) { func (c *AuthzClient) HasPermission(ctx context.Context, userID, permission string) (bool, error) {
return false, fmt.Errorf("not implemented: proto files not yet generated") if err := c.connect(ctx); err != nil {
return false, err
}
resp, err := c.client.HasPermission(ctx, &authzv1.HasPermissionRequest{
UserId: userID,
Permission: permission,
})
if err != nil {
return false, fmt.Errorf("has permission check failed: %w", err)
}
return resp.HasPermission, nil
} }
// GetUserPermissions returns all permissions for a user. // GetUserPermissions returns all permissions for a user.
func (c *AuthzClient) GetUserPermissions(ctx context.Context, userID string) ([]services.Permission, error) { func (c *AuthzClient) GetUserPermissions(ctx context.Context, userID string) ([]services.Permission, error) {
return nil, fmt.Errorf("not implemented: proto files not yet generated") if err := c.connect(ctx); err != nil {
return nil, err
}
resp, err := c.client.GetUserPermissions(ctx, &authzv1.GetUserPermissionsRequest{
UserId: userID,
})
if err != nil {
return nil, fmt.Errorf("get user permissions failed: %w", err)
}
permissions := make([]services.Permission, 0, len(resp.Permissions))
for _, p := range resp.Permissions {
permissions = append(permissions, services.Permission{
ID: p.Id,
Code: p.Code,
Name: p.Name,
Description: p.Description,
})
}
return permissions, nil
} }
// GetUserRoles returns all roles for a user. // GetUserRoles returns all roles for a user.
func (c *AuthzClient) GetUserRoles(ctx context.Context, userID string) ([]services.Role, error) { func (c *AuthzClient) GetUserRoles(ctx context.Context, userID string) ([]services.Role, error) {
return nil, fmt.Errorf("not implemented: proto files not yet generated") if err := c.connect(ctx); err != nil {
return nil, err
}
resp, err := c.client.GetUserRoles(ctx, &authzv1.GetUserRolesRequest{
UserId: userID,
})
if err != nil {
return nil, fmt.Errorf("get user roles failed: %w", err)
}
roles := make([]services.Role, 0, len(resp.Roles))
for _, r := range resp.Roles {
roles = append(roles, services.Role{
ID: r.Id,
Name: r.Name,
Description: r.Description,
Permissions: r.Permissions,
})
}
return roles, nil
} }

View File

@@ -5,59 +5,227 @@ import (
"context" "context"
"fmt" "fmt"
identityv1 "git.dcentral.systems/toolz/goplt/api/proto/generated/identity/v1"
"git.dcentral.systems/toolz/goplt/pkg/registry" "git.dcentral.systems/toolz/goplt/pkg/registry"
"git.dcentral.systems/toolz/goplt/pkg/services" "git.dcentral.systems/toolz/goplt/pkg/services"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
) )
// IdentityClient implements IdentityServiceClient using gRPC. // IdentityClient implements IdentityServiceClient using gRPC.
// This is a stub implementation - will be fully implemented when proto files are generated in Phase 4.
type IdentityClient struct { type IdentityClient struct {
registry registry.ServiceRegistry registry registry.ServiceRegistry
conn *grpc.ClientConn
client identityv1.IdentityServiceClient
} }
// NewIdentityClient creates a new gRPC client for the Identity Service. // NewIdentityClient creates a new gRPC client for the Identity Service.
func NewIdentityClient(reg registry.ServiceRegistry) (services.IdentityServiceClient, error) { func NewIdentityClient(reg registry.ServiceRegistry) (services.IdentityServiceClient, error) {
return &IdentityClient{ client := &IdentityClient{
registry: reg, registry: reg,
}, nil }
return client, nil
}
// connect connects to the Identity Service.
func (c *IdentityClient) connect(ctx context.Context) error {
if c.conn != nil {
return nil
}
instances, err := c.registry.Discover(ctx, "identity-service")
if err != nil {
return fmt.Errorf("failed to discover identity service: %w", err)
}
if len(instances) == 0 {
return fmt.Errorf("no instances found for identity-service")
}
instance := instances[0]
address := fmt.Sprintf("%s:%d", instance.Address, instance.Port)
conn, err := grpc.NewClient(address, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
return fmt.Errorf("failed to connect to identity-service at %s: %w", address, err)
}
c.conn = conn
c.client = identityv1.NewIdentityServiceClient(conn)
return nil
} }
// GetUser retrieves a user by ID. // GetUser retrieves a user by ID.
func (c *IdentityClient) GetUser(ctx context.Context, id string) (*services.User, error) { func (c *IdentityClient) GetUser(ctx context.Context, id string) (*services.User, error) {
return nil, fmt.Errorf("not implemented: proto files not yet generated") if err := c.connect(ctx); err != nil {
return nil, err
}
resp, err := c.client.GetUser(ctx, &identityv1.GetUserRequest{Id: id})
if err != nil {
return nil, fmt.Errorf("get user failed: %w", err)
}
return protoUserToServiceUser(resp.User), nil
} }
// GetUserByEmail retrieves a user by email address. // GetUserByEmail retrieves a user by email address.
func (c *IdentityClient) GetUserByEmail(ctx context.Context, email string) (*services.User, error) { func (c *IdentityClient) GetUserByEmail(ctx context.Context, email string) (*services.User, error) {
return nil, fmt.Errorf("not implemented: proto files not yet generated") if err := c.connect(ctx); err != nil {
return nil, err
}
resp, err := c.client.GetUserByEmail(ctx, &identityv1.GetUserByEmailRequest{Email: email})
if err != nil {
return nil, fmt.Errorf("get user by email failed: %w", err)
}
return protoUserToServiceUser(resp.User), nil
} }
// CreateUser creates a new user. // CreateUser creates a new user.
func (c *IdentityClient) CreateUser(ctx context.Context, user *services.CreateUserRequest) (*services.User, error) { func (c *IdentityClient) CreateUser(ctx context.Context, user *services.CreateUserRequest) (*services.User, error) {
return nil, fmt.Errorf("not implemented: proto files not yet generated") if err := c.connect(ctx); err != nil {
return nil, err
}
resp, err := c.client.CreateUser(ctx, &identityv1.CreateUserRequest{
Email: user.Email,
Username: user.Username,
Password: user.Password,
FirstName: user.FirstName,
LastName: user.LastName,
})
if err != nil {
return nil, fmt.Errorf("create user failed: %w", err)
}
return protoUserToServiceUser(resp.User), nil
} }
// UpdateUser updates an existing user. // UpdateUser updates an existing user.
func (c *IdentityClient) UpdateUser(ctx context.Context, id string, user *services.UpdateUserRequest) (*services.User, error) { func (c *IdentityClient) UpdateUser(ctx context.Context, id string, user *services.UpdateUserRequest) (*services.User, error) {
return nil, fmt.Errorf("not implemented: proto files not yet generated") if err := c.connect(ctx); err != nil {
return nil, err
}
var email, username, firstName, lastName *string
if user.Email != nil && *user.Email != "" {
email = user.Email
}
if user.Username != nil && *user.Username != "" {
username = user.Username
}
if user.FirstName != nil && *user.FirstName != "" {
firstName = user.FirstName
}
if user.LastName != nil && *user.LastName != "" {
lastName = user.LastName
}
resp, err := c.client.UpdateUser(ctx, &identityv1.UpdateUserRequest{
Id: id,
Email: email,
Username: username,
FirstName: firstName,
LastName: lastName,
})
if err != nil {
return nil, fmt.Errorf("update user failed: %w", err)
}
return protoUserToServiceUser(resp.User), nil
} }
// DeleteUser deletes a user. // DeleteUser deletes a user.
func (c *IdentityClient) DeleteUser(ctx context.Context, id string) error { func (c *IdentityClient) DeleteUser(ctx context.Context, id string) error {
return fmt.Errorf("not implemented: proto files not yet generated") if err := c.connect(ctx); err != nil {
return err
}
_, err := c.client.DeleteUser(ctx, &identityv1.DeleteUserRequest{Id: id})
if err != nil {
return fmt.Errorf("delete user failed: %w", err)
}
return nil
} }
// VerifyEmail verifies a user's email address using a verification token. // VerifyEmail verifies a user's email address using a verification token.
func (c *IdentityClient) VerifyEmail(ctx context.Context, token string) error { func (c *IdentityClient) VerifyEmail(ctx context.Context, token string) error {
return fmt.Errorf("not implemented: proto files not yet generated") if err := c.connect(ctx); err != nil {
return err
}
_, err := c.client.VerifyEmail(ctx, &identityv1.VerifyEmailRequest{Token: token})
if err != nil {
return fmt.Errorf("verify email failed: %w", err)
}
return nil
} }
// RequestPasswordReset requests a password reset token. // RequestPasswordReset requests a password reset token.
func (c *IdentityClient) RequestPasswordReset(ctx context.Context, email string) error { func (c *IdentityClient) RequestPasswordReset(ctx context.Context, email string) error {
return fmt.Errorf("not implemented: proto files not yet generated") if err := c.connect(ctx); err != nil {
return err
}
_, err := c.client.RequestPasswordReset(ctx, &identityv1.RequestPasswordResetRequest{Email: email})
if err != nil {
return fmt.Errorf("request password reset failed: %w", err)
}
return nil
} }
// ResetPassword resets a user's password using a reset token. // ResetPassword resets a user's password using a reset token.
func (c *IdentityClient) ResetPassword(ctx context.Context, token, newPassword string) error { func (c *IdentityClient) ResetPassword(ctx context.Context, token, newPassword string) error {
return fmt.Errorf("not implemented: proto files not yet generated") if err := c.connect(ctx); err != nil {
return err
}
_, err := c.client.ResetPassword(ctx, &identityv1.ResetPasswordRequest{
Token: token,
NewPassword: newPassword,
})
if err != nil {
return fmt.Errorf("reset password failed: %w", err)
}
return nil
}
// VerifyPassword verifies a user's password and returns the user if valid.
func (c *IdentityClient) VerifyPassword(ctx context.Context, email, password string) (*services.User, error) {
if err := c.connect(ctx); err != nil {
return nil, err
}
resp, err := c.client.VerifyPassword(ctx, &identityv1.VerifyPasswordRequest{
Email: email,
Password: password,
})
if err != nil {
return nil, fmt.Errorf("verify password failed: %w", err)
}
return protoUserToServiceUser(resp.User), nil
}
// protoUserToServiceUser converts a proto User to a service User.
func protoUserToServiceUser(u *identityv1.User) *services.User {
if u == nil {
return nil
}
return &services.User{
ID: u.Id,
Email: u.Email,
Username: u.Username,
FirstName: u.FirstName,
LastName: u.LastName,
EmailVerified: u.EmailVerified,
CreatedAt: u.CreatedAt,
UpdatedAt: u.UpdatedAt,
}
} }

View File

@@ -3,6 +3,7 @@ package config
import ( import (
"fmt" "fmt"
"strings"
"time" "time"
"git.dcentral.systems/toolz/goplt/pkg/config" "git.dcentral.systems/toolz/goplt/pkg/config"
@@ -95,9 +96,19 @@ func LoadConfig(env string) (config.ConfigProvider, error) {
// Enable environment variable support // Enable environment variable support
v.AutomaticEnv() v.AutomaticEnv()
// Environment variables can be set in UPPER_SNAKE_CASE format // Set env key replacer to convert UPPER_SNAKE_CASE to nested keys
// and will automatically map to nested keys (e.g., SERVER_PORT -> server.port) // e.g., DATABASE_DSN -> database.dsn, SERVER_PORT -> server.port
// Viper handles this automatically with AutomaticEnv() v.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
// Bind specific environment variables to config keys
if err := v.BindEnv("database.dsn", "DATABASE_DSN"); err != nil {
return nil, fmt.Errorf("failed to bind DATABASE_DSN: %w", err)
}
if err := v.BindEnv("registry.consul.address", "REGISTRY_CONSUL_ADDRESS"); err != nil {
return nil, fmt.Errorf("failed to bind REGISTRY_CONSUL_ADDRESS: %w", err)
}
if err := v.BindEnv("registry.type", "REGISTRY_TYPE"); err != nil {
return nil, fmt.Errorf("failed to bind REGISTRY_TYPE: %w", err)
}
return NewViperConfig(v), nil return NewViperConfig(v), nil
} }

View File

@@ -218,8 +218,14 @@ func ProvideServiceRegistry() fx.Option {
healthCheckDeregisterAfter = 30 * time.Second healthCheckDeregisterAfter = 30 * time.Second
} }
healthCheckHTTP := cfg.GetString("registry.consul.health_check.http") healthCheckHTTP := cfg.GetString("registry.consul.health_check.http")
if healthCheckHTTP == "" { healthCheckGRPC := cfg.GetString("registry.consul.health_check.grpc")
healthCheckHTTP = "/healthz" useGRPC := cfg.GetBool("registry.consul.health_check.use_grpc")
// Default to gRPC if not explicitly set (services are gRPC by default)
if !cfg.IsSet("registry.consul.health_check.use_grpc") {
useGRPC = true
}
if healthCheckGRPC == "" {
healthCheckGRPC = "grpc.health.v1.Health"
} }
consulCfg.HealthCheck = consul.HealthCheckConfig{ consulCfg.HealthCheck = consul.HealthCheckConfig{
@@ -227,6 +233,8 @@ func ProvideServiceRegistry() fx.Option {
Timeout: healthCheckTimeout, Timeout: healthCheckTimeout,
DeregisterAfter: healthCheckDeregisterAfter, DeregisterAfter: healthCheckDeregisterAfter,
HTTP: healthCheckHTTP, HTTP: healthCheckHTTP,
GRPC: healthCheckGRPC,
UseGRPC: useGRPC,
} }
return consul.NewRegistry(consulCfg) return consul.NewRegistry(consulCfg)

View File

@@ -1,153 +0,0 @@
// Code generated by ent, DO NOT EDIT.
package ent
import (
"encoding/json"
"fmt"
"strings"
"time"
"entgo.io/ent"
"entgo.io/ent/dialect/sql"
"git.dcentral.systems/toolz/goplt/internal/ent/auditlog"
)
// AuditLog is the model entity for the AuditLog schema.
type AuditLog struct {
config `json:"-"`
// ID of the ent.
ID string `json:"id,omitempty"`
// ID of the user/actor performing the action
ActorID string `json:"actor_id,omitempty"`
// Action performed (e.g., create, update, delete)
Action string `json:"action,omitempty"`
// ID of the target resource
TargetID string `json:"target_id,omitempty"`
// Additional metadata as JSON
Metadata map[string]interface{} `json:"metadata,omitempty"`
// Timestamp holds the value of the "timestamp" field.
Timestamp time.Time `json:"timestamp,omitempty"`
selectValues sql.SelectValues
}
// scanValues returns the types for scanning values from sql.Rows.
func (*AuditLog) scanValues(columns []string) ([]any, error) {
values := make([]any, len(columns))
for i := range columns {
switch columns[i] {
case auditlog.FieldMetadata:
values[i] = new([]byte)
case auditlog.FieldID, auditlog.FieldActorID, auditlog.FieldAction, auditlog.FieldTargetID:
values[i] = new(sql.NullString)
case auditlog.FieldTimestamp:
values[i] = new(sql.NullTime)
default:
values[i] = new(sql.UnknownType)
}
}
return values, nil
}
// assignValues assigns the values that were returned from sql.Rows (after scanning)
// to the AuditLog fields.
func (_m *AuditLog) assignValues(columns []string, values []any) error {
if m, n := len(values), len(columns); m < n {
return fmt.Errorf("mismatch number of scan values: %d != %d", m, n)
}
for i := range columns {
switch columns[i] {
case auditlog.FieldID:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field id", values[i])
} else if value.Valid {
_m.ID = value.String
}
case auditlog.FieldActorID:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field actor_id", values[i])
} else if value.Valid {
_m.ActorID = value.String
}
case auditlog.FieldAction:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field action", values[i])
} else if value.Valid {
_m.Action = value.String
}
case auditlog.FieldTargetID:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field target_id", values[i])
} else if value.Valid {
_m.TargetID = value.String
}
case auditlog.FieldMetadata:
if value, ok := values[i].(*[]byte); !ok {
return fmt.Errorf("unexpected type %T for field metadata", values[i])
} else if value != nil && len(*value) > 0 {
if err := json.Unmarshal(*value, &_m.Metadata); err != nil {
return fmt.Errorf("unmarshal field metadata: %w", err)
}
}
case auditlog.FieldTimestamp:
if value, ok := values[i].(*sql.NullTime); !ok {
return fmt.Errorf("unexpected type %T for field timestamp", values[i])
} else if value.Valid {
_m.Timestamp = value.Time
}
default:
_m.selectValues.Set(columns[i], values[i])
}
}
return nil
}
// Value returns the ent.Value that was dynamically selected and assigned to the AuditLog.
// This includes values selected through modifiers, order, etc.
func (_m *AuditLog) Value(name string) (ent.Value, error) {
return _m.selectValues.Get(name)
}
// Update returns a builder for updating this AuditLog.
// Note that you need to call AuditLog.Unwrap() before calling this method if this AuditLog
// was returned from a transaction, and the transaction was committed or rolled back.
func (_m *AuditLog) Update() *AuditLogUpdateOne {
return NewAuditLogClient(_m.config).UpdateOne(_m)
}
// Unwrap unwraps the AuditLog entity that was returned from a transaction after it was closed,
// so that all future queries will be executed through the driver which created the transaction.
func (_m *AuditLog) Unwrap() *AuditLog {
_tx, ok := _m.config.driver.(*txDriver)
if !ok {
panic("ent: AuditLog is not a transactional entity")
}
_m.config.driver = _tx.drv
return _m
}
// String implements the fmt.Stringer.
func (_m *AuditLog) String() string {
var builder strings.Builder
builder.WriteString("AuditLog(")
builder.WriteString(fmt.Sprintf("id=%v, ", _m.ID))
builder.WriteString("actor_id=")
builder.WriteString(_m.ActorID)
builder.WriteString(", ")
builder.WriteString("action=")
builder.WriteString(_m.Action)
builder.WriteString(", ")
builder.WriteString("target_id=")
builder.WriteString(_m.TargetID)
builder.WriteString(", ")
builder.WriteString("metadata=")
builder.WriteString(fmt.Sprintf("%v", _m.Metadata))
builder.WriteString(", ")
builder.WriteString("timestamp=")
builder.WriteString(_m.Timestamp.Format(time.ANSIC))
builder.WriteByte(')')
return builder.String()
}
// AuditLogs is a parsable slice of AuditLog.
type AuditLogs []*AuditLog

View File

@@ -1,85 +0,0 @@
// Code generated by ent, DO NOT EDIT.
package auditlog
import (
"time"
"entgo.io/ent/dialect/sql"
)
const (
// Label holds the string label denoting the auditlog type in the database.
Label = "audit_log"
// FieldID holds the string denoting the id field in the database.
FieldID = "id"
// FieldActorID holds the string denoting the actor_id field in the database.
FieldActorID = "actor_id"
// FieldAction holds the string denoting the action field in the database.
FieldAction = "action"
// FieldTargetID holds the string denoting the target_id field in the database.
FieldTargetID = "target_id"
// FieldMetadata holds the string denoting the metadata field in the database.
FieldMetadata = "metadata"
// FieldTimestamp holds the string denoting the timestamp field in the database.
FieldTimestamp = "timestamp"
// Table holds the table name of the auditlog in the database.
Table = "audit_logs"
)
// Columns holds all SQL columns for auditlog fields.
var Columns = []string{
FieldID,
FieldActorID,
FieldAction,
FieldTargetID,
FieldMetadata,
FieldTimestamp,
}
// ValidColumn reports if the column name is valid (part of the table columns).
func ValidColumn(column string) bool {
for i := range Columns {
if column == Columns[i] {
return true
}
}
return false
}
var (
// ActorIDValidator is a validator for the "actor_id" field. It is called by the builders before save.
ActorIDValidator func(string) error
// ActionValidator is a validator for the "action" field. It is called by the builders before save.
ActionValidator func(string) error
// DefaultTimestamp holds the default value on creation for the "timestamp" field.
DefaultTimestamp func() time.Time
)
// OrderOption defines the ordering options for the AuditLog queries.
type OrderOption func(*sql.Selector)
// ByID orders the results by the id field.
func ByID(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldID, opts...).ToFunc()
}
// ByActorID orders the results by the actor_id field.
func ByActorID(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldActorID, opts...).ToFunc()
}
// ByAction orders the results by the action field.
func ByAction(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldAction, opts...).ToFunc()
}
// ByTargetID orders the results by the target_id field.
func ByTargetID(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldTargetID, opts...).ToFunc()
}
// ByTimestamp orders the results by the timestamp field.
func ByTimestamp(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldTimestamp, opts...).ToFunc()
}

View File

@@ -1,355 +0,0 @@
// Code generated by ent, DO NOT EDIT.
package auditlog
import (
"time"
"entgo.io/ent/dialect/sql"
"git.dcentral.systems/toolz/goplt/internal/ent/predicate"
)
// ID filters vertices based on their ID field.
func ID(id string) predicate.AuditLog {
return predicate.AuditLog(sql.FieldEQ(FieldID, id))
}
// IDEQ applies the EQ predicate on the ID field.
func IDEQ(id string) predicate.AuditLog {
return predicate.AuditLog(sql.FieldEQ(FieldID, id))
}
// IDNEQ applies the NEQ predicate on the ID field.
func IDNEQ(id string) predicate.AuditLog {
return predicate.AuditLog(sql.FieldNEQ(FieldID, id))
}
// IDIn applies the In predicate on the ID field.
func IDIn(ids ...string) predicate.AuditLog {
return predicate.AuditLog(sql.FieldIn(FieldID, ids...))
}
// IDNotIn applies the NotIn predicate on the ID field.
func IDNotIn(ids ...string) predicate.AuditLog {
return predicate.AuditLog(sql.FieldNotIn(FieldID, ids...))
}
// IDGT applies the GT predicate on the ID field.
func IDGT(id string) predicate.AuditLog {
return predicate.AuditLog(sql.FieldGT(FieldID, id))
}
// IDGTE applies the GTE predicate on the ID field.
func IDGTE(id string) predicate.AuditLog {
return predicate.AuditLog(sql.FieldGTE(FieldID, id))
}
// IDLT applies the LT predicate on the ID field.
func IDLT(id string) predicate.AuditLog {
return predicate.AuditLog(sql.FieldLT(FieldID, id))
}
// IDLTE applies the LTE predicate on the ID field.
func IDLTE(id string) predicate.AuditLog {
return predicate.AuditLog(sql.FieldLTE(FieldID, id))
}
// IDEqualFold applies the EqualFold predicate on the ID field.
func IDEqualFold(id string) predicate.AuditLog {
return predicate.AuditLog(sql.FieldEqualFold(FieldID, id))
}
// IDContainsFold applies the ContainsFold predicate on the ID field.
func IDContainsFold(id string) predicate.AuditLog {
return predicate.AuditLog(sql.FieldContainsFold(FieldID, id))
}
// ActorID applies equality check predicate on the "actor_id" field. It's identical to ActorIDEQ.
func ActorID(v string) predicate.AuditLog {
return predicate.AuditLog(sql.FieldEQ(FieldActorID, v))
}
// Action applies equality check predicate on the "action" field. It's identical to ActionEQ.
func Action(v string) predicate.AuditLog {
return predicate.AuditLog(sql.FieldEQ(FieldAction, v))
}
// TargetID applies equality check predicate on the "target_id" field. It's identical to TargetIDEQ.
func TargetID(v string) predicate.AuditLog {
return predicate.AuditLog(sql.FieldEQ(FieldTargetID, v))
}
// Timestamp applies equality check predicate on the "timestamp" field. It's identical to TimestampEQ.
func Timestamp(v time.Time) predicate.AuditLog {
return predicate.AuditLog(sql.FieldEQ(FieldTimestamp, v))
}
// ActorIDEQ applies the EQ predicate on the "actor_id" field.
func ActorIDEQ(v string) predicate.AuditLog {
return predicate.AuditLog(sql.FieldEQ(FieldActorID, v))
}
// ActorIDNEQ applies the NEQ predicate on the "actor_id" field.
func ActorIDNEQ(v string) predicate.AuditLog {
return predicate.AuditLog(sql.FieldNEQ(FieldActorID, v))
}
// ActorIDIn applies the In predicate on the "actor_id" field.
func ActorIDIn(vs ...string) predicate.AuditLog {
return predicate.AuditLog(sql.FieldIn(FieldActorID, vs...))
}
// ActorIDNotIn applies the NotIn predicate on the "actor_id" field.
func ActorIDNotIn(vs ...string) predicate.AuditLog {
return predicate.AuditLog(sql.FieldNotIn(FieldActorID, vs...))
}
// ActorIDGT applies the GT predicate on the "actor_id" field.
func ActorIDGT(v string) predicate.AuditLog {
return predicate.AuditLog(sql.FieldGT(FieldActorID, v))
}
// ActorIDGTE applies the GTE predicate on the "actor_id" field.
func ActorIDGTE(v string) predicate.AuditLog {
return predicate.AuditLog(sql.FieldGTE(FieldActorID, v))
}
// ActorIDLT applies the LT predicate on the "actor_id" field.
func ActorIDLT(v string) predicate.AuditLog {
return predicate.AuditLog(sql.FieldLT(FieldActorID, v))
}
// ActorIDLTE applies the LTE predicate on the "actor_id" field.
func ActorIDLTE(v string) predicate.AuditLog {
return predicate.AuditLog(sql.FieldLTE(FieldActorID, v))
}
// ActorIDContains applies the Contains predicate on the "actor_id" field.
func ActorIDContains(v string) predicate.AuditLog {
return predicate.AuditLog(sql.FieldContains(FieldActorID, v))
}
// ActorIDHasPrefix applies the HasPrefix predicate on the "actor_id" field.
func ActorIDHasPrefix(v string) predicate.AuditLog {
return predicate.AuditLog(sql.FieldHasPrefix(FieldActorID, v))
}
// ActorIDHasSuffix applies the HasSuffix predicate on the "actor_id" field.
func ActorIDHasSuffix(v string) predicate.AuditLog {
return predicate.AuditLog(sql.FieldHasSuffix(FieldActorID, v))
}
// ActorIDEqualFold applies the EqualFold predicate on the "actor_id" field.
func ActorIDEqualFold(v string) predicate.AuditLog {
return predicate.AuditLog(sql.FieldEqualFold(FieldActorID, v))
}
// ActorIDContainsFold applies the ContainsFold predicate on the "actor_id" field.
func ActorIDContainsFold(v string) predicate.AuditLog {
return predicate.AuditLog(sql.FieldContainsFold(FieldActorID, v))
}
// ActionEQ applies the EQ predicate on the "action" field.
func ActionEQ(v string) predicate.AuditLog {
return predicate.AuditLog(sql.FieldEQ(FieldAction, v))
}
// ActionNEQ applies the NEQ predicate on the "action" field.
func ActionNEQ(v string) predicate.AuditLog {
return predicate.AuditLog(sql.FieldNEQ(FieldAction, v))
}
// ActionIn applies the In predicate on the "action" field.
func ActionIn(vs ...string) predicate.AuditLog {
return predicate.AuditLog(sql.FieldIn(FieldAction, vs...))
}
// ActionNotIn applies the NotIn predicate on the "action" field.
func ActionNotIn(vs ...string) predicate.AuditLog {
return predicate.AuditLog(sql.FieldNotIn(FieldAction, vs...))
}
// ActionGT applies the GT predicate on the "action" field.
func ActionGT(v string) predicate.AuditLog {
return predicate.AuditLog(sql.FieldGT(FieldAction, v))
}
// ActionGTE applies the GTE predicate on the "action" field.
func ActionGTE(v string) predicate.AuditLog {
return predicate.AuditLog(sql.FieldGTE(FieldAction, v))
}
// ActionLT applies the LT predicate on the "action" field.
func ActionLT(v string) predicate.AuditLog {
return predicate.AuditLog(sql.FieldLT(FieldAction, v))
}
// ActionLTE applies the LTE predicate on the "action" field.
func ActionLTE(v string) predicate.AuditLog {
return predicate.AuditLog(sql.FieldLTE(FieldAction, v))
}
// ActionContains applies the Contains predicate on the "action" field.
func ActionContains(v string) predicate.AuditLog {
return predicate.AuditLog(sql.FieldContains(FieldAction, v))
}
// ActionHasPrefix applies the HasPrefix predicate on the "action" field.
func ActionHasPrefix(v string) predicate.AuditLog {
return predicate.AuditLog(sql.FieldHasPrefix(FieldAction, v))
}
// ActionHasSuffix applies the HasSuffix predicate on the "action" field.
func ActionHasSuffix(v string) predicate.AuditLog {
return predicate.AuditLog(sql.FieldHasSuffix(FieldAction, v))
}
// ActionEqualFold applies the EqualFold predicate on the "action" field.
func ActionEqualFold(v string) predicate.AuditLog {
return predicate.AuditLog(sql.FieldEqualFold(FieldAction, v))
}
// ActionContainsFold applies the ContainsFold predicate on the "action" field.
func ActionContainsFold(v string) predicate.AuditLog {
return predicate.AuditLog(sql.FieldContainsFold(FieldAction, v))
}
// TargetIDEQ applies the EQ predicate on the "target_id" field.
func TargetIDEQ(v string) predicate.AuditLog {
return predicate.AuditLog(sql.FieldEQ(FieldTargetID, v))
}
// TargetIDNEQ applies the NEQ predicate on the "target_id" field.
func TargetIDNEQ(v string) predicate.AuditLog {
return predicate.AuditLog(sql.FieldNEQ(FieldTargetID, v))
}
// TargetIDIn applies the In predicate on the "target_id" field.
func TargetIDIn(vs ...string) predicate.AuditLog {
return predicate.AuditLog(sql.FieldIn(FieldTargetID, vs...))
}
// TargetIDNotIn applies the NotIn predicate on the "target_id" field.
func TargetIDNotIn(vs ...string) predicate.AuditLog {
return predicate.AuditLog(sql.FieldNotIn(FieldTargetID, vs...))
}
// TargetIDGT applies the GT predicate on the "target_id" field.
func TargetIDGT(v string) predicate.AuditLog {
return predicate.AuditLog(sql.FieldGT(FieldTargetID, v))
}
// TargetIDGTE applies the GTE predicate on the "target_id" field.
func TargetIDGTE(v string) predicate.AuditLog {
return predicate.AuditLog(sql.FieldGTE(FieldTargetID, v))
}
// TargetIDLT applies the LT predicate on the "target_id" field.
func TargetIDLT(v string) predicate.AuditLog {
return predicate.AuditLog(sql.FieldLT(FieldTargetID, v))
}
// TargetIDLTE applies the LTE predicate on the "target_id" field.
func TargetIDLTE(v string) predicate.AuditLog {
return predicate.AuditLog(sql.FieldLTE(FieldTargetID, v))
}
// TargetIDContains applies the Contains predicate on the "target_id" field.
func TargetIDContains(v string) predicate.AuditLog {
return predicate.AuditLog(sql.FieldContains(FieldTargetID, v))
}
// TargetIDHasPrefix applies the HasPrefix predicate on the "target_id" field.
func TargetIDHasPrefix(v string) predicate.AuditLog {
return predicate.AuditLog(sql.FieldHasPrefix(FieldTargetID, v))
}
// TargetIDHasSuffix applies the HasSuffix predicate on the "target_id" field.
func TargetIDHasSuffix(v string) predicate.AuditLog {
return predicate.AuditLog(sql.FieldHasSuffix(FieldTargetID, v))
}
// TargetIDIsNil applies the IsNil predicate on the "target_id" field.
func TargetIDIsNil() predicate.AuditLog {
return predicate.AuditLog(sql.FieldIsNull(FieldTargetID))
}
// TargetIDNotNil applies the NotNil predicate on the "target_id" field.
func TargetIDNotNil() predicate.AuditLog {
return predicate.AuditLog(sql.FieldNotNull(FieldTargetID))
}
// TargetIDEqualFold applies the EqualFold predicate on the "target_id" field.
func TargetIDEqualFold(v string) predicate.AuditLog {
return predicate.AuditLog(sql.FieldEqualFold(FieldTargetID, v))
}
// TargetIDContainsFold applies the ContainsFold predicate on the "target_id" field.
func TargetIDContainsFold(v string) predicate.AuditLog {
return predicate.AuditLog(sql.FieldContainsFold(FieldTargetID, v))
}
// MetadataIsNil applies the IsNil predicate on the "metadata" field.
func MetadataIsNil() predicate.AuditLog {
return predicate.AuditLog(sql.FieldIsNull(FieldMetadata))
}
// MetadataNotNil applies the NotNil predicate on the "metadata" field.
func MetadataNotNil() predicate.AuditLog {
return predicate.AuditLog(sql.FieldNotNull(FieldMetadata))
}
// TimestampEQ applies the EQ predicate on the "timestamp" field.
func TimestampEQ(v time.Time) predicate.AuditLog {
return predicate.AuditLog(sql.FieldEQ(FieldTimestamp, v))
}
// TimestampNEQ applies the NEQ predicate on the "timestamp" field.
func TimestampNEQ(v time.Time) predicate.AuditLog {
return predicate.AuditLog(sql.FieldNEQ(FieldTimestamp, v))
}
// TimestampIn applies the In predicate on the "timestamp" field.
func TimestampIn(vs ...time.Time) predicate.AuditLog {
return predicate.AuditLog(sql.FieldIn(FieldTimestamp, vs...))
}
// TimestampNotIn applies the NotIn predicate on the "timestamp" field.
func TimestampNotIn(vs ...time.Time) predicate.AuditLog {
return predicate.AuditLog(sql.FieldNotIn(FieldTimestamp, vs...))
}
// TimestampGT applies the GT predicate on the "timestamp" field.
func TimestampGT(v time.Time) predicate.AuditLog {
return predicate.AuditLog(sql.FieldGT(FieldTimestamp, v))
}
// TimestampGTE applies the GTE predicate on the "timestamp" field.
func TimestampGTE(v time.Time) predicate.AuditLog {
return predicate.AuditLog(sql.FieldGTE(FieldTimestamp, v))
}
// TimestampLT applies the LT predicate on the "timestamp" field.
func TimestampLT(v time.Time) predicate.AuditLog {
return predicate.AuditLog(sql.FieldLT(FieldTimestamp, v))
}
// TimestampLTE applies the LTE predicate on the "timestamp" field.
func TimestampLTE(v time.Time) predicate.AuditLog {
return predicate.AuditLog(sql.FieldLTE(FieldTimestamp, v))
}
// And groups predicates with the AND operator between them.
func And(predicates ...predicate.AuditLog) predicate.AuditLog {
return predicate.AuditLog(sql.AndPredicates(predicates...))
}
// Or groups predicates with the OR operator between them.
func Or(predicates ...predicate.AuditLog) predicate.AuditLog {
return predicate.AuditLog(sql.OrPredicates(predicates...))
}
// Not applies the not operator on the given predicate.
func Not(p predicate.AuditLog) predicate.AuditLog {
return predicate.AuditLog(sql.NotPredicates(p))
}

View File

@@ -1,277 +0,0 @@
// Code generated by ent, DO NOT EDIT.
package ent
import (
"context"
"errors"
"fmt"
"time"
"entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field"
"git.dcentral.systems/toolz/goplt/internal/ent/auditlog"
)
// AuditLogCreate is the builder for creating a AuditLog entity.
type AuditLogCreate struct {
config
mutation *AuditLogMutation
hooks []Hook
}
// SetActorID sets the "actor_id" field.
func (_c *AuditLogCreate) SetActorID(v string) *AuditLogCreate {
_c.mutation.SetActorID(v)
return _c
}
// SetAction sets the "action" field.
func (_c *AuditLogCreate) SetAction(v string) *AuditLogCreate {
_c.mutation.SetAction(v)
return _c
}
// SetTargetID sets the "target_id" field.
func (_c *AuditLogCreate) SetTargetID(v string) *AuditLogCreate {
_c.mutation.SetTargetID(v)
return _c
}
// SetNillableTargetID sets the "target_id" field if the given value is not nil.
func (_c *AuditLogCreate) SetNillableTargetID(v *string) *AuditLogCreate {
if v != nil {
_c.SetTargetID(*v)
}
return _c
}
// SetMetadata sets the "metadata" field.
func (_c *AuditLogCreate) SetMetadata(v map[string]interface{}) *AuditLogCreate {
_c.mutation.SetMetadata(v)
return _c
}
// SetTimestamp sets the "timestamp" field.
func (_c *AuditLogCreate) SetTimestamp(v time.Time) *AuditLogCreate {
_c.mutation.SetTimestamp(v)
return _c
}
// SetNillableTimestamp sets the "timestamp" field if the given value is not nil.
func (_c *AuditLogCreate) SetNillableTimestamp(v *time.Time) *AuditLogCreate {
if v != nil {
_c.SetTimestamp(*v)
}
return _c
}
// SetID sets the "id" field.
func (_c *AuditLogCreate) SetID(v string) *AuditLogCreate {
_c.mutation.SetID(v)
return _c
}
// Mutation returns the AuditLogMutation object of the builder.
func (_c *AuditLogCreate) Mutation() *AuditLogMutation {
return _c.mutation
}
// Save creates the AuditLog in the database.
func (_c *AuditLogCreate) Save(ctx context.Context) (*AuditLog, error) {
_c.defaults()
return withHooks(ctx, _c.sqlSave, _c.mutation, _c.hooks)
}
// SaveX calls Save and panics if Save returns an error.
func (_c *AuditLogCreate) SaveX(ctx context.Context) *AuditLog {
v, err := _c.Save(ctx)
if err != nil {
panic(err)
}
return v
}
// Exec executes the query.
func (_c *AuditLogCreate) Exec(ctx context.Context) error {
_, err := _c.Save(ctx)
return err
}
// ExecX is like Exec, but panics if an error occurs.
func (_c *AuditLogCreate) ExecX(ctx context.Context) {
if err := _c.Exec(ctx); err != nil {
panic(err)
}
}
// defaults sets the default values of the builder before save.
func (_c *AuditLogCreate) defaults() {
if _, ok := _c.mutation.Timestamp(); !ok {
v := auditlog.DefaultTimestamp()
_c.mutation.SetTimestamp(v)
}
}
// check runs all checks and user-defined validators on the builder.
func (_c *AuditLogCreate) check() error {
if _, ok := _c.mutation.ActorID(); !ok {
return &ValidationError{Name: "actor_id", err: errors.New(`ent: missing required field "AuditLog.actor_id"`)}
}
if v, ok := _c.mutation.ActorID(); ok {
if err := auditlog.ActorIDValidator(v); err != nil {
return &ValidationError{Name: "actor_id", err: fmt.Errorf(`ent: validator failed for field "AuditLog.actor_id": %w`, err)}
}
}
if _, ok := _c.mutation.Action(); !ok {
return &ValidationError{Name: "action", err: errors.New(`ent: missing required field "AuditLog.action"`)}
}
if v, ok := _c.mutation.Action(); ok {
if err := auditlog.ActionValidator(v); err != nil {
return &ValidationError{Name: "action", err: fmt.Errorf(`ent: validator failed for field "AuditLog.action": %w`, err)}
}
}
if _, ok := _c.mutation.Timestamp(); !ok {
return &ValidationError{Name: "timestamp", err: errors.New(`ent: missing required field "AuditLog.timestamp"`)}
}
return nil
}
func (_c *AuditLogCreate) sqlSave(ctx context.Context) (*AuditLog, error) {
if err := _c.check(); err != nil {
return nil, err
}
_node, _spec := _c.createSpec()
if err := sqlgraph.CreateNode(ctx, _c.driver, _spec); err != nil {
if sqlgraph.IsConstraintError(err) {
err = &ConstraintError{msg: err.Error(), wrap: err}
}
return nil, err
}
if _spec.ID.Value != nil {
if id, ok := _spec.ID.Value.(string); ok {
_node.ID = id
} else {
return nil, fmt.Errorf("unexpected AuditLog.ID type: %T", _spec.ID.Value)
}
}
_c.mutation.id = &_node.ID
_c.mutation.done = true
return _node, nil
}
func (_c *AuditLogCreate) createSpec() (*AuditLog, *sqlgraph.CreateSpec) {
var (
_node = &AuditLog{config: _c.config}
_spec = sqlgraph.NewCreateSpec(auditlog.Table, sqlgraph.NewFieldSpec(auditlog.FieldID, field.TypeString))
)
if id, ok := _c.mutation.ID(); ok {
_node.ID = id
_spec.ID.Value = id
}
if value, ok := _c.mutation.ActorID(); ok {
_spec.SetField(auditlog.FieldActorID, field.TypeString, value)
_node.ActorID = value
}
if value, ok := _c.mutation.Action(); ok {
_spec.SetField(auditlog.FieldAction, field.TypeString, value)
_node.Action = value
}
if value, ok := _c.mutation.TargetID(); ok {
_spec.SetField(auditlog.FieldTargetID, field.TypeString, value)
_node.TargetID = value
}
if value, ok := _c.mutation.Metadata(); ok {
_spec.SetField(auditlog.FieldMetadata, field.TypeJSON, value)
_node.Metadata = value
}
if value, ok := _c.mutation.Timestamp(); ok {
_spec.SetField(auditlog.FieldTimestamp, field.TypeTime, value)
_node.Timestamp = value
}
return _node, _spec
}
// AuditLogCreateBulk is the builder for creating many AuditLog entities in bulk.
type AuditLogCreateBulk struct {
config
err error
builders []*AuditLogCreate
}
// Save creates the AuditLog entities in the database.
func (_c *AuditLogCreateBulk) Save(ctx context.Context) ([]*AuditLog, error) {
if _c.err != nil {
return nil, _c.err
}
specs := make([]*sqlgraph.CreateSpec, len(_c.builders))
nodes := make([]*AuditLog, len(_c.builders))
mutators := make([]Mutator, len(_c.builders))
for i := range _c.builders {
func(i int, root context.Context) {
builder := _c.builders[i]
builder.defaults()
var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) {
mutation, ok := m.(*AuditLogMutation)
if !ok {
return nil, fmt.Errorf("unexpected mutation type %T", m)
}
if err := builder.check(); err != nil {
return nil, err
}
builder.mutation = mutation
var err error
nodes[i], specs[i] = builder.createSpec()
if i < len(mutators)-1 {
_, err = mutators[i+1].Mutate(root, _c.builders[i+1].mutation)
} else {
spec := &sqlgraph.BatchCreateSpec{Nodes: specs}
// Invoke the actual operation on the latest mutation in the chain.
if err = sqlgraph.BatchCreate(ctx, _c.driver, spec); err != nil {
if sqlgraph.IsConstraintError(err) {
err = &ConstraintError{msg: err.Error(), wrap: err}
}
}
}
if err != nil {
return nil, err
}
mutation.id = &nodes[i].ID
mutation.done = true
return nodes[i], nil
})
for i := len(builder.hooks) - 1; i >= 0; i-- {
mut = builder.hooks[i](mut)
}
mutators[i] = mut
}(i, ctx)
}
if len(mutators) > 0 {
if _, err := mutators[0].Mutate(ctx, _c.builders[0].mutation); err != nil {
return nil, err
}
}
return nodes, nil
}
// SaveX is like Save, but panics if an error occurs.
func (_c *AuditLogCreateBulk) SaveX(ctx context.Context) []*AuditLog {
v, err := _c.Save(ctx)
if err != nil {
panic(err)
}
return v
}
// Exec executes the query.
func (_c *AuditLogCreateBulk) Exec(ctx context.Context) error {
_, err := _c.Save(ctx)
return err
}
// ExecX is like Exec, but panics if an error occurs.
func (_c *AuditLogCreateBulk) ExecX(ctx context.Context) {
if err := _c.Exec(ctx); err != nil {
panic(err)
}
}

View File

@@ -1,88 +0,0 @@
// Code generated by ent, DO NOT EDIT.
package ent
import (
"context"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field"
"git.dcentral.systems/toolz/goplt/internal/ent/auditlog"
"git.dcentral.systems/toolz/goplt/internal/ent/predicate"
)
// AuditLogDelete is the builder for deleting a AuditLog entity.
type AuditLogDelete struct {
config
hooks []Hook
mutation *AuditLogMutation
}
// Where appends a list predicates to the AuditLogDelete builder.
func (_d *AuditLogDelete) Where(ps ...predicate.AuditLog) *AuditLogDelete {
_d.mutation.Where(ps...)
return _d
}
// Exec executes the deletion query and returns how many vertices were deleted.
func (_d *AuditLogDelete) Exec(ctx context.Context) (int, error) {
return withHooks(ctx, _d.sqlExec, _d.mutation, _d.hooks)
}
// ExecX is like Exec, but panics if an error occurs.
func (_d *AuditLogDelete) ExecX(ctx context.Context) int {
n, err := _d.Exec(ctx)
if err != nil {
panic(err)
}
return n
}
func (_d *AuditLogDelete) sqlExec(ctx context.Context) (int, error) {
_spec := sqlgraph.NewDeleteSpec(auditlog.Table, sqlgraph.NewFieldSpec(auditlog.FieldID, field.TypeString))
if ps := _d.mutation.predicates; len(ps) > 0 {
_spec.Predicate = func(selector *sql.Selector) {
for i := range ps {
ps[i](selector)
}
}
}
affected, err := sqlgraph.DeleteNodes(ctx, _d.driver, _spec)
if err != nil && sqlgraph.IsConstraintError(err) {
err = &ConstraintError{msg: err.Error(), wrap: err}
}
_d.mutation.done = true
return affected, err
}
// AuditLogDeleteOne is the builder for deleting a single AuditLog entity.
type AuditLogDeleteOne struct {
_d *AuditLogDelete
}
// Where appends a list predicates to the AuditLogDelete builder.
func (_d *AuditLogDeleteOne) Where(ps ...predicate.AuditLog) *AuditLogDeleteOne {
_d._d.mutation.Where(ps...)
return _d
}
// Exec executes the deletion query.
func (_d *AuditLogDeleteOne) Exec(ctx context.Context) error {
n, err := _d._d.Exec(ctx)
switch {
case err != nil:
return err
case n == 0:
return &NotFoundError{auditlog.Label}
default:
return nil
}
}
// ExecX is like Exec, but panics if an error occurs.
func (_d *AuditLogDeleteOne) ExecX(ctx context.Context) {
if err := _d.Exec(ctx); err != nil {
panic(err)
}
}

View File

@@ -1,527 +0,0 @@
// Code generated by ent, DO NOT EDIT.
package ent
import (
"context"
"fmt"
"math"
"entgo.io/ent"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field"
"git.dcentral.systems/toolz/goplt/internal/ent/auditlog"
"git.dcentral.systems/toolz/goplt/internal/ent/predicate"
)
// AuditLogQuery is the builder for querying AuditLog entities.
type AuditLogQuery struct {
config
ctx *QueryContext
order []auditlog.OrderOption
inters []Interceptor
predicates []predicate.AuditLog
// intermediate query (i.e. traversal path).
sql *sql.Selector
path func(context.Context) (*sql.Selector, error)
}
// Where adds a new predicate for the AuditLogQuery builder.
func (_q *AuditLogQuery) Where(ps ...predicate.AuditLog) *AuditLogQuery {
_q.predicates = append(_q.predicates, ps...)
return _q
}
// Limit the number of records to be returned by this query.
func (_q *AuditLogQuery) Limit(limit int) *AuditLogQuery {
_q.ctx.Limit = &limit
return _q
}
// Offset to start from.
func (_q *AuditLogQuery) Offset(offset int) *AuditLogQuery {
_q.ctx.Offset = &offset
return _q
}
// Unique configures the query builder to filter duplicate records on query.
// By default, unique is set to true, and can be disabled using this method.
func (_q *AuditLogQuery) Unique(unique bool) *AuditLogQuery {
_q.ctx.Unique = &unique
return _q
}
// Order specifies how the records should be ordered.
func (_q *AuditLogQuery) Order(o ...auditlog.OrderOption) *AuditLogQuery {
_q.order = append(_q.order, o...)
return _q
}
// First returns the first AuditLog entity from the query.
// Returns a *NotFoundError when no AuditLog was found.
func (_q *AuditLogQuery) First(ctx context.Context) (*AuditLog, error) {
nodes, err := _q.Limit(1).All(setContextOp(ctx, _q.ctx, ent.OpQueryFirst))
if err != nil {
return nil, err
}
if len(nodes) == 0 {
return nil, &NotFoundError{auditlog.Label}
}
return nodes[0], nil
}
// FirstX is like First, but panics if an error occurs.
func (_q *AuditLogQuery) FirstX(ctx context.Context) *AuditLog {
node, err := _q.First(ctx)
if err != nil && !IsNotFound(err) {
panic(err)
}
return node
}
// FirstID returns the first AuditLog ID from the query.
// Returns a *NotFoundError when no AuditLog ID was found.
func (_q *AuditLogQuery) FirstID(ctx context.Context) (id string, err error) {
var ids []string
if ids, err = _q.Limit(1).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryFirstID)); err != nil {
return
}
if len(ids) == 0 {
err = &NotFoundError{auditlog.Label}
return
}
return ids[0], nil
}
// FirstIDX is like FirstID, but panics if an error occurs.
func (_q *AuditLogQuery) FirstIDX(ctx context.Context) string {
id, err := _q.FirstID(ctx)
if err != nil && !IsNotFound(err) {
panic(err)
}
return id
}
// Only returns a single AuditLog entity found by the query, ensuring it only returns one.
// Returns a *NotSingularError when more than one AuditLog entity is found.
// Returns a *NotFoundError when no AuditLog entities are found.
func (_q *AuditLogQuery) Only(ctx context.Context) (*AuditLog, error) {
nodes, err := _q.Limit(2).All(setContextOp(ctx, _q.ctx, ent.OpQueryOnly))
if err != nil {
return nil, err
}
switch len(nodes) {
case 1:
return nodes[0], nil
case 0:
return nil, &NotFoundError{auditlog.Label}
default:
return nil, &NotSingularError{auditlog.Label}
}
}
// OnlyX is like Only, but panics if an error occurs.
func (_q *AuditLogQuery) OnlyX(ctx context.Context) *AuditLog {
node, err := _q.Only(ctx)
if err != nil {
panic(err)
}
return node
}
// OnlyID is like Only, but returns the only AuditLog ID in the query.
// Returns a *NotSingularError when more than one AuditLog ID is found.
// Returns a *NotFoundError when no entities are found.
func (_q *AuditLogQuery) OnlyID(ctx context.Context) (id string, err error) {
var ids []string
if ids, err = _q.Limit(2).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryOnlyID)); err != nil {
return
}
switch len(ids) {
case 1:
id = ids[0]
case 0:
err = &NotFoundError{auditlog.Label}
default:
err = &NotSingularError{auditlog.Label}
}
return
}
// OnlyIDX is like OnlyID, but panics if an error occurs.
func (_q *AuditLogQuery) OnlyIDX(ctx context.Context) string {
id, err := _q.OnlyID(ctx)
if err != nil {
panic(err)
}
return id
}
// All executes the query and returns a list of AuditLogs.
func (_q *AuditLogQuery) All(ctx context.Context) ([]*AuditLog, error) {
ctx = setContextOp(ctx, _q.ctx, ent.OpQueryAll)
if err := _q.prepareQuery(ctx); err != nil {
return nil, err
}
qr := querierAll[[]*AuditLog, *AuditLogQuery]()
return withInterceptors[[]*AuditLog](ctx, _q, qr, _q.inters)
}
// AllX is like All, but panics if an error occurs.
func (_q *AuditLogQuery) AllX(ctx context.Context) []*AuditLog {
nodes, err := _q.All(ctx)
if err != nil {
panic(err)
}
return nodes
}
// IDs executes the query and returns a list of AuditLog IDs.
func (_q *AuditLogQuery) IDs(ctx context.Context) (ids []string, err error) {
if _q.ctx.Unique == nil && _q.path != nil {
_q.Unique(true)
}
ctx = setContextOp(ctx, _q.ctx, ent.OpQueryIDs)
if err = _q.Select(auditlog.FieldID).Scan(ctx, &ids); err != nil {
return nil, err
}
return ids, nil
}
// IDsX is like IDs, but panics if an error occurs.
func (_q *AuditLogQuery) IDsX(ctx context.Context) []string {
ids, err := _q.IDs(ctx)
if err != nil {
panic(err)
}
return ids
}
// Count returns the count of the given query.
func (_q *AuditLogQuery) Count(ctx context.Context) (int, error) {
ctx = setContextOp(ctx, _q.ctx, ent.OpQueryCount)
if err := _q.prepareQuery(ctx); err != nil {
return 0, err
}
return withInterceptors[int](ctx, _q, querierCount[*AuditLogQuery](), _q.inters)
}
// CountX is like Count, but panics if an error occurs.
func (_q *AuditLogQuery) CountX(ctx context.Context) int {
count, err := _q.Count(ctx)
if err != nil {
panic(err)
}
return count
}
// Exist returns true if the query has elements in the graph.
func (_q *AuditLogQuery) Exist(ctx context.Context) (bool, error) {
ctx = setContextOp(ctx, _q.ctx, ent.OpQueryExist)
switch _, err := _q.FirstID(ctx); {
case IsNotFound(err):
return false, nil
case err != nil:
return false, fmt.Errorf("ent: check existence: %w", err)
default:
return true, nil
}
}
// ExistX is like Exist, but panics if an error occurs.
func (_q *AuditLogQuery) ExistX(ctx context.Context) bool {
exist, err := _q.Exist(ctx)
if err != nil {
panic(err)
}
return exist
}
// Clone returns a duplicate of the AuditLogQuery builder, including all associated steps. It can be
// used to prepare common query builders and use them differently after the clone is made.
func (_q *AuditLogQuery) Clone() *AuditLogQuery {
if _q == nil {
return nil
}
return &AuditLogQuery{
config: _q.config,
ctx: _q.ctx.Clone(),
order: append([]auditlog.OrderOption{}, _q.order...),
inters: append([]Interceptor{}, _q.inters...),
predicates: append([]predicate.AuditLog{}, _q.predicates...),
// clone intermediate query.
sql: _q.sql.Clone(),
path: _q.path,
}
}
// GroupBy is used to group vertices by one or more fields/columns.
// It is often used with aggregate functions, like: count, max, mean, min, sum.
//
// Example:
//
// var v []struct {
// ActorID string `json:"actor_id,omitempty"`
// Count int `json:"count,omitempty"`
// }
//
// client.AuditLog.Query().
// GroupBy(auditlog.FieldActorID).
// Aggregate(ent.Count()).
// Scan(ctx, &v)
func (_q *AuditLogQuery) GroupBy(field string, fields ...string) *AuditLogGroupBy {
_q.ctx.Fields = append([]string{field}, fields...)
grbuild := &AuditLogGroupBy{build: _q}
grbuild.flds = &_q.ctx.Fields
grbuild.label = auditlog.Label
grbuild.scan = grbuild.Scan
return grbuild
}
// Select allows the selection one or more fields/columns for the given query,
// instead of selecting all fields in the entity.
//
// Example:
//
// var v []struct {
// ActorID string `json:"actor_id,omitempty"`
// }
//
// client.AuditLog.Query().
// Select(auditlog.FieldActorID).
// Scan(ctx, &v)
func (_q *AuditLogQuery) Select(fields ...string) *AuditLogSelect {
_q.ctx.Fields = append(_q.ctx.Fields, fields...)
sbuild := &AuditLogSelect{AuditLogQuery: _q}
sbuild.label = auditlog.Label
sbuild.flds, sbuild.scan = &_q.ctx.Fields, sbuild.Scan
return sbuild
}
// Aggregate returns a AuditLogSelect configured with the given aggregations.
func (_q *AuditLogQuery) Aggregate(fns ...AggregateFunc) *AuditLogSelect {
return _q.Select().Aggregate(fns...)
}
func (_q *AuditLogQuery) prepareQuery(ctx context.Context) error {
for _, inter := range _q.inters {
if inter == nil {
return fmt.Errorf("ent: uninitialized interceptor (forgotten import ent/runtime?)")
}
if trv, ok := inter.(Traverser); ok {
if err := trv.Traverse(ctx, _q); err != nil {
return err
}
}
}
for _, f := range _q.ctx.Fields {
if !auditlog.ValidColumn(f) {
return &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)}
}
}
if _q.path != nil {
prev, err := _q.path(ctx)
if err != nil {
return err
}
_q.sql = prev
}
return nil
}
func (_q *AuditLogQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*AuditLog, error) {
var (
nodes = []*AuditLog{}
_spec = _q.querySpec()
)
_spec.ScanValues = func(columns []string) ([]any, error) {
return (*AuditLog).scanValues(nil, columns)
}
_spec.Assign = func(columns []string, values []any) error {
node := &AuditLog{config: _q.config}
nodes = append(nodes, node)
return node.assignValues(columns, values)
}
for i := range hooks {
hooks[i](ctx, _spec)
}
if err := sqlgraph.QueryNodes(ctx, _q.driver, _spec); err != nil {
return nil, err
}
if len(nodes) == 0 {
return nodes, nil
}
return nodes, nil
}
func (_q *AuditLogQuery) sqlCount(ctx context.Context) (int, error) {
_spec := _q.querySpec()
_spec.Node.Columns = _q.ctx.Fields
if len(_q.ctx.Fields) > 0 {
_spec.Unique = _q.ctx.Unique != nil && *_q.ctx.Unique
}
return sqlgraph.CountNodes(ctx, _q.driver, _spec)
}
func (_q *AuditLogQuery) querySpec() *sqlgraph.QuerySpec {
_spec := sqlgraph.NewQuerySpec(auditlog.Table, auditlog.Columns, sqlgraph.NewFieldSpec(auditlog.FieldID, field.TypeString))
_spec.From = _q.sql
if unique := _q.ctx.Unique; unique != nil {
_spec.Unique = *unique
} else if _q.path != nil {
_spec.Unique = true
}
if fields := _q.ctx.Fields; len(fields) > 0 {
_spec.Node.Columns = make([]string, 0, len(fields))
_spec.Node.Columns = append(_spec.Node.Columns, auditlog.FieldID)
for i := range fields {
if fields[i] != auditlog.FieldID {
_spec.Node.Columns = append(_spec.Node.Columns, fields[i])
}
}
}
if ps := _q.predicates; len(ps) > 0 {
_spec.Predicate = func(selector *sql.Selector) {
for i := range ps {
ps[i](selector)
}
}
}
if limit := _q.ctx.Limit; limit != nil {
_spec.Limit = *limit
}
if offset := _q.ctx.Offset; offset != nil {
_spec.Offset = *offset
}
if ps := _q.order; len(ps) > 0 {
_spec.Order = func(selector *sql.Selector) {
for i := range ps {
ps[i](selector)
}
}
}
return _spec
}
func (_q *AuditLogQuery) sqlQuery(ctx context.Context) *sql.Selector {
builder := sql.Dialect(_q.driver.Dialect())
t1 := builder.Table(auditlog.Table)
columns := _q.ctx.Fields
if len(columns) == 0 {
columns = auditlog.Columns
}
selector := builder.Select(t1.Columns(columns...)...).From(t1)
if _q.sql != nil {
selector = _q.sql
selector.Select(selector.Columns(columns...)...)
}
if _q.ctx.Unique != nil && *_q.ctx.Unique {
selector.Distinct()
}
for _, p := range _q.predicates {
p(selector)
}
for _, p := range _q.order {
p(selector)
}
if offset := _q.ctx.Offset; offset != nil {
// limit is mandatory for offset clause. We start
// with default value, and override it below if needed.
selector.Offset(*offset).Limit(math.MaxInt32)
}
if limit := _q.ctx.Limit; limit != nil {
selector.Limit(*limit)
}
return selector
}
// AuditLogGroupBy is the group-by builder for AuditLog entities.
type AuditLogGroupBy struct {
selector
build *AuditLogQuery
}
// Aggregate adds the given aggregation functions to the group-by query.
func (_g *AuditLogGroupBy) Aggregate(fns ...AggregateFunc) *AuditLogGroupBy {
_g.fns = append(_g.fns, fns...)
return _g
}
// Scan applies the selector query and scans the result into the given value.
func (_g *AuditLogGroupBy) Scan(ctx context.Context, v any) error {
ctx = setContextOp(ctx, _g.build.ctx, ent.OpQueryGroupBy)
if err := _g.build.prepareQuery(ctx); err != nil {
return err
}
return scanWithInterceptors[*AuditLogQuery, *AuditLogGroupBy](ctx, _g.build, _g, _g.build.inters, v)
}
func (_g *AuditLogGroupBy) sqlScan(ctx context.Context, root *AuditLogQuery, v any) error {
selector := root.sqlQuery(ctx).Select()
aggregation := make([]string, 0, len(_g.fns))
for _, fn := range _g.fns {
aggregation = append(aggregation, fn(selector))
}
if len(selector.SelectedColumns()) == 0 {
columns := make([]string, 0, len(*_g.flds)+len(_g.fns))
for _, f := range *_g.flds {
columns = append(columns, selector.C(f))
}
columns = append(columns, aggregation...)
selector.Select(columns...)
}
selector.GroupBy(selector.Columns(*_g.flds...)...)
if err := selector.Err(); err != nil {
return err
}
rows := &sql.Rows{}
query, args := selector.Query()
if err := _g.build.driver.Query(ctx, query, args, rows); err != nil {
return err
}
defer rows.Close()
return sql.ScanSlice(rows, v)
}
// AuditLogSelect is the builder for selecting fields of AuditLog entities.
type AuditLogSelect struct {
*AuditLogQuery
selector
}
// Aggregate adds the given aggregation functions to the selector query.
func (_s *AuditLogSelect) Aggregate(fns ...AggregateFunc) *AuditLogSelect {
_s.fns = append(_s.fns, fns...)
return _s
}
// Scan applies the selector query and scans the result into the given value.
func (_s *AuditLogSelect) Scan(ctx context.Context, v any) error {
ctx = setContextOp(ctx, _s.ctx, ent.OpQuerySelect)
if err := _s.prepareQuery(ctx); err != nil {
return err
}
return scanWithInterceptors[*AuditLogQuery, *AuditLogSelect](ctx, _s.AuditLogQuery, _s, _s.inters, v)
}
func (_s *AuditLogSelect) sqlScan(ctx context.Context, root *AuditLogQuery, v any) error {
selector := root.sqlQuery(ctx)
aggregation := make([]string, 0, len(_s.fns))
for _, fn := range _s.fns {
aggregation = append(aggregation, fn(selector))
}
switch n := len(*_s.selector.flds); {
case n == 0 && len(aggregation) > 0:
selector.Select(aggregation...)
case n != 0 && len(aggregation) > 0:
selector.AppendSelect(aggregation...)
}
rows := &sql.Rows{}
query, args := selector.Query()
if err := _s.driver.Query(ctx, query, args, rows); err != nil {
return err
}
defer rows.Close()
return sql.ScanSlice(rows, v)
}

View File

@@ -1,367 +0,0 @@
// Code generated by ent, DO NOT EDIT.
package ent
import (
"context"
"errors"
"fmt"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field"
"git.dcentral.systems/toolz/goplt/internal/ent/auditlog"
"git.dcentral.systems/toolz/goplt/internal/ent/predicate"
)
// AuditLogUpdate is the builder for updating AuditLog entities.
type AuditLogUpdate struct {
config
hooks []Hook
mutation *AuditLogMutation
}
// Where appends a list predicates to the AuditLogUpdate builder.
func (_u *AuditLogUpdate) Where(ps ...predicate.AuditLog) *AuditLogUpdate {
_u.mutation.Where(ps...)
return _u
}
// SetActorID sets the "actor_id" field.
func (_u *AuditLogUpdate) SetActorID(v string) *AuditLogUpdate {
_u.mutation.SetActorID(v)
return _u
}
// SetNillableActorID sets the "actor_id" field if the given value is not nil.
func (_u *AuditLogUpdate) SetNillableActorID(v *string) *AuditLogUpdate {
if v != nil {
_u.SetActorID(*v)
}
return _u
}
// SetAction sets the "action" field.
func (_u *AuditLogUpdate) SetAction(v string) *AuditLogUpdate {
_u.mutation.SetAction(v)
return _u
}
// SetNillableAction sets the "action" field if the given value is not nil.
func (_u *AuditLogUpdate) SetNillableAction(v *string) *AuditLogUpdate {
if v != nil {
_u.SetAction(*v)
}
return _u
}
// SetTargetID sets the "target_id" field.
func (_u *AuditLogUpdate) SetTargetID(v string) *AuditLogUpdate {
_u.mutation.SetTargetID(v)
return _u
}
// SetNillableTargetID sets the "target_id" field if the given value is not nil.
func (_u *AuditLogUpdate) SetNillableTargetID(v *string) *AuditLogUpdate {
if v != nil {
_u.SetTargetID(*v)
}
return _u
}
// ClearTargetID clears the value of the "target_id" field.
func (_u *AuditLogUpdate) ClearTargetID() *AuditLogUpdate {
_u.mutation.ClearTargetID()
return _u
}
// SetMetadata sets the "metadata" field.
func (_u *AuditLogUpdate) SetMetadata(v map[string]interface{}) *AuditLogUpdate {
_u.mutation.SetMetadata(v)
return _u
}
// ClearMetadata clears the value of the "metadata" field.
func (_u *AuditLogUpdate) ClearMetadata() *AuditLogUpdate {
_u.mutation.ClearMetadata()
return _u
}
// Mutation returns the AuditLogMutation object of the builder.
func (_u *AuditLogUpdate) Mutation() *AuditLogMutation {
return _u.mutation
}
// Save executes the query and returns the number of nodes affected by the update operation.
func (_u *AuditLogUpdate) Save(ctx context.Context) (int, error) {
return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks)
}
// SaveX is like Save, but panics if an error occurs.
func (_u *AuditLogUpdate) SaveX(ctx context.Context) int {
affected, err := _u.Save(ctx)
if err != nil {
panic(err)
}
return affected
}
// Exec executes the query.
func (_u *AuditLogUpdate) Exec(ctx context.Context) error {
_, err := _u.Save(ctx)
return err
}
// ExecX is like Exec, but panics if an error occurs.
func (_u *AuditLogUpdate) ExecX(ctx context.Context) {
if err := _u.Exec(ctx); err != nil {
panic(err)
}
}
// check runs all checks and user-defined validators on the builder.
func (_u *AuditLogUpdate) check() error {
if v, ok := _u.mutation.ActorID(); ok {
if err := auditlog.ActorIDValidator(v); err != nil {
return &ValidationError{Name: "actor_id", err: fmt.Errorf(`ent: validator failed for field "AuditLog.actor_id": %w`, err)}
}
}
if v, ok := _u.mutation.Action(); ok {
if err := auditlog.ActionValidator(v); err != nil {
return &ValidationError{Name: "action", err: fmt.Errorf(`ent: validator failed for field "AuditLog.action": %w`, err)}
}
}
return nil
}
func (_u *AuditLogUpdate) sqlSave(ctx context.Context) (_node int, err error) {
if err := _u.check(); err != nil {
return _node, err
}
_spec := sqlgraph.NewUpdateSpec(auditlog.Table, auditlog.Columns, sqlgraph.NewFieldSpec(auditlog.FieldID, field.TypeString))
if ps := _u.mutation.predicates; len(ps) > 0 {
_spec.Predicate = func(selector *sql.Selector) {
for i := range ps {
ps[i](selector)
}
}
}
if value, ok := _u.mutation.ActorID(); ok {
_spec.SetField(auditlog.FieldActorID, field.TypeString, value)
}
if value, ok := _u.mutation.Action(); ok {
_spec.SetField(auditlog.FieldAction, field.TypeString, value)
}
if value, ok := _u.mutation.TargetID(); ok {
_spec.SetField(auditlog.FieldTargetID, field.TypeString, value)
}
if _u.mutation.TargetIDCleared() {
_spec.ClearField(auditlog.FieldTargetID, field.TypeString)
}
if value, ok := _u.mutation.Metadata(); ok {
_spec.SetField(auditlog.FieldMetadata, field.TypeJSON, value)
}
if _u.mutation.MetadataCleared() {
_spec.ClearField(auditlog.FieldMetadata, field.TypeJSON)
}
if _node, err = sqlgraph.UpdateNodes(ctx, _u.driver, _spec); err != nil {
if _, ok := err.(*sqlgraph.NotFoundError); ok {
err = &NotFoundError{auditlog.Label}
} else if sqlgraph.IsConstraintError(err) {
err = &ConstraintError{msg: err.Error(), wrap: err}
}
return 0, err
}
_u.mutation.done = true
return _node, nil
}
// AuditLogUpdateOne is the builder for updating a single AuditLog entity.
type AuditLogUpdateOne struct {
config
fields []string
hooks []Hook
mutation *AuditLogMutation
}
// SetActorID sets the "actor_id" field.
func (_u *AuditLogUpdateOne) SetActorID(v string) *AuditLogUpdateOne {
_u.mutation.SetActorID(v)
return _u
}
// SetNillableActorID sets the "actor_id" field if the given value is not nil.
func (_u *AuditLogUpdateOne) SetNillableActorID(v *string) *AuditLogUpdateOne {
if v != nil {
_u.SetActorID(*v)
}
return _u
}
// SetAction sets the "action" field.
func (_u *AuditLogUpdateOne) SetAction(v string) *AuditLogUpdateOne {
_u.mutation.SetAction(v)
return _u
}
// SetNillableAction sets the "action" field if the given value is not nil.
func (_u *AuditLogUpdateOne) SetNillableAction(v *string) *AuditLogUpdateOne {
if v != nil {
_u.SetAction(*v)
}
return _u
}
// SetTargetID sets the "target_id" field.
func (_u *AuditLogUpdateOne) SetTargetID(v string) *AuditLogUpdateOne {
_u.mutation.SetTargetID(v)
return _u
}
// SetNillableTargetID sets the "target_id" field if the given value is not nil.
func (_u *AuditLogUpdateOne) SetNillableTargetID(v *string) *AuditLogUpdateOne {
if v != nil {
_u.SetTargetID(*v)
}
return _u
}
// ClearTargetID clears the value of the "target_id" field.
func (_u *AuditLogUpdateOne) ClearTargetID() *AuditLogUpdateOne {
_u.mutation.ClearTargetID()
return _u
}
// SetMetadata sets the "metadata" field.
func (_u *AuditLogUpdateOne) SetMetadata(v map[string]interface{}) *AuditLogUpdateOne {
_u.mutation.SetMetadata(v)
return _u
}
// ClearMetadata clears the value of the "metadata" field.
func (_u *AuditLogUpdateOne) ClearMetadata() *AuditLogUpdateOne {
_u.mutation.ClearMetadata()
return _u
}
// Mutation returns the AuditLogMutation object of the builder.
func (_u *AuditLogUpdateOne) Mutation() *AuditLogMutation {
return _u.mutation
}
// Where appends a list predicates to the AuditLogUpdate builder.
func (_u *AuditLogUpdateOne) Where(ps ...predicate.AuditLog) *AuditLogUpdateOne {
_u.mutation.Where(ps...)
return _u
}
// Select allows selecting one or more fields (columns) of the returned entity.
// The default is selecting all fields defined in the entity schema.
func (_u *AuditLogUpdateOne) Select(field string, fields ...string) *AuditLogUpdateOne {
_u.fields = append([]string{field}, fields...)
return _u
}
// Save executes the query and returns the updated AuditLog entity.
func (_u *AuditLogUpdateOne) Save(ctx context.Context) (*AuditLog, error) {
return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks)
}
// SaveX is like Save, but panics if an error occurs.
func (_u *AuditLogUpdateOne) SaveX(ctx context.Context) *AuditLog {
node, err := _u.Save(ctx)
if err != nil {
panic(err)
}
return node
}
// Exec executes the query on the entity.
func (_u *AuditLogUpdateOne) Exec(ctx context.Context) error {
_, err := _u.Save(ctx)
return err
}
// ExecX is like Exec, but panics if an error occurs.
func (_u *AuditLogUpdateOne) ExecX(ctx context.Context) {
if err := _u.Exec(ctx); err != nil {
panic(err)
}
}
// check runs all checks and user-defined validators on the builder.
func (_u *AuditLogUpdateOne) check() error {
if v, ok := _u.mutation.ActorID(); ok {
if err := auditlog.ActorIDValidator(v); err != nil {
return &ValidationError{Name: "actor_id", err: fmt.Errorf(`ent: validator failed for field "AuditLog.actor_id": %w`, err)}
}
}
if v, ok := _u.mutation.Action(); ok {
if err := auditlog.ActionValidator(v); err != nil {
return &ValidationError{Name: "action", err: fmt.Errorf(`ent: validator failed for field "AuditLog.action": %w`, err)}
}
}
return nil
}
func (_u *AuditLogUpdateOne) sqlSave(ctx context.Context) (_node *AuditLog, err error) {
if err := _u.check(); err != nil {
return _node, err
}
_spec := sqlgraph.NewUpdateSpec(auditlog.Table, auditlog.Columns, sqlgraph.NewFieldSpec(auditlog.FieldID, field.TypeString))
id, ok := _u.mutation.ID()
if !ok {
return nil, &ValidationError{Name: "id", err: errors.New(`ent: missing "AuditLog.id" for update`)}
}
_spec.Node.ID.Value = id
if fields := _u.fields; len(fields) > 0 {
_spec.Node.Columns = make([]string, 0, len(fields))
_spec.Node.Columns = append(_spec.Node.Columns, auditlog.FieldID)
for _, f := range fields {
if !auditlog.ValidColumn(f) {
return nil, &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)}
}
if f != auditlog.FieldID {
_spec.Node.Columns = append(_spec.Node.Columns, f)
}
}
}
if ps := _u.mutation.predicates; len(ps) > 0 {
_spec.Predicate = func(selector *sql.Selector) {
for i := range ps {
ps[i](selector)
}
}
}
if value, ok := _u.mutation.ActorID(); ok {
_spec.SetField(auditlog.FieldActorID, field.TypeString, value)
}
if value, ok := _u.mutation.Action(); ok {
_spec.SetField(auditlog.FieldAction, field.TypeString, value)
}
if value, ok := _u.mutation.TargetID(); ok {
_spec.SetField(auditlog.FieldTargetID, field.TypeString, value)
}
if _u.mutation.TargetIDCleared() {
_spec.ClearField(auditlog.FieldTargetID, field.TypeString)
}
if value, ok := _u.mutation.Metadata(); ok {
_spec.SetField(auditlog.FieldMetadata, field.TypeJSON, value)
}
if _u.mutation.MetadataCleared() {
_spec.ClearField(auditlog.FieldMetadata, field.TypeJSON)
}
_node = &AuditLog{config: _u.config}
_spec.Assign = _node.assignValues
_spec.ScanValues = _node.scanValues
if err = sqlgraph.UpdateNode(ctx, _u.driver, _spec); err != nil {
if _, ok := err.(*sqlgraph.NotFoundError); ok {
err = &NotFoundError{auditlog.Label}
} else if sqlgraph.IsConstraintError(err) {
err = &ConstraintError{msg: err.Error(), wrap: err}
}
return nil, err
}
_u.mutation.done = true
return _node, nil
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,618 +0,0 @@
// Code generated by ent, DO NOT EDIT.
package ent
import (
"context"
"errors"
"fmt"
"reflect"
"sync"
"entgo.io/ent"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"git.dcentral.systems/toolz/goplt/internal/ent/auditlog"
"git.dcentral.systems/toolz/goplt/internal/ent/permission"
"git.dcentral.systems/toolz/goplt/internal/ent/role"
"git.dcentral.systems/toolz/goplt/internal/ent/rolepermission"
"git.dcentral.systems/toolz/goplt/internal/ent/user"
"git.dcentral.systems/toolz/goplt/internal/ent/userrole"
)
// ent aliases to avoid import conflicts in user's code.
type (
Op = ent.Op
Hook = ent.Hook
Value = ent.Value
Query = ent.Query
QueryContext = ent.QueryContext
Querier = ent.Querier
QuerierFunc = ent.QuerierFunc
Interceptor = ent.Interceptor
InterceptFunc = ent.InterceptFunc
Traverser = ent.Traverser
TraverseFunc = ent.TraverseFunc
Policy = ent.Policy
Mutator = ent.Mutator
Mutation = ent.Mutation
MutateFunc = ent.MutateFunc
)
type clientCtxKey struct{}
// FromContext returns a Client stored inside a context, or nil if there isn't one.
func FromContext(ctx context.Context) *Client {
c, _ := ctx.Value(clientCtxKey{}).(*Client)
return c
}
// NewContext returns a new context with the given Client attached.
func NewContext(parent context.Context, c *Client) context.Context {
return context.WithValue(parent, clientCtxKey{}, c)
}
type txCtxKey struct{}
// TxFromContext returns a Tx stored inside a context, or nil if there isn't one.
func TxFromContext(ctx context.Context) *Tx {
tx, _ := ctx.Value(txCtxKey{}).(*Tx)
return tx
}
// NewTxContext returns a new context with the given Tx attached.
func NewTxContext(parent context.Context, tx *Tx) context.Context {
return context.WithValue(parent, txCtxKey{}, tx)
}
// OrderFunc applies an ordering on the sql selector.
// Deprecated: Use Asc/Desc functions or the package builders instead.
type OrderFunc func(*sql.Selector)
var (
initCheck sync.Once
columnCheck sql.ColumnCheck
)
// checkColumn checks if the column exists in the given table.
func checkColumn(t, c string) error {
initCheck.Do(func() {
columnCheck = sql.NewColumnCheck(map[string]func(string) bool{
auditlog.Table: auditlog.ValidColumn,
permission.Table: permission.ValidColumn,
role.Table: role.ValidColumn,
rolepermission.Table: rolepermission.ValidColumn,
user.Table: user.ValidColumn,
userrole.Table: userrole.ValidColumn,
})
})
return columnCheck(t, c)
}
// Asc applies the given fields in ASC order.
func Asc(fields ...string) func(*sql.Selector) {
return func(s *sql.Selector) {
for _, f := range fields {
if err := checkColumn(s.TableName(), f); err != nil {
s.AddError(&ValidationError{Name: f, err: fmt.Errorf("ent: %w", err)})
}
s.OrderBy(sql.Asc(s.C(f)))
}
}
}
// Desc applies the given fields in DESC order.
func Desc(fields ...string) func(*sql.Selector) {
return func(s *sql.Selector) {
for _, f := range fields {
if err := checkColumn(s.TableName(), f); err != nil {
s.AddError(&ValidationError{Name: f, err: fmt.Errorf("ent: %w", err)})
}
s.OrderBy(sql.Desc(s.C(f)))
}
}
}
// AggregateFunc applies an aggregation step on the group-by traversal/selector.
type AggregateFunc func(*sql.Selector) string
// As is a pseudo aggregation function for renaming another other functions with custom names. For example:
//
// GroupBy(field1, field2).
// Aggregate(ent.As(ent.Sum(field1), "sum_field1"), (ent.As(ent.Sum(field2), "sum_field2")).
// Scan(ctx, &v)
func As(fn AggregateFunc, end string) AggregateFunc {
return func(s *sql.Selector) string {
return sql.As(fn(s), end)
}
}
// Count applies the "count" aggregation function on each group.
func Count() AggregateFunc {
return func(s *sql.Selector) string {
return sql.Count("*")
}
}
// Max applies the "max" aggregation function on the given field of each group.
func Max(field string) AggregateFunc {
return func(s *sql.Selector) string {
if err := checkColumn(s.TableName(), field); err != nil {
s.AddError(&ValidationError{Name: field, err: fmt.Errorf("ent: %w", err)})
return ""
}
return sql.Max(s.C(field))
}
}
// Mean applies the "mean" aggregation function on the given field of each group.
func Mean(field string) AggregateFunc {
return func(s *sql.Selector) string {
if err := checkColumn(s.TableName(), field); err != nil {
s.AddError(&ValidationError{Name: field, err: fmt.Errorf("ent: %w", err)})
return ""
}
return sql.Avg(s.C(field))
}
}
// Min applies the "min" aggregation function on the given field of each group.
func Min(field string) AggregateFunc {
return func(s *sql.Selector) string {
if err := checkColumn(s.TableName(), field); err != nil {
s.AddError(&ValidationError{Name: field, err: fmt.Errorf("ent: %w", err)})
return ""
}
return sql.Min(s.C(field))
}
}
// Sum applies the "sum" aggregation function on the given field of each group.
func Sum(field string) AggregateFunc {
return func(s *sql.Selector) string {
if err := checkColumn(s.TableName(), field); err != nil {
s.AddError(&ValidationError{Name: field, err: fmt.Errorf("ent: %w", err)})
return ""
}
return sql.Sum(s.C(field))
}
}
// ValidationError returns when validating a field or edge fails.
type ValidationError struct {
Name string // Field or edge name.
err error
}
// Error implements the error interface.
func (e *ValidationError) Error() string {
return e.err.Error()
}
// Unwrap implements the errors.Wrapper interface.
func (e *ValidationError) Unwrap() error {
return e.err
}
// IsValidationError returns a boolean indicating whether the error is a validation error.
func IsValidationError(err error) bool {
if err == nil {
return false
}
var e *ValidationError
return errors.As(err, &e)
}
// NotFoundError returns when trying to fetch a specific entity and it was not found in the database.
type NotFoundError struct {
label string
}
// Error implements the error interface.
func (e *NotFoundError) Error() string {
return "ent: " + e.label + " not found"
}
// IsNotFound returns a boolean indicating whether the error is a not found error.
func IsNotFound(err error) bool {
if err == nil {
return false
}
var e *NotFoundError
return errors.As(err, &e)
}
// MaskNotFound masks not found error.
func MaskNotFound(err error) error {
if IsNotFound(err) {
return nil
}
return err
}
// NotSingularError returns when trying to fetch a singular entity and more then one was found in the database.
type NotSingularError struct {
label string
}
// Error implements the error interface.
func (e *NotSingularError) Error() string {
return "ent: " + e.label + " not singular"
}
// IsNotSingular returns a boolean indicating whether the error is a not singular error.
func IsNotSingular(err error) bool {
if err == nil {
return false
}
var e *NotSingularError
return errors.As(err, &e)
}
// NotLoadedError returns when trying to get a node that was not loaded by the query.
type NotLoadedError struct {
edge string
}
// Error implements the error interface.
func (e *NotLoadedError) Error() string {
return "ent: " + e.edge + " edge was not loaded"
}
// IsNotLoaded returns a boolean indicating whether the error is a not loaded error.
func IsNotLoaded(err error) bool {
if err == nil {
return false
}
var e *NotLoadedError
return errors.As(err, &e)
}
// ConstraintError returns when trying to create/update one or more entities and
// one or more of their constraints failed. For example, violation of edge or
// field uniqueness.
type ConstraintError struct {
msg string
wrap error
}
// Error implements the error interface.
func (e ConstraintError) Error() string {
return "ent: constraint failed: " + e.msg
}
// Unwrap implements the errors.Wrapper interface.
func (e *ConstraintError) Unwrap() error {
return e.wrap
}
// IsConstraintError returns a boolean indicating whether the error is a constraint failure.
func IsConstraintError(err error) bool {
if err == nil {
return false
}
var e *ConstraintError
return errors.As(err, &e)
}
// selector embedded by the different Select/GroupBy builders.
type selector struct {
label string
flds *[]string
fns []AggregateFunc
scan func(context.Context, any) error
}
// ScanX is like Scan, but panics if an error occurs.
func (s *selector) ScanX(ctx context.Context, v any) {
if err := s.scan(ctx, v); err != nil {
panic(err)
}
}
// Strings returns list of strings from a selector. It is only allowed when selecting one field.
func (s *selector) Strings(ctx context.Context) ([]string, error) {
if len(*s.flds) > 1 {
return nil, errors.New("ent: Strings is not achievable when selecting more than 1 field")
}
var v []string
if err := s.scan(ctx, &v); err != nil {
return nil, err
}
return v, nil
}
// StringsX is like Strings, but panics if an error occurs.
func (s *selector) StringsX(ctx context.Context) []string {
v, err := s.Strings(ctx)
if err != nil {
panic(err)
}
return v
}
// String returns a single string from a selector. It is only allowed when selecting one field.
func (s *selector) String(ctx context.Context) (_ string, err error) {
var v []string
if v, err = s.Strings(ctx); err != nil {
return
}
switch len(v) {
case 1:
return v[0], nil
case 0:
err = &NotFoundError{s.label}
default:
err = fmt.Errorf("ent: Strings returned %d results when one was expected", len(v))
}
return
}
// StringX is like String, but panics if an error occurs.
func (s *selector) StringX(ctx context.Context) string {
v, err := s.String(ctx)
if err != nil {
panic(err)
}
return v
}
// Ints returns list of ints from a selector. It is only allowed when selecting one field.
func (s *selector) Ints(ctx context.Context) ([]int, error) {
if len(*s.flds) > 1 {
return nil, errors.New("ent: Ints is not achievable when selecting more than 1 field")
}
var v []int
if err := s.scan(ctx, &v); err != nil {
return nil, err
}
return v, nil
}
// IntsX is like Ints, but panics if an error occurs.
func (s *selector) IntsX(ctx context.Context) []int {
v, err := s.Ints(ctx)
if err != nil {
panic(err)
}
return v
}
// Int returns a single int from a selector. It is only allowed when selecting one field.
func (s *selector) Int(ctx context.Context) (_ int, err error) {
var v []int
if v, err = s.Ints(ctx); err != nil {
return
}
switch len(v) {
case 1:
return v[0], nil
case 0:
err = &NotFoundError{s.label}
default:
err = fmt.Errorf("ent: Ints returned %d results when one was expected", len(v))
}
return
}
// IntX is like Int, but panics if an error occurs.
func (s *selector) IntX(ctx context.Context) int {
v, err := s.Int(ctx)
if err != nil {
panic(err)
}
return v
}
// Float64s returns list of float64s from a selector. It is only allowed when selecting one field.
func (s *selector) Float64s(ctx context.Context) ([]float64, error) {
if len(*s.flds) > 1 {
return nil, errors.New("ent: Float64s is not achievable when selecting more than 1 field")
}
var v []float64
if err := s.scan(ctx, &v); err != nil {
return nil, err
}
return v, nil
}
// Float64sX is like Float64s, but panics if an error occurs.
func (s *selector) Float64sX(ctx context.Context) []float64 {
v, err := s.Float64s(ctx)
if err != nil {
panic(err)
}
return v
}
// Float64 returns a single float64 from a selector. It is only allowed when selecting one field.
func (s *selector) Float64(ctx context.Context) (_ float64, err error) {
var v []float64
if v, err = s.Float64s(ctx); err != nil {
return
}
switch len(v) {
case 1:
return v[0], nil
case 0:
err = &NotFoundError{s.label}
default:
err = fmt.Errorf("ent: Float64s returned %d results when one was expected", len(v))
}
return
}
// Float64X is like Float64, but panics if an error occurs.
func (s *selector) Float64X(ctx context.Context) float64 {
v, err := s.Float64(ctx)
if err != nil {
panic(err)
}
return v
}
// Bools returns list of bools from a selector. It is only allowed when selecting one field.
func (s *selector) Bools(ctx context.Context) ([]bool, error) {
if len(*s.flds) > 1 {
return nil, errors.New("ent: Bools is not achievable when selecting more than 1 field")
}
var v []bool
if err := s.scan(ctx, &v); err != nil {
return nil, err
}
return v, nil
}
// BoolsX is like Bools, but panics if an error occurs.
func (s *selector) BoolsX(ctx context.Context) []bool {
v, err := s.Bools(ctx)
if err != nil {
panic(err)
}
return v
}
// Bool returns a single bool from a selector. It is only allowed when selecting one field.
func (s *selector) Bool(ctx context.Context) (_ bool, err error) {
var v []bool
if v, err = s.Bools(ctx); err != nil {
return
}
switch len(v) {
case 1:
return v[0], nil
case 0:
err = &NotFoundError{s.label}
default:
err = fmt.Errorf("ent: Bools returned %d results when one was expected", len(v))
}
return
}
// BoolX is like Bool, but panics if an error occurs.
func (s *selector) BoolX(ctx context.Context) bool {
v, err := s.Bool(ctx)
if err != nil {
panic(err)
}
return v
}
// withHooks invokes the builder operation with the given hooks, if any.
func withHooks[V Value, M any, PM interface {
*M
Mutation
}](ctx context.Context, exec func(context.Context) (V, error), mutation PM, hooks []Hook) (value V, err error) {
if len(hooks) == 0 {
return exec(ctx)
}
var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) {
mutationT, ok := any(m).(PM)
if !ok {
return nil, fmt.Errorf("unexpected mutation type %T", m)
}
// Set the mutation to the builder.
*mutation = *mutationT
return exec(ctx)
})
for i := len(hooks) - 1; i >= 0; i-- {
if hooks[i] == nil {
return value, fmt.Errorf("ent: uninitialized hook (forgotten import ent/runtime?)")
}
mut = hooks[i](mut)
}
v, err := mut.Mutate(ctx, mutation)
if err != nil {
return value, err
}
nv, ok := v.(V)
if !ok {
return value, fmt.Errorf("unexpected node type %T returned from %T", v, mutation)
}
return nv, nil
}
// setContextOp returns a new context with the given QueryContext attached (including its op) in case it does not exist.
func setContextOp(ctx context.Context, qc *QueryContext, op string) context.Context {
if ent.QueryFromContext(ctx) == nil {
qc.Op = op
ctx = ent.NewQueryContext(ctx, qc)
}
return ctx
}
func querierAll[V Value, Q interface {
sqlAll(context.Context, ...queryHook) (V, error)
}]() Querier {
return QuerierFunc(func(ctx context.Context, q Query) (Value, error) {
query, ok := q.(Q)
if !ok {
return nil, fmt.Errorf("unexpected query type %T", q)
}
return query.sqlAll(ctx)
})
}
func querierCount[Q interface {
sqlCount(context.Context) (int, error)
}]() Querier {
return QuerierFunc(func(ctx context.Context, q Query) (Value, error) {
query, ok := q.(Q)
if !ok {
return nil, fmt.Errorf("unexpected query type %T", q)
}
return query.sqlCount(ctx)
})
}
func withInterceptors[V Value](ctx context.Context, q Query, qr Querier, inters []Interceptor) (v V, err error) {
for i := len(inters) - 1; i >= 0; i-- {
qr = inters[i].Intercept(qr)
}
rv, err := qr.Query(ctx, q)
if err != nil {
return v, err
}
vt, ok := rv.(V)
if !ok {
return v, fmt.Errorf("unexpected type %T returned from %T. expected type: %T", vt, q, v)
}
return vt, nil
}
func scanWithInterceptors[Q1 ent.Query, Q2 interface {
sqlScan(context.Context, Q1, any) error
}](ctx context.Context, rootQuery Q1, selectOrGroup Q2, inters []Interceptor, v any) error {
rv := reflect.ValueOf(v)
var qr Querier = QuerierFunc(func(ctx context.Context, q Query) (Value, error) {
query, ok := q.(Q1)
if !ok {
return nil, fmt.Errorf("unexpected query type %T", q)
}
if err := selectOrGroup.sqlScan(ctx, query, v); err != nil {
return nil, err
}
if k := rv.Kind(); k == reflect.Pointer && rv.Elem().CanInterface() {
return rv.Elem().Interface(), nil
}
return v, nil
})
for i := len(inters) - 1; i >= 0; i-- {
qr = inters[i].Intercept(qr)
}
vv, err := qr.Query(ctx, rootQuery)
if err != nil {
return err
}
switch rv2 := reflect.ValueOf(vv); {
case rv.IsNil(), rv2.IsNil(), rv.Kind() != reflect.Pointer:
case rv.Type() == rv2.Type():
rv.Elem().Set(rv2.Elem())
case rv.Elem().Type() == rv2.Type():
rv.Elem().Set(rv2)
}
return nil
}
// queryHook describes an internal hook for the different sqlAll methods.
type queryHook func(context.Context, *sqlgraph.QuerySpec)

View File

@@ -1,84 +0,0 @@
// Code generated by ent, DO NOT EDIT.
package enttest
import (
"context"
"git.dcentral.systems/toolz/goplt/internal/ent"
// required by schema hooks.
_ "git.dcentral.systems/toolz/goplt/internal/ent/runtime"
"entgo.io/ent/dialect/sql/schema"
"git.dcentral.systems/toolz/goplt/internal/ent/migrate"
)
type (
// TestingT is the interface that is shared between
// testing.T and testing.B and used by enttest.
TestingT interface {
FailNow()
Error(...any)
}
// Option configures client creation.
Option func(*options)
options struct {
opts []ent.Option
migrateOpts []schema.MigrateOption
}
)
// WithOptions forwards options to client creation.
func WithOptions(opts ...ent.Option) Option {
return func(o *options) {
o.opts = append(o.opts, opts...)
}
}
// WithMigrateOptions forwards options to auto migration.
func WithMigrateOptions(opts ...schema.MigrateOption) Option {
return func(o *options) {
o.migrateOpts = append(o.migrateOpts, opts...)
}
}
func newOptions(opts []Option) *options {
o := &options{}
for _, opt := range opts {
opt(o)
}
return o
}
// Open calls ent.Open and auto-run migration.
func Open(t TestingT, driverName, dataSourceName string, opts ...Option) *ent.Client {
o := newOptions(opts)
c, err := ent.Open(driverName, dataSourceName, o.opts...)
if err != nil {
t.Error(err)
t.FailNow()
}
migrateSchema(t, c, o)
return c
}
// NewClient calls ent.NewClient and auto-run migration.
func NewClient(t TestingT, opts ...Option) *ent.Client {
o := newOptions(opts)
c := ent.NewClient(o.opts...)
migrateSchema(t, c, o)
return c
}
func migrateSchema(t TestingT, c *ent.Client, o *options) {
tables, err := schema.CopyTables(migrate.Tables)
if err != nil {
t.Error(err)
t.FailNow()
}
if err := migrate.Create(context.Background(), c.Schema, tables, o.migrateOpts...); err != nil {
t.Error(err)
t.FailNow()
}
}

View File

@@ -1,259 +0,0 @@
// Code generated by ent, DO NOT EDIT.
package hook
import (
"context"
"fmt"
"git.dcentral.systems/toolz/goplt/internal/ent"
)
// The AuditLogFunc type is an adapter to allow the use of ordinary
// function as AuditLog mutator.
type AuditLogFunc func(context.Context, *ent.AuditLogMutation) (ent.Value, error)
// Mutate calls f(ctx, m).
func (f AuditLogFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, error) {
if mv, ok := m.(*ent.AuditLogMutation); ok {
return f(ctx, mv)
}
return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.AuditLogMutation", m)
}
// The PermissionFunc type is an adapter to allow the use of ordinary
// function as Permission mutator.
type PermissionFunc func(context.Context, *ent.PermissionMutation) (ent.Value, error)
// Mutate calls f(ctx, m).
func (f PermissionFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, error) {
if mv, ok := m.(*ent.PermissionMutation); ok {
return f(ctx, mv)
}
return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.PermissionMutation", m)
}
// The RoleFunc type is an adapter to allow the use of ordinary
// function as Role mutator.
type RoleFunc func(context.Context, *ent.RoleMutation) (ent.Value, error)
// Mutate calls f(ctx, m).
func (f RoleFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, error) {
if mv, ok := m.(*ent.RoleMutation); ok {
return f(ctx, mv)
}
return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.RoleMutation", m)
}
// The RolePermissionFunc type is an adapter to allow the use of ordinary
// function as RolePermission mutator.
type RolePermissionFunc func(context.Context, *ent.RolePermissionMutation) (ent.Value, error)
// Mutate calls f(ctx, m).
func (f RolePermissionFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, error) {
if mv, ok := m.(*ent.RolePermissionMutation); ok {
return f(ctx, mv)
}
return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.RolePermissionMutation", m)
}
// The UserFunc type is an adapter to allow the use of ordinary
// function as User mutator.
type UserFunc func(context.Context, *ent.UserMutation) (ent.Value, error)
// Mutate calls f(ctx, m).
func (f UserFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, error) {
if mv, ok := m.(*ent.UserMutation); ok {
return f(ctx, mv)
}
return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.UserMutation", m)
}
// The UserRoleFunc type is an adapter to allow the use of ordinary
// function as UserRole mutator.
type UserRoleFunc func(context.Context, *ent.UserRoleMutation) (ent.Value, error)
// Mutate calls f(ctx, m).
func (f UserRoleFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, error) {
if mv, ok := m.(*ent.UserRoleMutation); ok {
return f(ctx, mv)
}
return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.UserRoleMutation", m)
}
// Condition is a hook condition function.
type Condition func(context.Context, ent.Mutation) bool
// And groups conditions with the AND operator.
func And(first, second Condition, rest ...Condition) Condition {
return func(ctx context.Context, m ent.Mutation) bool {
if !first(ctx, m) || !second(ctx, m) {
return false
}
for _, cond := range rest {
if !cond(ctx, m) {
return false
}
}
return true
}
}
// Or groups conditions with the OR operator.
func Or(first, second Condition, rest ...Condition) Condition {
return func(ctx context.Context, m ent.Mutation) bool {
if first(ctx, m) || second(ctx, m) {
return true
}
for _, cond := range rest {
if cond(ctx, m) {
return true
}
}
return false
}
}
// Not negates a given condition.
func Not(cond Condition) Condition {
return func(ctx context.Context, m ent.Mutation) bool {
return !cond(ctx, m)
}
}
// HasOp is a condition testing mutation operation.
func HasOp(op ent.Op) Condition {
return func(_ context.Context, m ent.Mutation) bool {
return m.Op().Is(op)
}
}
// HasAddedFields is a condition validating `.AddedField` on fields.
func HasAddedFields(field string, fields ...string) Condition {
return func(_ context.Context, m ent.Mutation) bool {
if _, exists := m.AddedField(field); !exists {
return false
}
for _, field := range fields {
if _, exists := m.AddedField(field); !exists {
return false
}
}
return true
}
}
// HasClearedFields is a condition validating `.FieldCleared` on fields.
func HasClearedFields(field string, fields ...string) Condition {
return func(_ context.Context, m ent.Mutation) bool {
if exists := m.FieldCleared(field); !exists {
return false
}
for _, field := range fields {
if exists := m.FieldCleared(field); !exists {
return false
}
}
return true
}
}
// HasFields is a condition validating `.Field` on fields.
func HasFields(field string, fields ...string) Condition {
return func(_ context.Context, m ent.Mutation) bool {
if _, exists := m.Field(field); !exists {
return false
}
for _, field := range fields {
if _, exists := m.Field(field); !exists {
return false
}
}
return true
}
}
// If executes the given hook under condition.
//
// hook.If(ComputeAverage, And(HasFields(...), HasAddedFields(...)))
func If(hk ent.Hook, cond Condition) ent.Hook {
return func(next ent.Mutator) ent.Mutator {
return ent.MutateFunc(func(ctx context.Context, m ent.Mutation) (ent.Value, error) {
if cond(ctx, m) {
return hk(next).Mutate(ctx, m)
}
return next.Mutate(ctx, m)
})
}
}
// On executes the given hook only for the given operation.
//
// hook.On(Log, ent.Delete|ent.Create)
func On(hk ent.Hook, op ent.Op) ent.Hook {
return If(hk, HasOp(op))
}
// Unless skips the given hook only for the given operation.
//
// hook.Unless(Log, ent.Update|ent.UpdateOne)
func Unless(hk ent.Hook, op ent.Op) ent.Hook {
return If(hk, Not(HasOp(op)))
}
// FixedError is a hook returning a fixed error.
func FixedError(err error) ent.Hook {
return func(ent.Mutator) ent.Mutator {
return ent.MutateFunc(func(context.Context, ent.Mutation) (ent.Value, error) {
return nil, err
})
}
}
// Reject returns a hook that rejects all operations that match op.
//
// func (T) Hooks() []ent.Hook {
// return []ent.Hook{
// Reject(ent.Delete|ent.Update),
// }
// }
func Reject(op ent.Op) ent.Hook {
hk := FixedError(fmt.Errorf("%s operation is not allowed", op))
return On(hk, op)
}
// Chain acts as a list of hooks and is effectively immutable.
// Once created, it will always hold the same set of hooks in the same order.
type Chain struct {
hooks []ent.Hook
}
// NewChain creates a new chain of hooks.
func NewChain(hooks ...ent.Hook) Chain {
return Chain{append([]ent.Hook(nil), hooks...)}
}
// Hook chains the list of hooks and returns the final hook.
func (c Chain) Hook() ent.Hook {
return func(mutator ent.Mutator) ent.Mutator {
for i := len(c.hooks) - 1; i >= 0; i-- {
mutator = c.hooks[i](mutator)
}
return mutator
}
}
// Append extends a chain, adding the specified hook
// as the last ones in the mutation flow.
func (c Chain) Append(hooks ...ent.Hook) Chain {
newHooks := make([]ent.Hook, 0, len(c.hooks)+len(hooks))
newHooks = append(newHooks, c.hooks...)
newHooks = append(newHooks, hooks...)
return Chain{newHooks}
}
// Extend extends a chain, adding the specified chain
// as the last ones in the mutation flow.
func (c Chain) Extend(chain Chain) Chain {
return c.Append(chain.hooks...)
}

View File

@@ -1,64 +0,0 @@
// Code generated by ent, DO NOT EDIT.
package migrate
import (
"context"
"fmt"
"io"
"entgo.io/ent/dialect"
"entgo.io/ent/dialect/sql/schema"
)
var (
// WithGlobalUniqueID sets the universal ids options to the migration.
// If this option is enabled, ent migration will allocate a 1<<32 range
// for the ids of each entity (table).
// Note that this option cannot be applied on tables that already exist.
WithGlobalUniqueID = schema.WithGlobalUniqueID
// WithDropColumn sets the drop column option to the migration.
// If this option is enabled, ent migration will drop old columns
// that were used for both fields and edges. This defaults to false.
WithDropColumn = schema.WithDropColumn
// WithDropIndex sets the drop index option to the migration.
// If this option is enabled, ent migration will drop old indexes
// that were defined in the schema. This defaults to false.
// Note that unique constraints are defined using `UNIQUE INDEX`,
// and therefore, it's recommended to enable this option to get more
// flexibility in the schema changes.
WithDropIndex = schema.WithDropIndex
// WithForeignKeys enables creating foreign-key in schema DDL. This defaults to true.
WithForeignKeys = schema.WithForeignKeys
)
// Schema is the API for creating, migrating and dropping a schema.
type Schema struct {
drv dialect.Driver
}
// NewSchema creates a new schema client.
func NewSchema(drv dialect.Driver) *Schema { return &Schema{drv: drv} }
// Create creates all schema resources.
func (s *Schema) Create(ctx context.Context, opts ...schema.MigrateOption) error {
return Create(ctx, s, Tables, opts...)
}
// Create creates all table resources using the given schema driver.
func Create(ctx context.Context, s *Schema, tables []*schema.Table, opts ...schema.MigrateOption) error {
migrate, err := schema.NewMigrate(s.drv, opts...)
if err != nil {
return fmt.Errorf("ent/migrate: %w", err)
}
return migrate.Create(ctx, tables...)
}
// WriteTo writes the schema changes to w instead of running them against the database.
//
// if err := client.Schema.WriteTo(context.Background(), os.Stdout); err != nil {
// log.Fatal(err)
// }
func (s *Schema) WriteTo(ctx context.Context, w io.Writer, opts ...schema.MigrateOption) error {
return Create(ctx, &Schema{drv: &schema.WriteDriver{Writer: w, Driver: s.drv}}, Tables, opts...)
}

View File

@@ -1,187 +0,0 @@
// Code generated by ent, DO NOT EDIT.
package migrate
import (
"entgo.io/ent/dialect/sql/schema"
"entgo.io/ent/schema/field"
)
var (
// AuditLogsColumns holds the columns for the "audit_logs" table.
AuditLogsColumns = []*schema.Column{
{Name: "id", Type: field.TypeString, Unique: true},
{Name: "actor_id", Type: field.TypeString},
{Name: "action", Type: field.TypeString},
{Name: "target_id", Type: field.TypeString, Nullable: true},
{Name: "metadata", Type: field.TypeJSON, Nullable: true},
{Name: "timestamp", Type: field.TypeTime},
}
// AuditLogsTable holds the schema information for the "audit_logs" table.
AuditLogsTable = &schema.Table{
Name: "audit_logs",
Columns: AuditLogsColumns,
PrimaryKey: []*schema.Column{AuditLogsColumns[0]},
Indexes: []*schema.Index{
{
Name: "auditlog_actor_id",
Unique: false,
Columns: []*schema.Column{AuditLogsColumns[1]},
},
{
Name: "auditlog_target_id",
Unique: false,
Columns: []*schema.Column{AuditLogsColumns[3]},
},
{
Name: "auditlog_timestamp",
Unique: false,
Columns: []*schema.Column{AuditLogsColumns[5]},
},
{
Name: "auditlog_action",
Unique: false,
Columns: []*schema.Column{AuditLogsColumns[2]},
},
},
}
// PermissionsColumns holds the columns for the "permissions" table.
PermissionsColumns = []*schema.Column{
{Name: "id", Type: field.TypeString, Unique: true},
{Name: "name", Type: field.TypeString, Unique: true},
}
// PermissionsTable holds the schema information for the "permissions" table.
PermissionsTable = &schema.Table{
Name: "permissions",
Columns: PermissionsColumns,
PrimaryKey: []*schema.Column{PermissionsColumns[0]},
}
// RolesColumns holds the columns for the "roles" table.
RolesColumns = []*schema.Column{
{Name: "id", Type: field.TypeString, Unique: true},
{Name: "name", Type: field.TypeString, Unique: true},
{Name: "description", Type: field.TypeString, Nullable: true},
{Name: "created_at", Type: field.TypeTime},
}
// RolesTable holds the schema information for the "roles" table.
RolesTable = &schema.Table{
Name: "roles",
Columns: RolesColumns,
PrimaryKey: []*schema.Column{RolesColumns[0]},
}
// RolePermissionsColumns holds the columns for the "role_permissions" table.
RolePermissionsColumns = []*schema.Column{
{Name: "id", Type: field.TypeInt, Increment: true},
{Name: "permission_role_permissions", Type: field.TypeString, Nullable: true},
{Name: "role_role_permissions", Type: field.TypeString, Nullable: true},
{Name: "role_id", Type: field.TypeString},
{Name: "permission_id", Type: field.TypeString},
}
// RolePermissionsTable holds the schema information for the "role_permissions" table.
RolePermissionsTable = &schema.Table{
Name: "role_permissions",
Columns: RolePermissionsColumns,
PrimaryKey: []*schema.Column{RolePermissionsColumns[0]},
ForeignKeys: []*schema.ForeignKey{
{
Symbol: "role_permissions_permissions_role_permissions",
Columns: []*schema.Column{RolePermissionsColumns[1]},
RefColumns: []*schema.Column{PermissionsColumns[0]},
OnDelete: schema.SetNull,
},
{
Symbol: "role_permissions_roles_role_permissions",
Columns: []*schema.Column{RolePermissionsColumns[2]},
RefColumns: []*schema.Column{RolesColumns[0]},
OnDelete: schema.SetNull,
},
{
Symbol: "role_permissions_roles_role",
Columns: []*schema.Column{RolePermissionsColumns[3]},
RefColumns: []*schema.Column{RolesColumns[0]},
OnDelete: schema.NoAction,
},
{
Symbol: "role_permissions_permissions_permission",
Columns: []*schema.Column{RolePermissionsColumns[4]},
RefColumns: []*schema.Column{PermissionsColumns[0]},
OnDelete: schema.NoAction,
},
},
}
// UsersColumns holds the columns for the "users" table.
UsersColumns = []*schema.Column{
{Name: "id", Type: field.TypeString, Unique: true},
{Name: "email", Type: field.TypeString, Unique: true},
{Name: "password_hash", Type: field.TypeString},
{Name: "verified", Type: field.TypeBool, Default: false},
{Name: "created_at", Type: field.TypeTime},
{Name: "updated_at", Type: field.TypeTime},
}
// UsersTable holds the schema information for the "users" table.
UsersTable = &schema.Table{
Name: "users",
Columns: UsersColumns,
PrimaryKey: []*schema.Column{UsersColumns[0]},
}
// UserRolesColumns holds the columns for the "user_roles" table.
UserRolesColumns = []*schema.Column{
{Name: "id", Type: field.TypeInt, Increment: true},
{Name: "role_user_roles", Type: field.TypeString, Nullable: true},
{Name: "user_user_roles", Type: field.TypeString, Nullable: true},
{Name: "user_id", Type: field.TypeString},
{Name: "role_id", Type: field.TypeString},
}
// UserRolesTable holds the schema information for the "user_roles" table.
UserRolesTable = &schema.Table{
Name: "user_roles",
Columns: UserRolesColumns,
PrimaryKey: []*schema.Column{UserRolesColumns[0]},
ForeignKeys: []*schema.ForeignKey{
{
Symbol: "user_roles_roles_user_roles",
Columns: []*schema.Column{UserRolesColumns[1]},
RefColumns: []*schema.Column{RolesColumns[0]},
OnDelete: schema.SetNull,
},
{
Symbol: "user_roles_users_user_roles",
Columns: []*schema.Column{UserRolesColumns[2]},
RefColumns: []*schema.Column{UsersColumns[0]},
OnDelete: schema.SetNull,
},
{
Symbol: "user_roles_users_user",
Columns: []*schema.Column{UserRolesColumns[3]},
RefColumns: []*schema.Column{UsersColumns[0]},
OnDelete: schema.NoAction,
},
{
Symbol: "user_roles_roles_role",
Columns: []*schema.Column{UserRolesColumns[4]},
RefColumns: []*schema.Column{RolesColumns[0]},
OnDelete: schema.NoAction,
},
},
}
// Tables holds all the tables in the schema.
Tables = []*schema.Table{
AuditLogsTable,
PermissionsTable,
RolesTable,
RolePermissionsTable,
UsersTable,
UserRolesTable,
}
)
func init() {
RolePermissionsTable.ForeignKeys[0].RefTable = PermissionsTable
RolePermissionsTable.ForeignKeys[1].RefTable = RolesTable
RolePermissionsTable.ForeignKeys[2].RefTable = RolesTable
RolePermissionsTable.ForeignKeys[3].RefTable = PermissionsTable
UserRolesTable.ForeignKeys[0].RefTable = RolesTable
UserRolesTable.ForeignKeys[1].RefTable = UsersTable
UserRolesTable.ForeignKeys[2].RefTable = UsersTable
UserRolesTable.ForeignKeys[3].RefTable = RolesTable
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,127 +0,0 @@
// Code generated by ent, DO NOT EDIT.
package ent
import (
"fmt"
"strings"
"entgo.io/ent"
"entgo.io/ent/dialect/sql"
"git.dcentral.systems/toolz/goplt/internal/ent/permission"
)
// Permission is the model entity for the Permission schema.
type Permission struct {
config `json:"-"`
// ID of the ent.
ID string `json:"id,omitempty"`
// Format: module.resource.action
Name string `json:"name,omitempty"`
// Edges holds the relations/edges for other nodes in the graph.
// The values are being populated by the PermissionQuery when eager-loading is set.
Edges PermissionEdges `json:"edges"`
selectValues sql.SelectValues
}
// PermissionEdges holds the relations/edges for other nodes in the graph.
type PermissionEdges struct {
// RolePermissions holds the value of the role_permissions edge.
RolePermissions []*RolePermission `json:"role_permissions,omitempty"`
// loadedTypes holds the information for reporting if a
// type was loaded (or requested) in eager-loading or not.
loadedTypes [1]bool
}
// RolePermissionsOrErr returns the RolePermissions value or an error if the edge
// was not loaded in eager-loading.
func (e PermissionEdges) RolePermissionsOrErr() ([]*RolePermission, error) {
if e.loadedTypes[0] {
return e.RolePermissions, nil
}
return nil, &NotLoadedError{edge: "role_permissions"}
}
// scanValues returns the types for scanning values from sql.Rows.
func (*Permission) scanValues(columns []string) ([]any, error) {
values := make([]any, len(columns))
for i := range columns {
switch columns[i] {
case permission.FieldID, permission.FieldName:
values[i] = new(sql.NullString)
default:
values[i] = new(sql.UnknownType)
}
}
return values, nil
}
// assignValues assigns the values that were returned from sql.Rows (after scanning)
// to the Permission fields.
func (_m *Permission) assignValues(columns []string, values []any) error {
if m, n := len(values), len(columns); m < n {
return fmt.Errorf("mismatch number of scan values: %d != %d", m, n)
}
for i := range columns {
switch columns[i] {
case permission.FieldID:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field id", values[i])
} else if value.Valid {
_m.ID = value.String
}
case permission.FieldName:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field name", values[i])
} else if value.Valid {
_m.Name = value.String
}
default:
_m.selectValues.Set(columns[i], values[i])
}
}
return nil
}
// Value returns the ent.Value that was dynamically selected and assigned to the Permission.
// This includes values selected through modifiers, order, etc.
func (_m *Permission) Value(name string) (ent.Value, error) {
return _m.selectValues.Get(name)
}
// QueryRolePermissions queries the "role_permissions" edge of the Permission entity.
func (_m *Permission) QueryRolePermissions() *RolePermissionQuery {
return NewPermissionClient(_m.config).QueryRolePermissions(_m)
}
// Update returns a builder for updating this Permission.
// Note that you need to call Permission.Unwrap() before calling this method if this Permission
// was returned from a transaction, and the transaction was committed or rolled back.
func (_m *Permission) Update() *PermissionUpdateOne {
return NewPermissionClient(_m.config).UpdateOne(_m)
}
// Unwrap unwraps the Permission entity that was returned from a transaction after it was closed,
// so that all future queries will be executed through the driver which created the transaction.
func (_m *Permission) Unwrap() *Permission {
_tx, ok := _m.config.driver.(*txDriver)
if !ok {
panic("ent: Permission is not a transactional entity")
}
_m.config.driver = _tx.drv
return _m
}
// String implements the fmt.Stringer.
func (_m *Permission) String() string {
var builder strings.Builder
builder.WriteString("Permission(")
builder.WriteString(fmt.Sprintf("id=%v, ", _m.ID))
builder.WriteString("name=")
builder.WriteString(_m.Name)
builder.WriteByte(')')
return builder.String()
}
// Permissions is a parsable slice of Permission.
type Permissions []*Permission

View File

@@ -1,83 +0,0 @@
// Code generated by ent, DO NOT EDIT.
package permission
import (
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
)
const (
// Label holds the string label denoting the permission type in the database.
Label = "permission"
// FieldID holds the string denoting the id field in the database.
FieldID = "id"
// FieldName holds the string denoting the name field in the database.
FieldName = "name"
// EdgeRolePermissions holds the string denoting the role_permissions edge name in mutations.
EdgeRolePermissions = "role_permissions"
// Table holds the table name of the permission in the database.
Table = "permissions"
// RolePermissionsTable is the table that holds the role_permissions relation/edge.
RolePermissionsTable = "role_permissions"
// RolePermissionsInverseTable is the table name for the RolePermission entity.
// It exists in this package in order to avoid circular dependency with the "rolepermission" package.
RolePermissionsInverseTable = "role_permissions"
// RolePermissionsColumn is the table column denoting the role_permissions relation/edge.
RolePermissionsColumn = "permission_role_permissions"
)
// Columns holds all SQL columns for permission fields.
var Columns = []string{
FieldID,
FieldName,
}
// ValidColumn reports if the column name is valid (part of the table columns).
func ValidColumn(column string) bool {
for i := range Columns {
if column == Columns[i] {
return true
}
}
return false
}
var (
// NameValidator is a validator for the "name" field. It is called by the builders before save.
NameValidator func(string) error
)
// OrderOption defines the ordering options for the Permission queries.
type OrderOption func(*sql.Selector)
// ByID orders the results by the id field.
func ByID(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldID, opts...).ToFunc()
}
// ByName orders the results by the name field.
func ByName(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldName, opts...).ToFunc()
}
// ByRolePermissionsCount orders the results by role_permissions count.
func ByRolePermissionsCount(opts ...sql.OrderTermOption) OrderOption {
return func(s *sql.Selector) {
sqlgraph.OrderByNeighborsCount(s, newRolePermissionsStep(), opts...)
}
}
// ByRolePermissions orders the results by role_permissions terms.
func ByRolePermissions(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption {
return func(s *sql.Selector) {
sqlgraph.OrderByNeighborTerms(s, newRolePermissionsStep(), append([]sql.OrderTerm{term}, terms...)...)
}
}
func newRolePermissionsStep() *sqlgraph.Step {
return sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.To(RolePermissionsInverseTable, FieldID),
sqlgraph.Edge(sqlgraph.O2M, false, RolePermissionsTable, RolePermissionsColumn),
)
}

View File

@@ -1,172 +0,0 @@
// Code generated by ent, DO NOT EDIT.
package permission
import (
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"git.dcentral.systems/toolz/goplt/internal/ent/predicate"
)
// ID filters vertices based on their ID field.
func ID(id string) predicate.Permission {
return predicate.Permission(sql.FieldEQ(FieldID, id))
}
// IDEQ applies the EQ predicate on the ID field.
func IDEQ(id string) predicate.Permission {
return predicate.Permission(sql.FieldEQ(FieldID, id))
}
// IDNEQ applies the NEQ predicate on the ID field.
func IDNEQ(id string) predicate.Permission {
return predicate.Permission(sql.FieldNEQ(FieldID, id))
}
// IDIn applies the In predicate on the ID field.
func IDIn(ids ...string) predicate.Permission {
return predicate.Permission(sql.FieldIn(FieldID, ids...))
}
// IDNotIn applies the NotIn predicate on the ID field.
func IDNotIn(ids ...string) predicate.Permission {
return predicate.Permission(sql.FieldNotIn(FieldID, ids...))
}
// IDGT applies the GT predicate on the ID field.
func IDGT(id string) predicate.Permission {
return predicate.Permission(sql.FieldGT(FieldID, id))
}
// IDGTE applies the GTE predicate on the ID field.
func IDGTE(id string) predicate.Permission {
return predicate.Permission(sql.FieldGTE(FieldID, id))
}
// IDLT applies the LT predicate on the ID field.
func IDLT(id string) predicate.Permission {
return predicate.Permission(sql.FieldLT(FieldID, id))
}
// IDLTE applies the LTE predicate on the ID field.
func IDLTE(id string) predicate.Permission {
return predicate.Permission(sql.FieldLTE(FieldID, id))
}
// IDEqualFold applies the EqualFold predicate on the ID field.
func IDEqualFold(id string) predicate.Permission {
return predicate.Permission(sql.FieldEqualFold(FieldID, id))
}
// IDContainsFold applies the ContainsFold predicate on the ID field.
func IDContainsFold(id string) predicate.Permission {
return predicate.Permission(sql.FieldContainsFold(FieldID, id))
}
// Name applies equality check predicate on the "name" field. It's identical to NameEQ.
func Name(v string) predicate.Permission {
return predicate.Permission(sql.FieldEQ(FieldName, v))
}
// NameEQ applies the EQ predicate on the "name" field.
func NameEQ(v string) predicate.Permission {
return predicate.Permission(sql.FieldEQ(FieldName, v))
}
// NameNEQ applies the NEQ predicate on the "name" field.
func NameNEQ(v string) predicate.Permission {
return predicate.Permission(sql.FieldNEQ(FieldName, v))
}
// NameIn applies the In predicate on the "name" field.
func NameIn(vs ...string) predicate.Permission {
return predicate.Permission(sql.FieldIn(FieldName, vs...))
}
// NameNotIn applies the NotIn predicate on the "name" field.
func NameNotIn(vs ...string) predicate.Permission {
return predicate.Permission(sql.FieldNotIn(FieldName, vs...))
}
// NameGT applies the GT predicate on the "name" field.
func NameGT(v string) predicate.Permission {
return predicate.Permission(sql.FieldGT(FieldName, v))
}
// NameGTE applies the GTE predicate on the "name" field.
func NameGTE(v string) predicate.Permission {
return predicate.Permission(sql.FieldGTE(FieldName, v))
}
// NameLT applies the LT predicate on the "name" field.
func NameLT(v string) predicate.Permission {
return predicate.Permission(sql.FieldLT(FieldName, v))
}
// NameLTE applies the LTE predicate on the "name" field.
func NameLTE(v string) predicate.Permission {
return predicate.Permission(sql.FieldLTE(FieldName, v))
}
// NameContains applies the Contains predicate on the "name" field.
func NameContains(v string) predicate.Permission {
return predicate.Permission(sql.FieldContains(FieldName, v))
}
// NameHasPrefix applies the HasPrefix predicate on the "name" field.
func NameHasPrefix(v string) predicate.Permission {
return predicate.Permission(sql.FieldHasPrefix(FieldName, v))
}
// NameHasSuffix applies the HasSuffix predicate on the "name" field.
func NameHasSuffix(v string) predicate.Permission {
return predicate.Permission(sql.FieldHasSuffix(FieldName, v))
}
// NameEqualFold applies the EqualFold predicate on the "name" field.
func NameEqualFold(v string) predicate.Permission {
return predicate.Permission(sql.FieldEqualFold(FieldName, v))
}
// NameContainsFold applies the ContainsFold predicate on the "name" field.
func NameContainsFold(v string) predicate.Permission {
return predicate.Permission(sql.FieldContainsFold(FieldName, v))
}
// HasRolePermissions applies the HasEdge predicate on the "role_permissions" edge.
func HasRolePermissions() predicate.Permission {
return predicate.Permission(func(s *sql.Selector) {
step := sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.Edge(sqlgraph.O2M, false, RolePermissionsTable, RolePermissionsColumn),
)
sqlgraph.HasNeighbors(s, step)
})
}
// HasRolePermissionsWith applies the HasEdge predicate on the "role_permissions" edge with a given conditions (other predicates).
func HasRolePermissionsWith(preds ...predicate.RolePermission) predicate.Permission {
return predicate.Permission(func(s *sql.Selector) {
step := newRolePermissionsStep()
sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) {
for _, p := range preds {
p(s)
}
})
})
}
// And groups predicates with the AND operator between them.
func And(predicates ...predicate.Permission) predicate.Permission {
return predicate.Permission(sql.AndPredicates(predicates...))
}
// Or groups predicates with the OR operator between them.
func Or(predicates ...predicate.Permission) predicate.Permission {
return predicate.Permission(sql.OrPredicates(predicates...))
}
// Not applies the not operator on the given predicate.
func Not(p predicate.Permission) predicate.Permission {
return predicate.Permission(sql.NotPredicates(p))
}

View File

@@ -1,231 +0,0 @@
// Code generated by ent, DO NOT EDIT.
package ent
import (
"context"
"errors"
"fmt"
"entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field"
"git.dcentral.systems/toolz/goplt/internal/ent/permission"
"git.dcentral.systems/toolz/goplt/internal/ent/rolepermission"
)
// PermissionCreate is the builder for creating a Permission entity.
type PermissionCreate struct {
config
mutation *PermissionMutation
hooks []Hook
}
// SetName sets the "name" field.
func (_c *PermissionCreate) SetName(v string) *PermissionCreate {
_c.mutation.SetName(v)
return _c
}
// SetID sets the "id" field.
func (_c *PermissionCreate) SetID(v string) *PermissionCreate {
_c.mutation.SetID(v)
return _c
}
// AddRolePermissionIDs adds the "role_permissions" edge to the RolePermission entity by IDs.
func (_c *PermissionCreate) AddRolePermissionIDs(ids ...int) *PermissionCreate {
_c.mutation.AddRolePermissionIDs(ids...)
return _c
}
// AddRolePermissions adds the "role_permissions" edges to the RolePermission entity.
func (_c *PermissionCreate) AddRolePermissions(v ...*RolePermission) *PermissionCreate {
ids := make([]int, len(v))
for i := range v {
ids[i] = v[i].ID
}
return _c.AddRolePermissionIDs(ids...)
}
// Mutation returns the PermissionMutation object of the builder.
func (_c *PermissionCreate) Mutation() *PermissionMutation {
return _c.mutation
}
// Save creates the Permission in the database.
func (_c *PermissionCreate) Save(ctx context.Context) (*Permission, error) {
return withHooks(ctx, _c.sqlSave, _c.mutation, _c.hooks)
}
// SaveX calls Save and panics if Save returns an error.
func (_c *PermissionCreate) SaveX(ctx context.Context) *Permission {
v, err := _c.Save(ctx)
if err != nil {
panic(err)
}
return v
}
// Exec executes the query.
func (_c *PermissionCreate) Exec(ctx context.Context) error {
_, err := _c.Save(ctx)
return err
}
// ExecX is like Exec, but panics if an error occurs.
func (_c *PermissionCreate) ExecX(ctx context.Context) {
if err := _c.Exec(ctx); err != nil {
panic(err)
}
}
// check runs all checks and user-defined validators on the builder.
func (_c *PermissionCreate) check() error {
if _, ok := _c.mutation.Name(); !ok {
return &ValidationError{Name: "name", err: errors.New(`ent: missing required field "Permission.name"`)}
}
if v, ok := _c.mutation.Name(); ok {
if err := permission.NameValidator(v); err != nil {
return &ValidationError{Name: "name", err: fmt.Errorf(`ent: validator failed for field "Permission.name": %w`, err)}
}
}
return nil
}
func (_c *PermissionCreate) sqlSave(ctx context.Context) (*Permission, error) {
if err := _c.check(); err != nil {
return nil, err
}
_node, _spec := _c.createSpec()
if err := sqlgraph.CreateNode(ctx, _c.driver, _spec); err != nil {
if sqlgraph.IsConstraintError(err) {
err = &ConstraintError{msg: err.Error(), wrap: err}
}
return nil, err
}
if _spec.ID.Value != nil {
if id, ok := _spec.ID.Value.(string); ok {
_node.ID = id
} else {
return nil, fmt.Errorf("unexpected Permission.ID type: %T", _spec.ID.Value)
}
}
_c.mutation.id = &_node.ID
_c.mutation.done = true
return _node, nil
}
func (_c *PermissionCreate) createSpec() (*Permission, *sqlgraph.CreateSpec) {
var (
_node = &Permission{config: _c.config}
_spec = sqlgraph.NewCreateSpec(permission.Table, sqlgraph.NewFieldSpec(permission.FieldID, field.TypeString))
)
if id, ok := _c.mutation.ID(); ok {
_node.ID = id
_spec.ID.Value = id
}
if value, ok := _c.mutation.Name(); ok {
_spec.SetField(permission.FieldName, field.TypeString, value)
_node.Name = value
}
if nodes := _c.mutation.RolePermissionsIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: permission.RolePermissionsTable,
Columns: []string{permission.RolePermissionsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(rolepermission.FieldID, field.TypeInt),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges = append(_spec.Edges, edge)
}
return _node, _spec
}
// PermissionCreateBulk is the builder for creating many Permission entities in bulk.
type PermissionCreateBulk struct {
config
err error
builders []*PermissionCreate
}
// Save creates the Permission entities in the database.
func (_c *PermissionCreateBulk) Save(ctx context.Context) ([]*Permission, error) {
if _c.err != nil {
return nil, _c.err
}
specs := make([]*sqlgraph.CreateSpec, len(_c.builders))
nodes := make([]*Permission, len(_c.builders))
mutators := make([]Mutator, len(_c.builders))
for i := range _c.builders {
func(i int, root context.Context) {
builder := _c.builders[i]
var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) {
mutation, ok := m.(*PermissionMutation)
if !ok {
return nil, fmt.Errorf("unexpected mutation type %T", m)
}
if err := builder.check(); err != nil {
return nil, err
}
builder.mutation = mutation
var err error
nodes[i], specs[i] = builder.createSpec()
if i < len(mutators)-1 {
_, err = mutators[i+1].Mutate(root, _c.builders[i+1].mutation)
} else {
spec := &sqlgraph.BatchCreateSpec{Nodes: specs}
// Invoke the actual operation on the latest mutation in the chain.
if err = sqlgraph.BatchCreate(ctx, _c.driver, spec); err != nil {
if sqlgraph.IsConstraintError(err) {
err = &ConstraintError{msg: err.Error(), wrap: err}
}
}
}
if err != nil {
return nil, err
}
mutation.id = &nodes[i].ID
mutation.done = true
return nodes[i], nil
})
for i := len(builder.hooks) - 1; i >= 0; i-- {
mut = builder.hooks[i](mut)
}
mutators[i] = mut
}(i, ctx)
}
if len(mutators) > 0 {
if _, err := mutators[0].Mutate(ctx, _c.builders[0].mutation); err != nil {
return nil, err
}
}
return nodes, nil
}
// SaveX is like Save, but panics if an error occurs.
func (_c *PermissionCreateBulk) SaveX(ctx context.Context) []*Permission {
v, err := _c.Save(ctx)
if err != nil {
panic(err)
}
return v
}
// Exec executes the query.
func (_c *PermissionCreateBulk) Exec(ctx context.Context) error {
_, err := _c.Save(ctx)
return err
}
// ExecX is like Exec, but panics if an error occurs.
func (_c *PermissionCreateBulk) ExecX(ctx context.Context) {
if err := _c.Exec(ctx); err != nil {
panic(err)
}
}

View File

@@ -1,88 +0,0 @@
// Code generated by ent, DO NOT EDIT.
package ent
import (
"context"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field"
"git.dcentral.systems/toolz/goplt/internal/ent/permission"
"git.dcentral.systems/toolz/goplt/internal/ent/predicate"
)
// PermissionDelete is the builder for deleting a Permission entity.
type PermissionDelete struct {
config
hooks []Hook
mutation *PermissionMutation
}
// Where appends a list predicates to the PermissionDelete builder.
func (_d *PermissionDelete) Where(ps ...predicate.Permission) *PermissionDelete {
_d.mutation.Where(ps...)
return _d
}
// Exec executes the deletion query and returns how many vertices were deleted.
func (_d *PermissionDelete) Exec(ctx context.Context) (int, error) {
return withHooks(ctx, _d.sqlExec, _d.mutation, _d.hooks)
}
// ExecX is like Exec, but panics if an error occurs.
func (_d *PermissionDelete) ExecX(ctx context.Context) int {
n, err := _d.Exec(ctx)
if err != nil {
panic(err)
}
return n
}
func (_d *PermissionDelete) sqlExec(ctx context.Context) (int, error) {
_spec := sqlgraph.NewDeleteSpec(permission.Table, sqlgraph.NewFieldSpec(permission.FieldID, field.TypeString))
if ps := _d.mutation.predicates; len(ps) > 0 {
_spec.Predicate = func(selector *sql.Selector) {
for i := range ps {
ps[i](selector)
}
}
}
affected, err := sqlgraph.DeleteNodes(ctx, _d.driver, _spec)
if err != nil && sqlgraph.IsConstraintError(err) {
err = &ConstraintError{msg: err.Error(), wrap: err}
}
_d.mutation.done = true
return affected, err
}
// PermissionDeleteOne is the builder for deleting a single Permission entity.
type PermissionDeleteOne struct {
_d *PermissionDelete
}
// Where appends a list predicates to the PermissionDelete builder.
func (_d *PermissionDeleteOne) Where(ps ...predicate.Permission) *PermissionDeleteOne {
_d._d.mutation.Where(ps...)
return _d
}
// Exec executes the deletion query.
func (_d *PermissionDeleteOne) Exec(ctx context.Context) error {
n, err := _d._d.Exec(ctx)
switch {
case err != nil:
return err
case n == 0:
return &NotFoundError{permission.Label}
default:
return nil
}
}
// ExecX is like Exec, but panics if an error occurs.
func (_d *PermissionDeleteOne) ExecX(ctx context.Context) {
if err := _d.Exec(ctx); err != nil {
panic(err)
}
}

View File

@@ -1,607 +0,0 @@
// Code generated by ent, DO NOT EDIT.
package ent
import (
"context"
"database/sql/driver"
"fmt"
"math"
"entgo.io/ent"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field"
"git.dcentral.systems/toolz/goplt/internal/ent/permission"
"git.dcentral.systems/toolz/goplt/internal/ent/predicate"
"git.dcentral.systems/toolz/goplt/internal/ent/rolepermission"
)
// PermissionQuery is the builder for querying Permission entities.
type PermissionQuery struct {
config
ctx *QueryContext
order []permission.OrderOption
inters []Interceptor
predicates []predicate.Permission
withRolePermissions *RolePermissionQuery
// intermediate query (i.e. traversal path).
sql *sql.Selector
path func(context.Context) (*sql.Selector, error)
}
// Where adds a new predicate for the PermissionQuery builder.
func (_q *PermissionQuery) Where(ps ...predicate.Permission) *PermissionQuery {
_q.predicates = append(_q.predicates, ps...)
return _q
}
// Limit the number of records to be returned by this query.
func (_q *PermissionQuery) Limit(limit int) *PermissionQuery {
_q.ctx.Limit = &limit
return _q
}
// Offset to start from.
func (_q *PermissionQuery) Offset(offset int) *PermissionQuery {
_q.ctx.Offset = &offset
return _q
}
// Unique configures the query builder to filter duplicate records on query.
// By default, unique is set to true, and can be disabled using this method.
func (_q *PermissionQuery) Unique(unique bool) *PermissionQuery {
_q.ctx.Unique = &unique
return _q
}
// Order specifies how the records should be ordered.
func (_q *PermissionQuery) Order(o ...permission.OrderOption) *PermissionQuery {
_q.order = append(_q.order, o...)
return _q
}
// QueryRolePermissions chains the current query on the "role_permissions" edge.
func (_q *PermissionQuery) QueryRolePermissions() *RolePermissionQuery {
query := (&RolePermissionClient{config: _q.config}).Query()
query.path = func(ctx context.Context) (fromU *sql.Selector, err error) {
if err := _q.prepareQuery(ctx); err != nil {
return nil, err
}
selector := _q.sqlQuery(ctx)
if err := selector.Err(); err != nil {
return nil, err
}
step := sqlgraph.NewStep(
sqlgraph.From(permission.Table, permission.FieldID, selector),
sqlgraph.To(rolepermission.Table, rolepermission.FieldID),
sqlgraph.Edge(sqlgraph.O2M, false, permission.RolePermissionsTable, permission.RolePermissionsColumn),
)
fromU = sqlgraph.SetNeighbors(_q.driver.Dialect(), step)
return fromU, nil
}
return query
}
// First returns the first Permission entity from the query.
// Returns a *NotFoundError when no Permission was found.
func (_q *PermissionQuery) First(ctx context.Context) (*Permission, error) {
nodes, err := _q.Limit(1).All(setContextOp(ctx, _q.ctx, ent.OpQueryFirst))
if err != nil {
return nil, err
}
if len(nodes) == 0 {
return nil, &NotFoundError{permission.Label}
}
return nodes[0], nil
}
// FirstX is like First, but panics if an error occurs.
func (_q *PermissionQuery) FirstX(ctx context.Context) *Permission {
node, err := _q.First(ctx)
if err != nil && !IsNotFound(err) {
panic(err)
}
return node
}
// FirstID returns the first Permission ID from the query.
// Returns a *NotFoundError when no Permission ID was found.
func (_q *PermissionQuery) FirstID(ctx context.Context) (id string, err error) {
var ids []string
if ids, err = _q.Limit(1).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryFirstID)); err != nil {
return
}
if len(ids) == 0 {
err = &NotFoundError{permission.Label}
return
}
return ids[0], nil
}
// FirstIDX is like FirstID, but panics if an error occurs.
func (_q *PermissionQuery) FirstIDX(ctx context.Context) string {
id, err := _q.FirstID(ctx)
if err != nil && !IsNotFound(err) {
panic(err)
}
return id
}
// Only returns a single Permission entity found by the query, ensuring it only returns one.
// Returns a *NotSingularError when more than one Permission entity is found.
// Returns a *NotFoundError when no Permission entities are found.
func (_q *PermissionQuery) Only(ctx context.Context) (*Permission, error) {
nodes, err := _q.Limit(2).All(setContextOp(ctx, _q.ctx, ent.OpQueryOnly))
if err != nil {
return nil, err
}
switch len(nodes) {
case 1:
return nodes[0], nil
case 0:
return nil, &NotFoundError{permission.Label}
default:
return nil, &NotSingularError{permission.Label}
}
}
// OnlyX is like Only, but panics if an error occurs.
func (_q *PermissionQuery) OnlyX(ctx context.Context) *Permission {
node, err := _q.Only(ctx)
if err != nil {
panic(err)
}
return node
}
// OnlyID is like Only, but returns the only Permission ID in the query.
// Returns a *NotSingularError when more than one Permission ID is found.
// Returns a *NotFoundError when no entities are found.
func (_q *PermissionQuery) OnlyID(ctx context.Context) (id string, err error) {
var ids []string
if ids, err = _q.Limit(2).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryOnlyID)); err != nil {
return
}
switch len(ids) {
case 1:
id = ids[0]
case 0:
err = &NotFoundError{permission.Label}
default:
err = &NotSingularError{permission.Label}
}
return
}
// OnlyIDX is like OnlyID, but panics if an error occurs.
func (_q *PermissionQuery) OnlyIDX(ctx context.Context) string {
id, err := _q.OnlyID(ctx)
if err != nil {
panic(err)
}
return id
}
// All executes the query and returns a list of Permissions.
func (_q *PermissionQuery) All(ctx context.Context) ([]*Permission, error) {
ctx = setContextOp(ctx, _q.ctx, ent.OpQueryAll)
if err := _q.prepareQuery(ctx); err != nil {
return nil, err
}
qr := querierAll[[]*Permission, *PermissionQuery]()
return withInterceptors[[]*Permission](ctx, _q, qr, _q.inters)
}
// AllX is like All, but panics if an error occurs.
func (_q *PermissionQuery) AllX(ctx context.Context) []*Permission {
nodes, err := _q.All(ctx)
if err != nil {
panic(err)
}
return nodes
}
// IDs executes the query and returns a list of Permission IDs.
func (_q *PermissionQuery) IDs(ctx context.Context) (ids []string, err error) {
if _q.ctx.Unique == nil && _q.path != nil {
_q.Unique(true)
}
ctx = setContextOp(ctx, _q.ctx, ent.OpQueryIDs)
if err = _q.Select(permission.FieldID).Scan(ctx, &ids); err != nil {
return nil, err
}
return ids, nil
}
// IDsX is like IDs, but panics if an error occurs.
func (_q *PermissionQuery) IDsX(ctx context.Context) []string {
ids, err := _q.IDs(ctx)
if err != nil {
panic(err)
}
return ids
}
// Count returns the count of the given query.
func (_q *PermissionQuery) Count(ctx context.Context) (int, error) {
ctx = setContextOp(ctx, _q.ctx, ent.OpQueryCount)
if err := _q.prepareQuery(ctx); err != nil {
return 0, err
}
return withInterceptors[int](ctx, _q, querierCount[*PermissionQuery](), _q.inters)
}
// CountX is like Count, but panics if an error occurs.
func (_q *PermissionQuery) CountX(ctx context.Context) int {
count, err := _q.Count(ctx)
if err != nil {
panic(err)
}
return count
}
// Exist returns true if the query has elements in the graph.
func (_q *PermissionQuery) Exist(ctx context.Context) (bool, error) {
ctx = setContextOp(ctx, _q.ctx, ent.OpQueryExist)
switch _, err := _q.FirstID(ctx); {
case IsNotFound(err):
return false, nil
case err != nil:
return false, fmt.Errorf("ent: check existence: %w", err)
default:
return true, nil
}
}
// ExistX is like Exist, but panics if an error occurs.
func (_q *PermissionQuery) ExistX(ctx context.Context) bool {
exist, err := _q.Exist(ctx)
if err != nil {
panic(err)
}
return exist
}
// Clone returns a duplicate of the PermissionQuery builder, including all associated steps. It can be
// used to prepare common query builders and use them differently after the clone is made.
func (_q *PermissionQuery) Clone() *PermissionQuery {
if _q == nil {
return nil
}
return &PermissionQuery{
config: _q.config,
ctx: _q.ctx.Clone(),
order: append([]permission.OrderOption{}, _q.order...),
inters: append([]Interceptor{}, _q.inters...),
predicates: append([]predicate.Permission{}, _q.predicates...),
withRolePermissions: _q.withRolePermissions.Clone(),
// clone intermediate query.
sql: _q.sql.Clone(),
path: _q.path,
}
}
// WithRolePermissions tells the query-builder to eager-load the nodes that are connected to
// the "role_permissions" edge. The optional arguments are used to configure the query builder of the edge.
func (_q *PermissionQuery) WithRolePermissions(opts ...func(*RolePermissionQuery)) *PermissionQuery {
query := (&RolePermissionClient{config: _q.config}).Query()
for _, opt := range opts {
opt(query)
}
_q.withRolePermissions = query
return _q
}
// GroupBy is used to group vertices by one or more fields/columns.
// It is often used with aggregate functions, like: count, max, mean, min, sum.
//
// Example:
//
// var v []struct {
// Name string `json:"name,omitempty"`
// Count int `json:"count,omitempty"`
// }
//
// client.Permission.Query().
// GroupBy(permission.FieldName).
// Aggregate(ent.Count()).
// Scan(ctx, &v)
func (_q *PermissionQuery) GroupBy(field string, fields ...string) *PermissionGroupBy {
_q.ctx.Fields = append([]string{field}, fields...)
grbuild := &PermissionGroupBy{build: _q}
grbuild.flds = &_q.ctx.Fields
grbuild.label = permission.Label
grbuild.scan = grbuild.Scan
return grbuild
}
// Select allows the selection one or more fields/columns for the given query,
// instead of selecting all fields in the entity.
//
// Example:
//
// var v []struct {
// Name string `json:"name,omitempty"`
// }
//
// client.Permission.Query().
// Select(permission.FieldName).
// Scan(ctx, &v)
func (_q *PermissionQuery) Select(fields ...string) *PermissionSelect {
_q.ctx.Fields = append(_q.ctx.Fields, fields...)
sbuild := &PermissionSelect{PermissionQuery: _q}
sbuild.label = permission.Label
sbuild.flds, sbuild.scan = &_q.ctx.Fields, sbuild.Scan
return sbuild
}
// Aggregate returns a PermissionSelect configured with the given aggregations.
func (_q *PermissionQuery) Aggregate(fns ...AggregateFunc) *PermissionSelect {
return _q.Select().Aggregate(fns...)
}
func (_q *PermissionQuery) prepareQuery(ctx context.Context) error {
for _, inter := range _q.inters {
if inter == nil {
return fmt.Errorf("ent: uninitialized interceptor (forgotten import ent/runtime?)")
}
if trv, ok := inter.(Traverser); ok {
if err := trv.Traverse(ctx, _q); err != nil {
return err
}
}
}
for _, f := range _q.ctx.Fields {
if !permission.ValidColumn(f) {
return &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)}
}
}
if _q.path != nil {
prev, err := _q.path(ctx)
if err != nil {
return err
}
_q.sql = prev
}
return nil
}
func (_q *PermissionQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*Permission, error) {
var (
nodes = []*Permission{}
_spec = _q.querySpec()
loadedTypes = [1]bool{
_q.withRolePermissions != nil,
}
)
_spec.ScanValues = func(columns []string) ([]any, error) {
return (*Permission).scanValues(nil, columns)
}
_spec.Assign = func(columns []string, values []any) error {
node := &Permission{config: _q.config}
nodes = append(nodes, node)
node.Edges.loadedTypes = loadedTypes
return node.assignValues(columns, values)
}
for i := range hooks {
hooks[i](ctx, _spec)
}
if err := sqlgraph.QueryNodes(ctx, _q.driver, _spec); err != nil {
return nil, err
}
if len(nodes) == 0 {
return nodes, nil
}
if query := _q.withRolePermissions; query != nil {
if err := _q.loadRolePermissions(ctx, query, nodes,
func(n *Permission) { n.Edges.RolePermissions = []*RolePermission{} },
func(n *Permission, e *RolePermission) { n.Edges.RolePermissions = append(n.Edges.RolePermissions, e) }); err != nil {
return nil, err
}
}
return nodes, nil
}
func (_q *PermissionQuery) loadRolePermissions(ctx context.Context, query *RolePermissionQuery, nodes []*Permission, init func(*Permission), assign func(*Permission, *RolePermission)) error {
fks := make([]driver.Value, 0, len(nodes))
nodeids := make(map[string]*Permission)
for i := range nodes {
fks = append(fks, nodes[i].ID)
nodeids[nodes[i].ID] = nodes[i]
if init != nil {
init(nodes[i])
}
}
query.withFKs = true
query.Where(predicate.RolePermission(func(s *sql.Selector) {
s.Where(sql.InValues(s.C(permission.RolePermissionsColumn), fks...))
}))
neighbors, err := query.All(ctx)
if err != nil {
return err
}
for _, n := range neighbors {
fk := n.permission_role_permissions
if fk == nil {
return fmt.Errorf(`foreign-key "permission_role_permissions" is nil for node %v`, n.ID)
}
node, ok := nodeids[*fk]
if !ok {
return fmt.Errorf(`unexpected referenced foreign-key "permission_role_permissions" returned %v for node %v`, *fk, n.ID)
}
assign(node, n)
}
return nil
}
func (_q *PermissionQuery) sqlCount(ctx context.Context) (int, error) {
_spec := _q.querySpec()
_spec.Node.Columns = _q.ctx.Fields
if len(_q.ctx.Fields) > 0 {
_spec.Unique = _q.ctx.Unique != nil && *_q.ctx.Unique
}
return sqlgraph.CountNodes(ctx, _q.driver, _spec)
}
func (_q *PermissionQuery) querySpec() *sqlgraph.QuerySpec {
_spec := sqlgraph.NewQuerySpec(permission.Table, permission.Columns, sqlgraph.NewFieldSpec(permission.FieldID, field.TypeString))
_spec.From = _q.sql
if unique := _q.ctx.Unique; unique != nil {
_spec.Unique = *unique
} else if _q.path != nil {
_spec.Unique = true
}
if fields := _q.ctx.Fields; len(fields) > 0 {
_spec.Node.Columns = make([]string, 0, len(fields))
_spec.Node.Columns = append(_spec.Node.Columns, permission.FieldID)
for i := range fields {
if fields[i] != permission.FieldID {
_spec.Node.Columns = append(_spec.Node.Columns, fields[i])
}
}
}
if ps := _q.predicates; len(ps) > 0 {
_spec.Predicate = func(selector *sql.Selector) {
for i := range ps {
ps[i](selector)
}
}
}
if limit := _q.ctx.Limit; limit != nil {
_spec.Limit = *limit
}
if offset := _q.ctx.Offset; offset != nil {
_spec.Offset = *offset
}
if ps := _q.order; len(ps) > 0 {
_spec.Order = func(selector *sql.Selector) {
for i := range ps {
ps[i](selector)
}
}
}
return _spec
}
func (_q *PermissionQuery) sqlQuery(ctx context.Context) *sql.Selector {
builder := sql.Dialect(_q.driver.Dialect())
t1 := builder.Table(permission.Table)
columns := _q.ctx.Fields
if len(columns) == 0 {
columns = permission.Columns
}
selector := builder.Select(t1.Columns(columns...)...).From(t1)
if _q.sql != nil {
selector = _q.sql
selector.Select(selector.Columns(columns...)...)
}
if _q.ctx.Unique != nil && *_q.ctx.Unique {
selector.Distinct()
}
for _, p := range _q.predicates {
p(selector)
}
for _, p := range _q.order {
p(selector)
}
if offset := _q.ctx.Offset; offset != nil {
// limit is mandatory for offset clause. We start
// with default value, and override it below if needed.
selector.Offset(*offset).Limit(math.MaxInt32)
}
if limit := _q.ctx.Limit; limit != nil {
selector.Limit(*limit)
}
return selector
}
// PermissionGroupBy is the group-by builder for Permission entities.
type PermissionGroupBy struct {
selector
build *PermissionQuery
}
// Aggregate adds the given aggregation functions to the group-by query.
func (_g *PermissionGroupBy) Aggregate(fns ...AggregateFunc) *PermissionGroupBy {
_g.fns = append(_g.fns, fns...)
return _g
}
// Scan applies the selector query and scans the result into the given value.
func (_g *PermissionGroupBy) Scan(ctx context.Context, v any) error {
ctx = setContextOp(ctx, _g.build.ctx, ent.OpQueryGroupBy)
if err := _g.build.prepareQuery(ctx); err != nil {
return err
}
return scanWithInterceptors[*PermissionQuery, *PermissionGroupBy](ctx, _g.build, _g, _g.build.inters, v)
}
func (_g *PermissionGroupBy) sqlScan(ctx context.Context, root *PermissionQuery, v any) error {
selector := root.sqlQuery(ctx).Select()
aggregation := make([]string, 0, len(_g.fns))
for _, fn := range _g.fns {
aggregation = append(aggregation, fn(selector))
}
if len(selector.SelectedColumns()) == 0 {
columns := make([]string, 0, len(*_g.flds)+len(_g.fns))
for _, f := range *_g.flds {
columns = append(columns, selector.C(f))
}
columns = append(columns, aggregation...)
selector.Select(columns...)
}
selector.GroupBy(selector.Columns(*_g.flds...)...)
if err := selector.Err(); err != nil {
return err
}
rows := &sql.Rows{}
query, args := selector.Query()
if err := _g.build.driver.Query(ctx, query, args, rows); err != nil {
return err
}
defer rows.Close()
return sql.ScanSlice(rows, v)
}
// PermissionSelect is the builder for selecting fields of Permission entities.
type PermissionSelect struct {
*PermissionQuery
selector
}
// Aggregate adds the given aggregation functions to the selector query.
func (_s *PermissionSelect) Aggregate(fns ...AggregateFunc) *PermissionSelect {
_s.fns = append(_s.fns, fns...)
return _s
}
// Scan applies the selector query and scans the result into the given value.
func (_s *PermissionSelect) Scan(ctx context.Context, v any) error {
ctx = setContextOp(ctx, _s.ctx, ent.OpQuerySelect)
if err := _s.prepareQuery(ctx); err != nil {
return err
}
return scanWithInterceptors[*PermissionQuery, *PermissionSelect](ctx, _s.PermissionQuery, _s, _s.inters, v)
}
func (_s *PermissionSelect) sqlScan(ctx context.Context, root *PermissionQuery, v any) error {
selector := root.sqlQuery(ctx)
aggregation := make([]string, 0, len(_s.fns))
for _, fn := range _s.fns {
aggregation = append(aggregation, fn(selector))
}
switch n := len(*_s.selector.flds); {
case n == 0 && len(aggregation) > 0:
selector.Select(aggregation...)
case n != 0 && len(aggregation) > 0:
selector.AppendSelect(aggregation...)
}
rows := &sql.Rows{}
query, args := selector.Query()
if err := _s.driver.Query(ctx, query, args, rows); err != nil {
return err
}
defer rows.Close()
return sql.ScanSlice(rows, v)
}

View File

@@ -1,398 +0,0 @@
// Code generated by ent, DO NOT EDIT.
package ent
import (
"context"
"errors"
"fmt"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field"
"git.dcentral.systems/toolz/goplt/internal/ent/permission"
"git.dcentral.systems/toolz/goplt/internal/ent/predicate"
"git.dcentral.systems/toolz/goplt/internal/ent/rolepermission"
)
// PermissionUpdate is the builder for updating Permission entities.
type PermissionUpdate struct {
config
hooks []Hook
mutation *PermissionMutation
}
// Where appends a list predicates to the PermissionUpdate builder.
func (_u *PermissionUpdate) Where(ps ...predicate.Permission) *PermissionUpdate {
_u.mutation.Where(ps...)
return _u
}
// SetName sets the "name" field.
func (_u *PermissionUpdate) SetName(v string) *PermissionUpdate {
_u.mutation.SetName(v)
return _u
}
// SetNillableName sets the "name" field if the given value is not nil.
func (_u *PermissionUpdate) SetNillableName(v *string) *PermissionUpdate {
if v != nil {
_u.SetName(*v)
}
return _u
}
// AddRolePermissionIDs adds the "role_permissions" edge to the RolePermission entity by IDs.
func (_u *PermissionUpdate) AddRolePermissionIDs(ids ...int) *PermissionUpdate {
_u.mutation.AddRolePermissionIDs(ids...)
return _u
}
// AddRolePermissions adds the "role_permissions" edges to the RolePermission entity.
func (_u *PermissionUpdate) AddRolePermissions(v ...*RolePermission) *PermissionUpdate {
ids := make([]int, len(v))
for i := range v {
ids[i] = v[i].ID
}
return _u.AddRolePermissionIDs(ids...)
}
// Mutation returns the PermissionMutation object of the builder.
func (_u *PermissionUpdate) Mutation() *PermissionMutation {
return _u.mutation
}
// ClearRolePermissions clears all "role_permissions" edges to the RolePermission entity.
func (_u *PermissionUpdate) ClearRolePermissions() *PermissionUpdate {
_u.mutation.ClearRolePermissions()
return _u
}
// RemoveRolePermissionIDs removes the "role_permissions" edge to RolePermission entities by IDs.
func (_u *PermissionUpdate) RemoveRolePermissionIDs(ids ...int) *PermissionUpdate {
_u.mutation.RemoveRolePermissionIDs(ids...)
return _u
}
// RemoveRolePermissions removes "role_permissions" edges to RolePermission entities.
func (_u *PermissionUpdate) RemoveRolePermissions(v ...*RolePermission) *PermissionUpdate {
ids := make([]int, len(v))
for i := range v {
ids[i] = v[i].ID
}
return _u.RemoveRolePermissionIDs(ids...)
}
// Save executes the query and returns the number of nodes affected by the update operation.
func (_u *PermissionUpdate) Save(ctx context.Context) (int, error) {
return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks)
}
// SaveX is like Save, but panics if an error occurs.
func (_u *PermissionUpdate) SaveX(ctx context.Context) int {
affected, err := _u.Save(ctx)
if err != nil {
panic(err)
}
return affected
}
// Exec executes the query.
func (_u *PermissionUpdate) Exec(ctx context.Context) error {
_, err := _u.Save(ctx)
return err
}
// ExecX is like Exec, but panics if an error occurs.
func (_u *PermissionUpdate) ExecX(ctx context.Context) {
if err := _u.Exec(ctx); err != nil {
panic(err)
}
}
// check runs all checks and user-defined validators on the builder.
func (_u *PermissionUpdate) check() error {
if v, ok := _u.mutation.Name(); ok {
if err := permission.NameValidator(v); err != nil {
return &ValidationError{Name: "name", err: fmt.Errorf(`ent: validator failed for field "Permission.name": %w`, err)}
}
}
return nil
}
func (_u *PermissionUpdate) sqlSave(ctx context.Context) (_node int, err error) {
if err := _u.check(); err != nil {
return _node, err
}
_spec := sqlgraph.NewUpdateSpec(permission.Table, permission.Columns, sqlgraph.NewFieldSpec(permission.FieldID, field.TypeString))
if ps := _u.mutation.predicates; len(ps) > 0 {
_spec.Predicate = func(selector *sql.Selector) {
for i := range ps {
ps[i](selector)
}
}
}
if value, ok := _u.mutation.Name(); ok {
_spec.SetField(permission.FieldName, field.TypeString, value)
}
if _u.mutation.RolePermissionsCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: permission.RolePermissionsTable,
Columns: []string{permission.RolePermissionsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(rolepermission.FieldID, field.TypeInt),
},
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := _u.mutation.RemovedRolePermissionsIDs(); len(nodes) > 0 && !_u.mutation.RolePermissionsCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: permission.RolePermissionsTable,
Columns: []string{permission.RolePermissionsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(rolepermission.FieldID, field.TypeInt),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := _u.mutation.RolePermissionsIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: permission.RolePermissionsTable,
Columns: []string{permission.RolePermissionsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(rolepermission.FieldID, field.TypeInt),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges.Add = append(_spec.Edges.Add, edge)
}
if _node, err = sqlgraph.UpdateNodes(ctx, _u.driver, _spec); err != nil {
if _, ok := err.(*sqlgraph.NotFoundError); ok {
err = &NotFoundError{permission.Label}
} else if sqlgraph.IsConstraintError(err) {
err = &ConstraintError{msg: err.Error(), wrap: err}
}
return 0, err
}
_u.mutation.done = true
return _node, nil
}
// PermissionUpdateOne is the builder for updating a single Permission entity.
type PermissionUpdateOne struct {
config
fields []string
hooks []Hook
mutation *PermissionMutation
}
// SetName sets the "name" field.
func (_u *PermissionUpdateOne) SetName(v string) *PermissionUpdateOne {
_u.mutation.SetName(v)
return _u
}
// SetNillableName sets the "name" field if the given value is not nil.
func (_u *PermissionUpdateOne) SetNillableName(v *string) *PermissionUpdateOne {
if v != nil {
_u.SetName(*v)
}
return _u
}
// AddRolePermissionIDs adds the "role_permissions" edge to the RolePermission entity by IDs.
func (_u *PermissionUpdateOne) AddRolePermissionIDs(ids ...int) *PermissionUpdateOne {
_u.mutation.AddRolePermissionIDs(ids...)
return _u
}
// AddRolePermissions adds the "role_permissions" edges to the RolePermission entity.
func (_u *PermissionUpdateOne) AddRolePermissions(v ...*RolePermission) *PermissionUpdateOne {
ids := make([]int, len(v))
for i := range v {
ids[i] = v[i].ID
}
return _u.AddRolePermissionIDs(ids...)
}
// Mutation returns the PermissionMutation object of the builder.
func (_u *PermissionUpdateOne) Mutation() *PermissionMutation {
return _u.mutation
}
// ClearRolePermissions clears all "role_permissions" edges to the RolePermission entity.
func (_u *PermissionUpdateOne) ClearRolePermissions() *PermissionUpdateOne {
_u.mutation.ClearRolePermissions()
return _u
}
// RemoveRolePermissionIDs removes the "role_permissions" edge to RolePermission entities by IDs.
func (_u *PermissionUpdateOne) RemoveRolePermissionIDs(ids ...int) *PermissionUpdateOne {
_u.mutation.RemoveRolePermissionIDs(ids...)
return _u
}
// RemoveRolePermissions removes "role_permissions" edges to RolePermission entities.
func (_u *PermissionUpdateOne) RemoveRolePermissions(v ...*RolePermission) *PermissionUpdateOne {
ids := make([]int, len(v))
for i := range v {
ids[i] = v[i].ID
}
return _u.RemoveRolePermissionIDs(ids...)
}
// Where appends a list predicates to the PermissionUpdate builder.
func (_u *PermissionUpdateOne) Where(ps ...predicate.Permission) *PermissionUpdateOne {
_u.mutation.Where(ps...)
return _u
}
// Select allows selecting one or more fields (columns) of the returned entity.
// The default is selecting all fields defined in the entity schema.
func (_u *PermissionUpdateOne) Select(field string, fields ...string) *PermissionUpdateOne {
_u.fields = append([]string{field}, fields...)
return _u
}
// Save executes the query and returns the updated Permission entity.
func (_u *PermissionUpdateOne) Save(ctx context.Context) (*Permission, error) {
return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks)
}
// SaveX is like Save, but panics if an error occurs.
func (_u *PermissionUpdateOne) SaveX(ctx context.Context) *Permission {
node, err := _u.Save(ctx)
if err != nil {
panic(err)
}
return node
}
// Exec executes the query on the entity.
func (_u *PermissionUpdateOne) Exec(ctx context.Context) error {
_, err := _u.Save(ctx)
return err
}
// ExecX is like Exec, but panics if an error occurs.
func (_u *PermissionUpdateOne) ExecX(ctx context.Context) {
if err := _u.Exec(ctx); err != nil {
panic(err)
}
}
// check runs all checks and user-defined validators on the builder.
func (_u *PermissionUpdateOne) check() error {
if v, ok := _u.mutation.Name(); ok {
if err := permission.NameValidator(v); err != nil {
return &ValidationError{Name: "name", err: fmt.Errorf(`ent: validator failed for field "Permission.name": %w`, err)}
}
}
return nil
}
func (_u *PermissionUpdateOne) sqlSave(ctx context.Context) (_node *Permission, err error) {
if err := _u.check(); err != nil {
return _node, err
}
_spec := sqlgraph.NewUpdateSpec(permission.Table, permission.Columns, sqlgraph.NewFieldSpec(permission.FieldID, field.TypeString))
id, ok := _u.mutation.ID()
if !ok {
return nil, &ValidationError{Name: "id", err: errors.New(`ent: missing "Permission.id" for update`)}
}
_spec.Node.ID.Value = id
if fields := _u.fields; len(fields) > 0 {
_spec.Node.Columns = make([]string, 0, len(fields))
_spec.Node.Columns = append(_spec.Node.Columns, permission.FieldID)
for _, f := range fields {
if !permission.ValidColumn(f) {
return nil, &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)}
}
if f != permission.FieldID {
_spec.Node.Columns = append(_spec.Node.Columns, f)
}
}
}
if ps := _u.mutation.predicates; len(ps) > 0 {
_spec.Predicate = func(selector *sql.Selector) {
for i := range ps {
ps[i](selector)
}
}
}
if value, ok := _u.mutation.Name(); ok {
_spec.SetField(permission.FieldName, field.TypeString, value)
}
if _u.mutation.RolePermissionsCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: permission.RolePermissionsTable,
Columns: []string{permission.RolePermissionsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(rolepermission.FieldID, field.TypeInt),
},
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := _u.mutation.RemovedRolePermissionsIDs(); len(nodes) > 0 && !_u.mutation.RolePermissionsCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: permission.RolePermissionsTable,
Columns: []string{permission.RolePermissionsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(rolepermission.FieldID, field.TypeInt),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := _u.mutation.RolePermissionsIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: permission.RolePermissionsTable,
Columns: []string{permission.RolePermissionsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(rolepermission.FieldID, field.TypeInt),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges.Add = append(_spec.Edges.Add, edge)
}
_node = &Permission{config: _u.config}
_spec.Assign = _node.assignValues
_spec.ScanValues = _node.scanValues
if err = sqlgraph.UpdateNode(ctx, _u.driver, _spec); err != nil {
if _, ok := err.(*sqlgraph.NotFoundError); ok {
err = &NotFoundError{permission.Label}
} else if sqlgraph.IsConstraintError(err) {
err = &ConstraintError{msg: err.Error(), wrap: err}
}
return nil, err
}
_u.mutation.done = true
return _node, nil
}

View File

@@ -1,25 +0,0 @@
// Code generated by ent, DO NOT EDIT.
package predicate
import (
"entgo.io/ent/dialect/sql"
)
// AuditLog is the predicate function for auditlog builders.
type AuditLog func(*sql.Selector)
// Permission is the predicate function for permission builders.
type Permission func(*sql.Selector)
// Role is the predicate function for role builders.
type Role func(*sql.Selector)
// RolePermission is the predicate function for rolepermission builders.
type RolePermission func(*sql.Selector)
// User is the predicate function for user builders.
type User func(*sql.Selector)
// UserRole is the predicate function for userrole builders.
type UserRole func(*sql.Selector)

View File

@@ -1,168 +0,0 @@
// Code generated by ent, DO NOT EDIT.
package ent
import (
"fmt"
"strings"
"time"
"entgo.io/ent"
"entgo.io/ent/dialect/sql"
"git.dcentral.systems/toolz/goplt/internal/ent/role"
)
// Role is the model entity for the Role schema.
type Role struct {
config `json:"-"`
// ID of the ent.
ID string `json:"id,omitempty"`
// Name holds the value of the "name" field.
Name string `json:"name,omitempty"`
// Description holds the value of the "description" field.
Description string `json:"description,omitempty"`
// CreatedAt holds the value of the "created_at" field.
CreatedAt time.Time `json:"created_at,omitempty"`
// Edges holds the relations/edges for other nodes in the graph.
// The values are being populated by the RoleQuery when eager-loading is set.
Edges RoleEdges `json:"edges"`
selectValues sql.SelectValues
}
// RoleEdges holds the relations/edges for other nodes in the graph.
type RoleEdges struct {
// RolePermissions holds the value of the role_permissions edge.
RolePermissions []*RolePermission `json:"role_permissions,omitempty"`
// UserRoles holds the value of the user_roles edge.
UserRoles []*UserRole `json:"user_roles,omitempty"`
// loadedTypes holds the information for reporting if a
// type was loaded (or requested) in eager-loading or not.
loadedTypes [2]bool
}
// RolePermissionsOrErr returns the RolePermissions value or an error if the edge
// was not loaded in eager-loading.
func (e RoleEdges) RolePermissionsOrErr() ([]*RolePermission, error) {
if e.loadedTypes[0] {
return e.RolePermissions, nil
}
return nil, &NotLoadedError{edge: "role_permissions"}
}
// UserRolesOrErr returns the UserRoles value or an error if the edge
// was not loaded in eager-loading.
func (e RoleEdges) UserRolesOrErr() ([]*UserRole, error) {
if e.loadedTypes[1] {
return e.UserRoles, nil
}
return nil, &NotLoadedError{edge: "user_roles"}
}
// scanValues returns the types for scanning values from sql.Rows.
func (*Role) scanValues(columns []string) ([]any, error) {
values := make([]any, len(columns))
for i := range columns {
switch columns[i] {
case role.FieldID, role.FieldName, role.FieldDescription:
values[i] = new(sql.NullString)
case role.FieldCreatedAt:
values[i] = new(sql.NullTime)
default:
values[i] = new(sql.UnknownType)
}
}
return values, nil
}
// assignValues assigns the values that were returned from sql.Rows (after scanning)
// to the Role fields.
func (_m *Role) assignValues(columns []string, values []any) error {
if m, n := len(values), len(columns); m < n {
return fmt.Errorf("mismatch number of scan values: %d != %d", m, n)
}
for i := range columns {
switch columns[i] {
case role.FieldID:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field id", values[i])
} else if value.Valid {
_m.ID = value.String
}
case role.FieldName:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field name", values[i])
} else if value.Valid {
_m.Name = value.String
}
case role.FieldDescription:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field description", values[i])
} else if value.Valid {
_m.Description = value.String
}
case role.FieldCreatedAt:
if value, ok := values[i].(*sql.NullTime); !ok {
return fmt.Errorf("unexpected type %T for field created_at", values[i])
} else if value.Valid {
_m.CreatedAt = value.Time
}
default:
_m.selectValues.Set(columns[i], values[i])
}
}
return nil
}
// Value returns the ent.Value that was dynamically selected and assigned to the Role.
// This includes values selected through modifiers, order, etc.
func (_m *Role) Value(name string) (ent.Value, error) {
return _m.selectValues.Get(name)
}
// QueryRolePermissions queries the "role_permissions" edge of the Role entity.
func (_m *Role) QueryRolePermissions() *RolePermissionQuery {
return NewRoleClient(_m.config).QueryRolePermissions(_m)
}
// QueryUserRoles queries the "user_roles" edge of the Role entity.
func (_m *Role) QueryUserRoles() *UserRoleQuery {
return NewRoleClient(_m.config).QueryUserRoles(_m)
}
// Update returns a builder for updating this Role.
// Note that you need to call Role.Unwrap() before calling this method if this Role
// was returned from a transaction, and the transaction was committed or rolled back.
func (_m *Role) Update() *RoleUpdateOne {
return NewRoleClient(_m.config).UpdateOne(_m)
}
// Unwrap unwraps the Role entity that was returned from a transaction after it was closed,
// so that all future queries will be executed through the driver which created the transaction.
func (_m *Role) Unwrap() *Role {
_tx, ok := _m.config.driver.(*txDriver)
if !ok {
panic("ent: Role is not a transactional entity")
}
_m.config.driver = _tx.drv
return _m
}
// String implements the fmt.Stringer.
func (_m *Role) String() string {
var builder strings.Builder
builder.WriteString("Role(")
builder.WriteString(fmt.Sprintf("id=%v, ", _m.ID))
builder.WriteString("name=")
builder.WriteString(_m.Name)
builder.WriteString(", ")
builder.WriteString("description=")
builder.WriteString(_m.Description)
builder.WriteString(", ")
builder.WriteString("created_at=")
builder.WriteString(_m.CreatedAt.Format(time.ANSIC))
builder.WriteByte(')')
return builder.String()
}
// Roles is a parsable slice of Role.
type Roles []*Role

View File

@@ -1,133 +0,0 @@
// Code generated by ent, DO NOT EDIT.
package role
import (
"time"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
)
const (
// Label holds the string label denoting the role type in the database.
Label = "role"
// FieldID holds the string denoting the id field in the database.
FieldID = "id"
// FieldName holds the string denoting the name field in the database.
FieldName = "name"
// FieldDescription holds the string denoting the description field in the database.
FieldDescription = "description"
// FieldCreatedAt holds the string denoting the created_at field in the database.
FieldCreatedAt = "created_at"
// EdgeRolePermissions holds the string denoting the role_permissions edge name in mutations.
EdgeRolePermissions = "role_permissions"
// EdgeUserRoles holds the string denoting the user_roles edge name in mutations.
EdgeUserRoles = "user_roles"
// Table holds the table name of the role in the database.
Table = "roles"
// RolePermissionsTable is the table that holds the role_permissions relation/edge.
RolePermissionsTable = "role_permissions"
// RolePermissionsInverseTable is the table name for the RolePermission entity.
// It exists in this package in order to avoid circular dependency with the "rolepermission" package.
RolePermissionsInverseTable = "role_permissions"
// RolePermissionsColumn is the table column denoting the role_permissions relation/edge.
RolePermissionsColumn = "role_role_permissions"
// UserRolesTable is the table that holds the user_roles relation/edge.
UserRolesTable = "user_roles"
// UserRolesInverseTable is the table name for the UserRole entity.
// It exists in this package in order to avoid circular dependency with the "userrole" package.
UserRolesInverseTable = "user_roles"
// UserRolesColumn is the table column denoting the user_roles relation/edge.
UserRolesColumn = "role_user_roles"
)
// Columns holds all SQL columns for role fields.
var Columns = []string{
FieldID,
FieldName,
FieldDescription,
FieldCreatedAt,
}
// ValidColumn reports if the column name is valid (part of the table columns).
func ValidColumn(column string) bool {
for i := range Columns {
if column == Columns[i] {
return true
}
}
return false
}
var (
// NameValidator is a validator for the "name" field. It is called by the builders before save.
NameValidator func(string) error
// DefaultCreatedAt holds the default value on creation for the "created_at" field.
DefaultCreatedAt func() time.Time
)
// OrderOption defines the ordering options for the Role queries.
type OrderOption func(*sql.Selector)
// ByID orders the results by the id field.
func ByID(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldID, opts...).ToFunc()
}
// ByName orders the results by the name field.
func ByName(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldName, opts...).ToFunc()
}
// ByDescription orders the results by the description field.
func ByDescription(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldDescription, opts...).ToFunc()
}
// ByCreatedAt orders the results by the created_at field.
func ByCreatedAt(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldCreatedAt, opts...).ToFunc()
}
// ByRolePermissionsCount orders the results by role_permissions count.
func ByRolePermissionsCount(opts ...sql.OrderTermOption) OrderOption {
return func(s *sql.Selector) {
sqlgraph.OrderByNeighborsCount(s, newRolePermissionsStep(), opts...)
}
}
// ByRolePermissions orders the results by role_permissions terms.
func ByRolePermissions(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption {
return func(s *sql.Selector) {
sqlgraph.OrderByNeighborTerms(s, newRolePermissionsStep(), append([]sql.OrderTerm{term}, terms...)...)
}
}
// ByUserRolesCount orders the results by user_roles count.
func ByUserRolesCount(opts ...sql.OrderTermOption) OrderOption {
return func(s *sql.Selector) {
sqlgraph.OrderByNeighborsCount(s, newUserRolesStep(), opts...)
}
}
// ByUserRoles orders the results by user_roles terms.
func ByUserRoles(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption {
return func(s *sql.Selector) {
sqlgraph.OrderByNeighborTerms(s, newUserRolesStep(), append([]sql.OrderTerm{term}, terms...)...)
}
}
func newRolePermissionsStep() *sqlgraph.Step {
return sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.To(RolePermissionsInverseTable, FieldID),
sqlgraph.Edge(sqlgraph.O2M, false, RolePermissionsTable, RolePermissionsColumn),
)
}
func newUserRolesStep() *sqlgraph.Step {
return sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.To(UserRolesInverseTable, FieldID),
sqlgraph.Edge(sqlgraph.O2M, false, UserRolesTable, UserRolesColumn),
)
}

View File

@@ -1,322 +0,0 @@
// Code generated by ent, DO NOT EDIT.
package role
import (
"time"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"git.dcentral.systems/toolz/goplt/internal/ent/predicate"
)
// ID filters vertices based on their ID field.
func ID(id string) predicate.Role {
return predicate.Role(sql.FieldEQ(FieldID, id))
}
// IDEQ applies the EQ predicate on the ID field.
func IDEQ(id string) predicate.Role {
return predicate.Role(sql.FieldEQ(FieldID, id))
}
// IDNEQ applies the NEQ predicate on the ID field.
func IDNEQ(id string) predicate.Role {
return predicate.Role(sql.FieldNEQ(FieldID, id))
}
// IDIn applies the In predicate on the ID field.
func IDIn(ids ...string) predicate.Role {
return predicate.Role(sql.FieldIn(FieldID, ids...))
}
// IDNotIn applies the NotIn predicate on the ID field.
func IDNotIn(ids ...string) predicate.Role {
return predicate.Role(sql.FieldNotIn(FieldID, ids...))
}
// IDGT applies the GT predicate on the ID field.
func IDGT(id string) predicate.Role {
return predicate.Role(sql.FieldGT(FieldID, id))
}
// IDGTE applies the GTE predicate on the ID field.
func IDGTE(id string) predicate.Role {
return predicate.Role(sql.FieldGTE(FieldID, id))
}
// IDLT applies the LT predicate on the ID field.
func IDLT(id string) predicate.Role {
return predicate.Role(sql.FieldLT(FieldID, id))
}
// IDLTE applies the LTE predicate on the ID field.
func IDLTE(id string) predicate.Role {
return predicate.Role(sql.FieldLTE(FieldID, id))
}
// IDEqualFold applies the EqualFold predicate on the ID field.
func IDEqualFold(id string) predicate.Role {
return predicate.Role(sql.FieldEqualFold(FieldID, id))
}
// IDContainsFold applies the ContainsFold predicate on the ID field.
func IDContainsFold(id string) predicate.Role {
return predicate.Role(sql.FieldContainsFold(FieldID, id))
}
// Name applies equality check predicate on the "name" field. It's identical to NameEQ.
func Name(v string) predicate.Role {
return predicate.Role(sql.FieldEQ(FieldName, v))
}
// Description applies equality check predicate on the "description" field. It's identical to DescriptionEQ.
func Description(v string) predicate.Role {
return predicate.Role(sql.FieldEQ(FieldDescription, v))
}
// CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ.
func CreatedAt(v time.Time) predicate.Role {
return predicate.Role(sql.FieldEQ(FieldCreatedAt, v))
}
// NameEQ applies the EQ predicate on the "name" field.
func NameEQ(v string) predicate.Role {
return predicate.Role(sql.FieldEQ(FieldName, v))
}
// NameNEQ applies the NEQ predicate on the "name" field.
func NameNEQ(v string) predicate.Role {
return predicate.Role(sql.FieldNEQ(FieldName, v))
}
// NameIn applies the In predicate on the "name" field.
func NameIn(vs ...string) predicate.Role {
return predicate.Role(sql.FieldIn(FieldName, vs...))
}
// NameNotIn applies the NotIn predicate on the "name" field.
func NameNotIn(vs ...string) predicate.Role {
return predicate.Role(sql.FieldNotIn(FieldName, vs...))
}
// NameGT applies the GT predicate on the "name" field.
func NameGT(v string) predicate.Role {
return predicate.Role(sql.FieldGT(FieldName, v))
}
// NameGTE applies the GTE predicate on the "name" field.
func NameGTE(v string) predicate.Role {
return predicate.Role(sql.FieldGTE(FieldName, v))
}
// NameLT applies the LT predicate on the "name" field.
func NameLT(v string) predicate.Role {
return predicate.Role(sql.FieldLT(FieldName, v))
}
// NameLTE applies the LTE predicate on the "name" field.
func NameLTE(v string) predicate.Role {
return predicate.Role(sql.FieldLTE(FieldName, v))
}
// NameContains applies the Contains predicate on the "name" field.
func NameContains(v string) predicate.Role {
return predicate.Role(sql.FieldContains(FieldName, v))
}
// NameHasPrefix applies the HasPrefix predicate on the "name" field.
func NameHasPrefix(v string) predicate.Role {
return predicate.Role(sql.FieldHasPrefix(FieldName, v))
}
// NameHasSuffix applies the HasSuffix predicate on the "name" field.
func NameHasSuffix(v string) predicate.Role {
return predicate.Role(sql.FieldHasSuffix(FieldName, v))
}
// NameEqualFold applies the EqualFold predicate on the "name" field.
func NameEqualFold(v string) predicate.Role {
return predicate.Role(sql.FieldEqualFold(FieldName, v))
}
// NameContainsFold applies the ContainsFold predicate on the "name" field.
func NameContainsFold(v string) predicate.Role {
return predicate.Role(sql.FieldContainsFold(FieldName, v))
}
// DescriptionEQ applies the EQ predicate on the "description" field.
func DescriptionEQ(v string) predicate.Role {
return predicate.Role(sql.FieldEQ(FieldDescription, v))
}
// DescriptionNEQ applies the NEQ predicate on the "description" field.
func DescriptionNEQ(v string) predicate.Role {
return predicate.Role(sql.FieldNEQ(FieldDescription, v))
}
// DescriptionIn applies the In predicate on the "description" field.
func DescriptionIn(vs ...string) predicate.Role {
return predicate.Role(sql.FieldIn(FieldDescription, vs...))
}
// DescriptionNotIn applies the NotIn predicate on the "description" field.
func DescriptionNotIn(vs ...string) predicate.Role {
return predicate.Role(sql.FieldNotIn(FieldDescription, vs...))
}
// DescriptionGT applies the GT predicate on the "description" field.
func DescriptionGT(v string) predicate.Role {
return predicate.Role(sql.FieldGT(FieldDescription, v))
}
// DescriptionGTE applies the GTE predicate on the "description" field.
func DescriptionGTE(v string) predicate.Role {
return predicate.Role(sql.FieldGTE(FieldDescription, v))
}
// DescriptionLT applies the LT predicate on the "description" field.
func DescriptionLT(v string) predicate.Role {
return predicate.Role(sql.FieldLT(FieldDescription, v))
}
// DescriptionLTE applies the LTE predicate on the "description" field.
func DescriptionLTE(v string) predicate.Role {
return predicate.Role(sql.FieldLTE(FieldDescription, v))
}
// DescriptionContains applies the Contains predicate on the "description" field.
func DescriptionContains(v string) predicate.Role {
return predicate.Role(sql.FieldContains(FieldDescription, v))
}
// DescriptionHasPrefix applies the HasPrefix predicate on the "description" field.
func DescriptionHasPrefix(v string) predicate.Role {
return predicate.Role(sql.FieldHasPrefix(FieldDescription, v))
}
// DescriptionHasSuffix applies the HasSuffix predicate on the "description" field.
func DescriptionHasSuffix(v string) predicate.Role {
return predicate.Role(sql.FieldHasSuffix(FieldDescription, v))
}
// DescriptionIsNil applies the IsNil predicate on the "description" field.
func DescriptionIsNil() predicate.Role {
return predicate.Role(sql.FieldIsNull(FieldDescription))
}
// DescriptionNotNil applies the NotNil predicate on the "description" field.
func DescriptionNotNil() predicate.Role {
return predicate.Role(sql.FieldNotNull(FieldDescription))
}
// DescriptionEqualFold applies the EqualFold predicate on the "description" field.
func DescriptionEqualFold(v string) predicate.Role {
return predicate.Role(sql.FieldEqualFold(FieldDescription, v))
}
// DescriptionContainsFold applies the ContainsFold predicate on the "description" field.
func DescriptionContainsFold(v string) predicate.Role {
return predicate.Role(sql.FieldContainsFold(FieldDescription, v))
}
// CreatedAtEQ applies the EQ predicate on the "created_at" field.
func CreatedAtEQ(v time.Time) predicate.Role {
return predicate.Role(sql.FieldEQ(FieldCreatedAt, v))
}
// CreatedAtNEQ applies the NEQ predicate on the "created_at" field.
func CreatedAtNEQ(v time.Time) predicate.Role {
return predicate.Role(sql.FieldNEQ(FieldCreatedAt, v))
}
// CreatedAtIn applies the In predicate on the "created_at" field.
func CreatedAtIn(vs ...time.Time) predicate.Role {
return predicate.Role(sql.FieldIn(FieldCreatedAt, vs...))
}
// CreatedAtNotIn applies the NotIn predicate on the "created_at" field.
func CreatedAtNotIn(vs ...time.Time) predicate.Role {
return predicate.Role(sql.FieldNotIn(FieldCreatedAt, vs...))
}
// CreatedAtGT applies the GT predicate on the "created_at" field.
func CreatedAtGT(v time.Time) predicate.Role {
return predicate.Role(sql.FieldGT(FieldCreatedAt, v))
}
// CreatedAtGTE applies the GTE predicate on the "created_at" field.
func CreatedAtGTE(v time.Time) predicate.Role {
return predicate.Role(sql.FieldGTE(FieldCreatedAt, v))
}
// CreatedAtLT applies the LT predicate on the "created_at" field.
func CreatedAtLT(v time.Time) predicate.Role {
return predicate.Role(sql.FieldLT(FieldCreatedAt, v))
}
// CreatedAtLTE applies the LTE predicate on the "created_at" field.
func CreatedAtLTE(v time.Time) predicate.Role {
return predicate.Role(sql.FieldLTE(FieldCreatedAt, v))
}
// HasRolePermissions applies the HasEdge predicate on the "role_permissions" edge.
func HasRolePermissions() predicate.Role {
return predicate.Role(func(s *sql.Selector) {
step := sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.Edge(sqlgraph.O2M, false, RolePermissionsTable, RolePermissionsColumn),
)
sqlgraph.HasNeighbors(s, step)
})
}
// HasRolePermissionsWith applies the HasEdge predicate on the "role_permissions" edge with a given conditions (other predicates).
func HasRolePermissionsWith(preds ...predicate.RolePermission) predicate.Role {
return predicate.Role(func(s *sql.Selector) {
step := newRolePermissionsStep()
sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) {
for _, p := range preds {
p(s)
}
})
})
}
// HasUserRoles applies the HasEdge predicate on the "user_roles" edge.
func HasUserRoles() predicate.Role {
return predicate.Role(func(s *sql.Selector) {
step := sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.Edge(sqlgraph.O2M, false, UserRolesTable, UserRolesColumn),
)
sqlgraph.HasNeighbors(s, step)
})
}
// HasUserRolesWith applies the HasEdge predicate on the "user_roles" edge with a given conditions (other predicates).
func HasUserRolesWith(preds ...predicate.UserRole) predicate.Role {
return predicate.Role(func(s *sql.Selector) {
step := newUserRolesStep()
sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) {
for _, p := range preds {
p(s)
}
})
})
}
// And groups predicates with the AND operator between them.
func And(predicates ...predicate.Role) predicate.Role {
return predicate.Role(sql.AndPredicates(predicates...))
}
// Or groups predicates with the OR operator between them.
func Or(predicates ...predicate.Role) predicate.Role {
return predicate.Role(sql.OrPredicates(predicates...))
}
// Not applies the not operator on the given predicate.
func Not(p predicate.Role) predicate.Role {
return predicate.Role(sql.NotPredicates(p))
}

View File

@@ -1,313 +0,0 @@
// Code generated by ent, DO NOT EDIT.
package ent
import (
"context"
"errors"
"fmt"
"time"
"entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field"
"git.dcentral.systems/toolz/goplt/internal/ent/role"
"git.dcentral.systems/toolz/goplt/internal/ent/rolepermission"
"git.dcentral.systems/toolz/goplt/internal/ent/userrole"
)
// RoleCreate is the builder for creating a Role entity.
type RoleCreate struct {
config
mutation *RoleMutation
hooks []Hook
}
// SetName sets the "name" field.
func (_c *RoleCreate) SetName(v string) *RoleCreate {
_c.mutation.SetName(v)
return _c
}
// SetDescription sets the "description" field.
func (_c *RoleCreate) SetDescription(v string) *RoleCreate {
_c.mutation.SetDescription(v)
return _c
}
// SetNillableDescription sets the "description" field if the given value is not nil.
func (_c *RoleCreate) SetNillableDescription(v *string) *RoleCreate {
if v != nil {
_c.SetDescription(*v)
}
return _c
}
// SetCreatedAt sets the "created_at" field.
func (_c *RoleCreate) SetCreatedAt(v time.Time) *RoleCreate {
_c.mutation.SetCreatedAt(v)
return _c
}
// SetNillableCreatedAt sets the "created_at" field if the given value is not nil.
func (_c *RoleCreate) SetNillableCreatedAt(v *time.Time) *RoleCreate {
if v != nil {
_c.SetCreatedAt(*v)
}
return _c
}
// SetID sets the "id" field.
func (_c *RoleCreate) SetID(v string) *RoleCreate {
_c.mutation.SetID(v)
return _c
}
// AddRolePermissionIDs adds the "role_permissions" edge to the RolePermission entity by IDs.
func (_c *RoleCreate) AddRolePermissionIDs(ids ...int) *RoleCreate {
_c.mutation.AddRolePermissionIDs(ids...)
return _c
}
// AddRolePermissions adds the "role_permissions" edges to the RolePermission entity.
func (_c *RoleCreate) AddRolePermissions(v ...*RolePermission) *RoleCreate {
ids := make([]int, len(v))
for i := range v {
ids[i] = v[i].ID
}
return _c.AddRolePermissionIDs(ids...)
}
// AddUserRoleIDs adds the "user_roles" edge to the UserRole entity by IDs.
func (_c *RoleCreate) AddUserRoleIDs(ids ...int) *RoleCreate {
_c.mutation.AddUserRoleIDs(ids...)
return _c
}
// AddUserRoles adds the "user_roles" edges to the UserRole entity.
func (_c *RoleCreate) AddUserRoles(v ...*UserRole) *RoleCreate {
ids := make([]int, len(v))
for i := range v {
ids[i] = v[i].ID
}
return _c.AddUserRoleIDs(ids...)
}
// Mutation returns the RoleMutation object of the builder.
func (_c *RoleCreate) Mutation() *RoleMutation {
return _c.mutation
}
// Save creates the Role in the database.
func (_c *RoleCreate) Save(ctx context.Context) (*Role, error) {
_c.defaults()
return withHooks(ctx, _c.sqlSave, _c.mutation, _c.hooks)
}
// SaveX calls Save and panics if Save returns an error.
func (_c *RoleCreate) SaveX(ctx context.Context) *Role {
v, err := _c.Save(ctx)
if err != nil {
panic(err)
}
return v
}
// Exec executes the query.
func (_c *RoleCreate) Exec(ctx context.Context) error {
_, err := _c.Save(ctx)
return err
}
// ExecX is like Exec, but panics if an error occurs.
func (_c *RoleCreate) ExecX(ctx context.Context) {
if err := _c.Exec(ctx); err != nil {
panic(err)
}
}
// defaults sets the default values of the builder before save.
func (_c *RoleCreate) defaults() {
if _, ok := _c.mutation.CreatedAt(); !ok {
v := role.DefaultCreatedAt()
_c.mutation.SetCreatedAt(v)
}
}
// check runs all checks and user-defined validators on the builder.
func (_c *RoleCreate) check() error {
if _, ok := _c.mutation.Name(); !ok {
return &ValidationError{Name: "name", err: errors.New(`ent: missing required field "Role.name"`)}
}
if v, ok := _c.mutation.Name(); ok {
if err := role.NameValidator(v); err != nil {
return &ValidationError{Name: "name", err: fmt.Errorf(`ent: validator failed for field "Role.name": %w`, err)}
}
}
if _, ok := _c.mutation.CreatedAt(); !ok {
return &ValidationError{Name: "created_at", err: errors.New(`ent: missing required field "Role.created_at"`)}
}
return nil
}
func (_c *RoleCreate) sqlSave(ctx context.Context) (*Role, error) {
if err := _c.check(); err != nil {
return nil, err
}
_node, _spec := _c.createSpec()
if err := sqlgraph.CreateNode(ctx, _c.driver, _spec); err != nil {
if sqlgraph.IsConstraintError(err) {
err = &ConstraintError{msg: err.Error(), wrap: err}
}
return nil, err
}
if _spec.ID.Value != nil {
if id, ok := _spec.ID.Value.(string); ok {
_node.ID = id
} else {
return nil, fmt.Errorf("unexpected Role.ID type: %T", _spec.ID.Value)
}
}
_c.mutation.id = &_node.ID
_c.mutation.done = true
return _node, nil
}
func (_c *RoleCreate) createSpec() (*Role, *sqlgraph.CreateSpec) {
var (
_node = &Role{config: _c.config}
_spec = sqlgraph.NewCreateSpec(role.Table, sqlgraph.NewFieldSpec(role.FieldID, field.TypeString))
)
if id, ok := _c.mutation.ID(); ok {
_node.ID = id
_spec.ID.Value = id
}
if value, ok := _c.mutation.Name(); ok {
_spec.SetField(role.FieldName, field.TypeString, value)
_node.Name = value
}
if value, ok := _c.mutation.Description(); ok {
_spec.SetField(role.FieldDescription, field.TypeString, value)
_node.Description = value
}
if value, ok := _c.mutation.CreatedAt(); ok {
_spec.SetField(role.FieldCreatedAt, field.TypeTime, value)
_node.CreatedAt = value
}
if nodes := _c.mutation.RolePermissionsIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: role.RolePermissionsTable,
Columns: []string{role.RolePermissionsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(rolepermission.FieldID, field.TypeInt),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges = append(_spec.Edges, edge)
}
if nodes := _c.mutation.UserRolesIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: role.UserRolesTable,
Columns: []string{role.UserRolesColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(userrole.FieldID, field.TypeInt),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges = append(_spec.Edges, edge)
}
return _node, _spec
}
// RoleCreateBulk is the builder for creating many Role entities in bulk.
type RoleCreateBulk struct {
config
err error
builders []*RoleCreate
}
// Save creates the Role entities in the database.
func (_c *RoleCreateBulk) Save(ctx context.Context) ([]*Role, error) {
if _c.err != nil {
return nil, _c.err
}
specs := make([]*sqlgraph.CreateSpec, len(_c.builders))
nodes := make([]*Role, len(_c.builders))
mutators := make([]Mutator, len(_c.builders))
for i := range _c.builders {
func(i int, root context.Context) {
builder := _c.builders[i]
builder.defaults()
var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) {
mutation, ok := m.(*RoleMutation)
if !ok {
return nil, fmt.Errorf("unexpected mutation type %T", m)
}
if err := builder.check(); err != nil {
return nil, err
}
builder.mutation = mutation
var err error
nodes[i], specs[i] = builder.createSpec()
if i < len(mutators)-1 {
_, err = mutators[i+1].Mutate(root, _c.builders[i+1].mutation)
} else {
spec := &sqlgraph.BatchCreateSpec{Nodes: specs}
// Invoke the actual operation on the latest mutation in the chain.
if err = sqlgraph.BatchCreate(ctx, _c.driver, spec); err != nil {
if sqlgraph.IsConstraintError(err) {
err = &ConstraintError{msg: err.Error(), wrap: err}
}
}
}
if err != nil {
return nil, err
}
mutation.id = &nodes[i].ID
mutation.done = true
return nodes[i], nil
})
for i := len(builder.hooks) - 1; i >= 0; i-- {
mut = builder.hooks[i](mut)
}
mutators[i] = mut
}(i, ctx)
}
if len(mutators) > 0 {
if _, err := mutators[0].Mutate(ctx, _c.builders[0].mutation); err != nil {
return nil, err
}
}
return nodes, nil
}
// SaveX is like Save, but panics if an error occurs.
func (_c *RoleCreateBulk) SaveX(ctx context.Context) []*Role {
v, err := _c.Save(ctx)
if err != nil {
panic(err)
}
return v
}
// Exec executes the query.
func (_c *RoleCreateBulk) Exec(ctx context.Context) error {
_, err := _c.Save(ctx)
return err
}
// ExecX is like Exec, but panics if an error occurs.
func (_c *RoleCreateBulk) ExecX(ctx context.Context) {
if err := _c.Exec(ctx); err != nil {
panic(err)
}
}

View File

@@ -1,88 +0,0 @@
// Code generated by ent, DO NOT EDIT.
package ent
import (
"context"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field"
"git.dcentral.systems/toolz/goplt/internal/ent/predicate"
"git.dcentral.systems/toolz/goplt/internal/ent/role"
)
// RoleDelete is the builder for deleting a Role entity.
type RoleDelete struct {
config
hooks []Hook
mutation *RoleMutation
}
// Where appends a list predicates to the RoleDelete builder.
func (_d *RoleDelete) Where(ps ...predicate.Role) *RoleDelete {
_d.mutation.Where(ps...)
return _d
}
// Exec executes the deletion query and returns how many vertices were deleted.
func (_d *RoleDelete) Exec(ctx context.Context) (int, error) {
return withHooks(ctx, _d.sqlExec, _d.mutation, _d.hooks)
}
// ExecX is like Exec, but panics if an error occurs.
func (_d *RoleDelete) ExecX(ctx context.Context) int {
n, err := _d.Exec(ctx)
if err != nil {
panic(err)
}
return n
}
func (_d *RoleDelete) sqlExec(ctx context.Context) (int, error) {
_spec := sqlgraph.NewDeleteSpec(role.Table, sqlgraph.NewFieldSpec(role.FieldID, field.TypeString))
if ps := _d.mutation.predicates; len(ps) > 0 {
_spec.Predicate = func(selector *sql.Selector) {
for i := range ps {
ps[i](selector)
}
}
}
affected, err := sqlgraph.DeleteNodes(ctx, _d.driver, _spec)
if err != nil && sqlgraph.IsConstraintError(err) {
err = &ConstraintError{msg: err.Error(), wrap: err}
}
_d.mutation.done = true
return affected, err
}
// RoleDeleteOne is the builder for deleting a single Role entity.
type RoleDeleteOne struct {
_d *RoleDelete
}
// Where appends a list predicates to the RoleDelete builder.
func (_d *RoleDeleteOne) Where(ps ...predicate.Role) *RoleDeleteOne {
_d._d.mutation.Where(ps...)
return _d
}
// Exec executes the deletion query.
func (_d *RoleDeleteOne) Exec(ctx context.Context) error {
n, err := _d._d.Exec(ctx)
switch {
case err != nil:
return err
case n == 0:
return &NotFoundError{role.Label}
default:
return nil
}
}
// ExecX is like Exec, but panics if an error occurs.
func (_d *RoleDeleteOne) ExecX(ctx context.Context) {
if err := _d.Exec(ctx); err != nil {
panic(err)
}
}

View File

@@ -1,682 +0,0 @@
// Code generated by ent, DO NOT EDIT.
package ent
import (
"context"
"database/sql/driver"
"fmt"
"math"
"entgo.io/ent"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field"
"git.dcentral.systems/toolz/goplt/internal/ent/predicate"
"git.dcentral.systems/toolz/goplt/internal/ent/role"
"git.dcentral.systems/toolz/goplt/internal/ent/rolepermission"
"git.dcentral.systems/toolz/goplt/internal/ent/userrole"
)
// RoleQuery is the builder for querying Role entities.
type RoleQuery struct {
config
ctx *QueryContext
order []role.OrderOption
inters []Interceptor
predicates []predicate.Role
withRolePermissions *RolePermissionQuery
withUserRoles *UserRoleQuery
// intermediate query (i.e. traversal path).
sql *sql.Selector
path func(context.Context) (*sql.Selector, error)
}
// Where adds a new predicate for the RoleQuery builder.
func (_q *RoleQuery) Where(ps ...predicate.Role) *RoleQuery {
_q.predicates = append(_q.predicates, ps...)
return _q
}
// Limit the number of records to be returned by this query.
func (_q *RoleQuery) Limit(limit int) *RoleQuery {
_q.ctx.Limit = &limit
return _q
}
// Offset to start from.
func (_q *RoleQuery) Offset(offset int) *RoleQuery {
_q.ctx.Offset = &offset
return _q
}
// Unique configures the query builder to filter duplicate records on query.
// By default, unique is set to true, and can be disabled using this method.
func (_q *RoleQuery) Unique(unique bool) *RoleQuery {
_q.ctx.Unique = &unique
return _q
}
// Order specifies how the records should be ordered.
func (_q *RoleQuery) Order(o ...role.OrderOption) *RoleQuery {
_q.order = append(_q.order, o...)
return _q
}
// QueryRolePermissions chains the current query on the "role_permissions" edge.
func (_q *RoleQuery) QueryRolePermissions() *RolePermissionQuery {
query := (&RolePermissionClient{config: _q.config}).Query()
query.path = func(ctx context.Context) (fromU *sql.Selector, err error) {
if err := _q.prepareQuery(ctx); err != nil {
return nil, err
}
selector := _q.sqlQuery(ctx)
if err := selector.Err(); err != nil {
return nil, err
}
step := sqlgraph.NewStep(
sqlgraph.From(role.Table, role.FieldID, selector),
sqlgraph.To(rolepermission.Table, rolepermission.FieldID),
sqlgraph.Edge(sqlgraph.O2M, false, role.RolePermissionsTable, role.RolePermissionsColumn),
)
fromU = sqlgraph.SetNeighbors(_q.driver.Dialect(), step)
return fromU, nil
}
return query
}
// QueryUserRoles chains the current query on the "user_roles" edge.
func (_q *RoleQuery) QueryUserRoles() *UserRoleQuery {
query := (&UserRoleClient{config: _q.config}).Query()
query.path = func(ctx context.Context) (fromU *sql.Selector, err error) {
if err := _q.prepareQuery(ctx); err != nil {
return nil, err
}
selector := _q.sqlQuery(ctx)
if err := selector.Err(); err != nil {
return nil, err
}
step := sqlgraph.NewStep(
sqlgraph.From(role.Table, role.FieldID, selector),
sqlgraph.To(userrole.Table, userrole.FieldID),
sqlgraph.Edge(sqlgraph.O2M, false, role.UserRolesTable, role.UserRolesColumn),
)
fromU = sqlgraph.SetNeighbors(_q.driver.Dialect(), step)
return fromU, nil
}
return query
}
// First returns the first Role entity from the query.
// Returns a *NotFoundError when no Role was found.
func (_q *RoleQuery) First(ctx context.Context) (*Role, error) {
nodes, err := _q.Limit(1).All(setContextOp(ctx, _q.ctx, ent.OpQueryFirst))
if err != nil {
return nil, err
}
if len(nodes) == 0 {
return nil, &NotFoundError{role.Label}
}
return nodes[0], nil
}
// FirstX is like First, but panics if an error occurs.
func (_q *RoleQuery) FirstX(ctx context.Context) *Role {
node, err := _q.First(ctx)
if err != nil && !IsNotFound(err) {
panic(err)
}
return node
}
// FirstID returns the first Role ID from the query.
// Returns a *NotFoundError when no Role ID was found.
func (_q *RoleQuery) FirstID(ctx context.Context) (id string, err error) {
var ids []string
if ids, err = _q.Limit(1).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryFirstID)); err != nil {
return
}
if len(ids) == 0 {
err = &NotFoundError{role.Label}
return
}
return ids[0], nil
}
// FirstIDX is like FirstID, but panics if an error occurs.
func (_q *RoleQuery) FirstIDX(ctx context.Context) string {
id, err := _q.FirstID(ctx)
if err != nil && !IsNotFound(err) {
panic(err)
}
return id
}
// Only returns a single Role entity found by the query, ensuring it only returns one.
// Returns a *NotSingularError when more than one Role entity is found.
// Returns a *NotFoundError when no Role entities are found.
func (_q *RoleQuery) Only(ctx context.Context) (*Role, error) {
nodes, err := _q.Limit(2).All(setContextOp(ctx, _q.ctx, ent.OpQueryOnly))
if err != nil {
return nil, err
}
switch len(nodes) {
case 1:
return nodes[0], nil
case 0:
return nil, &NotFoundError{role.Label}
default:
return nil, &NotSingularError{role.Label}
}
}
// OnlyX is like Only, but panics if an error occurs.
func (_q *RoleQuery) OnlyX(ctx context.Context) *Role {
node, err := _q.Only(ctx)
if err != nil {
panic(err)
}
return node
}
// OnlyID is like Only, but returns the only Role ID in the query.
// Returns a *NotSingularError when more than one Role ID is found.
// Returns a *NotFoundError when no entities are found.
func (_q *RoleQuery) OnlyID(ctx context.Context) (id string, err error) {
var ids []string
if ids, err = _q.Limit(2).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryOnlyID)); err != nil {
return
}
switch len(ids) {
case 1:
id = ids[0]
case 0:
err = &NotFoundError{role.Label}
default:
err = &NotSingularError{role.Label}
}
return
}
// OnlyIDX is like OnlyID, but panics if an error occurs.
func (_q *RoleQuery) OnlyIDX(ctx context.Context) string {
id, err := _q.OnlyID(ctx)
if err != nil {
panic(err)
}
return id
}
// All executes the query and returns a list of Roles.
func (_q *RoleQuery) All(ctx context.Context) ([]*Role, error) {
ctx = setContextOp(ctx, _q.ctx, ent.OpQueryAll)
if err := _q.prepareQuery(ctx); err != nil {
return nil, err
}
qr := querierAll[[]*Role, *RoleQuery]()
return withInterceptors[[]*Role](ctx, _q, qr, _q.inters)
}
// AllX is like All, but panics if an error occurs.
func (_q *RoleQuery) AllX(ctx context.Context) []*Role {
nodes, err := _q.All(ctx)
if err != nil {
panic(err)
}
return nodes
}
// IDs executes the query and returns a list of Role IDs.
func (_q *RoleQuery) IDs(ctx context.Context) (ids []string, err error) {
if _q.ctx.Unique == nil && _q.path != nil {
_q.Unique(true)
}
ctx = setContextOp(ctx, _q.ctx, ent.OpQueryIDs)
if err = _q.Select(role.FieldID).Scan(ctx, &ids); err != nil {
return nil, err
}
return ids, nil
}
// IDsX is like IDs, but panics if an error occurs.
func (_q *RoleQuery) IDsX(ctx context.Context) []string {
ids, err := _q.IDs(ctx)
if err != nil {
panic(err)
}
return ids
}
// Count returns the count of the given query.
func (_q *RoleQuery) Count(ctx context.Context) (int, error) {
ctx = setContextOp(ctx, _q.ctx, ent.OpQueryCount)
if err := _q.prepareQuery(ctx); err != nil {
return 0, err
}
return withInterceptors[int](ctx, _q, querierCount[*RoleQuery](), _q.inters)
}
// CountX is like Count, but panics if an error occurs.
func (_q *RoleQuery) CountX(ctx context.Context) int {
count, err := _q.Count(ctx)
if err != nil {
panic(err)
}
return count
}
// Exist returns true if the query has elements in the graph.
func (_q *RoleQuery) Exist(ctx context.Context) (bool, error) {
ctx = setContextOp(ctx, _q.ctx, ent.OpQueryExist)
switch _, err := _q.FirstID(ctx); {
case IsNotFound(err):
return false, nil
case err != nil:
return false, fmt.Errorf("ent: check existence: %w", err)
default:
return true, nil
}
}
// ExistX is like Exist, but panics if an error occurs.
func (_q *RoleQuery) ExistX(ctx context.Context) bool {
exist, err := _q.Exist(ctx)
if err != nil {
panic(err)
}
return exist
}
// Clone returns a duplicate of the RoleQuery builder, including all associated steps. It can be
// used to prepare common query builders and use them differently after the clone is made.
func (_q *RoleQuery) Clone() *RoleQuery {
if _q == nil {
return nil
}
return &RoleQuery{
config: _q.config,
ctx: _q.ctx.Clone(),
order: append([]role.OrderOption{}, _q.order...),
inters: append([]Interceptor{}, _q.inters...),
predicates: append([]predicate.Role{}, _q.predicates...),
withRolePermissions: _q.withRolePermissions.Clone(),
withUserRoles: _q.withUserRoles.Clone(),
// clone intermediate query.
sql: _q.sql.Clone(),
path: _q.path,
}
}
// WithRolePermissions tells the query-builder to eager-load the nodes that are connected to
// the "role_permissions" edge. The optional arguments are used to configure the query builder of the edge.
func (_q *RoleQuery) WithRolePermissions(opts ...func(*RolePermissionQuery)) *RoleQuery {
query := (&RolePermissionClient{config: _q.config}).Query()
for _, opt := range opts {
opt(query)
}
_q.withRolePermissions = query
return _q
}
// WithUserRoles tells the query-builder to eager-load the nodes that are connected to
// the "user_roles" edge. The optional arguments are used to configure the query builder of the edge.
func (_q *RoleQuery) WithUserRoles(opts ...func(*UserRoleQuery)) *RoleQuery {
query := (&UserRoleClient{config: _q.config}).Query()
for _, opt := range opts {
opt(query)
}
_q.withUserRoles = query
return _q
}
// GroupBy is used to group vertices by one or more fields/columns.
// It is often used with aggregate functions, like: count, max, mean, min, sum.
//
// Example:
//
// var v []struct {
// Name string `json:"name,omitempty"`
// Count int `json:"count,omitempty"`
// }
//
// client.Role.Query().
// GroupBy(role.FieldName).
// Aggregate(ent.Count()).
// Scan(ctx, &v)
func (_q *RoleQuery) GroupBy(field string, fields ...string) *RoleGroupBy {
_q.ctx.Fields = append([]string{field}, fields...)
grbuild := &RoleGroupBy{build: _q}
grbuild.flds = &_q.ctx.Fields
grbuild.label = role.Label
grbuild.scan = grbuild.Scan
return grbuild
}
// Select allows the selection one or more fields/columns for the given query,
// instead of selecting all fields in the entity.
//
// Example:
//
// var v []struct {
// Name string `json:"name,omitempty"`
// }
//
// client.Role.Query().
// Select(role.FieldName).
// Scan(ctx, &v)
func (_q *RoleQuery) Select(fields ...string) *RoleSelect {
_q.ctx.Fields = append(_q.ctx.Fields, fields...)
sbuild := &RoleSelect{RoleQuery: _q}
sbuild.label = role.Label
sbuild.flds, sbuild.scan = &_q.ctx.Fields, sbuild.Scan
return sbuild
}
// Aggregate returns a RoleSelect configured with the given aggregations.
func (_q *RoleQuery) Aggregate(fns ...AggregateFunc) *RoleSelect {
return _q.Select().Aggregate(fns...)
}
func (_q *RoleQuery) prepareQuery(ctx context.Context) error {
for _, inter := range _q.inters {
if inter == nil {
return fmt.Errorf("ent: uninitialized interceptor (forgotten import ent/runtime?)")
}
if trv, ok := inter.(Traverser); ok {
if err := trv.Traverse(ctx, _q); err != nil {
return err
}
}
}
for _, f := range _q.ctx.Fields {
if !role.ValidColumn(f) {
return &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)}
}
}
if _q.path != nil {
prev, err := _q.path(ctx)
if err != nil {
return err
}
_q.sql = prev
}
return nil
}
func (_q *RoleQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*Role, error) {
var (
nodes = []*Role{}
_spec = _q.querySpec()
loadedTypes = [2]bool{
_q.withRolePermissions != nil,
_q.withUserRoles != nil,
}
)
_spec.ScanValues = func(columns []string) ([]any, error) {
return (*Role).scanValues(nil, columns)
}
_spec.Assign = func(columns []string, values []any) error {
node := &Role{config: _q.config}
nodes = append(nodes, node)
node.Edges.loadedTypes = loadedTypes
return node.assignValues(columns, values)
}
for i := range hooks {
hooks[i](ctx, _spec)
}
if err := sqlgraph.QueryNodes(ctx, _q.driver, _spec); err != nil {
return nil, err
}
if len(nodes) == 0 {
return nodes, nil
}
if query := _q.withRolePermissions; query != nil {
if err := _q.loadRolePermissions(ctx, query, nodes,
func(n *Role) { n.Edges.RolePermissions = []*RolePermission{} },
func(n *Role, e *RolePermission) { n.Edges.RolePermissions = append(n.Edges.RolePermissions, e) }); err != nil {
return nil, err
}
}
if query := _q.withUserRoles; query != nil {
if err := _q.loadUserRoles(ctx, query, nodes,
func(n *Role) { n.Edges.UserRoles = []*UserRole{} },
func(n *Role, e *UserRole) { n.Edges.UserRoles = append(n.Edges.UserRoles, e) }); err != nil {
return nil, err
}
}
return nodes, nil
}
func (_q *RoleQuery) loadRolePermissions(ctx context.Context, query *RolePermissionQuery, nodes []*Role, init func(*Role), assign func(*Role, *RolePermission)) error {
fks := make([]driver.Value, 0, len(nodes))
nodeids := make(map[string]*Role)
for i := range nodes {
fks = append(fks, nodes[i].ID)
nodeids[nodes[i].ID] = nodes[i]
if init != nil {
init(nodes[i])
}
}
query.withFKs = true
query.Where(predicate.RolePermission(func(s *sql.Selector) {
s.Where(sql.InValues(s.C(role.RolePermissionsColumn), fks...))
}))
neighbors, err := query.All(ctx)
if err != nil {
return err
}
for _, n := range neighbors {
fk := n.role_role_permissions
if fk == nil {
return fmt.Errorf(`foreign-key "role_role_permissions" is nil for node %v`, n.ID)
}
node, ok := nodeids[*fk]
if !ok {
return fmt.Errorf(`unexpected referenced foreign-key "role_role_permissions" returned %v for node %v`, *fk, n.ID)
}
assign(node, n)
}
return nil
}
func (_q *RoleQuery) loadUserRoles(ctx context.Context, query *UserRoleQuery, nodes []*Role, init func(*Role), assign func(*Role, *UserRole)) error {
fks := make([]driver.Value, 0, len(nodes))
nodeids := make(map[string]*Role)
for i := range nodes {
fks = append(fks, nodes[i].ID)
nodeids[nodes[i].ID] = nodes[i]
if init != nil {
init(nodes[i])
}
}
query.withFKs = true
query.Where(predicate.UserRole(func(s *sql.Selector) {
s.Where(sql.InValues(s.C(role.UserRolesColumn), fks...))
}))
neighbors, err := query.All(ctx)
if err != nil {
return err
}
for _, n := range neighbors {
fk := n.role_user_roles
if fk == nil {
return fmt.Errorf(`foreign-key "role_user_roles" is nil for node %v`, n.ID)
}
node, ok := nodeids[*fk]
if !ok {
return fmt.Errorf(`unexpected referenced foreign-key "role_user_roles" returned %v for node %v`, *fk, n.ID)
}
assign(node, n)
}
return nil
}
func (_q *RoleQuery) sqlCount(ctx context.Context) (int, error) {
_spec := _q.querySpec()
_spec.Node.Columns = _q.ctx.Fields
if len(_q.ctx.Fields) > 0 {
_spec.Unique = _q.ctx.Unique != nil && *_q.ctx.Unique
}
return sqlgraph.CountNodes(ctx, _q.driver, _spec)
}
func (_q *RoleQuery) querySpec() *sqlgraph.QuerySpec {
_spec := sqlgraph.NewQuerySpec(role.Table, role.Columns, sqlgraph.NewFieldSpec(role.FieldID, field.TypeString))
_spec.From = _q.sql
if unique := _q.ctx.Unique; unique != nil {
_spec.Unique = *unique
} else if _q.path != nil {
_spec.Unique = true
}
if fields := _q.ctx.Fields; len(fields) > 0 {
_spec.Node.Columns = make([]string, 0, len(fields))
_spec.Node.Columns = append(_spec.Node.Columns, role.FieldID)
for i := range fields {
if fields[i] != role.FieldID {
_spec.Node.Columns = append(_spec.Node.Columns, fields[i])
}
}
}
if ps := _q.predicates; len(ps) > 0 {
_spec.Predicate = func(selector *sql.Selector) {
for i := range ps {
ps[i](selector)
}
}
}
if limit := _q.ctx.Limit; limit != nil {
_spec.Limit = *limit
}
if offset := _q.ctx.Offset; offset != nil {
_spec.Offset = *offset
}
if ps := _q.order; len(ps) > 0 {
_spec.Order = func(selector *sql.Selector) {
for i := range ps {
ps[i](selector)
}
}
}
return _spec
}
func (_q *RoleQuery) sqlQuery(ctx context.Context) *sql.Selector {
builder := sql.Dialect(_q.driver.Dialect())
t1 := builder.Table(role.Table)
columns := _q.ctx.Fields
if len(columns) == 0 {
columns = role.Columns
}
selector := builder.Select(t1.Columns(columns...)...).From(t1)
if _q.sql != nil {
selector = _q.sql
selector.Select(selector.Columns(columns...)...)
}
if _q.ctx.Unique != nil && *_q.ctx.Unique {
selector.Distinct()
}
for _, p := range _q.predicates {
p(selector)
}
for _, p := range _q.order {
p(selector)
}
if offset := _q.ctx.Offset; offset != nil {
// limit is mandatory for offset clause. We start
// with default value, and override it below if needed.
selector.Offset(*offset).Limit(math.MaxInt32)
}
if limit := _q.ctx.Limit; limit != nil {
selector.Limit(*limit)
}
return selector
}
// RoleGroupBy is the group-by builder for Role entities.
type RoleGroupBy struct {
selector
build *RoleQuery
}
// Aggregate adds the given aggregation functions to the group-by query.
func (_g *RoleGroupBy) Aggregate(fns ...AggregateFunc) *RoleGroupBy {
_g.fns = append(_g.fns, fns...)
return _g
}
// Scan applies the selector query and scans the result into the given value.
func (_g *RoleGroupBy) Scan(ctx context.Context, v any) error {
ctx = setContextOp(ctx, _g.build.ctx, ent.OpQueryGroupBy)
if err := _g.build.prepareQuery(ctx); err != nil {
return err
}
return scanWithInterceptors[*RoleQuery, *RoleGroupBy](ctx, _g.build, _g, _g.build.inters, v)
}
func (_g *RoleGroupBy) sqlScan(ctx context.Context, root *RoleQuery, v any) error {
selector := root.sqlQuery(ctx).Select()
aggregation := make([]string, 0, len(_g.fns))
for _, fn := range _g.fns {
aggregation = append(aggregation, fn(selector))
}
if len(selector.SelectedColumns()) == 0 {
columns := make([]string, 0, len(*_g.flds)+len(_g.fns))
for _, f := range *_g.flds {
columns = append(columns, selector.C(f))
}
columns = append(columns, aggregation...)
selector.Select(columns...)
}
selector.GroupBy(selector.Columns(*_g.flds...)...)
if err := selector.Err(); err != nil {
return err
}
rows := &sql.Rows{}
query, args := selector.Query()
if err := _g.build.driver.Query(ctx, query, args, rows); err != nil {
return err
}
defer rows.Close()
return sql.ScanSlice(rows, v)
}
// RoleSelect is the builder for selecting fields of Role entities.
type RoleSelect struct {
*RoleQuery
selector
}
// Aggregate adds the given aggregation functions to the selector query.
func (_s *RoleSelect) Aggregate(fns ...AggregateFunc) *RoleSelect {
_s.fns = append(_s.fns, fns...)
return _s
}
// Scan applies the selector query and scans the result into the given value.
func (_s *RoleSelect) Scan(ctx context.Context, v any) error {
ctx = setContextOp(ctx, _s.ctx, ent.OpQuerySelect)
if err := _s.prepareQuery(ctx); err != nil {
return err
}
return scanWithInterceptors[*RoleQuery, *RoleSelect](ctx, _s.RoleQuery, _s, _s.inters, v)
}
func (_s *RoleSelect) sqlScan(ctx context.Context, root *RoleQuery, v any) error {
selector := root.sqlQuery(ctx)
aggregation := make([]string, 0, len(_s.fns))
for _, fn := range _s.fns {
aggregation = append(aggregation, fn(selector))
}
switch n := len(*_s.selector.flds); {
case n == 0 && len(aggregation) > 0:
selector.Select(aggregation...)
case n != 0 && len(aggregation) > 0:
selector.AppendSelect(aggregation...)
}
rows := &sql.Rows{}
query, args := selector.Query()
if err := _s.driver.Query(ctx, query, args, rows); err != nil {
return err
}
defer rows.Close()
return sql.ScanSlice(rows, v)
}

View File

@@ -1,613 +0,0 @@
// Code generated by ent, DO NOT EDIT.
package ent
import (
"context"
"errors"
"fmt"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field"
"git.dcentral.systems/toolz/goplt/internal/ent/predicate"
"git.dcentral.systems/toolz/goplt/internal/ent/role"
"git.dcentral.systems/toolz/goplt/internal/ent/rolepermission"
"git.dcentral.systems/toolz/goplt/internal/ent/userrole"
)
// RoleUpdate is the builder for updating Role entities.
type RoleUpdate struct {
config
hooks []Hook
mutation *RoleMutation
}
// Where appends a list predicates to the RoleUpdate builder.
func (_u *RoleUpdate) Where(ps ...predicate.Role) *RoleUpdate {
_u.mutation.Where(ps...)
return _u
}
// SetName sets the "name" field.
func (_u *RoleUpdate) SetName(v string) *RoleUpdate {
_u.mutation.SetName(v)
return _u
}
// SetNillableName sets the "name" field if the given value is not nil.
func (_u *RoleUpdate) SetNillableName(v *string) *RoleUpdate {
if v != nil {
_u.SetName(*v)
}
return _u
}
// SetDescription sets the "description" field.
func (_u *RoleUpdate) SetDescription(v string) *RoleUpdate {
_u.mutation.SetDescription(v)
return _u
}
// SetNillableDescription sets the "description" field if the given value is not nil.
func (_u *RoleUpdate) SetNillableDescription(v *string) *RoleUpdate {
if v != nil {
_u.SetDescription(*v)
}
return _u
}
// ClearDescription clears the value of the "description" field.
func (_u *RoleUpdate) ClearDescription() *RoleUpdate {
_u.mutation.ClearDescription()
return _u
}
// AddRolePermissionIDs adds the "role_permissions" edge to the RolePermission entity by IDs.
func (_u *RoleUpdate) AddRolePermissionIDs(ids ...int) *RoleUpdate {
_u.mutation.AddRolePermissionIDs(ids...)
return _u
}
// AddRolePermissions adds the "role_permissions" edges to the RolePermission entity.
func (_u *RoleUpdate) AddRolePermissions(v ...*RolePermission) *RoleUpdate {
ids := make([]int, len(v))
for i := range v {
ids[i] = v[i].ID
}
return _u.AddRolePermissionIDs(ids...)
}
// AddUserRoleIDs adds the "user_roles" edge to the UserRole entity by IDs.
func (_u *RoleUpdate) AddUserRoleIDs(ids ...int) *RoleUpdate {
_u.mutation.AddUserRoleIDs(ids...)
return _u
}
// AddUserRoles adds the "user_roles" edges to the UserRole entity.
func (_u *RoleUpdate) AddUserRoles(v ...*UserRole) *RoleUpdate {
ids := make([]int, len(v))
for i := range v {
ids[i] = v[i].ID
}
return _u.AddUserRoleIDs(ids...)
}
// Mutation returns the RoleMutation object of the builder.
func (_u *RoleUpdate) Mutation() *RoleMutation {
return _u.mutation
}
// ClearRolePermissions clears all "role_permissions" edges to the RolePermission entity.
func (_u *RoleUpdate) ClearRolePermissions() *RoleUpdate {
_u.mutation.ClearRolePermissions()
return _u
}
// RemoveRolePermissionIDs removes the "role_permissions" edge to RolePermission entities by IDs.
func (_u *RoleUpdate) RemoveRolePermissionIDs(ids ...int) *RoleUpdate {
_u.mutation.RemoveRolePermissionIDs(ids...)
return _u
}
// RemoveRolePermissions removes "role_permissions" edges to RolePermission entities.
func (_u *RoleUpdate) RemoveRolePermissions(v ...*RolePermission) *RoleUpdate {
ids := make([]int, len(v))
for i := range v {
ids[i] = v[i].ID
}
return _u.RemoveRolePermissionIDs(ids...)
}
// ClearUserRoles clears all "user_roles" edges to the UserRole entity.
func (_u *RoleUpdate) ClearUserRoles() *RoleUpdate {
_u.mutation.ClearUserRoles()
return _u
}
// RemoveUserRoleIDs removes the "user_roles" edge to UserRole entities by IDs.
func (_u *RoleUpdate) RemoveUserRoleIDs(ids ...int) *RoleUpdate {
_u.mutation.RemoveUserRoleIDs(ids...)
return _u
}
// RemoveUserRoles removes "user_roles" edges to UserRole entities.
func (_u *RoleUpdate) RemoveUserRoles(v ...*UserRole) *RoleUpdate {
ids := make([]int, len(v))
for i := range v {
ids[i] = v[i].ID
}
return _u.RemoveUserRoleIDs(ids...)
}
// Save executes the query and returns the number of nodes affected by the update operation.
func (_u *RoleUpdate) Save(ctx context.Context) (int, error) {
return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks)
}
// SaveX is like Save, but panics if an error occurs.
func (_u *RoleUpdate) SaveX(ctx context.Context) int {
affected, err := _u.Save(ctx)
if err != nil {
panic(err)
}
return affected
}
// Exec executes the query.
func (_u *RoleUpdate) Exec(ctx context.Context) error {
_, err := _u.Save(ctx)
return err
}
// ExecX is like Exec, but panics if an error occurs.
func (_u *RoleUpdate) ExecX(ctx context.Context) {
if err := _u.Exec(ctx); err != nil {
panic(err)
}
}
// check runs all checks and user-defined validators on the builder.
func (_u *RoleUpdate) check() error {
if v, ok := _u.mutation.Name(); ok {
if err := role.NameValidator(v); err != nil {
return &ValidationError{Name: "name", err: fmt.Errorf(`ent: validator failed for field "Role.name": %w`, err)}
}
}
return nil
}
func (_u *RoleUpdate) sqlSave(ctx context.Context) (_node int, err error) {
if err := _u.check(); err != nil {
return _node, err
}
_spec := sqlgraph.NewUpdateSpec(role.Table, role.Columns, sqlgraph.NewFieldSpec(role.FieldID, field.TypeString))
if ps := _u.mutation.predicates; len(ps) > 0 {
_spec.Predicate = func(selector *sql.Selector) {
for i := range ps {
ps[i](selector)
}
}
}
if value, ok := _u.mutation.Name(); ok {
_spec.SetField(role.FieldName, field.TypeString, value)
}
if value, ok := _u.mutation.Description(); ok {
_spec.SetField(role.FieldDescription, field.TypeString, value)
}
if _u.mutation.DescriptionCleared() {
_spec.ClearField(role.FieldDescription, field.TypeString)
}
if _u.mutation.RolePermissionsCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: role.RolePermissionsTable,
Columns: []string{role.RolePermissionsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(rolepermission.FieldID, field.TypeInt),
},
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := _u.mutation.RemovedRolePermissionsIDs(); len(nodes) > 0 && !_u.mutation.RolePermissionsCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: role.RolePermissionsTable,
Columns: []string{role.RolePermissionsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(rolepermission.FieldID, field.TypeInt),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := _u.mutation.RolePermissionsIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: role.RolePermissionsTable,
Columns: []string{role.RolePermissionsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(rolepermission.FieldID, field.TypeInt),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges.Add = append(_spec.Edges.Add, edge)
}
if _u.mutation.UserRolesCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: role.UserRolesTable,
Columns: []string{role.UserRolesColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(userrole.FieldID, field.TypeInt),
},
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := _u.mutation.RemovedUserRolesIDs(); len(nodes) > 0 && !_u.mutation.UserRolesCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: role.UserRolesTable,
Columns: []string{role.UserRolesColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(userrole.FieldID, field.TypeInt),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := _u.mutation.UserRolesIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: role.UserRolesTable,
Columns: []string{role.UserRolesColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(userrole.FieldID, field.TypeInt),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges.Add = append(_spec.Edges.Add, edge)
}
if _node, err = sqlgraph.UpdateNodes(ctx, _u.driver, _spec); err != nil {
if _, ok := err.(*sqlgraph.NotFoundError); ok {
err = &NotFoundError{role.Label}
} else if sqlgraph.IsConstraintError(err) {
err = &ConstraintError{msg: err.Error(), wrap: err}
}
return 0, err
}
_u.mutation.done = true
return _node, nil
}
// RoleUpdateOne is the builder for updating a single Role entity.
type RoleUpdateOne struct {
config
fields []string
hooks []Hook
mutation *RoleMutation
}
// SetName sets the "name" field.
func (_u *RoleUpdateOne) SetName(v string) *RoleUpdateOne {
_u.mutation.SetName(v)
return _u
}
// SetNillableName sets the "name" field if the given value is not nil.
func (_u *RoleUpdateOne) SetNillableName(v *string) *RoleUpdateOne {
if v != nil {
_u.SetName(*v)
}
return _u
}
// SetDescription sets the "description" field.
func (_u *RoleUpdateOne) SetDescription(v string) *RoleUpdateOne {
_u.mutation.SetDescription(v)
return _u
}
// SetNillableDescription sets the "description" field if the given value is not nil.
func (_u *RoleUpdateOne) SetNillableDescription(v *string) *RoleUpdateOne {
if v != nil {
_u.SetDescription(*v)
}
return _u
}
// ClearDescription clears the value of the "description" field.
func (_u *RoleUpdateOne) ClearDescription() *RoleUpdateOne {
_u.mutation.ClearDescription()
return _u
}
// AddRolePermissionIDs adds the "role_permissions" edge to the RolePermission entity by IDs.
func (_u *RoleUpdateOne) AddRolePermissionIDs(ids ...int) *RoleUpdateOne {
_u.mutation.AddRolePermissionIDs(ids...)
return _u
}
// AddRolePermissions adds the "role_permissions" edges to the RolePermission entity.
func (_u *RoleUpdateOne) AddRolePermissions(v ...*RolePermission) *RoleUpdateOne {
ids := make([]int, len(v))
for i := range v {
ids[i] = v[i].ID
}
return _u.AddRolePermissionIDs(ids...)
}
// AddUserRoleIDs adds the "user_roles" edge to the UserRole entity by IDs.
func (_u *RoleUpdateOne) AddUserRoleIDs(ids ...int) *RoleUpdateOne {
_u.mutation.AddUserRoleIDs(ids...)
return _u
}
// AddUserRoles adds the "user_roles" edges to the UserRole entity.
func (_u *RoleUpdateOne) AddUserRoles(v ...*UserRole) *RoleUpdateOne {
ids := make([]int, len(v))
for i := range v {
ids[i] = v[i].ID
}
return _u.AddUserRoleIDs(ids...)
}
// Mutation returns the RoleMutation object of the builder.
func (_u *RoleUpdateOne) Mutation() *RoleMutation {
return _u.mutation
}
// ClearRolePermissions clears all "role_permissions" edges to the RolePermission entity.
func (_u *RoleUpdateOne) ClearRolePermissions() *RoleUpdateOne {
_u.mutation.ClearRolePermissions()
return _u
}
// RemoveRolePermissionIDs removes the "role_permissions" edge to RolePermission entities by IDs.
func (_u *RoleUpdateOne) RemoveRolePermissionIDs(ids ...int) *RoleUpdateOne {
_u.mutation.RemoveRolePermissionIDs(ids...)
return _u
}
// RemoveRolePermissions removes "role_permissions" edges to RolePermission entities.
func (_u *RoleUpdateOne) RemoveRolePermissions(v ...*RolePermission) *RoleUpdateOne {
ids := make([]int, len(v))
for i := range v {
ids[i] = v[i].ID
}
return _u.RemoveRolePermissionIDs(ids...)
}
// ClearUserRoles clears all "user_roles" edges to the UserRole entity.
func (_u *RoleUpdateOne) ClearUserRoles() *RoleUpdateOne {
_u.mutation.ClearUserRoles()
return _u
}
// RemoveUserRoleIDs removes the "user_roles" edge to UserRole entities by IDs.
func (_u *RoleUpdateOne) RemoveUserRoleIDs(ids ...int) *RoleUpdateOne {
_u.mutation.RemoveUserRoleIDs(ids...)
return _u
}
// RemoveUserRoles removes "user_roles" edges to UserRole entities.
func (_u *RoleUpdateOne) RemoveUserRoles(v ...*UserRole) *RoleUpdateOne {
ids := make([]int, len(v))
for i := range v {
ids[i] = v[i].ID
}
return _u.RemoveUserRoleIDs(ids...)
}
// Where appends a list predicates to the RoleUpdate builder.
func (_u *RoleUpdateOne) Where(ps ...predicate.Role) *RoleUpdateOne {
_u.mutation.Where(ps...)
return _u
}
// Select allows selecting one or more fields (columns) of the returned entity.
// The default is selecting all fields defined in the entity schema.
func (_u *RoleUpdateOne) Select(field string, fields ...string) *RoleUpdateOne {
_u.fields = append([]string{field}, fields...)
return _u
}
// Save executes the query and returns the updated Role entity.
func (_u *RoleUpdateOne) Save(ctx context.Context) (*Role, error) {
return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks)
}
// SaveX is like Save, but panics if an error occurs.
func (_u *RoleUpdateOne) SaveX(ctx context.Context) *Role {
node, err := _u.Save(ctx)
if err != nil {
panic(err)
}
return node
}
// Exec executes the query on the entity.
func (_u *RoleUpdateOne) Exec(ctx context.Context) error {
_, err := _u.Save(ctx)
return err
}
// ExecX is like Exec, but panics if an error occurs.
func (_u *RoleUpdateOne) ExecX(ctx context.Context) {
if err := _u.Exec(ctx); err != nil {
panic(err)
}
}
// check runs all checks and user-defined validators on the builder.
func (_u *RoleUpdateOne) check() error {
if v, ok := _u.mutation.Name(); ok {
if err := role.NameValidator(v); err != nil {
return &ValidationError{Name: "name", err: fmt.Errorf(`ent: validator failed for field "Role.name": %w`, err)}
}
}
return nil
}
func (_u *RoleUpdateOne) sqlSave(ctx context.Context) (_node *Role, err error) {
if err := _u.check(); err != nil {
return _node, err
}
_spec := sqlgraph.NewUpdateSpec(role.Table, role.Columns, sqlgraph.NewFieldSpec(role.FieldID, field.TypeString))
id, ok := _u.mutation.ID()
if !ok {
return nil, &ValidationError{Name: "id", err: errors.New(`ent: missing "Role.id" for update`)}
}
_spec.Node.ID.Value = id
if fields := _u.fields; len(fields) > 0 {
_spec.Node.Columns = make([]string, 0, len(fields))
_spec.Node.Columns = append(_spec.Node.Columns, role.FieldID)
for _, f := range fields {
if !role.ValidColumn(f) {
return nil, &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)}
}
if f != role.FieldID {
_spec.Node.Columns = append(_spec.Node.Columns, f)
}
}
}
if ps := _u.mutation.predicates; len(ps) > 0 {
_spec.Predicate = func(selector *sql.Selector) {
for i := range ps {
ps[i](selector)
}
}
}
if value, ok := _u.mutation.Name(); ok {
_spec.SetField(role.FieldName, field.TypeString, value)
}
if value, ok := _u.mutation.Description(); ok {
_spec.SetField(role.FieldDescription, field.TypeString, value)
}
if _u.mutation.DescriptionCleared() {
_spec.ClearField(role.FieldDescription, field.TypeString)
}
if _u.mutation.RolePermissionsCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: role.RolePermissionsTable,
Columns: []string{role.RolePermissionsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(rolepermission.FieldID, field.TypeInt),
},
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := _u.mutation.RemovedRolePermissionsIDs(); len(nodes) > 0 && !_u.mutation.RolePermissionsCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: role.RolePermissionsTable,
Columns: []string{role.RolePermissionsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(rolepermission.FieldID, field.TypeInt),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := _u.mutation.RolePermissionsIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: role.RolePermissionsTable,
Columns: []string{role.RolePermissionsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(rolepermission.FieldID, field.TypeInt),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges.Add = append(_spec.Edges.Add, edge)
}
if _u.mutation.UserRolesCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: role.UserRolesTable,
Columns: []string{role.UserRolesColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(userrole.FieldID, field.TypeInt),
},
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := _u.mutation.RemovedUserRolesIDs(); len(nodes) > 0 && !_u.mutation.UserRolesCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: role.UserRolesTable,
Columns: []string{role.UserRolesColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(userrole.FieldID, field.TypeInt),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := _u.mutation.UserRolesIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: role.UserRolesTable,
Columns: []string{role.UserRolesColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(userrole.FieldID, field.TypeInt),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges.Add = append(_spec.Edges.Add, edge)
}
_node = &Role{config: _u.config}
_spec.Assign = _node.assignValues
_spec.ScanValues = _node.scanValues
if err = sqlgraph.UpdateNode(ctx, _u.driver, _spec); err != nil {
if _, ok := err.(*sqlgraph.NotFoundError); ok {
err = &NotFoundError{role.Label}
} else if sqlgraph.IsConstraintError(err) {
err = &ConstraintError{msg: err.Error(), wrap: err}
}
return nil, err
}
_u.mutation.done = true
return _node, nil
}

Some files were not shown because too many files have changed in this diff Show More