diff --git a/postman/reactredux.postman_collection.json b/postman/reactredux.postman_collection.json index e5f987b..ed15190 100644 --- a/postman/reactredux.postman_collection.json +++ b/postman/reactredux.postman_collection.json @@ -1183,6 +1183,138 @@ "response": [] } ] + }, + { + "name": "File", + "item": [ + { + "name": "01-Get", + "request": { + "method": "GET", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "type": "default" + }, + { + "key": "Accept", + "value": "application/json", + "type": "default" + } + ], + "url": { + "raw": "https://localhost:7151/api/File/404c8232-9048-4519-bfba-6e78dc7005ca/fdc5aa50-ee68-4bae-a8e6-b8ae2c258f60/3930ff55-67e1-4763-be59-37407f91e0a9", + "protocol": "https", + "host": [ + "localhost" + ], + "port": "7151", + "path": [ + "api", + "File", + "404c8232-9048-4519-bfba-6e78dc7005ca", + "fdc5aa50-ee68-4bae-a8e6-b8ae2c258f60", + "3930ff55-67e1-4763-be59-37407f91e0a9" + ] + } + }, + "response": [] + } + ] + }, + { + "name": "Files", + "item": [ + { + "name": "01-Post", + "request": { + "method": "POST", + "header": [ + { + "warning": "This is a duplicate header and will be overridden by the Content-Type header generated by Postman.", + "key": "Content-Type", + "value": "application/json", + "type": "default" + }, + { + "key": "Accept", + "value": "application/json", + "type": "default" + } + ], + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "file", + "type": "file", + "src": "/C:/Users/maksym/Pictures/IMG_0146.JPG" + } + ] + }, + "url": { + "raw": "https://localhost:7151/api/Files/404c8232-9048-4519-bfba-6e78dc7005ca/fdc5aa50-ee68-4bae-a8e6-b8ae2c258f60", + "protocol": "https", + "host": [ + "localhost" + ], + "port": "7151", + "path": [ + "api", + "Files", + "404c8232-9048-4519-bfba-6e78dc7005ca", + "fdc5aa50-ee68-4bae-a8e6-b8ae2c258f60" + ] + } + }, + "response": [] + }, + { + "name": "02-Delete", + "request": { + "method": "DELETE", + "header": [ + { + "warning": "This is a duplicate header and will be overridden by the Content-Type header generated by Postman.", + "key": "Content-Type", + "value": "application/json", + "type": "default" + }, + { + "key": "Accept", + "value": "application/json", + "type": "default" + } + ], + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "file", + "type": "file", + "src": "/C:/Users/maksym/Pictures/IMG_0146.JPG" + } + ] + }, + "url": { + "raw": "https://localhost:7151/api/Files/404c8232-9048-4519-bfba-6e78dc7005ca/fdc5aa50-ee68-4bae-a8e6-b8ae2c258f60", + "protocol": "https", + "host": [ + "localhost" + ], + "port": "7151", + "path": [ + "api", + "Files", + "404c8232-9048-4519-bfba-6e78dc7005ca", + "fdc5aa50-ee68-4bae-a8e6-b8ae2c258f60" + ] + } + }, + "response": [] + } + ] } ] } \ No newline at end of file diff --git a/webapi/Core/DomainObjects/Documents/Category.cs b/webapi/Core/DomainObjects/Documents/Category.cs index 44f8f18..88be350 100644 --- a/webapi/Core/DomainObjects/Documents/Category.cs +++ b/webapi/Core/DomainObjects/Documents/Category.cs @@ -1,7 +1,7 @@ using Core.Abstractions.DomainObjects; using Core.DomainObjects.L10n; -namespace Core.DomainObjects { +namespace Core.DomainObjects.Documents { public class Category : DomainObjectDocumentBase { public Guid SiteId { get; set; } public List L10n { get; set; } diff --git a/webapi/DataProviders/Abstractions/BucketDataProviderBase.cs b/webapi/DataProviders/Abstractions/BucketDataProviderBase.cs new file mode 100644 index 0000000..95dbe75 --- /dev/null +++ b/webapi/DataProviders/Abstractions/BucketDataProviderBase.cs @@ -0,0 +1,199 @@ +using Microsoft.Extensions.Logging; + +using MongoDB.Bson; +using MongoDB.Driver; +using MongoDB.Driver.GridFS; + +using DomainResults.Common; + +namespace DataProviders.Abstractions { + + /// + /// + /// + /// + public abstract class BucketDataProviderBase : DataProviderBase { + + /// + /// + /// + /// + /// + public BucketDataProviderBase( + ILogger logger, + IMongoClient client + ) : base (logger, client){ } + + #region Upload + private protected (Guid?, IDomainResult) Upload(Guid siteId, Guid userId, BucketFile file, string bucketName) => + UploadAsync(siteId, userId, file, bucketName).Result; + + private protected Task<(Guid?, IDomainResult)> UploadAsync(Guid siteId, Guid userId, BucketFile file, string bucketName) => + UploadAsyncCore(siteId, userId, file, bucketName); + #endregion + + #region Upload many + private protected (List?, IDomainResult) UploadMany(Guid siteId, Guid userId, List files, string bucketName) => + UploadAsync(siteId, userId, files, bucketName).Result; + + private protected Task<(List?, IDomainResult)> UploadAsync(Guid siteId, Guid userId, List files, string bucketName) => + UploadManyAsyncCore(siteId, userId, files, bucketName); + #endregion + + #region Download + private protected (BucketFile?, IDomainResult) Download(Guid siteId, Guid userId, Guid fileId, string bucketName) => + DownloadAsync(siteId, userId, fileId, bucketName).Result; + + private protected Task<(BucketFile?, IDomainResult)> DownloadAsync(Guid siteId, Guid userId, Guid fileId, string bucketName) => + DownloadAsyncCore(siteId, userId, fileId, bucketName); + #endregion + + #region Delete + private protected IDomainResult DeleteOne(Guid siteId, Guid userId, Guid fileId, string bucketName) => + DeleteOneAsync(siteId, userId, fileId, bucketName).Result; + + private protected Task DeleteOneAsync(Guid siteId, Guid userId, Guid fileId, string bucketName) => + DeleteOneAsyncCore(siteId, userId, fileId, bucketName); + #endregion + + #region Delete many + private protected IDomainResult DeleteMany(Guid siteId, Guid userId, string bucketName) => + DeleteManyAsync(siteId, userId, bucketName).Result; + + private protected Task DeleteManyAsync(Guid siteId, Guid userId, string bucketName) => + DeleteManyAsyncCore(siteId, userId, bucketName); + #endregion + + #region Core methods + private GridFSBucket CreateBucket(string bucketName) => new GridFSBucket(_client.GetDatabase(_databaseName), new GridFSBucketOptions { + BucketName = bucketName, + ChunkSizeBytes = 1048576, // 1MB + WriteConcern = WriteConcern.WMajority, + ReadPreference = ReadPreference.Secondary + }); + + private async Task<(Guid?, IDomainResult)> UploadAsyncCore(Guid siteId, Guid userId, BucketFile file, string bucketName) { + var (list, result) = await UploadManyAsyncCore(siteId, userId, new List { file }, bucketName); + + if (!result.IsSuccess || list == null) + return (null, result); + + return (list.First(), result); + } + + public async Task<(List?, IDomainResult)> UploadManyAsyncCore(Guid siteId, Guid userId, List files, string bucketName) { + var options = new GridFSUploadOptions { + ChunkSizeBytes = 64512, // 63KB + }; + + try { + var bucket = CreateBucket(bucketName); + + var result = new List(); + foreach (var file in files) { + + options.Metadata = new BsonDocument { + { "siteId", $"{siteId}"}, + { "userId", $"{userId}"}, + { "fileName", file.Name }, + { "contentType", file.ContentType } + }; + + var fileId = Guid.NewGuid(); + await bucket.UploadFromBytesAsync($"{fileId}", file.Bytes, options); + result.Add(fileId); + } + + return result.Count > 0 + ? IDomainResult.Success(result) + : IDomainResult.Failed?>(); + } + catch (Exception ex) { + _logger.LogError(ex, "Bucket data provider error"); + return IDomainResult.Failed?>(); + } + } + + private async Task<(BucketFile?, IDomainResult)> DownloadAsyncCore(Guid siteId, Guid userId, Guid fileId, string bucketName) { + try { + var filter = Builders.Filter.And( + Builders.Filter.Eq(x => x.Metadata["siteId"], $"{siteId}"), + Builders.Filter.Eq(x => x.Metadata["userId"], $"{userId}"), + Builders.Filter.Eq(x => x.Filename, $"{fileId}") + ); + + var sort = Builders.Sort.Descending(x => x.UploadDateTime); + + var bucket = CreateBucket(bucketName); + + using var cursor = await bucket.FindAsync(filter, new GridFSFindOptions { + Limit = 1, + Sort = sort, + }); + + // fileInfo either has the matching file information or is null + var fileInfo = (await cursor.ToListAsync()).FirstOrDefault(); + if (fileInfo == null) + return IDomainResult.NotFound(); + + var fileName = fileInfo.Metadata["fileName"].ToString() ?? ""; + if (fileName == null) + return IDomainResult.Failed(); + + var contentType = fileInfo.Metadata["contentType"].ToString() ?? ""; + if (contentType == null) + return IDomainResult.Failed(); + + var bytes = await bucket.DownloadAsBytesByNameAsync($"{fileId}", new GridFSDownloadByNameOptions { + Revision = -1 + }); + + if(bytes == null) + return IDomainResult.Failed(); + + return IDomainResult.Success(new BucketFile(fileName, bytes, contentType)); + + } + catch (Exception ex) { + _logger.LogError(ex, "Bucket data provider error"); + return IDomainResult.Failed(); + } + } + + private Task DeleteOneAsyncCore(Guid siteId, Guid userId, Guid fileId, string bucketName) => + DeleteAsyncCore(siteId, userId, Builders.Filter.And( + Builders.Filter.Eq(x => x.Metadata["siteId"], $"{siteId}"), + Builders.Filter.Eq(x => x.Metadata["userId"], $"{userId}"), + Builders.Filter.Eq(x => x.Filename, $"{fileId}") + ), bucketName); + + private Task DeleteManyAsyncCore(Guid siteId, Guid userId, string bucketName) => + DeleteAsyncCore(siteId, userId, Builders.Filter.And( + Builders.Filter.Eq(x => x.Metadata["siteId"], $"{siteId}"), + Builders.Filter.Eq(x => x.Metadata["userId"], $"{userId}") + ), bucketName); + + private async Task DeleteAsyncCore(Guid siteId, Guid userId, FilterDefinition filter, string bucketName) { + try { + var bucket = CreateBucket(bucketName); + + using var cursor = await bucket.FindAsync(filter); + + var count = 0; + foreach (var fileInfo in await cursor.ToListAsync()) { + await bucket.DeleteAsync(fileInfo.Id); + count++; + } + + return count > 0 + ? IDomainResult.Success() + : IDomainResult.NotFound(); + } + catch (Exception ex) { + _logger.LogError(ex, "Bucket data provider error"); + return IDomainResult.Failed(); + } + } + #endregion + } +} diff --git a/webapi/DataProviders/Abstractions/CollectionDataProviderBase.cs b/webapi/DataProviders/Abstractions/CollectionDataProviderBase.cs new file mode 100644 index 0000000..5c5682d --- /dev/null +++ b/webapi/DataProviders/Abstractions/CollectionDataProviderBase.cs @@ -0,0 +1,220 @@ +using System.Linq.Expressions; + +using Microsoft.Extensions.Logging; + +using MongoDB.Bson.Serialization; +using MongoDB.Driver; + +using DomainResults.Common; + +using Core.Abstractions.DomainObjects; + +namespace DataProviders.Abstractions { + + /// + /// + /// + /// + public abstract class CollectionDataProviderBase : DataProviderBase> where T : DomainObjectDocumentBase { + + private protected readonly IIdGenerator _idGenerator; + private protected readonly ISessionService _sessionService; + + /// + /// Main constructor + /// + /// + /// + /// + /// + public CollectionDataProviderBase( + ILogger> logger, + IMongoClient client, + IIdGenerator idGenerator, + ISessionService sessionService + ) : base (logger, client){ + _idGenerator = idGenerator; + _sessionService = sessionService; + } + + #region Insert + private protected (Guid?, IDomainResult) Insert(T obj, string collectionName) => + InsertAsync(obj, collectionName).Result; + + private protected (Guid?, IDomainResult) Insert(T obj, string collectionName, Guid sessionId) => + InsertAsync(obj, collectionName, sessionId).Result; + + private protected Task<(Guid?, IDomainResult)> InsertAsync(T obj, string collectionName) => + InsertAsyncCore(obj, collectionName, null); + + private protected Task<(Guid?, IDomainResult)> InsertAsync(T obj, string collectionName, Guid sessionId) => + InsertAsyncCore(obj, collectionName, sessionId); + #endregion + + #region InsertMany + private protected (List?, IDomainResult) InsertMany(List objList, string collectionName) => + InsertManyAsync(objList, collectionName).Result; + + private protected (List?, IDomainResult) InsertMany(List objList, string collectionName, Guid sessionId) => + InsertManyAsync(objList, collectionName, sessionId).Result; + + private protected Task<(List?, IDomainResult)> InsertManyAsync(List objList, string collectionName) => + InsertManyAsyncCore(objList, collectionName, null); + + private protected Task<(List?, IDomainResult)> InsertManyAsync(List objList, string collectionName, Guid sessionId) => + InsertManyAsyncCore(objList, collectionName, sessionId); + #endregion + + #region Get + private protected (List?, IDomainResult) GetWithPredicate(Expression> predicate, string collectionName) => + GetWithPredicateCore(predicate, 0, 0, collectionName); + private protected (List?, IDomainResult) GetWithPredicate(Expression> predicate, int skip, int limit, string collectionName) => + GetWithPredicateCore(predicate, skip, limit, collectionName); + #endregion + + #region Update + private protected (Guid?, IDomainResult) UpdateWithPredicate(T obj, Expression> predicate, string collectionName) => + UpdateWithPredicateAsync(obj, predicate, collectionName).Result; + + private protected (Guid?, IDomainResult) UpdateWithPredicate(T obj, Expression> predicate, string collectionName, Guid sessionId) => + UpdateWithPredicateAsync(obj, predicate, collectionName, sessionId).Result; + + private protected Task<(Guid?, IDomainResult)> UpdateWithPredicateAsync(T obj, Expression> predicate, string collectionName) => + UpdateWithPredicateAsyncCore(obj, predicate, collectionName, null); + + private protected Task<(Guid?, IDomainResult)> UpdateWithPredicateAsync(T obj, Expression> predicate, string collectionName, Guid sessionId) => + UpdateWithPredicateAsyncCore(obj, predicate, collectionName, sessionId); + #endregion + + #region Delete + private protected IDomainResult DeleteWithPredicate(Expression> predicate, string collectionName) => + DeleteWithPredicateAsync(predicate, collectionName).Result; + + private protected IDomainResult DeleteWithPredicate(Expression> predicate, string collectionName, Guid sessionId) => + DeleteWithPredicateAsync(predicate, collectionName, sessionId).Result; + + private protected Task DeleteWithPredicateAsync(Expression> predicate, string collectionName) => + DeleteWithPredicateAsyncCore(predicate, collectionName, null); + + private protected Task DeleteWithPredicateAsync(Expression> predicate, string collectionName, Guid sessionId) => + DeleteWithPredicateAsyncCore(predicate, collectionName, sessionId); + #endregion + + #region DeleteMany + private protected IDomainResult DeleteManyWithPredicate(Expression> predicate, string collectionName) => + DeleteManyWithPredicateAsync(predicate, collectionName).Result; + + private protected IDomainResult DeleteManyWithPredicate(Expression> predicate, string collectionName, Guid sessionId) => + DeleteManyWithPredicateAsync(predicate, collectionName, sessionId).Result; + + private protected Task DeleteManyWithPredicateAsync(Expression> predicate, string collectionName) => + DeleteManyWithPredicateAsyncCore(predicate, collectionName, null); + + private protected Task DeleteManyWithPredicateAsync(Expression> predicate, string collectionName, Guid sessionId) => + DeleteManyWithPredicateAsyncCore(predicate, collectionName, sessionId); + #endregion + + #region Core methods + private async protected Task<(Guid?, IDomainResult)> InsertAsyncCore(T obj, string collectionName, Guid? sessionId) { + try { + var collection = _client.GetDatabase(_databaseName).GetCollection(collectionName); + + 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(); + } + } + + private async Task<(List?, IDomainResult)> InsertManyAsyncCore(List objList, string collectionName, Guid? sessionId) { + try { + var collection = _client.GetDatabase(_databaseName).GetCollection(collectionName); + + 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?>(); + } + } + + private (List?, IDomainResult) GetWithPredicateCore(Expression> predicate, int skip, int limit, string collectionName) { + try { + var result = _client.GetDatabase(_databaseName).GetCollection(collectionName) + .Find(predicate).Skip(skip).Limit(limit).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 async Task<(Guid?, IDomainResult)> UpdateWithPredicateAsyncCore(T obj, Expression> predicate, string collectionName, Guid? sessionId) { + try { + + var collection = _client.GetDatabase(_databaseName).GetCollection(collectionName); + + 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(); + } + } + + private async Task DeleteWithPredicateAsyncCore(Expression> predicate, string collectionName, Guid? sessionId) { + try { + var collection = _client.GetDatabase(_databaseName).GetCollection(collectionName); + + 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(); + } + } + + private async Task DeleteManyWithPredicateAsyncCore(Expression> predicate, string collectionName, Guid? sessionId) { + try { + var collection = _client.GetDatabase(_databaseName).GetCollection(collectionName); + + 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/webapi/DataProviders/Abstractions/DataProviderBase.cs b/webapi/DataProviders/Abstractions/DataProviderBase.cs index c0aca35..c8aedb3 100644 --- a/webapi/DataProviders/Abstractions/DataProviderBase.cs +++ b/webapi/DataProviders/Abstractions/DataProviderBase.cs @@ -1,264 +1,34 @@ -using System.Linq.Expressions; - -using Microsoft.Extensions.Logging; - -using MongoDB.Bson.Serialization; +using Microsoft.Extensions.Logging; using MongoDB.Driver; - -using DomainResults.Common; - -using Core.Abstractions.DomainObjects; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; namespace DataProviders.Abstractions { - public abstract class DataProviderBase where T : DomainObjectDocumentBase { + /// + /// + /// + /// + public abstract class DataProviderBase { private protected const string _databaseName = "reactredux"; - private protected readonly ILogger> _logger; + private protected readonly ILogger _logger; private protected readonly IMongoClient _client; - private protected readonly IIdGenerator _idGenerator; - private protected readonly ISessionService _sessionService; - - private protected List? _collection; /// - /// Main constructor + /// /// /// /// - /// - /// public DataProviderBase( - ILogger> logger, - IMongoClient client, - IIdGenerator idGenerator, - ISessionService sessionService + ILogger logger, + IMongoClient client ) { _logger = logger; _client = client; - _idGenerator = idGenerator; - _sessionService = sessionService; } - - #region Insert - private protected (Guid?, IDomainResult) Insert(T obj, string collectionName) => - InsertAsync(obj, collectionName).Result; - - private protected (Guid?, IDomainResult) Insert(T obj, string collectionName, Guid sessionId) => - InsertAsync(obj, collectionName, sessionId).Result; - - private protected Task<(Guid?, IDomainResult)> InsertAsync(T obj, string collectionName) => - InsertAsyncCore(obj, collectionName, null); - - private protected Task<(Guid?, IDomainResult)> InsertAsync(T obj, string collectionName, Guid sessionId) => - InsertAsyncCore(obj, collectionName, sessionId); - #endregion - - #region InsertMany - private protected (List?, IDomainResult) InsertMany(List objList, string collectionName) => - InsertManyAsync(objList, collectionName).Result; - - private protected (List?, IDomainResult) InsertMany(List objList, string collectionName, Guid sessionId) => - InsertManyAsync(objList, collectionName, sessionId).Result; - - private protected Task<(List?, IDomainResult)> InsertManyAsync(List objList, string collectionName) => - InsertManyAsyncCore(objList, collectionName, null); - - private protected Task<(List?, IDomainResult)> InsertManyAsync(List objList, string collectionName, Guid sessionId) => - InsertManyAsyncCore(objList, collectionName, sessionId); - #endregion - - #region Get - private protected (List?, IDomainResult) GetWithPredicate(Expression> predicate, string collectionName) => - GetWithPredicateCore(predicate, 0, 0, collectionName); - private protected (List?, IDomainResult) GetWithPredicate(Expression> predicate, int skip, int limit, string collectionName) => - GetWithPredicateCore(predicate, skip, limit, collectionName); - #endregion - - #region Update - private protected (Guid?, IDomainResult) UpdateWithPredicate(T obj, Expression> predicate, string collectionName) => - UpdateWithPredicateAsync(obj, predicate, collectionName).Result; - - private protected (Guid?, IDomainResult) UpdateWithPredicate(T obj, Expression> predicate, string collectionName, Guid sessionId) => - UpdateWithPredicateAsync(obj, predicate, collectionName, sessionId).Result; - - private protected Task<(Guid?, IDomainResult)> UpdateWithPredicateAsync(T obj, Expression> predicate, string collectionName) => - UpdateWithPredicateAsyncCore(obj, predicate, collectionName, null); - - private protected Task<(Guid?, IDomainResult)> UpdateWithPredicateAsync(T obj, Expression> predicate, string collectionName, Guid sessionId) => - UpdateWithPredicateAsyncCore(obj, predicate, collectionName, sessionId); - #endregion - - #region Exists - private protected (Guid?, IDomainResult) Exists(Guid id, string collectionName) { - var (_resultList, result) = GetWithPredicate(x => x.Id == id, 0, 0, collectionName); - - return (result.Status != DomainOperationStatus.Failed && _resultList != null && _resultList.Count > 0 - ? id - :null, - result); - } - #endregion - - #region Delete - private protected IDomainResult DeleteWithPredicate(Expression> predicate, string collectionName) => - DeleteWithPredicateAsync(predicate, collectionName).Result; - - private protected IDomainResult DeleteWithPredicate(Expression> predicate, string collectionName, Guid sessionId) => - DeleteWithPredicateAsync(predicate, collectionName, sessionId).Result; - - private protected Task DeleteWithPredicateAsync(Expression> predicate, string collectionName) => - DeleteWithPredicateAsyncCore(predicate, collectionName, null); - - private protected Task DeleteWithPredicateAsync(Expression> predicate, string collectionName, Guid sessionId) => - DeleteWithPredicateAsyncCore(predicate, collectionName, sessionId); - #endregion - - - #region DeleteMany - private protected IDomainResult DeleteManyWithPredicate(Expression> predicate, string collectionName) => - DeleteManyWithPredicateAsync(predicate, collectionName).Result; - - private protected IDomainResult DeleteManyWithPredicate(Expression> predicate, string collectionName, Guid sessionId) => - DeleteManyWithPredicateAsync(predicate, collectionName, sessionId).Result; - - private protected Task DeleteManyWithPredicateAsync(Expression> predicate, string collectionName) => - DeleteManyWithPredicateAsyncCore(predicate, collectionName, null); - - private protected Task DeleteManyWithPredicateAsync(Expression> predicate, string collectionName, Guid sessionId) => - DeleteManyWithPredicateAsyncCore(predicate, collectionName, sessionId); - #endregion - - - #region Core methods - private async protected Task<(Guid?, IDomainResult)> InsertAsyncCore(T obj, string collectionName, Guid? sessionId) { - try { - if (_collection != null) { - obj.Id = Guid.NewGuid(); - _collection.Add(obj); - return IDomainResult.Success(obj.Id); - } - - var collection = _client.GetDatabase(_databaseName).GetCollection(collectionName); - - 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(); - } - } - - private async Task<(List?, IDomainResult)> InsertManyAsyncCore(List objList, string collectionName, Guid? sessionId) { - try { - if (_collection != null) { - _collection = _collection.Concat(objList).ToList(); - return IDomainResult.Success(objList.Select(x => x.Id).ToList()); - } - - var collection = _client.GetDatabase(_databaseName).GetCollection(collectionName); - - 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?>(); - } - } - - private (List?, IDomainResult) GetWithPredicateCore(Expression> predicate, int skip, int limit, string collectionName) { - try { - List? result; - - if (_collection != null) { - result = _collection?.AsQueryable() - .Where(predicate).ToList(); - } - else { - result = _client.GetDatabase(_databaseName).GetCollection(collectionName) - .Find(predicate).Skip(skip).Limit(limit).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 async Task<(Guid?, IDomainResult)> UpdateWithPredicateAsyncCore(T obj, Expression> predicate, string collectionName, Guid? sessionId) { - try { - if (_collection != null) { - // remove element(s) from list - foreach (var element in _collection.AsQueryable().Where(predicate)) - _collection = _collection.Where(x => x.Id != element.Id).ToList(); - - // add updated element - _collection.Add(obj); - } - else { - var collection = _client.GetDatabase(_databaseName).GetCollection(collectionName); - - 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(); - } - } - - private async Task DeleteWithPredicateAsyncCore(Expression> predicate, string collectionName, Guid? sessionId) { - try { - var collection = _client.GetDatabase(_databaseName).GetCollection(collectionName); - - 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(); - } - } - - private async Task DeleteManyWithPredicateAsyncCore(Expression> predicate, string collectionName, Guid? sessionId) { - try { - var collection = _client.GetDatabase(_databaseName).GetCollection(collectionName); - - 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/webapi/DataProviders/BucketFile.cs b/webapi/DataProviders/BucketFile.cs new file mode 100644 index 0000000..9e7ff03 --- /dev/null +++ b/webapi/DataProviders/BucketFile.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace DataProviders { + public class BucketFile { + public string Name { get; private set; } + public byte[] Bytes { get; private set; } + public string ContentType { get; private set; } + + public BucketFile(string name, byte[] bytes, string contentType) { + Name = name; + Bytes = bytes; + ContentType = contentType; + } + } +} diff --git a/webapi/DataProviders/Buckets/ImagesBucketDataProvider.cs b/webapi/DataProviders/Buckets/ImagesBucketDataProvider.cs new file mode 100644 index 0000000..3d0eb77 --- /dev/null +++ b/webapi/DataProviders/Buckets/ImagesBucketDataProvider.cs @@ -0,0 +1,122 @@ +using Microsoft.Extensions.Logging; + +using DomainResults.Common; + +using MongoDB.Driver; + +using DataProviders.Abstractions; + +namespace DataProviders.Buckets { + + /// + /// + /// + public interface IImagesBucketDataProvider { + + /// + /// + /// + /// + /// + /// + /// + (Guid?, IDomainResult) Upload(Guid siteId, Guid userId, BucketFile file); + + /// + /// + /// + /// + /// + /// + /// + (List?, IDomainResult) UploadMany(Guid siteId, Guid userId, List files); + + /// + /// + /// + /// + /// + /// + /// + (BucketFile?, IDomainResult) Download(Guid siteId, Guid userId, Guid fileId); + + /// + /// + /// + /// + /// + /// + /// + IDomainResult DeleteOne(Guid siteId, Guid userId, Guid fileId); + + /// + /// + /// + /// + /// + /// + IDomainResult DeletMany(Guid siteId, Guid userId); + } + + /// + /// + /// + public class ImagesBucketDataProvider : BucketDataProviderBase, IImagesBucketDataProvider { + + private const string _bucketName = "images"; + + /// + /// + /// + /// + /// + public ImagesBucketDataProvider( + ILogger logger, + IMongoClient client + ) : base(logger, client) { } + + /// + /// + /// + /// + /// + /// + /// + public (Guid?, IDomainResult) Upload(Guid siteId, Guid userId, BucketFile file) => Upload(siteId, userId, file, _bucketName); + + /// + /// + /// + /// + /// + /// + /// + public (List?, IDomainResult) UploadMany(Guid siteId, Guid userId, List files) => UploadMany(siteId, userId, files, _bucketName); + + /// + /// + /// + /// + /// + /// + /// + public (BucketFile?, IDomainResult) Download(Guid siteId, Guid userId, Guid fileId) => Download(siteId, userId, fileId, _bucketName); + + /// + /// + /// + /// + /// + /// + /// + public IDomainResult DeleteOne(Guid siteId, Guid userId, Guid fileId) => DeleteOne(siteId, userId, fileId, _bucketName); + + /// + /// + /// + /// + /// + /// + public IDomainResult DeletMany(Guid siteId, Guid userId) => DeleteMany(siteId, userId, _bucketName); + } +} diff --git a/webapi/DataProviders/BlogCatalogDataProvider.cs b/webapi/DataProviders/Collections/BlogCatalogDataProvider.cs similarity index 92% rename from webapi/DataProviders/BlogCatalogDataProvider.cs rename to webapi/DataProviders/Collections/BlogCatalogDataProvider.cs index f3ae901..b2e175f 100644 --- a/webapi/DataProviders/BlogCatalogDataProvider.cs +++ b/webapi/DataProviders/Collections/BlogCatalogDataProvider.cs @@ -8,7 +8,7 @@ using MongoDB.Driver; using DataProviders.Abstractions; using Core.DomainObjects.Documents; -namespace DataProviders { +namespace DataProviders.Collections { public interface IBlogCatalogDataProvider { (Guid?, IDomainResult) Insert(BlogItem blogItem); (BlogItem?, IDomainResult) Get(Guid siteId, Guid blogId); @@ -20,12 +20,12 @@ namespace DataProviders { IDomainResult DeleteAll(Guid siteId); } - public class BlogCatalogDataProvider : DataProviderBase, IBlogCatalogDataProvider { + public class BlogCatalogDataProvider : CollectionDataProviderBase, IBlogCatalogDataProvider { private const string _collectionName = "blogcatalog"; public BlogCatalogDataProvider( - ILogger> logger, + ILogger> logger, IMongoClient client, IIdGenerator idGenerator, ISessionService sessionService) : base(logger, client, idGenerator, sessionService) { diff --git a/webapi/DataProviders/CategoryDataProvider.cs b/webapi/DataProviders/Collections/CategoryDataProvider.cs similarity index 90% rename from webapi/DataProviders/CategoryDataProvider.cs rename to webapi/DataProviders/Collections/CategoryDataProvider.cs index e69f7a5..24c2f19 100644 --- a/webapi/DataProviders/CategoryDataProvider.cs +++ b/webapi/DataProviders/Collections/CategoryDataProvider.cs @@ -6,9 +6,9 @@ using MongoDB.Bson.Serialization; using DomainResults.Common; using DataProviders.Abstractions; -using Core.DomainObjects; +using Core.DomainObjects.Documents; -namespace DataProviders { +namespace DataProviders.Collections { public interface ICategoryDataProvider { (Guid?, IDomainResult) Insert(Category obj); @@ -21,11 +21,11 @@ namespace DataProviders { IDomainResult DeleteAll(Guid siteId); } - public class CategoryDataProvider : DataProviderBase, ICategoryDataProvider { + public class CategoryDataProvider : CollectionDataProviderBase, ICategoryDataProvider { private const string _collectionName = "categories"; public CategoryDataProvider( - ILogger> logger, + ILogger> logger, IMongoClient client, IIdGenerator idGenerator, ISessionService sessionService) : base(logger, client, idGenerator, sessionService) { diff --git a/webapi/DataProviders/ContentDataProvider.cs b/webapi/DataProviders/Collections/ContentDataProvider.cs similarity index 76% rename from webapi/DataProviders/ContentDataProvider.cs rename to webapi/DataProviders/Collections/ContentDataProvider.cs index 91d0bb4..c7dd3fd 100644 --- a/webapi/DataProviders/ContentDataProvider.cs +++ b/webapi/DataProviders/Collections/ContentDataProvider.cs @@ -1,21 +1,24 @@ -using Core.DomainObjects.Documents; -using DataProviders.Abstractions; +using Microsoft.Extensions.Logging; + using DomainResults.Common; -using Microsoft.Extensions.Logging; + using MongoDB.Bson.Serialization; using MongoDB.Driver; -namespace DataProviders { +using DataProviders.Abstractions; +using Core.DomainObjects.Documents; + +namespace DataProviders.Collections { public interface IContentDataProvider { (Content?, IDomainResult) Get(Guid siteId, string locale); } - public class ContentDataProvider : DataProviderBase, IContentDataProvider { + public class ContentDataProvider : CollectionDataProviderBase, IContentDataProvider { private const string _collectionName = "content"; public ContentDataProvider( - ILogger> logger, + ILogger> logger, IMongoClient client, IIdGenerator idGenerator, ISessionService sessionService) : base(logger, client, idGenerator, sessionService) { diff --git a/webapi/DataProviders/ShopCartDataProvider.cs b/webapi/DataProviders/Collections/ShopCartDataProvider.cs similarity index 90% rename from webapi/DataProviders/ShopCartDataProvider.cs rename to webapi/DataProviders/Collections/ShopCartDataProvider.cs index c61cd1f..e271470 100644 --- a/webapi/DataProviders/ShopCartDataProvider.cs +++ b/webapi/DataProviders/Collections/ShopCartDataProvider.cs @@ -8,7 +8,7 @@ using MongoDB.Driver; using DataProviders.Abstractions; using Core.DomainObjects.Documents; -namespace DataProviders { +namespace DataProviders.Collections { public interface IShopCartDataProvider { (Guid?, IDomainResult) Insert(ShopCartItem obj); @@ -19,10 +19,10 @@ namespace DataProviders { IDomainResult DeleteAll(Guid siteId, Guid userId); } - public class ShopCartDataProvider : DataProviderBase, IShopCartDataProvider { + public class ShopCartDataProvider : CollectionDataProviderBase, IShopCartDataProvider { private const string _collectionName = "shopcart"; public ShopCartDataProvider( - ILogger> logger, + ILogger> logger, IMongoClient client, IIdGenerator idGenerator, ISessionService sessionService) : base(logger, client, idGenerator, sessionService) { diff --git a/webapi/DataProviders/ShopCatalogDataProvider.cs b/webapi/DataProviders/Collections/ShopCatalogDataProvider.cs similarity index 92% rename from webapi/DataProviders/ShopCatalogDataProvider.cs rename to webapi/DataProviders/Collections/ShopCatalogDataProvider.cs index df81408..fa30266 100644 --- a/webapi/DataProviders/ShopCatalogDataProvider.cs +++ b/webapi/DataProviders/Collections/ShopCatalogDataProvider.cs @@ -8,7 +8,7 @@ using MongoDB.Driver; using Core.DomainObjects.Documents; using DataProviders.Abstractions; -namespace DataProviders { +namespace DataProviders.Collections { public interface IShopCatalogDataProvider { (Guid?, IDomainResult) Insert(ShopItem obj); (ShopItem?, IDomainResult) Get(Guid siteId, string sku); @@ -20,12 +20,12 @@ namespace DataProviders { IDomainResult DeleteAll(Guid siteId); } - public class ShopCatalogDataProvider : DataProviderBase, IShopCatalogDataProvider { + public class ShopCatalogDataProvider : CollectionDataProviderBase, IShopCatalogDataProvider { private const string _collectionName = "shopcatalog"; public ShopCatalogDataProvider( - ILogger> logger, + ILogger> logger, IMongoClient client, IIdGenerator idGenerator, ISessionService sessionService) : base(logger, client, idGenerator, sessionService) { diff --git a/webapi/DataProviders/Converters/EnumerationSerializer.cs b/webapi/DataProviders/Converters/EnumerationSerializer.cs index dd37731..ccde615 100644 --- a/webapi/DataProviders/Converters/EnumerationSerializer.cs +++ b/webapi/DataProviders/Converters/EnumerationSerializer.cs @@ -37,8 +37,6 @@ namespace DataProviders.Converters { default: throw new NotImplementedException($"No implementation to deserialize {type}"); } - - return response; } private void Serialize(IBsonWriter writer, List values) { if (values != null) { diff --git a/webapi/DataProviders/DataProviders.csproj b/webapi/DataProviders/DataProviders.csproj index 1de6c3d..125abff 100644 --- a/webapi/DataProviders/DataProviders.csproj +++ b/webapi/DataProviders/DataProviders.csproj @@ -11,6 +11,7 @@ + diff --git a/webapi/DataProviders/Extensions/ServiceCollectionExtensions.cs b/webapi/DataProviders/Extensions/ServiceCollectionExtensions.cs index 868f414..552e8bd 100644 --- a/webapi/DataProviders/Extensions/ServiceCollectionExtensions.cs +++ b/webapi/DataProviders/Extensions/ServiceCollectionExtensions.cs @@ -4,6 +4,9 @@ using MongoDB.Driver; using MongoDB.Bson.Serialization; using MongoDB.Bson.Serialization.IdGenerators; +using DataProviders.Collections; +using DataProviders.Buckets; + namespace DataProviders.Extensions { public static class ServiceCollectionExtensions { @@ -13,14 +16,21 @@ namespace DataProviders.Extensions services.AddSingleton(x => new MongoClient(config.ConnectionString)); services.AddSingleton(); + Mappings.RegisterClassMap(); + services.AddSingleton(); + #region Collections services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); - Mappings.RegisterClassMap(); + #endregion + + #region Buckets + services.AddSingleton(); + #endregion } } } diff --git a/webapi/WeatherForecast/Controllers/FileController.cs b/webapi/WeatherForecast/Controllers/FileController.cs new file mode 100644 index 0000000..86ea2fb --- /dev/null +++ b/webapi/WeatherForecast/Controllers/FileController.cs @@ -0,0 +1,62 @@ +using DomainResults.Mvc; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using System.Net.Http.Headers; +using WeatherForecast.Services; + +namespace WeatherForecast.Controllers { + + /// + /// + /// + [ApiController] + [AllowAnonymous] + [Route("api/[controller]")] + public class FileController : Controller { + + private readonly IFileService _fileService; + + /// + /// + /// + /// + public FileController( + IFileService fileService + ) { + _fileService = fileService; + } + + /// + /// + /// + /// + /// + /// + /// + [HttpGet("{siteId}/{userId}/{fileId}")] + public IActionResult Get([FromRoute] Guid siteId, [FromRoute] Guid userid, [FromRoute] Guid fileId) { + var (file, result) = _fileService.Get(siteId, userid, fileId); + + if (!result.IsSuccess || file == null) + return result.ToActionResult(); + + var stream = new MemoryStream(file.Bytes); + return new FileStreamResult(stream, file.ContentType) { + FileDownloadName = file.Name + }; + } + + /// + /// + /// + /// + /// + /// + /// + [HttpDelete("{siteId}/{userId}/{fileId}")] + public IActionResult Delete([FromRoute] Guid siteId, [FromRoute] Guid userId, [FromRoute] Guid fileId) { + var result = _fileService.Delete(siteId, userId, fileId); + return result.ToActionResult(); + } + } +} diff --git a/webapi/WeatherForecast/Controllers/FilesController.cs b/webapi/WeatherForecast/Controllers/FilesController.cs new file mode 100644 index 0000000..e473552 --- /dev/null +++ b/webapi/WeatherForecast/Controllers/FilesController.cs @@ -0,0 +1,54 @@ +using DomainResults.Mvc; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using WeatherForecast.Services; + +namespace WeatherForecast.Controllers { + + /// + /// + /// + [ApiController] + [AllowAnonymous] + [Route("api/[controller]")] + public class FilesController : Controller { + + private readonly IFilesService _filesService; + + /// + /// + /// + /// + public FilesController( + IFilesService filesService + ) { + _filesService = filesService; + } + + /// + /// + /// + /// + /// + /// + /// + [HttpPost("{siteId}/{userId}")] + public IActionResult Post([FromRoute] Guid siteId, [FromRoute] Guid userId, List file) { + var result = _filesService.Post(siteId, userId, file); + return result.ToActionResult(); + } + + /// + /// + /// + /// + /// + /// + + [HttpDelete("{siteId}/{userId}")] + public IActionResult Delete([FromRoute] Guid siteId, [FromRoute] Guid userId) { + var result = _filesService.Delete(siteId, userId); + return result.ToActionResult(); + } + } +} diff --git a/webapi/WeatherForecast/Models/Abstractions/PostItemResponseModelBase.cs b/webapi/WeatherForecast/Models/Abstractions/PostItemResponseModelBase.cs index cfeab7e..06576b2 100644 --- a/webapi/WeatherForecast/Models/Abstractions/PostItemResponseModelBase.cs +++ b/webapi/WeatherForecast/Models/Abstractions/PostItemResponseModelBase.cs @@ -1,6 +1,6 @@ using Core.Abstractions.DomainObjects; using Core.Abstractions.Models; -using Core.DomainObjects; +using Core.DomainObjects.Documents; using Core.Enumerations; using WeatherForecast.Models.Responses; using WeatherForecast.Models.Responses.L10n; diff --git a/webapi/WeatherForecast/Models/Requests/CategoryItemRequestModel.cs b/webapi/WeatherForecast/Models/Requests/CategoryItemRequestModel.cs index 4830564..0f5f52d 100644 --- a/webapi/WeatherForecast/Models/Requests/CategoryItemRequestModel.cs +++ b/webapi/WeatherForecast/Models/Requests/CategoryItemRequestModel.cs @@ -1,5 +1,5 @@ using Core.Abstractions.Models; -using Core.DomainObjects; +using Core.DomainObjects.Documents; using Core.DomainObjects.L10n; using Core.Enumerations; using Extensions; diff --git a/webapi/WeatherForecast/Models/Responses/CategoryItemResponseModel.cs b/webapi/WeatherForecast/Models/Responses/CategoryItemResponseModel.cs index 1d1af47..f395a09 100644 --- a/webapi/WeatherForecast/Models/Responses/CategoryItemResponseModel.cs +++ b/webapi/WeatherForecast/Models/Responses/CategoryItemResponseModel.cs @@ -1,5 +1,5 @@ using Core.Abstractions.Models; -using Core.DomainObjects; +using Core.DomainObjects.Documents; using Core.Enumerations; using WeatherForecast.Models.Responses.L10n; diff --git a/webapi/WeatherForecast/Services/BlogItemService.cs b/webapi/WeatherForecast/Services/BlogItemService.cs index cf40a73..baf2f7c 100644 --- a/webapi/WeatherForecast/Services/BlogItemService.cs +++ b/webapi/WeatherForecast/Services/BlogItemService.cs @@ -4,11 +4,13 @@ using ExtensionMethods; using DataProviders; using Core.DomainObjects; +using Core.DomainObjects.Documents; using Core.DomainObjects.L10n; using Core.Enumerations; using WeatherForecast.Models.Requests; using WeatherForecast.Models.Responses; +using DataProviders.Collections; namespace WeatherForecast.Services { diff --git a/webapi/WeatherForecast/Services/BlogItemsService.cs b/webapi/WeatherForecast/Services/BlogItemsService.cs index d75f890..278bc1f 100644 --- a/webapi/WeatherForecast/Services/BlogItemsService.cs +++ b/webapi/WeatherForecast/Services/BlogItemsService.cs @@ -7,6 +7,8 @@ using Core.DomainObjects; using Core.Enumerations; using WeatherForecast.Models.Responses; +using DataProviders.Collections; +using Core.DomainObjects.Documents; namespace WeatherForecast.Services { diff --git a/webapi/WeatherForecast/Services/CategoryItemService.cs b/webapi/WeatherForecast/Services/CategoryItemService.cs index 7837dc5..4ae0e88 100644 --- a/webapi/WeatherForecast/Services/CategoryItemService.cs +++ b/webapi/WeatherForecast/Services/CategoryItemService.cs @@ -6,6 +6,7 @@ using Core.Enumerations; using WeatherForecast.Models.Requests; using WeatherForecast.Models.Responses; +using DataProviders.Collections; namespace WeatherForecast.Services { diff --git a/webapi/WeatherForecast/Services/CategoryItemsService.cs b/webapi/WeatherForecast/Services/CategoryItemsService.cs index b12e24b..bf219eb 100644 --- a/webapi/WeatherForecast/Services/CategoryItemsService.cs +++ b/webapi/WeatherForecast/Services/CategoryItemsService.cs @@ -6,6 +6,7 @@ using Core.Abstractions; using Core.Enumerations; using WeatherForecast.Models.Responses; +using DataProviders.Collections; namespace WeatherForecast.Services { diff --git a/webapi/WeatherForecast/Services/ContentService.cs b/webapi/WeatherForecast/Services/ContentService.cs index 2659699..5c1a0be 100644 --- a/webapi/WeatherForecast/Services/ContentService.cs +++ b/webapi/WeatherForecast/Services/ContentService.cs @@ -1,6 +1,6 @@ using DomainResults.Common; -using DataProviders; +using DataProviders.Collections; using WeatherForecast.Models.Responses; diff --git a/webapi/WeatherForecast/Services/FileService.cs b/webapi/WeatherForecast/Services/FileService.cs new file mode 100644 index 0000000..c634a61 --- /dev/null +++ b/webapi/WeatherForecast/Services/FileService.cs @@ -0,0 +1,87 @@ +using DataProviders; +using DataProviders.Buckets; +using DomainResults.Common; + +namespace WeatherForecast.Services { + + /// + /// + /// + public interface IFileService { + /// + /// + /// + /// + /// + /// + /// + (BucketFile?, IDomainResult) Get(Guid siteId, Guid userId, Guid fileId); + + /// + /// + /// + /// + /// + /// + /// + IDomainResult Delete(Guid siteId, Guid userId, Guid fileId); + } + + /// + /// + /// + public class FileService : IFileService { + + private readonly ILogger _logger; + private readonly IImagesBucketDataProvider _imageBucketDataProvider; + + /// + /// + /// + /// + /// + public FileService( + ILogger logger, + IImagesBucketDataProvider imageBucketDataProvider + ) { + _logger = logger; + _imageBucketDataProvider = imageBucketDataProvider; + } + + /// + /// + /// + /// + /// + /// + /// + public (BucketFile?, IDomainResult) Get(Guid siteId, Guid userId, Guid fileId) { + try { + var (file, result) = _imageBucketDataProvider.Download(siteId, userId, fileId); + if (!result.IsSuccess || file == null) + return (null, result); + + return IDomainResult.Success(file); + } + catch (Exception ex) { + return IDomainResult.Failed(ex.Message); + } + } + + /// + /// + /// + /// + /// + /// + /// + public IDomainResult Delete(Guid siteId, Guid userId, Guid fileId) { + try { + return _imageBucketDataProvider.DeleteOne(siteId, userId, fileId); + } + catch (Exception ex) { + return IDomainResult.Failed(ex.Message); + } + } + } +} diff --git a/webapi/WeatherForecast/Services/FilesService.cs b/webapi/WeatherForecast/Services/FilesService.cs new file mode 100644 index 0000000..76767c1 --- /dev/null +++ b/webapi/WeatherForecast/Services/FilesService.cs @@ -0,0 +1,100 @@ +using DataProviders; +using DataProviders.Buckets; +using DomainResults.Common; +using Microsoft.AspNetCore.Mvc; + +namespace WeatherForecast.Services { + + /// + /// + /// + public interface IFilesService { + + /// + /// + /// + /// + /// + /// + /// + (List?, IDomainResult) Post(Guid siteId, Guid userId, List files); + + /// + /// + /// + /// + /// + /// + IDomainResult Delete(Guid siteId, Guid userId); + } + + /// + /// + /// + public class FilesService : IFilesService { + + private readonly ILogger _logger; + private readonly IImagesBucketDataProvider _imageBucketDataProvider; + + /// + /// + /// + /// + /// + public FilesService( + ILogger logger, + IImagesBucketDataProvider imageBucketDataProvider + ) { + _logger = logger; + _imageBucketDataProvider = imageBucketDataProvider; + } + + /// + /// Process uploaded files + /// + /// + /// + /// + /// + public (List?, IDomainResult) Post(Guid siteId, Guid userId, List files) { + try { + // Don't rely on or trust the FileName property without validation. + + var newFiles = new List(); + foreach (var formFile in files) { + if (formFile.Length > 0) { + using var ms = new MemoryStream(); + formFile.CopyTo(ms); + + newFiles.Add(new BucketFile(formFile.FileName, ms.ToArray(), formFile.ContentType)); + } + } + + var (list, result) = _imageBucketDataProvider.UploadMany(siteId, userId, newFiles); + + if (!result.IsSuccess || list == null) + return IDomainResult.Failed?>(); + + return IDomainResult.Success(list); + } + catch (Exception ex) { + return IDomainResult.Failed?> (ex.Message); + } + } + + /// + /// + /// + /// + /// + /// + public IDomainResult Delete(Guid siteId, Guid userId) { + try { + return _imageBucketDataProvider.DeletMany(siteId, userId); + } + catch (Exception ex) { + return IDomainResult.Failed(ex.Message); + } + } + } +} diff --git a/webapi/WeatherForecast/Services/ShopCartItemService.cs b/webapi/WeatherForecast/Services/ShopCartItemService.cs index dc92d21..30077b6 100644 --- a/webapi/WeatherForecast/Services/ShopCartItemService.cs +++ b/webapi/WeatherForecast/Services/ShopCartItemService.cs @@ -1,6 +1,6 @@ using DomainResults.Common; -using DataProviders; +using DataProviders.Collections; using Core.Abstractions; using Core.Enumerations; diff --git a/webapi/WeatherForecast/Services/ShopCartItemsService.cs b/webapi/WeatherForecast/Services/ShopCartItemsService.cs index efe5347..3e4d6e5 100644 --- a/webapi/WeatherForecast/Services/ShopCartItemsService.cs +++ b/webapi/WeatherForecast/Services/ShopCartItemsService.cs @@ -6,6 +6,7 @@ using Core.Enumerations; using Core.Abstractions; using WeatherForecast.Models.Responses; +using DataProviders.Collections; namespace WeatherForecast.Services { diff --git a/webapi/WeatherForecast/Services/ShopItemService.cs b/webapi/WeatherForecast/Services/ShopItemService.cs index 468cfca..2f36382 100644 --- a/webapi/WeatherForecast/Services/ShopItemService.cs +++ b/webapi/WeatherForecast/Services/ShopItemService.cs @@ -10,6 +10,8 @@ using Core.Enumerations; using WeatherForecast.Models; using WeatherForecast.Models.Requests; +using DataProviders.Collections; +using Core.DomainObjects.Documents; namespace WeatherForecast.Services { diff --git a/webapi/WeatherForecast/Services/ShopItemsService.cs b/webapi/WeatherForecast/Services/ShopItemsService.cs index 7a21bc4..c8086d0 100644 --- a/webapi/WeatherForecast/Services/ShopItemsService.cs +++ b/webapi/WeatherForecast/Services/ShopItemsService.cs @@ -8,6 +8,8 @@ using Core.Enumerations; using WeatherForecast.Models; using WeatherForecast.Models.Responses; +using DataProviders.Collections; +using Core.DomainObjects.Documents; namespace WeatherForecast.Services { diff --git a/webapi/WeatherForecast/Startup.cs b/webapi/WeatherForecast/Startup.cs index 94590cf..70d8b81 100644 --- a/webapi/WeatherForecast/Startup.cs +++ b/webapi/WeatherForecast/Startup.cs @@ -80,6 +80,9 @@ namespace WeatherForecast { services.AddScoped(); services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.RegisterDataproviders(appSettings); #region Swagger