package repositories import ( "context" "database/sql" "time" "github.com/creativenoz/aurganize-v62/backend/internal/models" "github.com/google/uuid" "github.com/jmoiron/sqlx" "github.com/rs/zerolog/log" ) type TenantRepository struct { db *sqlx.DB } func NewTenantRepository(db *sqlx.DB) *TenantRepository { log.Info(). Str("repository", "tenant"). Str("component", "repository_init"). Bool("has_db_connection", db != nil). Msg("tenant repository initialized") return &TenantRepository{ db: db, } } type Execer interface { GetContext(ctx context.Context, des interface{}, query string, args ...interface{}) error ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error) SelectContext(ctx context.Context, dest interface{}, query string, args ...interface{}) error QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) QueryRowContext(ctx context.Context, query string, args ...interface{}) *sql.Row } func (tr *TenantRepository) CreateTx(ctx context.Context, tx Execer, input *models.CreateTenantInput, slug string) (*models.Tenant, error) { log.Info(). Str("repository", "tenant"). Str("action", "create_tenant_tx_started"). Str("tenant_name", input.Name). Str("slug", slug). Str("email", *input.Email). Str("timezone", input.Timezone). Str("subscription_plan", "basic"). Str("subscription_status", "trial"). Bool("in_transaction", true). Msg("creating tenant within transaction") tenant := &models.Tenant{} // trailEndsAt := time.Now().Add(14 * 24 * time.Hour) loc, err := time.LoadLocation(input.Timezone) if err != nil { log.Warn(). Str("repository", "tenant"). Str("action", "invalid_timezone_fallback"). Str("invalid_timezone", input.Timezone). Str("fallback_timezone", "UTC"). Str("tenant_name", input.Name). Msg("invalid timezone provided, falling back to UTC") loc = time.UTC } now := time.Now().In(loc) trialEndsAt := now.Add(14 * 24 * time.Hour) log.Debug(). Str("repository", "tenant"). Str("action", "trial_period_calculated"). Int("trial_duration_days", 14). Time("trial_ends_at", trialEndsAt). Str("tenant_name", input.Name). Msg("trial period configured for new tenant") query := ` INSERT INTO tenants ( name, slug, email, timezone, currency, locale, subscription_status, subscription_plan, trial_ends_at, status ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) RETURNING id, name, slug, email, phone, website, address_line1, address_line2, city, state, country, postal_code, timezone, currency, locale, subscription_status, subscription_plan, subscription_expires_at, trial_ends_at, max_users, max_contracts, max_storage_mb, status, created_at, updated_at, deleted_at ` err = tx.GetContext( ctx, tenant, query, input.Name, slug, input.Email, input.Timezone, input.Currency, input.Locale, "trial", "basic", trialEndsAt, "active", ) if err != nil { log.Error(). Str("repository", "tenant"). Str("action", "create_tenant_tx_failed"). Str("tenant_name", input.Name). Str("slug", slug). Str("email", *input.Email). Bool("in_transaction", true). Err(err). Msg("CRITICAL: failed to create tenant in transaction - registration will fail") return nil, err } log.Info(). Str("repository", "tenant"). Str("action", "create_tenant_tx_success"). Str("tenant_id", tenant.ID.String()). Str("tenant_name", tenant.Name). Str("slug", tenant.Slug). Str("email", *tenant.Email). Str("subscription_status", tenant.SubscriptionStatus). Str("subscription_plan", tenant.SubscriptionPlan). Time("trial_ends_at", *tenant.TrialEnds). Str("currency", tenant.Currency). Str("timezone", tenant.Timezone). Bool("in_transaction", true). Msg("tenant created successfully in transaction") return tenant, nil } // Create creates a new tenant func (tr *TenantRepository) Create(ctx context.Context, input *models.CreateTenantInput, slug string) (*models.Tenant, error) { log.Info(). Str("repository", "tenant"). Str("action", "create_tenant_started"). Str("tenant_name", input.Name). Str("slug", slug). Str("email", *input.Email). Str("timezone", input.Timezone). Bool("in_transaction", false). Msg("creating tenant (standalone, not in transaction)") tenant := &models.Tenant{} // trailEndsAt := time.Now().Add(14 * 24 * time.Hour) loc, err := time.LoadLocation(input.Timezone) if err != nil { log.Warn(). Str("repository", "tenant"). Str("action", "invalid_timezone_fallback"). Str("invalid_timezone", input.Timezone). Str("fallback_timezone", "UTC"). Str("tenant_name", input.Name). Msg("invalid timezone provided, falling back to UTC") loc = time.UTC } now := time.Now().In(loc) trialEndsAt := now.Add(14 * 24 * time.Hour) log.Debug(). Str("repository", "tenant"). Str("action", "trial_period_calculated"). Int("trial_duration_days", 14). Time("trial_ends_at", trialEndsAt). Str("tenant_name", input.Name). Msg("trial period configured for new tenant") query := ` INSERT INTO tenants ( name, slug, email, timezone, currency, locale, subscription_status, subscription_plan, trial_ends_at, status ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) RETURNING id, name, slug, email, phone, website, address_line1, address_line2, city, state, country, postal_code, timezone, currency, locale, subscription_status, subscription_plan, subscription_expires_at, trial_ends_at, max_users, max_contracts, max_storage_mb, status, created_at, updated_at, deleted_at ` err = tr.db.GetContext( ctx, tenant, query, input.Name, slug, input.Email, input.Timezone, input.Currency, input.Locale, "trial", "basic", trialEndsAt, "active", ) if err != nil { log.Error(). Str("repository", "tenant"). Str("action", "create_tenant_failed"). Str("tenant_name", input.Name). Str("slug", slug). Err(err). Msg("failed to create tenant") return nil, err } log.Info(). Str("repository", "tenant"). Str("action", "create_tenant_success"). Str("tenant_id", tenant.ID.String()). Str("tenant_name", tenant.Name). Str("slug", tenant.Slug). Str("subscription_status", tenant.SubscriptionStatus). Time("trial_ends_at", *tenant.TrialEnds). Bool("in_transaction", false). Msg("tenant created successfully") return tenant, nil } func (tr *TenantRepository) FindByID(ctx context.Context, tenantId uuid.UUID) (*models.Tenant, error) { log.Debug(). Str("repository", "tenant"). Str("action", "find_tenant_by_id_started"). Str("tenant_id", tenantId.String()). Msg("looking up tenant by id") tenant := &models.Tenant{} query := ` SELECT id, name, slug, email, phone, website, address_line1, address_line2, city, state, country, postal_code, timezone, currency, locale, subscription_status, subscription_plan, subscription_expires_at, trial_ends_at, max_users, max_contracts, max_storage_mb, status, created_at, updated_at, deleted_at FROM tenants WHERE id = $1 AND deleted_at IS NULL ` err := tr.db.GetContext(ctx, tenant, query, tenantId) if err == sql.ErrNoRows { log.Warn(). Str("repository", "tenant"). Str("action", "tenant_not_found_by_id"). Str("tenant_id", tenantId.String()). Msg("tenant not found - may be deleted or never existed") return nil, nil } if err != nil { log.Error(). Str("repository", "tenant"). Str("action", "find_tenant_by_id_error"). Str("tenant_id", tenantId.String()). Err(err). Msg("database error while looking up tenant by id") return nil, err } log.Debug(). Str("repository", "tenant"). Str("action", "tenant_found_by_id"). Str("tenant_id", tenant.ID.String()). Str("tenant_name", tenant.Name). Str("subscription_status", tenant.SubscriptionStatus). Str("subscription_plan", tenant.SubscriptionPlan). Str("status", tenant.Status). Msg("tenant found by id") return tenant, nil } func (tr *TenantRepository) FindBySlug(ctx context.Context, slug string) (*models.Tenant, error) { log.Debug(). Str("repository", "tenant"). Str("action", "find_tenant_by_slug_started"). Str("slug", slug). Msg("looking up tenant by slug") tenant := &models.Tenant{} query := ` SELECT id, name, slug, email, phone, website, address_line1, address_line2, city, state, country, postal_code, timezone, currency, locale, subscription_status, subscription_plan, subscription_expires_at, trial_ends_at, max_users, max_contracts, max_storage_mb, status, created_at, updated_at, deleted_at FROM tenants WHERE slug = $1 AND deleted_at IS NULL ` err := tr.db.GetContext(ctx, tenant, query, slug) if err == sql.ErrNoRows { log.Warn(). Str("repository", "tenant"). Str("action", "tenant_not_found_by_slug"). Str("slug", slug). Msg("tenant not found by slug") return nil, nil } if err != nil { log.Error(). Str("repository", "tenant"). Str("action", "find_tenant_by_slug_error"). Str("slug", slug). Err(err). Msg("database error while looking up tenant by slug") return nil, err } log.Debug(). Str("repository", "tenant"). Str("action", "tenant_found_by_slug"). Str("tenant_id", tenant.ID.String()). Str("tenant_name", tenant.Name). Str("slug", tenant.Slug). Str("subscription_status", tenant.SubscriptionStatus). Msg("tenant found by slug") return tenant, nil } func (tr *TenantRepository) SlugExists(ctx context.Context, slug string) (bool, error) { log.Debug(). Str("repository", "tenant"). Str("action", "checking_slug_exists"). Str("slug", slug). Msg("checking if tenant slug is available") var exists bool = false query := ` SELECT EXISTS ( SELECT 1 FROM tenants WHERE slug = $1 AND deleted_at IS NULL ) ` err := tr.db.GetContext(ctx, &exists, query, slug) if err != nil { log.Error(). Str("repository", "tenant"). Str("action", "slug_exists_check_error"). Str("slug", slug). Err(err). Msg("database error while checking slug existence") return true, err } if exists { log.Info(). Str("repository", "tenant"). Str("action", "slug_exists_conflict"). Str("slug", slug). Bool("exists", true). Msg("slug already taken - registration will require different name") } else { log.Debug(). Str("repository", "tenant"). Str("action", "slug_available"). Str("slug", slug). Bool("exists", false). Msg("slug is available for registration") } return exists, nil } func (tr TenantRepository) Update(ctx context.Context, id uuid.UUID, updates map[string]interface{}) error { // TODO return nil }