mirror of
https://github.com/andwati/telecom-platform.git
synced 2026-10-26 22:52:16 +00:00
feat: Add tenant service implementation with CRUD operations, user management, API key handling, and event logging
- Add ServiceImpl struct with repository, rate limiter, event publisher, config manager, audit logger, metrics collector, and logger fields - Add CreateTenant with validation, domain uniqueness check, default settings/quotas/features initialization, and tenant created event - Add GetTenant, GetTenantByDomain, UpdateTenant, DeleteTenant, and ListTenants methods with event logging
This commit is contained in:
@@ -0,0 +1,771 @@
|
||||
package tenant
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
// ServiceImpl implements the tenant service interface
|
||||
type ServiceImpl struct {
|
||||
repository Repository
|
||||
rateLimiter RateLimiter
|
||||
eventPublisher EventPublisher
|
||||
configManager ConfigManager
|
||||
auditLogger AuditLogger
|
||||
metricsCollector MetricsCollector
|
||||
logger *logrus.Logger
|
||||
}
|
||||
|
||||
// NewService creates a new tenant service
|
||||
func NewService(
|
||||
repository Repository,
|
||||
rateLimiter RateLimiter,
|
||||
eventPublisher EventPublisher,
|
||||
configManager ConfigManager,
|
||||
auditLogger AuditLogger,
|
||||
metricsCollector MetricsCollector,
|
||||
logger *logrus.Logger,
|
||||
) Service {
|
||||
return &ServiceImpl{
|
||||
repository: repository,
|
||||
rateLimiter: rateLimiter,
|
||||
eventPublisher: eventPublisher,
|
||||
configManager: configManager,
|
||||
auditLogger: auditLogger,
|
||||
metricsCollector: metricsCollector,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// CreateTenant creates a new tenant
|
||||
func (s *ServiceImpl) CreateTenant(ctx context.Context, req *CreateTenantRequest) (*Tenant, error) {
|
||||
// Validate request
|
||||
if err := s.validateCreateTenantRequest(req); err != nil {
|
||||
return nil, fmt.Errorf("validation failed: %w", err)
|
||||
}
|
||||
|
||||
// Check if domain already exists
|
||||
existing, err := s.repository.GetTenantByDomain(ctx, req.Domain)
|
||||
if err == nil && existing != nil {
|
||||
return nil, errors.New("domain already exists")
|
||||
}
|
||||
|
||||
// Create tenant
|
||||
tenant := &Tenant{
|
||||
ID: uuid.New().String(),
|
||||
Name: req.Name,
|
||||
Domain: req.Domain,
|
||||
Status: TenantStatusActive,
|
||||
Plan: req.Plan,
|
||||
MaxUsers: req.MaxUsers,
|
||||
MaxProfiles: req.MaxProfiles,
|
||||
MaxCarriers: req.MaxCarriers,
|
||||
Settings: req.Settings,
|
||||
Metadata: req.Metadata,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
|
||||
// Set default settings if not provided
|
||||
if tenant.Settings == nil {
|
||||
tenant.Settings = s.getDefaultSettings(req.Plan)
|
||||
}
|
||||
|
||||
// Save tenant
|
||||
if err := s.repository.CreateTenant(ctx, tenant); err != nil {
|
||||
s.logger.WithError(err).Error("Failed to create tenant")
|
||||
return nil, fmt.Errorf("failed to create tenant: %w", err)
|
||||
}
|
||||
|
||||
// Create initial configuration
|
||||
config := &TenantConfig{
|
||||
TenantID: tenant.ID,
|
||||
Config: make(map[string]interface{}),
|
||||
Settings: tenant.Settings,
|
||||
Quotas: s.getDefaultQuotas(req.Plan),
|
||||
Features: s.getDefaultFeatures(req.Plan),
|
||||
}
|
||||
|
||||
if err := s.repository.UpdateConfig(ctx, config); err != nil {
|
||||
s.logger.WithError(err).Error("Failed to create tenant config")
|
||||
}
|
||||
|
||||
// Publish tenant created event
|
||||
event := &TenantEvent{
|
||||
ID: uuid.New().String(),
|
||||
TenantID: tenant.ID,
|
||||
UserID: "",
|
||||
EventType: TenantEventCreated,
|
||||
EventData: map[string]interface{}{
|
||||
"tenant_id": tenant.ID,
|
||||
"name": tenant.Name,
|
||||
"domain": tenant.Domain,
|
||||
"plan": tenant.Plan,
|
||||
},
|
||||
Timestamp: time.Now(),
|
||||
}
|
||||
|
||||
if err := s.repository.CreateEvent(ctx, event); err != nil {
|
||||
s.logger.WithError(err).Error("Failed to create tenant event")
|
||||
}
|
||||
|
||||
s.logger.WithFields(logrus.Fields{
|
||||
"tenant_id": tenant.ID,
|
||||
"name": tenant.Name,
|
||||
"domain": tenant.Domain,
|
||||
}).Info("Tenant created successfully")
|
||||
|
||||
return tenant, nil
|
||||
}
|
||||
|
||||
// GetTenant retrieves a tenant by ID
|
||||
func (s *ServiceImpl) GetTenant(ctx context.Context, id string) (*Tenant, error) {
|
||||
tenant, err := s.repository.GetTenant(ctx, id)
|
||||
if err != nil {
|
||||
s.logger.WithError(err).WithField("tenant_id", id).Error("Failed to get tenant")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return tenant, nil
|
||||
}
|
||||
|
||||
// GetTenantByDomain retrieves a tenant by domain
|
||||
func (s *ServiceImpl) GetTenantByDomain(ctx context.Context, domain string) (*Tenant, error) {
|
||||
tenant, err := s.repository.GetTenantByDomain(ctx, domain)
|
||||
if err != nil {
|
||||
s.logger.WithError(err).WithField("domain", domain).Error("Failed to get tenant by domain")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return tenant, nil
|
||||
}
|
||||
|
||||
// UpdateTenant updates an existing tenant
|
||||
func (s *ServiceImpl) UpdateTenant(ctx context.Context, id string, req *UpdateTenantRequest) (*Tenant, error) {
|
||||
// Get existing tenant
|
||||
tenant, err := s.repository.GetTenant(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Apply updates
|
||||
if req.Name != nil {
|
||||
tenant.Name = *req.Name
|
||||
}
|
||||
if req.Status != nil {
|
||||
tenant.Status = *req.Status
|
||||
}
|
||||
if req.Plan != nil {
|
||||
tenant.Plan = *req.Plan
|
||||
}
|
||||
if req.MaxUsers != nil {
|
||||
tenant.MaxUsers = *req.MaxUsers
|
||||
}
|
||||
if req.MaxProfiles != nil {
|
||||
tenant.MaxProfiles = *req.MaxProfiles
|
||||
}
|
||||
if req.MaxCarriers != nil {
|
||||
tenant.MaxCarriers = *req.MaxCarriers
|
||||
}
|
||||
if req.Settings != nil {
|
||||
tenant.Settings = req.Settings
|
||||
}
|
||||
if req.Metadata != nil {
|
||||
tenant.Metadata = req.Metadata
|
||||
}
|
||||
|
||||
tenant.UpdatedAt = time.Now()
|
||||
|
||||
// Save tenant
|
||||
if err := s.repository.UpdateTenant(ctx, tenant); err != nil {
|
||||
s.logger.WithError(err).Error("Failed to update tenant")
|
||||
return nil, fmt.Errorf("failed to update tenant: %w", err)
|
||||
}
|
||||
|
||||
// Log tenant updated event
|
||||
event := &TenantEvent{
|
||||
ID: uuid.New().String(),
|
||||
TenantID: tenant.ID,
|
||||
UserID: "",
|
||||
EventType: TenantEventUpdated,
|
||||
EventData: map[string]interface{}{
|
||||
"tenant_id": tenant.ID,
|
||||
"updates": req,
|
||||
},
|
||||
Timestamp: time.Now(),
|
||||
}
|
||||
|
||||
if err := s.repository.CreateEvent(ctx, event); err != nil {
|
||||
s.logger.WithError(err).Error("Failed to create tenant event")
|
||||
}
|
||||
|
||||
s.logger.WithField("tenant_id", tenant.ID).Info("Tenant updated successfully")
|
||||
|
||||
return tenant, nil
|
||||
}
|
||||
|
||||
// DeleteTenant deletes a tenant
|
||||
func (s *ServiceImpl) DeleteTenant(ctx context.Context, id string) error {
|
||||
// Get tenant for logging
|
||||
tenant, err := s.repository.GetTenant(ctx, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Delete tenant
|
||||
if err := s.repository.DeleteTenant(ctx, id); err != nil {
|
||||
s.logger.WithError(err).Error("Failed to delete tenant")
|
||||
return fmt.Errorf("failed to delete tenant: %w", err)
|
||||
}
|
||||
|
||||
// Log tenant deleted event
|
||||
event := &TenantEvent{
|
||||
ID: uuid.New().String(),
|
||||
TenantID: id,
|
||||
UserID: "",
|
||||
EventType: TenantEventDeleted,
|
||||
EventData: map[string]interface{}{
|
||||
"tenant_id": id,
|
||||
"name": tenant.Name,
|
||||
},
|
||||
Timestamp: time.Now(),
|
||||
}
|
||||
|
||||
if err := s.repository.CreateEvent(ctx, event); err != nil {
|
||||
s.logger.WithError(err).Error("Failed to create tenant event")
|
||||
}
|
||||
|
||||
s.logger.WithField("tenant_id", id).Info("Tenant deleted successfully")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ListTenants lists tenants with filtering
|
||||
func (s *ServiceImpl) ListTenants(ctx context.Context, filter *TenantFilter) ([]*Tenant, error) {
|
||||
tenants, err := s.repository.ListTenants(ctx, filter)
|
||||
if err != nil {
|
||||
s.logger.WithError(err).Error("Failed to list tenants")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return tenants, nil
|
||||
}
|
||||
|
||||
// AddUserToTenant adds a user to a tenant
|
||||
func (s *ServiceImpl) AddUserToTenant(ctx context.Context, req *CreateTenantUserRequest) (*TenantUser, error) {
|
||||
// Validate request
|
||||
if err := s.validateCreateTenantUserRequest(req); err != nil {
|
||||
return nil, fmt.Errorf("validation failed: %w", err)
|
||||
}
|
||||
|
||||
// Check if user already exists
|
||||
existing, err := s.repository.GetTenantUser(ctx, req.TenantID, req.UserID)
|
||||
if err == nil && existing != nil {
|
||||
return nil, errors.New("user already exists in tenant")
|
||||
}
|
||||
|
||||
// Create tenant user
|
||||
tenantUser := &TenantUser{
|
||||
ID: uuid.New().String(),
|
||||
TenantID: req.TenantID,
|
||||
UserID: req.UserID,
|
||||
Email: req.Email,
|
||||
Role: req.Role,
|
||||
Status: TenantUserStatusActive,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
|
||||
// Save tenant user
|
||||
if err := s.repository.CreateTenantUser(ctx, tenantUser); err != nil {
|
||||
s.logger.WithError(err).Error("Failed to create tenant user")
|
||||
return nil, fmt.Errorf("failed to add user to tenant: %w", err)
|
||||
}
|
||||
|
||||
// Log user added event
|
||||
event := &TenantEvent{
|
||||
ID: uuid.New().String(),
|
||||
TenantID: req.TenantID,
|
||||
UserID: req.UserID,
|
||||
EventType: TenantEventUserAdded,
|
||||
EventData: map[string]interface{}{
|
||||
"tenant_id": req.TenantID,
|
||||
"user_id": req.UserID,
|
||||
"email": req.Email,
|
||||
"role": req.Role,
|
||||
},
|
||||
Timestamp: time.Now(),
|
||||
}
|
||||
|
||||
if err := s.repository.CreateEvent(ctx, event); err != nil {
|
||||
s.logger.WithError(err).Error("Failed to create tenant event")
|
||||
}
|
||||
|
||||
s.logger.WithFields(logrus.Fields{
|
||||
"tenant_id": req.TenantID,
|
||||
"user_id": req.UserID,
|
||||
"role": req.Role,
|
||||
}).Info("User added to tenant successfully")
|
||||
|
||||
return tenantUser, nil
|
||||
}
|
||||
|
||||
// GetTenantUser retrieves a tenant user
|
||||
func (s *ServiceImpl) GetTenantUser(ctx context.Context, tenantID, userID string) (*TenantUser, error) {
|
||||
user, err := s.repository.GetTenantUser(ctx, tenantID, userID)
|
||||
if err != nil {
|
||||
s.logger.WithError(err).WithFields(logrus.Fields{
|
||||
"tenant_id": tenantID,
|
||||
"user_id": userID,
|
||||
}).Error("Failed to get tenant user")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
// UpdateTenantUser updates a tenant user
|
||||
func (s *ServiceImpl) UpdateTenantUser(ctx context.Context, tenantID, userID string, req *UpdateTenantUserRequest) (*TenantUser, error) {
|
||||
// Get existing user
|
||||
user, err := s.repository.GetTenantUser(ctx, tenantID, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Apply updates
|
||||
if req.Role != nil {
|
||||
user.Role = *req.Role
|
||||
}
|
||||
if req.Status != nil {
|
||||
user.Status = *req.Status
|
||||
}
|
||||
|
||||
user.UpdatedAt = time.Now()
|
||||
|
||||
// Save user
|
||||
if err := s.repository.UpdateTenantUser(ctx, user); err != nil {
|
||||
s.logger.WithError(err).Error("Failed to update tenant user")
|
||||
return nil, fmt.Errorf("failed to update tenant user: %w", err)
|
||||
}
|
||||
|
||||
// Log user updated event
|
||||
event := &TenantEvent{
|
||||
ID: uuid.New().String(),
|
||||
TenantID: tenantID,
|
||||
UserID: userID,
|
||||
EventType: TenantEventUserUpdated,
|
||||
EventData: map[string]interface{}{
|
||||
"tenant_id": tenantID,
|
||||
"user_id": userID,
|
||||
"updates": req,
|
||||
},
|
||||
Timestamp: time.Now(),
|
||||
}
|
||||
|
||||
if err := s.repository.CreateEvent(ctx, event); err != nil {
|
||||
s.logger.WithError(err).Error("Failed to create tenant event")
|
||||
}
|
||||
|
||||
s.logger.WithFields(logrus.Fields{
|
||||
"tenant_id": tenantID,
|
||||
"user_id": userID,
|
||||
}).Info("Tenant user updated successfully")
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
// RemoveUserFromTenant removes a user from a tenant
|
||||
func (s *ServiceImpl) RemoveUserFromTenant(ctx context.Context, tenantID, userID string) error {
|
||||
// Delete tenant user
|
||||
if err := s.repository.DeleteTenantUser(ctx, tenantID, userID); err != nil {
|
||||
s.logger.WithError(err).Error("Failed to remove user from tenant")
|
||||
return fmt.Errorf("failed to remove user from tenant: %w", err)
|
||||
}
|
||||
|
||||
// Log user removed event
|
||||
event := &TenantEvent{
|
||||
ID: uuid.New().String(),
|
||||
TenantID: tenantID,
|
||||
UserID: userID,
|
||||
EventType: TenantEventUserRemoved,
|
||||
EventData: map[string]interface{}{
|
||||
"tenant_id": tenantID,
|
||||
"user_id": userID,
|
||||
},
|
||||
Timestamp: time.Now(),
|
||||
}
|
||||
|
||||
if err := s.repository.CreateEvent(ctx, event); err != nil {
|
||||
s.logger.WithError(err).Error("Failed to create tenant event")
|
||||
}
|
||||
|
||||
s.logger.WithFields(logrus.Fields{
|
||||
"tenant_id": tenantID,
|
||||
"user_id": userID,
|
||||
}).Info("User removed from tenant successfully")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ListTenantUsers lists tenant users with filtering
|
||||
func (s *ServiceImpl) ListTenantUsers(ctx context.Context, filter *TenantUserFilter) ([]*TenantUser, error) {
|
||||
users, err := s.repository.ListTenantUsers(ctx, filter)
|
||||
if err != nil {
|
||||
s.logger.WithError(err).Error("Failed to list tenant users")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return users, nil
|
||||
}
|
||||
|
||||
// CreateAPIKey creates a new API key for a tenant
|
||||
func (s *ServiceImpl) CreateAPIKey(ctx context.Context, tenantID string, req *CreateAPIKeyRequest) (*TenantAPIKey, string, error) {
|
||||
// Validate request
|
||||
if err := s.validateCreateAPIKeyRequest(req); err != nil {
|
||||
return nil, "", fmt.Errorf("validation failed: %w", err)
|
||||
}
|
||||
|
||||
// Generate API key
|
||||
apiKey := s.generateAPIKey()
|
||||
keyHash, err := s.hashAPIKey(apiKey)
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("failed to hash API key: %w", err)
|
||||
}
|
||||
|
||||
// Create API key record
|
||||
apiKeyRecord := &TenantAPIKey{
|
||||
ID: uuid.New().String(),
|
||||
TenantID: tenantID,
|
||||
Name: req.Name,
|
||||
KeyHash: keyHash,
|
||||
KeyPrefix: apiKey[:8],
|
||||
Permissions: req.Permissions,
|
||||
RateLimit: req.RateLimit,
|
||||
ExpiresAt: req.ExpiresAt,
|
||||
Status: APIKeyStatusActive,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
|
||||
// Save API key
|
||||
if err := s.repository.CreateAPIKey(ctx, apiKeyRecord); err != nil {
|
||||
s.logger.WithError(err).Error("Failed to create API key")
|
||||
return nil, "", fmt.Errorf("failed to create API key: %w", err)
|
||||
}
|
||||
|
||||
// Log API key created event
|
||||
event := &TenantEvent{
|
||||
ID: uuid.New().String(),
|
||||
TenantID: tenantID,
|
||||
UserID: "",
|
||||
EventType: TenantEventAPIKeyCreated,
|
||||
EventData: map[string]interface{}{
|
||||
"tenant_id": tenantID,
|
||||
"key_id": apiKeyRecord.ID,
|
||||
"name": req.Name,
|
||||
},
|
||||
Timestamp: time.Now(),
|
||||
}
|
||||
|
||||
if err := s.repository.CreateEvent(ctx, event); err != nil {
|
||||
s.logger.WithError(err).Error("Failed to create tenant event")
|
||||
}
|
||||
|
||||
s.logger.WithFields(logrus.Fields{
|
||||
"tenant_id": tenantID,
|
||||
"key_id": apiKeyRecord.ID,
|
||||
"name": req.Name,
|
||||
}).Info("API key created successfully")
|
||||
|
||||
return apiKeyRecord, apiKey, nil
|
||||
}
|
||||
|
||||
// GetAPIKey retrieves an API key by ID
|
||||
func (s *ServiceImpl) GetAPIKey(ctx context.Context, id string) (*TenantAPIKey, error) {
|
||||
apiKey, err := s.repository.GetAPIKey(ctx, id)
|
||||
if err != nil {
|
||||
s.logger.WithError(err).WithField("key_id", id).Error("Failed to get API key")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return apiKey, nil
|
||||
}
|
||||
|
||||
// UpdateAPIKey updates an API key
|
||||
func (s *ServiceImpl) UpdateAPIKey(ctx context.Context, id string, req *UpdateAPIKeyRequest) (*TenantAPIKey, error) {
|
||||
// Get existing API key
|
||||
apiKey, err := s.repository.GetAPIKey(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Apply updates
|
||||
if req.Name != nil {
|
||||
apiKey.Name = *req.Name
|
||||
}
|
||||
if req.Permissions != nil {
|
||||
apiKey.Permissions = req.Permissions
|
||||
}
|
||||
if req.RateLimit != nil {
|
||||
apiKey.RateLimit = *req.RateLimit
|
||||
}
|
||||
if req.ExpiresAt != nil {
|
||||
apiKey.ExpiresAt = req.ExpiresAt
|
||||
}
|
||||
if req.Status != nil {
|
||||
apiKey.Status = *req.Status
|
||||
}
|
||||
|
||||
apiKey.UpdatedAt = time.Now()
|
||||
|
||||
// Save API key
|
||||
if err := s.repository.UpdateAPIKey(ctx, apiKey); err != nil {
|
||||
s.logger.WithError(err).Error("Failed to update API key")
|
||||
return nil, fmt.Errorf("failed to update API key: %w", err)
|
||||
}
|
||||
|
||||
s.logger.WithField("key_id", id).Info("API key updated successfully")
|
||||
|
||||
return apiKey, nil
|
||||
}
|
||||
|
||||
// DeleteAPIKey deletes an API key
|
||||
func (s *ServiceImpl) DeleteAPIKey(ctx context.Context, id string) error {
|
||||
// Get API key for logging
|
||||
apiKey, err := s.repository.GetAPIKey(ctx, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Delete API key
|
||||
if err := s.repository.DeleteAPIKey(ctx, id); err != nil {
|
||||
s.logger.WithError(err).Error("Failed to delete API key")
|
||||
return fmt.Errorf("failed to delete API key: %w", err)
|
||||
}
|
||||
|
||||
// Log API key revoked event
|
||||
event := &TenantEvent{
|
||||
ID: uuid.New().String(),
|
||||
TenantID: apiKey.TenantID,
|
||||
UserID: "",
|
||||
EventType: TenantEventAPIKeyRevoked,
|
||||
EventData: map[string]interface{}{
|
||||
"tenant_id": apiKey.TenantID,
|
||||
"key_id": id,
|
||||
"name": apiKey.Name,
|
||||
},
|
||||
Timestamp: time.Now(),
|
||||
}
|
||||
|
||||
if err := s.repository.CreateEvent(ctx, event); err != nil {
|
||||
s.logger.WithError(err).Error("Failed to create tenant event")
|
||||
}
|
||||
|
||||
s.logger.WithField("key_id", id).Info("API key deleted successfully")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ListAPIKeys lists API keys for a tenant
|
||||
func (s *ServiceImpl) ListAPIKeys(ctx context.Context, tenantID string) ([]*TenantAPIKey, error) {
|
||||
apiKeys, err := s.repository.ListAPIKeys(ctx, tenantID)
|
||||
if err != nil {
|
||||
s.logger.WithError(err).WithField("tenant_id", tenantID).Error("Failed to list API keys")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return apiKeys, nil
|
||||
}
|
||||
|
||||
// ValidateAPIKey validates an API key and returns the key record
|
||||
func (s *ServiceImpl) ValidateAPIKey(ctx context.Context, key string) (*TenantAPIKey, error) {
|
||||
// Hash the provided key
|
||||
keyHash, err := s.hashAPIKey(key)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to hash API key: %w", err)
|
||||
}
|
||||
|
||||
// Look up API key by hash
|
||||
apiKey, err := s.repository.GetAPIKeyByHash(ctx, keyHash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Check if key is active
|
||||
if apiKey.Status != APIKeyStatusActive {
|
||||
return nil, errors.New("API key is not active")
|
||||
}
|
||||
|
||||
// Check if key has expired
|
||||
if apiKey.ExpiresAt != nil && time.Now().After(*apiKey.ExpiresAt) {
|
||||
return nil, errors.New("API key has expired")
|
||||
}
|
||||
|
||||
// Update last used timestamp
|
||||
now := time.Now()
|
||||
apiKey.LastUsed = &now
|
||||
if err := s.repository.UpdateAPIKey(ctx, apiKey); err != nil {
|
||||
s.logger.WithError(err).Error("Failed to update API key last used")
|
||||
}
|
||||
|
||||
return apiKey, nil
|
||||
}
|
||||
|
||||
// Helper methods
|
||||
func (s *ServiceImpl) validateCreateTenantRequest(req *CreateTenantRequest) error {
|
||||
if req.Name == "" {
|
||||
return errors.New("name is required")
|
||||
}
|
||||
if req.Domain == "" {
|
||||
return errors.New("domain is required")
|
||||
}
|
||||
if req.Plan == "" {
|
||||
return errors.New("plan is required")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *ServiceImpl) validateCreateTenantUserRequest(req *CreateTenantUserRequest) error {
|
||||
if req.TenantID == "" {
|
||||
return errors.New("tenant ID is required")
|
||||
}
|
||||
if req.UserID == "" {
|
||||
return errors.New("user ID is required")
|
||||
}
|
||||
if req.Email == "" {
|
||||
return errors.New("email is required")
|
||||
}
|
||||
if req.Role == "" {
|
||||
return errors.New("role is required")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *ServiceImpl) validateCreateAPIKeyRequest(req *CreateAPIKeyRequest) error {
|
||||
if req.Name == "" {
|
||||
return errors.New("name is required")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *ServiceImpl) getDefaultSettings(plan TenantPlan) *TenantSettings {
|
||||
settings := &TenantSettings{
|
||||
DefaultCurrency: "USD",
|
||||
SupportedCurrencies: []string{"USD", "EUR", "GBP"},
|
||||
APIRateLimitPerMinute: 60,
|
||||
APIRateLimitPerHour: 1000,
|
||||
SessionTimeout: 120, // 2 hours
|
||||
DataRetentionDays: 90,
|
||||
ComplianceRegions: []string{"US", "EU"},
|
||||
}
|
||||
|
||||
switch plan {
|
||||
case TenantPlanFree:
|
||||
settings.EnableMultiCurrency = false
|
||||
settings.EnableAdvancedAnalytics = false
|
||||
settings.EnableAPIAccess = true
|
||||
settings.EnableWebhooks = false
|
||||
settings.Require2FA = false
|
||||
case TenantPlanBasic:
|
||||
settings.EnableMultiCurrency = true
|
||||
settings.EnableAdvancedAnalytics = false
|
||||
settings.EnableAPIAccess = true
|
||||
settings.EnableWebhooks = false
|
||||
settings.Require2FA = false
|
||||
case TenantPlanPro:
|
||||
settings.EnableMultiCurrency = true
|
||||
settings.EnableAdvancedAnalytics = true
|
||||
settings.EnableAPIAccess = true
|
||||
settings.EnableWebhooks = true
|
||||
settings.Require2FA = true
|
||||
case TenantPlanEnterprise:
|
||||
settings.EnableMultiCurrency = true
|
||||
settings.EnableAdvancedAnalytics = true
|
||||
settings.EnableAPIAccess = true
|
||||
settings.EnableWebhooks = true
|
||||
settings.Require2FA = true
|
||||
settings.APIRateLimitPerMinute = 1000
|
||||
settings.APIRateLimitPerHour = 10000
|
||||
}
|
||||
|
||||
return settings
|
||||
}
|
||||
|
||||
func (s *ServiceImpl) getDefaultQuotas(plan TenantPlan) []ResourceQuota {
|
||||
quotas := []ResourceQuota{}
|
||||
|
||||
switch plan {
|
||||
case TenantPlanFree:
|
||||
quotas = append(quotas, ResourceQuota{ResourceType: "users", Limit: 5, Period: "monthly"})
|
||||
quotas = append(quotas, ResourceQuota{ResourceType: "profiles", Limit: 100, Period: "monthly"})
|
||||
quotas = append(quotas, ResourceQuota{ResourceType: "carriers", Limit: 3, Period: "monthly"})
|
||||
case TenantPlanBasic:
|
||||
quotas = append(quotas, ResourceQuota{ResourceType: "users", Limit: 25, Period: "monthly"})
|
||||
quotas = append(quotas, ResourceQuota{ResourceType: "profiles", Limit: 1000, Period: "monthly"})
|
||||
quotas = append(quotas, ResourceQuota{ResourceType: "carriers", Limit: 10, Period: "monthly"})
|
||||
case TenantPlanPro:
|
||||
quotas = append(quotas, ResourceQuota{ResourceType: "users", Limit: 100, Period: "monthly"})
|
||||
quotas = append(quotas, ResourceQuota{ResourceType: "profiles", Limit: 10000, Period: "monthly"})
|
||||
quotas = append(quotas, ResourceQuota{ResourceType: "carriers", Limit: 50, Period: "monthly"})
|
||||
case TenantPlanEnterprise:
|
||||
quotas = append(quotas, ResourceQuota{ResourceType: "users", Limit: -1, Period: "monthly"}) // Unlimited
|
||||
quotas = append(quotas, ResourceQuota{ResourceType: "profiles", Limit: -1, Period: "monthly"}) // Unlimited
|
||||
quotas = append(quotas, ResourceQuota{ResourceType: "carriers", Limit: -1, Period: "monthly"}) // Unlimited
|
||||
}
|
||||
|
||||
return quotas
|
||||
}
|
||||
|
||||
func (s *ServiceImpl) getDefaultFeatures(plan TenantPlan) map[string]bool {
|
||||
features := map[string]bool{
|
||||
"multi_currency": false,
|
||||
"advanced_analytics": false,
|
||||
"api_access": true,
|
||||
"webhooks": false,
|
||||
"custom_branding": false,
|
||||
"priority_support": false,
|
||||
}
|
||||
|
||||
switch plan {
|
||||
case TenantPlanBasic:
|
||||
features["multi_currency"] = true
|
||||
case TenantPlanPro:
|
||||
features["multi_currency"] = true
|
||||
features["advanced_analytics"] = true
|
||||
features["webhooks"] = true
|
||||
features["custom_branding"] = true
|
||||
case TenantPlanEnterprise:
|
||||
features["multi_currency"] = true
|
||||
features["advanced_analytics"] = true
|
||||
features["webhooks"] = true
|
||||
features["custom_branding"] = true
|
||||
features["priority_support"] = true
|
||||
}
|
||||
|
||||
return features
|
||||
}
|
||||
|
||||
func (s *ServiceImpl) generateAPIKey() string {
|
||||
bytes := make([]byte, 32)
|
||||
if _, err := rand.Read(bytes); err != nil {
|
||||
// Fallback to less secure method if crypto/rand fails
|
||||
return uuid.New().String()
|
||||
}
|
||||
return "tk_" + hex.EncodeToString(bytes)
|
||||
}
|
||||
|
||||
func (s *ServiceImpl) hashAPIKey(key string) (string, error) {
|
||||
hash, err := bcrypt.GenerateFromPassword([]byte(key), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(hash), nil
|
||||
}
|
||||
Reference in New Issue
Block a user