(refactor): dkim data provider file retrieval

This commit is contained in:
Maksym Sadovnychyy 2023-01-22 00:27:39 +01:00
parent 9e59ca7f30
commit 44a4b7a5e9
85 changed files with 1436 additions and 452 deletions

View File

@ -0,0 +1,10 @@
smtpService.Connect("smtp.ionos.it", 465, true);
smtpService.Authenticate("commercial@maks-it.com", "E23{R#<X&#Lyz");
Company Inc, 3 Abbey Road, San Francisco CA 94102
Don't like these emails? Unsubscribe.
Powered by MAKS-IT.com.

View File

@ -0,0 +1,152 @@
<!doctype html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Simple Transactional Email</title>
<style>
@media only screen and (max-width: 620px) {
table.body h1 {
font-size: 28px !important;
margin-bottom: 10px !important;
}
table.body p,
table.body ul,
table.body ol,
table.body td,
table.body span,
table.body a {
font-size: 16px !important;
}
table.body .wrapper,
table.body .article {
padding: 10px !important;
}
table.body .content {
padding: 0 !important;
}
table.body .container {
padding: 0 !important;
width: 100% !important;
}
table.body .main {
border-left-width: 0 !important;
border-radius: 0 !important;
border-right-width: 0 !important;
}
table.body .btn table {
width: 100% !important;
}
table.body .btn a {
width: 100% !important;
}
table.body .img-responsive {
height: auto !important;
max-width: 100% !important;
width: auto !important;
}
}
@media all {
.ExternalClass {
width: 100%;
}
.ExternalClass,
.ExternalClass p,
.ExternalClass span,
.ExternalClass font,
.ExternalClass td,
.ExternalClass div {
line-height: 100%;
}
.apple-link a {
color: inherit !important;
font-family: inherit !important;
font-size: inherit !important;
font-weight: inherit !important;
line-height: inherit !important;
text-decoration: none !important;
}
#MessageViewBody a {
color: inherit;
text-decoration: none;
font-size: inherit;
font-family: inherit;
font-weight: inherit;
line-height: inherit;
}
.btn-primary table td:hover {
background-color: #34495e !important;
}
.btn-primary a:hover {
background-color: #34495e !important;
border-color: #34495e !important;
}
}
</style>
</head>
<body style="background-color: #f6f6f6; font-family: sans-serif; -webkit-font-smoothing: antialiased; font-size: 14px; line-height: 1.4; margin: 0; padding: 0; -ms-text-size-adjust: 100%; -webkit-text-size-adjust: 100%;">
<span class="preheader" style="color: transparent; display: none; height: 0; max-height: 0; max-width: 0; opacity: 0; overflow: hidden; mso-hide: all; visibility: hidden; width: 0;">This is preheader text. Some clients will show this text as a preview.</span>
<table role="presentation" border="0" cellpadding="0" cellspacing="0" class="body" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; background-color: #f6f6f6; width: 100%;" width="100%" bgcolor="#f6f6f6">
<tr>
<td style="font-family: sans-serif; font-size: 14px; vertical-align: top;" valign="top">&nbsp;</td>
<td class="container" style="font-family: sans-serif; font-size: 14px; vertical-align: top; display: block; max-width: 580px; padding: 10px; width: 580px; margin: 0 auto;" width="580" valign="top">
<div class="content" style="box-sizing: border-box; display: block; margin: 0 auto; max-width: 580px; padding: 10px;">
<!-- START CENTERED WHITE CONTAINER -->
<table role="presentation" class="main" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; background: #ffffff; border-radius: 3px; width: 100%;" width="100%">
<!-- START MAIN CONTENT AREA -->
<tr>
<td class="wrapper" style="font-family: sans-serif; font-size: 14px; vertical-align: top; box-sizing: border-box; padding: 20px;" valign="top">
<table role="presentation" border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%;" width="100%">
<tr>
<td style="font-family: sans-serif; font-size: 14px; vertical-align: top;" valign="top">
{{content}}
</td>
</tr>
</table>
</td>
</tr>
<!-- END MAIN CONTENT AREA -->
</table>
<!-- END CENTERED WHITE CONTAINER -->
<!-- START FOOTER -->
<div class="footer" style="clear: both; margin-top: 10px; text-align: center; width: 100%;">
<table role="presentation" border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%;" width="100%">
<tr>
<td class="content-block" style="font-family: sans-serif; vertical-align: top; padding-bottom: 10px; padding-top: 10px; color: #999999; font-size: 12px; text-align: center;" valign="top" align="center">
<span class="apple-link" style="color: #999999; font-size: 12px; text-align: center;">{{address}}</span>
<br> {{usubscribeText}} <a href="https://maks-it.com" style="text-decoration: underline; color: #999999; font-size: 12px; text-align: center;">{{unsubscribe}}</a>.
</td>
</tr>
<tr>
<td class="content-block powered-by" style="font-family: sans-serif; vertical-align: top; padding-bottom: 10px; padding-top: 10px; color: #999999; font-size: 12px; text-align: center;" valign="top" align="center">
Powered by <a href="https://maks-it.com" style="color: #999999; font-size: 12px; text-align: center; text-decoration: none;">MAKS-IT.com</a>.
</td>
</tr>
</table>
</div>
<!-- END FOOTER -->
</div>
</td>
<td style="font-family: sans-serif; font-size: 14px; vertical-align: top;" valign="top">&nbsp;</td>
</tr>
</table>
</body>
</html>

View File

@ -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;
}
}

View File

