Skip to content

Commit

Permalink
Added folder system
Browse files Browse the repository at this point in the history
  • Loading branch information
DavidLazarescu committed Feb 5, 2024
1 parent a0f08ee commit 1aedb7c
Show file tree
Hide file tree
Showing 13 changed files with 364 additions and 0 deletions.
18 changes: 18 additions & 0 deletions src/Application/Common/DTOs/Folders/FolderInDto.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
namespace Application.Common.DTOs.Folders;

public class FolderInDto
{
public string Guid { get; set; }

public string Name { get; set; }

public string Color { get; set; }

public string Icon { get; set; }

public string Description { get; set; }

public string LastModified { get; set; }

public ICollection<FolderInDto> Children { get; set; } = new List<FolderInDto>();
}
20 changes: 20 additions & 0 deletions src/Application/Common/DTOs/Folders/FolderOutDto.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using System.Collections.ObjectModel;

namespace Application.Common.DTOs.Folders;

public class FolderOutDto
{
public string Guid { get; set; }

public string Name { get; set; }

public string Color { get; set; }

public string Icon { get; set; }

public string Description { get; set; }

public string LastModified { get; set; }

public ICollection<FolderOutDto> Children { get; set; } = new Collection<FolderOutDto>();
}
11 changes: 11 additions & 0 deletions src/Application/Interfaces/Repositories/IFolderRepository.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using Domain.Entities;

namespace Application.Interfaces.Repositories;

public interface IFolderRepository
{
public Task SaveChangesAsync();
public Task<Folder> GetFolderAsync(Guid folderId);
public Task CreateFolderAsync(Folder folder);
public void RemoveFolder(Folder folder);
}
9 changes: 9 additions & 0 deletions src/Application/Interfaces/Services/IFolderService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using Application.Common.DTOs.Folders;

namespace Application.Interfaces.Services;

public interface IFolderService
{
public Task UpdateFoldersAsync(string email, FolderInDto folderInDto);
public Task<FolderOutDto> GetFoldersAsync(string email);
}
102 changes: 102 additions & 0 deletions src/Application/Services/FolderService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
using Application.Common.DTOs.Folders;
using Application.Common.Exceptions;
using Application.Interfaces.Repositories;
using Application.Interfaces.Services;
using Domain.Entities;

namespace Application.Services;

public class FolderService(IUserRepository userRepository, IFolderRepository folderRepository) : IFolderService
{
public IUserRepository UserRepository { get; } = userRepository;
public IFolderRepository FolderRepository { get; } = folderRepository;

public async Task UpdateFoldersAsync(string email, FolderInDto folderInDto)
{
var user = await UserRepository.GetAsync(email, true);
if (user == null)
throw new CommonErrorException(400, "No user with this email exists", 17);

var folder = FolderInDtoToFolder(folderInDto);
// If the user has no root folder, create it and set it as the user's root folder
if(user.RootFolderId == Guid.Empty)
{
await FolderRepository.CreateFolderAsync(folder);
user.RootFolderId = folder.FolderId;

await UserRepository.SaveChangesAsync();
return;
}

if(folder.FolderId != user.RootFolderId)
throw new CommonErrorException(400, "Folder id does not match user root folder id", 0);

var existingFolder = await FolderRepository.GetFolderAsync(user.RootFolderId);
if(existingFolder == null)
throw new CommonErrorException(400, "User has no root folder", 0);

FolderRepository.RemoveFolder(existingFolder);
await FolderRepository.CreateFolderAsync(folder);
await FolderRepository.SaveChangesAsync();
}

private Folder FolderInDtoToFolder(FolderInDto folderInDto)
{
var folder = new Folder
{
FolderId = new Guid(folderInDto.Guid),
Name = folderInDto.Name,
Color = folderInDto.Color,
Icon = folderInDto.Icon,
Description = folderInDto.Description,
LastModified = folderInDto.LastModified,
Children = new List<Folder>()
};

foreach (var child in folderInDto.Children)
{
var childFolder = FolderInDtoToFolder(child);
folder.Children.Add(childFolder);
}

return folder;
}

public async Task<FolderOutDto> GetFoldersAsync(string email)
{
var user = await UserRepository.GetAsync(email, false);
if (user == null)
{
throw new CommonErrorException(400, "No user with this email exists", 17);
}

if(user.RootFolderId == Guid.Empty)
{
throw new CommonErrorException(400, "User has no root folder", 22);
}

var folder = await FolderRepository.GetFolderAsync(user.RootFolderId);
return await FolderToFolderOutDto(folder);
}

private async Task<FolderOutDto> FolderToFolderOutDto(Folder folder)
{
var folderOutDto = new FolderOutDto
{
Guid = folder.FolderId.ToString(),
Name = folder.Name,
Color = folder.Color,
Icon = folder.Icon,
LastModified = folder.LastModified,
Description = folder.Description
};

foreach (var child in folder.Children)
{
var childFolderOutDto = await FolderToFolderOutDto(child);
folderOutDto.Children.Add(childFolderOutDto);
}

return folderOutDto;
}
}
30 changes: 30 additions & 0 deletions src/Domain/Entities/Folder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#nullable enable
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace Domain.Entities;

