This commit is contained in:
2023-07-29 16:25:16 +03:00
parent 90f0386bfe
commit 4312a5c084
34 changed files with 1998 additions and 61 deletions
+35
View File
@@ -0,0 +1,35 @@
namespace MyOffice.Core.Attributes;
using System;
[AttributeUsage(AttributeTargets.All, AllowMultiple = true, Inherited = true)]
public class LinkAttribute : Attribute
{
public LinkAttribute(Type type)
{
}
}
[AttributeUsage(AttributeTargets.All, AllowMultiple = true, Inherited = true)]
public class LinkFromAttribute : Attribute
{
public LinkFromAttribute(Type type)
{
}
}
[AttributeUsage(AttributeTargets.All, AllowMultiple = true, Inherited = true)]
public class LinkToAttribute : Attribute
{
public LinkToAttribute(Type type)
{
}
}
[AttributeUsage(AttributeTargets.All, AllowMultiple = true, Inherited = true)]
public class LinkWithAttribute : Attribute
{
public LinkWithAttribute(Type type)
{
}
}
+20 -10
View File
@@ -24,6 +24,26 @@ public static class StringExtensions
return str != null && str.Equals(value, StringComparison.OrdinalIgnoreCase);
}
public static string? SafeTrim(this string? str)
{
return str == null ? str : str!.Trim();
}
public static string? MaxOf(this string? str, int maxLength)
{
if (str == null)
{
return null;
}
if (str.Length < maxLength)
{
return str;
}
return str.Substring(0, maxLength);
}
public static bool AsBool(this string? str, bool defaultValue = false)
{
return true.ToString().Equals(str, StringComparison.OrdinalIgnoreCase) || defaultValue;
@@ -31,21 +51,11 @@ public static class StringExtensions
public static Guid AsGuid(this string str)
{
/*str = str
.Replace("_", "/")
.Replace("-", "+");
byte[] buffer = Convert.FromBase64String(str + "==");
return new Guid(buffer);*/
return Guid.Parse(str);
}
public static Guid? AsGuidNull(this string str)
{
/*str = str
.Replace("_", "/")
.Replace("-", "+");
byte[] buffer = Convert.FromBase64String(str + "==");
return new Guid(buffer);*/
if (Guid.TryParse(str, out var guid))
{
return guid;
+1
View File
@@ -14,6 +14,7 @@ public class Account
public IEnumerable<AccountAccess>? AccessRights { get; set; }
public IEnumerable<Motion>? Motions { get; set; }
public IEnumerable<AccountAccountCategory>? Categories { get; set; }
public IEnumerable<AccountAccessInvite>? Invites { get; set; }
}
public class AccountDetailed
@@ -1,12 +1,15 @@
namespace MyOffice.Data.Models.Accounts;
using Core.Attributes;
using Users;
public class AccountAccessInvite
{
public Guid Id { get; set; }
public Guid UserId { get; set; }
public User User { get; set; }
public User? User { get; set; }
public Guid AccountId { get; set; }
public Account? Account { get; set; }
public DateTime CreatedOn { get; set; }
public DateTime? AcceptedOn { get; set; }
public DateTime? RejectedOn { get; set; }
@@ -6,4 +6,8 @@
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\MyOffice.Core\MyOffice.Core.csproj" />
</ItemGroup>
</Project>
@@ -1,5 +1,6 @@
namespace MyOffice.Data.Repositories.Account;
using Microsoft.EntityFrameworkCore;
using Models.Accounts;
using MyOffice.Data.Repositories;
@@ -24,4 +25,18 @@ public class AccountAccessInviteRepository : AppRepository<AccountAccessInvite>,
{
return UpdateBase(invite) > 0;
}
public IEnumerable<AccountAccessInvite> GetActive(string email)
{
return _context
.AccountAccessInvites
.Include(x => x.Account)
.Where(x => x.Email == email && !x.AcceptedOn.HasValue && !x.RejectedOn.HasValue);
}
public AccountAccessInvite? Get(Guid id)
{
return _context.AccountAccessInvites.FirstOrDefault(x => x.Id == id);
}
}
@@ -5,6 +5,10 @@ using MyOffice.Data.Repositories;
public class AccountAccessRepository : AppRepository<AccountAccess>, IAccountAccessRepository
{
public bool Add(AccountAccess accountAccess)
{
return AddBase(accountAccess) > 0;
}
public bool Update(AccountAccess accountAccess)
{
@@ -90,7 +90,7 @@ public class AccountRepository : AppRepository<Account>, IAccountRepository
public Account? Get(Guid userId, Guid id)
{
return _context.Accounts!
.Include(x => x.Categories)!
.Include(x => x.Categories!.Where(c => c.Category.UserId == userId))!
.ThenInclude(x => x.Category)
.Include(x => x.AccessRights)!
.ThenInclude(x => x.User)
@@ -5,6 +5,8 @@ using Models.Accounts;
public interface IAccountAccessInviteRepository
{
AccountAccessInvite? Get(Guid userId, string email);
IEnumerable<AccountAccessInvite> GetActive(string email);
AccountAccessInvite? Get(Guid id);
bool Add(AccountAccessInvite invite);
bool Update(AccountAccessInvite invite);
}
@@ -4,6 +4,7 @@ using Models.Accounts;
public interface IAccountAccessRepository
{
bool Add(AccountAccess accountAccess);
bool Update(AccountAccess accountAccess);
bool Delete(AccountAccess accountAccess);
}
+5
View File
@@ -176,6 +176,11 @@ public class AppDbContext : DbContext
.WithMany(x => x.AccountAccessInvites)
.HasForeignKey(x => x.UserId);
modelBuilder.Entity<AccountAccessInvite>()
.HasOne(x => x.Account)
.WithMany(x => x.Invites)
.HasForeignKey(x => x.AccountId);
modelBuilder.Entity<Motion>()
.HasOne(x => x.Account)
.WithMany(x => x.Motions)
@@ -0,0 +1,713 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using MyOffice.DbContext;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace MyOffice.Migrations.Postgres.Migrations
{
[DbContext(typeof(AppDbContext))]
[Migration("20230728181916_AccountAccessInvites2")]
partial class AccountAccessInvites2
{
/// <inheritdoc />
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<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<string>("CurrencyGlobalId")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("text");
b.Property<Guid?>("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<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<Guid>("AccountId")
.HasColumnType("uuid");
b.Property<bool>("IsAllowManage")
.HasColumnType("boolean");
b.Property<bool>("IsAllowRead")
.HasColumnType("boolean");
b.Property<bool>("IsAllowWrite")
.HasColumnType("boolean");
b.Property<string>("Name")
.HasColumnType("text");
b.Property<Guid>("OwnerId")
.HasColumnType("uuid");
b.Property<string>("Type")
.IsRequired()
.HasColumnType("text");
b.Property<Guid>("UserId")
.HasColumnType("uuid");
b.HasKey("Id");
b.HasIndex("AccountId");
b.HasIndex("OwnerId");
b.HasIndex("UserId");
b.ToTable("AccountAccesses");
});
modelBuilder.Entity("MyOffice.Data.Models.Accounts.AccountAccessInvite", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<DateTime?>("AcceptedOn")
.HasColumnType("timestamp with time zone");
b.Property<DateTime>("CreatedOn")
.HasColumnType("timestamp with time zone");
b.Property<string>("Email")
.IsRequired()
.HasColumnType("text");
b.Property<bool>("IsAllowWrite")
.HasColumnType("boolean");
b.Property<DateTime?>("RejectedOn")
.HasColumnType("timestamp with time zone");
b.Property<Guid>("UserId")
.HasColumnType("uuid");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("AccountAccessInvites");
});
modelBuilder.Entity("MyOffice.Data.Models.Accounts.AccountAccountCategory", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<Guid>("AccountId")
.HasColumnType("uuid");
b.Property<Guid>("CategoryId")
.HasColumnType("uuid");
b.HasKey("Id");
b.HasIndex("AccountId");
b.HasIndex("CategoryId");
b.ToTable("AccountAccountCategories");
});
modelBuilder.Entity("MyOffice.Data.Models.Accounts.AccountCategory", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("text");
b.Property<Guid>("UserId")
.HasColumnType("uuid");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("AccountCategories");
});
modelBuilder.Entity("MyOffice.Data.Models.Accounts.Motion", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<Guid>("AccountId")
.HasColumnType("uuid");
b.Property<decimal>("AmountMinus")
.HasPrecision(18, 6)
.HasColumnType("numeric(18,6)");
b.Property<decimal>("AmountPlus")
.HasPrecision(18, 6)
.HasColumnType("numeric(18,6)");
b.Property<DateTime>("CreatedOn")
.HasColumnType("timestamp with time zone");
b.Property<DateTime>("DateTime")
.HasColumnType("timestamp with time zone");
b.Property<DateTime?>("DeletedOn")
.HasColumnType("timestamp with time zone");
b.Property<string>("Description")
.HasColumnType("text");
b.Property<int>("ItemId")
.HasColumnType("integer");
b.Property<Guid?>("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<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<string>("CurrencyGlobalId")
.IsRequired()
.HasColumnType("text");
b.Property<int?>("CurrentRateId")
.HasColumnType("integer");
b.Property<bool>("IsPrimary")
.HasColumnType("boolean");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("text");
b.Property<string>("ShortName")
.IsRequired()
.HasColumnType("text");
b.Property<Guid>("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<string>("Id")
.HasColumnType("text");
b.Property<int>("DefaultQuantity")
.HasColumnType("integer");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Symbol")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.ToTable("CurrencyGlobals");
});
modelBuilder.Entity("MyOffice.Data.Models.Currencies.CurrencyRate", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<Guid>("CurrencyId")
.HasColumnType("uuid");
b.Property<DateTime>("DateTime")
.HasColumnType("timestamp with time zone");
b.Property<int>("Quantity")
.HasColumnType("integer");
b.Property<decimal>("Rate")
.HasColumnType("numeric");
b.HasKey("Id");
b.HasIndex("CurrencyId");
b.ToTable("CurrencyRates");
});
modelBuilder.Entity("MyOffice.Data.Models.Items.Item", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<Guid>("CategoryId")
.HasColumnType("uuid");
b.Property<Guid>("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<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<bool>("IsInternal")
.HasColumnType("boolean");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("text");
b.Property<Guid>("UserId")
.HasColumnType("uuid");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("ItemCategories");
});
modelBuilder.Entity("MyOffice.Data.Models.Items.ItemGlobal", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.ToTable("ItemGlobals");
});
modelBuilder.Entity("MyOffice.Data.Models.Users.User", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<string>("CurrencyId")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Email")
.IsRequired()
.HasColumnType("text")
.UseCollation("my_ci_collation");
b.Property<string>("FirstName")
.HasColumnType("text");
b.Property<string>("FullName")
.HasColumnType("text");
b.Property<bool>("IsEmailConfirmed")
.HasColumnType("boolean");
b.Property<string>("LastName")
.HasColumnType("text");
b.Property<string>("PasswordHash")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Phone")
.HasColumnType("text");
b.Property<string>("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<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<DateTime>("CreatedOn")
.HasColumnType("timestamp with time zone");
b.Property<string>("Email")
.IsRequired()
.HasColumnType("text")
.UseCollation("my_ci_collation");
b.Property<string>("ExternalId")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Provider")
.IsRequired()
.HasColumnType("text")
.UseCollation("my_ci_collation");
b.Property<Guid>("UserId")
.HasColumnType("uuid");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("UserClaims");
});
modelBuilder.Entity("MyOffice.Data.Models.Accounts.Account", b =>
{
b.HasOne("MyOffice.Data.Models.Currencies.CurrencyGlobal", "CurrencyGlobal")
.WithMany("Accounts")
.HasForeignKey("CurrencyGlobalId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("MyOffice.Data.Models.Users.User", "Owner")
.WithMany("Accounts")
.HasForeignKey("OwnerId");
b.Navigation("CurrencyGlobal");
b.Navigation("Owner");
});
modelBuilder.Entity("MyOffice.Data.Models.Accounts.AccountAccess", b =>
{
b.HasOne("MyOffice.Data.Models.Accounts.Account", "Account")
.WithMany("AccessRights")
.HasForeignKey("AccountId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("MyOffice.Data.Models.Users.User", "Owner")
.WithMany("AccountAccessOwners")
.HasForeignKey("OwnerId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("MyOffice.Data.Models.Users.User", "User")
.WithMany("AccountAccess")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Account");
b.Navigation("Owner");
b.Navigation("User");
});
modelBuilder.Entity("MyOffice.Data.Models.Accounts.AccountAccessInvite", b =>
{
b.HasOne("MyOffice.Data.Models.Users.User", "User")
.WithMany("AccountAccessInvites")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("User");
});
modelBuilder.Entity("MyOffice.Data.Models.Accounts.AccountAccountCategory", b =>
{
b.HasOne("MyOffice.Data.Models.Accounts.Account", "Account")
.WithMany("Categories")
.HasForeignKey("AccountId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("MyOffice.Data.Models.Accounts.AccountCategory", "Category")
.WithMany("Accounts")
.HasForeignKey("CategoryId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Account");
b.Navigation("Category");
});
modelBuilder.Entity("MyOffice.Data.Models.Accounts.AccountCategory", b =>
{
b.HasOne("MyOffice.Data.Models.Users.User", "User")
.WithMany("AccountCategories")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("User");
});
modelBuilder.Entity("MyOffice.Data.Models.Accounts.Motion", b =>
{
b.HasOne("MyOffice.Data.Models.Accounts.Account", "Account")
.WithMany("Motions")
.HasForeignKey("AccountId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("MyOffice.Data.Models.Items.Item", "Item")
.WithMany("Motions")
.HasForeignKey("ItemId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("MyOffice.Data.Models.Users.User", null)
.WithMany("AccountMotions")
.HasForeignKey("UserId");
b.Navigation("Account");
b.Navigation("Item");
});
modelBuilder.Entity("MyOffice.Data.Models.Currencies.Currency", b =>
{
b.HasOne("MyOffice.Data.Models.Currencies.CurrencyGlobal", "CurrencyGlobal")
.WithMany("Currencies")
.HasForeignKey("CurrencyGlobalId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("MyOffice.Data.Models.Currencies.CurrencyRate", "CurrentRate")
.WithMany("Currencies")
.HasForeignKey("CurrentRateId");
b.HasOne("MyOffice.Data.Models.Users.User", "User")
.WithMany("Currencies")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("CurrencyGlobal");
b.Navigation("CurrentRate");
b.Navigation("User");
});
modelBuilder.Entity("MyOffice.Data.Models.Currencies.CurrencyRate", b =>
{
b.HasOne("MyOffice.Data.Models.Currencies.Currency", "Currency")
.WithMany("Rates")
.HasForeignKey("CurrencyId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Currency");
});
modelBuilder.Entity("MyOffice.Data.Models.Items.Item", b =>
{
b.HasOne("MyOffice.Data.Models.Items.ItemCategory", "Category")
.WithMany("Items")
.HasForeignKey("CategoryId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("MyOffice.Data.Models.Items.ItemGlobal", "ItemGlobal")
.WithMany("Items")
.HasForeignKey("ItemGlobalId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Category");
b.Navigation("ItemGlobal");
});
modelBuilder.Entity("MyOffice.Data.Models.Items.ItemCategory", b =>
{
b.HasOne("MyOffice.Data.Models.Users.User", "User")
.WithMany("ItemCategories")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("User");
});
modelBuilder.Entity("MyOffice.Data.Models.Users.User", b =>
{
b.HasOne("MyOffice.Data.Models.Currencies.CurrencyGlobal", "Currency")
.WithMany()
.HasForeignKey("CurrencyId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Currency");
});
modelBuilder.Entity("MyOffice.Data.Models.Users.UserExternal", b =>
{
b.HasOne("MyOffice.Data.Models.Users.User", "User")
.WithMany("UserClaims")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("User");
});
modelBuilder.Entity("MyOffice.Data.Models.Accounts.Account", b =>
{
b.Navigation("AccessRights");
b.Navigation("Categories");
b.Navigation("Motions");
});
modelBuilder.Entity("MyOffice.Data.Models.Accounts.AccountCategory", b =>
{
b.Navigation("Accounts");
});
modelBuilder.Entity("MyOffice.Data.Models.Currencies.Currency", b =>
{
b.Navigation("Rates");
});
modelBuilder.Entity("MyOffice.Data.Models.Currencies.CurrencyGlobal", b =>
{
b.Navigation("Accounts");
b.Navigation("Currencies");
});
modelBuilder.Entity("MyOffice.Data.Models.Currencies.CurrencyRate", b =>
{
b.Navigation("Currencies");
});
modelBuilder.Entity("MyOffice.Data.Models.Items.Item", b =>
{
b.Navigation("Motions");
});
modelBuilder.Entity("MyOffice.Data.Models.Items.ItemCategory", b =>
{
b.Navigation("Items");
});
modelBuilder.Entity("MyOffice.Data.Models.Items.ItemGlobal", b =>
{
b.Navigation("Items");
});
modelBuilder.Entity("MyOffice.Data.Models.Users.User", b =>
{
b.Navigation("AccountAccess");
b.Navigation("AccountAccessInvites");
b.Navigation("AccountAccessOwners");
b.Navigation("AccountCategories");
b.Navigation("AccountMotions");
b.Navigation("Accounts");
b.Navigation("Currencies");
b.Navigation("ItemCategories");
b.Navigation("UserClaims");
});
#pragma warning restore 612, 618
}
}
}
@@ -0,0 +1,51 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace MyOffice.Migrations.Postgres.Migrations
{
/// <inheritdoc />
public partial class AccountAccessInvites2 : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<Guid>(
name: "UserId",
table: "AccountAccessInvites",
type: "uuid",
nullable: false,
defaultValue: new Guid("00000000-0000-0000-0000-000000000000"));
migrationBuilder.CreateIndex(
name: "IX_AccountAccessInvites_UserId",
table: "AccountAccessInvites",
column: "UserId");
migrationBuilder.AddForeignKey(
name: "FK_AccountAccessInvites_Users_UserId",
table: "AccountAccessInvites",
column: "UserId",
principalTable: "Users",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_AccountAccessInvites_Users_UserId",
table: "AccountAccessInvites");
migrationBuilder.DropIndex(
name: "IX_AccountAccessInvites_UserId",
table: "AccountAccessInvites");
migrationBuilder.DropColumn(
name: "UserId",
table: "AccountAccessInvites");
}
}
}
@@ -0,0 +1,728 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using MyOffice.DbContext;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace MyOffice.Migrations.Postgres.Migrations
{
[DbContext(typeof(AppDbContext))]
[Migration("20230728192017_AccountAccessInvites3")]
partial class AccountAccessInvites3
{
/// <inheritdoc />
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<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<string>("CurrencyGlobalId")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("text");
b.Property<Guid?>("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<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<Guid>("AccountId")
.HasColumnType("uuid");
b.Property<bool>("IsAllowManage")
.HasColumnType("boolean");
b.Property<bool>("IsAllowRead")
.HasColumnType("boolean");
b.Property<bool>("IsAllowWrite")
.HasColumnType("boolean");
b.Property<string>("Name")
.HasColumnType("text");
b.Property<Guid>("OwnerId")
.HasColumnType("uuid");
b.Property<string>("Type")
.IsRequired()
.HasColumnType("text");
b.Property<Guid>("UserId")
.HasColumnType("uuid");
b.HasKey("Id");
b.HasIndex("AccountId");
b.HasIndex("OwnerId");
b.HasIndex("UserId");
b.ToTable("AccountAccesses");
});
modelBuilder.Entity("MyOffice.Data.Models.Accounts.AccountAccessInvite", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<DateTime?>("AcceptedOn")
.HasColumnType("timestamp with time zone");
b.Property<Guid>("AccountId")
.HasColumnType("uuid");
b.Property<DateTime>("CreatedOn")
.HasColumnType("timestamp with time zone");
b.Property<string>("Email")
.IsRequired()
.HasColumnType("text");
b.Property<bool>("IsAllowWrite")
.HasColumnType("boolean");
b.Property<DateTime?>("RejectedOn")
.HasColumnType("timestamp with time zone");
b.Property<Guid>("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<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<Guid>("AccountId")
.HasColumnType("uuid");
b.Property<Guid>("CategoryId")
.HasColumnType("uuid");
b.HasKey("Id");
b.HasIndex("AccountId");
b.HasIndex("CategoryId");
b.ToTable("AccountAccountCategories");
});
modelBuilder.Entity("MyOffice.Data.Models.Accounts.AccountCategory", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("text");
b.Property<Guid>("UserId")
.HasColumnType("uuid");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("AccountCategories");
});
modelBuilder.Entity("MyOffice.Data.Models.Accounts.Motion", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<Guid>("AccountId")
.HasColumnType("uuid");
b.Property<decimal>("AmountMinus")
.HasPrecision(18, 6)
.HasColumnType("numeric(18,6)");
b.Property<decimal>("AmountPlus")
.HasPrecision(18, 6)
.HasColumnType("numeric(18,6)");
b.Property<DateTime>("CreatedOn")
.HasColumnType("timestamp with time zone");
b.Property<DateTime>("DateTime")
.HasColumnType("timestamp with time zone");
b.Property<DateTime?>("DeletedOn")
.HasColumnType("timestamp with time zone");
b.Property<string>("Description")
.HasColumnType("text");
b.Property<int>("ItemId")
.HasColumnType("integer");
b.Property<Guid?>("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<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<string>("CurrencyGlobalId")
.IsRequired()
.HasColumnType("text");
b.Property<int?>("CurrentRateId")
.HasColumnType("integer");
b.Property<bool>("IsPrimary")
.HasColumnType("boolean");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("text");
b.Property<string>("ShortName")
.IsRequired()
.HasColumnType("text");
b.Property<Guid>("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<string>("Id")
.HasColumnType("text");
b.Property<int>("DefaultQuantity")
.HasColumnType("integer");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Symbol")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.ToTable("CurrencyGlobals");
});
modelBuilder.Entity("MyOffice.Data.Models.Currencies.CurrencyRate", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<Guid>("CurrencyId")
.HasColumnType("uuid");
b.Property<DateTime>("DateTime")
.HasColumnType("timestamp with time zone");
b.Property<int>("Quantity")
.HasColumnType("integer");
b.Property<decimal>("Rate")
.HasColumnType("numeric");
b.HasKey("Id");
b.HasIndex("CurrencyId");
b.ToTable("CurrencyRates");
});
modelBuilder.Entity("MyOffice.Data.Models.Items.Item", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<Guid>("CategoryId")
.HasColumnType("uuid");
b.Property<Guid>("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<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<bool>("IsInternal")
.HasColumnType("boolean");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("text");
b.Property<Guid>("UserId")
.HasColumnType("uuid");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("ItemCategories");
});
modelBuilder.Entity("MyOffice.Data.Models.Items.ItemGlobal", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.ToTable("ItemGlobals");
});
modelBuilder.Entity("MyOffice.Data.Models.Users.User", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<string>("CurrencyId")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Email")
.IsRequired()
.HasColumnType("text")
.UseCollation("my_ci_collation");
b.Property<string>("FirstName")
.HasColumnType("text");
b.Property<string>("FullName")
.HasColumnType("text");
b.Property<bool>("IsEmailConfirmed")
.HasColumnType("boolean");
b.Property<string>("LastName")
.HasColumnType("text");
b.Property<string>("PasswordHash")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Phone")
.HasColumnType("text");
b.Property<string>("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<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<DateTime>("CreatedOn")
.HasColumnType("timestamp with time zone");
b.Property<string>("Email")
.IsRequired()
.HasColumnType("text")
.UseCollation("my_ci_collation");
b.Property<string>("ExternalId")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Provider")
.IsRequired()
.HasColumnType("text")
.UseCollation("my_ci_collation");
b.Property<Guid>("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
}
}
}
@@ -0,0 +1,51 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace MyOffice.Migrations.Postgres.Migrations
{
/// <inheritdoc />
public partial class AccountAccessInvites3 : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<Guid>(
name: "AccountId",
table: "AccountAccessInvites",
type: "uuid",
nullable: false,
defaultValue: new Guid("00000000-0000-0000-0000-000000000000"));
migrationBuilder.CreateIndex(
name: "IX_AccountAccessInvites_AccountId",
table: "AccountAccessInvites",
column: "AccountId");
migrationBuilder.AddForeignKey(
name: "FK_AccountAccessInvites_Accounts_AccountId",
table: "AccountAccessInvites",
column: "AccountId",
principalTable: "Accounts",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_AccountAccessInvites_Accounts_AccountId",
table: "AccountAccessInvites");
migrationBuilder.DropIndex(
name: "IX_AccountAccessInvites_AccountId",
table: "AccountAccessInvites");
migrationBuilder.DropColumn(
name: "AccountId",
table: "AccountAccessInvites");
}
}
}
@@ -102,6 +102,9 @@ namespace MyOffice.Migrations.Postgres.Migrations
b.Property<DateTime?>("AcceptedOn")
.HasColumnType("timestamp with time zone");
b.Property<Guid>("AccountId")
.HasColumnType("uuid");
b.Property<DateTime>("CreatedOn")
.HasColumnType("timestamp with time zone");
@@ -115,8 +118,15 @@ namespace MyOffice.Migrations.Postgres.Migrations
b.Property<DateTime?>("RejectedOn")
.HasColumnType("timestamp with time zone");
b.Property<Guid>("UserId")
.HasColumnType("uuid");
b.HasKey("Id");
b.HasIndex("AccountId");
b.HasIndex("UserId");
b.ToTable("AccountAccessInvites");
});
@@ -481,6 +491,25 @@ namespace MyOffice.Migrations.Postgres.Migrations
b.Navigation("User");
});
modelBuilder.Entity("MyOffice.Data.Models.Accounts.AccountAccessInvite", b =>
{
b.HasOne("MyOffice.Data.Models.Accounts.Account", "Account")
.WithMany("Invites")
.HasForeignKey("AccountId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("MyOffice.Data.Models.Users.User", "User")
.WithMany("AccountAccessInvites")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Account");
b.Navigation("User");
});
modelBuilder.Entity("MyOffice.Data.Models.Accounts.AccountAccountCategory", b =>
{
b.HasOne("MyOffice.Data.Models.Accounts.Account", "Account")
@@ -628,6 +657,8 @@ namespace MyOffice.Migrations.Postgres.Migrations
b.Navigation("Categories");
b.Navigation("Invites");
b.Navigation("Motions");
});
@@ -672,6 +703,8 @@ namespace MyOffice.Migrations.Postgres.Migrations
{
b.Navigation("AccountAccess");
b.Navigation("AccountAccessInvites");
b.Navigation("AccountAccessOwners");
b.Navigation("AccountCategories");
+3
View File
@@ -18,6 +18,9 @@ export class ApiRoutes {
static SettingsAccountAccesses = '/api/settings/accounts/:id/access';
static SettingsAccountAccess = '/api/settings/accounts/:id/access/:access';
static SettingsAccountAccountCategory = '/api/settings/accounts/:id/category/:categoryId';
static SettingsAccountInvites = '/api/settings/accounts/invites';
static SettingsAccountInviteAccept = '/api/settings/accounts/invites/:id/accept';
static SettingsAccountInviteReject = '/api/settings/accounts/invites/:id/reject';
static SettingsItemCategories = '/api/settings/item-categories';
static SettingsItemCategory = '/api/settings/item-categories/:id';
@@ -24,8 +24,12 @@ export class ErrorInterceptor implements HttpInterceptor {
this.authenticationService.logout();
location.reload();
}
console.log(err);
const error = err.message || err.error.message || err.statusText;
let error = err.message || err.statusText;
error = !err.error ? error : err.error.message || err.error.title;
if (!error) {
console.log('not parsed error', err);
}
return throwError(error);
//return throwError(err.error || err);
})
@@ -0,0 +1,5 @@
export interface AccountInviteModel {
id?: string,
account?: string,
allowWrite: boolean,
}
@@ -21,6 +21,7 @@ import { NgxCurrencyModule } from "ngx-currency";
import { MatExpansionModule } from '@angular/material/expansion';
import { MatAutocompleteModule } from '@angular/material/autocomplete';
import { MatCheckboxModule } from '@angular/material/checkbox';
import { MatMenuModule } from '@angular/material/menu';
// app
import { PagesRoutingModule } from './pages-routing.module';
@@ -88,6 +89,7 @@ import { SettingsItemCategoryEditComponent } from './settings/item/edit.item.cat
NgxMaskModule,
NgxCurrencyModule,
MatCheckboxModule,
MatMenuModule,
],
providers: [
{ provide: MAT_DATE_LOCALE, useValue: 'en-GB' },
@@ -17,13 +17,19 @@
</mat-card-subtitle>
</mat-card-title-group>
<div fxFlex></div>
<div *ngIf="invites && invites.length > 0" class="m-r-10">
<button mat-raised-button color="warn" [matMenuTriggerFor]="menu">Invites</button>
<mat-menu #menu="matMenu">
<button *ngFor="let invite of invites" mat-menu-item (click)="startInvite(invite)">{{invite.account}}</button>
</mat-menu>
</div>
<button class="btn-space " (click)="add()" mat-raised-button color="primary">
Add
</button>
</mat-card-header>
<mat-card-content>
<div class="body table-responsive">
<mat-tab-group mat-stretch-tabs="false" mat-align-tabs="start">
<mat-tab-group mat-stretch-tabs="false" mat-align-tabs="start" [(selectedIndex)]="tabIndex">
<mat-tab *ngFor="let category of accountCategories" label="{{category.name}}">
<table class="table">
<tbody>
@@ -10,6 +10,7 @@ import { MatDialog } from '@angular/material/dialog';
import { ApiRoutes } from '../../../api-routes';
import { AccountModel } from '../../../model/account.model';
import { AccountCategoryModel } from '../../../model/account.category.model';
import { AccountInviteModel } from '../../../model/account.invite.model';
import { SettingsAccountAddComponent } from './add.account.component';
import { SettingsAccountEditComponent } from './edit.account.component';
import { SettingsAccountAccessComponent } from './access.account.component';
@@ -22,6 +23,8 @@ import { AccountCategoryService } from '../../../services/account.category.servi
export class SettingsAccountComponent {
public accountCategories?: AccountCategoryModel[];
public accounts?: AccountModel[];
public invites?: AccountInviteModel[];
public tabIndex = 0;
constructor(
private httpClient: HttpClient,
@@ -32,13 +35,35 @@ export class SettingsAccountComponent {
ngOnInit(): void {
this.loadCategories();
this.loadInvites();
}
private loadAccounts() {
private loadAccounts(accountToShow?: string) {
this.httpClient
.get<AccountModel[]>(ApiRoutes.SettingsAccounts)
.subscribe(data => {
this.accounts = data;
if (accountToShow) {
var idx = -1;
var account = this.accounts.find(x => x.id === accountToShow);
if (account && account.categories && account.categories.length > 0) {
for (var i = 0; i < this.accountCategories!.length; i++) {
if (account!.categories!.find(c => c.id === this.accountCategories![i].id)) {
idx = i;
break;
}
}
}
this.tabIndex = idx === -1 ? this.accountCategories!.length : idx;
}
});
}
private loadInvites() {
return this.httpClient
.get<AccountInviteModel[]>(ApiRoutes.SettingsAccountInvites)
.subscribe(response => {
this.invites = response;
});
}
@@ -118,6 +143,57 @@ export class SettingsAccountComponent {
this.loadAccounts();
}
});
}
public startInvite(invite: AccountInviteModel) {
Swal.fire({
title: 'Accept to invite the account "' + invite.account + '"',
input: 'text',
inputLabel: 'Accept with name',
inputValue: invite.account,
showDenyButton: true,
showCancelButton: true,
confirmButtonText: 'Accept',
denyButtonText: 'Reject',
showLoaderOnConfirm: true,
preConfirm: (input) => {
return new Promise((resolve, reject) => {
this.httpClient.post(ApiRoutes.SettingsAccountInviteAccept.replace(':id', invite.id!), { name: input })
.subscribe(data => {
resolve(data);
}, error => {
Swal.showValidationMessage(error);
reject();
});
}).catch(x => {
return false;
});
},
preDeny: (x) => {
return new Promise((resolve, reject) => {
this.httpClient.post(ApiRoutes.SettingsAccountInviteReject.replace(':id', invite.id!), null)
.subscribe(data => {
resolve(data);
}, error => {
Swal.showValidationMessage(error.detail);
reject();
});
}).catch(x => {
return false;
});
},
allowOutsideClick: () => !Swal.isLoading(),
}).then((result) => {
var id;
if (result.value) {
var account = result.value as AccountModel;
id = account.id;
}
this.loadInvites();
this.loadAccounts(id);
});
}
}
+80 -5
View File
@@ -14,6 +14,7 @@
using Microsoft.Extensions.Logging;
using MyOffice.Services.Account.Domain;
using System.Collections.Generic;
using Identity;
public class AccountService
{
@@ -29,6 +30,7 @@
private readonly IItemRepository _itemRepository;
private readonly IItemGlobalRepository _itemGlobalRepository;
private readonly ItemService _itemService;
private readonly IContextProvider _contextProvider;
public AccountService(
ILogger<AccountService> logger,
@@ -42,7 +44,8 @@
IMotionRepository motionRepository,
IItemRepository itemRepository,
IItemGlobalRepository itemGlobalRepository,
ItemService itemService
ItemService itemService,
IContextProvider contextProvider
)
{
_logger = logger;
@@ -57,6 +60,7 @@
_itemRepository = itemRepository;
_itemGlobalRepository = itemGlobalRepository;
_itemService = itemService;
_contextProvider = contextProvider;
}
public List<AccountCategoryDto> GetAllCategories(Guid userId)
@@ -70,7 +74,7 @@
{
var result = new Exec<AccountCategoryDto, GeneralExecStatus>(GeneralExecStatus.success);
var category = _accountCategoryRepository.Get(userId, id);
var category = _accountCategoryRepository.Get(userId, id);
if (category == null)
{
@@ -528,13 +532,13 @@
if (account.AccessRights!.Any(x => x.User!.Email.EqualsIgnoreCase(email)))
{
return result.Set(AccessInviteStatus.account_not_found);
return result.Set(AccessInviteStatus.access_exists);
}
var access = _accountAccessInviteRepository.Get(userId, email);
if (access != null)
{
return result.Set(AccessInviteStatus.access_exists);
return result.Set(AccessInviteStatus.invite_exists);
}
var invite = new AccountAccessInvite
@@ -542,7 +546,8 @@
Id = Guid.NewGuid(),
CreatedOn = DateTime.UtcNow,
UserId = userId,
Email = email,
AccountId = account.Id,
Email = email.SafeTrim()!,
IsAllowWrite = isAllowWrite,
};
@@ -606,5 +611,75 @@
return result.Set(_mapper.Map<AccountDto>(account));
}
public List<AccountAccessInviteDto> InvitesGet(string email)
{
return _mapper.Map<List<AccountAccessInviteDto>>(_accountAccessInviteRepository.GetActive(email).ToList());
}
public Exec<AccountDto, InviteAcceptStatus> InviteAccept(Guid userId, Guid id, string name)
{
var result = new Exec<AccountDto, InviteAcceptStatus>(InviteAcceptStatus.success);
var invite = _accountAccessInviteRepository.Get(id);
if (invite == null
|| !invite.Email.EqualsIgnoreCase(_contextProvider.User.Email)
|| invite.AcceptedOn.HasValue
|| invite.RejectedOn.HasValue
)
{
return result.Set(InviteAcceptStatus.invite_not_found);
}
var account = _accountRepository.Get(invite.UserId, invite.AccountId);
if (account == null)
{
return result.Set(InviteAcceptStatus.account_not_found);
}
if (account.AccessRights!.Any(x => x.UserId == userId))
{
result.Set(InviteAcceptStatus.already_accepted);
}
else
{
_accountAccessRepository.Add(new AccountAccess
{
UserId = userId,
AccountId = account.Id,
OwnerId = invite.UserId,
Name = name,
IsAllowWrite = invite.IsAllowWrite,
Type = AccountAccessTypeEnum.external,
});
}
invite.AcceptedOn = DateTime.UtcNow;
_accountAccessInviteRepository.Update(invite);
account = _accountRepository.Get(userId, account.Id);
return result.Set(_mapper.Map<AccountDto>(account));
}
public Exec<AccountAccessInviteDto, GeneralExecStatus> InviteReject(Guid id)
{
var result = new Exec<AccountAccessInviteDto, GeneralExecStatus>(GeneralExecStatus.success);
var invite = _accountAccessInviteRepository.Get(id);
if (invite == null
|| !invite.Email.EqualsIgnoreCase(_contextProvider.User.Email)
|| invite.AcceptedOn.HasValue
|| invite.RejectedOn.HasValue
)
{
return result.Set(GeneralExecStatus.not_found);
}
invite.RejectedOn = DateTime.UtcNow;
_accountAccessInviteRepository.Update(invite);
return result.Set(_mapper.Map<AccountAccessInviteDto>(invite));
}
}
}
@@ -1,8 +1,12 @@
namespace MyOffice.Services.Account.Domain;
using Core.Attributes;
using Data.Models.Accounts;
using Data.Models.Users;
using Mapper;
[Link(typeof(AccountAccess))]
[Link(typeof(AccountServiceProfile))]
public class AccountAccessDto
{
/// <summary>
@@ -0,0 +1,12 @@
namespace MyOffice.Services.Account.Domain;
using Core.Attributes;
using Data.Models.Accounts;
[Link(typeof(AccountAccessInvite))]
public class AccountAccessInviteDto
{
public string Id { get; set; } = null!;
public string Account { get; set; } = null!;
public bool IsAllowWrite { get; set; }
}
@@ -2,11 +2,11 @@
using MyOffice.Data.Models.Currencies;
using System;
using Data.Models.Accounts;
public class AccountDto
{
public Guid Id { get; set; }
public Guid CurrentUserId { get; set; }
public string CurrencyGlobalId { get; set; } = null!;
public CurrencyGlobal? CurrencyGlobal { get; set; }
public Guid CurrencyId { get; set; }
@@ -20,4 +20,8 @@ public class AccountDto
public string Type { get; set; } = null!;
public List<AccountAccessDto>? AccessRights { get; set; }
public List<AccountAccountCategoryDto>? Categories { get; set; }
public Guid CurrentUserId { get; set; }
public AccountAccess? AccountAccess { get; set; }
}
@@ -0,0 +1,10 @@
namespace MyOffice.Services.Account.Domain
{
public enum InviteAcceptStatus
{
success,
invite_not_found,
account_not_found,
already_accepted,
}
}
@@ -14,7 +14,20 @@ public class AccountServiceProfile : Profile
CreateMap<Account, AccountDto>()
.ForMember(x => x.HasMotions, o => o.MapFrom(x => x.Motions!.Any()))
.ForMember(x => x.CurrentUserId, o => o.MapFrom<UserIdResolver>())
.ForMember(x => x.Type, o => o.MapFrom((src, dst) => src.AccessRights!.FirstOrDefault(x => x.UserId == dst.CurrentUserId)!.Type.ToString()))
.ForMember(x => x.AccountAccess, o =>
{
o.MapFrom((src, dst) => src.AccessRights!.FirstOrDefault(x => x.UserId == dst.CurrentUserId));
})
.ForMember(x => x.Type, o =>
{
o.SetMappingOrder(1);
o.MapFrom((src, dst) => dst.AccountAccess?.Type.ToString());
})
.ForMember(x => x.Name, o =>
{
o.SetMappingOrder(1);
o.MapFrom((src, dst) => dst.AccountAccess?.Name ?? src.Name);
})
;
CreateMap<AccountDetailed, AccountDetailedDto>();
@@ -27,5 +40,9 @@ public class AccountServiceProfile : Profile
.ForMember(x => x.Id, o => o.MapFrom(x => x.Id))
.ForMember(x => x.AllowDelete, o => o.MapFrom(x => !x.Accounts!.Any()))
;
CreateMap<AccountAccessInvite, AccountAccessInviteDto>()
.ForMember(x => x.Account, o => o.MapFrom(x => x.Account!.Name))
;
}
}
@@ -10,6 +10,7 @@ using Models.Account;
using Services.Account;
using Services.Account.Domain;
using MyOffice.Web.Infrastructure.Attributes;
using Services.Identity;
[Authorize]
[ApiController]
@@ -19,16 +20,19 @@ public class SettingsAccountController : BaseApiController
private readonly ILogger<SettingsAccountController> _logger;
private readonly IMapper _mapper;
private readonly AccountService _accountService;
private readonly IContextProvider _contextProvider;
public SettingsAccountController(
ILogger<SettingsAccountController> logger,
IMapper mapper,
AccountService accountService
AccountService accountService,
IContextProvider contextProvider
)
{
_logger = logger;
_mapper = mapper;
_accountService = accountService;
_contextProvider = contextProvider;
}
[HttpGet("~/api/settings/account-categories")]
@@ -251,8 +255,6 @@ public class SettingsAccountController : BaseApiController
return ProblemBadRequest("Account not found.");
case AccessInviteStatus.access_exists:
return ProblemBadRequest("Access allowed.");
case AccessInviteStatus.invite_exists:
case AccessInviteStatus.success:
return exec.Result!;
@@ -279,6 +281,53 @@ public class SettingsAccountController : BaseApiController
default:
throw new NotSupportedException(exec.Status.ToString());
}
}
[HttpGet("~/api/settings/accounts/invites")]
public List<AccountAccessInviteViewModel> AccountInvites()
{
var invites = _accountService.InvitesGet(_contextProvider.User.Email);
return _mapper.Map<List<AccountAccessInviteViewModel>>(invites);
}
[HttpPost("~/api/settings/accounts/invites/{id}/accept")]
public object AccountInviteAccept([AsGuid]string id, AccountInviteAcceptRequest request)
{
var exec = _accountService.InviteAccept(UserId, id.AsGuid(), request.Name);
switch (exec.Status)
{
case InviteAcceptStatus.invite_not_found:
return ProblemBadRequest("Invite not found.");
case InviteAcceptStatus.account_not_found:
return ProblemBadRequest("Account not found.");
case InviteAcceptStatus.already_accepted:
case InviteAcceptStatus.success:
return _mapper.Map<AccountViewModel>(exec.Result!);
default:
throw new NotSupportedException(exec.Status.ToString());
}
}
[HttpPost("~/api/settings/accounts/invites/{id}/reject")]
public object AccountInviteReject([AsGuid] string id)
{
var exec = _accountService.InviteReject(id.AsGuid());
switch (exec.Status)
{
case GeneralExecStatus.not_found:
case GeneralExecStatus.failure:
return ProblemBadRequest("Invite not found.");
case GeneralExecStatus.success:
return _mapper.Map<AccountAccessInviteViewModel>(exec.Result!);
default:
throw new NotSupportedException(exec.Status.ToString());
}
}
}
@@ -0,0 +1,13 @@
namespace MyOffice.Web.Models.Account;
using Core.Attributes;
using Services.Account.Domain;
[LinkFrom(typeof(AccountAccessInviteDto))]
[LinkWith(typeof(AccountViewModelProfile))]
public class AccountAccessInviteViewModel
{
public string? Id { get; set; }
public string? Account { get; set; }
public bool? AllowWrite { get; set; }
}
@@ -1,29 +1,16 @@
namespace MyOffice.Web.Models.Account
namespace MyOffice.Web.Models.Account;
using MyOffice.Core.Attributes;
using MyOffice.Services.Account.Domain;
using System.ComponentModel.DataAnnotations;
[Link(typeof(AccountCategoryDto))]
public class AccountCategoryViewModel
{
using System.ComponentModel.DataAnnotations;
using Core.Extensions;
using Data.Models.Accounts;
public string? Id { get; set; }
public class AccountCategoryViewModel
{
public string? Id { get; set; }
[Required]
public string? Name { get; set; }
[Required]
public string? Name { get; set; }
public bool? AllowDelete { get; set; }
}
public static class AccountCategoryViewModelExtensions
{
public static AccountCategoryViewModel ToModel(this AccountCategory input)
{
return new AccountCategoryViewModel
{
Id = input.Id.ToShort(),
Name = input.Name,
AllowDelete = !input.Accounts?.Any(),
};
}
}
public bool? AllowDelete { get; set; }
}
@@ -0,0 +1,6 @@
namespace MyOffice.Web.Models.Account;
public class AccountInviteAcceptRequest
{
public string Name { get; set; } = null!;
}
@@ -3,11 +3,7 @@
using AutoMapper;
using MyOffice.Services.Identity;
using Services.Account.Domain;
using Services.Mapper;
using System.Security.Cryptography;
using Core.Extensions;
using User;
using static MyOffice.Web.Models.Account.AccountAccessViewModel;
public class CustomResolver : IValueResolver<AccountAccessDto, AccessRightsViewModel, bool>
{
@@ -61,11 +57,15 @@ public class AccountViewModelProfile : Profile
(src, dst) => src.OwnerId == src.UserId))
;
CreateMap<AccountAccessItemViewModel, AccountAccessDto>()
CreateMap<AccountAccessViewModel.AccountAccessItemViewModel, AccountAccessDto>()
.ForMember(x => x.IsAllowWrite, o => o.MapFrom(x => x.AllowWrite))
;
CreateMap<AccountCategoryDto, AccountCategoryViewModel>()
;
CreateMap<AccountAccessInviteDto, AccountAccessInviteViewModel>()
.ForMember(x => x.AllowWrite, o => o.MapFrom(x => x.IsAllowWrite))
;
}
}
+8
View File
@@ -64,6 +64,13 @@ using MyOffice.Services.Mapper;
//TODO: Response model from base type
//TODO: XXXResult -> XXXStatus
//TODO: SPA isAllowDelete -> allowDelete (remove is)
//TODO: Repository auto registration
//TODO: Services auto registration
//TODO: openid-configuration failed - lock login
//TODO: BUG some times after login redirect to dashboard but exists return url
//TODO: email confirmation
//TODO: email password reset
//TODO: automapper -> Extension ToModel() ToDbo() FromModel() FromDbo()
public class Program
{
@@ -262,6 +269,7 @@ public class Program
builder.Services.AddScoped<IAccountCategoryRepository, AccountCategoryRepository>();
builder.Services.AddScoped<IAccountRepository, AccountRepository>();
builder.Services.AddScoped<IAccountAccessRepository, AccountAccessRepository>();
builder.Services.AddScoped<IAccountAccessInviteRepository, AccountAccessInviteRepository>();
builder.Services.AddScoped<IAccountAccountCategoryRepository, AccountAccountCategoryRepository>();
builder.Services.AddScoped<IItemCategoryRepository, ItemCategoryRepository>();