@ -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 {
/// <summary>
///
/// </summary>
/// <typeparam name="T"></typeparam>
public abstract class CollectionDataProviderBase<T> : DataProviderBase<CollectionDataProviderBase<T>> where T : DomainObjectDocumentBase<T> {
private protected readonly IIdGenerator _idGenerator;
private protected readonly ISessionService _sessionService;
private protected readonly IMongoCollection<T> _collection;
/// <summary>
/// Main constructor
/// </summary>
/// <param name="logger"></param>
/// <param name="client"></param>
/// <param name="idGenerator"></param>
/// <param name="dataProviderUtils"></param>
public CollectionDataProviderBase(
ILogger<CollectionDataProviderBase<T>> 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<T>(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<Guid?>();
}
}
#endregion
#region InsertMany
private protected (List<Guid>?, IDomainResult) InsertMany(List<T> objList) =>
InsertManyAsync(objList).Result;
private protected (List<Guid>?, IDomainResult) InsertMany(List<T> objList, Guid sessionId) =>
InsertManyAsync(objList, sessionId).Result;
private protected Task<(List<Guid>?, IDomainResult)> InsertManyAsync(List<T> objList) =>
InsertManyAsync(objList, null);
private protected Task<(List<Guid>?, IDomainResult)> InsertManyAsync(List<T> objList, Guid sessionId) =>
InsertManyAsync(objList, sessionId);
protected virtual async Task<(List<Guid>?, IDomainResult)> InsertManyAsync(List<T> 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<List<Guid>?>();
}
}
#endregion
#region Count
private protected (int?, IDomainResult) CountWithPredicate(Expression<Func<T, bool>> predicate) =>
CountWithPredicate(new List<Expression<Func<T, bool>>> { predicate });
private protected (int?, IDomainResult) CountWithPredicate(List<Expression<Func<T, bool>>> 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<int?>();
}
}
#endregion
#region Get
/// <summary>
///
/// </summary>
/// <typeparam name="TResult"></typeparam>
/// <param name="predicate"></param>
/// <param name="selector"></param>
/// <returns></returns>
private protected (List<TResult>?, IDomainResult) GetWithPredicate<TResult>(Expression<Func<T, bool>> predicate, Expression<Func<T, TResult>> selector) =>
GetWithPredicate(new List<Expression<Func<T, bool>>> { predicate }, selector, null, null);
private protected (List<TResult>?, IDomainResult) GetWithPredicate<TResult>(Expression<Func<T, bool>> predicates, Expression<Func<T, TResult>> selector, int? skip, int? limit) =>
GetWithPredicate(new List<Expression<Func<T, bool>>> { predicates }, selector, skip, limit);
/// <summary>
///
/// </summary>
/// <typeparam name="TResult"></typeparam>
/// <param name="predicates"></param>
/// <param name="selector"></param>
/// <param name="skip"></param>
/// <param name="limit"></param>
/// <returns></returns>
private protected (List<TResult>?, IDomainResult) GetWithPredicate<TResult>(List<Expression<Func<T, bool>>> predicates, Expression<Func<T, TResult>> 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<List<TResult>?>();
}
catch (Exception ex) {
_logger.LogError(ex, "Data provider error");
return IDomainResult.Failed<List<TResult>?>();
}
}
/// <summary>
///
/// </summary>
/// <param name="predicates"></param>
/// <returns></returns>
private protected IQueryable<T> GetWithPredicate(List<Expression<Func<T, bool>>> predicates) {
var query = GetQuery();
foreach (var predicate in predicates)
query = query.Where(predicate);
return query;
}
/// <summary>
///
/// </summary>
/// <returns></returns>
protected virtual IQueryable<T> GetQuery() => _collection.AsQueryable();
#endregion
#region Update
private protected (Guid?, IDomainResult) UpdateWithPredicate(T obj, Expression<Func<T, bool>> predicate) =>
UpdateWithPredicateAsync(obj, predicate).Result;
private protected (Guid?, IDomainResult) UpdateWithPredicate(T obj, Expression<Func<T, bool>> predicate, Guid sessionId) =>
UpdateWithPredicateAsync(obj, predicate, sessionId).Result;
private protected Task<(Guid?, IDomainResult)> UpdateWithPredicateAsync(T obj, Expression<Func<T, bool>> predicate) =>
UpdateWithPredicateAsync(obj, predicate, null);
private protected Task<(Guid?, IDomainResult)> UpdateWithPredicateAsync(T obj, Expression<Func<T, bool>> predicate, Guid sessionId) =>
UpdateWithPredicateAsync(obj, predicate, sessionId);
protected virtual async Task<(Guid?, IDomainResult)> UpdateWithPredicateAsync(T obj, Expression<Func<T, bool>> 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<Guid?>();
}
}
#endregion
#region Delete
private protected IDomainResult DeleteWithPredicate(Expression<Func<T, bool>> predicate) =>
DeleteWithPredicateAsync(predicate).Result;
private protected IDomainResult DeleteWithPredicate(Expression<Func<T, bool>> predicate, Guid sessionId) =>
DeleteWithPredicateAsync(predicate, sessionId).Result;
private protected Task<IDomainResult> DeleteWithPredicateAsync(Expression<Func<T, bool>> predicate) =>
DeleteWithPredicateAsync(predicate, null);
private protected Task<IDomainResult> DeleteWithPredicateAsync(Expression<Func<T, bool>> predicate, Guid sessionId) =>
DeleteWithPredicateAsync(predicate, sessionId);
protected virtual async Task<IDomainResult> DeleteWithPredicateAsync(Expression<Func<T, bool>> predicate, Guid? sessionId) {
try {
if (sessionId != null)
await _collection.DeleteOneAsync<T>(_sessionService.GetSession(sessionId.Value), predicate);
else
await _collection.DeleteOneAsync<T>(predicate);
return IDomainResult.Success();
}
catch (Exception ex) {
_logger.LogError(ex, "Data provider error");
return IDomainResult.Failed();
}
}
#endregion
#region DeleteMany
private protected IDomainResult DeleteManyWithPredicate(Expression<Func<T, bool>> predicate) =>
DeleteManyWithPredicateAsync(predicate).Result;
private protected IDomainResult DeleteManyWithPredicate(Expression<Func<T, bool>> predicate, Guid sessionId) =>
DeleteManyWithPredicateAsync(predicate, sessionId).Result;
private protected Task<IDomainResult> DeleteManyWithPredicateAsync(Expression<Func<T, bool>> predicate) =>
DeleteManyWithPredicateAsync(predicate, null);
private protected Task<IDomainResult> DeleteManyWithPredicateAsync(Expression<Func<T, bool>> predicate, Guid sessionId) =>
DeleteManyWithPredicateAsync(predicate, sessionId);
protected virtual async Task<IDomainResult> DeleteManyWithPredicateAsync(Expression<Func<T, bool>> 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
}
}

View File