public class Folder
{
[DatabaseGenerated(DatabaseGeneratedOption.None)]
[Key]
public Guid FolderId { get; set; }

[Required]
public string Name { get; set; }

Check warning on line 14 in src/Domain/Entities/Folder.cs

View workflow job for this annotation

GitHub Actions / deploy

Non-nullable property 'Name' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

Check warning on line 14 in src/Domain/Entities/Folder.cs

View workflow job for this annotation

GitHub Actions / test

Non-nullable property 'Name' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

[Required]
public string Color { get; set; }

Check warning on line 17 in src/Domain/Entities/Folder.cs

View workflow job for this annotation

GitHub Actions / deploy

Non-nullable property 'Color' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

Check warning on line 17 in src/Domain/Entities/Folder.cs

View workflow job for this annotation

GitHub Actions / test

Non-nullable property 'Color' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

[Required]
public string Icon { get; set; }

Check warning on line 20 in src/Domain/Entities/Folder.cs

View workflow job for this annotation

GitHub Actions / deploy

Non-nullable property 'Icon' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

Check warning on line 20 in src/Domain/Entities/Folder.cs

View workflow job for this annotation

GitHub Actions / test

Non-nullable property 'Icon' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

public string Description { get; set; }

Check warning on line 22 in src/Domain/Entities/Folder.cs

View workflow job for this annotation

GitHub Actions / deploy

Non-nullable property 'Description' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

Check warning on line 22 in src/Domain/Entities/Folder.cs

View workflow job for this annotation

GitHub Actions / test

Non-nullable property 'Description' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

[Required]
public string LastModified { get; set; }

Check warning on line 25 in src/Domain/Entities/Folder.cs

View workflow job for this annotation

GitHub Actions / deploy

Non-nullable property 'LastModified' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

Check warning on line 25 in src/Domain/Entities/Folder.cs

View workflow job for this annotation

GitHub Actions / test

Non-nullable property 'LastModified' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

public Guid? ParentFolderId { get; set; }
public Folder? ParentFolder { get; set; }
public List<Folder>? Children { get; set; } = new List<Folder>();
}
2 changes: 2 additions & 0 deletions src/Domain/Entities/User.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ public class User : IdentityUser
[Required]
public int AiExplanationRequestsMadeToday { get; set; } = 0;

public Guid RootFolderId { get; set; }

public ICollection<Book> Books { get; set; }
public ICollection<Tag> Tags { get; set; }
}
1 change: 1 addition & 0 deletions src/Infrastructure/Persistence/DataContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ public class DataContext : IdentityDbContext<User>
public DbSet<Bookmark> Bookmarks { get; set; }
public DbSet<Tag> Tags { get; set; }
public DbSet<Product> Products { get; set; }
public DbSet<Folder> Folders { get; set; }

