diff --git a/scratch/check_db.cs b/scratch/check_db.cs new file mode 100644 index 0000000..f4fa88f --- /dev/null +++ b/scratch/check_db.cs @@ -0,0 +1,44 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.EntityFrameworkCore; +using NexusReader.Infrastructure.Persistence; +using Microsoft.Extensions.Configuration; +using NexusReader.Domain.Entities; +using System; +using System.Linq; +using System.Threading.Tasks; + +var configuration = new ConfigurationBuilder() + .AddJsonFile("src/NexusReader.Web.New/appsettings.json") + .Build(); + +var services = new ServiceCollection(); +var pgConnectionString = configuration.GetConnectionString("PostgresConnection"); +if (!string.IsNullOrEmpty(pgConnectionString)) +{ + services.AddDbContext(options => options.UseNpgsql(pgConnectionString)); +} +else +{ + services.AddDbContext(options => options.UseSqlite(configuration.GetConnectionString("SqliteConnection"))); +} + +var serviceProvider = services.BuildServiceProvider(); +using var scope = serviceProvider.CreateScope(); +var dbContext = scope.ServiceProvider.GetRequiredService(); + +try +{ + var user = await dbContext.Users.FirstOrDefaultAsync(u => u.Email == "admin@nexus.com"); + if (user == null) + { + Console.WriteLine("User admin@nexus.com NOT FOUND in database."); + } + else + { + Console.WriteLine($"User found: {user.Email}, Id: {user.Id}, EmailConfirmed: {user.EmailConfirmed}"); + } +} +catch (Exception ex) +{ + Console.WriteLine($"Error accessing database: {ex.Message}"); +} diff --git a/src/NexusReader.Infrastructure/Persistence/DbInitializer.cs b/src/NexusReader.Infrastructure/Persistence/DbInitializer.cs new file mode 100644 index 0000000..1170bf3 --- /dev/null +++ b/src/NexusReader.Infrastructure/Persistence/DbInitializer.cs @@ -0,0 +1,72 @@ +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.DependencyInjection; +using NexusReader.Domain.Entities; +using System; +using System.Linq; +using System.Threading.Tasks; + +namespace NexusReader.Infrastructure.Persistence; + +public static class DbInitializer +{ + public static async Task SeedAsync(IServiceProvider serviceProvider) + { + using var scope = serviceProvider.CreateScope(); + var userManager = scope.ServiceProvider.GetRequiredService>(); + var roleManager = scope.ServiceProvider.GetRequiredService>(); + + try + { + Console.WriteLine("[Seeder] Starting database seeding..."); + + // Seed Roles + string[] roleNames = { "Admin", "User" }; + foreach (var roleName in roleNames) + { + var roleExist = await roleManager.RoleExistsAsync(roleName); + if (!roleExist) + { + await roleManager.CreateAsync(new IdentityRole(roleName)); + Console.WriteLine($"[Seeder] Created role: {roleName}"); + } + } + + // Seed Admin User + var adminEmail = "admin@nexus.com"; + var adminUser = await userManager.FindByEmailAsync(adminEmail); + + if (adminUser == null) + { + adminUser = new NexusUser + { + UserName = adminEmail, + Email = adminEmail, + EmailConfirmed = true, + CurrentPlan = "Enterprise", + AITokenLimit = 1000000, + TenantId = Guid.NewGuid() + }; + + var createPowerUser = await userManager.CreateAsync(adminUser, "Admin123!"); + if (createPowerUser.Succeeded) + { + await userManager.AddToRoleAsync(adminUser, "Admin"); + Console.WriteLine($"[Seeder] Admin user created successfully: {adminEmail}"); + } + else + { + var errors = string.Join(", ", createPowerUser.Errors.Select(e => e.Description)); + Console.WriteLine($"[Seeder] Failed to create admin user: {errors}"); + } + } + else + { + Console.WriteLine("[Seeder] Admin user already exists."); + } + } + catch (Exception ex) + { + Console.WriteLine($"[Seeder] Critical error during seeding: {ex.Message}"); + } + } +} diff --git a/src/NexusReader.Maui/Main.razor b/src/NexusReader.Maui/Main.razor index 021c022..89cac43 100644 --- a/src/NexusReader.Maui/Main.razor +++ b/src/NexusReader.Maui/Main.razor @@ -3,7 +3,11 @@ - + + + + + diff --git a/src/NexusReader.Maui/wwwroot/index.html b/src/NexusReader.Maui/wwwroot/index.html index 544600a..e0a77ce 100644 --- a/src/NexusReader.Maui/wwwroot/index.html +++ b/src/NexusReader.Maui/wwwroot/index.html @@ -11,7 +11,12 @@ -
Loading...
+
+
+
+
Nexus Reader
+
+
An unhandled error has occurred. diff --git a/src/NexusReader.UI.Shared/Layout/MainLayout.razor b/src/NexusReader.UI.Shared/Layout/MainLayout.razor index a53e1e3..ab44b8e 100644 --- a/src/NexusReader.UI.Shared/Layout/MainLayout.razor +++ b/src/NexusReader.UI.Shared/Layout/MainLayout.razor @@ -11,18 +11,18 @@ @inject NavigationManager NavigationManager @implements IDisposable -
-
-
- @Body -
- -
+ + +
+
+
+ @Body +
+ +
- + - -
@@ -46,9 +46,18 @@
-
-
-
+
+ + +
+
+
Weryfikacja...
+
+
+ + @Body + +
An unhandled error has occurred. diff --git a/src/NexusReader.UI.Shared/wwwroot/app.css b/src/NexusReader.UI.Shared/wwwroot/app.css index 723e410..df8d6f2 100644 --- a/src/NexusReader.UI.Shared/wwwroot/app.css +++ b/src/NexusReader.UI.Shared/wwwroot/app.css @@ -99,4 +99,57 @@ h1:focus { color: white; margin: 1rem; border-radius: 8px; -} \ No newline at end of file +} + +/* Preloader Styles */ +#app-preloader, .app-preloader { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: radial-gradient(circle at center, #1a1a1a 0%, var(--nexus-bg) 100%); + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + z-index: 9999; + transition: opacity 0.8s ease, visibility 0.8s; +} + +#app-preloader.loaded { + opacity: 0; + visibility: hidden; +} + +.preloader-spinner { + width: 80px; + height: 80px; + border: 3px solid rgba(0, 255, 153, 0.1); + border-top: 3px solid var(--nexus-neon); + border-radius: 50%; + animation: spin 1s cubic-bezier(0.4, 0, 0.2, 1) infinite; + filter: drop-shadow(0 0 10px var(--nexus-neon)); + margin-bottom: 20px; +} + +.preloader-text { + color: var(--nexus-neon); + font-family: var(--nexus-font-sans); + letter-spacing: 4px; + text-transform: uppercase; + font-size: 0.8rem; + font-weight: 500; + animation: pulse 2s infinite; +} + +@keyframes spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } +} + +@keyframes pulse { + 0%, 100% { opacity: 1; transform: scale(1); } + 50% { opacity: 0.5; transform: scale(0.95); } +} + \ No newline at end of file diff --git a/src/NexusReader.Web.New/Components/App.razor b/src/NexusReader.Web.New/Components/App.razor index faf4c1e..266dea5 100644 --- a/src/NexusReader.Web.New/Components/App.razor +++ b/src/NexusReader.Web.New/Components/App.razor @@ -5,7 +5,6 @@ - @@ -14,9 +13,35 @@ +
+
+
Nexus Reader
+
+ + diff --git a/src/NexusReader.Web.New/Program.cs b/src/NexusReader.Web.New/Program.cs index c61d952..80a67c6 100644 --- a/src/NexusReader.Web.New/Program.cs +++ b/src/NexusReader.Web.New/Program.cs @@ -81,6 +81,7 @@ builder.Services.AddAuthentication(options => }); builder.Services.AddIdentityApiEndpoints() + .AddRoles() .AddEntityFrameworkStores(); builder.Services.ConfigureApplicationCookie(options => @@ -113,11 +114,38 @@ builder.Services.Configure(options => var app = builder.Build(); -// Ensure Database is initialized +// Ensure Database is initialized and seeded using (var scope = app.Services.CreateScope()) { - var dbContext = scope.ServiceProvider.GetRequiredService(); - await dbContext.Database.MigrateAsync(); + var services = scope.ServiceProvider; + var logger = services.GetRequiredService>(); + var dbContext = services.GetRequiredService(); + + int maxRetries = 5; + int delayMs = 2000; + + for (int i = 0; i < maxRetries; i++) + { + try + { + logger.LogInformation("Próba połączenia z bazą danych (próba {Attempt}/{MaxRetries})...", i + 1, maxRetries); + await dbContext.Database.MigrateAsync(); + await DbInitializer.SeedAsync(services); + logger.LogInformation("Baza danych zainicjowana pomyślnie."); + break; + } + catch (Npgsql.NpgsqlException ex) when (i < maxRetries - 1) + { + logger.LogWarning("Błąd połączenia z bazą danych: {Message}. Ponowna próba za {Delay}ms...", ex.Message, delayMs); + await Task.Delay(delayMs); + delayMs *= 2; // Exponential backoff + } + catch (Exception ex) + { + logger.LogCritical(ex, "Krytyczny błąd podczas inicjalizacji bazy danych."); + throw; + } + } } // Configure the HTTP request pipeline. diff --git a/src/NexusReader.Web.New/appsettings.json b/src/NexusReader.Web.New/appsettings.json index f60ca83..747b806 100644 --- a/src/NexusReader.Web.New/appsettings.json +++ b/src/NexusReader.Web.New/appsettings.json @@ -26,5 +26,6 @@ "Model": "gemini-2.5-flash-lite", "MaxOutputTokens": 8192 } - } + }, + "ApiBaseUrl": "http://localhost:5000" } diff --git a/src/NexusReader.Web.New/nexus.db-shm b/src/NexusReader.Web.New/nexus.db-shm deleted file mode 100644 index ea5e4de..0000000 Binary files a/src/NexusReader.Web.New/nexus.db-shm and /dev/null differ diff --git a/src/NexusReader.Web.New/nexus.db-wal b/src/NexusReader.Web.New/nexus.db-wal deleted file mode 100644 index cc93d51..0000000 Binary files a/src/NexusReader.Web.New/nexus.db-wal and /dev/null differ