diff --git a/Aberwyn/Aberwyn.csproj b/Aberwyn/Aberwyn.csproj
index da92c65..33d4e20 100644
--- a/Aberwyn/Aberwyn.csproj
+++ b/Aberwyn/Aberwyn.csproj
@@ -16,6 +16,14 @@
+
+
+
+
+
+
+
+
diff --git a/Aberwyn/Controllers/AdminController.cs b/Aberwyn/Controllers/AdminController.cs
index e80f2ce..3412677 100644
--- a/Aberwyn/Controllers/AdminController.cs
+++ b/Aberwyn/Controllers/AdminController.cs
@@ -36,6 +36,24 @@ namespace Aberwyn.Controllers
ViewBag.AllRoles = allRoles;
return View(model);
}
+ [HttpPost]
+ public async Task CreateUser(string email, string password)
+ {
+ var user = new ApplicationUser { UserName = email, Email = email };
+ var result = await _userManager.CreateAsync(user, password);
+
+ if (result.Succeeded)
+ {
+ TempData["Message"] = "Användare skapad!";
+ return RedirectToAction("Index");
+ }
+
+ foreach (var error in result.Errors)
+ {
+ ModelState.AddModelError("", error.Description);
+ }
+ return RedirectToAction("Index");
+ }
[HttpPost]
public async Task AddToRole(string userId, string role)
diff --git a/Aberwyn/Controllers/BudgetApiController.cs b/Aberwyn/Controllers/BudgetApiController.cs
index 2c39080..245ca67 100644
--- a/Aberwyn/Controllers/BudgetApiController.cs
+++ b/Aberwyn/Controllers/BudgetApiController.cs
@@ -1,97 +1,325 @@
-using Microsoft.AspNetCore.Mvc;
-using Aberwyn.Data;
+using Aberwyn.Data;
using Aberwyn.Models;
-using System.Collections.Generic;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.EntityFrameworkCore;
namespace Aberwyn.Controllers
{
- [Route("api/[controller]")]
+ [Authorize(Roles = "Budget")]
[ApiController]
+ [Route("api/budget")]
public class BudgetApiController : ControllerBase
{
- private readonly BudgetService _budgetService;
+ private readonly ApplicationDbContext _context;
- public BudgetApiController(BudgetService budgetService)
+ public BudgetApiController(ApplicationDbContext context)
{
- _budgetService = budgetService;
+ _context = context;
}
- [HttpGet("items")]
- public IActionResult GetBudgetItems([FromQuery] int month, [FromQuery] int year)
+ [HttpGet("{year:int}/{month:int}")]
+ public async Task GetBudget(int year, int month)
{
try
{
- var items = _budgetService.GetBudgetItems(month, year);
- return Ok(items);
- }
- catch (Exception ex)
- {
- // Log the exception (consider using a logging framework)
- return StatusCode(500, new { message = "An error occurred while fetching budget items.", details = ex.Message });
- }
- }
+ var period = await _context.BudgetPeriods
+ .Include(p => p.Categories)
+ .ThenInclude(c => c.Items)
+ .FirstOrDefaultAsync(p => p.Year == year && p.Month == month);
- [HttpGet("categories")]
- public IActionResult GetCategories()
- {
- try
- {
- var categories = _budgetService.GetCategories();
- return Ok(categories);
- }
- catch (Exception ex)
- {
- // Log the exception
- return StatusCode(500, new { message = "An error occurred while fetching categories.", details = ex.Message });
- }
- }
-
- [HttpPut("items")]
- public IActionResult UpdateBudgetItem([FromBody] BudgetItem item)
- {
- if (item == null || item.ID <= 0)
- {
- return BadRequest("Invalid budget item data.");
- }
-
- try
- {
- var result = _budgetService.UpdateBudgetItem(item);
- if (result)
+ if (period == null)
{
- return Ok("Item updated successfully.");
+ return Ok(new BudgetDto
+ {
+ Year = year,
+ Month = month,
+ Categories = new List()
+ });
}
- return StatusCode(500, "Error updating item.");
- }
- catch (Exception ex)
- {
- // Log the exception
- return StatusCode(500, new { message = "An error occurred while updating the item.", details = ex.Message });
- }
- }
- [HttpPost("items")]
- public IActionResult AddBudgetItem([FromBody] BudgetItem item)
- {
- if (item == null || string.IsNullOrEmpty(item.Name) || item.Amount <= 0)
- {
- return BadRequest("Invalid budget item data.");
- }
- try
- {
- var result = _budgetService.AddBudgetItem(item);
- if (result)
+ var dto = new BudgetDto
{
- return CreatedAtAction(nameof(GetBudgetItems), new { id = item.ID }, item);
- }
- return StatusCode(500, "Error adding item.");
+ Id = period.Id,
+ Year = period.Year,
+ Month = period.Month,
+ Categories = period.Categories
+ .OrderBy(cat => cat.Order)
+ .Select(cat => new BudgetCategoryDto
+ {
+ Id = cat.Id,
+ Name = cat.Name,
+ Color = cat.Color,
+ Items = cat.Items
+ .OrderBy(i => i.Order) // ← sortera innan mappning
+ .Select(i => new BudgetItemDto
+ {
+ Id = i.Id,
+ Name = i.Name,
+ Amount = i.Amount,
+ IsExpense = i.IsExpense,
+ IncludeInSummary = i.IncludeInSummary
+ }).ToList()
+ }).ToList()
+ };
+
+ return Ok(dto);
}
catch (Exception ex)
{
- // Log the exception
- return StatusCode(500, new { message = "An error occurred while adding the item.", details = ex.Message });
+ return StatusCode(500, $"Fel: {ex.Message} \n{ex.StackTrace}");
}
}
+
+ [HttpPut("category/{id}")]
+ public async Task UpdateCategory(int id, [FromBody] BudgetCategoryDto updatedCategory)
+ {
+ var category = await _context.BudgetCategories
+ .Include(c => c.Items)
+ .FirstOrDefaultAsync(c => c.Id == id);
+
+ if (category == null)
+ return NotFound();
+
+ // Uppdatera kategoriinformation
+ category.Name = updatedCategory.Name;
+ category.Color = updatedCategory.Color;
+
+ // Spara undan inkommande IDs (ignorera nya med Id = 0)
+ var incomingItemIds = updatedCategory.Items
+ .Where(i => i.Id != 0)
+ .Select(i => i.Id)
+ .ToList();
+
+ // Ta bort poster som inte längre finns i inkommande data
+ var itemsToRemove = category.Items
+ .Where(i => !incomingItemIds.Contains(i.Id))
+ .ToList();
+
+ _context.BudgetItems.RemoveRange(itemsToRemove);
+
+ // Lägg till nya poster eller uppdatera befintliga
+ foreach (var incoming in updatedCategory.Items)
+ {
+ if (incoming.Id == 0)
+ {
+ category.Items.Add(new BudgetItem
+ {
+ Name = incoming.Name,
+ Amount = incoming.Amount,
+ IsExpense = incoming.IsExpense,
+ IncludeInSummary = incoming.IncludeInSummary,
+ Order = incoming.Order,
+ BudgetCategoryId = category.Id
+ });
+ }
+ else
+ {
+ var existing = category.Items.FirstOrDefault(i => i.Id == incoming.Id);
+ if (existing != null)
+ {
+ existing.Name = incoming.Name;
+ existing.Amount = incoming.Amount;
+ existing.IsExpense = incoming.IsExpense;
+ existing.IncludeInSummary = incoming.IncludeInSummary;
+ }
+ }
+ }
+
+ await _context.SaveChangesAsync();
+ return NoContent();
+ }
+
+ [HttpPut("category/order")]
+ public async Task UpdateCategoryOrder([FromBody] List orderedCategories)
+ {
+ foreach (var dto in orderedCategories)
+ {
+ var cat = await _context.BudgetCategories.FindAsync(dto.Id);
+ if (cat != null)
+ cat.Order = dto.Order;
+ }
+
+ await _context.SaveChangesAsync();
+ return NoContent();
+ }
+
+
+
+ [HttpPost]
+ public async Task CreatePeriod([FromBody] BudgetPeriod newPeriod)
+ {
+ _context.BudgetPeriods.Add(newPeriod);
+ await _context.SaveChangesAsync();
+ return CreatedAtAction(nameof(GetBudget), new { year = newPeriod.Year, month = newPeriod.Month }, newPeriod);
+ }
+
+ [HttpPut("item/{id}")]
+ public async Task UpdateItem(int id, [FromBody] BudgetItem updatedItem)
+ {
+ var item = await _context.BudgetItems.FindAsync(id);
+ if (item == null) return NotFound();
+
+ item.Name = updatedItem.Name;
+ item.Amount = updatedItem.Amount;
+ item.IsExpense = updatedItem.IsExpense;
+ item.IncludeInSummary = updatedItem.IncludeInSummary;
+ item.Order = updatedItem.Order;
+ item.BudgetCategoryId = updatedItem.BudgetCategoryId;
+
+ await _context.SaveChangesAsync();
+ return Ok();
+ }
+
+ [HttpPost("item")]
+ public async Task CreateItem([FromBody] BudgetItem newItem)
+ {
+ if (newItem == null || newItem.BudgetCategoryId == 0)
+ return BadRequest("Ogiltig data.");
+
+ _context.BudgetItems.Add(newItem);
+ await _context.SaveChangesAsync();
+
+ return Ok(new { id = newItem.Id });
+ }
+
+ [HttpDelete("item/{id}")]
+ public async Task DeleteItem(int id)
+ {
+ var item = await _context.BudgetItems.FindAsync(id);
+ if (item == null) return NotFound();
+
+ _context.BudgetItems.Remove(item);
+ await _context.SaveChangesAsync();
+ return NoContent();
+ }
+ [HttpDelete("{year:int}/{month:int}")]
+ public async Task DeleteMonth(int year, int month)
+ {
+ var period = await _context.BudgetPeriods
+ .Include(p => p.Categories)
+ .ThenInclude(c => c.Items)
+ .FirstOrDefaultAsync(p => p.Year == year && p.Month == month);
+
+ if (period == null)
+ return NotFound();
+
+ // Ta bort alla items → kategorier → period
+ foreach (var category in period.Categories)
+ {
+ _context.BudgetItems.RemoveRange(category.Items);
+ }
+
+ _context.BudgetCategories.RemoveRange(period.Categories);
+ _context.BudgetPeriods.Remove(period);
+
+ await _context.SaveChangesAsync();
+ return NoContent();
+ }
+
+
+ [HttpPost("category")]
+ public async Task CreateCategory([FromBody] BudgetCategoryDto newCategoryDto)
+ {
+ if (newCategoryDto == null || string.IsNullOrWhiteSpace(newCategoryDto.Name))
+ return BadRequest("Ogiltig data.");
+
+ // Kontrollera att rätt period finns
+ var period = await _context.BudgetPeriods
+ .FirstOrDefaultAsync(p => p.Year == newCategoryDto.Year && p.Month == newCategoryDto.Month);
+
+ if (period == null)
+ {
+ // Skapa ny period om den inte finns
+ period = new BudgetPeriod
+ {
+ Year = newCategoryDto.Year,
+ Month = newCategoryDto.Month
+ };
+ _context.BudgetPeriods.Add(period);
+ await _context.SaveChangesAsync(); // Vi behöver spara för att få period.Id
+ }
+
+ var category = new BudgetCategory
+ {
+ Name = newCategoryDto.Name,
+ Color = newCategoryDto.Color ?? "#666666",
+ BudgetPeriodId = period.Id,
+ Order = newCategoryDto.Order
+ };
+
+ _context.BudgetCategories.Add(category);
+ await _context.SaveChangesAsync();
+
+ return Ok(new { id = category.Id });
+ }
+ [HttpDelete("category/{id}")]
+ public async Task DeleteCategory(int id)
+ {
+ var category = await _context.BudgetCategories
+ .Include(c => c.Items)
+ .FirstOrDefaultAsync(c => c.Id == id);
+
+ if (category == null)
+ return NotFound();
+
+ _context.BudgetItems.RemoveRange(category.Items);
+ _context.BudgetCategories.Remove(category);
+
+ await _context.SaveChangesAsync();
+
+ return NoContent();
+ }
+
+ [HttpPost("copy/{year:int}/{month:int}")]
+ public async Task CopyFromPreviousMonth(int year, int month)
+ {
+ var targetPeriod = await _context.BudgetPeriods
+ .Include(p => p.Categories)
+ .ThenInclude(c => c.Items)
+ .FirstOrDefaultAsync(p => p.Year == year && p.Month == month);
+
+ if (targetPeriod != null && targetPeriod.Categories.Any())
+ return BadRequest("Det finns redan data för denna månad.");
+
+ // Räkna ut föregående månad
+ var previous = new DateTime(year, month, 1).AddMonths(-1);
+ var previousPeriod = await _context.BudgetPeriods
+ .Include(p => p.Categories)
+ .ThenInclude(c => c.Items)
+ .FirstOrDefaultAsync(p => p.Year == previous.Year && p.Month == previous.Month);
+
+ if (previousPeriod == null)
+ return NotFound("Ingen data att kopiera från.");
+
+ // Skapa ny period
+ var newPeriod = new BudgetPeriod
+ {
+ Year = year,
+ Month = month,
+ Categories = previousPeriod.Categories.Select(cat => new BudgetCategory
+ {
+ Name = cat.Name,
+ Color = cat.Color,
+ Order = cat.Order,
+ Items = cat.Items.Select(item => new BudgetItem
+ {
+ Name = item.Name,
+ Amount = item.Amount,
+ IsExpense = item.IsExpense,
+ IncludeInSummary = item.IncludeInSummary,
+ Order = item.Order
+ }).ToList()
+ }).ToList()
+ };
+
+ _context.BudgetPeriods.Add(newPeriod);
+ await _context.SaveChangesAsync();
+
+ return Ok();
+ }
+
+
}
}
diff --git a/Aberwyn/Controllers/BudgetController.cs b/Aberwyn/Controllers/BudgetController.cs
new file mode 100644
index 0000000..e6cbdab
--- /dev/null
+++ b/Aberwyn/Controllers/BudgetController.cs
@@ -0,0 +1,15 @@
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+
+namespace Aberwyn.Controllers
+{
+ public class BudgetController : Controller
+ {
+ [Authorize(Roles = "Budget")]
+ public IActionResult Index()
+ {
+ ViewData["HideSidebar"] = true;
+ return View();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Aberwyn/Controllers/FoodMenuController.cs b/Aberwyn/Controllers/FoodMenuController.cs
index ad23c37..1682a63 100644
--- a/Aberwyn/Controllers/FoodMenuController.cs
+++ b/Aberwyn/Controllers/FoodMenuController.cs
@@ -9,6 +9,7 @@ using System.Globalization;
using System.Linq;
using System.Collections.Generic;
using System.Text.RegularExpressions;
+using Microsoft.AspNetCore.Authorization;
namespace Aberwyn.Controllers
{
@@ -22,7 +23,7 @@ namespace Aberwyn.Controllers
_configuration = configuration;
_env = env;
}
-
+ [Authorize(Roles = "Budget")]
public IActionResult Veckomeny(int? week, int? year)
{
var menuService = new MenuService(_configuration, _env);
diff --git a/Aberwyn/Controllers/HomeController.cs b/Aberwyn/Controllers/HomeController.cs
index dda595f..7395ef3 100644
--- a/Aberwyn/Controllers/HomeController.cs
+++ b/Aberwyn/Controllers/HomeController.cs
@@ -10,15 +10,14 @@ namespace Aberwyn.Controllers
public class HomeController : Controller
{
private readonly ILogger _logger;
- private readonly BudgetService _budgetService;
+ //private readonly BudgetService _budgetService;
private readonly MenuService _menuService;
// Constructor to inject dependencies
- public HomeController(ILogger logger, BudgetService budgetService, MenuService menuService)
+ public HomeController(ILogger logger, MenuService menuService)
{
_logger = logger;
- _budgetService = budgetService;
_menuService = menuService;
}
@@ -62,32 +61,12 @@ namespace Aberwyn.Controllers
return View(model);
}
-
-
- // Optimized Budget Action to fetch filtered data directly from the database
- public IActionResult Budget(string month, int? year)
+ public IActionResult Budget()
{
- // Default to current month and year if parameters are not provided
- int selectedMonth = !string.IsNullOrEmpty(month)
- ? Array.IndexOf(CultureInfo.CurrentCulture.DateTimeFormat.MonthNames, month) + 1
- : DateTime.Now.Month;
-
- int selectedYear = year ?? DateTime.Now.Year;
-
- // Fetch budget items for the selected month and year directly from the database
- var budgetItems = _budgetService.GetBudgetItems(selectedMonth, selectedYear);
-
- // Create the BudgetModel
- var budgetModel = new BudgetModel
- {
- BudgetItems = budgetItems.ToList() // Ensure this is a list
- };
-
- // Pass the BudgetModel to the view
- return View(budgetModel);
+ ViewData["HideSidebar"] = true;
+ return View();
}
-
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error()
{
diff --git a/Aberwyn/Controllers/MealController.cs b/Aberwyn/Controllers/MealController.cs
index 3d3d35a..d21051c 100644
--- a/Aberwyn/Controllers/MealController.cs
+++ b/Aberwyn/Controllers/MealController.cs
@@ -1,6 +1,7 @@
using Microsoft.AspNetCore.Mvc;
using Aberwyn.Models;
using Aberwyn.Data;
+using Microsoft.AspNetCore.Authorization;
namespace Aberwyn.Controllers
{
diff --git a/Aberwyn/Data/ApplicationDbContext.cs b/Aberwyn/Data/ApplicationDbContext.cs
index 1e8eb36..96a2609 100644
--- a/Aberwyn/Data/ApplicationDbContext.cs
+++ b/Aberwyn/Data/ApplicationDbContext.cs
@@ -11,5 +11,8 @@ namespace Aberwyn.Data
{
}
+ public DbSet BudgetPeriods { get; set; }
+ public DbSet BudgetCategories { get; set; }
+ public DbSet BudgetItems { get; set; }
}
-}
\ No newline at end of file
+}
diff --git a/Aberwyn/Data/BudgetService.cs b/Aberwyn/Data/BudgetService.cs
index e762cfd..17421f5 100644
--- a/Aberwyn/Data/BudgetService.cs
+++ b/Aberwyn/Data/BudgetService.cs
@@ -117,7 +117,7 @@ namespace Aberwyn.Data
return cmd.ExecuteNonQuery() > 0; // Returns true if a row was inserted
}
}
- }
+ }*/
// New method to fetch all categories
public List GetCategories()
diff --git a/Aberwyn/Data/TestDataSeeder.cs b/Aberwyn/Data/TestDataSeeder.cs
new file mode 100644
index 0000000..9425c67
--- /dev/null
+++ b/Aberwyn/Data/TestDataSeeder.cs
@@ -0,0 +1,55 @@
+using Aberwyn.Models;
+using Microsoft.EntityFrameworkCore;
+
+namespace Aberwyn.Data
+{
+ public static class TestDataSeeder
+ {
+ public static async Task SeedBudget(ApplicationDbContext context)
+ {
+ if (await context.BudgetPeriods.AnyAsync()) return;
+
+ var period = new BudgetPeriod
+ {
+ Year = DateTime.Now.Year,
+ Month = DateTime.Now.Month,
+ Categories = new List
+ {
+ new BudgetCategory
+ {
+ Name = "Inkomster",
+ Color = "#2d6a4f",
+ Items = new List
+ {
+ new BudgetItem { Name = "Elias lön", Amount = 27000, IsExpense = false },
+ new BudgetItem { Name = "Elin lön", Amount = 24000, IsExpense = false },
+ }
+ },
+ new BudgetCategory
+ {
+ Name = "Fasta utgifter",
+ Color = "#c1121f",
+ Items = new List
+ {
+ new BudgetItem { Name = "Hyra", Amount = 8900, IsExpense = true },
+ new BudgetItem { Name = "El", Amount = 1200, IsExpense = true },
+ new BudgetItem { Name = "Internet", Amount = 400, IsExpense = true },
+ }
+ },
+ new BudgetCategory
+ {
+ Name = "Sparande",
+ Color = "#6a4c93",
+ Items = new List
+ {
+ new BudgetItem { Name = "Buffert", Amount = 3000, IsExpense = false, IncludeInSummary = false },
+ }
+ }
+ }
+ };
+
+ context.BudgetPeriods.Add(period);
+ await context.SaveChangesAsync();
+ }
+ }
+}
diff --git a/Aberwyn/Migrations/20250515202922_CreateBudgetSchema.Designer.cs b/Aberwyn/Migrations/20250515202922_CreateBudgetSchema.Designer.cs
new file mode 100644
index 0000000..dea295a
--- /dev/null
+++ b/Aberwyn/Migrations/20250515202922_CreateBudgetSchema.Designer.cs
@@ -0,0 +1,375 @@
+//
+using System;
+using Aberwyn.Data;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+
+#nullable disable
+
+namespace Aberwyn.Migrations
+{
+ [DbContext(typeof(ApplicationDbContext))]
+ [Migration("20250515202922_CreateBudgetSchema")]
+ partial class CreateBudgetSchema
+ {
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "6.0.36")
+ .HasAnnotation("Relational:MaxIdentifierLength", 64);
+
+ modelBuilder.Entity("Aberwyn.Models.ApplicationUser", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("varchar(255)");
+
+ b.Property("AccessFailedCount")
+ .HasColumnType("int");
+
+ b.Property("ConcurrencyStamp")
+ .IsConcurrencyToken()
+ .HasColumnType("longtext");
+
+ b.Property("Email")
+ .HasMaxLength(256)
+ .HasColumnType("varchar(256)");
+
+ b.Property("EmailConfirmed")
+ .HasColumnType("tinyint(1)");
+
+ b.Property("LockoutEnabled")
+ .HasColumnType("tinyint(1)");
+
+ b.Property("LockoutEnd")
+ .HasColumnType("datetime(6)");
+
+ b.Property("NormalizedEmail")
+ .HasMaxLength(256)
+ .HasColumnType("varchar(256)");
+
+ b.Property("NormalizedUserName")
+ .HasMaxLength(256)
+ .HasColumnType("varchar(256)");
+
+ b.Property("PasswordHash")
+ .HasColumnType("longtext");
+
+ b.Property("PhoneNumber")
+ .HasColumnType("longtext");
+
+ b.Property("PhoneNumberConfirmed")
+ .HasColumnType("tinyint(1)");
+
+ b.Property("SecurityStamp")
+ .HasColumnType("longtext");
+
+ b.Property("TwoFactorEnabled")
+ .HasColumnType("tinyint(1)");
+
+ b.Property("UserName")
+ .HasMaxLength(256)
+ .HasColumnType("varchar(256)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("NormalizedEmail")
+ .HasDatabaseName("EmailIndex");
+
+ b.HasIndex("NormalizedUserName")
+ .IsUnique()
+ .HasDatabaseName("UserNameIndex");
+
+ b.ToTable("AspNetUsers", (string)null);
+ });
+
+ modelBuilder.Entity("Aberwyn.Models.BudgetCategory", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ b.Property("BudgetPeriodId")
+ .HasColumnType("int");
+
+ b.Property("Color")
+ .IsRequired()
+ .HasColumnType("longtext");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("longtext");
+
+ b.HasKey("Id");
+
+ b.HasIndex("BudgetPeriodId");
+
+ b.ToTable("BudgetCategories");
+ });
+
+ modelBuilder.Entity("Aberwyn.Models.BudgetItem", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ b.Property("Amount")
+ .HasColumnType("decimal(65,30)");
+
+ b.Property("BudgetCategoryId")
+ .HasColumnType("int");
+
+ b.Property("IncludeInSummary")
+ .HasColumnType("tinyint(1)");
+
+ b.Property("IsExpense")
+ .HasColumnType("tinyint(1)");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("longtext");
+
+ b.Property("Person")
+ .IsRequired()
+ .HasColumnType("longtext");
+
+ b.HasKey("Id");
+
+ b.HasIndex("BudgetCategoryId");
+
+ b.ToTable("BudgetItems");
+ });
+
+ modelBuilder.Entity("Aberwyn.Models.BudgetPeriod", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ b.Property("Month")
+ .HasColumnType("int");
+
+ b.Property("Year")
+ .HasColumnType("int");
+
+ b.HasKey("Id");
+
+ b.ToTable("BudgetPeriods");
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("varchar(255)");
+
+ b.Property("ConcurrencyStamp")
+ .IsConcurrencyToken()
+ .HasColumnType("longtext");
+
+ b.Property("Name")
+ .HasMaxLength(256)
+ .HasColumnType("varchar(256)");
+
+ b.Property("NormalizedName")
+ .HasMaxLength(256)
+ .HasColumnType("varchar(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("int");
+
+ b.Property("ClaimType")
+ .HasColumnType("longtext");
+
+ b.Property("ClaimValue")
+ .HasColumnType("longtext");
+
+ b.Property("RoleId")
+ .IsRequired()
+ .HasColumnType("varchar(255)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("RoleId");
+
+ b.ToTable("AspNetRoleClaims", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ b.Property("ClaimType")
+ .HasColumnType("longtext");
+
+ b.Property("ClaimValue")
+ .HasColumnType("longtext");
+
+ b.Property("UserId")
+ .IsRequired()
+ .HasColumnType("varchar(255)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("AspNetUserClaims", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b =>
+ {
+ b.Property("LoginProvider")
+ .HasColumnType("varchar(255)");
+
+ b.Property("ProviderKey")
+ .HasColumnType("varchar(255)");
+
+ b.Property("ProviderDisplayName")
+ .HasColumnType("longtext");
+
+ b.Property("UserId")
+ .IsRequired()
+ .HasColumnType("varchar(255)");
+
+ b.HasKey("LoginProvider", "ProviderKey");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("AspNetUserLogins", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b =>
+ {
+ b.Property("UserId")
+ .HasColumnType("varchar(255)");
+
+ b.Property("RoleId")
+ .HasColumnType("varchar(255)");
+
+ b.HasKey("UserId", "RoleId");
+
+ b.HasIndex("RoleId");
+
+ b.ToTable("AspNetUserRoles", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b =>
+ {
+ b.Property("UserId")
+ .HasColumnType("varchar(255)");
+
+ b.Property("LoginProvider")
+ .HasColumnType("varchar(255)");
+
+ b.Property("Name")
+ .HasColumnType("varchar(255)");
+
+ b.Property("Value")
+ .HasColumnType("longtext");
+
+ b.HasKey("UserId", "LoginProvider", "Name");
+
+ b.ToTable("AspNetUserTokens", (string)null);
+ });
+
+ modelBuilder.Entity("Aberwyn.Models.BudgetCategory", b =>
+ {
+ b.HasOne("Aberwyn.Models.BudgetPeriod", "BudgetPeriod")
+ .WithMany("Categories")
+ .HasForeignKey("BudgetPeriodId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("BudgetPeriod");
+ });
+
+ modelBuilder.Entity("Aberwyn.Models.BudgetItem", b =>
+ {
+ b.HasOne("Aberwyn.Models.BudgetCategory", "BudgetCategory")
+ .WithMany("Items")
+ .HasForeignKey("BudgetCategoryId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("BudgetCategory");
+ });
+
+ 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("Aberwyn.Models.ApplicationUser", null)
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b =>
+ {
+ b.HasOne("Aberwyn.Models.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("Aberwyn.Models.ApplicationUser", null)
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b =>
+ {
+ b.HasOne("Aberwyn.Models.ApplicationUser", null)
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Aberwyn.Models.BudgetCategory", b =>
+ {
+ b.Navigation("Items");
+ });
+
+ modelBuilder.Entity("Aberwyn.Models.BudgetPeriod", b =>
+ {
+ b.Navigation("Categories");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/Aberwyn/Migrations/20250515202922_CreateBudgetSchema.cs b/Aberwyn/Migrations/20250515202922_CreateBudgetSchema.cs
new file mode 100644
index 0000000..961cca1
--- /dev/null
+++ b/Aberwyn/Migrations/20250515202922_CreateBudgetSchema.cs
@@ -0,0 +1,101 @@
+using Microsoft.EntityFrameworkCore.Metadata;
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace Aberwyn.Migrations
+{
+ public partial class CreateBudgetSchema : Migration
+ {
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.CreateTable(
+ name: "BudgetPeriods",
+ columns: table => new
+ {
+ Id = table.Column(type: "int", nullable: false)
+ .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
+ Year = table.Column(type: "int", nullable: false),
+ Month = table.Column(type: "int", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_BudgetPeriods", x => x.Id);
+ })
+ .Annotation("MySql:CharSet", "utf8mb4");
+
+ migrationBuilder.CreateTable(
+ name: "BudgetCategories",
+ columns: table => new
+ {
+ Id = table.Column(type: "int", nullable: false)
+ .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
+ Name = table.Column(type: "longtext", nullable: false)
+ .Annotation("MySql:CharSet", "utf8mb4"),
+ Color = table.Column(type: "longtext", nullable: false)
+ .Annotation("MySql:CharSet", "utf8mb4"),
+ BudgetPeriodId = table.Column(type: "int", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_BudgetCategories", x => x.Id);
+ table.ForeignKey(
+ name: "FK_BudgetCategories_BudgetPeriods_BudgetPeriodId",
+ column: x => x.BudgetPeriodId,
+ principalTable: "BudgetPeriods",
+ principalColumn: "Id",
+ onDelete: ReferentialAction.Cascade);
+ })
+ .Annotation("MySql:CharSet", "utf8mb4");
+
+ migrationBuilder.CreateTable(
+ name: "BudgetItems",
+ columns: table => new
+ {
+ Id = table.Column(type: "int", nullable: false)
+ .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
+ Name = table.Column(type: "longtext", nullable: false)
+ .Annotation("MySql:CharSet", "utf8mb4"),
+ Person = table.Column(type: "longtext", nullable: false)
+ .Annotation("MySql:CharSet", "utf8mb4"),
+ Amount = table.Column(type: "decimal(65,30)", nullable: false),
+ IsExpense = table.Column(type: "tinyint(1)", nullable: false),
+ IncludeInSummary = table.Column(type: "tinyint(1)", nullable: false),
+ BudgetCategoryId = table.Column(type: "int", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_BudgetItems", x => x.Id);
+ table.ForeignKey(
+ name: "FK_BudgetItems_BudgetCategories_BudgetCategoryId",
+ column: x => x.BudgetCategoryId,
+ principalTable: "BudgetCategories",
+ principalColumn: "Id",
+ onDelete: ReferentialAction.Cascade);
+ })
+ .Annotation("MySql:CharSet", "utf8mb4");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_BudgetCategories_BudgetPeriodId",
+ table: "BudgetCategories",
+ column: "BudgetPeriodId");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_BudgetItems_BudgetCategoryId",
+ table: "BudgetItems",
+ column: "BudgetCategoryId");
+ }
+
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropTable(
+ name: "BudgetItems");
+
+ migrationBuilder.DropTable(
+ name: "BudgetCategories");
+
+ migrationBuilder.DropTable(
+ name: "BudgetPeriods");
+ }
+ }
+}
diff --git a/Aberwyn/Migrations/20250515204407_MakePersonNullable.Designer.cs b/Aberwyn/Migrations/20250515204407_MakePersonNullable.Designer.cs
new file mode 100644
index 0000000..388a551
--- /dev/null
+++ b/Aberwyn/Migrations/20250515204407_MakePersonNullable.Designer.cs
@@ -0,0 +1,374 @@
+//
+using System;
+using Aberwyn.Data;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+
+#nullable disable
+
+namespace Aberwyn.Migrations
+{
+ [DbContext(typeof(ApplicationDbContext))]
+ [Migration("20250515204407_MakePersonNullable")]
+ partial class MakePersonNullable
+ {
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "6.0.36")
+ .HasAnnotation("Relational:MaxIdentifierLength", 64);
+
+ modelBuilder.Entity("Aberwyn.Models.ApplicationUser", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("varchar(255)");
+
+ b.Property("AccessFailedCount")
+ .HasColumnType("int");
+
+ b.Property("ConcurrencyStamp")
+ .IsConcurrencyToken()
+ .HasColumnType("longtext");
+
+ b.Property("Email")
+ .HasMaxLength(256)
+ .HasColumnType("varchar(256)");
+
+ b.Property("EmailConfirmed")
+ .HasColumnType("tinyint(1)");
+
+ b.Property("LockoutEnabled")
+ .HasColumnType("tinyint(1)");
+
+ b.Property("LockoutEnd")
+ .HasColumnType("datetime(6)");
+
+ b.Property("NormalizedEmail")
+ .HasMaxLength(256)
+ .HasColumnType("varchar(256)");
+
+ b.Property("NormalizedUserName")
+ .HasMaxLength(256)
+ .HasColumnType("varchar(256)");
+
+ b.Property("PasswordHash")
+ .HasColumnType("longtext");
+
+ b.Property("PhoneNumber")
+ .HasColumnType("longtext");
+
+ b.Property("PhoneNumberConfirmed")
+ .HasColumnType("tinyint(1)");
+
+ b.Property("SecurityStamp")
+ .HasColumnType("longtext");
+
+ b.Property("TwoFactorEnabled")
+ .HasColumnType("tinyint(1)");
+
+ b.Property("UserName")
+ .HasMaxLength(256)
+ .HasColumnType("varchar(256)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("NormalizedEmail")
+ .HasDatabaseName("EmailIndex");
+
+ b.HasIndex("NormalizedUserName")
+ .IsUnique()
+ .HasDatabaseName("UserNameIndex");
+
+ b.ToTable("AspNetUsers", (string)null);
+ });
+
+ modelBuilder.Entity("Aberwyn.Models.BudgetCategory", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ b.Property("BudgetPeriodId")
+ .HasColumnType("int");
+
+ b.Property("Color")
+ .IsRequired()
+ .HasColumnType("longtext");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("longtext");
+
+ b.HasKey("Id");
+
+ b.HasIndex("BudgetPeriodId");
+
+ b.ToTable("BudgetCategories");
+ });
+
+ modelBuilder.Entity("Aberwyn.Models.BudgetItem", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ b.Property("Amount")
+ .HasColumnType("decimal(65,30)");
+
+ b.Property("BudgetCategoryId")
+ .HasColumnType("int");
+
+ b.Property("IncludeInSummary")
+ .HasColumnType("tinyint(1)");
+
+ b.Property("IsExpense")
+ .HasColumnType("tinyint(1)");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("longtext");
+
+ b.Property("Person")
+ .HasColumnType("longtext");
+
+ b.HasKey("Id");
+
+ b.HasIndex("BudgetCategoryId");
+
+ b.ToTable("BudgetItems");
+ });
+
+ modelBuilder.Entity("Aberwyn.Models.BudgetPeriod", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ b.Property("Month")
+ .HasColumnType("int");
+
+ b.Property("Year")
+ .HasColumnType("int");
+
+ b.HasKey("Id");
+
+ b.ToTable("BudgetPeriods");
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("varchar(255)");
+
+ b.Property("ConcurrencyStamp")
+ .IsConcurrencyToken()
+ .HasColumnType("longtext");
+
+ b.Property("Name")
+ .HasMaxLength(256)
+ .HasColumnType("varchar(256)");
+
+ b.Property("NormalizedName")
+ .HasMaxLength(256)
+ .HasColumnType("varchar(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("int");
+
+ b.Property("ClaimType")
+ .HasColumnType("longtext");
+
+ b.Property("ClaimValue")
+ .HasColumnType("longtext");
+
+ b.Property("RoleId")
+ .IsRequired()
+ .HasColumnType("varchar(255)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("RoleId");
+
+ b.ToTable("AspNetRoleClaims", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ b.Property("ClaimType")
+ .HasColumnType("longtext");
+
+ b.Property("ClaimValue")
+ .HasColumnType("longtext");
+
+ b.Property("UserId")
+ .IsRequired()
+ .HasColumnType("varchar(255)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("AspNetUserClaims", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b =>
+ {
+ b.Property("LoginProvider")
+ .HasColumnType("varchar(255)");
+
+ b.Property("ProviderKey")
+ .HasColumnType("varchar(255)");
+
+ b.Property("ProviderDisplayName")
+ .HasColumnType("longtext");
+
+ b.Property("UserId")
+ .IsRequired()
+ .HasColumnType("varchar(255)");
+
+ b.HasKey("LoginProvider", "ProviderKey");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("AspNetUserLogins", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b =>
+ {
+ b.Property("UserId")
+ .HasColumnType("varchar(255)");
+
+ b.Property("RoleId")
+ .HasColumnType("varchar(255)");
+
+ b.HasKey("UserId", "RoleId");
+
+ b.HasIndex("RoleId");
+
+ b.ToTable("AspNetUserRoles", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b =>
+ {
+ b.Property("UserId")
+ .HasColumnType("varchar(255)");
+
+ b.Property("LoginProvider")
+ .HasColumnType("varchar(255)");
+
+ b.Property("Name")
+ .HasColumnType("varchar(255)");
+
+ b.Property("Value")
+ .HasColumnType("longtext");
+
+ b.HasKey("UserId", "LoginProvider", "Name");
+
+ b.ToTable("AspNetUserTokens", (string)null);
+ });
+
+ modelBuilder.Entity("Aberwyn.Models.BudgetCategory", b =>
+ {
+ b.HasOne("Aberwyn.Models.BudgetPeriod", "BudgetPeriod")
+ .WithMany("Categories")
+ .HasForeignKey("BudgetPeriodId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("BudgetPeriod");
+ });
+
+ modelBuilder.Entity("Aberwyn.Models.BudgetItem", b =>
+ {
+ b.HasOne("Aberwyn.Models.BudgetCategory", "BudgetCategory")
+ .WithMany("Items")
+ .HasForeignKey("BudgetCategoryId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("BudgetCategory");
+ });
+
+ 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("Aberwyn.Models.ApplicationUser", null)
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b =>
+ {
+ b.HasOne("Aberwyn.Models.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("Aberwyn.Models.ApplicationUser", null)
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b =>
+ {
+ b.HasOne("Aberwyn.Models.ApplicationUser", null)
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Aberwyn.Models.BudgetCategory", b =>
+ {
+ b.Navigation("Items");
+ });
+
+ modelBuilder.Entity("Aberwyn.Models.BudgetPeriod", b =>
+ {
+ b.Navigation("Categories");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/Aberwyn/Migrations/20250515204407_MakePersonNullable.cs b/Aberwyn/Migrations/20250515204407_MakePersonNullable.cs
new file mode 100644
index 0000000..edab9b1
--- /dev/null
+++ b/Aberwyn/Migrations/20250515204407_MakePersonNullable.cs
@@ -0,0 +1,43 @@
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace Aberwyn.Migrations
+{
+ public partial class MakePersonNullable : Migration
+ {
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.AlterColumn(
+ name: "Person",
+ table: "BudgetItems",
+ type: "longtext",
+ nullable: true,
+ oldClrType: typeof(string),
+ oldType: "longtext")
+ .Annotation("MySql:CharSet", "utf8mb4")
+ .OldAnnotation("MySql:CharSet", "utf8mb4");
+ }
+
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.UpdateData(
+ table: "BudgetItems",
+ keyColumn: "Person",
+ keyValue: null,
+ column: "Person",
+ value: "");
+
+ migrationBuilder.AlterColumn(
+ name: "Person",
+ table: "BudgetItems",
+ type: "longtext",
+ nullable: false,
+ oldClrType: typeof(string),
+ oldType: "longtext",
+ oldNullable: true)
+ .Annotation("MySql:CharSet", "utf8mb4")
+ .OldAnnotation("MySql:CharSet", "utf8mb4");
+ }
+ }
+}
diff --git a/Aberwyn/Migrations/20250519213736_AddOrderToBudgetCategory.Designer.cs b/Aberwyn/Migrations/20250519213736_AddOrderToBudgetCategory.Designer.cs
new file mode 100644
index 0000000..63e2e0d
--- /dev/null
+++ b/Aberwyn/Migrations/20250519213736_AddOrderToBudgetCategory.Designer.cs
@@ -0,0 +1,380 @@
+//
+using System;
+using Aberwyn.Data;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+
+#nullable disable
+
+namespace Aberwyn.Migrations
+{
+ [DbContext(typeof(ApplicationDbContext))]
+ [Migration("20250519213736_AddOrderToBudgetCategory")]
+ partial class AddOrderToBudgetCategory
+ {
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "6.0.36")
+ .HasAnnotation("Relational:MaxIdentifierLength", 64);
+
+ modelBuilder.Entity("Aberwyn.Models.ApplicationUser", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("varchar(255)");
+
+ b.Property("AccessFailedCount")
+ .HasColumnType("int");
+
+ b.Property("ConcurrencyStamp")
+ .IsConcurrencyToken()
+ .HasColumnType("longtext");
+
+ b.Property("Email")
+ .HasMaxLength(256)
+ .HasColumnType("varchar(256)");
+
+ b.Property("EmailConfirmed")
+ .HasColumnType("tinyint(1)");
+
+ b.Property("LockoutEnabled")
+ .HasColumnType("tinyint(1)");
+
+ b.Property("LockoutEnd")
+ .HasColumnType("datetime(6)");
+
+ b.Property("NormalizedEmail")
+ .HasMaxLength(256)
+ .HasColumnType("varchar(256)");
+
+ b.Property("NormalizedUserName")
+ .HasMaxLength(256)
+ .HasColumnType("varchar(256)");
+
+ b.Property("PasswordHash")
+ .HasColumnType("longtext");
+
+ b.Property("PhoneNumber")
+ .HasColumnType("longtext");
+
+ b.Property("PhoneNumberConfirmed")
+ .HasColumnType("tinyint(1)");
+
+ b.Property("SecurityStamp")
+ .HasColumnType("longtext");
+
+ b.Property("TwoFactorEnabled")
+ .HasColumnType("tinyint(1)");
+
+ b.Property("UserName")
+ .HasMaxLength(256)
+ .HasColumnType("varchar(256)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("NormalizedEmail")
+ .HasDatabaseName("EmailIndex");
+
+ b.HasIndex("NormalizedUserName")
+ .IsUnique()
+ .HasDatabaseName("UserNameIndex");
+
+ b.ToTable("AspNetUsers", (string)null);
+ });
+
+ modelBuilder.Entity("Aberwyn.Models.BudgetCategory", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ b.Property("BudgetPeriodId")
+ .HasColumnType("int");
+
+ b.Property("Color")
+ .IsRequired()
+ .HasColumnType("longtext");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("longtext");
+
+ b.Property("Order")
+ .HasColumnType("int");
+
+ b.HasKey("Id");
+
+ b.HasIndex("BudgetPeriodId");
+
+ b.ToTable("BudgetCategories");
+ });
+
+ modelBuilder.Entity("Aberwyn.Models.BudgetItem", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ b.Property("Amount")
+ .HasColumnType("decimal(65,30)");
+
+ b.Property("BudgetCategoryId")
+ .HasColumnType("int");
+
+ b.Property("IncludeInSummary")
+ .HasColumnType("tinyint(1)");
+
+ b.Property("IsExpense")
+ .HasColumnType("tinyint(1)");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("longtext");
+
+ b.Property("Order")
+ .HasColumnType("int");
+
+ b.HasKey("Id");
+
+ b.HasIndex("BudgetCategoryId");
+
+ b.ToTable("BudgetItems");
+ });
+
+ modelBuilder.Entity("Aberwyn.Models.BudgetPeriod", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ b.Property("Month")
+ .HasColumnType("int");
+
+ b.Property("Order")
+ .HasColumnType("int");
+
+ b.Property("Year")
+ .HasColumnType("int");
+
+ b.HasKey("Id");
+
+ b.ToTable("BudgetPeriods");
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("varchar(255)");
+
+ b.Property("ConcurrencyStamp")
+ .IsConcurrencyToken()
+ .HasColumnType("longtext");
+
+ b.Property("Name")
+ .HasMaxLength(256)
+ .HasColumnType("varchar(256)");
+
+ b.Property("NormalizedName")
+ .HasMaxLength(256)
+ .HasColumnType("varchar(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("int");
+
+ b.Property("ClaimType")
+ .HasColumnType("longtext");
+
+ b.Property("ClaimValue")
+ .HasColumnType("longtext");
+
+ b.Property("RoleId")
+ .IsRequired()
+ .HasColumnType("varchar(255)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("RoleId");
+
+ b.ToTable("AspNetRoleClaims", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ b.Property("ClaimType")
+ .HasColumnType("longtext");
+
+ b.Property("ClaimValue")
+ .HasColumnType("longtext");
+
+ b.Property("UserId")
+ .IsRequired()
+ .HasColumnType("varchar(255)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("AspNetUserClaims", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b =>
+ {
+ b.Property("LoginProvider")
+ .HasColumnType("varchar(255)");
+
+ b.Property("ProviderKey")
+ .HasColumnType("varchar(255)");
+
+ b.Property("ProviderDisplayName")
+ .HasColumnType("longtext");
+
+ b.Property("UserId")
+ .IsRequired()
+ .HasColumnType("varchar(255)");
+
+ b.HasKey("LoginProvider", "ProviderKey");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("AspNetUserLogins", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b =>
+ {
+ b.Property("UserId")
+ .HasColumnType("varchar(255)");
+
+ b.Property("RoleId")
+ .HasColumnType("varchar(255)");
+
+ b.HasKey("UserId", "RoleId");
+
+ b.HasIndex("RoleId");
+
+ b.ToTable("AspNetUserRoles", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b =>
+ {
+ b.Property("UserId")
+ .HasColumnType("varchar(255)");
+
+ b.Property("LoginProvider")
+ .HasColumnType("varchar(255)");
+
+ b.Property("Name")
+ .HasColumnType("varchar(255)");
+
+ b.Property("Value")
+ .HasColumnType("longtext");
+
+ b.HasKey("UserId", "LoginProvider", "Name");
+
+ b.ToTable("AspNetUserTokens", (string)null);
+ });
+
+ modelBuilder.Entity("Aberwyn.Models.BudgetCategory", b =>
+ {
+ b.HasOne("Aberwyn.Models.BudgetPeriod", "BudgetPeriod")
+ .WithMany("Categories")
+ .HasForeignKey("BudgetPeriodId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("BudgetPeriod");
+ });
+
+ modelBuilder.Entity("Aberwyn.Models.BudgetItem", b =>
+ {
+ b.HasOne("Aberwyn.Models.BudgetCategory", "BudgetCategory")
+ .WithMany("Items")
+ .HasForeignKey("BudgetCategoryId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("BudgetCategory");
+ });
+
+ 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("Aberwyn.Models.ApplicationUser", null)
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b =>
+ {
+ b.HasOne("Aberwyn.Models.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("Aberwyn.Models.ApplicationUser", null)
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b =>
+ {
+ b.HasOne("Aberwyn.Models.ApplicationUser", null)
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Aberwyn.Models.BudgetCategory", b =>
+ {
+ b.Navigation("Items");
+ });
+
+ modelBuilder.Entity("Aberwyn.Models.BudgetPeriod", b =>
+ {
+ b.Navigation("Categories");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/Aberwyn/Migrations/20250519213736_AddOrderToBudgetCategory.cs b/Aberwyn/Migrations/20250519213736_AddOrderToBudgetCategory.cs
new file mode 100644
index 0000000..481de48
--- /dev/null
+++ b/Aberwyn/Migrations/20250519213736_AddOrderToBudgetCategory.cs
@@ -0,0 +1,59 @@
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace Aberwyn.Migrations
+{
+ public partial class AddOrderToBudgetCategory : Migration
+ {
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropColumn(
+ name: "Person",
+ table: "BudgetItems");
+
+ migrationBuilder.AddColumn(
+ name: "Order",
+ table: "BudgetPeriods",
+ type: "int",
+ nullable: false,
+ defaultValue: 0);
+
+ migrationBuilder.AddColumn(
+ name: "Order",
+ table: "BudgetItems",
+ type: "int",
+ nullable: false,
+ defaultValue: 0);
+
+ migrationBuilder.AddColumn(
+ name: "Order",
+ table: "BudgetCategories",
+ type: "int",
+ nullable: false,
+ defaultValue: 0);
+ }
+
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropColumn(
+ name: "Order",
+ table: "BudgetPeriods");
+
+ migrationBuilder.DropColumn(
+ name: "Order",
+ table: "BudgetItems");
+
+ migrationBuilder.DropColumn(
+ name: "Order",
+ table: "BudgetCategories");
+
+ migrationBuilder.AddColumn(
+ name: "Person",
+ table: "BudgetItems",
+ type: "longtext",
+ nullable: true)
+ .Annotation("MySql:CharSet", "utf8mb4");
+ }
+ }
+}
diff --git a/Aberwyn/Migrations/ApplicationDbContextModelSnapshot.cs b/Aberwyn/Migrations/ApplicationDbContextModelSnapshot.cs
index 7781902..e9f964c 100644
--- a/Aberwyn/Migrations/ApplicationDbContextModelSnapshot.cs
+++ b/Aberwyn/Migrations/ApplicationDbContextModelSnapshot.cs
@@ -83,6 +83,85 @@ namespace Aberwyn.Migrations
b.ToTable("AspNetUsers", (string)null);
});
+ modelBuilder.Entity("Aberwyn.Models.BudgetCategory", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ b.Property("BudgetPeriodId")
+ .HasColumnType("int");
+
+ b.Property("Color")
+ .IsRequired()
+ .HasColumnType("longtext");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("longtext");
+
+ b.Property("Order")
+ .HasColumnType("int");
+
+ b.HasKey("Id");
+
+ b.HasIndex("BudgetPeriodId");
+
+ b.ToTable("BudgetCategories");
+ });
+
+ modelBuilder.Entity("Aberwyn.Models.BudgetItem", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ b.Property("Amount")
+ .HasColumnType("decimal(65,30)");
+
+ b.Property("BudgetCategoryId")
+ .HasColumnType("int");
+
+ b.Property("IncludeInSummary")
+ .HasColumnType("tinyint(1)");
+
+ b.Property("IsExpense")
+ .HasColumnType("tinyint(1)");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("longtext");
+
+ b.Property("Order")
+ .HasColumnType("int");
+
+ b.HasKey("Id");
+
+ b.HasIndex("BudgetCategoryId");
+
+ b.ToTable("BudgetItems");
+ });
+
+ modelBuilder.Entity("Aberwyn.Models.BudgetPeriod", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ b.Property("Month")
+ .HasColumnType("int");
+
+ b.Property("Order")
+ .HasColumnType("int");
+
+ b.Property("Year")
+ .HasColumnType("int");
+
+ b.HasKey("Id");
+
+ b.ToTable("BudgetPeriods");
+ });
+
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
{
b.Property("Id")
@@ -211,6 +290,28 @@ namespace Aberwyn.Migrations
b.ToTable("AspNetUserTokens", (string)null);
});
+ modelBuilder.Entity("Aberwyn.Models.BudgetCategory", b =>
+ {
+ b.HasOne("Aberwyn.Models.BudgetPeriod", "BudgetPeriod")
+ .WithMany("Categories")
+ .HasForeignKey("BudgetPeriodId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("BudgetPeriod");
+ });
+
+ modelBuilder.Entity("Aberwyn.Models.BudgetItem", b =>
+ {
+ b.HasOne("Aberwyn.Models.BudgetCategory", "BudgetCategory")
+ .WithMany("Items")
+ .HasForeignKey("BudgetCategoryId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("BudgetCategory");
+ });
+
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
@@ -261,6 +362,16 @@ namespace Aberwyn.Migrations
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
+
+ modelBuilder.Entity("Aberwyn.Models.BudgetCategory", b =>
+ {
+ b.Navigation("Items");
+ });
+
+ modelBuilder.Entity("Aberwyn.Models.BudgetPeriod", b =>
+ {
+ b.Navigation("Categories");
+ });
#pragma warning restore 612, 618
}
}
diff --git a/Aberwyn/Models/Budget.cs b/Aberwyn/Models/Budget.cs
new file mode 100644
index 0000000..ee6c29f
--- /dev/null
+++ b/Aberwyn/Models/Budget.cs
@@ -0,0 +1,85 @@
+using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+
+namespace Aberwyn.Models
+{
+ public class BudgetPeriod
+ {
+ public int Id { get; set; }
+ public int Year { get; set; }
+ public int Month { get; set; }
+ public int Order { get; set; }
+ public List Categories { get; set; } = new();
+ }
+
+ public class BudgetCategory
+ {
+ public int Id { get; set; }
+ public string Name { get; set; }
+ public string Color { get; set; } // t.ex. "red", "green", "yellow"
+ public int BudgetPeriodId { get; set; }
+ public int Order { get; set; }
+
+ [JsonIgnore]
+ [ValidateNever]
+ public BudgetPeriod BudgetPeriod { get; set; }
+
+ public List Items { get; set; } = new();
+ }
+
+ public class BudgetItem
+ {
+ public int Id { get; set; }
+ public string Name { get; set; }
+ public decimal Amount { get; set; }
+ public bool IsExpense { get; set; } // true = utgift, false = inkomst/spar
+ public bool IncludeInSummary { get; set; } = true;
+ public int Order { get; set; } //
+
+ public int BudgetCategoryId { get; set; }
+
+ [JsonIgnore]
+ [ValidateNever]
+ public BudgetCategory BudgetCategory { get; set; }
+ }
+ // DTOs/BudgetDto.cs
+ public class BudgetDto
+ {
+ public int Id { get; set; }
+ public int Year { get; set; }
+ public int Month { get; set; }
+ public int Order { get; set; }
+
+ public List Categories { get; set; } = new();
+ }
+
+ public class BudgetCategoryDto
+ {
+ public int Id { get; set; }
+ public string Name { get; set; }
+ public string Color { get; set; }
+ public List Items { get; set; } = new();
+ public int Order { get; set; }
+
+ public int Year { get; set; }
+ public int Month { get; set; }
+ }
+
+ public class BudgetItemDto
+ {
+ public int Id { get; set; }
+ public string Name { get; set; }
+ public decimal Amount { get; set; }
+ public int Order { get; set; }
+
+ public bool IsExpense { get; set; }
+ public bool IncludeInSummary { get; set; }
+ }
+
+
+ public class BudgetModel
+ {
+ public List