public DataContext(DbContextOptions<DataContext> options) :
base(options)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using Domain.Entities;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;

namespace Infrastructure.Persistence.EntityConfigurations;

public class FolderConfiguration : IEntityTypeConfiguration<Folder>
{
public void Configure(EntityTypeBuilder<Folder> builder)
{
builder
.HasOne(f => f.ParentFolder)
.WithMany(f => f.Children)
.HasForeignKey(f => f.ParentFolderId)
.OnDelete(DeleteBehavior.NoAction);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,41 @@ protected override void BuildModel(ModelBuilder modelBuilder)
b.ToTable("Bookmarks");
});

modelBuilder.Entity("Domain.Entities.Folder", b =>
{
b.Property<Guid>("FolderId")
.HasColumnType("uniqueidentifier");
b.Property<string>("Color")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("Description")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("Icon")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("LastModified")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<Guid?>("ParentFolderId")
.HasColumnType("uniqueidentifier");
b.HasKey("FolderId");
b.HasIndex("ParentFolderId");
b.ToTable("Folders");
});

modelBuilder.Entity("Domain.Entities.Highlight", b =>
{
b.Property<Guid>("HighlightId")
Expand Down Expand Up @@ -349,6 +384,9 @@ protected override void BuildModel(ModelBuilder modelBuilder)
b.Property<DateTime>("ProfilePictureLastUpdated")
.HasColumnType("datetime2");
b.Property<Guid>("RootFolderId")
.HasColumnType("uniqueidentifier");
b.Property<string>("SecurityStamp")
.HasColumnType("nvarchar(max)");
Expand Down Expand Up @@ -530,6 +568,16 @@ protected override void BuildModel(ModelBuilder modelBuilder)
b.Navigation("Book");
});

modelBuilder.Entity("Domain.Entities.Folder", b =>
{
b.HasOne("Domain.Entities.Folder", "ParentFolder")
.WithMany("Children")
.HasForeignKey("ParentFolderId")
.OnDelete(DeleteBehavior.NoAction);
b.Navigation("ParentFolder");
});

modelBuilder.Entity("Domain.Entities.Highlight", b =>
{
b.HasOne("Domain.Entities.Book", "Book")
Expand Down Expand Up @@ -638,6 +686,11 @@ protected override void BuildModel(ModelBuilder modelBuilder)
b.Navigation("Tags");
});

modelBuilder.Entity("Domain.Entities.Folder", b =>
{
b.Navigation("Children");
});

modelBuilder.Entity("Domain.Entities.Highlight", b =>
{
b.Navigation("Rects");
Expand Down
50 changes: 50 additions & 0 deletions src/Infrastructure/Persistence/Repository/FolderRepository.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
using Application.Interfaces.Repositories;
using Domain.Entities;
using Microsoft.EntityFrameworkCore;

namespace Infrastructure.Persistence.Repository;

public class FolderRepository(DataContext context) : IFolderRepository
{
public DataContext Context { get; } = context;

public async Task SaveChangesAsync()
{
await Context.SaveChangesAsync();
}

public async Task<Folder> GetFolderAsync(Guid folderId)
{
return await Context.Folders
.Where(f => f.FolderId == folderId)
.Include(f => f.Children)
.ThenInclude(f => f.Children)
.ThenInclude(f => f.Children)
.ThenInclude(f => f.Children)
.ThenInclude(f => f.Children)
.FirstOrDefaultAsync();
}

public async Task CreateFolderAsync(Folder folder)
{
await Context.Folders.AddAsync(folder);
}

public void RemoveFolder(Folder folder)
{
var allFolders = new List<Folder>();
GetAllChildren(folder, allFolders);
allFolders.Add(folder);

Context.Folders.RemoveRange(allFolders);
}

private void GetAllChildren(Folder folder, List<Folder> allChildren)
{
allChildren.Add(folder);
foreach (var child in folder.Children)
{
GetAllChildren(child, allChildren);
}
}
}
Loading

0 comments on commit 1aedb7c

Please sign in to comment.