diff --git a/backend/Data/AppDbContext.cs b/backend/Data/AppDbContext.cs index a73683e..85d2a1c 100644 --- a/backend/Data/AppDbContext.cs +++ b/backend/Data/AppDbContext.cs @@ -18,7 +18,11 @@ public class AppDbContext(DbContextOptions options) { base.OnModelCreating(b); - b.Entity().HasIndex(d => d.OwnerUserId); + b.Entity() + .HasOne() + .WithMany() + .HasForeignKey(d => d.OwnerUserId) + .OnDelete(DeleteBehavior.Cascade); b.Entity().HasIndex(d => d.TokenHash).IsUnique(); b.Entity() .HasOne(d => d.Config) @@ -27,5 +31,6 @@ public class AppDbContext(DbContextOptions options) .OnDelete(DeleteBehavior.Cascade); b.Entity().HasKey(c => c.DeviceId); b.Entity().HasKey(s => s.Id); + b.Entity().Property(s => s.Id).ValueGeneratedNever(); } } diff --git a/backend/Migrations/20260611184536_AddDeviceOwnerFkAndPinSettingsId.Designer.cs b/backend/Migrations/20260611184536_AddDeviceOwnerFkAndPinSettingsId.Designer.cs new file mode 100644 index 0000000..b37df77 --- /dev/null +++ b/backend/Migrations/20260611184536_AddDeviceOwnerFkAndPinSettingsId.Designer.cs @@ -0,0 +1,380 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using backend.Data; + +#nullable disable + +namespace backend.Migrations +{ + [DbContext(typeof(AppDbContext))] + [Migration("20260611184536_AddDeviceOwnerFkAndPinSettingsId")] + partial class AddDeviceOwnerFkAndPinSettingsId + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "9.0.17"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + 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"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("UserId") + .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") + .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("backend.Auth.ApplicationUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AccessFailedCount") + .HasColumnType("INTEGER"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EmailConfirmed") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnabled") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnd") + .HasColumnType("TEXT"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("PasswordHash") + .HasColumnType("TEXT"); + + b.Property("PhoneNumber") + .HasColumnType("TEXT"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("INTEGER"); + + b.Property("SecurityStamp") + .HasColumnType("TEXT"); + + b.Property("TwoFactorEnabled") + .HasColumnType("INTEGER"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("backend.Devices.Device", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("IsRevoked") + .HasColumnType("INTEGER"); + + b.Property("LastSeenAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("OwnerUserId") + .HasColumnType("TEXT"); + + b.Property("PairedAt") + .HasColumnType("TEXT"); + + b.Property("TokenHash") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OwnerUserId"); + + b.HasIndex("TokenHash") + .IsUnique(); + + b.ToTable("Devices"); + }); + + modelBuilder.Entity("backend.Devices.DeviceConfig", b => + { + b.Property("DeviceId") + .HasColumnType("TEXT"); + + b.Property("EnabledToolsJson") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("IdleTimeoutSeconds") + .HasColumnType("INTEGER"); + + b.Property("Model") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("SystemPrompt") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Voice") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("DeviceId"); + + b.ToTable("DeviceConfigs"); + }); + + modelBuilder.Entity("backend.Settings.SystemSettings", b => + { + b.Property("Id") + .HasColumnType("INTEGER"); + + b.Property("DefaultIdleTimeoutSeconds") + .HasColumnType("INTEGER"); + + b.Property("DefaultModel") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("DefaultSystemPrompt") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("DefaultVoice") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("SystemSettings"); + }); + + 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("backend.Auth.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("backend.Auth.ApplicationUser", 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("backend.Auth.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("backend.Auth.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("backend.Devices.Device", b => + { + b.HasOne("backend.Auth.ApplicationUser", null) + .WithMany() + .HasForeignKey("OwnerUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("backend.Devices.DeviceConfig", b => + { + b.HasOne("backend.Devices.Device", null) + .WithOne("Config") + .HasForeignKey("backend.Devices.DeviceConfig", "DeviceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("backend.Devices.Device", b => + { + b.Navigation("Config"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/backend/Migrations/20260611184536_AddDeviceOwnerFkAndPinSettingsId.cs b/backend/Migrations/20260611184536_AddDeviceOwnerFkAndPinSettingsId.cs new file mode 100644 index 0000000..28f570f --- /dev/null +++ b/backend/Migrations/20260611184536_AddDeviceOwnerFkAndPinSettingsId.cs @@ -0,0 +1,48 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace backend.Migrations +{ + /// + public partial class AddDeviceOwnerFkAndPinSettingsId : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "Id", + table: "SystemSettings", + type: "INTEGER", + nullable: false, + oldClrType: typeof(int), + oldType: "INTEGER") + .OldAnnotation("Sqlite:Autoincrement", true); + + migrationBuilder.AddForeignKey( + name: "FK_Devices_AspNetUsers_OwnerUserId", + table: "Devices", + column: "OwnerUserId", + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_Devices_AspNetUsers_OwnerUserId", + table: "Devices"); + + migrationBuilder.AlterColumn( + name: "Id", + table: "SystemSettings", + type: "INTEGER", + nullable: false, + oldClrType: typeof(int), + oldType: "INTEGER") + .Annotation("Sqlite:Autoincrement", true); + } + } +} diff --git a/backend/Migrations/AppDbContextModelSnapshot.cs b/backend/Migrations/AppDbContextModelSnapshot.cs index 6ce3e2e..e3287e6 100644 --- a/backend/Migrations/AppDbContextModelSnapshot.cs +++ b/backend/Migrations/AppDbContextModelSnapshot.cs @@ -276,7 +276,6 @@ namespace backend.Migrations modelBuilder.Entity("backend.Settings.SystemSettings", b => { b.Property("Id") - .ValueGeneratedOnAdd() .HasColumnType("INTEGER"); b.Property("DefaultIdleTimeoutSeconds") @@ -350,6 +349,15 @@ namespace backend.Migrations .IsRequired(); }); + modelBuilder.Entity("backend.Devices.Device", b => + { + b.HasOne("backend.Auth.ApplicationUser", null) + .WithMany() + .HasForeignKey("OwnerUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + modelBuilder.Entity("backend.Devices.DeviceConfig", b => { b.HasOne("backend.Devices.Device", null) diff --git a/backend/Program.cs b/backend/Program.cs index bf2df6b..f4c0978 100644 --- a/backend/Program.cs +++ b/backend/Program.cs @@ -19,9 +19,9 @@ using (var scope = app.Services.CreateScope()) db.Database.Migrate(); await IdentitySetup.SeedRolesAsync(sp); - if (!db.SystemSettings.Any()) + if (db.SystemSettings.Find(1) is null) { - db.SystemSettings.Add(new backend.Settings.SystemSettings()); + db.SystemSettings.Add(new backend.Settings.SystemSettings { Id = 1 }); await db.SaveChangesAsync(); } }