From 90f0386bfee33596889e984ef9cd86962928481e Mon Sep 17 00:00:00 2001 From: Alexandr Sulimov Date: Fri, 28 Jul 2023 21:18:18 +0300 Subject: [PATCH] fix --- .../Accounts/AccountAccess.cs | 5 +- .../Accounts/AccountAccessInvite.cs | 15 + MyOffice.Data.Models/Users/User.cs | 2 + .../Account/AccountAccessInviteRepository.cs | 27 + .../Account/AccountAccessRepository.cs | 5 + .../Account/AccountRepository.cs | 11 +- .../Account/IAccountAccessInviteRepository.cs | 10 + .../Account/IAccountAccessRepository.cs | 1 + MyOffice.DbContext/AppDbContext.cs | 22 +- .../20230726160638_AccessOwner.Designer.cs | 663 +++++++++++++++++ .../Migrations/20230726160638_AccessOwner.cs | 49 ++ .../20230726161226_AccessOwner2.Designer.cs | 665 +++++++++++++++++ .../Migrations/20230726161226_AccessOwner2.cs | 60 ++ ...728172249_AccountAccessInvites.Designer.cs | 695 ++++++++++++++++++ .../20230728172249_AccountAccessInvites.cs | 48 ++ .../Migrations/AppDbContextModelSnapshot.cs | 45 ++ MyOffice.SPA/package.json | 2 + MyOffice.SPA/src/app/api-routes.ts | 2 + .../app/model/account.accessRight.model.ts | 2 + MyOffice.SPA/src/app/model/account.model.ts | 1 + .../app/pages/accounts/account.component.scss | 7 + MyOffice.SPA/src/app/pages/pages.module.ts | 2 + .../account/access.account.component.html | 61 ++ .../account/access.account.component.scss | 0 .../account/access.account.component.ts | 131 ++++ .../settings/account/account.component.html | 5 +- .../settings/account/account.component.ts | 14 + .../account/add.account.component.html | 10 +- .../account/edit.account.component.html | 35 +- .../account/edit.account.component.ts | 10 + MyOffice.Services/Account/AccountService.cs | 184 ++++- .../Account/AccountServiceProfile.cs | 30 - .../Account/Domain/AccessInviteStatus.cs | 9 + .../Account/Domain/AccountAccessDto.cs | 14 + ...ccountAddResult.cs => AccountAddStatus.cs} | 2 +- .../Account/Domain/AccountCategoryDto.cs | 5 +- .../Account/Domain/AccountDto.cs | 43 +- ...ountEditResult.cs => AccountEditStatus.cs} | 2 +- ...{MotionAddResult.cs => MotionAddStatus.cs} | 2 +- ...nDeleteResult.cs => MotionDeleteStatus.cs} | 2 +- ...nUpdateResult.cs => MotionUpdateStatus.cs} | 2 +- MyOffice.Services/Account/Domain/UserDto.cs | 2 +- .../Dashboard/Domain/DashboardData.cs | 2 +- .../Mapper/AccountServiceProfile.cs | 31 + MyOffice.Services/Mapper/UserIdResolver.cs | 21 + MyOffice.Web/Controllers/AccountController.cs | 19 +- .../Controllers/SettingsAccountController.cs | 101 ++- .../Attributes/AsGuidAttribute.cs | 35 + .../Models/Account/AccessRightsViewModel.cs | 26 +- .../Models/Account/AccountViewModel.cs | 72 +- .../Models/Account/AccountViewModelProfile.cs | 71 ++ .../Models/Account/ViewModelProfile.cs | 12 + MyOffice.Web/Program.cs | 42 +- 53 files changed, 3067 insertions(+), 267 deletions(-) create mode 100644 MyOffice.Data.Models/Accounts/AccountAccessInvite.cs create mode 100644 MyOffice.Data.Repositories/Account/AccountAccessInviteRepository.cs create mode 100644 MyOffice.Data.Repositories/Account/IAccountAccessInviteRepository.cs create mode 100644 MyOffice.Migration.Postgres/Migrations/20230726160638_AccessOwner.Designer.cs create mode 100644 MyOffice.Migration.Postgres/Migrations/20230726160638_AccessOwner.cs create mode 100644 MyOffice.Migration.Postgres/Migrations/20230726161226_AccessOwner2.Designer.cs create mode 100644 MyOffice.Migration.Postgres/Migrations/20230726161226_AccessOwner2.cs create mode 100644 MyOffice.Migration.Postgres/Migrations/20230728172249_AccountAccessInvites.Designer.cs create mode 100644 MyOffice.Migration.Postgres/Migrations/20230728172249_AccountAccessInvites.cs create mode 100644 MyOffice.SPA/src/app/pages/settings/account/access.account.component.html create mode 100644 MyOffice.SPA/src/app/pages/settings/account/access.account.component.scss create mode 100644 MyOffice.SPA/src/app/pages/settings/account/access.account.component.ts delete mode 100644 MyOffice.Services/Account/AccountServiceProfile.cs create mode 100644 MyOffice.Services/Account/Domain/AccessInviteStatus.cs rename MyOffice.Services/Account/Domain/{AccountAddResult.cs => AccountAddStatus.cs} (80%) rename MyOffice.Services/Account/Domain/{AccountEditResult.cs => AccountEditStatus.cs} (80%) rename MyOffice.Services/Account/Domain/{MotionAddResult.cs => MotionAddStatus.cs} (76%) rename MyOffice.Services/Account/Domain/{MotionDeleteResult.cs => MotionDeleteStatus.cs} (72%) rename MyOffice.Services/Account/Domain/{MotionUpdateResult.cs => MotionUpdateStatus.cs} (72%) create mode 100644 MyOffice.Services/Mapper/AccountServiceProfile.cs create mode 100644 MyOffice.Services/Mapper/UserIdResolver.cs create mode 100644 MyOffice.Web/Infrastructure/Attributes/AsGuidAttribute.cs create mode 100644 MyOffice.Web/Models/Account/AccountViewModelProfile.cs create mode 100644 MyOffice.Web/Models/Account/ViewModelProfile.cs diff --git a/MyOffice.Data.Models/Accounts/AccountAccess.cs b/MyOffice.Data.Models/Accounts/AccountAccess.cs index 4b4e305..0d1aeb3 100644 --- a/MyOffice.Data.Models/Accounts/AccountAccess.cs +++ b/MyOffice.Data.Models/Accounts/AccountAccess.cs @@ -17,9 +17,12 @@ public class AccountAccess public Account? Account { get; set; } public Guid UserId { get; set; } public User? User { get; set; } - + public Guid OwnerId { get; set; } + public User? Owner { get; set; } + public bool IsAllowRead { get; set; } public bool IsAllowWrite { get; set; } public bool IsAllowManage { get; set; } public AccountAccessTypeEnum Type { get; set; } + public string? Name { get; set; } } \ No newline at end of file diff --git a/MyOffice.Data.Models/Accounts/AccountAccessInvite.cs b/MyOffice.Data.Models/Accounts/AccountAccessInvite.cs new file mode 100644 index 0000000..cf59859 --- /dev/null +++ b/MyOffice.Data.Models/Accounts/AccountAccessInvite.cs @@ -0,0 +1,15 @@ +namespace MyOffice.Data.Models.Accounts; + +using Users; + +public class AccountAccessInvite +{ + public Guid Id { get; set; } + public Guid UserId { get; set; } + public User User { get; set; } + public DateTime CreatedOn { get; set; } + public DateTime? AcceptedOn { get; set; } + public DateTime? RejectedOn { get; set; } + public string Email { get; set; } = null!; + public bool IsAllowWrite { get; set; } +} \ No newline at end of file diff --git a/MyOffice.Data.Models/Users/User.cs b/MyOffice.Data.Models/Users/User.cs index 7536438..2246452 100644 --- a/MyOffice.Data.Models/Users/User.cs +++ b/MyOffice.Data.Models/Users/User.cs @@ -30,4 +30,6 @@ public class User public IEnumerable? AccountCategories { get; set; } public IEnumerable? ItemCategories { get; set; } public IEnumerable? Accounts { get; set; } + public IEnumerable? AccountAccessOwners { get; set; } + public IEnumerable? AccountAccessInvites { get; set; } } \ No newline at end of file diff --git a/MyOffice.Data.Repositories/Account/AccountAccessInviteRepository.cs b/MyOffice.Data.Repositories/Account/AccountAccessInviteRepository.cs new file mode 100644 index 0000000..b705f99 --- /dev/null +++ b/MyOffice.Data.Repositories/Account/AccountAccessInviteRepository.cs @@ -0,0 +1,27 @@ +namespace MyOffice.Data.Repositories.Account; + +using Models.Accounts; +using MyOffice.Data.Repositories; + +public class AccountAccessInviteRepository : AppRepository, IAccountAccessInviteRepository +{ + public AccountAccessInvite? Get(Guid userId, string email) + { + return _context.AccountAccessInvites + .FirstOrDefault(x => x.UserId == userId + && x.Email == email + && !x.AcceptedOn.HasValue + && !x.RejectedOn.HasValue + ); + } + + public bool Add(AccountAccessInvite invite) + { + return AddBase(invite) > 0; + } + + public bool Update(AccountAccessInvite invite) + { + return UpdateBase(invite) > 0; + } +} \ No newline at end of file diff --git a/MyOffice.Data.Repositories/Account/AccountAccessRepository.cs b/MyOffice.Data.Repositories/Account/AccountAccessRepository.cs index 5c5424c..aa5b267 100644 --- a/MyOffice.Data.Repositories/Account/AccountAccessRepository.cs +++ b/MyOffice.Data.Repositories/Account/AccountAccessRepository.cs @@ -10,4 +10,9 @@ public class AccountAccessRepository : AppRepository, IAccountAcc { return UpdateBase(accountAccess) > 0; } + + public bool Delete(AccountAccess accountAccess) + { + return RemoveBase(accountAccess) > 0; + } } \ No newline at end of file diff --git a/MyOffice.Data.Repositories/Account/AccountRepository.cs b/MyOffice.Data.Repositories/Account/AccountRepository.cs index 7dbd16f..1812e1b 100644 --- a/MyOffice.Data.Repositories/Account/AccountRepository.cs +++ b/MyOffice.Data.Repositories/Account/AccountRepository.cs @@ -1,16 +1,25 @@ namespace MyOffice.Data.Repositories.Account; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; using Models.Accounts; using MyOffice.Data.Repositories; public class AccountRepository : AppRepository, IAccountRepository { + private readonly ILogger _logger; + public AccountRepository( + ILogger logger + ) + { + _logger = logger; + } + public List GetAll(Guid userId) { return _context.Accounts! .Include(x => x.CurrencyGlobal) - .Include(x => x.Categories)! + .Include(x => x.Categories!.Where(x => x.Category.UserId == userId))! .ThenInclude(x => x.Category) .Include(x => x.AccessRights)! .ThenInclude(x => x.User) diff --git a/MyOffice.Data.Repositories/Account/IAccountAccessInviteRepository.cs b/MyOffice.Data.Repositories/Account/IAccountAccessInviteRepository.cs new file mode 100644 index 0000000..9f9167b --- /dev/null +++ b/MyOffice.Data.Repositories/Account/IAccountAccessInviteRepository.cs @@ -0,0 +1,10 @@ +namespace MyOffice.Data.Repositories.Account; + +using Models.Accounts; + +public interface IAccountAccessInviteRepository +{ + AccountAccessInvite? Get(Guid userId, string email); + bool Add(AccountAccessInvite invite); + bool Update(AccountAccessInvite invite); +} \ No newline at end of file diff --git a/MyOffice.Data.Repositories/Account/IAccountAccessRepository.cs b/MyOffice.Data.Repositories/Account/IAccountAccessRepository.cs index 627dbee..fe2cf60 100644 --- a/MyOffice.Data.Repositories/Account/IAccountAccessRepository.cs +++ b/MyOffice.Data.Repositories/Account/IAccountAccessRepository.cs @@ -5,4 +5,5 @@ using Models.Accounts; public interface IAccountAccessRepository { bool Update(AccountAccess accountAccess); + bool Delete(AccountAccess accountAccess); } \ No newline at end of file diff --git a/MyOffice.DbContext/AppDbContext.cs b/MyOffice.DbContext/AppDbContext.cs index 3ba5af7..a736003 100644 --- a/MyOffice.DbContext/AppDbContext.cs +++ b/MyOffice.DbContext/AppDbContext.cs @@ -39,6 +39,7 @@ public class AppDbContext : DbContext public DbSet Accounts { get; set; } = null!; public DbSet AccountAccountCategories { get; set; } = null!; public DbSet AccountAccesses { get; set; } = null!; + public DbSet AccountAccessInvites { get; set; } = null!; public DbSet ItemCategories { get; set; } = null!; public DbSet ItemGlobals { get; set; } = null!; @@ -71,14 +72,6 @@ public class AppDbContext : DbContext .Property(x => x.Email) .UseCollation(noCaseCollation); - /*modelBuilder.Entity() - .Property(x => x.Name) - .UseCollation(noCaseCollation);*/ - - /*modelBuilder.Entity() - .Property(x => x.Name) - .UseCollation(noCaseCollation);*/ - modelBuilder.Entity() .HasOne(x => x.User) @@ -141,6 +134,9 @@ public class AppDbContext : DbContext modelBuilder.Entity() .HasKey(x => x.Id); + modelBuilder.Entity() + .HasKey(x => x.Id); + modelBuilder.Entity() .HasKey(x => x.Id); @@ -170,6 +166,16 @@ public class AppDbContext : DbContext .WithMany(x => x.AccountAccess) .HasForeignKey(x => x.UserId); + modelBuilder.Entity() + .HasOne(x => x.Owner) + .WithMany(x => x.AccountAccessOwners) + .HasForeignKey(x => x.OwnerId); + + modelBuilder.Entity() + .HasOne(x => x.User) + .WithMany(x => x.AccountAccessInvites) + .HasForeignKey(x => x.UserId); + modelBuilder.Entity() .HasOne(x => x.Account) .WithMany(x => x.Motions) diff --git a/MyOffice.Migration.Postgres/Migrations/20230726160638_AccessOwner.Designer.cs b/MyOffice.Migration.Postgres/Migrations/20230726160638_AccessOwner.Designer.cs new file mode 100644 index 0000000..4e0b046 --- /dev/null +++ b/MyOffice.Migration.Postgres/Migrations/20230726160638_AccessOwner.Designer.cs @@ -0,0 +1,663 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using MyOffice.DbContext; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace MyOffice.Migrations.Postgres.Migrations +{ + [DbContext(typeof(AppDbContext))] + [Migration("20230726160638_AccessOwner")] + partial class AccessOwner + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("Npgsql:CollationDefinition:my_ci_collation", "en-u-ks-primary,en-u-ks-primary,icu,False") + .HasAnnotation("ProductVersion", "7.0.0") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("MyOffice.Data.Models.Accounts.Account", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CurrencyGlobalId") + .IsRequired() + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("OwnerId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("CurrencyGlobalId"); + + b.HasIndex("OwnerId"); + + b.ToTable("Accounts"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Accounts.AccountAccess", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AccountId") + .HasColumnType("uuid"); + + b.Property("IsAllowManage") + .HasColumnType("boolean"); + + b.Property("IsAllowRead") + .HasColumnType("boolean"); + + b.Property("IsAllowWrite") + .HasColumnType("boolean"); + + b.Property("OwnerId") + .HasColumnType("uuid"); + + b.Property("Type") + .IsRequired() + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("AccountId"); + + b.HasIndex("OwnerId"); + + b.HasIndex("UserId"); + + b.ToTable("AccountAccesses"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Accounts.AccountAccountCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AccountId") + .HasColumnType("uuid"); + + b.Property("CategoryId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("AccountId"); + + b.HasIndex("CategoryId"); + + b.ToTable("AccountAccountCategories"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Accounts.AccountCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AccountCategories"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Accounts.Motion", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccountId") + .HasColumnType("uuid"); + + b.Property("AmountMinus") + .HasPrecision(18, 6) + .HasColumnType("numeric(18,6)"); + + b.Property("AmountPlus") + .HasPrecision(18, 6) + .HasColumnType("numeric(18,6)"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("DateTime") + .HasColumnType("timestamp with time zone"); + + b.Property("DeletedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("ItemId") + .HasColumnType("integer"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("AccountId"); + + b.HasIndex("ItemId"); + + b.HasIndex("UserId"); + + b.ToTable("Motions"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Currencies.Currency", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CurrencyGlobalId") + .IsRequired() + .HasColumnType("text"); + + b.Property("CurrentRateId") + .HasColumnType("integer"); + + b.Property("IsPrimary") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("ShortName") + .IsRequired() + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("CurrencyGlobalId"); + + b.HasIndex("CurrentRateId"); + + b.HasIndex("UserId"); + + b.ToTable("Currencies"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Currencies.CurrencyGlobal", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("DefaultQuantity") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Symbol") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("CurrencyGlobals"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Currencies.CurrencyRate", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CurrencyId") + .HasColumnType("uuid"); + + b.Property("DateTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Quantity") + .HasColumnType("integer"); + + b.Property("Rate") + .HasColumnType("numeric"); + + b.HasKey("Id"); + + b.HasIndex("CurrencyId"); + + b.ToTable("CurrencyRates"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Items.Item", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CategoryId") + .HasColumnType("uuid"); + + b.Property("ItemGlobalId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("CategoryId"); + + b.HasIndex("ItemGlobalId"); + + b.ToTable("Items"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Items.ItemCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("IsInternal") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("ItemCategories"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Items.ItemGlobal", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("ItemGlobals"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Users.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CurrencyId") + .IsRequired() + .HasColumnType("text"); + + b.Property("Email") + .IsRequired() + .HasColumnType("text") + .UseCollation("my_ci_collation"); + + b.Property("FirstName") + .HasColumnType("text"); + + b.Property("FullName") + .HasColumnType("text"); + + b.Property("IsEmailConfirmed") + .HasColumnType("boolean"); + + b.Property("LastName") + .HasColumnType("text"); + + b.Property("PasswordHash") + .IsRequired() + .HasColumnType("text"); + + b.Property("Phone") + .HasColumnType("text"); + + b.Property("UserName") + .IsRequired() + .HasColumnType("text") + .UseCollation("my_ci_collation"); + + b.HasKey("Id"); + + b.HasIndex("CurrencyId"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Users.UserExternal", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .IsRequired() + .HasColumnType("text") + .UseCollation("my_ci_collation"); + + b.Property("ExternalId") + .IsRequired() + .HasColumnType("text"); + + b.Property("Provider") + .IsRequired() + .HasColumnType("text") + .UseCollation("my_ci_collation"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("UserClaims"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Accounts.Account", b => + { + b.HasOne("MyOffice.Data.Models.Currencies.CurrencyGlobal", "CurrencyGlobal") + .WithMany("Accounts") + .HasForeignKey("CurrencyGlobalId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("MyOffice.Data.Models.Users.User", "Owner") + .WithMany("Accounts") + .HasForeignKey("OwnerId"); + + b.Navigation("CurrencyGlobal"); + + b.Navigation("Owner"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Accounts.AccountAccess", b => + { + b.HasOne("MyOffice.Data.Models.Accounts.Account", "Account") + .WithMany("AccessRights") + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("MyOffice.Data.Models.Users.User", "Owner") + .WithMany("AccountAccessOwners") + .HasForeignKey("OwnerId"); + + b.HasOne("MyOffice.Data.Models.Users.User", "User") + .WithMany("AccountAccess") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Account"); + + b.Navigation("Owner"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Accounts.AccountAccountCategory", b => + { + b.HasOne("MyOffice.Data.Models.Accounts.Account", "Account") + .WithMany("Categories") + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("MyOffice.Data.Models.Accounts.AccountCategory", "Category") + .WithMany("Accounts") + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Account"); + + b.Navigation("Category"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Accounts.AccountCategory", b => + { + b.HasOne("MyOffice.Data.Models.Users.User", "User") + .WithMany("AccountCategories") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Accounts.Motion", b => + { + b.HasOne("MyOffice.Data.Models.Accounts.Account", "Account") + .WithMany("Motions") + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("MyOffice.Data.Models.Items.Item", "Item") + .WithMany("Motions") + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("MyOffice.Data.Models.Users.User", null) + .WithMany("AccountMotions") + .HasForeignKey("UserId"); + + b.Navigation("Account"); + + b.Navigation("Item"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Currencies.Currency", b => + { + b.HasOne("MyOffice.Data.Models.Currencies.CurrencyGlobal", "CurrencyGlobal") + .WithMany("Currencies") + .HasForeignKey("CurrencyGlobalId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("MyOffice.Data.Models.Currencies.CurrencyRate", "CurrentRate") + .WithMany("Currencies") + .HasForeignKey("CurrentRateId"); + + b.HasOne("MyOffice.Data.Models.Users.User", "User") + .WithMany("Currencies") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CurrencyGlobal"); + + b.Navigation("CurrentRate"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Currencies.CurrencyRate", b => + { + b.HasOne("MyOffice.Data.Models.Currencies.Currency", "Currency") + .WithMany("Rates") + .HasForeignKey("CurrencyId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Currency"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Items.Item", b => + { + b.HasOne("MyOffice.Data.Models.Items.ItemCategory", "Category") + .WithMany("Items") + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("MyOffice.Data.Models.Items.ItemGlobal", "ItemGlobal") + .WithMany("Items") + .HasForeignKey("ItemGlobalId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Category"); + + b.Navigation("ItemGlobal"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Items.ItemCategory", b => + { + b.HasOne("MyOffice.Data.Models.Users.User", "User") + .WithMany("ItemCategories") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Users.User", b => + { + b.HasOne("MyOffice.Data.Models.Currencies.CurrencyGlobal", "Currency") + .WithMany() + .HasForeignKey("CurrencyId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Currency"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Users.UserExternal", b => + { + b.HasOne("MyOffice.Data.Models.Users.User", "User") + .WithMany("UserClaims") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Accounts.Account", b => + { + b.Navigation("AccessRights"); + + b.Navigation("Categories"); + + b.Navigation("Motions"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Accounts.AccountCategory", b => + { + b.Navigation("Accounts"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Currencies.Currency", b => + { + b.Navigation("Rates"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Currencies.CurrencyGlobal", b => + { + b.Navigation("Accounts"); + + b.Navigation("Currencies"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Currencies.CurrencyRate", b => + { + b.Navigation("Currencies"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Items.Item", b => + { + b.Navigation("Motions"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Items.ItemCategory", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Items.ItemGlobal", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Users.User", b => + { + b.Navigation("AccountAccess"); + + b.Navigation("AccountAccessOwners"); + + b.Navigation("AccountCategories"); + + b.Navigation("AccountMotions"); + + b.Navigation("Accounts"); + + b.Navigation("Currencies"); + + b.Navigation("ItemCategories"); + + b.Navigation("UserClaims"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/MyOffice.Migration.Postgres/Migrations/20230726160638_AccessOwner.cs b/MyOffice.Migration.Postgres/Migrations/20230726160638_AccessOwner.cs new file mode 100644 index 0000000..5b12a91 --- /dev/null +++ b/MyOffice.Migration.Postgres/Migrations/20230726160638_AccessOwner.cs @@ -0,0 +1,49 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace MyOffice.Migrations.Postgres.Migrations +{ + /// + public partial class AccessOwner : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "OwnerId", + table: "AccountAccesses", + type: "uuid", + nullable: true); + + migrationBuilder.CreateIndex( + name: "IX_AccountAccesses_OwnerId", + table: "AccountAccesses", + column: "OwnerId"); + + migrationBuilder.AddForeignKey( + name: "FK_AccountAccesses_Users_OwnerId", + table: "AccountAccesses", + column: "OwnerId", + principalTable: "Users", + principalColumn: "Id"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_AccountAccesses_Users_OwnerId", + table: "AccountAccesses"); + + migrationBuilder.DropIndex( + name: "IX_AccountAccesses_OwnerId", + table: "AccountAccesses"); + + migrationBuilder.DropColumn( + name: "OwnerId", + table: "AccountAccesses"); + } + } +} diff --git a/MyOffice.Migration.Postgres/Migrations/20230726161226_AccessOwner2.Designer.cs b/MyOffice.Migration.Postgres/Migrations/20230726161226_AccessOwner2.Designer.cs new file mode 100644 index 0000000..0615901 --- /dev/null +++ b/MyOffice.Migration.Postgres/Migrations/20230726161226_AccessOwner2.Designer.cs @@ -0,0 +1,665 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using MyOffice.DbContext; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace MyOffice.Migrations.Postgres.Migrations +{ + [DbContext(typeof(AppDbContext))] + [Migration("20230726161226_AccessOwner2")] + partial class AccessOwner2 + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("Npgsql:CollationDefinition:my_ci_collation", "en-u-ks-primary,en-u-ks-primary,icu,False") + .HasAnnotation("ProductVersion", "7.0.0") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("MyOffice.Data.Models.Accounts.Account", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CurrencyGlobalId") + .IsRequired() + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("OwnerId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("CurrencyGlobalId"); + + b.HasIndex("OwnerId"); + + b.ToTable("Accounts"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Accounts.AccountAccess", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AccountId") + .HasColumnType("uuid"); + + b.Property("IsAllowManage") + .HasColumnType("boolean"); + + b.Property("IsAllowRead") + .HasColumnType("boolean"); + + b.Property("IsAllowWrite") + .HasColumnType("boolean"); + + b.Property("OwnerId") + .HasColumnType("uuid"); + + b.Property("Type") + .IsRequired() + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("AccountId"); + + b.HasIndex("OwnerId"); + + b.HasIndex("UserId"); + + b.ToTable("AccountAccesses"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Accounts.AccountAccountCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AccountId") + .HasColumnType("uuid"); + + b.Property("CategoryId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("AccountId"); + + b.HasIndex("CategoryId"); + + b.ToTable("AccountAccountCategories"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Accounts.AccountCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AccountCategories"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Accounts.Motion", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccountId") + .HasColumnType("uuid"); + + b.Property("AmountMinus") + .HasPrecision(18, 6) + .HasColumnType("numeric(18,6)"); + + b.Property("AmountPlus") + .HasPrecision(18, 6) + .HasColumnType("numeric(18,6)"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("DateTime") + .HasColumnType("timestamp with time zone"); + + b.Property("DeletedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("ItemId") + .HasColumnType("integer"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("AccountId"); + + b.HasIndex("ItemId"); + + b.HasIndex("UserId"); + + b.ToTable("Motions"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Currencies.Currency", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CurrencyGlobalId") + .IsRequired() + .HasColumnType("text"); + + b.Property("CurrentRateId") + .HasColumnType("integer"); + + b.Property("IsPrimary") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("ShortName") + .IsRequired() + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("CurrencyGlobalId"); + + b.HasIndex("CurrentRateId"); + + b.HasIndex("UserId"); + + b.ToTable("Currencies"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Currencies.CurrencyGlobal", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("DefaultQuantity") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Symbol") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("CurrencyGlobals"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Currencies.CurrencyRate", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CurrencyId") + .HasColumnType("uuid"); + + b.Property("DateTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Quantity") + .HasColumnType("integer"); + + b.Property("Rate") + .HasColumnType("numeric"); + + b.HasKey("Id"); + + b.HasIndex("CurrencyId"); + + b.ToTable("CurrencyRates"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Items.Item", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CategoryId") + .HasColumnType("uuid"); + + b.Property("ItemGlobalId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("CategoryId"); + + b.HasIndex("ItemGlobalId"); + + b.ToTable("Items"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Items.ItemCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("IsInternal") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("ItemCategories"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Items.ItemGlobal", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("ItemGlobals"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Users.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CurrencyId") + .IsRequired() + .HasColumnType("text"); + + b.Property("Email") + .IsRequired() + .HasColumnType("text") + .UseCollation("my_ci_collation"); + + b.Property("FirstName") + .HasColumnType("text"); + + b.Property("FullName") + .HasColumnType("text"); + + b.Property("IsEmailConfirmed") + .HasColumnType("boolean"); + + b.Property("LastName") + .HasColumnType("text"); + + b.Property("PasswordHash") + .IsRequired() + .HasColumnType("text"); + + b.Property("Phone") + .HasColumnType("text"); + + b.Property("UserName") + .IsRequired() + .HasColumnType("text") + .UseCollation("my_ci_collation"); + + b.HasKey("Id"); + + b.HasIndex("CurrencyId"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Users.UserExternal", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .IsRequired() + .HasColumnType("text") + .UseCollation("my_ci_collation"); + + b.Property("ExternalId") + .IsRequired() + .HasColumnType("text"); + + b.Property("Provider") + .IsRequired() + .HasColumnType("text") + .UseCollation("my_ci_collation"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("UserClaims"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Accounts.Account", b => + { + b.HasOne("MyOffice.Data.Models.Currencies.CurrencyGlobal", "CurrencyGlobal") + .WithMany("Accounts") + .HasForeignKey("CurrencyGlobalId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("MyOffice.Data.Models.Users.User", "Owner") + .WithMany("Accounts") + .HasForeignKey("OwnerId"); + + b.Navigation("CurrencyGlobal"); + + b.Navigation("Owner"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Accounts.AccountAccess", b => + { + b.HasOne("MyOffice.Data.Models.Accounts.Account", "Account") + .WithMany("AccessRights") + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("MyOffice.Data.Models.Users.User", "Owner") + .WithMany("AccountAccessOwners") + .HasForeignKey("OwnerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("MyOffice.Data.Models.Users.User", "User") + .WithMany("AccountAccess") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Account"); + + b.Navigation("Owner"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Accounts.AccountAccountCategory", b => + { + b.HasOne("MyOffice.Data.Models.Accounts.Account", "Account") + .WithMany("Categories") + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("MyOffice.Data.Models.Accounts.AccountCategory", "Category") + .WithMany("Accounts") + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Account"); + + b.Navigation("Category"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Accounts.AccountCategory", b => + { + b.HasOne("MyOffice.Data.Models.Users.User", "User") + .WithMany("AccountCategories") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Accounts.Motion", b => + { + b.HasOne("MyOffice.Data.Models.Accounts.Account", "Account") + .WithMany("Motions") + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("MyOffice.Data.Models.Items.Item", "Item") + .WithMany("Motions") + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("MyOffice.Data.Models.Users.User", null) + .WithMany("AccountMotions") + .HasForeignKey("UserId"); + + b.Navigation("Account"); + + b.Navigation("Item"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Currencies.Currency", b => + { + b.HasOne("MyOffice.Data.Models.Currencies.CurrencyGlobal", "CurrencyGlobal") + .WithMany("Currencies") + .HasForeignKey("CurrencyGlobalId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("MyOffice.Data.Models.Currencies.CurrencyRate", "CurrentRate") + .WithMany("Currencies") + .HasForeignKey("CurrentRateId"); + + b.HasOne("MyOffice.Data.Models.Users.User", "User") + .WithMany("Currencies") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CurrencyGlobal"); + + b.Navigation("CurrentRate"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Currencies.CurrencyRate", b => + { + b.HasOne("MyOffice.Data.Models.Currencies.Currency", "Currency") + .WithMany("Rates") + .HasForeignKey("CurrencyId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Currency"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Items.Item", b => + { + b.HasOne("MyOffice.Data.Models.Items.ItemCategory", "Category") + .WithMany("Items") + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("MyOffice.Data.Models.Items.ItemGlobal", "ItemGlobal") + .WithMany("Items") + .HasForeignKey("ItemGlobalId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Category"); + + b.Navigation("ItemGlobal"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Items.ItemCategory", b => + { + b.HasOne("MyOffice.Data.Models.Users.User", "User") + .WithMany("ItemCategories") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Users.User", b => + { + b.HasOne("MyOffice.Data.Models.Currencies.CurrencyGlobal", "Currency") + .WithMany() + .HasForeignKey("CurrencyId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Currency"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Users.UserExternal", b => + { + b.HasOne("MyOffice.Data.Models.Users.User", "User") + .WithMany("UserClaims") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Accounts.Account", b => + { + b.Navigation("AccessRights"); + + b.Navigation("Categories"); + + b.Navigation("Motions"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Accounts.AccountCategory", b => + { + b.Navigation("Accounts"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Currencies.Currency", b => + { + b.Navigation("Rates"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Currencies.CurrencyGlobal", b => + { + b.Navigation("Accounts"); + + b.Navigation("Currencies"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Currencies.CurrencyRate", b => + { + b.Navigation("Currencies"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Items.Item", b => + { + b.Navigation("Motions"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Items.ItemCategory", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Items.ItemGlobal", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Users.User", b => + { + b.Navigation("AccountAccess"); + + b.Navigation("AccountAccessOwners"); + + b.Navigation("AccountCategories"); + + b.Navigation("AccountMotions"); + + b.Navigation("Accounts"); + + b.Navigation("Currencies"); + + b.Navigation("ItemCategories"); + + b.Navigation("UserClaims"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/MyOffice.Migration.Postgres/Migrations/20230726161226_AccessOwner2.cs b/MyOffice.Migration.Postgres/Migrations/20230726161226_AccessOwner2.cs new file mode 100644 index 0000000..457656b --- /dev/null +++ b/MyOffice.Migration.Postgres/Migrations/20230726161226_AccessOwner2.cs @@ -0,0 +1,60 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace MyOffice.Migrations.Postgres.Migrations +{ + /// + public partial class AccessOwner2 : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_AccountAccesses_Users_OwnerId", + table: "AccountAccesses"); + + migrationBuilder.AlterColumn( + name: "OwnerId", + table: "AccountAccesses", + type: "uuid", + nullable: false, + defaultValue: new Guid("00000000-0000-0000-0000-000000000000"), + oldClrType: typeof(Guid), + oldType: "uuid", + oldNullable: true); + + migrationBuilder.AddForeignKey( + name: "FK_AccountAccesses_Users_OwnerId", + table: "AccountAccesses", + column: "OwnerId", + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_AccountAccesses_Users_OwnerId", + table: "AccountAccesses"); + + migrationBuilder.AlterColumn( + name: "OwnerId", + table: "AccountAccesses", + type: "uuid", + nullable: true, + oldClrType: typeof(Guid), + oldType: "uuid"); + + migrationBuilder.AddForeignKey( + name: "FK_AccountAccesses_Users_OwnerId", + table: "AccountAccesses", + column: "OwnerId", + principalTable: "Users", + principalColumn: "Id"); + } + } +} diff --git a/MyOffice.Migration.Postgres/Migrations/20230728172249_AccountAccessInvites.Designer.cs b/MyOffice.Migration.Postgres/Migrations/20230728172249_AccountAccessInvites.Designer.cs new file mode 100644 index 0000000..a5d9362 --- /dev/null +++ b/MyOffice.Migration.Postgres/Migrations/20230728172249_AccountAccessInvites.Designer.cs @@ -0,0 +1,695 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using MyOffice.DbContext; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace MyOffice.Migrations.Postgres.Migrations +{ + [DbContext(typeof(AppDbContext))] + [Migration("20230728172249_AccountAccessInvites")] + partial class AccountAccessInvites + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("Npgsql:CollationDefinition:my_ci_collation", "en-u-ks-primary,en-u-ks-primary,icu,False") + .HasAnnotation("ProductVersion", "7.0.0") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("MyOffice.Data.Models.Accounts.Account", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CurrencyGlobalId") + .IsRequired() + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("OwnerId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("CurrencyGlobalId"); + + b.HasIndex("OwnerId"); + + b.ToTable("Accounts"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Accounts.AccountAccess", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AccountId") + .HasColumnType("uuid"); + + b.Property("IsAllowManage") + .HasColumnType("boolean"); + + b.Property("IsAllowRead") + .HasColumnType("boolean"); + + b.Property("IsAllowWrite") + .HasColumnType("boolean"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("OwnerId") + .HasColumnType("uuid"); + + b.Property("Type") + .IsRequired() + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("AccountId"); + + b.HasIndex("OwnerId"); + + b.HasIndex("UserId"); + + b.ToTable("AccountAccesses"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Accounts.AccountAccessInvite", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AcceptedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsAllowWrite") + .HasColumnType("boolean"); + + b.Property("RejectedOn") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.ToTable("AccountAccessInvites"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Accounts.AccountAccountCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AccountId") + .HasColumnType("uuid"); + + b.Property("CategoryId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("AccountId"); + + b.HasIndex("CategoryId"); + + b.ToTable("AccountAccountCategories"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Accounts.AccountCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AccountCategories"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Accounts.Motion", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccountId") + .HasColumnType("uuid"); + + b.Property("AmountMinus") + .HasPrecision(18, 6) + .HasColumnType("numeric(18,6)"); + + b.Property("AmountPlus") + .HasPrecision(18, 6) + .HasColumnType("numeric(18,6)"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("DateTime") + .HasColumnType("timestamp with time zone"); + + b.Property("DeletedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("ItemId") + .HasColumnType("integer"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("AccountId"); + + b.HasIndex("ItemId"); + + b.HasIndex("UserId"); + + b.ToTable("Motions"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Currencies.Currency", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CurrencyGlobalId") + .IsRequired() + .HasColumnType("text"); + + b.Property("CurrentRateId") + .HasColumnType("integer"); + + b.Property("IsPrimary") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("ShortName") + .IsRequired() + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("CurrencyGlobalId"); + + b.HasIndex("CurrentRateId"); + + b.HasIndex("UserId"); + + b.ToTable("Currencies"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Currencies.CurrencyGlobal", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("DefaultQuantity") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Symbol") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("CurrencyGlobals"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Currencies.CurrencyRate", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CurrencyId") + .HasColumnType("uuid"); + + b.Property("DateTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Quantity") + .HasColumnType("integer"); + + b.Property("Rate") + .HasColumnType("numeric"); + + b.HasKey("Id"); + + b.HasIndex("CurrencyId"); + + b.ToTable("CurrencyRates"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Items.Item", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CategoryId") + .HasColumnType("uuid"); + + b.Property("ItemGlobalId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("CategoryId"); + + b.HasIndex("ItemGlobalId"); + + b.ToTable("Items"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Items.ItemCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("IsInternal") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("ItemCategories"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Items.ItemGlobal", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("ItemGlobals"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Users.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CurrencyId") + .IsRequired() + .HasColumnType("text"); + + b.Property("Email") + .IsRequired() + .HasColumnType("text") + .UseCollation("my_ci_collation"); + + b.Property("FirstName") + .HasColumnType("text"); + + b.Property("FullName") + .HasColumnType("text"); + + b.Property("IsEmailConfirmed") + .HasColumnType("boolean"); + + b.Property("LastName") + .HasColumnType("text"); + + b.Property("PasswordHash") + .IsRequired() + .HasColumnType("text"); + + b.Property("Phone") + .HasColumnType("text"); + + b.Property("UserName") + .IsRequired() + .HasColumnType("text") + .UseCollation("my_ci_collation"); + + b.HasKey("Id"); + + b.HasIndex("CurrencyId"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Users.UserExternal", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .IsRequired() + .HasColumnType("text") + .UseCollation("my_ci_collation"); + + b.Property("ExternalId") + .IsRequired() + .HasColumnType("text"); + + b.Property("Provider") + .IsRequired() + .HasColumnType("text") + .UseCollation("my_ci_collation"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("UserClaims"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Accounts.Account", b => + { + b.HasOne("MyOffice.Data.Models.Currencies.CurrencyGlobal", "CurrencyGlobal") + .WithMany("Accounts") + .HasForeignKey("CurrencyGlobalId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("MyOffice.Data.Models.Users.User", "Owner") + .WithMany("Accounts") + .HasForeignKey("OwnerId"); + + b.Navigation("CurrencyGlobal"); + + b.Navigation("Owner"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Accounts.AccountAccess", b => + { + b.HasOne("MyOffice.Data.Models.Accounts.Account", "Account") + .WithMany("AccessRights") + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("MyOffice.Data.Models.Users.User", "Owner") + .WithMany("AccountAccessOwners") + .HasForeignKey("OwnerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("MyOffice.Data.Models.Users.User", "User") + .WithMany("AccountAccess") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Account"); + + b.Navigation("Owner"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Accounts.AccountAccountCategory", b => + { + b.HasOne("MyOffice.Data.Models.Accounts.Account", "Account") + .WithMany("Categories") + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("MyOffice.Data.Models.Accounts.AccountCategory", "Category") + .WithMany("Accounts") + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Account"); + + b.Navigation("Category"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Accounts.AccountCategory", b => + { + b.HasOne("MyOffice.Data.Models.Users.User", "User") + .WithMany("AccountCategories") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Accounts.Motion", b => + { + b.HasOne("MyOffice.Data.Models.Accounts.Account", "Account") + .WithMany("Motions") + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("MyOffice.Data.Models.Items.Item", "Item") + .WithMany("Motions") + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("MyOffice.Data.Models.Users.User", null) + .WithMany("AccountMotions") + .HasForeignKey("UserId"); + + b.Navigation("Account"); + + b.Navigation("Item"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Currencies.Currency", b => + { + b.HasOne("MyOffice.Data.Models.Currencies.CurrencyGlobal", "CurrencyGlobal") + .WithMany("Currencies") + .HasForeignKey("CurrencyGlobalId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("MyOffice.Data.Models.Currencies.CurrencyRate", "CurrentRate") + .WithMany("Currencies") + .HasForeignKey("CurrentRateId"); + + b.HasOne("MyOffice.Data.Models.Users.User", "User") + .WithMany("Currencies") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CurrencyGlobal"); + + b.Navigation("CurrentRate"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Currencies.CurrencyRate", b => + { + b.HasOne("MyOffice.Data.Models.Currencies.Currency", "Currency") + .WithMany("Rates") + .HasForeignKey("CurrencyId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Currency"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Items.Item", b => + { + b.HasOne("MyOffice.Data.Models.Items.ItemCategory", "Category") + .WithMany("Items") + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("MyOffice.Data.Models.Items.ItemGlobal", "ItemGlobal") + .WithMany("Items") + .HasForeignKey("ItemGlobalId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Category"); + + b.Navigation("ItemGlobal"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Items.ItemCategory", b => + { + b.HasOne("MyOffice.Data.Models.Users.User", "User") + .WithMany("ItemCategories") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Users.User", b => + { + b.HasOne("MyOffice.Data.Models.Currencies.CurrencyGlobal", "Currency") + .WithMany() + .HasForeignKey("CurrencyId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Currency"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Users.UserExternal", b => + { + b.HasOne("MyOffice.Data.Models.Users.User", "User") + .WithMany("UserClaims") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Accounts.Account", b => + { + b.Navigation("AccessRights"); + + b.Navigation("Categories"); + + b.Navigation("Motions"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Accounts.AccountCategory", b => + { + b.Navigation("Accounts"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Currencies.Currency", b => + { + b.Navigation("Rates"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Currencies.CurrencyGlobal", b => + { + b.Navigation("Accounts"); + + b.Navigation("Currencies"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Currencies.CurrencyRate", b => + { + b.Navigation("Currencies"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Items.Item", b => + { + b.Navigation("Motions"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Items.ItemCategory", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Items.ItemGlobal", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Users.User", b => + { + b.Navigation("AccountAccess"); + + b.Navigation("AccountAccessOwners"); + + b.Navigation("AccountCategories"); + + b.Navigation("AccountMotions"); + + b.Navigation("Accounts"); + + b.Navigation("Currencies"); + + b.Navigation("ItemCategories"); + + b.Navigation("UserClaims"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/MyOffice.Migration.Postgres/Migrations/20230728172249_AccountAccessInvites.cs b/MyOffice.Migration.Postgres/Migrations/20230728172249_AccountAccessInvites.cs new file mode 100644 index 0000000..46bed99 --- /dev/null +++ b/MyOffice.Migration.Postgres/Migrations/20230728172249_AccountAccessInvites.cs @@ -0,0 +1,48 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace MyOffice.Migrations.Postgres.Migrations +{ + /// + public partial class AccountAccessInvites : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "Name", + table: "AccountAccesses", + type: "text", + nullable: true); + + migrationBuilder.CreateTable( + name: "AccountAccessInvites", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + CreatedOn = table.Column(type: "timestamp with time zone", nullable: false), + AcceptedOn = table.Column(type: "timestamp with time zone", nullable: true), + RejectedOn = table.Column(type: "timestamp with time zone", nullable: true), + Email = table.Column(type: "text", nullable: false), + IsAllowWrite = table.Column(type: "boolean", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AccountAccessInvites", x => x.Id); + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "AccountAccessInvites"); + + migrationBuilder.DropColumn( + name: "Name", + table: "AccountAccesses"); + } + } +} diff --git a/MyOffice.Migration.Postgres/Migrations/AppDbContextModelSnapshot.cs b/MyOffice.Migration.Postgres/Migrations/AppDbContextModelSnapshot.cs index 544aa65..9c3cc9a 100644 --- a/MyOffice.Migration.Postgres/Migrations/AppDbContextModelSnapshot.cs +++ b/MyOffice.Migration.Postgres/Migrations/AppDbContextModelSnapshot.cs @@ -69,6 +69,12 @@ namespace MyOffice.Migrations.Postgres.Migrations b.Property("IsAllowWrite") .HasColumnType("boolean"); + b.Property("Name") + .HasColumnType("text"); + + b.Property("OwnerId") + .HasColumnType("uuid"); + b.Property("Type") .IsRequired() .HasColumnType("text"); @@ -80,11 +86,40 @@ namespace MyOffice.Migrations.Postgres.Migrations b.HasIndex("AccountId"); + b.HasIndex("OwnerId"); + b.HasIndex("UserId"); b.ToTable("AccountAccesses"); }); + modelBuilder.Entity("MyOffice.Data.Models.Accounts.AccountAccessInvite", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AcceptedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsAllowWrite") + .HasColumnType("boolean"); + + b.Property("RejectedOn") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.ToTable("AccountAccessInvites"); + }); + modelBuilder.Entity("MyOffice.Data.Models.Accounts.AccountAccountCategory", b => { b.Property("Id") @@ -427,6 +462,12 @@ namespace MyOffice.Migrations.Postgres.Migrations .OnDelete(DeleteBehavior.Cascade) .IsRequired(); + b.HasOne("MyOffice.Data.Models.Users.User", "Owner") + .WithMany("AccountAccessOwners") + .HasForeignKey("OwnerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + b.HasOne("MyOffice.Data.Models.Users.User", "User") .WithMany("AccountAccess") .HasForeignKey("UserId") @@ -435,6 +476,8 @@ namespace MyOffice.Migrations.Postgres.Migrations b.Navigation("Account"); + b.Navigation("Owner"); + b.Navigation("User"); }); @@ -629,6 +672,8 @@ namespace MyOffice.Migrations.Postgres.Migrations { b.Navigation("AccountAccess"); + b.Navigation("AccountAccessOwners"); + b.Navigation("AccountCategories"); b.Navigation("AccountMotions"); diff --git a/MyOffice.SPA/package.json b/MyOffice.SPA/package.json index a4dd4e3..7becff2 100644 --- a/MyOffice.SPA/package.json +++ b/MyOffice.SPA/package.json @@ -55,6 +55,7 @@ "chart.js": "^4.2.1", "core-js": "^3.28.0", "echarts": "^5.4.1", + "lodash": "^4.17.21", "moment": "^2.29.4", "ng-apexcharts": "^1.7.4", "ng-image-fullscreen-view": "^3.0.3", @@ -84,6 +85,7 @@ "@types/ckeditor__ckeditor5-build-classic": "^29.0.1", "@types/d3": "^7.4.0", "@types/jasmine": "~4.3.0", + "@types/lodash": "^4.14.196", "@typescript-eslint/eslint-plugin": "5.48.2", "@typescript-eslint/parser": "5.48.2", "eslint": "^8.33.0", diff --git a/MyOffice.SPA/src/app/api-routes.ts b/MyOffice.SPA/src/app/api-routes.ts index 15fb899..36cd7f7 100644 --- a/MyOffice.SPA/src/app/api-routes.ts +++ b/MyOffice.SPA/src/app/api-routes.ts @@ -15,6 +15,8 @@ export class ApiRoutes { static SettingsAccounts = '/api/settings/accounts'; static SettingsAccount = '/api/settings/accounts/:id'; + static SettingsAccountAccesses = '/api/settings/accounts/:id/access'; + static SettingsAccountAccess = '/api/settings/accounts/:id/access/:access'; static SettingsAccountAccountCategory = '/api/settings/accounts/:id/category/:categoryId'; static SettingsItemCategories = '/api/settings/item-categories'; diff --git a/MyOffice.SPA/src/app/model/account.accessRight.model.ts b/MyOffice.SPA/src/app/model/account.accessRight.model.ts index 5c0b0d3..0836e3f 100644 --- a/MyOffice.SPA/src/app/model/account.accessRight.model.ts +++ b/MyOffice.SPA/src/app/model/account.accessRight.model.ts @@ -5,4 +5,6 @@ export interface AccountAccessRightModel { isAllowRead: boolean; isAllowWrite: boolean; isAllowManage: boolean; + isAllowDelete: boolean; + isOwner: boolean; } diff --git a/MyOffice.SPA/src/app/model/account.model.ts b/MyOffice.SPA/src/app/model/account.model.ts index 4631b7d..4e4cf5d 100644 --- a/MyOffice.SPA/src/app/model/account.model.ts +++ b/MyOffice.SPA/src/app/model/account.model.ts @@ -8,6 +8,7 @@ export interface AccountModel { currencyName?: string, type?: string, allowDelete: boolean, + allowManage: boolean, categories?: AccountCategoryModel[], accessRights?: AccountAccessRightModel[], } diff --git a/MyOffice.SPA/src/app/pages/accounts/account.component.scss b/MyOffice.SPA/src/app/pages/accounts/account.component.scss index e69de29..d30fa0b 100644 --- a/MyOffice.SPA/src/app/pages/accounts/account.component.scss +++ b/MyOffice.SPA/src/app/pages/accounts/account.component.scss @@ -0,0 +1,7 @@ +account-motion:nth-child(even) { + filter: brightness(1) +} + +account-motion:nth-child(odd) { + filter: brightness(1.75) +} diff --git a/MyOffice.SPA/src/app/pages/pages.module.ts b/MyOffice.SPA/src/app/pages/pages.module.ts index b159d11..e0e82f6 100644 --- a/MyOffice.SPA/src/app/pages/pages.module.ts +++ b/MyOffice.SPA/src/app/pages/pages.module.ts @@ -33,6 +33,7 @@ import { SettingsAccountCategoryComponent } from './settings/account/account.cat import { SettingsAccountComponent } from './settings/account/account.component'; import { SettingsAccountAddComponent } from './settings/account/add.account.component'; import { SettingsAccountEditComponent } from './settings/account/edit.account.component'; +import { SettingsAccountAccessComponent } from './settings/account/access.account.component'; import { CurrencyService } from '../services/currency.service'; import { ItemService } from '../services/item.service'; import { SettingsItemCategoryComponent } from './settings/item/item.category.component'; @@ -55,6 +56,7 @@ import { SettingsItemCategoryEditComponent } from './settings/item/edit.item.cat SettingsAccountComponent, SettingsAccountAddComponent, SettingsAccountEditComponent, + SettingsAccountAccessComponent, SettingsItemCategoryComponent, SettingsItemComponent, diff --git a/MyOffice.SPA/src/app/pages/settings/account/access.account.component.html b/MyOffice.SPA/src/app/pages/settings/account/access.account.component.html new file mode 100644 index 0000000..a69cac8 --- /dev/null +++ b/MyOffice.SPA/src/app/pages/settings/account/access.account.component.html @@ -0,0 +1,61 @@ +
+

+ Account access rights +

+
+
+ +
+
+ +
+ + Email + + +
+
+
+
User rights
+
+ + Allow write + +
+
+
+
+
+
+ + {{access.user.email}} + (owner) +
+
+ + Allow write + +
+
+ +
+
+
+ + +
+
+ {{errorMessage}} +
+
+
+
+ + +
+
+
+ +
+
+
diff --git a/MyOffice.SPA/src/app/pages/settings/account/access.account.component.scss b/MyOffice.SPA/src/app/pages/settings/account/access.account.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/MyOffice.SPA/src/app/pages/settings/account/access.account.component.ts b/MyOffice.SPA/src/app/pages/settings/account/access.account.component.ts new file mode 100644 index 0000000..392d7fb --- /dev/null +++ b/MyOffice.SPA/src/app/pages/settings/account/access.account.component.ts @@ -0,0 +1,131 @@ +// angular +import { Component } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Inject } from '@angular/core'; +import { UntypedFormBuilder } from '@angular/forms'; +import { UntypedFormGroup } from '@angular/forms'; +import { FormArray } from '@angular/forms'; +import { FormGroup } from '@angular/forms'; +import { Validators } from '@angular/forms'; + +// libs +import Swal from 'sweetalert2'; +import { MAT_DIALOG_DATA } from '@angular/material/dialog'; +import * as ld from 'lodash'; + +// app +import { ApiRoutes } from '../../../api-routes'; +import { AccountModel } from '../../../model/account.model'; +import { MatDialogRef } from '@angular/material/dialog'; +import { CurrencyService } from '../../../services/currency.service'; +import { CurrencyModel } from '../../../model/currency.model'; +import { AccountCategoryService } from '../../../services/account.category.service'; +import { AccountCategoryModel } from '../../../model/account.category.model'; +import { AccountAccessRightModel } from '../../../model/account.accessRight.model'; +import { AccountService } from '../../../services/account.service'; + +@Component({ + templateUrl: './access.account.component.html', + styleUrls: ['./access.account.component.scss'], +}) +export class SettingsAccountAccessComponent { + public editForm!: UntypedFormGroup; + public errorMessage?: string; + public currencies?: CurrencyModel[]; + public categories?: AccountCategoryModel[]; + public types = new Map(); + + public accessRightsSorted: AccountAccessRightModel[]; + + constructor( + private fb: UntypedFormBuilder, + private httpClient: HttpClient, + private dialogRef: MatDialogRef, + private accountService: AccountService, + @Inject(MAT_DIALOG_DATA) public account: AccountModel + ) { + this.types = this.accountService.getTypes(); + + this.accessRightsSorted = ld.orderBy(account.accessRights!, ['isOwner', 'user.email'], ['desc', 'asc']); + + } + + ngOnInit(): void { + + var accesses = this.fb.array([]); + + this.editForm = this.fb.group({ + email: [ + '', + ], + allowWrite: [ + false, + ], + accesses: accesses, + }); + + this.accessRightsSorted.forEach((x, i) => { + var g = this.fb.group({ + userId: [ + x.user.id + ], + allowWrite: [{ + value: x.isAllowWrite, + disabled: !x.isAllowDelete, + }], + }); + accesses.push(g); + }); + } + + removeAccessRight(accessRight: AccountAccessRightModel) { + Swal.fire({ + title: 'Delete access ' + accessRight.user.email, + showCancelButton: true, + confirmButtonText: 'Delete', + showLoaderOnConfirm: true, + preConfirm: (name) => { + return new Promise((resolve, reject) => { + var url = ApiRoutes.SettingsAccountAccess + .replace(':id', this.account.id!) + .replace(':access', accessRight.user.id); + + this.errorMessage = undefined; + this.httpClient + .delete(url, this.editForm.value) + .subscribe(response => { + resolve(response); + }, error => { + Swal.showValidationMessage(error.detail); + reject(); + }); + + }).catch(x => { + return false; + }); + }, + allowOutsideClick: () => !Swal.isLoading(), + }).then((result) => { + if (result.isConfirmed) { + this.dialogRef.close({ refresh: true }); + } + }); + } + + closeDialog(): void { + this.dialogRef.close(); + } + + onSubmitClick() { + if (this.editForm.valid) { + this.errorMessage = undefined; + this.httpClient + .post(ApiRoutes.SettingsAccountAccesses.replace(':id', this.account.id!), this.editForm.value) + .subscribe(response => { + this.dialogRef.close({ refresh: true }); + }, error => { + this.errorMessage = error.detail; + }); + } + } +} diff --git a/MyOffice.SPA/src/app/pages/settings/account/account.component.html b/MyOffice.SPA/src/app/pages/settings/account/account.component.html index 4207782..e60c1f4 100644 --- a/MyOffice.SPA/src/app/pages/settings/account/account.component.html +++ b/MyOffice.SPA/src/app/pages/settings/account/account.component.html @@ -35,7 +35,10 @@ - + diff --git a/MyOffice.SPA/src/app/pages/settings/account/account.component.ts b/MyOffice.SPA/src/app/pages/settings/account/account.component.ts index b558604..4829470 100644 --- a/MyOffice.SPA/src/app/pages/settings/account/account.component.ts +++ b/MyOffice.SPA/src/app/pages/settings/account/account.component.ts @@ -12,6 +12,7 @@ import { AccountModel } from '../../../model/account.model'; import { AccountCategoryModel } from '../../../model/account.category.model'; import { SettingsAccountAddComponent } from './add.account.component'; import { SettingsAccountEditComponent } from './edit.account.component'; +import { SettingsAccountAccessComponent } from './access.account.component'; import { AccountCategoryService } from '../../../services/account.category.service'; @Component({ @@ -106,4 +107,17 @@ export class SettingsAccountComponent { this.loadAccounts(); }); } + + public access(account: AccountModel) { + this.dialogModel.open(SettingsAccountAccessComponent, { + width: '640px', + disableClose: true, + data: account, + }).afterClosed().subscribe(x => { + if (x && x.refresh) { + this.loadAccounts(); + } + }); + + } } diff --git a/MyOffice.SPA/src/app/pages/settings/account/add.account.component.html b/MyOffice.SPA/src/app/pages/settings/account/add.account.component.html index 5b6981a..fa412a9 100644 --- a/MyOffice.SPA/src/app/pages/settings/account/add.account.component.html +++ b/MyOffice.SPA/src/app/pages/settings/account/add.account.component.html @@ -43,7 +43,7 @@
-
+
Category @@ -55,14 +55,6 @@
-
-
- - User - - -
-
diff --git a/MyOffice.SPA/src/app/pages/settings/account/edit.account.component.html b/MyOffice.SPA/src/app/pages/settings/account/edit.account.component.html index 706b2f6..34c94d1 100644 --- a/MyOffice.SPA/src/app/pages/settings/account/edit.account.component.html +++ b/MyOffice.SPA/src/app/pages/settings/account/edit.account.component.html @@ -45,7 +45,7 @@
-
+
@@ -62,7 +62,7 @@
- +
{{category.name}}
- -
- -
-
- - Category - - - {{category.name}} - - - -
-
-
- - - - - - - -
{{accessRight.user.email}} - -
-
-
-
diff --git a/MyOffice.SPA/src/app/pages/settings/account/edit.account.component.ts b/MyOffice.SPA/src/app/pages/settings/account/edit.account.component.ts index 040da43..37f0310 100644 --- a/MyOffice.SPA/src/app/pages/settings/account/edit.account.component.ts +++ b/MyOffice.SPA/src/app/pages/settings/account/edit.account.component.ts @@ -9,6 +9,7 @@ import { Validators } from '@angular/forms'; // libs import Swal from 'sweetalert2'; import { MAT_DIALOG_DATA } from '@angular/material/dialog'; +import * as ld from 'lodash'; // app import { ApiRoutes } from '../../../api-routes'; @@ -32,6 +33,9 @@ export class SettingsAccountEditComponent { public categories?: AccountCategoryModel[]; public types = new Map(); + public categoriesSorted: AccountCategoryModel[]; + public accessRightsSorted: AccountAccessRightModel[]; + constructor( private fb: UntypedFormBuilder, private httpClient: HttpClient, @@ -42,6 +46,9 @@ export class SettingsAccountEditComponent { @Inject(MAT_DIALOG_DATA) public account: AccountModel ) { this.types = this.accountService.getTypes(); + + this.categoriesSorted = ld.orderBy(account.categories!, ['name'], ['asc']); + this.accessRightsSorted = ld.orderBy(account.accessRights!, ['isOwner', 'user.email'], ['desc', 'asc']); } ngOnInit(): void { @@ -63,6 +70,9 @@ export class SettingsAccountEditComponent { type: [ this.account.type, ], + userEmail: [ + '', + ], }); this.currencyService diff --git a/MyOffice.Services/Account/AccountService.cs b/MyOffice.Services/Account/AccountService.cs index 9e5ff54..b75568a 100644 --- a/MyOffice.Services/Account/AccountService.cs +++ b/MyOffice.Services/Account/AccountService.cs @@ -13,6 +13,7 @@ using Item.Domain; using Microsoft.Extensions.Logging; using MyOffice.Services.Account.Domain; + using System.Collections.Generic; public class AccountService { @@ -20,6 +21,7 @@ private readonly IMapper _mapper; private readonly IAccountCategoryRepository _accountCategoryRepository; private readonly IAccountAccessRepository _accountAccessRepository; + private readonly IAccountAccessInviteRepository _accountAccessInviteRepository; private readonly IAccountRepository _accountRepository; private readonly ICurrencyRepository _currencyRepository; private readonly IAccountAccountCategoryRepository _accountAccountCategoryRepository; @@ -33,6 +35,7 @@ IMapper mapper, IAccountCategoryRepository accountCategoryRepository, IAccountAccessRepository accountAccessRepository, + IAccountAccessInviteRepository accountAccessInviteRepository, IAccountRepository accountRepository, ICurrencyRepository currencyRepository, IAccountAccountCategoryRepository accountAccountCategoryRepository, @@ -46,6 +49,7 @@ _mapper = mapper; _accountCategoryRepository = accountCategoryRepository; _accountAccessRepository = accountAccessRepository; + _accountAccessInviteRepository = accountAccessInviteRepository; _accountRepository = accountRepository; _currencyRepository = currencyRepository; _accountAccountCategoryRepository = accountAccountCategoryRepository; @@ -55,14 +59,16 @@ _itemService = itemService; } - public List GetAllCategories(Guid userId) + public List GetAllCategories(Guid userId) { - return _accountCategoryRepository.GetAll(userId); + var categories = _accountCategoryRepository.GetAll(userId); + + return _mapper.Map>(categories); } - public Exec GetCategory(Guid userId, Guid id) + public Exec GetCategory(Guid userId, Guid id) { - var result = new Exec(GeneralExecStatus.success); + var result = new Exec(GeneralExecStatus.success); var category = _accountCategoryRepository.Get(userId, id); @@ -71,15 +77,15 @@ return result.Set(GeneralExecStatus.not_found); } - return result.Set(category); + return result.Set(_mapper.Map(category)); } - public Exec CategoryAdd(Guid userId, AccountCategory category) + public Exec CategoryAdd(Guid userId, AccountCategory category) { if (category == null) throw new ArgumentNullException(nameof(category)); - var result = new Exec(GeneralExecStatus.success); + var result = new Exec(GeneralExecStatus.success); category.Id = Guid.NewGuid(); category.UserId = userId; @@ -89,15 +95,15 @@ return result.Set(GeneralExecStatus.failure); } - return result.Set(category); + return result.Set(_mapper.Map(category)); } - public Exec CategoryUpdate(Guid userId, Guid id, AccountCategory category) + public Exec CategoryUpdate(Guid userId, Guid id, AccountCategory category) { if (category == null) throw new ArgumentNullException(nameof(category)); - var result = new Exec(GeneralExecStatus.success); + var result = new Exec(GeneralExecStatus.success); var exists = _accountCategoryRepository.Get(userId, id); if (exists == null) @@ -112,12 +118,12 @@ return result.Set(GeneralExecStatus.failure); } - return result.Set(exists); + return result.Set(_mapper.Map(exists)); } - public Exec CategoryRemove(Guid userId, Guid id) + public Exec CategoryRemove(Guid userId, Guid id) { - var result = new Exec(AccountCategoryRemoveResult.success); + var result = new Exec(AccountCategoryRemoveResult.success); var exists = _accountCategoryRepository.Get(userId, id); if (exists == null) @@ -134,12 +140,14 @@ return result.Set(AccountCategoryRemoveResult.failure); } - return result.Set(exists); + return result.Set(_mapper.Map(exists)); } public List GetAllAccounts(Guid userId) { - return _mapper.Map>(_accountRepository.GetAll(userId)); + var accounts = _accountRepository.GetAll(userId); + + return _mapper.Map>(accounts, o => o.Items.Add("UserId", userId)); } public List GetByCategory(Guid userId, Guid categoryId) @@ -165,22 +173,22 @@ return result.Set(_mapper.Map(account)); } - public Exec AccountAdd(Guid userId, AccountAdd input) + public Exec AccountAdd(Guid userId, AccountAdd input) { if (input == null) throw new ArgumentNullException(nameof(input)); - var result = new Exec(AccountAddResult.success); + var result = new Exec(AccountAddStatus.success); var category = _accountCategoryRepository.Get(userId, input.CategoryId); if (category == null) { - return result.Set(AccountAddResult.category_not_found); + return result.Set(AccountAddStatus.category_not_found); } var currency = _currencyRepository.GetByGlobalCurrency(userId, input.CurrencyId); if (currency == null) { - return result.Set(AccountAddResult.currency_not_found); + return result.Set(AccountAddStatus.currency_not_found); } var account = new Account @@ -188,6 +196,7 @@ Id = Guid.NewGuid(), Name = input.Name, CurrencyGlobalId = currency.CurrencyGlobalId, + OwnerId = userId, }; account.Categories = new List @@ -204,6 +213,7 @@ { AccountId = account.Id, UserId = userId, + OwnerId = userId, IsAllowManage = true, IsAllowRead = true, IsAllowWrite = true, @@ -212,7 +222,7 @@ if (!_accountRepository.Add(account)) { - return result.Set(AccountAddResult.failure); + return result.Set(AccountAddStatus.failure); } var access = account.AccessRights!.FirstOrDefault(x => x.UserId == userId); @@ -249,12 +259,12 @@ return result; } - public Exec AccountUpdate(Guid userId, Guid accountId, AccountEdit input) + public Exec AccountUpdate(Guid userId, Guid accountId, AccountEdit input) { if (input == null) throw new ArgumentNullException(nameof(input)); - var result = new Exec(AccountEditResult.success); + var result = new Exec(AccountEditStatus.success); AccountCategory? category = null; if (input.CategoryId.HasValue) @@ -262,20 +272,20 @@ category = _accountCategoryRepository.Get(userId, input.CategoryId.Value); if (category == null) { - return result.Set(AccountEditResult.category_not_found); + return result.Set(AccountEditStatus.category_not_found); } } var currency = _currencyRepository.GetByGlobalCurrency(userId, input.CurrencyId); if (currency == null) { - return result.Set(AccountEditResult.currency_not_found); + return result.Set(AccountEditStatus.currency_not_found); } var account = _accountRepository.Get(userId, accountId); if (account == null) { - return result.Set(AccountEditResult.not_found); + return result.Set(AccountEditStatus.not_found); } account.Name = input.Name; @@ -298,7 +308,7 @@ if (!_accountRepository.Update(account)) { - return result.Set(AccountEditResult.failure); + return result.Set(AccountEditStatus.failure); } var access = account.AccessRights!.FirstOrDefault(x => x.UserId == userId); @@ -332,7 +342,7 @@ return result.Set(categories); } - public Exec, MotionAddResult> MotionAdd( + public Exec, MotionAddStatus> MotionAdd( Guid userId, Guid accountId, MotionAddUpdate motion @@ -343,18 +353,18 @@ if (motion.Item == null) throw new ArgumentNullException(nameof(motion.Item)); - var result = new Exec, MotionAddResult>(MotionAddResult.success); + var result = new Exec, MotionAddStatus>(MotionAddStatus.success); var account = _accountRepository.Get(userId, accountId); if (account == null) { - return result.Set(MotionAddResult.account_not_found); + return result.Set(MotionAddStatus.account_not_found); } var itemExec = _itemService.GetOrCreate(userId, motion.Item); if (itemExec.Status != ItemGetOrAddResult.success) { - return result.Set(MotionAddResult.failure); + return result.Set(MotionAddStatus.failure); } var motionDb = new Motion @@ -371,7 +381,7 @@ if (!_motionRepository.Add(motionDb)) { - return result.Set(MotionAddResult.failure); + return result.Set(MotionAddStatus.failure); } result.Set(new List()); @@ -415,7 +425,7 @@ return result; } - public Exec MotionUpdate( + public Exec MotionUpdate( Guid userId, Guid accountId, Guid motionId, @@ -427,18 +437,18 @@ if (motion.Item == null) throw new ArgumentNullException(nameof(motion.Item)); - var result = new Exec(MotionUpdateResult.success); + var result = new Exec(MotionUpdateStatus.success); var exists = _motionRepository.Get(userId, motionId); if (exists == null || exists.AccountId != accountId) { - return result.Set(MotionUpdateResult.not_found); + return result.Set(MotionUpdateStatus.not_found); } var itemExec = _itemService.GetOrCreate(userId, motion.Item); if (itemExec.Status != ItemGetOrAddResult.success) { - return result.Set(MotionUpdateResult.failure); + return result.Set(MotionUpdateStatus.failure); } exists.Item.Id = itemExec.Result!.Id; @@ -449,7 +459,7 @@ if (!_motionRepository.Update(exists)) { - return result.Set(MotionUpdateResult.failure); + return result.Set(MotionUpdateStatus.failure); } @@ -476,23 +486,23 @@ return result.Set(motions); } - public Exec MotionRemove( + public Exec MotionRemove( Guid userId, Guid accountId, Guid motionId ) { - var result = new Exec(MotionDeleteResult.success); + var result = new Exec(MotionDeleteStatus.success); var exists = _motionRepository.Get(userId, motionId); if (exists == null || exists.AccountId != accountId) { - return result.Set(MotionDeleteResult.not_found); + return result.Set(MotionDeleteStatus.not_found); } if (!_motionRepository.Remove(exists)) { - return result.Set(MotionDeleteResult.failure); + return result.Set(MotionDeleteStatus.failure); } return result.Set(exists); @@ -502,5 +512,99 @@ { return _accountRepository.FindAccounts(userId, term); } + + public Exec AccessInvite(Guid userId, Guid accountId, string email, bool isAllowWrite) + { + if (email == null) + throw new ArgumentNullException(nameof(email)); + + var result = new Exec(AccessInviteStatus.success); + + var account = _accountRepository.Get(userId, accountId); + if (account == null) + { + return result.Set(AccessInviteStatus.account_not_found); + } + + if (account.AccessRights!.Any(x => x.User!.Email.EqualsIgnoreCase(email))) + { + return result.Set(AccessInviteStatus.account_not_found); + } + + var access = _accountAccessInviteRepository.Get(userId, email); + if (access != null) + { + return result.Set(AccessInviteStatus.access_exists); + } + + var invite = new AccountAccessInvite + { + Id = Guid.NewGuid(), + CreatedOn = DateTime.UtcNow, + UserId = userId, + Email = email, + IsAllowWrite = isAllowWrite, + }; + + _accountAccessInviteRepository.Add(invite); + + return result.Set(invite); + } + + public Exec AccessUpdate(Guid userId, Guid accountId, List accesses) + { + if (accesses == null) + throw new ArgumentNullException(nameof(accesses)); + + var result = new Exec(GeneralExecStatus.success); + + var account = _accountRepository.Get(userId, accountId); + if (account == null) + { + return result.Set(GeneralExecStatus.not_found); + } + + foreach (var access in account.AccessRights!) + { + var newAccess = accesses.FirstOrDefault(x => x.UserId == access.UserId); + if (newAccess != null && newAccess.IsAllowWrite != access.IsAllowWrite) + { + access.IsAllowWrite = newAccess.IsAllowWrite; + _accountAccessRepository.Update(access); + } + } + + account = _accountRepository.Get(userId, accountId); + + return result.Set(_mapper.Map(account)); + } + + public Exec AccessDelete(Guid userId, Guid accountId, Guid accessUserId) + { + var result = new Exec(GeneralExecStatus.success); + + var account = _accountRepository.Get(userId, accountId); + if (account == null || !account.AccessRights!.Any() || account.AccessRights!.Count() == 1) + { + return result.Set(GeneralExecStatus.not_found); + } + + var access = account.AccessRights!.FirstOrDefault(x => x.UserId == accessUserId); + if (access == null) + { + return result.Set(GeneralExecStatus.not_found); + } + if (access.OwnerId == access.UserId) + { + return result.Set(GeneralExecStatus.not_found); + } + + _accountAccessRepository.Delete(access); + + account = _accountRepository.Get(userId, accountId); + + return result.Set(_mapper.Map(account)); + + } } } diff --git a/MyOffice.Services/Account/AccountServiceProfile.cs b/MyOffice.Services/Account/AccountServiceProfile.cs deleted file mode 100644 index cb8346c..0000000 --- a/MyOffice.Services/Account/AccountServiceProfile.cs +++ /dev/null @@ -1,30 +0,0 @@ -using MyOffice.Services.Account.Domain; - -namespace MyOffice.Services.Account; - -using AutoMapper; -using Data.Models.Accounts; -using Data.Models.Users; - -public class AccountServiceProfile : Profile -{ - public AccountServiceProfile() - { - CreateMap(); - - CreateMap() - .ForMember(x => x.HasMotions, o => o.MapFrom(x => x.Motions.Any())) - ; - - CreateMap(); - - CreateMap(); - - CreateMap(); - - CreateMap() - .ForMember(x => x.Id, o => o.MapFrom(x => x.Id)) - .ForMember(x => x.Id, o => o.MapFrom(x => x.Id)) - ; - } -} \ No newline at end of file diff --git a/MyOffice.Services/Account/Domain/AccessInviteStatus.cs b/MyOffice.Services/Account/Domain/AccessInviteStatus.cs new file mode 100644 index 0000000..eaecacb --- /dev/null +++ b/MyOffice.Services/Account/Domain/AccessInviteStatus.cs @@ -0,0 +1,9 @@ +namespace MyOffice.Services.Account.Domain; + +public enum AccessInviteStatus +{ + success, + account_not_found, + access_exists, + invite_exists, +} \ No newline at end of file diff --git a/MyOffice.Services/Account/Domain/AccountAccessDto.cs b/MyOffice.Services/Account/Domain/AccountAccessDto.cs index 0d96ee1..e37fc38 100644 --- a/MyOffice.Services/Account/Domain/AccountAccessDto.cs +++ b/MyOffice.Services/Account/Domain/AccountAccessDto.cs @@ -5,10 +5,24 @@ using Data.Models.Users; public class AccountAccessDto { + /// + /// Current user + /// + public Guid CurrenUserId { get; set; } + public Guid AccountId { get; set; } public AccountDto? Account { get; set; } + + /// + /// User can access to account + /// public Guid UserId { get; set; } public User? User { get; set; } + /// + /// Who add access + /// + public Guid OwnerId { get; set; } + public User? Owner { get; set; } public bool IsAllowRead { get; set; } public bool IsAllowWrite { get; set; } diff --git a/MyOffice.Services/Account/Domain/AccountAddResult.cs b/MyOffice.Services/Account/Domain/AccountAddStatus.cs similarity index 80% rename from MyOffice.Services/Account/Domain/AccountAddResult.cs rename to MyOffice.Services/Account/Domain/AccountAddStatus.cs index e3717c1..b50b045 100644 --- a/MyOffice.Services/Account/Domain/AccountAddResult.cs +++ b/MyOffice.Services/Account/Domain/AccountAddStatus.cs @@ -1,6 +1,6 @@ namespace MyOffice.Services.Account.Domain { - public enum AccountAddResult + public enum AccountAddStatus { success, failure, diff --git a/MyOffice.Services/Account/Domain/AccountCategoryDto.cs b/MyOffice.Services/Account/Domain/AccountCategoryDto.cs index 46377ab..9c0abbc 100644 --- a/MyOffice.Services/Account/Domain/AccountCategoryDto.cs +++ b/MyOffice.Services/Account/Domain/AccountCategoryDto.cs @@ -1,6 +1,9 @@ -public class AccountCategoryDto +namespace MyOffice.Services.Account.Domain; + +public class AccountCategoryDto { public Guid Id { get; set; } public Guid UserId { get; set; } public string Name { get; set; } = null!; + public bool AllowDelete { get; set; } } \ No newline at end of file diff --git a/MyOffice.Services/Account/Domain/AccountDto.cs b/MyOffice.Services/Account/Domain/AccountDto.cs index 43036da..4f1f965 100644 --- a/MyOffice.Services/Account/Domain/AccountDto.cs +++ b/MyOffice.Services/Account/Domain/AccountDto.cs @@ -1,22 +1,23 @@ -namespace MyOffice.Services.Account.Domain -{ - using MyOffice.Data.Models.Currencies; - using System; +namespace MyOffice.Services.Account.Domain; - public class AccountDto - { - public Guid Id { get; set; } - public string CurrencyGlobalId { get; set; } = null!; - public CurrencyGlobal? CurrencyGlobal { get; set; } - public Guid CurrencyId { get; set; } - public Currency? Currency { get; set; } - public Guid UserId { get; set; } - public UserDto? User { get; set; } - public Guid OwnerId { get; set; } - public UserDto? Owner { get; set; } - public string Name { get; set; } = null!; - public bool HasMotions { get; set; } - public List? AccessRights { get; set; } - public List? Categories { get; set; } - } -} \ No newline at end of file +using MyOffice.Data.Models.Currencies; +using System; + +public class AccountDto +{ + public Guid Id { get; set; } + public Guid CurrentUserId { get; set; } + public string CurrencyGlobalId { get; set; } = null!; + public CurrencyGlobal? CurrencyGlobal { get; set; } + public Guid CurrencyId { get; set; } + public Currency? Currency { get; set; } + public Guid UserId { get; set; } + public UserDto? User { get; set; } + public Guid OwnerId { get; set; } + public UserDto? Owner { get; set; } + public string Name { get; set; } = null!; + public bool HasMotions { get; set; } + public string Type { get; set; } = null!; + public List? AccessRights { get; set; } + public List? Categories { get; set; } +} diff --git a/MyOffice.Services/Account/Domain/AccountEditResult.cs b/MyOffice.Services/Account/Domain/AccountEditStatus.cs similarity index 80% rename from MyOffice.Services/Account/Domain/AccountEditResult.cs rename to MyOffice.Services/Account/Domain/AccountEditStatus.cs index 4b91955..f5fb425 100644 --- a/MyOffice.Services/Account/Domain/AccountEditResult.cs +++ b/MyOffice.Services/Account/Domain/AccountEditStatus.cs @@ -1,6 +1,6 @@ namespace MyOffice.Services.Account.Domain; -public enum AccountEditResult +public enum AccountEditStatus { success, failure, diff --git a/MyOffice.Services/Account/Domain/MotionAddResult.cs b/MyOffice.Services/Account/Domain/MotionAddStatus.cs similarity index 76% rename from MyOffice.Services/Account/Domain/MotionAddResult.cs rename to MyOffice.Services/Account/Domain/MotionAddStatus.cs index c693208..d78284a 100644 --- a/MyOffice.Services/Account/Domain/MotionAddResult.cs +++ b/MyOffice.Services/Account/Domain/MotionAddStatus.cs @@ -1,6 +1,6 @@ namespace MyOffice.Services.Account.Domain; -public enum MotionAddResult +public enum MotionAddStatus { success, failure, diff --git a/MyOffice.Services/Account/Domain/MotionDeleteResult.cs b/MyOffice.Services/Account/Domain/MotionDeleteStatus.cs similarity index 72% rename from MyOffice.Services/Account/Domain/MotionDeleteResult.cs rename to MyOffice.Services/Account/Domain/MotionDeleteStatus.cs index b141dbe..4c8d2b3 100644 --- a/MyOffice.Services/Account/Domain/MotionDeleteResult.cs +++ b/MyOffice.Services/Account/Domain/MotionDeleteStatus.cs @@ -1,6 +1,6 @@ namespace MyOffice.Services.Account.Domain; -public enum MotionDeleteResult +public enum MotionDeleteStatus { success, failure, diff --git a/MyOffice.Services/Account/Domain/MotionUpdateResult.cs b/MyOffice.Services/Account/Domain/MotionUpdateStatus.cs similarity index 72% rename from MyOffice.Services/Account/Domain/MotionUpdateResult.cs rename to MyOffice.Services/Account/Domain/MotionUpdateStatus.cs index b45f98d..fec4fe8 100644 --- a/MyOffice.Services/Account/Domain/MotionUpdateResult.cs +++ b/MyOffice.Services/Account/Domain/MotionUpdateStatus.cs @@ -1,6 +1,6 @@ namespace MyOffice.Services.Account.Domain; -public enum MotionUpdateResult +public enum MotionUpdateStatus { success, failure, diff --git a/MyOffice.Services/Account/Domain/UserDto.cs b/MyOffice.Services/Account/Domain/UserDto.cs index 5e92d59..48ebee8 100644 --- a/MyOffice.Services/Account/Domain/UserDto.cs +++ b/MyOffice.Services/Account/Domain/UserDto.cs @@ -12,6 +12,6 @@ public class UserDto public string? FullName { get; set; } public string? Phone { get; set; } - public string CurrencyId { get; set; } + public string CurrencyId { get; set; } = null!; public CurrencyGlobal? Currency { get; set; } } \ No newline at end of file diff --git a/MyOffice.Services/Dashboard/Domain/DashboardData.cs b/MyOffice.Services/Dashboard/Domain/DashboardData.cs index d3dd8b1..1a60644 100644 --- a/MyOffice.Services/Dashboard/Domain/DashboardData.cs +++ b/MyOffice.Services/Dashboard/Domain/DashboardData.cs @@ -13,7 +13,7 @@ public class DashboardData public decimal? Balance { get; set; } public decimal? BalanceDebit { get; set; } public decimal? BalanceCredit { get; set; } - public List BalanceRests { get; set; } + public List BalanceRests { get; set; } = null!; } public class DashboardRestData diff --git a/MyOffice.Services/Mapper/AccountServiceProfile.cs b/MyOffice.Services/Mapper/AccountServiceProfile.cs new file mode 100644 index 0000000..e0fcd62 --- /dev/null +++ b/MyOffice.Services/Mapper/AccountServiceProfile.cs @@ -0,0 +1,31 @@ +namespace MyOffice.Services.Mapper; + +using Account.Domain; +using AutoMapper; +using Data.Models.Accounts; +using Data.Models.Users; + +public class AccountServiceProfile : Profile +{ + public AccountServiceProfile() + { + CreateMap(); + + CreateMap() + .ForMember(x => x.HasMotions, o => o.MapFrom(x => x.Motions!.Any())) + .ForMember(x => x.CurrentUserId, o => o.MapFrom()) + .ForMember(x => x.Type, o => o.MapFrom((src, dst) => src.AccessRights!.FirstOrDefault(x => x.UserId == dst.CurrentUserId)!.Type.ToString())) + ; + + CreateMap(); + + CreateMap(); + + CreateMap(); + + CreateMap() + .ForMember(x => x.Id, o => o.MapFrom(x => x.Id)) + .ForMember(x => x.AllowDelete, o => o.MapFrom(x => !x.Accounts!.Any())) + ; + } +} \ No newline at end of file diff --git a/MyOffice.Services/Mapper/UserIdResolver.cs b/MyOffice.Services/Mapper/UserIdResolver.cs new file mode 100644 index 0000000..cd87127 --- /dev/null +++ b/MyOffice.Services/Mapper/UserIdResolver.cs @@ -0,0 +1,21 @@ +namespace MyOffice.Services.Mapper; + +using AutoMapper; +using Identity; + +public class UserIdResolver : IValueResolver +{ + private readonly IContextProvider _contextProvider; + + public UserIdResolver( + IContextProvider contextProvider + ) + { + _contextProvider = contextProvider; + } + + public Guid Resolve(object source, object destination, Guid member, ResolutionContext context) + { + return _contextProvider.UserId; + } +} \ No newline at end of file diff --git a/MyOffice.Web/Controllers/AccountController.cs b/MyOffice.Web/Controllers/AccountController.cs index 9ff9279..b48798a 100644 --- a/MyOffice.Web/Controllers/AccountController.cs +++ b/MyOffice.Web/Controllers/AccountController.cs @@ -58,7 +58,6 @@ public class AccountController : BaseApiController case GeneralExecStatus.success: return _mapper.Map(exec.Result); - //return exec.Result!.ToModel(); default: throw new NotSupportedException(exec.Status.ToString()); @@ -92,11 +91,11 @@ public class AccountController : BaseApiController switch (exec.Status) { - case MotionAddResult.account_not_found: + case MotionAddStatus.account_not_found: return ProblemBadRequest("Account not found."); - case MotionAddResult.failure: + case MotionAddStatus.failure: return ProblemBadRequest("Adding motion failed."); - case MotionAddResult.success: + case MotionAddStatus.success: return _mapper.Map(exec.Result!); default: @@ -111,11 +110,11 @@ public class AccountController : BaseApiController switch (exec.Status) { - case MotionUpdateResult.not_found: + case MotionUpdateStatus.not_found: return ProblemBadRequest("Motion not found."); - case MotionUpdateResult.failure: + case MotionUpdateStatus.failure: return ProblemBadRequest("Updating motion failed."); - case MotionUpdateResult.success: + case MotionUpdateStatus.success: return exec.Result!.ToModel(); default: @@ -130,11 +129,11 @@ public class AccountController : BaseApiController switch (exec.Status) { - case MotionDeleteResult.not_found: + case MotionDeleteStatus.not_found: return ProblemBadRequest("Motion not found."); - case MotionDeleteResult.failure: + case MotionDeleteStatus.failure: return ProblemBadRequest("Deliting motion failed."); - case MotionDeleteResult.success: + case MotionDeleteStatus.success: return exec.Result!.ToModel(); default: diff --git a/MyOffice.Web/Controllers/SettingsAccountController.cs b/MyOffice.Web/Controllers/SettingsAccountController.cs index 7b0c41e..e734ec1 100644 --- a/MyOffice.Web/Controllers/SettingsAccountController.cs +++ b/MyOffice.Web/Controllers/SettingsAccountController.cs @@ -9,6 +9,7 @@ using Microsoft.AspNetCore.Authorization; using Models.Account; using Services.Account; using Services.Account.Domain; +using MyOffice.Web.Infrastructure.Attributes; [Authorize] [ApiController] @@ -35,11 +36,11 @@ public class SettingsAccountController : BaseApiController { var list = _accountService.GetAllCategories(UserId); - return list.OrderBy(x => x.Name).Select(x => x.ToModel()); + return _mapper.Map(list).OrderBy(x => x.Name); } [HttpGet("~/api/settings/account-categories/{id}")] - public object AccountCategory(string id) + public object AccountCategory([AsGuid] string id) { var exec = _accountService.GetCategory(UserId, id.AsGuid()); @@ -50,7 +51,7 @@ public class SettingsAccountController : BaseApiController return ProblemBadRequest("Account category not found."); case GeneralExecStatus.success: - return exec.Result!.ToModel(); + return _mapper.Map(exec.Result!); default: throw new NotSupportedException(exec.Status.ToString()); @@ -76,7 +77,7 @@ public class SettingsAccountController : BaseApiController } [HttpPut("~/api/settings/account-categories/{id}")] - public object AccountCategoriesEdit(string id, AccountCategoryViewModel request) + public object AccountCategoriesEdit([AsGuid] string id, AccountCategoryViewModel request) { var exec = _accountService.CategoryUpdate(UserId, id.AsGuid(), new AccountCategory { Name = request.Name! }); switch (exec.Status) @@ -94,7 +95,7 @@ public class SettingsAccountController : BaseApiController } [HttpDelete("~/api/settings/account-categories/{id}")] - public object AccountCategoriesDelete(string id) + public object AccountCategoriesDelete([AsGuid] string id) { var exec = _accountService.CategoryRemove(UserId, id.AsGuid()); switch (exec.Status) @@ -115,7 +116,7 @@ public class SettingsAccountController : BaseApiController } [HttpGet("~/api/settings/accounts")] - public List AccountsGet(string? category) + public List AccountsGet([AsGuid(true)] string? category) { var list = category.IsPresent() ? _accountService.GetByCategory(UserId, category!.AsGuid()) @@ -137,13 +138,13 @@ public class SettingsAccountController : BaseApiController switch (exec.Status) { - case AccountAddResult.category_not_found: + case AccountAddStatus.category_not_found: return ProblemBadRequest("Category not found."); - case AccountAddResult.currency_not_found: + case AccountAddStatus.currency_not_found: return ProblemBadRequest("Currency not found."); - case AccountAddResult.failure: + case AccountAddStatus.failure: return ProblemBadRequest("Adding account failed."); - case AccountAddResult.success: + case AccountAddStatus.success: return exec.Result!; default: @@ -153,7 +154,7 @@ public class SettingsAccountController : BaseApiController [HttpPut("~/api/settings/accounts/{id}")] - public object AccountsAdd(string id, AccountEditRequestModel request) + public object AccountsAdd([AsGuid] string id, AccountEditRequestModel request) { var exec = _accountService.AccountUpdate(UserId, id.AsGuid(), new AccountEdit { @@ -166,15 +167,15 @@ public class SettingsAccountController : BaseApiController switch (exec.Status) { - case AccountEditResult.not_found: + case AccountEditStatus.not_found: return ProblemBadRequest("Account not found."); - case AccountEditResult.category_not_found: + case AccountEditStatus.category_not_found: return ProblemBadRequest("Category not found."); - case AccountEditResult.currency_not_found: + case AccountEditStatus.currency_not_found: return ProblemBadRequest("Currency not found."); - case AccountEditResult.failure: + case AccountEditStatus.failure: return ProblemBadRequest("Adding account failed."); - case AccountEditResult.success: + case AccountEditStatus.success: return exec.Result!; default: @@ -183,7 +184,7 @@ public class SettingsAccountController : BaseApiController } [HttpDelete("~/api/settings/accounts/{id}")] - public object AccountsDelete(string id) + public object AccountsDelete([AsGuid] string id) { var exec = _accountService.AccountDelete(UserId, id); switch (exec.Status) @@ -199,7 +200,7 @@ public class SettingsAccountController : BaseApiController } [HttpDelete("~/api/settings/accounts/{id}/category/{categoryId}")] - public object AccountsCategoryRemove(string id, string categoryId) + public object AccountsCategoryRemove([AsGuid] string id, [AsGuid] string categoryId) { var exec = _accountService.AccountCategoryRemove(UserId, id.AsGuid(), categoryId.AsGuid()); @@ -216,4 +217,68 @@ public class SettingsAccountController : BaseApiController throw new NotSupportedException(exec.Status.ToString()); } } + + [HttpPost("~/api/settings/accounts/{id}/access")] + public object AccountsAdd([AsGuid] string id, AccountAccessViewModel request) + { + var model = _mapper.Map>(request.Accesses); + + var exec = _accountService.AccessUpdate(UserId, id.AsGuid(), model); + + switch (exec.Status) + { + case GeneralExecStatus.not_found: + case GeneralExecStatus.failure: + return ProblemBadRequest("Account not found."); + + case GeneralExecStatus.success: + break; + + default: + throw new NotSupportedException(exec.Status.ToString()); + } + + if (!request.Email.IsPresent()) + { + return exec.Result!; + } + + var inviteExec = _accountService.AccessInvite(UserId, id.AsGuid(), request.Email!, request.AllowWrite); + + switch (inviteExec.Status) + { + case AccessInviteStatus.account_not_found: + return ProblemBadRequest("Account not found."); + + case AccessInviteStatus.access_exists: + return ProblemBadRequest("Access allowed."); + + case AccessInviteStatus.invite_exists: + case AccessInviteStatus.success: + return exec.Result!; + + default: + throw new NotSupportedException(exec.Status.ToString()); + } + } + + [HttpDelete("~/api/settings/accounts/{id}/access/{userId}")] + public object AccountsAdd([AsGuid] string id, [AsGuid] string userId) + { + var exec = _accountService.AccessDelete(UserId, id.AsGuid(), userId.AsGuid()); + + switch (exec.Status) + { + case GeneralExecStatus.not_found: + case GeneralExecStatus.failure: + return ProblemBadRequest("Account not found."); + + case GeneralExecStatus.success: + return exec.Result!; + + default: + throw new NotSupportedException(exec.Status.ToString()); + } + + } } \ No newline at end of file diff --git a/MyOffice.Web/Infrastructure/Attributes/AsGuidAttribute.cs b/MyOffice.Web/Infrastructure/Attributes/AsGuidAttribute.cs new file mode 100644 index 0000000..1439835 --- /dev/null +++ b/MyOffice.Web/Infrastructure/Attributes/AsGuidAttribute.cs @@ -0,0 +1,35 @@ +namespace MyOffice.Web.Infrastructure.Attributes +{ + using System.ComponentModel.DataAnnotations; + using Core.Extensions; + + public class AsGuidAttribute: ValidationAttribute + { + private readonly bool _nullable; + + public AsGuidAttribute(bool nullable = false) + { + _nullable = nullable; + } + + protected override ValidationResult? IsValid(object? value, ValidationContext validationContext) + { + if (value == null && _nullable) + { + return ValidationResult.Success; + } + + if (value == null || value.ToString().IsMissing()) + { + return new ValidationResult("Id parameter is not valid"); + } + + if (!Guid.TryParse(value.ToString(), out _)) + { + return new ValidationResult("Id parameter is not valid"); + } + + return ValidationResult.Success; + } + } +} diff --git a/MyOffice.Web/Models/Account/AccessRightsViewModel.cs b/MyOffice.Web/Models/Account/AccessRightsViewModel.cs index 4d43bd9..e65db16 100644 --- a/MyOffice.Web/Models/Account/AccessRightsViewModel.cs +++ b/MyOffice.Web/Models/Account/AccessRightsViewModel.cs @@ -1,7 +1,6 @@ namespace MyOffice.Web.Models.Account { - using Data.Models.Accounts; - using Services.Account.Domain; + using Newtonsoft.Json; using User; public class AccessRightsViewModel @@ -10,19 +9,16 @@ public bool IsAllowRead { get; set; } public bool IsAllowWrite { get; set; } public bool IsAllowManage { get; set; } - } + public bool IsAllowDelete { get; set; } + public bool IsOwner { get; set; } - public static class AccessRightsViewModelExtensions - { - public static AccessRightsViewModel ToModel(this AccountAccessDto accountAccess) - { - return new AccessRightsViewModel - { - User = accountAccess.User!.ToModel(), - IsAllowManage = accountAccess.IsAllowManage, - IsAllowRead = accountAccess.IsAllowRead, - IsAllowWrite = accountAccess.IsAllowWrite, - }; - } + #region DEBUG + + [JsonIgnore] + public Guid UserId { get; set; } + [JsonIgnore] + public Guid OwnerId { get; set; } + + #endregion DEBUG } } diff --git a/MyOffice.Web/Models/Account/AccountViewModel.cs b/MyOffice.Web/Models/Account/AccountViewModel.cs index ef62fa6..48c9ed3 100644 --- a/MyOffice.Web/Models/Account/AccountViewModel.cs +++ b/MyOffice.Web/Models/Account/AccountViewModel.cs @@ -1,12 +1,7 @@ namespace MyOffice.Web.Models.Account { + using Newtonsoft.Json; using System.ComponentModel.DataAnnotations; - using AutoMapper; - using Core.Extensions; - using Data.Models.Accounts; - using Services.Account.Domain; - using Services.Identity; - using User; public class AccountViewModel { @@ -20,71 +15,28 @@ public string CurrencyId { get; set; } = null!; public string? CurrencyName { get; set; } public bool AllowDelete { get; set; } + public bool AllowManage { get; set; } public List? Categories { get; set; } [Required] public string CategoryId { get; set; } = null!; + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] public List? AccessRights { get; set; } } - public class ViewModelProfile : Profile + + public class AccountAccessViewModel { - public ViewModelProfile() + public class AccountAccessItemViewModel { - CreateMap().ConvertUsing(x => x.ToShort()); + public string UserId { get; set; } = null!; + public bool AllowWrite { get; set; } } + + public string? Email { get; set; } + public bool AllowWrite { get; set; } + public List Accesses { get; set; } = null!; } - - public class AccountViewModelProfile : Profile - { - public AccountViewModelProfile( - IContextProvider contextProvider - ) - { - - CreateMap() - .ForMember(x => x.Account, o => o.MapFrom(x => x.Account)) - ; - - CreateMap(); - - CreateMap(); - - CreateMap() - .ForMember(x => x.Type, o => o.MapFrom(x => x.AccessRights!.FirstOrDefault(a => a.UserId == contextProvider.UserId)!.Type.ToString())) - .ForMember(x => x.CurrencyId, o => o.MapFrom(x => x.CurrencyGlobalId)) - .ForMember(x => x.CurrencyName, o => o.MapFrom(x => x.Currency!.Name)) - .ForMember(x => x.AllowDelete, o => o.MapFrom(x => !x.HasMotions)) - ; - - CreateMap() - .ForMember(x => x.Id, o => o.MapFrom(x => x.CategoryId)) - .ForMember(x => x.Name, o => o.MapFrom(x => x.Category!.Name)) - ; - - CreateMap(); - } - } - - /*public static class AccountViewModelExtensions - { - public static AccountViewModel ToModel(this AccountDto input) - { - return new AccountViewModel - { - Id = input.Id.ToShort(), - Name = input.Name, - CurrencyId = input.CurrencyGlobalId, - CurrencyName = input.CurrencyGlobal!.Name, - Categories = input.Categories! - .Select(x => x.Category!.ToModel()) - .ToList(), - AccessRights = input.AccessRights! - .Select(x => x.ToModel()) - .ToList(), - }; - } - }*/ } diff --git a/MyOffice.Web/Models/Account/AccountViewModelProfile.cs b/MyOffice.Web/Models/Account/AccountViewModelProfile.cs new file mode 100644 index 0000000..2c3ffb2 --- /dev/null +++ b/MyOffice.Web/Models/Account/AccountViewModelProfile.cs @@ -0,0 +1,71 @@ +namespace MyOffice.Web.Models.Account; + +using AutoMapper; +using MyOffice.Services.Identity; +using Services.Account.Domain; +using Services.Mapper; +using System.Security.Cryptography; +using Core.Extensions; +using User; +using static MyOffice.Web.Models.Account.AccountAccessViewModel; + +public class CustomResolver : IValueResolver +{ + private readonly ILogger _logger; + private readonly IContextProvider _contextProvider; + + public CustomResolver( + ILogger logger, + IContextProvider contextProvider + ) + { + _logger = logger; + _contextProvider = contextProvider; + } + + public bool Resolve(AccountAccessDto source, AccessRightsViewModel destination, bool member, ResolutionContext context) + { + return source.OwnerId == _contextProvider.UserId; + } +} + +public class AccountViewModelProfile : Profile +{ + public AccountViewModelProfile() + { + CreateMap() + .ForMember(x => x.Account, o => o.MapFrom(x => x.Account)) + ; + + CreateMap(); + + CreateMap(); + + CreateMap() + .ForMember(x => x.CurrencyId, o => o.MapFrom(x => x.CurrencyGlobalId)) + .ForMember(x => x.CurrencyName, o => o.MapFrom(x => x.Currency!.Name)) + .ForMember(x => x.AllowDelete, o => o.MapFrom(x => !x.HasMotions)) + .ForMember(x => x.AllowManage, o => o.MapFrom(x => x.AccessRights!.Any(a => a.OwnerId == x.CurrentUserId))) + .ForMember(x => x.AccessRights, o => o.MapFrom((s, d) => s.OwnerId != s.CurrentUserId ? null : s.AccessRights)) + ; + + CreateMap() + .ForMember(x => x.Id, o => o.MapFrom(x => x.CategoryId)) + .ForMember(x => x.Name, o => o.MapFrom(x => x.Category!.Name)) + ; + + CreateMap() + .ForMember(x => x.IsAllowDelete, o => o.MapFrom( + (src, dst) => src.Account!.OwnerId == src.Account.CurrentUserId && src.UserId != src.Account.CurrentUserId)) + .ForMember(x => x.IsOwner, o => o.MapFrom( + (src, dst) => src.OwnerId == src.UserId)) + ; + + CreateMap() + .ForMember(x => x.IsAllowWrite, o => o.MapFrom(x => x.AllowWrite)) + ; + + CreateMap() + ; + } +} \ No newline at end of file diff --git a/MyOffice.Web/Models/Account/ViewModelProfile.cs b/MyOffice.Web/Models/Account/ViewModelProfile.cs new file mode 100644 index 0000000..ac688e3 --- /dev/null +++ b/MyOffice.Web/Models/Account/ViewModelProfile.cs @@ -0,0 +1,12 @@ +namespace MyOffice.Web.Models.Account; + +using AutoMapper; +using Core.Extensions; + +public class ViewModelProfile : Profile +{ + public ViewModelProfile() + { + CreateMap().ConvertUsing(x => x.ToShort()); + } +} \ No newline at end of file diff --git a/MyOffice.Web/Program.cs b/MyOffice.Web/Program.cs index 4aa7ab3..4047fef 100644 --- a/MyOffice.Web/Program.cs +++ b/MyOffice.Web/Program.cs @@ -42,16 +42,28 @@ using MyOffice.Services.Currency; using Services.Account; using Services.Item; using MyOffice.Services.Dashboard; -using Services.Account.Domain; using MyOffice.Services.Identity; using AutoMapper; using Models.Motion; +using MyOffice.Services.Account.Domain; +using MyOffice.Services.Mapper; //TODO: Data.Model only Repository and Service, response <-> mapper <-> web <-> mapper <-> service <-> repository //TODO: Project management //TODO: Model names. Controller = xxxRequest/xxxResponse. Service xxxInput/xxxOutput. //TODO: Validate string as Guid when id -//TODO: Logging +//TODO: Test back +//TODO: Test front +//TODO: Test DB ??? +//TODO: Email confirmation +//TODO: Password recovery +//TODO: Account sharing +//TODO: /dashboard/dashboard -> /dashboard/main or /dashboard +//TODO: SPA load categories twice menu + setting (cache) +//TODO: SPA update account category -> update menu +//TODO: Response model from base type +//TODO: XXXResult -> XXXStatus +//TODO: SPA isAllowDelete -> allowDelete (remove is) public class Program { @@ -79,6 +91,12 @@ public class Program //File Logger builder.Logging.AddFile(builder.Configuration.GetSection("Logging")); + builder.Services.Configure(options => + { + //options.AddFilter("IdentityServer4", LogLevel.Warning); // or LogLevel.None to completely disable + options.AddFilter("IdentityServer4", LogLevel.None); // or LogLevel.None to completely disable + }); + // shared configuration builder.Configuration.AddConfiguration(SharedConfiguration.CreateConfigurationContainer()); @@ -180,9 +198,8 @@ public class Program o.UserInteraction = new UserInteractionOptions() { LoginUrl = "/authentication/signin", - LoginReturnUrlParameter = "returnUrl" + LoginReturnUrlParameter = "returnUrl", }; - //o.IssuerUri = "http://localhost:9300"; }) .AddSigningCredential(CreateSigningCredential(builder)) .AddPersistedGrantStore() @@ -222,14 +239,15 @@ public class Program private static void AddMapping(WebApplicationBuilder builder) { - builder.Services.AddSingleton(provider => new MapperConfiguration(cfg => - { - cfg.AddProfile(new AccountServiceProfile()); - cfg.AddProfile(new ViewModelProfile()); - cfg.AddProfile(new AccountViewModelProfile(provider.CreateScope().ServiceProvider.GetService()!)); - - cfg.AddProfile(new AccountControllerProfile()); - }).CreateMapper()); + builder.Services.AddAutoMapper( + c => + { + c.AllowNullCollections = true; + c.AllowNullDestinationValues = true; + }, + typeof(AccountViewModelProfile), + typeof(AccountServiceProfile) + ); } private static void AddRepositories(WebApplicationBuilder builder)