diff --git a/docs/model.ndm2 b/docs/model.ndm2 index d21861a..27f010a 100644 --- a/docs/model.ndm2 +++ b/docs/model.ndm2 @@ -12045,6 +12045,503 @@ "filestreamPartitionScheme": "", "dataCompressions": [] } + }, + { + "objectType": "Table_MSSQL", + "name": "OtpService", + "comment": "", + "owner": "", + "isChangeTracking": false, + "isTrackColumnsUpdated": false, + "oldName": "", + "isSystemTable": false, + "createTime": "", + "modifyTime": "", + "objectID": 0, + "numberOfRows": 0, + "identityCurrent": 0, + "dataLength": 0, + "indexLength": 0, + "fields": [ + { + "objectType": "TableField_MSSQL", + "name": "SendOtp", + "type": "Command", + "size": 0, + "isNullable": "Yes", + "scale": 0, + "comment": "ارسال رمز پویا", + "computedExpression": "", + "defaultValue": "", + "defaultValueType": "Others", + "schema": "", + "userDefinedType": "", + "collate": "", + "isWithValues": false, + "isFilestream": false, + "isColumnSet": false, + "isPersisted": false, + "isSparse": false, + "isRowGUIDColumn": false, + "oldName": "SendOtp", + "computedBaseType": "", + "isDefaultConstraint": false, + "defaultConstraint": "", + "isIdentity": false, + "isExistingField": false, + "identitySeed": 0, + "identityIncrement": 0, + "identityIsNotForReplication": false + }, + { + "objectType": "TableField_MSSQL", + "name": "VerifyOtpCode", + "type": "Command", + "size": 0, + "isNullable": "Yes", + "scale": 0, + "comment": "فعال سازی رمز پویا", + "computedExpression": "", + "defaultValue": "", + "defaultValueType": "Others", + "schema": "", + "userDefinedType": "", + "collate": "", + "isWithValues": false, + "isFilestream": false, + "isColumnSet": false, + "isPersisted": false, + "isSparse": false, + "isRowGUIDColumn": false, + "oldName": "VerifyOtpCode", + "computedBaseType": "", + "isDefaultConstraint": false, + "defaultConstraint": "", + "isIdentity": false, + "isExistingField": false, + "identitySeed": 0, + "identityIncrement": 0, + "identityIsNotForReplication": false + } + ], + "indexes": [], + "foreignKeys": [ + { + "objectType": "ForeignKey_MSSQL", + "name": "fk_OtpService_VerifyOtpCodeRequest_1", + "fields": [ + "VerifyOtpCode" + ], + "referencedSchema": "BackOffice", + "referencedTable": "VerifyOtpCodeRequest", + "referencedFields": [ + "Mobile" + ], + "onDelete": "", + "onUpdate": "", + "isNotForReplication": false, + "isEnabled": true, + "comment": "", + "sourceCardinality": "NoneRelationship", + "targetCardinality": "NoneRelationship", + "oldName": "" + }, + { + "objectType": "ForeignKey_MSSQL", + "name": "fk_OtpService_VerifyOtpCodeResponse_1", + "fields": [ + "VerifyOtpCode" + ], + "referencedSchema": "BackOffice", + "referencedTable": "VerifyOtpCodeResponse", + "referencedFields": [ + "Token" + ], + "onDelete": "", + "onUpdate": "", + "isNotForReplication": false, + "isEnabled": true, + "comment": "", + "sourceCardinality": "NoneRelationship", + "targetCardinality": "NoneRelationship", + "oldName": "" + }, + { + "objectType": "ForeignKey_MSSQL", + "name": "fk_OtpService_SendOtpRequest_1", + "fields": [ + "SendOtp" + ], + "referencedSchema": "BackOffice", + "referencedTable": "SendOtpRequest", + "referencedFields": [ + "Mobile" + ], + "onDelete": "", + "onUpdate": "", + "isNotForReplication": false, + "isEnabled": true, + "comment": "", + "sourceCardinality": "NoneRelationship", + "targetCardinality": "NoneRelationship", + "oldName": "" + } + ], + "uniques": [], + "checks": [], + "triggers": [], + "storage": { + "objectType": "Storage_MSSQL", + "name": "", + "oldName": "", + "storageType": "Default", + "filegroup": "", + "textImageFilegroup": "", + "filestreamFilegroup": "", + "partitionScheme": "", + "partitionColumn": "", + "filestreamPartitionScheme": "", + "dataCompressions": [] + } + }, + { + "objectType": "Table_MSSQL", + "name": "VerifyOtpCodeRequest", + "comment": "ورودی فعال سازی رمز پویا", + "owner": "", + "isChangeTracking": false, + "isTrackColumnsUpdated": false, + "oldName": "", + "isSystemTable": false, + "createTime": "", + "modifyTime": "", + "objectID": 9803, + "numberOfRows": 0, + "identityCurrent": 0, + "dataLength": 0, + "indexLength": 0, + "fields": [ + { + "objectType": "TableField_MSSQL", + "name": "Mobile", + "type": "nvarchar", + "size": -2147483648, + "isNullable": "No", + "scale": -2147483648, + "comment": "شماره موبایل", + "computedExpression": "", + "defaultValue": "", + "defaultValueType": "Others", + "schema": "", + "userDefinedType": "", + "collate": "", + "isWithValues": false, + "isFilestream": false, + "isColumnSet": false, + "isPersisted": false, + "isSparse": false, + "isRowGUIDColumn": false, + "oldName": "Mobile", + "computedBaseType": "", + "isDefaultConstraint": false, + "defaultConstraint": "", + "isIdentity": false, + "isExistingField": false, + "identitySeed": 0, + "identityIncrement": 0, + "identityIsNotForReplication": false + }, + { + "objectType": "TableField_MSSQL", + "name": "Code", + "type": "nvarchar", + "size": -2147483648, + "isNullable": "No", + "scale": -2147483648, + "comment": "رمز پویا", + "computedExpression": "", + "defaultValue": "", + "defaultValueType": "Others", + "schema": "", + "userDefinedType": "", + "collate": "", + "isWithValues": false, + "isFilestream": false, + "isColumnSet": false, + "isPersisted": false, + "isSparse": false, + "isRowGUIDColumn": false, + "oldName": "Code", + "computedBaseType": "", + "isDefaultConstraint": false, + "defaultConstraint": "", + "isIdentity": false, + "isExistingField": false, + "identitySeed": 0, + "identityIncrement": 0, + "identityIsNotForReplication": false + }, + { + "objectType": "TableField_MSSQL", + "name": "ParentId", + "type": "bigint", + "size": -2147483648, + "isNullable": "Yes", + "scale": -2147483648, + "comment": "شناسه پدر", + "computedExpression": "", + "defaultValue": "", + "defaultValueType": "Others", + "schema": "", + "userDefinedType": "", + "collate": "", + "isWithValues": false, + "isFilestream": false, + "isColumnSet": false, + "isPersisted": false, + "isSparse": false, + "isRowGUIDColumn": false, + "oldName": "ParentId", + "computedBaseType": "", + "isDefaultConstraint": false, + "defaultConstraint": "", + "isIdentity": false, + "isExistingField": false, + "identitySeed": 0, + "identityIncrement": 0, + "identityIsNotForReplication": false + } + ], + "indexes": [], + "primaryKey": { + "objectType": "PrimaryKey_MSSQL", + "name": "_copy_10_copy_1_copy_1", + "fields": [], + "fillFactor": 0, + "oldName": "", + "isClustered": false, + "isPadded": false, + "noRecomputeStatistics": false, + "ignoreDuplicatedKeyValues": false, + "allowRowLocks": false, + "allowPageLocks": false, + "storage": { + "objectType": "Storage_MSSQL", + "name": "", + "oldName": "", + "storageType": "Default", + "filegroup": "", + "textImageFilegroup": "", + "filestreamFilegroup": "", + "partitionScheme": "", + "partitionColumn": "", + "filestreamPartitionScheme": "", + "dataCompressions": [] + } + }, + "foreignKeys": [], + "uniques": [], + "checks": [], + "triggers": [], + "storage": { + "objectType": "Storage_MSSQL", + "name": "", + "oldName": "", + "storageType": "Default", + "filegroup": "", + "textImageFilegroup": "", + "filestreamFilegroup": "", + "partitionScheme": "", + "partitionColumn": "", + "filestreamPartitionScheme": "", + "dataCompressions": [] + } + }, + { + "objectType": "Table_MSSQL", + "name": "SendOtpRequest", + "comment": "ورودی ارسال رمز پویا", + "owner": "", + "isChangeTracking": false, + "isTrackColumnsUpdated": false, + "oldName": "", + "isSystemTable": false, + "createTime": "", + "modifyTime": "", + "objectID": 9803, + "numberOfRows": 0, + "identityCurrent": 0, + "dataLength": 0, + "indexLength": 0, + "fields": [ + { + "objectType": "TableField_MSSQL", + "name": "Mobile", + "type": "nvarchar", + "size": -2147483648, + "isNullable": "No", + "scale": -2147483648, + "comment": "شماره موبایل", + "computedExpression": "", + "defaultValue": "", + "defaultValueType": "Others", + "schema": "", + "userDefinedType": "", + "collate": "", + "isWithValues": false, + "isFilestream": false, + "isColumnSet": false, + "isPersisted": false, + "isSparse": false, + "isRowGUIDColumn": false, + "oldName": "Mobile", + "computedBaseType": "", + "isDefaultConstraint": false, + "defaultConstraint": "", + "isIdentity": false, + "isExistingField": false, + "identitySeed": 0, + "identityIncrement": 0, + "identityIsNotForReplication": false + } + ], + "indexes": [], + "primaryKey": { + "objectType": "PrimaryKey_MSSQL", + "name": "_copy_10_copy_1", + "fields": [], + "fillFactor": 0, + "oldName": "", + "isClustered": false, + "isPadded": false, + "noRecomputeStatistics": false, + "ignoreDuplicatedKeyValues": false, + "allowRowLocks": false, + "allowPageLocks": false, + "storage": { + "objectType": "Storage_MSSQL", + "name": "", + "oldName": "", + "storageType": "Default", + "filegroup": "", + "textImageFilegroup": "", + "filestreamFilegroup": "", + "partitionScheme": "", + "partitionColumn": "", + "filestreamPartitionScheme": "", + "dataCompressions": [] + } + }, + "foreignKeys": [], + "uniques": [], + "checks": [], + "triggers": [], + "storage": { + "objectType": "Storage_MSSQL", + "name": "", + "oldName": "", + "storageType": "Default", + "filegroup": "", + "textImageFilegroup": "", + "filestreamFilegroup": "", + "partitionScheme": "", + "partitionColumn": "", + "filestreamPartitionScheme": "", + "dataCompressions": [] + } + }, + { + "objectType": "Table_MSSQL", + "name": "VerifyOtpCodeResponse", + "comment": "خروجی فعال سازی رمز پویا", + "owner": "", + "isChangeTracking": false, + "isTrackColumnsUpdated": false, + "oldName": "", + "isSystemTable": false, + "createTime": "", + "modifyTime": "", + "objectID": 9803, + "numberOfRows": 0, + "identityCurrent": 0, + "dataLength": 0, + "indexLength": 0, + "fields": [ + { + "objectType": "TableField_MSSQL", + "name": "Token", + "type": "nvarchar", + "size": -2147483648, + "isNullable": "No", + "scale": -2147483648, + "comment": "توکن", + "computedExpression": "", + "defaultValue": "", + "defaultValueType": "Others", + "schema": "", + "userDefinedType": "", + "collate": "", + "isWithValues": false, + "isFilestream": false, + "isColumnSet": false, + "isPersisted": false, + "isSparse": false, + "isRowGUIDColumn": false, + "oldName": "Token", + "computedBaseType": "", + "isDefaultConstraint": false, + "defaultConstraint": "", + "isIdentity": false, + "isExistingField": false, + "identitySeed": 0, + "identityIncrement": 0, + "identityIsNotForReplication": false + } + ], + "indexes": [], + "primaryKey": { + "objectType": "PrimaryKey_MSSQL", + "name": "_copy_10_copy_1_copy_1_copy_1", + "fields": [], + "fillFactor": 0, + "oldName": "", + "isClustered": false, + "isPadded": false, + "noRecomputeStatistics": false, + "ignoreDuplicatedKeyValues": false, + "allowRowLocks": false, + "allowPageLocks": false, + "storage": { + "objectType": "Storage_MSSQL", + "name": "", + "oldName": "", + "storageType": "Default", + "filegroup": "", + "textImageFilegroup": "", + "filestreamFilegroup": "", + "partitionScheme": "", + "partitionColumn": "", + "filestreamPartitionScheme": "", + "dataCompressions": [] + } + }, + "foreignKeys": [], + "uniques": [], + "checks": [], + "triggers": [], + "storage": { + "objectType": "Storage_MSSQL", + "name": "", + "oldName": "", + "storageType": "Default", + "filegroup": "", + "textImageFilegroup": "", + "filestreamFilegroup": "", + "partitionScheme": "", + "partitionColumn": "", + "filestreamPartitionScheme": "", + "dataCompressions": [] + } } ], "views": [] @@ -13138,6 +13635,70 @@ "b": 147, "a": 1 } + }, + { + "type": "table", + "schemaName": "BackOffice", + "tableName": "OtpService", + "x": 5740, + "y": 20, + "width": 310, + "height": 120, + "isBold": false, + "titleColor": { + "r": 0, + "g": 0, + "b": 0, + "a": 1 + } + }, + { + "type": "table", + "schemaName": "BackOffice", + "tableName": "VerifyOtpCodeRequest", + "x": 6100, + "y": 20, + "width": 310, + "height": 130, + "isBold": false, + "titleColor": { + "r": 255, + "g": 214, + "b": 147, + "a": 1 + } + }, + { + "type": "table", + "schemaName": "BackOffice", + "tableName": "SendOtpRequest", + "x": 5380, + "y": 30, + "width": 310, + "height": 100, + "isBold": false, + "titleColor": { + "r": 255, + "g": 214, + "b": 147, + "a": 1 + } + }, + { + "type": "table", + "schemaName": "BackOffice", + "tableName": "VerifyOtpCodeResponse", + "x": 5740, + "y": 190, + "width": 310, + "height": 100, + "isBold": false, + "titleColor": { + "r": 200, + "g": 255, + "b": 160, + "a": 1 + } } ], "layers": [], @@ -15417,6 +15978,108 @@ "isFontItalic": false, "isVisible": false } + }, + { + "name": "fk_OtpService_VerifyOtpCodeRequest_1", + "sourceTableName": "OtpService", + "sourceSchemaName": "BackOffice", + "lineWidth": 1, + "visible": true, + "vertices": [ + { + "x": 6065, + "y": 80 + }, + { + "x": 6085, + "y": 80 + } + ], + "label": { + "x": 6061, + "y": 52, + "width": 270, + "height": 30, + "fontName": "Arial Unicode MS", + "fontSize": 14, + "fontColor": { + "r": 204, + "g": 204, + "b": 204, + "a": 1 + }, + "isFontBold": false, + "isFontItalic": false, + "isVisible": false + } + }, + { + "name": "fk_OtpService_VerifyOtpCodeResponse_1", + "sourceTableName": "OtpService", + "sourceSchemaName": "BackOffice", + "lineWidth": 1, + "visible": true, + "vertices": [ + { + "x": 5880, + "y": 155 + }, + { + "x": 5880, + "y": 175 + } + ], + "label": { + "x": 5882, + "y": 105, + "width": 280, + "height": 30, + "fontName": "Arial Unicode MS", + "fontSize": 14, + "fontColor": { + "r": 204, + "g": 204, + "b": 204, + "a": 1 + }, + "isFontBold": false, + "isFontItalic": false, + "isVisible": false + } + }, + { + "name": "fk_OtpService_SendOtpRequest_1", + "sourceTableName": "OtpService", + "sourceSchemaName": "BackOffice", + "lineWidth": 1, + "visible": true, + "vertices": [ + { + "x": 5725, + "y": 80 + }, + { + "x": 5705, + "y": 80 + } + ], + "label": { + "x": 5735, + "y": 42, + "width": 231, + "height": 30, + "fontName": "Arial Unicode MS", + "fontSize": 14, + "fontColor": { + "r": 204, + "g": 204, + "b": 204, + "a": 1 + }, + "isFontBold": false, + "isFontItalic": false, + "isVisible": false + } } ], "viewRelations": [] diff --git a/src/BackOffice.BFF.Application/BackOffice.BFF.Application.csproj b/src/BackOffice.BFF.Application/BackOffice.BFF.Application.csproj new file mode 100644 index 0000000..44fbade --- /dev/null +++ b/src/BackOffice.BFF.Application/BackOffice.BFF.Application.csproj @@ -0,0 +1,19 @@ + + + + net7.0 + enable + + + + + + + + + + + + + + diff --git a/src/BackOffice.BFF.Application/Common/Behaviours/LoggingBehaviour.cs b/src/BackOffice.BFF.Application/Common/Behaviours/LoggingBehaviour.cs new file mode 100644 index 0000000..2bcc372 --- /dev/null +++ b/src/BackOffice.BFF.Application/Common/Behaviours/LoggingBehaviour.cs @@ -0,0 +1,24 @@ +using MediatR.Pipeline; +using Microsoft.Extensions.Logging; + +namespace BackOffice.BFF.Application.Common.Behaviours; + +public class LoggingBehaviour : IRequestPreProcessor where TRequest : notnull +{ + private readonly ILogger _logger; + private readonly ICurrentUserService _currentUserService; + + public LoggingBehaviour(ILogger logger, ICurrentUserService currentUserService) + { + _logger = logger; + _currentUserService = currentUserService; + } + + public async Task Process(TRequest request, CancellationToken cancellationToken) + { + var requestName = typeof(TRequest).Name; + var userId = _currentUserService.UserId ?? string.Empty; + _logger.LogInformation("Request: {Name} {@UserId} {@Request}", + requestName, userId, request); + } +} diff --git a/src/BackOffice.BFF.Application/Common/Behaviours/PerformanceBehaviour.cs b/src/BackOffice.BFF.Application/Common/Behaviours/PerformanceBehaviour.cs new file mode 100644 index 0000000..84a3dcf --- /dev/null +++ b/src/BackOffice.BFF.Application/Common/Behaviours/PerformanceBehaviour.cs @@ -0,0 +1,43 @@ +using System.Diagnostics; +using MediatR; +using Microsoft.Extensions.Logging; + +namespace BackOffice.BFF.Application.Common.Behaviours; + +public class PerformanceBehaviour : IPipelineBehavior + where TRequest : IRequest +{ + private readonly Stopwatch _timer; + private readonly ILogger _logger; + private readonly ICurrentUserService _currentUserService; + + public PerformanceBehaviour(ILogger logger, ICurrentUserService currentUserService) + { + _timer = new Stopwatch(); + _logger = logger; + _currentUserService = currentUserService; + } + + public async Task Handle(TRequest request, RequestHandlerDelegate next, + CancellationToken cancellationToken) + { + _timer.Start(); + + var response = await next(); + + _timer.Stop(); + + var elapsedMilliseconds = _timer.ElapsedMilliseconds; + + if (elapsedMilliseconds > 500) + { + var requestName = typeof(TRequest).Name; + var userId = _currentUserService.UserId ?? string.Empty; + + _logger.LogWarning("Long Running Request: {Name} ({ElapsedMilliseconds} milliseconds) {@UserId} {@Request}", + requestName, elapsedMilliseconds, userId, request); + } + + return response; + } +} diff --git a/src/BackOffice.BFF.Application/Common/Behaviours/UnhandledExceptionBehaviour.cs b/src/BackOffice.BFF.Application/Common/Behaviours/UnhandledExceptionBehaviour.cs new file mode 100644 index 0000000..a17309c --- /dev/null +++ b/src/BackOffice.BFF.Application/Common/Behaviours/UnhandledExceptionBehaviour.cs @@ -0,0 +1,31 @@ +using Microsoft.Extensions.Logging; + +namespace BackOffice.BFF.Application.Common.Behaviours; + +public class UnhandledExceptionBehaviour : IPipelineBehavior + where TRequest : IRequest +{ + private readonly ILogger _logger; + + public UnhandledExceptionBehaviour(ILogger logger) + { + _logger = logger; + } + + public async Task Handle(TRequest request, RequestHandlerDelegate next, + CancellationToken cancellationToken) + { + try + { + return await next(); + } + catch (Exception ex) + { + var requestName = typeof(TRequest).Name; + + _logger.LogError(ex, "Request: Unhandled Exception for Request {Name} {@Request}", requestName, request); + + throw; + } + } +} diff --git a/src/BackOffice.BFF.Application/Common/Behaviours/ValidationBehaviour.cs b/src/BackOffice.BFF.Application/Common/Behaviours/ValidationBehaviour.cs new file mode 100644 index 0000000..60456a5 --- /dev/null +++ b/src/BackOffice.BFF.Application/Common/Behaviours/ValidationBehaviour.cs @@ -0,0 +1,37 @@ +using ValidationException = BackOffice.BFF.Application.Common.Exceptions.ValidationException; + +namespace BackOffice.BFF.Application.Common.Behaviours; + +public class ValidationBehaviour : IPipelineBehavior + where TRequest : IRequest +{ + private readonly IEnumerable> _validators; + + public ValidationBehaviour(IEnumerable> validators) + { + _validators = validators; + } + + public async Task Handle(TRequest request, RequestHandlerDelegate next, + CancellationToken cancellationToken) + { + if (_validators.Any()) + { + var context = new ValidationContext(request); + + var validationResults = await Task.WhenAll( + _validators.Select(v => + v.ValidateAsync(context, cancellationToken))); + + var failures = validationResults + .Where(r => r.Errors.Any()) + .SelectMany(r => r.Errors) + .ToList(); + + if (failures.Any()) + throw new ValidationException(failures); + } + + return await next(); + } +} diff --git a/src/BackOffice.BFF.Application/Common/Exceptions/DuplicateException.cs b/src/BackOffice.BFF.Application/Common/Exceptions/DuplicateException.cs new file mode 100644 index 0000000..506696f --- /dev/null +++ b/src/BackOffice.BFF.Application/Common/Exceptions/DuplicateException.cs @@ -0,0 +1,29 @@ +namespace BackOffice.BFF.Application.Common.Exceptions; + +public class DuplicateException : Exception +{ + public DuplicateException() + : base() + { + } + + public DuplicateException(string message) + : base(message) + { + } + + public DuplicateException(string message, Exception innerException) + : base(message, innerException) + { + } + + public DuplicateException(string name, object key) + : base($"Entity \"{name}\" ({key}) already exists.") + { + } + + public DuplicateException(string name, string field, object? key) + : base($"Entity \"{name}\" field \"{field}\" ({key}) already exists.") + { + } +} diff --git a/src/BackOffice.BFF.Application/Common/Exceptions/ForbiddenAccessException.cs b/src/BackOffice.BFF.Application/Common/Exceptions/ForbiddenAccessException.cs new file mode 100644 index 0000000..c06479f --- /dev/null +++ b/src/BackOffice.BFF.Application/Common/Exceptions/ForbiddenAccessException.cs @@ -0,0 +1,8 @@ +namespace BackOffice.BFF.Application.Common.Exceptions; + +public class ForbiddenAccessException : Exception +{ + public ForbiddenAccessException() : base() + { + } +} diff --git a/src/BackOffice.BFF.Application/Common/Exceptions/NotFoundException.cs b/src/BackOffice.BFF.Application/Common/Exceptions/NotFoundException.cs new file mode 100644 index 0000000..aa97cbd --- /dev/null +++ b/src/BackOffice.BFF.Application/Common/Exceptions/NotFoundException.cs @@ -0,0 +1,24 @@ +namespace BackOffice.BFF.Application.Common.Exceptions; + +public class NotFoundException : Exception +{ + public NotFoundException() + : base() + { + } + + public NotFoundException(string message) + : base(message) + { + } + + public NotFoundException(string message, Exception innerException) + : base(message, innerException) + { + } + + public NotFoundException(string name, object key) + : base($"Entity \"{name}\" ({key}) was not found.") + { + } +} diff --git a/src/BackOffice.BFF.Application/Common/Exceptions/ValidationException.cs b/src/BackOffice.BFF.Application/Common/Exceptions/ValidationException.cs new file mode 100644 index 0000000..8b117cb --- /dev/null +++ b/src/BackOffice.BFF.Application/Common/Exceptions/ValidationException.cs @@ -0,0 +1,22 @@ +using FluentValidation.Results; + +namespace BackOffice.BFF.Application.Common.Exceptions; + +public class ValidationException : Exception +{ + public ValidationException() + : base("One or more validation failures have occurred.") + { + Errors = new Dictionary(); + } + + public ValidationException(IEnumerable failures) + : this() + { + Errors = failures + .GroupBy(e => e.PropertyName, e => e.ErrorMessage) + .ToDictionary(failureGroup => failureGroup.Key, failureGroup => failureGroup.ToArray()); + } + + public IDictionary Errors { get; } +} diff --git a/src/BackOffice.BFF.Application/Common/Interfaces/IApplicationContractContext.cs b/src/BackOffice.BFF.Application/Common/Interfaces/IApplicationContractContext.cs new file mode 100644 index 0000000..7ef9830 --- /dev/null +++ b/src/BackOffice.BFF.Application/Common/Interfaces/IApplicationContractContext.cs @@ -0,0 +1,17 @@ +using CMSMicroservice.Protobuf.Protos.Package; +using FMSMicroservice.Protobuf.Protos.FileInfo; + +namespace BackOffice.BFF.Application.Common.Interfaces; + +public interface IApplicationContractContext +{ + #region FM + + FileInfoContract.FileInfoContractClient FileInfos { get; } + + #endregion + #region CMS + PackageContract.PackageContractClient Packages { get; } + + #endregion +} diff --git a/src/BackOffice.BFF.Application/Common/Interfaces/ICurrentUserService.cs b/src/BackOffice.BFF.Application/Common/Interfaces/ICurrentUserService.cs new file mode 100644 index 0000000..f2e6270 --- /dev/null +++ b/src/BackOffice.BFF.Application/Common/Interfaces/ICurrentUserService.cs @@ -0,0 +1,6 @@ +namespace BackOffice.BFF.Application.Common.Interfaces; + +public interface ICurrentUserService +{ + string? UserId { get; } +} diff --git a/src/BackOffice.BFF.Application/Common/Interfaces/ITokenProvider.cs b/src/BackOffice.BFF.Application/Common/Interfaces/ITokenProvider.cs new file mode 100644 index 0000000..c1107c2 --- /dev/null +++ b/src/BackOffice.BFF.Application/Common/Interfaces/ITokenProvider.cs @@ -0,0 +1,6 @@ +namespace BackOffice.BFF.Application.Common.Interfaces; + +public interface ITokenProvider +{ + Task GetTokenAsync(); +} diff --git a/src/BackOffice.BFF.Application/Common/Mappings/GeneralMapping.cs b/src/BackOffice.BFF.Application/Common/Mappings/GeneralMapping.cs new file mode 100644 index 0000000..c7e33fe --- /dev/null +++ b/src/BackOffice.BFF.Application/Common/Mappings/GeneralMapping.cs @@ -0,0 +1,64 @@ +using System.Globalization; +namespace BackOffice.BFF.Application.Common.Mappings; + +public class GeneralMapping : IRegister +{ + void IRegister.Register(TypeAdapterConfig config) + { + config.NewConfig() + .MapWith(src => decimal.Parse(src)); + + config.NewConfig() + .MapWith(src => src.ToString("R", new CultureInfo("en-us"))); + + config.NewConfig() + .MapWith(src => src == null ? string.Empty : src.Value.ToString("R", new CultureInfo("en-us"))); + + config.NewConfig() + .MapWith(src => string.IsNullOrEmpty(src) ? null : decimal.Parse(src)); + + config.NewConfig() + .MapWith(src => src == Guid.Empty ? string.Empty : src.ToString()); + + config.NewConfig() + .MapWith(src => string.IsNullOrEmpty(src) ? Guid.Empty : Guid.Parse(src)); + + config.NewConfig() + .MapWith(src => string.IsNullOrEmpty(src) ? null : Guid.Parse(src)); + + config.NewConfig() + .MapWith(src => src.ToDateTime()); + + config.NewConfig() + .MapWith(src => src == null ? null : src.ToDateTime()); + + config.NewConfig() + .MapWith(src => Timestamp.FromDateTime(DateTime.SpecifyKind(src, DateTimeKind.Utc))); + + config.NewConfig() + .MapWith(src => src.HasValue ? Timestamp.FromDateTime(DateTime.SpecifyKind(src.Value, DateTimeKind.Utc)) : null); + + config.NewConfig() + .MapWith(src => src.ToTimeSpan()); + + config.NewConfig() + .MapWith(src => src == null ? null : src.ToTimeSpan()); + + config.NewConfig() + .MapWith(src => Duration.FromTimeSpan(src)); + + config.NewConfig() + .MapWith(src => src.HasValue ? Duration.FromTimeSpan(src.Value) : null); + + config.Default + .UseDestinationValue(member => member.SetterModifier == AccessModifier.None && + member.Type.IsGenericType && + member.Type.GetGenericTypeDefinition() == typeof(Google.Protobuf.Collections.RepeatedField<>)); + + config.NewConfig() + .MapWith(src => src.ToByteArray()); + + config.NewConfig() + .MapWith(src => Google.Protobuf.ByteString.CopyFrom(src)); + } +} diff --git a/src/BackOffice.BFF.Application/Common/Mappings/PackageProfile.cs b/src/BackOffice.BFF.Application/Common/Mappings/PackageProfile.cs new file mode 100644 index 0000000..d33887d --- /dev/null +++ b/src/BackOffice.BFF.Application/Common/Mappings/PackageProfile.cs @@ -0,0 +1,10 @@ +namespace BackOffice.BFF.Application.Common.Mappings; + +public class PackageProfile : IRegister +{ + void IRegister.Register(TypeAdapterConfig config) + { + //config.NewConfig() + // .Map(dest => dest.FullName, src => $"{src.Firstname} {src.Lastname}"); + } +} diff --git a/src/BackOffice.BFF.Application/Common/Mappings/RoleProfile.cs b/src/BackOffice.BFF.Application/Common/Mappings/RoleProfile.cs new file mode 100644 index 0000000..5067769 --- /dev/null +++ b/src/BackOffice.BFF.Application/Common/Mappings/RoleProfile.cs @@ -0,0 +1,10 @@ +namespace BackOffice.BFF.Application.Common.Mappings; + +public class RoleProfile : IRegister +{ + void IRegister.Register(TypeAdapterConfig config) + { + //config.NewConfig() + // .Map(dest => dest.FullName, src => $"{src.Firstname} {src.Lastname}"); + } +} diff --git a/src/BackOffice.BFF.Application/Common/Mappings/UserAddressProfile.cs b/src/BackOffice.BFF.Application/Common/Mappings/UserAddressProfile.cs new file mode 100644 index 0000000..c7b19c7 --- /dev/null +++ b/src/BackOffice.BFF.Application/Common/Mappings/UserAddressProfile.cs @@ -0,0 +1,10 @@ +namespace BackOffice.BFF.Application.Common.Mappings; + +public class UserAddressProfile : IRegister +{ + void IRegister.Register(TypeAdapterConfig config) + { + //config.NewConfig() + // .Map(dest => dest.FullName, src => $"{src.Firstname} {src.Lastname}"); + } +} diff --git a/src/BackOffice.BFF.Application/Common/Mappings/UserOrderProfile.cs b/src/BackOffice.BFF.Application/Common/Mappings/UserOrderProfile.cs new file mode 100644 index 0000000..f32ec6f --- /dev/null +++ b/src/BackOffice.BFF.Application/Common/Mappings/UserOrderProfile.cs @@ -0,0 +1,10 @@ +namespace BackOffice.BFF.Application.Common.Mappings; + +public class UserOrderProfile : IRegister +{ + void IRegister.Register(TypeAdapterConfig config) + { + //config.NewConfig() + // .Map(dest => dest.FullName, src => $"{src.Firstname} {src.Lastname}"); + } +} diff --git a/src/BackOffice.BFF.Application/Common/Mappings/UserProfile.cs b/src/BackOffice.BFF.Application/Common/Mappings/UserProfile.cs new file mode 100644 index 0000000..2fc57f2 --- /dev/null +++ b/src/BackOffice.BFF.Application/Common/Mappings/UserProfile.cs @@ -0,0 +1,10 @@ +namespace BackOffice.BFF.Application.Common.Mappings; + +public class UserProfile : IRegister +{ + void IRegister.Register(TypeAdapterConfig config) + { + //config.NewConfig() + // .Map(dest => dest.FullName, src => $"{src.Firstname} {src.Lastname}"); + } +} diff --git a/src/BackOffice.BFF.Application/Common/Mappings/UserRoleProfile.cs b/src/BackOffice.BFF.Application/Common/Mappings/UserRoleProfile.cs new file mode 100644 index 0000000..d8c531d --- /dev/null +++ b/src/BackOffice.BFF.Application/Common/Mappings/UserRoleProfile.cs @@ -0,0 +1,10 @@ +namespace BackOffice.BFF.Application.Common.Mappings; + +public class UserRoleProfile : IRegister +{ + void IRegister.Register(TypeAdapterConfig config) + { + //config.NewConfig() + // .Map(dest => dest.FullName, src => $"{src.Firstname} {src.Lastname}"); + } +} diff --git a/src/BackOffice.BFF.Application/Common/Models/MetaData.cs b/src/BackOffice.BFF.Application/Common/Models/MetaData.cs new file mode 100644 index 0000000..afae90a --- /dev/null +++ b/src/BackOffice.BFF.Application/Common/Models/MetaData.cs @@ -0,0 +1,22 @@ +namespace BackOffice.BFF.Application.Common.Models; + +public class MetaData +{ + //صفحه جاری + public long CurrentPage { get; set; } + + //تعداد کل صفحات + public long TotalPage { get; set; } + + //تعداد در هر صفحه + public long PageSize { get; set; } + + //تعداد کل آیتم‌ها + public long TotalCount { get; set; } + + //قبلی دارد؟ + public bool HasPrevious { get; set; } + + //بعدی دارد؟ + public bool HasNext { get; set; } +} diff --git a/src/BackOffice.BFF.Application/Common/Models/PaginationState.cs b/src/BackOffice.BFF.Application/Common/Models/PaginationState.cs new file mode 100644 index 0000000..95cc1b0 --- /dev/null +++ b/src/BackOffice.BFF.Application/Common/Models/PaginationState.cs @@ -0,0 +1,10 @@ +namespace BackOffice.BFF.Application.Common.Models; + +public class PaginationState +{ + //شماره صفحه + public int PageNumber { get; set; } + + //اندازه صفحه + public int PageSize { get; set; } +} diff --git a/src/BackOffice.BFF.Application/ConfigureServices.cs b/src/BackOffice.BFF.Application/ConfigureServices.cs new file mode 100644 index 0000000..937fde5 --- /dev/null +++ b/src/BackOffice.BFF.Application/ConfigureServices.cs @@ -0,0 +1,32 @@ +using BackOffice.BFF.Application.Common.Behaviours; +using MapsterMapper; +using System.Reflection; + +namespace Microsoft.Extensions.DependencyInjection; + +public static class ConfigureServices +{ + public static IServiceCollection AddApplicationServices(this IServiceCollection services) + { + services.AddMapping(); + services.AddValidatorsFromAssembly(Assembly.GetExecutingAssembly()); + services.AddMediatR(AppDomain.CurrentDomain.GetAssemblies()); + + services.AddTransient(typeof(IPipelineBehavior<,>), typeof(UnhandledExceptionBehaviour<,>)); + services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ValidationBehaviour<,>)); + services.AddTransient(typeof(IPipelineBehavior<,>), typeof(PerformanceBehaviour<,>)); + + return services; + } + private static IServiceCollection AddMapping(this IServiceCollection services) + { + var typeAdapterConfig = TypeAdapterConfig.GlobalSettings; + // scans the assembly and gets the IRegister, adding the registration to the TypeAdapterConfig + typeAdapterConfig.Scan(Assembly.GetExecutingAssembly()); + // register the mapper as Singleton service for my application + var mapperConfig = new Mapper(typeAdapterConfig); + services.AddSingleton(mapperConfig); + return services; + } +} + diff --git a/src/BackOffice.BFF.Application/GlobalUsings.cs b/src/BackOffice.BFF.Application/GlobalUsings.cs new file mode 100644 index 0000000..fe7cf40 --- /dev/null +++ b/src/BackOffice.BFF.Application/GlobalUsings.cs @@ -0,0 +1,12 @@ +global using MediatR; +global using FluentValidation; +global using Mapster; +global using System.Threading.Tasks; +global using System.Threading; +global using System.Collections.Generic; +global using System; +global using System.Linq; +global using Google.Protobuf.WellKnownTypes; +global using BackOffice.BFF.Application.Common.Interfaces; +global using BackOffice.BFF.Application.Common.Models; +global using BackOffice.BFF.Application.Common.Exceptions; diff --git a/src/BackOffice.BFF.Application/PackageCQ/Commands/CreateNewPackage/CreateNewPackageCommand.cs b/src/BackOffice.BFF.Application/PackageCQ/Commands/CreateNewPackage/CreateNewPackageCommand.cs new file mode 100644 index 0000000..91f7697 --- /dev/null +++ b/src/BackOffice.BFF.Application/PackageCQ/Commands/CreateNewPackage/CreateNewPackageCommand.cs @@ -0,0 +1,21 @@ +namespace BackOffice.BFF.Application.PackageCQ.Commands.CreateNewPackage; +public record CreateNewPackageCommand : IRequest +{ + //عنوان + public string Title { get; init; } + //توضیحات + public string Description { get; init; } + //فایل تصویر + public BoostCardFileModel ImageFile { get; init; } + //قیمت + public long Price { get; init; } + +}public class BoostCardFileModel +{ + //فایل + public byte[] File { get; set; } + //نام + public string FileName { get; set; } + //نوع فایل + public string Mime { get; set; } +} diff --git a/src/BackOffice.BFF.Application/PackageCQ/Commands/CreateNewPackage/CreateNewPackageCommandHandler.cs b/src/BackOffice.BFF.Application/PackageCQ/Commands/CreateNewPackage/CreateNewPackageCommandHandler.cs new file mode 100644 index 0000000..dc1105d --- /dev/null +++ b/src/BackOffice.BFF.Application/PackageCQ/Commands/CreateNewPackage/CreateNewPackageCommandHandler.cs @@ -0,0 +1,37 @@ +using BackOffice.BFF.Application.UserCQ.Commands.CreateNewUser; +using CMSMicroservice.Protobuf.Protos.Package; +using Google.Protobuf; + +namespace BackOffice.BFF.Application.PackageCQ.Commands.CreateNewPackage; +public class CreateNewPackageCommandHandler : IRequestHandler +{ + private readonly IApplicationContractContext _context; + + public CreateNewPackageCommandHandler(IApplicationContractContext context) + { + _context = context; + } + + public async Task Handle(CreateNewPackageCommand request, CancellationToken cancellationToken) + { + var createNewPackageRequest = request.Adapt(); + if (request.ImageFile != null && request.ImageFile.File != null && request.ImageFile.File.Length > 0) + { + var createNewFileInfo = await _context.FileInfos.CreateNewFileInfoAsync(new() + { + Directory = "Images/Package", + IsBase64 = false, + MIME = request.ImageFile.Mime, + FileName = request.ImageFile.FileName, + File = ByteString.CopyFrom(request.ImageFile.File) + }, cancellationToken: cancellationToken); + + if (createNewFileInfo != null && !string.IsNullOrWhiteSpace(createNewFileInfo.File)) + createNewPackageRequest.ImagePath = createNewFileInfo.File; + } + + var response = await _context.Packages.CreateNewPackageAsync(createNewPackageRequest, cancellationToken: cancellationToken); + + return response.Adapt(); + } +} diff --git a/src/BackOffice.BFF.Application/PackageCQ/Commands/CreateNewPackage/CreateNewPackageCommandValidator.cs b/src/BackOffice.BFF.Application/PackageCQ/Commands/CreateNewPackage/CreateNewPackageCommandValidator.cs new file mode 100644 index 0000000..b9c8ed3 --- /dev/null +++ b/src/BackOffice.BFF.Application/PackageCQ/Commands/CreateNewPackage/CreateNewPackageCommandValidator.cs @@ -0,0 +1,22 @@ +namespace BackOffice.BFF.Application.PackageCQ.Commands.CreateNewPackage; +public class CreateNewPackageCommandValidator : AbstractValidator +{ + public CreateNewPackageCommandValidator() + { + RuleFor(model => model.Title) + .NotEmpty(); + RuleFor(model => model.Description) + .NotEmpty(); + RuleFor(model => model.ImageFile) + .NotNull(); + RuleFor(model => model.Price) + .NotNull(); + } + public Func>> ValidateValue => async (model, propertyName) => + { + var result = await ValidateAsync(ValidationContext.CreateWithOptions((CreateNewPackageCommand)model, x => x.IncludeProperties(propertyName))); + if (result.IsValid) + return Array.Empty(); + return result.Errors.Select(e => e.ErrorMessage); + }; +} diff --git a/src/BackOffice.BFF.Application/PackageCQ/Commands/CreateNewPackage/CreateNewPackageResponseDto.cs b/src/BackOffice.BFF.Application/PackageCQ/Commands/CreateNewPackage/CreateNewPackageResponseDto.cs new file mode 100644 index 0000000..64d4b6d --- /dev/null +++ b/src/BackOffice.BFF.Application/PackageCQ/Commands/CreateNewPackage/CreateNewPackageResponseDto.cs @@ -0,0 +1,7 @@ +namespace BackOffice.BFF.Application.PackageCQ.Commands.CreateNewPackage; +public class CreateNewPackageResponseDto +{ + //شناسه + public long Id { get; set; } + +} \ No newline at end of file diff --git a/src/BackOffice.BFF.Application/PackageCQ/Commands/DeletePackage/DeletePackageCommand.cs b/src/BackOffice.BFF.Application/PackageCQ/Commands/DeletePackage/DeletePackageCommand.cs new file mode 100644 index 0000000..233e9e1 --- /dev/null +++ b/src/BackOffice.BFF.Application/PackageCQ/Commands/DeletePackage/DeletePackageCommand.cs @@ -0,0 +1,7 @@ +namespace BackOffice.BFF.Application.PackageCQ.Commands.DeletePackage; +public record DeletePackageCommand : IRequest +{ + //شناسه + public long Id { get; init; } + +} \ No newline at end of file diff --git a/src/BackOffice.BFF.Application/PackageCQ/Commands/DeletePackage/DeletePackageCommandHandler.cs b/src/BackOffice.BFF.Application/PackageCQ/Commands/DeletePackage/DeletePackageCommandHandler.cs new file mode 100644 index 0000000..f9723dd --- /dev/null +++ b/src/BackOffice.BFF.Application/PackageCQ/Commands/DeletePackage/DeletePackageCommandHandler.cs @@ -0,0 +1,19 @@ +using CMSMicroservice.Protobuf.Protos.Package; + +namespace BackOffice.BFF.Application.PackageCQ.Commands.DeletePackage; +public class DeletePackageCommandHandler : IRequestHandler +{ + private readonly IApplicationContractContext _context; + + public DeletePackageCommandHandler(IApplicationContractContext context) + { + _context = context; + } + + public async Task Handle(DeletePackageCommand request, CancellationToken cancellationToken) + { + await _context.Packages.DeletePackageAsync(request.Adapt(), cancellationToken: cancellationToken); + + return Unit.Value; + } +} diff --git a/src/BackOffice.BFF.Application/PackageCQ/Commands/DeletePackage/DeletePackageCommandValidator.cs b/src/BackOffice.BFF.Application/PackageCQ/Commands/DeletePackage/DeletePackageCommandValidator.cs new file mode 100644 index 0000000..6c69be0 --- /dev/null +++ b/src/BackOffice.BFF.Application/PackageCQ/Commands/DeletePackage/DeletePackageCommandValidator.cs @@ -0,0 +1,16 @@ +namespace BackOffice.BFF.Application.PackageCQ.Commands.DeletePackage; +public class DeletePackageCommandValidator : AbstractValidator +{ + public DeletePackageCommandValidator() + { + RuleFor(model => model.Id) + .NotNull(); + } + public Func>> ValidateValue => async (model, propertyName) => + { + var result = await ValidateAsync(ValidationContext.CreateWithOptions((DeletePackageCommand)model, x => x.IncludeProperties(propertyName))); + if (result.IsValid) + return Array.Empty(); + return result.Errors.Select(e => e.ErrorMessage); + }; +} diff --git a/src/BackOffice.BFF.Application/PackageCQ/Commands/UpdatePackage/UpdatePackageCommand.cs b/src/BackOffice.BFF.Application/PackageCQ/Commands/UpdatePackage/UpdatePackageCommand.cs new file mode 100644 index 0000000..bcd7ce3 --- /dev/null +++ b/src/BackOffice.BFF.Application/PackageCQ/Commands/UpdatePackage/UpdatePackageCommand.cs @@ -0,0 +1,25 @@ +namespace BackOffice.BFF.Application.PackageCQ.Commands.UpdatePackage; +public record UpdatePackageCommand : IRequest +{ + //شناسه + public long Id { get; init; } + //عنوان + public string Title { get; init; } + //توضیحات + public string Description { get; init; } + //آدرس تصویر + public string? ImagePath { get; init; } + //قیمت + public long Price { get; init; } + //فایل تصویر + public BoostCardFileModel? ImageFile { get; init; } + +}public class BoostCardFileModel +{ + //فایل + public byte[] File { get; set; } + //نام + public string FileName { get; set; } + //نوع فایل + public string Mime { get; set; } +} diff --git a/src/BackOffice.BFF.Application/PackageCQ/Commands/UpdatePackage/UpdatePackageCommandHandler.cs b/src/BackOffice.BFF.Application/PackageCQ/Commands/UpdatePackage/UpdatePackageCommandHandler.cs new file mode 100644 index 0000000..bbe3480 --- /dev/null +++ b/src/BackOffice.BFF.Application/PackageCQ/Commands/UpdatePackage/UpdatePackageCommandHandler.cs @@ -0,0 +1,35 @@ +using CMSMicroservice.Protobuf.Protos.Package; +using Google.Protobuf; + +namespace BackOffice.BFF.Application.PackageCQ.Commands.UpdatePackage; +public class UpdatePackageCommandHandler : IRequestHandler +{ + private readonly IApplicationContractContext _context; + + public UpdatePackageCommandHandler(IApplicationContractContext context) + { + _context = context; + } + + public async Task Handle(UpdatePackageCommand request, CancellationToken cancellationToken) + { + var updatePackageRequest = request.Adapt(); + if (request.ImageFile != null && request.ImageFile.File != null && request.ImageFile.File.Length > 0) + { + var createNewFileInfo = await _context.FileInfos.CreateNewFileInfoAsync(new() + { + Directory = "Images/Package", + IsBase64 = false, + MIME = request.ImageFile.Mime, + FileName = request.ImageFile.FileName, + File = ByteString.CopyFrom(request.ImageFile.File) + }, cancellationToken: cancellationToken); + + if (createNewFileInfo != null && !string.IsNullOrWhiteSpace(createNewFileInfo.File)) + updatePackageRequest.ImagePath = createNewFileInfo.File; + } + await _context.Packages.UpdatePackageAsync(updatePackageRequest, cancellationToken: cancellationToken); + + return Unit.Value; + } +} diff --git a/src/BackOffice.BFF.Application/PackageCQ/Commands/UpdatePackage/UpdatePackageCommandValidator.cs b/src/BackOffice.BFF.Application/PackageCQ/Commands/UpdatePackage/UpdatePackageCommandValidator.cs new file mode 100644 index 0000000..a49b5d9 --- /dev/null +++ b/src/BackOffice.BFF.Application/PackageCQ/Commands/UpdatePackage/UpdatePackageCommandValidator.cs @@ -0,0 +1,22 @@ +namespace BackOffice.BFF.Application.PackageCQ.Commands.UpdatePackage; +public class UpdatePackageCommandValidator : AbstractValidator +{ + public UpdatePackageCommandValidator() + { + RuleFor(model => model.Id) + .NotNull(); + RuleFor(model => model.Title) + .NotEmpty(); + RuleFor(model => model.Description) + .NotEmpty(); + RuleFor(model => model.Price) + .NotNull(); + } + public Func>> ValidateValue => async (model, propertyName) => + { + var result = await ValidateAsync(ValidationContext.CreateWithOptions((UpdatePackageCommand)model, x => x.IncludeProperties(propertyName))); + if (result.IsValid) + return Array.Empty(); + return result.Errors.Select(e => e.ErrorMessage); + }; +} diff --git a/src/BackOffice.BFF.Application/PackageCQ/Queries/GetAllPackageByFilter/GetAllPackageByFilterQuery.cs b/src/BackOffice.BFF.Application/PackageCQ/Queries/GetAllPackageByFilter/GetAllPackageByFilterQuery.cs new file mode 100644 index 0000000..8113b8b --- /dev/null +++ b/src/BackOffice.BFF.Application/PackageCQ/Queries/GetAllPackageByFilter/GetAllPackageByFilterQuery.cs @@ -0,0 +1,23 @@ +namespace BackOffice.BFF.Application.PackageCQ.Queries.GetAllPackageByFilter; +public record GetAllPackageByFilterQuery : IRequest +{ + //موقعیت صفحه بندی + public PaginationState? PaginationState { get; init; } + //مرتب سازی بر اساس + public string? SortBy { get; init; } + //فیلتر + public GetAllPackageByFilterFilter? Filter { get; init; } + +}public class GetAllPackageByFilterFilter +{ + //شناسه + public long? Id { get; set; } + //عنوان + public string? Title { get; set; } + //توضیحات + public string? Description { get; set; } + //آدرس تصویر + public string? ImagePath { get; set; } + //قیمت + public long? Price { get; set; } +} diff --git a/src/BackOffice.BFF.Application/PackageCQ/Queries/GetAllPackageByFilter/GetAllPackageByFilterQueryHandler.cs b/src/BackOffice.BFF.Application/PackageCQ/Queries/GetAllPackageByFilter/GetAllPackageByFilterQueryHandler.cs new file mode 100644 index 0000000..2dc525b --- /dev/null +++ b/src/BackOffice.BFF.Application/PackageCQ/Queries/GetAllPackageByFilter/GetAllPackageByFilterQueryHandler.cs @@ -0,0 +1,21 @@ +using BackOffice.BFF.Application.UserCQ.Queries.GetAllUserByFilter; +using CMSMicroservice.Protobuf.Protos.Package; + +namespace BackOffice.BFF.Application.PackageCQ.Queries.GetAllPackageByFilter; +public class GetAllPackageByFilterQueryHandler : IRequestHandler +{ + private readonly IApplicationContractContext _context; + + public GetAllPackageByFilterQueryHandler(IApplicationContractContext context) + { + _context = context; + } + + public async Task Handle(GetAllPackageByFilterQuery request, CancellationToken cancellationToken) + { + var response = await _context.Packages.GetAllPackageByFilterAsync(request.Adapt(), cancellationToken: cancellationToken); + + return response.Adapt(); + + } +} diff --git a/src/BackOffice.BFF.Application/PackageCQ/Queries/GetAllPackageByFilter/GetAllPackageByFilterQueryValidator.cs b/src/BackOffice.BFF.Application/PackageCQ/Queries/GetAllPackageByFilter/GetAllPackageByFilterQueryValidator.cs new file mode 100644 index 0000000..1930763 --- /dev/null +++ b/src/BackOffice.BFF.Application/PackageCQ/Queries/GetAllPackageByFilter/GetAllPackageByFilterQueryValidator.cs @@ -0,0 +1,14 @@ +namespace BackOffice.BFF.Application.PackageCQ.Queries.GetAllPackageByFilter; +public class GetAllPackageByFilterQueryValidator : AbstractValidator +{ + public GetAllPackageByFilterQueryValidator() + { + } + public Func>> ValidateValue => async (model, propertyName) => + { + var result = await ValidateAsync(ValidationContext.CreateWithOptions((GetAllPackageByFilterQuery)model, x => x.IncludeProperties(propertyName))); + if (result.IsValid) + return Array.Empty(); + return result.Errors.Select(e => e.ErrorMessage); + }; +} diff --git a/src/BackOffice.BFF.Application/PackageCQ/Queries/GetAllPackageByFilter/GetAllPackageByFilterResponseDto.cs b/src/BackOffice.BFF.Application/PackageCQ/Queries/GetAllPackageByFilter/GetAllPackageByFilterResponseDto.cs new file mode 100644 index 0000000..1cc866c --- /dev/null +++ b/src/BackOffice.BFF.Application/PackageCQ/Queries/GetAllPackageByFilter/GetAllPackageByFilterResponseDto.cs @@ -0,0 +1,21 @@ +namespace BackOffice.BFF.Application.PackageCQ.Queries.GetAllPackageByFilter; +public class GetAllPackageByFilterResponseDto +{ + //متادیتا + public MetaData MetaData { get; set; } + //مدل خروجی + public List? Models { get; set; } + +}public class GetAllPackageByFilterResponseModel +{ + //شناسه + public long Id { get; set; } + //عنوان + public string Title { get; set; } + //توضیحات + public string Description { get; set; } + //آدرس تصویر + public string ImagePath { get; set; } + //قیمت + public long Price { get; set; } +} diff --git a/src/BackOffice.BFF.Application/PackageCQ/Queries/GetPackage/GetPackageQuery.cs b/src/BackOffice.BFF.Application/PackageCQ/Queries/GetPackage/GetPackageQuery.cs new file mode 100644 index 0000000..5a0ca0c --- /dev/null +++ b/src/BackOffice.BFF.Application/PackageCQ/Queries/GetPackage/GetPackageQuery.cs @@ -0,0 +1,7 @@ +namespace BackOffice.BFF.Application.PackageCQ.Queries.GetPackage; +public record GetPackageQuery : IRequest +{ + //شناسه + public long Id { get; init; } + +} \ No newline at end of file diff --git a/src/BackOffice.BFF.Application/PackageCQ/Queries/GetPackage/GetPackageQueryHandler.cs b/src/BackOffice.BFF.Application/PackageCQ/Queries/GetPackage/GetPackageQueryHandler.cs new file mode 100644 index 0000000..4464990 --- /dev/null +++ b/src/BackOffice.BFF.Application/PackageCQ/Queries/GetPackage/GetPackageQueryHandler.cs @@ -0,0 +1,20 @@ +using BackOffice.BFF.Application.UserCQ.Queries.GetUser; +using CMSMicroservice.Protobuf.Protos.Package; + +namespace BackOffice.BFF.Application.PackageCQ.Queries.GetPackage; +public class GetPackageQueryHandler : IRequestHandler +{ + private readonly IApplicationContractContext _context; + + public GetPackageQueryHandler(IApplicationContractContext context) + { + _context = context; + } + + public async Task Handle(GetPackageQuery request, CancellationToken cancellationToken) + { + var response = await _context.Packages.GetPackageAsync(request.Adapt(), cancellationToken: cancellationToken); + + return response.Adapt(); + } +} diff --git a/src/BackOffice.BFF.Application/PackageCQ/Queries/GetPackage/GetPackageQueryValidator.cs b/src/BackOffice.BFF.Application/PackageCQ/Queries/GetPackage/GetPackageQueryValidator.cs new file mode 100644 index 0000000..ee54cc1 --- /dev/null +++ b/src/BackOffice.BFF.Application/PackageCQ/Queries/GetPackage/GetPackageQueryValidator.cs @@ -0,0 +1,16 @@ +namespace BackOffice.BFF.Application.PackageCQ.Queries.GetPackage; +public class GetPackageQueryValidator : AbstractValidator +{ + public GetPackageQueryValidator() + { + RuleFor(model => model.Id) + .NotNull(); + } + public Func>> ValidateValue => async (model, propertyName) => + { + var result = await ValidateAsync(ValidationContext.CreateWithOptions((GetPackageQuery)model, x => x.IncludeProperties(propertyName))); + if (result.IsValid) + return Array.Empty(); + return result.Errors.Select(e => e.ErrorMessage); + }; +} diff --git a/src/BackOffice.BFF.Application/PackageCQ/Queries/GetPackage/GetPackageResponseDto.cs b/src/BackOffice.BFF.Application/PackageCQ/Queries/GetPackage/GetPackageResponseDto.cs new file mode 100644 index 0000000..415f467 --- /dev/null +++ b/src/BackOffice.BFF.Application/PackageCQ/Queries/GetPackage/GetPackageResponseDto.cs @@ -0,0 +1,15 @@ +namespace BackOffice.BFF.Application.PackageCQ.Queries.GetPackage; +public class GetPackageResponseDto +{ + //شناسه + public long Id { get; set; } + //عنوان + public string Title { get; set; } + //توضیحات + public string Description { get; set; } + //آدرس تصویر + public string ImagePath { get; set; } + //قیمت + public long Price { get; set; } + +} \ No newline at end of file diff --git a/src/BackOffice.BFF.Application/RoleCQ/Commands/CreateNewRole/CreateNewRoleCommand.cs b/src/BackOffice.BFF.Application/RoleCQ/Commands/CreateNewRole/CreateNewRoleCommand.cs new file mode 100644 index 0000000..c86d61d --- /dev/null +++ b/src/BackOffice.BFF.Application/RoleCQ/Commands/CreateNewRole/CreateNewRoleCommand.cs @@ -0,0 +1,9 @@ +namespace BackOffice.BFF.Application.RoleCQ.Commands.CreateNewRole; +public record CreateNewRoleCommand : IRequest +{ + //نام لاتین + public string Name { get; init; } + //عنوان + public string Title { get; init; } + +} \ No newline at end of file diff --git a/src/BackOffice.BFF.Application/RoleCQ/Commands/CreateNewRole/CreateNewRoleCommandHandler.cs b/src/BackOffice.BFF.Application/RoleCQ/Commands/CreateNewRole/CreateNewRoleCommandHandler.cs new file mode 100644 index 0000000..2c4770d --- /dev/null +++ b/src/BackOffice.BFF.Application/RoleCQ/Commands/CreateNewRole/CreateNewRoleCommandHandler.cs @@ -0,0 +1,16 @@ +namespace BackOffice.BFF.Application.RoleCQ.Commands.CreateNewRole; +public class CreateNewRoleCommandHandler : IRequestHandler +{ + private readonly IApplicationContractContext _context; + + public CreateNewRoleCommandHandler(IApplicationContractContext context) + { + _context = context; + } + + public async Task Handle(CreateNewRoleCommand request, CancellationToken cancellationToken) + { + //TODO: Implement your business logic + return new CreateNewRoleResponseDto(); + } +} diff --git a/src/BackOffice.BFF.Application/RoleCQ/Commands/CreateNewRole/CreateNewRoleCommandValidator.cs b/src/BackOffice.BFF.Application/RoleCQ/Commands/CreateNewRole/CreateNewRoleCommandValidator.cs new file mode 100644 index 0000000..2336f69 --- /dev/null +++ b/src/BackOffice.BFF.Application/RoleCQ/Commands/CreateNewRole/CreateNewRoleCommandValidator.cs @@ -0,0 +1,18 @@ +namespace BackOffice.BFF.Application.RoleCQ.Commands.CreateNewRole; +public class CreateNewRoleCommandValidator : AbstractValidator +{ + public CreateNewRoleCommandValidator() + { + RuleFor(model => model.Name) + .NotEmpty(); + RuleFor(model => model.Title) + .NotEmpty(); + } + public Func>> ValidateValue => async (model, propertyName) => + { + var result = await ValidateAsync(ValidationContext.CreateWithOptions((CreateNewRoleCommand)model, x => x.IncludeProperties(propertyName))); + if (result.IsValid) + return Array.Empty(); + return result.Errors.Select(e => e.ErrorMessage); + }; +} diff --git a/src/BackOffice.BFF.Application/RoleCQ/Commands/CreateNewRole/CreateNewRoleResponseDto.cs b/src/BackOffice.BFF.Application/RoleCQ/Commands/CreateNewRole/CreateNewRoleResponseDto.cs new file mode 100644 index 0000000..dabb7ba --- /dev/null +++ b/src/BackOffice.BFF.Application/RoleCQ/Commands/CreateNewRole/CreateNewRoleResponseDto.cs @@ -0,0 +1,7 @@ +namespace BackOffice.BFF.Application.RoleCQ.Commands.CreateNewRole; +public class CreateNewRoleResponseDto +{ + //شناسه + public long Id { get; set; } + +} \ No newline at end of file diff --git a/src/BackOffice.BFF.Application/RoleCQ/Commands/DeleteRole/DeleteRoleCommand.cs b/src/BackOffice.BFF.Application/RoleCQ/Commands/DeleteRole/DeleteRoleCommand.cs new file mode 100644 index 0000000..97f2ef1 --- /dev/null +++ b/src/BackOffice.BFF.Application/RoleCQ/Commands/DeleteRole/DeleteRoleCommand.cs @@ -0,0 +1,7 @@ +namespace BackOffice.BFF.Application.RoleCQ.Commands.DeleteRole; +public record DeleteRoleCommand : IRequest +{ + //شناسه + public long Id { get; init; } + +} \ No newline at end of file diff --git a/src/BackOffice.BFF.Application/RoleCQ/Commands/DeleteRole/DeleteRoleCommandHandler.cs b/src/BackOffice.BFF.Application/RoleCQ/Commands/DeleteRole/DeleteRoleCommandHandler.cs new file mode 100644 index 0000000..2912ae1 --- /dev/null +++ b/src/BackOffice.BFF.Application/RoleCQ/Commands/DeleteRole/DeleteRoleCommandHandler.cs @@ -0,0 +1,16 @@ +namespace BackOffice.BFF.Application.RoleCQ.Commands.DeleteRole; +public class DeleteRoleCommandHandler : IRequestHandler +{ + private readonly IApplicationContractContext _context; + + public DeleteRoleCommandHandler(IApplicationContractContext context) + { + _context = context; + } + + public async Task Handle(DeleteRoleCommand request, CancellationToken cancellationToken) + { + //TODO: Implement your business logic + return new Unit(); + } +} diff --git a/src/BackOffice.BFF.Application/RoleCQ/Commands/DeleteRole/DeleteRoleCommandValidator.cs b/src/BackOffice.BFF.Application/RoleCQ/Commands/DeleteRole/DeleteRoleCommandValidator.cs new file mode 100644 index 0000000..a53298f --- /dev/null +++ b/src/BackOffice.BFF.Application/RoleCQ/Commands/DeleteRole/DeleteRoleCommandValidator.cs @@ -0,0 +1,16 @@ +namespace BackOffice.BFF.Application.RoleCQ.Commands.DeleteRole; +public class DeleteRoleCommandValidator : AbstractValidator +{ + public DeleteRoleCommandValidator() + { + RuleFor(model => model.Id) + .NotNull(); + } + public Func>> ValidateValue => async (model, propertyName) => + { + var result = await ValidateAsync(ValidationContext.CreateWithOptions((DeleteRoleCommand)model, x => x.IncludeProperties(propertyName))); + if (result.IsValid) + return Array.Empty(); + return result.Errors.Select(e => e.ErrorMessage); + }; +} diff --git a/src/BackOffice.BFF.Application/RoleCQ/Commands/UpdateRole/UpdateRoleCommand.cs b/src/BackOffice.BFF.Application/RoleCQ/Commands/UpdateRole/UpdateRoleCommand.cs new file mode 100644 index 0000000..092d4fe --- /dev/null +++ b/src/BackOffice.BFF.Application/RoleCQ/Commands/UpdateRole/UpdateRoleCommand.cs @@ -0,0 +1,11 @@ +namespace BackOffice.BFF.Application.RoleCQ.Commands.UpdateRole; +public record UpdateRoleCommand : IRequest +{ + //شناسه + public long Id { get; init; } + //نام لاتین + public string Name { get; init; } + //عنوان + public string Title { get; init; } + +} \ No newline at end of file diff --git a/src/BackOffice.BFF.Application/RoleCQ/Commands/UpdateRole/UpdateRoleCommandHandler.cs b/src/BackOffice.BFF.Application/RoleCQ/Commands/UpdateRole/UpdateRoleCommandHandler.cs new file mode 100644 index 0000000..487a327 --- /dev/null +++ b/src/BackOffice.BFF.Application/RoleCQ/Commands/UpdateRole/UpdateRoleCommandHandler.cs @@ -0,0 +1,16 @@ +namespace BackOffice.BFF.Application.RoleCQ.Commands.UpdateRole; +public class UpdateRoleCommandHandler : IRequestHandler +{ + private readonly IApplicationContractContext _context; + + public UpdateRoleCommandHandler(IApplicationContractContext context) + { + _context = context; + } + + public async Task Handle(UpdateRoleCommand request, CancellationToken cancellationToken) + { + //TODO: Implement your business logic + return new Unit(); + } +} diff --git a/src/BackOffice.BFF.Application/RoleCQ/Commands/UpdateRole/UpdateRoleCommandValidator.cs b/src/BackOffice.BFF.Application/RoleCQ/Commands/UpdateRole/UpdateRoleCommandValidator.cs new file mode 100644 index 0000000..9afaa20 --- /dev/null +++ b/src/BackOffice.BFF.Application/RoleCQ/Commands/UpdateRole/UpdateRoleCommandValidator.cs @@ -0,0 +1,20 @@ +namespace BackOffice.BFF.Application.RoleCQ.Commands.UpdateRole; +public class UpdateRoleCommandValidator : AbstractValidator +{ + public UpdateRoleCommandValidator() + { + RuleFor(model => model.Id) + .NotNull(); + RuleFor(model => model.Name) + .NotEmpty(); + RuleFor(model => model.Title) + .NotEmpty(); + } + public Func>> ValidateValue => async (model, propertyName) => + { + var result = await ValidateAsync(ValidationContext.CreateWithOptions((UpdateRoleCommand)model, x => x.IncludeProperties(propertyName))); + if (result.IsValid) + return Array.Empty(); + return result.Errors.Select(e => e.ErrorMessage); + }; +} diff --git a/src/BackOffice.BFF.Application/RoleCQ/Queries/GetAllRoleByFilter/GetAllRoleByFilterQuery.cs b/src/BackOffice.BFF.Application/RoleCQ/Queries/GetAllRoleByFilter/GetAllRoleByFilterQuery.cs new file mode 100644 index 0000000..b8a0986 --- /dev/null +++ b/src/BackOffice.BFF.Application/RoleCQ/Queries/GetAllRoleByFilter/GetAllRoleByFilterQuery.cs @@ -0,0 +1,19 @@ +namespace BackOffice.BFF.Application.RoleCQ.Queries.GetAllRoleByFilter; +public record GetAllRoleByFilterQuery : IRequest +{ + //موقعیت صفحه بندی + public PaginationState? PaginationState { get; init; } + //مرتب سازی بر اساس + public string? SortBy { get; init; } + //فیلتر + public GetAllRoleByFilterFilter? Filter { get; init; } + +}public class GetAllRoleByFilterFilter +{ + //شناسه + public long? Id { get; set; } + //نام لاتین + public string? Name { get; set; } + //عنوان + public string? Title { get; set; } +} diff --git a/src/BackOffice.BFF.Application/RoleCQ/Queries/GetAllRoleByFilter/GetAllRoleByFilterQueryHandler.cs b/src/BackOffice.BFF.Application/RoleCQ/Queries/GetAllRoleByFilter/GetAllRoleByFilterQueryHandler.cs new file mode 100644 index 0000000..8636d63 --- /dev/null +++ b/src/BackOffice.BFF.Application/RoleCQ/Queries/GetAllRoleByFilter/GetAllRoleByFilterQueryHandler.cs @@ -0,0 +1,16 @@ +namespace BackOffice.BFF.Application.RoleCQ.Queries.GetAllRoleByFilter; +public class GetAllRoleByFilterQueryHandler : IRequestHandler +{ + private readonly IApplicationContractContext _context; + + public GetAllRoleByFilterQueryHandler(IApplicationContractContext context) + { + _context = context; + } + + public async Task Handle(GetAllRoleByFilterQuery request, CancellationToken cancellationToken) + { + //TODO: Implement your business logic + return new GetAllRoleByFilterResponseDto(); + } +} diff --git a/src/BackOffice.BFF.Application/RoleCQ/Queries/GetAllRoleByFilter/GetAllRoleByFilterQueryValidator.cs b/src/BackOffice.BFF.Application/RoleCQ/Queries/GetAllRoleByFilter/GetAllRoleByFilterQueryValidator.cs new file mode 100644 index 0000000..b176c25 --- /dev/null +++ b/src/BackOffice.BFF.Application/RoleCQ/Queries/GetAllRoleByFilter/GetAllRoleByFilterQueryValidator.cs @@ -0,0 +1,14 @@ +namespace BackOffice.BFF.Application.RoleCQ.Queries.GetAllRoleByFilter; +public class GetAllRoleByFilterQueryValidator : AbstractValidator +{ + public GetAllRoleByFilterQueryValidator() + { + } + public Func>> ValidateValue => async (model, propertyName) => + { + var result = await ValidateAsync(ValidationContext.CreateWithOptions((GetAllRoleByFilterQuery)model, x => x.IncludeProperties(propertyName))); + if (result.IsValid) + return Array.Empty(); + return result.Errors.Select(e => e.ErrorMessage); + }; +} diff --git a/src/BackOffice.BFF.Application/RoleCQ/Queries/GetAllRoleByFilter/GetAllRoleByFilterResponseDto.cs b/src/BackOffice.BFF.Application/RoleCQ/Queries/GetAllRoleByFilter/GetAllRoleByFilterResponseDto.cs new file mode 100644 index 0000000..83a34f1 --- /dev/null +++ b/src/BackOffice.BFF.Application/RoleCQ/Queries/GetAllRoleByFilter/GetAllRoleByFilterResponseDto.cs @@ -0,0 +1,17 @@ +namespace BackOffice.BFF.Application.RoleCQ.Queries.GetAllRoleByFilter; +public class GetAllRoleByFilterResponseDto +{ + //متادیتا + public MetaData MetaData { get; set; } + //مدل خروجی + public List? Models { get; set; } + +}public class GetAllRoleByFilterResponseModel +{ + //شناسه + public long Id { get; set; } + //نام لاتین + public string Name { get; set; } + //عنوان + public string Title { get; set; } +} diff --git a/src/BackOffice.BFF.Application/RoleCQ/Queries/GetRole/GetRoleQuery.cs b/src/BackOffice.BFF.Application/RoleCQ/Queries/GetRole/GetRoleQuery.cs new file mode 100644 index 0000000..53fcb4e --- /dev/null +++ b/src/BackOffice.BFF.Application/RoleCQ/Queries/GetRole/GetRoleQuery.cs @@ -0,0 +1,7 @@ +namespace BackOffice.BFF.Application.RoleCQ.Queries.GetRole; +public record GetRoleQuery : IRequest +{ + //شناسه + public long Id { get; init; } + +} \ No newline at end of file diff --git a/src/BackOffice.BFF.Application/RoleCQ/Queries/GetRole/GetRoleQueryHandler.cs b/src/BackOffice.BFF.Application/RoleCQ/Queries/GetRole/GetRoleQueryHandler.cs new file mode 100644 index 0000000..c6ee6d7 --- /dev/null +++ b/src/BackOffice.BFF.Application/RoleCQ/Queries/GetRole/GetRoleQueryHandler.cs @@ -0,0 +1,16 @@ +namespace BackOffice.BFF.Application.RoleCQ.Queries.GetRole; +public class GetRoleQueryHandler : IRequestHandler +{ + private readonly IApplicationContractContext _context; + + public GetRoleQueryHandler(IApplicationContractContext context) + { + _context = context; + } + + public async Task Handle(GetRoleQuery request, CancellationToken cancellationToken) + { + //TODO: Implement your business logic + return new GetRoleResponseDto(); + } +} diff --git a/src/BackOffice.BFF.Application/RoleCQ/Queries/GetRole/GetRoleQueryValidator.cs b/src/BackOffice.BFF.Application/RoleCQ/Queries/GetRole/GetRoleQueryValidator.cs new file mode 100644 index 0000000..0ab1f43 --- /dev/null +++ b/src/BackOffice.BFF.Application/RoleCQ/Queries/GetRole/GetRoleQueryValidator.cs @@ -0,0 +1,16 @@ +namespace BackOffice.BFF.Application.RoleCQ.Queries.GetRole; +public class GetRoleQueryValidator : AbstractValidator +{ + public GetRoleQueryValidator() + { + RuleFor(model => model.Id) + .NotNull(); + } + public Func>> ValidateValue => async (model, propertyName) => + { + var result = await ValidateAsync(ValidationContext.CreateWithOptions((GetRoleQuery)model, x => x.IncludeProperties(propertyName))); + if (result.IsValid) + return Array.Empty(); + return result.Errors.Select(e => e.ErrorMessage); + }; +} diff --git a/src/BackOffice.BFF.Application/RoleCQ/Queries/GetRole/GetRoleResponseDto.cs b/src/BackOffice.BFF.Application/RoleCQ/Queries/GetRole/GetRoleResponseDto.cs new file mode 100644 index 0000000..6cb8dfe --- /dev/null +++ b/src/BackOffice.BFF.Application/RoleCQ/Queries/GetRole/GetRoleResponseDto.cs @@ -0,0 +1,11 @@ +namespace BackOffice.BFF.Application.RoleCQ.Queries.GetRole; +public class GetRoleResponseDto +{ + //شناسه + public long Id { get; set; } + //نام لاتین + public string Name { get; set; } + //عنوان + public string Title { get; set; } + +} \ No newline at end of file diff --git a/src/BackOffice.BFF.Application/UserAddressCQ/Commands/CreateNewUserAddress/CreateNewUserAddressCommand.cs b/src/BackOffice.BFF.Application/UserAddressCQ/Commands/CreateNewUserAddress/CreateNewUserAddressCommand.cs new file mode 100644 index 0000000..e472528 --- /dev/null +++ b/src/BackOffice.BFF.Application/UserAddressCQ/Commands/CreateNewUserAddress/CreateNewUserAddressCommand.cs @@ -0,0 +1,17 @@ +namespace BackOffice.BFF.Application.UserAddressCQ.Commands.CreateNewUserAddress; +public record CreateNewUserAddressCommand : IRequest +{ + //شناسه کاربر + public long UserId { get; init; } + //عنوان + public string Title { get; init; } + //آدرس + public string Address { get; init; } + //کدپستی + public string PostalCode { get; init; } + //پیشفرض؟ + public bool IsDefault { get; init; } + //شناسه شهر + public long CityId { get; init; } + +} \ No newline at end of file diff --git a/src/BackOffice.BFF.Application/UserAddressCQ/Commands/CreateNewUserAddress/CreateNewUserAddressCommandHandler.cs b/src/BackOffice.BFF.Application/UserAddressCQ/Commands/CreateNewUserAddress/CreateNewUserAddressCommandHandler.cs new file mode 100644 index 0000000..5bc37d5 --- /dev/null +++ b/src/BackOffice.BFF.Application/UserAddressCQ/Commands/CreateNewUserAddress/CreateNewUserAddressCommandHandler.cs @@ -0,0 +1,16 @@ +namespace BackOffice.BFF.Application.UserAddressCQ.Commands.CreateNewUserAddress; +public class CreateNewUserAddressCommandHandler : IRequestHandler +{ + private readonly IApplicationContractContext _context; + + public CreateNewUserAddressCommandHandler(IApplicationContractContext context) + { + _context = context; + } + + public async Task Handle(CreateNewUserAddressCommand request, CancellationToken cancellationToken) + { + //TODO: Implement your business logic + return new CreateNewUserAddressResponseDto(); + } +} diff --git a/src/BackOffice.BFF.Application/UserAddressCQ/Commands/CreateNewUserAddress/CreateNewUserAddressCommandValidator.cs b/src/BackOffice.BFF.Application/UserAddressCQ/Commands/CreateNewUserAddress/CreateNewUserAddressCommandValidator.cs new file mode 100644 index 0000000..421e2fe --- /dev/null +++ b/src/BackOffice.BFF.Application/UserAddressCQ/Commands/CreateNewUserAddress/CreateNewUserAddressCommandValidator.cs @@ -0,0 +1,26 @@ +namespace BackOffice.BFF.Application.UserAddressCQ.Commands.CreateNewUserAddress; +public class CreateNewUserAddressCommandValidator : AbstractValidator +{ + public CreateNewUserAddressCommandValidator() + { + RuleFor(model => model.UserId) + .NotNull(); + RuleFor(model => model.Title) + .NotEmpty(); + RuleFor(model => model.Address) + .NotEmpty(); + RuleFor(model => model.PostalCode) + .NotEmpty(); + RuleFor(model => model.IsDefault) + .NotNull(); + RuleFor(model => model.CityId) + .NotNull(); + } + public Func>> ValidateValue => async (model, propertyName) => + { + var result = await ValidateAsync(ValidationContext.CreateWithOptions((CreateNewUserAddressCommand)model, x => x.IncludeProperties(propertyName))); + if (result.IsValid) + return Array.Empty(); + return result.Errors.Select(e => e.ErrorMessage); + }; +} diff --git a/src/BackOffice.BFF.Application/UserAddressCQ/Commands/CreateNewUserAddress/CreateNewUserAddressResponseDto.cs b/src/BackOffice.BFF.Application/UserAddressCQ/Commands/CreateNewUserAddress/CreateNewUserAddressResponseDto.cs new file mode 100644 index 0000000..5d500aa --- /dev/null +++ b/src/BackOffice.BFF.Application/UserAddressCQ/Commands/CreateNewUserAddress/CreateNewUserAddressResponseDto.cs @@ -0,0 +1,7 @@ +namespace BackOffice.BFF.Application.UserAddressCQ.Commands.CreateNewUserAddress; +public class CreateNewUserAddressResponseDto +{ + //شناسه + public long Id { get; set; } + +} \ No newline at end of file diff --git a/src/BackOffice.BFF.Application/UserAddressCQ/Commands/DeleteUserAddress/DeleteUserAddressCommand.cs b/src/BackOffice.BFF.Application/UserAddressCQ/Commands/DeleteUserAddress/DeleteUserAddressCommand.cs new file mode 100644 index 0000000..f9a7e1d --- /dev/null +++ b/src/BackOffice.BFF.Application/UserAddressCQ/Commands/DeleteUserAddress/DeleteUserAddressCommand.cs @@ -0,0 +1,7 @@ +namespace BackOffice.BFF.Application.UserAddressCQ.Commands.DeleteUserAddress; +public record DeleteUserAddressCommand : IRequest +{ + //شناسه + public long Id { get; init; } + +} \ No newline at end of file diff --git a/src/BackOffice.BFF.Application/UserAddressCQ/Commands/DeleteUserAddress/DeleteUserAddressCommandHandler.cs b/src/BackOffice.BFF.Application/UserAddressCQ/Commands/DeleteUserAddress/DeleteUserAddressCommandHandler.cs new file mode 100644 index 0000000..1d20d78 --- /dev/null +++ b/src/BackOffice.BFF.Application/UserAddressCQ/Commands/DeleteUserAddress/DeleteUserAddressCommandHandler.cs @@ -0,0 +1,16 @@ +namespace BackOffice.BFF.Application.UserAddressCQ.Commands.DeleteUserAddress; +public class DeleteUserAddressCommandHandler : IRequestHandler +{ + private readonly IApplicationContractContext _context; + + public DeleteUserAddressCommandHandler(IApplicationContractContext context) + { + _context = context; + } + + public async Task Handle(DeleteUserAddressCommand request, CancellationToken cancellationToken) + { + //TODO: Implement your business logic + return new Unit(); + } +} diff --git a/src/BackOffice.BFF.Application/UserAddressCQ/Commands/DeleteUserAddress/DeleteUserAddressCommandValidator.cs b/src/BackOffice.BFF.Application/UserAddressCQ/Commands/DeleteUserAddress/DeleteUserAddressCommandValidator.cs new file mode 100644 index 0000000..3f778aa --- /dev/null +++ b/src/BackOffice.BFF.Application/UserAddressCQ/Commands/DeleteUserAddress/DeleteUserAddressCommandValidator.cs @@ -0,0 +1,16 @@ +namespace BackOffice.BFF.Application.UserAddressCQ.Commands.DeleteUserAddress; +public class DeleteUserAddressCommandValidator : AbstractValidator +{ + public DeleteUserAddressCommandValidator() + { + RuleFor(model => model.Id) + .NotNull(); + } + public Func>> ValidateValue => async (model, propertyName) => + { + var result = await ValidateAsync(ValidationContext.CreateWithOptions((DeleteUserAddressCommand)model, x => x.IncludeProperties(propertyName))); + if (result.IsValid) + return Array.Empty(); + return result.Errors.Select(e => e.ErrorMessage); + }; +} diff --git a/src/BackOffice.BFF.Application/UserAddressCQ/Commands/UpdateUserAddress/UpdateUserAddressCommand.cs b/src/BackOffice.BFF.Application/UserAddressCQ/Commands/UpdateUserAddress/UpdateUserAddressCommand.cs new file mode 100644 index 0000000..92cff7a --- /dev/null +++ b/src/BackOffice.BFF.Application/UserAddressCQ/Commands/UpdateUserAddress/UpdateUserAddressCommand.cs @@ -0,0 +1,19 @@ +namespace BackOffice.BFF.Application.UserAddressCQ.Commands.UpdateUserAddress; +public record UpdateUserAddressCommand : IRequest +{ + //شناسه + public long Id { get; init; } + //شناسه کاربر + public long UserId { get; init; } + //عنوان + public string Title { get; init; } + //آدرس + public string Address { get; init; } + //کدپستی + public string PostalCode { get; init; } + //پیشفرض؟ + public bool IsDefault { get; init; } + //شناسه شهر + public long CityId { get; init; } + +} \ No newline at end of file diff --git a/src/BackOffice.BFF.Application/UserAddressCQ/Commands/UpdateUserAddress/UpdateUserAddressCommandHandler.cs b/src/BackOffice.BFF.Application/UserAddressCQ/Commands/UpdateUserAddress/UpdateUserAddressCommandHandler.cs new file mode 100644 index 0000000..e3f93d3 --- /dev/null +++ b/src/BackOffice.BFF.Application/UserAddressCQ/Commands/UpdateUserAddress/UpdateUserAddressCommandHandler.cs @@ -0,0 +1,16 @@ +namespace BackOffice.BFF.Application.UserAddressCQ.Commands.UpdateUserAddress; +public class UpdateUserAddressCommandHandler : IRequestHandler +{ + private readonly IApplicationContractContext _context; + + public UpdateUserAddressCommandHandler(IApplicationContractContext context) + { + _context = context; + } + + public async Task Handle(UpdateUserAddressCommand request, CancellationToken cancellationToken) + { + //TODO: Implement your business logic + return new Unit(); + } +} diff --git a/src/BackOffice.BFF.Application/UserAddressCQ/Commands/UpdateUserAddress/UpdateUserAddressCommandValidator.cs b/src/BackOffice.BFF.Application/UserAddressCQ/Commands/UpdateUserAddress/UpdateUserAddressCommandValidator.cs new file mode 100644 index 0000000..b560594 --- /dev/null +++ b/src/BackOffice.BFF.Application/UserAddressCQ/Commands/UpdateUserAddress/UpdateUserAddressCommandValidator.cs @@ -0,0 +1,28 @@ +namespace BackOffice.BFF.Application.UserAddressCQ.Commands.UpdateUserAddress; +public class UpdateUserAddressCommandValidator : AbstractValidator +{ + public UpdateUserAddressCommandValidator() + { + RuleFor(model => model.Id) + .NotNull(); + RuleFor(model => model.UserId) + .NotNull(); + RuleFor(model => model.Title) + .NotEmpty(); + RuleFor(model => model.Address) + .NotEmpty(); + RuleFor(model => model.PostalCode) + .NotEmpty(); + RuleFor(model => model.IsDefault) + .NotNull(); + RuleFor(model => model.CityId) + .NotNull(); + } + public Func>> ValidateValue => async (model, propertyName) => + { + var result = await ValidateAsync(ValidationContext.CreateWithOptions((UpdateUserAddressCommand)model, x => x.IncludeProperties(propertyName))); + if (result.IsValid) + return Array.Empty(); + return result.Errors.Select(e => e.ErrorMessage); + }; +} diff --git a/src/BackOffice.BFF.Application/UserAddressCQ/Queries/GetAllUserAddressByFilter/GetAllUserAddressByFilterQuery.cs b/src/BackOffice.BFF.Application/UserAddressCQ/Queries/GetAllUserAddressByFilter/GetAllUserAddressByFilterQuery.cs new file mode 100644 index 0000000..72af251 --- /dev/null +++ b/src/BackOffice.BFF.Application/UserAddressCQ/Queries/GetAllUserAddressByFilter/GetAllUserAddressByFilterQuery.cs @@ -0,0 +1,27 @@ +namespace BackOffice.BFF.Application.UserAddressCQ.Queries.GetAllUserAddressByFilter; +public record GetAllUserAddressByFilterQuery : IRequest +{ + //موقعیت صفحه بندی + public PaginationState? PaginationState { get; init; } + //مرتب سازی بر اساس + public string? SortBy { get; init; } + //فیلتر + public GetAllUserAddressByFilterFilter? Filter { get; init; } + +}public class GetAllUserAddressByFilterFilter +{ + //شناسه + public long? Id { get; set; } + //شناسه کاربر + public long? UserId { get; set; } + //عنوان + public string? Title { get; set; } + //آدرس + public string? Address { get; set; } + //کدپستی + public string? PostalCode { get; set; } + //پیشفرض؟ + public bool? IsDefault { get; set; } + //شناسه شهر + public long? CityId { get; set; } +} diff --git a/src/BackOffice.BFF.Application/UserAddressCQ/Queries/GetAllUserAddressByFilter/GetAllUserAddressByFilterQueryHandler.cs b/src/BackOffice.BFF.Application/UserAddressCQ/Queries/GetAllUserAddressByFilter/GetAllUserAddressByFilterQueryHandler.cs new file mode 100644 index 0000000..b49f84d --- /dev/null +++ b/src/BackOffice.BFF.Application/UserAddressCQ/Queries/GetAllUserAddressByFilter/GetAllUserAddressByFilterQueryHandler.cs @@ -0,0 +1,16 @@ +namespace BackOffice.BFF.Application.UserAddressCQ.Queries.GetAllUserAddressByFilter; +public class GetAllUserAddressByFilterQueryHandler : IRequestHandler +{ + private readonly IApplicationContractContext _context; + + public GetAllUserAddressByFilterQueryHandler(IApplicationContractContext context) + { + _context = context; + } + + public async Task Handle(GetAllUserAddressByFilterQuery request, CancellationToken cancellationToken) + { + //TODO: Implement your business logic + return new GetAllUserAddressByFilterResponseDto(); + } +} diff --git a/src/BackOffice.BFF.Application/UserAddressCQ/Queries/GetAllUserAddressByFilter/GetAllUserAddressByFilterQueryValidator.cs b/src/BackOffice.BFF.Application/UserAddressCQ/Queries/GetAllUserAddressByFilter/GetAllUserAddressByFilterQueryValidator.cs new file mode 100644 index 0000000..1937c8b --- /dev/null +++ b/src/BackOffice.BFF.Application/UserAddressCQ/Queries/GetAllUserAddressByFilter/GetAllUserAddressByFilterQueryValidator.cs @@ -0,0 +1,14 @@ +namespace BackOffice.BFF.Application.UserAddressCQ.Queries.GetAllUserAddressByFilter; +public class GetAllUserAddressByFilterQueryValidator : AbstractValidator +{ + public GetAllUserAddressByFilterQueryValidator() + { + } + public Func>> ValidateValue => async (model, propertyName) => + { + var result = await ValidateAsync(ValidationContext.CreateWithOptions((GetAllUserAddressByFilterQuery)model, x => x.IncludeProperties(propertyName))); + if (result.IsValid) + return Array.Empty(); + return result.Errors.Select(e => e.ErrorMessage); + }; +} diff --git a/src/BackOffice.BFF.Application/UserAddressCQ/Queries/GetAllUserAddressByFilter/GetAllUserAddressByFilterResponseDto.cs b/src/BackOffice.BFF.Application/UserAddressCQ/Queries/GetAllUserAddressByFilter/GetAllUserAddressByFilterResponseDto.cs new file mode 100644 index 0000000..b96bbc1 --- /dev/null +++ b/src/BackOffice.BFF.Application/UserAddressCQ/Queries/GetAllUserAddressByFilter/GetAllUserAddressByFilterResponseDto.cs @@ -0,0 +1,25 @@ +namespace BackOffice.BFF.Application.UserAddressCQ.Queries.GetAllUserAddressByFilter; +public class GetAllUserAddressByFilterResponseDto +{ + //متادیتا + public MetaData MetaData { get; set; } + //مدل خروجی + public List? Models { get; set; } + +}public class GetAllUserAddressByFilterResponseModel +{ + //شناسه + public long Id { get; set; } + //شناسه کاربر + public long UserId { get; set; } + //عنوان + public string Title { get; set; } + //آدرس + public string Address { get; set; } + //کدپستی + public string PostalCode { get; set; } + //پیشفرض؟ + public bool IsDefault { get; set; } + //شناسه شهر + public long CityId { get; set; } +} diff --git a/src/BackOffice.BFF.Application/UserAddressCQ/Queries/GetUserAddress/GetUserAddressQuery.cs b/src/BackOffice.BFF.Application/UserAddressCQ/Queries/GetUserAddress/GetUserAddressQuery.cs new file mode 100644 index 0000000..ac3d2c5 --- /dev/null +++ b/src/BackOffice.BFF.Application/UserAddressCQ/Queries/GetUserAddress/GetUserAddressQuery.cs @@ -0,0 +1,7 @@ +namespace BackOffice.BFF.Application.UserAddressCQ.Queries.GetUserAddress; +public record GetUserAddressQuery : IRequest +{ + //شناسه + public long Id { get; init; } + +} \ No newline at end of file diff --git a/src/BackOffice.BFF.Application/UserAddressCQ/Queries/GetUserAddress/GetUserAddressQueryHandler.cs b/src/BackOffice.BFF.Application/UserAddressCQ/Queries/GetUserAddress/GetUserAddressQueryHandler.cs new file mode 100644 index 0000000..ee46d91 --- /dev/null +++ b/src/BackOffice.BFF.Application/UserAddressCQ/Queries/GetUserAddress/GetUserAddressQueryHandler.cs @@ -0,0 +1,16 @@ +namespace BackOffice.BFF.Application.UserAddressCQ.Queries.GetUserAddress; +public class GetUserAddressQueryHandler : IRequestHandler +{ + private readonly IApplicationContractContext _context; + + public GetUserAddressQueryHandler(IApplicationContractContext context) + { + _context = context; + } + + public async Task Handle(GetUserAddressQuery request, CancellationToken cancellationToken) + { + //TODO: Implement your business logic + return new GetUserAddressResponseDto(); + } +} diff --git a/src/BackOffice.BFF.Application/UserAddressCQ/Queries/GetUserAddress/GetUserAddressQueryValidator.cs b/src/BackOffice.BFF.Application/UserAddressCQ/Queries/GetUserAddress/GetUserAddressQueryValidator.cs new file mode 100644 index 0000000..fecfc45 --- /dev/null +++ b/src/BackOffice.BFF.Application/UserAddressCQ/Queries/GetUserAddress/GetUserAddressQueryValidator.cs @@ -0,0 +1,16 @@ +namespace BackOffice.BFF.Application.UserAddressCQ.Queries.GetUserAddress; +public class GetUserAddressQueryValidator : AbstractValidator +{ + public GetUserAddressQueryValidator() + { + RuleFor(model => model.Id) + .NotNull(); + } + public Func>> ValidateValue => async (model, propertyName) => + { + var result = await ValidateAsync(ValidationContext.CreateWithOptions((GetUserAddressQuery)model, x => x.IncludeProperties(propertyName))); + if (result.IsValid) + return Array.Empty(); + return result.Errors.Select(e => e.ErrorMessage); + }; +} diff --git a/src/BackOffice.BFF.Application/UserAddressCQ/Queries/GetUserAddress/GetUserAddressResponseDto.cs b/src/BackOffice.BFF.Application/UserAddressCQ/Queries/GetUserAddress/GetUserAddressResponseDto.cs new file mode 100644 index 0000000..479d5d2 --- /dev/null +++ b/src/BackOffice.BFF.Application/UserAddressCQ/Queries/GetUserAddress/GetUserAddressResponseDto.cs @@ -0,0 +1,19 @@ +namespace BackOffice.BFF.Application.UserAddressCQ.Queries.GetUserAddress; +public class GetUserAddressResponseDto +{ + //شناسه + public long Id { get; set; } + //شناسه کاربر + public long UserId { get; set; } + //عنوان + public string Title { get; set; } + //آدرس + public string Address { get; set; } + //کدپستی + public string PostalCode { get; set; } + //پیشفرض؟ + public bool IsDefault { get; set; } + //شناسه شهر + public long CityId { get; set; } + +} \ No newline at end of file diff --git a/src/BackOffice.BFF.Application/UserCQ/Commands/CreateNewUser/CreateNewUserCommand.cs b/src/BackOffice.BFF.Application/UserCQ/Commands/CreateNewUser/CreateNewUserCommand.cs new file mode 100644 index 0000000..12900ef --- /dev/null +++ b/src/BackOffice.BFF.Application/UserCQ/Commands/CreateNewUser/CreateNewUserCommand.cs @@ -0,0 +1,17 @@ +namespace BackOffice.BFF.Application.UserCQ.Commands.CreateNewUser; +public record CreateNewUserCommand : IRequest +{ + //نام + public string? FirstName { get; init; } + //نام خانوادگی + public string? LastName { get; init; } + //شماره موبایل + public string Mobile { get; init; } + //کد ملی + public string? NationalCode { get; init; } + //آدرس آواتار + public string? AvatarPath { get; init; } + //شناسه والد + public long? ParentId { get; init; } + +} \ No newline at end of file diff --git a/src/BackOffice.BFF.Application/UserCQ/Commands/CreateNewUser/CreateNewUserCommandHandler.cs b/src/BackOffice.BFF.Application/UserCQ/Commands/CreateNewUser/CreateNewUserCommandHandler.cs new file mode 100644 index 0000000..a325652 --- /dev/null +++ b/src/BackOffice.BFF.Application/UserCQ/Commands/CreateNewUser/CreateNewUserCommandHandler.cs @@ -0,0 +1,16 @@ +namespace BackOffice.BFF.Application.UserCQ.Commands.CreateNewUser; +public class CreateNewUserCommandHandler : IRequestHandler +{ + private readonly IApplicationContractContext _context; + + public CreateNewUserCommandHandler(IApplicationContractContext context) + { + _context = context; + } + + public async Task Handle(CreateNewUserCommand request, CancellationToken cancellationToken) + { + //TODO: Implement your business logic + return new CreateNewUserResponseDto(); + } +} diff --git a/src/BackOffice.BFF.Application/UserCQ/Commands/CreateNewUser/CreateNewUserCommandValidator.cs b/src/BackOffice.BFF.Application/UserCQ/Commands/CreateNewUser/CreateNewUserCommandValidator.cs new file mode 100644 index 0000000..d5aa1ca --- /dev/null +++ b/src/BackOffice.BFF.Application/UserCQ/Commands/CreateNewUser/CreateNewUserCommandValidator.cs @@ -0,0 +1,16 @@ +namespace BackOffice.BFF.Application.UserCQ.Commands.CreateNewUser; +public class CreateNewUserCommandValidator : AbstractValidator +{ + public CreateNewUserCommandValidator() + { + RuleFor(model => model.Mobile) + .NotEmpty(); + } + public Func>> ValidateValue => async (model, propertyName) => + { + var result = await ValidateAsync(ValidationContext.CreateWithOptions((CreateNewUserCommand)model, x => x.IncludeProperties(propertyName))); + if (result.IsValid) + return Array.Empty(); + return result.Errors.Select(e => e.ErrorMessage); + }; +} diff --git a/src/BackOffice.BFF.Application/UserCQ/Commands/CreateNewUser/CreateNewUserResponseDto.cs b/src/BackOffice.BFF.Application/UserCQ/Commands/CreateNewUser/CreateNewUserResponseDto.cs new file mode 100644 index 0000000..352f628 --- /dev/null +++ b/src/BackOffice.BFF.Application/UserCQ/Commands/CreateNewUser/CreateNewUserResponseDto.cs @@ -0,0 +1,7 @@ +namespace BackOffice.BFF.Application.UserCQ.Commands.CreateNewUser; +public class CreateNewUserResponseDto +{ + //شناسه + public long Id { get; set; } + +} \ No newline at end of file diff --git a/src/BackOffice.BFF.Application/UserCQ/Commands/DeleteUser/DeleteUserCommand.cs b/src/BackOffice.BFF.Application/UserCQ/Commands/DeleteUser/DeleteUserCommand.cs new file mode 100644 index 0000000..b1f6006 --- /dev/null +++ b/src/BackOffice.BFF.Application/UserCQ/Commands/DeleteUser/DeleteUserCommand.cs @@ -0,0 +1,7 @@ +namespace BackOffice.BFF.Application.UserCQ.Commands.DeleteUser; +public record DeleteUserCommand : IRequest +{ + //شناسه + public long Id { get; init; } + +} \ No newline at end of file diff --git a/src/BackOffice.BFF.Application/UserCQ/Commands/DeleteUser/DeleteUserCommandHandler.cs b/src/BackOffice.BFF.Application/UserCQ/Commands/DeleteUser/DeleteUserCommandHandler.cs new file mode 100644 index 0000000..7570b56 --- /dev/null +++ b/src/BackOffice.BFF.Application/UserCQ/Commands/DeleteUser/DeleteUserCommandHandler.cs @@ -0,0 +1,16 @@ +namespace BackOffice.BFF.Application.UserCQ.Commands.DeleteUser; +public class DeleteUserCommandHandler : IRequestHandler +{ + private readonly IApplicationContractContext _context; + + public DeleteUserCommandHandler(IApplicationContractContext context) + { + _context = context; + } + + public async Task Handle(DeleteUserCommand request, CancellationToken cancellationToken) + { + //TODO: Implement your business logic + return new Unit(); + } +} diff --git a/src/BackOffice.BFF.Application/UserCQ/Commands/DeleteUser/DeleteUserCommandValidator.cs b/src/BackOffice.BFF.Application/UserCQ/Commands/DeleteUser/DeleteUserCommandValidator.cs new file mode 100644 index 0000000..0676e14 --- /dev/null +++ b/src/BackOffice.BFF.Application/UserCQ/Commands/DeleteUser/DeleteUserCommandValidator.cs @@ -0,0 +1,16 @@ +namespace BackOffice.BFF.Application.UserCQ.Commands.DeleteUser; +public class DeleteUserCommandValidator : AbstractValidator +{ + public DeleteUserCommandValidator() + { + RuleFor(model => model.Id) + .NotNull(); + } + public Func>> ValidateValue => async (model, propertyName) => + { + var result = await ValidateAsync(ValidationContext.CreateWithOptions((DeleteUserCommand)model, x => x.IncludeProperties(propertyName))); + if (result.IsValid) + return Array.Empty(); + return result.Errors.Select(e => e.ErrorMessage); + }; +} diff --git a/src/BackOffice.BFF.Application/UserCQ/Commands/UpdateUser/UpdateUserCommand.cs b/src/BackOffice.BFF.Application/UserCQ/Commands/UpdateUser/UpdateUserCommand.cs new file mode 100644 index 0000000..7007763 --- /dev/null +++ b/src/BackOffice.BFF.Application/UserCQ/Commands/UpdateUser/UpdateUserCommand.cs @@ -0,0 +1,19 @@ +namespace BackOffice.BFF.Application.UserCQ.Commands.UpdateUser; +public record UpdateUserCommand : IRequest +{ + //شناسه + public long Id { get; init; } + //نام + public string? FirstName { get; init; } + //نام خانوادگی + public string? LastName { get; init; } + //شماره موبایل + public string Mobile { get; init; } + //کد ملی + public string? NationalCode { get; init; } + //آدرس آواتار + public string? AvatarPath { get; init; } + //شناسه والد + public long? ParentId { get; init; } + +} \ No newline at end of file diff --git a/src/BackOffice.BFF.Application/UserCQ/Commands/UpdateUser/UpdateUserCommandHandler.cs b/src/BackOffice.BFF.Application/UserCQ/Commands/UpdateUser/UpdateUserCommandHandler.cs new file mode 100644 index 0000000..5ec96f6 --- /dev/null +++ b/src/BackOffice.BFF.Application/UserCQ/Commands/UpdateUser/UpdateUserCommandHandler.cs @@ -0,0 +1,16 @@ +namespace BackOffice.BFF.Application.UserCQ.Commands.UpdateUser; +public class UpdateUserCommandHandler : IRequestHandler +{ + private readonly IApplicationContractContext _context; + + public UpdateUserCommandHandler(IApplicationContractContext context) + { + _context = context; + } + + public async Task Handle(UpdateUserCommand request, CancellationToken cancellationToken) + { + //TODO: Implement your business logic + return new Unit(); + } +} diff --git a/src/BackOffice.BFF.Application/UserCQ/Commands/UpdateUser/UpdateUserCommandValidator.cs b/src/BackOffice.BFF.Application/UserCQ/Commands/UpdateUser/UpdateUserCommandValidator.cs new file mode 100644 index 0000000..3286f6e --- /dev/null +++ b/src/BackOffice.BFF.Application/UserCQ/Commands/UpdateUser/UpdateUserCommandValidator.cs @@ -0,0 +1,18 @@ +namespace BackOffice.BFF.Application.UserCQ.Commands.UpdateUser; +public class UpdateUserCommandValidator : AbstractValidator +{ + public UpdateUserCommandValidator() + { + RuleFor(model => model.Id) + .NotNull(); + RuleFor(model => model.Mobile) + .NotEmpty(); + } + public Func>> ValidateValue => async (model, propertyName) => + { + var result = await ValidateAsync(ValidationContext.CreateWithOptions((UpdateUserCommand)model, x => x.IncludeProperties(propertyName))); + if (result.IsValid) + return Array.Empty(); + return result.Errors.Select(e => e.ErrorMessage); + }; +} diff --git a/src/BackOffice.BFF.Application/UserCQ/Queries/GetAllUserByFilter/GetAllUserByFilterQuery.cs b/src/BackOffice.BFF.Application/UserCQ/Queries/GetAllUserByFilter/GetAllUserByFilterQuery.cs new file mode 100644 index 0000000..6190ee5 --- /dev/null +++ b/src/BackOffice.BFF.Application/UserCQ/Queries/GetAllUserByFilter/GetAllUserByFilterQuery.cs @@ -0,0 +1,27 @@ +namespace BackOffice.BFF.Application.UserCQ.Queries.GetAllUserByFilter; +public record GetAllUserByFilterQuery : IRequest +{ + //موقعیت صفحه بندی + public PaginationState? PaginationState { get; init; } + //مرتب سازی بر اساس + public string? SortBy { get; init; } + //فیلتر + public GetAllUserByFilterFilter? Filter { get; init; } + +}public class GetAllUserByFilterFilter +{ + //شناسه + public long? Id { get; set; } + //نام + public string? FirstName { get; set; } + //نام خانوادگی + public string? LastName { get; set; } + //شماره موبایل + public string? Mobile { get; set; } + //کد ملی + public string? NationalCode { get; set; } + //آدرس آواتار + public string? AvatarPath { get; set; } + //شناسه والد + public long? ParentId { get; set; } +} diff --git a/src/BackOffice.BFF.Application/UserCQ/Queries/GetAllUserByFilter/GetAllUserByFilterQueryHandler.cs b/src/BackOffice.BFF.Application/UserCQ/Queries/GetAllUserByFilter/GetAllUserByFilterQueryHandler.cs new file mode 100644 index 0000000..3ecf829 --- /dev/null +++ b/src/BackOffice.BFF.Application/UserCQ/Queries/GetAllUserByFilter/GetAllUserByFilterQueryHandler.cs @@ -0,0 +1,16 @@ +namespace BackOffice.BFF.Application.UserCQ.Queries.GetAllUserByFilter; +public class GetAllUserByFilterQueryHandler : IRequestHandler +{ + private readonly IApplicationContractContext _context; + + public GetAllUserByFilterQueryHandler(IApplicationContractContext context) + { + _context = context; + } + + public async Task Handle(GetAllUserByFilterQuery request, CancellationToken cancellationToken) + { + //TODO: Implement your business logic + return new GetAllUserByFilterResponseDto(); + } +} diff --git a/src/BackOffice.BFF.Application/UserCQ/Queries/GetAllUserByFilter/GetAllUserByFilterQueryValidator.cs b/src/BackOffice.BFF.Application/UserCQ/Queries/GetAllUserByFilter/GetAllUserByFilterQueryValidator.cs new file mode 100644 index 0000000..8b13cbf --- /dev/null +++ b/src/BackOffice.BFF.Application/UserCQ/Queries/GetAllUserByFilter/GetAllUserByFilterQueryValidator.cs @@ -0,0 +1,14 @@ +namespace BackOffice.BFF.Application.UserCQ.Queries.GetAllUserByFilter; +public class GetAllUserByFilterQueryValidator : AbstractValidator +{ + public GetAllUserByFilterQueryValidator() + { + } + public Func>> ValidateValue => async (model, propertyName) => + { + var result = await ValidateAsync(ValidationContext.CreateWithOptions((GetAllUserByFilterQuery)model, x => x.IncludeProperties(propertyName))); + if (result.IsValid) + return Array.Empty(); + return result.Errors.Select(e => e.ErrorMessage); + }; +} diff --git a/src/BackOffice.BFF.Application/UserCQ/Queries/GetAllUserByFilter/GetAllUserByFilterResponseDto.cs b/src/BackOffice.BFF.Application/UserCQ/Queries/GetAllUserByFilter/GetAllUserByFilterResponseDto.cs new file mode 100644 index 0000000..5f10e7e --- /dev/null +++ b/src/BackOffice.BFF.Application/UserCQ/Queries/GetAllUserByFilter/GetAllUserByFilterResponseDto.cs @@ -0,0 +1,25 @@ +namespace BackOffice.BFF.Application.UserCQ.Queries.GetAllUserByFilter; +public class GetAllUserByFilterResponseDto +{ + //متادیتا + public MetaData MetaData { get; set; } + //مدل خروجی + public List? Models { get; set; } + +}public class GetAllUserByFilterResponseModel +{ + //شناسه + public long Id { get; set; } + //نام + public string? FirstName { get; set; } + //نام خانوادگی + public string? LastName { get; set; } + //شماره موبایل + public string Mobile { get; set; } + //کد ملی + public string? NationalCode { get; set; } + //آدرس آواتار + public string? AvatarPath { get; set; } + //شناسه والد + public long? ParentId { get; set; } +} diff --git a/src/BackOffice.BFF.Application/UserCQ/Queries/GetUser/GetUserQuery.cs b/src/BackOffice.BFF.Application/UserCQ/Queries/GetUser/GetUserQuery.cs new file mode 100644 index 0000000..293e4e8 --- /dev/null +++ b/src/BackOffice.BFF.Application/UserCQ/Queries/GetUser/GetUserQuery.cs @@ -0,0 +1,7 @@ +namespace BackOffice.BFF.Application.UserCQ.Queries.GetUser; +public record GetUserQuery : IRequest +{ + //شناسه + public long Id { get; init; } + +} \ No newline at end of file diff --git a/src/BackOffice.BFF.Application/UserCQ/Queries/GetUser/GetUserQueryHandler.cs b/src/BackOffice.BFF.Application/UserCQ/Queries/GetUser/GetUserQueryHandler.cs new file mode 100644 index 0000000..27135e0 --- /dev/null +++ b/src/BackOffice.BFF.Application/UserCQ/Queries/GetUser/GetUserQueryHandler.cs @@ -0,0 +1,16 @@ +namespace BackOffice.BFF.Application.UserCQ.Queries.GetUser; +public class GetUserQueryHandler : IRequestHandler +{ + private readonly IApplicationContractContext _context; + + public GetUserQueryHandler(IApplicationContractContext context) + { + _context = context; + } + + public async Task Handle(GetUserQuery request, CancellationToken cancellationToken) + { + //TODO: Implement your business logic + return new GetUserResponseDto(); + } +} diff --git a/src/BackOffice.BFF.Application/UserCQ/Queries/GetUser/GetUserQueryValidator.cs b/src/BackOffice.BFF.Application/UserCQ/Queries/GetUser/GetUserQueryValidator.cs new file mode 100644 index 0000000..7554a2d --- /dev/null +++ b/src/BackOffice.BFF.Application/UserCQ/Queries/GetUser/GetUserQueryValidator.cs @@ -0,0 +1,16 @@ +namespace BackOffice.BFF.Application.UserCQ.Queries.GetUser; +public class GetUserQueryValidator : AbstractValidator +{ + public GetUserQueryValidator() + { + RuleFor(model => model.Id) + .NotNull(); + } + public Func>> ValidateValue => async (model, propertyName) => + { + var result = await ValidateAsync(ValidationContext.CreateWithOptions((GetUserQuery)model, x => x.IncludeProperties(propertyName))); + if (result.IsValid) + return Array.Empty(); + return result.Errors.Select(e => e.ErrorMessage); + }; +} diff --git a/src/BackOffice.BFF.Application/UserCQ/Queries/GetUser/GetUserResponseDto.cs b/src/BackOffice.BFF.Application/UserCQ/Queries/GetUser/GetUserResponseDto.cs new file mode 100644 index 0000000..bd29e9c --- /dev/null +++ b/src/BackOffice.BFF.Application/UserCQ/Queries/GetUser/GetUserResponseDto.cs @@ -0,0 +1,19 @@ +namespace BackOffice.BFF.Application.UserCQ.Queries.GetUser; +public class GetUserResponseDto +{ + //شناسه + public long Id { get; set; } + //نام + public string? FirstName { get; set; } + //نام خانوادگی + public string? LastName { get; set; } + //شماره موبایل + public string Mobile { get; set; } + //کد ملی + public string? NationalCode { get; set; } + //آدرس آواتار + public string? AvatarPath { get; set; } + //شناسه والد + public long? ParentId { get; set; } + +} \ No newline at end of file diff --git a/src/BackOffice.BFF.Application/UserOrderCQ/Commands/CreateNewUserOrder/CreateNewUserOrderCommand.cs b/src/BackOffice.BFF.Application/UserOrderCQ/Commands/CreateNewUserOrder/CreateNewUserOrderCommand.cs new file mode 100644 index 0000000..b344005 --- /dev/null +++ b/src/BackOffice.BFF.Application/UserOrderCQ/Commands/CreateNewUserOrder/CreateNewUserOrderCommand.cs @@ -0,0 +1,17 @@ +namespace BackOffice.BFF.Application.UserOrderCQ.Commands.CreateNewUserOrder; +public record CreateNewUserOrderCommand : IRequest +{ + //قیمت + public long Price { get; init; } + //شناسه پکیج + public long PackageId { get; init; } + //شناسه تراکنش + public long? TransactionId { get; init; } + //وضعیت پرداخت + public bool PaymentStatus { get; init; } + //تاریخ پرداخت + public DateTime? PaymentDate { get; init; } + //شناسه کاربر + public long UserId { get; init; } + +} \ No newline at end of file diff --git a/src/BackOffice.BFF.Application/UserOrderCQ/Commands/CreateNewUserOrder/CreateNewUserOrderCommandHandler.cs b/src/BackOffice.BFF.Application/UserOrderCQ/Commands/CreateNewUserOrder/CreateNewUserOrderCommandHandler.cs new file mode 100644 index 0000000..42ec0b7 --- /dev/null +++ b/src/BackOffice.BFF.Application/UserOrderCQ/Commands/CreateNewUserOrder/CreateNewUserOrderCommandHandler.cs @@ -0,0 +1,16 @@ +namespace BackOffice.BFF.Application.UserOrderCQ.Commands.CreateNewUserOrder; +public class CreateNewUserOrderCommandHandler : IRequestHandler +{ + private readonly IApplicationContractContext _context; + + public CreateNewUserOrderCommandHandler(IApplicationContractContext context) + { + _context = context; + } + + public async Task Handle(CreateNewUserOrderCommand request, CancellationToken cancellationToken) + { + //TODO: Implement your business logic + return new CreateNewUserOrderResponseDto(); + } +} diff --git a/src/BackOffice.BFF.Application/UserOrderCQ/Commands/CreateNewUserOrder/CreateNewUserOrderCommandValidator.cs b/src/BackOffice.BFF.Application/UserOrderCQ/Commands/CreateNewUserOrder/CreateNewUserOrderCommandValidator.cs new file mode 100644 index 0000000..c2dff5f --- /dev/null +++ b/src/BackOffice.BFF.Application/UserOrderCQ/Commands/CreateNewUserOrder/CreateNewUserOrderCommandValidator.cs @@ -0,0 +1,22 @@ +namespace BackOffice.BFF.Application.UserOrderCQ.Commands.CreateNewUserOrder; +public class CreateNewUserOrderCommandValidator : AbstractValidator +{ + public CreateNewUserOrderCommandValidator() + { + RuleFor(model => model.Price) + .NotNull(); + RuleFor(model => model.PackageId) + .NotNull(); + RuleFor(model => model.PaymentStatus) + .NotNull(); + RuleFor(model => model.UserId) + .NotNull(); + } + public Func>> ValidateValue => async (model, propertyName) => + { + var result = await ValidateAsync(ValidationContext.CreateWithOptions((CreateNewUserOrderCommand)model, x => x.IncludeProperties(propertyName))); + if (result.IsValid) + return Array.Empty(); + return result.Errors.Select(e => e.ErrorMessage); + }; +} diff --git a/src/BackOffice.BFF.Application/UserOrderCQ/Commands/CreateNewUserOrder/CreateNewUserOrderResponseDto.cs b/src/BackOffice.BFF.Application/UserOrderCQ/Commands/CreateNewUserOrder/CreateNewUserOrderResponseDto.cs new file mode 100644 index 0000000..b3a4736 --- /dev/null +++ b/src/BackOffice.BFF.Application/UserOrderCQ/Commands/CreateNewUserOrder/CreateNewUserOrderResponseDto.cs @@ -0,0 +1,7 @@ +namespace BackOffice.BFF.Application.UserOrderCQ.Commands.CreateNewUserOrder; +public class CreateNewUserOrderResponseDto +{ + //شناسه + public long Id { get; set; } + +} \ No newline at end of file diff --git a/src/BackOffice.BFF.Application/UserOrderCQ/Commands/DeleteUserOrder/DeleteUserOrderCommand.cs b/src/BackOffice.BFF.Application/UserOrderCQ/Commands/DeleteUserOrder/DeleteUserOrderCommand.cs new file mode 100644 index 0000000..8b4d7ef --- /dev/null +++ b/src/BackOffice.BFF.Application/UserOrderCQ/Commands/DeleteUserOrder/DeleteUserOrderCommand.cs @@ -0,0 +1,7 @@ +namespace BackOffice.BFF.Application.UserOrderCQ.Commands.DeleteUserOrder; +public record DeleteUserOrderCommand : IRequest +{ + //شناسه + public long Id { get; init; } + +} \ No newline at end of file diff --git a/src/BackOffice.BFF.Application/UserOrderCQ/Commands/DeleteUserOrder/DeleteUserOrderCommandHandler.cs b/src/BackOffice.BFF.Application/UserOrderCQ/Commands/DeleteUserOrder/DeleteUserOrderCommandHandler.cs new file mode 100644 index 0000000..38cca7f --- /dev/null +++ b/src/BackOffice.BFF.Application/UserOrderCQ/Commands/DeleteUserOrder/DeleteUserOrderCommandHandler.cs @@ -0,0 +1,16 @@ +namespace BackOffice.BFF.Application.UserOrderCQ.Commands.DeleteUserOrder; +public class DeleteUserOrderCommandHandler : IRequestHandler +{ + private readonly IApplicationContractContext _context; + + public DeleteUserOrderCommandHandler(IApplicationContractContext context) + { + _context = context; + } + + public async Task Handle(DeleteUserOrderCommand request, CancellationToken cancellationToken) + { + //TODO: Implement your business logic + return new Unit(); + } +} diff --git a/src/BackOffice.BFF.Application/UserOrderCQ/Commands/DeleteUserOrder/DeleteUserOrderCommandValidator.cs b/src/BackOffice.BFF.Application/UserOrderCQ/Commands/DeleteUserOrder/DeleteUserOrderCommandValidator.cs new file mode 100644 index 0000000..72a2bc3 --- /dev/null +++ b/src/BackOffice.BFF.Application/UserOrderCQ/Commands/DeleteUserOrder/DeleteUserOrderCommandValidator.cs @@ -0,0 +1,16 @@ +namespace BackOffice.BFF.Application.UserOrderCQ.Commands.DeleteUserOrder; +public class DeleteUserOrderCommandValidator : AbstractValidator +{ + public DeleteUserOrderCommandValidator() + { + RuleFor(model => model.Id) + .NotNull(); + } + public Func>> ValidateValue => async (model, propertyName) => + { + var result = await ValidateAsync(ValidationContext.CreateWithOptions((DeleteUserOrderCommand)model, x => x.IncludeProperties(propertyName))); + if (result.IsValid) + return Array.Empty(); + return result.Errors.Select(e => e.ErrorMessage); + }; +} diff --git a/src/BackOffice.BFF.Application/UserOrderCQ/Commands/UpdateUserOrder/UpdateUserOrderCommand.cs b/src/BackOffice.BFF.Application/UserOrderCQ/Commands/UpdateUserOrder/UpdateUserOrderCommand.cs new file mode 100644 index 0000000..108d595 --- /dev/null +++ b/src/BackOffice.BFF.Application/UserOrderCQ/Commands/UpdateUserOrder/UpdateUserOrderCommand.cs @@ -0,0 +1,19 @@ +namespace BackOffice.BFF.Application.UserOrderCQ.Commands.UpdateUserOrder; +public record UpdateUserOrderCommand : IRequest +{ + //شناسه + public long Id { get; init; } + //قیمت + public long Price { get; init; } + //شناسه پکیج + public long PackageId { get; init; } + //شناسه تراکنش + public long? TransactionId { get; init; } + //وضعیت پرداخت + public bool PaymentStatus { get; init; } + //تاریخ پرداخت + public DateTime? PaymentDate { get; init; } + //شناسه کاربر + public long UserId { get; init; } + +} \ No newline at end of file diff --git a/src/BackOffice.BFF.Application/UserOrderCQ/Commands/UpdateUserOrder/UpdateUserOrderCommandHandler.cs b/src/BackOffice.BFF.Application/UserOrderCQ/Commands/UpdateUserOrder/UpdateUserOrderCommandHandler.cs new file mode 100644 index 0000000..a0ac407 --- /dev/null +++ b/src/BackOffice.BFF.Application/UserOrderCQ/Commands/UpdateUserOrder/UpdateUserOrderCommandHandler.cs @@ -0,0 +1,16 @@ +namespace BackOffice.BFF.Application.UserOrderCQ.Commands.UpdateUserOrder; +public class UpdateUserOrderCommandHandler : IRequestHandler +{ + private readonly IApplicationContractContext _context; + + public UpdateUserOrderCommandHandler(IApplicationContractContext context) + { + _context = context; + } + + public async Task Handle(UpdateUserOrderCommand request, CancellationToken cancellationToken) + { + //TODO: Implement your business logic + return new Unit(); + } +} diff --git a/src/BackOffice.BFF.Application/UserOrderCQ/Commands/UpdateUserOrder/UpdateUserOrderCommandValidator.cs b/src/BackOffice.BFF.Application/UserOrderCQ/Commands/UpdateUserOrder/UpdateUserOrderCommandValidator.cs new file mode 100644 index 0000000..5ec5b2c --- /dev/null +++ b/src/BackOffice.BFF.Application/UserOrderCQ/Commands/UpdateUserOrder/UpdateUserOrderCommandValidator.cs @@ -0,0 +1,24 @@ +namespace BackOffice.BFF.Application.UserOrderCQ.Commands.UpdateUserOrder; +public class UpdateUserOrderCommandValidator : AbstractValidator +{ + public UpdateUserOrderCommandValidator() + { + RuleFor(model => model.Id) + .NotNull(); + RuleFor(model => model.Price) + .NotNull(); + RuleFor(model => model.PackageId) + .NotNull(); + RuleFor(model => model.PaymentStatus) + .NotNull(); + RuleFor(model => model.UserId) + .NotNull(); + } + public Func>> ValidateValue => async (model, propertyName) => + { + var result = await ValidateAsync(ValidationContext.CreateWithOptions((UpdateUserOrderCommand)model, x => x.IncludeProperties(propertyName))); + if (result.IsValid) + return Array.Empty(); + return result.Errors.Select(e => e.ErrorMessage); + }; +} diff --git a/src/BackOffice.BFF.Application/UserOrderCQ/Queries/GetAllUserOrderByFilter/GetAllUserOrderByFilterQuery.cs b/src/BackOffice.BFF.Application/UserOrderCQ/Queries/GetAllUserOrderByFilter/GetAllUserOrderByFilterQuery.cs new file mode 100644 index 0000000..075b856 --- /dev/null +++ b/src/BackOffice.BFF.Application/UserOrderCQ/Queries/GetAllUserOrderByFilter/GetAllUserOrderByFilterQuery.cs @@ -0,0 +1,27 @@ +namespace BackOffice.BFF.Application.UserOrderCQ.Queries.GetAllUserOrderByFilter; +public record GetAllUserOrderByFilterQuery : IRequest +{ + //موقعیت صفحه بندی + public PaginationState? PaginationState { get; init; } + //مرتب سازی بر اساس + public string? SortBy { get; init; } + //فیلتر + public GetAllUserOrderByFilterFilter? Filter { get; init; } + +}public class GetAllUserOrderByFilterFilter +{ + //شناسه + public long? Id { get; set; } + //قیمت + public long? Price { get; set; } + //شناسه پکیج + public long? PackageId { get; set; } + //شناسه تراکنش + public long? TransactionId { get; set; } + //وضعیت پرداخت + public bool? PaymentStatus { get; set; } + //تاریخ پرداخت + public DateTime? PaymentDate { get; set; } + //شناسه کاربر + public long? UserId { get; set; } +} diff --git a/src/BackOffice.BFF.Application/UserOrderCQ/Queries/GetAllUserOrderByFilter/GetAllUserOrderByFilterQueryHandler.cs b/src/BackOffice.BFF.Application/UserOrderCQ/Queries/GetAllUserOrderByFilter/GetAllUserOrderByFilterQueryHandler.cs new file mode 100644 index 0000000..c9a2a1a --- /dev/null +++ b/src/BackOffice.BFF.Application/UserOrderCQ/Queries/GetAllUserOrderByFilter/GetAllUserOrderByFilterQueryHandler.cs @@ -0,0 +1,16 @@ +namespace BackOffice.BFF.Application.UserOrderCQ.Queries.GetAllUserOrderByFilter; +public class GetAllUserOrderByFilterQueryHandler : IRequestHandler +{ + private readonly IApplicationContractContext _context; + + public GetAllUserOrderByFilterQueryHandler(IApplicationContractContext context) + { + _context = context; + } + + public async Task Handle(GetAllUserOrderByFilterQuery request, CancellationToken cancellationToken) + { + //TODO: Implement your business logic + return new GetAllUserOrderByFilterResponseDto(); + } +} diff --git a/src/BackOffice.BFF.Application/UserOrderCQ/Queries/GetAllUserOrderByFilter/GetAllUserOrderByFilterQueryValidator.cs b/src/BackOffice.BFF.Application/UserOrderCQ/Queries/GetAllUserOrderByFilter/GetAllUserOrderByFilterQueryValidator.cs new file mode 100644 index 0000000..c9a16d7 --- /dev/null +++ b/src/BackOffice.BFF.Application/UserOrderCQ/Queries/GetAllUserOrderByFilter/GetAllUserOrderByFilterQueryValidator.cs @@ -0,0 +1,14 @@ +namespace BackOffice.BFF.Application.UserOrderCQ.Queries.GetAllUserOrderByFilter; +public class GetAllUserOrderByFilterQueryValidator : AbstractValidator +{ + public GetAllUserOrderByFilterQueryValidator() + { + } + public Func>> ValidateValue => async (model, propertyName) => + { + var result = await ValidateAsync(ValidationContext.CreateWithOptions((GetAllUserOrderByFilterQuery)model, x => x.IncludeProperties(propertyName))); + if (result.IsValid) + return Array.Empty(); + return result.Errors.Select(e => e.ErrorMessage); + }; +} diff --git a/src/BackOffice.BFF.Application/UserOrderCQ/Queries/GetAllUserOrderByFilter/GetAllUserOrderByFilterResponseDto.cs b/src/BackOffice.BFF.Application/UserOrderCQ/Queries/GetAllUserOrderByFilter/GetAllUserOrderByFilterResponseDto.cs new file mode 100644 index 0000000..f831ac1 --- /dev/null +++ b/src/BackOffice.BFF.Application/UserOrderCQ/Queries/GetAllUserOrderByFilter/GetAllUserOrderByFilterResponseDto.cs @@ -0,0 +1,25 @@ +namespace BackOffice.BFF.Application.UserOrderCQ.Queries.GetAllUserOrderByFilter; +public class GetAllUserOrderByFilterResponseDto +{ + //متادیتا + public MetaData MetaData { get; set; } + //مدل خروجی + public List? Models { get; set; } + +}public class GetAllUserOrderByFilterResponseModel +{ + //شناسه + public long Id { get; set; } + //قیمت + public long Price { get; set; } + //شناسه پکیج + public long PackageId { get; set; } + //شناسه تراکنش + public long? TransactionId { get; set; } + //وضعیت پرداخت + public bool PaymentStatus { get; set; } + //تاریخ پرداخت + public DateTime? PaymentDate { get; set; } + //شناسه کاربر + public long UserId { get; set; } +} diff --git a/src/BackOffice.BFF.Application/UserOrderCQ/Queries/GetUserOrder/GetUserOrderQuery.cs b/src/BackOffice.BFF.Application/UserOrderCQ/Queries/GetUserOrder/GetUserOrderQuery.cs new file mode 100644 index 0000000..7775264 --- /dev/null +++ b/src/BackOffice.BFF.Application/UserOrderCQ/Queries/GetUserOrder/GetUserOrderQuery.cs @@ -0,0 +1,7 @@ +namespace BackOffice.BFF.Application.UserOrderCQ.Queries.GetUserOrder; +public record GetUserOrderQuery : IRequest +{ + //شناسه + public long Id { get; init; } + +} \ No newline at end of file diff --git a/src/BackOffice.BFF.Application/UserOrderCQ/Queries/GetUserOrder/GetUserOrderQueryHandler.cs b/src/BackOffice.BFF.Application/UserOrderCQ/Queries/GetUserOrder/GetUserOrderQueryHandler.cs new file mode 100644 index 0000000..5834783 --- /dev/null +++ b/src/BackOffice.BFF.Application/UserOrderCQ/Queries/GetUserOrder/GetUserOrderQueryHandler.cs @@ -0,0 +1,16 @@ +namespace BackOffice.BFF.Application.UserOrderCQ.Queries.GetUserOrder; +public class GetUserOrderQueryHandler : IRequestHandler +{ + private readonly IApplicationContractContext _context; + + public GetUserOrderQueryHandler(IApplicationContractContext context) + { + _context = context; + } + + public async Task Handle(GetUserOrderQuery request, CancellationToken cancellationToken) + { + //TODO: Implement your business logic + return new GetUserOrderResponseDto(); + } +} diff --git a/src/BackOffice.BFF.Application/UserOrderCQ/Queries/GetUserOrder/GetUserOrderQueryValidator.cs b/src/BackOffice.BFF.Application/UserOrderCQ/Queries/GetUserOrder/GetUserOrderQueryValidator.cs new file mode 100644 index 0000000..3bd3081 --- /dev/null +++ b/src/BackOffice.BFF.Application/UserOrderCQ/Queries/GetUserOrder/GetUserOrderQueryValidator.cs @@ -0,0 +1,16 @@ +namespace BackOffice.BFF.Application.UserOrderCQ.Queries.GetUserOrder; +public class GetUserOrderQueryValidator : AbstractValidator +{ + public GetUserOrderQueryValidator() + { + RuleFor(model => model.Id) + .NotNull(); + } + public Func>> ValidateValue => async (model, propertyName) => + { + var result = await ValidateAsync(ValidationContext.CreateWithOptions((GetUserOrderQuery)model, x => x.IncludeProperties(propertyName))); + if (result.IsValid) + return Array.Empty(); + return result.Errors.Select(e => e.ErrorMessage); + }; +} diff --git a/src/BackOffice.BFF.Application/UserOrderCQ/Queries/GetUserOrder/GetUserOrderResponseDto.cs b/src/BackOffice.BFF.Application/UserOrderCQ/Queries/GetUserOrder/GetUserOrderResponseDto.cs new file mode 100644 index 0000000..b1bcfd4 --- /dev/null +++ b/src/BackOffice.BFF.Application/UserOrderCQ/Queries/GetUserOrder/GetUserOrderResponseDto.cs @@ -0,0 +1,19 @@ +namespace BackOffice.BFF.Application.UserOrderCQ.Queries.GetUserOrder; +public class GetUserOrderResponseDto +{ + //شناسه + public long Id { get; set; } + //قیمت + public long Price { get; set; } + //شناسه پکیج + public long PackageId { get; set; } + //شناسه تراکنش + public long? TransactionId { get; set; } + //وضعیت پرداخت + public bool PaymentStatus { get; set; } + //تاریخ پرداخت + public DateTime? PaymentDate { get; set; } + //شناسه کاربر + public long UserId { get; set; } + +} \ No newline at end of file diff --git a/src/BackOffice.BFF.Application/UserRoleCQ/Commands/CreateNewUserRole/CreateNewUserRoleCommand.cs b/src/BackOffice.BFF.Application/UserRoleCQ/Commands/CreateNewUserRole/CreateNewUserRoleCommand.cs new file mode 100644 index 0000000..afe12c7 --- /dev/null +++ b/src/BackOffice.BFF.Application/UserRoleCQ/Commands/CreateNewUserRole/CreateNewUserRoleCommand.cs @@ -0,0 +1,9 @@ +namespace BackOffice.BFF.Application.UserRoleCQ.Commands.CreateNewUserRole; +public record CreateNewUserRoleCommand : IRequest +{ + //شناسه نقش + public long RoleId { get; init; } + //شناسه کاربر + public long UserId { get; init; } + +} \ No newline at end of file diff --git a/src/BackOffice.BFF.Application/UserRoleCQ/Commands/CreateNewUserRole/CreateNewUserRoleCommandHandler.cs b/src/BackOffice.BFF.Application/UserRoleCQ/Commands/CreateNewUserRole/CreateNewUserRoleCommandHandler.cs new file mode 100644 index 0000000..9133813 --- /dev/null +++ b/src/BackOffice.BFF.Application/UserRoleCQ/Commands/CreateNewUserRole/CreateNewUserRoleCommandHandler.cs @@ -0,0 +1,16 @@ +namespace BackOffice.BFF.Application.UserRoleCQ.Commands.CreateNewUserRole; +public class CreateNewUserRoleCommandHandler : IRequestHandler +{ + private readonly IApplicationContractContext _context; + + public CreateNewUserRoleCommandHandler(IApplicationContractContext context) + { + _context = context; + } + + public async Task Handle(CreateNewUserRoleCommand request, CancellationToken cancellationToken) + { + //TODO: Implement your business logic + return new CreateNewUserRoleResponseDto(); + } +} diff --git a/src/BackOffice.BFF.Application/UserRoleCQ/Commands/CreateNewUserRole/CreateNewUserRoleCommandValidator.cs b/src/BackOffice.BFF.Application/UserRoleCQ/Commands/CreateNewUserRole/CreateNewUserRoleCommandValidator.cs new file mode 100644 index 0000000..5b6b740 --- /dev/null +++ b/src/BackOffice.BFF.Application/UserRoleCQ/Commands/CreateNewUserRole/CreateNewUserRoleCommandValidator.cs @@ -0,0 +1,18 @@ +namespace BackOffice.BFF.Application.UserRoleCQ.Commands.CreateNewUserRole; +public class CreateNewUserRoleCommandValidator : AbstractValidator +{ + public CreateNewUserRoleCommandValidator() + { + RuleFor(model => model.RoleId) + .NotNull(); + RuleFor(model => model.UserId) + .NotNull(); + } + public Func>> ValidateValue => async (model, propertyName) => + { + var result = await ValidateAsync(ValidationContext.CreateWithOptions((CreateNewUserRoleCommand)model, x => x.IncludeProperties(propertyName))); + if (result.IsValid) + return Array.Empty(); + return result.Errors.Select(e => e.ErrorMessage); + }; +} diff --git a/src/BackOffice.BFF.Application/UserRoleCQ/Commands/CreateNewUserRole/CreateNewUserRoleResponseDto.cs b/src/BackOffice.BFF.Application/UserRoleCQ/Commands/CreateNewUserRole/CreateNewUserRoleResponseDto.cs new file mode 100644 index 0000000..6ae8c5f --- /dev/null +++ b/src/BackOffice.BFF.Application/UserRoleCQ/Commands/CreateNewUserRole/CreateNewUserRoleResponseDto.cs @@ -0,0 +1,7 @@ +namespace BackOffice.BFF.Application.UserRoleCQ.Commands.CreateNewUserRole; +public class CreateNewUserRoleResponseDto +{ + //شناسه + public long Id { get; set; } + +} \ No newline at end of file diff --git a/src/BackOffice.BFF.Application/UserRoleCQ/Commands/DeleteUserRole/DeleteUserRoleCommand.cs b/src/BackOffice.BFF.Application/UserRoleCQ/Commands/DeleteUserRole/DeleteUserRoleCommand.cs new file mode 100644 index 0000000..0c3e9d7 --- /dev/null +++ b/src/BackOffice.BFF.Application/UserRoleCQ/Commands/DeleteUserRole/DeleteUserRoleCommand.cs @@ -0,0 +1,7 @@ +namespace BackOffice.BFF.Application.UserRoleCQ.Commands.DeleteUserRole; +public record DeleteUserRoleCommand : IRequest +{ + //شناسه + public long Id { get; init; } + +} \ No newline at end of file diff --git a/src/BackOffice.BFF.Application/UserRoleCQ/Commands/DeleteUserRole/DeleteUserRoleCommandHandler.cs b/src/BackOffice.BFF.Application/UserRoleCQ/Commands/DeleteUserRole/DeleteUserRoleCommandHandler.cs new file mode 100644 index 0000000..72b1c8c --- /dev/null +++ b/src/BackOffice.BFF.Application/UserRoleCQ/Commands/DeleteUserRole/DeleteUserRoleCommandHandler.cs @@ -0,0 +1,16 @@ +namespace BackOffice.BFF.Application.UserRoleCQ.Commands.DeleteUserRole; +public class DeleteUserRoleCommandHandler : IRequestHandler +{ + private readonly IApplicationContractContext _context; + + public DeleteUserRoleCommandHandler(IApplicationContractContext context) + { + _context = context; + } + + public async Task Handle(DeleteUserRoleCommand request, CancellationToken cancellationToken) + { + //TODO: Implement your business logic + return new Unit(); + } +} diff --git a/src/BackOffice.BFF.Application/UserRoleCQ/Commands/DeleteUserRole/DeleteUserRoleCommandValidator.cs b/src/BackOffice.BFF.Application/UserRoleCQ/Commands/DeleteUserRole/DeleteUserRoleCommandValidator.cs new file mode 100644 index 0000000..3f93f23 --- /dev/null +++ b/src/BackOffice.BFF.Application/UserRoleCQ/Commands/DeleteUserRole/DeleteUserRoleCommandValidator.cs @@ -0,0 +1,16 @@ +namespace BackOffice.BFF.Application.UserRoleCQ.Commands.DeleteUserRole; +public class DeleteUserRoleCommandValidator : AbstractValidator +{ + public DeleteUserRoleCommandValidator() + { + RuleFor(model => model.Id) + .NotNull(); + } + public Func>> ValidateValue => async (model, propertyName) => + { + var result = await ValidateAsync(ValidationContext.CreateWithOptions((DeleteUserRoleCommand)model, x => x.IncludeProperties(propertyName))); + if (result.IsValid) + return Array.Empty(); + return result.Errors.Select(e => e.ErrorMessage); + }; +} diff --git a/src/BackOffice.BFF.Application/UserRoleCQ/Commands/UpdateUserRole/UpdateUserRoleCommand.cs b/src/BackOffice.BFF.Application/UserRoleCQ/Commands/UpdateUserRole/UpdateUserRoleCommand.cs new file mode 100644 index 0000000..b99d921 --- /dev/null +++ b/src/BackOffice.BFF.Application/UserRoleCQ/Commands/UpdateUserRole/UpdateUserRoleCommand.cs @@ -0,0 +1,11 @@ +namespace BackOffice.BFF.Application.UserRoleCQ.Commands.UpdateUserRole; +public record UpdateUserRoleCommand : IRequest +{ + //شناسه + public long Id { get; init; } + //شناسه نقش + public long RoleId { get; init; } + //شناسه کاربر + public long UserId { get; init; } + +} \ No newline at end of file diff --git a/src/BackOffice.BFF.Application/UserRoleCQ/Commands/UpdateUserRole/UpdateUserRoleCommandHandler.cs b/src/BackOffice.BFF.Application/UserRoleCQ/Commands/UpdateUserRole/UpdateUserRoleCommandHandler.cs new file mode 100644 index 0000000..4958167 --- /dev/null +++ b/src/BackOffice.BFF.Application/UserRoleCQ/Commands/UpdateUserRole/UpdateUserRoleCommandHandler.cs @@ -0,0 +1,16 @@ +namespace BackOffice.BFF.Application.UserRoleCQ.Commands.UpdateUserRole; +public class UpdateUserRoleCommandHandler : IRequestHandler +{ + private readonly IApplicationContractContext _context; + + public UpdateUserRoleCommandHandler(IApplicationContractContext context) + { + _context = context; + } + + public async Task Handle(UpdateUserRoleCommand request, CancellationToken cancellationToken) + { + //TODO: Implement your business logic + return new Unit(); + } +} diff --git a/src/BackOffice.BFF.Application/UserRoleCQ/Commands/UpdateUserRole/UpdateUserRoleCommandValidator.cs b/src/BackOffice.BFF.Application/UserRoleCQ/Commands/UpdateUserRole/UpdateUserRoleCommandValidator.cs new file mode 100644 index 0000000..9cba917 --- /dev/null +++ b/src/BackOffice.BFF.Application/UserRoleCQ/Commands/UpdateUserRole/UpdateUserRoleCommandValidator.cs @@ -0,0 +1,20 @@ +namespace BackOffice.BFF.Application.UserRoleCQ.Commands.UpdateUserRole; +public class UpdateUserRoleCommandValidator : AbstractValidator +{ + public UpdateUserRoleCommandValidator() + { + RuleFor(model => model.Id) + .NotNull(); + RuleFor(model => model.RoleId) + .NotNull(); + RuleFor(model => model.UserId) + .NotNull(); + } + public Func>> ValidateValue => async (model, propertyName) => + { + var result = await ValidateAsync(ValidationContext.CreateWithOptions((UpdateUserRoleCommand)model, x => x.IncludeProperties(propertyName))); + if (result.IsValid) + return Array.Empty(); + return result.Errors.Select(e => e.ErrorMessage); + }; +} diff --git a/src/BackOffice.BFF.Application/UserRoleCQ/Queries/GetAllUserRoleByFilter/GetAllUserRoleByFilterQuery.cs b/src/BackOffice.BFF.Application/UserRoleCQ/Queries/GetAllUserRoleByFilter/GetAllUserRoleByFilterQuery.cs new file mode 100644 index 0000000..aa02939 --- /dev/null +++ b/src/BackOffice.BFF.Application/UserRoleCQ/Queries/GetAllUserRoleByFilter/GetAllUserRoleByFilterQuery.cs @@ -0,0 +1,19 @@ +namespace BackOffice.BFF.Application.UserRoleCQ.Queries.GetAllUserRoleByFilter; +public record GetAllUserRoleByFilterQuery : IRequest +{ + //موقعیت صفحه بندی + public PaginationState? PaginationState { get; init; } + //مرتب سازی بر اساس + public string? SortBy { get; init; } + //فیلتر + public GetAllUserRoleByFilterFilter? Filter { get; init; } + +}public class GetAllUserRoleByFilterFilter +{ + //شناسه + public long? Id { get; set; } + //شناسه نقش + public long? RoleId { get; set; } + //شناسه کاربر + public long? UserId { get; set; } +} diff --git a/src/BackOffice.BFF.Application/UserRoleCQ/Queries/GetAllUserRoleByFilter/GetAllUserRoleByFilterQueryHandler.cs b/src/BackOffice.BFF.Application/UserRoleCQ/Queries/GetAllUserRoleByFilter/GetAllUserRoleByFilterQueryHandler.cs new file mode 100644 index 0000000..66140ae --- /dev/null +++ b/src/BackOffice.BFF.Application/UserRoleCQ/Queries/GetAllUserRoleByFilter/GetAllUserRoleByFilterQueryHandler.cs @@ -0,0 +1,16 @@ +namespace BackOffice.BFF.Application.UserRoleCQ.Queries.GetAllUserRoleByFilter; +public class GetAllUserRoleByFilterQueryHandler : IRequestHandler +{ + private readonly IApplicationContractContext _context; + + public GetAllUserRoleByFilterQueryHandler(IApplicationContractContext context) + { + _context = context; + } + + public async Task Handle(GetAllUserRoleByFilterQuery request, CancellationToken cancellationToken) + { + //TODO: Implement your business logic + return new GetAllUserRoleByFilterResponseDto(); + } +} diff --git a/src/BackOffice.BFF.Application/UserRoleCQ/Queries/GetAllUserRoleByFilter/GetAllUserRoleByFilterQueryValidator.cs b/src/BackOffice.BFF.Application/UserRoleCQ/Queries/GetAllUserRoleByFilter/GetAllUserRoleByFilterQueryValidator.cs new file mode 100644 index 0000000..d08c61e --- /dev/null +++ b/src/BackOffice.BFF.Application/UserRoleCQ/Queries/GetAllUserRoleByFilter/GetAllUserRoleByFilterQueryValidator.cs @@ -0,0 +1,14 @@ +namespace BackOffice.BFF.Application.UserRoleCQ.Queries.GetAllUserRoleByFilter; +public class GetAllUserRoleByFilterQueryValidator : AbstractValidator +{ + public GetAllUserRoleByFilterQueryValidator() + { + } + public Func>> ValidateValue => async (model, propertyName) => + { + var result = await ValidateAsync(ValidationContext.CreateWithOptions((GetAllUserRoleByFilterQuery)model, x => x.IncludeProperties(propertyName))); + if (result.IsValid) + return Array.Empty(); + return result.Errors.Select(e => e.ErrorMessage); + }; +} diff --git a/src/BackOffice.BFF.Application/UserRoleCQ/Queries/GetAllUserRoleByFilter/GetAllUserRoleByFilterResponseDto.cs b/src/BackOffice.BFF.Application/UserRoleCQ/Queries/GetAllUserRoleByFilter/GetAllUserRoleByFilterResponseDto.cs new file mode 100644 index 0000000..a7a15d3 --- /dev/null +++ b/src/BackOffice.BFF.Application/UserRoleCQ/Queries/GetAllUserRoleByFilter/GetAllUserRoleByFilterResponseDto.cs @@ -0,0 +1,17 @@ +namespace BackOffice.BFF.Application.UserRoleCQ.Queries.GetAllUserRoleByFilter; +public class GetAllUserRoleByFilterResponseDto +{ + //متادیتا + public MetaData MetaData { get; set; } + //مدل خروجی + public List? Models { get; set; } + +}public class GetAllUserRoleByFilterResponseModel +{ + //شناسه + public long Id { get; set; } + //شناسه نقش + public long RoleId { get; set; } + //شناسه کاربر + public long UserId { get; set; } +} diff --git a/src/BackOffice.BFF.Application/UserRoleCQ/Queries/GetUserRole/GetUserRoleQuery.cs b/src/BackOffice.BFF.Application/UserRoleCQ/Queries/GetUserRole/GetUserRoleQuery.cs new file mode 100644 index 0000000..3111659 --- /dev/null +++ b/src/BackOffice.BFF.Application/UserRoleCQ/Queries/GetUserRole/GetUserRoleQuery.cs @@ -0,0 +1,7 @@ +namespace BackOffice.BFF.Application.UserRoleCQ.Queries.GetUserRole; +public record GetUserRoleQuery : IRequest +{ + //شناسه + public long Id { get; init; } + +} \ No newline at end of file diff --git a/src/BackOffice.BFF.Application/UserRoleCQ/Queries/GetUserRole/GetUserRoleQueryHandler.cs b/src/BackOffice.BFF.Application/UserRoleCQ/Queries/GetUserRole/GetUserRoleQueryHandler.cs new file mode 100644 index 0000000..2dad9ff --- /dev/null +++ b/src/BackOffice.BFF.Application/UserRoleCQ/Queries/GetUserRole/GetUserRoleQueryHandler.cs @@ -0,0 +1,16 @@ +namespace BackOffice.BFF.Application.UserRoleCQ.Queries.GetUserRole; +public class GetUserRoleQueryHandler : IRequestHandler +{ + private readonly IApplicationContractContext _context; + + public GetUserRoleQueryHandler(IApplicationContractContext context) + { + _context = context; + } + + public async Task Handle(GetUserRoleQuery request, CancellationToken cancellationToken) + { + //TODO: Implement your business logic + return new GetUserRoleResponseDto(); + } +} diff --git a/src/BackOffice.BFF.Application/UserRoleCQ/Queries/GetUserRole/GetUserRoleQueryValidator.cs b/src/BackOffice.BFF.Application/UserRoleCQ/Queries/GetUserRole/GetUserRoleQueryValidator.cs new file mode 100644 index 0000000..cdc799e --- /dev/null +++ b/src/BackOffice.BFF.Application/UserRoleCQ/Queries/GetUserRole/GetUserRoleQueryValidator.cs @@ -0,0 +1,16 @@ +namespace BackOffice.BFF.Application.UserRoleCQ.Queries.GetUserRole; +public class GetUserRoleQueryValidator : AbstractValidator +{ + public GetUserRoleQueryValidator() + { + RuleFor(model => model.Id) + .NotNull(); + } + public Func>> ValidateValue => async (model, propertyName) => + { + var result = await ValidateAsync(ValidationContext.CreateWithOptions((GetUserRoleQuery)model, x => x.IncludeProperties(propertyName))); + if (result.IsValid) + return Array.Empty(); + return result.Errors.Select(e => e.ErrorMessage); + }; +} diff --git a/src/BackOffice.BFF.Application/UserRoleCQ/Queries/GetUserRole/GetUserRoleResponseDto.cs b/src/BackOffice.BFF.Application/UserRoleCQ/Queries/GetUserRole/GetUserRoleResponseDto.cs new file mode 100644 index 0000000..0a57eab --- /dev/null +++ b/src/BackOffice.BFF.Application/UserRoleCQ/Queries/GetUserRole/GetUserRoleResponseDto.cs @@ -0,0 +1,11 @@ +namespace BackOffice.BFF.Application.UserRoleCQ.Queries.GetUserRole; +public class GetUserRoleResponseDto +{ + //شناسه + public long Id { get; set; } + //شناسه نقش + public long RoleId { get; set; } + //شناسه کاربر + public long UserId { get; set; } + +} \ No newline at end of file diff --git a/src/BackOffice.BFF.Domain/BackOffice.BFF.Domain.csproj b/src/BackOffice.BFF.Domain/BackOffice.BFF.Domain.csproj new file mode 100644 index 0000000..099f95f --- /dev/null +++ b/src/BackOffice.BFF.Domain/BackOffice.BFF.Domain.csproj @@ -0,0 +1,20 @@ + + + + net7.0 + enable + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + diff --git a/src/BackOffice.BFF.Infrastructure/BackOffice.BFF.Infrastructure.csproj b/src/BackOffice.BFF.Infrastructure/BackOffice.BFF.Infrastructure.csproj new file mode 100644 index 0000000..a67c66f --- /dev/null +++ b/src/BackOffice.BFF.Infrastructure/BackOffice.BFF.Infrastructure.csproj @@ -0,0 +1,16 @@ + + + + net7.0 + enable + + + + + + + + + + + \ No newline at end of file diff --git a/src/BackOffice.BFF.Infrastructure/ConfigureGrpcServices.cs b/src/BackOffice.BFF.Infrastructure/ConfigureGrpcServices.cs new file mode 100644 index 0000000..7bd3374 --- /dev/null +++ b/src/BackOffice.BFF.Infrastructure/ConfigureGrpcServices.cs @@ -0,0 +1,101 @@ +using System.Diagnostics; +using System.Reflection; +using BackOffice.BFF.Application.Common.Interfaces; +using Grpc.Core; +using System.Net.Http; +using Grpc.Net.ClientFactory; +using Microsoft.Extensions.Configuration; + +namespace Microsoft.Extensions.DependencyInjection; + +public static class ConfigureGrpcServices +{ + public static IServiceCollection BatchRegisterGrpcClients(this IServiceCollection services, + IConfiguration configuration) + { + Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); + var grpcClients = assemblies + .Where(x => + x.FullName != null && + x.FullName.Contains("Microservice.Protobuf") && + x.GetTypes().Any(type => type.BaseType != null && type.BaseType.Name == "ClientBase`1" && type.BaseType.Namespace == "Grpc.Core") + ) + .SelectMany(x => x + .GetTypes() + .Where(type => type.BaseType != null && type.BaseType.Name == "ClientBase`1" && type.BaseType.Namespace == "Grpc.Core").ToList() + ).ToList(); + + // get configuration as dictionary + var clientAddress = configuration.GetSection("GrpcChannelOptions") + .GetChildren().ToDictionary(x => x.Key, x => x.Value); + + // get register method + var method = typeof(GrpcClientServiceExtensions).GetMethod("AddGrpcClient", + new[] { typeof(IServiceCollection), typeof(string), typeof(Action) }); + + var httpHandler = new HttpClientHandler(); + //TODO: چک شود + // Return `true` to allow certificates that are untrusted/invalid + httpHandler.ServerCertificateCustomValidationCallback = + HttpClientHandler.DangerousAcceptAnyServerCertificateValidator; + + // register clients + foreach (var (key, address) in clientAddress) + { + var msName = key.Replace("MSAddress", ""); + // loop over clients + foreach (var grpcClient in grpcClients.Where(t => + t.AssemblyQualifiedName != null && + t.AssemblyQualifiedName.StartsWith($"{msName}Microservice")) + .ToList()) + { + // make generic method with parameter + var generic = method?.MakeGenericMethod(grpcClient); + + Console.WriteLine($"Registering {grpcClient.Name} of {msName} to {address}"); + //if (grpcClient.Name == "PublicMessageContractClient") + // continue; + + // invoke method with parameters + var callResult = generic?.Invoke(null, + new object[] + { + services,$"{grpcClient.Namespace}",(GrpcClientFactoryOptions o) => { o.Address = new Uri(address); } + }); + + if (callResult == null) + { + Console.WriteLine($"Error registering {grpcClient.Name} of {msName} to {address}"); + continue; + } + + ((IHttpClientBuilder)callResult) + .AddCallCredentials(CallCredentials) + .ConfigureChannel(o => + { + o.HttpHandler = httpHandler; + o.UnsafeUseInsecureChannelCallCredentials = true; + o.MaxReceiveMessageSize = 1000 * 1024 * 1024; // 1 GB + o.MaxSendMessageSize = 1000 * 1024 * 1024; // 1 GB + }); + } + } + + return services; + } + + public static IServiceCollection AddInfrastructureGrpcServices(this IServiceCollection services, + IConfiguration configuration) + { + services.BatchRegisterGrpcClients(configuration); + return services; + } + + private static async Task CallCredentials(AuthInterceptorContext context, Metadata metadata, + IServiceProvider serviceProvider) + { + var provider = serviceProvider.GetRequiredService(); + var token = await provider.GetTokenAsync(); + metadata.Add("Authorization", $"Bearer {token}"); + } +} \ No newline at end of file diff --git a/src/BackOffice.BFF.Infrastructure/ConfigureServices.cs b/src/BackOffice.BFF.Infrastructure/ConfigureServices.cs new file mode 100644 index 0000000..a0f3445 --- /dev/null +++ b/src/BackOffice.BFF.Infrastructure/ConfigureServices.cs @@ -0,0 +1,77 @@ +using System.Diagnostics; +using BackOffice.BFF.Application.Common.Interfaces; +using BackOffice.BFF.Infrastructure.Services; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Configuration; + +namespace Microsoft.Extensions.DependencyInjection; + +public static class ConfigureServices +{ + public static IServiceCollection AddInfrastructureServices(this IServiceCollection services, IConfiguration configuration) + { + services.AddSingleton(); + services.AddInfrastructureGrpcServices(configuration); + #region AddAuthentication + + var message = ""; + services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) + .AddJwtBearer(jwtBearerOptions => + { + jwtBearerOptions.Authority = configuration["Authentication:Authority"]; + jwtBearerOptions.Audience = configuration["Authentication:Audience"]; + jwtBearerOptions.TokenValidationParameters.ValidateAudience = false; + jwtBearerOptions.TokenValidationParameters.ValidateIssuer = true; + jwtBearerOptions.TokenValidationParameters.ValidateIssuerSigningKey = false; + try + { + jwtBearerOptions.Events = new JwtBearerEvents + { + OnAuthenticationFailed = ctx => + { + ctx.Response.StatusCode = StatusCodes.Status401Unauthorized; + message += "From OnAuthenticationFailed:\n"; + message += ctx.Exception.Message; + return Task.CompletedTask; + }, + + OnChallenge = ctx => + { + message += "From OnChallenge:\n"; + ctx.Response.StatusCode = StatusCodes.Status401Unauthorized; + ctx.Response.ContentType = "text/plain"; + return ctx.Response.WriteAsync(message); + }, + + OnMessageReceived = ctx => + { + message = "From OnMessageReceived:\n"; + ctx.Request.Headers.TryGetValue("Authorization", out var BearerToken); + if (BearerToken.Count == 0) + BearerToken = "no Bearer token sent\n"; + message += "Authorization Header sent: " + BearerToken + "\n"; + return Task.CompletedTask; + }, + + OnTokenValidated = ctx => + { + Debug.WriteLine("token: " + ctx.SecurityToken.ToString()); + return Task.CompletedTask; + } + }; + } + catch (Exception e) + { + Console.WriteLine(e); + throw; + } + }); + + services.AddAuthorization(); + + #endregion + + return services; + } +} \ No newline at end of file diff --git a/src/BackOffice.BFF.Infrastructure/GlobalUsings.cs b/src/BackOffice.BFF.Infrastructure/GlobalUsings.cs new file mode 100644 index 0000000..549d4e4 --- /dev/null +++ b/src/BackOffice.BFF.Infrastructure/GlobalUsings.cs @@ -0,0 +1,5 @@ +global using System; +global using System.Collections.Generic; +global using System.Linq; +global using System.Text; +global using System.Threading.Tasks; \ No newline at end of file diff --git a/src/BackOffice.BFF.Infrastructure/Services/ApplicationContractContext.cs b/src/BackOffice.BFF.Infrastructure/Services/ApplicationContractContext.cs new file mode 100644 index 0000000..e912367 --- /dev/null +++ b/src/BackOffice.BFF.Infrastructure/Services/ApplicationContractContext.cs @@ -0,0 +1,42 @@ +using BackOffice.BFF.Application.Common.Interfaces; +using CMSMicroservice.Protobuf.Protos.Package; +using FMSMicroservice.Protobuf.Protos.FileInfo; +using Microsoft.Extensions.DependencyInjection; + +namespace BackOffice.BFF.Infrastructure.Services; + +public class ApplicationContractContext : IApplicationContractContext +{ + #region members + + private readonly IServiceProvider _serviceProvider; + + #endregion + + + #region utilities + + private T GetService() where T : Grpc.Core.ClientBase + { + return _serviceProvider.GetService() ?? + throw new Exception($"requested service not registered: {typeof(T).FullName}"); + } + + #endregion + + public ApplicationContractContext(IServiceProvider serviceProvider) + { + _serviceProvider = serviceProvider; + } + + #region FM + + //public FileLogContract.FileLogContractClient FileManagements => GetService(); + + #endregion + #region CMS + public PackageContract.PackageContractClient Packages => GetService(); + public FileInfoContract.FileInfoContractClient FileInfos => GetService(); + + #endregion +} \ No newline at end of file diff --git a/src/BackOffice.BFF.WebApi/BackOffice.BFF.WebApi.csproj b/src/BackOffice.BFF.WebApi/BackOffice.BFF.WebApi.csproj new file mode 100644 index 0000000..60859ee --- /dev/null +++ b/src/BackOffice.BFF.WebApi/BackOffice.BFF.WebApi.csproj @@ -0,0 +1,32 @@ + + + + net7.0 + enable + Linux + ..\..\.. + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/BackOffice.BFF.WebApi/Common/Mappings/GeneralMapping.cs b/src/BackOffice.BFF.WebApi/Common/Mappings/GeneralMapping.cs new file mode 100644 index 0000000..d82c1d7 --- /dev/null +++ b/src/BackOffice.BFF.WebApi/Common/Mappings/GeneralMapping.cs @@ -0,0 +1,64 @@ +using System.Globalization; +namespace BackOffice.BFF.WebApi.Common.Mappings; + +public class GeneralMapping : IRegister +{ + void IRegister.Register(TypeAdapterConfig config) + { + config.NewConfig() + .MapWith(src => decimal.Parse(src)); + + config.NewConfig() + .MapWith(src => src.ToString("R", new CultureInfo("en-us"))); + + config.NewConfig() + .MapWith(src => src == null ? string.Empty : src.Value.ToString("R", new CultureInfo("en-us"))); + + config.NewConfig() + .MapWith(src => string.IsNullOrEmpty(src) ? null : decimal.Parse(src)); + + config.NewConfig() + .MapWith(src => src == Guid.Empty ? string.Empty : src.ToString()); + + config.NewConfig() + .MapWith(src => string.IsNullOrEmpty(src) ? Guid.Empty : Guid.Parse(src)); + + config.NewConfig() + .MapWith(src => string.IsNullOrEmpty(src) ? null : Guid.Parse(src)); + + config.NewConfig() + .MapWith(src => src.ToDateTime()); + + config.NewConfig() + .MapWith(src => src == null ? null : src.ToDateTime()); + + config.NewConfig() + .MapWith(src => Timestamp.FromDateTime(DateTime.SpecifyKind(src, DateTimeKind.Utc))); + + config.NewConfig() + .MapWith(src => src.HasValue ? Timestamp.FromDateTime(DateTime.SpecifyKind(src.Value, DateTimeKind.Utc)) : null); + + config.NewConfig() + .MapWith(src => src.ToTimeSpan()); + + config.NewConfig() + .MapWith(src => src == null ? null : src.ToTimeSpan()); + + config.NewConfig() + .MapWith(src => Duration.FromTimeSpan(src)); + + config.NewConfig() + .MapWith(src => src.HasValue ? Duration.FromTimeSpan(src.Value) : null); + + config.Default + .UseDestinationValue(member => member.SetterModifier == AccessModifier.None && + member.Type.IsGenericType && + member.Type.GetGenericTypeDefinition() == typeof(Google.Protobuf.Collections.RepeatedField<>)); + + config.NewConfig() + .MapWith(src => src.ToByteArray()); + + config.NewConfig() + .MapWith(src => Google.Protobuf.ByteString.CopyFrom(src)); + } +} diff --git a/src/BackOffice.BFF.WebApi/Common/Mappings/PackageProfile.cs b/src/BackOffice.BFF.WebApi/Common/Mappings/PackageProfile.cs new file mode 100644 index 0000000..0c866f4 --- /dev/null +++ b/src/BackOffice.BFF.WebApi/Common/Mappings/PackageProfile.cs @@ -0,0 +1,10 @@ +namespace BackOffice.BFF.WebApi.Common.Mappings; + +public class PackageProfile : IRegister +{ + void IRegister.Register(TypeAdapterConfig config) + { + //config.NewConfig() + // .Map(dest => dest.FullName, src => $"{src.Firstname} {src.Lastname}"); + } +} diff --git a/src/BackOffice.BFF.WebApi/Common/Mappings/RoleProfile.cs b/src/BackOffice.BFF.WebApi/Common/Mappings/RoleProfile.cs new file mode 100644 index 0000000..30f6935 --- /dev/null +++ b/src/BackOffice.BFF.WebApi/Common/Mappings/RoleProfile.cs @@ -0,0 +1,10 @@ +namespace BackOffice.BFF.WebApi.Common.Mappings; + +public class RoleProfile : IRegister +{ + void IRegister.Register(TypeAdapterConfig config) + { + //config.NewConfig() + // .Map(dest => dest.FullName, src => $"{src.Firstname} {src.Lastname}"); + } +} diff --git a/src/BackOffice.BFF.WebApi/Common/Mappings/UserAddressProfile.cs b/src/BackOffice.BFF.WebApi/Common/Mappings/UserAddressProfile.cs new file mode 100644 index 0000000..463e792 --- /dev/null +++ b/src/BackOffice.BFF.WebApi/Common/Mappings/UserAddressProfile.cs @@ -0,0 +1,10 @@ +namespace BackOffice.BFF.WebApi.Common.Mappings; + +public class UserAddressProfile : IRegister +{ + void IRegister.Register(TypeAdapterConfig config) + { + //config.NewConfig() + // .Map(dest => dest.FullName, src => $"{src.Firstname} {src.Lastname}"); + } +} diff --git a/src/BackOffice.BFF.WebApi/Common/Mappings/UserOrderProfile.cs b/src/BackOffice.BFF.WebApi/Common/Mappings/UserOrderProfile.cs new file mode 100644 index 0000000..476e951 --- /dev/null +++ b/src/BackOffice.BFF.WebApi/Common/Mappings/UserOrderProfile.cs @@ -0,0 +1,10 @@ +namespace BackOffice.BFF.WebApi.Common.Mappings; + +public class UserOrderProfile : IRegister +{ + void IRegister.Register(TypeAdapterConfig config) + { + //config.NewConfig() + // .Map(dest => dest.FullName, src => $"{src.Firstname} {src.Lastname}"); + } +} diff --git a/src/BackOffice.BFF.WebApi/Common/Mappings/UserProfile.cs b/src/BackOffice.BFF.WebApi/Common/Mappings/UserProfile.cs new file mode 100644 index 0000000..e8b14fa --- /dev/null +++ b/src/BackOffice.BFF.WebApi/Common/Mappings/UserProfile.cs @@ -0,0 +1,10 @@ +namespace BackOffice.BFF.WebApi.Common.Mappings; + +public class UserProfile : IRegister +{ + void IRegister.Register(TypeAdapterConfig config) + { + //config.NewConfig() + // .Map(dest => dest.FullName, src => $"{src.Firstname} {src.Lastname}"); + } +} diff --git a/src/BackOffice.BFF.WebApi/Common/Mappings/UserRoleProfile.cs b/src/BackOffice.BFF.WebApi/Common/Mappings/UserRoleProfile.cs new file mode 100644 index 0000000..7d92ca1 --- /dev/null +++ b/src/BackOffice.BFF.WebApi/Common/Mappings/UserRoleProfile.cs @@ -0,0 +1,10 @@ +namespace BackOffice.BFF.WebApi.Common.Mappings; + +public class UserRoleProfile : IRegister +{ + void IRegister.Register(TypeAdapterConfig config) + { + //config.NewConfig() + // .Map(dest => dest.FullName, src => $"{src.Firstname} {src.Lastname}"); + } +} diff --git a/src/BackOffice.BFF.WebApi/Common/Services/AppTokenProvider.cs b/src/BackOffice.BFF.WebApi/Common/Services/AppTokenProvider.cs new file mode 100644 index 0000000..f3b6b16 --- /dev/null +++ b/src/BackOffice.BFF.WebApi/Common/Services/AppTokenProvider.cs @@ -0,0 +1,49 @@ +using BackOffice.BFF.Application.Common.Interfaces; +using Microsoft.Net.Http.Headers; + +namespace BackOffice.BFF.WebApi.Common.Services; + +public class AppTokenProvider : ITokenProvider +{ + private readonly IHttpContextAccessor _httpContextAccessor; + private string? _token; + + public AppTokenProvider(IHttpContextAccessor httpContextAccessor) + { + _httpContextAccessor = httpContextAccessor; + } + + public async Task GetTokenAsync() + { + if (_token != null) + { + return _token; + } + + // get token string + var authorizationToken = _httpContextAccessor.HttpContext?.Request.Headers[HeaderNames.Authorization]; + + if (!string.IsNullOrEmpty(authorizationToken)) + _token = authorizationToken.ToString().Replace("Bearer ", ""); + + return _token; + + // return await Task.FromResult(GetToken()) ?? string.Empty; + } + + private string? GetToken() + { + if (_token != null) return _token; + + // get token string + var authorizationToken = _httpContextAccessor.HttpContext?.Request.Headers[HeaderNames.Authorization]; + if (authorizationToken is null) return _token; + + var token = authorizationToken.Value.ToString(); + if (string.IsNullOrEmpty(token)) return _token; + + _token = token; + + return _token; + } +} diff --git a/src/BackOffice.BFF.WebApi/Common/Services/CurrentUserService.cs b/src/BackOffice.BFF.WebApi/Common/Services/CurrentUserService.cs new file mode 100644 index 0000000..0d0682e --- /dev/null +++ b/src/BackOffice.BFF.WebApi/Common/Services/CurrentUserService.cs @@ -0,0 +1,17 @@ +using System.Security.Claims; +using BackOffice.BFF.Application.Common.Interfaces; +using Microsoft.AspNetCore.Http; + +namespace BackOffice.BFF.WebApi.Common.Services; + +public class CurrentUserService : ICurrentUserService +{ + private readonly IHttpContextAccessor _httpContextAccessor; + + public CurrentUserService(IHttpContextAccessor httpContextAccessor) + { + _httpContextAccessor = httpContextAccessor; + } + + public string? UserId => _httpContextAccessor.HttpContext?.User?.FindFirstValue(ClaimTypes.NameIdentifier); +} diff --git a/src/BackOffice.BFF.WebApi/Common/Services/DispatchRequestToCQRS.cs b/src/BackOffice.BFF.WebApi/Common/Services/DispatchRequestToCQRS.cs new file mode 100644 index 0000000..3aa3759 --- /dev/null +++ b/src/BackOffice.BFF.WebApi/Common/Services/DispatchRequestToCQRS.cs @@ -0,0 +1,87 @@ +namespace BackOffice.BFF.WebApi.Common.Services; +public interface IDispatchRequestToCQRS +{ + Task Handle(TRequest request, + ServerCallContext context); + Task Handle(TRequest request, + ServerCallContext context); + Task Handle(ServerCallContext context); +} +public class DispatchRequestToCQRS : IDispatchRequestToCQRS +{ + private readonly ISender _sender; + + public DispatchRequestToCQRS(ISender sender) + { + _sender = sender; + } + + public async Task Handle(TRequest request, + ServerCallContext context) + { + try + { + if (request is null) + { + throw new ArgumentNullException(nameof(request)); + } + + var cqrsInput = request.Adapt(); + + if (cqrsInput is null) + { + throw new ArgumentNullException(nameof(cqrsInput)); + } + + var output = await _sender.Send(cqrsInput, context.CancellationToken); + return (output ?? throw new InvalidOperationException()).Adapt(); + } + catch (Exception) + { + throw; + } + } + public async Task Handle(ServerCallContext context) + { + try + { + var cqrsInput = Activator.CreateInstance(); + if (cqrsInput is null) + { + throw new ArgumentNullException(nameof(cqrsInput)); + } + + var output = await _sender.Send(cqrsInput, context.CancellationToken); + return (output ?? throw new InvalidOperationException()).Adapt(); + } + catch (Exception) + { + throw; + } + } + public async Task Handle(TRequest request, + ServerCallContext context) + { + try + { + if (request is null) + { + throw new ArgumentNullException(nameof(request)); + } + + var cqrsInput = request.Adapt(); + + if (cqrsInput is null) + { + throw new ArgumentNullException(nameof(cqrsInput)); + } + + await _sender.Send(cqrsInput, context.CancellationToken); + return new Empty(); + } + catch (Exception) + { + throw; + } + } +} diff --git a/src/BackOffice.BFF.WebApi/ConfigureServices.cs b/src/BackOffice.BFF.WebApi/ConfigureServices.cs new file mode 100644 index 0000000..51e97db --- /dev/null +++ b/src/BackOffice.BFF.WebApi/ConfigureServices.cs @@ -0,0 +1,69 @@ +using BackOffice.BFF.Application.Common.Interfaces; +using BackOffice.BFF.WebApi.Common.Services; +using MapsterMapper; +using Microsoft.Extensions.Configuration; +using System.Reflection; + +namespace Microsoft.Extensions.DependencyInjection; + +public static class ConfigureServices +{ + public static IServiceCollection AddPresentationServices(this IServiceCollection services, IConfiguration configuration) + { + services.AddMapping(); + services.AddHttpContextAccessor(); + services.AddTransient(); + services.AddTransient(); + services.AddScoped(); + return services; + } + + private static IServiceCollection AddMapping(this IServiceCollection services) + { + var typeAdapterConfig = TypeAdapterConfig.GlobalSettings; + // scans the assembly and gets the IRegister, adding the registration to the TypeAdapterConfig + typeAdapterConfig.Scan(Assembly.GetExecutingAssembly()); + // register the mapper as Singleton service for my application + var mapperConfig = new Mapper(typeAdapterConfig); + services.AddSingleton(mapperConfig); + return services; + } + + // get all grpc endpoint that end with "Service" in specified assembly and register them as grpc client + public static WebApplication ConfigureGrpcEndpoints(this WebApplication app, Assembly? assembly = null, + Action? configure = null) + { + if (assembly is not null) + { + var assemblyName = assembly.GetName().Name; + var grpcServices = assembly.GetTypes() + // check name and type + .Where(t => t.Name.EndsWith("Service") && t.IsClass) + // check folder by assembly qualified name + .Where(t => t.AssemblyQualifiedName != null && + t.AssemblyQualifiedName.Contains($"{assemblyName}.Services")) + // check parent name ends with "ContractBase" + .Where(t => t.BaseType?.Name.EndsWith("ContractBase") == true) + .ToList(); + + app.UseEndpoints(endpoints => + { + foreach (var service in grpcServices) + { + // how to use type as generic parameter in csharp? + // https://stackoverflow.com/questions/3957817/calling-generic-method-with-type-variable + var method = typeof(GrpcEndpointRouteBuilderExtensions).GetMethod("MapGrpcService"); + var generic = method?.MakeGenericMethod(service); + generic?.Invoke(null, new object[] { endpoints }); + } + }); + } + + if (configure is not null) + { + app.UseEndpoints(configure.Invoke); + } + + return app; + } +} diff --git a/src/BackOffice.BFF.WebApi/GlobalUsings.cs b/src/BackOffice.BFF.WebApi/GlobalUsings.cs new file mode 100644 index 0000000..f566ed2 --- /dev/null +++ b/src/BackOffice.BFF.WebApi/GlobalUsings.cs @@ -0,0 +1,11 @@ +global using Google.Protobuf.WellKnownTypes; +global using Grpc.Core; +global using Mapster; +global using MediatR; +global using Microsoft.AspNetCore.Authorization; +global using Microsoft.AspNetCore.Http; +global using System.Threading.Tasks; +global using Microsoft.AspNetCore.Builder; +global using System; +global using Microsoft.AspNetCore.Routing; +global using System.Linq; diff --git a/src/BackOffice.BFF.WebApi/Program.cs b/src/BackOffice.BFF.WebApi/Program.cs new file mode 100644 index 0000000..7a4879c --- /dev/null +++ b/src/BackOffice.BFF.WebApi/Program.cs @@ -0,0 +1,101 @@ +using System.Reflection; +using System.Runtime.InteropServices; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.AspNetCore.Server.Kestrel.Core; +using Serilog; +using Serilog.Core; +using Serilog.Sinks.MSSqlServer; +using Microsoft.OpenApi.Models; + +var builder = WebApplication.CreateBuilder(args); +if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) +{ + builder.WebHost.ConfigureKestrel(options => + { + // Setup a HTTP/2 endpoint without TLS. + options.ListenLocalhost(5000, o => o.Protocols = + HttpProtocols.Http2); + }); +} + +var levelSwitch = new LoggingLevelSwitch(); +var logger = new LoggerConfiguration() + .WriteTo.Seq("https://seq.afrino.co", + apiKey: "KWWhEFFuHMJFhX0dYvBD", + controlLevelSwitch: levelSwitch) + .CreateLogger(); +builder.Logging.AddSerilog(logger); +#if DEBUG +Serilog.Debugging.SelfLog.Enable(msg => Console.WriteLine(msg)); +#endif +// Additional configuration is required to successfully run gRPC on macOS. +// For instructions on how to configure Kestrel and gRPC clients on macOS, visit https://go.microsoft.com/fwlink/?linkid=2099682 + +// Add services to the container. +builder.Services.AddGrpc(options => +{ + options.EnableDetailedErrors = true; + options.MaxReceiveMessageSize = 1000 * 1024 * 1024; // 1 GB + options.MaxSendMessageSize = 1000 * 1024 * 1024; // 1 GB +}).AddJsonTranscoding(); +builder.Services.AddInfrastructureServices(builder.Configuration); +builder.Services.AddApplicationServices(); +builder.Services.AddPresentationServices(builder.Configuration); + +#region Configure Cors + +builder.Services.AddCors(options => +{ + options.AddPolicy("AllowAll", + builder => builder.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader().WithExposedHeaders("Grpc-Status", + "Grpc-Message", "Grpc-Encoding", "Grpc-Accept-Encoding", "validation-errors-text")); +}); + +#endregion +builder.Services.AddGrpcSwagger(); +builder.Services.AddSwaggerGen(c => +{ + c.SwaggerDoc("v1", new OpenApiInfo { Title = "gRPC transcoding", Version = "v1" }); + c.CustomSchemaIds(type=>type.ToString()); + c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme + { + In = ParameterLocation.Header, + Description = "Please insert JWT with Bearer into field", + Name = "Authorization", + Type = SecuritySchemeType.ApiKey + }); + c.AddSecurityRequirement(new OpenApiSecurityRequirement + { + { + new OpenApiSecurityScheme + { + Reference = new OpenApiReference + { + Type = ReferenceType.SecurityScheme, + Id = "Bearer" + } + }, + new string[] { } + } + }); +}); +var app = builder.Build(); + +app.UseRouting(); +app.UseCors("AllowAll"); +app.UseAuthentication(); +app.UseAuthorization(); +app.UseGrpcWeb(new GrpcWebOptions { DefaultEnabled = true }); // Configure the HTTP request pipeline. +app.ConfigureGrpcEndpoints(Assembly.GetExecutingAssembly(), endpoints => +{ + // endpoints.MapGrpcService(); +}); +app.MapGet("/", () => "Communication with gRPC endpoints must be made through a gRPC client. To learn how to create a client, visit: https://go.microsoft.com/fwlink/?linkid=2086909"); +app.UseSwagger(); +app.UseSwaggerUI(c => +{ + c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1"); +}); +app.Run(); diff --git a/src/BackOffice.BFF.WebApi/Properties/launchSettings.json b/src/BackOffice.BFF.WebApi/Properties/launchSettings.json new file mode 100644 index 0000000..444a8bc --- /dev/null +++ b/src/BackOffice.BFF.WebApi/Properties/launchSettings.json @@ -0,0 +1,12 @@ +{ + "profiles": { + "BackOffice.BFF.WebApi": { + "commandName": "Project", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "https://localhost:6468;http://localhost:6469" + } + } +} \ No newline at end of file diff --git a/src/BackOffice.BFF.WebApi/Services/PackageService.cs b/src/BackOffice.BFF.WebApi/Services/PackageService.cs new file mode 100644 index 0000000..ba79cdc --- /dev/null +++ b/src/BackOffice.BFF.WebApi/Services/PackageService.cs @@ -0,0 +1,37 @@ +using BackOffice.BFF.Package.Protobuf.Protos.Package; +using BackOffice.BFF.WebApi.Common.Services; +using BackOffice.BFF.Application.PackageCQ.Commands.CreateNewPackage; +using BackOffice.BFF.Application.PackageCQ.Commands.UpdatePackage; +using BackOffice.BFF.Application.PackageCQ.Commands.DeletePackage; +using BackOffice.BFF.Application.PackageCQ.Queries.GetPackage; +using BackOffice.BFF.Application.PackageCQ.Queries.GetAllPackageByFilter; +namespace BackOffice.BFF.WebApi.Services; +public class PackageService : PackageContract.PackageContractBase +{ + private readonly IDispatchRequestToCQRS _dispatchRequestToCQRS; + + public PackageService(IDispatchRequestToCQRS dispatchRequestToCQRS) + { + _dispatchRequestToCQRS = dispatchRequestToCQRS; + } + public override async Task CreateNewPackage(CreateNewPackageRequest request, ServerCallContext context) + { + return await _dispatchRequestToCQRS.Handle(request, context); + } + public override async Task UpdatePackage(UpdatePackageRequest request, ServerCallContext context) + { + return await _dispatchRequestToCQRS.Handle(request, context); + } + public override async Task DeletePackage(DeletePackageRequest request, ServerCallContext context) + { + return await _dispatchRequestToCQRS.Handle(request, context); + } + public override async Task GetPackage(GetPackageRequest request, ServerCallContext context) + { + return await _dispatchRequestToCQRS.Handle(request, context); + } + public override async Task GetAllPackageByFilter(GetAllPackageByFilterRequest request, ServerCallContext context) + { + return await _dispatchRequestToCQRS.Handle(request, context); + } +} diff --git a/src/BackOffice.BFF.WebApi/Services/RoleService.cs b/src/BackOffice.BFF.WebApi/Services/RoleService.cs new file mode 100644 index 0000000..a0a5cb2 --- /dev/null +++ b/src/BackOffice.BFF.WebApi/Services/RoleService.cs @@ -0,0 +1,37 @@ +using BackOffice.BFF.Role.Protobuf.Protos.Role; +using BackOffice.BFF.WebApi.Common.Services; +using BackOffice.BFF.Application.RoleCQ.Commands.CreateNewRole; +using BackOffice.BFF.Application.RoleCQ.Commands.UpdateRole; +using BackOffice.BFF.Application.RoleCQ.Commands.DeleteRole; +using BackOffice.BFF.Application.RoleCQ.Queries.GetRole; +using BackOffice.BFF.Application.RoleCQ.Queries.GetAllRoleByFilter; +namespace BackOffice.BFF.WebApi.Services; +public class RoleService : RoleContract.RoleContractBase +{ + private readonly IDispatchRequestToCQRS _dispatchRequestToCQRS; + + public RoleService(IDispatchRequestToCQRS dispatchRequestToCQRS) + { + _dispatchRequestToCQRS = dispatchRequestToCQRS; + } + public override async Task CreateNewRole(CreateNewRoleRequest request, ServerCallContext context) + { + return await _dispatchRequestToCQRS.Handle(request, context); + } + public override async Task UpdateRole(UpdateRoleRequest request, ServerCallContext context) + { + return await _dispatchRequestToCQRS.Handle(request, context); + } + public override async Task DeleteRole(DeleteRoleRequest request, ServerCallContext context) + { + return await _dispatchRequestToCQRS.Handle(request, context); + } + public override async Task GetRole(GetRoleRequest request, ServerCallContext context) + { + return await _dispatchRequestToCQRS.Handle(request, context); + } + public override async Task GetAllRoleByFilter(GetAllRoleByFilterRequest request, ServerCallContext context) + { + return await _dispatchRequestToCQRS.Handle(request, context); + } +} diff --git a/src/BackOffice.BFF.WebApi/Services/UserAddressService.cs b/src/BackOffice.BFF.WebApi/Services/UserAddressService.cs new file mode 100644 index 0000000..cd76707 --- /dev/null +++ b/src/BackOffice.BFF.WebApi/Services/UserAddressService.cs @@ -0,0 +1,37 @@ +using BackOffice.BFF.UserAddress.Protobuf.Protos.UserAddress; +using BackOffice.BFF.WebApi.Common.Services; +using BackOffice.BFF.Application.UserAddressCQ.Commands.CreateNewUserAddress; +using BackOffice.BFF.Application.UserAddressCQ.Commands.UpdateUserAddress; +using BackOffice.BFF.Application.UserAddressCQ.Commands.DeleteUserAddress; +using BackOffice.BFF.Application.UserAddressCQ.Queries.GetUserAddress; +using BackOffice.BFF.Application.UserAddressCQ.Queries.GetAllUserAddressByFilter; +namespace BackOffice.BFF.WebApi.Services; +public class UserAddressService : UserAddressContract.UserAddressContractBase +{ + private readonly IDispatchRequestToCQRS _dispatchRequestToCQRS; + + public UserAddressService(IDispatchRequestToCQRS dispatchRequestToCQRS) + { + _dispatchRequestToCQRS = dispatchRequestToCQRS; + } + public override async Task CreateNewUserAddress(CreateNewUserAddressRequest request, ServerCallContext context) + { + return await _dispatchRequestToCQRS.Handle(request, context); + } + public override async Task UpdateUserAddress(UpdateUserAddressRequest request, ServerCallContext context) + { + return await _dispatchRequestToCQRS.Handle(request, context); + } + public override async Task DeleteUserAddress(DeleteUserAddressRequest request, ServerCallContext context) + { + return await _dispatchRequestToCQRS.Handle(request, context); + } + public override async Task GetUserAddress(GetUserAddressRequest request, ServerCallContext context) + { + return await _dispatchRequestToCQRS.Handle(request, context); + } + public override async Task GetAllUserAddressByFilter(GetAllUserAddressByFilterRequest request, ServerCallContext context) + { + return await _dispatchRequestToCQRS.Handle(request, context); + } +} diff --git a/src/BackOffice.BFF.WebApi/Services/UserOrderService.cs b/src/BackOffice.BFF.WebApi/Services/UserOrderService.cs new file mode 100644 index 0000000..4e8b15c --- /dev/null +++ b/src/BackOffice.BFF.WebApi/Services/UserOrderService.cs @@ -0,0 +1,37 @@ +using BackOffice.BFF.UserOrder.Protobuf.Protos.UserOrder; +using BackOffice.BFF.WebApi.Common.Services; +using BackOffice.BFF.Application.UserOrderCQ.Commands.CreateNewUserOrder; +using BackOffice.BFF.Application.UserOrderCQ.Commands.UpdateUserOrder; +using BackOffice.BFF.Application.UserOrderCQ.Commands.DeleteUserOrder; +using BackOffice.BFF.Application.UserOrderCQ.Queries.GetUserOrder; +using BackOffice.BFF.Application.UserOrderCQ.Queries.GetAllUserOrderByFilter; +namespace BackOffice.BFF.WebApi.Services; +public class UserOrderService : UserOrderContract.UserOrderContractBase +{ + private readonly IDispatchRequestToCQRS _dispatchRequestToCQRS; + + public UserOrderService(IDispatchRequestToCQRS dispatchRequestToCQRS) + { + _dispatchRequestToCQRS = dispatchRequestToCQRS; + } + public override async Task CreateNewUserOrder(CreateNewUserOrderRequest request, ServerCallContext context) + { + return await _dispatchRequestToCQRS.Handle(request, context); + } + public override async Task UpdateUserOrder(UpdateUserOrderRequest request, ServerCallContext context) + { + return await _dispatchRequestToCQRS.Handle(request, context); + } + public override async Task DeleteUserOrder(DeleteUserOrderRequest request, ServerCallContext context) + { + return await _dispatchRequestToCQRS.Handle(request, context); + } + public override async Task GetUserOrder(GetUserOrderRequest request, ServerCallContext context) + { + return await _dispatchRequestToCQRS.Handle(request, context); + } + public override async Task GetAllUserOrderByFilter(GetAllUserOrderByFilterRequest request, ServerCallContext context) + { + return await _dispatchRequestToCQRS.Handle(request, context); + } +} diff --git a/src/BackOffice.BFF.WebApi/Services/UserRoleService.cs b/src/BackOffice.BFF.WebApi/Services/UserRoleService.cs new file mode 100644 index 0000000..59ca385 --- /dev/null +++ b/src/BackOffice.BFF.WebApi/Services/UserRoleService.cs @@ -0,0 +1,37 @@ +using BackOffice.BFF.UserRole.Protobuf.Protos.UserRole; +using BackOffice.BFF.WebApi.Common.Services; +using BackOffice.BFF.Application.UserRoleCQ.Commands.CreateNewUserRole; +using BackOffice.BFF.Application.UserRoleCQ.Commands.UpdateUserRole; +using BackOffice.BFF.Application.UserRoleCQ.Commands.DeleteUserRole; +using BackOffice.BFF.Application.UserRoleCQ.Queries.GetUserRole; +using BackOffice.BFF.Application.UserRoleCQ.Queries.GetAllUserRoleByFilter; +namespace BackOffice.BFF.WebApi.Services; +public class UserRoleService : UserRoleContract.UserRoleContractBase +{ + private readonly IDispatchRequestToCQRS _dispatchRequestToCQRS; + + public UserRoleService(IDispatchRequestToCQRS dispatchRequestToCQRS) + { + _dispatchRequestToCQRS = dispatchRequestToCQRS; + } + public override async Task CreateNewUserRole(CreateNewUserRoleRequest request, ServerCallContext context) + { + return await _dispatchRequestToCQRS.Handle(request, context); + } + public override async Task UpdateUserRole(UpdateUserRoleRequest request, ServerCallContext context) + { + return await _dispatchRequestToCQRS.Handle(request, context); + } + public override async Task DeleteUserRole(DeleteUserRoleRequest request, ServerCallContext context) + { + return await _dispatchRequestToCQRS.Handle(request, context); + } + public override async Task GetUserRole(GetUserRoleRequest request, ServerCallContext context) + { + return await _dispatchRequestToCQRS.Handle(request, context); + } + public override async Task GetAllUserRoleByFilter(GetAllUserRoleByFilterRequest request, ServerCallContext context) + { + return await _dispatchRequestToCQRS.Handle(request, context); + } +} diff --git a/src/BackOffice.BFF.WebApi/Services/UserService.cs b/src/BackOffice.BFF.WebApi/Services/UserService.cs new file mode 100644 index 0000000..93bac4b --- /dev/null +++ b/src/BackOffice.BFF.WebApi/Services/UserService.cs @@ -0,0 +1,37 @@ +using BackOffice.BFF.User.Protobuf.Protos.User; +using BackOffice.BFF.WebApi.Common.Services; +using BackOffice.BFF.Application.UserCQ.Commands.CreateNewUser; +using BackOffice.BFF.Application.UserCQ.Commands.UpdateUser; +using BackOffice.BFF.Application.UserCQ.Commands.DeleteUser; +using BackOffice.BFF.Application.UserCQ.Queries.GetUser; +using BackOffice.BFF.Application.UserCQ.Queries.GetAllUserByFilter; +namespace BackOffice.BFF.WebApi.Services; +public class UserService : UserContract.UserContractBase +{ + private readonly IDispatchRequestToCQRS _dispatchRequestToCQRS; + + public UserService(IDispatchRequestToCQRS dispatchRequestToCQRS) + { + _dispatchRequestToCQRS = dispatchRequestToCQRS; + } + public override async Task CreateNewUser(CreateNewUserRequest request, ServerCallContext context) + { + return await _dispatchRequestToCQRS.Handle(request, context); + } + public override async Task UpdateUser(UpdateUserRequest request, ServerCallContext context) + { + return await _dispatchRequestToCQRS.Handle(request, context); + } + public override async Task DeleteUser(DeleteUserRequest request, ServerCallContext context) + { + return await _dispatchRequestToCQRS.Handle(request, context); + } + public override async Task GetUser(GetUserRequest request, ServerCallContext context) + { + return await _dispatchRequestToCQRS.Handle(request, context); + } + public override async Task GetAllUserByFilter(GetAllUserByFilterRequest request, ServerCallContext context) + { + return await _dispatchRequestToCQRS.Handle(request, context); + } +} diff --git a/src/BackOffice.BFF.WebApi/appsettings.Development.json b/src/BackOffice.BFF.WebApi/appsettings.Development.json new file mode 100644 index 0000000..fe20c40 --- /dev/null +++ b/src/BackOffice.BFF.WebApi/appsettings.Development.json @@ -0,0 +1,10 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Debug", + "System": "Information", + "Grpc": "Information", + "Microsoft": "Information" + } + } +} diff --git a/src/BackOffice.BFF.WebApi/appsettings.json b/src/BackOffice.BFF.WebApi/appsettings.json new file mode 100644 index 0000000..f0820e3 --- /dev/null +++ b/src/BackOffice.BFF.WebApi/appsettings.json @@ -0,0 +1,24 @@ +{ + "JwtSecurityKey": "TvlZVx5TJaHs8e9HgUdGzhGP2CIidoI444nAj+8+g7c=", + "JwtIssuer": "https://localhost", + "JwtAudience": "https://localhost", + "JwtExpiryInDays": 365, + "AllowedHosts": "*", + "Kestrel": { + "EndpointDefaults": { + "Protocols": "Http2" + } + }, + "ConnectionStrings": { + "LogConnection": "Data Source=.,2019; Initial Catalog=DBName;User ID=dbuser;Password=dbpassword;Connection Timeout=300000;MultipleActiveResultSets=True;Encrypt=False", + "providerName": "System.Data.SqlClient" + }, + "GrpcChannelOptions": { + "FMSMSAddress": "https://dl.afrino.co", + "CMSMSAddress": "https://localhost:32846" + }, + "Authentication": { + "Authority": "https://ids.domain.com/", + "Audience": "domain_api" + } +} diff --git a/src/BackOffice.BFF.sln b/src/BackOffice.BFF.sln new file mode 100644 index 0000000..d7fa122 --- /dev/null +++ b/src/BackOffice.BFF.sln @@ -0,0 +1,88 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BackOffice.BFF.Domain", "BackOffice.BFF.Domain\BackOffice.BFF.Domain.csproj", "{9AC57CFF-BEE5-46D2-BFF7-6787E2BB8441}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BackOffice.BFF.Application", "BackOffice.BFF.Application\BackOffice.BFF.Application.csproj", "{C857BF04-2856-46D0-90EF-1C8A6E9420CA}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BackOffice.BFF.Infrastructure", "BackOffice.BFF.Infrastructure\BackOffice.BFF.Infrastructure.csproj", "{B3BCF789-C32E-49EC-8A35-FFF8C9A05D1C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BackOffice.BFF.WebApi", "BackOffice.BFF.WebApi\BackOffice.BFF.WebApi.csproj", "{39ED8B99-36D2-43D4-8491-F8302446385E}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Protobufs", "Protobufs", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BackOffice.BFF.Package.Protobuf", "Protobufs\BackOffice.BFF.Package.Protobuf\BackOffice.BFF.Package.Protobuf.csproj", "{794F666D-6D5D-73EC-C654-104EAFA4E58A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BackOffice.BFF.Role.Protobuf", "Protobufs\BackOffice.BFF.Role.Protobuf\BackOffice.BFF.Role.Protobuf.csproj", "{BD79FF62-CD7B-7873-7200-FA8EBAEFA9E1}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BackOffice.BFF.User.Protobuf", "Protobufs\BackOffice.BFF.User.Protobuf\BackOffice.BFF.User.Protobuf.csproj", "{5FC682A7-F5D3-63A2-0A48-0D42DABF954B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BackOffice.BFF.UserOrder.Protobuf", "Protobufs\BackOffice.BFF.UserOrder.Protobuf\BackOffice.BFF.UserOrder.Protobuf.csproj", "{B688459B-67B0-3170-79C6-AB05DB7E911D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BackOffice.BFF.UserRole.Protobuf", "Protobufs\BackOffice.BFF.UserRole.Protobuf\BackOffice.BFF.UserRole.Protobuf.csproj", "{75481681-ABB4-2A4C-8901-FE7242DE5B20}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BackOffice.BFF.UserAddress.Protobuf", "Protobufs\BackOffice.BFF.UserAddress.Protobuf\BackOffice.BFF.UserAddress.Protobuf.csproj", "{CB811954-E42E-75BB-A02D-689180B72E28}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {9AC57CFF-BEE5-46D2-BFF7-6787E2BB8441}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9AC57CFF-BEE5-46D2-BFF7-6787E2BB8441}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9AC57CFF-BEE5-46D2-BFF7-6787E2BB8441}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9AC57CFF-BEE5-46D2-BFF7-6787E2BB8441}.Release|Any CPU.Build.0 = Release|Any CPU + {C857BF04-2856-46D0-90EF-1C8A6E9420CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C857BF04-2856-46D0-90EF-1C8A6E9420CA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C857BF04-2856-46D0-90EF-1C8A6E9420CA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C857BF04-2856-46D0-90EF-1C8A6E9420CA}.Release|Any CPU.Build.0 = Release|Any CPU + {B3BCF789-C32E-49EC-8A35-FFF8C9A05D1C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B3BCF789-C32E-49EC-8A35-FFF8C9A05D1C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B3BCF789-C32E-49EC-8A35-FFF8C9A05D1C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B3BCF789-C32E-49EC-8A35-FFF8C9A05D1C}.Release|Any CPU.Build.0 = Release|Any CPU + {39ED8B99-36D2-43D4-8491-F8302446385E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {39ED8B99-36D2-43D4-8491-F8302446385E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {39ED8B99-36D2-43D4-8491-F8302446385E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {39ED8B99-36D2-43D4-8491-F8302446385E}.Release|Any CPU.Build.0 = Release|Any CPU + {794F666D-6D5D-73EC-C654-104EAFA4E58A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {794F666D-6D5D-73EC-C654-104EAFA4E58A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {794F666D-6D5D-73EC-C654-104EAFA4E58A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {794F666D-6D5D-73EC-C654-104EAFA4E58A}.Release|Any CPU.Build.0 = Release|Any CPU + {BD79FF62-CD7B-7873-7200-FA8EBAEFA9E1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BD79FF62-CD7B-7873-7200-FA8EBAEFA9E1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BD79FF62-CD7B-7873-7200-FA8EBAEFA9E1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BD79FF62-CD7B-7873-7200-FA8EBAEFA9E1}.Release|Any CPU.Build.0 = Release|Any CPU + {5FC682A7-F5D3-63A2-0A48-0D42DABF954B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5FC682A7-F5D3-63A2-0A48-0D42DABF954B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5FC682A7-F5D3-63A2-0A48-0D42DABF954B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5FC682A7-F5D3-63A2-0A48-0D42DABF954B}.Release|Any CPU.Build.0 = Release|Any CPU + {B688459B-67B0-3170-79C6-AB05DB7E911D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B688459B-67B0-3170-79C6-AB05DB7E911D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B688459B-67B0-3170-79C6-AB05DB7E911D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B688459B-67B0-3170-79C6-AB05DB7E911D}.Release|Any CPU.Build.0 = Release|Any CPU + {75481681-ABB4-2A4C-8901-FE7242DE5B20}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {75481681-ABB4-2A4C-8901-FE7242DE5B20}.Debug|Any CPU.Build.0 = Debug|Any CPU + {75481681-ABB4-2A4C-8901-FE7242DE5B20}.Release|Any CPU.ActiveCfg = Release|Any CPU + {75481681-ABB4-2A4C-8901-FE7242DE5B20}.Release|Any CPU.Build.0 = Release|Any CPU + {CB811954-E42E-75BB-A02D-689180B72E28}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CB811954-E42E-75BB-A02D-689180B72E28}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CB811954-E42E-75BB-A02D-689180B72E28}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CB811954-E42E-75BB-A02D-689180B72E28}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {794F666D-6D5D-73EC-C654-104EAFA4E58A} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} + {BD79FF62-CD7B-7873-7200-FA8EBAEFA9E1} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} + {5FC682A7-F5D3-63A2-0A48-0D42DABF954B} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} + {B688459B-67B0-3170-79C6-AB05DB7E911D} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} + {75481681-ABB4-2A4C-8901-FE7242DE5B20} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} + {CB811954-E42E-75BB-A02D-689180B72E28} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {0AE1AB4A-3C91-4853-93C2-C2476E79F845} + EndGlobalSection +EndGlobal diff --git a/src/Protobufs/BackOffice.BFF.Package.Protobuf/BackOffice.BFF.Package.Protobuf.csproj b/src/Protobufs/BackOffice.BFF.Package.Protobuf/BackOffice.BFF.Package.Protobuf.csproj new file mode 100644 index 0000000..e24336e --- /dev/null +++ b/src/Protobufs/BackOffice.BFF.Package.Protobuf/BackOffice.BFF.Package.Protobuf.csproj @@ -0,0 +1,41 @@ + + + + net7.0 + enable + enable + 0.0.111 + None + False + False + Foursat.BackOffice.BFF.Package.Protobuf + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + + + + $(PackageOutputPath)$(PackageId).$(Version).nupkg + + dotnet nuget push **/*.nupkg --source https://git.afrino.co/api/packages/FourSat/nuget/index.json --api-key 061a5cb15517c6da39c16cfce8556c55ae104d0d --skip-duplicate + + + + + + + diff --git a/src/Protobufs/BackOffice.BFF.Package.Protobuf/ConfigureServices.cs b/src/Protobufs/BackOffice.BFF.Package.Protobuf/ConfigureServices.cs new file mode 100644 index 0000000..9b39014 --- /dev/null +++ b/src/Protobufs/BackOffice.BFF.Package.Protobuf/ConfigureServices.cs @@ -0,0 +1,14 @@ +using FluentValidation; +using System.Reflection; + +namespace Microsoft.Extensions.DependencyInjection; + +public static class ConfigureServices +{ + public static IServiceCollection AddPackageProtobufServices(this IServiceCollection services) + { + services.AddValidatorsFromAssembly(Assembly.GetExecutingAssembly()); + return services; + } +} + diff --git a/src/Protobufs/BackOffice.BFF.Package.Protobuf/Protos/google/api/annotations.proto b/src/Protobufs/BackOffice.BFF.Package.Protobuf/Protos/google/api/annotations.proto new file mode 100644 index 0000000..85c361b --- /dev/null +++ b/src/Protobufs/BackOffice.BFF.Package.Protobuf/Protos/google/api/annotations.proto @@ -0,0 +1,31 @@ +// Copyright (c) 2015, Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +import "google/api/http.proto"; +import "google/protobuf/descriptor.proto"; + +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "AnnotationsProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +extend google.protobuf.MethodOptions { + // See `HttpRule`. + HttpRule http = 72295728; +} diff --git a/src/Protobufs/BackOffice.BFF.Package.Protobuf/Protos/google/api/http.proto b/src/Protobufs/BackOffice.BFF.Package.Protobuf/Protos/google/api/http.proto new file mode 100644 index 0000000..b8426ba --- /dev/null +++ b/src/Protobufs/BackOffice.BFF.Package.Protobuf/Protos/google/api/http.proto @@ -0,0 +1,377 @@ +// Copyright 2019 Google LLC. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +syntax = "proto3"; + +package google.api; + +option cc_enable_arenas = true; +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "HttpProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +// Defines the HTTP configuration for an API service. It contains a list of +// [HttpRule][google.api.HttpRule], each specifying the mapping of an RPC method +// to one or more HTTP REST API methods. +message Http { + // A list of HTTP configuration rules that apply to individual API methods. + // + // **NOTE:** All service configuration rules follow "last one wins" order. + repeated HttpRule rules = 1; + + // When set to true, URL path parameters will be fully URI-decoded except in + // cases of single segment matches in reserved expansion, where "%2F" will be + // left encoded. + // + // The default behavior is to not decode RFC 6570 reserved characters in multi + // segment matches. + bool fully_decode_reserved_expansion = 2; +} + +// # gRPC Transcoding +// +// gRPC Transcoding is a feature for mapping between a gRPC method and one or +// more HTTP REST endpoints. It allows developers to build a single API service +// that supports both gRPC APIs and REST APIs. Many systems, including [Google +// APIs](https://github.com/googleapis/googleapis), +// [Cloud Endpoints](https://cloud.google.com/endpoints), [gRPC +// Gateway](https://github.com/grpc-ecosystem/grpc-gateway), +// and [Envoy](https://github.com/envoyproxy/envoy) proxy support this feature +// and use it for large scale production services. +// +// `HttpRule` defines the schema of the gRPC/REST mapping. The mapping specifies +// how different portions of the gRPC request message are mapped to the URL +// path, URL query parameters, and HTTP request body. It also controls how the +// gRPC response message is mapped to the HTTP response body. `HttpRule` is +// typically specified as an `google.api.http` annotation on the gRPC method. +// +// Each mapping specifies a URL path template and an HTTP method. The path +// template may refer to one or more fields in the gRPC request message, as long +// as each field is a non-repeated field with a primitive (non-message) type. +// The path template controls how fields of the request message are mapped to +// the URL path. +// +// Example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get: "/v1/{name=messages/*}" +// }; +// } +// } +// message GetMessageRequest { +// string name = 1; // Mapped to URL path. +// } +// message Message { +// string text = 1; // The resource content. +// } +// +// This enables an HTTP REST to gRPC mapping as below: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456` | `GetMessage(name: "messages/123456")` +// +// Any fields in the request message which are not bound by the path template +// automatically become HTTP query parameters if there is no HTTP request body. +// For example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get:"/v1/messages/{message_id}" +// }; +// } +// } +// message GetMessageRequest { +// message SubMessage { +// string subfield = 1; +// } +// string message_id = 1; // Mapped to URL path. +// int64 revision = 2; // Mapped to URL query parameter `revision`. +// SubMessage sub = 3; // Mapped to URL query parameter `sub.subfield`. +// } +// +// This enables a HTTP JSON to RPC mapping as below: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456?revision=2&sub.subfield=foo` | +// `GetMessage(message_id: "123456" revision: 2 sub: SubMessage(subfield: +// "foo"))` +// +// Note that fields which are mapped to URL query parameters must have a +// primitive type or a repeated primitive type or a non-repeated message type. +// In the case of a repeated type, the parameter can be repeated in the URL +// as `...?param=A¶m=B`. In the case of a message type, each field of the +// message is mapped to a separate parameter, such as +// `...?foo.a=A&foo.b=B&foo.c=C`. +// +// For HTTP methods that allow a request body, the `body` field +// specifies the mapping. Consider a REST update method on the +// message resource collection: +// +// service Messaging { +// rpc UpdateMessage(UpdateMessageRequest) returns (Message) { +// option (google.api.http) = { +// patch: "/v1/messages/{message_id}" +// body: "message" +// }; +// } +// } +// message UpdateMessageRequest { +// string message_id = 1; // mapped to the URL +// Message message = 2; // mapped to the body +// } +// +// The following HTTP JSON to RPC mapping is enabled, where the +// representation of the JSON in the request body is determined by +// protos JSON encoding: +// +// HTTP | gRPC +// -----|----- +// `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: +// "123456" message { text: "Hi!" })` +// +// The special name `*` can be used in the body mapping to define that +// every field not bound by the path template should be mapped to the +// request body. This enables the following alternative definition of +// the update method: +// +// service Messaging { +// rpc UpdateMessage(Message) returns (Message) { +// option (google.api.http) = { +// patch: "/v1/messages/{message_id}" +// body: "*" +// }; +// } +// } +// message Message { +// string message_id = 1; +// string text = 2; +// } +// +// +// The following HTTP JSON to RPC mapping is enabled: +// +// HTTP | gRPC +// -----|----- +// `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: +// "123456" text: "Hi!")` +// +// Note that when using `*` in the body mapping, it is not possible to +// have HTTP parameters, as all fields not bound by the path end in +// the body. This makes this option more rarely used in practice when +// defining REST APIs. The common usage of `*` is in custom methods +// which don't use the URL at all for transferring data. +// +// It is possible to define multiple HTTP methods for one RPC by using +// the `additional_bindings` option. Example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get: "/v1/messages/{message_id}" +// additional_bindings { +// get: "/v1/users/{user_id}/messages/{message_id}" +// } +// }; +// } +// } +// message GetMessageRequest { +// string message_id = 1; +// string user_id = 2; +// } +// +// This enables the following two alternative HTTP JSON to RPC mappings: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456` | `GetMessage(message_id: "123456")` +// `GET /v1/users/me/messages/123456` | `GetMessage(user_id: "me" message_id: +// "123456")` +// +// ## Rules for HTTP mapping +// +// 1. Leaf request fields (recursive expansion nested messages in the request +// message) are classified into three categories: +// - Fields referred by the path template. They are passed via the URL path. +// - Fields referred by the [HttpRule.body][google.api.HttpRule.body]. They are passed via the HTTP +// request body. +// - All other fields are passed via the URL query parameters, and the +// parameter name is the field path in the request message. A repeated +// field can be represented as multiple query parameters under the same +// name. +// 2. If [HttpRule.body][google.api.HttpRule.body] is "*", there is no URL query parameter, all fields +// are passed via URL path and HTTP request body. +// 3. If [HttpRule.body][google.api.HttpRule.body] is omitted, there is no HTTP request body, all +// fields are passed via URL path and URL query parameters. +// +// ### Path template syntax +// +// Template = "/" Segments [ Verb ] ; +// Segments = Segment { "/" Segment } ; +// Segment = "*" | "**" | LITERAL | Variable ; +// Variable = "{" FieldPath [ "=" Segments ] "}" ; +// FieldPath = IDENT { "." IDENT } ; +// Verb = ":" LITERAL ; +// +// The syntax `*` matches a single URL path segment. The syntax `**` matches +// zero or more URL path segments, which must be the last part of the URL path +// except the `Verb`. +// +// The syntax `Variable` matches part of the URL path as specified by its +// template. A variable template must not contain other variables. If a variable +// matches a single path segment, its template may be omitted, e.g. `{var}` +// is equivalent to `{var=*}`. +// +// The syntax `LITERAL` matches literal text in the URL path. If the `LITERAL` +// contains any reserved character, such characters should be percent-encoded +// before the matching. +// +// If a variable contains exactly one path segment, such as `"{var}"` or +// `"{var=*}"`, when such a variable is expanded into a URL path on the client +// side, all characters except `[-_.~0-9a-zA-Z]` are percent-encoded. The +// server side does the reverse decoding. Such variables show up in the +// [Discovery +// Document](https://developers.google.com/discovery/v1/reference/apis) as +// `{var}`. +// +// If a variable contains multiple path segments, such as `"{var=foo/*}"` +// or `"{var=**}"`, when such a variable is expanded into a URL path on the +// client side, all characters except `[-_.~/0-9a-zA-Z]` are percent-encoded. +// The server side does the reverse decoding, except "%2F" and "%2f" are left +// unchanged. Such variables show up in the +// [Discovery +// Document](https://developers.google.com/discovery/v1/reference/apis) as +// `{+var}`. +// +// ## Using gRPC API Service Configuration +// +// gRPC API Service Configuration (service config) is a configuration language +// for configuring a gRPC service to become a user-facing product. The +// service config is simply the YAML representation of the `google.api.Service` +// proto message. +// +// As an alternative to annotating your proto file, you can configure gRPC +// transcoding in your service config YAML files. You do this by specifying a +// `HttpRule` that maps the gRPC method to a REST endpoint, achieving the same +// effect as the proto annotation. This can be particularly useful if you +// have a proto that is reused in multiple services. Note that any transcoding +// specified in the service config will override any matching transcoding +// configuration in the proto. +// +// Example: +// +// http: +// rules: +// # Selects a gRPC method and applies HttpRule to it. +// - selector: example.v1.Messaging.GetMessage +// get: /v1/messages/{message_id}/{sub.subfield} +// +// ## Special notes +// +// When gRPC Transcoding is used to map a gRPC to JSON REST endpoints, the +// proto to JSON conversion must follow the [proto3 +// specification](https://developers.google.com/protocol-buffers/docs/proto3#json). +// +// While the single segment variable follows the semantics of +// [RFC 6570](https://tools.ietf.org/html/rfc6570) Section 3.2.2 Simple String +// Expansion, the multi segment variable **does not** follow RFC 6570 Section +// 3.2.3 Reserved Expansion. The reason is that the Reserved Expansion +// does not expand special characters like `?` and `#`, which would lead +// to invalid URLs. As the result, gRPC Transcoding uses a custom encoding +// for multi segment variables. +// +// The path variables **must not** refer to any repeated or mapped field, +// because client libraries are not capable of handling such variable expansion. +// +// The path variables **must not** capture the leading "/" character. The reason +// is that the most common use case "{var}" does not capture the leading "/" +// character. For consistency, all path variables must share the same behavior. +// +// Repeated message fields must not be mapped to URL query parameters, because +// no client library can support such complicated mapping. +// +// If an API needs to use a JSON array for request or response body, it can map +// the request or response body to a repeated field. However, some gRPC +// Transcoding implementations may not support this feature. +message HttpRule { + // Selects a method to which this rule applies. + // + // Refer to [selector][google.api.DocumentationRule.selector] for syntax details. + string selector = 1; + + // Determines the URL pattern is matched by this rules. This pattern can be + // used with any of the {get|put|post|delete|patch} methods. A custom method + // can be defined using the 'custom' field. + oneof pattern { + // Maps to HTTP GET. Used for listing and getting information about + // resources. + string get = 2; + + // Maps to HTTP PUT. Used for replacing a resource. + string put = 3; + + // Maps to HTTP POST. Used for creating a resource or performing an action. + string post = 4; + + // Maps to HTTP DELETE. Used for deleting a resource. + string delete = 5; + + // Maps to HTTP PATCH. Used for updating a resource. + string patch = 6; + + // The custom pattern is used for specifying an HTTP method that is not + // included in the `pattern` field, such as HEAD, or "*" to leave the + // HTTP method unspecified for this rule. The wild-card rule is useful + // for services that provide content to Web (HTML) clients. + CustomHttpPattern custom = 8; + } + + // The name of the request field whose value is mapped to the HTTP request + // body, or `*` for mapping all request fields not captured by the path + // pattern to the HTTP body, or omitted for not having any HTTP request body. + // + // NOTE: the referred field must be present at the top-level of the request + // message type. + string body = 7; + + // Optional. The name of the response field whose value is mapped to the HTTP + // response body. When omitted, the entire response message will be used + // as the HTTP response body. + // + // NOTE: The referred field must be present at the top-level of the response + // message type. + string response_body = 12; + + // Additional HTTP bindings for the selector. Nested bindings must + // not contain an `additional_bindings` field themselves (that is, + // the nesting may only be one level deep). + repeated HttpRule additional_bindings = 11; +} + +// A custom pattern is used for defining custom HTTP verb. +message CustomHttpPattern { + // The name of this custom HTTP verb. + string kind = 1; + + // The path matched by this custom verb. + string path = 2; +} + diff --git a/src/Protobufs/BackOffice.BFF.Package.Protobuf/Protos/package.proto b/src/Protobufs/BackOffice.BFF.Package.Protobuf/Protos/package.proto new file mode 100644 index 0000000..85c8b6c --- /dev/null +++ b/src/Protobufs/BackOffice.BFF.Package.Protobuf/Protos/package.proto @@ -0,0 +1,142 @@ +syntax = "proto3"; + +package package; + +import "google/protobuf/empty.proto"; +import "google/protobuf/wrappers.proto"; +import "google/protobuf/duration.proto"; +import "google/protobuf/timestamp.proto"; +import "google/api/annotations.proto"; + +option csharp_namespace = "BackOffice.BFF.Package.Protobuf.Protos.Package"; + +service PackageContract +{ + rpc CreateNewPackage(CreateNewPackageRequest) returns (CreateNewPackageResponse){ + option (google.api.http) = { + post: "/CreateNewPackage" + body: "*" + }; + }; + rpc UpdatePackage(UpdatePackageRequest) returns (google.protobuf.Empty){ + option (google.api.http) = { + put: "/UpdatePackage" + body: "*" + }; + }; + rpc DeletePackage(DeletePackageRequest) returns (google.protobuf.Empty){ + option (google.api.http) = { + delete: "/DeletePackage" + body: "*" + }; + }; + rpc GetPackage(GetPackageRequest) returns (GetPackageResponse){ + option (google.api.http) = { + get: "/GetPackage" + + }; + }; + rpc GetAllPackageByFilter(GetAllPackageByFilterRequest) returns (GetAllPackageByFilterResponse){ + option (google.api.http) = { + get: "/GetAllPackageByFilter" + + }; + }; +} +message CreateNewPackageRequest +{ + string title = 1; + string description = 2; + BoostCardFileModel image_file = 3; + int64 price = 4; +} +message BoostCardFileModel +{ + bytes file = 1; + string file_name = 2; + string mime = 3; +} +message CreateNewPackageResponse +{ + int64 id = 1; +} +message UpdatePackageRequest +{ + int64 id = 1; + string title = 2; + string description = 3; + google.protobuf.StringValue image_path = 4; + int64 price = 5; + BoostCardFileModel image_file = 6; +} +message DeletePackageRequest +{ + int64 id = 1; +} +message GetPackageRequest +{ + int64 id = 1; +} +message GetPackageResponse +{ + int64 id = 1; + string title = 2; + string description = 3; + string image_path = 4; + int64 price = 5; +} +message GetAllPackageByFilterRequest +{ + PaginationState pagination_state = 1; + google.protobuf.StringValue sort_by = 2; + GetAllPackageByFilterFilter filter = 3; +} +message GetAllPackageByFilterFilter +{ + google.protobuf.Int64Value id = 1; + google.protobuf.StringValue title = 2; + google.protobuf.StringValue description = 3; + google.protobuf.StringValue image_path = 4; + google.protobuf.Int64Value price = 5; +} +message GetAllPackageByFilterResponse +{ + MetaData meta_data = 1; + repeated GetAllPackageByFilterResponseModel models = 2; +} +message GetAllPackageByFilterResponseModel +{ + int64 id = 1; + string title = 2; + string description = 3; + string image_path = 4; + int64 price = 5; +} + +message PaginationState +{ + int32 page_number = 1; + + int32 page_size = 2; +} +message MetaData +{ + int64 current_page = 1; + + int64 total_page = 2; + + int64 page_size = 3; + + int64 total_count = 4; + + bool has_previous = 5; + + bool has_next = 6; +} +message DecimalValue +{ + + int64 units = 1; + + sfixed32 nanos = 2; +} diff --git a/src/Protobufs/BackOffice.BFF.Package.Protobuf/Validator/CreateNewPackageRequestValidator.cs b/src/Protobufs/BackOffice.BFF.Package.Protobuf/Validator/CreateNewPackageRequestValidator.cs new file mode 100644 index 0000000..d386cb4 --- /dev/null +++ b/src/Protobufs/BackOffice.BFF.Package.Protobuf/Validator/CreateNewPackageRequestValidator.cs @@ -0,0 +1,25 @@ +using FluentValidation; +using BackOffice.BFF.Package.Protobuf.Protos.Package; +namespace BackOffice.BFF.Package.Protobuf.Validator; + +public class CreateNewPackageRequestValidator : AbstractValidator +{ + public CreateNewPackageRequestValidator() + { + RuleFor(model => model.Title) + .NotEmpty(); + RuleFor(model => model.Description) + .NotEmpty(); + RuleFor(model => model.ImageFile) + .NotNull(); + RuleFor(model => model.Price) + .NotNull(); + } + public Func>> ValidateValue => async (model, propertyName) => + { + var result = await ValidateAsync(ValidationContext.CreateWithOptions((CreateNewPackageRequest)model, x => x.IncludeProperties(propertyName))); + if (result.IsValid) + return Array.Empty(); + return result.Errors.Select(e => e.ErrorMessage); + }; +} diff --git a/src/Protobufs/BackOffice.BFF.Package.Protobuf/Validator/DeletePackageRequestValidator.cs b/src/Protobufs/BackOffice.BFF.Package.Protobuf/Validator/DeletePackageRequestValidator.cs new file mode 100644 index 0000000..ea8626e --- /dev/null +++ b/src/Protobufs/BackOffice.BFF.Package.Protobuf/Validator/DeletePackageRequestValidator.cs @@ -0,0 +1,19 @@ +using FluentValidation; +using BackOffice.BFF.Package.Protobuf.Protos.Package; +namespace BackOffice.BFF.Package.Protobuf.Validator; + +public class DeletePackageRequestValidator : AbstractValidator +{ + public DeletePackageRequestValidator() + { + RuleFor(model => model.Id) + .NotNull(); + } + public Func>> ValidateValue => async (model, propertyName) => + { + var result = await ValidateAsync(ValidationContext.CreateWithOptions((DeletePackageRequest)model, x => x.IncludeProperties(propertyName))); + if (result.IsValid) + return Array.Empty(); + return result.Errors.Select(e => e.ErrorMessage); + }; +} diff --git a/src/Protobufs/BackOffice.BFF.Package.Protobuf/Validator/GetAllPackageByFilterRequestValidator.cs b/src/Protobufs/BackOffice.BFF.Package.Protobuf/Validator/GetAllPackageByFilterRequestValidator.cs new file mode 100644 index 0000000..a780278 --- /dev/null +++ b/src/Protobufs/BackOffice.BFF.Package.Protobuf/Validator/GetAllPackageByFilterRequestValidator.cs @@ -0,0 +1,17 @@ +using FluentValidation; +using BackOffice.BFF.Package.Protobuf.Protos.Package; +namespace BackOffice.BFF.Package.Protobuf.Validator; + +public class GetAllPackageByFilterRequestValidator : AbstractValidator +{ + public GetAllPackageByFilterRequestValidator() + { + } + public Func>> ValidateValue => async (model, propertyName) => + { + var result = await ValidateAsync(ValidationContext.CreateWithOptions((GetAllPackageByFilterRequest)model, x => x.IncludeProperties(propertyName))); + if (result.IsValid) + return Array.Empty(); + return result.Errors.Select(e => e.ErrorMessage); + }; +} diff --git a/src/Protobufs/BackOffice.BFF.Package.Protobuf/Validator/GetPackageRequestValidator.cs b/src/Protobufs/BackOffice.BFF.Package.Protobuf/Validator/GetPackageRequestValidator.cs new file mode 100644 index 0000000..5f51a78 --- /dev/null +++ b/src/Protobufs/BackOffice.BFF.Package.Protobuf/Validator/GetPackageRequestValidator.cs @@ -0,0 +1,19 @@ +using FluentValidation; +using BackOffice.BFF.Package.Protobuf.Protos.Package; +namespace BackOffice.BFF.Package.Protobuf.Validator; + +public class GetPackageRequestValidator : AbstractValidator +{ + public GetPackageRequestValidator() + { + RuleFor(model => model.Id) + .NotNull(); + } + public Func>> ValidateValue => async (model, propertyName) => + { + var result = await ValidateAsync(ValidationContext.CreateWithOptions((GetPackageRequest)model, x => x.IncludeProperties(propertyName))); + if (result.IsValid) + return Array.Empty(); + return result.Errors.Select(e => e.ErrorMessage); + }; +} diff --git a/src/Protobufs/BackOffice.BFF.Package.Protobuf/Validator/UpdatePackageRequestValidator.cs b/src/Protobufs/BackOffice.BFF.Package.Protobuf/Validator/UpdatePackageRequestValidator.cs new file mode 100644 index 0000000..29b298b --- /dev/null +++ b/src/Protobufs/BackOffice.BFF.Package.Protobuf/Validator/UpdatePackageRequestValidator.cs @@ -0,0 +1,25 @@ +using FluentValidation; +using BackOffice.BFF.Package.Protobuf.Protos.Package; +namespace BackOffice.BFF.Package.Protobuf.Validator; + +public class UpdatePackageRequestValidator : AbstractValidator +{ + public UpdatePackageRequestValidator() + { + RuleFor(model => model.Id) + .NotNull(); + RuleFor(model => model.Title) + .NotEmpty(); + RuleFor(model => model.Description) + .NotEmpty(); + RuleFor(model => model.Price) + .NotNull(); + } + public Func>> ValidateValue => async (model, propertyName) => + { + var result = await ValidateAsync(ValidationContext.CreateWithOptions((UpdatePackageRequest)model, x => x.IncludeProperties(propertyName))); + if (result.IsValid) + return Array.Empty(); + return result.Errors.Select(e => e.ErrorMessage); + }; +} diff --git a/src/Protobufs/BackOffice.BFF.Role.Protobuf/BackOffice.BFF.Role.Protobuf.csproj b/src/Protobufs/BackOffice.BFF.Role.Protobuf/BackOffice.BFF.Role.Protobuf.csproj new file mode 100644 index 0000000..e21b5c2 --- /dev/null +++ b/src/Protobufs/BackOffice.BFF.Role.Protobuf/BackOffice.BFF.Role.Protobuf.csproj @@ -0,0 +1,27 @@ + + + + net7.0 + enable + enable + 1.0.0 + None + False + False + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + diff --git a/src/Protobufs/BackOffice.BFF.Role.Protobuf/ConfigureServices.cs b/src/Protobufs/BackOffice.BFF.Role.Protobuf/ConfigureServices.cs new file mode 100644 index 0000000..7c7ff2a --- /dev/null +++ b/src/Protobufs/BackOffice.BFF.Role.Protobuf/ConfigureServices.cs @@ -0,0 +1,14 @@ +using FluentValidation; +using System.Reflection; + +namespace Microsoft.Extensions.DependencyInjection; + +public static class ConfigureServices +{ + public static IServiceCollection AddRoleProtobufServices(this IServiceCollection services) + { + services.AddValidatorsFromAssembly(Assembly.GetExecutingAssembly()); + return services; + } +} + diff --git a/src/Protobufs/BackOffice.BFF.Role.Protobuf/Protos/google/api/annotations.proto b/src/Protobufs/BackOffice.BFF.Role.Protobuf/Protos/google/api/annotations.proto new file mode 100644 index 0000000..85c361b --- /dev/null +++ b/src/Protobufs/BackOffice.BFF.Role.Protobuf/Protos/google/api/annotations.proto @@ -0,0 +1,31 @@ +// Copyright (c) 2015, Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +import "google/api/http.proto"; +import "google/protobuf/descriptor.proto"; + +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "AnnotationsProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +extend google.protobuf.MethodOptions { + // See `HttpRule`. + HttpRule http = 72295728; +} diff --git a/src/Protobufs/BackOffice.BFF.Role.Protobuf/Protos/google/api/http.proto b/src/Protobufs/BackOffice.BFF.Role.Protobuf/Protos/google/api/http.proto new file mode 100644 index 0000000..b8426ba --- /dev/null +++ b/src/Protobufs/BackOffice.BFF.Role.Protobuf/Protos/google/api/http.proto @@ -0,0 +1,377 @@ +// Copyright 2019 Google LLC. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +syntax = "proto3"; + +package google.api; + +option cc_enable_arenas = true; +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "HttpProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +// Defines the HTTP configuration for an API service. It contains a list of +// [HttpRule][google.api.HttpRule], each specifying the mapping of an RPC method +// to one or more HTTP REST API methods. +message Http { + // A list of HTTP configuration rules that apply to individual API methods. + // + // **NOTE:** All service configuration rules follow "last one wins" order. + repeated HttpRule rules = 1; + + // When set to true, URL path parameters will be fully URI-decoded except in + // cases of single segment matches in reserved expansion, where "%2F" will be + // left encoded. + // + // The default behavior is to not decode RFC 6570 reserved characters in multi + // segment matches. + bool fully_decode_reserved_expansion = 2; +} + +// # gRPC Transcoding +// +// gRPC Transcoding is a feature for mapping between a gRPC method and one or +// more HTTP REST endpoints. It allows developers to build a single API service +// that supports both gRPC APIs and REST APIs. Many systems, including [Google +// APIs](https://github.com/googleapis/googleapis), +// [Cloud Endpoints](https://cloud.google.com/endpoints), [gRPC +// Gateway](https://github.com/grpc-ecosystem/grpc-gateway), +// and [Envoy](https://github.com/envoyproxy/envoy) proxy support this feature +// and use it for large scale production services. +// +// `HttpRule` defines the schema of the gRPC/REST mapping. The mapping specifies +// how different portions of the gRPC request message are mapped to the URL +// path, URL query parameters, and HTTP request body. It also controls how the +// gRPC response message is mapped to the HTTP response body. `HttpRule` is +// typically specified as an `google.api.http` annotation on the gRPC method. +// +// Each mapping specifies a URL path template and an HTTP method. The path +// template may refer to one or more fields in the gRPC request message, as long +// as each field is a non-repeated field with a primitive (non-message) type. +// The path template controls how fields of the request message are mapped to +// the URL path. +// +// Example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get: "/v1/{name=messages/*}" +// }; +// } +// } +// message GetMessageRequest { +// string name = 1; // Mapped to URL path. +// } +// message Message { +// string text = 1; // The resource content. +// } +// +// This enables an HTTP REST to gRPC mapping as below: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456` | `GetMessage(name: "messages/123456")` +// +// Any fields in the request message which are not bound by the path template +// automatically become HTTP query parameters if there is no HTTP request body. +// For example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get:"/v1/messages/{message_id}" +// }; +// } +// } +// message GetMessageRequest { +// message SubMessage { +// string subfield = 1; +// } +// string message_id = 1; // Mapped to URL path. +// int64 revision = 2; // Mapped to URL query parameter `revision`. +// SubMessage sub = 3; // Mapped to URL query parameter `sub.subfield`. +// } +// +// This enables a HTTP JSON to RPC mapping as below: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456?revision=2&sub.subfield=foo` | +// `GetMessage(message_id: "123456" revision: 2 sub: SubMessage(subfield: +// "foo"))` +// +// Note that fields which are mapped to URL query parameters must have a +// primitive type or a repeated primitive type or a non-repeated message type. +// In the case of a repeated type, the parameter can be repeated in the URL +// as `...?param=A¶m=B`. In the case of a message type, each field of the +// message is mapped to a separate parameter, such as +// `...?foo.a=A&foo.b=B&foo.c=C`. +// +// For HTTP methods that allow a request body, the `body` field +// specifies the mapping. Consider a REST update method on the +// message resource collection: +// +// service Messaging { +// rpc UpdateMessage(UpdateMessageRequest) returns (Message) { +// option (google.api.http) = { +// patch: "/v1/messages/{message_id}" +// body: "message" +// }; +// } +// } +// message UpdateMessageRequest { +// string message_id = 1; // mapped to the URL +// Message message = 2; // mapped to the body +// } +// +// The following HTTP JSON to RPC mapping is enabled, where the +// representation of the JSON in the request body is determined by +// protos JSON encoding: +// +// HTTP | gRPC +// -----|----- +// `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: +// "123456" message { text: "Hi!" })` +// +// The special name `*` can be used in the body mapping to define that +// every field not bound by the path template should be mapped to the +// request body. This enables the following alternative definition of +// the update method: +// +// service Messaging { +// rpc UpdateMessage(Message) returns (Message) { +// option (google.api.http) = { +// patch: "/v1/messages/{message_id}" +// body: "*" +// }; +// } +// } +// message Message { +// string message_id = 1; +// string text = 2; +// } +// +// +// The following HTTP JSON to RPC mapping is enabled: +// +// HTTP | gRPC +// -----|----- +// `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: +// "123456" text: "Hi!")` +// +// Note that when using `*` in the body mapping, it is not possible to +// have HTTP parameters, as all fields not bound by the path end in +// the body. This makes this option more rarely used in practice when +// defining REST APIs. The common usage of `*` is in custom methods +// which don't use the URL at all for transferring data. +// +// It is possible to define multiple HTTP methods for one RPC by using +// the `additional_bindings` option. Example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get: "/v1/messages/{message_id}" +// additional_bindings { +// get: "/v1/users/{user_id}/messages/{message_id}" +// } +// }; +// } +// } +// message GetMessageRequest { +// string message_id = 1; +// string user_id = 2; +// } +// +// This enables the following two alternative HTTP JSON to RPC mappings: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456` | `GetMessage(message_id: "123456")` +// `GET /v1/users/me/messages/123456` | `GetMessage(user_id: "me" message_id: +// "123456")` +// +// ## Rules for HTTP mapping +// +// 1. Leaf request fields (recursive expansion nested messages in the request +// message) are classified into three categories: +// - Fields referred by the path template. They are passed via the URL path. +// - Fields referred by the [HttpRule.body][google.api.HttpRule.body]. They are passed via the HTTP +// request body. +// - All other fields are passed via the URL query parameters, and the +// parameter name is the field path in the request message. A repeated +// field can be represented as multiple query parameters under the same +// name. +// 2. If [HttpRule.body][google.api.HttpRule.body] is "*", there is no URL query parameter, all fields +// are passed via URL path and HTTP request body. +// 3. If [HttpRule.body][google.api.HttpRule.body] is omitted, there is no HTTP request body, all +// fields are passed via URL path and URL query parameters. +// +// ### Path template syntax +// +// Template = "/" Segments [ Verb ] ; +// Segments = Segment { "/" Segment } ; +// Segment = "*" | "**" | LITERAL | Variable ; +// Variable = "{" FieldPath [ "=" Segments ] "}" ; +// FieldPath = IDENT { "." IDENT } ; +// Verb = ":" LITERAL ; +// +// The syntax `*` matches a single URL path segment. The syntax `**` matches +// zero or more URL path segments, which must be the last part of the URL path +// except the `Verb`. +// +// The syntax `Variable` matches part of the URL path as specified by its +// template. A variable template must not contain other variables. If a variable +// matches a single path segment, its template may be omitted, e.g. `{var}` +// is equivalent to `{var=*}`. +// +// The syntax `LITERAL` matches literal text in the URL path. If the `LITERAL` +// contains any reserved character, such characters should be percent-encoded +// before the matching. +// +// If a variable contains exactly one path segment, such as `"{var}"` or +// `"{var=*}"`, when such a variable is expanded into a URL path on the client +// side, all characters except `[-_.~0-9a-zA-Z]` are percent-encoded. The +// server side does the reverse decoding. Such variables show up in the +// [Discovery +// Document](https://developers.google.com/discovery/v1/reference/apis) as +// `{var}`. +// +// If a variable contains multiple path segments, such as `"{var=foo/*}"` +// or `"{var=**}"`, when such a variable is expanded into a URL path on the +// client side, all characters except `[-_.~/0-9a-zA-Z]` are percent-encoded. +// The server side does the reverse decoding, except "%2F" and "%2f" are left +// unchanged. Such variables show up in the +// [Discovery +// Document](https://developers.google.com/discovery/v1/reference/apis) as +// `{+var}`. +// +// ## Using gRPC API Service Configuration +// +// gRPC API Service Configuration (service config) is a configuration language +// for configuring a gRPC service to become a user-facing product. The +// service config is simply the YAML representation of the `google.api.Service` +// proto message. +// +// As an alternative to annotating your proto file, you can configure gRPC +// transcoding in your service config YAML files. You do this by specifying a +// `HttpRule` that maps the gRPC method to a REST endpoint, achieving the same +// effect as the proto annotation. This can be particularly useful if you +// have a proto that is reused in multiple services. Note that any transcoding +// specified in the service config will override any matching transcoding +// configuration in the proto. +// +// Example: +// +// http: +// rules: +// # Selects a gRPC method and applies HttpRule to it. +// - selector: example.v1.Messaging.GetMessage +// get: /v1/messages/{message_id}/{sub.subfield} +// +// ## Special notes +// +// When gRPC Transcoding is used to map a gRPC to JSON REST endpoints, the +// proto to JSON conversion must follow the [proto3 +// specification](https://developers.google.com/protocol-buffers/docs/proto3#json). +// +// While the single segment variable follows the semantics of +// [RFC 6570](https://tools.ietf.org/html/rfc6570) Section 3.2.2 Simple String +// Expansion, the multi segment variable **does not** follow RFC 6570 Section +// 3.2.3 Reserved Expansion. The reason is that the Reserved Expansion +// does not expand special characters like `?` and `#`, which would lead +// to invalid URLs. As the result, gRPC Transcoding uses a custom encoding +// for multi segment variables. +// +// The path variables **must not** refer to any repeated or mapped field, +// because client libraries are not capable of handling such variable expansion. +// +// The path variables **must not** capture the leading "/" character. The reason +// is that the most common use case "{var}" does not capture the leading "/" +// character. For consistency, all path variables must share the same behavior. +// +// Repeated message fields must not be mapped to URL query parameters, because +// no client library can support such complicated mapping. +// +// If an API needs to use a JSON array for request or response body, it can map +// the request or response body to a repeated field. However, some gRPC +// Transcoding implementations may not support this feature. +message HttpRule { + // Selects a method to which this rule applies. + // + // Refer to [selector][google.api.DocumentationRule.selector] for syntax details. + string selector = 1; + + // Determines the URL pattern is matched by this rules. This pattern can be + // used with any of the {get|put|post|delete|patch} methods. A custom method + // can be defined using the 'custom' field. + oneof pattern { + // Maps to HTTP GET. Used for listing and getting information about + // resources. + string get = 2; + + // Maps to HTTP PUT. Used for replacing a resource. + string put = 3; + + // Maps to HTTP POST. Used for creating a resource or performing an action. + string post = 4; + + // Maps to HTTP DELETE. Used for deleting a resource. + string delete = 5; + + // Maps to HTTP PATCH. Used for updating a resource. + string patch = 6; + + // The custom pattern is used for specifying an HTTP method that is not + // included in the `pattern` field, such as HEAD, or "*" to leave the + // HTTP method unspecified for this rule. The wild-card rule is useful + // for services that provide content to Web (HTML) clients. + CustomHttpPattern custom = 8; + } + + // The name of the request field whose value is mapped to the HTTP request + // body, or `*` for mapping all request fields not captured by the path + // pattern to the HTTP body, or omitted for not having any HTTP request body. + // + // NOTE: the referred field must be present at the top-level of the request + // message type. + string body = 7; + + // Optional. The name of the response field whose value is mapped to the HTTP + // response body. When omitted, the entire response message will be used + // as the HTTP response body. + // + // NOTE: The referred field must be present at the top-level of the response + // message type. + string response_body = 12; + + // Additional HTTP bindings for the selector. Nested bindings must + // not contain an `additional_bindings` field themselves (that is, + // the nesting may only be one level deep). + repeated HttpRule additional_bindings = 11; +} + +// A custom pattern is used for defining custom HTTP verb. +message CustomHttpPattern { + // The name of this custom HTTP verb. + string kind = 1; + + // The path matched by this custom verb. + string path = 2; +} + diff --git a/src/Protobufs/BackOffice.BFF.Role.Protobuf/Protos/role.proto b/src/Protobufs/BackOffice.BFF.Role.Protobuf/Protos/role.proto new file mode 100644 index 0000000..0b15e36 --- /dev/null +++ b/src/Protobufs/BackOffice.BFF.Role.Protobuf/Protos/role.proto @@ -0,0 +1,125 @@ +syntax = "proto3"; + +package role; + +import "google/protobuf/empty.proto"; +import "google/protobuf/wrappers.proto"; +import "google/protobuf/duration.proto"; +import "google/protobuf/timestamp.proto"; +import "google/api/annotations.proto"; + +option csharp_namespace = "BackOffice.BFF.Role.Protobuf.Protos.Role"; + +service RoleContract +{ + rpc CreateNewRole(CreateNewRoleRequest) returns (CreateNewRoleResponse){ + option (google.api.http) = { + post: "/CreateNewRole" + body: "*" + }; + }; + rpc UpdateRole(UpdateRoleRequest) returns (google.protobuf.Empty){ + option (google.api.http) = { + put: "/UpdateRole" + body: "*" + }; + }; + rpc DeleteRole(DeleteRoleRequest) returns (google.protobuf.Empty){ + option (google.api.http) = { + delete: "/DeleteRole" + body: "*" + }; + }; + rpc GetRole(GetRoleRequest) returns (GetRoleResponse){ + option (google.api.http) = { + get: "/GetRole" + + }; + }; + rpc GetAllRoleByFilter(GetAllRoleByFilterRequest) returns (GetAllRoleByFilterResponse){ + option (google.api.http) = { + get: "/GetAllRoleByFilter" + + }; + }; +} +message CreateNewRoleRequest +{ + string name = 1; + string title = 2; +} +message CreateNewRoleResponse +{ + int64 id = 1; +} +message UpdateRoleRequest +{ + int64 id = 1; + string name = 2; + string title = 3; +} +message DeleteRoleRequest +{ + int64 id = 1; +} +message GetRoleRequest +{ + int64 id = 1; +} +message GetRoleResponse +{ + int64 id = 1; + string name = 2; + string title = 3; +} +message GetAllRoleByFilterRequest +{ + PaginationState pagination_state = 1; + google.protobuf.StringValue sort_by = 2; + GetAllRoleByFilterFilter filter = 3; +} +message GetAllRoleByFilterFilter +{ + google.protobuf.Int64Value id = 1; + google.protobuf.StringValue name = 2; + google.protobuf.StringValue title = 3; +} +message GetAllRoleByFilterResponse +{ + MetaData meta_data = 1; + repeated GetAllRoleByFilterResponseModel models = 2; +} +message GetAllRoleByFilterResponseModel +{ + int64 id = 1; + string name = 2; + string title = 3; +} + +message PaginationState +{ + int32 page_number = 1; + + int32 page_size = 2; +} +message MetaData +{ + int64 current_page = 1; + + int64 total_page = 2; + + int64 page_size = 3; + + int64 total_count = 4; + + bool has_previous = 5; + + bool has_next = 6; +} +message DecimalValue +{ + + int64 units = 1; + + sfixed32 nanos = 2; +} diff --git a/src/Protobufs/BackOffice.BFF.Role.Protobuf/Validator/CreateNewRoleRequestValidator.cs b/src/Protobufs/BackOffice.BFF.Role.Protobuf/Validator/CreateNewRoleRequestValidator.cs new file mode 100644 index 0000000..ab37584 --- /dev/null +++ b/src/Protobufs/BackOffice.BFF.Role.Protobuf/Validator/CreateNewRoleRequestValidator.cs @@ -0,0 +1,21 @@ +using FluentValidation; +using BackOffice.BFF.Role.Protobuf.Protos.Role; +namespace BackOffice.BFF.Role.Protobuf.Validator; + +public class CreateNewRoleRequestValidator : AbstractValidator +{ + public CreateNewRoleRequestValidator() + { + RuleFor(model => model.Name) + .NotEmpty(); + RuleFor(model => model.Title) + .NotEmpty(); + } + public Func>> ValidateValue => async (model, propertyName) => + { + var result = await ValidateAsync(ValidationContext.CreateWithOptions((CreateNewRoleRequest)model, x => x.IncludeProperties(propertyName))); + if (result.IsValid) + return Array.Empty(); + return result.Errors.Select(e => e.ErrorMessage); + }; +} diff --git a/src/Protobufs/BackOffice.BFF.Role.Protobuf/Validator/DeleteRoleRequestValidator.cs b/src/Protobufs/BackOffice.BFF.Role.Protobuf/Validator/DeleteRoleRequestValidator.cs new file mode 100644 index 0000000..c862f4a --- /dev/null +++ b/src/Protobufs/BackOffice.BFF.Role.Protobuf/Validator/DeleteRoleRequestValidator.cs @@ -0,0 +1,19 @@ +using FluentValidation; +using BackOffice.BFF.Role.Protobuf.Protos.Role; +namespace BackOffice.BFF.Role.Protobuf.Validator; + +public class DeleteRoleRequestValidator : AbstractValidator +{ + public DeleteRoleRequestValidator() + { + RuleFor(model => model.Id) + .NotNull(); + } + public Func>> ValidateValue => async (model, propertyName) => + { + var result = await ValidateAsync(ValidationContext.CreateWithOptions((DeleteRoleRequest)model, x => x.IncludeProperties(propertyName))); + if (result.IsValid) + return Array.Empty(); + return result.Errors.Select(e => e.ErrorMessage); + }; +} diff --git a/src/Protobufs/BackOffice.BFF.Role.Protobuf/Validator/GetAllRoleByFilterRequestValidator.cs b/src/Protobufs/BackOffice.BFF.Role.Protobuf/Validator/GetAllRoleByFilterRequestValidator.cs new file mode 100644 index 0000000..640c139 --- /dev/null +++ b/src/Protobufs/BackOffice.BFF.Role.Protobuf/Validator/GetAllRoleByFilterRequestValidator.cs @@ -0,0 +1,17 @@ +using FluentValidation; +using BackOffice.BFF.Role.Protobuf.Protos.Role; +namespace BackOffice.BFF.Role.Protobuf.Validator; + +public class GetAllRoleByFilterRequestValidator : AbstractValidator +{ + public GetAllRoleByFilterRequestValidator() + { + } + public Func>> ValidateValue => async (model, propertyName) => + { + var result = await ValidateAsync(ValidationContext.CreateWithOptions((GetAllRoleByFilterRequest)model, x => x.IncludeProperties(propertyName))); + if (result.IsValid) + return Array.Empty(); + return result.Errors.Select(e => e.ErrorMessage); + }; +} diff --git a/src/Protobufs/BackOffice.BFF.Role.Protobuf/Validator/GetRoleRequestValidator.cs b/src/Protobufs/BackOffice.BFF.Role.Protobuf/Validator/GetRoleRequestValidator.cs new file mode 100644 index 0000000..c9bcee4 --- /dev/null +++ b/src/Protobufs/BackOffice.BFF.Role.Protobuf/Validator/GetRoleRequestValidator.cs @@ -0,0 +1,19 @@ +using FluentValidation; +using BackOffice.BFF.Role.Protobuf.Protos.Role; +namespace BackOffice.BFF.Role.Protobuf.Validator; + +public class GetRoleRequestValidator : AbstractValidator +{ + public GetRoleRequestValidator() + { + RuleFor(model => model.Id) + .NotNull(); + } + public Func>> ValidateValue => async (model, propertyName) => + { + var result = await ValidateAsync(ValidationContext.CreateWithOptions((GetRoleRequest)model, x => x.IncludeProperties(propertyName))); + if (result.IsValid) + return Array.Empty(); + return result.Errors.Select(e => e.ErrorMessage); + }; +} diff --git a/src/Protobufs/BackOffice.BFF.Role.Protobuf/Validator/UpdateRoleRequestValidator.cs b/src/Protobufs/BackOffice.BFF.Role.Protobuf/Validator/UpdateRoleRequestValidator.cs new file mode 100644 index 0000000..d7f8c30 --- /dev/null +++ b/src/Protobufs/BackOffice.BFF.Role.Protobuf/Validator/UpdateRoleRequestValidator.cs @@ -0,0 +1,23 @@ +using FluentValidation; +using BackOffice.BFF.Role.Protobuf.Protos.Role; +namespace BackOffice.BFF.Role.Protobuf.Validator; + +public class UpdateRoleRequestValidator : AbstractValidator +{ + public UpdateRoleRequestValidator() + { + RuleFor(model => model.Id) + .NotNull(); + RuleFor(model => model.Name) + .NotEmpty(); + RuleFor(model => model.Title) + .NotEmpty(); + } + public Func>> ValidateValue => async (model, propertyName) => + { + var result = await ValidateAsync(ValidationContext.CreateWithOptions((UpdateRoleRequest)model, x => x.IncludeProperties(propertyName))); + if (result.IsValid) + return Array.Empty(); + return result.Errors.Select(e => e.ErrorMessage); + }; +} diff --git a/src/Protobufs/BackOffice.BFF.User.Protobuf/BackOffice.BFF.User.Protobuf.csproj b/src/Protobufs/BackOffice.BFF.User.Protobuf/BackOffice.BFF.User.Protobuf.csproj new file mode 100644 index 0000000..40788ee --- /dev/null +++ b/src/Protobufs/BackOffice.BFF.User.Protobuf/BackOffice.BFF.User.Protobuf.csproj @@ -0,0 +1,27 @@ + + + + net7.0 + enable + enable + 1.0.0 + None + False + False + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + diff --git a/src/Protobufs/BackOffice.BFF.User.Protobuf/ConfigureServices.cs b/src/Protobufs/BackOffice.BFF.User.Protobuf/ConfigureServices.cs new file mode 100644 index 0000000..c39ed80 --- /dev/null +++ b/src/Protobufs/BackOffice.BFF.User.Protobuf/ConfigureServices.cs @@ -0,0 +1,14 @@ +using FluentValidation; +using System.Reflection; + +namespace Microsoft.Extensions.DependencyInjection; + +public static class ConfigureServices +{ + public static IServiceCollection AddUserProtobufServices(this IServiceCollection services) + { + services.AddValidatorsFromAssembly(Assembly.GetExecutingAssembly()); + return services; + } +} + diff --git a/src/Protobufs/BackOffice.BFF.User.Protobuf/Protos/google/api/annotations.proto b/src/Protobufs/BackOffice.BFF.User.Protobuf/Protos/google/api/annotations.proto new file mode 100644 index 0000000..85c361b --- /dev/null +++ b/src/Protobufs/BackOffice.BFF.User.Protobuf/Protos/google/api/annotations.proto @@ -0,0 +1,31 @@ +// Copyright (c) 2015, Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +import "google/api/http.proto"; +import "google/protobuf/descriptor.proto"; + +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "AnnotationsProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +extend google.protobuf.MethodOptions { + // See `HttpRule`. + HttpRule http = 72295728; +} diff --git a/src/Protobufs/BackOffice.BFF.User.Protobuf/Protos/google/api/http.proto b/src/Protobufs/BackOffice.BFF.User.Protobuf/Protos/google/api/http.proto new file mode 100644 index 0000000..b8426ba --- /dev/null +++ b/src/Protobufs/BackOffice.BFF.User.Protobuf/Protos/google/api/http.proto @@ -0,0 +1,377 @@ +// Copyright 2019 Google LLC. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +syntax = "proto3"; + +package google.api; + +option cc_enable_arenas = true; +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "HttpProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +// Defines the HTTP configuration for an API service. It contains a list of +// [HttpRule][google.api.HttpRule], each specifying the mapping of an RPC method +// to one or more HTTP REST API methods. +message Http { + // A list of HTTP configuration rules that apply to individual API methods. + // + // **NOTE:** All service configuration rules follow "last one wins" order. + repeated HttpRule rules = 1; + + // When set to true, URL path parameters will be fully URI-decoded except in + // cases of single segment matches in reserved expansion, where "%2F" will be + // left encoded. + // + // The default behavior is to not decode RFC 6570 reserved characters in multi + // segment matches. + bool fully_decode_reserved_expansion = 2; +} + +// # gRPC Transcoding +// +// gRPC Transcoding is a feature for mapping between a gRPC method and one or +// more HTTP REST endpoints. It allows developers to build a single API service +// that supports both gRPC APIs and REST APIs. Many systems, including [Google +// APIs](https://github.com/googleapis/googleapis), +// [Cloud Endpoints](https://cloud.google.com/endpoints), [gRPC +// Gateway](https://github.com/grpc-ecosystem/grpc-gateway), +// and [Envoy](https://github.com/envoyproxy/envoy) proxy support this feature +// and use it for large scale production services. +// +// `HttpRule` defines the schema of the gRPC/REST mapping. The mapping specifies +// how different portions of the gRPC request message are mapped to the URL +// path, URL query parameters, and HTTP request body. It also controls how the +// gRPC response message is mapped to the HTTP response body. `HttpRule` is +// typically specified as an `google.api.http` annotation on the gRPC method. +// +// Each mapping specifies a URL path template and an HTTP method. The path +// template may refer to one or more fields in the gRPC request message, as long +// as each field is a non-repeated field with a primitive (non-message) type. +// The path template controls how fields of the request message are mapped to +// the URL path. +// +// Example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get: "/v1/{name=messages/*}" +// }; +// } +// } +// message GetMessageRequest { +// string name = 1; // Mapped to URL path. +// } +// message Message { +// string text = 1; // The resource content. +// } +// +// This enables an HTTP REST to gRPC mapping as below: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456` | `GetMessage(name: "messages/123456")` +// +// Any fields in the request message which are not bound by the path template +// automatically become HTTP query parameters if there is no HTTP request body. +// For example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get:"/v1/messages/{message_id}" +// }; +// } +// } +// message GetMessageRequest { +// message SubMessage { +// string subfield = 1; +// } +// string message_id = 1; // Mapped to URL path. +// int64 revision = 2; // Mapped to URL query parameter `revision`. +// SubMessage sub = 3; // Mapped to URL query parameter `sub.subfield`. +// } +// +// This enables a HTTP JSON to RPC mapping as below: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456?revision=2&sub.subfield=foo` | +// `GetMessage(message_id: "123456" revision: 2 sub: SubMessage(subfield: +// "foo"))` +// +// Note that fields which are mapped to URL query parameters must have a +// primitive type or a repeated primitive type or a non-repeated message type. +// In the case of a repeated type, the parameter can be repeated in the URL +// as `...?param=A¶m=B`. In the case of a message type, each field of the +// message is mapped to a separate parameter, such as +// `...?foo.a=A&foo.b=B&foo.c=C`. +// +// For HTTP methods that allow a request body, the `body` field +// specifies the mapping. Consider a REST update method on the +// message resource collection: +// +// service Messaging { +// rpc UpdateMessage(UpdateMessageRequest) returns (Message) { +// option (google.api.http) = { +// patch: "/v1/messages/{message_id}" +// body: "message" +// }; +// } +// } +// message UpdateMessageRequest { +// string message_id = 1; // mapped to the URL +// Message message = 2; // mapped to the body +// } +// +// The following HTTP JSON to RPC mapping is enabled, where the +// representation of the JSON in the request body is determined by +// protos JSON encoding: +// +// HTTP | gRPC +// -----|----- +// `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: +// "123456" message { text: "Hi!" })` +// +// The special name `*` can be used in the body mapping to define that +// every field not bound by the path template should be mapped to the +// request body. This enables the following alternative definition of +// the update method: +// +// service Messaging { +// rpc UpdateMessage(Message) returns (Message) { +// option (google.api.http) = { +// patch: "/v1/messages/{message_id}" +// body: "*" +// }; +// } +// } +// message Message { +// string message_id = 1; +// string text = 2; +// } +// +// +// The following HTTP JSON to RPC mapping is enabled: +// +// HTTP | gRPC +// -----|----- +// `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: +// "123456" text: "Hi!")` +// +// Note that when using `*` in the body mapping, it is not possible to +// have HTTP parameters, as all fields not bound by the path end in +// the body. This makes this option more rarely used in practice when +// defining REST APIs. The common usage of `*` is in custom methods +// which don't use the URL at all for transferring data. +// +// It is possible to define multiple HTTP methods for one RPC by using +// the `additional_bindings` option. Example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get: "/v1/messages/{message_id}" +// additional_bindings { +// get: "/v1/users/{user_id}/messages/{message_id}" +// } +// }; +// } +// } +// message GetMessageRequest { +// string message_id = 1; +// string user_id = 2; +// } +// +// This enables the following two alternative HTTP JSON to RPC mappings: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456` | `GetMessage(message_id: "123456")` +// `GET /v1/users/me/messages/123456` | `GetMessage(user_id: "me" message_id: +// "123456")` +// +// ## Rules for HTTP mapping +// +// 1. Leaf request fields (recursive expansion nested messages in the request +// message) are classified into three categories: +// - Fields referred by the path template. They are passed via the URL path. +// - Fields referred by the [HttpRule.body][google.api.HttpRule.body]. They are passed via the HTTP +// request body. +// - All other fields are passed via the URL query parameters, and the +// parameter name is the field path in the request message. A repeated +// field can be represented as multiple query parameters under the same +// name. +// 2. If [HttpRule.body][google.api.HttpRule.body] is "*", there is no URL query parameter, all fields +// are passed via URL path and HTTP request body. +// 3. If [HttpRule.body][google.api.HttpRule.body] is omitted, there is no HTTP request body, all +// fields are passed via URL path and URL query parameters. +// +// ### Path template syntax +// +// Template = "/" Segments [ Verb ] ; +// Segments = Segment { "/" Segment } ; +// Segment = "*" | "**" | LITERAL | Variable ; +// Variable = "{" FieldPath [ "=" Segments ] "}" ; +// FieldPath = IDENT { "." IDENT } ; +// Verb = ":" LITERAL ; +// +// The syntax `*` matches a single URL path segment. The syntax `**` matches +// zero or more URL path segments, which must be the last part of the URL path +// except the `Verb`. +// +// The syntax `Variable` matches part of the URL path as specified by its +// template. A variable template must not contain other variables. If a variable +// matches a single path segment, its template may be omitted, e.g. `{var}` +// is equivalent to `{var=*}`. +// +// The syntax `LITERAL` matches literal text in the URL path. If the `LITERAL` +// contains any reserved character, such characters should be percent-encoded +// before the matching. +// +// If a variable contains exactly one path segment, such as `"{var}"` or +// `"{var=*}"`, when such a variable is expanded into a URL path on the client +// side, all characters except `[-_.~0-9a-zA-Z]` are percent-encoded. The +// server side does the reverse decoding. Such variables show up in the +// [Discovery +// Document](https://developers.google.com/discovery/v1/reference/apis) as +// `{var}`. +// +// If a variable contains multiple path segments, such as `"{var=foo/*}"` +// or `"{var=**}"`, when such a variable is expanded into a URL path on the +// client side, all characters except `[-_.~/0-9a-zA-Z]` are percent-encoded. +// The server side does the reverse decoding, except "%2F" and "%2f" are left +// unchanged. Such variables show up in the +// [Discovery +// Document](https://developers.google.com/discovery/v1/reference/apis) as +// `{+var}`. +// +// ## Using gRPC API Service Configuration +// +// gRPC API Service Configuration (service config) is a configuration language +// for configuring a gRPC service to become a user-facing product. The +// service config is simply the YAML representation of the `google.api.Service` +// proto message. +// +// As an alternative to annotating your proto file, you can configure gRPC +// transcoding in your service config YAML files. You do this by specifying a +// `HttpRule` that maps the gRPC method to a REST endpoint, achieving the same +// effect as the proto annotation. This can be particularly useful if you +// have a proto that is reused in multiple services. Note that any transcoding +// specified in the service config will override any matching transcoding +// configuration in the proto. +// +// Example: +// +// http: +// rules: +// # Selects a gRPC method and applies HttpRule to it. +// - selector: example.v1.Messaging.GetMessage +// get: /v1/messages/{message_id}/{sub.subfield} +// +// ## Special notes +// +// When gRPC Transcoding is used to map a gRPC to JSON REST endpoints, the +// proto to JSON conversion must follow the [proto3 +// specification](https://developers.google.com/protocol-buffers/docs/proto3#json). +// +// While the single segment variable follows the semantics of +// [RFC 6570](https://tools.ietf.org/html/rfc6570) Section 3.2.2 Simple String +// Expansion, the multi segment variable **does not** follow RFC 6570 Section +// 3.2.3 Reserved Expansion. The reason is that the Reserved Expansion +// does not expand special characters like `?` and `#`, which would lead +// to invalid URLs. As the result, gRPC Transcoding uses a custom encoding +// for multi segment variables. +// +// The path variables **must not** refer to any repeated or mapped field, +// because client libraries are not capable of handling such variable expansion. +// +// The path variables **must not** capture the leading "/" character. The reason +// is that the most common use case "{var}" does not capture the leading "/" +// character. For consistency, all path variables must share the same behavior. +// +// Repeated message fields must not be mapped to URL query parameters, because +// no client library can support such complicated mapping. +// +// If an API needs to use a JSON array for request or response body, it can map +// the request or response body to a repeated field. However, some gRPC +// Transcoding implementations may not support this feature. +message HttpRule { + // Selects a method to which this rule applies. + // + // Refer to [selector][google.api.DocumentationRule.selector] for syntax details. + string selector = 1; + + // Determines the URL pattern is matched by this rules. This pattern can be + // used with any of the {get|put|post|delete|patch} methods. A custom method + // can be defined using the 'custom' field. + oneof pattern { + // Maps to HTTP GET. Used for listing and getting information about + // resources. + string get = 2; + + // Maps to HTTP PUT. Used for replacing a resource. + string put = 3; + + // Maps to HTTP POST. Used for creating a resource or performing an action. + string post = 4; + + // Maps to HTTP DELETE. Used for deleting a resource. + string delete = 5; + + // Maps to HTTP PATCH. Used for updating a resource. + string patch = 6; + + // The custom pattern is used for specifying an HTTP method that is not + // included in the `pattern` field, such as HEAD, or "*" to leave the + // HTTP method unspecified for this rule. The wild-card rule is useful + // for services that provide content to Web (HTML) clients. + CustomHttpPattern custom = 8; + } + + // The name of the request field whose value is mapped to the HTTP request + // body, or `*` for mapping all request fields not captured by the path + // pattern to the HTTP body, or omitted for not having any HTTP request body. + // + // NOTE: the referred field must be present at the top-level of the request + // message type. + string body = 7; + + // Optional. The name of the response field whose value is mapped to the HTTP + // response body. When omitted, the entire response message will be used + // as the HTTP response body. + // + // NOTE: The referred field must be present at the top-level of the response + // message type. + string response_body = 12; + + // Additional HTTP bindings for the selector. Nested bindings must + // not contain an `additional_bindings` field themselves (that is, + // the nesting may only be one level deep). + repeated HttpRule additional_bindings = 11; +} + +// A custom pattern is used for defining custom HTTP verb. +message CustomHttpPattern { + // The name of this custom HTTP verb. + string kind = 1; + + // The path matched by this custom verb. + string path = 2; +} + diff --git a/src/Protobufs/BackOffice.BFF.User.Protobuf/Protos/user.proto b/src/Protobufs/BackOffice.BFF.User.Protobuf/Protos/user.proto new file mode 100644 index 0000000..f1f3c79 --- /dev/null +++ b/src/Protobufs/BackOffice.BFF.User.Protobuf/Protos/user.proto @@ -0,0 +1,145 @@ +syntax = "proto3"; + +package user; + +import "google/protobuf/empty.proto"; +import "google/protobuf/wrappers.proto"; +import "google/protobuf/duration.proto"; +import "google/protobuf/timestamp.proto"; +import "google/api/annotations.proto"; + +option csharp_namespace = "BackOffice.BFF.User.Protobuf.Protos.User"; + +service UserContract +{ + rpc CreateNewUser(CreateNewUserRequest) returns (CreateNewUserResponse){ + option (google.api.http) = { + post: "/CreateNewUser" + body: "*" + }; + }; + rpc UpdateUser(UpdateUserRequest) returns (google.protobuf.Empty){ + option (google.api.http) = { + put: "/UpdateUser" + body: "*" + }; + }; + rpc DeleteUser(DeleteUserRequest) returns (google.protobuf.Empty){ + option (google.api.http) = { + delete: "/DeleteUser" + body: "*" + }; + }; + rpc GetUser(GetUserRequest) returns (GetUserResponse){ + option (google.api.http) = { + get: "/GetUser" + + }; + }; + rpc GetAllUserByFilter(GetAllUserByFilterRequest) returns (GetAllUserByFilterResponse){ + option (google.api.http) = { + get: "/GetAllUserByFilter" + + }; + }; +} +message CreateNewUserRequest +{ + google.protobuf.StringValue first_name = 1; + google.protobuf.StringValue last_name = 2; + string mobile = 3; + google.protobuf.StringValue national_code = 4; + google.protobuf.StringValue avatar_path = 5; + google.protobuf.Int64Value parent_id = 6; +} +message CreateNewUserResponse +{ + int64 id = 1; +} +message UpdateUserRequest +{ + int64 id = 1; + google.protobuf.StringValue first_name = 2; + google.protobuf.StringValue last_name = 3; + string mobile = 4; + google.protobuf.StringValue national_code = 5; + google.protobuf.StringValue avatar_path = 6; + google.protobuf.Int64Value parent_id = 7; +} +message DeleteUserRequest +{ + int64 id = 1; +} +message GetUserRequest +{ + int64 id = 1; +} +message GetUserResponse +{ + int64 id = 1; + google.protobuf.StringValue first_name = 2; + google.protobuf.StringValue last_name = 3; + string mobile = 4; + google.protobuf.StringValue national_code = 5; + google.protobuf.StringValue avatar_path = 6; + google.protobuf.Int64Value parent_id = 7; +} +message GetAllUserByFilterRequest +{ + PaginationState pagination_state = 1; + google.protobuf.StringValue sort_by = 2; + GetAllUserByFilterFilter filter = 3; +} +message GetAllUserByFilterFilter +{ + google.protobuf.Int64Value id = 1; + google.protobuf.StringValue first_name = 2; + google.protobuf.StringValue last_name = 3; + google.protobuf.StringValue mobile = 4; + google.protobuf.StringValue national_code = 5; + google.protobuf.StringValue avatar_path = 6; + google.protobuf.Int64Value parent_id = 7; +} +message GetAllUserByFilterResponse +{ + MetaData meta_data = 1; + repeated GetAllUserByFilterResponseModel models = 2; +} +message GetAllUserByFilterResponseModel +{ + int64 id = 1; + google.protobuf.StringValue first_name = 2; + google.protobuf.StringValue last_name = 3; + string mobile = 4; + google.protobuf.StringValue national_code = 5; + google.protobuf.StringValue avatar_path = 6; + google.protobuf.Int64Value parent_id = 7; +} + +message PaginationState +{ + int32 page_number = 1; + + int32 page_size = 2; +} +message MetaData +{ + int64 current_page = 1; + + int64 total_page = 2; + + int64 page_size = 3; + + int64 total_count = 4; + + bool has_previous = 5; + + bool has_next = 6; +} +message DecimalValue +{ + + int64 units = 1; + + sfixed32 nanos = 2; +} diff --git a/src/Protobufs/BackOffice.BFF.User.Protobuf/Validator/CreateNewUserRequestValidator.cs b/src/Protobufs/BackOffice.BFF.User.Protobuf/Validator/CreateNewUserRequestValidator.cs new file mode 100644 index 0000000..fe94a1e --- /dev/null +++ b/src/Protobufs/BackOffice.BFF.User.Protobuf/Validator/CreateNewUserRequestValidator.cs @@ -0,0 +1,19 @@ +using FluentValidation; +using BackOffice.BFF.User.Protobuf.Protos.User; +namespace BackOffice.BFF.User.Protobuf.Validator; + +public class CreateNewUserRequestValidator : AbstractValidator +{ + public CreateNewUserRequestValidator() + { + RuleFor(model => model.Mobile) + .NotEmpty(); + } + public Func>> ValidateValue => async (model, propertyName) => + { + var result = await ValidateAsync(ValidationContext.CreateWithOptions((CreateNewUserRequest)model, x => x.IncludeProperties(propertyName))); + if (result.IsValid) + return Array.Empty(); + return result.Errors.Select(e => e.ErrorMessage); + }; +} diff --git a/src/Protobufs/BackOffice.BFF.User.Protobuf/Validator/DeleteUserRequestValidator.cs b/src/Protobufs/BackOffice.BFF.User.Protobuf/Validator/DeleteUserRequestValidator.cs new file mode 100644 index 0000000..47707d0 --- /dev/null +++ b/src/Protobufs/BackOffice.BFF.User.Protobuf/Validator/DeleteUserRequestValidator.cs @@ -0,0 +1,19 @@ +using FluentValidation; +using BackOffice.BFF.User.Protobuf.Protos.User; +namespace BackOffice.BFF.User.Protobuf.Validator; + +public class DeleteUserRequestValidator : AbstractValidator +{ + public DeleteUserRequestValidator() + { + RuleFor(model => model.Id) + .NotNull(); + } + public Func>> ValidateValue => async (model, propertyName) => + { + var result = await ValidateAsync(ValidationContext.CreateWithOptions((DeleteUserRequest)model, x => x.IncludeProperties(propertyName))); + if (result.IsValid) + return Array.Empty(); + return result.Errors.Select(e => e.ErrorMessage); + }; +} diff --git a/src/Protobufs/BackOffice.BFF.User.Protobuf/Validator/GetAllUserByFilterRequestValidator.cs b/src/Protobufs/BackOffice.BFF.User.Protobuf/Validator/GetAllUserByFilterRequestValidator.cs new file mode 100644 index 0000000..b5baaa2 --- /dev/null +++ b/src/Protobufs/BackOffice.BFF.User.Protobuf/Validator/GetAllUserByFilterRequestValidator.cs @@ -0,0 +1,17 @@ +using FluentValidation; +using BackOffice.BFF.User.Protobuf.Protos.User; +namespace BackOffice.BFF.User.Protobuf.Validator; + +public class GetAllUserByFilterRequestValidator : AbstractValidator +{ + public GetAllUserByFilterRequestValidator() + { + } + public Func>> ValidateValue => async (model, propertyName) => + { + var result = await ValidateAsync(ValidationContext.CreateWithOptions((GetAllUserByFilterRequest)model, x => x.IncludeProperties(propertyName))); + if (result.IsValid) + return Array.Empty(); + return result.Errors.Select(e => e.ErrorMessage); + }; +} diff --git a/src/Protobufs/BackOffice.BFF.User.Protobuf/Validator/GetUserRequestValidator.cs b/src/Protobufs/BackOffice.BFF.User.Protobuf/Validator/GetUserRequestValidator.cs new file mode 100644 index 0000000..bfa7dd2 --- /dev/null +++ b/src/Protobufs/BackOffice.BFF.User.Protobuf/Validator/GetUserRequestValidator.cs @@ -0,0 +1,19 @@ +using FluentValidation; +using BackOffice.BFF.User.Protobuf.Protos.User; +namespace BackOffice.BFF.User.Protobuf.Validator; + +public class GetUserRequestValidator : AbstractValidator +{ + public GetUserRequestValidator() + { + RuleFor(model => model.Id) + .NotNull(); + } + public Func>> ValidateValue => async (model, propertyName) => + { + var result = await ValidateAsync(ValidationContext.CreateWithOptions((GetUserRequest)model, x => x.IncludeProperties(propertyName))); + if (result.IsValid) + return Array.Empty(); + return result.Errors.Select(e => e.ErrorMessage); + }; +} diff --git a/src/Protobufs/BackOffice.BFF.User.Protobuf/Validator/UpdateUserRequestValidator.cs b/src/Protobufs/BackOffice.BFF.User.Protobuf/Validator/UpdateUserRequestValidator.cs new file mode 100644 index 0000000..27ba2de --- /dev/null +++ b/src/Protobufs/BackOffice.BFF.User.Protobuf/Validator/UpdateUserRequestValidator.cs @@ -0,0 +1,21 @@ +using FluentValidation; +using BackOffice.BFF.User.Protobuf.Protos.User; +namespace BackOffice.BFF.User.Protobuf.Validator; + +public class UpdateUserRequestValidator : AbstractValidator +{ + public UpdateUserRequestValidator() + { + RuleFor(model => model.Id) + .NotNull(); + RuleFor(model => model.Mobile) + .NotEmpty(); + } + public Func>> ValidateValue => async (model, propertyName) => + { + var result = await ValidateAsync(ValidationContext.CreateWithOptions((UpdateUserRequest)model, x => x.IncludeProperties(propertyName))); + if (result.IsValid) + return Array.Empty(); + return result.Errors.Select(e => e.ErrorMessage); + }; +} diff --git a/src/Protobufs/BackOffice.BFF.UserAddress.Protobuf/BackOffice.BFF.UserAddress.Protobuf.csproj b/src/Protobufs/BackOffice.BFF.UserAddress.Protobuf/BackOffice.BFF.UserAddress.Protobuf.csproj new file mode 100644 index 0000000..9b7dc6b --- /dev/null +++ b/src/Protobufs/BackOffice.BFF.UserAddress.Protobuf/BackOffice.BFF.UserAddress.Protobuf.csproj @@ -0,0 +1,27 @@ + + + + net7.0 + enable + enable + 1.0.0 + None + False + False + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + diff --git a/src/Protobufs/BackOffice.BFF.UserAddress.Protobuf/ConfigureServices.cs b/src/Protobufs/BackOffice.BFF.UserAddress.Protobuf/ConfigureServices.cs new file mode 100644 index 0000000..eee4faf --- /dev/null +++ b/src/Protobufs/BackOffice.BFF.UserAddress.Protobuf/ConfigureServices.cs @@ -0,0 +1,14 @@ +using FluentValidation; +using System.Reflection; + +namespace Microsoft.Extensions.DependencyInjection; + +public static class ConfigureServices +{ + public static IServiceCollection AddUserAddressProtobufServices(this IServiceCollection services) + { + services.AddValidatorsFromAssembly(Assembly.GetExecutingAssembly()); + return services; + } +} + diff --git a/src/Protobufs/BackOffice.BFF.UserAddress.Protobuf/Protos/google/api/annotations.proto b/src/Protobufs/BackOffice.BFF.UserAddress.Protobuf/Protos/google/api/annotations.proto new file mode 100644 index 0000000..85c361b --- /dev/null +++ b/src/Protobufs/BackOffice.BFF.UserAddress.Protobuf/Protos/google/api/annotations.proto @@ -0,0 +1,31 @@ +// Copyright (c) 2015, Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +import "google/api/http.proto"; +import "google/protobuf/descriptor.proto"; + +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "AnnotationsProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +extend google.protobuf.MethodOptions { + // See `HttpRule`. + HttpRule http = 72295728; +} diff --git a/src/Protobufs/BackOffice.BFF.UserAddress.Protobuf/Protos/google/api/http.proto b/src/Protobufs/BackOffice.BFF.UserAddress.Protobuf/Protos/google/api/http.proto new file mode 100644 index 0000000..b8426ba --- /dev/null +++ b/src/Protobufs/BackOffice.BFF.UserAddress.Protobuf/Protos/google/api/http.proto @@ -0,0 +1,377 @@ +// Copyright 2019 Google LLC. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +syntax = "proto3"; + +package google.api; + +option cc_enable_arenas = true; +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "HttpProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +// Defines the HTTP configuration for an API service. It contains a list of +// [HttpRule][google.api.HttpRule], each specifying the mapping of an RPC method +// to one or more HTTP REST API methods. +message Http { + // A list of HTTP configuration rules that apply to individual API methods. + // + // **NOTE:** All service configuration rules follow "last one wins" order. + repeated HttpRule rules = 1; + + // When set to true, URL path parameters will be fully URI-decoded except in + // cases of single segment matches in reserved expansion, where "%2F" will be + // left encoded. + // + // The default behavior is to not decode RFC 6570 reserved characters in multi + // segment matches. + bool fully_decode_reserved_expansion = 2; +} + +// # gRPC Transcoding +// +// gRPC Transcoding is a feature for mapping between a gRPC method and one or +// more HTTP REST endpoints. It allows developers to build a single API service +// that supports both gRPC APIs and REST APIs. Many systems, including [Google +// APIs](https://github.com/googleapis/googleapis), +// [Cloud Endpoints](https://cloud.google.com/endpoints), [gRPC +// Gateway](https://github.com/grpc-ecosystem/grpc-gateway), +// and [Envoy](https://github.com/envoyproxy/envoy) proxy support this feature +// and use it for large scale production services. +// +// `HttpRule` defines the schema of the gRPC/REST mapping. The mapping specifies +// how different portions of the gRPC request message are mapped to the URL +// path, URL query parameters, and HTTP request body. It also controls how the +// gRPC response message is mapped to the HTTP response body. `HttpRule` is +// typically specified as an `google.api.http` annotation on the gRPC method. +// +// Each mapping specifies a URL path template and an HTTP method. The path +// template may refer to one or more fields in the gRPC request message, as long +// as each field is a non-repeated field with a primitive (non-message) type. +// The path template controls how fields of the request message are mapped to +// the URL path. +// +// Example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get: "/v1/{name=messages/*}" +// }; +// } +// } +// message GetMessageRequest { +// string name = 1; // Mapped to URL path. +// } +// message Message { +// string text = 1; // The resource content. +// } +// +// This enables an HTTP REST to gRPC mapping as below: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456` | `GetMessage(name: "messages/123456")` +// +// Any fields in the request message which are not bound by the path template +// automatically become HTTP query parameters if there is no HTTP request body. +// For example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get:"/v1/messages/{message_id}" +// }; +// } +// } +// message GetMessageRequest { +// message SubMessage { +// string subfield = 1; +// } +// string message_id = 1; // Mapped to URL path. +// int64 revision = 2; // Mapped to URL query parameter `revision`. +// SubMessage sub = 3; // Mapped to URL query parameter `sub.subfield`. +// } +// +// This enables a HTTP JSON to RPC mapping as below: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456?revision=2&sub.subfield=foo` | +// `GetMessage(message_id: "123456" revision: 2 sub: SubMessage(subfield: +// "foo"))` +// +// Note that fields which are mapped to URL query parameters must have a +// primitive type or a repeated primitive type or a non-repeated message type. +// In the case of a repeated type, the parameter can be repeated in the URL +// as `...?param=A¶m=B`. In the case of a message type, each field of the +// message is mapped to a separate parameter, such as +// `...?foo.a=A&foo.b=B&foo.c=C`. +// +// For HTTP methods that allow a request body, the `body` field +// specifies the mapping. Consider a REST update method on the +// message resource collection: +// +// service Messaging { +// rpc UpdateMessage(UpdateMessageRequest) returns (Message) { +// option (google.api.http) = { +// patch: "/v1/messages/{message_id}" +// body: "message" +// }; +// } +// } +// message UpdateMessageRequest { +// string message_id = 1; // mapped to the URL +// Message message = 2; // mapped to the body +// } +// +// The following HTTP JSON to RPC mapping is enabled, where the +// representation of the JSON in the request body is determined by +// protos JSON encoding: +// +// HTTP | gRPC +// -----|----- +// `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: +// "123456" message { text: "Hi!" })` +// +// The special name `*` can be used in the body mapping to define that +// every field not bound by the path template should be mapped to the +// request body. This enables the following alternative definition of +// the update method: +// +// service Messaging { +// rpc UpdateMessage(Message) returns (Message) { +// option (google.api.http) = { +// patch: "/v1/messages/{message_id}" +// body: "*" +// }; +// } +// } +// message Message { +// string message_id = 1; +// string text = 2; +// } +// +// +// The following HTTP JSON to RPC mapping is enabled: +// +// HTTP | gRPC +// -----|----- +// `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: +// "123456" text: "Hi!")` +// +// Note that when using `*` in the body mapping, it is not possible to +// have HTTP parameters, as all fields not bound by the path end in +// the body. This makes this option more rarely used in practice when +// defining REST APIs. The common usage of `*` is in custom methods +// which don't use the URL at all for transferring data. +// +// It is possible to define multiple HTTP methods for one RPC by using +// the `additional_bindings` option. Example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get: "/v1/messages/{message_id}" +// additional_bindings { +// get: "/v1/users/{user_id}/messages/{message_id}" +// } +// }; +// } +// } +// message GetMessageRequest { +// string message_id = 1; +// string user_id = 2; +// } +// +// This enables the following two alternative HTTP JSON to RPC mappings: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456` | `GetMessage(message_id: "123456")` +// `GET /v1/users/me/messages/123456` | `GetMessage(user_id: "me" message_id: +// "123456")` +// +// ## Rules for HTTP mapping +// +// 1. Leaf request fields (recursive expansion nested messages in the request +// message) are classified into three categories: +// - Fields referred by the path template. They are passed via the URL path. +// - Fields referred by the [HttpRule.body][google.api.HttpRule.body]. They are passed via the HTTP +// request body. +// - All other fields are passed via the URL query parameters, and the +// parameter name is the field path in the request message. A repeated +// field can be represented as multiple query parameters under the same +// name. +// 2. If [HttpRule.body][google.api.HttpRule.body] is "*", there is no URL query parameter, all fields +// are passed via URL path and HTTP request body. +// 3. If [HttpRule.body][google.api.HttpRule.body] is omitted, there is no HTTP request body, all +// fields are passed via URL path and URL query parameters. +// +// ### Path template syntax +// +// Template = "/" Segments [ Verb ] ; +// Segments = Segment { "/" Segment } ; +// Segment = "*" | "**" | LITERAL | Variable ; +// Variable = "{" FieldPath [ "=" Segments ] "}" ; +// FieldPath = IDENT { "." IDENT } ; +// Verb = ":" LITERAL ; +// +// The syntax `*` matches a single URL path segment. The syntax `**` matches +// zero or more URL path segments, which must be the last part of the URL path +// except the `Verb`. +// +// The syntax `Variable` matches part of the URL path as specified by its +// template. A variable template must not contain other variables. If a variable +// matches a single path segment, its template may be omitted, e.g. `{var}` +// is equivalent to `{var=*}`. +// +// The syntax `LITERAL` matches literal text in the URL path. If the `LITERAL` +// contains any reserved character, such characters should be percent-encoded +// before the matching. +// +// If a variable contains exactly one path segment, such as `"{var}"` or +// `"{var=*}"`, when such a variable is expanded into a URL path on the client +// side, all characters except `[-_.~0-9a-zA-Z]` are percent-encoded. The +// server side does the reverse decoding. Such variables show up in the +// [Discovery +// Document](https://developers.google.com/discovery/v1/reference/apis) as +// `{var}`. +// +// If a variable contains multiple path segments, such as `"{var=foo/*}"` +// or `"{var=**}"`, when such a variable is expanded into a URL path on the +// client side, all characters except `[-_.~/0-9a-zA-Z]` are percent-encoded. +// The server side does the reverse decoding, except "%2F" and "%2f" are left +// unchanged. Such variables show up in the +// [Discovery +// Document](https://developers.google.com/discovery/v1/reference/apis) as +// `{+var}`. +// +// ## Using gRPC API Service Configuration +// +// gRPC API Service Configuration (service config) is a configuration language +// for configuring a gRPC service to become a user-facing product. The +// service config is simply the YAML representation of the `google.api.Service` +// proto message. +// +// As an alternative to annotating your proto file, you can configure gRPC +// transcoding in your service config YAML files. You do this by specifying a +// `HttpRule` that maps the gRPC method to a REST endpoint, achieving the same +// effect as the proto annotation. This can be particularly useful if you +// have a proto that is reused in multiple services. Note that any transcoding +// specified in the service config will override any matching transcoding +// configuration in the proto. +// +// Example: +// +// http: +// rules: +// # Selects a gRPC method and applies HttpRule to it. +// - selector: example.v1.Messaging.GetMessage +// get: /v1/messages/{message_id}/{sub.subfield} +// +// ## Special notes +// +// When gRPC Transcoding is used to map a gRPC to JSON REST endpoints, the +// proto to JSON conversion must follow the [proto3 +// specification](https://developers.google.com/protocol-buffers/docs/proto3#json). +// +// While the single segment variable follows the semantics of +// [RFC 6570](https://tools.ietf.org/html/rfc6570) Section 3.2.2 Simple String +// Expansion, the multi segment variable **does not** follow RFC 6570 Section +// 3.2.3 Reserved Expansion. The reason is that the Reserved Expansion +// does not expand special characters like `?` and `#`, which would lead +// to invalid URLs. As the result, gRPC Transcoding uses a custom encoding +// for multi segment variables. +// +// The path variables **must not** refer to any repeated or mapped field, +// because client libraries are not capable of handling such variable expansion. +// +// The path variables **must not** capture the leading "/" character. The reason +// is that the most common use case "{var}" does not capture the leading "/" +// character. For consistency, all path variables must share the same behavior. +// +// Repeated message fields must not be mapped to URL query parameters, because +// no client library can support such complicated mapping. +// +// If an API needs to use a JSON array for request or response body, it can map +// the request or response body to a repeated field. However, some gRPC +// Transcoding implementations may not support this feature. +message HttpRule { + // Selects a method to which this rule applies. + // + // Refer to [selector][google.api.DocumentationRule.selector] for syntax details. + string selector = 1; + + // Determines the URL pattern is matched by this rules. This pattern can be + // used with any of the {get|put|post|delete|patch} methods. A custom method + // can be defined using the 'custom' field. + oneof pattern { + // Maps to HTTP GET. Used for listing and getting information about + // resources. + string get = 2; + + // Maps to HTTP PUT. Used for replacing a resource. + string put = 3; + + // Maps to HTTP POST. Used for creating a resource or performing an action. + string post = 4; + + // Maps to HTTP DELETE. Used for deleting a resource. + string delete = 5; + + // Maps to HTTP PATCH. Used for updating a resource. + string patch = 6; + + // The custom pattern is used for specifying an HTTP method that is not + // included in the `pattern` field, such as HEAD, or "*" to leave the + // HTTP method unspecified for this rule. The wild-card rule is useful + // for services that provide content to Web (HTML) clients. + CustomHttpPattern custom = 8; + } + + // The name of the request field whose value is mapped to the HTTP request + // body, or `*` for mapping all request fields not captured by the path + // pattern to the HTTP body, or omitted for not having any HTTP request body. + // + // NOTE: the referred field must be present at the top-level of the request + // message type. + string body = 7; + + // Optional. The name of the response field whose value is mapped to the HTTP + // response body. When omitted, the entire response message will be used + // as the HTTP response body. + // + // NOTE: The referred field must be present at the top-level of the response + // message type. + string response_body = 12; + + // Additional HTTP bindings for the selector. Nested bindings must + // not contain an `additional_bindings` field themselves (that is, + // the nesting may only be one level deep). + repeated HttpRule additional_bindings = 11; +} + +// A custom pattern is used for defining custom HTTP verb. +message CustomHttpPattern { + // The name of this custom HTTP verb. + string kind = 1; + + // The path matched by this custom verb. + string path = 2; +} + diff --git a/src/Protobufs/BackOffice.BFF.UserAddress.Protobuf/Protos/useraddress.proto b/src/Protobufs/BackOffice.BFF.UserAddress.Protobuf/Protos/useraddress.proto new file mode 100644 index 0000000..51f4c1a --- /dev/null +++ b/src/Protobufs/BackOffice.BFF.UserAddress.Protobuf/Protos/useraddress.proto @@ -0,0 +1,145 @@ +syntax = "proto3"; + +package useraddress; + +import "google/protobuf/empty.proto"; +import "google/protobuf/wrappers.proto"; +import "google/protobuf/duration.proto"; +import "google/protobuf/timestamp.proto"; +import "google/api/annotations.proto"; + +option csharp_namespace = "BackOffice.BFF.UserAddress.Protobuf.Protos.UserAddress"; + +service UserAddressContract +{ + rpc CreateNewUserAddress(CreateNewUserAddressRequest) returns (CreateNewUserAddressResponse){ + option (google.api.http) = { + post: "/CreateNewUserAddress" + body: "*" + }; + }; + rpc UpdateUserAddress(UpdateUserAddressRequest) returns (google.protobuf.Empty){ + option (google.api.http) = { + put: "/UpdateUserAddress" + body: "*" + }; + }; + rpc DeleteUserAddress(DeleteUserAddressRequest) returns (google.protobuf.Empty){ + option (google.api.http) = { + delete: "/DeleteUserAddress" + body: "*" + }; + }; + rpc GetUserAddress(GetUserAddressRequest) returns (GetUserAddressResponse){ + option (google.api.http) = { + get: "/GetUserAddress" + + }; + }; + rpc GetAllUserAddressByFilter(GetAllUserAddressByFilterRequest) returns (GetAllUserAddressByFilterResponse){ + option (google.api.http) = { + get: "/GetAllUserAddressByFilter" + + }; + }; +} +message CreateNewUserAddressRequest +{ + int64 user_id = 1; + string title = 2; + string address = 3; + string postal_code = 4; + bool is_default = 5; + int64 city_id = 6; +} +message CreateNewUserAddressResponse +{ + int64 id = 1; +} +message UpdateUserAddressRequest +{ + int64 id = 1; + int64 user_id = 2; + string title = 3; + string address = 4; + string postal_code = 5; + bool is_default = 6; + int64 city_id = 7; +} +message DeleteUserAddressRequest +{ + int64 id = 1; +} +message GetUserAddressRequest +{ + int64 id = 1; +} +message GetUserAddressResponse +{ + int64 id = 1; + int64 user_id = 2; + string title = 3; + string address = 4; + string postal_code = 5; + bool is_default = 6; + int64 city_id = 7; +} +message GetAllUserAddressByFilterRequest +{ + PaginationState pagination_state = 1; + google.protobuf.StringValue sort_by = 2; + GetAllUserAddressByFilterFilter filter = 3; +} +message GetAllUserAddressByFilterFilter +{ + google.protobuf.Int64Value id = 1; + google.protobuf.Int64Value user_id = 2; + google.protobuf.StringValue title = 3; + google.protobuf.StringValue address = 4; + google.protobuf.StringValue postal_code = 5; + google.protobuf.BoolValue is_default = 6; + google.protobuf.Int64Value city_id = 7; +} +message GetAllUserAddressByFilterResponse +{ + MetaData meta_data = 1; + repeated GetAllUserAddressByFilterResponseModel models = 2; +} +message GetAllUserAddressByFilterResponseModel +{ + int64 id = 1; + int64 user_id = 2; + string title = 3; + string address = 4; + string postal_code = 5; + bool is_default = 6; + int64 city_id = 7; +} + +message PaginationState +{ + int32 page_number = 1; + + int32 page_size = 2; +} +message MetaData +{ + int64 current_page = 1; + + int64 total_page = 2; + + int64 page_size = 3; + + int64 total_count = 4; + + bool has_previous = 5; + + bool has_next = 6; +} +message DecimalValue +{ + + int64 units = 1; + + sfixed32 nanos = 2; +} diff --git a/src/Protobufs/BackOffice.BFF.UserAddress.Protobuf/Validator/CreateNewUserAddressRequestValidator.cs b/src/Protobufs/BackOffice.BFF.UserAddress.Protobuf/Validator/CreateNewUserAddressRequestValidator.cs new file mode 100644 index 0000000..d611be7 --- /dev/null +++ b/src/Protobufs/BackOffice.BFF.UserAddress.Protobuf/Validator/CreateNewUserAddressRequestValidator.cs @@ -0,0 +1,29 @@ +using FluentValidation; +using BackOffice.BFF.UserAddress.Protobuf.Protos.UserAddress; +namespace BackOffice.BFF.UserAddress.Protobuf.Validator; + +public class CreateNewUserAddressRequestValidator : AbstractValidator +{ + public CreateNewUserAddressRequestValidator() + { + RuleFor(model => model.UserId) + .NotNull(); + RuleFor(model => model.Title) + .NotEmpty(); + RuleFor(model => model.Address) + .NotEmpty(); + RuleFor(model => model.PostalCode) + .NotEmpty(); + RuleFor(model => model.IsDefault) + .NotNull(); + RuleFor(model => model.CityId) + .NotNull(); + } + public Func>> ValidateValue => async (model, propertyName) => + { + var result = await ValidateAsync(ValidationContext.CreateWithOptions((CreateNewUserAddressRequest)model, x => x.IncludeProperties(propertyName))); + if (result.IsValid) + return Array.Empty(); + return result.Errors.Select(e => e.ErrorMessage); + }; +} diff --git a/src/Protobufs/BackOffice.BFF.UserAddress.Protobuf/Validator/DeleteUserAddressRequestValidator.cs b/src/Protobufs/BackOffice.BFF.UserAddress.Protobuf/Validator/DeleteUserAddressRequestValidator.cs new file mode 100644 index 0000000..98b9db6 --- /dev/null +++ b/src/Protobufs/BackOffice.BFF.UserAddress.Protobuf/Validator/DeleteUserAddressRequestValidator.cs @@ -0,0 +1,19 @@ +using FluentValidation; +using BackOffice.BFF.UserAddress.Protobuf.Protos.UserAddress; +namespace BackOffice.BFF.UserAddress.Protobuf.Validator; + +public class DeleteUserAddressRequestValidator : AbstractValidator +{ + public DeleteUserAddressRequestValidator() + { + RuleFor(model => model.Id) + .NotNull(); + } + public Func>> ValidateValue => async (model, propertyName) => + { + var result = await ValidateAsync(ValidationContext.CreateWithOptions((DeleteUserAddressRequest)model, x => x.IncludeProperties(propertyName))); + if (result.IsValid) + return Array.Empty(); + return result.Errors.Select(e => e.ErrorMessage); + }; +} diff --git a/src/Protobufs/BackOffice.BFF.UserAddress.Protobuf/Validator/GetAllUserAddressByFilterRequestValidator.cs b/src/Protobufs/BackOffice.BFF.UserAddress.Protobuf/Validator/GetAllUserAddressByFilterRequestValidator.cs new file mode 100644 index 0000000..0f40f51 --- /dev/null +++ b/src/Protobufs/BackOffice.BFF.UserAddress.Protobuf/Validator/GetAllUserAddressByFilterRequestValidator.cs @@ -0,0 +1,17 @@ +using FluentValidation; +using BackOffice.BFF.UserAddress.Protobuf.Protos.UserAddress; +namespace BackOffice.BFF.UserAddress.Protobuf.Validator; + +public class GetAllUserAddressByFilterRequestValidator : AbstractValidator +{ + public GetAllUserAddressByFilterRequestValidator() + { + } + public Func>> ValidateValue => async (model, propertyName) => + { + var result = await ValidateAsync(ValidationContext.CreateWithOptions((GetAllUserAddressByFilterRequest)model, x => x.IncludeProperties(propertyName))); + if (result.IsValid) + return Array.Empty(); + return result.Errors.Select(e => e.ErrorMessage); + }; +} diff --git a/src/Protobufs/BackOffice.BFF.UserAddress.Protobuf/Validator/GetUserAddressRequestValidator.cs b/src/Protobufs/BackOffice.BFF.UserAddress.Protobuf/Validator/GetUserAddressRequestValidator.cs new file mode 100644 index 0000000..f4aad49 --- /dev/null +++ b/src/Protobufs/BackOffice.BFF.UserAddress.Protobuf/Validator/GetUserAddressRequestValidator.cs @@ -0,0 +1,19 @@ +using FluentValidation; +using BackOffice.BFF.UserAddress.Protobuf.Protos.UserAddress; +namespace BackOffice.BFF.UserAddress.Protobuf.Validator; + +public class GetUserAddressRequestValidator : AbstractValidator +{ + public GetUserAddressRequestValidator() + { + RuleFor(model => model.Id) + .NotNull(); + } + public Func>> ValidateValue => async (model, propertyName) => + { + var result = await ValidateAsync(ValidationContext.CreateWithOptions((GetUserAddressRequest)model, x => x.IncludeProperties(propertyName))); + if (result.IsValid) + return Array.Empty(); + return result.Errors.Select(e => e.ErrorMessage); + }; +} diff --git a/src/Protobufs/BackOffice.BFF.UserAddress.Protobuf/Validator/UpdateUserAddressRequestValidator.cs b/src/Protobufs/BackOffice.BFF.UserAddress.Protobuf/Validator/UpdateUserAddressRequestValidator.cs new file mode 100644 index 0000000..35306ff --- /dev/null +++ b/src/Protobufs/BackOffice.BFF.UserAddress.Protobuf/Validator/UpdateUserAddressRequestValidator.cs @@ -0,0 +1,31 @@ +using FluentValidation; +using BackOffice.BFF.UserAddress.Protobuf.Protos.UserAddress; +namespace BackOffice.BFF.UserAddress.Protobuf.Validator; + +public class UpdateUserAddressRequestValidator : AbstractValidator +{ + public UpdateUserAddressRequestValidator() + { + RuleFor(model => model.Id) + .NotNull(); + RuleFor(model => model.UserId) + .NotNull(); + RuleFor(model => model.Title) + .NotEmpty(); + RuleFor(model => model.Address) + .NotEmpty(); + RuleFor(model => model.PostalCode) + .NotEmpty(); + RuleFor(model => model.IsDefault) + .NotNull(); + RuleFor(model => model.CityId) + .NotNull(); + } + public Func>> ValidateValue => async (model, propertyName) => + { + var result = await ValidateAsync(ValidationContext.CreateWithOptions((UpdateUserAddressRequest)model, x => x.IncludeProperties(propertyName))); + if (result.IsValid) + return Array.Empty(); + return result.Errors.Select(e => e.ErrorMessage); + }; +} diff --git a/src/Protobufs/BackOffice.BFF.UserOrder.Protobuf/BackOffice.BFF.UserOrder.Protobuf.csproj b/src/Protobufs/BackOffice.BFF.UserOrder.Protobuf/BackOffice.BFF.UserOrder.Protobuf.csproj new file mode 100644 index 0000000..6e1b96c --- /dev/null +++ b/src/Protobufs/BackOffice.BFF.UserOrder.Protobuf/BackOffice.BFF.UserOrder.Protobuf.csproj @@ -0,0 +1,27 @@ + + + + net7.0 + enable + enable + 1.0.0 + None + False + False + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + diff --git a/src/Protobufs/BackOffice.BFF.UserOrder.Protobuf/ConfigureServices.cs b/src/Protobufs/BackOffice.BFF.UserOrder.Protobuf/ConfigureServices.cs new file mode 100644 index 0000000..976b460 --- /dev/null +++ b/src/Protobufs/BackOffice.BFF.UserOrder.Protobuf/ConfigureServices.cs @@ -0,0 +1,14 @@ +using FluentValidation; +using System.Reflection; + +namespace Microsoft.Extensions.DependencyInjection; + +public static class ConfigureServices +{ + public static IServiceCollection AddUserOrderProtobufServices(this IServiceCollection services) + { + services.AddValidatorsFromAssembly(Assembly.GetExecutingAssembly()); + return services; + } +} + diff --git a/src/Protobufs/BackOffice.BFF.UserOrder.Protobuf/Protos/google/api/annotations.proto b/src/Protobufs/BackOffice.BFF.UserOrder.Protobuf/Protos/google/api/annotations.proto new file mode 100644 index 0000000..85c361b --- /dev/null +++ b/src/Protobufs/BackOffice.BFF.UserOrder.Protobuf/Protos/google/api/annotations.proto @@ -0,0 +1,31 @@ +// Copyright (c) 2015, Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +import "google/api/http.proto"; +import "google/protobuf/descriptor.proto"; + +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "AnnotationsProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +extend google.protobuf.MethodOptions { + // See `HttpRule`. + HttpRule http = 72295728; +} diff --git a/src/Protobufs/BackOffice.BFF.UserOrder.Protobuf/Protos/google/api/http.proto b/src/Protobufs/BackOffice.BFF.UserOrder.Protobuf/Protos/google/api/http.proto new file mode 100644 index 0000000..b8426ba --- /dev/null +++ b/src/Protobufs/BackOffice.BFF.UserOrder.Protobuf/Protos/google/api/http.proto @@ -0,0 +1,377 @@ +// Copyright 2019 Google LLC. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +syntax = "proto3"; + +package google.api; + +option cc_enable_arenas = true; +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "HttpProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +// Defines the HTTP configuration for an API service. It contains a list of +// [HttpRule][google.api.HttpRule], each specifying the mapping of an RPC method +// to one or more HTTP REST API methods. +message Http { + // A list of HTTP configuration rules that apply to individual API methods. + // + // **NOTE:** All service configuration rules follow "last one wins" order. + repeated HttpRule rules = 1; + + // When set to true, URL path parameters will be fully URI-decoded except in + // cases of single segment matches in reserved expansion, where "%2F" will be + // left encoded. + // + // The default behavior is to not decode RFC 6570 reserved characters in multi + // segment matches. + bool fully_decode_reserved_expansion = 2; +} + +// # gRPC Transcoding +// +// gRPC Transcoding is a feature for mapping between a gRPC method and one or +// more HTTP REST endpoints. It allows developers to build a single API service +// that supports both gRPC APIs and REST APIs. Many systems, including [Google +// APIs](https://github.com/googleapis/googleapis), +// [Cloud Endpoints](https://cloud.google.com/endpoints), [gRPC +// Gateway](https://github.com/grpc-ecosystem/grpc-gateway), +// and [Envoy](https://github.com/envoyproxy/envoy) proxy support this feature +// and use it for large scale production services. +// +// `HttpRule` defines the schema of the gRPC/REST mapping. The mapping specifies +// how different portions of the gRPC request message are mapped to the URL +// path, URL query parameters, and HTTP request body. It also controls how the +// gRPC response message is mapped to the HTTP response body. `HttpRule` is +// typically specified as an `google.api.http` annotation on the gRPC method. +// +// Each mapping specifies a URL path template and an HTTP method. The path +// template may refer to one or more fields in the gRPC request message, as long +// as each field is a non-repeated field with a primitive (non-message) type. +// The path template controls how fields of the request message are mapped to +// the URL path. +// +// Example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get: "/v1/{name=messages/*}" +// }; +// } +// } +// message GetMessageRequest { +// string name = 1; // Mapped to URL path. +// } +// message Message { +// string text = 1; // The resource content. +// } +// +// This enables an HTTP REST to gRPC mapping as below: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456` | `GetMessage(name: "messages/123456")` +// +// Any fields in the request message which are not bound by the path template +// automatically become HTTP query parameters if there is no HTTP request body. +// For example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get:"/v1/messages/{message_id}" +// }; +// } +// } +// message GetMessageRequest { +// message SubMessage { +// string subfield = 1; +// } +// string message_id = 1; // Mapped to URL path. +// int64 revision = 2; // Mapped to URL query parameter `revision`. +// SubMessage sub = 3; // Mapped to URL query parameter `sub.subfield`. +// } +// +// This enables a HTTP JSON to RPC mapping as below: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456?revision=2&sub.subfield=foo` | +// `GetMessage(message_id: "123456" revision: 2 sub: SubMessage(subfield: +// "foo"))` +// +// Note that fields which are mapped to URL query parameters must have a +// primitive type or a repeated primitive type or a non-repeated message type. +// In the case of a repeated type, the parameter can be repeated in the URL +// as `...?param=A¶m=B`. In the case of a message type, each field of the +// message is mapped to a separate parameter, such as +// `...?foo.a=A&foo.b=B&foo.c=C`. +// +// For HTTP methods that allow a request body, the `body` field +// specifies the mapping. Consider a REST update method on the +// message resource collection: +// +// service Messaging { +// rpc UpdateMessage(UpdateMessageRequest) returns (Message) { +// option (google.api.http) = { +// patch: "/v1/messages/{message_id}" +// body: "message" +// }; +// } +// } +// message UpdateMessageRequest { +// string message_id = 1; // mapped to the URL +// Message message = 2; // mapped to the body +// } +// +// The following HTTP JSON to RPC mapping is enabled, where the +// representation of the JSON in the request body is determined by +// protos JSON encoding: +// +// HTTP | gRPC +// -----|----- +// `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: +// "123456" message { text: "Hi!" })` +// +// The special name `*` can be used in the body mapping to define that +// every field not bound by the path template should be mapped to the +// request body. This enables the following alternative definition of +// the update method: +// +// service Messaging { +// rpc UpdateMessage(Message) returns (Message) { +// option (google.api.http) = { +// patch: "/v1/messages/{message_id}" +// body: "*" +// }; +// } +// } +// message Message { +// string message_id = 1; +// string text = 2; +// } +// +// +// The following HTTP JSON to RPC mapping is enabled: +// +// HTTP | gRPC +// -----|----- +// `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: +// "123456" text: "Hi!")` +// +// Note that when using `*` in the body mapping, it is not possible to +// have HTTP parameters, as all fields not bound by the path end in +// the body. This makes this option more rarely used in practice when +// defining REST APIs. The common usage of `*` is in custom methods +// which don't use the URL at all for transferring data. +// +// It is possible to define multiple HTTP methods for one RPC by using +// the `additional_bindings` option. Example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get: "/v1/messages/{message_id}" +// additional_bindings { +// get: "/v1/users/{user_id}/messages/{message_id}" +// } +// }; +// } +// } +// message GetMessageRequest { +// string message_id = 1; +// string user_id = 2; +// } +// +// This enables the following two alternative HTTP JSON to RPC mappings: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456` | `GetMessage(message_id: "123456")` +// `GET /v1/users/me/messages/123456` | `GetMessage(user_id: "me" message_id: +// "123456")` +// +// ## Rules for HTTP mapping +// +// 1. Leaf request fields (recursive expansion nested messages in the request +// message) are classified into three categories: +// - Fields referred by the path template. They are passed via the URL path. +// - Fields referred by the [HttpRule.body][google.api.HttpRule.body]. They are passed via the HTTP +// request body. +// - All other fields are passed via the URL query parameters, and the +// parameter name is the field path in the request message. A repeated +// field can be represented as multiple query parameters under the same +// name. +// 2. If [HttpRule.body][google.api.HttpRule.body] is "*", there is no URL query parameter, all fields +// are passed via URL path and HTTP request body. +// 3. If [HttpRule.body][google.api.HttpRule.body] is omitted, there is no HTTP request body, all +// fields are passed via URL path and URL query parameters. +// +// ### Path template syntax +// +// Template = "/" Segments [ Verb ] ; +// Segments = Segment { "/" Segment } ; +// Segment = "*" | "**" | LITERAL | Variable ; +// Variable = "{" FieldPath [ "=" Segments ] "}" ; +// FieldPath = IDENT { "." IDENT } ; +// Verb = ":" LITERAL ; +// +// The syntax `*` matches a single URL path segment. The syntax `**` matches +// zero or more URL path segments, which must be the last part of the URL path +// except the `Verb`. +// +// The syntax `Variable` matches part of the URL path as specified by its +// template. A variable template must not contain other variables. If a variable +// matches a single path segment, its template may be omitted, e.g. `{var}` +// is equivalent to `{var=*}`. +// +// The syntax `LITERAL` matches literal text in the URL path. If the `LITERAL` +// contains any reserved character, such characters should be percent-encoded +// before the matching. +// +// If a variable contains exactly one path segment, such as `"{var}"` or +// `"{var=*}"`, when such a variable is expanded into a URL path on the client +// side, all characters except `[-_.~0-9a-zA-Z]` are percent-encoded. The +// server side does the reverse decoding. Such variables show up in the +// [Discovery +// Document](https://developers.google.com/discovery/v1/reference/apis) as +// `{var}`. +// +// If a variable contains multiple path segments, such as `"{var=foo/*}"` +// or `"{var=**}"`, when such a variable is expanded into a URL path on the +// client side, all characters except `[-_.~/0-9a-zA-Z]` are percent-encoded. +// The server side does the reverse decoding, except "%2F" and "%2f" are left +// unchanged. Such variables show up in the +// [Discovery +// Document](https://developers.google.com/discovery/v1/reference/apis) as +// `{+var}`. +// +// ## Using gRPC API Service Configuration +// +// gRPC API Service Configuration (service config) is a configuration language +// for configuring a gRPC service to become a user-facing product. The +// service config is simply the YAML representation of the `google.api.Service` +// proto message. +// +// As an alternative to annotating your proto file, you can configure gRPC +// transcoding in your service config YAML files. You do this by specifying a +// `HttpRule` that maps the gRPC method to a REST endpoint, achieving the same +// effect as the proto annotation. This can be particularly useful if you +// have a proto that is reused in multiple services. Note that any transcoding +// specified in the service config will override any matching transcoding +// configuration in the proto. +// +// Example: +// +// http: +// rules: +// # Selects a gRPC method and applies HttpRule to it. +// - selector: example.v1.Messaging.GetMessage +// get: /v1/messages/{message_id}/{sub.subfield} +// +// ## Special notes +// +// When gRPC Transcoding is used to map a gRPC to JSON REST endpoints, the +// proto to JSON conversion must follow the [proto3 +// specification](https://developers.google.com/protocol-buffers/docs/proto3#json). +// +// While the single segment variable follows the semantics of +// [RFC 6570](https://tools.ietf.org/html/rfc6570) Section 3.2.2 Simple String +// Expansion, the multi segment variable **does not** follow RFC 6570 Section +// 3.2.3 Reserved Expansion. The reason is that the Reserved Expansion +// does not expand special characters like `?` and `#`, which would lead +// to invalid URLs. As the result, gRPC Transcoding uses a custom encoding +// for multi segment variables. +// +// The path variables **must not** refer to any repeated or mapped field, +// because client libraries are not capable of handling such variable expansion. +// +// The path variables **must not** capture the leading "/" character. The reason +// is that the most common use case "{var}" does not capture the leading "/" +// character. For consistency, all path variables must share the same behavior. +// +// Repeated message fields must not be mapped to URL query parameters, because +// no client library can support such complicated mapping. +// +// If an API needs to use a JSON array for request or response body, it can map +// the request or response body to a repeated field. However, some gRPC +// Transcoding implementations may not support this feature. +message HttpRule { + // Selects a method to which this rule applies. + // + // Refer to [selector][google.api.DocumentationRule.selector] for syntax details. + string selector = 1; + + // Determines the URL pattern is matched by this rules. This pattern can be + // used with any of the {get|put|post|delete|patch} methods. A custom method + // can be defined using the 'custom' field. + oneof pattern { + // Maps to HTTP GET. Used for listing and getting information about + // resources. + string get = 2; + + // Maps to HTTP PUT. Used for replacing a resource. + string put = 3; + + // Maps to HTTP POST. Used for creating a resource or performing an action. + string post = 4; + + // Maps to HTTP DELETE. Used for deleting a resource. + string delete = 5; + + // Maps to HTTP PATCH. Used for updating a resource. + string patch = 6; + + // The custom pattern is used for specifying an HTTP method that is not + // included in the `pattern` field, such as HEAD, or "*" to leave the + // HTTP method unspecified for this rule. The wild-card rule is useful + // for services that provide content to Web (HTML) clients. + CustomHttpPattern custom = 8; + } + + // The name of the request field whose value is mapped to the HTTP request + // body, or `*` for mapping all request fields not captured by the path + // pattern to the HTTP body, or omitted for not having any HTTP request body. + // + // NOTE: the referred field must be present at the top-level of the request + // message type. + string body = 7; + + // Optional. The name of the response field whose value is mapped to the HTTP + // response body. When omitted, the entire response message will be used + // as the HTTP response body. + // + // NOTE: The referred field must be present at the top-level of the response + // message type. + string response_body = 12; + + // Additional HTTP bindings for the selector. Nested bindings must + // not contain an `additional_bindings` field themselves (that is, + // the nesting may only be one level deep). + repeated HttpRule additional_bindings = 11; +} + +// A custom pattern is used for defining custom HTTP verb. +message CustomHttpPattern { + // The name of this custom HTTP verb. + string kind = 1; + + // The path matched by this custom verb. + string path = 2; +} + diff --git a/src/Protobufs/BackOffice.BFF.UserOrder.Protobuf/Protos/userorder.proto b/src/Protobufs/BackOffice.BFF.UserOrder.Protobuf/Protos/userorder.proto new file mode 100644 index 0000000..4e35d56 --- /dev/null +++ b/src/Protobufs/BackOffice.BFF.UserOrder.Protobuf/Protos/userorder.proto @@ -0,0 +1,145 @@ +syntax = "proto3"; + +package userorder; + +import "google/protobuf/empty.proto"; +import "google/protobuf/wrappers.proto"; +import "google/protobuf/duration.proto"; +import "google/protobuf/timestamp.proto"; +import "google/api/annotations.proto"; + +option csharp_namespace = "BackOffice.BFF.UserOrder.Protobuf.Protos.UserOrder"; + +service UserOrderContract +{ + rpc CreateNewUserOrder(CreateNewUserOrderRequest) returns (CreateNewUserOrderResponse){ + option (google.api.http) = { + post: "/CreateNewUserOrder" + body: "*" + }; + }; + rpc UpdateUserOrder(UpdateUserOrderRequest) returns (google.protobuf.Empty){ + option (google.api.http) = { + put: "/UpdateUserOrder" + body: "*" + }; + }; + rpc DeleteUserOrder(DeleteUserOrderRequest) returns (google.protobuf.Empty){ + option (google.api.http) = { + delete: "/DeleteUserOrder" + body: "*" + }; + }; + rpc GetUserOrder(GetUserOrderRequest) returns (GetUserOrderResponse){ + option (google.api.http) = { + get: "/GetUserOrder" + + }; + }; + rpc GetAllUserOrderByFilter(GetAllUserOrderByFilterRequest) returns (GetAllUserOrderByFilterResponse){ + option (google.api.http) = { + get: "/GetAllUserOrderByFilter" + + }; + }; +} +message CreateNewUserOrderRequest +{ + int64 price = 1; + int64 package_id = 2; + google.protobuf.Int64Value transaction_id = 3; + bool payment_status = 4; + google.protobuf.Timestamp payment_date = 5; + int64 user_id = 6; +} +message CreateNewUserOrderResponse +{ + int64 id = 1; +} +message UpdateUserOrderRequest +{ + int64 id = 1; + int64 price = 2; + int64 package_id = 3; + google.protobuf.Int64Value transaction_id = 4; + bool payment_status = 5; + google.protobuf.Timestamp payment_date = 6; + int64 user_id = 7; +} +message DeleteUserOrderRequest +{ + int64 id = 1; +} +message GetUserOrderRequest +{ + int64 id = 1; +} +message GetUserOrderResponse +{ + int64 id = 1; + int64 price = 2; + int64 package_id = 3; + google.protobuf.Int64Value transaction_id = 4; + bool payment_status = 5; + google.protobuf.Timestamp payment_date = 6; + int64 user_id = 7; +} +message GetAllUserOrderByFilterRequest +{ + PaginationState pagination_state = 1; + google.protobuf.StringValue sort_by = 2; + GetAllUserOrderByFilterFilter filter = 3; +} +message GetAllUserOrderByFilterFilter +{ + google.protobuf.Int64Value id = 1; + google.protobuf.Int64Value price = 2; + google.protobuf.Int64Value package_id = 3; + google.protobuf.Int64Value transaction_id = 4; + google.protobuf.BoolValue payment_status = 5; + google.protobuf.Timestamp payment_date = 6; + google.protobuf.Int64Value user_id = 7; +} +message GetAllUserOrderByFilterResponse +{ + MetaData meta_data = 1; + repeated GetAllUserOrderByFilterResponseModel models = 2; +} +message GetAllUserOrderByFilterResponseModel +{ + int64 id = 1; + int64 price = 2; + int64 package_id = 3; + google.protobuf.Int64Value transaction_id = 4; + bool payment_status = 5; + google.protobuf.Timestamp payment_date = 6; + int64 user_id = 7; +} + +message PaginationState +{ + int32 page_number = 1; + + int32 page_size = 2; +} +message MetaData +{ + int64 current_page = 1; + + int64 total_page = 2; + + int64 page_size = 3; + + int64 total_count = 4; + + bool has_previous = 5; + + bool has_next = 6; +} +message DecimalValue +{ + + int64 units = 1; + + sfixed32 nanos = 2; +} diff --git a/src/Protobufs/BackOffice.BFF.UserOrder.Protobuf/Validator/CreateNewUserOrderRequestValidator.cs b/src/Protobufs/BackOffice.BFF.UserOrder.Protobuf/Validator/CreateNewUserOrderRequestValidator.cs new file mode 100644 index 0000000..b76b999 --- /dev/null +++ b/src/Protobufs/BackOffice.BFF.UserOrder.Protobuf/Validator/CreateNewUserOrderRequestValidator.cs @@ -0,0 +1,25 @@ +using FluentValidation; +using BackOffice.BFF.UserOrder.Protobuf.Protos.UserOrder; +namespace BackOffice.BFF.UserOrder.Protobuf.Validator; + +public class CreateNewUserOrderRequestValidator : AbstractValidator +{ + public CreateNewUserOrderRequestValidator() + { + RuleFor(model => model.Price) + .NotNull(); + RuleFor(model => model.PackageId) + .NotNull(); + RuleFor(model => model.PaymentStatus) + .NotNull(); + RuleFor(model => model.UserId) + .NotNull(); + } + public Func>> ValidateValue => async (model, propertyName) => + { + var result = await ValidateAsync(ValidationContext.CreateWithOptions((CreateNewUserOrderRequest)model, x => x.IncludeProperties(propertyName))); + if (result.IsValid) + return Array.Empty(); + return result.Errors.Select(e => e.ErrorMessage); + }; +} diff --git a/src/Protobufs/BackOffice.BFF.UserOrder.Protobuf/Validator/DeleteUserOrderRequestValidator.cs b/src/Protobufs/BackOffice.BFF.UserOrder.Protobuf/Validator/DeleteUserOrderRequestValidator.cs new file mode 100644 index 0000000..6920b06 --- /dev/null +++ b/src/Protobufs/BackOffice.BFF.UserOrder.Protobuf/Validator/DeleteUserOrderRequestValidator.cs @@ -0,0 +1,19 @@ +using FluentValidation; +using BackOffice.BFF.UserOrder.Protobuf.Protos.UserOrder; +namespace BackOffice.BFF.UserOrder.Protobuf.Validator; + +public class DeleteUserOrderRequestValidator : AbstractValidator +{ + public DeleteUserOrderRequestValidator() + { + RuleFor(model => model.Id) + .NotNull(); + } + public Func>> ValidateValue => async (model, propertyName) => + { + var result = await ValidateAsync(ValidationContext.CreateWithOptions((DeleteUserOrderRequest)model, x => x.IncludeProperties(propertyName))); + if (result.IsValid) + return Array.Empty(); + return result.Errors.Select(e => e.ErrorMessage); + }; +} diff --git a/src/Protobufs/BackOffice.BFF.UserOrder.Protobuf/Validator/GetAllUserOrderByFilterRequestValidator.cs b/src/Protobufs/BackOffice.BFF.UserOrder.Protobuf/Validator/GetAllUserOrderByFilterRequestValidator.cs new file mode 100644 index 0000000..4dc5c0e --- /dev/null +++ b/src/Protobufs/BackOffice.BFF.UserOrder.Protobuf/Validator/GetAllUserOrderByFilterRequestValidator.cs @@ -0,0 +1,17 @@ +using FluentValidation; +using BackOffice.BFF.UserOrder.Protobuf.Protos.UserOrder; +namespace BackOffice.BFF.UserOrder.Protobuf.Validator; + +public class GetAllUserOrderByFilterRequestValidator : AbstractValidator +{ + public GetAllUserOrderByFilterRequestValidator() + { + } + public Func>> ValidateValue => async (model, propertyName) => + { + var result = await ValidateAsync(ValidationContext.CreateWithOptions((GetAllUserOrderByFilterRequest)model, x => x.IncludeProperties(propertyName))); + if (result.IsValid) + return Array.Empty(); + return result.Errors.Select(e => e.ErrorMessage); + }; +} diff --git a/src/Protobufs/BackOffice.BFF.UserOrder.Protobuf/Validator/GetUserOrderRequestValidator.cs b/src/Protobufs/BackOffice.BFF.UserOrder.Protobuf/Validator/GetUserOrderRequestValidator.cs new file mode 100644 index 0000000..55c93ff --- /dev/null +++ b/src/Protobufs/BackOffice.BFF.UserOrder.Protobuf/Validator/GetUserOrderRequestValidator.cs @@ -0,0 +1,19 @@ +using FluentValidation; +using BackOffice.BFF.UserOrder.Protobuf.Protos.UserOrder; +namespace BackOffice.BFF.UserOrder.Protobuf.Validator; + +public class GetUserOrderRequestValidator : AbstractValidator +{ + public GetUserOrderRequestValidator() + { + RuleFor(model => model.Id) + .NotNull(); + } + public Func>> ValidateValue => async (model, propertyName) => + { + var result = await ValidateAsync(ValidationContext.CreateWithOptions((GetUserOrderRequest)model, x => x.IncludeProperties(propertyName))); + if (result.IsValid) + return Array.Empty(); + return result.Errors.Select(e => e.ErrorMessage); + }; +} diff --git a/src/Protobufs/BackOffice.BFF.UserOrder.Protobuf/Validator/UpdateUserOrderRequestValidator.cs b/src/Protobufs/BackOffice.BFF.UserOrder.Protobuf/Validator/UpdateUserOrderRequestValidator.cs new file mode 100644 index 0000000..f137ac2 --- /dev/null +++ b/src/Protobufs/BackOffice.BFF.UserOrder.Protobuf/Validator/UpdateUserOrderRequestValidator.cs @@ -0,0 +1,27 @@ +using FluentValidation; +using BackOffice.BFF.UserOrder.Protobuf.Protos.UserOrder; +namespace BackOffice.BFF.UserOrder.Protobuf.Validator; + +public class UpdateUserOrderRequestValidator : AbstractValidator +{ + public UpdateUserOrderRequestValidator() + { + RuleFor(model => model.Id) + .NotNull(); + RuleFor(model => model.Price) + .NotNull(); + RuleFor(model => model.PackageId) + .NotNull(); + RuleFor(model => model.PaymentStatus) + .NotNull(); + RuleFor(model => model.UserId) + .NotNull(); + } + public Func>> ValidateValue => async (model, propertyName) => + { + var result = await ValidateAsync(ValidationContext.CreateWithOptions((UpdateUserOrderRequest)model, x => x.IncludeProperties(propertyName))); + if (result.IsValid) + return Array.Empty(); + return result.Errors.Select(e => e.ErrorMessage); + }; +} diff --git a/src/Protobufs/BackOffice.BFF.UserRole.Protobuf/BackOffice.BFF.UserRole.Protobuf.csproj b/src/Protobufs/BackOffice.BFF.UserRole.Protobuf/BackOffice.BFF.UserRole.Protobuf.csproj new file mode 100644 index 0000000..4504d13 --- /dev/null +++ b/src/Protobufs/BackOffice.BFF.UserRole.Protobuf/BackOffice.BFF.UserRole.Protobuf.csproj @@ -0,0 +1,27 @@ + + + + net7.0 + enable + enable + 1.0.0 + None + False + False + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + diff --git a/src/Protobufs/BackOffice.BFF.UserRole.Protobuf/ConfigureServices.cs b/src/Protobufs/BackOffice.BFF.UserRole.Protobuf/ConfigureServices.cs new file mode 100644 index 0000000..5331f03 --- /dev/null +++ b/src/Protobufs/BackOffice.BFF.UserRole.Protobuf/ConfigureServices.cs @@ -0,0 +1,14 @@ +using FluentValidation; +using System.Reflection; + +namespace Microsoft.Extensions.DependencyInjection; + +public static class ConfigureServices +{ + public static IServiceCollection AddUserRoleProtobufServices(this IServiceCollection services) + { + services.AddValidatorsFromAssembly(Assembly.GetExecutingAssembly()); + return services; + } +} + diff --git a/src/Protobufs/BackOffice.BFF.UserRole.Protobuf/Protos/google/api/annotations.proto b/src/Protobufs/BackOffice.BFF.UserRole.Protobuf/Protos/google/api/annotations.proto new file mode 100644 index 0000000..85c361b --- /dev/null +++ b/src/Protobufs/BackOffice.BFF.UserRole.Protobuf/Protos/google/api/annotations.proto @@ -0,0 +1,31 @@ +// Copyright (c) 2015, Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +import "google/api/http.proto"; +import "google/protobuf/descriptor.proto"; + +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "AnnotationsProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +extend google.protobuf.MethodOptions { + // See `HttpRule`. + HttpRule http = 72295728; +} diff --git a/src/Protobufs/BackOffice.BFF.UserRole.Protobuf/Protos/google/api/http.proto b/src/Protobufs/BackOffice.BFF.UserRole.Protobuf/Protos/google/api/http.proto new file mode 100644 index 0000000..b8426ba --- /dev/null +++ b/src/Protobufs/BackOffice.BFF.UserRole.Protobuf/Protos/google/api/http.proto @@ -0,0 +1,377 @@ +// Copyright 2019 Google LLC. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +syntax = "proto3"; + +package google.api; + +option cc_enable_arenas = true; +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "HttpProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +// Defines the HTTP configuration for an API service. It contains a list of +// [HttpRule][google.api.HttpRule], each specifying the mapping of an RPC method +// to one or more HTTP REST API methods. +message Http { + // A list of HTTP configuration rules that apply to individual API methods. + // + // **NOTE:** All service configuration rules follow "last one wins" order. + repeated HttpRule rules = 1; + + // When set to true, URL path parameters will be fully URI-decoded except in + // cases of single segment matches in reserved expansion, where "%2F" will be + // left encoded. + // + // The default behavior is to not decode RFC 6570 reserved characters in multi + // segment matches. + bool fully_decode_reserved_expansion = 2; +} + +// # gRPC Transcoding +// +// gRPC Transcoding is a feature for mapping between a gRPC method and one or +// more HTTP REST endpoints. It allows developers to build a single API service +// that supports both gRPC APIs and REST APIs. Many systems, including [Google +// APIs](https://github.com/googleapis/googleapis), +// [Cloud Endpoints](https://cloud.google.com/endpoints), [gRPC +// Gateway](https://github.com/grpc-ecosystem/grpc-gateway), +// and [Envoy](https://github.com/envoyproxy/envoy) proxy support this feature +// and use it for large scale production services. +// +// `HttpRule` defines the schema of the gRPC/REST mapping. The mapping specifies +// how different portions of the gRPC request message are mapped to the URL +// path, URL query parameters, and HTTP request body. It also controls how the +// gRPC response message is mapped to the HTTP response body. `HttpRule` is +// typically specified as an `google.api.http` annotation on the gRPC method. +// +// Each mapping specifies a URL path template and an HTTP method. The path +// template may refer to one or more fields in the gRPC request message, as long +// as each field is a non-repeated field with a primitive (non-message) type. +// The path template controls how fields of the request message are mapped to +// the URL path. +// +// Example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get: "/v1/{name=messages/*}" +// }; +// } +// } +// message GetMessageRequest { +// string name = 1; // Mapped to URL path. +// } +// message Message { +// string text = 1; // The resource content. +// } +// +// This enables an HTTP REST to gRPC mapping as below: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456` | `GetMessage(name: "messages/123456")` +// +// Any fields in the request message which are not bound by the path template +// automatically become HTTP query parameters if there is no HTTP request body. +// For example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get:"/v1/messages/{message_id}" +// }; +// } +// } +// message GetMessageRequest { +// message SubMessage { +// string subfield = 1; +// } +// string message_id = 1; // Mapped to URL path. +// int64 revision = 2; // Mapped to URL query parameter `revision`. +// SubMessage sub = 3; // Mapped to URL query parameter `sub.subfield`. +// } +// +// This enables a HTTP JSON to RPC mapping as below: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456?revision=2&sub.subfield=foo` | +// `GetMessage(message_id: "123456" revision: 2 sub: SubMessage(subfield: +// "foo"))` +// +// Note that fields which are mapped to URL query parameters must have a +// primitive type or a repeated primitive type or a non-repeated message type. +// In the case of a repeated type, the parameter can be repeated in the URL +// as `...?param=A¶m=B`. In the case of a message type, each field of the +// message is mapped to a separate parameter, such as +// `...?foo.a=A&foo.b=B&foo.c=C`. +// +// For HTTP methods that allow a request body, the `body` field +// specifies the mapping. Consider a REST update method on the +// message resource collection: +// +// service Messaging { +// rpc UpdateMessage(UpdateMessageRequest) returns (Message) { +// option (google.api.http) = { +// patch: "/v1/messages/{message_id}" +// body: "message" +// }; +// } +// } +// message UpdateMessageRequest { +// string message_id = 1; // mapped to the URL +// Message message = 2; // mapped to the body +// } +// +// The following HTTP JSON to RPC mapping is enabled, where the +// representation of the JSON in the request body is determined by +// protos JSON encoding: +// +// HTTP | gRPC +// -----|----- +// `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: +// "123456" message { text: "Hi!" })` +// +// The special name `*` can be used in the body mapping to define that +// every field not bound by the path template should be mapped to the +// request body. This enables the following alternative definition of +// the update method: +// +// service Messaging { +// rpc UpdateMessage(Message) returns (Message) { +// option (google.api.http) = { +// patch: "/v1/messages/{message_id}" +// body: "*" +// }; +// } +// } +// message Message { +// string message_id = 1; +// string text = 2; +// } +// +// +// The following HTTP JSON to RPC mapping is enabled: +// +// HTTP | gRPC +// -----|----- +// `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: +// "123456" text: "Hi!")` +// +// Note that when using `*` in the body mapping, it is not possible to +// have HTTP parameters, as all fields not bound by the path end in +// the body. This makes this option more rarely used in practice when +// defining REST APIs. The common usage of `*` is in custom methods +// which don't use the URL at all for transferring data. +// +// It is possible to define multiple HTTP methods for one RPC by using +// the `additional_bindings` option. Example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get: "/v1/messages/{message_id}" +// additional_bindings { +// get: "/v1/users/{user_id}/messages/{message_id}" +// } +// }; +// } +// } +// message GetMessageRequest { +// string message_id = 1; +// string user_id = 2; +// } +// +// This enables the following two alternative HTTP JSON to RPC mappings: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456` | `GetMessage(message_id: "123456")` +// `GET /v1/users/me/messages/123456` | `GetMessage(user_id: "me" message_id: +// "123456")` +// +// ## Rules for HTTP mapping +// +// 1. Leaf request fields (recursive expansion nested messages in the request +// message) are classified into three categories: +// - Fields referred by the path template. They are passed via the URL path. +// - Fields referred by the [HttpRule.body][google.api.HttpRule.body]. They are passed via the HTTP +// request body. +// - All other fields are passed via the URL query parameters, and the +// parameter name is the field path in the request message. A repeated +// field can be represented as multiple query parameters under the same +// name. +// 2. If [HttpRule.body][google.api.HttpRule.body] is "*", there is no URL query parameter, all fields +// are passed via URL path and HTTP request body. +// 3. If [HttpRule.body][google.api.HttpRule.body] is omitted, there is no HTTP request body, all +// fields are passed via URL path and URL query parameters. +// +// ### Path template syntax +// +// Template = "/" Segments [ Verb ] ; +// Segments = Segment { "/" Segment } ; +// Segment = "*" | "**" | LITERAL | Variable ; +// Variable = "{" FieldPath [ "=" Segments ] "}" ; +// FieldPath = IDENT { "." IDENT } ; +// Verb = ":" LITERAL ; +// +// The syntax `*` matches a single URL path segment. The syntax `**` matches +// zero or more URL path segments, which must be the last part of the URL path +// except the `Verb`. +// +// The syntax `Variable` matches part of the URL path as specified by its +// template. A variable template must not contain other variables. If a variable +// matches a single path segment, its template may be omitted, e.g. `{var}` +// is equivalent to `{var=*}`. +// +// The syntax `LITERAL` matches literal text in the URL path. If the `LITERAL` +// contains any reserved character, such characters should be percent-encoded +// before the matching. +// +// If a variable contains exactly one path segment, such as `"{var}"` or +// `"{var=*}"`, when such a variable is expanded into a URL path on the client +// side, all characters except `[-_.~0-9a-zA-Z]` are percent-encoded. The +// server side does the reverse decoding. Such variables show up in the +// [Discovery +// Document](https://developers.google.com/discovery/v1/reference/apis) as +// `{var}`. +// +// If a variable contains multiple path segments, such as `"{var=foo/*}"` +// or `"{var=**}"`, when such a variable is expanded into a URL path on the +// client side, all characters except `[-_.~/0-9a-zA-Z]` are percent-encoded. +// The server side does the reverse decoding, except "%2F" and "%2f" are left +// unchanged. Such variables show up in the +// [Discovery +// Document](https://developers.google.com/discovery/v1/reference/apis) as +// `{+var}`. +// +// ## Using gRPC API Service Configuration +// +// gRPC API Service Configuration (service config) is a configuration language +// for configuring a gRPC service to become a user-facing product. The +// service config is simply the YAML representation of the `google.api.Service` +// proto message. +// +// As an alternative to annotating your proto file, you can configure gRPC +// transcoding in your service config YAML files. You do this by specifying a +// `HttpRule` that maps the gRPC method to a REST endpoint, achieving the same +// effect as the proto annotation. This can be particularly useful if you +// have a proto that is reused in multiple services. Note that any transcoding +// specified in the service config will override any matching transcoding +// configuration in the proto. +// +// Example: +// +// http: +// rules: +// # Selects a gRPC method and applies HttpRule to it. +// - selector: example.v1.Messaging.GetMessage +// get: /v1/messages/{message_id}/{sub.subfield} +// +// ## Special notes +// +// When gRPC Transcoding is used to map a gRPC to JSON REST endpoints, the +// proto to JSON conversion must follow the [proto3 +// specification](https://developers.google.com/protocol-buffers/docs/proto3#json). +// +// While the single segment variable follows the semantics of +// [RFC 6570](https://tools.ietf.org/html/rfc6570) Section 3.2.2 Simple String +// Expansion, the multi segment variable **does not** follow RFC 6570 Section +// 3.2.3 Reserved Expansion. The reason is that the Reserved Expansion +// does not expand special characters like `?` and `#`, which would lead +// to invalid URLs. As the result, gRPC Transcoding uses a custom encoding +// for multi segment variables. +// +// The path variables **must not** refer to any repeated or mapped field, +// because client libraries are not capable of handling such variable expansion. +// +// The path variables **must not** capture the leading "/" character. The reason +// is that the most common use case "{var}" does not capture the leading "/" +// character. For consistency, all path variables must share the same behavior. +// +// Repeated message fields must not be mapped to URL query parameters, because +// no client library can support such complicated mapping. +// +// If an API needs to use a JSON array for request or response body, it can map +// the request or response body to a repeated field. However, some gRPC +// Transcoding implementations may not support this feature. +message HttpRule { + // Selects a method to which this rule applies. + // + // Refer to [selector][google.api.DocumentationRule.selector] for syntax details. + string selector = 1; + + // Determines the URL pattern is matched by this rules. This pattern can be + // used with any of the {get|put|post|delete|patch} methods. A custom method + // can be defined using the 'custom' field. + oneof pattern { + // Maps to HTTP GET. Used for listing and getting information about + // resources. + string get = 2; + + // Maps to HTTP PUT. Used for replacing a resource. + string put = 3; + + // Maps to HTTP POST. Used for creating a resource or performing an action. + string post = 4; + + // Maps to HTTP DELETE. Used for deleting a resource. + string delete = 5; + + // Maps to HTTP PATCH. Used for updating a resource. + string patch = 6; + + // The custom pattern is used for specifying an HTTP method that is not + // included in the `pattern` field, such as HEAD, or "*" to leave the + // HTTP method unspecified for this rule. The wild-card rule is useful + // for services that provide content to Web (HTML) clients. + CustomHttpPattern custom = 8; + } + + // The name of the request field whose value is mapped to the HTTP request + // body, or `*` for mapping all request fields not captured by the path + // pattern to the HTTP body, or omitted for not having any HTTP request body. + // + // NOTE: the referred field must be present at the top-level of the request + // message type. + string body = 7; + + // Optional. The name of the response field whose value is mapped to the HTTP + // response body. When omitted, the entire response message will be used + // as the HTTP response body. + // + // NOTE: The referred field must be present at the top-level of the response + // message type. + string response_body = 12; + + // Additional HTTP bindings for the selector. Nested bindings must + // not contain an `additional_bindings` field themselves (that is, + // the nesting may only be one level deep). + repeated HttpRule additional_bindings = 11; +} + +// A custom pattern is used for defining custom HTTP verb. +message CustomHttpPattern { + // The name of this custom HTTP verb. + string kind = 1; + + // The path matched by this custom verb. + string path = 2; +} + diff --git a/src/Protobufs/BackOffice.BFF.UserRole.Protobuf/Protos/userrole.proto b/src/Protobufs/BackOffice.BFF.UserRole.Protobuf/Protos/userrole.proto new file mode 100644 index 0000000..f0d103d --- /dev/null +++ b/src/Protobufs/BackOffice.BFF.UserRole.Protobuf/Protos/userrole.proto @@ -0,0 +1,125 @@ +syntax = "proto3"; + +package userrole; + +import "google/protobuf/empty.proto"; +import "google/protobuf/wrappers.proto"; +import "google/protobuf/duration.proto"; +import "google/protobuf/timestamp.proto"; +import "google/api/annotations.proto"; + +option csharp_namespace = "BackOffice.BFF.UserRole.Protobuf.Protos.UserRole"; + +service UserRoleContract +{ + rpc CreateNewUserRole(CreateNewUserRoleRequest) returns (CreateNewUserRoleResponse){ + option (google.api.http) = { + post: "/CreateNewUserRole" + body: "*" + }; + }; + rpc UpdateUserRole(UpdateUserRoleRequest) returns (google.protobuf.Empty){ + option (google.api.http) = { + put: "/UpdateUserRole" + body: "*" + }; + }; + rpc DeleteUserRole(DeleteUserRoleRequest) returns (google.protobuf.Empty){ + option (google.api.http) = { + delete: "/DeleteUserRole" + body: "*" + }; + }; + rpc GetUserRole(GetUserRoleRequest) returns (GetUserRoleResponse){ + option (google.api.http) = { + get: "/GetUserRole" + + }; + }; + rpc GetAllUserRoleByFilter(GetAllUserRoleByFilterRequest) returns (GetAllUserRoleByFilterResponse){ + option (google.api.http) = { + get: "/GetAllUserRoleByFilter" + + }; + }; +} +message CreateNewUserRoleRequest +{ + int64 role_id = 1; + int64 user_id = 2; +} +message CreateNewUserRoleResponse +{ + int64 id = 1; +} +message UpdateUserRoleRequest +{ + int64 id = 1; + int64 role_id = 2; + int64 user_id = 3; +} +message DeleteUserRoleRequest +{ + int64 id = 1; +} +message GetUserRoleRequest +{ + int64 id = 1; +} +message GetUserRoleResponse +{ + int64 id = 1; + int64 role_id = 2; + int64 user_id = 3; +} +message GetAllUserRoleByFilterRequest +{ + PaginationState pagination_state = 1; + google.protobuf.StringValue sort_by = 2; + GetAllUserRoleByFilterFilter filter = 3; +} +message GetAllUserRoleByFilterFilter +{ + google.protobuf.Int64Value id = 1; + google.protobuf.Int64Value role_id = 2; + google.protobuf.Int64Value user_id = 3; +} +message GetAllUserRoleByFilterResponse +{ + MetaData meta_data = 1; + repeated GetAllUserRoleByFilterResponseModel models = 2; +} +message GetAllUserRoleByFilterResponseModel +{ + int64 id = 1; + int64 role_id = 2; + int64 user_id = 3; +} + +message PaginationState +{ + int32 page_number = 1; + + int32 page_size = 2; +} +message MetaData +{ + int64 current_page = 1; + + int64 total_page = 2; + + int64 page_size = 3; + + int64 total_count = 4; + + bool has_previous = 5; + + bool has_next = 6; +} +message DecimalValue +{ + + int64 units = 1; + + sfixed32 nanos = 2; +} diff --git a/src/Protobufs/BackOffice.BFF.UserRole.Protobuf/Validator/CreateNewUserRoleRequestValidator.cs b/src/Protobufs/BackOffice.BFF.UserRole.Protobuf/Validator/CreateNewUserRoleRequestValidator.cs new file mode 100644 index 0000000..ea5cc4a --- /dev/null +++ b/src/Protobufs/BackOffice.BFF.UserRole.Protobuf/Validator/CreateNewUserRoleRequestValidator.cs @@ -0,0 +1,21 @@ +using FluentValidation; +using BackOffice.BFF.UserRole.Protobuf.Protos.UserRole; +namespace BackOffice.BFF.UserRole.Protobuf.Validator; + +public class CreateNewUserRoleRequestValidator : AbstractValidator +{ + public CreateNewUserRoleRequestValidator() + { + RuleFor(model => model.RoleId) + .NotNull(); + RuleFor(model => model.UserId) + .NotNull(); + } + public Func>> ValidateValue => async (model, propertyName) => + { + var result = await ValidateAsync(ValidationContext.CreateWithOptions((CreateNewUserRoleRequest)model, x => x.IncludeProperties(propertyName))); + if (result.IsValid) + return Array.Empty(); + return result.Errors.Select(e => e.ErrorMessage); + }; +} diff --git a/src/Protobufs/BackOffice.BFF.UserRole.Protobuf/Validator/DeleteUserRoleRequestValidator.cs b/src/Protobufs/BackOffice.BFF.UserRole.Protobuf/Validator/DeleteUserRoleRequestValidator.cs new file mode 100644 index 0000000..1e7a351 --- /dev/null +++ b/src/Protobufs/BackOffice.BFF.UserRole.Protobuf/Validator/DeleteUserRoleRequestValidator.cs @@ -0,0 +1,19 @@ +using FluentValidation; +using BackOffice.BFF.UserRole.Protobuf.Protos.UserRole; +namespace BackOffice.BFF.UserRole.Protobuf.Validator; + +public class DeleteUserRoleRequestValidator : AbstractValidator +{ + public DeleteUserRoleRequestValidator() + { + RuleFor(model => model.Id) + .NotNull(); + } + public Func>> ValidateValue => async (model, propertyName) => + { + var result = await ValidateAsync(ValidationContext.CreateWithOptions((DeleteUserRoleRequest)model, x => x.IncludeProperties(propertyName))); + if (result.IsValid) + return Array.Empty(); + return result.Errors.Select(e => e.ErrorMessage); + }; +} diff --git a/src/Protobufs/BackOffice.BFF.UserRole.Protobuf/Validator/GetAllUserRoleByFilterRequestValidator.cs b/src/Protobufs/BackOffice.BFF.UserRole.Protobuf/Validator/GetAllUserRoleByFilterRequestValidator.cs new file mode 100644 index 0000000..34fead4 --- /dev/null +++ b/src/Protobufs/BackOffice.BFF.UserRole.Protobuf/Validator/GetAllUserRoleByFilterRequestValidator.cs @@ -0,0 +1,17 @@ +using FluentValidation; +using BackOffice.BFF.UserRole.Protobuf.Protos.UserRole; +namespace BackOffice.BFF.UserRole.Protobuf.Validator; + +public class GetAllUserRoleByFilterRequestValidator : AbstractValidator +{ + public GetAllUserRoleByFilterRequestValidator() + { + } + public Func>> ValidateValue => async (model, propertyName) => + { + var result = await ValidateAsync(ValidationContext.CreateWithOptions((GetAllUserRoleByFilterRequest)model, x => x.IncludeProperties(propertyName))); + if (result.IsValid) + return Array.Empty(); + return result.Errors.Select(e => e.ErrorMessage); + }; +} diff --git a/src/Protobufs/BackOffice.BFF.UserRole.Protobuf/Validator/GetUserRoleRequestValidator.cs b/src/Protobufs/BackOffice.BFF.UserRole.Protobuf/Validator/GetUserRoleRequestValidator.cs new file mode 100644 index 0000000..5c38a0f --- /dev/null +++ b/src/Protobufs/BackOffice.BFF.UserRole.Protobuf/Validator/GetUserRoleRequestValidator.cs @@ -0,0 +1,19 @@ +using FluentValidation; +using BackOffice.BFF.UserRole.Protobuf.Protos.UserRole; +namespace BackOffice.BFF.UserRole.Protobuf.Validator; + +public class GetUserRoleRequestValidator : AbstractValidator +{ + public GetUserRoleRequestValidator() + { + RuleFor(model => model.Id) + .NotNull(); + } + public Func>> ValidateValue => async (model, propertyName) => + { + var result = await ValidateAsync(ValidationContext.CreateWithOptions((GetUserRoleRequest)model, x => x.IncludeProperties(propertyName))); + if (result.IsValid) + return Array.Empty(); + return result.Errors.Select(e => e.ErrorMessage); + }; +} diff --git a/src/Protobufs/BackOffice.BFF.UserRole.Protobuf/Validator/UpdateUserRoleRequestValidator.cs b/src/Protobufs/BackOffice.BFF.UserRole.Protobuf/Validator/UpdateUserRoleRequestValidator.cs new file mode 100644 index 0000000..3960ef3 --- /dev/null +++ b/src/Protobufs/BackOffice.BFF.UserRole.Protobuf/Validator/UpdateUserRoleRequestValidator.cs @@ -0,0 +1,23 @@ +using FluentValidation; +using BackOffice.BFF.UserRole.Protobuf.Protos.UserRole; +namespace BackOffice.BFF.UserRole.Protobuf.Validator; + +public class UpdateUserRoleRequestValidator : AbstractValidator +{ + public UpdateUserRoleRequestValidator() + { + RuleFor(model => model.Id) + .NotNull(); + RuleFor(model => model.RoleId) + .NotNull(); + RuleFor(model => model.UserId) + .NotNull(); + } + public Func>> ValidateValue => async (model, propertyName) => + { + var result = await ValidateAsync(ValidationContext.CreateWithOptions((UpdateUserRoleRequest)model, x => x.IncludeProperties(propertyName))); + if (result.IsValid) + return Array.Empty(); + return result.Errors.Select(e => e.ErrorMessage); + }; +}