@ -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 {
/// <summary>
///
/// </summary>
/// <typeparam name="T"></typeparam>
public abstract class BucketDataProviderBase : DataProviderBase<BucketDataProviderBase> {
/// <summary>
///
/// </summary>
/// <typeparam name="T"></typeparam>
public abstract class BucketDataProviderBase : DataProviderBase<BucketDataProviderBase> {
private readonly GridFSBucket _bucket;
protected readonly GridFSBucket _bucket;
/// <summary>
///
@ -27,8 +26,9 @@ namespace DataProviders.Abstractions
public BucketDataProviderBase(
ILogger<BucketDataProviderBase> 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<Guid>?, IDomainResult) UploadMany(List<BucketFile> files) =>
UploadManyAsync(files).Result;
public async Task<(List<Guid>?, IDomainResult)> UploadManyAsync(List<BucketFile> files) {
protected virtual async Task<(List<Guid>?, IDomainResult)> UploadManyAsync(List<BucketFile> files) {
var options = new GridFSUploadOptions {
ChunkSizeBytes = 64512, // 63KB
};
@ -63,11 +63,8 @@ namespace DataProviders.Abstractions
try {
var result = new List<Guid>();
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<BucketFile>?, IDomainResult) Download(FilterDefinition<GridFSFileInfo> filter) =>
DownloadAsync(filter).Result;
private protected async Task<(List<BucketFile>?, IDomainResult)> DownloadAsync(FilterDefinition<GridFSFileInfo> filter) {
protected virtual async Task<(List<BucketFile>?, IDomainResult)> DownloadAsync(FilterDefinition<GridFSFileInfo> filter) {
try {
var sort = Builders<GridFSFileInfo>.Sort.Descending(x => x.UploadDateTime);
@ -107,19 +105,17 @@ namespace DataProviders.Abstractions
return IDomainResult.NotFound<List<BucketFile>?>();
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<List<BucketFile>?>();
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

View File

@ -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;
}
}
}

View File

@ -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 {
/// <summary>
///
/// </summary>
/// <param name="siteId"></param>
/// <param name="userId"></param>
/// <param name="file"></param>
/// <returns></returns>
(Guid?, IDomainResult) Upload(BucketFile file);
/// <summary>
///
/// </summary>
/// <param name="siteId"></param>
/// <param name="fileId"></param>
/// <returns></returns>
(BucketFile?, IDomainResult) Download(Guid siteId, Guid fileId);
}
public class DkimBucketDataProvider : BucketDataProviderBase, IDkimBucketDataProvider {
/// <summary>
///
/// </summary>
/// <param name="logger"></param>
/// <param name="client"></param>
public DkimBucketDataProvider(
ILogger<BucketDataProviderBase> logger,
IMongoClient client
) : base(logger, client, "reactredux", "dkims") { }
/// <summary>
///
/// </summary>
/// <param name="siteId"></param>
/// <param name="fileId"></param>
/// <returns></returns>
public (BucketFile?, IDomainResult) Download(Guid siteId, Guid fileId) {
var filter = Builders<GridFSFileInfo>.Filter.And(
Builders<GridFSFileInfo>.Filter.Eq(x => x.Metadata["siteId"], $"{siteId}"),
Builders<GridFSFileInfo>.Filter.Eq(x => x.Filename, $"{fileId}")
);
var (list, result) = Download(filter);
if (!result.IsSuccess || list == null)
return (null, result);
return (list.First(), result);
}
}
}

View File

@ -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 {
/// <summary>
///
/// </summary>
public interface IImageBucketDataProvider {
/// <summary>
///
/// </summary>
public interface IImageBucketDataProvider {
/// <summary>
///
@ -84,9 +84,41 @@ namespace DataProviders.Buckets
public ImageBucketDataProvider(
ILogger<BucketDataProviderBase> logger,
IMongoClient client
) : base(logger, client, "images") { }
) : base(logger, client, "reactredux", "images") { }
#region Upload many
protected override async Task<(List<Guid>?, IDomainResult)> UploadManyAsync(List<BucketFile> files) {
var options = new GridFSUploadOptions {
ChunkSizeBytes = 64512, // 63KB
};
try {
var result = new List<Guid>();
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<List<Guid>?>();
}
catch (Exception ex) {
_logger.LogError(ex, "Bucket data provider error");
return IDomainResult.Failed<List<Guid>?>();
}
}
#endregion
#region Download
/// <summary>
///
/// </summary>
@ -130,7 +162,9 @@ namespace DataProviders.Buckets
return (list.First(), result);
}
#endregion
#region Download many
/// <summary>
///
/// </summary>
@ -152,11 +186,58 @@ namespace DataProviders.Buckets
);
}
if (filter == null)
return IDomainResult.Failed<List<BucketFile>?>();
var result = Download(filter);
return result;
}
protected override async Task<(List<BucketFile>?, IDomainResult)> DownloadAsync(FilterDefinition<GridFSFileInfo> filter) {
try {
var sort = Builders<GridFSFileInfo>.Sort.Descending(x => x.UploadDateTime);
using var cursor = await _bucket.FindAsync(filter, new GridFSFindOptions {
Sort = sort,
});
var result = new List<BucketFile>();
// fileInfo either has the matching file information or is null
foreach (var fileInfo in await cursor.ToListAsync()) {
if (fileInfo == null)
return IDomainResult.NotFound<List<BucketFile>?>();
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<List<BucketFile>?>();
result.Add(new BucketFile(id, siteId.Value, userId.Value, published, fileName, bytes, contentType));
}
return result.Count > 0
? IDomainResult.Success(result)
: IDomainResult.NotFound<List<BucketFile>?>();
}
catch (Exception ex) {
_logger.LogError(ex, "Bucket data provider error");
return IDomainResult.Failed<List<BucketFile>?>();
}
}
#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<GridFSFileInfo>.Filter.Eq(x => x.Metadata["siteId"], $"{siteId}"),
Builders<GridFSFileInfo>.Filter.Eq(x => x.Metadata["userId"], $"{userId}")
));
#endregion
}
}

View File

