From 7c5be176faa5c83b1545d4fd96059f4f4a3b8426 Mon Sep 17 00:00:00 2001 From: Kyle Spearrin Date: Wed, 8 Jan 2020 20:28:16 -0500 Subject: [PATCH] Stub out EF repo base with user repo --- src/Core/Bit/Core/Models/Table/Ciper.cs | 6 ++ src/Core/Core.csproj | 2 + src/Core/Models/EntityFramework/Cipher.cs | 17 ++++ src/Core/Models/EntityFramework/User.cs | 18 +++++ .../BaseEntityFrameworkRepository.cs | 16 ++++ .../EntityFramework/DatabaseContext.cs | 36 +++++++++ .../EntityFramework/Repository.cs | 65 ++++++++++++++++ .../EntityFramework/UserRepository.cs | 78 +++++++++++++++++++ .../Utilities/ServiceCollectionExtensions.cs | 6 +- 9 files changed, 243 insertions(+), 1 deletion(-) create mode 100644 src/Core/Bit/Core/Models/Table/Ciper.cs create mode 100644 src/Core/Models/EntityFramework/Cipher.cs create mode 100644 src/Core/Models/EntityFramework/User.cs create mode 100644 src/Core/Repositories/EntityFramework/BaseEntityFrameworkRepository.cs create mode 100644 src/Core/Repositories/EntityFramework/DatabaseContext.cs create mode 100644 src/Core/Repositories/EntityFramework/Repository.cs create mode 100644 src/Core/Repositories/EntityFramework/UserRepository.cs diff --git a/src/Core/Bit/Core/Models/Table/Ciper.cs b/src/Core/Bit/Core/Models/Table/Ciper.cs new file mode 100644 index 0000000000..00fe0be3af --- /dev/null +++ b/src/Core/Bit/Core/Models/Table/Ciper.cs @@ -0,0 +1,6 @@ +namespace Bit.Core.Models.Table +{ + internal class Ciper + { + } +} \ No newline at end of file diff --git a/src/Core/Core.csproj b/src/Core/Core.csproj index a698e28754..718b7a1d35 100644 --- a/src/Core/Core.csproj +++ b/src/Core/Core.csproj @@ -21,6 +21,7 @@ + @@ -39,6 +40,7 @@ + diff --git a/src/Core/Models/EntityFramework/Cipher.cs b/src/Core/Models/EntityFramework/Cipher.cs new file mode 100644 index 0000000000..4eb27ebe81 --- /dev/null +++ b/src/Core/Models/EntityFramework/Cipher.cs @@ -0,0 +1,17 @@ +using AutoMapper; + +namespace Bit.Core.Models.EntityFramework +{ + public class Cipher : Table.Cipher + { + public User User { get; set; } + } + + public class CipherMapperProfile : Profile + { + public CipherMapperProfile() + { + CreateMap(); + } + } +} diff --git a/src/Core/Models/EntityFramework/User.cs b/src/Core/Models/EntityFramework/User.cs new file mode 100644 index 0000000000..eaecc6a152 --- /dev/null +++ b/src/Core/Models/EntityFramework/User.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; +using AutoMapper; + +namespace Bit.Core.Models.EntityFramework +{ + public class User : Table.User + { + public ICollection Ciphers { get; set; } + } + + public class UserMapperProfile : Profile + { + public UserMapperProfile() + { + CreateMap(); + } + } +} diff --git a/src/Core/Repositories/EntityFramework/BaseEntityFrameworkRepository.cs b/src/Core/Repositories/EntityFramework/BaseEntityFrameworkRepository.cs new file mode 100644 index 0000000000..bf8a059a8d --- /dev/null +++ b/src/Core/Repositories/EntityFramework/BaseEntityFrameworkRepository.cs @@ -0,0 +1,16 @@ +using AutoMapper; + +namespace Bit.Core.Repositories.EntityFramework +{ + public abstract class BaseEntityFrameworkRepository + { + public BaseEntityFrameworkRepository(DatabaseContext databaseContext, IMapper mapper) + { + DatabaseContext = databaseContext; + Mapper = mapper; + } + + protected DatabaseContext DatabaseContext { get; private set; } + protected IMapper Mapper { get; private set; } + } +} diff --git a/src/Core/Repositories/EntityFramework/DatabaseContext.cs b/src/Core/Repositories/EntityFramework/DatabaseContext.cs new file mode 100644 index 0000000000..e49854838c --- /dev/null +++ b/src/Core/Repositories/EntityFramework/DatabaseContext.cs @@ -0,0 +1,36 @@ +using System; +using Bit.Core.Models.EntityFramework; +using Microsoft.EntityFrameworkCore; + +namespace Bit.Core.Repositories.EntityFramework +{ + public class DatabaseContext : DbContext + { + private readonly GlobalSettings _globalSettings; + + public DatabaseContext( + DbContextOptions options, + GlobalSettings globalSettings) + : base(options) + { + _globalSettings = globalSettings; + } + + public DbSet Users { get; set; } + public DbSet Ciphers { get; set; } + + protected override void OnConfiguring(DbContextOptionsBuilder builder) + { + if(!string.IsNullOrWhiteSpace(_globalSettings.PostgreSql?.ConnectionString)) + { + builder.UseNpgsql(_globalSettings.PostgreSql.ConnectionString); + } + } + + protected override void OnModelCreating(ModelBuilder builder) + { + builder.Entity().ToTable(nameof(User)); + builder.Entity().ToTable(nameof(Cipher)); + } + } +} diff --git a/src/Core/Repositories/EntityFramework/Repository.cs b/src/Core/Repositories/EntityFramework/Repository.cs new file mode 100644 index 0000000000..e325947c03 --- /dev/null +++ b/src/Core/Repositories/EntityFramework/Repository.cs @@ -0,0 +1,65 @@ +using System; +using System.Threading.Tasks; +using Bit.Core.Models.Table; +using AutoMapper; +using Microsoft.EntityFrameworkCore; + +namespace Bit.Core.Repositories.EntityFramework +{ + public abstract class Repository : BaseEntityFrameworkRepository, IRepository + where TId : IEquatable + where T : class, ITableObject + where TEntity : class, ITableObject + { + public Repository(DatabaseContext databaseContext, IMapper mapper, Func> getDbSet) + : base(databaseContext, mapper) + { + GetDbSet = getDbSet; + } + + protected Func> GetDbSet { get; private set; } + + public virtual async Task GetByIdAsync(TId id) + { + var entity = await GetDbSet().FindAsync(id); + return entity as T; + } + + public virtual async Task CreateAsync(T obj) + { + var entity = Mapper.Map(obj); + DatabaseContext.Add(entity); + await DatabaseContext.SaveChangesAsync(); + } + + public virtual async Task ReplaceAsync(T obj) + { + var entity = await GetDbSet().FindAsync(obj.Id); + if(entity != null) + { + var mappedEntity = Mapper.Map(obj); + DatabaseContext.Entry(entity).CurrentValues.SetValues(mappedEntity); + await DatabaseContext.SaveChangesAsync(); + } + } + + public virtual async Task UpsertAsync(T obj) + { + if(obj.Id.Equals(default(T))) + { + await CreateAsync(obj); + } + else + { + await ReplaceAsync(obj); + } + } + + public virtual async Task DeleteAsync(T obj) + { + var entity = Mapper.Map(obj); + DatabaseContext.Entry(entity).State = EntityState.Deleted; + await DatabaseContext.SaveChangesAsync(); + } + } +} diff --git a/src/Core/Repositories/EntityFramework/UserRepository.cs b/src/Core/Repositories/EntityFramework/UserRepository.cs new file mode 100644 index 0000000000..29c6439466 --- /dev/null +++ b/src/Core/Repositories/EntityFramework/UserRepository.cs @@ -0,0 +1,78 @@ +using System; +using TableModel = Bit.Core.Models.Table; +using EFModel = Bit.Core.Models.EntityFramework; +using DataModel = Bit.Core.Models.Data; +using AutoMapper; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using System.Collections.Generic; +using System.Linq; + +namespace Bit.Core.Repositories.EntityFramework +{ + public class UserRepository : Repository, IUserRepository + { + public UserRepository(DatabaseContext databaseContext, IMapper mapper) + : base(databaseContext, mapper, () => databaseContext.Users) + { } + + public async Task GetByEmailAsync(string email) + { + return await GetDbSet().FirstOrDefaultAsync(e => e.Email == email); + } + + public async Task GetKdfInformationByEmailAsync(string email) + { + return await GetDbSet().Where(e => e.Email == email) + .Select(e => new DataModel.UserKdfInformation + { + Kdf = e.Kdf, + KdfIterations = e.KdfIterations + }).SingleOrDefaultAsync(); + } + + public async Task> SearchAsync(string email, int skip, int take) + { + var users = await GetDbSet() + .Where(e => e.Email == null || e.Email.StartsWith(email)) + .OrderBy(e => e.Email) + .Skip(skip).Take(take) + .ToListAsync(); + return Mapper.Map>(users); + } + + public async Task> GetManyByPremiumAsync(bool premium) + { + var users = await GetDbSet().Where(e => e.Premium == premium).ToListAsync(); + return Mapper.Map>(users); + } + + public async Task GetPublicKeyAsync(Guid id) + { + return await GetDbSet().Where(e => e.Id == id).Select(e => e.PublicKey).SingleOrDefaultAsync(); + } + + public async Task GetAccountRevisionDateAsync(Guid id) + { + return await GetDbSet().Where(e => e.Id == id).Select(e => e.AccountRevisionDate).SingleOrDefaultAsync(); + } + + public async Task UpdateStorageAsync(Guid id) + { + // TODO + } + + public async Task UpdateRenewalReminderDateAsync(Guid id, DateTime renewalReminderDate) + { + var user = new EFModel.User + { + Id = id, + RenewalReminderDate = renewalReminderDate + }; + var set = GetDbSet(); + set.Attach(user); + DatabaseContext.Entry(user).Property(e => e.RenewalReminderDate).IsModified = true; + await DatabaseContext.SaveChangesAsync(); + } + } +} diff --git a/src/Core/Utilities/ServiceCollectionExtensions.cs b/src/Core/Utilities/ServiceCollectionExtensions.cs index 6747af98a5..14b6db8c35 100644 --- a/src/Core/Utilities/ServiceCollectionExtensions.cs +++ b/src/Core/Utilities/ServiceCollectionExtensions.cs @@ -20,6 +20,7 @@ using System; using System.IO; using SqlServerRepos = Bit.Core.Repositories.SqlServer; using PostgreSqlRepos = Bit.Core.Repositories.PostgreSql; +using EntityFrameworkRepos = Bit.Core.Repositories.EntityFramework; using NoopRepos = Bit.Core.Repositories.Noop; using System.Threading.Tasks; using TableStorageRepos = Bit.Core.Repositories.TableStorage; @@ -32,6 +33,7 @@ using System.Linq; using System.Security.Cryptography.X509Certificates; using Bit.Core.Utilities; using Serilog.Context; +using AutoMapper; namespace Bit.Core.Utilities { @@ -41,7 +43,9 @@ namespace Bit.Core.Utilities { if(!string.IsNullOrWhiteSpace(globalSettings.PostgreSql?.ConnectionString)) { - services.AddSingleton(); + services.AddAutoMapper(typeof(EntityFrameworkRepos.UserRepository)); + services.AddDbContext(); + services.AddSingleton(); } else {