diff --git a/postman/New Text Document.txt b/postman/New Text Document.txt new file mode 100644 index 0000000..55e1bc8 --- /dev/null +++ b/postman/New Text Document.txt @@ -0,0 +1,10 @@ + smtpService.Connect("smtp.ionos.it", 465, true); + smtpService.Authenticate("commercial@maks-it.com", "E23{R# + + + + + Simple Transactional Email + + + + + + + + + + + + + diff --git a/src/WeatherForecast/templates/email.html b/postman/templates/email.html similarity index 100% rename from src/WeatherForecast/templates/email.html rename to postman/templates/email.html diff --git a/src/CryptoProvider/AesKey.cs b/src/CryptoProvider/AesKey.cs index 2ed988f..c517f41 100644 --- a/src/CryptoProvider/AesKey.cs +++ b/src/CryptoProvider/AesKey.cs @@ -6,12 +6,12 @@ using System.Threading.Tasks; namespace CryptoProvider { public interface IAesKey { - public string? IV { get; set; } - public string? Key { get; set; } + public string IV { get; set; } + public string Key { get; set; } } public class AesKey : IAesKey { - public string? IV { get; set; } - public string? Key { get; set; } + public string IV { get; set; } = string.Empty; + public string Key { get; set; } = string.Empty; } } \ No newline at end of file diff --git a/src/DataProviders/Abstractions/CollectionDataProviderBase.cs b/src/DataProviders/Abstractions/CollectionDataProviderBase.cs deleted file mode 100644 index 9dd7cce..0000000 --- a/src/DataProviders/Abstractions/CollectionDataProviderBase.cs +++ /dev/null @@ -1,283 +0,0 @@ -using System.Linq.Expressions; - -using Microsoft.Extensions.Logging; - -using MongoDB.Bson.Serialization; -using MongoDB.Driver; - -using DomainResults.Common; - -using DomainObjects.Abstractions; -using System; -using System.Collections.Generic; - -namespace DataProviders.Abstractions { - - /// - /// - /// - /// - public abstract class CollectionDataProviderBase : DataProviderBase> where T : DomainObjectDocumentBase { - - private protected readonly IIdGenerator _idGenerator; - private protected readonly ISessionService _sessionService; - private protected readonly IMongoCollection _collection; - - /// - /// Main constructor - /// - /// - /// - /// - /// - public CollectionDataProviderBase( - ILogger> logger, - IMongoClient client, - IIdGenerator idGenerator, - ISessionService sessionService, - string databaseName, - string collectionName - ) : base(logger, client, databaseName) { - _idGenerator = idGenerator; - _sessionService = sessionService; - - if (!_database.ListCollectionNames().ToList().Contains(collectionName)) - _database.CreateCollection(collectionName); - - _collection = _database.GetCollection(collectionName); - } - - #region Insert - public (Guid?, IDomainResult) Insert(T obj) => - InsertAsync(obj).Result; - - public (Guid?, IDomainResult) Insert(T obj, Guid sessionId) => - InsertAsync(obj, sessionId).Result; - - public Task<(Guid?, IDomainResult)> InsertAsync(T obj) => - InsertAsync(obj, null); - - public Task<(Guid?, IDomainResult)> InsertAsync(T obj, Guid sessionId) => - InsertAsync(obj, sessionId); - - protected virtual async Task<(Guid?, IDomainResult)> InsertAsync(T obj, Guid? sessionId) { - try { - if (sessionId != null) - await _collection.InsertOneAsync(_sessionService.GetSession(sessionId.Value), obj); - else - _collection.InsertOne(obj); - - return IDomainResult.Success(obj.Id); - } - catch (Exception ex) { - _logger.LogError(ex, "Data provider error"); - return IDomainResult.Failed(); - } - } - #endregion - - #region InsertMany - private protected (List?, IDomainResult) InsertMany(List objList) => - InsertManyAsync(objList).Result; - - private protected (List?, IDomainResult) InsertMany(List objList, Guid sessionId) => - InsertManyAsync(objList, sessionId).Result; - - private protected Task<(List?, IDomainResult)> InsertManyAsync(List objList) => - InsertManyAsync(objList, null); - - private protected Task<(List?, IDomainResult)> InsertManyAsync(List objList, Guid sessionId) => - InsertManyAsync(objList, sessionId); - - protected virtual async Task<(List?, IDomainResult)> InsertManyAsync(List objList, Guid? sessionId) { - try { - if (sessionId != null) - await _collection.InsertManyAsync(_sessionService.GetSession(sessionId.Value), objList); - else - _collection.InsertMany(objList); - - return IDomainResult.Success(objList.Select(x => x.Id).ToList()); - } - catch (Exception ex) { - _logger.LogError(ex, "Data provider error"); - return IDomainResult.Failed?>(); - } - } - #endregion - - #region Count - private protected (int?, IDomainResult) CountWithPredicate(Expression> predicate) => - CountWithPredicate(new List>> { predicate }); - - private protected (int?, IDomainResult) CountWithPredicate(List>> predicates) { - try { - var query = GetWithPredicate(predicates); - - var result = query.Count(); - - return IDomainResult.Success(result); - } - catch (Exception ex) { - _logger.LogError(ex, "Data provider error"); - return IDomainResult.Failed(); - } - } - #endregion - - #region Get - /// - /// - /// - /// - /// - /// - /// - private protected (List?, IDomainResult) GetWithPredicate(Expression> predicate, Expression> selector) => - GetWithPredicate(new List>> { predicate }, selector, null, null); - - private protected (List?, IDomainResult) GetWithPredicate(Expression> predicates, Expression> selector, int? skip, int? limit) => - GetWithPredicate(new List>> { predicates }, selector, skip, limit); - - /// - /// - /// - /// - /// - /// - /// - /// - /// - private protected (List?, IDomainResult) GetWithPredicate(List>> predicates, Expression> selector, int? skip, int? limit) { - try { - var query = GetWithPredicate(predicates).Select(selector); - - if (skip != null) - query = query.Skip(skip.Value); - - if (limit != null) - query = query.Take(limit.Value); - - var result = query.ToList(); - - return result != null && result.Count > 0 - ? IDomainResult.Success(result) - : IDomainResult.NotFound?>(); - } - catch (Exception ex) { - _logger.LogError(ex, "Data provider error"); - return IDomainResult.Failed?>(); - } - } - - /// - /// - /// - /// - /// - private protected IQueryable GetWithPredicate(List>> predicates) { - var query = GetQuery(); - - foreach (var predicate in predicates) - query = query.Where(predicate); - - return query; - } - - /// - /// - /// - /// - protected virtual IQueryable GetQuery() => _collection.AsQueryable(); - #endregion - - #region Update - private protected (Guid?, IDomainResult) UpdateWithPredicate(T obj, Expression> predicate) => - UpdateWithPredicateAsync(obj, predicate).Result; - - private protected (Guid?, IDomainResult) UpdateWithPredicate(T obj, Expression> predicate, Guid sessionId) => - UpdateWithPredicateAsync(obj, predicate, sessionId).Result; - - private protected Task<(Guid?, IDomainResult)> UpdateWithPredicateAsync(T obj, Expression> predicate) => - UpdateWithPredicateAsync(obj, predicate, null); - - private protected Task<(Guid?, IDomainResult)> UpdateWithPredicateAsync(T obj, Expression> predicate, Guid sessionId) => - UpdateWithPredicateAsync(obj, predicate, sessionId); - - protected virtual async Task<(Guid?, IDomainResult)> UpdateWithPredicateAsync(T obj, Expression> predicate, Guid? sessionId) { - try { - - - if (sessionId != null) - await _collection.ReplaceOneAsync(_sessionService.GetSession(sessionId.Value), predicate, obj); - else - await _collection.ReplaceOneAsync(predicate, obj); - - return IDomainResult.Success(obj.Id); - } - catch (Exception ex) { - _logger.LogError(ex, "Data provider error"); - return IDomainResult.Failed(); - } - } - #endregion - - #region Delete - private protected IDomainResult DeleteWithPredicate(Expression> predicate) => - DeleteWithPredicateAsync(predicate).Result; - - private protected IDomainResult DeleteWithPredicate(Expression> predicate, Guid sessionId) => - DeleteWithPredicateAsync(predicate, sessionId).Result; - - private protected Task DeleteWithPredicateAsync(Expression> predicate) => - DeleteWithPredicateAsync(predicate, null); - - private protected Task DeleteWithPredicateAsync(Expression> predicate, Guid sessionId) => - DeleteWithPredicateAsync(predicate, sessionId); - - protected virtual async Task DeleteWithPredicateAsync(Expression> predicate, Guid? sessionId) { - try { - if (sessionId != null) - await _collection.DeleteOneAsync(_sessionService.GetSession(sessionId.Value), predicate); - else - await _collection.DeleteOneAsync(predicate); - - return IDomainResult.Success(); - } - catch (Exception ex) { - _logger.LogError(ex, "Data provider error"); - return IDomainResult.Failed(); - } - } - #endregion - - #region DeleteMany - private protected IDomainResult DeleteManyWithPredicate(Expression> predicate) => - DeleteManyWithPredicateAsync(predicate).Result; - - private protected IDomainResult DeleteManyWithPredicate(Expression> predicate, Guid sessionId) => - DeleteManyWithPredicateAsync(predicate, sessionId).Result; - - private protected Task DeleteManyWithPredicateAsync(Expression> predicate) => - DeleteManyWithPredicateAsync(predicate, null); - - private protected Task DeleteManyWithPredicateAsync(Expression> predicate, Guid sessionId) => - DeleteManyWithPredicateAsync(predicate, sessionId); - - protected virtual async Task DeleteManyWithPredicateAsync(Expression> predicate, Guid? sessionId) { - try { - - if (sessionId != null) - await _collection.DeleteManyAsync(_sessionService.GetSession(sessionId.Value), predicate); - else - await _collection.DeleteManyAsync(predicate); - - return IDomainResult.Success(); - } - catch (Exception ex) { - _logger.LogError(ex, "Data provider error"); - return IDomainResult.Failed(); - } - } - #endregion - } -} diff --git a/src/DataProviders/Abstractions/BucketDataProviderBase.cs b/src/DataProviders/Buckets/Abstractions/BucketDataProviderBase.cs similarity index 82% rename from src/DataProviders/Abstractions/BucketDataProviderBase.cs rename to src/DataProviders/Buckets/Abstractions/BucketDataProviderBase.cs index 2bff450..03fda44 100644 --- a/src/DataProviders/Abstractions/BucketDataProviderBase.cs +++ b/src/DataProviders/Buckets/Abstractions/BucketDataProviderBase.cs @@ -6,18 +6,17 @@ using MongoDB.Driver.GridFS; using DomainResults.Common; using Extensions; -using DataProviders.Buckets; +using DataProviders.Abstractions; -namespace DataProviders.Abstractions -{ +namespace DataProviders.Buckets.Abstractions { - /// - /// - /// - /// - public abstract class BucketDataProviderBase : DataProviderBase { + /// + /// + /// + /// + public abstract class BucketDataProviderBase : DataProviderBase { - private readonly GridFSBucket _bucket; + protected readonly GridFSBucket _bucket; /// /// @@ -27,8 +26,9 @@ namespace DataProviders.Abstractions public BucketDataProviderBase( ILogger logger, IMongoClient client, + string databaseName, string bucketName - ) : base (logger, client, "reactredux") { + ) : base(logger, client, databaseName) { _bucket = new GridFSBucket(_database, new GridFSBucketOptions { BucketName = bucketName, ChunkSizeBytes = 1048576, // 1MB @@ -55,7 +55,7 @@ namespace DataProviders.Abstractions public (List?, IDomainResult) UploadMany(List files) => UploadManyAsync(files).Result; - public async Task<(List?, IDomainResult)> UploadManyAsync(List files) { + protected virtual async Task<(List?, IDomainResult)> UploadManyAsync(List files) { var options = new GridFSUploadOptions { ChunkSizeBytes = 64512, // 63KB }; @@ -63,11 +63,8 @@ namespace DataProviders.Abstractions try { var result = new List(); foreach (var file in files) { - options.Metadata = new BsonDocument { { "siteId", $"{file.SiteId}"}, - { "userId", $"{file.UserId}"}, - { "published", file.Published.ToString() }, { "fileName", file.Name }, { "contentType", file.ContentType } }; @@ -91,7 +88,8 @@ namespace DataProviders.Abstractions private protected (List?, IDomainResult) Download(FilterDefinition filter) => DownloadAsync(filter).Result; - private protected async Task<(List?, IDomainResult)> DownloadAsync(FilterDefinition filter) { + protected virtual async Task<(List?, IDomainResult)> DownloadAsync(FilterDefinition filter) { + try { var sort = Builders.Sort.Descending(x => x.UploadDateTime); @@ -107,19 +105,17 @@ namespace DataProviders.Abstractions return IDomainResult.NotFound?>(); var id = fileInfo.Filename.ToGuid(); - var userId = fileInfo.Metadata["userId"].ToString()?.ToNullableGuid(); var siteId = fileInfo.Metadata["siteId"].ToString()?.ToNullableGuid(); - var published = fileInfo.Metadata["published"].ToString()?.ToNullableDateTime(); var fileName = fileInfo.Metadata["fileName"].ToString() ?? ""; var bytes = await _bucket.DownloadAsBytesByNameAsync($"{fileInfo.Filename}", new GridFSDownloadByNameOptions { Revision = -1 }); var contentType = fileInfo.Metadata["contentType"].ToString() ?? ""; - if (siteId == null || userId == null || bytes == null) + if (siteId == null || bytes == null) return IDomainResult.Failed?>(); - result.Add(new BucketFile(id, siteId.Value, userId.Value, published, fileName, bytes, contentType)); + result.Add(new BucketFile(id, siteId.Value, fileName, bytes, contentType)); } return result.Count > 0 diff --git a/src/DataProviders/Buckets/BucketFile.cs b/src/DataProviders/Buckets/BucketFile.cs index 3b773a0..b29660e 100644 --- a/src/DataProviders/Buckets/BucketFile.cs +++ b/src/DataProviders/Buckets/BucketFile.cs @@ -4,36 +4,42 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -namespace DataProviders.Buckets -{ - public class BucketFile - { +namespace DataProviders.Buckets { + public class BucketFile { - public Guid Id { get; private set; } - public Guid SiteId { get; private set; } - public Guid UserId { get; private set; } + public Guid Id { get; private set; } + public Guid SiteId { get; private set; } + public Guid? UserId { get; private set; } - public DateTime? Published { get; private set; } + public DateTime? Published { get; private set; } - public string Name { get; private set; } - public byte[] Bytes { get; private set; } - public string ContentType { get; private set; } + public string Name { get; private set; } + public byte[] Bytes { get; private set; } + public string ContentType { get; private set; } - public BucketFile(Guid id, Guid siteId, Guid userId, DateTime? published, string name, byte[] bytes, string contentType) - { - Id = id; - SiteId = siteId; - UserId = userId; - - Published = published; - - Name = name; - Bytes = bytes; - ContentType = contentType; - } - - public BucketFile(Guid id, Guid siteId, Guid userId, string name, byte[] bytes, string contentType) - : this(id, siteId, userId, null, name, bytes, contentType) { } + public BucketFile(Guid siteId, string name, byte[] bytes, string contentType) { + Id = Guid.NewGuid(); + SiteId = siteId; + Name = name; + Bytes = bytes; + ContentType = contentType; } + + public BucketFile(Guid id, Guid siteId, string name, byte[] bytes, string contentType) { + Id = id; + SiteId = siteId; + Name = name; + Bytes = bytes; + ContentType = contentType; + } + + public BucketFile(Guid id, Guid siteId, Guid userId, string name, byte[] bytes, string contentType) : this(id, siteId, name, bytes, contentType) { + UserId = userId; + } + + public BucketFile(Guid id, Guid siteId, Guid userId, DateTime? published, string name, byte[] bytes, string contentType) : this(id, siteId, userId, name, bytes, contentType) { + Published = published; + } + } } diff --git a/src/DataProviders/Buckets/DkimBucketDataProvider.cs b/src/DataProviders/Buckets/DkimBucketDataProvider.cs new file mode 100644 index 0000000..f677cc2 --- /dev/null +++ b/src/DataProviders/Buckets/DkimBucketDataProvider.cs @@ -0,0 +1,64 @@ + +using Microsoft.Extensions.Logging; + +using DomainResults.Common; + +using MongoDB.Driver; +using MongoDB.Driver.GridFS; + +using DataProviders.Buckets.Abstractions; + +namespace DataProviders.Buckets { + + public interface IDkimBucketDataProvider { + /// + /// + /// + /// + /// + /// + /// + (Guid?, IDomainResult) Upload(BucketFile file); + + /// + /// + /// + /// + /// + /// + (BucketFile?, IDomainResult) Download(Guid siteId, Guid fileId); + } + + public class DkimBucketDataProvider : BucketDataProviderBase, IDkimBucketDataProvider { + + /// + /// + /// + /// + /// + public DkimBucketDataProvider( + ILogger logger, + IMongoClient client + ) : base(logger, client, "reactredux", "dkims") { } + + /// + /// + /// + /// + /// + /// + public (BucketFile?, IDomainResult) Download(Guid siteId, Guid fileId) { + var filter = Builders.Filter.And( + Builders.Filter.Eq(x => x.Metadata["siteId"], $"{siteId}"), + Builders.Filter.Eq(x => x.Filename, $"{fileId}") + ); + + var (list, result) = Download(filter); + + if (!result.IsSuccess || list == null) + return (null, result); + + return (list.First(), result); + } + } +} diff --git a/src/DataProviders/Buckets/ImageBucketDataProvider.cs b/src/DataProviders/Buckets/ImageBucketDataProvider.cs index 3dcba68..8f7fb15 100644 --- a/src/DataProviders/Buckets/ImageBucketDataProvider.cs +++ b/src/DataProviders/Buckets/ImageBucketDataProvider.cs @@ -3,17 +3,17 @@ using DomainResults.Common; using MongoDB.Driver; - -using DataProviders.Abstractions; using MongoDB.Driver.GridFS; +using DataProviders.Buckets.Abstractions; +using MongoDB.Bson; +using Extensions; -namespace DataProviders.Buckets -{ +namespace DataProviders.Buckets { - /// - /// - /// - public interface IImageBucketDataProvider { + /// + /// + /// + public interface IImageBucketDataProvider { /// /// @@ -84,9 +84,41 @@ namespace DataProviders.Buckets public ImageBucketDataProvider( ILogger logger, IMongoClient client - ) : base(logger, client, "images") { } + ) : base(logger, client, "reactredux", "images") { } + #region Upload many + protected override async Task<(List?, IDomainResult)> UploadManyAsync(List files) { + var options = new GridFSUploadOptions { + ChunkSizeBytes = 64512, // 63KB + }; + try { + var result = new List(); + foreach (var file in files) { + options.Metadata = new BsonDocument { + { "siteId", $"{file.SiteId}"}, + { "userId", $"{file.UserId}"}, + { "published", file.Published.ToString() }, + { "fileName", file.Name }, + { "contentType", file.ContentType } + }; + + await _bucket.UploadFromBytesAsync($"{file.Id}", file.Bytes, options); + result.Add(file.Id); + } + + return result.Count > 0 + ? IDomainResult.Success(result) + : IDomainResult.Failed?>(); + } + catch (Exception ex) { + _logger.LogError(ex, "Bucket data provider error"); + return IDomainResult.Failed?>(); + } + } + #endregion + + #region Download /// /// /// @@ -130,7 +162,9 @@ namespace DataProviders.Buckets return (list.First(), result); } + #endregion + #region Download many /// /// /// @@ -152,11 +186,58 @@ namespace DataProviders.Buckets ); } + if (filter == null) + return IDomainResult.Failed?>(); + var result = Download(filter); return result; } + protected override async Task<(List?, IDomainResult)> DownloadAsync(FilterDefinition filter) { + try { + var sort = Builders.Sort.Descending(x => x.UploadDateTime); + + using var cursor = await _bucket.FindAsync(filter, new GridFSFindOptions { + Sort = sort, + }); + + var result = new List(); + + // fileInfo either has the matching file information or is null + foreach (var fileInfo in await cursor.ToListAsync()) { + if (fileInfo == null) + return IDomainResult.NotFound?>(); + + var id = fileInfo.Filename.ToGuid(); + var userId = fileInfo.Metadata["userId"].ToString()?.ToNullableGuid(); + var siteId = fileInfo.Metadata["siteId"].ToString()?.ToNullableGuid(); + var published = fileInfo.Metadata["published"].ToString()?.ToNullableDateTime(); + var fileName = fileInfo.Metadata["fileName"].ToString() ?? ""; + var bytes = await _bucket.DownloadAsBytesByNameAsync($"{fileInfo.Filename}", new GridFSDownloadByNameOptions { + Revision = -1 + }); + var contentType = fileInfo.Metadata["contentType"].ToString() ?? ""; + + if (siteId == null || userId == null || bytes == null) + return IDomainResult.Failed?>(); + + result.Add(new BucketFile(id, siteId.Value, userId.Value, published, fileName, bytes, contentType)); + } + + return result.Count > 0 + ? IDomainResult.Success(result) + : IDomainResult.NotFound?>(); + + } + catch (Exception ex) { + _logger.LogError(ex, "Bucket data provider error"); + return IDomainResult.Failed?>(); + } + } + #endregion + + #region Delete public IDomainResult DeleteOne(Guid siteId, Guid userId, Guid fileId) => DeleteOneAsync(siteId, userId, fileId).Result; @@ -175,5 +256,6 @@ namespace DataProviders.Buckets Builders.Filter.Eq(x => x.Metadata["siteId"], $"{siteId}"), Builders.Filter.Eq(x => x.Metadata["userId"], $"{userId}") )); + #endregion } } diff --git a/src/DataProviders/Buckets/TemplateBucketDataProvider.cs b/src/DataProviders/Buckets/TemplateBucketDataProvider.cs new file mode 100644 index 0000000..372ae13 --- /dev/null +++ b/src/DataProviders/Buckets/TemplateBucketDataProvider.cs @@ -0,0 +1,58 @@ +using DataProviders.Buckets.Abstractions; +using DomainResults.Common; +using Microsoft.Extensions.Logging; +using MongoDB.Driver; +using MongoDB.Driver.GridFS; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace DataProviders.Buckets { + + /// + /// + /// + public interface ITemplateBucketDataProvider { + + /// + /// + /// + /// + /// + /// + /// + (Guid?, IDomainResult) Upload(BucketFile file); + + /// + /// + /// + /// + /// + /// + (BucketFile?, IDomainResult) Download(Guid siteId, Guid fileId); + } + + public class TemplateBucketDataProvider : BucketDataProviderBase, ITemplateBucketDataProvider { + public TemplateBucketDataProvider( + ILogger logger, + IMongoClient client + ) : base(logger, client, "reactredux", "templates") { } + + public (BucketFile?, IDomainResult) Download(Guid siteId, Guid fileId) { + var filter = Builders.Filter.And( + Builders.Filter.Eq(x => x.Metadata["siteId"], $"{siteId}"), + Builders.Filter.Eq(x => x.Filename, $"{fileId}") + ); + + var (list, result) = Download(filter); + + if (!result.IsSuccess || list == null) + return (null, result); + + return (list.First(), result); + } + + } +} diff --git a/src/DataProviders/Collections/Abstractions/CollectionDataProviderBase.cs b/src/DataProviders/Collections/Abstractions/CollectionDataProviderBase.cs new file mode 100644 index 0000000..1bf16ee --- /dev/null +++ b/src/DataProviders/Collections/Abstractions/CollectionDataProviderBase.cs @@ -0,0 +1,309 @@ +using System.Linq.Expressions; + +using Microsoft.Extensions.Logging; + +using MongoDB.Bson.Serialization; +using MongoDB.Driver; + +using DomainResults.Common; + +using DomainObjects.Abstractions; +using System; +using System.Collections.Generic; +using DataProviders.Abstractions; + +namespace DataProviders.Collections.Abstractions +{ + + /// + /// + /// + /// + public abstract class CollectionDataProviderBase : DataProviderBase> where T : DomainObjectDocumentBase + { + + private protected readonly IIdGenerator _idGenerator; + private protected readonly ISessionService _sessionService; + private protected readonly IMongoCollection _collection; + + /// + /// Main constructor + /// + /// + /// + /// + /// + public CollectionDataProviderBase( + ILogger> logger, + IMongoClient client, + IIdGenerator idGenerator, + ISessionService sessionService, + string databaseName, + string collectionName + ) : base(logger, client, databaseName) + { + _idGenerator = idGenerator; + _sessionService = sessionService; + + if (!_database.ListCollectionNames().ToList().Contains(collectionName)) + _database.CreateCollection(collectionName); + + _collection = _database.GetCollection(collectionName); + } + + #region Insert + public (Guid?, IDomainResult) Insert(T obj) => + InsertAsync(obj).Result; + + public (Guid?, IDomainResult) Insert(T obj, Guid sessionId) => + InsertAsync(obj, sessionId).Result; + + public Task<(Guid?, IDomainResult)> InsertAsync(T obj) => + InsertAsync(obj, null); + + public Task<(Guid?, IDomainResult)> InsertAsync(T obj, Guid sessionId) => + InsertAsync(obj, sessionId); + + protected virtual async Task<(Guid?, IDomainResult)> InsertAsync(T obj, Guid? sessionId) + { + try + { + if (sessionId != null) + await _collection.InsertOneAsync(_sessionService.GetSession(sessionId.Value), obj); + else + _collection.InsertOne(obj); + + return IDomainResult.Success(obj.Id); + } + catch (Exception ex) + { + _logger.LogError(ex, "Data provider error"); + return IDomainResult.Failed(); + } + } + #endregion + + #region InsertMany + private protected (List?, IDomainResult) InsertMany(List objList) => + InsertManyAsync(objList).Result; + + private protected (List?, IDomainResult) InsertMany(List objList, Guid sessionId) => + InsertManyAsync(objList, sessionId).Result; + + private protected Task<(List?, IDomainResult)> InsertManyAsync(List objList) => + InsertManyAsync(objList, null); + + private protected Task<(List?, IDomainResult)> InsertManyAsync(List objList, Guid sessionId) => + InsertManyAsync(objList, sessionId); + + protected virtual async Task<(List?, IDomainResult)> InsertManyAsync(List objList, Guid? sessionId) + { + try + { + if (sessionId != null) + await _collection.InsertManyAsync(_sessionService.GetSession(sessionId.Value), objList); + else + _collection.InsertMany(objList); + + return IDomainResult.Success(objList.Select(x => x.Id).ToList()); + } + catch (Exception ex) + { + _logger.LogError(ex, "Data provider error"); + return IDomainResult.Failed?>(); + } + } + #endregion + + #region Count + private protected (int?, IDomainResult) CountWithPredicate(Expression> predicate) => + CountWithPredicate(new List>> { predicate }); + + private protected (int?, IDomainResult) CountWithPredicate(List>> predicates) + { + try + { + var query = GetWithPredicate(predicates); + + var result = query.Count(); + + return IDomainResult.Success(result); + } + catch (Exception ex) + { + _logger.LogError(ex, "Data provider error"); + return IDomainResult.Failed(); + } + } + #endregion + + #region Get + /// + /// + /// + /// + /// + /// + /// + private protected (List?, IDomainResult) GetWithPredicate(Expression> predicate, Expression> selector) => + GetWithPredicate(new List>> { predicate }, selector, null, null); + + private protected (List?, IDomainResult) GetWithPredicate(Expression> predicates, Expression> selector, int? skip, int? limit) => + GetWithPredicate(new List>> { predicates }, selector, skip, limit); + + /// + /// + /// + /// + /// + /// + /// + /// + /// + private protected (List?, IDomainResult) GetWithPredicate(List>> predicates, Expression> selector, int? skip, int? limit) + { + try + { + var query = GetWithPredicate(predicates).Select(selector); + + if (skip != null) + query = query.Skip(skip.Value); + + if (limit != null) + query = query.Take(limit.Value); + + var result = query.ToList(); + + return result != null && result.Count > 0 + ? IDomainResult.Success(result) + : IDomainResult.NotFound?>(); + } + catch (Exception ex) + { + _logger.LogError(ex, "Data provider error"); + return IDomainResult.Failed?>(); + } + } + + /// + /// + /// + /// + /// + private protected IQueryable GetWithPredicate(List>> predicates) + { + var query = GetQuery(); + + foreach (var predicate in predicates) + query = query.Where(predicate); + + return query; + } + + /// + /// + /// + /// + protected virtual IQueryable GetQuery() => _collection.AsQueryable(); + #endregion + + #region Update + private protected (Guid?, IDomainResult) UpdateWithPredicate(T obj, Expression> predicate) => + UpdateWithPredicateAsync(obj, predicate).Result; + + private protected (Guid?, IDomainResult) UpdateWithPredicate(T obj, Expression> predicate, Guid sessionId) => + UpdateWithPredicateAsync(obj, predicate, sessionId).Result; + + private protected Task<(Guid?, IDomainResult)> UpdateWithPredicateAsync(T obj, Expression> predicate) => + UpdateWithPredicateAsync(obj, predicate, null); + + private protected Task<(Guid?, IDomainResult)> UpdateWithPredicateAsync(T obj, Expression> predicate, Guid sessionId) => + UpdateWithPredicateAsync(obj, predicate, sessionId); + + protected virtual async Task<(Guid?, IDomainResult)> UpdateWithPredicateAsync(T obj, Expression> predicate, Guid? sessionId) + { + try + { + + + if (sessionId != null) + await _collection.ReplaceOneAsync(_sessionService.GetSession(sessionId.Value), predicate, obj); + else + await _collection.ReplaceOneAsync(predicate, obj); + + return IDomainResult.Success(obj.Id); + } + catch (Exception ex) + { + _logger.LogError(ex, "Data provider error"); + return IDomainResult.Failed(); + } + } + #endregion + + #region Delete + private protected IDomainResult DeleteWithPredicate(Expression> predicate) => + DeleteWithPredicateAsync(predicate).Result; + + private protected IDomainResult DeleteWithPredicate(Expression> predicate, Guid sessionId) => + DeleteWithPredicateAsync(predicate, sessionId).Result; + + private protected Task DeleteWithPredicateAsync(Expression> predicate) => + DeleteWithPredicateAsync(predicate, null); + + private protected Task DeleteWithPredicateAsync(Expression> predicate, Guid sessionId) => + DeleteWithPredicateAsync(predicate, sessionId); + + protected virtual async Task DeleteWithPredicateAsync(Expression> predicate, Guid? sessionId) + { + try + { + if (sessionId != null) + await _collection.DeleteOneAsync(_sessionService.GetSession(sessionId.Value), predicate); + else + await _collection.DeleteOneAsync(predicate); + + return IDomainResult.Success(); + } + catch (Exception ex) + { + _logger.LogError(ex, "Data provider error"); + return IDomainResult.Failed(); + } + } + #endregion + + #region DeleteMany + private protected IDomainResult DeleteManyWithPredicate(Expression> predicate) => + DeleteManyWithPredicateAsync(predicate).Result; + + private protected IDomainResult DeleteManyWithPredicate(Expression> predicate, Guid sessionId) => + DeleteManyWithPredicateAsync(predicate, sessionId).Result; + + private protected Task DeleteManyWithPredicateAsync(Expression> predicate) => + DeleteManyWithPredicateAsync(predicate, null); + + private protected Task DeleteManyWithPredicateAsync(Expression> predicate, Guid sessionId) => + DeleteManyWithPredicateAsync(predicate, sessionId); + + protected virtual async Task DeleteManyWithPredicateAsync(Expression> predicate, Guid? sessionId) + { + try + { + + if (sessionId != null) + await _collection.DeleteManyAsync(_sessionService.GetSession(sessionId.Value), predicate); + else + await _collection.DeleteManyAsync(predicate); + + return IDomainResult.Success(); + } + catch (Exception ex) + { + _logger.LogError(ex, "Data provider error"); + return IDomainResult.Failed(); + } + } + #endregion + } +} diff --git a/src/DataProviders/Collections/BlogCatalogDataProvider.cs b/src/DataProviders/Collections/BlogCatalogDataProvider.cs index 77359bb..80bbe6b 100644 --- a/src/DataProviders/Collections/BlogCatalogDataProvider.cs +++ b/src/DataProviders/Collections/BlogCatalogDataProvider.cs @@ -5,12 +5,12 @@ using DomainResults.Common; using MongoDB.Bson.Serialization; using MongoDB.Driver; -using DataProviders.Abstractions; - using DomainObjects.Documents; +using DataProviders.Collections.Abstractions; -namespace DataProviders.Collections { - public interface IBlogCatalogDataProvider { +namespace DataProviders.Collections +{ + public interface IBlogCatalogDataProvider { (Guid?, IDomainResult) Insert(BlogDocument blogItem); (BlogDocument?, IDomainResult) Get(Guid siteId, Guid blogId); (BlogDocument?, IDomainResult) GetBySlug(Guid siteId, string slug); diff --git a/src/DataProviders/Collections/CategoryDataProvider.cs b/src/DataProviders/Collections/CategoryDataProvider.cs index c92dc97..a9b2249 100644 --- a/src/DataProviders/Collections/CategoryDataProvider.cs +++ b/src/DataProviders/Collections/CategoryDataProvider.cs @@ -4,15 +4,15 @@ using MongoDB.Driver; using MongoDB.Bson.Serialization; using DomainResults.Common; - -using DataProviders.Abstractions; using DomainObjects.Documents; using DomainObjects.L10n; using DomainObjects.Enumerations; +using DataProviders.Collections.Abstractions; -namespace DataProviders.Collections { +namespace DataProviders.Collections +{ - public interface ICategoryDataProvider { + public interface ICategoryDataProvider { (Guid?, IDomainResult) Insert(CategoryDocument obj); (Guid?, IDomainResult) CreateDefault(Guid siteId); (CategoryDocument?, IDomainResult) Get(Guid siteId, Guid categoryId); diff --git a/src/DataProviders/Collections/ContentDataProvider.cs b/src/DataProviders/Collections/ContentDataProvider.cs index 09bf281..143799d 100644 --- a/src/DataProviders/Collections/ContentDataProvider.cs +++ b/src/DataProviders/Collections/ContentDataProvider.cs @@ -4,13 +4,13 @@ using DomainResults.Common; using MongoDB.Bson.Serialization; using MongoDB.Driver; - -using DataProviders.Abstractions; using DomainObjects.Documents; +using DataProviders.Collections.Abstractions; -namespace DataProviders.Collections { +namespace DataProviders.Collections +{ - public interface IContentDataProvider { + public interface IContentDataProvider { (List?, IDomainResult) Get(Guid siteId); } diff --git a/src/DataProviders/Collections/ShopCartDataProvider.cs b/src/DataProviders/Collections/ShopCartDataProvider.cs index 5c897fd..66a16a9 100644 --- a/src/DataProviders/Collections/ShopCartDataProvider.cs +++ b/src/DataProviders/Collections/ShopCartDataProvider.cs @@ -5,13 +5,13 @@ using DomainResults.Common; using MongoDB.Bson.Serialization; using MongoDB.Driver; -using DataProviders.Abstractions; - using DomainObjects.Documents; +using DataProviders.Collections.Abstractions; -namespace DataProviders.Collections { +namespace DataProviders.Collections +{ - public interface IShopCartDataProvider { + public interface IShopCartDataProvider { (Guid?, IDomainResult) Insert(ShopCartDocument obj); (List?, IDomainResult) GetAll(Guid siteId, Guid userId); (ShopCartDocument?, IDomainResult) Get(Guid siteId, Guid userId, string sku); diff --git a/src/DataProviders/Collections/ShopCatalogDataProvider.cs b/src/DataProviders/Collections/ShopCatalogDataProvider.cs index 0043e66..a0fb020 100644 --- a/src/DataProviders/Collections/ShopCatalogDataProvider.cs +++ b/src/DataProviders/Collections/ShopCatalogDataProvider.cs @@ -4,12 +4,12 @@ using DomainResults.Common; using MongoDB.Bson.Serialization; using MongoDB.Driver; - -using DataProviders.Abstractions; using DomainObjects.Documents; +using DataProviders.Collections.Abstractions; -namespace DataProviders.Collections { - public interface IShopCatalogDataProvider { +namespace DataProviders.Collections +{ + public interface IShopCatalogDataProvider { (Guid?, IDomainResult) Insert(ShopDocument obj); (ShopDocument?, IDomainResult) Get(Guid siteId, string sku); (ShopDocument?, IDomainResult) GetBySlug(Guid siteId, string slug); diff --git a/src/DataProviders/Collections/SiteDataProvider.cs b/src/DataProviders/Collections/SiteDataProvider.cs index eab74a0..bcc20b4 100644 --- a/src/DataProviders/Collections/SiteDataProvider.cs +++ b/src/DataProviders/Collections/SiteDataProvider.cs @@ -5,9 +5,9 @@ using MongoDB.Driver; using DomainObjects.Documents.Sites; using DomainResults.Common; -using DataProviders.Abstractions; using Microsoft.AspNetCore.Mvc; using System.Collections.Generic; +using DataProviders.Collections.Abstractions; namespace DataProviders.Collections; @@ -28,6 +28,7 @@ public class SiteDataProvider : CollectionDataProviderBase, ISiteDataProvi IIdGenerator idGenerator, ISessionService sessionService) : base(logger, client, idGenerator, sessionService, _databaseName, _collectionName) { } + public (Site?, IDomainResult) GetByHostName(string hostName) { var (list, result) = GetWithPredicate(x => x.Hosts.Contains(hostName), x => x); diff --git a/src/DataProviders/Collections/UserDataProvider.cs b/src/DataProviders/Collections/UserDataProvider.cs index 95d6285..5664d76 100644 --- a/src/DataProviders/Collections/UserDataProvider.cs +++ b/src/DataProviders/Collections/UserDataProvider.cs @@ -1,12 +1,11 @@ using Microsoft.Extensions.Logging; -using DataProviders.Abstractions; - using DomainResults.Common; using MongoDB.Bson.Serialization; using MongoDB.Driver; using DomainObjects.Documents.Users; +using DataProviders.Collections.Abstractions; namespace DataProviders.Collections; diff --git a/src/DataProviders/Extensions/ServiceCollectionExtensions.cs b/src/DataProviders/Extensions/ServiceCollectionExtensions.cs index 74da74b..f4705ab 100644 --- a/src/DataProviders/Extensions/ServiceCollectionExtensions.cs +++ b/src/DataProviders/Extensions/ServiceCollectionExtensions.cs @@ -38,6 +38,8 @@ namespace DataProviders.Extensions #region Buckets services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); #endregion } } diff --git a/src/DomainObjects/Contact.cs b/src/DomainObjects/Contact.cs index fae6699..029daff 100644 --- a/src/DomainObjects/Contact.cs +++ b/src/DomainObjects/Contact.cs @@ -6,13 +6,12 @@ namespace DomainObjects; public class Contact : DomainObjectBase { public ContactTypes Type { get; set; } public string Value { get; set; } - public bool Confirmed { get; set; } + public bool? Confirmed { get; set; } public bool? Primary { get; set; } public Contact(ContactTypes type, string value) { Type = type; Value = value; - Confirmed = false; } public override int GetHashCode() { diff --git a/src/DomainObjects/Documents/Users/Address.cs b/src/DomainObjects/Documents/Address.cs similarity index 80% rename from src/DomainObjects/Documents/Users/Address.cs rename to src/DomainObjects/Documents/Address.cs index 832f8ec..7f05b4a 100644 --- a/src/DomainObjects/Documents/Users/Address.cs +++ b/src/DomainObjects/Documents/Address.cs @@ -1,4 +1,4 @@ -namespace DomainObjects.Documents.Users; +namespace DomainObjects.Documents; public class Address { diff --git a/src/DomainObjects/Documents/MailboxConnectionSettings.cs b/src/DomainObjects/Documents/MailboxConnectionSettings.cs new file mode 100644 index 0000000..c9845dc --- /dev/null +++ b/src/DomainObjects/Documents/MailboxConnectionSettings.cs @@ -0,0 +1,32 @@ +using CryptoProvider; +using DomainObjects.Abstractions; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace DomainObjects.Documents; + +public class MailboxConnectionSettings : DomainObjectBase { + + public string Server { get; set; } + + public uint Port { get; set; } + + public bool UseSsl { get; set; } + + public string UserName { get; set; } + + public string Password { get; private set; } + public void SetPassword(string iv, string key, string password) { + Password = AesService.EncryptString(iv, key, password); + } + + public string GetPassword(string iv, string key) => + AesService.DecryptString(iv, key, Password); + + public override int GetHashCode() { + throw new NotImplementedException(); + } +} diff --git a/src/DomainObjects/Documents/Sites/DkimSettings.cs b/src/DomainObjects/Documents/Sites/DkimSettings.cs new file mode 100644 index 0000000..5975a1f --- /dev/null +++ b/src/DomainObjects/Documents/Sites/DkimSettings.cs @@ -0,0 +1,16 @@ +using DomainObjects.Abstractions; + +namespace DomainObjects.Documents.Sites; +public class DkimSettings : DomainObjectBase { + + public string HostName { get; set; } + + public string Selector { get; set; } + + public Guid DkimId { get; set; } + + public override int GetHashCode() { + throw new NotImplementedException(); + } +} + diff --git a/src/DomainObjects/Documents/Sites/PassworRecoverySettings.cs b/src/DomainObjects/Documents/Sites/PassworRecoverySettings.cs new file mode 100644 index 0000000..793ef88 --- /dev/null +++ b/src/DomainObjects/Documents/Sites/PassworRecoverySettings.cs @@ -0,0 +1,27 @@ +using DomainObjects.Abstractions; + +namespace DomainObjects.Documents.Sites; + +public class PassworRecoverySettings : DomainObjectBase { + + /// + /// DomainKeys Identified Mail (DKIM) is an email authentication method designed to detect forged sender addresses in email (email spoofing),
+ /// a technique often used in phishing and email spam. + ///
+ public DkimSettings DkimSettings { get; set; } + + public Guid TemplateId { get; set; } + + public string Name { get; set; } + + public Contact Email { get; set; } + + public string Subject { get; set; } + + public List Paragraphs { get; set; } + + public override int GetHashCode() { + throw new NotImplementedException(); + } +} + diff --git a/src/DomainObjects/Documents/Sites/Site.cs b/src/DomainObjects/Documents/Sites/Site.cs index 1537f1f..81ff66f 100644 --- a/src/DomainObjects/Documents/Sites/Site.cs +++ b/src/DomainObjects/Documents/Sites/Site.cs @@ -1,11 +1,23 @@ using DomainObjects.Abstractions; namespace DomainObjects.Documents.Sites { + public class Site : DomainObjectDocumentBase { public string Name { get; set; } + public List Hosts { get; set; } + public Address Address { get; set; } + + public string PoweredBy { get; set; } + + public MailboxConnectionSettings SmtpSettings { get; set; } + + public MailboxConnectionSettings ImapSettings { get; set; } + + public PassworRecoverySettings PassworRecoverySettings { get; set; } + public Site(string name, List hosts) { Name = name; Hosts = hosts; diff --git a/src/EmailProvider/EmailMessageBuilder.cs b/src/EmailProvider/EmailMessageBuilder.cs index a8eb2fb..d6ba49e 100644 --- a/src/EmailProvider/EmailMessageBuilder.cs +++ b/src/EmailProvider/EmailMessageBuilder.cs @@ -89,6 +89,23 @@ namespace EmailProvider { }.Sign(_mimeMessage, new HeaderId[] { HeaderId.From, HeaderId.Subject, HeaderId.To }); } + + public void DkimSign(string domain, string selector, byte[] bytes) { + + using var ms = new MemoryStream(bytes); + + new DkimSigner( + ms, + domain, + selector + ) { + HeaderCanonicalizationAlgorithm = DkimCanonicalizationAlgorithm.Simple, + BodyCanonicalizationAlgorithm = DkimCanonicalizationAlgorithm.Simple, + AgentOrUserIdentifier = $"@{domain}", // your domain name + QueryMethod = "dns/txt", + }.Sign(_mimeMessage, new HeaderId[] { HeaderId.From, HeaderId.Subject, HeaderId.To }); + } + public byte [] Build() { using var stream = new MemoryStream(); diff --git a/src/WeatherForecast/Configuration.cs b/src/WeatherForecast/Configuration.cs index c5d34ec..5a2f5e7 100644 --- a/src/WeatherForecast/Configuration.cs +++ b/src/WeatherForecast/Configuration.cs @@ -3,15 +3,36 @@ using DataProviders; namespace WeatherForecast { + /// + /// + /// public interface IJwtConfig { - public string? Secret { get; set; } - public int? Expires { get; set; } + /// + /// + /// + public string Secret { get; set; } + + /// + /// + /// + public int Expires { get; set; } } + /// + /// + /// public class JwtConfig : IJwtConfig { - public string? Secret { get; set; } - public int? Expires { get; set; } + + /// + /// + /// + public string Secret { get; set; } = string.Empty; + + /// + /// + /// + public int Expires { get; set; } } /// diff --git a/src/WeatherForecast/Controllers/AccountController.cs b/src/WeatherForecast/Controllers/AccountController.cs index 20852e3..657c7ee 100644 --- a/src/WeatherForecast/Controllers/AccountController.cs +++ b/src/WeatherForecast/Controllers/AccountController.cs @@ -8,6 +8,8 @@ using WeatherForecast.Policies; using WeatherForecast.Services; using DomainObjects.Documents.Users; using Microsoft.AspNetCore.Identity; +using Org.BouncyCastle.Asn1.Ocsp; +using DomainResults.Common; namespace WeatherForecast.Controllers; @@ -47,13 +49,16 @@ public class AccountController : ControllerBase { return result.ToActionResult(); } + /// + /// + /// + /// + /// [HttpPost("[action]")] public IActionResult Register([FromBody] RegisterRequestModel requestData) { return BadRequest(); } - - #region Password /// /// Passing the Username in the request body is a more secure alternative to passing it as a GET param @@ -63,7 +68,12 @@ public class AccountController : ControllerBase { /// [HttpPut($"{_password}/[action]")] public IActionResult Recovery([FromBody] PasswordRecoveryRequestModel requestData) { - var result = _accountService.PasswordRecovery(requestData); + + var hostName = Request?.Headers["X-Forwarded-Host"].FirstOrDefault(); + if (hostName == null) + return IDomainResult.Failed().ToActionResult(); + + var result = _accountService.PasswordRecovery(hostName, requestData); return result.ToActionResult(); } diff --git a/src/WeatherForecast/Controllers/DkimController.cs b/src/WeatherForecast/Controllers/DkimController.cs new file mode 100644 index 0000000..1bf65f1 --- /dev/null +++ b/src/WeatherForecast/Controllers/DkimController.cs @@ -0,0 +1,55 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +using DomainResults.Common; +using DomainResults.Mvc; + +using DataProviders.Buckets; +using WeatherForecast.Services; + +namespace WeatherForecast.Controllers; + +/// +/// +/// +[AllowAnonymous] +[Route("api/[controller]")] +public class DkimController : ControllerBase { + + + private readonly IAuthorizationService _authorizationService; + private readonly IDkimService _dkimService; + + /// + /// + /// + /// + /// + public DkimController( + IAuthorizationService authorizationService, + IDkimService dkimService + ) { + _authorizationService = authorizationService; + _dkimService = dkimService; + } + + /// + /// Allows to upload private dkim certificate + /// + /// + /// + /// + [HttpPost("{siteId}")] + public IActionResult Post([FromRoute] Guid siteId, IFormFile file) { + + if (!(file.Length > 0)) + return IDomainResult.Failed().ToActionResult(); + + using var ms = new MemoryStream(); + file.CopyTo(ms); + + var result = _dkimService.Post(new BucketFile(siteId, file.FileName, ms.ToArray(), file.ContentType)); + + return result.ToActionResult(); + } +} diff --git a/src/WeatherForecast/Controllers/FilesController.cs b/src/WeatherForecast/Controllers/FilesController.cs index ebf3e17..95a23dd 100644 --- a/src/WeatherForecast/Controllers/FilesController.cs +++ b/src/WeatherForecast/Controllers/FilesController.cs @@ -22,7 +22,8 @@ public class FilesController : ControllerBase { /// /// /// - /// + /// + /// public FilesController( IAuthorizationService authorizationService, IImageBucketDataProvider imageBucketDataProvider diff --git a/src/WeatherForecast/Controllers/InitializationController.cs b/src/WeatherForecast/Controllers/InitializationController.cs index 6b0b13d..b49e246 100644 --- a/src/WeatherForecast/Controllers/InitializationController.cs +++ b/src/WeatherForecast/Controllers/InitializationController.cs @@ -5,18 +5,31 @@ using WeatherForecast.Models.Initialization.Requests; using WeatherForecast.Services; namespace WeatherForecast.Controllers; + +/// +/// +/// [Route("api/[controller]")] [ApiController] public class InitializationController : ControllerBase { private readonly IInitializationService _initializationService; + /// + /// + /// + /// public InitializationController( IInitializationService initializationService ) { _initializationService = initializationService; } + /// + /// + /// + /// + /// [HttpPost] public IActionResult InitializeSystem([FromBody] InitializeSystemRequestModel requestData) { var result = _initializationService.InitializeSystem(requestData); diff --git a/src/WeatherForecast/Controllers/TemplateController.cs b/src/WeatherForecast/Controllers/TemplateController.cs new file mode 100644 index 0000000..b206568 --- /dev/null +++ b/src/WeatherForecast/Controllers/TemplateController.cs @@ -0,0 +1,56 @@ + +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +using DomainResults.Common; +using DomainResults.Mvc; + +using DataProviders.Buckets; +using WeatherForecast.Services; + +namespace WeatherForecast.Controllers; + +/// +/// +/// +[AllowAnonymous] +[Route("api/[controller]")] +public class TemplateController : ControllerBase { + + + private readonly IAuthorizationService _authorizationService; + private readonly ITemplateService _templateService; + + /// + /// + /// + /// + /// + public TemplateController( + IAuthorizationService authorizationService, + ITemplateService templateService + ) { + _authorizationService = authorizationService; + _templateService = templateService; + } + + /// + /// Allows to upload private dkim certificate + /// + /// + /// + /// + [HttpPost("{siteId}")] + public IActionResult Post([FromRoute] Guid siteId, IFormFile file) { + + if (!(file.Length > 0)) + return IDomainResult.Failed().ToActionResult(); + + using var ms = new MemoryStream(); + file.CopyTo(ms); + + var result = _templateService.Post(new BucketFile(siteId, file.FileName, ms.ToArray(), file.ContentType)); + + return result.ToActionResult(); + } +} diff --git a/src/WeatherForecast/Controllers/UtilsController.cs b/src/WeatherForecast/Controllers/UtilsController.cs new file mode 100644 index 0000000..d445f30 --- /dev/null +++ b/src/WeatherForecast/Controllers/UtilsController.cs @@ -0,0 +1,31 @@ + + + +using CryptoProvider; +using DomainResults.Mvc; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using WeatherForecast.Models.Initialization.Requests; +using WeatherForecast.Services; + +namespace WeatherForecast.Controllers; + + +/// +/// +/// +[Route("api/[controller]")] +[ApiController] +public class UtilsController : ControllerBase { + + /// + /// + /// + /// + [HttpGet("[action]")] + public IActionResult Aes() { + return Ok(AesService.GenerateKey()); + } + +} + diff --git a/src/WeatherForecast/Models/Abstractions/PersonModelBase.cs b/src/WeatherForecast/Models/Abstractions/PersonModelBase.cs index 4cf7958..3da109e 100644 --- a/src/WeatherForecast/Models/Abstractions/PersonModelBase.cs +++ b/src/WeatherForecast/Models/Abstractions/PersonModelBase.cs @@ -1,6 +1,5 @@ using DomainObjects.Abstractions; using Core.Abstractions.Models; -using DomainObjects.Abstractions; using WeatherForecast.Models.Abstractions.PostItem.Responses; namespace WeatherForecast.Models.Abstractions { diff --git a/src/WeatherForecast/Models/Account/Requests/AuthenticationRequestModel.cs b/src/WeatherForecast/Models/Account/Requests/AuthenticationRequestModel.cs index 9ea49a1..b018860 100644 --- a/src/WeatherForecast/Models/Account/Requests/AuthenticationRequestModel.cs +++ b/src/WeatherForecast/Models/Account/Requests/AuthenticationRequestModel.cs @@ -12,12 +12,12 @@ namespace WeatherForecast.Models.Account.Requests { /// /// /// - public string? Username { get; set; } + public string Username { get; set; } = string.Empty; /// /// /// - public string? Password { get; set; } + public string Password { get; set; } = string.Empty; /// /// diff --git a/src/WeatherForecast/Models/Account/Requests/CreateAccountRequestModel.cs b/src/WeatherForecast/Models/Account/Requests/CreateAccountRequestModel.cs index e560c27..10dcdc5 100644 --- a/src/WeatherForecast/Models/Account/Requests/CreateAccountRequestModel.cs +++ b/src/WeatherForecast/Models/Account/Requests/CreateAccountRequestModel.cs @@ -1,4 +1,21 @@ -namespace WeatherForecast.Models.Account.Requests { - public class CreateAccountRequestModel { +using Core.Abstractions.Models; +using System.ComponentModel.DataAnnotations; + +namespace WeatherForecast.Models.Account.Requests { + + /// + /// + /// + public class CreateAccountRequestModel : RequestModelBase { + + /// + /// + /// + /// + /// + /// + public override IEnumerable Validate(ValidationContext validationContext) { + throw new NotImplementedException(); + } } } diff --git a/src/WeatherForecast/Models/Account/Requests/PasswordRecoveryRequestModel.cs b/src/WeatherForecast/Models/Account/Requests/PasswordRecoveryRequestModel.cs index ea6da7c..c722ead 100644 --- a/src/WeatherForecast/Models/Account/Requests/PasswordRecoveryRequestModel.cs +++ b/src/WeatherForecast/Models/Account/Requests/PasswordRecoveryRequestModel.cs @@ -3,10 +3,22 @@ using Core.Enumerations; using System.ComponentModel.DataAnnotations; namespace WeatherForecast.Models.Account.Requests { + + /// + /// + /// public class PasswordRecoveryRequestModel : RequestModelBase { + /// + /// + /// public string Username { get; set; } + /// + /// + /// + /// + /// public override IEnumerable Validate(ValidationContext validationContext) { if (string.IsNullOrWhiteSpace(Username)) yield return new ValidationResult($"{nameof(Username)} ${Errors.NullOrEmpty}"); diff --git a/src/WeatherForecast/Models/Account/Requests/PasswordResetRequestModel.cs b/src/WeatherForecast/Models/Account/Requests/PasswordResetRequestModel.cs index b99b4a0..36de533 100644 --- a/src/WeatherForecast/Models/Account/Requests/PasswordResetRequestModel.cs +++ b/src/WeatherForecast/Models/Account/Requests/PasswordResetRequestModel.cs @@ -24,6 +24,11 @@ namespace WeatherForecast.Models.Account.Requests { /// public string RePassword { get; set; } + /// + /// + /// + /// + /// public override IEnumerable Validate(ValidationContext validationContext) { if (string.IsNullOrWhiteSpace(Token)) diff --git a/src/WeatherForecast/Models/Account/Requests/RegisterRequestModel.cs b/src/WeatherForecast/Models/Account/Requests/RegisterRequestModel.cs index dc7ff63..c4ddc92 100644 --- a/src/WeatherForecast/Models/Account/Requests/RegisterRequestModel.cs +++ b/src/WeatherForecast/Models/Account/Requests/RegisterRequestModel.cs @@ -1,4 +1,21 @@ -namespace WeatherForecast.Models.Account.Requests { - public class RegisterRequestModel { +using Core.Abstractions.Models; +using System.ComponentModel.DataAnnotations; + +namespace WeatherForecast.Models.Account.Requests { + + /// + /// + /// + public class RegisterRequestModel : RequestModelBase { + + /// + /// + /// + /// + /// + /// + public override IEnumerable Validate(ValidationContext validationContext) { + throw new NotImplementedException(); } + } } diff --git a/src/WeatherForecast/Models/Content/Responses/Pages/HomePageModel.cs b/src/WeatherForecast/Models/Content/Responses/Pages/HomePageModel.cs index deecd6b..7ece6f4 100644 --- a/src/WeatherForecast/Models/Content/Responses/Pages/HomePageModel.cs +++ b/src/WeatherForecast/Models/Content/Responses/Pages/HomePageModel.cs @@ -1,5 +1,4 @@ using DomainObjects.Pages; -using DomainObjects.Pages; using WeatherForecast.Models.Abstractions; using WeatherForecast.Models.Content.Responses.PageSections; diff --git a/src/WeatherForecast/Models/File/Responses/FileResponseModel.cs b/src/WeatherForecast/Models/File/Responses/FileResponseModel.cs index 7d00a27..ecf013b 100644 --- a/src/WeatherForecast/Models/File/Responses/FileResponseModel.cs +++ b/src/WeatherForecast/Models/File/Responses/FileResponseModel.cs @@ -1,11 +1,26 @@ using Core.Abstractions.Models; namespace WeatherForecast.Models.File.Responses { + + /// + /// + /// public class FileResponseModel : ResponseModelBase { - public string Name { get; set; } - public byte[] Bytes { get; set; } + /// + /// File name + /// + public string Name { get; set; } = string.Empty; - public string ContentType { get; set; } + /// + /// Byte contente, when file is returned as stream + /// + + public byte[] Bytes { get; set; } = Array.Empty(); + + /// + /// MIME content type + /// + public string ContentType { get; set; } = string.Empty; } } diff --git a/src/WeatherForecast/Models/Initialization/Requests/InitializeSystemRequestModel.cs b/src/WeatherForecast/Models/Initialization/Requests/InitializeSystemRequestModel.cs index e9f0971..d61a056 100644 --- a/src/WeatherForecast/Models/Initialization/Requests/InitializeSystemRequestModel.cs +++ b/src/WeatherForecast/Models/Initialization/Requests/InitializeSystemRequestModel.cs @@ -5,24 +5,66 @@ using System.ComponentModel.DataAnnotations; using System.Reflection.Metadata; namespace WeatherForecast.Models.Initialization.Requests { + + /// + /// + /// public class InitializeSystemRequestModel : RequestModelBase { + /// + /// + /// public string SiteName { get; set; } + /// + /// + /// public string Host { get; set; } + /// + /// + /// public string Username { get; set; } + /// + /// + /// public string Email { get; set; } + /// + /// + /// public string Password { get; set; } + /// + /// + /// public string RePassword { get; set; } + /// + /// + /// + public string DkimBase64 { get; set; } + + /// + /// + /// + public string ServiceEmlTemplateBase64 { get; set; } + + /// + /// + /// + /// + /// public InitializeSystemRequestModel ToDomainObject() { throw new NotImplementedException(); } + /// + /// + /// + /// + /// public override IEnumerable Validate(ValidationContext validationContext) { if (string.IsNullOrWhiteSpace(SiteName)) @@ -45,6 +87,12 @@ namespace WeatherForecast.Models.Initialization.Requests { if (string.Compare(Password, RePassword) != 0) yield return new ValidationResult($"{nameof(Password)} and {nameof(RePassword)} ${Errors.NotMatched}"); + + if (string.IsNullOrWhiteSpace(DkimBase64)) + yield return new ValidationResult($"{nameof(DkimBase64)} {Errors.NullOrWhiteSpace}"); + + if (string.IsNullOrWhiteSpace(ServiceEmlTemplateBase64)) + yield return new ValidationResult($"{nameof(ServiceEmlTemplateBase64)} {Errors.NullOrWhiteSpace}"); } } } diff --git a/src/WeatherForecast/Policies/Abstractions/AuthorizationHandlerBase.cs b/src/WeatherForecast/Policies/Abstractions/AuthorizationHandlerBase.cs index bbcde17..90b72e4 100644 --- a/src/WeatherForecast/Policies/Abstractions/AuthorizationHandlerBase.cs +++ b/src/WeatherForecast/Policies/Abstractions/AuthorizationHandlerBase.cs @@ -62,7 +62,7 @@ public static class AuthorisationHandlerHelper { /// /// /// - /// + /// /// /// public static Roles? GetRole(Site site, User user) { @@ -109,6 +109,12 @@ public abstract class AuthorizationHandlerBase : AuthorizationHand protected (Site?, User?) GetUser(AuthorizationHandlerContext context) => AuthorisationHandlerHelper.GetUser(context, _aesKey, _contextAccessor, _siteDataProvider, _userDataProvider); + /// + /// + /// + /// + /// + /// protected Roles? GetRole(Site site, User user) => AuthorisationHandlerHelper.GetRole(site, user); } @@ -152,6 +158,12 @@ public abstract class AuthorizationHandlerBase : Author protected (Site?, User?) GetUser(AuthorizationHandlerContext context) => AuthorisationHandlerHelper.GetUser(context, _aesKey, _contextAccessor, _siteDataProvider, _userDataProvider); + /// + /// + /// + /// + /// + /// protected Roles? GetRole(Site site, User user) => AuthorisationHandlerHelper.GetRole(site, user); } diff --git a/src/WeatherForecast/Policies/FileAuthorisationHandler.cs b/src/WeatherForecast/Policies/FileAuthorisationHandler.cs index 3f1da3c..647afd9 100644 --- a/src/WeatherForecast/Policies/FileAuthorisationHandler.cs +++ b/src/WeatherForecast/Policies/FileAuthorisationHandler.cs @@ -20,7 +20,9 @@ public class FileAuthorisationHandler : AuthorizationHandlerBase /// /// + /// /// + /// /// /// public FileAuthorisationHandler( diff --git a/src/WeatherForecast/Policies/PasswordChangeAuthorizationHandler.cs b/src/WeatherForecast/Policies/PasswordChangeAuthorizationHandler.cs index 8cb830a..020253b 100644 --- a/src/WeatherForecast/Policies/PasswordChangeAuthorizationHandler.cs +++ b/src/WeatherForecast/Policies/PasswordChangeAuthorizationHandler.cs @@ -57,6 +57,10 @@ public class PasswordChangeAuthorizationHandler : AuthorizationHandlerBase public class PasswordChangeRequirement : AuthorizationRequirementBase { + + /// + /// + /// public string OldPassword { get; init; } } diff --git a/src/WeatherForecast/Policies/ShopCartAuthorizationHandler.cs b/src/WeatherForecast/Policies/ShopCartAuthorizationHandler.cs index d6259fd..9fc5071 100644 --- a/src/WeatherForecast/Policies/ShopCartAuthorizationHandler.cs +++ b/src/WeatherForecast/Policies/ShopCartAuthorizationHandler.cs @@ -15,9 +15,10 @@ namespace WeatherForecast.Policies { /// /// /// + /// /// + /// /// - /// public ShopCartAuthorizationHandler( IOptions configuration, IHttpContextAccessor contextAccessor, diff --git a/src/WeatherForecast/Services/AccountService.cs b/src/WeatherForecast/Services/AccountService.cs index a54dc35..b1e55f0 100644 --- a/src/WeatherForecast/Services/AccountService.cs +++ b/src/WeatherForecast/Services/AccountService.cs @@ -7,10 +7,8 @@ using WeatherForecast.Models.Account.Requests; using DomainObjects.Documents.Users; using EmailProvider; using DomainObjects.Enumerations; -using Org.BouncyCastle.Crypto; -using Microsoft.AspNetCore.Identity; -using Extensions; -using DomainObjects.Documents.Sites; +using DataProviders.Buckets; +using System.Text; namespace WeatherForecast.Services { @@ -28,9 +26,10 @@ namespace WeatherForecast.Services { /// /// /// + /// /// /// - IDomainResult PasswordRecovery(PasswordRecoveryRequestModel requestData); + IDomainResult PasswordRecovery(string hostName, PasswordRecoveryRequestModel requestData); /// @@ -56,10 +55,13 @@ namespace WeatherForecast.Services { /// public class AccountService : ServiceBase, IAccountService { - private readonly IAesKey? _aesKey; - private readonly IJwtConfig? _jwtConfig; + private readonly IAesKey _aesKey; + private readonly IJwtConfig _jwtConfig; private readonly IUserDataProvider _userDataProvider; + private readonly ISiteDataProvider _siteDataProvider; + private readonly IDkimBucketDataProvider _dkimBucketDataProvider; + private readonly ITemplateBucketDataProvider _templateBucketDataProvider; /// /// @@ -67,14 +69,32 @@ namespace WeatherForecast.Services { /// /// /// + /// + /// + /// public AccountService( ILogger logger, IOptions options, - IUserDataProvider userDataProvider + IUserDataProvider userDataProvider, + ISiteDataProvider siteDataProvider, + IDkimBucketDataProvider dkimBucketDataProvider, + ITemplateBucketDataProvider templateBucketDataProvider ) : base(logger) { + + if (options.Value.JwtTokenEncryption == null) + throw new ArgumentNullException(); + _aesKey = options.Value.JwtTokenEncryption; + + if(options.Value.JwtConfig == null) + throw new ArgumentNullException(); + _jwtConfig = options.Value.JwtConfig; + _userDataProvider = userDataProvider; + _siteDataProvider = siteDataProvider; + _dkimBucketDataProvider = dkimBucketDataProvider; + _templateBucketDataProvider = templateBucketDataProvider; } /// @@ -99,7 +119,7 @@ namespace WeatherForecast.Services { if (!user.Authentication.ValidatePassword(requestData.Password)) return IDomainResult.Unauthorized(); - var token = user.AddToken(_aesKey.IV, _aesKey.Key, _jwtConfig.Secret, _jwtConfig.Expires.Value); + var token = user.AddToken(_aesKey.IV, _aesKey.Key, _jwtConfig.Secret, _jwtConfig.Expires); var (_, usdateUserResult) = _userDataProvider.Update(user); if (!usdateUserResult.IsSuccess) @@ -108,24 +128,14 @@ namespace WeatherForecast.Services { return IDomainResult.Success(token); } - - - - - - - - - - - /// /// Passing the Username in the request body is a more secure alternative to passing it as a GET param /// + /// /// /// /// - public IDomainResult PasswordRecovery(PasswordRecoveryRequestModel requestData) { + public IDomainResult PasswordRecovery(string hostName, PasswordRecoveryRequestModel requestData) { var (user, getUserResult) = _userDataProvider.GetByUsername(requestData.Username); if (!getUserResult.IsSuccess || user == null) return getUserResult; @@ -135,27 +145,38 @@ namespace WeatherForecast.Services { if (email == null) return IDomainResult.Failed(); - var template = File.ReadAllText(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "templates", "email-inlined.html")) - .Replace("{{content}}", $"Your recovery token: {user.Authentication.PasswordRecovery()}"); + var (site, getSiteResult) = _siteDataProvider.GetByHostName(hostName); + if (!getSiteResult.IsSuccess || site == null) + return IDomainResult.Failed(); + + var dkimSettings = site.PassworRecoverySettings.DkimSettings; + + var (dkimPrivateKey, downloadDkimPrivateKeyResult) = _dkimBucketDataProvider.Download(site.Id, dkimSettings.DkimId); + if(!downloadDkimPrivateKeyResult.IsSuccess || dkimPrivateKey == null) + return IDomainResult.Failed(); + + var (template, getTemplateResult) = _templateBucketDataProvider.Download(site.Id, site.PassworRecoverySettings.TemplateId); + if (!getTemplateResult.IsSuccess || template == null) + return IDomainResult.Failed(); + + var htmlBody = Encoding.UTF8.GetString(template.Bytes); + + htmlBody = htmlBody.Replace("{{token}}", user.Authentication.PasswordRecovery()); _userDataProvider.Update(user); var emailBuilder = new EmailMessageBuilder(); - emailBuilder.AddFrom("commercial", "commercial@maks-it.com"); + emailBuilder.AddFrom(site.PassworRecoverySettings.Name, site.PassworRecoverySettings.Email.Value); emailBuilder.AddTo(user.Username, email.Value); - emailBuilder.AddSubject("Password recovery"); - - - emailBuilder.AddHtmlBody(template); - - // TODO: save inside mongo file bucket - emailBuilder.DkimSign("maks-it.com", "default", Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "dkim", "maks-it.com")); + emailBuilder.AddSubject(site.PassworRecoverySettings.Subject); + emailBuilder.AddHtmlBody(htmlBody); + emailBuilder.DkimSign(dkimSettings.HostName, dkimSettings.Selector, dkimPrivateKey.Bytes); using var smtpService = new SMTPService(); // TODO: change email password and manage configs inside database - smtpService.Connect("smtp.ionos.it", 465, true); - smtpService.Authenticate("commercial@maks-it.com", "E23{R# - /// Only if user is authenticated, do not expose this methods as anonymous + /// /// /// + /// /// /// public (Guid?, IDomainResult) PasswordChange(User user, string oldPassword, string newPassword) { diff --git a/src/WeatherForecast/Services/DkimService.cs b/src/WeatherForecast/Services/DkimService.cs new file mode 100644 index 0000000..d74a1d8 --- /dev/null +++ b/src/WeatherForecast/Services/DkimService.cs @@ -0,0 +1,54 @@ +using Core.Abstractions; +using DataProviders.Buckets; +using DomainResults.Common; +using Microsoft.AspNetCore.Authorization; + +namespace WeatherForecast.Services { + + /// + /// + /// + public interface IDkimService { + + /// + /// + /// + /// + /// + (Guid?, IDomainResult) Post(BucketFile file); + } + + /// + /// + /// + public class DkimService : ServiceBase, IDkimService { + + private readonly IDkimBucketDataProvider _dkimBucketDataProvider; + + /// + /// + /// + /// + /// + public DkimService( + ILogger logger, + IDkimBucketDataProvider dkimBucketDataProvider + ) : base (logger) { + _dkimBucketDataProvider = dkimBucketDataProvider; + } + + /// + /// + /// + /// + /// + public (Guid?, IDomainResult) Post(BucketFile file) { + + var (fileId, uploadFileResult) = _dkimBucketDataProvider.Upload(file); + if (!uploadFileResult.IsSuccess || fileId == null) + return IDomainResult.Failed(); + + return IDomainResult.Success(fileId); + } + } +} diff --git a/src/WeatherForecast/Services/InitializationService.cs b/src/WeatherForecast/Services/InitializationService.cs index 9279582..b03d552 100644 --- a/src/WeatherForecast/Services/InitializationService.cs +++ b/src/WeatherForecast/Services/InitializationService.cs @@ -9,10 +9,20 @@ using DomainResults.Common; using Extensions; using Microsoft.Extensions.Options; using WeatherForecast.Models.Initialization.Requests; +using DataProviders.Buckets; namespace WeatherForecast.Services; +/// +/// +/// public interface IInitializationService { + + /// + /// + /// + /// + /// IDomainResult InitializeSystem(InitializeSystemRequestModel requestData); } @@ -22,11 +32,13 @@ public interface IInitializationService { /// public class InitializationService : ServiceBase, IInitializationService { - private readonly IAesKey? _aesKey; - private readonly IJwtConfig? _jwtConfig; + private readonly IAesKey _aesKey; + private readonly IJwtConfig _jwtConfig; private readonly ISiteDataProvider _siteDataProvider; private readonly IUserDataProvider _userDataProvider; + private readonly IDkimBucketDataProvider _dkimBucketDataProvider; + private readonly ITemplateBucketDataProvider _serviceEmlTemplateBucketDataProvider; /// /// @@ -35,16 +47,22 @@ public class InitializationService : ServiceBase, IInitia /// /// /// + /// + /// public InitializationService( ILogger logger, IOptions options, ISiteDataProvider siteDataProvider, - IUserDataProvider userDataProvider + IUserDataProvider userDataProvider, + IDkimBucketDataProvider dkimBucketDataProvider, + ITemplateBucketDataProvider serviceEmlTemplateBucketDataProvider ) : base(logger) { _aesKey = options.Value.JwtTokenEncryption; _jwtConfig = options.Value.JwtConfig; _siteDataProvider = siteDataProvider; _userDataProvider = userDataProvider; + _dkimBucketDataProvider = dkimBucketDataProvider; + _serviceEmlTemplateBucketDataProvider = serviceEmlTemplateBucketDataProvider; } @@ -58,8 +76,37 @@ public class InitializationService : ServiceBase, IInitia var userId = "fdc5aa50-ee68-4bae-a8e6-b8ae2c258f60".ToGuid(); var siteId = "404c8232-9048-4519-bfba-6e78dc7005ca".ToGuid(); + #region Upload dkim for service email + var (_, dkimUpdloadResult) = _dkimBucketDataProvider.Upload(new BucketFile( + siteId, + requestData.Host, + Convert.FromBase64String(requestData.DkimBase64), + "application/x-pem-file" + )); + + if (!dkimUpdloadResult.IsSuccess) + return IDomainResult.Failed(); + #endregion + + #region Upload basic service email templates + var (templateId, _) = _serviceEmlTemplateBucketDataProvider.Upload(new BucketFile( + siteId, + "template.html", + Convert.FromBase64String(requestData.ServiceEmlTemplateBase64), + "text/html" + )); + + if (!dkimUpdloadResult.IsSuccess || templateId == null) + return IDomainResult.Failed(); + #endregion + var (_, siteInsetResult) = _siteDataProvider.Insert(new Site(requestData.SiteName, new List { requestData.Host }) { - Id = siteId + Id = siteId, + PassworRecoverySettings = new PassworRecoverySettings { + Name = "Password recovery support", + Email = new Contact(ContactTypes.Email, $"support@{requestData.Host}"), + TemplateId = templateId.Value + } }); var user = new User(new List { new SiteRole(siteId, Roles.Admin) }, requestData.Username, requestData.Email, requestData.Password) { diff --git a/src/WeatherForecast/Services/ShopCartItemService.cs b/src/WeatherForecast/Services/ShopCartItemService.cs index 0d37f1e..617613b 100644 --- a/src/WeatherForecast/Services/ShopCartItemService.cs +++ b/src/WeatherForecast/Services/ShopCartItemService.cs @@ -29,9 +29,7 @@ public interface IShopCartItemService { /// /// /// - /// - /// - /// + /// /// /// (ShopCartItemResponseModel?, IDomainResult) Get(ShopCartDocument cartItem, string? locale); @@ -39,9 +37,7 @@ public interface IShopCartItemService { /// /// /// - /// - /// - /// + /// /// /// (Guid?, IDomainResult) Update(ShopCartDocument cartItem, ShopCartItemRequestModel requestData); diff --git a/src/WeatherForecast/Services/ShopItemService.cs b/src/WeatherForecast/Services/ShopItemService.cs index 35de991..ff47e73 100644 --- a/src/WeatherForecast/Services/ShopItemService.cs +++ b/src/WeatherForecast/Services/ShopItemService.cs @@ -18,17 +18,14 @@ namespace WeatherForecast.Services { /// /// /// - /// - /// - /// + /// /// (Guid?, IDomainResult) Post(ShopDocument shopItem); /// /// /// - /// - /// + /// /// (ShopItemResponseModel?, IDomainResult) Get(ShopDocument shopItem); @@ -101,8 +98,7 @@ namespace WeatherForecast.Services { /// /// /// - /// - /// + /// /// public (ShopItemResponseModel?, IDomainResult) Get(ShopDocument shopItem) { var (categories, getCategoryResult) = _categoryDataProvider.GetMany(shopItem.SiteId, shopItem.Categories); diff --git a/src/WeatherForecast/Services/TemplateService.cs b/src/WeatherForecast/Services/TemplateService.cs new file mode 100644 index 0000000..34622b2 --- /dev/null +++ b/src/WeatherForecast/Services/TemplateService.cs @@ -0,0 +1,54 @@ +using Core.Abstractions; +using DataProviders.Buckets; +using DomainResults.Common; + +namespace WeatherForecast.Services { + + /// + /// + /// + public interface ITemplateService { + + /// + /// + /// + /// + /// + (Guid?, IDomainResult) Post(BucketFile file); + } + + /// + /// + /// + public class TemplateService : ServiceBase, ITemplateService { + + + private readonly ITemplateBucketDataProvider _templateBucketDataProvider; + + /// + /// + /// + /// + /// + public TemplateService( + ILogger logger, + ITemplateBucketDataProvider templateBucketDataProvider + ) : base(logger) { + _templateBucketDataProvider = templateBucketDataProvider; + } + + /// + /// + /// + /// + /// + public (Guid?, IDomainResult) Post(BucketFile file) { + + var (fileId, uploadFileResult) = _templateBucketDataProvider.Upload(file); + if (!uploadFileResult.IsSuccess || fileId == null) + return IDomainResult.Failed(); + + return IDomainResult.Success(fileId); + } + } +} diff --git a/src/WeatherForecast/Startup.cs b/src/WeatherForecast/Startup.cs index 9dc21cf..45669f6 100644 --- a/src/WeatherForecast/Startup.cs +++ b/src/WeatherForecast/Startup.cs @@ -13,7 +13,6 @@ using ImageProvider.Extensions; using Core.Middlewares; using Microsoft.AspNetCore.Authorization; using WeatherForecast.Policies; -using Microsoft.AspNetCore.Identity; namespace WeatherForecast { @@ -58,7 +57,7 @@ namespace WeatherForecast { options.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull); #region configure jwt authentication - if (appSettings.JwtConfig?.Secret != null) { + if (appSettings?.JwtConfig?.Secret != null) { services.AddAuthentication(options => { options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; @@ -93,12 +92,14 @@ namespace WeatherForecast { services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); #endregion services.RegisterDataproviders(appSettings); diff --git a/src/WeatherForecast/WeatherForecast.csproj b/src/WeatherForecast/WeatherForecast.csproj index ec66207..c63337b 100644 --- a/src/WeatherForecast/WeatherForecast.csproj +++ b/src/WeatherForecast/WeatherForecast.csproj @@ -12,6 +12,13 @@ Linux + + + + + + + @@ -35,20 +42,10 @@ - - - - PreserveNewest - - PreserveNewest - - - PreserveNewest - diff --git a/src/docker-compose/mongo/data/db/WiredTiger.turtle b/src/docker-compose/mongo/data/db/WiredTiger.turtle index eaa1477..af66381 100644 --- a/src/docker-compose/mongo/data/db/WiredTiger.turtle +++ b/src/docker-compose/mongo/data/db/WiredTiger.turtle @@ -3,4 +3,4 @@ WiredTiger 10.0.2: (December 21, 2021) WiredTiger version major=10,minor=0,patch=2 file:WiredTiger.wt -access_pattern_hint=none,allocation_size=4KB,app_metadata=,assert=(commit_timestamp=none,durable_timestamp=none,read_timestamp=none,write_timestamp=off),block_allocation=best,block_compressor=,cache_resident=false,checksum=on,collator=,columns=,dictionary=0,encryption=(keyid=,name=),format=btree,huffman_key=,huffman_value=,id=0,ignore_in_memory_cache_size=false,internal_item_max=0,internal_key_max=0,internal_key_truncate=true,internal_page_max=4KB,key_format=S,key_gap=10,leaf_item_max=0,leaf_key_max=0,leaf_page_max=32KB,leaf_value_max=0,log=(enabled=true),memory_page_image_max=0,memory_page_max=5MB,os_cache_dirty_max=0,os_cache_max=0,prefix_compression=false,prefix_compression_min=4,readonly=false,split_deepen_min_child=0,split_deepen_per_child=0,split_pct=90,tiered_object=false,tiered_storage=(auth_token=,bucket=,bucket_prefix=,cache_directory=,local_retention=300,name=,object_target_size=0),value_format=S,verbose=[],version=(major=1,minor=1),write_timestamp_usage=none,checkpoint=(WiredTigerCheckpoint.87032=(addr="018781e4072edf558b81e49885698c8c81e48750482f808080e301bfc0e2dfc0",order=87032,time=1671750241,size=69632,newest_start_durable_ts=0,oldest_start_ts=0,newest_txn=442,newest_stop_durable_ts=0,newest_stop_ts=-1,newest_stop_txn=-11,prepare=0,write_gen=261469,run_write_gen=260980)),checkpoint_backup_info=,checkpoint_lsn=(33,135936) +access_pattern_hint=none,allocation_size=4KB,app_metadata=,assert=(commit_timestamp=none,durable_timestamp=none,read_timestamp=none,write_timestamp=off),block_allocation=best,block_compressor=,cache_resident=false,checksum=on,collator=,columns=,dictionary=0,encryption=(keyid=,name=),format=btree,huffman_key=,huffman_value=,id=0,ignore_in_memory_cache_size=false,internal_item_max=0,internal_key_max=0,internal_key_truncate=true,internal_page_max=4KB,key_format=S,key_gap=10,leaf_item_max=0,leaf_key_max=0,leaf_page_max=32KB,leaf_value_max=0,log=(enabled=true),memory_page_image_max=0,memory_page_max=5MB,os_cache_dirty_max=0,os_cache_max=0,prefix_compression=false,prefix_compression_min=4,readonly=false,split_deepen_min_child=0,split_deepen_per_child=0,split_pct=90,tiered_object=false,tiered_storage=(auth_token=,bucket=,bucket_prefix=,cache_directory=,local_retention=300,name=,object_target_size=0),value_format=S,verbose=[],version=(major=1,minor=1),write_timestamp_usage=none,checkpoint=(WiredTigerCheckpoint.98221=(addr="018081e4af72785d8181e4aac961f88281e479ff3f5b808080e301bfc0e2dfc0",order=98221,time=1674344889,size=69632,newest_start_durable_ts=0,oldest_start_ts=0,newest_txn=202,newest_stop_durable_ts=0,newest_stop_ts=-1,newest_stop_txn=-11,prepare=0,write_gen=295056,run_write_gen=294763)),checkpoint_backup_info=,checkpoint_lsn=(38,77312) diff --git a/src/docker-compose/mongo/data/db/WiredTiger.wt b/src/docker-compose/mongo/data/db/WiredTiger.wt index 2da9323..36bcd8c 100644 Binary files a/src/docker-compose/mongo/data/db/WiredTiger.wt and b/src/docker-compose/mongo/data/db/WiredTiger.wt differ diff --git a/src/docker-compose/mongo/data/db/collection-2--4715807334585891142.wt b/src/docker-compose/mongo/data/db/collection-2--4715807334585891142.wt index 908b9d2..c5a7ebb 100644 Binary files a/src/docker-compose/mongo/data/db/collection-2--4715807334585891142.wt and b/src/docker-compose/mongo/data/db/collection-2--4715807334585891142.wt differ diff --git a/src/docker-compose/mongo/data/db/collection-4--4715807334585891142.wt b/src/docker-compose/mongo/data/db/collection-4--4715807334585891142.wt index 313428e..e298ba4 100644 Binary files a/src/docker-compose/mongo/data/db/collection-4--4715807334585891142.wt and b/src/docker-compose/mongo/data/db/collection-4--4715807334585891142.wt differ diff --git a/src/docker-compose/mongo/data/db/diagnostic.data/metrics.2022-10-22T17-46-56Z-00000 b/src/docker-compose/mongo/data/db/diagnostic.data/metrics.2022-10-22T17-46-56Z-00000 deleted file mode 100644 index 576b86a..0000000 Binary files a/src/docker-compose/mongo/data/db/diagnostic.data/metrics.2022-10-22T17-46-56Z-00000 and /dev/null differ diff --git a/src/docker-compose/mongo/data/db/diagnostic.data/metrics.2022-10-26T10-04-53Z-00000 b/src/docker-compose/mongo/data/db/diagnostic.data/metrics.2022-10-26T10-04-53Z-00000 deleted file mode 100644 index e16776a..0000000 Binary files a/src/docker-compose/mongo/data/db/diagnostic.data/metrics.2022-10-26T10-04-53Z-00000 and /dev/null differ diff --git a/src/docker-compose/mongo/data/db/diagnostic.data/metrics.2022-10-26T10-06-47Z-00000 b/src/docker-compose/mongo/data/db/diagnostic.data/metrics.2022-10-26T10-06-47Z-00000 deleted file mode 100644 index 9b411bd..0000000 Binary files a/src/docker-compose/mongo/data/db/diagnostic.data/metrics.2022-10-26T10-06-47Z-00000 and /dev/null differ diff --git a/src/docker-compose/mongo/data/db/diagnostic.data/metrics.2022-10-28T10-58-26Z-00000 b/src/docker-compose/mongo/data/db/diagnostic.data/metrics.2022-10-28T10-58-26Z-00000 deleted file mode 100644 index dd09983..0000000 Binary files a/src/docker-compose/mongo/data/db/diagnostic.data/metrics.2022-10-28T10-58-26Z-00000 and /dev/null differ diff --git a/src/docker-compose/mongo/data/db/diagnostic.data/metrics.2022-10-28T14-12-29Z-00000 b/src/docker-compose/mongo/data/db/diagnostic.data/metrics.2022-10-28T14-12-29Z-00000 deleted file mode 100644 index 941e642..0000000 Binary files a/src/docker-compose/mongo/data/db/diagnostic.data/metrics.2022-10-28T14-12-29Z-00000 and /dev/null differ diff --git a/src/docker-compose/mongo/data/db/diagnostic.data/metrics.2022-10-29T07-25-47Z-00000 b/src/docker-compose/mongo/data/db/diagnostic.data/metrics.2022-10-29T07-25-47Z-00000 deleted file mode 100644 index fde5f2f..0000000 Binary files a/src/docker-compose/mongo/data/db/diagnostic.data/metrics.2022-10-29T07-25-47Z-00000 and /dev/null differ diff --git a/src/docker-compose/mongo/data/db/diagnostic.data/metrics.2022-10-31T07-55-57Z-00000 b/src/docker-compose/mongo/data/db/diagnostic.data/metrics.2022-10-31T07-55-57Z-00000 deleted file mode 100644 index 273b13f..0000000 Binary files a/src/docker-compose/mongo/data/db/diagnostic.data/metrics.2022-10-31T07-55-57Z-00000 and /dev/null differ diff --git a/src/docker-compose/mongo/data/db/diagnostic.data/metrics.2022-11-03T11-14-27Z-00000 b/src/docker-compose/mongo/data/db/diagnostic.data/metrics.2022-11-03T11-14-27Z-00000 deleted file mode 100644 index e864614..0000000 Binary files a/src/docker-compose/mongo/data/db/diagnostic.data/metrics.2022-11-03T11-14-27Z-00000 and /dev/null differ diff --git a/src/docker-compose/mongo/data/db/diagnostic.data/metrics.2022-11-09T13-34-12Z-00000 b/src/docker-compose/mongo/data/db/diagnostic.data/metrics.2022-11-09T13-34-12Z-00000 deleted file mode 100644 index f712f40..0000000 Binary files a/src/docker-compose/mongo/data/db/diagnostic.data/metrics.2022-11-09T13-34-12Z-00000 and /dev/null differ diff --git a/src/docker-compose/mongo/data/db/diagnostic.data/metrics.2022-12-22T20-21-56Z-00000 b/src/docker-compose/mongo/data/db/diagnostic.data/metrics.2022-12-22T20-21-56Z-00000 index 106f6d2..2628808 100644 Binary files a/src/docker-compose/mongo/data/db/diagnostic.data/metrics.2022-12-22T20-21-56Z-00000 and b/src/docker-compose/mongo/data/db/diagnostic.data/metrics.2022-12-22T20-21-56Z-00000 differ diff --git a/src/docker-compose/mongo/data/db/diagnostic.data/metrics.2022-10-24T19-44-08Z-00000 b/src/docker-compose/mongo/data/db/diagnostic.data/metrics.2022-12-26T00-17-36Z-00000 similarity index 54% rename from src/docker-compose/mongo/data/db/diagnostic.data/metrics.2022-10-24T19-44-08Z-00000 rename to src/docker-compose/mongo/data/db/diagnostic.data/metrics.2022-12-26T00-17-36Z-00000 index 50ba368..8f811b0 100644 Binary files a/src/docker-compose/mongo/data/db/diagnostic.data/metrics.2022-10-24T19-44-08Z-00000 and b/src/docker-compose/mongo/data/db/diagnostic.data/metrics.2022-12-26T00-17-36Z-00000 differ diff --git a/src/docker-compose/mongo/data/db/diagnostic.data/metrics.2022-10-26T21-56-17Z-00000 b/src/docker-compose/mongo/data/db/diagnostic.data/metrics.2022-12-27T16-52-39Z-00000 similarity index 52% rename from src/docker-compose/mongo/data/db/diagnostic.data/metrics.2022-10-26T21-56-17Z-00000 rename to src/docker-compose/mongo/data/db/diagnostic.data/metrics.2022-12-27T16-52-39Z-00000 index 01d92c3..c3fa752 100644 Binary files a/src/docker-compose/mongo/data/db/diagnostic.data/metrics.2022-10-26T21-56-17Z-00000 and b/src/docker-compose/mongo/data/db/diagnostic.data/metrics.2022-12-27T16-52-39Z-00000 differ diff --git a/src/docker-compose/mongo/data/db/diagnostic.data/metrics.2022-12-29T09-32-39Z-00000 b/src/docker-compose/mongo/data/db/diagnostic.data/metrics.2022-12-29T09-32-39Z-00000 new file mode 100644 index 0000000..3fd64cc Binary files /dev/null and b/src/docker-compose/mongo/data/db/diagnostic.data/metrics.2022-12-29T09-32-39Z-00000 differ diff --git a/src/docker-compose/mongo/data/db/diagnostic.data/metrics.2022-12-31T02-32-39Z-00000 b/src/docker-compose/mongo/data/db/diagnostic.data/metrics.2022-12-31T02-32-39Z-00000 new file mode 100644 index 0000000..b016a9b Binary files /dev/null and b/src/docker-compose/mongo/data/db/diagnostic.data/metrics.2022-12-31T02-32-39Z-00000 differ diff --git a/src/docker-compose/mongo/data/db/diagnostic.data/metrics.2023-01-06T17-51-07Z-00000 b/src/docker-compose/mongo/data/db/diagnostic.data/metrics.2023-01-06T17-51-07Z-00000 new file mode 100644 index 0000000..1caba0c Binary files /dev/null and b/src/docker-compose/mongo/data/db/diagnostic.data/metrics.2023-01-06T17-51-07Z-00000 differ diff --git a/src/docker-compose/mongo/data/db/diagnostic.data/metrics.2023-01-06T19-11-56Z-00000 b/src/docker-compose/mongo/data/db/diagnostic.data/metrics.2023-01-06T19-11-56Z-00000 new file mode 100644 index 0000000..63169ae Binary files /dev/null and b/src/docker-compose/mongo/data/db/diagnostic.data/metrics.2023-01-06T19-11-56Z-00000 differ diff --git a/src/docker-compose/mongo/data/db/diagnostic.data/metrics.2023-01-08T12-21-56Z-00000 b/src/docker-compose/mongo/data/db/diagnostic.data/metrics.2023-01-08T12-21-56Z-00000 new file mode 100644 index 0000000..ed702e4 Binary files /dev/null and b/src/docker-compose/mongo/data/db/diagnostic.data/metrics.2023-01-08T12-21-56Z-00000 differ diff --git a/src/docker-compose/mongo/data/db/diagnostic.data/metrics.2023-01-20T18-12-38Z-00000 b/src/docker-compose/mongo/data/db/diagnostic.data/metrics.2023-01-20T18-12-38Z-00000 new file mode 100644 index 0000000..994592e Binary files /dev/null and b/src/docker-compose/mongo/data/db/diagnostic.data/metrics.2023-01-20T18-12-38Z-00000 differ diff --git a/src/docker-compose/mongo/data/db/diagnostic.data/metrics.2023-01-21T22-11-06Z-00000 b/src/docker-compose/mongo/data/db/diagnostic.data/metrics.2023-01-21T22-11-06Z-00000 new file mode 100644 index 0000000..1816c48 Binary files /dev/null and b/src/docker-compose/mongo/data/db/diagnostic.data/metrics.2023-01-21T22-11-06Z-00000 differ diff --git a/src/docker-compose/mongo/data/db/diagnostic.data/metrics.interim b/src/docker-compose/mongo/data/db/diagnostic.data/metrics.interim index f507cdd..353a9c3 100644 Binary files a/src/docker-compose/mongo/data/db/diagnostic.data/metrics.interim and b/src/docker-compose/mongo/data/db/diagnostic.data/metrics.interim differ diff --git a/src/docker-compose/mongo/data/db/index-3--4715807334585891142.wt b/src/docker-compose/mongo/data/db/index-3--4715807334585891142.wt index 35d2be5..8a5572c 100644 Binary files a/src/docker-compose/mongo/data/db/index-3--4715807334585891142.wt and b/src/docker-compose/mongo/data/db/index-3--4715807334585891142.wt differ diff --git a/src/docker-compose/mongo/data/db/index-5--4715807334585891142.wt b/src/docker-compose/mongo/data/db/index-5--4715807334585891142.wt index 5a76050..97cc3ae 100644 Binary files a/src/docker-compose/mongo/data/db/index-5--4715807334585891142.wt and b/src/docker-compose/mongo/data/db/index-5--4715807334585891142.wt differ diff --git a/src/docker-compose/mongo/data/db/index-6--4715807334585891142.wt b/src/docker-compose/mongo/data/db/index-6--4715807334585891142.wt index 53bc6ac..81befa6 100644 Binary files a/src/docker-compose/mongo/data/db/index-6--4715807334585891142.wt and b/src/docker-compose/mongo/data/db/index-6--4715807334585891142.wt differ diff --git a/src/docker-compose/mongo/data/db/journal/WiredTigerLog.0000000033 b/src/docker-compose/mongo/data/db/journal/WiredTigerLog.0000000038 similarity index 99% rename from src/docker-compose/mongo/data/db/journal/WiredTigerLog.0000000033 rename to src/docker-compose/mongo/data/db/journal/WiredTigerLog.0000000038 index ac71d12..24a35a6 100644 Binary files a/src/docker-compose/mongo/data/db/journal/WiredTigerLog.0000000033 and b/src/docker-compose/mongo/data/db/journal/WiredTigerLog.0000000038 differ diff --git a/src/docker-compose/mongo/data/db/sizeStorer.wt b/src/docker-compose/mongo/data/db/sizeStorer.wt index 9202dae..40b0b6f 100644 Binary files a/src/docker-compose/mongo/data/db/sizeStorer.wt and b/src/docker-compose/mongo/data/db/sizeStorer.wt differ