diff --git a/Controllers/AuthController.cs b/Controllers/AuthController.cs index da263d3..e855953 100644 --- a/Controllers/AuthController.cs +++ b/Controllers/AuthController.cs @@ -1,6 +1,7 @@ using Microsoft.AspNetCore.Mvc; using ValuationBackend.Models; using ValuationBackend.Services; +using System.Threading.Tasks; namespace ValuationBackend.Controllers { @@ -9,10 +10,12 @@ namespace ValuationBackend.Controllers public class AuthController : ControllerBase { private readonly IAuthService _authService; + private readonly PasswordResetService _passwordResetService; - public AuthController(IAuthService authService) + public AuthController(IAuthService authService, PasswordResetService passwordResetService) { _authService = authService; + _passwordResetService = passwordResetService; } [HttpPost("login")] @@ -46,14 +49,34 @@ public async Task Logout([FromBody] LogoutRequest request) return Ok(new { msg = "success" }); } - [HttpPost("forgot-password")] - public async Task ForgotPassword([FromBody] ForgotPasswordRequest request) + // --- New Password Reset Endpoints --- + + [HttpPost("request-password-reset")] + public async Task RequestPasswordReset([FromBody] EmailDto dto) { - var result = await _authService.ForgotPasswordAsync(request.Username); - if (!result) - return NotFound(new { msg = "User not found" }); + await _passwordResetService.RequestPasswordResetAsync(dto.Email); + return Ok(new { message = "If the email exists, an OTP has been sent." }); + } - return Ok(new { msg = "success" }); + [HttpPost("verify-otp")] + public async Task VerifyOtp([FromBody] OtpDto dto) + { + var valid = await _passwordResetService.VerifyOtpAsync(dto.Email, dto.Otp); + if (!valid) return BadRequest(new { message = "Invalid or expired OTP." }); + return Ok(new { message = "OTP verified." }); } + + [HttpPost("reset-password")] + public async Task ResetPassword([FromBody] ResetPasswordDto dto) + { + var success = await _passwordResetService.ResetPasswordAsync(dto.Email, dto.Otp, dto.NewPassword); + if (!success) return BadRequest(new { message = "Invalid OTP or email." }); + return Ok(new { message = "Password reset successful." }); + } + + // --- DTOs for password reset --- + public class EmailDto { public string Email { get; set; } } + public class OtpDto { public string Email { get; set; } public string Otp { get; set; } } + public class ResetPasswordDto { public string Email { get; set; } public string Otp { get; set; } public string NewPassword { get; set; } } } } diff --git a/Controllers/iteration2/DecisionFieldController.cs b/Controllers/iteration2/DecisionFieldController.cs new file mode 100644 index 0000000..acef1d2 --- /dev/null +++ b/Controllers/iteration2/DecisionFieldController.cs @@ -0,0 +1,36 @@ +using Microsoft.AspNetCore.Mvc; +using System.Collections.Generic; +using System.Threading.Tasks; +using ValuationBackend.Models.DTOs; +using ValuationBackend.Services; +using ValuationBackend.Data; +using ValuationBackend.Models; +using Microsoft.EntityFrameworkCore; + + +[ApiController] +[Route("api/decision-fields")] +public class DecisionFieldController : ControllerBase +{ + private readonly ValuationContext _context; + + public DecisionFieldController(ValuationContext context) + { + _context = context; + } + + [HttpPost] + public async Task CreateField([FromBody] DecisionField field) + { + _context.DecisionFields.Add(field); + await _context.SaveChangesAsync(); + return Ok(field); + } + + [HttpGet] + public async Task GetAllFields() + { + var fields = await _context.DecisionFields.ToListAsync(); + return Ok(fields); + } +} diff --git a/Controllers/iteration2/RatingFileController.cs b/Controllers/iteration2/RatingFileController.cs new file mode 100644 index 0000000..0123ea2 --- /dev/null +++ b/Controllers/iteration2/RatingFileController.cs @@ -0,0 +1,34 @@ +using Microsoft.AspNetCore.Mvc; + +[ApiController] +[Route("api/{requestType}/ratingfile")] +public class RatingFileController : ControllerBase +{ + // 1. Load all street names + [HttpGet("streets")] + public IActionResult GetStreets(string requestType) + { + var streets = new[] + { + new { id = "S1", name = "Main Street" }, + new { id = "S2", name = "Highway Road" }, + new { id = "S3", name = "Park Avenue" } + }; + + return Ok(new { streets }); + } + + // 2. Load obsolete numbers for selected street + [HttpGet("streets/{streetId}/obsolete-numbers")] + public IActionResult GetObsoleteNumbers(string requestType, string streetId) + { + var obsoleteNumbers = new[] + { + new { id = "O1", number = "101" }, + new { id = "O2", number = "102" }, + new { id = "O3", number = "103" } + }; + + return Ok(new { obsoleteNumbers }); + } +} diff --git a/Data/AppDbContext.cs b/Data/AppDbContext.cs index 2449d1a..766af6c 100644 --- a/Data/AppDbContext.cs +++ b/Data/AppDbContext.cs @@ -10,7 +10,7 @@ public AppDbContext(DbContextOptions options) : base(options) { } public DbSet RatingRequests { get; set; } - + public DbSet LandMiscellaneousMasterFiles { get; set; } public DbSet Reconciliations { get; set; } @@ -62,6 +62,10 @@ public AppDbContext(DbContextOptions options) public DbSet PropertyCategories { get; set; } + + public DbSet DecisionFields { get; set; } + public DbSet PasswordResets { get; set; } + protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); diff --git a/Data/DBInitializer.cs b/Data/DBInitializer.cs index 5716f1d..1b569e0 100644 --- a/Data/DBInitializer.cs +++ b/Data/DBInitializer.cs @@ -1913,5 +1913,7 @@ private static void InitializeReconciliations(AppDbContext context) context.SaveChanges(); Console.WriteLine("Reconciliations seeded."); } + + } } diff --git a/Data/ValuationContext.cs b/Data/ValuationContext.cs new file mode 100644 index 0000000..5554fb4 --- /dev/null +++ b/Data/ValuationContext.cs @@ -0,0 +1,13 @@ +using Microsoft.EntityFrameworkCore; +using ValuationBackend.Models; + +namespace ValuationBackend.Data +{ + public class ValuationContext : DbContext + { + public ValuationContext(DbContextOptions options) : base(options) { } + + public DbSet DecisionFields { get; set; } + // ...add other DbSets as needed... + } +} diff --git a/Migrations/20250428193956_InitialCreate.cs b/Migrations/20250428193956_InitialCreate.cs index 5ce4d63..8d21868 100644 --- a/Migrations/20250428193956_InitialCreate.cs +++ b/Migrations/20250428193956_InitialCreate.cs @@ -27,6 +27,22 @@ protected override void Up(MigrationBuilder migrationBuilder) { table.PrimaryKey("PK_RatingRequests", x => x.Id); }); + + migrationBuilder.CreateTable( + name: "PasswordResets", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + Email = table.Column(nullable: false), + Otp = table.Column(nullable: false), + ExpiresAt = table.Column(nullable: false), + Used = table.Column(nullable: false, defaultValue: false) + }, + constraints: table => + { + table.PrimaryKey("PK_PasswordResets", x => x.Id); + }); } /// diff --git a/Migrations/20250721174110_AddPasswordResetTable.Designer.cs b/Migrations/20250721174110_AddPasswordResetTable.Designer.cs new file mode 100644 index 0000000..46b5169 --- /dev/null +++ b/Migrations/20250721174110_AddPasswordResetTable.Designer.cs @@ -0,0 +1,1919 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using ValuationBackend.Data; + +#nullable disable + +namespace ValuationBackend.Migrations +{ + [DbContext(typeof(AppDbContext))] + [Migration("20250721174110_AddPasswordResetTable")] + partial class AddPasswordResetTable + { + /// + 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("LandAquisitionMasterFile", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("MasterFileNo") + .HasColumnType("integer"); + + b.Property("MasterFilesRefNo") + .IsRequired() + .HasColumnType("text"); + + b.Property("PlanNo") + .IsRequired() + .HasColumnType("text"); + + b.Property("PlanType") + .IsRequired() + .HasColumnType("text"); + + b.Property("RequestingAuthorityReferenceNo") + .IsRequired() + .HasColumnType("text"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("LandAquisitionMasterFiles"); + }); + + modelBuilder.Entity("ValuationBackend.Models.Asset", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AssetNo") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .HasColumnType("integer"); + + b.Property("IsRatingCard") + .HasColumnType("boolean"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("RdSt") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("RequestId") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Ward") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("Id"); + + b.HasIndex("RequestId"); + + b.ToTable("Assets"); + }); + + modelBuilder.Entity("ValuationBackend.Models.AssetDivision", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Area") + .HasColumnType("numeric"); + + b.Property("AssetId") + .HasColumnType("integer"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("LandType") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("NewAssetNo") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("AssetId"); + + b.ToTable("AssetDivisions"); + }); + + modelBuilder.Entity("ValuationBackend.Models.AssetNumberChange", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ChangedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("DateOfChange") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("FieldSize") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("FieldType") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("NewAssetNo") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("OldAssetNo") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Reason") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.HasKey("Id"); + + b.ToTable("AssetNumberChanges"); + }); + + modelBuilder.Entity("ValuationBackend.Models.BuildingRatesLA", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AssessmentNumber") + .HasColumnType("text"); + + b.Property("ConstructedBy") + .HasColumnType("text"); + + b.Property("Cost") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DescriptionOfProperty") + .HasColumnType("text"); + + b.Property("FloorAreaSQFT") + .HasColumnType("text"); + + b.Property("LocationLatitude") + .HasColumnType("text"); + + b.Property("LocationLongitude") + .HasColumnType("text"); + + b.Property("MasterFileId") + .IsRequired() + .HasColumnType("text"); + + b.Property("Owner") + .HasColumnType("text"); + + b.Property("RatePerSQFT") + .HasColumnType("text"); + + b.Property("Remarks") + .HasColumnType("text"); + + b.Property("ReportId") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("YearOfConstruction") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ReportId"); + + b.ToTable("BuildingRatesLA"); + }); + + modelBuilder.Entity("ValuationBackend.Models.ConditionReport", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AccessCategory") + .IsRequired() + .HasColumnType("text"); + + b.Property("AccessCategoryDescription") + .IsRequired() + .HasColumnType("text"); + + b.Property("AcquiredExtent") + .IsRequired() + .HasColumnType("text"); + + b.Property("AcquiringOfficerSignature") + .IsRequired() + .HasColumnType("text"); + + b.Property("AcquisitionName") + .IsRequired() + .HasColumnType("text"); + + b.Property("AssessmentNumber") + .IsRequired() + .HasColumnType("text"); + + b.Property("AtLotNumber") + .IsRequired() + .HasColumnType("text"); + + b.Property("AtPlanNumber") + .IsRequired() + .HasColumnType("text"); + + b.Property("BoundaryBottom") + .IsRequired() + .HasColumnType("text"); + + b.Property("BoundaryEast") + .IsRequired() + .HasColumnType("text"); + + b.Property("BoundaryNorth") + .IsRequired() + .HasColumnType("text"); + + b.Property("BoundarySouth") + .IsRequired() + .HasColumnType("text"); + + b.Property("BoundaryWest") + .IsRequired() + .HasColumnType("text"); + + b.Property("BuildingDescription") + .IsRequired() + .HasColumnType("text"); + + b.Property("BuildingInfo") + .IsRequired() + .HasColumnType("text"); + + b.Property("ChiefValuerRepresentativeSignature") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DateOfSection3BA") + .IsRequired() + .HasColumnType("text"); + + b.Property("DatePrepared") + .IsRequired() + .HasColumnType("text"); + + b.Property("DepthOfLand") + .IsRequired() + .HasColumnType("text"); + + b.Property("DescriptionOfLand") + .IsRequired() + .HasColumnType("text"); + + b.Property("DetailsOfBusiness") + .IsRequired() + .HasColumnType("text"); + + b.Property("Frontage") + .IsRequired() + .HasColumnType("text"); + + b.Property("GramasewakaSignature") + .IsRequired() + .HasColumnType("text"); + + b.Property("LandUseDescription") + .IsRequired() + .HasColumnType("text"); + + b.Property("LandUseType") + .IsRequired() + .HasColumnType("text"); + + b.Property("LevelWithAccess") + .IsRequired() + .HasColumnType("text"); + + b.Property("MasterFileId") + .IsRequired() + .HasColumnType("text"); + + b.Property("NameOfTheLand") + .IsRequired() + .HasColumnType("text"); + + b.Property("NameOfTheVillage") + .IsRequired() + .HasColumnType("text"); + + b.Property("OtherConstructionsDescription") + .IsRequired() + .HasColumnType("text"); + + b.Property("OtherConstructionsInfo") + .IsRequired() + .HasColumnType("text"); + + b.Property("PlantationDetails") + .IsRequired() + .HasColumnType("text"); + + b.Property("PpCadLotNumber") + .IsRequired() + .HasColumnType("text"); + + b.Property("PpCadNumber") + .IsRequired() + .HasColumnType("text"); + + b.Property("ReportId") + .HasColumnType("integer"); + + b.Property("RoadName") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ReportId"); + + b.ToTable("ConditionReports"); + }); + + modelBuilder.Entity("ValuationBackend.Models.DomesticRatingCard", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Access") + .HasColumnType("text"); + + b.Property("Age") + .HasColumnType("integer"); + + b.Property("AssetId") + .HasColumnType("integer"); + + b.Property("Condition") + .HasColumnType("text"); + + b.Property("Conveniences") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text"); + + b.Property("Floor") + .HasColumnType("text"); + + b.Property("NewNumber") + .IsRequired() + .HasColumnType("text"); + + b.Property("Notes") + .HasColumnType("text"); + + b.Property("Occupier") + .HasColumnType("text"); + + b.Property("Owner") + .IsRequired() + .HasColumnType("text"); + + b.Property("ParkingSpace") + .HasColumnType("text"); + + b.Property("Plantations") + .HasColumnType("text"); + + b.Property("PropertySubCategory") + .HasColumnType("text"); + + b.Property("PropertyType") + .HasColumnType("text"); + + b.Property("RentPM") + .HasColumnType("numeric"); + + b.Property("RoadName") + .HasColumnType("text"); + + b.Property("SelectWalls") + .HasColumnType("text"); + + b.Property("SuggestedRate") + .HasColumnType("numeric"); + + b.Property("Terms") + .HasColumnType("text"); + + b.Property("TsBop") + .HasColumnType("text"); + + b.Property("WardNumber") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("AssetId"); + + b.ToTable("DomesticRatingCards"); + }); + + modelBuilder.Entity("ValuationBackend.Models.ImageData", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ImageBase64") + .IsRequired() + .HasColumnType("text"); + + b.Property("ReportId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.ToTable("ImageData"); + }); + + modelBuilder.Entity("ValuationBackend.Models.InspectionBuilding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AgeYears") + .HasColumnType("text"); + + b.Property("BathroomToilet") + .HasColumnType("text"); + + b.Property("BathroomToiletDoorsFittings") + .HasColumnType("text"); + + b.Property("BuildingCategory") + .HasColumnType("text"); + + b.Property("BuildingClass") + .HasColumnType("text"); + + b.Property("BuildingConditions") + .HasColumnType("text"); + + b.Property("BuildingId") + .HasColumnType("text"); + + b.Property("BuildingName") + .HasColumnType("text"); + + b.Property("Ceiling") + .HasColumnType("text"); + + b.Property("Condition") + .HasColumnType("text"); + + b.Property("Conveniences") + .HasColumnType("text"); + + b.Property("Design") + .HasColumnType("text"); + + b.Property("DetailOfBuilding") + .HasColumnType("text"); + + b.Property("Door") + .HasColumnType("text"); + + b.Property("ExpectedLifePeriodYears") + .HasColumnType("text"); + + b.Property("FloorFinisher") + .HasColumnType("text"); + + b.Property("FloorStructure") + .HasColumnType("text"); + + b.Property("FoundationStructure") + .HasColumnType("text"); + + b.Property("HandRail") + .HasColumnType("text"); + + b.Property("InspectionReportId") + .HasColumnType("integer"); + + b.Property("NatureOfConstruction") + .HasColumnType("text"); + + b.Property("NoOfFloorsAboveGround") + .HasColumnType("text") + .HasColumnName("NoOfFloorsAboveGround"); + + b.Property("NoOfFloorsBelowGround") + .HasColumnType("text") + .HasColumnName("NoOfFloorsBelowGround"); + + b.Property("OtherDoors") + .HasColumnType("text"); + + b.Property("PantryCupboard") + .HasColumnType("text"); + + b.Property("ParkingSpace") + .HasColumnType("text"); + + b.Property("RoofFinisher") + .HasColumnType("text"); + + b.Property("RoofFrame") + .HasColumnType("text"); + + b.Property("RoofMaterial") + .HasColumnType("text"); + + b.Property("Services") + .HasColumnType("text"); + + b.Property("Structure") + .HasColumnType("text"); + + b.Property("WallFinisher") + .HasColumnType("text"); + + b.Property("WallStructure") + .HasColumnType("text"); + + b.Property("Window") + .HasColumnType("text"); + + b.Property("WindowProtection") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("InspectionReportId"); + + b.ToTable("InspectionBuildings"); + }); + + modelBuilder.Entity("ValuationBackend.Models.InspectionReport", b => + { + b.Property("InspectionReportId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("InspectionReportId")); + + b.Property("DetailsOfAssestsInventoryItems") + .HasColumnType("text") + .HasColumnName("DetailsOfAssestsInventoryItems"); + + b.Property("DetailsOfBusiness") + .HasColumnType("text"); + + b.Property("District") + .HasColumnType("text"); + + b.Property("DsDivision") + .HasColumnType("text"); + + b.Property("GnDivision") + .HasColumnType("text"); + + b.Property("InspectionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("MasterFileId") + .IsRequired() + .HasColumnType("text"); + + b.Property("MasterFileRefNo") + .IsRequired() + .HasColumnType("text"); + + b.Property("OtherConstructionDetails") + .HasColumnType("text"); + + b.Property("OtherInformation") + .HasColumnType("text"); + + b.Property("Province") + .HasColumnType("text"); + + b.Property("Remark") + .HasColumnType("text"); + + b.Property("ReportId") + .HasColumnType("integer"); + + b.Property("Village") + .HasColumnType("text"); + + b.HasKey("InspectionReportId"); + + b.HasIndex("ReportId"); + + b.ToTable("InspectionReports"); + }); + + modelBuilder.Entity("ValuationBackend.Models.LMBuildingRates", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AssessmentNumber") + .HasColumnType("text"); + + b.Property("ConstructedBy") + .HasColumnType("text"); + + b.Property("Cost") + .HasColumnType("text"); + + b.Property("DescriptionOfProperty") + .HasColumnType("text"); + + b.Property("FloorArea") + .HasColumnType("text"); + + b.Property("LocationLatitude") + .HasColumnType("text"); + + b.Property("LocationLongitude") + .HasColumnType("text"); + + b.Property("MasterFileRefNo") + .IsRequired() + .HasColumnType("text"); + + b.Property("Owner") + .HasColumnType("text"); + + b.Property("RatePerSQFT") + .HasColumnType("text"); + + b.Property("Remarks") + .HasColumnType("text"); + + b.Property("ReportId") + .HasColumnType("integer"); + + b.Property("YearOfConstruction") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ReportId"); + + b.ToTable("LMBuildingRates"); + }); + + modelBuilder.Entity("ValuationBackend.Models.LMPastValuation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("DateOfValuation") + .HasColumnType("text"); + + b.Property("Extent") + .HasColumnType("text"); + + b.Property("FileNo_GnDivision") + .HasColumnType("text"); + + b.Property("LocationLatitude") + .HasColumnType("text"); + + b.Property("LocationLongitude") + .HasColumnType("text"); + + b.Property("MasterFileRefNo") + .IsRequired() + .HasColumnType("text"); + + b.Property("PlanOfParticulars") + .HasColumnType("text"); + + b.Property("PurposeOfValuation") + .HasColumnType("text"); + + b.Property("Rate") + .HasColumnType("text"); + + b.Property("RateType") + .HasColumnType("text"); + + b.Property("Remarks") + .HasColumnType("text"); + + b.Property("ReportId") + .HasColumnType("integer"); + + b.Property("Situation") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ReportId"); + + b.ToTable("LMPastValuations"); + }); + + modelBuilder.Entity("ValuationBackend.Models.LMRentalEvidence", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AssessmentNo") + .HasColumnType("text"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("FloorRate") + .HasColumnType("text"); + + b.Property("HeadOfTerms") + .HasColumnType("text"); + + b.Property("LocationLatitude") + .HasColumnType("text"); + + b.Property("LocationLongitude") + .HasColumnType("text"); + + b.Property("MasterFileRefNo") + .IsRequired() + .HasColumnType("text"); + + b.Property("Occupier") + .HasColumnType("text"); + + b.Property("Owner") + .HasColumnType("text"); + + b.Property("RatePer") + .HasColumnType("text"); + + b.Property("RatePerMonth") + .HasColumnType("text"); + + b.Property("Remarks") + .HasColumnType("text"); + + b.Property("ReportId") + .HasColumnType("integer"); + + b.Property("Situation") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ReportId"); + + b.ToTable("LMRentalEvidences"); + }); + + modelBuilder.Entity("ValuationBackend.Models.LMSalesEvidence", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AssetNumber") + .HasColumnType("text"); + + b.Property("Consideration") + .HasColumnType("text"); + + b.Property("DeedAttestedNumber") + .HasColumnType("text"); + + b.Property("DeedNumber") + .HasColumnType("text"); + + b.Property("DescriptionOfProperty") + .HasColumnType("text"); + + b.Property("Extent") + .HasColumnType("text"); + + b.Property("LandRegistryReferences") + .HasColumnType("text"); + + b.Property("LocationLatitude") + .HasColumnType("text"); + + b.Property("LocationLongitude") + .HasColumnType("text"); + + b.Property("LotNumber") + .HasColumnType("text"); + + b.Property("MasterFileRefNo") + .IsRequired() + .HasColumnType("text"); + + b.Property("NotaryName") + .HasColumnType("text"); + + b.Property("PlanDate") + .HasColumnType("text"); + + b.Property("PlanNumber") + .HasColumnType("text"); + + b.Property("Rate") + .HasColumnType("text"); + + b.Property("RateType") + .HasColumnType("text"); + + b.Property("Remarks") + .HasColumnType("text"); + + b.Property("ReportId") + .HasColumnType("integer"); + + b.Property("Road") + .HasColumnType("text"); + + b.Property("Situation") + .HasColumnType("text"); + + b.Property("Vendor") + .HasColumnType("text"); + + b.Property("Village") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ReportId"); + + b.ToTable("LMSalesEvidences"); + }); + + modelBuilder.Entity("ValuationBackend.Models.LandMiscellaneousMasterFile", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Lots") + .HasColumnType("integer"); + + b.Property("MasterFileNo") + .HasColumnType("integer"); + + b.Property("PlanNo") + .HasColumnType("text"); + + b.Property("PlanType") + .HasColumnType("text"); + + b.Property("RequestingAuthorityReferenceNo") + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("LandMiscellaneousMasterFiles"); + }); + + modelBuilder.Entity("ValuationBackend.Models.MasterDataItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Category") + .IsRequired() + .HasColumnType("text"); + + b.Property("Value") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("MasterDataItems"); + }); + + modelBuilder.Entity("ValuationBackend.Models.PasswordReset", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Email") + .IsRequired() + .HasColumnType("text"); + + b.Property("ExpiresAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Otp") + .IsRequired() + .HasColumnType("text"); + + b.Property("Used") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.ToTable("PasswordResets"); + }); + + modelBuilder.Entity("ValuationBackend.Models.PastValuationsLA", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DateOfValuation") + .HasColumnType("text"); + + b.Property("Extent") + .HasColumnType("text"); + + b.Property("FileNoGNDivision") + .HasColumnType("text"); + + b.Property("LocationLatitude") + .HasColumnType("text"); + + b.Property("LocationLongitude") + .HasColumnType("text"); + + b.Property("MasterFileId") + .IsRequired() + .HasColumnType("text"); + + b.Property("MasterFileRefNo") + .IsRequired() + .HasColumnType("text"); + + b.Property("PlanOfParticulars") + .HasColumnType("text"); + + b.Property("PurposeOfValuation") + .HasColumnType("text"); + + b.Property("Rate") + .HasColumnType("text"); + + b.Property("RateType") + .HasColumnType("text"); + + b.Property("Remarks") + .HasColumnType("text"); + + b.Property("ReportId") + .HasColumnType("integer"); + + b.Property("Situation") + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("ReportId"); + + b.ToTable("PastValuationsLA"); + }); + + modelBuilder.Entity("ValuationBackend.Models.PropertyCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.HasKey("Id"); + + b.ToTable("PropertyCategories"); + }); + + modelBuilder.Entity("ValuationBackend.Models.RatingRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("LocalAuthority") + .IsRequired() + .HasColumnType("text"); + + b.Property("RatingReferenceNo") + .IsRequired() + .HasColumnType("text"); + + b.Property("RequestType") + .IsRequired() + .HasColumnType("text"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("YearOfRevision") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.ToTable("RatingRequests"); + }); + + modelBuilder.Entity("ValuationBackend.Models.Reconciliation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AssetId") + .HasColumnType("integer"); + + b.Property("NewNo") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ObsoleteNo") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("StreetName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("AssetId"); + + b.ToTable("Reconciliations"); + }); + + modelBuilder.Entity("ValuationBackend.Models.RentalEvidenceLA", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AssessmentNo") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("FloorRateSQFT") + .HasColumnType("text"); + + b.Property("HeadOfTerms") + .HasColumnType("text"); + + b.Property("LocationLatitude") + .HasColumnType("text"); + + b.Property("LocationLongitude") + .HasColumnType("text"); + + b.Property("MasterFileId") + .IsRequired() + .HasColumnType("text"); + + b.Property("MasterFileRefNo") + .IsRequired() + .HasColumnType("text"); + + b.Property("Occupier") + .HasColumnType("text"); + + b.Property("Owner") + .HasColumnType("text"); + + b.Property("RatePerMonth") + .HasColumnType("text"); + + b.Property("RatePerSqft") + .HasColumnType("text"); + + b.Property("Remarks") + .HasColumnType("text"); + + b.Property("ReportId") + .HasColumnType("integer"); + + b.Property("Situation") + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("ReportId"); + + b.ToTable("RentalEvidencesLA", (string)null); + }); + + modelBuilder.Entity("ValuationBackend.Models.Report", b => + { + b.Property("ReportId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ReportId")); + + b.Property("Description") + .IsRequired() + .HasColumnType("text"); + + b.Property("ReportType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Timestamp") + .HasColumnType("timestamp with time zone"); + + b.HasKey("ReportId"); + + b.ToTable("Reports"); + }); + + modelBuilder.Entity("ValuationBackend.Models.Request", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LocalAuthority") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("RatingReferenceNo") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("RequestTypeId") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("boolean"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("YearOfRevision") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("RequestTypeId"); + + b.ToTable("Requests"); + }); + + modelBuilder.Entity("ValuationBackend.Models.RequestType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(2) + .HasColumnType("character varying(2)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.HasKey("Id"); + + b.ToTable("RequestTypes"); + }); + + modelBuilder.Entity("ValuationBackend.Models.SalesEvidenceLA", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AssetNumber") + .HasColumnType("text"); + + b.Property("Consideration") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeedAttestedNumber") + .HasColumnType("text"); + + b.Property("DeedNumber") + .HasColumnType("text"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("DescriptionOfProperty") + .HasColumnType("text"); + + b.Property("Extent") + .HasColumnType("text"); + + b.Property("FloorRate") + .HasColumnType("text"); + + b.Property("LandRegistryReferences") + .HasColumnType("text"); + + b.Property("LocationLatitude") + .HasColumnType("text"); + + b.Property("LocationLongitude") + .HasColumnType("text"); + + b.Property("LotNumber") + .HasColumnType("text"); + + b.Property("MasterFileId") + .IsRequired() + .HasColumnType("text"); + + b.Property("MasterFileRefNo") + .IsRequired() + .HasColumnType("text"); + + b.Property("NotaryName") + .HasColumnType("text"); + + b.Property("Occupier") + .HasColumnType("text"); + + b.Property("Owner") + .HasColumnType("text"); + + b.Property("PlanDate") + .HasColumnType("text"); + + b.Property("PlanNumber") + .HasColumnType("text"); + + b.Property("Rate") + .HasColumnType("text"); + + b.Property("RateType") + .HasColumnType("text"); + + b.Property("Remarks") + .HasColumnType("text"); + + b.Property("ReportId") + .HasColumnType("integer"); + + b.Property("Road") + .HasColumnType("text"); + + b.Property("Situation") + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Vendor") + .HasColumnType("text"); + + b.Property("Village") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ReportId"); + + b.ToTable("SalesEvidencesLA", (string)null); + }); + + modelBuilder.Entity("ValuationBackend.Models.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AssignedDivision") + .IsRequired() + .HasColumnType("text"); + + b.Property("EmpEmail") + .IsRequired() + .HasColumnType("text"); + + b.Property("EmpId") + .IsRequired() + .HasColumnType("text"); + + b.Property("EmpName") + .IsRequired() + .HasColumnType("text"); + + b.Property("PasswordHash") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("PasswordSalt") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("Position") + .IsRequired() + .HasColumnType("text"); + + b.Property("ProfilePicture") + .HasColumnType("bytea"); + + b.Property("Username") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("ValuationBackend.Models.UserTask", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AssignedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("IsCompleted") + .HasColumnType("boolean"); + + b.Property("LandAcquisitionId") + .HasColumnType("integer"); + + b.Property("LandMiscellaneousId") + .HasColumnType("integer"); + + b.Property("ReferenceNumber") + .HasColumnType("text"); + + b.Property("RequestId") + .HasColumnType("integer"); + + b.Property("TaskType") + .IsRequired() + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.Property("Username") + .IsRequired() + .HasColumnType("text"); + + b.Property("WorkItemDescription") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("UserTasks"); + }); + + modelBuilder.Entity("ValuationBackend.Models.iteration2.RatingCards.OfficesRatingCard", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AccessType") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Age") + .HasColumnType("integer"); + + b.Property("AssessmentNumber") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("AssetId") + .HasColumnType("integer"); + + b.Property("BuildingSelection") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CeilingHeight") + .HasColumnType("decimal(18,2)"); + + b.Property("Condition") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Conveniences") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .IsRequired() + .HasColumnType("text"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("FloorNumber") + .HasColumnType("integer"); + + b.Property("FloorType") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("LocalAuthority") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("LocalAuthorityCode") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("NewNumber") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Notes") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("ObsoleteNumber") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Occupier") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("OfficeGrade") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("OfficeSuite") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("Owner") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ParkingSpace") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("PropertySubCategory") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("PropertyType") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("RentPM") + .HasColumnType("decimal(18,2)"); + + b.Property("RoadName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("SuggestedRate") + .HasColumnType("decimal(18,2)"); + + b.Property("Terms") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("TotalArea") + .HasColumnType("decimal(18,2)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UpdatedBy") + .HasColumnType("text"); + + b.Property("UsableFloorArea") + .HasColumnType("decimal(18,2)"); + + b.Property("WallType") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("WardNumber") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("AssetId"); + + b.ToTable("OfficesRatingCards"); + }); + + modelBuilder.Entity("ValuationBackend.Models.Asset", b => + { + b.HasOne("ValuationBackend.Models.Request", "Request") + .WithMany() + .HasForeignKey("RequestId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Request"); + }); + + modelBuilder.Entity("ValuationBackend.Models.AssetDivision", b => + { + b.HasOne("ValuationBackend.Models.Asset", "Asset") + .WithMany() + .HasForeignKey("AssetId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Asset"); + }); + + modelBuilder.Entity("ValuationBackend.Models.BuildingRatesLA", b => + { + b.HasOne("ValuationBackend.Models.Report", "Report") + .WithMany() + .HasForeignKey("ReportId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Report"); + }); + + modelBuilder.Entity("ValuationBackend.Models.ConditionReport", b => + { + b.HasOne("ValuationBackend.Models.Report", "Report") + .WithMany() + .HasForeignKey("ReportId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Report"); + }); + + modelBuilder.Entity("ValuationBackend.Models.DomesticRatingCard", b => + { + b.HasOne("ValuationBackend.Models.Asset", "Asset") + .WithMany() + .HasForeignKey("AssetId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Asset"); + }); + + modelBuilder.Entity("ValuationBackend.Models.InspectionBuilding", b => + { + b.HasOne("ValuationBackend.Models.InspectionReport", "InspectionReport") + .WithMany("Buildings") + .HasForeignKey("InspectionReportId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("InspectionReport"); + }); + + modelBuilder.Entity("ValuationBackend.Models.InspectionReport", b => + { + b.HasOne("ValuationBackend.Models.Report", "Report") + .WithMany() + .HasForeignKey("ReportId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Report"); + }); + + modelBuilder.Entity("ValuationBackend.Models.LMBuildingRates", b => + { + b.HasOne("ValuationBackend.Models.Report", "Report") + .WithMany() + .HasForeignKey("ReportId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Report"); + }); + + modelBuilder.Entity("ValuationBackend.Models.LMPastValuation", b => + { + b.HasOne("ValuationBackend.Models.Report", "Report") + .WithMany() + .HasForeignKey("ReportId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Report"); + }); + + modelBuilder.Entity("ValuationBackend.Models.LMRentalEvidence", b => + { + b.HasOne("ValuationBackend.Models.Report", "Report") + .WithMany() + .HasForeignKey("ReportId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Report"); + }); + + modelBuilder.Entity("ValuationBackend.Models.LMSalesEvidence", b => + { + b.HasOne("ValuationBackend.Models.Report", "Report") + .WithMany() + .HasForeignKey("ReportId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Report"); + }); + + modelBuilder.Entity("ValuationBackend.Models.PastValuationsLA", b => + { + b.HasOne("ValuationBackend.Models.Report", "Report") + .WithMany() + .HasForeignKey("ReportId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Report"); + }); + + modelBuilder.Entity("ValuationBackend.Models.Reconciliation", b => + { + b.HasOne("ValuationBackend.Models.Asset", "Asset") + .WithMany() + .HasForeignKey("AssetId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Asset"); + }); + + modelBuilder.Entity("ValuationBackend.Models.RentalEvidenceLA", b => + { + b.HasOne("ValuationBackend.Models.Report", "Report") + .WithMany() + .HasForeignKey("ReportId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Report"); + }); + + modelBuilder.Entity("ValuationBackend.Models.Request", b => + { + b.HasOne("ValuationBackend.Models.RequestType", "RequestType") + .WithMany() + .HasForeignKey("RequestTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RequestType"); + }); + + modelBuilder.Entity("ValuationBackend.Models.SalesEvidenceLA", b => + { + b.HasOne("ValuationBackend.Models.Report", "Report") + .WithMany() + .HasForeignKey("ReportId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Report"); + }); + + modelBuilder.Entity("ValuationBackend.Models.UserTask", b => + { + b.HasOne("ValuationBackend.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("ValuationBackend.Models.iteration2.RatingCards.OfficesRatingCard", b => + { + b.HasOne("ValuationBackend.Models.Asset", "Asset") + .WithMany() + .HasForeignKey("AssetId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Asset"); + }); + + modelBuilder.Entity("ValuationBackend.Models.InspectionReport", b => + { + b.Navigation("Buildings"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Migrations/20250721174110_AddPasswordResetTable.cs b/Migrations/20250721174110_AddPasswordResetTable.cs new file mode 100644 index 0000000..7025b37 --- /dev/null +++ b/Migrations/20250721174110_AddPasswordResetTable.cs @@ -0,0 +1,39 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace ValuationBackend.Migrations +{ + /// + public partial class AddPasswordResetTable : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "PasswordResets", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + Email = table.Column(type: "text", nullable: false), + Otp = table.Column(type: "text", nullable: false), + ExpiresAt = table.Column(type: "timestamp with time zone", nullable: false), + Used = table.Column(type: "boolean", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_PasswordResets", x => x.Id); + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "PasswordResets"); + } + } +} diff --git a/Migrations/AppDbContextModelSnapshot.cs b/Migrations/AppDbContextModelSnapshot.cs index 6b99b74..3e31145 100644 --- a/Migrations/AppDbContextModelSnapshot.cs +++ b/Migrations/AppDbContextModelSnapshot.cs @@ -1016,6 +1016,33 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("MasterDataItems"); }); + modelBuilder.Entity("ValuationBackend.Models.PasswordReset", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Email") + .IsRequired() + .HasColumnType("text"); + + b.Property("ExpiresAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Otp") + .IsRequired() + .HasColumnType("text"); + + b.Property("Used") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.ToTable("PasswordResets"); + }); + modelBuilder.Entity("ValuationBackend.Models.PastValuationsLA", b => { b.Property("Id") diff --git a/Models/PasswordReset.cs b/Models/PasswordReset.cs new file mode 100644 index 0000000..9029ecc --- /dev/null +++ b/Models/PasswordReset.cs @@ -0,0 +1,18 @@ +using System; +using System.ComponentModel.DataAnnotations; + +namespace ValuationBackend.Models +{ + public class PasswordReset + { + [Key] + public int Id { get; set; } + [Required] + public string Email { get; set; } + [Required] + public string Otp { get; set; } + [Required] + public DateTime ExpiresAt { get; set; } + public bool Used { get; set; } = false; + } +} \ No newline at end of file diff --git a/Models/iteration2/DTOs/ObsoleteNumberDto.cs b/Models/iteration2/DTOs/ObsoleteNumberDto.cs new file mode 100644 index 0000000..052669c --- /dev/null +++ b/Models/iteration2/DTOs/ObsoleteNumberDto.cs @@ -0,0 +1,5 @@ +public class ObsoleteNumberDto +{ + public string Id { get; set; } + public string Number { get; set; } +} diff --git a/Models/iteration2/DTOs/streets.cs b/Models/iteration2/DTOs/streets.cs new file mode 100644 index 0000000..0339563 --- /dev/null +++ b/Models/iteration2/DTOs/streets.cs @@ -0,0 +1,5 @@ +public class StreetDto +{ + public string Id { get; set; } + public string Name { get; set; } +} diff --git a/Models/iteration2/Decision.cs b/Models/iteration2/Decision.cs new file mode 100644 index 0000000..96f60eb --- /dev/null +++ b/Models/iteration2/Decision.cs @@ -0,0 +1,8 @@ +public class DecisionField +{ + public int Id { get; set; } + public string Field { get; set; } // E.g., "Zone" + public string FieldDescription { get; set; } // E.g., "Zoning Category" + public string FieldType { get; set; } // E.g., "string", "number" + public int FieldSize { get; set; } // E.g., 255 +} diff --git a/Program.cs b/Program.cs index 71407b2..e02f5de 100644 --- a/Program.cs +++ b/Program.cs @@ -7,6 +7,7 @@ using ValuationBackend.Extensions; using ValuationBackend.Models; using DotNetEnv; +using ValuationBackend.Services; // Load environment variables from .env file Env.Load(); @@ -32,6 +33,8 @@ // Register repositories and services using extension methods builder.Services.AddRepositories(); builder.Services.AddServices(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); // Configure PostgreSQL @@ -65,6 +68,10 @@ options.ExpiryMinutes = jwtSettings.ExpiryMinutes; }); +builder.Services.AddDbContext(options => + options.UseNpgsql(builder.Configuration.GetConnectionString("DefaultConnection"))); + + // Add Authentication var key = Encoding.UTF8.GetBytes(jwtSettings.SecretKey); builder.Services.AddAuthentication(options => diff --git a/repositories/DecisionFieldRepository.cs b/repositories/DecisionFieldRepository.cs new file mode 100644 index 0000000..219e17a --- /dev/null +++ b/repositories/DecisionFieldRepository.cs @@ -0,0 +1,32 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using ValuationBackend.Data; +using ValuationBackend.Models; + +namespace ValuationBackend.Repositories +{ + public class DecisionFieldRepository + { + private readonly ValuationContext _context; + + public DecisionFieldRepository(ValuationContext context) + { + _context = context; + } + + public async Task> GetAllAsync() + { + return await _context.DecisionFields.ToListAsync(); + } + + public async Task AddAsync(DecisionField field) + { + _context.DecisionFields.Add(field); + await _context.SaveChangesAsync(); + return field; + } + + // Add more methods as needed (e.g., GetByIdAsync, UpdateAsync, DeleteAsync) + } +} diff --git a/repositories/iteration2/AssetRepository.cs b/repositories/iteration2/AssetRepository.cs index 367a8ee..6a93fb0 100644 --- a/repositories/iteration2/AssetRepository.cs +++ b/repositories/iteration2/AssetRepository.cs @@ -1,4 +1,5 @@ using Microsoft.EntityFrameworkCore; +using System.Threading.Tasks; using ValuationBackend.Data; using ValuationBackend.Models; @@ -94,6 +95,14 @@ public Asset Update(Asset asset) return asset; } + public async Task UpdateAsync(Asset asset) + { + asset.UpdatedAt = DateTime.UtcNow; + _context.Assets.Update(asset); + await _context.SaveChangesAsync(); + return asset; + } + public bool Delete(int id) { var asset = _context.Assets.Find(id); diff --git a/repositories/iteration2/IAssetRepository.cs b/repositories/iteration2/IAssetRepository.cs index 542f873..4e9e4ef 100644 --- a/repositories/iteration2/IAssetRepository.cs +++ b/repositories/iteration2/IAssetRepository.cs @@ -1,3 +1,4 @@ +using System.Threading.Tasks; using ValuationBackend.Models; namespace ValuationBackend.Repositories @@ -14,6 +15,7 @@ public interface IAssetRepository List GetByRatingCard(bool isRatingCard); Asset Create(Asset asset); Asset Update(Asset asset); + Task UpdateAsync(Asset asset); bool Delete(int id); bool Exists(int id); } diff --git a/services/AssetNumberChangeService.cs b/services/AssetNumberChangeService.cs index 8fe6079..4e5431e 100644 --- a/services/AssetNumberChangeService.cs +++ b/services/AssetNumberChangeService.cs @@ -37,7 +37,7 @@ public async Task> GetByNewAssetNoAsync(string ne public async Task CreateAssetNumberChangeAsync(AssetNumberChange assetNumberChange) { - // Set default values if not provided + // Set default values if not provided assetNumberChange.DateOfChange = DateTime.UtcNow; if (assetNumberChange.ChangedDate == null) { diff --git a/services/DecisionFieldService.cs b/services/DecisionFieldService.cs new file mode 100644 index 0000000..1d705f9 --- /dev/null +++ b/services/DecisionFieldService.cs @@ -0,0 +1,29 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using ValuationBackend.Models; +using ValuationBackend.Repositories; + +namespace ValuationBackend.Services +{ + public class DecisionFieldService + { + private readonly DecisionFieldRepository _repository; + + public DecisionFieldService(DecisionFieldRepository repository) + { + _repository = repository; + } + + public async Task> GetAllFieldsAsync() + { + return await _repository.GetAllAsync(); + } + + public async Task CreateFieldAsync(DecisionField field) + { + return await _repository.AddAsync(field); + } + + // Add more methods as needed for business logic + } +} diff --git a/services/EmailSender.cs b/services/EmailSender.cs new file mode 100644 index 0000000..d46390e --- /dev/null +++ b/services/EmailSender.cs @@ -0,0 +1,35 @@ +using System.Net; +using System.Net.Mail; +using System.Threading.Tasks; +using System; + +namespace ValuationBackend.Services +{ + public class EmailSender : IEmailSender + { + public async Task SendAsync(string to, string subject, string body) + { + // Read from environment variables (set in .env or your deployment environment) + var gmailAddress = Environment.GetEnvironmentVariable("GMAIL_ADDRESS"); + var gmailAppPassword = Environment.GetEnvironmentVariable("GMAIL_APP_PASSWORD"); + + var smtpClient = new SmtpClient("smtp.gmail.com") + { + Port = 587, + Credentials = new NetworkCredential(gmailAddress, gmailAppPassword), + EnableSsl = true, + }; + + var mailMessage = new MailMessage + { + From = new MailAddress(gmailAddress, "Valuation Department"), + Subject = subject, + Body = body, + IsBodyHtml = false, + }; + mailMessage.To.Add(to); + + await smtpClient.SendMailAsync(mailMessage); + } + } +} \ No newline at end of file diff --git a/services/IEmailSender.cs b/services/IEmailSender.cs new file mode 100644 index 0000000..640cc53 --- /dev/null +++ b/services/IEmailSender.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; + +namespace ValuationBackend.Services +{ + public interface IEmailSender + { + Task SendAsync(string to, string subject, string body); + } +} \ No newline at end of file diff --git a/services/PasswordResetService.cs b/services/PasswordResetService.cs new file mode 100644 index 0000000..bb3c227 --- /dev/null +++ b/services/PasswordResetService.cs @@ -0,0 +1,85 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using ValuationBackend.Data; +using ValuationBackend.Models; +using ValuationBackend.Services; + +namespace ValuationBackend.Services +{ + public class PasswordResetService + { + private readonly AppDbContext _context; + private readonly IEmailSender _emailSender; + + public PasswordResetService(AppDbContext context, IEmailSender emailSender) + { + _context = context; + _emailSender = emailSender; + } + + public async Task RequestPasswordResetAsync(string email) + { + var user = await _context.Users.FirstOrDefaultAsync(u => u.EmpEmail == email); + if (user == null) return; // Don't reveal user existence + + var otp = GenerateOtp(); + var expiresAt = DateTime.UtcNow.AddMinutes(10); + + var reset = new PasswordReset + { + Email = email, + Otp = otp, + ExpiresAt = expiresAt, + Used = false + }; + _context.PasswordResets.Add(reset); + await _context.SaveChangesAsync(); + + var body = $"Your OTP is: {otp}\nThis OTP will expire in 10 minutes."; + await _emailSender.SendAsync(email, "Your OTP Code", body); + } + + public async Task VerifyOtpAsync(string email, string otp) + { + var reset = await _context.PasswordResets + .Where(r => r.Email == email && !r.Used && r.ExpiresAt > DateTime.UtcNow) + .OrderByDescending(r => r.ExpiresAt) + .FirstOrDefaultAsync(); + + if (reset == null || reset.Otp != otp) return false; + return true; + } + + public async Task ResetPasswordAsync(string email, string otp, string newPassword) + { + var reset = await _context.PasswordResets + .Where(r => r.Email == email && !r.Used && r.ExpiresAt > DateTime.UtcNow) + .OrderByDescending(r => r.ExpiresAt) + .FirstOrDefaultAsync(); + + if (reset == null || reset.Otp != otp) return false; + + var user = await _context.Users.FirstOrDefaultAsync(u => u.EmpEmail == email); + if (user == null) return false; + + // Hash the password using HMACSHA512 + using (var hmac = new System.Security.Cryptography.HMACSHA512()) + { + user.PasswordSalt = hmac.Key; + user.PasswordHash = hmac.ComputeHash(System.Text.Encoding.UTF8.GetBytes(newPassword)); + } + + reset.Used = true; + await _context.SaveChangesAsync(); + return true; + } + + private string GenerateOtp() + { + var random = new Random(); + return random.Next(100000, 999999).ToString(); + } + } +} \ No newline at end of file diff --git a/services/iteration2/OfficesRatingCardService.cs b/services/iteration2/OfficesRatingCardService.cs index 0540e77..d7ad992 100644 --- a/services/iteration2/OfficesRatingCardService.cs +++ b/services/iteration2/OfficesRatingCardService.cs @@ -95,7 +95,7 @@ public async Task CreateAsync(CreateOfficesRatingCardDto d // Update the asset's IsRatingCard flag to true asset.IsRatingCard = true; - _assetRepository.Update(asset); + await _assetRepository.UpdateAsync(asset); return _mapper.Map(created); } @@ -133,7 +133,7 @@ public async Task DeleteAsync(int id) if (asset != null) { asset.IsRatingCard = false; - _assetRepository.Update(asset); + await _assetRepository.UpdateAsync(asset); } }