diff --git a/USEntryCoach.Server/Data/ApplicationDbContext.cs b/USEntryCoach.Server/Data/ApplicationDbContext.cs new file mode 100644 index 0000000..35850da --- /dev/null +++ b/USEntryCoach.Server/Data/ApplicationDbContext.cs @@ -0,0 +1,10 @@ +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Identity.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; + +namespace USEntryCoach.Server.Data; + +public class ApplicationDbContext(DbContextOptions options) : IdentityDbContext(options) +{ + +} \ No newline at end of file diff --git a/USEntryCoach.Server/Migrations/20250527123305_Initial.Designer.cs b/USEntryCoach.Server/Migrations/20250527123305_Initial.Designer.cs new file mode 100644 index 0000000..83f2001 --- /dev/null +++ b/USEntryCoach.Server/Migrations/20250527123305_Initial.Designer.cs @@ -0,0 +1,277 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using USEntryCoach.Server.Data; + +#nullable disable + +namespace USEntryCoach.Server.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20250527123305_Initial")] + partial class Initial + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.5") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("AccessFailedCount") + .HasColumnType("integer"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("boolean"); + + b.Property("LockoutEnabled") + .HasColumnType("boolean"); + + b.Property("LockoutEnd") + .HasColumnType("timestamp with time zone"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PasswordHash") + .HasColumnType("text"); + + b.Property("PhoneNumber") + .HasColumnType("text"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("boolean"); + + b.Property("SecurityStamp") + .HasColumnType("text"); + + b.Property("TwoFactorEnabled") + .HasColumnType("boolean"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("ProviderKey") + .HasColumnType("text"); + + b.Property("ProviderDisplayName") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("RoleId") + .HasColumnType("text"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/USEntryCoach.Server/Migrations/20250527123305_Initial.cs b/USEntryCoach.Server/Migrations/20250527123305_Initial.cs new file mode 100644 index 0000000..43ee6e4 --- /dev/null +++ b/USEntryCoach.Server/Migrations/20250527123305_Initial.cs @@ -0,0 +1,223 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace USEntryCoach.Server.Migrations +{ + /// + public partial class Initial : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "AspNetRoles", + columns: table => new + { + Id = table.Column(type: "text", nullable: false), + Name = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), + NormalizedName = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), + ConcurrencyStamp = table.Column(type: "text", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoles", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetUsers", + columns: table => new + { + Id = table.Column(type: "text", nullable: false), + UserName = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), + NormalizedUserName = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), + Email = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), + NormalizedEmail = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), + EmailConfirmed = table.Column(type: "boolean", nullable: false), + PasswordHash = table.Column(type: "text", nullable: true), + SecurityStamp = table.Column(type: "text", nullable: true), + ConcurrencyStamp = table.Column(type: "text", nullable: true), + PhoneNumber = table.Column(type: "text", nullable: true), + PhoneNumberConfirmed = table.Column(type: "boolean", nullable: false), + TwoFactorEnabled = table.Column(type: "boolean", nullable: false), + LockoutEnd = table.Column(type: "timestamp with time zone", nullable: true), + LockoutEnabled = table.Column(type: "boolean", nullable: false), + AccessFailedCount = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUsers", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetRoleClaims", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + RoleId = table.Column(type: "text", nullable: false), + ClaimType = table.Column(type: "text", nullable: true), + ClaimValue = table.Column(type: "text", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetRoleClaims_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserClaims", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + UserId = table.Column(type: "text", nullable: false), + ClaimType = table.Column(type: "text", nullable: true), + ClaimValue = table.Column(type: "text", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetUserClaims_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserLogins", + columns: table => new + { + LoginProvider = table.Column(type: "text", nullable: false), + ProviderKey = table.Column(type: "text", nullable: false), + ProviderDisplayName = table.Column(type: "text", nullable: true), + UserId = table.Column(type: "text", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey }); + table.ForeignKey( + name: "FK_AspNetUserLogins_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserRoles", + columns: table => new + { + UserId = table.Column(type: "text", nullable: false), + RoleId = table.Column(type: "text", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId }); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserTokens", + columns: table => new + { + UserId = table.Column(type: "text", nullable: false), + LoginProvider = table.Column(type: "text", nullable: false), + Name = table.Column(type: "text", nullable: false), + Value = table.Column(type: "text", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name }); + table.ForeignKey( + name: "FK_AspNetUserTokens_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_AspNetRoleClaims_RoleId", + table: "AspNetRoleClaims", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "RoleNameIndex", + table: "AspNetRoles", + column: "NormalizedName", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserClaims_UserId", + table: "AspNetUserClaims", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserLogins_UserId", + table: "AspNetUserLogins", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserRoles_RoleId", + table: "AspNetUserRoles", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "EmailIndex", + table: "AspNetUsers", + column: "NormalizedEmail"); + + migrationBuilder.CreateIndex( + name: "UserNameIndex", + table: "AspNetUsers", + column: "NormalizedUserName", + unique: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "AspNetRoleClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserLogins"); + + migrationBuilder.DropTable( + name: "AspNetUserRoles"); + + migrationBuilder.DropTable( + name: "AspNetUserTokens"); + + migrationBuilder.DropTable( + name: "AspNetRoles"); + + migrationBuilder.DropTable( + name: "AspNetUsers"); + } + } +} diff --git a/USEntryCoach.Server/Migrations/ApplicationDbContextModelSnapshot.cs b/USEntryCoach.Server/Migrations/ApplicationDbContextModelSnapshot.cs new file mode 100644 index 0000000..8a4a707 --- /dev/null +++ b/USEntryCoach.Server/Migrations/ApplicationDbContextModelSnapshot.cs @@ -0,0 +1,274 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using USEntryCoach.Server.Data; + +#nullable disable + +namespace USEntryCoach.Server.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + partial class ApplicationDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.5") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("AccessFailedCount") + .HasColumnType("integer"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("boolean"); + + b.Property("LockoutEnabled") + .HasColumnType("boolean"); + + b.Property("LockoutEnd") + .HasColumnType("timestamp with time zone"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PasswordHash") + .HasColumnType("text"); + + b.Property("PhoneNumber") + .HasColumnType("text"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("boolean"); + + b.Property("SecurityStamp") + .HasColumnType("text"); + + b.Property("TwoFactorEnabled") + .HasColumnType("boolean"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("ProviderKey") + .HasColumnType("text"); + + b.Property("ProviderDisplayName") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("RoleId") + .HasColumnType("text"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/USEntryCoach.Server/Program.cs b/USEntryCoach.Server/Program.cs index 806ad61..d36cc7a 100644 --- a/USEntryCoach.Server/Program.cs +++ b/USEntryCoach.Server/Program.cs @@ -1,8 +1,10 @@ using System.Security.Claims; +using Microsoft.AspNetCore.Identity; using System.Text; using System.Text.Json.Nodes; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Http.HttpResults; +using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; using Microsoft.IdentityModel.Tokens; using USEntryCoach.Server.Data; @@ -21,28 +23,28 @@ builder.Services.AddOptionsWithValidateOnStart().Bind(au ApiSettings? apiSettings = apiSettingsSection.Get(); AuthenticationSettings? authSettings = authSettingsSection.Get(); - -builder.Services.AddSingleton(); - -// Configure JWT token generation. -builder.Services.AddAuthentication(config => -{ - config.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; - config.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; -}).AddJwtBearer(config => -{ - // TODO: Only for debug! - config.RequireHttpsMetadata = false; - - config.SaveToken = true; - config.TokenValidationParameters = new TokenValidationParameters() - { - ValidateIssuerSigningKey = true, - IssuerSigningKey = new SymmetricSecurityKey(authSettings!.JwtGenerationSecretBytes), - ValidateIssuer = false, - ValidateAudience = false - }; -}); +// +// builder.Services.AddSingleton(); +// +// // Configure JWT token generation. +// builder.Services.AddAuthentication(config => +// { +// config.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; +// config.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; +// }).AddJwtBearer(config => +// { +// // TODO: Only for debug! +// config.RequireHttpsMetadata = false; +// +// config.SaveToken = true; +// config.TokenValidationParameters = new TokenValidationParameters() +// { +// ValidateIssuerSigningKey = true, +// IssuerSigningKey = new SymmetricSecurityKey(authSettings!.JwtGenerationSecretBytes), +// ValidateIssuer = false, +// ValidateAudience = false +// }; +// }); builder.Services.AddAuthorization(options => { @@ -54,17 +56,32 @@ builder.Services.AddAuthorization(options => }); }); -builder.Services.AddDbContext(options => +builder.Services.AddDbContext(options => options.UseNpgsql(builder.Configuration.GetConnectionString("Default"))); +builder.Services.AddAuthorization(); + +builder.Services.AddIdentityApiEndpoints() + .AddEntityFrameworkStores(); + +// +// builder.Services.AddDefaultIdentity(options => options.SignIn.RequireConfirmedAccount = true) +// .AddEntityFrameworkStores(); +// +// builder.Services.AddDbContext(options => +// options.UseNpgsql(builder.Configuration.GetConnectionString("Default"))); + var app = builder.Build(); -app.UseAuthentication(); -app.UseAuthorization(); +app.MapIdentityApi(); +// +// app.UseAuthentication(); +// app.UseAuthorization(); app.UseDefaultFiles(); app.MapStaticAssets(); + // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { @@ -78,45 +95,45 @@ HttpClient client = new(); client.DefaultRequestHeaders.Clear(); client.DefaultRequestHeaders.Add("Authorization", $"Bearer {apiSettings.OpenAiToken}"); -app.MapPost("/login", Login); +//app.MapPost("/login", Login); app.MapGet("/hello", () => "Hello World!").RequireAuthorization("admin_greetings"); - -async Task Login(LoginCredentials credentials, TokenService tokenService, HttpContext context) -{ - // TODO: Check with database - User? user = null; - - if (credentials is {Username:"developer", Password:"dev"}) - { - user = new User() - { - Username = credentials.Username, - Password = credentials.Password, - Id = Guid.CreateVersion7(), - Role = UserRole.Developer - }; - } - else if (credentials is {Username:"user", Password:"us"}) - { - user = new User() - { - Username = credentials.Username, - Password = credentials.Password, - Id = Guid.CreateVersion7(), - Role = UserRole.User - }; - } - - if (user == null) - { - return Results.Unauthorized(); - } - - var token = tokenService.GenerateToken(user); - - return Results.Ok(new { token }); -} +// +// async Task Login(LoginCredentials credentials, TokenService tokenService, HttpContext context) +// { +// // TODO: Check with database +// User? user = null; +// +// if (credentials is {Username:"developer", Password:"dev"}) +// { +// user = new User() +// { +// Username = credentials.Username, +// Password = credentials.Password, +// Id = Guid.CreateVersion7(), +// Role = UserRole.Developer +// }; +// } +// else if (credentials is {Username:"user", Password:"us"}) +// { +// user = new User() +// { +// Username = credentials.Username, +// Password = credentials.Password, +// Id = Guid.CreateVersion7(), +// Role = UserRole.User +// }; +// } +// +// if (user == null) +// { +// return Results.Unauthorized(); +// } +// +// var token = tokenService.GenerateToken(user); +// +// return Results.Ok(new { token }); +// } app.MapGet("/developer", (ClaimsPrincipal user) => { @@ -180,29 +197,4 @@ app.MapGet("/ephemeral_token", async () => app.MapFallbackToFile("/index.html"); -using (var scope = app.Services.CreateScope()) -{ - var services = scope.ServiceProvider; - - var context = services.GetRequiredService(); - context.Database.EnsureCreated(); - - Blog b = new() - { - Url = "BLa" - }; - - Post p = new() - { - Blog = b, - Content = "sd zgsödiog söffdgkjasödfl kjasdfökljasdfölasddj föadj kfö", - Title = "Hallo Welt" - }; - - b.Posts.Add(p); - - context.Blogs.Add(b); - context.SaveChanges(); -} - app.Run(); diff --git a/USEntryCoach.Server/USEntryCoach.Server.csproj b/USEntryCoach.Server/USEntryCoach.Server.csproj index 64488f1..4e0ab57 100644 --- a/USEntryCoach.Server/USEntryCoach.Server.csproj +++ b/USEntryCoach.Server/USEntryCoach.Server.csproj @@ -14,6 +14,7 @@ + 9.*-* @@ -32,4 +33,8 @@ + + + + diff --git a/usentrycoach.client/package-lock.json b/usentrycoach.client/package-lock.json index 6961625..e07e7c8 100644 --- a/usentrycoach.client/package-lock.json +++ b/usentrycoach.client/package-lock.json @@ -8,6 +8,7 @@ "name": "usentrycoach.client", "version": "0.0.0", "dependencies": { + "classnames": "^2.5.1", "react": "^19.1.0", "react-dom": "^19.1.0", "zod": "^3.25.23" @@ -1781,6 +1782,11 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/classnames": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==" + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", diff --git a/usentrycoach.client/package.json b/usentrycoach.client/package.json index 4108711..b60dd4a 100644 --- a/usentrycoach.client/package.json +++ b/usentrycoach.client/package.json @@ -10,6 +10,7 @@ "preview": "vite preview" }, "dependencies": { + "classnames": "^2.5.1", "react": "^19.1.0", "react-dom": "^19.1.0", "zod": "^3.25.23" diff --git a/usentrycoach.client/src/App.tsx b/usentrycoach.client/src/App.tsx index 505a3db..6af2cb4 100644 --- a/usentrycoach.client/src/App.tsx +++ b/usentrycoach.client/src/App.tsx @@ -9,7 +9,7 @@ export default function App() if (!token) { - return + return ; } return diff --git a/usentrycoach.client/src/Components/Login.css b/usentrycoach.client/src/Components/Login.css new file mode 100644 index 0000000..44be949 --- /dev/null +++ b/usentrycoach.client/src/Components/Login.css @@ -0,0 +1,84 @@ +/*#login-container*/ +/*{*/ +/* top: 0;*/ +/* left: 0;*/ +/* position: absolute;*/ +/* width: 100vw;*/ +/* height: 100vh;*/ +/* backdrop-filter: blur(5px);*/ +/*}*/ + +form +{ + /*position: absolute;*/ + /*left: 50vw;*/ + /*top: 50vh;*/ + /*transform: translate(-50%, -50%);*/ + width: 120%; + height: auto; + display: flex; + gap: 1rem; + flex-direction: column; + align-items: center; + padding: 0 25px 20px 25px; + border-radius: 10px; + border-color: #282828; + border-style: solid; + border-width: 2px; + background: #323232; + filter: drop-shadow(0px 0px 8px rgb(146, 97, 251)); +} + +.input-field +{ + width: 100%; +} + +.register-row +{ + display: flex; + white-space-collapse: preserve; +} + +.register-row a +{ + cursor: pointer; +} + +form input +{ + padding: 0.4rem; + width: 100%; + box-sizing: border-box; +} + +label +{ + width: 100%; +} + +label p +{ + justify-self: start; + display: block; + margin: 0; +} + +#button-login +{ + width: 100%; + padding: 0.5em; +} + +#login-error +{ + background: #bf830e; + border-radius: 5px; + width: 0; /* Collapses element so it doesn't affect parent width */ + min-width: 100%; /* Expands element to parent width */ +} + +#login-error p { + word-wrap: break-word; + overflow-wrap: break-word; +} diff --git a/usentrycoach.client/src/Components/Login.tsx b/usentrycoach.client/src/Components/Login.tsx index 2e25275..4458264 100644 --- a/usentrycoach.client/src/Components/Login.tsx +++ b/usentrycoach.client/src/Components/Login.tsx @@ -1,33 +1,62 @@ import { useState, type FormEvent } from 'react'; import { z } from 'zod/v4'; +import './Login.css'; +import classNames from "classnames"; export default function Login({ setToken } : {setToken: (token: string) => void}) { - const [username, setUsername] = useState(""); + + const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); + const [loginError, setLoginError] = useState(null); + const [waiting, setWaiting] = useState(false); async function doLogin(event: FormEvent) { + setWaiting(true); + event.preventDefault(); - const token = await loginUser({username, password}); + try + { + const token = await loginUser({email, password}); + setToken(token); + } + catch (e) + { + console.error(e); + setLoginError("Ein fehler ist aufgetreten."); + } - setToken(token); + setWaiting(false); } return ( -
-
void doLogin(e)} method="action"> +
+ void doLogin(e)} method="action" inert={waiting}> +

