using Microsoft.EntityFrameworkCore; using Npgsql; using WorkClub.Application.Clubs.DTOs; using WorkClub.Application.Interfaces; using WorkClub.Domain.Enums; using WorkClub.Infrastructure.Data; namespace WorkClub.Api.Services; public class ClubService { private readonly AppDbContext _context; private readonly ITenantProvider _tenantProvider; private readonly IHttpContextAccessor _httpContextAccessor; private readonly ILogger _logger; public ClubService( AppDbContext context, ITenantProvider tenantProvider, IHttpContextAccessor httpContextAccessor, ILogger logger) { _context = context; _tenantProvider = tenantProvider; _httpContextAccessor = httpContextAccessor; _logger = logger; } public async Task> GetMyClubsAsync() { try { var clubsClaim = _httpContextAccessor.HttpContext?.User.FindFirst("clubs")?.Value; _logger.LogInformation("GetMyClubsAsync: Clubs claim value: {ClubsClaim}", clubsClaim); if (string.IsNullOrEmpty(clubsClaim)) { _logger.LogWarning("GetMyClubsAsync: No clubs claim found for user"); return new List(); } // Parse UUIDs from comma-separated claim, filtering out non-UUID values (like role names) var tenantIds = clubsClaim.Split(',', StringSplitOptions.RemoveEmptyEntries) .Select(t => t.Trim()) .Where(t => !string.IsNullOrEmpty(t) && Guid.TryParse(t, out _)) .ToList(); _logger.LogInformation("GetMyClubsAsync: Parsed {Count} valid tenant IDs from claim", tenantIds.Count); if (tenantIds.Count == 0) { _logger.LogWarning("GetMyClubsAsync: No valid tenant IDs found in clubs claim: {ClubsClaim}", clubsClaim); return new List(); } var clubDtos = new List(); var connectionString = _context.Database.GetConnectionString(); foreach (var tenantId in tenantIds) { try { await using var connection = new NpgsqlConnection(connectionString); await connection.OpenAsync(); await using var transaction = await connection.BeginTransactionAsync(); // Set RLS context - tenantId is already validated as a valid GUID // Use direct string since SET LOCAL doesn't support parameters using (var command = connection.CreateCommand()) { command.Transaction = transaction; command.CommandText = $"SET LOCAL app.current_tenant_id = '{tenantId}'"; await command.ExecuteNonQueryAsync(); } Guid? clubId = null; string? clubName = null; int? sportTypeInt = null; // Fetch club details using (var command = connection.CreateCommand()) { command.Transaction = transaction; command.CommandText = @" SELECT c.""Id"", c.""Name"", c.""SportType"" FROM clubs AS c WHERE c.""TenantId"" = @tenantId"; var parameter = command.CreateParameter(); parameter.ParameterName = "@tenantId"; parameter.Value = tenantId; command.Parameters.Add(parameter); using (var reader = await command.ExecuteReaderAsync()) { if (await reader.ReadAsync()) { clubId = reader.GetGuid(0); clubName = reader.GetString(1); sportTypeInt = reader.GetInt32(2); } } } // Fetch member count if club exists if (clubId.HasValue && clubName != null && sportTypeInt.HasValue) { using (var memberCommand = connection.CreateCommand()) { memberCommand.Transaction = transaction; memberCommand.CommandText = @" SELECT COUNT(*) FROM members AS m WHERE m.""ClubId"" = @clubId"; var param = memberCommand.CreateParameter(); param.ParameterName = "@clubId"; param.Value = clubId; memberCommand.Parameters.Add(param); var memberCountResult = await memberCommand.ExecuteScalarAsync(); var memberCount = memberCountResult != null ? Convert.ToInt32(memberCountResult) : 0; var sportTypeEnum = ((SportType)sportTypeInt.Value).ToString(); clubDtos.Add(new ClubListDto( clubId.Value, clubName, sportTypeEnum, memberCount, Guid.Parse(tenantId) )); } } await transaction.CommitAsync(); } catch (Exception ex) { _logger.LogError(ex, "GetMyClubsAsync: Error processing tenant {TenantId}", tenantId); // Continue with next tenant instead of failing entirely } } _logger.LogInformation("GetMyClubsAsync: Returning {Count} clubs", clubDtos.Count); return clubDtos; } catch (Exception ex) { _logger.LogError(ex, "GetMyClubsAsync: Unexpected error getting user clubs"); // Return empty list instead of throwing to prevent 500 error return new List(); } } public async Task GetCurrentClubAsync() { try { var tenantId = _tenantProvider.GetTenantId(); var club = await _context.Clubs .FirstOrDefaultAsync(c => c.TenantId == tenantId); if (club == null) return null; return new ClubDetailDto( club.Id, club.Name, club.SportType.ToString(), club.Description, club.CreatedAt, club.UpdatedAt ); } catch (Exception ex) { _logger.LogError(ex, "GetCurrentClubAsync: Error getting current club"); return null; } } }