diff --git a/MyOffice.Core/Attributes/LinkAttribute.cs b/MyOffice.Core/Attributes/LinkAttribute.cs new file mode 100644 index 0000000..94868e7 --- /dev/null +++ b/MyOffice.Core/Attributes/LinkAttribute.cs @@ -0,0 +1,35 @@ +namespace MyOffice.Core.Attributes; + +using System; + +[AttributeUsage(AttributeTargets.All, AllowMultiple = true, Inherited = true)] +public class LinkAttribute : Attribute +{ + public LinkAttribute(Type type) + { + } +} + +[AttributeUsage(AttributeTargets.All, AllowMultiple = true, Inherited = true)] +public class LinkFromAttribute : Attribute +{ + public LinkFromAttribute(Type type) + { + } +} + +[AttributeUsage(AttributeTargets.All, AllowMultiple = true, Inherited = true)] +public class LinkToAttribute : Attribute +{ + public LinkToAttribute(Type type) + { + } +} + +[AttributeUsage(AttributeTargets.All, AllowMultiple = true, Inherited = true)] +public class LinkWithAttribute : Attribute +{ + public LinkWithAttribute(Type type) + { + } +} \ No newline at end of file diff --git a/MyOffice.Core/Extensions/StringExtensions.cs b/MyOffice.Core/Extensions/StringExtensions.cs index e55934b..18e13ce 100644 --- a/MyOffice.Core/Extensions/StringExtensions.cs +++ b/MyOffice.Core/Extensions/StringExtensions.cs @@ -24,6 +24,26 @@ public static class StringExtensions return str != null && str.Equals(value, StringComparison.OrdinalIgnoreCase); } + public static string? SafeTrim(this string? str) + { + return str == null ? str : str!.Trim(); + } + + public static string? MaxOf(this string? str, int maxLength) + { + if (str == null) + { + return null; + } + + if (str.Length < maxLength) + { + return str; + } + + return str.Substring(0, maxLength); + } + public static bool AsBool(this string? str, bool defaultValue = false) { return true.ToString().Equals(str, StringComparison.OrdinalIgnoreCase) || defaultValue; @@ -31,21 +51,11 @@ public static class StringExtensions public static Guid AsGuid(this string str) { - /*str = str - .Replace("_", "/") - .Replace("-", "+"); - byte[] buffer = Convert.FromBase64String(str + "=="); - return new Guid(buffer);*/ return Guid.Parse(str); } public static Guid? AsGuidNull(this string str) { - /*str = str - .Replace("_", "/") - .Replace("-", "+"); - byte[] buffer = Convert.FromBase64String(str + "=="); - return new Guid(buffer);*/ if (Guid.TryParse(str, out var guid)) { return guid; diff --git a/MyOffice.Data.Models/Accounts/Account.cs b/MyOffice.Data.Models/Accounts/Account.cs index 700e96d..92751ef 100644 --- a/MyOffice.Data.Models/Accounts/Account.cs +++ b/MyOffice.Data.Models/Accounts/Account.cs @@ -14,6 +14,7 @@ public class Account public IEnumerable? AccessRights { get; set; } public IEnumerable? Motions { get; set; } public IEnumerable? Categories { get; set; } + public IEnumerable? Invites { get; set; } } public class AccountDetailed diff --git a/MyOffice.Data.Models/Accounts/AccountAccessInvite.cs b/MyOffice.Data.Models/Accounts/AccountAccessInvite.cs index cf59859..59fef7b 100644 --- a/MyOffice.Data.Models/Accounts/AccountAccessInvite.cs +++ b/MyOffice.Data.Models/Accounts/AccountAccessInvite.cs @@ -1,12 +1,15 @@ namespace MyOffice.Data.Models.Accounts; +using Core.Attributes; using Users; public class AccountAccessInvite { public Guid Id { get; set; } public Guid UserId { get; set; } - public User User { get; set; } + public User? User { get; set; } + public Guid AccountId { get; set; } + public Account? Account { get; set; } public DateTime CreatedOn { get; set; } public DateTime? AcceptedOn { get; set; } public DateTime? RejectedOn { get; set; } diff --git a/MyOffice.Data.Models/MyOffice.Data.Models.csproj b/MyOffice.Data.Models/MyOffice.Data.Models.csproj index 132c02c..ebb4b0a 100644 --- a/MyOffice.Data.Models/MyOffice.Data.Models.csproj +++ b/MyOffice.Data.Models/MyOffice.Data.Models.csproj @@ -6,4 +6,8 @@ enable + + + + diff --git a/MyOffice.Data.Repositories/Account/AccountAccessInviteRepository.cs b/MyOffice.Data.Repositories/Account/AccountAccessInviteRepository.cs index b705f99..c497aef 100644 --- a/MyOffice.Data.Repositories/Account/AccountAccessInviteRepository.cs +++ b/MyOffice.Data.Repositories/Account/AccountAccessInviteRepository.cs @@ -1,5 +1,6 @@ namespace MyOffice.Data.Repositories.Account; +using Microsoft.EntityFrameworkCore; using Models.Accounts; using MyOffice.Data.Repositories; @@ -24,4 +25,18 @@ public class AccountAccessInviteRepository : AppRepository, { return UpdateBase(invite) > 0; } + + public IEnumerable GetActive(string email) + { + return _context + .AccountAccessInvites + .Include(x => x.Account) + .Where(x => x.Email == email && !x.AcceptedOn.HasValue && !x.RejectedOn.HasValue); + + } + + public AccountAccessInvite? Get(Guid id) + { + return _context.AccountAccessInvites.FirstOrDefault(x => x.Id == id); + } } \ No newline at end of file diff --git a/MyOffice.Data.Repositories/Account/AccountAccessRepository.cs b/MyOffice.Data.Repositories/Account/AccountAccessRepository.cs index aa5b267..4125913 100644 --- a/MyOffice.Data.Repositories/Account/AccountAccessRepository.cs +++ b/MyOffice.Data.Repositories/Account/AccountAccessRepository.cs @@ -5,6 +5,10 @@ using MyOffice.Data.Repositories; public class AccountAccessRepository : AppRepository, IAccountAccessRepository { + public bool Add(AccountAccess accountAccess) + { + return AddBase(accountAccess) > 0; + } public bool Update(AccountAccess accountAccess) { diff --git a/MyOffice.Data.Repositories/Account/AccountRepository.cs b/MyOffice.Data.Repositories/Account/AccountRepository.cs index 1812e1b..69a6ab6 100644 --- a/MyOffice.Data.Repositories/Account/AccountRepository.cs +++ b/MyOffice.Data.Repositories/Account/AccountRepository.cs @@ -90,7 +90,7 @@ public class AccountRepository : AppRepository, IAccountRepository public Account? Get(Guid userId, Guid id) { return _context.Accounts! - .Include(x => x.Categories)! + .Include(x => x.Categories!.Where(c => c.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 index 9f9167b..add55f8 100644 --- a/MyOffice.Data.Repositories/Account/IAccountAccessInviteRepository.cs +++ b/MyOffice.Data.Repositories/Account/IAccountAccessInviteRepository.cs @@ -5,6 +5,8 @@ using Models.Accounts; public interface IAccountAccessInviteRepository { AccountAccessInvite? Get(Guid userId, string email); + IEnumerable GetActive(string email); + AccountAccessInvite? Get(Guid id); 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 fe2cf60..8e673a1 100644 --- a/MyOffice.Data.Repositories/Account/IAccountAccessRepository.cs +++ b/MyOffice.Data.Repositories/Account/IAccountAccessRepository.cs @@ -4,6 +4,7 @@ using Models.Accounts; public interface IAccountAccessRepository { + bool Add(AccountAccess accountAccess); 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 a736003..f3ecd1a 100644 --- a/MyOffice.DbContext/AppDbContext.cs +++ b/MyOffice.DbContext/AppDbContext.cs @@ -176,6 +176,11 @@ public class AppDbContext : DbContext .WithMany(x => x.AccountAccessInvites) .HasForeignKey(x => x.UserId); + modelBuilder.Entity() + .HasOne(x => x.Account) + .WithMany(x => x.Invites) + .HasForeignKey(x => x.AccountId); + modelBuilder.Entity() .HasOne(x => x.Account) .WithMany(x => x.Motions) diff --git a/MyOffice.Migration.Postgres/Migrations/20230728181916_AccountAccessInvites2.Designer.cs b/MyOffice.Migration.Postgres/Migrations/20230728181916_AccountAccessInvites2.Designer.cs new file mode 100644 index 0000000..247cc3b --- /dev/null +++ b/MyOffice.Migration.Postgres/Migrations/20230728181916_AccountAccessInvites2.Designer.cs @@ -0,0 +1,713 @@ +// +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("20230728181916_AccountAccessInvites2")] + partial class AccountAccessInvites2 + { + /// + 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.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + 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.AccountAccessInvite", b => + { + b.HasOne("MyOffice.Data.Models.Users.User", "User") + .WithMany("AccountAccessInvites") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + 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("AccountAccessInvites"); + + 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/20230728181916_AccountAccessInvites2.cs b/MyOffice.Migration.Postgres/Migrations/20230728181916_AccountAccessInvites2.cs new file mode 100644 index 0000000..30b2752 --- /dev/null +++ b/MyOffice.Migration.Postgres/Migrations/20230728181916_AccountAccessInvites2.cs @@ -0,0 +1,51 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace MyOffice.Migrations.Postgres.Migrations +{ + /// + public partial class AccountAccessInvites2 : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "UserId", + table: "AccountAccessInvites", + type: "uuid", + nullable: false, + defaultValue: new Guid("00000000-0000-0000-0000-000000000000")); + + migrationBuilder.CreateIndex( + name: "IX_AccountAccessInvites_UserId", + table: "AccountAccessInvites", + column: "UserId"); + + migrationBuilder.AddForeignKey( + name: "FK_AccountAccessInvites_Users_UserId", + table: "AccountAccessInvites", + column: "UserId", + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_AccountAccessInvites_Users_UserId", + table: "AccountAccessInvites"); + + migrationBuilder.DropIndex( + name: "IX_AccountAccessInvites_UserId", + table: "AccountAccessInvites"); + + migrationBuilder.DropColumn( + name: "UserId", + table: "AccountAccessInvites"); + } + } +} diff --git a/MyOffice.Migration.Postgres/Migrations/20230728192017_AccountAccessInvites3.Designer.cs b/MyOffice.Migration.Postgres/Migrations/20230728192017_AccountAccessInvites3.Designer.cs new file mode 100644 index 0000000..4574f4d --- /dev/null +++ b/MyOffice.Migration.Postgres/Migrations/20230728192017_AccountAccessInvites3.Designer.cs @@ -0,0 +1,728 @@ +// +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("20230728192017_AccountAccessInvites3")] + partial class AccountAccessInvites3 + { + /// + 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("AccountId") + .HasColumnType("uuid"); + + 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.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("AccountId"); + + b.HasIndex("UserId"); + + 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.AccountAccessInvite", b => + { + b.HasOne("MyOffice.Data.Models.Accounts.Account", "Account") + .WithMany("Invites") + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("MyOffice.Data.Models.Users.User", "User") + .WithMany("AccountAccessInvites") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Account"); + + 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("Invites"); + + 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("AccountAccessInvites"); + + 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/20230728192017_AccountAccessInvites3.cs b/MyOffice.Migration.Postgres/Migrations/20230728192017_AccountAccessInvites3.cs new file mode 100644 index 0000000..7f22a0c --- /dev/null +++ b/MyOffice.Migration.Postgres/Migrations/20230728192017_AccountAccessInvites3.cs @@ -0,0 +1,51 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace MyOffice.Migrations.Postgres.Migrations +{ + /// + public partial class AccountAccessInvites3 : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "AccountId", + table: "AccountAccessInvites", + type: "uuid", + nullable: false, + defaultValue: new Guid("00000000-0000-0000-0000-000000000000")); + + migrationBuilder.CreateIndex( + name: "IX_AccountAccessInvites_AccountId", + table: "AccountAccessInvites", + column: "AccountId"); + + migrationBuilder.AddForeignKey( + name: "FK_AccountAccessInvites_Accounts_AccountId", + table: "AccountAccessInvites", + column: "AccountId", + principalTable: "Accounts", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_AccountAccessInvites_Accounts_AccountId", + table: "AccountAccessInvites"); + + migrationBuilder.DropIndex( + name: "IX_AccountAccessInvites_AccountId", + table: "AccountAccessInvites"); + + migrationBuilder.DropColumn( + name: "AccountId", + table: "AccountAccessInvites"); + } + } +} diff --git a/MyOffice.Migration.Postgres/Migrations/AppDbContextModelSnapshot.cs b/MyOffice.Migration.Postgres/Migrations/AppDbContextModelSnapshot.cs index 9c3cc9a..d12a24f 100644 --- a/MyOffice.Migration.Postgres/Migrations/AppDbContextModelSnapshot.cs +++ b/MyOffice.Migration.Postgres/Migrations/AppDbContextModelSnapshot.cs @@ -102,6 +102,9 @@ namespace MyOffice.Migrations.Postgres.Migrations b.Property("AcceptedOn") .HasColumnType("timestamp with time zone"); + b.Property("AccountId") + .HasColumnType("uuid"); + b.Property("CreatedOn") .HasColumnType("timestamp with time zone"); @@ -115,8 +118,15 @@ namespace MyOffice.Migrations.Postgres.Migrations b.Property("RejectedOn") .HasColumnType("timestamp with time zone"); + b.Property("UserId") + .HasColumnType("uuid"); + b.HasKey("Id"); + b.HasIndex("AccountId"); + + b.HasIndex("UserId"); + b.ToTable("AccountAccessInvites"); }); @@ -481,6 +491,25 @@ namespace MyOffice.Migrations.Postgres.Migrations b.Navigation("User"); }); + modelBuilder.Entity("MyOffice.Data.Models.Accounts.AccountAccessInvite", b => + { + b.HasOne("MyOffice.Data.Models.Accounts.Account", "Account") + .WithMany("Invites") + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("MyOffice.Data.Models.Users.User", "User") + .WithMany("AccountAccessInvites") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Account"); + + b.Navigation("User"); + }); + modelBuilder.Entity("MyOffice.Data.Models.Accounts.AccountAccountCategory", b => { b.HasOne("MyOffice.Data.Models.Accounts.Account", "Account") @@ -628,6 +657,8 @@ namespace MyOffice.Migrations.Postgres.Migrations b.Navigation("Categories"); + b.Navigation("Invites"); + b.Navigation("Motions"); }); @@ -672,6 +703,8 @@ namespace MyOffice.Migrations.Postgres.Migrations { b.Navigation("AccountAccess"); + b.Navigation("AccountAccessInvites"); + b.Navigation("AccountAccessOwners"); b.Navigation("AccountCategories"); diff --git a/MyOffice.SPA/src/app/api-routes.ts b/MyOffice.SPA/src/app/api-routes.ts index 36cd7f7..0a2c41a 100644 --- a/MyOffice.SPA/src/app/api-routes.ts +++ b/MyOffice.SPA/src/app/api-routes.ts @@ -18,6 +18,9 @@ export class ApiRoutes { static SettingsAccountAccesses = '/api/settings/accounts/:id/access'; static SettingsAccountAccess = '/api/settings/accounts/:id/access/:access'; static SettingsAccountAccountCategory = '/api/settings/accounts/:id/category/:categoryId'; + static SettingsAccountInvites = '/api/settings/accounts/invites'; + static SettingsAccountInviteAccept = '/api/settings/accounts/invites/:id/accept'; + static SettingsAccountInviteReject = '/api/settings/accounts/invites/:id/reject'; static SettingsItemCategories = '/api/settings/item-categories'; static SettingsItemCategory = '/api/settings/item-categories/:id'; diff --git a/MyOffice.SPA/src/app/core/interceptor/error.interceptor.ts b/MyOffice.SPA/src/app/core/interceptor/error.interceptor.ts index 91e172a..1b0c7bd 100644 --- a/MyOffice.SPA/src/app/core/interceptor/error.interceptor.ts +++ b/MyOffice.SPA/src/app/core/interceptor/error.interceptor.ts @@ -24,8 +24,12 @@ export class ErrorInterceptor implements HttpInterceptor { this.authenticationService.logout(); location.reload(); } - console.log(err); - const error = err.message || err.error.message || err.statusText; + let error = err.message || err.statusText; + error = !err.error ? error : err.error.message || err.error.title; + + if (!error) { + console.log('not parsed error', err); + } return throwError(error); //return throwError(err.error || err); }) diff --git a/MyOffice.SPA/src/app/model/account.invite.model.ts b/MyOffice.SPA/src/app/model/account.invite.model.ts new file mode 100644 index 0000000..7c45d0b --- /dev/null +++ b/MyOffice.SPA/src/app/model/account.invite.model.ts @@ -0,0 +1,5 @@ +export interface AccountInviteModel { + id?: string, + account?: string, + allowWrite: boolean, +} diff --git a/MyOffice.SPA/src/app/pages/pages.module.ts b/MyOffice.SPA/src/app/pages/pages.module.ts index e0e82f6..56c9715 100644 --- a/MyOffice.SPA/src/app/pages/pages.module.ts +++ b/MyOffice.SPA/src/app/pages/pages.module.ts @@ -21,6 +21,7 @@ import { NgxCurrencyModule } from "ngx-currency"; import { MatExpansionModule } from '@angular/material/expansion'; import { MatAutocompleteModule } from '@angular/material/autocomplete'; import { MatCheckboxModule } from '@angular/material/checkbox'; +import { MatMenuModule } from '@angular/material/menu'; // app import { PagesRoutingModule } from './pages-routing.module'; @@ -88,6 +89,7 @@ import { SettingsItemCategoryEditComponent } from './settings/item/edit.item.cat NgxMaskModule, NgxCurrencyModule, MatCheckboxModule, + MatMenuModule, ], providers: [ { provide: MAT_DATE_LOCALE, useValue: 'en-GB' }, 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 e60c1f4..bdca80e 100644 --- a/MyOffice.SPA/src/app/pages/settings/account/account.component.html +++ b/MyOffice.SPA/src/app/pages/settings/account/account.component.html @@ -17,13 +17,19 @@
+
+ + + + +
- + 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 4829470..241461d 100644 --- a/MyOffice.SPA/src/app/pages/settings/account/account.component.ts +++ b/MyOffice.SPA/src/app/pages/settings/account/account.component.ts @@ -10,6 +10,7 @@ import { MatDialog } from '@angular/material/dialog'; import { ApiRoutes } from '../../../api-routes'; import { AccountModel } from '../../../model/account.model'; import { AccountCategoryModel } from '../../../model/account.category.model'; +import { AccountInviteModel } from '../../../model/account.invite.model'; import { SettingsAccountAddComponent } from './add.account.component'; import { SettingsAccountEditComponent } from './edit.account.component'; import { SettingsAccountAccessComponent } from './access.account.component'; @@ -22,6 +23,8 @@ import { AccountCategoryService } from '../../../services/account.category.servi export class SettingsAccountComponent { public accountCategories?: AccountCategoryModel[]; public accounts?: AccountModel[]; + public invites?: AccountInviteModel[]; + public tabIndex = 0; constructor( private httpClient: HttpClient, @@ -32,13 +35,35 @@ export class SettingsAccountComponent { ngOnInit(): void { this.loadCategories(); + this.loadInvites(); } - private loadAccounts() { + private loadAccounts(accountToShow?: string) { this.httpClient .get(ApiRoutes.SettingsAccounts) .subscribe(data => { this.accounts = data; + if (accountToShow) { + var idx = -1; + var account = this.accounts.find(x => x.id === accountToShow); + if (account && account.categories && account.categories.length > 0) { + for (var i = 0; i < this.accountCategories!.length; i++) { + if (account!.categories!.find(c => c.id === this.accountCategories![i].id)) { + idx = i; + break; + } + } + } + this.tabIndex = idx === -1 ? this.accountCategories!.length : idx; + } + }); + } + + private loadInvites() { + return this.httpClient + .get(ApiRoutes.SettingsAccountInvites) + .subscribe(response => { + this.invites = response; }); } @@ -118,6 +143,57 @@ export class SettingsAccountComponent { this.loadAccounts(); } }); + } + + public startInvite(invite: AccountInviteModel) { + Swal.fire({ + title: 'Accept to invite the account "' + invite.account + '"', + input: 'text', + inputLabel: 'Accept with name', + inputValue: invite.account, + showDenyButton: true, + showCancelButton: true, + confirmButtonText: 'Accept', + denyButtonText: 'Reject', + showLoaderOnConfirm: true, + preConfirm: (input) => { + return new Promise((resolve, reject) => { + this.httpClient.post(ApiRoutes.SettingsAccountInviteAccept.replace(':id', invite.id!), { name: input }) + .subscribe(data => { + resolve(data); + }, error => { + Swal.showValidationMessage(error); + reject(); + }); + + }).catch(x => { + return false; + }); + }, + preDeny: (x) => { + return new Promise((resolve, reject) => { + this.httpClient.post(ApiRoutes.SettingsAccountInviteReject.replace(':id', invite.id!), null) + .subscribe(data => { + resolve(data); + }, error => { + Swal.showValidationMessage(error.detail); + reject(); + }); + + }).catch(x => { + return false; + }); + }, + allowOutsideClick: () => !Swal.isLoading(), + }).then((result) => { + var id; + if (result.value) { + var account = result.value as AccountModel; + id = account.id; + } + this.loadInvites(); + this.loadAccounts(id); + }); } } diff --git a/MyOffice.Services/Account/AccountService.cs b/MyOffice.Services/Account/AccountService.cs index b75568a..e5b842f 100644 --- a/MyOffice.Services/Account/AccountService.cs +++ b/MyOffice.Services/Account/AccountService.cs @@ -14,6 +14,7 @@ using Microsoft.Extensions.Logging; using MyOffice.Services.Account.Domain; using System.Collections.Generic; + using Identity; public class AccountService { @@ -29,6 +30,7 @@ private readonly IItemRepository _itemRepository; private readonly IItemGlobalRepository _itemGlobalRepository; private readonly ItemService _itemService; + private readonly IContextProvider _contextProvider; public AccountService( ILogger logger, @@ -42,7 +44,8 @@ IMotionRepository motionRepository, IItemRepository itemRepository, IItemGlobalRepository itemGlobalRepository, - ItemService itemService + ItemService itemService, + IContextProvider contextProvider ) { _logger = logger; @@ -57,6 +60,7 @@ _itemRepository = itemRepository; _itemGlobalRepository = itemGlobalRepository; _itemService = itemService; + _contextProvider = contextProvider; } public List GetAllCategories(Guid userId) @@ -69,8 +73,8 @@ public Exec GetCategory(Guid userId, Guid id) { var result = new Exec(GeneralExecStatus.success); - - var category = _accountCategoryRepository.Get(userId, id); + + var category = _accountCategoryRepository.Get(userId, id); if (category == null) { @@ -456,7 +460,7 @@ exists.Description = motion.Description; exists.AmountPlus = motion.Plus; exists.AmountMinus = motion.Minus; - + if (!_motionRepository.Update(exists)) { return result.Set(MotionUpdateStatus.failure); @@ -467,8 +471,8 @@ } public Exec, GeneralExecStatus> GetMotions( - Guid userId, - Guid accountId, + Guid userId, + Guid accountId, DateTime dateFrom, DateTime dateTo ) @@ -528,13 +532,13 @@ if (account.AccessRights!.Any(x => x.User!.Email.EqualsIgnoreCase(email))) { - return result.Set(AccessInviteStatus.account_not_found); + return result.Set(AccessInviteStatus.access_exists); } var access = _accountAccessInviteRepository.Get(userId, email); if (access != null) { - return result.Set(AccessInviteStatus.access_exists); + return result.Set(AccessInviteStatus.invite_exists); } var invite = new AccountAccessInvite @@ -542,7 +546,8 @@ Id = Guid.NewGuid(), CreatedOn = DateTime.UtcNow, UserId = userId, - Email = email, + AccountId = account.Id, + Email = email.SafeTrim()!, IsAllowWrite = isAllowWrite, }; @@ -606,5 +611,75 @@ return result.Set(_mapper.Map(account)); } + + public List InvitesGet(string email) + { + return _mapper.Map>(_accountAccessInviteRepository.GetActive(email).ToList()); + } + + public Exec InviteAccept(Guid userId, Guid id, string name) + { + var result = new Exec(InviteAcceptStatus.success); + + var invite = _accountAccessInviteRepository.Get(id); + if (invite == null + || !invite.Email.EqualsIgnoreCase(_contextProvider.User.Email) + || invite.AcceptedOn.HasValue + || invite.RejectedOn.HasValue + ) + { + return result.Set(InviteAcceptStatus.invite_not_found); + } + + var account = _accountRepository.Get(invite.UserId, invite.AccountId); + if (account == null) + { + return result.Set(InviteAcceptStatus.account_not_found); + } + + if (account.AccessRights!.Any(x => x.UserId == userId)) + { + result.Set(InviteAcceptStatus.already_accepted); + } + else + { + _accountAccessRepository.Add(new AccountAccess + { + UserId = userId, + AccountId = account.Id, + OwnerId = invite.UserId, + Name = name, + IsAllowWrite = invite.IsAllowWrite, + Type = AccountAccessTypeEnum.external, + }); + } + + invite.AcceptedOn = DateTime.UtcNow; + _accountAccessInviteRepository.Update(invite); + + account = _accountRepository.Get(userId, account.Id); + + return result.Set(_mapper.Map(account)); + } + + public Exec InviteReject(Guid id) + { + var result = new Exec(GeneralExecStatus.success); + + var invite = _accountAccessInviteRepository.Get(id); + if (invite == null + || !invite.Email.EqualsIgnoreCase(_contextProvider.User.Email) + || invite.AcceptedOn.HasValue + || invite.RejectedOn.HasValue + ) + { + return result.Set(GeneralExecStatus.not_found); + } + + invite.RejectedOn = DateTime.UtcNow; + _accountAccessInviteRepository.Update(invite); + + return result.Set(_mapper.Map(invite)); + } } } diff --git a/MyOffice.Services/Account/Domain/AccountAccessDto.cs b/MyOffice.Services/Account/Domain/AccountAccessDto.cs index e37fc38..f366027 100644 --- a/MyOffice.Services/Account/Domain/AccountAccessDto.cs +++ b/MyOffice.Services/Account/Domain/AccountAccessDto.cs @@ -1,8 +1,12 @@ namespace MyOffice.Services.Account.Domain; +using Core.Attributes; using Data.Models.Accounts; using Data.Models.Users; +using Mapper; +[Link(typeof(AccountAccess))] +[Link(typeof(AccountServiceProfile))] public class AccountAccessDto { /// diff --git a/MyOffice.Services/Account/Domain/AccountAccessInviteDto.cs b/MyOffice.Services/Account/Domain/AccountAccessInviteDto.cs new file mode 100644 index 0000000..1588449 --- /dev/null +++ b/MyOffice.Services/Account/Domain/AccountAccessInviteDto.cs @@ -0,0 +1,12 @@ +namespace MyOffice.Services.Account.Domain; + +using Core.Attributes; +using Data.Models.Accounts; + +[Link(typeof(AccountAccessInvite))] +public class AccountAccessInviteDto +{ + public string Id { get; set; } = null!; + public string Account { get; set; } = null!; + public bool IsAllowWrite { 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 4f1f965..960b650 100644 --- a/MyOffice.Services/Account/Domain/AccountDto.cs +++ b/MyOffice.Services/Account/Domain/AccountDto.cs @@ -2,11 +2,11 @@ using MyOffice.Data.Models.Currencies; using System; +using Data.Models.Accounts; 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; } @@ -20,4 +20,8 @@ public class AccountDto public string Type { get; set; } = null!; public List? AccessRights { get; set; } public List? Categories { get; set; } + + + public Guid CurrentUserId { get; set; } + public AccountAccess? AccountAccess { get; set; } } diff --git a/MyOffice.Services/Account/Domain/InviteAcceptStatus.cs b/MyOffice.Services/Account/Domain/InviteAcceptStatus.cs new file mode 100644 index 0000000..cfec492 --- /dev/null +++ b/MyOffice.Services/Account/Domain/InviteAcceptStatus.cs @@ -0,0 +1,10 @@ +namespace MyOffice.Services.Account.Domain +{ + public enum InviteAcceptStatus + { + success, + invite_not_found, + account_not_found, + already_accepted, + } +} diff --git a/MyOffice.Services/Mapper/AccountServiceProfile.cs b/MyOffice.Services/Mapper/AccountServiceProfile.cs index e0fcd62..04598c4 100644 --- a/MyOffice.Services/Mapper/AccountServiceProfile.cs +++ b/MyOffice.Services/Mapper/AccountServiceProfile.cs @@ -14,7 +14,20 @@ public class AccountServiceProfile : Profile 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())) + .ForMember(x => x.AccountAccess, o => + { + o.MapFrom((src, dst) => src.AccessRights!.FirstOrDefault(x => x.UserId == dst.CurrentUserId)); + }) + .ForMember(x => x.Type, o => + { + o.SetMappingOrder(1); + o.MapFrom((src, dst) => dst.AccountAccess?.Type.ToString()); + }) + .ForMember(x => x.Name, o => + { + o.SetMappingOrder(1); + o.MapFrom((src, dst) => dst.AccountAccess?.Name ?? src.Name); + }) ; CreateMap(); @@ -27,5 +40,9 @@ public class AccountServiceProfile : Profile .ForMember(x => x.Id, o => o.MapFrom(x => x.Id)) .ForMember(x => x.AllowDelete, o => o.MapFrom(x => !x.Accounts!.Any())) ; + + CreateMap() + .ForMember(x => x.Account, o => o.MapFrom(x => x.Account!.Name)) + ; } } \ No newline at end of file diff --git a/MyOffice.Web/Controllers/SettingsAccountController.cs b/MyOffice.Web/Controllers/SettingsAccountController.cs index e734ec1..bd18f0f 100644 --- a/MyOffice.Web/Controllers/SettingsAccountController.cs +++ b/MyOffice.Web/Controllers/SettingsAccountController.cs @@ -10,6 +10,7 @@ using Models.Account; using Services.Account; using Services.Account.Domain; using MyOffice.Web.Infrastructure.Attributes; +using Services.Identity; [Authorize] [ApiController] @@ -19,16 +20,19 @@ public class SettingsAccountController : BaseApiController private readonly ILogger _logger; private readonly IMapper _mapper; private readonly AccountService _accountService; + private readonly IContextProvider _contextProvider; public SettingsAccountController( ILogger logger, IMapper mapper, - AccountService accountService + AccountService accountService, + IContextProvider contextProvider ) { _logger = logger; _mapper = mapper; _accountService = accountService; + _contextProvider = contextProvider; } [HttpGet("~/api/settings/account-categories")] @@ -251,8 +255,6 @@ public class SettingsAccountController : BaseApiController return ProblemBadRequest("Account not found."); case AccessInviteStatus.access_exists: - return ProblemBadRequest("Access allowed."); - case AccessInviteStatus.invite_exists: case AccessInviteStatus.success: return exec.Result!; @@ -279,6 +281,53 @@ public class SettingsAccountController : BaseApiController default: throw new NotSupportedException(exec.Status.ToString()); } + } + [HttpGet("~/api/settings/accounts/invites")] + public List AccountInvites() + { + var invites = _accountService.InvitesGet(_contextProvider.User.Email); + + return _mapper.Map>(invites); + } + + [HttpPost("~/api/settings/accounts/invites/{id}/accept")] + public object AccountInviteAccept([AsGuid]string id, AccountInviteAcceptRequest request) + { + var exec = _accountService.InviteAccept(UserId, id.AsGuid(), request.Name); + + switch (exec.Status) + { + case InviteAcceptStatus.invite_not_found: + return ProblemBadRequest("Invite not found."); + case InviteAcceptStatus.account_not_found: + return ProblemBadRequest("Account not found."); + + case InviteAcceptStatus.already_accepted: + case InviteAcceptStatus.success: + return _mapper.Map(exec.Result!); + + default: + throw new NotSupportedException(exec.Status.ToString()); + } + } + + [HttpPost("~/api/settings/accounts/invites/{id}/reject")] + public object AccountInviteReject([AsGuid] string id) + { + var exec = _accountService.InviteReject(id.AsGuid()); + + switch (exec.Status) + { + case GeneralExecStatus.not_found: + case GeneralExecStatus.failure: + return ProblemBadRequest("Invite not found."); + + case GeneralExecStatus.success: + return _mapper.Map(exec.Result!); + + default: + throw new NotSupportedException(exec.Status.ToString()); + } } } \ No newline at end of file diff --git a/MyOffice.Web/Models/Account/AccountAccessInviteViewModel.cs b/MyOffice.Web/Models/Account/AccountAccessInviteViewModel.cs new file mode 100644 index 0000000..6fbaf55 --- /dev/null +++ b/MyOffice.Web/Models/Account/AccountAccessInviteViewModel.cs @@ -0,0 +1,13 @@ +namespace MyOffice.Web.Models.Account; + +using Core.Attributes; +using Services.Account.Domain; + +[LinkFrom(typeof(AccountAccessInviteDto))] +[LinkWith(typeof(AccountViewModelProfile))] +public class AccountAccessInviteViewModel +{ + public string? Id { get; set; } + public string? Account { get; set; } + public bool? AllowWrite { get; set; } +} \ No newline at end of file diff --git a/MyOffice.Web/Models/Account/AccountCategoryViewModel.cs b/MyOffice.Web/Models/Account/AccountCategoryViewModel.cs index 98cface..b3818a0 100644 --- a/MyOffice.Web/Models/Account/AccountCategoryViewModel.cs +++ b/MyOffice.Web/Models/Account/AccountCategoryViewModel.cs @@ -1,29 +1,16 @@ -namespace MyOffice.Web.Models.Account +namespace MyOffice.Web.Models.Account; + +using MyOffice.Core.Attributes; +using MyOffice.Services.Account.Domain; +using System.ComponentModel.DataAnnotations; + +[Link(typeof(AccountCategoryDto))] +public class AccountCategoryViewModel { - using System.ComponentModel.DataAnnotations; - using Core.Extensions; - using Data.Models.Accounts; + public string? Id { get; set; } - public class AccountCategoryViewModel - { - public string? Id { get; set; } - - [Required] - public string? Name { get; set; } + [Required] + public string? Name { get; set; } - public bool? AllowDelete { get; set; } - } - - public static class AccountCategoryViewModelExtensions - { - public static AccountCategoryViewModel ToModel(this AccountCategory input) - { - return new AccountCategoryViewModel - { - Id = input.Id.ToShort(), - Name = input.Name, - AllowDelete = !input.Accounts?.Any(), - }; - } - } -} + public bool? AllowDelete { get; set; } +} \ No newline at end of file diff --git a/MyOffice.Web/Models/Account/AccountInviteAcceptRequest.cs b/MyOffice.Web/Models/Account/AccountInviteAcceptRequest.cs new file mode 100644 index 0000000..246160f --- /dev/null +++ b/MyOffice.Web/Models/Account/AccountInviteAcceptRequest.cs @@ -0,0 +1,6 @@ +namespace MyOffice.Web.Models.Account; + +public class AccountInviteAcceptRequest +{ + public string Name { get; set; } = null!; +} diff --git a/MyOffice.Web/Models/Account/AccountViewModelProfile.cs b/MyOffice.Web/Models/Account/AccountViewModelProfile.cs index 2c3ffb2..decf2d8 100644 --- a/MyOffice.Web/Models/Account/AccountViewModelProfile.cs +++ b/MyOffice.Web/Models/Account/AccountViewModelProfile.cs @@ -3,11 +3,7 @@ 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 { @@ -61,11 +57,15 @@ public class AccountViewModelProfile : Profile (src, dst) => src.OwnerId == src.UserId)) ; - CreateMap() + CreateMap() .ForMember(x => x.IsAllowWrite, o => o.MapFrom(x => x.AllowWrite)) ; CreateMap() ; + + CreateMap() + .ForMember(x => x.AllowWrite, o => o.MapFrom(x => x.IsAllowWrite)) + ; } } \ No newline at end of file diff --git a/MyOffice.Web/Program.cs b/MyOffice.Web/Program.cs index 4047fef..8a9112e 100644 --- a/MyOffice.Web/Program.cs +++ b/MyOffice.Web/Program.cs @@ -64,6 +64,13 @@ using MyOffice.Services.Mapper; //TODO: Response model from base type //TODO: XXXResult -> XXXStatus //TODO: SPA isAllowDelete -> allowDelete (remove is) +//TODO: Repository auto registration +//TODO: Services auto registration +//TODO: openid-configuration failed - lock login +//TODO: BUG some times after login redirect to dashboard but exists return url +//TODO: email confirmation +//TODO: email password reset +//TODO: automapper -> Extension ToModel() ToDbo() FromModel() FromDbo() public class Program { @@ -262,6 +269,7 @@ public class Program builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); + builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped();