From 9e11db157b1a2b8832750647838689f15acc0230 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Tracewicz?= Date: Fri, 27 Sep 2024 21:06:01 +0200 Subject: [PATCH] Closes #60 Introduce Item - List mapping --- Makefile | 5 +- .../bruno/KSummarized/ToDo/Items/Create.bru | 3 +- backend/src/api/Responses/TodoList.cs | 6 +- backend/src/core/TodoItem.cs | 7 +- backend/src/core/TodoList.cs | 3 +- .../Data/ApplicationDbContext.cs | 2 +- backend/src/infrastructure/Data/Tag.cs | 2 +- .../src/infrastructure/Data/TodoItemModel.cs | 4 +- .../src/infrastructure/Data/TodoListModel.cs | 1 + .../src/infrastructure/Data/TodoService.cs | 33 +++- ...7182157_Introduce list mapping.Designer.cs | 171 ++++++++++++++++++ .../20240927182157_Introduce list mapping.cs | 114 ++++++++++++ .../ApplicationDbContextModelSnapshot.cs | 28 ++- 13 files changed, 354 insertions(+), 25 deletions(-) create mode 100644 backend/src/infrastructure/Migrations/20240927182157_Introduce list mapping.Designer.cs create mode 100644 backend/src/infrastructure/Migrations/20240927182157_Introduce list mapping.cs diff --git a/Makefile b/Makefile index fbcc81a..f3e8949 100644 --- a/Makefile +++ b/Makefile @@ -4,5 +4,8 @@ dev: no-reload: docker compose --profile without-hot-reload up --build -.PHONY: dev no-reload +db: + docker compose up db -d + +.PHONY: dev no-reload db .DEFAULT_GOAL := dev diff --git a/backend/bruno/KSummarized/ToDo/Items/Create.bru b/backend/bruno/KSummarized/ToDo/Items/Create.bru index 7e50b18..fd2b3fc 100644 --- a/backend/bruno/KSummarized/ToDo/Items/Create.bru +++ b/backend/bruno/KSummarized/ToDo/Items/Create.bru @@ -44,7 +44,8 @@ body:json { "tags": [], "subtasks": [] } - ] + ], + "listId": {{listId}} } } diff --git a/backend/src/api/Responses/TodoList.cs b/backend/src/api/Responses/TodoList.cs index 9207e35..bc4beac 100644 --- a/backend/src/api/Responses/TodoList.cs +++ b/backend/src/api/Responses/TodoList.cs @@ -1,8 +1,10 @@ +using core; + namespace api.Resonses; -public record TodoList(int Id, string Name); +public record TodoList(int Id, string Name, IEnumerable Items); public static class MapExtensions { - public static TodoList ToResponse(this core.TodoList list) => new TodoList(list.Id, list.Name); + public static TodoList ToResponse(this core.TodoList list) => new TodoList(list.Id, list.Name, list.Items); } diff --git a/backend/src/core/TodoItem.cs b/backend/src/core/TodoItem.cs index dbc2f2e..dec6073 100644 --- a/backend/src/core/TodoItem.cs +++ b/backend/src/core/TodoItem.cs @@ -4,9 +4,10 @@ public record TodoItem { public int? Id { get; set; } public required string Name { get; set; } - public bool Compleated {get; set;} - public DateTime Deadline {get; set;} + public bool Compleated { get; set; } + public DateTime Deadline { get; set; } public string Notes { get; set; } = null!; public required IEnumerable Tags { get; set; } public required IEnumerable Subtasks { get; set; } -} \ No newline at end of file + public int ListId { get; set; } +} diff --git a/backend/src/core/TodoList.cs b/backend/src/core/TodoList.cs index 8392f04..6fe7c76 100644 --- a/backend/src/core/TodoList.cs +++ b/backend/src/core/TodoList.cs @@ -5,6 +5,5 @@ public class TodoList public int Id { get; set; } public required string Name { get; set; } public required Guid Owner { get; set; } - - public bool IsAuthorized(Guid person) => Owner.Equals(person); + public required IEnumerable Items { get; set; } } diff --git a/backend/src/infrastructure/Data/ApplicationDbContext.cs b/backend/src/infrastructure/Data/ApplicationDbContext.cs index f091328..5e9e228 100644 --- a/backend/src/infrastructure/Data/ApplicationDbContext.cs +++ b/backend/src/infrastructure/Data/ApplicationDbContext.cs @@ -6,5 +6,5 @@ public class ApplicationDbContext(DbContextOptions options { public required DbSet TodoLists { get; set; } public required DbSet Todos { get; set; } - public required DbSet Tags { get; set; } + public required DbSet Tags { get; set; } } diff --git a/backend/src/infrastructure/Data/Tag.cs b/backend/src/infrastructure/Data/Tag.cs index 78bd2fa..48f2c3a 100644 --- a/backend/src/infrastructure/Data/Tag.cs +++ b/backend/src/infrastructure/Data/Tag.cs @@ -4,7 +4,7 @@ namespace infrastructure.Data; [Table("Tags")] -public class Tag +public class TagModel { [Key] public int Id { get; set; } diff --git a/backend/src/infrastructure/Data/TodoItemModel.cs b/backend/src/infrastructure/Data/TodoItemModel.cs index d3d1989..a9e976d 100644 --- a/backend/src/infrastructure/Data/TodoItemModel.cs +++ b/backend/src/infrastructure/Data/TodoItemModel.cs @@ -13,8 +13,10 @@ public class TodoItemModel public DateTime Deadline { get; set; } [MaxLength(4096)] public string Notes { get; set; } = null!; - public required ICollection Tags { get; set; } + public required ICollection Tags { get; set; } public required ICollection Subtasks { get; set; } public int? MainTaskId { get; set; } public TodoItemModel? MainTask { get; set; } + public int ListId { get; set; } + public TodoListModel? List { get; set; } } diff --git a/backend/src/infrastructure/Data/TodoListModel.cs b/backend/src/infrastructure/Data/TodoListModel.cs index d838b5b..5494087 100644 --- a/backend/src/infrastructure/Data/TodoListModel.cs +++ b/backend/src/infrastructure/Data/TodoListModel.cs @@ -11,4 +11,5 @@ public class TodoListModel [MaxLength(512)] public required string Name { get; set; } public required Guid Owner { get; set; } + public required ICollection Items { get; set; } } diff --git a/backend/src/infrastructure/Data/TodoService.cs b/backend/src/infrastructure/Data/TodoService.cs index 6e08cf9..38b8717 100644 --- a/backend/src/infrastructure/Data/TodoService.cs +++ b/backend/src/infrastructure/Data/TodoService.cs @@ -21,25 +21,40 @@ public IEnumerable GetLists(Guid user) { return _context.TodoLists.AsNoTracking() .Where(list => list.Owner.Equals(user)) - .Select(list => new TodoList { Id = list.Id, Name = list.Name, Owner = list.Owner }) + .Select(list => new TodoList + { + Id = list.Id, + Name = list.Name, + Owner = list.Owner, + Items = Enumerable.Empty() + }) .AsEnumerable(); } public TodoList? GetList(Guid user, int id) { var list = _context.TodoLists.AsNoTracking() + .Include(l => l.Items) .SingleOrDefault(l => l.Owner.Equals(user) && l.Id == id); - //TODO: Consider creating a mapper instead of this manual new - if (list is not null) { return new() { Id = list.Id, Name = list.Name, Owner = list.Owner }; } + if (list is not null) + { + return new() + { + Id = list.Id, + Name = list.Name, + Owner = list.Owner, + Items = list.Items.Select(i => MapTodoItem(i)).ToList() + }; + }; return null; } public async Task CreateList(Guid user, string name) { - var newList = new TodoListModel() { Name = name, Owner = user }; + var newList = new TodoListModel() { Name = name, Owner = user, Items = [] }; _context.TodoLists.Add(newList); await _context.SaveChangesAsync(); - return new TodoList() { Id = newList.Id, Name = newList.Name, Owner = newList.Owner }; + return new TodoList() { Id = newList.Id, Name = newList.Name, Owner = newList.Owner, Items = [] }; } public bool DeleteList(Guid user, int id) @@ -73,7 +88,8 @@ public async Task CreateItem(Guid user, TodoItem item) Deadline = item.Deadline, Notes = item.Notes, Subtasks = [], - Tags = [] + Tags = [], + ListId = item.ListId }; foreach (var st in item.Subtasks) { @@ -85,7 +101,8 @@ public async Task CreateItem(Guid user, TodoItem item) Deadline = st.Deadline, Notes = st.Notes, Tags = [], - Subtasks = [] + Subtasks = [], + ListId = item.ListId }; newItem.Subtasks.Add(newSubtask); } @@ -204,7 +221,7 @@ public async Task UpdateItem(Guid user, TodoItem item) private static TodoItem MapTodoItem(infrastructure.Data.TodoItemModel item) { - Log.Information("Mapped {item}", JsonSerializer.Serialize(item, new JsonSerializerOptions() { ReferenceHandler = ReferenceHandler.Preserve })); + Log.Debug("Mapped {item}", JsonSerializer.Serialize(item, new JsonSerializerOptions() { ReferenceHandler = ReferenceHandler.Preserve })); return new TodoItem() { Id = item.Id, diff --git a/backend/src/infrastructure/Migrations/20240927182157_Introduce list mapping.Designer.cs b/backend/src/infrastructure/Migrations/20240927182157_Introduce list mapping.Designer.cs new file mode 100644 index 0000000..a9e8f50 --- /dev/null +++ b/backend/src/infrastructure/Migrations/20240927182157_Introduce list mapping.Designer.cs @@ -0,0 +1,171 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using infrastructure.Data; + +#nullable disable + +namespace infrastructure.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20240927182157_Introduce list mapping")] + partial class Introducelistmapping + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("TagModelTodoItemModel", b => + { + b.Property("ItemsId") + .HasColumnType("integer"); + + b.Property("TagsId") + .HasColumnType("integer"); + + b.HasKey("ItemsId", "TagsId"); + + b.HasIndex("TagsId"); + + b.ToTable("TagModelTodoItemModel"); + }); + + modelBuilder.Entity("infrastructure.Data.TagModel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Name") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("Owner") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.ToTable("Tags"); + }); + + modelBuilder.Entity("infrastructure.Data.TodoItemModel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Compleated") + .HasColumnType("boolean"); + + b.Property("Deadline") + .HasColumnType("timestamp with time zone"); + + b.Property("ListId") + .HasColumnType("integer"); + + b.Property("MainTaskId") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("Notes") + .IsRequired() + .HasMaxLength(4096) + .HasColumnType("character varying(4096)"); + + b.Property("Owner") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ListId"); + + b.HasIndex("MainTaskId"); + + b.ToTable("Todos"); + }); + + modelBuilder.Entity("infrastructure.Data.TodoListModel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Name") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("Owner") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.ToTable("todo_lists"); + }); + + modelBuilder.Entity("TagModelTodoItemModel", b => + { + b.HasOne("infrastructure.Data.TodoItemModel", null) + .WithMany() + .HasForeignKey("ItemsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("infrastructure.Data.TagModel", null) + .WithMany() + .HasForeignKey("TagsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("infrastructure.Data.TodoItemModel", b => + { + b.HasOne("infrastructure.Data.TodoListModel", "List") + .WithMany("Items") + .HasForeignKey("ListId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("infrastructure.Data.TodoItemModel", "MainTask") + .WithMany("Subtasks") + .HasForeignKey("MainTaskId"); + + b.Navigation("List"); + + b.Navigation("MainTask"); + }); + + modelBuilder.Entity("infrastructure.Data.TodoItemModel", b => + { + b.Navigation("Subtasks"); + }); + + modelBuilder.Entity("infrastructure.Data.TodoListModel", b => + { + b.Navigation("Items"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/backend/src/infrastructure/Migrations/20240927182157_Introduce list mapping.cs b/backend/src/infrastructure/Migrations/20240927182157_Introduce list mapping.cs new file mode 100644 index 0000000..332ebf1 --- /dev/null +++ b/backend/src/infrastructure/Migrations/20240927182157_Introduce list mapping.cs @@ -0,0 +1,114 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace infrastructure.Migrations +{ + /// + public partial class Introducelistmapping : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "TagTodoItemModel"); + + migrationBuilder.AddColumn( + name: "ListId", + table: "Todos", + type: "integer", + nullable: false, + defaultValue: 0); + + migrationBuilder.CreateTable( + name: "TagModelTodoItemModel", + columns: table => new + { + ItemsId = table.Column(type: "integer", nullable: false), + TagsId = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_TagModelTodoItemModel", x => new { x.ItemsId, x.TagsId }); + table.ForeignKey( + name: "FK_TagModelTodoItemModel_Tags_TagsId", + column: x => x.TagsId, + principalTable: "Tags", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_TagModelTodoItemModel_Todos_ItemsId", + column: x => x.ItemsId, + principalTable: "Todos", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_Todos_ListId", + table: "Todos", + column: "ListId"); + + migrationBuilder.CreateIndex( + name: "IX_TagModelTodoItemModel_TagsId", + table: "TagModelTodoItemModel", + column: "TagsId"); + + migrationBuilder.AddForeignKey( + name: "FK_Todos_todo_lists_ListId", + table: "Todos", + column: "ListId", + principalTable: "todo_lists", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_Todos_todo_lists_ListId", + table: "Todos"); + + migrationBuilder.DropTable( + name: "TagModelTodoItemModel"); + + migrationBuilder.DropIndex( + name: "IX_Todos_ListId", + table: "Todos"); + + migrationBuilder.DropColumn( + name: "ListId", + table: "Todos"); + + migrationBuilder.CreateTable( + name: "TagTodoItemModel", + columns: table => new + { + ItemsId = table.Column(type: "integer", nullable: false), + TagsId = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_TagTodoItemModel", x => new { x.ItemsId, x.TagsId }); + table.ForeignKey( + name: "FK_TagTodoItemModel_Tags_TagsId", + column: x => x.TagsId, + principalTable: "Tags", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_TagTodoItemModel_Todos_ItemsId", + column: x => x.ItemsId, + principalTable: "Todos", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_TagTodoItemModel_TagsId", + table: "TagTodoItemModel", + column: "TagsId"); + } + } +} diff --git a/backend/src/infrastructure/Migrations/ApplicationDbContextModelSnapshot.cs b/backend/src/infrastructure/Migrations/ApplicationDbContextModelSnapshot.cs index 399b238..fb4e1d9 100644 --- a/backend/src/infrastructure/Migrations/ApplicationDbContextModelSnapshot.cs +++ b/backend/src/infrastructure/Migrations/ApplicationDbContextModelSnapshot.cs @@ -22,7 +22,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - modelBuilder.Entity("TagTodoItemModel", b => + modelBuilder.Entity("TagModelTodoItemModel", b => { b.Property("ItemsId") .HasColumnType("integer"); @@ -34,10 +34,10 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("TagsId"); - b.ToTable("TagTodoItemModel"); + b.ToTable("TagModelTodoItemModel"); }); - modelBuilder.Entity("infrastructure.Data.Tag", b => + modelBuilder.Entity("infrastructure.Data.TagModel", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -72,6 +72,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("Deadline") .HasColumnType("timestamp with time zone"); + b.Property("ListId") + .HasColumnType("integer"); + b.Property("MainTaskId") .HasColumnType("integer"); @@ -90,6 +93,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasKey("Id"); + b.HasIndex("ListId"); + b.HasIndex("MainTaskId"); b.ToTable("Todos"); @@ -116,7 +121,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("todo_lists"); }); - modelBuilder.Entity("TagTodoItemModel", b => + modelBuilder.Entity("TagModelTodoItemModel", b => { b.HasOne("infrastructure.Data.TodoItemModel", null) .WithMany() @@ -124,7 +129,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) .OnDelete(DeleteBehavior.Cascade) .IsRequired(); - b.HasOne("infrastructure.Data.Tag", null) + b.HasOne("infrastructure.Data.TagModel", null) .WithMany() .HasForeignKey("TagsId") .OnDelete(DeleteBehavior.Cascade) @@ -133,10 +138,18 @@ protected override void BuildModel(ModelBuilder modelBuilder) modelBuilder.Entity("infrastructure.Data.TodoItemModel", b => { + b.HasOne("infrastructure.Data.TodoListModel", "List") + .WithMany("Items") + .HasForeignKey("ListId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + b.HasOne("infrastructure.Data.TodoItemModel", "MainTask") .WithMany("Subtasks") .HasForeignKey("MainTaskId"); + b.Navigation("List"); + b.Navigation("MainTask"); }); @@ -144,6 +157,11 @@ protected override void BuildModel(ModelBuilder modelBuilder) { b.Navigation("Subtasks"); }); + + modelBuilder.Entity("infrastructure.Data.TodoListModel", b => + { + b.Navigation("Items"); + }); #pragma warning restore 612, 618 } }