@ -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 {
/// <summary>
///
/// </summary>
public interface ITemplateBucketDataProvider {
/// <summary>
///
/// </summary>
/// <param name="siteId"></param>
/// <param name="userId"></param>
/// <param name="file"></param>
/// <returns></returns>
(Guid?, IDomainResult) Upload(BucketFile file);
/// <summary>
///
/// </summary>
/// <param name="siteId"></param>
/// <param name="fileId"></param>
/// <returns></returns>
(BucketFile?, IDomainResult) Download(Guid siteId, Guid fileId);
}
public class TemplateBucketDataProvider : BucketDataProviderBase, ITemplateBucketDataProvider {
public TemplateBucketDataProvider(
ILogger<BucketDataProviderBase> logger,
IMongoClient client
) : base(logger, client, "reactredux", "templates") { }
public (BucketFile?, IDomainResult) Download(Guid siteId, Guid fileId) {
var filter = Builders<GridFSFileInfo>.Filter.And(
Builders<GridFSFileInfo>.Filter.Eq(x => x.Metadata["siteId"], $"{siteId}"),
Builders<GridFSFileInfo>.Filter.Eq(x => x.Filename, $"{fileId}")
);
var (list, result) = Download(filter);
if (!result.IsSuccess || list == null)
return (null, result);
return (list.First(), result);
}
}
}

View File

@ -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
{
/// <summary>
///
/// </summary>
/// <typeparam name="T"></typeparam>
public abstract class CollectionDataProviderBase<T> : DataProviderBase<CollectionDataProviderBase<T>> where T : DomainObjectDocumentBase<T>
{
private protected readonly IIdGenerator _idGenerator;
private protected readonly ISessionService _sessionService;
private protected readonly IMongoCollection<T> _collection;
/// <summary>
/// Main constructor
/// </summary>
/// <param name="logger"></param>
/// <param name="client"></param>
/// <param name="idGenerator"></param>
/// <param name="dataProviderUtils"></param>
public CollectionDataProviderBase(
ILogger<CollectionDataProviderBase<T>> 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<T>(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<Guid?>();
}
}
#endregion
#region InsertMany
private protected (List<Guid>?, IDomainResult) InsertMany(List<T> objList) =>
InsertManyAsync(objList).Result;
private protected (List<Guid>?, IDomainResult) InsertMany(List<T> objList, Guid sessionId) =>
InsertManyAsync(objList, sessionId).Result;
private protected Task<(List<Guid>?, IDomainResult)> InsertManyAsync(List<T> objList) =>
InsertManyAsync(objList, null);
private protected Task<(List<Guid>?, IDomainResult)> InsertManyAsync(List<T> objList, Guid sessionId) =>
InsertManyAsync(objList, sessionId);
protected virtual async Task<(List<Guid>?, IDomainResult)> InsertManyAsync(List<T> 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<List<Guid>?>();
}
}
#endregion
#region Count
private protected (int?, IDomainResult) CountWithPredicate(Expression<Func<T, bool>> predicate) =>
CountWithPredicate(new List<Expression<Func<T, bool>>> { predicate });
private protected (int?, IDomainResult) CountWithPredicate(List<Expression<Func<T, bool>>> 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<int?>();
}
}
#endregion
#region Get
/// <summary>
///
/// </summary>
/// <typeparam name="TResult"></typeparam>
/// <param name="predicate"></param>
/// <param name="selector"></param>
/// <returns></returns>
private protected (List<TResult>?, IDomainResult) GetWithPredicate<TResult>(Expression<Func<T, bool>> predicate, Expression<Func<T, TResult>> selector) =>
GetWithPredicate(new List<Expression<Func<T, bool>>> { predicate }, selector, null, null);
private protected (List<TResult>?, IDomainResult) GetWithPredicate<TResult>(Expression<Func<T, bool>> predicates, Expression<Func<T, TResult>> selector, int? skip, int? limit) =>
GetWithPredicate(new List<Expression<Func<T, bool>>> { predicates }, selector, skip, limit);
/// <summary>
///
/// </summary>
/// <typeparam name="TResult"></typeparam>
/// <param name="predicates"></param>
/// <param name="selector"></param>
/// <param name="skip"></param>
/// <param name="limit"></param>
/// <returns></returns>
private protected (List<TResult>?, IDomainResult) GetWithPredicate<TResult>(List<Expression<Func<T, bool>>> predicates, Expression<Func<T, TResult>> 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<List<TResult>?>();
}
catch (Exception ex)
{
_logger.LogError(ex, "Data provider error");
return IDomainResult.Failed<List<TResult>?>();
}
}
/// <summary>
///
/// </summary>
/// <param name="predicates"></param>
/// <returns></returns>
private protected IQueryable<T> GetWithPredicate(List<Expression<Func<T, bool>>> predicates)
{
var query = GetQuery();
foreach (var predicate in predicates)
query = query.Where(predicate);
return query;
}
/// <summary>
///
/// </summary>
/// <returns></returns>
protected virtual IQueryable<T> GetQuery() => _collection.AsQueryable();
#endregion
#region Update
private protected (Guid?, IDomainResult) UpdateWithPredicate(T obj, Expression<Func<T, bool>> predicate) =>
UpdateWithPredicateAsync(obj, predicate).Result;
private protected (Guid?, IDomainResult) UpdateWithPredicate(T obj, Expression<Func<T, bool>> predicate, Guid sessionId) =>
UpdateWithPredicateAsync(obj, predicate, sessionId).Result;
private protected Task<(Guid?, IDomainResult)> UpdateWithPredicateAsync(T obj, Expression<Func<T, bool>> predicate) =>
UpdateWithPredicateAsync(obj, predicate, null);
private protected Task<(Guid?, IDomainResult)> UpdateWithPredicateAsync(T obj, Expression<Func<T, bool>> predicate, Guid sessionId) =>
UpdateWithPredicateAsync(obj, predicate, sessionId);
protected virtual async Task<(Guid?, IDomainResult)> UpdateWithPredicateAsync(T obj, Expression<Func<T, bool>> 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<Guid?>();
}
}
#endregion
#region Delete
private protected IDomainResult DeleteWithPredicate(Expression<Func<T, bool>> predicate) =>
DeleteWithPredicateAsync(predicate).Result;
private protected IDomainResult DeleteWithPredicate(Expression<Func<T, bool>> predicate, Guid sessionId) =>
DeleteWithPredicateAsync(predicate, sessionId).Result;
private protected Task<IDomainResult> DeleteWithPredicateAsync(Expression<Func<T, bool>> predicate) =>
DeleteWithPredicateAsync(predicate, null);
private protected Task<IDomainResult> DeleteWithPredicateAsync(Expression<Func<T, bool>> predicate, Guid sessionId) =>
DeleteWithPredicateAsync(predicate, sessionId);
protected virtual async Task<IDomainResult> DeleteWithPredicateAsync(Expression<Func<T, bool>> predicate, Guid? sessionId)
{
try
{
if (sessionId != null)
await _collection.DeleteOneAsync<T>(_sessionService.GetSession(sessionId.Value), predicate);
else
await _collection.DeleteOneAsync<T>(predicate);
return IDomainResult.Success();
}
catch (Exception ex)
{
_logger.LogError(ex, "Data provider error");
return IDomainResult.Failed();
}
}
#endregion
#region DeleteMany
private protected IDomainResult DeleteManyWithPredicate(Expression<Func<T, bool>> predicate) =>
DeleteManyWithPredicateAsync(predicate).Result;
private protected IDomainResult DeleteManyWithPredicate(Expression<Func<T, bool>> predicate, Guid sessionId) =>
DeleteManyWithPredicateAsync(predicate, sessionId).Result;
private protected Task<IDomainResult> DeleteManyWithPredicateAsync(Expression<Func<T, bool>> predicate) =>
DeleteManyWithPredicateAsync(predicate, null);
private protected Task<IDomainResult> DeleteManyWithPredicateAsync(Expression<Func<T, bool>> predicate, Guid sessionId) =>
DeleteManyWithPredicateAsync(predicate, sessionId);
protected virtual async Task<IDomainResult> DeleteManyWithPredicateAsync(Expression<Func<T, bool>> 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
}
}

