mirror of
https://github.com/andwati/telecom-platform.git
synced 2026-10-18 02:52:53 +00:00
refactor: Move tenant repository implementation to tenant package and update imports
- Move GormTenantRepository implementation from repository package to tenant package - Move TenantResourceValidator from repository to tenant package - Update tenant_aware_repository.go to import tenant package for Tenant and TenantStatusActive types - Remove duplicate tenant model definitions and helper methods from repository package - Update ValidateTenant to use tenant.Tenant and tenant.TenantStatusActive types
This commit is contained in:
@@ -4,12 +4,13 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/tenant"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// TenantAwareRepository provides tenant isolation for existing repositories
|
||||
type TenantAwareRepository struct {
|
||||
db *gorm.DB
|
||||
db *gorm.DB
|
||||
tenantID string
|
||||
}
|
||||
|
||||
@@ -40,8 +41,8 @@ func (r *TenantAwareRepository) ValidateTenant(ctx context.Context) error {
|
||||
return fmt.Errorf("tenant ID is required")
|
||||
}
|
||||
|
||||
var tenant Tenant
|
||||
err := r.db.WithContext(ctx).Where("id = ? AND status = ?", r.tenantID, TenantStatusActive).First(&tenant).Error
|
||||
var tenantRecord tenant.Tenant
|
||||
err := r.db.WithContext(ctx).Where("id = ? AND status = ?", r.tenantID, tenant.TenantStatusActive).First(&tenantRecord).Error
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return fmt.Errorf("tenant not found or inactive")
|
||||
@@ -73,220 +74,6 @@ func (r *TenantAwareRepository) TenantScopedTransaction(ctx context.Context, fn
|
||||
})
|
||||
}
|
||||
|
||||
// Helper methods for common tenant-aware operations
|
||||
|
||||
// CreateWithTenant creates a record with tenant ID
|
||||
func (r *TenantAwareRepository) CreateWithTenant(ctx context.Context, model interface{}) error {
|
||||
if err := r.ValidateTenant(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Set tenant ID if the model has a TenantID field
|
||||
if modelWithTenant, ok := model.(interface{ SetTenantID(string) }); ok {
|
||||
modelWithTenant.SetTenantID(r.tenantID)
|
||||
}
|
||||
|
||||
return r.db.WithContext(ctx).Create(model).Error
|
||||
}
|
||||
|
||||
// GetByTenantID retrieves a record by ID within the current tenant
|
||||
func (r *TenantAwareRepository) GetByTenantID(ctx context.Context, model interface{}, id string) error {
|
||||
if err := r.ValidateTenant(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return r.TenantScopedQuery(ctx, model).Where("id = ?", id).First(model).Error
|
||||
}
|
||||
|
||||
// UpdateWithTenant updates a record within the current tenant
|
||||
func (r *TenantAwareRepository) UpdateWithTenant(ctx context.Context, model interface{}) error {
|
||||
if err := r.ValidateTenant(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return r.db.WithContext(ctx).Save(model).Error
|
||||
}
|
||||
|
||||
// DeleteWithTenant deletes a record within the current tenant
|
||||
func (r *TenantAwareRepository) DeleteWithTenant(ctx context.Context, model interface{}, id string) error {
|
||||
if err := r.ValidateTenant(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return r.TenantScopedQuery(ctx, model).Where("id = ?", id).Delete(model).Error
|
||||
}
|
||||
|
||||
// ListWithTenant lists records within the current tenant
|
||||
func (r *TenantAwareRepository) ListWithTenant(ctx context.Context, model interface{}, results interface{}, filters map[string]interface{}) error {
|
||||
if err := r.ValidateTenant(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
query := r.TenantScopedQuery(ctx, model)
|
||||
|
||||
// Apply filters
|
||||
for key, value := range filters {
|
||||
query = query.Where(key+" = ?", value)
|
||||
}
|
||||
|
||||
return query.Find(results).Error
|
||||
}
|
||||
|
||||
// CountWithTenant counts records within the current tenant
|
||||
func (r *TenantAwareRepository) CountWithTenant(ctx context.Context, model interface{}, filters map[string]interface{}) (int64, error) {
|
||||
if err := r.ValidateTenant(ctx); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
query := r.TenantScopedQuery(ctx, model)
|
||||
|
||||
// Apply filters
|
||||
for key, value := range filters {
|
||||
query = query.Where(key+" = ?", value)
|
||||
}
|
||||
|
||||
var count int64
|
||||
err := query.Count(&count).Error
|
||||
return count, err
|
||||
}
|
||||
|
||||
// TenantAwareModel is an interface for models that support tenant isolation
|
||||
type TenantAwareModel interface {
|
||||
SetTenantID(tenantID string)
|
||||
GetTenantID() string
|
||||
}
|
||||
|
||||
// BaseTenantModel provides a base implementation for tenant-aware models
|
||||
type BaseTenantModel struct {
|
||||
TenantID string `json:"tenant_id" gorm:"column:tenant_id;index;not null"`
|
||||
}
|
||||
|
||||
// SetTenantID sets the tenant ID
|
||||
func (m *BaseTenantModel) SetTenantID(tenantID string) {
|
||||
m.TenantID = tenantID
|
||||
}
|
||||
|
||||
// GetTenantID returns the tenant ID
|
||||
func (m *BaseTenantModel) GetTenantID() string {
|
||||
return m.TenantID
|
||||
}
|
||||
|
||||
// TenantIsolationMiddleware provides database-level tenant isolation
|
||||
type TenantIsolationMiddleware struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewTenantIsolationMiddleware creates a new tenant isolation middleware
|
||||
func NewTenantIsolationMiddleware(db *gorm.DB) *TenantIsolationMiddleware {
|
||||
return &TenantIsolationMiddleware{db: db}
|
||||
}
|
||||
|
||||
// WithTenantIsolation applies tenant isolation to a database operation
|
||||
func (m *TenantIsolationMiddleware) WithTenantIsolation(ctx context.Context, tenantID string, operation func(*gorm.DB) error) error {
|
||||
if tenantID == "" {
|
||||
return fmt.Errorf("tenant ID is required for tenant-isolated operations")
|
||||
}
|
||||
|
||||
// Validate tenant exists
|
||||
var tenant Tenant
|
||||
err := m.db.WithContext(ctx).Where("id = ? AND status = ?", tenantID, TenantStatusActive).First(&tenant).Error
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return fmt.Errorf("tenant not found or inactive")
|
||||
}
|
||||
return fmt.Errorf("failed to validate tenant: %w", err)
|
||||
}
|
||||
|
||||
// Execute operation with tenant context
|
||||
tx := m.db.WithContext(ctx).Where("tenant_id = ?", tenantID)
|
||||
return operation(tx)
|
||||
}
|
||||
|
||||
// GetTenantFromContext extracts tenant ID from context
|
||||
func GetTenantFromContext(ctx context.Context) string {
|
||||
if tenantID, ok := ctx.Value("tenant_id").(string); ok {
|
||||
return tenantID
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// SetTenantInContext sets tenant ID in context
|
||||
func SetTenantInContext(ctx context.Context, tenantID string) context.Context {
|
||||
return context.WithValue(ctx, "tenant_id", tenantID)
|
||||
}
|
||||
|
||||
// TenantQueryBuilder helps build tenant-aware queries
|
||||
type TenantQueryBuilder struct {
|
||||
db *gorm.DB
|
||||
tenantID string
|
||||
query *gorm.DB
|
||||
}
|
||||
|
||||
// NewTenantQueryBuilder creates a new tenant query builder
|
||||
func NewTenantQueryBuilder(db *gorm.DB, tenantID string) *TenantQueryBuilder {
|
||||
return &TenantQueryBuilder{
|
||||
db: db,
|
||||
tenantID: tenantID,
|
||||
query: db.Where("tenant_id = ?", tenantID),
|
||||
}
|
||||
}
|
||||
|
||||
// Where adds a where clause to the query
|
||||
func (b *TenantQueryBuilder) Where(query string, args ...interface{}) *TenantQueryBuilder {
|
||||
b.query = b.query.Where(query, args...)
|
||||
return b
|
||||
}
|
||||
|
||||
// Order adds ordering to the query
|
||||
func (b *TenantQueryBuilder) Order(value string) *TenantQueryBuilder {
|
||||
b.query = b.query.Order(value)
|
||||
return b
|
||||
}
|
||||
|
||||
// Limit adds a limit to the query
|
||||
func (b *TenantQueryBuilder) Limit(limit int) *TenantQueryBuilder {
|
||||
b.query = b.query.Limit(limit)
|
||||
return b
|
||||
}
|
||||
|
||||
// Offset adds an offset to the query
|
||||
func (b *TenantQueryBuilder) Offset(offset int) *TenantQueryBuilder {
|
||||
b.query = b.query.Offset(offset)
|
||||
return b
|
||||
}
|
||||
|
||||
// Find executes the find operation
|
||||
func (b *TenantQueryBuilder) Find(dest interface{}) error {
|
||||
return b.query.Find(dest).Error
|
||||
}
|
||||
|
||||
// First executes the first operation
|
||||
func (b *TenantQueryBuilder) First(dest interface{}) error {
|
||||
return b.query.First(dest).Error
|
||||
}
|
||||
|
||||
// Count executes the count operation
|
||||
func (b *TenantQueryBuilder) Count() (int64, error) {
|
||||
var count int64
|
||||
err := b.query.Count(&count).Error
|
||||
return count, err
|
||||
}
|
||||
|
||||
// GetQuery returns the underlying gorm query
|
||||
func (b *TenantQueryBuilder) GetQuery() *gorm.DB {
|
||||
return b.query
|
||||
}
|
||||
|
||||
// TenantResourceValidator validates resource access across tenants
|
||||
type TenantResourceValidator struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewTenantResourceValidator creates a new tenant resource validator
|
||||
func NewTenantResourceValidator(db *gorm.DB) *TenantResourceValidator {
|
||||
return &TenantResourceValidator{db: db}
|
||||
}
|
||||
|
||||
// ValidateResourceAccess validates that a resource belongs to a tenant
|
||||
func (v *TenantResourceValidator) ValidateResourceAccess(ctx context.Context, tenantID, resourceType, resourceID string) error {
|
||||
switch resourceType {
|
||||
|
||||
@@ -3,307 +3,11 @@ package repository
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/tenant"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// GormTenantRepository implements the tenant repository interface using GORM
|
||||
type GormTenantRepository struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewGormTenantRepository creates a new GORM tenant repository
|
||||
func NewGormTenantRepository(db *gorm.DB) TenantRepository {
|
||||
return &GormTenantRepository{db: db}
|
||||
}
|
||||
|
||||
// CreateTenant creates a new tenant
|
||||
func (r *GormTenantRepository) CreateTenant(ctx context.Context, tenant *tenant.Tenant) error {
|
||||
return r.db.WithContext(ctx).Create(tenant).Error
|
||||
}
|
||||
|
||||
// GetTenant retrieves a tenant by ID
|
||||
func (r *GormTenantRepository) GetTenant(ctx context.Context, id string) (*tenant.Tenant, error) {
|
||||
var tenant tenant.Tenant
|
||||
err := r.db.WithContext(ctx).Where("id = ?", id).First(&tenant).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &tenant, nil
|
||||
}
|
||||
|
||||
// GetTenantByDomain retrieves a tenant by domain
|
||||
func (r *GormTenantRepository) GetTenantByDomain(ctx context.Context, domain string) (*tenant.Tenant, error) {
|
||||
var tenant tenant.Tenant
|
||||
err := r.db.WithContext(ctx).Where("domain = ?", domain).First(&tenant).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &tenant, nil
|
||||
}
|
||||
|
||||
// UpdateTenant updates an existing tenant
|
||||
func (r *GormTenantRepository) UpdateTenant(ctx context.Context, tenant *tenant.Tenant) error {
|
||||
return r.db.WithContext(ctx).Save(tenant).Error
|
||||
}
|
||||
|
||||
// DeleteTenant deletes a tenant
|
||||
func (r *GormTenantRepository) DeleteTenant(ctx context.Context, id string) error {
|
||||
return r.db.WithContext(ctx).Delete(&tenant.Tenant{}, "id = ?", id).Error
|
||||
}
|
||||
|
||||
// ListTenants lists tenants with filtering
|
||||
func (r *GormTenantRepository) ListTenants(ctx context.Context, filter *tenant.TenantFilter) ([]*tenant.Tenant, error) {
|
||||
query := r.db.WithContext(ctx).Model(&tenant.Tenant{})
|
||||
|
||||
// Apply filters
|
||||
if filter.ID != "" {
|
||||
query = query.Where("id = ?", filter.ID)
|
||||
}
|
||||
if filter.Name != "" {
|
||||
query = query.Where("name ILIKE ?", "%"+filter.Name+"%")
|
||||
}
|
||||
if filter.Domain != "" {
|
||||
query = query.Where("domain ILIKE ?", "%"+filter.Domain+"%")
|
||||
}
|
||||
if filter.Status != "" {
|
||||
query = query.Where("status = ?", filter.Status)
|
||||
}
|
||||
if filter.Plan != "" {
|
||||
query = query.Where("plan = ?", filter.Plan)
|
||||
}
|
||||
|
||||
// Apply sorting
|
||||
if filter.SortBy != "" {
|
||||
order := filter.SortBy
|
||||
if filter.SortOrder == "desc" {
|
||||
order += " DESC"
|
||||
}
|
||||
query = query.Order(order)
|
||||
} else {
|
||||
query = query.Order("created_at DESC")
|
||||
}
|
||||
|
||||
// Apply pagination
|
||||
if filter.Limit > 0 {
|
||||
query = query.Limit(filter.Limit)
|
||||
}
|
||||
if filter.Offset > 0 {
|
||||
query = query.Offset(filter.Offset)
|
||||
}
|
||||
|
||||
var tenants []*tenant.Tenant
|
||||
err := query.Find(&tenants).Error
|
||||
return tenants, err
|
||||
}
|
||||
|
||||
// CountTenants counts tenants with filtering
|
||||
func (r *GormTenantRepository) CountTenants(ctx context.Context, filter *tenant.TenantFilter) (int, error) {
|
||||
query := r.db.WithContext(ctx).Model(&tenant.Tenant{})
|
||||
|
||||
// Apply filters
|
||||
if filter.ID != "" {
|
||||
query = query.Where("id = ?", filter.ID)
|
||||
}
|
||||
if filter.Name != "" {
|
||||
query = query.Where("name ILIKE ?", "%"+filter.Name+"%")
|
||||
}
|
||||
if filter.Domain != "" {
|
||||
query = query.Where("domain ILIKE ?", "%"+filter.Domain+"%")
|
||||
}
|
||||
if filter.Status != "" {
|
||||
query = query.Where("status = ?", filter.Status)
|
||||
}
|
||||
if filter.Plan != "" {
|
||||
query = query.Where("plan = ?", filter.Plan)
|
||||
}
|
||||
|
||||
var count int64
|
||||
err := query.Count(&count).Error
|
||||
return int(count), err
|
||||
}
|
||||
|
||||
// CreateTenantUser creates a new tenant user
|
||||
func (r *GormTenantRepository) CreateTenantUser(ctx context.Context, user *tenant.TenantUser) error {
|
||||
return r.db.WithContext(ctx).Create(user).Error
|
||||
}
|
||||
|
||||
// GetTenantUser retrieves a tenant user
|
||||
func (r *GormTenantRepository) GetTenantUser(ctx context.Context, tenantID, userID string) (*tenant.TenantUser, error) {
|
||||
var user tenant.TenantUser
|
||||
err := r.db.WithContext(ctx).Where("tenant_id = ? AND user_id = ?", tenantID, userID).First(&user).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
// UpdateTenantUser updates a tenant user
|
||||
func (r *GormTenantRepository) UpdateTenantUser(ctx context.Context, user *tenant.TenantUser) error {
|
||||
return r.db.WithContext(ctx).Save(user).Error
|
||||
}
|
||||
|
||||
// DeleteTenantUser deletes a tenant user
|
||||
func (r *GormTenantRepository) DeleteTenantUser(ctx context.Context, tenantID, userID string) error {
|
||||
return r.db.WithContext(ctx).Delete(&tenant.TenantUser{}, "tenant_id = ? AND user_id = ?", tenantID, userID).Error
|
||||
}
|
||||
|
||||
// ListTenantUsers lists tenant users with filtering
|
||||
func (r *GormTenantRepository) ListTenantUsers(ctx context.Context, filter *tenant.TenantUserFilter) ([]*tenant.TenantUser, error) {
|
||||
query := r.db.WithContext(ctx).Model(&tenant.TenantUser{})
|
||||
|
||||
// Apply filters
|
||||
if filter.TenantID != "" {
|
||||
query = query.Where("tenant_id = ?", filter.TenantID)
|
||||
}
|
||||
if filter.UserID != "" {
|
||||
query = query.Where("user_id = ?", filter.UserID)
|
||||
}
|
||||
if filter.Email != "" {
|
||||
query = query.Where("email ILIKE ?", "%"+filter.Email+"%")
|
||||
}
|
||||
if filter.Role != "" {
|
||||
query = query.Where("role = ?", filter.Role)
|
||||
}
|
||||
if filter.Status != "" {
|
||||
query = query.Where("status = ?", filter.Status)
|
||||
}
|
||||
|
||||
// Apply pagination
|
||||
if filter.Limit > 0 {
|
||||
query = query.Limit(filter.Limit)
|
||||
}
|
||||
if filter.Offset > 0 {
|
||||
query = query.Offset(filter.Offset)
|
||||
}
|
||||
|
||||
var users []*tenant.TenantUser
|
||||
err := query.Find(&users).Error
|
||||
return users, err
|
||||
}
|
||||
|
||||
// CountTenantUsers counts tenant users with filtering
|
||||
func (r *GormTenantRepository) CountTenantUsers(ctx context.Context, filter *tenant.TenantUserFilter) (int, error) {
|
||||
query := r.db.WithContext(ctx).Model(&tenant.TenantUser{})
|
||||
|
||||
// Apply filters
|
||||
if filter.TenantID != "" {
|
||||
query = query.Where("tenant_id = ?", filter.TenantID)
|
||||
}
|
||||
if filter.UserID != "" {
|
||||
query = query.Where("user_id = ?", filter.UserID)
|
||||
}
|
||||
if filter.Email != "" {
|
||||
query = query.Where("email ILIKE ?", "%"+filter.Email+"%")
|
||||
}
|
||||
if filter.Role != "" {
|
||||
query = query.Where("role = ?", filter.Role)
|
||||
}
|
||||
if filter.Status != "" {
|
||||
query = query.Where("status = ?", filter.Status)
|
||||
}
|
||||
|
||||
var count int64
|
||||
err := query.Count(&count).Error
|
||||
return int(count), err
|
||||
}
|
||||
|
||||
// CreateAPIKey creates a new API key
|
||||
func (r *GormTenantRepository) CreateAPIKey(ctx context.Context, apiKey *tenant.TenantAPIKey) error {
|
||||
return r.db.WithContext(ctx).Create(apiKey).Error
|
||||
}
|
||||
|
||||
// GetAPIKey retrieves an API key by ID
|
||||
func (r *GormTenantRepository) GetAPIKey(ctx context.Context, id string) (*tenant.TenantAPIKey, error) {
|
||||
var apiKey tenant.TenantAPIKey
|
||||
err := r.db.WithContext(ctx).Where("id = ?", id).First(&apiKey).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &apiKey, nil
|
||||
}
|
||||
|
||||
// GetAPIKeyByHash retrieves an API key by hash
|
||||
func (r *GormTenantRepository) GetAPIKeyByHash(ctx context.Context, keyHash string) (*tenant.TenantAPIKey, error) {
|
||||
var apiKey tenant.TenantAPIKey
|
||||
err := r.db.WithContext(ctx).Where("key_hash = ?", keyHash).First(&apiKey).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &apiKey, nil
|
||||
}
|
||||
|
||||
// UpdateAPIKey updates an API key
|
||||
func (r *GormTenantRepository) UpdateAPIKey(ctx context.Context, apiKey *tenant.TenantAPIKey) error {
|
||||
return r.db.WithContext(ctx).Save(apiKey).Error
|
||||
}
|
||||
|
||||
// DeleteAPIKey deletes an API key
|
||||
func (r *GormTenantRepository) DeleteAPIKey(ctx context.Context, id string) error {
|
||||
return r.db.WithContext(ctx).Delete(&tenant.TenantAPIKey{}, "id = ?", id).Error
|
||||
}
|
||||
|
||||
// ListAPIKeys lists API keys for a tenant
|
||||
func (r *GormTenantRepository) ListAPIKeys(ctx context.Context, tenantID string) ([]*tenant.TenantAPIKey, error) {
|
||||
var apiKeys []*tenant.TenantAPIKey
|
||||
err := r.db.WithContext(ctx).Where("tenant_id = ?", tenantID).Order("created_at DESC").Find(&apiKeys).Error
|
||||
return apiKeys, err
|
||||
}
|
||||
|
||||
// CreateUsage creates a new usage record
|
||||
func (r *GormTenantRepository) CreateUsage(ctx context.Context, usage *tenant.TenantUsage) error {
|
||||
return r.db.WithContext(ctx).Create(usage).Error
|
||||
}
|
||||
|
||||
// GetUsage retrieves usage by tenant and resource type
|
||||
func (r *GormTenantRepository) GetUsage(ctx context.Context, tenantID, resourceType string) (*tenant.TenantUsage, error) {
|
||||
var usage tenant.TenantUsage
|
||||
err := r.db.WithContext(ctx).Where("tenant_id = ? AND resource_type = ?", tenantID, resourceType).First(&usage).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &usage, nil
|
||||
}
|
||||
|
||||
// UpdateUsage updates a usage record
|
||||
func (r *GormTenantRepository) UpdateUsage(ctx context.Context, usage *tenant.TenantUsage) error {
|
||||
return r.db.WithContext(ctx).Save(usage).Error
|
||||
}
|
||||
|
||||
// ListUsage lists usage records with filtering
|
||||
func (r *GormTenantRepository) ListUsage(ctx context.Context, filter *tenant.TenantUsageFilter) ([]*tenant.TenantUsage, error) {
|
||||
query := r.db.WithContext(ctx).Model(&tenant.TenantUsage{})
|
||||
|
||||
// Apply filters
|
||||
if filter.TenantID != "" {
|
||||
query = query.Where("tenant_id = ?", filter.TenantID)
|
||||
}
|
||||
if filter.ResourceType != "" {
|
||||
query = query.Where("resource_type = ?", filter.ResourceType)
|
||||
}
|
||||
if !filter.PeriodStart.IsZero() {
|
||||
query = query.Where("period_start >= ?", filter.PeriodStart)
|
||||
}
|
||||
if !filter.PeriodEnd.IsZero() {
|
||||
query = query.Where("period_end <= ?", filter.PeriodEnd)
|
||||
}
|
||||
|
||||
// Apply pagination
|
||||
if filter.Limit > 0 {
|
||||
query = query.Limit(filter.Limit)
|
||||
}
|
||||
if filter.Offset > 0 {
|
||||
query = query.Offset(filter.Offset)
|
||||
}
|
||||
|
||||
var usage []*tenant.TenantUsage
|
||||
err := query.Find(&usage).Error
|
||||
return usage, err
|
||||
}
|
||||
|
||||
// GetUsageStats retrieves usage statistics for a tenant
|
||||
func (r *GormTenantRepository) GetUsageStats(ctx context.Context, tenantID string) (*tenant.TenantUsageStats, error) {
|
||||
// This is a complex query that would typically involve joins and aggregations
|
||||
@@ -336,7 +40,7 @@ func (r *GormTenantRepository) GetUsageStats(ctx context.Context, tenantID strin
|
||||
// GetConfig retrieves tenant configuration
|
||||
func (r *GormTenantRepository) GetConfig(ctx context.Context, tenantID string) (*tenant.TenantConfig, error) {
|
||||
// Get tenant to extract settings
|
||||
tenant, err := r.GetTenant(ctx, tenantID)
|
||||
tenantRecord, err := r.GetTenant(ctx, tenantID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -345,13 +49,13 @@ func (r *GormTenantRepository) GetConfig(ctx context.Context, tenantID string) (
|
||||
config := &tenant.TenantConfig{
|
||||
TenantID: tenantID,
|
||||
Config: make(map[string]interface{}),
|
||||
Settings: tenant.Settings,
|
||||
Settings: tenantRecord.Settings,
|
||||
Quotas: []tenant.ResourceQuota{},
|
||||
Features: make(map[string]bool),
|
||||
}
|
||||
|
||||
// Add default quotas based on plan
|
||||
switch tenant.Plan {
|
||||
switch tenantRecord.Plan {
|
||||
case tenant.TenantPlanFree:
|
||||
config.Quotas = []tenant.ResourceQuota{
|
||||
{ResourceType: "users", Limit: 5, Period: "monthly"},
|
||||
@@ -379,7 +83,7 @@ func (r *GormTenantRepository) GetConfig(ctx context.Context, tenantID string) (
|
||||
}
|
||||
|
||||
// Add default features based on plan
|
||||
switch tenant.Plan {
|
||||
switch tenantRecord.Plan {
|
||||
case tenant.TenantPlanFree:
|
||||
config.Features = map[string]bool{
|
||||
"multi_currency": false,
|
||||
@@ -410,16 +114,16 @@ func (r *GormTenantRepository) GetConfig(ctx context.Context, tenantID string) (
|
||||
func (r *GormTenantRepository) UpdateConfig(ctx context.Context, config *tenant.TenantConfig) error {
|
||||
// Store configuration in tenant metadata or separate table
|
||||
// For now, update tenant settings
|
||||
tenant, err := r.GetTenant(ctx, config.TenantID)
|
||||
tenantRecord, err := r.GetTenant(ctx, config.TenantID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tenant.Settings = config.Settings
|
||||
tenant.Metadata = config.Config
|
||||
tenant.UpdatedAt = time.Now()
|
||||
tenantRecord.Settings = config.Settings
|
||||
tenantRecord.Metadata = config.Config
|
||||
tenantRecord.UpdatedAt = time.Now()
|
||||
|
||||
return r.UpdateTenant(ctx, tenant)
|
||||
return r.UpdateTenant(ctx, tenantRecord)
|
||||
}
|
||||
|
||||
// CreateEvent creates a new tenant event
|
||||
@@ -480,57 +184,3 @@ func (r *GormTenantRepository) ListEvents(ctx context.Context, tenantID string,
|
||||
|
||||
return events, nil
|
||||
}
|
||||
|
||||
// Helper methods for tenant isolation in other repositories
|
||||
|
||||
// TenantAwareQuery adds tenant filtering to database queries
|
||||
func (r *GormTenantRepository) TenantAwareQuery(ctx context.Context, model interface{}, tenantID string) *gorm.DB {
|
||||
query := r.db.WithContext(ctx).Model(model)
|
||||
|
||||
// Add tenant filter if the model has tenant_id field
|
||||
if tenantID != "" {
|
||||
query = query.Where("tenant_id = ?", tenantID)
|
||||
}
|
||||
|
||||
return query
|
||||
}
|
||||
|
||||
// EnsureTenantIsolation ensures that queries are tenant-isolated
|
||||
func (r *GormTenantRepository) EnsureTenantIsolation(ctx context.Context, tenantID string) error {
|
||||
if tenantID == "" {
|
||||
return fmt.Errorf("tenant ID is required for tenant-isolated operations")
|
||||
}
|
||||
|
||||
// Validate tenant exists
|
||||
_, err := r.GetTenant(ctx, tenantID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid tenant ID: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetTenantFromContext extracts tenant ID from context
|
||||
func (r *GormTenantRepository) GetTenantFromContext(ctx context.Context) string {
|
||||
if tenantID, ok := ctx.Value("tenant_id").(string); ok {
|
||||
return tenantID
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// WithTenantIsolation applies tenant isolation to a database operation
|
||||
func (r *GormTenantRepository) WithTenantIsolation(ctx context.Context, operation func(*gorm.DB) error) error {
|
||||
tenantID := r.GetTenantFromContext(ctx)
|
||||
if tenantID == "" {
|
||||
return fmt.Errorf("tenant ID not found in context")
|
||||
}
|
||||
|
||||
// Validate tenant exists
|
||||
if err := r.EnsureTenantIsolation(ctx, tenantID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Execute operation with tenant filtering
|
||||
tx := r.db.WithContext(ctx).Where("tenant_id = ?", tenantID)
|
||||
return operation(tx)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/tenant"
|
||||
)
|
||||
|
||||
// CreateAPIKey creates a new API key
|
||||
func (r *GormTenantRepository) CreateAPIKey(ctx context.Context, apiKey *tenant.TenantAPIKey) error {
|
||||
return r.db.WithContext(ctx).Create(apiKey).Error
|
||||
}
|
||||
|
||||
// GetAPIKey retrieves an API key by ID
|
||||
func (r *GormTenantRepository) GetAPIKey(ctx context.Context, id string) (*tenant.TenantAPIKey, error) {
|
||||
var apiKey tenant.TenantAPIKey
|
||||
err := r.db.WithContext(ctx).Where("id = ?", id).First(&apiKey).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &apiKey, nil
|
||||
}
|
||||
|
||||
// GetAPIKeyByHash retrieves an API key by hash
|
||||
func (r *GormTenantRepository) GetAPIKeyByHash(ctx context.Context, keyHash string) (*tenant.TenantAPIKey, error) {
|
||||
var apiKey tenant.TenantAPIKey
|
||||
err := r.db.WithContext(ctx).Where("key_hash = ?", keyHash).First(&apiKey).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &apiKey, nil
|
||||
}
|
||||
|
||||
// UpdateAPIKey updates an API key
|
||||
func (r *GormTenantRepository) UpdateAPIKey(ctx context.Context, apiKey *tenant.TenantAPIKey) error {
|
||||
return r.db.WithContext(ctx).Save(apiKey).Error
|
||||
}
|
||||
|
||||
// DeleteAPIKey deletes an API key
|
||||
func (r *GormTenantRepository) DeleteAPIKey(ctx context.Context, id string) error {
|
||||
return r.db.WithContext(ctx).Delete(&tenant.TenantAPIKey{}, "id = ?", id).Error
|
||||
}
|
||||
|
||||
// ListAPIKeys lists API keys for a tenant
|
||||
func (r *GormTenantRepository) ListAPIKeys(ctx context.Context, tenantID string) ([]*tenant.TenantAPIKey, error) {
|
||||
var apiKeys []*tenant.TenantAPIKey
|
||||
err := r.db.WithContext(ctx).Where("tenant_id = ?", tenantID).Order("created_at DESC").Find(&apiKeys).Error
|
||||
return apiKeys, err
|
||||
}
|
||||
|
||||
// CreateUsage creates a new usage record
|
||||
func (r *GormTenantRepository) CreateUsage(ctx context.Context, usage *tenant.TenantUsage) error {
|
||||
return r.db.WithContext(ctx).Create(usage).Error
|
||||
}
|
||||
|
||||
// GetUsage retrieves usage by tenant and resource type
|
||||
func (r *GormTenantRepository) GetUsage(ctx context.Context, tenantID, resourceType string) (*tenant.TenantUsage, error) {
|
||||
var usage tenant.TenantUsage
|
||||
err := r.db.WithContext(ctx).Where("tenant_id = ? AND resource_type = ?", tenantID, resourceType).First(&usage).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &usage, nil
|
||||
}
|
||||
|
||||
// UpdateUsage updates a usage record
|
||||
func (r *GormTenantRepository) UpdateUsage(ctx context.Context, usage *tenant.TenantUsage) error {
|
||||
return r.db.WithContext(ctx).Save(usage).Error
|
||||
}
|
||||
|
||||
// ListUsage lists usage records with filtering
|
||||
func (r *GormTenantRepository) ListUsage(ctx context.Context, filter *tenant.TenantUsageFilter) ([]*tenant.TenantUsage, error) {
|
||||
query := r.db.WithContext(ctx).Model(&tenant.TenantUsage{})
|
||||
|
||||
// Apply filters
|
||||
if filter.TenantID != "" {
|
||||
query = query.Where("tenant_id = ?", filter.TenantID)
|
||||
}
|
||||
if filter.ResourceType != "" {
|
||||
query = query.Where("resource_type = ?", filter.ResourceType)
|
||||
}
|
||||
if !filter.PeriodStart.IsZero() {
|
||||
query = query.Where("period_start >= ?", filter.PeriodStart)
|
||||
}
|
||||
if !filter.PeriodEnd.IsZero() {
|
||||
query = query.Where("period_end <= ?", filter.PeriodEnd)
|
||||
}
|
||||
|
||||
// Apply pagination
|
||||
if filter.Limit > 0 {
|
||||
query = query.Limit(filter.Limit)
|
||||
}
|
||||
if filter.Offset > 0 {
|
||||
query = query.Offset(filter.Offset)
|
||||
}
|
||||
|
||||
var usage []*tenant.TenantUsage
|
||||
err := query.Find(&usage).Error
|
||||
return usage, err
|
||||
}
|
||||
|
||||
@@ -0,0 +1,183 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/tenant"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// GormTenantRepository implements the tenant repository interface using GORM
|
||||
type GormTenantRepository struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewGormTenantRepository creates a new GORM tenant repository
|
||||
func NewGormTenantRepository(db *gorm.DB) TenantRepository {
|
||||
return &GormTenantRepository{db: db}
|
||||
}
|
||||
|
||||
// CreateTenant creates a new tenant
|
||||
func (r *GormTenantRepository) CreateTenant(ctx context.Context, tenant *tenant.Tenant) error {
|
||||
return r.db.WithContext(ctx).Create(tenant).Error
|
||||
}
|
||||
|
||||
// GetTenant retrieves a tenant by ID
|
||||
func (r *GormTenantRepository) GetTenant(ctx context.Context, id string) (*tenant.Tenant, error) {
|
||||
var tenant tenant.Tenant
|
||||
err := r.db.WithContext(ctx).Where("id = ?", id).First(&tenant).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &tenant, nil
|
||||
}
|
||||
|
||||
// GetTenantByDomain retrieves a tenant by domain
|
||||
func (r *GormTenantRepository) GetTenantByDomain(ctx context.Context, domain string) (*tenant.Tenant, error) {
|
||||
var tenant tenant.Tenant
|
||||
err := r.db.WithContext(ctx).Where("domain = ?", domain).First(&tenant).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &tenant, nil
|
||||
}
|
||||
|
||||
// UpdateTenant updates an existing tenant
|
||||
func (r *GormTenantRepository) UpdateTenant(ctx context.Context, tenant *tenant.Tenant) error {
|
||||
return r.db.WithContext(ctx).Save(tenant).Error
|
||||
}
|
||||
|
||||
// DeleteTenant deletes a tenant
|
||||
func (r *GormTenantRepository) DeleteTenant(ctx context.Context, id string) error {
|
||||
return r.db.WithContext(ctx).Delete(&tenant.Tenant{}, "id = ?", id).Error
|
||||
}
|
||||
|
||||
// ListTenants lists tenants with filtering
|
||||
func (r *GormTenantRepository) ListTenants(ctx context.Context, filter *tenant.TenantFilter) ([]*tenant.Tenant, error) {
|
||||
query := r.db.WithContext(ctx).Model(&tenant.Tenant{})
|
||||
|
||||
// Apply filters
|
||||
if filter.ID != "" {
|
||||
query = query.Where("id = ?", filter.ID)
|
||||
}
|
||||
if filter.Name != "" {
|
||||
query = query.Where("name ILIKE ?", "%"+filter.Name+"%")
|
||||
}
|
||||
if filter.Domain != "" {
|
||||
query = query.Where("domain ILIKE ?", "%"+filter.Domain+"%")
|
||||
}
|
||||
if filter.Status != "" {
|
||||
query = query.Where("status = ?", filter.Status)
|
||||
}
|
||||
if filter.Plan != "" {
|
||||
query = query.Where("plan = ?", filter.Plan)
|
||||
}
|
||||
|
||||
// Apply sorting
|
||||
if filter.SortBy != "" {
|
||||
order := filter.SortBy
|
||||
if filter.SortOrder == "desc" {
|
||||
order += " DESC"
|
||||
}
|
||||
query = query.Order(order)
|
||||
} else {
|
||||
query = query.Order("created_at DESC")
|
||||
}
|
||||
|
||||
// Apply pagination
|
||||
if filter.Limit > 0 {
|
||||
query = query.Limit(filter.Limit)
|
||||
}
|
||||
if filter.Offset > 0 {
|
||||
query = query.Offset(filter.Offset)
|
||||
}
|
||||
|
||||
var tenants []*tenant.Tenant
|
||||
err := query.Find(&tenants).Error
|
||||
return tenants, err
|
||||
}
|
||||
|
||||
// CountTenants counts tenants with filtering
|
||||
func (r *GormTenantRepository) CountTenants(ctx context.Context, filter *tenant.TenantFilter) (int, error) {
|
||||
query := r.db.WithContext(ctx).Model(&tenant.Tenant{})
|
||||
|
||||
// Apply filters
|
||||
if filter.ID != "" {
|
||||
query = query.Where("id = ?", filter.ID)
|
||||
}
|
||||
if filter.Name != "" {
|
||||
query = query.Where("name ILIKE ?", "%"+filter.Name+"%")
|
||||
}
|
||||
if filter.Domain != "" {
|
||||
query = query.Where("domain ILIKE ?", "%"+filter.Domain+"%")
|
||||
}
|
||||
if filter.Status != "" {
|
||||
query = query.Where("status = ?", filter.Status)
|
||||
}
|
||||
if filter.Plan != "" {
|
||||
query = query.Where("plan = ?", filter.Plan)
|
||||
}
|
||||
|
||||
var count int64
|
||||
err := query.Count(&count).Error
|
||||
return int(count), err
|
||||
}
|
||||
|
||||
// CreateTenantUser creates a new tenant user
|
||||
func (r *GormTenantRepository) CreateTenantUser(ctx context.Context, user *tenant.TenantUser) error {
|
||||
return r.db.WithContext(ctx).Create(user).Error
|
||||
}
|
||||
|
||||
// GetTenantUser retrieves a tenant user
|
||||
func (r *GormTenantRepository) GetTenantUser(ctx context.Context, tenantID, userID string) (*tenant.TenantUser, error) {
|
||||
var user tenant.TenantUser
|
||||
err := r.db.WithContext(ctx).Where("tenant_id = ? AND user_id = ?", tenantID, userID).First(&user).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
// UpdateTenantUser updates a tenant user
|
||||
func (r *GormTenantRepository) UpdateTenantUser(ctx context.Context, user *tenant.TenantUser) error {
|
||||
return r.db.WithContext(ctx).Save(user).Error
|
||||
}
|
||||
|
||||
// DeleteTenantUser deletes a tenant user
|
||||
func (r *GormTenantRepository) DeleteTenantUser(ctx context.Context, tenantID, userID string) error {
|
||||
return r.db.WithContext(ctx).Delete(&tenant.TenantUser{}, "tenant_id = ? AND user_id = ?", tenantID, userID).Error
|
||||
}
|
||||
|
||||
// ListTenantUsers lists tenant users with filtering
|
||||
func (r *GormTenantRepository) ListTenantUsers(ctx context.Context, filter *tenant.TenantUserFilter) ([]*tenant.TenantUser, error) {
|
||||
query := r.db.WithContext(ctx).Model(&tenant.TenantUser{})
|
||||
|
||||
// Apply filters
|
||||
if filter.TenantID != "" {
|
||||
query = query.Where("tenant_id = ?", filter.TenantID)
|
||||
}
|
||||
if filter.UserID != "" {
|
||||
query = query.Where("user_id = ?", filter.UserID)
|
||||
}
|
||||
if filter.Email != "" {
|
||||
query = query.Where("email ILIKE ?", "%"+filter.Email+"%")
|
||||
}
|
||||
if filter.Role != "" {
|
||||
query = query.Where("role = ?", filter.Role)
|
||||
}
|
||||
if filter.Status != "" {
|
||||
query = query.Where("status = ?", filter.Status)
|
||||
}
|
||||
|
||||
// Apply pagination
|
||||
if filter.Limit > 0 {
|
||||
query = query.Limit(filter.Limit)
|
||||
}
|
||||
if filter.Offset > 0 {
|
||||
query = query.Offset(filter.Offset)
|
||||
}
|
||||
|
||||
var users []*tenant.TenantUser
|
||||
err := query.Find(&users).Error
|
||||
return users, err
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// TenantAwareQuery adds tenant filtering to database queries
|
||||
func (r *GormTenantRepository) TenantAwareQuery(ctx context.Context, model interface{}, tenantID string) *gorm.DB {
|
||||
query := r.db.WithContext(ctx).Model(model)
|
||||
|
||||
// Add tenant filter if the model has tenant_id field
|
||||
if tenantID != "" {
|
||||
query = query.Where("tenant_id = ?", tenantID)
|
||||
}
|
||||
|
||||
return query
|
||||
}
|
||||
|
||||
// EnsureTenantIsolation ensures that queries are tenant-isolated
|
||||
func (r *GormTenantRepository) EnsureTenantIsolation(ctx context.Context, tenantID string) error {
|
||||
if tenantID == "" {
|
||||
return fmt.Errorf("tenant ID is required for tenant-isolated operations")
|
||||
}
|
||||
|
||||
// Validate tenant exists
|
||||
_, err := r.GetTenant(ctx, tenantID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid tenant ID: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetTenantFromContext extracts tenant ID from context
|
||||
func (r *GormTenantRepository) GetTenantFromContext(ctx context.Context) string {
|
||||
if tenantID, ok := ctx.Value("tenant_id").(string); ok {
|
||||
return tenantID
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// WithTenantIsolation applies tenant isolation to a database operation
|
||||
func (r *GormTenantRepository) WithTenantIsolation(ctx context.Context, operation func(*gorm.DB) error) error {
|
||||
tenantID := r.GetTenantFromContext(ctx)
|
||||
if tenantID == "" {
|
||||
return fmt.Errorf("tenant ID not found in context")
|
||||
}
|
||||
|
||||
// Validate tenant exists
|
||||
if err := r.EnsureTenantIsolation(ctx, tenantID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Execute operation with tenant filtering
|
||||
tx := r.db.WithContext(ctx).Where("tenant_id = ?", tenantID)
|
||||
return operation(tx)
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/tenant"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func (r *TenantAwareRepository) CreateWithTenant(ctx context.Context, model interface{}) error {
|
||||
if err := r.ValidateTenant(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Set tenant ID if the model has a TenantID field
|
||||
if modelWithTenant, ok := model.(interface{ SetTenantID(string) }); ok {
|
||||
modelWithTenant.SetTenantID(r.tenantID)
|
||||
}
|
||||
|
||||
return r.db.WithContext(ctx).Create(model).Error
|
||||
}
|
||||
|
||||
func (r *TenantAwareRepository) GetByTenantID(ctx context.Context, model interface{}, id string) error {
|
||||
if err := r.ValidateTenant(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return r.TenantScopedQuery(ctx, model).Where("id = ?", id).First(model).Error
|
||||
}
|
||||
|
||||
func (r *TenantAwareRepository) UpdateWithTenant(ctx context.Context, model interface{}) error {
|
||||
if err := r.ValidateTenant(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return r.db.WithContext(ctx).Save(model).Error
|
||||
}
|
||||
|
||||
func (r *TenantAwareRepository) DeleteWithTenant(ctx context.Context, model interface{}, id string) error {
|
||||
if err := r.ValidateTenant(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return r.TenantScopedQuery(ctx, model).Where("id = ?", id).Delete(model).Error
|
||||
}
|
||||
|
||||
func (r *TenantAwareRepository) ListWithTenant(ctx context.Context, model interface{}, results interface{}, filters map[string]interface{}) error {
|
||||
if err := r.ValidateTenant(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
query := r.TenantScopedQuery(ctx, model)
|
||||
|
||||
// Apply filters
|
||||
for key, value := range filters {
|
||||
query = query.Where(key+" = ?", value)
|
||||
}
|
||||
|
||||
return query.Find(results).Error
|
||||
}
|
||||
|
||||
func (r *TenantAwareRepository) CountWithTenant(ctx context.Context, model interface{}, filters map[string]interface{}) (int64, error) {
|
||||
if err := r.ValidateTenant(ctx); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
query := r.TenantScopedQuery(ctx, model)
|
||||
|
||||
// Apply filters
|
||||
for key, value := range filters {
|
||||
query = query.Where(key+" = ?", value)
|
||||
}
|
||||
|
||||
var count int64
|
||||
err := query.Count(&count).Error
|
||||
return count, err
|
||||
}
|
||||
|
||||
type TenantAwareModel interface {
|
||||
SetTenantID(tenantID string)
|
||||
GetTenantID() string
|
||||
}
|
||||
|
||||
type BaseTenantModel struct {
|
||||
TenantID string `json:"tenant_id" gorm:"column:tenant_id;index;not null"`
|
||||
}
|
||||
|
||||
func (m *BaseTenantModel) SetTenantID(tenantID string) {
|
||||
m.TenantID = tenantID
|
||||
}
|
||||
|
||||
func (m *BaseTenantModel) GetTenantID() string {
|
||||
return m.TenantID
|
||||
}
|
||||
|
||||
type TenantIsolationMiddleware struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewTenantIsolationMiddleware(db *gorm.DB) *TenantIsolationMiddleware {
|
||||
return &TenantIsolationMiddleware{db: db}
|
||||
}
|
||||
|
||||
func (m *TenantIsolationMiddleware) WithTenantIsolation(ctx context.Context, tenantID string, operation func(*gorm.DB) error) error {
|
||||
if tenantID == "" {
|
||||
return fmt.Errorf("tenant ID is required for tenant-isolated operations")
|
||||
}
|
||||
|
||||
var tenantRecord tenant.Tenant
|
||||
err := m.db.WithContext(ctx).Where("id = ? AND status = ?", tenantID, tenant.TenantStatusActive).First(&tenantRecord).Error
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return fmt.Errorf("tenant not found or inactive")
|
||||
}
|
||||
return fmt.Errorf("failed to validate tenant: %w", err)
|
||||
}
|
||||
|
||||
tx := m.db.WithContext(ctx).Where("tenant_id = ?", tenantID)
|
||||
return operation(tx)
|
||||
}
|
||||
|
||||
func GetTenantFromContext(ctx context.Context) string {
|
||||
if tenantID, ok := ctx.Value("tenant_id").(string); ok {
|
||||
return tenantID
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func SetTenantInContext(ctx context.Context, tenantID string) context.Context {
|
||||
return context.WithValue(ctx, "tenant_id", tenantID)
|
||||
}
|
||||
|
||||
type TenantQueryBuilder struct {
|
||||
db *gorm.DB
|
||||
tenantID string
|
||||
query *gorm.DB
|
||||
}
|
||||
|
||||
func NewTenantQueryBuilder(db *gorm.DB, tenantID string) *TenantQueryBuilder {
|
||||
return &TenantQueryBuilder{
|
||||
db: db,
|
||||
tenantID: tenantID,
|
||||
query: db.Where("tenant_id = ?", tenantID),
|
||||
}
|
||||
}
|
||||
|
||||
func (b *TenantQueryBuilder) Where(query string, args ...interface{}) *TenantQueryBuilder {
|
||||
b.query = b.query.Where(query, args...)
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *TenantQueryBuilder) Order(value string) *TenantQueryBuilder {
|
||||
b.query = b.query.Order(value)
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *TenantQueryBuilder) Limit(limit int) *TenantQueryBuilder {
|
||||
b.query = b.query.Limit(limit)
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *TenantQueryBuilder) Offset(offset int) *TenantQueryBuilder {
|
||||
b.query = b.query.Offset(offset)
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *TenantQueryBuilder) Find(dest interface{}) error {
|
||||
return b.query.Find(dest).Error
|
||||
}
|
||||
|
||||
func (b *TenantQueryBuilder) First(dest interface{}) error {
|
||||
return b.query.First(dest).Error
|
||||
}
|
||||
|
||||
func (b *TenantQueryBuilder) Count() (int64, error) {
|
||||
var count int64
|
||||
err := b.query.Count(&count).Error
|
||||
return count, err
|
||||
}
|
||||
|
||||
func (b *TenantQueryBuilder) GetQuery() *gorm.DB {
|
||||
return b.query
|
||||
}
|
||||
|
||||
type TenantResourceValidator struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewTenantResourceValidator(db *gorm.DB) *TenantResourceValidator {
|
||||
return &TenantResourceValidator{db: db}
|
||||
}
|
||||
@@ -11,19 +11,17 @@ import (
|
||||
"github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/rateplan"
|
||||
)
|
||||
|
||||
// RatePlanCurrencyIntegrator integrates currency system with rate plans
|
||||
type RatePlanCurrencyIntegrator struct {
|
||||
billingService currency.BillingService
|
||||
exchangeService *ExchangeRateService
|
||||
exchangeService *currency.ExchangeRateService
|
||||
ratePlanService rateplan.Service
|
||||
logger *logrus.Logger
|
||||
baseCurrency string
|
||||
}
|
||||
|
||||
// NewRatePlanCurrencyIntegrator creates a new rate plan currency integrator
|
||||
func NewRatePlanCurrencyIntegrator(
|
||||
billingService currency.BillingService,
|
||||
exchangeService *ExchangeRateService,
|
||||
exchangeService *currency.ExchangeRateService,
|
||||
ratePlanService rateplan.Service,
|
||||
logger *logrus.Logger,
|
||||
baseCurrency string,
|
||||
@@ -37,20 +35,22 @@ func NewRatePlanCurrencyIntegrator(
|
||||
}
|
||||
}
|
||||
|
||||
// SubscribeToPlanWithCurrency subscribes to a rate plan with currency conversion
|
||||
func (rpci *RatePlanCurrencyIntegrator) SubscribeToPlanWithCurrency(ctx context.Context, profileID string, planID string, targetCurrency string) (*rateplan.RatePlanSubscription, error) {
|
||||
// Get the rate plan
|
||||
plan, err := rpci.ratePlanService.GetRatePlan(ctx, planID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get rate plan: %w", err)
|
||||
}
|
||||
|
||||
// Convert price to requested currency if needed
|
||||
subscriptionPrice := plan.BasePrice
|
||||
exchangeRate := 1.0
|
||||
|
||||
if targetCurrency != plan.Currency {
|
||||
conversion, err := rpci.exchangeService.ConvertAmount(ctx, plan.BasePrice, plan.Currency, targetCurrency)
|
||||
conversionReq := ¤cy.CurrencyConversionRequest{
|
||||
Amount: plan.BasePrice,
|
||||
FromCurrency: plan.Currency,
|
||||
ToCurrency: targetCurrency,
|
||||
}
|
||||
conversion, err := rpci.billingService.ConvertAmount(ctx, conversionReq)
|
||||
if err != nil {
|
||||
rpci.logger.WithError(err).Error("Failed to convert rate plan price")
|
||||
return nil, fmt.Errorf("currency conversion failed: %w", err)
|
||||
@@ -59,7 +59,6 @@ func (rpci *RatePlanCurrencyIntegrator) SubscribeToPlanWithCurrency(ctx context.
|
||||
exchangeRate = conversion.ExchangeRate
|
||||
}
|
||||
|
||||
// Create subscription with currency information
|
||||
subscription := &rateplan.RatePlanSubscription{
|
||||
ProfileID: profileID,
|
||||
RatePlanID: planID,
|
||||
@@ -76,7 +75,6 @@ func (rpci *RatePlanCurrencyIntegrator) SubscribeToPlanWithCurrency(ctx context.
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
|
||||
// Create subscription request
|
||||
subscribeReq := &rateplan.SubscribeRequest{
|
||||
ProfileID: profileID,
|
||||
RatePlanID: planID,
|
||||
@@ -89,7 +87,6 @@ func (rpci *RatePlanCurrencyIntegrator) SubscribeToPlanWithCurrency(ctx context.
|
||||
return nil, fmt.Errorf("failed to create subscription: %w", err)
|
||||
}
|
||||
|
||||
// Process initial billing
|
||||
billingReq := ¤cy.BillingRequest{
|
||||
ProfileID: profileID,
|
||||
SubscriptionID: createdSubscription.ID,
|
||||
@@ -115,18 +112,14 @@ func (rpci *RatePlanCurrencyIntegrator) SubscribeToPlanWithCurrency(ctx context.
|
||||
return createdSubscription, nil
|
||||
}
|
||||
|
||||
// CalculatePlanCostInCurrency calculates the cost of a rate plan in a specific currency
|
||||
func (rpci *RatePlanCurrencyIntegrator) CalculatePlanCostInCurrency(ctx context.Context, planID string, targetCurrency string, usageData *rateplan.RatePlanUsage) (*currency.BillingSummary, error) {
|
||||
// Get the rate plan
|
||||
plan, err := rpci.ratePlanService.GetRatePlan(ctx, planID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get rate plan: %w", err)
|
||||
}
|
||||
|
||||
// Calculate base cost
|
||||
baseCost := plan.BasePrice
|
||||
|
||||
// Add overage costs if usage data is provided
|
||||
if usageData != nil {
|
||||
overageCost, err := rpci.calculateOverageCost(ctx, plan, usageData)
|
||||
if err != nil {
|
||||
@@ -136,12 +129,16 @@ func (rpci *RatePlanCurrencyIntegrator) CalculatePlanCostInCurrency(ctx context.
|
||||
}
|
||||
}
|
||||
|
||||
// Convert to requested currency
|
||||
convertedCost := baseCost
|
||||
exchangeRate := 1.0
|
||||
|
||||
if targetCurrency != plan.Currency {
|
||||
conversion, err := rpci.exchangeService.ConvertAmount(ctx, baseCost, plan.Currency, targetCurrency)
|
||||
conversionReq := ¤cy.CurrencyConversionRequest{
|
||||
Amount: baseCost,
|
||||
FromCurrency: plan.Currency,
|
||||
ToCurrency: targetCurrency,
|
||||
}
|
||||
conversion, err := rpci.billingService.ConvertAmount(ctx, conversionReq)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("currency conversion failed: %w", err)
|
||||
}
|
||||
@@ -149,7 +146,6 @@ func (rpci *RatePlanCurrencyIntegrator) CalculatePlanCostInCurrency(ctx context.
|
||||
exchangeRate = conversion.ExchangeRate
|
||||
}
|
||||
|
||||
// Create billing summary
|
||||
summary := ¤cy.BillingSummary{
|
||||
ProfileID: usageData.ProfileID,
|
||||
TotalAmount: convertedCost,
|
||||
@@ -171,3 +167,30 @@ func (rpci *RatePlanCurrencyIntegrator) CalculatePlanCostInCurrency(ctx context.
|
||||
|
||||
return summary, nil
|
||||
}
|
||||
|
||||
func (rpci *RatePlanCurrencyIntegrator) calculateOverageCost(ctx context.Context, plan *rateplan.RatePlan, usage *rateplan.RatePlanUsage) (float64, error) {
|
||||
overageCost := 0.0
|
||||
|
||||
if plan.DataAllowance != nil && usage.DataUsed > plan.DataAllowance.Amount {
|
||||
dataOverage := usage.DataUsed - plan.DataAllowance.Amount
|
||||
if plan.OverageRates != nil {
|
||||
overageCost += float64(dataOverage) * plan.OverageRates.DataRate
|
||||
}
|
||||
}
|
||||
|
||||
if plan.VoiceAllowance != nil && usage.VoiceUsed > plan.VoiceAllowance.Minutes {
|
||||
voiceOverage := usage.VoiceUsed - plan.VoiceAllowance.Minutes
|
||||
if plan.OverageRates != nil {
|
||||
overageCost += float64(voiceOverage) * plan.OverageRates.VoiceRate
|
||||
}
|
||||
}
|
||||
|
||||
if plan.SMSAllowance != nil && usage.SMSUsed > plan.SMSAllowance.Messages {
|
||||
smsOverage := usage.SMSUsed - plan.SMSAllowance.Messages
|
||||
if plan.OverageRates != nil {
|
||||
overageCost += float64(smsOverage) * plan.OverageRates.SMSRate
|
||||
}
|
||||
}
|
||||
|
||||
return overageCost, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user