From 033b4fc247b16d092593da6cf2c77fb0a9b000cd Mon Sep 17 00:00:00 2001 From: Alexandr Sulimov Date: Thu, 4 Jun 2026 09:44:23 +0300 Subject: [PATCH] fix --- MyOffice.Core/Error.cs | 23 + MyOffice.Core/Helpers/RandomizationHelper.cs | 18 + .../Notifications/EmailTemplate.cs | 17 + .../Verifications/VerificationCode.cs | 18 + .../VerificationCodeTemplateEnum.cs | 7 + .../Verifications/VerificationCodeTypeEnum.cs | 7 + .../Notifications/EmailTemplateRepository.cs | 12 + .../Notifications/IEmailTemplateRepository.cs | 8 + .../IVerificationCodeRepository.cs | 11 + .../VerificationCodeRepository.cs | 22 + MyOffice.DbContext/AppDbContext.cs | 104 ++- .../20231217163807_acc_cat_unique.Designer.cs | 730 ++++++++++++++++ .../20231217163807_acc_cat_unique.cs | 37 + ...231217185452_acc_access_unique.Designer.cs | 733 ++++++++++++++++ .../20231217185452_acc_access_unique.cs | 28 + ...0240107073407_VerificationCode.Designer.cs | 790 ++++++++++++++++++ .../20240107073407_VerificationCode.cs | 55 ++ ...240107075033_VerificationCode2.Designer.cs | 790 ++++++++++++++++++ .../20240107075033_VerificationCode2.cs | 22 + .../Migrations/AppDbContextModelSnapshot.cs | 65 +- .../app/pages/accounts/account.component.ts | 16 +- MyOffice.Services/Account/Domain/MotionDto.cs | 11 + .../Notifications/EmailNotificationService.cs | 80 ++ .../Verifications/VerificationService.cs | 51 ++ .../Controllers/SettingsAccountController.cs | 39 +- .../Attributes/DefaultFromBodyAttribute.cs | 9 +- .../Account/AccountAccessInviteViewModel.cs | 2 +- MyOffice.Web/Models/Motion/MotionViewModel.cs | 6 +- MyOffice.Web/Program.cs | 9 +- 29 files changed, 3661 insertions(+), 59 deletions(-) create mode 100644 MyOffice.Core/Error.cs create mode 100644 MyOffice.Core/Helpers/RandomizationHelper.cs create mode 100644 MyOffice.Data.Models/Notifications/EmailTemplate.cs create mode 100644 MyOffice.Data.Models/Verifications/VerificationCode.cs create mode 100644 MyOffice.Data.Models/Verifications/VerificationCodeTemplateEnum.cs create mode 100644 MyOffice.Data.Models/Verifications/VerificationCodeTypeEnum.cs create mode 100644 MyOffice.Data.Repositories/Notifications/EmailTemplateRepository.cs create mode 100644 MyOffice.Data.Repositories/Notifications/IEmailTemplateRepository.cs create mode 100644 MyOffice.Data.Repositories/Verifications/IVerificationCodeRepository.cs create mode 100644 MyOffice.Data.Repositories/Verifications/VerificationCodeRepository.cs create mode 100644 MyOffice.Migration.Postgres/Migrations/20231217163807_acc_cat_unique.Designer.cs create mode 100644 MyOffice.Migration.Postgres/Migrations/20231217163807_acc_cat_unique.cs create mode 100644 MyOffice.Migration.Postgres/Migrations/20231217185452_acc_access_unique.Designer.cs create mode 100644 MyOffice.Migration.Postgres/Migrations/20231217185452_acc_access_unique.cs create mode 100644 MyOffice.Migration.Postgres/Migrations/20240107073407_VerificationCode.Designer.cs create mode 100644 MyOffice.Migration.Postgres/Migrations/20240107073407_VerificationCode.cs create mode 100644 MyOffice.Migration.Postgres/Migrations/20240107075033_VerificationCode2.Designer.cs create mode 100644 MyOffice.Migration.Postgres/Migrations/20240107075033_VerificationCode2.cs create mode 100644 MyOffice.Services/Notifications/EmailNotificationService.cs create mode 100644 MyOffice.Services/Verifications/VerificationService.cs diff --git a/MyOffice.Core/Error.cs b/MyOffice.Core/Error.cs new file mode 100644 index 0000000..48cf996 --- /dev/null +++ b/MyOffice.Core/Error.cs @@ -0,0 +1,23 @@ +namespace MyOffice.Core; + +public class Error +{ + private Exception? _exception; + + public Error(string message) + { + Message = message; + } + + public Error(Exception exception): this(exception.GetType().Name) + { + _exception = exception; + } + + public Error(string message, Exception exception): this(message) + { + _exception = exception; + } + + public string Message { get; private set;} +} \ No newline at end of file diff --git a/MyOffice.Core/Helpers/RandomizationHelper.cs b/MyOffice.Core/Helpers/RandomizationHelper.cs new file mode 100644 index 0000000..f240a4f --- /dev/null +++ b/MyOffice.Core/Helpers/RandomizationHelper.cs @@ -0,0 +1,18 @@ +namespace MyOffice.Core.Helpers; + +using MyOffice.Core.Extensions; + +public class RandomizationHelper +{ + public static string Generate(int length) + { + var result = ""; + + while(result.Length < length) + { + result += Guid.NewGuid().ToShort(); + } + + return result.Substring(0, length); + } +} diff --git a/MyOffice.Data.Models/Notifications/EmailTemplate.cs b/MyOffice.Data.Models/Notifications/EmailTemplate.cs new file mode 100644 index 0000000..4b17ef2 --- /dev/null +++ b/MyOffice.Data.Models/Notifications/EmailTemplate.cs @@ -0,0 +1,17 @@ +namespace MyOffice.Data.Models.Notifications; + +public enum EmailTemplateEnum +{ + PasswordRestore, +} + +public class EmailTemplate +{ + public string Id { get; set; } + public DateTime CreatedOn { get; set; } + public string Description { get; set; } + public string Subject { get; set; } + public string Sender { get; set; } + public string SenderName { get; set; } + public string Template { get; set; } +} \ No newline at end of file diff --git a/MyOffice.Data.Models/Verifications/VerificationCode.cs b/MyOffice.Data.Models/Verifications/VerificationCode.cs new file mode 100644 index 0000000..272f823 --- /dev/null +++ b/MyOffice.Data.Models/Verifications/VerificationCode.cs @@ -0,0 +1,18 @@ +namespace MyOffice.Data.Models.Verifications; + +using MyOffice.Data.Models.Users; + +public class VerificationCode +{ + public int Id { get; set; } + public Guid UserId { get; set; } + public User? User { get; set; } + public string Code { get; set; } = null!; + public string DestinationType { get; set; } = null!; + public string Destination { get; set; } = null!; + public DateTime CreatedOn { get; set; } + public DateTime ExpiresOn { get; set; } + public DateTime? VerifiedOn { get; set; } + public string Template { get; set; } = null!; + public string? Metadata { get; set; } +} \ No newline at end of file diff --git a/MyOffice.Data.Models/Verifications/VerificationCodeTemplateEnum.cs b/MyOffice.Data.Models/Verifications/VerificationCodeTemplateEnum.cs new file mode 100644 index 0000000..96096a0 --- /dev/null +++ b/MyOffice.Data.Models/Verifications/VerificationCodeTemplateEnum.cs @@ -0,0 +1,7 @@ +namespace MyOffice.Data.Models.Verifications; + +public enum VerificationCodeTemplateEnum +{ + password_restore, + email_confirm, +} diff --git a/MyOffice.Data.Models/Verifications/VerificationCodeTypeEnum.cs b/MyOffice.Data.Models/Verifications/VerificationCodeTypeEnum.cs new file mode 100644 index 0000000..642a403 --- /dev/null +++ b/MyOffice.Data.Models/Verifications/VerificationCodeTypeEnum.cs @@ -0,0 +1,7 @@ +namespace MyOffice.Data.Models.Verifications; + +public enum VerificationCodeTypeEnum +{ + email, + phone, +} diff --git a/MyOffice.Data.Repositories/Notifications/EmailTemplateRepository.cs b/MyOffice.Data.Repositories/Notifications/EmailTemplateRepository.cs new file mode 100644 index 0000000..ed693fb --- /dev/null +++ b/MyOffice.Data.Repositories/Notifications/EmailTemplateRepository.cs @@ -0,0 +1,12 @@ +namespace MyOffice.Data.Repositories.Item; + +using MyOffice.Data.Models.Notifications; + +public class EmailTemplateRepository : AppRepository, IEmailTemplateRepository +{ + public EmailTemplate? Get(EmailTemplateEnum emailTemplate) + { + var emailTemplateStr = emailTemplate.ToString(); + return _context.EmailTemplates.FirstOrDefault(x => x.Id == emailTemplateStr); + } +} \ No newline at end of file diff --git a/MyOffice.Data.Repositories/Notifications/IEmailTemplateRepository.cs b/MyOffice.Data.Repositories/Notifications/IEmailTemplateRepository.cs new file mode 100644 index 0000000..65849f2 --- /dev/null +++ b/MyOffice.Data.Repositories/Notifications/IEmailTemplateRepository.cs @@ -0,0 +1,8 @@ +namespace MyOffice.Data.Repositories.Item; + +using MyOffice.Data.Models.Notifications; + +public interface IEmailTemplateRepository +{ + EmailTemplate? Get(EmailTemplateEnum emailTemplate); +} \ No newline at end of file diff --git a/MyOffice.Data.Repositories/Verifications/IVerificationCodeRepository.cs b/MyOffice.Data.Repositories/Verifications/IVerificationCodeRepository.cs new file mode 100644 index 0000000..4c07131 --- /dev/null +++ b/MyOffice.Data.Repositories/Verifications/IVerificationCodeRepository.cs @@ -0,0 +1,11 @@ +using MyOffice.Data.Models.Verifications; + +namespace MyOffice.Data.Repositories.Item; + + +public interface IVerificationCodeRepository +{ + VerificationCode? GetByCode(string code); + bool Add(VerificationCode entity); + bool Update(VerificationCode entity); +} \ No newline at end of file diff --git a/MyOffice.Data.Repositories/Verifications/VerificationCodeRepository.cs b/MyOffice.Data.Repositories/Verifications/VerificationCodeRepository.cs new file mode 100644 index 0000000..687c1e5 --- /dev/null +++ b/MyOffice.Data.Repositories/Verifications/VerificationCodeRepository.cs @@ -0,0 +1,22 @@ +namespace MyOffice.Data.Repositories.Item; + +using Microsoft.EntityFrameworkCore; +using MyOffice.Data.Models.Verifications; + +public class VerificationCodeRepository : AppRepository, IVerificationCodeRepository +{ + public bool Add(VerificationCode entity) + { + return AddBase(entity) > 0; + } + + public bool Update(VerificationCode entity) + { + return UpdateBase(entity) > 0; + } + + public VerificationCode? GetByCode(string code) + { + return _context.Verifications.FirstOrDefault(x => x.Code == code); + } +} \ No newline at end of file diff --git a/MyOffice.DbContext/AppDbContext.cs b/MyOffice.DbContext/AppDbContext.cs index 088a012..1ce105d 100644 --- a/MyOffice.DbContext/AppDbContext.cs +++ b/MyOffice.DbContext/AppDbContext.cs @@ -6,6 +6,8 @@ using Data.Models.Items; using Microsoft.EntityFrameworkCore; using Data.Models.Users; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using MyOffice.Data.Models.Verifications; +using MyOffice.Data.Models.Notifications; public enum AppDbContextProvidersEnum { @@ -46,6 +48,9 @@ public class AppDbContext : DbContext public DbSet Items { get; set; } = null!; public DbSet Motions { get; set; } = null!; + public DbSet Verifications { get; set; } = null!; + public DbSet EmailTemplates { get; set; } = null!; + protected override void OnModelCreating(ModelBuilder modelBuilder) { var noCaseCollation = _noCaseCollation[_provider]; @@ -82,10 +87,29 @@ public class AppDbContext : DbContext CurrencyCreating(modelBuilder); AccountCreating(modelBuilder); MotionsCreating(modelBuilder); + VerificationsCreating(modelBuilder); + EmailTemplateCreating(modelBuilder); base.OnModelCreating(modelBuilder); } + private void VerificationsCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity() + .HasKey(x => x.Id); + + modelBuilder.Entity() + .HasOne(x => x.User) + .WithMany() + .HasForeignKey(x => x.UserId); + } + + private void EmailTemplateCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity() + .HasKey(x => x.Id); + } + private void UserCreating(ModelBuilder modelBuilder) { modelBuilder.Entity() @@ -128,24 +152,11 @@ public class AppDbContext : DbContext private void AccountCreating(ModelBuilder modelBuilder) { + #region Account + modelBuilder.Entity() .HasKey(x => x.Id); - modelBuilder.Entity() - .HasKey(x => x.Id); - - modelBuilder.Entity() - .HasKey(x => x.Id); - - modelBuilder.Entity() - .HasKey(x => x.Id); - - modelBuilder.Entity() - .HasKey(x => x.Id); - - modelBuilder.Entity() - .HasKey(x => x.Id); - modelBuilder.Entity() .HasOne(x => x.CurrencyGlobal) .WithMany(x => x.Accounts) @@ -156,6 +167,13 @@ public class AppDbContext : DbContext .WithMany(x => x.Accounts) .HasForeignKey(x => x.OwnerId); + #endregion Account + + #region AccountAccess + + modelBuilder.Entity() + .HasKey(x => x.Id); + modelBuilder.Entity() .HasOne(x => x.Account) .WithMany(x => x.AccessRights) @@ -171,6 +189,26 @@ public class AppDbContext : DbContext .WithMany(x => x.AccountAccessOwners) .HasForeignKey(x => x.OwnerId); + modelBuilder + .Entity() + .Property(d => d.Type) + .HasConversion(new EnumToStringConverter()); + + modelBuilder.Entity() + .HasIndex(p => new { p.AccountId, p.UserId }) + .IsUnique(); + + modelBuilder.Entity() + .HasIndex(p => new { p.AccountId, p.OwnerId }) + .IsUnique(); + + #endregion AccountAccess + + #region AccountAccessInvite + + modelBuilder.Entity() + .HasKey(x => x.Id); + modelBuilder.Entity() .HasOne(x => x.User) .WithMany(x => x.AccountAccessInvites) @@ -180,16 +218,37 @@ public class AppDbContext : DbContext .HasOne(x => x.Account) .WithMany(x => x.Invites) .HasForeignKey(x => x.AccountId); + + #endregion AccountAccessInvite + + #region Motion + + modelBuilder.Entity() + .HasKey(x => x.Id); modelBuilder.Entity() .HasOne(x => x.Account) .WithMany(x => x.Motions) .HasForeignKey(x => x.AccountId); + + #endregion Motion + + #region AccountCategory + + modelBuilder.Entity() + .HasKey(x => x.Id); modelBuilder.Entity() .HasOne(x => x.User) .WithMany(x => x.AccountCategories) .HasForeignKey(x => x.UserId); + + #endregion AccountCategory + + #region AccountAccountCategory + + modelBuilder.Entity() + .HasKey(x => x.Id); modelBuilder.Entity() .HasOne(x => x.Account) @@ -201,13 +260,16 @@ public class AppDbContext : DbContext .WithMany(x => x.Accounts) .HasForeignKey(x => x.CategoryId); - modelBuilder - .Entity() - .Property(d => d.Type) - .HasConversion(new EnumToStringConverter()); + modelBuilder.Entity() + .HasIndex(p => new { p.AccountId, p.CategoryId }) + .IsUnique(); - modelBuilder.Entity() - .HasIndex(p => new { p.AccountId, p.UserId}).IsUnique(); + modelBuilder.Entity() + .HasOne(x => x.Category) + .WithMany(x => x.Accounts) + .HasForeignKey(x => x.CategoryId); + + #endregion AccountAccountCategory } private void MotionsCreating(ModelBuilder modelBuilder) diff --git a/MyOffice.Migration.Postgres/Migrations/20231217163807_acc_cat_unique.Designer.cs b/MyOffice.Migration.Postgres/Migrations/20231217163807_acc_cat_unique.Designer.cs new file mode 100644 index 0000000..445508f --- /dev/null +++ b/MyOffice.Migration.Postgres/Migrations/20231217163807_acc_cat_unique.Designer.cs @@ -0,0 +1,730 @@ +// +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("20231217163807_acc_cat_unique")] + partial class acccatunique + { + /// + 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("OwnerId"); + + b.HasIndex("UserId"); + + b.HasIndex("AccountId", "UserId") + .IsUnique(); + + 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("CategoryId"); + + b.HasIndex("AccountId", "CategoryId") + .IsUnique(); + + 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/20231217163807_acc_cat_unique.cs b/MyOffice.Migration.Postgres/Migrations/20231217163807_acc_cat_unique.cs new file mode 100644 index 0000000..fe91a77 --- /dev/null +++ b/MyOffice.Migration.Postgres/Migrations/20231217163807_acc_cat_unique.cs @@ -0,0 +1,37 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace MyOffice.Migrations.Postgres.Migrations +{ + /// + public partial class acccatunique : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex( + name: "IX_AccountAccountCategories_AccountId", + table: "AccountAccountCategories"); + + migrationBuilder.CreateIndex( + name: "IX_AccountAccountCategories_AccountId_CategoryId", + table: "AccountAccountCategories", + columns: new[] { "AccountId", "CategoryId" }, + unique: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex( + name: "IX_AccountAccountCategories_AccountId_CategoryId", + table: "AccountAccountCategories"); + + migrationBuilder.CreateIndex( + name: "IX_AccountAccountCategories_AccountId", + table: "AccountAccountCategories", + column: "AccountId"); + } + } +} diff --git a/MyOffice.Migration.Postgres/Migrations/20231217185452_acc_access_unique.Designer.cs b/MyOffice.Migration.Postgres/Migrations/20231217185452_acc_access_unique.Designer.cs new file mode 100644 index 0000000..86bbaf1 --- /dev/null +++ b/MyOffice.Migration.Postgres/Migrations/20231217185452_acc_access_unique.Designer.cs @@ -0,0 +1,733 @@ +// +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("20231217185452_acc_access_unique")] + partial class accaccessunique + { + /// + 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("OwnerId"); + + b.HasIndex("UserId"); + + b.HasIndex("AccountId", "OwnerId") + .IsUnique(); + + b.HasIndex("AccountId", "UserId") + .IsUnique(); + + 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("CategoryId"); + + b.HasIndex("AccountId", "CategoryId") + .IsUnique(); + + 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/20231217185452_acc_access_unique.cs b/MyOffice.Migration.Postgres/Migrations/20231217185452_acc_access_unique.cs new file mode 100644 index 0000000..ef3eb44 --- /dev/null +++ b/MyOffice.Migration.Postgres/Migrations/20231217185452_acc_access_unique.cs @@ -0,0 +1,28 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace MyOffice.Migrations.Postgres.Migrations +{ + /// + public partial class accaccessunique : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateIndex( + name: "IX_AccountAccesses_AccountId_OwnerId", + table: "AccountAccesses", + columns: new[] { "AccountId", "OwnerId" }, + unique: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex( + name: "IX_AccountAccesses_AccountId_OwnerId", + table: "AccountAccesses"); + } + } +} diff --git a/MyOffice.Migration.Postgres/Migrations/20240107073407_VerificationCode.Designer.cs b/MyOffice.Migration.Postgres/Migrations/20240107073407_VerificationCode.Designer.cs new file mode 100644 index 0000000..57a8887 --- /dev/null +++ b/MyOffice.Migration.Postgres/Migrations/20240107073407_VerificationCode.Designer.cs @@ -0,0 +1,790 @@ +// +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("20240107073407_VerificationCode")] + partial class VerificationCode + { + /// + 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("OwnerId"); + + b.HasIndex("UserId"); + + b.HasIndex("AccountId", "OwnerId") + .IsUnique(); + + b.HasIndex("AccountId", "UserId") + .IsUnique(); + + 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("CategoryId"); + + b.HasIndex("AccountId", "CategoryId") + .IsUnique(); + + 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.Verifications.VerificationCode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Code") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("Destination") + .IsRequired() + .HasColumnType("text"); + + b.Property("DestinationType") + .IsRequired() + .HasColumnType("text"); + + b.Property("ExpiresOn") + .HasColumnType("timestamp with time zone"); + + b.Property("Metadata") + .HasColumnType("text"); + + b.Property("Template") + .IsRequired() + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("VerifiedOn") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Verifications"); + }); + + 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.Verifications.VerificationCode", b => + { + b.HasOne("MyOffice.Data.Models.Users.User", "User") + .WithMany() + .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/20240107073407_VerificationCode.cs b/MyOffice.Migration.Postgres/Migrations/20240107073407_VerificationCode.cs new file mode 100644 index 0000000..0dfbe47 --- /dev/null +++ b/MyOffice.Migration.Postgres/Migrations/20240107073407_VerificationCode.cs @@ -0,0 +1,55 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace MyOffice.Migrations.Postgres.Migrations +{ + /// + public partial class VerificationCode : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Verifications", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + UserId = table.Column(type: "uuid", nullable: false), + Code = table.Column(type: "text", nullable: false), + DestinationType = table.Column(type: "text", nullable: false), + Destination = table.Column(type: "text", nullable: false), + CreatedOn = table.Column(type: "timestamp with time zone", nullable: false), + ExpiresOn = table.Column(type: "timestamp with time zone", nullable: false), + VerifiedOn = table.Column(type: "timestamp with time zone", nullable: true), + Template = table.Column(type: "text", nullable: false), + Metadata = table.Column(type: "text", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Verifications", x => x.Id); + table.ForeignKey( + name: "FK_Verifications_Users_UserId", + column: x => x.UserId, + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_Verifications_UserId", + table: "Verifications", + column: "UserId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Verifications"); + } + } +} diff --git a/MyOffice.Migration.Postgres/Migrations/20240107075033_VerificationCode2.Designer.cs b/MyOffice.Migration.Postgres/Migrations/20240107075033_VerificationCode2.Designer.cs new file mode 100644 index 0000000..afd56e5 --- /dev/null +++ b/MyOffice.Migration.Postgres/Migrations/20240107075033_VerificationCode2.Designer.cs @@ -0,0 +1,790 @@ +// +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("20240107075033_VerificationCode2")] + partial class VerificationCode2 + { + /// + 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("OwnerId"); + + b.HasIndex("UserId"); + + b.HasIndex("AccountId", "OwnerId") + .IsUnique(); + + b.HasIndex("AccountId", "UserId") + .IsUnique(); + + 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("CategoryId"); + + b.HasIndex("AccountId", "CategoryId") + .IsUnique(); + + 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.Verifications.VerificationCode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Code") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("Destination") + .IsRequired() + .HasColumnType("text"); + + b.Property("DestinationType") + .IsRequired() + .HasColumnType("text"); + + b.Property("ExpiresOn") + .HasColumnType("timestamp with time zone"); + + b.Property("Metadata") + .HasColumnType("text"); + + b.Property("Template") + .IsRequired() + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("VerifiedOn") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Verifications"); + }); + + 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.Verifications.VerificationCode", b => + { + b.HasOne("MyOffice.Data.Models.Users.User", "User") + .WithMany() + .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/20240107075033_VerificationCode2.cs b/MyOffice.Migration.Postgres/Migrations/20240107075033_VerificationCode2.cs new file mode 100644 index 0000000..05f4374 --- /dev/null +++ b/MyOffice.Migration.Postgres/Migrations/20240107075033_VerificationCode2.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace MyOffice.Migrations.Postgres.Migrations +{ + /// + public partial class VerificationCode2 : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + + } + } +} diff --git a/MyOffice.Migration.Postgres/Migrations/AppDbContextModelSnapshot.cs b/MyOffice.Migration.Postgres/Migrations/AppDbContextModelSnapshot.cs index 1827fed..0a93bc7 100644 --- a/MyOffice.Migration.Postgres/Migrations/AppDbContextModelSnapshot.cs +++ b/MyOffice.Migration.Postgres/Migrations/AppDbContextModelSnapshot.cs @@ -88,6 +88,9 @@ namespace MyOffice.Migrations.Postgres.Migrations b.HasIndex("UserId"); + b.HasIndex("AccountId", "OwnerId") + .IsUnique(); + b.HasIndex("AccountId", "UserId") .IsUnique(); @@ -147,10 +150,11 @@ namespace MyOffice.Migrations.Postgres.Migrations b.HasKey("Id"); - b.HasIndex("AccountId"); - b.HasIndex("CategoryId"); + b.HasIndex("AccountId", "CategoryId") + .IsUnique(); + b.ToTable("AccountAccountCategories"); }); @@ -448,6 +452,52 @@ namespace MyOffice.Migrations.Postgres.Migrations b.ToTable("UserClaims"); }); + modelBuilder.Entity("MyOffice.Data.Models.Verifications.VerificationCode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Code") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("Destination") + .IsRequired() + .HasColumnType("text"); + + b.Property("DestinationType") + .IsRequired() + .HasColumnType("text"); + + b.Property("ExpiresOn") + .HasColumnType("timestamp with time zone"); + + b.Property("Metadata") + .HasColumnType("text"); + + b.Property("Template") + .IsRequired() + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("VerifiedOn") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Verifications"); + }); + modelBuilder.Entity("MyOffice.Data.Models.Accounts.Account", b => { b.HasOne("MyOffice.Data.Models.Currencies.CurrencyGlobal", "CurrencyGlobal") @@ -652,6 +702,17 @@ namespace MyOffice.Migrations.Postgres.Migrations b.Navigation("User"); }); + modelBuilder.Entity("MyOffice.Data.Models.Verifications.VerificationCode", b => + { + b.HasOne("MyOffice.Data.Models.Users.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + modelBuilder.Entity("MyOffice.Data.Models.Accounts.Account", b => { b.Navigation("AccessRights"); diff --git a/MyOffice.SPA/src/app/pages/accounts/account.component.ts b/MyOffice.SPA/src/app/pages/accounts/account.component.ts index 96817a2..1aed47c 100644 --- a/MyOffice.SPA/src/app/pages/accounts/account.component.ts +++ b/MyOffice.SPA/src/app/pages/accounts/account.component.ts @@ -47,6 +47,10 @@ export class AccountComponent { } ngOnInit(): void { + /*this.activatedRoute.params.subscribe(params => { + console.log(params); + this.loadMotions(); + });*/ this.loadMotions(); this.reloadEvent.subscribe(x => { @@ -86,13 +90,11 @@ export class AccountComponent { url += '?from=' + moment(this.dateFrom).format('yyyy-MM-DD'); url += '&to=' + moment(this.dateTo).format('yyyy-MM-DD'); - this.activatedRoute.params.subscribe(params => { - this.httpClient - .get(url) - .subscribe(data => { - this.motions = data; - }); - }); + this.httpClient + .get(url) + .subscribe(data => { + this.motions = data; + }); } afterExpand() { diff --git a/MyOffice.Services/Account/Domain/MotionDto.cs b/MyOffice.Services/Account/Domain/MotionDto.cs index 3d7bcb8..cc2aaed 100644 --- a/MyOffice.Services/Account/Domain/MotionDto.cs +++ b/MyOffice.Services/Account/Domain/MotionDto.cs @@ -1,5 +1,8 @@ namespace MyOffice.Services.Account.Domain; +using AutoMapper; +using MyOffice.Data.Models.Accounts; + public class MotionDto { public Guid Id { get; set; } @@ -13,4 +16,12 @@ public class MotionDto public decimal AmountPlus { get; set; } public decimal AmountMinus { get; set; } public DateTime? DeletedOn { get; set; } +} + +public class MotionDtoProfile: Profile +{ + public MotionDtoProfile() + { + CreateMap(); + } } \ No newline at end of file diff --git a/MyOffice.Services/Notifications/EmailNotificationService.cs b/MyOffice.Services/Notifications/EmailNotificationService.cs new file mode 100644 index 0000000..350fbf4 --- /dev/null +++ b/MyOffice.Services/Notifications/EmailNotificationService.cs @@ -0,0 +1,80 @@ +namespace MyOffice.Services.Notifications; + +using MyOffice.Core; +using MyOffice.Data.Models.Notifications; +using MyOffice.Data.Models.Users; +using MyOffice.Data.Models.Verifications; +using MyOffice.Data.Repositories.Item; +using MyOffice.Services.Verifications; + +public class EmailNotificationService +{ + private readonly VerificationService _verificationService; + + public EmailNotificationService( + VerificationService verificationService + ) + { + _verificationService = verificationService; + } + + public void PasswordResetEmail(User user) + { + var code = _verificationService.Add( + user, + user.Email, + VerificationCodeTemplateEnum.password_restore, + VerificationCodeTypeEnum.email + ); + + var subject = ""; + var body = ""; + } +} + +public class TemplateSevice +{ + private readonly IEmailTemplateRepository _emailTemplateRepository; + + public TemplateSevice( + IEmailTemplateRepository emailTemplateRepository + ) + { + _emailTemplateRepository = emailTemplateRepository; + } + + public Exec GetTemplate( + EmailTemplateEnum template, + Dictionary tokens + ) + { + var result = new Exec(GeneralExecStatus.success); + + var emailTemplate = _emailTemplateRepository.Get(template); + if (emailTemplate == null) + { + return result.Set(GeneralExecStatus.not_found); + } + + result.Result = new EmailTemplateFormated + { + Subject = emailTemplate.Subject + Body = emailTemplate.Template, + }; + + return result; + } +} + +public class EmailTemplateFormated +{ + public string Sender { get; set; } + public string SenderName { get; set; } + public string Subject { get; set; } + public string Body { get; set; } +} + +public interface IEmailSender +{ + //Exec Send(string from, string[] to, string subject, string body); +} \ No newline at end of file diff --git a/MyOffice.Services/Verifications/VerificationService.cs b/MyOffice.Services/Verifications/VerificationService.cs new file mode 100644 index 0000000..988b11b --- /dev/null +++ b/MyOffice.Services/Verifications/VerificationService.cs @@ -0,0 +1,51 @@ +namespace MyOffice.Services.Verifications; + +using MyOffice.Core.Helpers; +using MyOffice.Data.Models.Users; +using MyOffice.Data.Models.Verifications; +using MyOffice.Data.Repositories.Item; + +public class VerificationService +{ + private readonly TimeSpan _defaultExpires = TimeSpan.FromDays(1); + + private readonly IVerificationCodeRepository _verificationCodeRepository; + + public VerificationService( + IVerificationCodeRepository verificationCodeRepository + ) + { + _verificationCodeRepository = verificationCodeRepository; + } + + public VerificationCode Add( + User user, + string destination, + VerificationCodeTemplateEnum template, + VerificationCodeTypeEnum type, + DateTime? expiresOn = null, + string? metadata = null + ) + { + if (user == null) + throw new ArgumentNullException(nameof(user)); + if (destination == null) + throw new ArgumentNullException(nameof(destination)); + + var verificationCode = new VerificationCode + { + UserId = user.Id, + Code = RandomizationHelper.Generate(40), + CreatedOn = DateTime.UtcNow, + ExpiresOn = expiresOn ?? DateTime.UtcNow.Add(_defaultExpires), + Template = template.ToString(), + DestinationType = type.ToString(), + Destination = destination, + Metadata = metadata, + }; + + _verificationCodeRepository.Add(verificationCode); + + return verificationCode; + } +} \ No newline at end of file diff --git a/MyOffice.Web/Controllers/SettingsAccountController.cs b/MyOffice.Web/Controllers/SettingsAccountController.cs index c51a0a4..7361b3d 100644 --- a/MyOffice.Web/Controllers/SettingsAccountController.cs +++ b/MyOffice.Web/Controllers/SettingsAccountController.cs @@ -43,7 +43,7 @@ public class SettingsAccountController : BaseApiController return OkResponse(_mapper.Map>(list.OrderBy(x => x.Name))); } - public object AccountsAdd(AccountViewModel request) + public ObjectResult AccountsAdd(AccountViewModel request) { var exec = _accountService.AccountAdd(UserId, new AccountAdd { @@ -62,15 +62,14 @@ public class SettingsAccountController : BaseApiController case AccountAddStatus.failure: return ProblemBadResponse("Adding account failed."); case AccountAddStatus.success: - return _mapper.Map(exec.Result!); + return OkResponse(_mapper.Map(exec.Result!)); default: throw new NotSupportedException(exec.Status.ToString()); } } - - public object AccountsUpdate([AsGuid] string id, AccountEditRequestModel request) + public ObjectResult AccountsUpdate([AsGuid] string id, AccountEditRequestModel request) { var exec = _accountService.AccountUpdate(UserId, id.AsGuid(), new AccountEdit { @@ -92,14 +91,14 @@ public class SettingsAccountController : BaseApiController case AccountEditStatus.failure: return ProblemBadResponse("Adding account failed."); case AccountEditStatus.success: - return _mapper.Map(exec.Result!); + return OkResponse(_mapper.Map(exec.Result!)); default: throw new NotSupportedException(exec.Status.ToString()); } } - public object AccountsDelete([AsGuid] string id) + public ObjectResult AccountsDelete([AsGuid] string id) { var exec = _accountService.AccountDelete(UserId, id); switch (exec.Status) @@ -107,14 +106,14 @@ public class SettingsAccountController : BaseApiController case GeneralExecStatus.not_found: return ProblemBadResponse("Account not found."); case GeneralExecStatus.success: - return _mapper.Map(exec.Result!); + return OkResponse(_mapper.Map(exec.Result!)); default: throw new NotSupportedException(exec.Status.ToString()); } } - public object AccountsCategoryDelete([AsGuid] string id, [AsGuid] string categoryId) + public ObjectResult AccountsCategoryDelete([AsGuid] string id, [AsGuid] string categoryId) { var exec = _accountService.AccountCategoryRemove(UserId, id.AsGuid(), categoryId.AsGuid()); @@ -125,14 +124,14 @@ public class SettingsAccountController : BaseApiController return ProblemBadResponse("Category not found."); case GeneralExecStatus.success: - return exec.Result!; + return OkResponse(_mapper.Map(exec.Result!)); default: throw new NotSupportedException(exec.Status.ToString()); } } - public object AccountsAccessAdd([AsGuid] string id, AccountAccessViewModel request) + public ObjectResult AccountsAccessAdd([AsGuid] string id, AccountAccessViewModel request) { var model = _mapper.Map>(request.Accesses); @@ -153,7 +152,7 @@ public class SettingsAccountController : BaseApiController if (!request.Email.IsPresent()) { - return exec.Result!; + return OkResponse(_mapper.Map(exec.Result!)); } var inviteExec = _accountService.AccessInvite(UserId, id.AsGuid(), request.Email!, request.AllowWrite); @@ -166,14 +165,14 @@ public class SettingsAccountController : BaseApiController case AccessInviteStatus.access_exists: case AccessInviteStatus.invite_exists: case AccessInviteStatus.success: - return exec.Result!; + return OkResponse(_mapper.Map(exec.Result!)); default: throw new NotSupportedException(exec.Status.ToString()); } } - public object AccountsAccessDelete([AsGuid] string id, [AsGuid] string userId) + public ObjectResult AccountsAccessDelete([AsGuid] string id, [AsGuid] string userId) { var exec = _accountService.AccessDelete(UserId, id.AsGuid(), userId.AsGuid()); @@ -184,21 +183,21 @@ public class SettingsAccountController : BaseApiController return ProblemBadResponse("Account not found."); case GeneralExecStatus.success: - return exec.Result!; + return OkResponse(_mapper.Map(exec.Result!)); default: throw new NotSupportedException(exec.Status.ToString()); } } - public List AccountInvites() + public ObjectResult AccountInvites() { var invites = _accountService.InvitesGet(_contextProvider.User.Email); - return _mapper.Map>(invites); + return OkResponse(_mapper.Map>(invites)); } - public object AccountInviteAccept([AsGuid] string id, AccountInviteAcceptRequest request) + public ObjectResult AccountInviteAccept([AsGuid] string id, AccountInviteAcceptRequest request) { var exec = _accountService.InviteAccept(UserId, id.AsGuid(), request.Name); @@ -211,14 +210,14 @@ public class SettingsAccountController : BaseApiController case InviteAcceptStatus.already_accepted: case InviteAcceptStatus.success: - return _mapper.Map(exec.Result!); + return OkResponse(_mapper.Map(exec.Result!)); default: throw new NotSupportedException(exec.Status.ToString()); } } - public object AccountInviteReject([AsGuid] string id) + public ObjectResult AccountInviteReject([AsGuid] string id) { var exec = _accountService.InviteReject(id.AsGuid()); @@ -229,7 +228,7 @@ public class SettingsAccountController : BaseApiController return ProblemBadResponse("Invite not found."); case GeneralExecStatus.success: - return _mapper.Map(exec.Result!); + return OkResponse(_mapper.Map(exec.Result!)); default: throw new NotSupportedException(exec.Status.ToString()); diff --git a/MyOffice.Web/Infrastructure/Attributes/DefaultFromBodyAttribute.cs b/MyOffice.Web/Infrastructure/Attributes/DefaultFromBodyAttribute.cs index 5f249c1..4f077c3 100644 --- a/MyOffice.Web/Infrastructure/Attributes/DefaultFromBodyAttribute.cs +++ b/MyOffice.Web/Infrastructure/Attributes/DefaultFromBodyAttribute.cs @@ -1,5 +1,6 @@ namespace MyOffice.Web.Infrastructure.Attributes; +using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.ApplicationModels; using Microsoft.AspNetCore.Mvc.ModelBinding; @@ -17,10 +18,15 @@ public class DefaultFromBodyBindingConvention : IActionModelConvention throw new ArgumentNullException(nameof(action)); } - if (action.Controller.Attributes.Any(a => a is DefaultFromBodyAttribute)) + if (action.Controller.Attributes.Any(x => x is DefaultFromBodyAttribute)) { foreach (var parameter in action.Parameters) { + if (parameter.Attributes.Any(x => x is FromQueryAttribute)) + { + continue; + } + var paramType = parameter.ParameterInfo.ParameterType; var isSimpleType = paramType.IsPrimitive || paramType.IsEnum @@ -28,6 +34,7 @@ public class DefaultFromBodyBindingConvention : IActionModelConvention || paramType == typeof(int) || paramType == typeof(Guid) || paramType == typeof(DateTime) + || paramType == typeof(DateTime?) || paramType == typeof(decimal); if (!isSimpleType) diff --git a/MyOffice.Web/Models/Account/AccountAccessInviteViewModel.cs b/MyOffice.Web/Models/Account/AccountAccessInviteViewModel.cs index 43cc3c9..b691570 100644 --- a/MyOffice.Web/Models/Account/AccountAccessInviteViewModel.cs +++ b/MyOffice.Web/Models/Account/AccountAccessInviteViewModel.cs @@ -3,7 +3,7 @@ using AutoMapper; using MyOffice.Services.Account.Domain; -public class AccountAccessInviteViewModel +public class AccountAccessInviteViewModel: IResponseModel { public string? Id { get; set; } public string? Account { get; set; } diff --git a/MyOffice.Web/Models/Motion/MotionViewModel.cs b/MyOffice.Web/Models/Motion/MotionViewModel.cs index 5160846..c5a0f11 100644 --- a/MyOffice.Web/Models/Motion/MotionViewModel.cs +++ b/MyOffice.Web/Models/Motion/MotionViewModel.cs @@ -18,6 +18,10 @@ public class MotionViewModelProfile : Profile { public MotionViewModelProfile() { - CreateMap(); + CreateMap() + .ForMember(x => x.Item, x => x.MapFrom(m => m.Item.Name)) + .ForMember(x => x.Minus, x => x.MapFrom(m => m.AmountMinus)) + .ForMember(x => x.Plus, x => x.MapFrom(m => m.AmountPlus)) + ; } } \ No newline at end of file diff --git a/MyOffice.Web/Program.cs b/MyOffice.Web/Program.cs index 750ab0a..321546d 100644 --- a/MyOffice.Web/Program.cs +++ b/MyOffice.Web/Program.cs @@ -74,8 +74,6 @@ using MyOffice.Web.Infrastructure.Filters; //TODO: SPA all http requests -> services //TODO: Items, select category -> save url to allow refresh //TODO: Accounts, select category -> save url to allow refresh -//TODO: report income -//TODO: report outcome public partial class Program { @@ -160,10 +158,6 @@ public partial class Program options.JsonSerializerOptions.DictionaryKeyPolicy = JsonNamingPolicy.CamelCase; }); - // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle - //builder.Services.AddEndpointsApiExplorer(); - //builder.Services.AddSwaggerGen(); - builder.Services.AddCors(); builder.Services.AddControllersWithViews(options => @@ -306,6 +300,9 @@ public partial class Program builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); + + builder.Services.AddScoped(); + builder.Services.AddScoped(); } private static void AddBusinessServices(WebApplicationBuilder builder)