View File

@ -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);

View File

@ -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);

View File

@ -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<ContentDocument>?, IDomainResult) Get(Guid siteId);
}

View File

@ -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<ShopCartDocument>?, IDomainResult) GetAll(Guid siteId, Guid userId);
(ShopCartDocument?, IDomainResult) Get(Guid siteId, Guid userId, string sku);

View File

@ -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);

View File

@ -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<Site>, 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);

View File

@ -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;

View File

@ -38,6 +38,8 @@ namespace DataProviders.Extensions
#region Buckets
services.AddSingleton<IImageBucketDataProvider, ImageBucketDataProvider>();
services.AddSingleton<IDkimBucketDataProvider, DkimBucketDataProvider>();
services.AddSingleton<ITemplateBucketDataProvider, TemplateBucketDataProvider>();
#endregion
}
}

View File

@ -6,13 +6,12 @@ namespace DomainObjects;
public class Contact : DomainObjectBase<Contact> {
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() {

View File

@ -1,4 +1,4 @@
namespace DomainObjects.Documents.Users;
namespace DomainObjects.Documents;
public class Address
{

View File

@ -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<MailboxConnectionSettings> {
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();
}
}

View File

@ -0,0 +1,16 @@
using DomainObjects.Abstractions;
namespace DomainObjects.Documents.Sites;
public class DkimSettings : DomainObjectBase<DkimSettings> {
public string HostName { get; set; }
public string Selector { get; set; }
public Guid DkimId { get; set; }
public override int GetHashCode() {
throw new NotImplementedException();
}
}

View File

@ -0,0 +1,27 @@
using DomainObjects.Abstractions;
namespace DomainObjects.Documents.Sites;
public class PassworRecoverySettings : DomainObjectBase<PassworRecoverySettings> {
/// <summary>
/// DomainKeys Identified Mail (DKIM) is an email authentication method designed to detect forged sender addresses in email (email spoofing),<br />
/// a technique often used in phishing and email spam.
/// </summary>
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<string> Paragraphs { get; set; }
public override int GetHashCode() {
throw new NotImplementedException();
}
}

View File

@ -1,11 +1,23 @@
using DomainObjects.Abstractions;
namespace DomainObjects.Documents.Sites {
public class Site : DomainObjectDocumentBase<Site> {
public string Name { get; set; }
public List<string> 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<string> hosts) {
Name = name;
Hosts = hosts;

View File

@ -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();

View File

@ -3,15 +3,36 @@ using DataProviders;
namespace WeatherForecast {
/// <summary>
///
/// </summary>
public interface IJwtConfig {
public string? Secret { get; set; }
public int? Expires { get; set; }
/// <summary>
///
/// </summary>
public string Secret { get; set; }
/// <summary>
///
/// </summary>
public int Expires { get; set; }
}
/// <summary>
///
/// </summary>
public class JwtConfig : IJwtConfig {
public string? Secret { get; set; }
public int? Expires { get; set; }
/// <summary>
///
/// </summary>
public string Secret { get; set; } = string.Empty;
/// <summary>
///
/// </summary>
public int Expires { get; set; }
}
/// <summary>

View File

@ -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();
}
/// <summary>
///
/// </summary>
/// <param name="requestData"></param>
/// <returns></returns>
[HttpPost("[action]")]
public IActionResult Register([FromBody] RegisterRequestModel requestData) {
return BadRequest();
}
#region Password
/// <summary>
/// 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 {
/// <exception cref="NotImplementedException"></exception>
[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();
}

View File

@ -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;
/// <summary>
///
/// </summary>
[AllowAnonymous]
[Route("api/[controller]")]
public class DkimController : ControllerBase {
private readonly IAuthorizationService _authorizationService;
private readonly IDkimService _dkimService;
/// <summary>
///
/// </summary>
/// <param name="authorizationService"></param>
/// <param name="dkimService"></param>
public DkimController(
IAuthorizationService authorizationService,
IDkimService dkimService
) {
_authorizationService = authorizationService;
_dkimService = dkimService;
}
/// <summary>
/// Allows to upload private dkim certificate
/// </summary>
/// <param name="siteId"></param>
/// <param name="file"></param>
/// <returns></returns>
[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();
}
}

View File

@ -22,7 +22,8 @@ public class FilesController : ControllerBase {
/// <summary>
///
/// </summary>
/// <param name="filesService"></param>
/// <param name="authorizationService"></param>
/// <param name="imageBucketDataProvider"></param>
public FilesController(
IAuthorizationService authorizationService,
IImageBucketDataProvider imageBucketDataProvider

View File

@ -5,18 +5,31 @@ using WeatherForecast.Models.Initialization.Requests;
using WeatherForecast.Services;
namespace WeatherForecast.Controllers;
/// <summary>
///
/// </summary>
[Route("api/[controller]")]
[ApiController]
public class InitializationController : ControllerBase {
private readonly IInitializationService _initializationService;
/// <summary>
///
/// </summary>
/// <param name="initializationService"></param>
public InitializationController(
IInitializationService initializationService
) {
_initializationService = initializationService;
}
/// <summary>
///
/// </summary>
/// <param name="requestData"></param>
/// <returns></returns>
[HttpPost]
public IActionResult InitializeSystem([FromBody] InitializeSystemRequestModel requestData) {
var result = _initializationService.InitializeSystem(requestData);

View File

@ -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;
/// <summary>
///
/// </summary>
[AllowAnonymous]
[Route("api/[controller]")]
public class TemplateController : ControllerBase {
private readonly IAuthorizationService _authorizationService;
private readonly ITemplateService _templateService;
/// <summary>
///
/// </summary>
/// <param name="authorizationService"></param>
/// <param name="templateService"></param>
public TemplateController(
IAuthorizationService authorizationService,
ITemplateService templateService
) {
_authorizationService = authorizationService;
_templateService = templateService;
}
/// <summary>
/// Allows to upload private dkim certificate
/// </summary>
/// <param name="siteId"></param>
/// <param name="file"></param>
/// <returns></returns>
[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();
}
}

View File

@ -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;
/// <summary>
///
/// </summary>
[Route("api/[controller]")]
[ApiController]
public class UtilsController : ControllerBase {
/// <summary>
///
/// </summary>
/// <returns></returns>
[HttpGet("[action]")]
public IActionResult Aes() {
return Ok(AesService.GenerateKey());
}
}

View File

@ -1,6 +1,5 @@
using DomainObjects.Abstractions;
using Core.Abstractions.Models;
using DomainObjects.Abstractions;
using WeatherForecast.Models.Abstractions.PostItem.Responses;
namespace WeatherForecast.Models.Abstractions {

View File

@ -12,12 +12,12 @@ namespace WeatherForecast.Models.Account.Requests {
/// <summary>
///
/// </summary>
public string? Username { get; set; }
public string Username { get; set; } = string.Empty;
/// <summary>
///
/// </summary>
public string? Password { get; set; }
public string Password { get; set; } = string.Empty;
/// <summary>
///

View File

@ -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 {
/// <summary>
///
/// </summary>
public class CreateAccountRequestModel : RequestModelBase {
/// <summary>
///
/// </summary>
/// <param name="validationContext"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
public override IEnumerable<ValidationResult> Validate(ValidationContext validationContext) {
throw new NotImplementedException();
}
}
}

View File

@ -3,10 +3,22 @@ using Core.Enumerations;
using System.ComponentModel.DataAnnotations;
namespace WeatherForecast.Models.Account.Requests {
/// <summary>
///
/// </summary>
public class PasswordRecoveryRequestModel : RequestModelBase {
/// <summary>
///
/// </summary>
public string Username { get; set; }
/// <summary>
///
/// </summary>
/// <param name="validationContext"></param>
/// <returns></returns>
public override IEnumerable<ValidationResult> Validate(ValidationContext validationContext) {
if (string.IsNullOrWhiteSpace(Username))
yield return new ValidationResult($"{nameof(Username)} ${Errors.NullOrEmpty}");

View File

@ -24,6 +24,11 @@ namespace WeatherForecast.Models.Account.Requests {
/// </summary>
public string RePassword { get; set; }
/// <summary>
///
/// </summary>
/// <param name="validationContext"></param>
/// <returns></returns>
public override IEnumerable<ValidationResult> Validate(ValidationContext validationContext) {
if (string.IsNullOrWhiteSpace(Token))

View File

@ -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 {
/// <summary>
///
/// </summary>
public class RegisterRequestModel : RequestModelBase {
/// <summary>
///
/// </summary>
/// <param name="validationContext"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
public override IEnumerable<ValidationResult> Validate(ValidationContext validationContext) {
throw new NotImplementedException();
}
}
}

View File

@ -1,5 +1,4 @@
using DomainObjects.Pages;
using DomainObjects.Pages;
using WeatherForecast.Models.Abstractions;
using WeatherForecast.Models.Content.Responses.PageSections;

View File

@ -1,11 +1,26 @@
using Core.Abstractions.Models;
namespace WeatherForecast.Models.File.Responses {
/// <summary>
///
/// </summary>
public class FileResponseModel : ResponseModelBase {
public string Name { get; set; }
public byte[] Bytes { get; set; }
/// <summary>
/// File name
/// </summary>
public string Name { get; set; } = string.Empty;
public string ContentType { get; set; }
/// <summary>
/// Byte contente, when file is returned as stream
/// </summary>
public byte[] Bytes { get; set; } = Array.Empty<byte>();
/// <summary>
/// MIME content type
/// </summary>
public string ContentType { get; set; } = string.Empty;
}
}

View File

@ -5,24 +5,66 @@ using System.ComponentModel.DataAnnotations;
using System.Reflection.Metadata;
namespace WeatherForecast.Models.Initialization.Requests {
/// <summary>
///
/// </summary>
public class InitializeSystemRequestModel : RequestModelBase<InitializeSystemRequestModel> {
/// <summary>
///
/// </summary>
public string SiteName { get; set; }
/// <summary>
///
/// </summary>
public string Host { get; set; }
/// <summary>
///
/// </summary>
public string Username { get; set; }
/// <summary>
///
/// </summary>
public string Email { get; set; }
/// <summary>
///
/// </summary>
public string Password { get; set; }
/// <summary>
///
/// </summary>
public string RePassword { get; set; }
/// <summary>
///
/// </summary>
public string DkimBase64 { get; set; }
/// <summary>
///
/// </summary>
public string ServiceEmlTemplateBase64 { get; set; }
/// <summary>
///
/// </summary>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
public InitializeSystemRequestModel ToDomainObject() {
throw new NotImplementedException();
}
/// <summary>
///
/// </summary>
/// <param name="validationContext"></param>
/// <returns></returns>
public override IEnumerable<ValidationResult> 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}");
}
}
}

View File

@ -62,7 +62,7 @@ public static class AuthorisationHandlerHelper {
/// <summary>
///
/// </summary>
/// <param name="contextAccessor"></param>
/// <param name="site"></param>
/// <param name="user"></param>
/// <returns></returns>
public static Roles? GetRole(Site site, User user) {
@ -109,6 +109,12 @@ public abstract class AuthorizationHandlerBase<TRequirement> : AuthorizationHand
protected (Site?, User?) GetUser(AuthorizationHandlerContext context) =>
AuthorisationHandlerHelper.GetUser(context, _aesKey, _contextAccessor, _siteDataProvider, _userDataProvider);
/// <summary>
///
/// </summary>
/// <param name="site"></param>
/// <param name="user"></param>
/// <returns></returns>
protected Roles? GetRole(Site site, User user) =>
AuthorisationHandlerHelper.GetRole(site, user);
}
@ -152,6 +158,12 @@ public abstract class AuthorizationHandlerBase<TRequirement, TResource> : Author
protected (Site?, User?) GetUser(AuthorizationHandlerContext context) =>
AuthorisationHandlerHelper.GetUser(context, _aesKey, _contextAccessor, _siteDataProvider, _userDataProvider);
/// <summary>
///
/// </summary>
/// <param name="site"></param>
/// <param name="user"></param>
/// <returns></returns>
protected Roles? GetRole(Site site, User user) =>
AuthorisationHandlerHelper.GetRole(site, user);
}

View File

@ -20,7 +20,9 @@ public class FileAuthorisationHandler : AuthorizationHandlerBase<FileAuthorisati
/// <summary>
///
/// </summary>
/// <param name="configuration"></param>
/// <param name="contextAccessor"></param>
/// <param name="siteDataProvider"></param>
/// <param name="userDataProvider"></param>
/// <param name="fileSecurityService"></param>
public FileAuthorisationHandler(

View File

@ -57,6 +57,10 @@ public class PasswordChangeAuthorizationHandler : AuthorizationHandlerBase<Passw
///
/// </summary>
public class PasswordChangeRequirement : AuthorizationRequirementBase {
/// <summary>
///
/// </summary>
public string OldPassword { get; init; }
}

View File

@ -15,9 +15,10 @@ namespace WeatherForecast.Policies {
/// <summary>
///
/// </summary>
/// <param name="configuration"></param>
/// <param name="contextAccessor"></param>
/// <param name="siteDataProvider"></param>
/// <param name="userDataProvider"></param>
/// <param name="accountService"></param>
public ShopCartAuthorizationHandler(
IOptions<Configuration> configuration,
IHttpContextAccessor contextAccessor,

View File

@ -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 {
/// <summary>
///
/// </summary>
/// <param name="hostName"></param>
/// <param name="requestData"></param>
/// <returns></returns>
IDomainResult PasswordRecovery(PasswordRecoveryRequestModel requestData);
IDomainResult PasswordRecovery(string hostName, PasswordRecoveryRequestModel requestData);
/// <summary>
@ -56,10 +55,13 @@ namespace WeatherForecast.Services {
/// </summary>
public class AccountService : ServiceBase<AccountService>, 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;
/// <summary>
///
@ -67,14 +69,32 @@ namespace WeatherForecast.Services {
/// <param name="logger"></param>
/// <param name="options"></param>
/// <param name="userDataProvider"></param>
/// <param name="siteDataProvider"></param>
/// <param name="dkimBucketDataProvider"></param>
/// <param name="templateBucketDataProvider"></param>
public AccountService(
ILogger<AccountService> logger,
IOptions<Configuration> 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;
}
/// <summary>
@ -99,7 +119,7 @@ namespace WeatherForecast.Services {
if (!user.Authentication.ValidatePassword(requestData.Password))
return IDomainResult.Unauthorized<string?>();
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);
}
/// <summary>
/// Passing the Username in the request body is a more secure alternative to passing it as a GET param
/// </summary>
/// <param name="hostName"></param>
/// <param name="requestData"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
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#<X&#Lyz");
smtpService.Connect(site.SmtpSettings.Server, site.SmtpSettings.Port, site.SmtpSettings.UseSsl);
//smtpService.Authenticate(site.PassworRecoverySettings.SmtpSettings.UserName, site.PassworRecoverySettings.SmtpSettings.GetPassword());
smtpService.Send(emailBuilder.Build());
@ -183,9 +204,10 @@ namespace WeatherForecast.Services {
}
/// <summary>
/// Only if user is authenticated, do not expose this methods as anonymous
///
/// </summary>
/// <param name="user"></param>
/// <param name="oldPassword"></param>
/// <param name="newPassword"></param>
/// <returns></returns>
public (Guid?, IDomainResult) PasswordChange(User user, string oldPassword, string newPassword) {

View File

@ -0,0 +1,54 @@
using Core.Abstractions;
using DataProviders.Buckets;
using DomainResults.Common;
using Microsoft.AspNetCore.Authorization;
namespace WeatherForecast.Services {
/// <summary>
///
/// </summary>
public interface IDkimService {
/// <summary>
///
/// </summary>
/// <param name="file"></param>
/// <returns></returns>
(Guid?, IDomainResult) Post(BucketFile file);
}
/// <summary>
///
/// </summary>
public class DkimService : ServiceBase<DkimService>, IDkimService {
private readonly IDkimBucketDataProvider _dkimBucketDataProvider;
/// <summary>
///
/// </summary>
/// <param name="logger"></param>
/// <param name="dkimBucketDataProvider"></param>
public DkimService(
ILogger<DkimService> logger,
IDkimBucketDataProvider dkimBucketDataProvider
) : base (logger) {
_dkimBucketDataProvider = dkimBucketDataProvider;
}
/// <summary>
///
/// </summary>
/// <param name="file"></param>
/// <returns></returns>
public (Guid?, IDomainResult) Post(BucketFile file) {
var (fileId, uploadFileResult) = _dkimBucketDataProvider.Upload(file);
if (!uploadFileResult.IsSuccess || fileId == null)
return IDomainResult.Failed<Guid?>();
return IDomainResult.Success(fileId);
}
}
}

View File

@ -9,10 +9,20 @@ using DomainResults.Common;
using Extensions;
using Microsoft.Extensions.Options;
using WeatherForecast.Models.Initialization.Requests;
using DataProviders.Buckets;
namespace WeatherForecast.Services;
/// <summary>
///
/// </summary>
public interface IInitializationService {
/// <summary>
///
/// </summary>
/// <param name="requestData"></param>
/// <returns></returns>
IDomainResult InitializeSystem(InitializeSystemRequestModel requestData);
}
@ -22,11 +32,13 @@ public interface IInitializationService {
/// </summary>
public class InitializationService : ServiceBase<InitializationService>, 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;
/// <summary>
///
@ -35,16 +47,22 @@ public class InitializationService : ServiceBase<InitializationService>, IInitia
/// <param name="options"></param>
/// <param name="siteDataProvider"></param>
/// <param name="userDataProvider"></param>
/// <param name="dkimBucketDataProvider"></param>
/// <param name="serviceEmlTemplateBucketDataProvider"></param>
public InitializationService(
ILogger<InitializationService> logger,
IOptions<Configuration> 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<InitializationService>, 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<string> { 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<SiteRole> { new SiteRole(siteId, Roles.Admin) }, requestData.Username, requestData.Email, requestData.Password) {

View File

@ -29,9 +29,7 @@ public interface IShopCartItemService {
/// <summary>
///
/// </summary>
/// <param name="siteId"></param>
/// <param name="userId"></param>
/// <param name="sku"></param>
/// <param name="cartItem"></param>
/// <param name="locale"></param>
/// <returns></returns>
(ShopCartItemResponseModel?, IDomainResult) Get(ShopCartDocument cartItem, string? locale);
@ -39,9 +37,7 @@ public interface IShopCartItemService {
/// <summary>
///
/// </summary>
/// <param name="siteId"></param>
/// <param name="userId"></param>
/// <param name="sku"></param>
/// <param name="cartItem"></param>
/// <param name="requestData"></param>
/// <returns></returns>
(Guid?, IDomainResult) Update(ShopCartDocument cartItem, ShopCartItemRequestModel requestData);

View File

@ -18,17 +18,14 @@ namespace WeatherForecast.Services {
/// <summary>
///
/// </summary>
/// <param name="siteId"></param>
/// <param name="sku"></param>
/// <param name="requestModel"></param>
/// <param name="shopItem"></param>
/// <returns></returns>
(Guid?, IDomainResult) Post(ShopDocument shopItem);
/// <summary>
///
/// </summary>
/// <param name="siteId"></param>
/// <param name="sku"></param>
/// <param name="shopItem"></param>
/// <returns></returns>
(ShopItemResponseModel?, IDomainResult) Get(ShopDocument shopItem);
@ -101,8 +98,7 @@ namespace WeatherForecast.Services {
/// <summary>
///
/// </summary>
/// <param name="siteId"></param>
/// <param name="sku"></param>
/// <param name="shopItem"></param>
/// <returns></returns>
public (ShopItemResponseModel?, IDomainResult) Get(ShopDocument shopItem) {
var (categories, getCategoryResult) = _categoryDataProvider.GetMany(shopItem.SiteId, shopItem.Categories);

View File

@ -0,0 +1,54 @@
using Core.Abstractions;
using DataProviders.Buckets;
using DomainResults.Common;
namespace WeatherForecast.Services {
/// <summary>
///
/// </summary>
public interface ITemplateService {
/// <summary>
///
/// </summary>
/// <param name="file"></param>
/// <returns></returns>
(Guid?, IDomainResult) Post(BucketFile file);
}
/// <summary>
///
/// </summary>
public class TemplateService : ServiceBase<TemplateService>, ITemplateService {
private readonly ITemplateBucketDataProvider _templateBucketDataProvider;
/// <summary>
///
/// </summary>
/// <param name="logger"></param>
/// <param name="templateBucketDataProvider"></param>
public TemplateService(
ILogger<TemplateService> logger,
ITemplateBucketDataProvider templateBucketDataProvider
) : base(logger) {
_templateBucketDataProvider = templateBucketDataProvider;
}
/// <summary>
///
/// </summary>
/// <param name="file"></param>
/// <returns></returns>
public (Guid?, IDomainResult) Post(BucketFile file) {
var (fileId, uploadFileResult) = _templateBucketDataProvider.Upload(file);
if (!uploadFileResult.IsSuccess || fileId == null)
return IDomainResult.Failed<Guid?>();
return IDomainResult.Success(fileId);
}
}
}

View File

@ -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<ICategoryItemService, CategoryItemService>();
services.AddScoped<ICategoryItemsService, CategoryItemsService>();
services.AddScoped<IContentService, ContentService>();
services.AddScoped<IDkimService, DkimService>();
services.AddScoped<IImageService, ImageService>();
services.AddScoped<IInitializationService, InitializationService>();
services.AddScoped<IShopCartItemService, ShopCartItemService>();
services.AddScoped<IShopCartItemsService, ShopCartItemsService>();
services.AddScoped<IShopItemService, ShopItemService>();
services.AddScoped<IShopItemsService, ShopItemsService>();
services.AddScoped<ITemplateService, TemplateService>();
#endregion
services.RegisterDataproviders(appSettings);

View File

@ -12,6 +12,13 @@
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
</PropertyGroup>
<ItemGroup>
<Compile Remove="templates\**" />
<Content Remove="templates\**" />
<EmbeddedResource Remove="templates\**" />
<None Remove="templates\**" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="DomainResult" Version="3.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.10" />
@ -35,20 +42,10 @@
<ProjectReference Include="..\ImageProvider\ImageProvider.csproj" />
</ItemGroup>
<ItemGroup>
<Folder Include="templates\" />
</ItemGroup>
<ItemGroup>
<None Update="dkim\maks-it.com">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="templates\email-inlined.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="templates\email.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>

View File

@ -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)