+ Login +

+ { + loginError ? ( +
+

{loginError}

+
) : <> + } - - + +
+ New? Register here! +
); @@ -37,10 +66,39 @@ const LoginResponse = z.object({ token: z.string() }); -async function loginUser(credentials : {username: string, password: string}): Promise +async function loginUser(credentials : {email: string, password: string}): Promise +{ + const queryParams = new URLSearchParams({ + useCookies: "true" + }); + + const url = `login?${queryParams.toString()}`; + + // Get a session token for OpenAI Realtime API + const response = await fetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(credentials) + }); + + if (!response.ok) + { + throw new Error(response.statusText); + // 500: internal server error + } + + //const responseJson:unknown = await response.json(); + //const parsedToken = LoginResponse.parse(responseJson); + + return "abc"; //parsedToken.token; +} + +async function registerUser(credentials : {email: string, password: string}): Promise { // Get a session token for OpenAI Realtime API - const response = await fetch('login', { + const response = await fetch('register', { method: 'POST', headers: { 'Content-Type': 'application/json' diff --git a/usentrycoach.client/src/index.css b/usentrycoach.client/src/index.css index 08a3ac9..c9f960f 100644 --- a/usentrycoach.client/src/index.css +++ b/usentrycoach.client/src/index.css @@ -43,12 +43,14 @@ button { font-weight: 500; font-family: inherit; background-color: #1a1a1a; - cursor: pointer; transition: border-color 0.25s; } -button:hover { + +button:hover:not([disabled]) { border-color: #646cff; + cursor: pointer; } + button:focus, button:focus-visible { outline: 4px auto -webkit-focus-ring-color; @@ -66,3 +68,12 @@ button:focus-visible { background-color: #f9f9f9; } } + +.button-submit { + background-color: #208ddb; +} + +.button-submit:hover:not([disabled]) { + background-color: #3c9be0; + border-color: #c7e3f6; +} diff --git a/usentrycoach.client/tsconfig.app.json b/usentrycoach.client/tsconfig.app.json index c9ccbd4..c22d8e3 100644 --- a/usentrycoach.client/tsconfig.app.json +++ b/usentrycoach.client/tsconfig.app.json @@ -17,8 +17,8 @@ /* Linting */ "strict": true, - "noUnusedLocals": true, - "noUnusedParameters": true, + //"noUnusedLocals": true, + //"noUnusedParameters": true, "erasableSyntaxOnly": true, "noFallthroughCasesInSwitch": true, "noUncheckedSideEffectImports": true diff --git a/usentrycoach.client/vite.config.ts b/usentrycoach.client/vite.config.ts index 3d90eb2..c45cf69 100644 --- a/usentrycoach.client/vite.config.ts +++ b/usentrycoach.client/vite.config.ts @@ -44,7 +44,7 @@ const target = env.ASPNETCORE_HTTPS_PORT ? `https://localhost:${env.ASPNETCORE_H env.ASPNETCORE_URLS ? env.ASPNETCORE_URLS.split(';')[0] : 'https://localhost:7085'; // Define a list of all existing backend routes. -const backendRoutes = ['/login', '/ephemeral_token']; +const backendRoutes = ['/login', '/register', '/ephemeral_token']; // For development, we have a node.js server running that delivers our frontend. // We have to configure proxies for the backend calls, so that node.js will forward them to the backend.