refactor: Align Epic 0 & Epic 1 with true microservices architecture
Refactor core kernel and infrastructure to support true microservices architecture where services are independently deployable. Phase 1: Core Kernel Cleanup - Remove database provider from CoreModule (services create their own) - Update ProvideHealthRegistry to not depend on database - Add schema support to database client (NewClientWithSchema) - Update main entry point to remove database dependency - Core kernel now provides only: config, logger, error bus, health, metrics, tracer, service registry Phase 2: Service Registry Implementation - Create ServiceRegistry interface (pkg/registry/registry.go) - Implement Consul registry (internal/registry/consul/consul.go) - Add Consul dependency (github.com/hashicorp/consul/api) - Add registry configuration to config/default.yaml - Add ProvideServiceRegistry() to DI container Phase 3: Service Client Interfaces - Create service client interfaces: - pkg/services/auth.go - AuthServiceClient - pkg/services/identity.go - IdentityServiceClient - pkg/services/authz.go - AuthzServiceClient - pkg/services/audit.go - AuditServiceClient - Create ServiceClientFactory (internal/client/factory.go) - Create stub gRPC client implementations (internal/client/grpc/) - Add ProvideServiceClientFactory() to DI container Phase 4: gRPC Service Definitions - Create proto files for all core services: - api/proto/auth.proto - api/proto/identity.proto - api/proto/authz.proto - api/proto/audit.proto - Add generate-proto target to Makefile Phase 5: API Gateway Implementation - Create API Gateway service entry point (cmd/api-gateway/main.go) - Create Gateway implementation (services/gateway/gateway.go) - Add gateway configuration to config/default.yaml - Gateway registers with Consul and routes requests to backend services All code compiles successfully. Core services (Auth, Identity, Authz, Audit) will be implemented in Epic 2 using these foundations.
This commit is contained in:
21
Makefile
21
Makefile
@@ -110,6 +110,27 @@ generate:
|
|||||||
@echo "Running code generation..."
|
@echo "Running code generation..."
|
||||||
$(GO) generate ./...
|
$(GO) generate ./...
|
||||||
|
|
||||||
|
generate-proto:
|
||||||
|
@echo "Generating gRPC code from proto files..."
|
||||||
|
@if ! command -v protoc > /dev/null; then \
|
||||||
|
echo "protoc not found. Install Protocol Buffers compiler."; \
|
||||||
|
exit 1; \
|
||||||
|
fi
|
||||||
|
@if ! command -v protoc-gen-go > /dev/null; then \
|
||||||
|
echo "protoc-gen-go not found. Install with: go install google.golang.org/protobuf/cmd/protoc-gen-go@latest"; \
|
||||||
|
exit 1; \
|
||||||
|
fi
|
||||||
|
@if ! command -v protoc-gen-go-grpc > /dev/null; then \
|
||||||
|
echo "protoc-gen-go-grpc not found. Install with: go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest"; \
|
||||||
|
exit 1; \
|
||||||
|
fi
|
||||||
|
@mkdir -p api/proto/generated
|
||||||
|
@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/*.proto
|
||||||
|
@echo "gRPC code generation complete"
|
||||||
|
|
||||||
verify: fmt-check lint test
|
verify: fmt-check lint test
|
||||||
@echo "Verification complete"
|
@echo "Verification complete"
|
||||||
|
|
||||||
|
|||||||
56
api/proto/audit.proto
Normal file
56
api/proto/audit.proto
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package audit.v1;
|
||||||
|
|
||||||
|
option go_package = "git.dcentral.systems/toolz/goplt/api/proto/generated/audit/v1;auditv1";
|
||||||
|
|
||||||
|
// AuditService provides audit logging operations.
|
||||||
|
service AuditService {
|
||||||
|
// Record records an audit log entry.
|
||||||
|
rpc Record(RecordRequest) returns (RecordResponse);
|
||||||
|
|
||||||
|
// Query queries audit logs based on filters.
|
||||||
|
rpc Query(QueryRequest) returns (QueryResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
// AuditLogEntry represents an audit log entry.
|
||||||
|
message AuditLogEntry {
|
||||||
|
string user_id = 1;
|
||||||
|
string action = 2; // e.g., "user.create", "user.update"
|
||||||
|
string resource = 3; // e.g., "user", "role"
|
||||||
|
string resource_id = 4;
|
||||||
|
string ip_address = 5;
|
||||||
|
string user_agent = 6;
|
||||||
|
map<string, string> metadata = 7;
|
||||||
|
int64 timestamp = 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
// RecordRequest contains an audit log entry to record.
|
||||||
|
message RecordRequest {
|
||||||
|
AuditLogEntry entry = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// RecordResponse indicates success.
|
||||||
|
message RecordResponse {
|
||||||
|
bool success = 1;
|
||||||
|
string id = 2; // Audit log entry ID
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryRequest contains filters for querying audit logs.
|
||||||
|
message QueryRequest {
|
||||||
|
optional string user_id = 1;
|
||||||
|
optional string action = 2;
|
||||||
|
optional string resource = 3;
|
||||||
|
optional string resource_id = 4;
|
||||||
|
optional int64 start_time = 5;
|
||||||
|
optional int64 end_time = 6;
|
||||||
|
int32 limit = 7; // Max number of results
|
||||||
|
int32 offset = 8; // Pagination offset
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryResponse contains audit log entries.
|
||||||
|
message QueryResponse {
|
||||||
|
repeated AuditLogEntry entries = 1;
|
||||||
|
int32 total = 2; // Total number of matching entries
|
||||||
|
}
|
||||||
|
|
||||||
71
api/proto/auth.proto
Normal file
71
api/proto/auth.proto
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package auth.v1;
|
||||||
|
|
||||||
|
option go_package = "git.dcentral.systems/toolz/goplt/api/proto/generated/auth/v1;authv1";
|
||||||
|
|
||||||
|
// AuthService provides authentication operations.
|
||||||
|
service AuthService {
|
||||||
|
// Login authenticates a user and returns access and refresh tokens.
|
||||||
|
rpc Login(LoginRequest) returns (LoginResponse);
|
||||||
|
|
||||||
|
// RefreshToken refreshes an access token using a refresh token.
|
||||||
|
rpc RefreshToken(RefreshTokenRequest) returns (RefreshTokenResponse);
|
||||||
|
|
||||||
|
// ValidateToken validates a JWT token and returns the token claims.
|
||||||
|
rpc ValidateToken(ValidateTokenRequest) returns (ValidateTokenResponse);
|
||||||
|
|
||||||
|
// Logout invalidates a refresh token.
|
||||||
|
rpc Logout(LogoutRequest) returns (LogoutResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoginRequest contains login credentials.
|
||||||
|
message LoginRequest {
|
||||||
|
string email = 1;
|
||||||
|
string password = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoginResponse contains authentication tokens.
|
||||||
|
message LoginResponse {
|
||||||
|
string access_token = 1;
|
||||||
|
string refresh_token = 2;
|
||||||
|
int64 expires_in = 3; // seconds
|
||||||
|
string token_type = 4; // "Bearer"
|
||||||
|
}
|
||||||
|
|
||||||
|
// RefreshTokenRequest contains a refresh token.
|
||||||
|
message RefreshTokenRequest {
|
||||||
|
string refresh_token = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// RefreshTokenResponse contains new authentication tokens.
|
||||||
|
message RefreshTokenResponse {
|
||||||
|
string access_token = 1;
|
||||||
|
string refresh_token = 2;
|
||||||
|
int64 expires_in = 3; // seconds
|
||||||
|
string token_type = 4; // "Bearer"
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateTokenRequest contains a JWT token to validate.
|
||||||
|
message ValidateTokenRequest {
|
||||||
|
string token = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateTokenResponse contains token claims.
|
||||||
|
message ValidateTokenResponse {
|
||||||
|
string user_id = 1;
|
||||||
|
string email = 2;
|
||||||
|
repeated string roles = 3;
|
||||||
|
int64 expires_at = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
// LogoutRequest contains a refresh token to invalidate.
|
||||||
|
message LogoutRequest {
|
||||||
|
string refresh_token = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// LogoutResponse indicates success.
|
||||||
|
message LogoutResponse {
|
||||||
|
bool success = 1;
|
||||||
|
}
|
||||||
|
|
||||||
80
api/proto/authz.proto
Normal file
80
api/proto/authz.proto
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package authz.v1;
|
||||||
|
|
||||||
|
option go_package = "git.dcentral.systems/toolz/goplt/api/proto/generated/authz/v1;authzv1";
|
||||||
|
|
||||||
|
// AuthzService provides authorization operations.
|
||||||
|
service AuthzService {
|
||||||
|
// Authorize checks if a user has a specific permission and returns an error if not.
|
||||||
|
rpc Authorize(AuthorizeRequest) returns (AuthorizeResponse);
|
||||||
|
|
||||||
|
// HasPermission checks if a user has a specific permission.
|
||||||
|
rpc HasPermission(HasPermissionRequest) returns (HasPermissionResponse);
|
||||||
|
|
||||||
|
// GetUserPermissions returns all permissions for a user.
|
||||||
|
rpc GetUserPermissions(GetUserPermissionsRequest) returns (GetUserPermissionsResponse);
|
||||||
|
|
||||||
|
// GetUserRoles returns all roles for a user.
|
||||||
|
rpc GetUserRoles(GetUserRolesRequest) returns (GetUserRolesResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Permission represents a permission in the system.
|
||||||
|
message Permission {
|
||||||
|
string id = 1;
|
||||||
|
string code = 2;
|
||||||
|
string name = 3;
|
||||||
|
string description = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Role represents a role in the system.
|
||||||
|
message Role {
|
||||||
|
string id = 1;
|
||||||
|
string name = 2;
|
||||||
|
string description = 3;
|
||||||
|
repeated string permissions = 4; // Permission codes
|
||||||
|
}
|
||||||
|
|
||||||
|
// AuthorizeRequest contains user ID and permission to check.
|
||||||
|
message AuthorizeRequest {
|
||||||
|
string user_id = 1;
|
||||||
|
string permission = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// AuthorizeResponse indicates authorization result.
|
||||||
|
message AuthorizeResponse {
|
||||||
|
bool authorized = 1;
|
||||||
|
string message = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasPermissionRequest contains user ID and permission to check.
|
||||||
|
message HasPermissionRequest {
|
||||||
|
string user_id = 1;
|
||||||
|
string permission = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasPermissionResponse indicates if the user has the permission.
|
||||||
|
message HasPermissionResponse {
|
||||||
|
bool has_permission = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserPermissionsRequest contains a user ID.
|
||||||
|
message GetUserPermissionsRequest {
|
||||||
|
string user_id = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserPermissionsResponse contains all permissions for the user.
|
||||||
|
message GetUserPermissionsResponse {
|
||||||
|
repeated Permission permissions = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserRolesRequest contains a user ID.
|
||||||
|
message GetUserRolesRequest {
|
||||||
|
string user_id = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserRolesResponse contains all roles for the user.
|
||||||
|
message GetUserRolesResponse {
|
||||||
|
repeated Role roles = 1;
|
||||||
|
}
|
||||||
|
|
||||||
134
api/proto/identity.proto
Normal file
134
api/proto/identity.proto
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package identity.v1;
|
||||||
|
|
||||||
|
option go_package = "git.dcentral.systems/toolz/goplt/api/proto/generated/identity/v1;identityv1";
|
||||||
|
|
||||||
|
// IdentityService provides user management operations.
|
||||||
|
service IdentityService {
|
||||||
|
// GetUser retrieves a user by ID.
|
||||||
|
rpc GetUser(GetUserRequest) returns (GetUserResponse);
|
||||||
|
|
||||||
|
// GetUserByEmail retrieves a user by email address.
|
||||||
|
rpc GetUserByEmail(GetUserByEmailRequest) returns (GetUserByEmailResponse);
|
||||||
|
|
||||||
|
// CreateUser creates a new user.
|
||||||
|
rpc CreateUser(CreateUserRequest) returns (CreateUserResponse);
|
||||||
|
|
||||||
|
// UpdateUser updates an existing user.
|
||||||
|
rpc UpdateUser(UpdateUserRequest) returns (UpdateUserResponse);
|
||||||
|
|
||||||
|
// DeleteUser deletes a user.
|
||||||
|
rpc DeleteUser(DeleteUserRequest) returns (DeleteUserResponse);
|
||||||
|
|
||||||
|
// VerifyEmail verifies a user's email address using a verification token.
|
||||||
|
rpc VerifyEmail(VerifyEmailRequest) returns (VerifyEmailResponse);
|
||||||
|
|
||||||
|
// RequestPasswordReset requests a password reset token.
|
||||||
|
rpc RequestPasswordReset(RequestPasswordResetRequest) returns (RequestPasswordResetResponse);
|
||||||
|
|
||||||
|
// ResetPassword resets a user's password using a reset token.
|
||||||
|
rpc ResetPassword(ResetPasswordRequest) returns (ResetPasswordResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
// User represents a user in the system.
|
||||||
|
message User {
|
||||||
|
string id = 1;
|
||||||
|
string email = 2;
|
||||||
|
string username = 3;
|
||||||
|
string first_name = 4;
|
||||||
|
string last_name = 5;
|
||||||
|
bool email_verified = 6;
|
||||||
|
int64 created_at = 7;
|
||||||
|
int64 updated_at = 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserRequest contains a user ID.
|
||||||
|
message GetUserRequest {
|
||||||
|
string id = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserResponse contains a user.
|
||||||
|
message GetUserResponse {
|
||||||
|
User user = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserByEmailRequest contains an email address.
|
||||||
|
message GetUserByEmailRequest {
|
||||||
|
string email = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserByEmailResponse contains a user.
|
||||||
|
message GetUserByEmailResponse {
|
||||||
|
User user = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateUserRequest contains user data for creation.
|
||||||
|
message CreateUserRequest {
|
||||||
|
string email = 1;
|
||||||
|
string username = 2;
|
||||||
|
string password = 3;
|
||||||
|
string first_name = 4;
|
||||||
|
string last_name = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateUserResponse contains the created user.
|
||||||
|
message CreateUserResponse {
|
||||||
|
User user = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateUserRequest contains user data for update.
|
||||||
|
message UpdateUserRequest {
|
||||||
|
string id = 1;
|
||||||
|
optional string email = 2;
|
||||||
|
optional string username = 3;
|
||||||
|
optional string first_name = 4;
|
||||||
|
optional string last_name = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateUserResponse contains the updated user.
|
||||||
|
message UpdateUserResponse {
|
||||||
|
User user = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteUserRequest contains a user ID.
|
||||||
|
message DeleteUserRequest {
|
||||||
|
string id = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteUserResponse indicates success.
|
||||||
|
message DeleteUserResponse {
|
||||||
|
bool success = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// VerifyEmailRequest contains a verification token.
|
||||||
|
message VerifyEmailRequest {
|
||||||
|
string token = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// VerifyEmailResponse indicates success.
|
||||||
|
message VerifyEmailResponse {
|
||||||
|
bool success = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequestPasswordResetRequest contains an email address.
|
||||||
|
message RequestPasswordResetRequest {
|
||||||
|
string email = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequestPasswordResetResponse indicates success.
|
||||||
|
message RequestPasswordResetResponse {
|
||||||
|
bool success = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResetPasswordRequest contains a reset token and new password.
|
||||||
|
message ResetPasswordRequest {
|
||||||
|
string token = 1;
|
||||||
|
string new_password = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResetPasswordResponse indicates success.
|
||||||
|
message ResetPasswordResponse {
|
||||||
|
bool success = 1;
|
||||||
|
}
|
||||||
|
|
||||||
156
cmd/api-gateway/main.go
Normal file
156
cmd/api-gateway/main.go
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
// Package main provides the API Gateway service entry point.
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.dcentral.systems/toolz/goplt/internal/client"
|
||||||
|
"git.dcentral.systems/toolz/goplt/internal/di"
|
||||||
|
"git.dcentral.systems/toolz/goplt/internal/health"
|
||||||
|
"git.dcentral.systems/toolz/goplt/internal/metrics"
|
||||||
|
"git.dcentral.systems/toolz/goplt/internal/server"
|
||||||
|
"git.dcentral.systems/toolz/goplt/pkg/config"
|
||||||
|
"git.dcentral.systems/toolz/goplt/pkg/errorbus"
|
||||||
|
"git.dcentral.systems/toolz/goplt/pkg/logger"
|
||||||
|
"git.dcentral.systems/toolz/goplt/pkg/registry"
|
||||||
|
"git.dcentral.systems/toolz/goplt/services/gateway"
|
||||||
|
"go.opentelemetry.io/otel/trace"
|
||||||
|
"go.uber.org/fx"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// Create DI container with core kernel services
|
||||||
|
container := di.NewContainer(
|
||||||
|
// Invoke lifecycle hooks
|
||||||
|
fx.Invoke(di.RegisterLifecycleHooks),
|
||||||
|
// Create API Gateway
|
||||||
|
fx.Invoke(func(
|
||||||
|
cfg config.ConfigProvider,
|
||||||
|
log logger.Logger,
|
||||||
|
healthRegistry *health.Registry,
|
||||||
|
metricsRegistry *metrics.Metrics,
|
||||||
|
errorBus errorbus.ErrorPublisher,
|
||||||
|
tracer trace.TracerProvider,
|
||||||
|
serviceRegistry registry.ServiceRegistry,
|
||||||
|
clientFactory *client.ServiceClientFactory,
|
||||||
|
lc fx.Lifecycle,
|
||||||
|
) {
|
||||||
|
// Create HTTP server using server foundation
|
||||||
|
srv, err := server.NewServer(cfg, log, healthRegistry, metricsRegistry, errorBus, tracer)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Failed to create API Gateway server",
|
||||||
|
logger.Error(err),
|
||||||
|
)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup gateway routes
|
||||||
|
gateway, err := gateway.NewGateway(cfg, log, clientFactory, serviceRegistry)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Failed to create API Gateway",
|
||||||
|
logger.Error(err),
|
||||||
|
)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
gateway.SetupRoutes(srv.Router())
|
||||||
|
|
||||||
|
// Register with Consul
|
||||||
|
gatewayPort := cfg.GetInt("gateway.port")
|
||||||
|
if gatewayPort == 0 {
|
||||||
|
gatewayPort = 8080
|
||||||
|
}
|
||||||
|
gatewayHost := cfg.GetString("gateway.host")
|
||||||
|
if gatewayHost == "" {
|
||||||
|
gatewayHost = "localhost"
|
||||||
|
}
|
||||||
|
|
||||||
|
serviceInstance := ®istry.ServiceInstance{
|
||||||
|
ID: fmt.Sprintf("api-gateway-%d", os.Getpid()),
|
||||||
|
Name: "api-gateway",
|
||||||
|
Address: gatewayHost,
|
||||||
|
Port: gatewayPort,
|
||||||
|
Tags: []string{"gateway", "http"},
|
||||||
|
Metadata: map[string]string{
|
||||||
|
"version": "1.0.0",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register lifecycle hooks
|
||||||
|
lc.Append(fx.Hook{
|
||||||
|
OnStart: func(ctx context.Context) error {
|
||||||
|
// Register with service registry
|
||||||
|
if err := serviceRegistry.Register(ctx, serviceInstance); err != nil {
|
||||||
|
log.Warn("Failed to register API Gateway with service registry",
|
||||||
|
logger.Error(err),
|
||||||
|
)
|
||||||
|
// Continue anyway - gateway can work without registry
|
||||||
|
} else {
|
||||||
|
log.Info("API Gateway registered with service registry",
|
||||||
|
logger.String("service_id", serviceInstance.ID),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start HTTP server
|
||||||
|
addr := fmt.Sprintf("%s:%d", cfg.GetString("server.host"), gatewayPort)
|
||||||
|
log.Info("API Gateway starting",
|
||||||
|
logger.String("addr", addr),
|
||||||
|
)
|
||||||
|
|
||||||
|
errChan := make(chan error, 1)
|
||||||
|
go func() {
|
||||||
|
if err := srv.Start(); err != nil && err != http.ErrServerClosed {
|
||||||
|
log.Error("API Gateway server failed",
|
||||||
|
logger.String("error", err.Error()),
|
||||||
|
)
|
||||||
|
errChan <- err
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Wait a short time to detect immediate binding errors
|
||||||
|
select {
|
||||||
|
case err := <-errChan:
|
||||||
|
return fmt.Errorf("API Gateway failed to start: %w", err)
|
||||||
|
case <-time.After(500 * time.Millisecond):
|
||||||
|
log.Info("API Gateway started successfully",
|
||||||
|
logger.String("addr", addr),
|
||||||
|
)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
},
|
||||||
|
OnStop: func(ctx context.Context) error {
|
||||||
|
// Deregister from service registry
|
||||||
|
if err := serviceRegistry.Deregister(ctx, serviceInstance.ID); err != nil {
|
||||||
|
log.Warn("Failed to deregister API Gateway from service registry",
|
||||||
|
logger.Error(err),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
log.Info("API Gateway deregistered from service registry")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shutdown HTTP server
|
||||||
|
return srv.Shutdown(ctx)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Create root context
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
// Start the application
|
||||||
|
if err := container.Start(ctx); err != nil {
|
||||||
|
log := logger.GetGlobalLogger()
|
||||||
|
if log != nil {
|
||||||
|
log.Error("Failed to start API Gateway",
|
||||||
|
logger.Error(err),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(os.Stderr, "Failed to start API Gateway: %v\n", err)
|
||||||
|
}
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,23 +7,28 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
|
|
||||||
"git.dcentral.systems/toolz/goplt/internal/di"
|
"git.dcentral.systems/toolz/goplt/internal/di"
|
||||||
"git.dcentral.systems/toolz/goplt/internal/infra/database"
|
"git.dcentral.systems/toolz/goplt/internal/health"
|
||||||
"git.dcentral.systems/toolz/goplt/internal/server"
|
"git.dcentral.systems/toolz/goplt/internal/metrics"
|
||||||
|
"git.dcentral.systems/toolz/goplt/pkg/config"
|
||||||
"git.dcentral.systems/toolz/goplt/pkg/logger"
|
"git.dcentral.systems/toolz/goplt/pkg/logger"
|
||||||
"go.uber.org/fx"
|
"go.uber.org/fx"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
// Create DI container with lifecycle hooks
|
// Create DI container with lifecycle hooks
|
||||||
// We need to invoke the HTTP server to ensure all providers execute
|
// This is a minimal entry point for testing core kernel infrastructure
|
||||||
|
// Services will have their own entry points (cmd/{service}/main.go)
|
||||||
container := di.NewContainer(
|
container := di.NewContainer(
|
||||||
// Invoke lifecycle hooks
|
// Invoke lifecycle hooks
|
||||||
fx.Invoke(di.RegisterLifecycleHooks),
|
fx.Invoke(di.RegisterLifecycleHooks),
|
||||||
// Force HTTP server to be created (which triggers all dependencies)
|
// Verify core kernel services are available
|
||||||
// This ensures database, health, metrics, etc. are all created
|
fx.Invoke(func(
|
||||||
fx.Invoke(func(_ *server.Server, _ *database.Client) {
|
_ config.ConfigProvider,
|
||||||
// Both server and database are created, hooks are registered
|
_ logger.Logger,
|
||||||
// This ensures all providers execute
|
_ *health.Registry,
|
||||||
|
_ *metrics.Metrics,
|
||||||
|
) {
|
||||||
|
// Core kernel services are available
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -24,3 +24,30 @@ tracing:
|
|||||||
service_name: "platform"
|
service_name: "platform"
|
||||||
service_version: "1.0.0"
|
service_version: "1.0.0"
|
||||||
otlp_endpoint: ""
|
otlp_endpoint: ""
|
||||||
|
|
||||||
|
registry:
|
||||||
|
type: consul
|
||||||
|
consul:
|
||||||
|
address: "localhost:8500"
|
||||||
|
datacenter: "dc1"
|
||||||
|
scheme: "http"
|
||||||
|
health_check:
|
||||||
|
interval: "10s"
|
||||||
|
timeout: "3s"
|
||||||
|
deregister_after: "30s"
|
||||||
|
http: "/healthz"
|
||||||
|
|
||||||
|
gateway:
|
||||||
|
port: 8080
|
||||||
|
host: "0.0.0.0"
|
||||||
|
routes:
|
||||||
|
- path: "/api/v1/auth/**"
|
||||||
|
service: "auth-service"
|
||||||
|
auth_required: false
|
||||||
|
- path: "/api/v1/users/**"
|
||||||
|
service: "identity-service"
|
||||||
|
auth_required: true
|
||||||
|
cors:
|
||||||
|
allowed_origins: ["*"]
|
||||||
|
allowed_methods: ["GET", "POST", "PUT", "DELETE", "PATCH"]
|
||||||
|
allowed_headers: ["Authorization", "Content-Type"]
|
||||||
|
|||||||
20
go.mod
20
go.mod
@@ -1,6 +1,6 @@
|
|||||||
module git.dcentral.systems/toolz/goplt
|
module git.dcentral.systems/toolz/goplt
|
||||||
|
|
||||||
go 1.24
|
go 1.25.3
|
||||||
|
|
||||||
require (
|
require (
|
||||||
entgo.io/ent v0.14.5
|
entgo.io/ent v0.14.5
|
||||||
@@ -23,6 +23,7 @@ require (
|
|||||||
ariga.io/atlas v0.32.1-0.20250325101103-175b25e1c1b9 // indirect
|
ariga.io/atlas v0.32.1-0.20250325101103-175b25e1c1b9 // indirect
|
||||||
github.com/agext/levenshtein v1.2.3 // indirect
|
github.com/agext/levenshtein v1.2.3 // indirect
|
||||||
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
|
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
|
||||||
|
github.com/armon/go-metrics v0.4.1 // indirect
|
||||||
github.com/beorn7/perks v1.0.1 // indirect
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
github.com/bmatcuk/doublestar v1.3.4 // indirect
|
github.com/bmatcuk/doublestar v1.3.4 // indirect
|
||||||
github.com/bytedance/sonic v1.14.0 // indirect
|
github.com/bytedance/sonic v1.14.0 // indirect
|
||||||
@@ -30,6 +31,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/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
|
||||||
github.com/gin-contrib/sse v1.1.0 // indirect
|
github.com/gin-contrib/sse v1.1.0 // indirect
|
||||||
@@ -39,16 +41,28 @@ require (
|
|||||||
github.com/go-playground/locales v0.14.1 // indirect
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
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/goccy/go-json v0.10.5 // indirect
|
github.com/goccy/go-json v0.10.5 // 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/consul/api v1.33.0 // indirect
|
||||||
|
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||||
|
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||||
|
github.com/hashicorp/go-hclog v1.5.0 // indirect
|
||||||
|
github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
|
||||||
|
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||||
|
github.com/hashicorp/go-rootcerts v1.0.2 // indirect
|
||||||
|
github.com/hashicorp/golang-lru v0.5.4 // indirect
|
||||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||||
github.com/hashicorp/hcl/v2 v2.18.1 // indirect
|
github.com/hashicorp/hcl/v2 v2.18.1 // indirect
|
||||||
|
github.com/hashicorp/serf v0.10.1 // indirect
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||||
github.com/leodido/go-urn v1.4.0 // indirect
|
github.com/leodido/go-urn v1.4.0 // indirect
|
||||||
github.com/magiconair/properties v1.8.7 // indirect
|
github.com/magiconair/properties v1.8.7 // indirect
|
||||||
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
|
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||||
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
|
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
|
||||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
@@ -78,8 +92,8 @@ require (
|
|||||||
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/crypto v0.41.0 // indirect
|
||||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
|
golang.org/x/exp v0.0.0-20250808145144-a408d31f581a // indirect
|
||||||
golang.org/x/mod v0.26.0 // indirect
|
golang.org/x/mod v0.27.0 // indirect
|
||||||
golang.org/x/net v0.43.0 // indirect
|
golang.org/x/net v0.43.0 // indirect
|
||||||
golang.org/x/sys v0.35.0 // indirect
|
golang.org/x/sys v0.35.0 // indirect
|
||||||
golang.org/x/text v0.28.0 // indirect
|
golang.org/x/text v0.28.0 // indirect
|
||||||
|
|||||||
180
go.sum
180
go.sum
@@ -4,12 +4,26 @@ entgo.io/ent v0.14.5 h1:Rj2WOYJtCkWyFo6a+5wB3EfBRP0rnx1fMk6gGA0UUe4=
|
|||||||
entgo.io/ent v0.14.5/go.mod h1:zTzLmWtPvGpmSwtkaayM2cm5m819NdM7z7tYPq3vN0U=
|
entgo.io/ent v0.14.5/go.mod h1:zTzLmWtPvGpmSwtkaayM2cm5m819NdM7z7tYPq3vN0U=
|
||||||
github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60=
|
github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60=
|
||||||
github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
|
github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
|
||||||
|
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
|
||||||
github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo=
|
github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo=
|
||||||
github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=
|
github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=
|
||||||
|
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||||
|
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||||
|
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||||
|
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||||
github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY=
|
github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY=
|
||||||
github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4=
|
github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4=
|
||||||
|
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
|
||||||
|
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
|
||||||
|
github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJA=
|
||||||
|
github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4=
|
||||||
|
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||||
|
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||||
|
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||||
|
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||||
|
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
||||||
github.com/bmatcuk/doublestar v1.3.4 h1:gPypJ5xD31uhX6Tf54sDPUOBXTqKH4c9aPY66CyQrS0=
|
github.com/bmatcuk/doublestar v1.3.4 h1:gPypJ5xD31uhX6Tf54sDPUOBXTqKH4c9aPY66CyQrS0=
|
||||||
github.com/bmatcuk/doublestar v1.3.4/go.mod h1:wiQtGV+rzVYxB7WIlirSN++5HPtPlXEo9MEoZQC/PmE=
|
github.com/bmatcuk/doublestar v1.3.4/go.mod h1:wiQtGV+rzVYxB7WIlirSN++5HPtPlXEo9MEoZQC/PmE=
|
||||||
github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ=
|
github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ=
|
||||||
@@ -18,14 +32,22 @@ github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZw
|
|||||||
github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
|
github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
|
||||||
github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
|
github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
|
||||||
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
|
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
|
||||||
|
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
|
github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag=
|
||||||
|
github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
|
||||||
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
|
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
|
||||||
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
|
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||||
|
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
|
||||||
|
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
|
||||||
|
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
|
||||||
|
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
|
||||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||||
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
||||||
@@ -36,6 +58,10 @@ github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w
|
|||||||
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
|
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
|
||||||
github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ=
|
github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ=
|
||||||
github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
||||||
|
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||||
|
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||||
|
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||||
|
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||||
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||||
@@ -51,12 +77,22 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn
|
|||||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||||
github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4=
|
github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4=
|
||||||
github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
|
github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
|
||||||
|
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||||
github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68=
|
github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68=
|
||||||
github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
|
github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
|
||||||
|
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
|
||||||
|
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||||
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/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.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||||
|
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
|
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
|
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
@@ -64,18 +100,59 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
|||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 h1:8Tjv8EJ+pM1xP8mK6egEbD1OgnVTyacbefKhmbLhIhU=
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 h1:8Tjv8EJ+pM1xP8mK6egEbD1OgnVTyacbefKhmbLhIhU=
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2/go.mod h1:pkJQ2tZHJ0aFOVEEot6oZmaVEZcRme73eIFmhiVuRWs=
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2/go.mod h1:pkJQ2tZHJ0aFOVEEot6oZmaVEZcRme73eIFmhiVuRWs=
|
||||||
|
github.com/hashicorp/consul/api v1.33.0 h1:MnFUzN1Bo6YDGi/EsRLbVNgA4pyCymmcswrE5j4OHBM=
|
||||||
|
github.com/hashicorp/consul/api v1.33.0/go.mod h1:vLz2I/bqqCYiG0qRHGerComvbwSWKswc8rRFtnYBrIw=
|
||||||
|
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||||
|
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
|
||||||
|
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||||
|
github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||||
|
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
|
||||||
|
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
|
||||||
|
github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c=
|
||||||
|
github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
|
||||||
|
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
||||||
|
github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc=
|
||||||
|
github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
||||||
|
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
|
||||||
|
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
|
||||||
|
github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
|
||||||
|
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
|
||||||
|
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
|
||||||
|
github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
|
||||||
|
github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc=
|
||||||
|
github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
|
||||||
|
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
|
||||||
|
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
|
||||||
|
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||||
|
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||||
|
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||||
|
github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
|
||||||
|
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
|
||||||
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||||
github.com/hashicorp/hcl/v2 v2.18.1 h1:6nxnOJFku1EuSawSD81fuviYUV8DxFr3fp2dUi3ZYSo=
|
github.com/hashicorp/hcl/v2 v2.18.1 h1:6nxnOJFku1EuSawSD81fuviYUV8DxFr3fp2dUi3ZYSo=
|
||||||
github.com/hashicorp/hcl/v2 v2.18.1/go.mod h1:ThLC89FV4p9MPW804KVbe/cEXoQ8NZEh+JtMeeGErHE=
|
github.com/hashicorp/hcl/v2 v2.18.1/go.mod h1:ThLC89FV4p9MPW804KVbe/cEXoQ8NZEh+JtMeeGErHE=
|
||||||
|
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
|
||||||
|
github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc=
|
||||||
|
github.com/hashicorp/memberlist v0.5.0/go.mod h1:yvyXLpo0QaGE59Y7hDTsTzDD25JYBZ4mHgHUZ8lrOI0=
|
||||||
|
github.com/hashicorp/serf v0.10.1 h1:Z1H2J60yRKvfDYAOZLd2MU0ND4AH/WDz7xYHDWQsIPY=
|
||||||
|
github.com/hashicorp/serf v0.10.1/go.mod h1:yL2t6BqATOLGc5HF7qbFkTfXoPIY0WZdWHfEvMqbG+4=
|
||||||
|
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||||
|
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
|
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||||
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
||||||
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
||||||
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||||
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||||
|
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
|
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||||
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||||
@@ -86,42 +163,86 @@ github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
|||||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||||
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
|
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
|
||||||
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||||
|
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||||
|
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||||
|
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||||
|
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||||
|
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
||||||
|
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||||
|
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||||
|
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||||
|
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||||
|
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
|
||||||
|
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||||
|
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||||
|
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-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/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
|
||||||
|
github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
|
||||||
|
github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI=
|
||||||
|
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
||||||
|
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||||
github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0=
|
github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0=
|
||||||
github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0=
|
github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0=
|
||||||
|
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
|
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
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/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||||
|
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||||
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||||
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||||
|
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
|
||||||
|
github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
|
||||||
|
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||||
|
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||||
|
github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
|
||||||
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
|
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
|
||||||
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
|
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
|
||||||
|
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||||
|
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
|
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
|
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
|
||||||
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
|
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
|
||||||
|
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||||
|
github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4=
|
||||||
github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs=
|
github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs=
|
||||||
github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA=
|
github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA=
|
||||||
|
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||||
|
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||||
|
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
|
||||||
github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
|
github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
|
||||||
github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
|
github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
|
||||||
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||||
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||||
|
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||||
github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
|
github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
|
||||||
github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
|
github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
|
||||||
github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
|
github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
|
||||||
github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
|
github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
|
||||||
|
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||||
github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8=
|
github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8=
|
||||||
github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I=
|
github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I=
|
||||||
|
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||||
|
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||||
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
|
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
|
||||||
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
|
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
|
||||||
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
|
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
|
||||||
@@ -133,16 +254,21 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An
|
|||||||
github.com/spf13/viper v1.18.0 h1:pN6W1ub/G4OfnM+NR9p7xP9R6TltLUzp5JG9yZD3Qg0=
|
github.com/spf13/viper v1.18.0 h1:pN6W1ub/G4OfnM+NR9p7xP9R6TltLUzp5JG9yZD3Qg0=
|
||||||
github.com/spf13/viper v1.18.0/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk=
|
github.com/spf13/viper v1.18.0/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk=
|
||||||
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.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/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
|
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.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
|
||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||||
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||||
|
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||||
github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA=
|
github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA=
|
||||||
@@ -189,19 +315,67 @@ go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
|
|||||||
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
|
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
|
||||||
golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c=
|
golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c=
|
||||||
golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk=
|
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-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.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
|
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
|
||||||
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
|
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
|
||||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
|
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
|
||||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
|
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
|
||||||
|
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/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg=
|
golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg=
|
||||||
golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ=
|
golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ=
|
||||||
|
golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ=
|
||||||
|
golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc=
|
||||||
|
golang.org/x/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-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/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-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8=
|
||||||
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
|
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
|
||||||
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
|
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
|
||||||
|
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-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-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
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-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/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.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.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
|
||||||
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
|
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.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.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
|
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
|
||||||
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
|
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
|
||||||
|
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/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=
|
||||||
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 h1:BIRfGDEjiHRrk0QKZe3Xv2ieMhtgRGeLcZQ0mIVn4EY=
|
google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 h1:BIRfGDEjiHRrk0QKZe3Xv2ieMhtgRGeLcZQ0mIVn4EY=
|
||||||
@@ -212,11 +386,17 @@ google.golang.org/grpc v1.75.0 h1:+TW+dqTd2Biwe6KKfhE5JpiYIBWq865PhKGSXiivqt4=
|
|||||||
google.golang.org/grpc v1.75.0/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ=
|
google.golang.org/grpc v1.75.0/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ=
|
||||||
google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc=
|
google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc=
|
||||||
google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
|
google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
|
||||||
|
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||||
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||||
|
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
|||||||
51
internal/client/factory.go
Normal file
51
internal/client/factory.go
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
// Package client provides service client factory for creating service clients.
|
||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"git.dcentral.systems/toolz/goplt/internal/client/grpc"
|
||||||
|
"git.dcentral.systems/toolz/goplt/pkg/registry"
|
||||||
|
"git.dcentral.systems/toolz/goplt/pkg/services"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ServiceClientFactory creates service clients for inter-service communication.
|
||||||
|
type ServiceClientFactory struct {
|
||||||
|
registry registry.ServiceRegistry
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewServiceClientFactory creates a new service client factory.
|
||||||
|
func NewServiceClientFactory(reg registry.ServiceRegistry) *ServiceClientFactory {
|
||||||
|
return &ServiceClientFactory{
|
||||||
|
registry: reg,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAuthClient returns an AuthServiceClient.
|
||||||
|
func (f *ServiceClientFactory) GetAuthClient() (services.AuthServiceClient, error) {
|
||||||
|
return grpc.NewAuthClient(f.registry)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetIdentityClient returns an IdentityServiceClient.
|
||||||
|
func (f *ServiceClientFactory) GetIdentityClient() (services.IdentityServiceClient, error) {
|
||||||
|
return grpc.NewIdentityClient(f.registry)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAuthzClient returns an AuthzServiceClient.
|
||||||
|
func (f *ServiceClientFactory) GetAuthzClient() (services.AuthzServiceClient, error) {
|
||||||
|
return grpc.NewAuthzClient(f.registry)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAuditClient returns an AuditServiceClient.
|
||||||
|
func (f *ServiceClientFactory) GetAuditClient() (services.AuditServiceClient, error) {
|
||||||
|
return grpc.NewAuditClient(f.registry)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DiscoverService discovers service instances for a given service name.
|
||||||
|
func (f *ServiceClientFactory) DiscoverService(ctx context.Context, serviceName string) ([]*registry.ServiceInstance, error) {
|
||||||
|
if f.registry == nil {
|
||||||
|
return nil, fmt.Errorf("service registry is not available")
|
||||||
|
}
|
||||||
|
return f.registry.Discover(ctx, serviceName)
|
||||||
|
}
|
||||||
33
internal/client/grpc/audit_client.go
Normal file
33
internal/client/grpc/audit_client.go
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
// Package grpc provides gRPC client implementations for service clients.
|
||||||
|
package grpc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"git.dcentral.systems/toolz/goplt/pkg/registry"
|
||||||
|
"git.dcentral.systems/toolz/goplt/pkg/services"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
registry registry.ServiceRegistry
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAuditClient creates a new gRPC client for the Audit Service.
|
||||||
|
func NewAuditClient(reg registry.ServiceRegistry) (services.AuditServiceClient, error) {
|
||||||
|
return &AuditClient{
|
||||||
|
registry: reg,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Record records an audit log entry.
|
||||||
|
func (c *AuditClient) Record(ctx context.Context, entry *services.AuditLogEntry) error {
|
||||||
|
return fmt.Errorf("not implemented: proto files not yet generated")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Query queries audit logs based on filters.
|
||||||
|
func (c *AuditClient) Query(ctx context.Context, filters *services.AuditLogFilters) ([]services.AuditLogEntry, error) {
|
||||||
|
return nil, fmt.Errorf("not implemented: proto files not yet generated")
|
||||||
|
}
|
||||||
75
internal/client/grpc/auth_client.go
Normal file
75
internal/client/grpc/auth_client.go
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
// Package grpc provides gRPC client implementations for service clients.
|
||||||
|
package grpc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"git.dcentral.systems/toolz/goplt/pkg/registry"
|
||||||
|
"git.dcentral.systems/toolz/goplt/pkg/services"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/grpc/credentials/insecure"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
registry registry.ServiceRegistry
|
||||||
|
// conn will be set when proto files are available
|
||||||
|
// conn *grpc.ClientConn
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAuthClient creates a new gRPC client for the Auth Service.
|
||||||
|
func NewAuthClient(reg registry.ServiceRegistry) (services.AuthServiceClient, error) {
|
||||||
|
return &AuthClient{
|
||||||
|
registry: reg,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Login authenticates a user and returns access and refresh tokens.
|
||||||
|
func (c *AuthClient) Login(ctx context.Context, email, password string) (*services.TokenResponse, error) {
|
||||||
|
// TODO: Implement when proto files are generated
|
||||||
|
return nil, fmt.Errorf("not implemented: proto files not yet generated")
|
||||||
|
}
|
||||||
|
|
||||||
|
// RefreshToken refreshes an access token using a refresh token.
|
||||||
|
func (c *AuthClient) RefreshToken(ctx context.Context, refreshToken string) (*services.TokenResponse, error) {
|
||||||
|
// TODO: Implement when proto files are generated
|
||||||
|
return nil, fmt.Errorf("not implemented: proto files not yet generated")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateToken validates a JWT token and returns the token claims.
|
||||||
|
func (c *AuthClient) ValidateToken(ctx context.Context, token string) (*services.TokenClaims, error) {
|
||||||
|
// TODO: Implement when proto files are generated
|
||||||
|
return nil, fmt.Errorf("not implemented: proto files not yet generated")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Logout invalidates a refresh token.
|
||||||
|
func (c *AuthClient) Logout(ctx context.Context, refreshToken string) error {
|
||||||
|
// TODO: Implement when proto files are generated
|
||||||
|
return fmt.Errorf("not implemented: proto files not yet generated")
|
||||||
|
}
|
||||||
|
|
||||||
|
// connectToService discovers and connects to a service instance.
|
||||||
|
func connectToService(ctx context.Context, reg registry.ServiceRegistry, serviceName string) (*grpc.ClientConn, error) {
|
||||||
|
instances, err := reg.Discover(ctx, serviceName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to discover service %s: %w", serviceName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(instances) == 0 {
|
||||||
|
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
|
||||||
|
}
|
||||||
43
internal/client/grpc/authz_client.go
Normal file
43
internal/client/grpc/authz_client.go
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
// Package grpc provides gRPC client implementations for service clients.
|
||||||
|
package grpc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"git.dcentral.systems/toolz/goplt/pkg/registry"
|
||||||
|
"git.dcentral.systems/toolz/goplt/pkg/services"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
registry registry.ServiceRegistry
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAuthzClient creates a new gRPC client for the Authz Service.
|
||||||
|
func NewAuthzClient(reg registry.ServiceRegistry) (services.AuthzServiceClient, error) {
|
||||||
|
return &AuthzClient{
|
||||||
|
registry: reg,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
return fmt.Errorf("not implemented: proto files not yet generated")
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasPermission checks if a user has a specific permission.
|
||||||
|
func (c *AuthzClient) HasPermission(ctx context.Context, userID, permission string) (bool, error) {
|
||||||
|
return false, fmt.Errorf("not implemented: proto files not yet generated")
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserPermissions returns all permissions for a user.
|
||||||
|
func (c *AuthzClient) GetUserPermissions(ctx context.Context, userID string) ([]services.Permission, error) {
|
||||||
|
return nil, fmt.Errorf("not implemented: proto files not yet generated")
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserRoles returns all roles for a user.
|
||||||
|
func (c *AuthzClient) GetUserRoles(ctx context.Context, userID string) ([]services.Role, error) {
|
||||||
|
return nil, fmt.Errorf("not implemented: proto files not yet generated")
|
||||||
|
}
|
||||||
63
internal/client/grpc/identity_client.go
Normal file
63
internal/client/grpc/identity_client.go
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
// Package grpc provides gRPC client implementations for service clients.
|
||||||
|
package grpc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"git.dcentral.systems/toolz/goplt/pkg/registry"
|
||||||
|
"git.dcentral.systems/toolz/goplt/pkg/services"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
registry registry.ServiceRegistry
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewIdentityClient creates a new gRPC client for the Identity Service.
|
||||||
|
func NewIdentityClient(reg registry.ServiceRegistry) (services.IdentityServiceClient, error) {
|
||||||
|
return &IdentityClient{
|
||||||
|
registry: reg,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUser retrieves a user by ID.
|
||||||
|
func (c *IdentityClient) GetUser(ctx context.Context, id string) (*services.User, error) {
|
||||||
|
return nil, fmt.Errorf("not implemented: proto files not yet generated")
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserByEmail retrieves a user by email address.
|
||||||
|
func (c *IdentityClient) GetUserByEmail(ctx context.Context, email string) (*services.User, error) {
|
||||||
|
return nil, fmt.Errorf("not implemented: proto files not yet generated")
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateUser creates a new user.
|
||||||
|
func (c *IdentityClient) CreateUser(ctx context.Context, user *services.CreateUserRequest) (*services.User, error) {
|
||||||
|
return nil, fmt.Errorf("not implemented: proto files not yet generated")
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateUser updates an existing user.
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteUser deletes a user.
|
||||||
|
func (c *IdentityClient) DeleteUser(ctx context.Context, id string) error {
|
||||||
|
return fmt.Errorf("not implemented: proto files not yet generated")
|
||||||
|
}
|
||||||
|
|
||||||
|
// VerifyEmail verifies a user's email address using a verification token.
|
||||||
|
func (c *IdentityClient) VerifyEmail(ctx context.Context, token string) error {
|
||||||
|
return fmt.Errorf("not implemented: proto files not yet generated")
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequestPasswordReset requests a password reset token.
|
||||||
|
func (c *IdentityClient) RequestPasswordReset(ctx context.Context, email string) error {
|
||||||
|
return fmt.Errorf("not implemented: proto files not yet generated")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResetPassword resets a user's password using a reset token.
|
||||||
|
func (c *IdentityClient) ResetPassword(ctx context.Context, token, newPassword string) error {
|
||||||
|
return fmt.Errorf("not implemented: proto files not yet generated")
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"git.dcentral.systems/toolz/goplt/internal/client"
|
||||||
configimpl "git.dcentral.systems/toolz/goplt/internal/config"
|
configimpl "git.dcentral.systems/toolz/goplt/internal/config"
|
||||||
errorbusimpl "git.dcentral.systems/toolz/goplt/internal/errorbus"
|
errorbusimpl "git.dcentral.systems/toolz/goplt/internal/errorbus"
|
||||||
"git.dcentral.systems/toolz/goplt/internal/health"
|
"git.dcentral.systems/toolz/goplt/internal/health"
|
||||||
@@ -14,10 +15,12 @@ import (
|
|||||||
loggerimpl "git.dcentral.systems/toolz/goplt/internal/logger"
|
loggerimpl "git.dcentral.systems/toolz/goplt/internal/logger"
|
||||||
"git.dcentral.systems/toolz/goplt/internal/metrics"
|
"git.dcentral.systems/toolz/goplt/internal/metrics"
|
||||||
"git.dcentral.systems/toolz/goplt/internal/observability"
|
"git.dcentral.systems/toolz/goplt/internal/observability"
|
||||||
|
"git.dcentral.systems/toolz/goplt/internal/registry/consul"
|
||||||
"git.dcentral.systems/toolz/goplt/internal/server"
|
"git.dcentral.systems/toolz/goplt/internal/server"
|
||||||
"git.dcentral.systems/toolz/goplt/pkg/config"
|
"git.dcentral.systems/toolz/goplt/pkg/config"
|
||||||
"git.dcentral.systems/toolz/goplt/pkg/errorbus"
|
"git.dcentral.systems/toolz/goplt/pkg/errorbus"
|
||||||
"git.dcentral.systems/toolz/goplt/pkg/logger"
|
"git.dcentral.systems/toolz/goplt/pkg/logger"
|
||||||
|
"git.dcentral.systems/toolz/goplt/pkg/registry"
|
||||||
"go.opentelemetry.io/otel/trace"
|
"go.opentelemetry.io/otel/trace"
|
||||||
"go.opentelemetry.io/otel/trace/noop"
|
"go.opentelemetry.io/otel/trace/noop"
|
||||||
"go.uber.org/fx"
|
"go.uber.org/fx"
|
||||||
@@ -158,13 +161,11 @@ func ProvideErrorBus() fx.Option {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ProvideHealthRegistry creates an FX option that provides the health check registry.
|
// ProvideHealthRegistry creates an FX option that provides the health check registry.
|
||||||
|
// Note: Database health checkers are registered by services that create their own database clients.
|
||||||
func ProvideHealthRegistry() fx.Option {
|
func ProvideHealthRegistry() fx.Option {
|
||||||
return fx.Provide(func(dbClient *database.Client) (*health.Registry, error) {
|
return fx.Provide(func() (*health.Registry, error) {
|
||||||
registry := health.NewRegistry()
|
registry := health.NewRegistry()
|
||||||
|
// Services will register their own health checkers (e.g., database, external dependencies)
|
||||||
// Register database health checker
|
|
||||||
registry.Register("database", health.NewDatabaseChecker(dbClient))
|
|
||||||
|
|
||||||
return registry, nil
|
return registry, nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -176,6 +177,72 @@ func ProvideMetrics() fx.Option {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ProvideServiceRegistry creates an FX option that provides the service registry.
|
||||||
|
func ProvideServiceRegistry() fx.Option {
|
||||||
|
return fx.Provide(func(cfg config.ConfigProvider) (registry.ServiceRegistry, error) {
|
||||||
|
registryType := cfg.GetString("registry.type")
|
||||||
|
if registryType == "" {
|
||||||
|
registryType = "consul"
|
||||||
|
}
|
||||||
|
|
||||||
|
switch registryType {
|
||||||
|
case "consul":
|
||||||
|
consulCfg := consul.Config{
|
||||||
|
Address: cfg.GetString("registry.consul.address"),
|
||||||
|
Datacenter: cfg.GetString("registry.consul.datacenter"),
|
||||||
|
Scheme: cfg.GetString("registry.consul.scheme"),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set defaults
|
||||||
|
if consulCfg.Address == "" {
|
||||||
|
consulCfg.Address = "localhost:8500"
|
||||||
|
}
|
||||||
|
if consulCfg.Datacenter == "" {
|
||||||
|
consulCfg.Datacenter = "dc1"
|
||||||
|
}
|
||||||
|
if consulCfg.Scheme == "" {
|
||||||
|
consulCfg.Scheme = "http"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse health check configuration
|
||||||
|
healthCheckInterval := cfg.GetDuration("registry.consul.health_check.interval")
|
||||||
|
if healthCheckInterval == 0 {
|
||||||
|
healthCheckInterval = 10 * time.Second
|
||||||
|
}
|
||||||
|
healthCheckTimeout := cfg.GetDuration("registry.consul.health_check.timeout")
|
||||||
|
if healthCheckTimeout == 0 {
|
||||||
|
healthCheckTimeout = 3 * time.Second
|
||||||
|
}
|
||||||
|
healthCheckDeregisterAfter := cfg.GetDuration("registry.consul.health_check.deregister_after")
|
||||||
|
if healthCheckDeregisterAfter == 0 {
|
||||||
|
healthCheckDeregisterAfter = 30 * time.Second
|
||||||
|
}
|
||||||
|
healthCheckHTTP := cfg.GetString("registry.consul.health_check.http")
|
||||||
|
if healthCheckHTTP == "" {
|
||||||
|
healthCheckHTTP = "/healthz"
|
||||||
|
}
|
||||||
|
|
||||||
|
consulCfg.HealthCheck = consul.HealthCheckConfig{
|
||||||
|
Interval: healthCheckInterval,
|
||||||
|
Timeout: healthCheckTimeout,
|
||||||
|
DeregisterAfter: healthCheckDeregisterAfter,
|
||||||
|
HTTP: healthCheckHTTP,
|
||||||
|
}
|
||||||
|
|
||||||
|
return consul.NewRegistry(consulCfg)
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unsupported registry type: %s", registryType)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProvideServiceClientFactory creates an FX option that provides the service client factory.
|
||||||
|
func ProvideServiceClientFactory() fx.Option {
|
||||||
|
return fx.Provide(func(reg registry.ServiceRegistry) (*client.ServiceClientFactory, error) {
|
||||||
|
return client.NewServiceClientFactory(reg), nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// ProvideTracer creates an FX option that provides the OpenTelemetry tracer.
|
// ProvideTracer creates an FX option that provides the OpenTelemetry tracer.
|
||||||
func ProvideTracer() fx.Option {
|
func ProvideTracer() fx.Option {
|
||||||
return fx.Provide(func(cfg config.ConfigProvider, lc fx.Lifecycle) (trace.TracerProvider, error) {
|
return fx.Provide(func(cfg config.ConfigProvider, lc fx.Lifecycle) (trace.TracerProvider, error) {
|
||||||
@@ -318,18 +385,21 @@ func ProvideHTTPServer() fx.Option {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// CoreModule returns an FX option that provides all core services.
|
// CoreModule returns an FX option that provides all core kernel infrastructure services.
|
||||||
// This includes configuration, logging, database, error bus, health checks, metrics, tracing, and HTTP server.
|
// This includes configuration, logging, error bus, health checks, metrics, tracing, service registry, and service client factory.
|
||||||
|
// Note: Database and HTTP server are NOT included - services will create their own instances.
|
||||||
|
// HTTP server foundation is available via server.NewServer() for services to use.
|
||||||
func CoreModule() fx.Option {
|
func CoreModule() fx.Option {
|
||||||
return fx.Options(
|
return fx.Options(
|
||||||
ProvideConfig(),
|
ProvideConfig(),
|
||||||
ProvideLogger(),
|
ProvideLogger(),
|
||||||
ProvideDatabase(),
|
|
||||||
ProvideErrorBus(),
|
ProvideErrorBus(),
|
||||||
ProvideHealthRegistry(),
|
ProvideHealthRegistry(),
|
||||||
ProvideMetrics(),
|
ProvideMetrics(),
|
||||||
ProvideTracer(),
|
ProvideTracer(),
|
||||||
ProvideHTTPServer(),
|
ProvideServiceRegistry(),
|
||||||
|
ProvideServiceClientFactory(),
|
||||||
|
// Note: ProvideDatabase() and ProvideHTTPServer() are removed - services create their own
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,13 +22,15 @@ type Client struct {
|
|||||||
// Config holds database configuration.
|
// Config holds database configuration.
|
||||||
type Config struct {
|
type Config struct {
|
||||||
DSN string
|
DSN string
|
||||||
|
Schema string // Schema name for schema isolation (e.g., "identity", "auth", "authz", "audit")
|
||||||
MaxConnections int
|
MaxConnections int
|
||||||
MaxIdleConns int
|
MaxIdleConns int
|
||||||
ConnMaxLifetime time.Duration
|
ConnMaxLifetime time.Duration
|
||||||
ConnMaxIdleTime time.Duration
|
ConnMaxIdleTime time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewClient creates a new Ent client with connection pooling.
|
// NewClient creates a new Ent client with connection pooling and schema isolation support.
|
||||||
|
// If schema is provided, it will be created if it doesn't exist and set as the search path.
|
||||||
func NewClient(cfg Config) (*Client, error) {
|
func NewClient(cfg Config) (*Client, error) {
|
||||||
// Open database connection
|
// Open database connection
|
||||||
db, err := sql.Open("postgres", cfg.DSN)
|
db, err := sql.Open("postgres", cfg.DSN)
|
||||||
@@ -51,6 +53,19 @@ func NewClient(cfg Config) (*Client, error) {
|
|||||||
return nil, fmt.Errorf("failed to ping database: %w", err)
|
return nil, fmt.Errorf("failed to ping database: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create schema if provided
|
||||||
|
if cfg.Schema != "" {
|
||||||
|
if err := createSchemaIfNotExists(ctx, db, cfg.Schema); err != nil {
|
||||||
|
_ = db.Close()
|
||||||
|
return nil, fmt.Errorf("failed to create schema %s: %w", cfg.Schema, err)
|
||||||
|
}
|
||||||
|
// Set search path to the schema
|
||||||
|
if _, err := db.ExecContext(ctx, fmt.Sprintf("SET search_path TO %s", cfg.Schema)); err != nil {
|
||||||
|
_ = db.Close()
|
||||||
|
return nil, fmt.Errorf("failed to set search path to schema %s: %w", cfg.Schema, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Create Ent driver
|
// Create Ent driver
|
||||||
drv := entsql.OpenDB(dialect.Postgres, db)
|
drv := entsql.OpenDB(dialect.Postgres, db)
|
||||||
|
|
||||||
@@ -63,6 +78,49 @@ func NewClient(cfg Config) (*Client, error) {
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewClientWithSchema is a convenience function that creates a client with a specific schema.
|
||||||
|
func NewClientWithSchema(dsn string, schema string) (*Client, error) {
|
||||||
|
return NewClient(Config{
|
||||||
|
DSN: dsn,
|
||||||
|
Schema: schema,
|
||||||
|
MaxConnections: 25,
|
||||||
|
MaxIdleConns: 5,
|
||||||
|
ConnMaxLifetime: 5 * time.Minute,
|
||||||
|
ConnMaxIdleTime: 10 * time.Minute,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// createSchemaIfNotExists creates a PostgreSQL schema if it doesn't exist.
|
||||||
|
func createSchemaIfNotExists(ctx context.Context, db *sql.DB, schemaName string) error {
|
||||||
|
// Use a transaction to ensure atomicity
|
||||||
|
tx, err := db.BeginTx(ctx, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer tx.Rollback()
|
||||||
|
|
||||||
|
// Check if schema exists
|
||||||
|
var exists bool
|
||||||
|
err = tx.QueryRowContext(ctx,
|
||||||
|
"SELECT EXISTS(SELECT 1 FROM information_schema.schemata WHERE schema_name = $1)",
|
||||||
|
schemaName,
|
||||||
|
).Scan(&exists)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create schema if it doesn't exist
|
||||||
|
if !exists {
|
||||||
|
// Use fmt.Sprintf for schema name since it's a configuration value, not user input
|
||||||
|
_, err = tx.ExecContext(ctx, fmt.Sprintf("CREATE SCHEMA IF NOT EXISTS %s", schemaName))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return tx.Commit()
|
||||||
|
}
|
||||||
|
|
||||||
// Close closes the database connection.
|
// Close closes the database connection.
|
||||||
func (c *Client) Close() error {
|
func (c *Client) Close() error {
|
||||||
if err := c.Client.Close(); err != nil {
|
if err := c.Client.Close(); err != nil {
|
||||||
|
|||||||
198
internal/registry/consul/consul.go
Normal file
198
internal/registry/consul/consul.go
Normal file
@@ -0,0 +1,198 @@
|
|||||||
|
// Package consul provides Consul-based service registry implementation.
|
||||||
|
package consul
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.dcentral.systems/toolz/goplt/pkg/registry"
|
||||||
|
consulapi "github.com/hashicorp/consul/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ConsulRegistry implements ServiceRegistry using Consul.
|
||||||
|
type ConsulRegistry struct {
|
||||||
|
client *consulapi.Client
|
||||||
|
config *Config
|
||||||
|
}
|
||||||
|
|
||||||
|
// Config holds Consul registry configuration.
|
||||||
|
type Config struct {
|
||||||
|
Address string // Consul agent address (e.g., "localhost:8500")
|
||||||
|
Datacenter string // Consul datacenter
|
||||||
|
Scheme string // "http" or "https"
|
||||||
|
HealthCheck HealthCheckConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
// HealthCheckConfig holds health check configuration.
|
||||||
|
type HealthCheckConfig struct {
|
||||||
|
Interval time.Duration // Health check interval
|
||||||
|
Timeout time.Duration // Health check timeout
|
||||||
|
DeregisterAfter time.Duration // Time to wait before deregistering unhealthy service
|
||||||
|
HTTP string // HTTP health check endpoint (e.g., "/healthz")
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRegistry creates a new Consul-based service registry.
|
||||||
|
func NewRegistry(cfg Config) (*ConsulRegistry, error) {
|
||||||
|
consulConfig := consulapi.DefaultConfig()
|
||||||
|
if cfg.Address != "" {
|
||||||
|
consulConfig.Address = cfg.Address
|
||||||
|
}
|
||||||
|
if cfg.Datacenter != "" {
|
||||||
|
consulConfig.Datacenter = cfg.Datacenter
|
||||||
|
}
|
||||||
|
if cfg.Scheme != "" {
|
||||||
|
consulConfig.Scheme = cfg.Scheme
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := consulapi.NewClient(consulConfig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create Consul client: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &ConsulRegistry{
|
||||||
|
client: client,
|
||||||
|
config: &cfg,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register registers a service instance with Consul.
|
||||||
|
func (r *ConsulRegistry) Register(ctx context.Context, service *registry.ServiceInstance) error {
|
||||||
|
registration := &consulapi.AgentServiceRegistration{
|
||||||
|
ID: service.ID,
|
||||||
|
Name: service.Name,
|
||||||
|
Address: service.Address,
|
||||||
|
Port: service.Port,
|
||||||
|
Tags: service.Tags,
|
||||||
|
Meta: service.Metadata,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add health check if configured
|
||||||
|
if r.config.HealthCheck.HTTP != "" {
|
||||||
|
healthCheckURL := fmt.Sprintf("http://%s:%d%s", service.Address, service.Port, r.config.HealthCheck.HTTP)
|
||||||
|
registration.Check = &consulapi.AgentServiceCheck{
|
||||||
|
HTTP: healthCheckURL,
|
||||||
|
Interval: r.config.HealthCheck.Interval.String(),
|
||||||
|
Timeout: r.config.HealthCheck.Timeout.String(),
|
||||||
|
DeregisterCriticalServiceAfter: r.config.HealthCheck.DeregisterAfter.String(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return r.client.Agent().ServiceRegister(registration)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deregister removes a service instance from Consul.
|
||||||
|
func (r *ConsulRegistry) Deregister(ctx context.Context, serviceID string) error {
|
||||||
|
return r.client.Agent().ServiceDeregister(serviceID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Discover returns all healthy instances of a service.
|
||||||
|
func (r *ConsulRegistry) Discover(ctx context.Context, serviceName string) ([]*registry.ServiceInstance, error) {
|
||||||
|
services, _, err := r.client.Health().Service(serviceName, "", true, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to discover service %s: %w", serviceName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
instances := make([]*registry.ServiceInstance, 0, len(services))
|
||||||
|
for _, service := range services {
|
||||||
|
instances = append(instances, ®istry.ServiceInstance{
|
||||||
|
ID: service.Service.ID,
|
||||||
|
Name: service.Service.Service,
|
||||||
|
Address: service.Service.Address,
|
||||||
|
Port: service.Service.Port,
|
||||||
|
Tags: service.Service.Tags,
|
||||||
|
Metadata: service.Service.Meta,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return instances, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Watch returns a channel that receives updates when service instances change.
|
||||||
|
func (r *ConsulRegistry) Watch(ctx context.Context, serviceName string) (<-chan []*registry.ServiceInstance, error) {
|
||||||
|
updates := make(chan []*registry.ServiceInstance, 10)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer close(updates)
|
||||||
|
|
||||||
|
lastIndex := uint64(0)
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
services, meta, err := r.client.Health().Service(serviceName, "", true, &consulapi.QueryOptions{
|
||||||
|
WaitIndex: lastIndex,
|
||||||
|
WaitTime: 10 * time.Second,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
// Log error and continue
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if meta.LastIndex != lastIndex {
|
||||||
|
instances := make([]*registry.ServiceInstance, 0, len(services))
|
||||||
|
for _, service := range services {
|
||||||
|
instances = append(instances, ®istry.ServiceInstance{
|
||||||
|
ID: service.Service.ID,
|
||||||
|
Name: service.Service.Service,
|
||||||
|
Address: service.Service.Address,
|
||||||
|
Port: service.Service.Port,
|
||||||
|
Tags: service.Service.Tags,
|
||||||
|
Metadata: service.Service.Meta,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case updates <- instances:
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
lastIndex = meta.LastIndex
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return updates, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Health returns the health status of a service instance.
|
||||||
|
func (r *ConsulRegistry) Health(ctx context.Context, serviceID string) (*registry.HealthStatus, error) {
|
||||||
|
entries, _, err := r.client.Health().Service(serviceID, "", false, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get health for service %s: %w", serviceID, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(entries) == 0 {
|
||||||
|
return ®istry.HealthStatus{
|
||||||
|
ServiceID: serviceID,
|
||||||
|
Status: "unknown",
|
||||||
|
Message: "service not found",
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check health status from service entry checks
|
||||||
|
status := "healthy"
|
||||||
|
message := "all checks passing"
|
||||||
|
|
||||||
|
// Get the first entry (should be the service instance)
|
||||||
|
entry := entries[0]
|
||||||
|
for _, check := range entry.Checks {
|
||||||
|
if check.Status == consulapi.HealthCritical {
|
||||||
|
status = "critical"
|
||||||
|
message = check.Output
|
||||||
|
break
|
||||||
|
} else if check.Status == consulapi.HealthWarning {
|
||||||
|
status = "unhealthy"
|
||||||
|
message = check.Output
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ®istry.HealthStatus{
|
||||||
|
ServiceID: serviceID,
|
||||||
|
Status: status,
|
||||||
|
Message: message,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
41
pkg/registry/registry.go
Normal file
41
pkg/registry/registry.go
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
// Package registry provides service registry interface for service discovery.
|
||||||
|
package registry
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ServiceRegistry is the interface for service discovery and registration.
|
||||||
|
type ServiceRegistry interface {
|
||||||
|
// Register registers a service instance with the registry.
|
||||||
|
Register(ctx context.Context, service *ServiceInstance) error
|
||||||
|
|
||||||
|
// Deregister removes a service instance from the registry.
|
||||||
|
Deregister(ctx context.Context, serviceID string) error
|
||||||
|
|
||||||
|
// Discover returns all healthy instances of a service.
|
||||||
|
Discover(ctx context.Context, serviceName string) ([]*ServiceInstance, error)
|
||||||
|
|
||||||
|
// Watch returns a channel that receives updates when service instances change.
|
||||||
|
Watch(ctx context.Context, serviceName string) (<-chan []*ServiceInstance, error)
|
||||||
|
|
||||||
|
// Health returns the health status of a service instance.
|
||||||
|
Health(ctx context.Context, serviceID string) (*HealthStatus, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServiceInstance represents a service instance in the registry.
|
||||||
|
type ServiceInstance struct {
|
||||||
|
ID string // Unique instance ID
|
||||||
|
Name string // Service name (e.g., "auth-service", "identity-service")
|
||||||
|
Address string // Service address (IP or hostname)
|
||||||
|
Port int // Service port
|
||||||
|
Tags []string // Service tags for filtering
|
||||||
|
Metadata map[string]string // Additional metadata
|
||||||
|
}
|
||||||
|
|
||||||
|
// HealthStatus represents the health status of a service instance.
|
||||||
|
type HealthStatus struct {
|
||||||
|
ServiceID string // Service instance ID
|
||||||
|
Status string // Health status: "healthy", "unhealthy", "critical"
|
||||||
|
Message string // Optional status message
|
||||||
|
}
|
||||||
39
pkg/services/audit.go
Normal file
39
pkg/services/audit.go
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
// Package services provides service client interfaces for inter-service communication.
|
||||||
|
package services
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AuditServiceClient is the interface for communicating with the Audit Service.
|
||||||
|
type AuditServiceClient interface {
|
||||||
|
// Record records an audit log entry.
|
||||||
|
Record(ctx context.Context, entry *AuditLogEntry) error
|
||||||
|
|
||||||
|
// Query queries audit logs based on filters.
|
||||||
|
Query(ctx context.Context, filters *AuditLogFilters) ([]AuditLogEntry, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AuditLogEntry represents an audit log entry.
|
||||||
|
type AuditLogEntry struct {
|
||||||
|
UserID string `json:"user_id"`
|
||||||
|
Action string `json:"action"` // e.g., "user.create", "user.update"
|
||||||
|
Resource string `json:"resource"` // e.g., "user", "role"
|
||||||
|
ResourceID string `json:"resource_id"`
|
||||||
|
IPAddress string `json:"ip_address"`
|
||||||
|
UserAgent string `json:"user_agent"`
|
||||||
|
Metadata map[string]string `json:"metadata"`
|
||||||
|
Timestamp int64 `json:"timestamp"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AuditLogFilters contains filters for querying audit logs.
|
||||||
|
type AuditLogFilters struct {
|
||||||
|
UserID *string `json:"user_id,omitempty"`
|
||||||
|
Action *string `json:"action,omitempty"`
|
||||||
|
Resource *string `json:"resource,omitempty"`
|
||||||
|
ResourceID *string `json:"resource_id,omitempty"`
|
||||||
|
StartTime *int64 `json:"start_time,omitempty"`
|
||||||
|
EndTime *int64 `json:"end_time,omitempty"`
|
||||||
|
Limit int `json:"limit"` // Max number of results
|
||||||
|
Offset int `json:"offset"` // Pagination offset
|
||||||
|
}
|
||||||
37
pkg/services/auth.go
Normal file
37
pkg/services/auth.go
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
// Package services provides service client interfaces for inter-service communication.
|
||||||
|
package services
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AuthServiceClient is the interface for communicating with the Auth Service.
|
||||||
|
type AuthServiceClient interface {
|
||||||
|
// Login authenticates a user and returns access and refresh tokens.
|
||||||
|
Login(ctx context.Context, email, password string) (*TokenResponse, error)
|
||||||
|
|
||||||
|
// RefreshToken refreshes an access token using a refresh token.
|
||||||
|
RefreshToken(ctx context.Context, refreshToken string) (*TokenResponse, error)
|
||||||
|
|
||||||
|
// ValidateToken validates a JWT token and returns the token claims.
|
||||||
|
ValidateToken(ctx context.Context, token string) (*TokenClaims, error)
|
||||||
|
|
||||||
|
// Logout invalidates a refresh token.
|
||||||
|
Logout(ctx context.Context, refreshToken string) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// TokenResponse contains the authentication tokens.
|
||||||
|
type TokenResponse struct {
|
||||||
|
AccessToken string `json:"access_token"`
|
||||||
|
RefreshToken string `json:"refresh_token"`
|
||||||
|
ExpiresIn int64 `json:"expires_in"` // seconds
|
||||||
|
TokenType string `json:"token_type"` // "Bearer"
|
||||||
|
}
|
||||||
|
|
||||||
|
// TokenClaims contains the claims from a validated JWT token.
|
||||||
|
type TokenClaims struct {
|
||||||
|
UserID string `json:"user_id"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
Roles []string `json:"roles"`
|
||||||
|
ExpiresAt int64 `json:"expires_at"`
|
||||||
|
}
|
||||||
37
pkg/services/authz.go
Normal file
37
pkg/services/authz.go
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
// Package services provides service client interfaces for inter-service communication.
|
||||||
|
package services
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AuthzServiceClient is the interface for communicating with the Authz Service.
|
||||||
|
type AuthzServiceClient interface {
|
||||||
|
// Authorize checks if a user has a specific permission and returns an error if not.
|
||||||
|
Authorize(ctx context.Context, userID, permission string) error
|
||||||
|
|
||||||
|
// HasPermission checks if a user has a specific permission.
|
||||||
|
HasPermission(ctx context.Context, userID, permission string) (bool, error)
|
||||||
|
|
||||||
|
// GetUserPermissions returns all permissions for a user.
|
||||||
|
GetUserPermissions(ctx context.Context, userID string) ([]Permission, error)
|
||||||
|
|
||||||
|
// GetUserRoles returns all roles for a user.
|
||||||
|
GetUserRoles(ctx context.Context, userID string) ([]Role, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Permission represents a permission in the system.
|
||||||
|
type Permission struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Code string `json:"code"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Role represents a role in the system.
|
||||||
|
type Role struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
Permissions []string `json:"permissions"` // Permission codes
|
||||||
|
}
|
||||||
62
pkg/services/identity.go
Normal file
62
pkg/services/identity.go
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
// Package services provides service client interfaces for inter-service communication.
|
||||||
|
package services
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
)
|
||||||
|
|
||||||
|
// IdentityServiceClient is the interface for communicating with the Identity Service.
|
||||||
|
type IdentityServiceClient interface {
|
||||||
|
// GetUser retrieves a user by ID.
|
||||||
|
GetUser(ctx context.Context, id string) (*User, error)
|
||||||
|
|
||||||
|
// GetUserByEmail retrieves a user by email address.
|
||||||
|
GetUserByEmail(ctx context.Context, email string) (*User, error)
|
||||||
|
|
||||||
|
// CreateUser creates a new user.
|
||||||
|
CreateUser(ctx context.Context, user *CreateUserRequest) (*User, error)
|
||||||
|
|
||||||
|
// UpdateUser updates an existing user.
|
||||||
|
UpdateUser(ctx context.Context, id string, user *UpdateUserRequest) (*User, error)
|
||||||
|
|
||||||
|
// DeleteUser deletes a user.
|
||||||
|
DeleteUser(ctx context.Context, id string) error
|
||||||
|
|
||||||
|
// VerifyEmail verifies a user's email address using a verification token.
|
||||||
|
VerifyEmail(ctx context.Context, token string) error
|
||||||
|
|
||||||
|
// RequestPasswordReset requests a password reset token.
|
||||||
|
RequestPasswordReset(ctx context.Context, email string) error
|
||||||
|
|
||||||
|
// ResetPassword resets a user's password using a reset token.
|
||||||
|
ResetPassword(ctx context.Context, token, newPassword string) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// User represents a user in the system.
|
||||||
|
type User struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
Username string `json:"username"`
|
||||||
|
FirstName string `json:"first_name"`
|
||||||
|
LastName string `json:"last_name"`
|
||||||
|
EmailVerified bool `json:"email_verified"`
|
||||||
|
CreatedAt int64 `json:"created_at"`
|
||||||
|
UpdatedAt int64 `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateUserRequest contains the data needed to create a new user.
|
||||||
|
type CreateUserRequest struct {
|
||||||
|
Email string `json:"email"`
|
||||||
|
Username string `json:"username"`
|
||||||
|
Password string `json:"password"`
|
||||||
|
FirstName string `json:"first_name"`
|
||||||
|
LastName string `json:"last_name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateUserRequest contains the data needed to update a user.
|
||||||
|
type UpdateUserRequest struct {
|
||||||
|
Email *string `json:"email,omitempty"`
|
||||||
|
Username *string `json:"username,omitempty"`
|
||||||
|
FirstName *string `json:"first_name,omitempty"`
|
||||||
|
LastName *string `json:"last_name,omitempty"`
|
||||||
|
}
|
||||||
127
services/gateway/gateway.go
Normal file
127
services/gateway/gateway.go
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
// Package gateway provides API Gateway implementation.
|
||||||
|
package gateway
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httputil"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
"git.dcentral.systems/toolz/goplt/internal/client"
|
||||||
|
"git.dcentral.systems/toolz/goplt/pkg/config"
|
||||||
|
"git.dcentral.systems/toolz/goplt/pkg/logger"
|
||||||
|
"git.dcentral.systems/toolz/goplt/pkg/registry"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Gateway handles routing requests to backend services.
|
||||||
|
type Gateway struct {
|
||||||
|
config config.ConfigProvider
|
||||||
|
log logger.Logger
|
||||||
|
clientFactory *client.ServiceClientFactory
|
||||||
|
registry registry.ServiceRegistry
|
||||||
|
routes []RouteConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
// RouteConfig defines a route configuration.
|
||||||
|
type RouteConfig struct {
|
||||||
|
Path string
|
||||||
|
Service string
|
||||||
|
AuthRequired bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewGateway creates a new API Gateway instance.
|
||||||
|
func NewGateway(
|
||||||
|
cfg config.ConfigProvider,
|
||||||
|
log logger.Logger,
|
||||||
|
clientFactory *client.ServiceClientFactory,
|
||||||
|
reg registry.ServiceRegistry,
|
||||||
|
) (*Gateway, error) {
|
||||||
|
// Load route configurations
|
||||||
|
routes := loadRoutes(cfg)
|
||||||
|
|
||||||
|
return &Gateway{
|
||||||
|
config: cfg,
|
||||||
|
log: log,
|
||||||
|
clientFactory: clientFactory,
|
||||||
|
registry: reg,
|
||||||
|
routes: routes,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetupRoutes configures routes on the Gin router.
|
||||||
|
func (g *Gateway) SetupRoutes(router *gin.Engine) {
|
||||||
|
// Setup route handlers
|
||||||
|
for _, route := range g.routes {
|
||||||
|
route := route // Capture for closure
|
||||||
|
router.Any(route.Path, g.handleRoute(route))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default handler for unmatched routes
|
||||||
|
router.NoRoute(func(c *gin.Context) {
|
||||||
|
c.JSON(http.StatusNotFound, gin.H{
|
||||||
|
"error": "Route not found",
|
||||||
|
"path": c.Request.URL.Path,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleRoute returns a handler function for a route.
|
||||||
|
func (g *Gateway) handleRoute(route RouteConfig) gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
// TODO: Add authentication middleware if auth_required is true
|
||||||
|
// TODO: Add rate limiting middleware
|
||||||
|
// TODO: Add CORS middleware
|
||||||
|
|
||||||
|
// Discover service instances
|
||||||
|
ctx := c.Request.Context()
|
||||||
|
instances, err := g.registry.Discover(ctx, route.Service)
|
||||||
|
if err != nil {
|
||||||
|
g.log.Error("Failed to discover service",
|
||||||
|
logger.String("service", route.Service),
|
||||||
|
logger.Error(err),
|
||||||
|
)
|
||||||
|
c.JSON(http.StatusServiceUnavailable, gin.H{
|
||||||
|
"error": "Service unavailable",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(instances) == 0 {
|
||||||
|
g.log.Warn("No instances found for service",
|
||||||
|
logger.String("service", route.Service),
|
||||||
|
)
|
||||||
|
c.JSON(http.StatusServiceUnavailable, gin.H{
|
||||||
|
"error": "Service unavailable",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use first healthy instance (load balancing can be added later)
|
||||||
|
instance := instances[0]
|
||||||
|
targetURL := fmt.Sprintf("http://%s:%d", instance.Address, instance.Port)
|
||||||
|
|
||||||
|
// Create reverse proxy
|
||||||
|
target, err := url.Parse(targetURL)
|
||||||
|
if err != nil {
|
||||||
|
g.log.Error("Failed to parse target URL",
|
||||||
|
logger.String("url", targetURL),
|
||||||
|
logger.Error(err),
|
||||||
|
)
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
"error": "Internal server error",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
proxy := httputil.NewSingleHostReverseProxy(target)
|
||||||
|
proxy.ServeHTTP(c.Writer, c.Request)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadRoutes loads route configurations from config.
|
||||||
|
func loadRoutes(cfg config.ConfigProvider) []RouteConfig {
|
||||||
|
// For now, return empty routes - will be loaded from config in future
|
||||||
|
// This is a placeholder implementation
|
||||||
|
return []RouteConfig{}
|
||||||
|
}
|
||||||
127
services/gateway/internal/gateway.go
Normal file
127
services/gateway/internal/gateway.go
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
// Package gateway provides API Gateway implementation.
|
||||||
|
package gateway
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httputil"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
"git.dcentral.systems/toolz/goplt/internal/client"
|
||||||
|
"git.dcentral.systems/toolz/goplt/pkg/config"
|
||||||
|
"git.dcentral.systems/toolz/goplt/pkg/logger"
|
||||||
|
"git.dcentral.systems/toolz/goplt/pkg/registry"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Gateway handles routing requests to backend services.
|
||||||
|
type Gateway struct {
|
||||||
|
config config.ConfigProvider
|
||||||
|
log logger.Logger
|
||||||
|
clientFactory *client.ServiceClientFactory
|
||||||
|
registry registry.ServiceRegistry
|
||||||
|
routes []RouteConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
// RouteConfig defines a route configuration.
|
||||||
|
type RouteConfig struct {
|
||||||
|
Path string
|
||||||
|
Service string
|
||||||
|
AuthRequired bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewGateway creates a new API Gateway instance.
|
||||||
|
func NewGateway(
|
||||||
|
cfg config.ConfigProvider,
|
||||||
|
log logger.Logger,
|
||||||
|
clientFactory *client.ServiceClientFactory,
|
||||||
|
reg registry.ServiceRegistry,
|
||||||
|
) (*Gateway, error) {
|
||||||
|
// Load route configurations
|
||||||
|
routes := loadRoutes(cfg)
|
||||||
|
|
||||||
|
return &Gateway{
|
||||||
|
config: cfg,
|
||||||
|
log: log,
|
||||||
|
clientFactory: clientFactory,
|
||||||
|
registry: reg,
|
||||||
|
routes: routes,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetupRoutes configures routes on the Gin router.
|
||||||
|
func (g *Gateway) SetupRoutes(router *gin.Engine) {
|
||||||
|
// Setup route handlers
|
||||||
|
for _, route := range g.routes {
|
||||||
|
route := route // Capture for closure
|
||||||
|
router.Any(route.Path, g.handleRoute(route))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default handler for unmatched routes
|
||||||
|
router.NoRoute(func(c *gin.Context) {
|
||||||
|
c.JSON(http.StatusNotFound, gin.H{
|
||||||
|
"error": "Route not found",
|
||||||
|
"path": c.Request.URL.Path,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleRoute returns a handler function for a route.
|
||||||
|
func (g *Gateway) handleRoute(route RouteConfig) gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
// TODO: Add authentication middleware if auth_required is true
|
||||||
|
// TODO: Add rate limiting middleware
|
||||||
|
// TODO: Add CORS middleware
|
||||||
|
|
||||||
|
// Discover service instances
|
||||||
|
ctx := c.Request.Context()
|
||||||
|
instances, err := g.registry.Discover(ctx, route.Service)
|
||||||
|
if err != nil {
|
||||||
|
g.log.Error("Failed to discover service",
|
||||||
|
logger.String("service", route.Service),
|
||||||
|
logger.Error(err),
|
||||||
|
)
|
||||||
|
c.JSON(http.StatusServiceUnavailable, gin.H{
|
||||||
|
"error": "Service unavailable",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(instances) == 0 {
|
||||||
|
g.log.Warn("No instances found for service",
|
||||||
|
logger.String("service", route.Service),
|
||||||
|
)
|
||||||
|
c.JSON(http.StatusServiceUnavailable, gin.H{
|
||||||
|
"error": "Service unavailable",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use first healthy instance (load balancing can be added later)
|
||||||
|
instance := instances[0]
|
||||||
|
targetURL := fmt.Sprintf("http://%s:%d", instance.Address, instance.Port)
|
||||||
|
|
||||||
|
// Create reverse proxy
|
||||||
|
target, err := url.Parse(targetURL)
|
||||||
|
if err != nil {
|
||||||
|
g.log.Error("Failed to parse target URL",
|
||||||
|
logger.String("url", targetURL),
|
||||||
|
logger.Error(err),
|
||||||
|
)
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
"error": "Internal server error",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
proxy := httputil.NewSingleHostReverseProxy(target)
|
||||||
|
proxy.ServeHTTP(c.Writer, c.Request)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadRoutes loads route configurations from config.
|
||||||
|
func loadRoutes(cfg config.ConfigProvider) []RouteConfig {
|
||||||
|
// For now, return empty routes - will be loaded from config in future
|
||||||
|
// This is a placeholder implementation
|
||||||
|
return []RouteConfig{}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user