(refactor): post services refactoring
This commit is contained in:
parent
ab3154e375
commit
d9d0a14083
File diff suppressed because one or more lines are too long
@ -25,7 +25,7 @@
|
||||
{
|
||||
"locale": 0,
|
||||
"alt": "...",
|
||||
"href": "/title-image-2",
|
||||
"target": "/title-image-2",
|
||||
"title": "Title image 2",
|
||||
"description": "Title image 1 description."
|
||||
}
|
||||
@ -77,7 +77,7 @@
|
||||
{
|
||||
"locale": 0,
|
||||
"alt": "...",
|
||||
"href": "/title-image-2",
|
||||
"target": "/title-image-2",
|
||||
"title": "Title image 2",
|
||||
"description": "Title image 1 description."
|
||||
}
|
||||
@ -129,7 +129,7 @@
|
||||
{
|
||||
"locale": 0,
|
||||
"alt": "...",
|
||||
"href": "/title-image-2",
|
||||
"target": "/title-image-2",
|
||||
"title": "Title image 2",
|
||||
"description": "Title image 1 description."
|
||||
}
|
||||
@ -181,7 +181,7 @@
|
||||
{
|
||||
"locale": 0,
|
||||
"alt": "...",
|
||||
"href": "/title-image-2",
|
||||
"target": "/title-image-2",
|
||||
"title": "Title image 2",
|
||||
"description": "Title image 1 description."
|
||||
}
|
||||
|
||||
@ -24,7 +24,7 @@ namespace Core.Abstractions.DomainObjects {
|
||||
|
||||
public List<string>? Tags { get; set; }
|
||||
|
||||
public List<Guid>? Categories { get; set; }
|
||||
public List<Guid> Categories { get; set; }
|
||||
|
||||
public bool? FamilyFriendly { get; set; }
|
||||
}
|
||||
|
||||
@ -0,0 +1,75 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
using DomainResults.Common;
|
||||
|
||||
using MongoDB.Driver;
|
||||
|
||||
using DataProviders.Abstractions;
|
||||
|
||||
namespace DataProviders.Buckets {
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class SpecificBucketDataProviderBase : BucketDataProviderBase {
|
||||
|
||||
private readonly string _bucketName;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="logger"></param>
|
||||
/// <param name="client"></param>
|
||||
public SpecificBucketDataProviderBase(
|
||||
ILogger<BucketDataProviderBase> logger,
|
||||
IMongoClient client,
|
||||
string bucketName
|
||||
) : base(logger, client) {
|
||||
_bucketName = bucketName;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="siteId"></param>
|
||||
/// <param name="userId"></param>
|
||||
/// <param name="file"></param>
|
||||
/// <returns></returns>
|
||||
public (Guid?, IDomainResult) Upload(Guid siteId, Guid userId, BucketFile file) => Upload(siteId, userId, file, _bucketName);
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="siteId"></param>
|
||||
/// <param name="userId"></param>
|
||||
/// <param name="files"></param>
|
||||
/// <returns></returns>
|
||||
public (List<Guid>?, IDomainResult) UploadMany(Guid siteId, Guid userId, List<BucketFile> files) => UploadMany(siteId, userId, files, _bucketName);
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="siteId"></param>
|
||||
/// <param name="userId"></param>
|
||||
/// <param name="fileId"></param>
|
||||
/// <returns></returns>
|
||||
public (BucketFile?, IDomainResult) Download(Guid siteId, Guid userId, Guid fileId) => Download(siteId, userId, fileId, _bucketName);
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="siteId"></param>
|
||||
/// <param name="userId"></param>
|
||||
/// <param name="fileId"></param>
|
||||
/// <returns></returns>
|
||||
public IDomainResult DeleteOne(Guid siteId, Guid userId, Guid fileId) => DeleteOne(siteId, userId, fileId, _bucketName);
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="siteId"></param>
|
||||
/// <param name="userId"></param>
|
||||
/// <returns></returns>
|
||||
public IDomainResult DeletMany(Guid siteId, Guid userId) => DeleteMany(siteId, userId, _bucketName);
|
||||
}
|
||||
}
|
||||
@ -1,9 +1,4 @@
|
||||
using DomainResults.Common;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace DataProviders.Buckets {
|
||||
/// <summary>
|
||||
|
||||
@ -11,9 +11,7 @@ namespace DataProviders.Buckets {
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class ImagesBucketDataProvider : BucketDataProviderBase, IBucketDataProvider {
|
||||
|
||||
private const string _bucketName = "images";
|
||||
public class ImagesBucketDataProvider : SpecificBucketDataProviderBase, IBucketDataProvider {
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
@ -23,50 +21,6 @@ namespace DataProviders.Buckets {
|
||||
public ImagesBucketDataProvider(
|
||||
ILogger<BucketDataProviderBase> logger,
|
||||
IMongoClient client
|
||||
) : base(logger, client) { }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="siteId"></param>
|
||||
/// <param name="userId"></param>
|
||||
/// <param name="file"></param>
|
||||
/// <returns></returns>
|
||||
public (Guid?, IDomainResult) Upload(Guid siteId, Guid userId, BucketFile file) => Upload(siteId, userId, file, _bucketName);
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="siteId"></param>
|
||||
/// <param name="userId"></param>
|
||||
/// <param name="files"></param>
|
||||
/// <returns></returns>
|
||||
public (List<Guid>?, IDomainResult) UploadMany(Guid siteId, Guid userId, List<BucketFile> files) => UploadMany(siteId, userId, files, _bucketName);
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="siteId"></param>
|
||||
/// <param name="userId"></param>
|
||||
/// <param name="fileId"></param>
|
||||
/// <returns></returns>
|
||||
public (BucketFile?, IDomainResult) Download(Guid siteId, Guid userId, Guid fileId) => Download(siteId, userId, fileId, _bucketName);
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="siteId"></param>
|
||||
/// <param name="userId"></param>
|
||||
/// <param name="fileId"></param>
|
||||
/// <returns></returns>
|
||||
public IDomainResult DeleteOne(Guid siteId, Guid userId, Guid fileId) => DeleteOne(siteId, userId, fileId, _bucketName);
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="siteId"></param>
|
||||
/// <param name="userId"></param>
|
||||
/// <returns></returns>
|
||||
public IDomainResult DeletMany(Guid siteId, Guid userId) => DeleteMany(siteId, userId, _bucketName);
|
||||
) : base(logger, client, "images") { }
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,12 +7,16 @@ using DomainResults.Common;
|
||||
|
||||
using DataProviders.Abstractions;
|
||||
using Core.DomainObjects.Documents;
|
||||
using Core.DomainObjects.L10n;
|
||||
using Core.Enumerations;
|
||||
|
||||
namespace DataProviders.Collections {
|
||||
|
||||
public interface ICategoryDataProvider {
|
||||
(Guid?, IDomainResult) Insert(Category obj);
|
||||
(Guid?, IDomainResult) CreateDefault(Guid siteId);
|
||||
(Category?, IDomainResult) Get(Guid siteId, Guid categoryId);
|
||||
(List<Category>?, IDomainResult) GetMany(Guid siteId, List<Guid> categoryIds);
|
||||
(Category?, IDomainResult) GetBySlug(Guid siteId, string slug);
|
||||
(List<Category>?, IDomainResult) GetBySlugs(Guid siteId, List<string> slugs);
|
||||
(List<Category>?, IDomainResult) GetAll(Guid siteId);
|
||||
@ -34,6 +38,24 @@ namespace DataProviders.Collections {
|
||||
public (Guid?, IDomainResult) Insert(Category obj) =>
|
||||
Insert(obj, _collectionName);
|
||||
|
||||
public (Guid?, IDomainResult) CreateDefault(Guid siteId) {
|
||||
var (list, result) = GetWithPredicate(x => x.SiteId == siteId && x.L10n.Any(y => y.Slug == "default"), _collectionName);
|
||||
|
||||
if (result.IsSuccess && list != null)
|
||||
return (list.First().Id, result);
|
||||
|
||||
return Insert(new Category {
|
||||
SiteId = siteId,
|
||||
L10n = new List<CategoryL10n> {
|
||||
new CategoryL10n {
|
||||
Locale = Locales.Us,
|
||||
Slug = "default",
|
||||
Text = "Default category"
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public (Category?, IDomainResult) Get(Guid siteId, Guid categoryId) {
|
||||
var (list, result) = GetWithPredicate(x => x.SiteId == siteId && x.Id == categoryId, _collectionName);
|
||||
|
||||
@ -43,6 +65,9 @@ namespace DataProviders.Collections {
|
||||
return (list.First(), result);
|
||||
}
|
||||
|
||||
public (List<Category>?, IDomainResult) GetMany(Guid siteId, List<Guid> categoryIds) =>
|
||||
GetWithPredicate(x => x.SiteId == siteId && categoryIds.Contains(x.Id), _collectionName);
|
||||
|
||||
public (Category?, IDomainResult) GetBySlug(Guid siteId, string slug) {
|
||||
var (list, result) = GetBySlugs(siteId, new List<string> { slug });
|
||||
|
||||
|
||||
86
webapi/Extensions/BytesExtensions.cs
Normal file
86
webapi/Extensions/BytesExtensions.cs
Normal file
@ -0,0 +1,86 @@
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace Extensions {
|
||||
|
||||
/// <summary>
|
||||
/// https://www.syncfusion.com/succinctly-free-ebooks/application-security-in-net-succinctly/comparing-byte-arrays
|
||||
/// </summary>
|
||||
public static class BytesExtensions {
|
||||
|
||||
/// <summary>
|
||||
/// Insecure direct comparison implements the fundamental BAC requirement, but it also makes a variable number of comparisons based on the position of the first unequal byte pair, which leaks timing information that can help mount effective guessing attacks to uncover the data. These types of attacks are known as side-channel attacks (the side channel in this case is timing). You should never use direct comparison with sensitive or secret data.
|
||||
/// </summary>
|
||||
/// <param name="a"></param>
|
||||
/// <param name="b"></param>
|
||||
/// <returns></returns>
|
||||
public static bool DirectComparison(byte[] a, byte[] b) {
|
||||
|
||||
if (a.Length != b.Length)
|
||||
return false;
|
||||
|
||||
for (int i = 0; i < a.Length; ++i) {
|
||||
if (a[i] != b[i])
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The AND comparison is similar to a direct comparison that keeps comparing byte pairs even after the result is determined to be false. It performs a fixed amount of work in every comparison, which protects against timing attacks. AND comparison is the BAC approach used by Microsoft within their internal CryptoUtil classes (System.Web.Security.Cryptography namespace—System.Web assembly and System.Web.Helpers namespace—System.Web.WebPages assembly).
|
||||
/// </summary>
|
||||
/// <param name="a"></param>
|
||||
/// <param name="b"></param>
|
||||
/// <returns></returns>
|
||||
public static bool AndComparison(byte[] a, byte[] b) {
|
||||
|
||||
bool f = a.Length == b.Length;
|
||||
|
||||
for (int i = 0; i < a.Length && i < b.Length; ++i) {
|
||||
|
||||
f &= a[i] == b[i];
|
||||
|
||||
}
|
||||
|
||||
return f;
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// XOR comparison exploits the fact that equal byte pairs XOR to zero and OR-ing all XOR-ed results together would only produce zero if all byte pairs are equal. The XOR approach might have a slight advantage over the AND approach because the XOR-OR combination is pure bit shifting, and is typically compiled into direct assembly equivalents, while Boolean AND combination can be compiled into conditional branching instructions, which might have different timings.
|
||||
/// </summary>
|
||||
/// <param name="a"></param>
|
||||
/// <param name="b"></param>
|
||||
/// <returns></returns>
|
||||
public static bool XORCcomparison(this byte[] a, byte[] b) {
|
||||
int x = a.Length ^ b.Length;
|
||||
|
||||
for (int i = 0; i < a.Length && i < b.Length; ++i) {
|
||||
x |= a[i] ^ b[i];
|
||||
}
|
||||
|
||||
return x == 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="a"></param>
|
||||
/// <param name="b"></param>
|
||||
/// <returns></returns>
|
||||
public static bool DoubleHMAC(byte[] a, byte[] b) {
|
||||
byte[] aHMAC, bHMAC;
|
||||
|
||||
//using var hmac = HMACFactories.HMACSHA1();
|
||||
|
||||
//// key unpredictability not required
|
||||
//hmac.Key = Guid.NewGuid().ToByteArray();
|
||||
//aHMAC = hmac.ComputeHash(a);
|
||||
//bHMAC = hmac.ComputeHash(b);
|
||||
|
||||
//return XORCcomparison(aHMAC, bHMAC) && XORCcomparison(a, b);
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,7 @@
|
||||
using Core.Enumerations;
|
||||
using DomainResults.Common;
|
||||
using ExtensionMethods;
|
||||
using Extensions;
|
||||
|
||||
namespace FileSecurityService {
|
||||
|
||||
@ -132,7 +133,6 @@ namespace FileSecurityService {
|
||||
}
|
||||
#endregion
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -166,14 +166,7 @@ namespace FileSecurityService {
|
||||
var sample = new byte[signBytes.Length];
|
||||
Buffer.BlockCopy(bytes, offset, sample, 0, signBytes.Length);
|
||||
|
||||
int x = sample.Length ^ signBytes.Length;
|
||||
|
||||
// https://www.syncfusion.com/succinctly-free-ebooks/application-security-in-net-succinctly/comparing-byte-arrays
|
||||
for (int i = 0; i < sample.Length && i < signBytes.Length; ++i) {
|
||||
x |= sample[i] ^ signBytes[i];
|
||||
}
|
||||
|
||||
if (x == 0) return IDomainResult.Success(data.MediaType);
|
||||
if (sample.XORCcomparison(signBytes)) return IDomainResult.Success(data.MediaType);
|
||||
}
|
||||
|
||||
return IDomainResult.Failed<MediaTypes?>();
|
||||
|
||||
@ -19,8 +19,10 @@ namespace WeatherForecast.Models.Abstractions {
|
||||
/// </summary>
|
||||
public List<PostItemL10nModel>? L10n { get; set; }
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public List<MediaAttachmentRequestModel>? MediaAttachments { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
|
||||
@ -51,7 +51,10 @@ namespace WeatherForecast.Models.Requests {
|
||||
/// <returns></returns>
|
||||
public override IEnumerable<ValidationResult> Validate(ValidationContext validationContext) {
|
||||
if (L10n.IsNullOrEmpty())
|
||||
yield return new ValidationResult($"{nameof(L10n)} ${Errors.NullOrEmpty}");
|
||||
yield return new ValidationResult($"{nameof(L10n)} {Errors.NullOrEmpty}");
|
||||
|
||||
if (MediaAttachments.IsNullOrEmpty())
|
||||
yield return new ValidationResult($"{nameof(MediaAttachments)} {Errors.NullOrEmpty}");
|
||||
}
|
||||
|
||||
private bool HasValidationErrors(BlogItemRequestModel model) => Validate(new ValidationContext(model)).Any();
|
||||
|
||||
@ -1,56 +0,0 @@
|
||||
using Core.Abstractions.Models;
|
||||
using Core.DomainObjects;
|
||||
using Core.Enumerations;
|
||||
using Extensions;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using WeatherForecast.Models.Requests.L10n;
|
||||
|
||||
namespace WeatherForecast.Models {
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class ImageRequestModel : RequestModelBase<MediaAttachment> {
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public List<ImageL10nModel>? L10n { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public string? Src { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public string? Alt { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="NotImplementedException"></exception>
|
||||
public override MediaAttachment ToDomainObject() {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="validationContext"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="NotImplementedException"></exception>
|
||||
public override IEnumerable<ValidationResult> Validate(ValidationContext validationContext) {
|
||||
if (L10n.IsNullOrEmpty())
|
||||
yield return new ValidationResult($"{nameof(L10n)} ${Errors.NullOrEmpty}");
|
||||
|
||||
if (string.IsNullOrWhiteSpace(Src))
|
||||
yield return new ValidationResult($"{nameof(Src)} ${Errors.NullOrWhiteSpace}");
|
||||
|
||||
if (string.IsNullOrWhiteSpace(Alt))
|
||||
yield return new ValidationResult($"{nameof(Alt)} ${Errors.NullOrWhiteSpace}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -10,7 +10,7 @@ namespace WeatherForecast.Models.Requests.L10n {
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class ImageL10nModel : RequestModelBase<MediaAttachmentL10n> {
|
||||
public class MediaAttachmentL10nModel : RequestModelBase<MediaAttachmentL10n> {
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
@ -22,6 +22,21 @@ namespace WeatherForecast.Models.Requests.L10n {
|
||||
/// </summary>
|
||||
public string? Alt { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public string? Target { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public string? Title { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public string? Description { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
@ -50,7 +50,7 @@ namespace WeatherForecast.Models.Requests.L10n {
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public string? ContentType { get; set; }
|
||||
public string? TextFormat { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
@ -75,7 +75,7 @@ namespace WeatherForecast.Models.Requests.L10n {
|
||||
// TODO: create plain text creation logic
|
||||
PlainText = "TODO",
|
||||
|
||||
TextFormat = Enumeration.FromDisplayName<TextFormat>(ContentType),
|
||||
TextFormat = Enumeration.FromDisplayName<TextFormat>(TextFormat),
|
||||
Badges = Badges
|
||||
};
|
||||
}
|
||||
@ -106,13 +106,13 @@ namespace WeatherForecast.Models.Requests.L10n {
|
||||
if (string.IsNullOrWhiteSpace(ShortText))
|
||||
yield return new ValidationResult($"{nameof(ShortText)} {Errors.NullOrWhiteSpace.Name}");
|
||||
|
||||
if (string.IsNullOrWhiteSpace(ContentType))
|
||||
yield return new ValidationResult($"{nameof(ContentType)} {Errors.NullOrWhiteSpace.Name}");
|
||||
else if (Enumeration.FromDisplayName<TextFormat>(ContentType) == null)
|
||||
yield return new ValidationResult($"{nameof(ContentType)} {Errors.WrongOrNotManaged}");
|
||||
if (string.IsNullOrWhiteSpace(TextFormat))
|
||||
yield return new ValidationResult($"{nameof(TextFormat)} {Errors.NullOrWhiteSpace.Name}");
|
||||
else if (Enumeration.FromDisplayName<TextFormat>(TextFormat) == null)
|
||||
yield return new ValidationResult($"{nameof(TextFormat)} {Errors.WrongOrNotManaged}");
|
||||
}
|
||||
|
||||
[MemberNotNullWhen(false, new[] { nameof(Locale), nameof(Slug), nameof(Description), nameof(Title), nameof(Text), nameof(ShortText), nameof(ContentType) })]
|
||||
[MemberNotNullWhen(false, new[] { nameof(Locale), nameof(Slug), nameof(Description), nameof(Title), nameof(Text), nameof(ShortText), nameof(TextFormat) })]
|
||||
private bool HasValidationErrors() => Validate(new ValidationContext(this)).Any();
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,69 @@
|
||||
using Core.Abstractions;
|
||||
using Core.Abstractions.Models;
|
||||
using Core.DomainObjects;
|
||||
using Core.DomainObjects.L10n;
|
||||
using Core.Enumerations;
|
||||
using Extensions;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using WeatherForecast.Models.Requests.L10n;
|
||||
|
||||
namespace WeatherForecast.Models {
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class MediaAttachmentRequestModel : RequestModelBase<MediaAttachment> {
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public string? Src { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public string? MediaType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public List<MediaAttachmentL10nModel>? L10n { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="NotImplementedException"></exception>
|
||||
public override MediaAttachment ToDomainObject() {
|
||||
if (HasValidationErrors(this))
|
||||
throw new ValidationException();
|
||||
|
||||
return new MediaAttachment {
|
||||
Src = Src,
|
||||
MediaType = Enumeration.FromDisplayName<MediaTypes>(MediaType),
|
||||
L10n = L10n.Select(x => x.ToDomainObject()).ToList()
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="validationContext"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="NotImplementedException"></exception>
|
||||
public override IEnumerable<ValidationResult> Validate(ValidationContext validationContext) {
|
||||
if (string.IsNullOrWhiteSpace(Src))
|
||||
yield return new ValidationResult($"{nameof(Src)} {Errors.NullOrWhiteSpace}");
|
||||
|
||||
if (string.IsNullOrWhiteSpace(MediaType))
|
||||
yield return new ValidationResult($"{nameof(MediaType)} {Errors.NullOrWhiteSpace}");
|
||||
|
||||
if (L10n.IsNullOrEmpty())
|
||||
yield return new ValidationResult($"{nameof(L10n)} {Errors.NullOrEmpty}");
|
||||
}
|
||||
|
||||
[MemberNotNullWhen(false, new[] { nameof(Src), nameof(MediaType), nameof(L10n) })]
|
||||
private bool HasValidationErrors(MediaAttachmentRequestModel model) => Validate(new ValidationContext(model)).Any();
|
||||
}
|
||||
}
|
||||
@ -37,7 +37,7 @@ namespace WeatherForecast.Models.Requests {
|
||||
/// <returns></returns>
|
||||
public override IEnumerable<ValidationResult> Validate(ValidationContext validationContext) {
|
||||
if (Quantity == null || (Quantity != null && Quantity == 0))
|
||||
yield return new ValidationResult($"{nameof(Quantity)} ${Errors.NullOrEmpty}");
|
||||
yield return new ValidationResult($"{nameof(Quantity)} {Errors.NullOrEmpty}");
|
||||
}
|
||||
|
||||
[MemberNotNullWhen(false, new[] { nameof(Quantity) })]
|
||||
|
||||
@ -71,16 +71,19 @@ namespace WeatherForecast.Models.Requests {
|
||||
/// <returns></returns>
|
||||
public override IEnumerable<ValidationResult> Validate(ValidationContext validationContext) {
|
||||
if (L10n.IsNullOrEmpty())
|
||||
yield return new ValidationResult($"{nameof(L10n)} ${Errors.NullOrEmpty}");
|
||||
yield return new ValidationResult($"{nameof(L10n)} {Errors.NullOrEmpty}");
|
||||
|
||||
if (MediaAttachments.IsNullOrEmpty())
|
||||
yield return new ValidationResult($"{nameof(MediaAttachments)} {Errors.NullOrEmpty}");
|
||||
|
||||
if (string.IsNullOrWhiteSpace(BrandName))
|
||||
yield return new ValidationResult($"{nameof(BrandName)} ${Errors.NullOrWhiteSpace}");
|
||||
yield return new ValidationResult($"{nameof(BrandName)} {Errors.NullOrWhiteSpace}");
|
||||
|
||||
if (Price == null || (Price != null && Price == 0))
|
||||
yield return new ValidationResult($"{nameof(Price)} {Errors.NullOrEmpty.Name}");
|
||||
|
||||
if (Quantity == null || (Quantity != null && Quantity == 0))
|
||||
yield return new ValidationResult($"{nameof(Quantity)} ${Errors.NullOrEmpty}");
|
||||
yield return new ValidationResult($"{nameof(Quantity)} {Errors.NullOrEmpty}");
|
||||
}
|
||||
|
||||
[MemberNotNullWhen(false, new[] { nameof(BrandName), nameof(Price), nameof(Quantity) })]
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
using Core.DomainObjects;
|
||||
using Core.Abstractions.Models;
|
||||
using Core.DomainObjects;
|
||||
using Core.Enumerations;
|
||||
using WeatherForecast.Models.Responses.L10n;
|
||||
|
||||
@ -7,7 +8,7 @@ namespace WeatherForecast.Models {
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class MediaAttachmentResponseModel {
|
||||
public class MediaAttachmentResponseModel : ResponseModelBase {
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
|
||||
@ -0,0 +1,46 @@
|
||||
using DataProviders.Collections;
|
||||
using DomainResults.Common;
|
||||
|
||||
namespace WeatherForecast.Services.Abstractions {
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public abstract class PostItemServiceBase {
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
protected readonly ICategoryDataProvider _categoryDataProvider;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="categoryDataProvider"></param>
|
||||
public PostItemServiceBase(
|
||||
ICategoryDataProvider categoryDataProvider
|
||||
) {
|
||||
_categoryDataProvider = categoryDataProvider;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="siteId"></param>
|
||||
/// <param name="categories"></param>
|
||||
/// <returns></returns>
|
||||
public (List<Guid>?, IDomainResult) AddCategoryIfNullOrEmpty(Guid siteId, List<Guid>? categories) {
|
||||
categories ??= new List<Guid>();
|
||||
|
||||
if (categories.Count == 0) {
|
||||
var (catId, catCreateResult) = _categoryDataProvider.CreateDefault(siteId);
|
||||
if (!catCreateResult.IsSuccess || catId == null)
|
||||
return IDomainResult.Failed<List<Guid>?>();
|
||||
|
||||
categories.Add(catId.Value);
|
||||
}
|
||||
|
||||
return IDomainResult.Success(categories);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -11,6 +11,7 @@ using Core.Enumerations;
|
||||
using WeatherForecast.Models.Requests;
|
||||
using WeatherForecast.Models.Responses;
|
||||
using DataProviders.Collections;
|
||||
using WeatherForecast.Services.Abstractions;
|
||||
|
||||
namespace WeatherForecast.Services {
|
||||
|
||||
@ -63,10 +64,9 @@ namespace WeatherForecast.Services {
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class BlogItemService : IBlogItemService {
|
||||
public class BlogItemService : PostItemServiceBase, IBlogItemService {
|
||||
private readonly ILogger<BlogItemService> _logger;
|
||||
private readonly IBlogCatalogDataProvider _blogCatalogDataProvider;
|
||||
private readonly ICategoryDataProvider _categoryDataProvider;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
@ -78,10 +78,9 @@ namespace WeatherForecast.Services {
|
||||
ILogger<BlogItemService> logger,
|
||||
IBlogCatalogDataProvider blogCatalogDataProvider,
|
||||
ICategoryDataProvider categoryDataProvider
|
||||
) {
|
||||
) : base(categoryDataProvider) {
|
||||
_logger = logger;
|
||||
_blogCatalogDataProvider = blogCatalogDataProvider;
|
||||
_categoryDataProvider = categoryDataProvider;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -103,25 +102,13 @@ namespace WeatherForecast.Services {
|
||||
// TODO: should be recovered from users by jwt
|
||||
item.Author = "fdc5aa50-ee68-4bae-a8e6-b8ae2c258f60".ToGuid();
|
||||
|
||||
// TODO: should be placed to object storage
|
||||
item.MediaAttachments = new List<MediaAttachment>() {
|
||||
new MediaAttachment {
|
||||
Src = "https://dummyimage.com/450x300/dee2e6/6c757d.jpg",
|
||||
L10n = new List<MediaAttachmentL10n> {
|
||||
new MediaAttachmentL10n {
|
||||
Locale = Locales.Us,
|
||||
Alt = "..."
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// TODO: default value shoud not be hardcoded by database id
|
||||
item.Categories ??= new List<Guid> { "e154e33f-3cc7-468d-bb66-e0390ddb9ae0".ToGuid() };
|
||||
var (categories, addCategoriesResult) = AddCategoryIfNullOrEmpty(siteId, item.Categories);
|
||||
if (!addCategoriesResult.IsSuccess || categories == null)
|
||||
return (null, addCategoriesResult);
|
||||
|
||||
item.Categories = categories;
|
||||
|
||||
var (id, insertResult) = _blogCatalogDataProvider.Insert(item);
|
||||
|
||||
if (!insertResult.IsSuccess)
|
||||
return IDomainResult.Failed<Guid?>();
|
||||
|
||||
@ -146,17 +133,9 @@ namespace WeatherForecast.Services {
|
||||
if (!result.IsSuccess || item == null)
|
||||
return (null, result);
|
||||
|
||||
var categories = new List<Category>();
|
||||
|
||||
if (item.Categories != null) {
|
||||
foreach (var catId in item.Categories) {
|
||||
var (category, getCategoryResult) = _categoryDataProvider.Get(siteId, catId);
|
||||
if (!getCategoryResult.IsSuccess || category == null)
|
||||
return (null, getCategoryResult);
|
||||
|
||||
categories.Add(category);
|
||||
}
|
||||
}
|
||||
var (categories, getCategoryResult) = _categoryDataProvider.GetMany(siteId, item.Categories);
|
||||
if (!getCategoryResult.IsSuccess || categories == null)
|
||||
return IDomainResult.Failed<BlogItemResponseModel?>();
|
||||
|
||||
return IDomainResult.Success(new BlogItemResponseModel(item, categories));
|
||||
}
|
||||
@ -181,17 +160,9 @@ namespace WeatherForecast.Services {
|
||||
|
||||
var locale = item.L10n.SingleOrDefault(x => x.Slug == slug)?.Locale ?? Locales.Us;
|
||||
|
||||
var categories = new List<Category>();
|
||||
|
||||
if (item.Categories != null) {
|
||||
foreach (var catId in item.Categories) {
|
||||
var (category, getCategoryResult) = _categoryDataProvider.Get(siteId, catId);
|
||||
if (!getCategoryResult.IsSuccess || category == null)
|
||||
return (null, getCategoryResult);
|
||||
|
||||
categories.Add(category);
|
||||
}
|
||||
}
|
||||
var (categories, getCategoryResult) = _categoryDataProvider.GetMany(siteId, item.Categories);
|
||||
if (!getCategoryResult.IsSuccess || categories == null)
|
||||
return IDomainResult.Failed<BlogItemResponseModel?>();
|
||||
|
||||
return IDomainResult.Success(new BlogItemResponseModel(item, categories, locale));
|
||||
}
|
||||
@ -223,6 +194,12 @@ namespace WeatherForecast.Services {
|
||||
newItem.Created = item.Created;
|
||||
newItem.Author = item.Author;
|
||||
|
||||
var (categories, addCategoriesResult) = AddCategoryIfNullOrEmpty(siteId, item.Categories);
|
||||
if (!addCategoriesResult.IsSuccess || categories == null)
|
||||
return (null, addCategoriesResult);
|
||||
|
||||
item.Categories = categories;
|
||||
|
||||
if (!item.Equals(newItem)) {
|
||||
var (id, updateResult) = _blogCatalogDataProvider.Update(newItem);
|
||||
if (!updateResult.IsSuccess || id == null)
|
||||
|
||||
@ -80,17 +80,9 @@ namespace WeatherForecast.Services {
|
||||
|
||||
var blogItems = new List<BlogItemResponseModel>();
|
||||
foreach (var item in items) {
|
||||
var categories = new List<Category>();
|
||||
|
||||
if (item.Categories != null) {
|
||||
foreach (var catId in item.Categories) {
|
||||
var (cat, getCategoryResult) = _categoryDataProvider.Get(siteId, catId);
|
||||
if (!getCategoryResult.IsSuccess || cat == null)
|
||||
return (null, getCategoryResult);
|
||||
|
||||
categories.Add(cat);
|
||||
}
|
||||
}
|
||||
var (categories, getCategoryResult) = _categoryDataProvider.GetMany(siteId, item.Categories);
|
||||
if (!getCategoryResult.IsSuccess || categories == null)
|
||||
return IDomainResult.Failed<BlogItemsResponseModel?>();
|
||||
|
||||
if (locale != null)
|
||||
blogItems.Add(new BlogItemResponseModel(item, categories, Enumeration.FromDisplayName<Locales>(locale) ?? Locales.Us));
|
||||
|
||||
@ -1,9 +1,6 @@
|
||||
using Core;
|
||||
using Core.Enumerations;
|
||||
using DataProviders;
|
||||
using DataProviders;
|
||||
using DataProviders.Buckets;
|
||||
using DomainResults.Common;
|
||||
using FileSecurityService;
|
||||
|
||||
namespace WeatherForecast.Services {
|
||||
|
||||
@ -46,22 +43,22 @@ namespace WeatherForecast.Services {
|
||||
public class FileService : IFileService {
|
||||
|
||||
private readonly ILogger<FilesService> _logger;
|
||||
private readonly IFileSecurityService _fileSecurityService;
|
||||
private readonly IFilesService _filesService;
|
||||
private readonly IBucketDataProvider _imageBucketDataProvider;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="logger"></param>
|
||||
/// <param name="fileSecurityService"></param>
|
||||
/// <param name="filesService"></param>
|
||||
/// <param name="imageBucketDataProvider"></param>
|
||||
public FileService(
|
||||
ILogger<FilesService> logger,
|
||||
IFileSecurityService fileSecurityService,
|
||||
IFilesService filesService,
|
||||
IBucketDataProvider imageBucketDataProvider
|
||||
) {
|
||||
_logger = logger;
|
||||
_fileSecurityService = fileSecurityService;
|
||||
_filesService = filesService;
|
||||
_imageBucketDataProvider = imageBucketDataProvider;
|
||||
}
|
||||
|
||||
@ -73,37 +70,12 @@ namespace WeatherForecast.Services {
|
||||
/// <param name="file"></param>
|
||||
/// <returns></returns>
|
||||
public (Guid?, IDomainResult) Post(Guid siteId, Guid userId, IFormFile file) {
|
||||
try {
|
||||
if (!(file.Length > 0))
|
||||
return IDomainResult.Failed<Guid?>();
|
||||
var (list, result) = _filesService.Post(siteId, userId, new List<IFormFile> { file });
|
||||
|
||||
using var ms = new MemoryStream();
|
||||
file.CopyTo(ms);
|
||||
var bytes = ms.ToArray();
|
||||
if (!result.IsSuccess || list == null)
|
||||
return (null, result);
|
||||
|
||||
var (mediaType, signatureResult) = _fileSecurityService.CheckFileSignature(file.FileName, bytes, file.ContentType);
|
||||
if(!signatureResult.IsSuccess || mediaType == null)
|
||||
return IDomainResult.Failed<Guid?>();
|
||||
|
||||
var bucketFile = new BucketFile(file.FileName, bytes, file.ContentType);
|
||||
|
||||
IDomainResult result;
|
||||
Guid? fileId;
|
||||
|
||||
if (mediaType == MediaTypes.Image)
|
||||
(fileId, result) = _imageBucketDataProvider.Upload(siteId, userId, bucketFile);
|
||||
else
|
||||
return IDomainResult.Failed<Guid?>();
|
||||
|
||||
if (!result.IsSuccess || fileId == null)
|
||||
return IDomainResult.Failed<Guid?>();
|
||||
|
||||
return IDomainResult.Success(fileId);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
_logger.LogError(ex, "Unhandled exception");
|
||||
return IDomainResult.Failed<Guid?>(ex.Message);
|
||||
}
|
||||
return (list.First(), result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@ -1,17 +1,14 @@
|
||||
using DomainResults.Common;
|
||||
|
||||
using DataProviders;
|
||||
|
||||
using ExtensionMethods;
|
||||
|
||||
using Core.DomainObjects;
|
||||
using Core.DomainObjects.L10n;
|
||||
using Core.Enumerations;
|
||||
|
||||
using WeatherForecast.Models;
|
||||
using WeatherForecast.Models.Requests;
|
||||
using DataProviders.Collections;
|
||||
using Core.DomainObjects.Documents;
|
||||
using WeatherForecast.Services.Abstractions;
|
||||
|
||||
namespace WeatherForecast.Services {
|
||||
|
||||
@ -66,11 +63,10 @@ namespace WeatherForecast.Services {
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class ShopItemService : IShopItemService {
|
||||
public class ShopItemService : PostItemServiceBase, IShopItemService {
|
||||
|
||||
private readonly ILogger<ShopItemService> _logger;
|
||||
private readonly IShopCatalogDataProvider _shopCatalogDataProvider;
|
||||
private readonly ICategoryDataProvider _categoryDataProvider;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
@ -82,10 +78,10 @@ namespace WeatherForecast.Services {
|
||||
ILogger<ShopItemService> logger,
|
||||
IShopCatalogDataProvider shopCatalogDataProvider,
|
||||
ICategoryDataProvider categoryDataProvider
|
||||
) {
|
||||
) : base(categoryDataProvider) {
|
||||
_logger = logger;
|
||||
_shopCatalogDataProvider = shopCatalogDataProvider;
|
||||
_categoryDataProvider = categoryDataProvider;
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -109,21 +105,11 @@ namespace WeatherForecast.Services {
|
||||
// TODO: should be recovered from users by jwt
|
||||
item.Author = "fdc5aa50-ee68-4bae-a8e6-b8ae2c258f60".ToGuid();
|
||||
|
||||
// TODO: should be placed to object storage
|
||||
item.MediaAttachments = new List<MediaAttachment>() {
|
||||
new MediaAttachment {
|
||||
Src = "https://dummyimage.com/450x300/dee2e6/6c757d.jpg",
|
||||
L10n = new List<MediaAttachmentL10n> {
|
||||
new MediaAttachmentL10n {
|
||||
Locale = Locales.Us,
|
||||
Alt = "..."
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
var (categories, addCategoriesResult) = AddCategoryIfNullOrEmpty(siteId, item.Categories);
|
||||
if (!addCategoriesResult.IsSuccess || categories == null)
|
||||
return (null, addCategoriesResult);
|
||||
|
||||
// TODO: default value shoud not be hardcoded by database id
|
||||
item.Categories ??= new List<Guid> { "e154e33f-3cc7-468d-bb66-e0390ddb9ae0".ToGuid() };
|
||||
item.Categories = categories;
|
||||
|
||||
var (id, insertResult) = _shopCatalogDataProvider.Insert(item);
|
||||
|
||||
@ -136,8 +122,6 @@ namespace WeatherForecast.Services {
|
||||
_logger.LogError(ex, "Unhandled exception");
|
||||
return IDomainResult.Failed<Guid?>(ex.Message);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -153,16 +137,9 @@ namespace WeatherForecast.Services {
|
||||
if (!result.IsSuccess || item == null)
|
||||
return (null, result);
|
||||
|
||||
var categories = new List<Category>();
|
||||
if (item.Categories != null) {
|
||||
foreach (var catId in item.Categories) {
|
||||
var (category, getCategoryResult) = _categoryDataProvider.Get(siteId, catId);
|
||||
if (!getCategoryResult.IsSuccess || category == null)
|
||||
return (null, getCategoryResult);
|
||||
|
||||
categories.Add(category);
|
||||
}
|
||||
}
|
||||
var (categories, getCategoryResult) = _categoryDataProvider.GetMany(siteId, item.Categories);
|
||||
if (!getCategoryResult.IsSuccess || categories == null)
|
||||
return IDomainResult.Failed<ShopItemResponseModel?>();
|
||||
|
||||
return IDomainResult.Success(new ShopItemResponseModel(item, categories));
|
||||
}
|
||||
@ -187,16 +164,9 @@ namespace WeatherForecast.Services {
|
||||
|
||||
var locale = item.L10n.SingleOrDefault(x => x.Slug == slug)?.Locale ?? Locales.Us;
|
||||
|
||||
var categories = new List<Category>();
|
||||
if (item.Categories != null) {
|
||||
foreach (var catId in item.Categories) {
|
||||
var (category, getCategoryResult) = _categoryDataProvider.Get(siteId, catId);
|
||||
if (!getCategoryResult.IsSuccess || category == null)
|
||||
return (null, getCategoryResult);
|
||||
|
||||
categories.Add(category);
|
||||
}
|
||||
}
|
||||
var (categories, getCategoryResult) = _categoryDataProvider.GetMany(siteId, item.Categories);
|
||||
if (!getCategoryResult.IsSuccess || categories == null)
|
||||
return IDomainResult.Failed<ShopItemResponseModel?>();
|
||||
|
||||
return IDomainResult.Success(new ShopItemResponseModel(item, categories, locale));
|
||||
}
|
||||
@ -227,6 +197,12 @@ namespace WeatherForecast.Services {
|
||||
newItem.Created = item.Created;
|
||||
newItem.Author = item.Author;
|
||||
|
||||
var (categories, addCategoriesResult) = AddCategoryIfNullOrEmpty(siteId, item.Categories);
|
||||
if (!addCategoriesResult.IsSuccess || categories == null)
|
||||
return (null, addCategoriesResult);
|
||||
|
||||
item.Categories = categories;
|
||||
|
||||
if (!item.Equals(newItem)) {
|
||||
var (id, updateResult) = _shopCatalogDataProvider.Update(newItem);
|
||||
if (!updateResult.IsSuccess || id == null)
|
||||
|
||||
@ -83,18 +83,11 @@ namespace WeatherForecast.Services {
|
||||
var shopItems = new List<ShopItemResponseModel>();
|
||||
foreach (var item in items) {
|
||||
|
||||
var categories = new List<Category>();
|
||||
if (item.Categories != null) {
|
||||
foreach (var catId in item.Categories) {
|
||||
var (cat, getCategoryResult) = _categoryDataProvider.Get(siteId, catId);
|
||||
if (!getCategoryResult.IsSuccess || cat == null)
|
||||
return (null, getCategoryResult);
|
||||
var (categories, getCategoryResult) = _categoryDataProvider.GetMany(siteId, item.Categories);
|
||||
if (!getCategoryResult.IsSuccess || categories == null)
|
||||
return IDomainResult.Failed<ShopItemsResponseModel?>();
|
||||
|
||||
categories.Add(cat);
|
||||
}
|
||||
}
|
||||
|
||||
if(locale != null)
|
||||
if (locale != null)
|
||||
shopItems.Add(new ShopItemResponseModel(item, categories, Enumeration.FromDisplayName<Locales>(locale) ?? Locales.Us));
|
||||
else
|
||||
shopItems.Add(new ShopItemResponseModel(item, categories));
|
||||
|
||||
Loading…
Reference in New Issue
Block a user