From 2f3fa422cf3793b199d8f08dee760fd466a75a9d Mon Sep 17 00:00:00 2001 From: HosseinJavadi Date: Fri, 5 Sep 2025 12:44:40 +0430 Subject: [PATCH] cdn --- .../EncryptionIdHelper.cs | 44 ++++++++ .../BusinessRuleValidationException.cs | 2 + .../TypeIdValueBase.cs | 10 +- VizhTalk.CDN.Domain/Entities/Media.cs | 5 + VizhTalk.CDN.Domain/Entities/User.cs | 7 ++ .../VizhTalk.CDN.Domain.csproj | 13 +++ .../VizhTalk.CDN.Infrastructure.csproj | 14 +++ VizhTalk.Gateway.BFF/Program.cs | 1 + .../Mappers/MapperConfig.cs | 9 +- .../QueryHandlers/LanguageQueryHandler.cs | 16 ++- .../ValueObjects/LanguageTitle.cs | 1 - .../Services/LanguageService.cs | 5 + .../Helpers/CompressionHelper.cs | 31 ++++++ .../Helpers/EncryptionHelper.cs | 45 ++++++++ VizhTalk.Services.Grpc.Learning/Id.cs | 34 ++++++ .../Proto/Languages/Commands.proto | 3 +- .../Proto/Languages/DTOs.proto | 5 +- .../Proto/Languages/Queries.proto | 5 + .../Proto/Languages/Services.proto | 1 + .../Proto/Shared.proto | 3 + .../Queries/LanguageQueries.cs | 1 + VizhTalk.sln | 7 ++ .../Vizhtalk.CDN.Application.csproj | 13 +++ Vizhtalk.CDN.Presentation/Entities/Media.cs | 14 +++ Vizhtalk.CDN.Presentation/Program.cs | 6 ++ .../Properties/launchSettings.json | 38 +++++++ Vizhtalk.CDN.Presentation/TypeIds/MediaId.cs | 11 ++ .../ValueObjects/ContentType.cs | 16 +++ .../ValueObjects/MediaName.cs | 17 +++ .../Rules/ValidContentTypeRule.cs | 101 ++++++++++++++++++ .../Vizhtalk.CDN.Presentation.csproj | 18 ++++ .../appsettings.Development.json | 8 ++ Vizhtalk.CDN.Presentation/appsettings.json | 12 +++ 33 files changed, 503 insertions(+), 13 deletions(-) create mode 100644 VizhTalk.BuildingBlocks.Domain/EncryptionIdHelper.cs create mode 100644 VizhTalk.CDN.Domain/Entities/Media.cs create mode 100644 VizhTalk.CDN.Domain/Entities/User.cs create mode 100644 VizhTalk.CDN.Domain/VizhTalk.CDN.Domain.csproj create mode 100644 VizhTalk.CDN.Infrastructure/VizhTalk.CDN.Infrastructure.csproj create mode 100644 VizhTalk.Services.Grpc.Learning/Helpers/CompressionHelper.cs create mode 100644 VizhTalk.Services.Grpc.Learning/Helpers/EncryptionHelper.cs create mode 100644 VizhTalk.Services.Grpc.Learning/Id.cs create mode 100644 Vizhtalk.CDN.Application/Vizhtalk.CDN.Application.csproj create mode 100644 Vizhtalk.CDN.Presentation/Entities/Media.cs create mode 100644 Vizhtalk.CDN.Presentation/Program.cs create mode 100644 Vizhtalk.CDN.Presentation/Properties/launchSettings.json create mode 100644 Vizhtalk.CDN.Presentation/TypeIds/MediaId.cs create mode 100644 Vizhtalk.CDN.Presentation/ValueObjects/ContentType.cs create mode 100644 Vizhtalk.CDN.Presentation/ValueObjects/MediaName.cs create mode 100644 Vizhtalk.CDN.Presentation/ValueObjects/Rules/ValidContentTypeRule.cs create mode 100644 Vizhtalk.CDN.Presentation/Vizhtalk.CDN.Presentation.csproj create mode 100644 Vizhtalk.CDN.Presentation/appsettings.Development.json create mode 100644 Vizhtalk.CDN.Presentation/appsettings.json diff --git a/VizhTalk.BuildingBlocks.Domain/EncryptionIdHelper.cs b/VizhTalk.BuildingBlocks.Domain/EncryptionIdHelper.cs new file mode 100644 index 0000000..75e0649 --- /dev/null +++ b/VizhTalk.BuildingBlocks.Domain/EncryptionIdHelper.cs @@ -0,0 +1,44 @@ +using System.Security.Cryptography; +using System.Text; + +namespace VizhTalk.BuildingBlocks.Domain; + +public class EncryptionIdHelper +{ + private const string Alphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + public static string Encrypt(string input) + { + byte[] bytes = Encoding.UTF8.GetBytes(input); + + // تبدیل بایت‌ها به عدد بزرگ + var number = new System.Numerics.BigInteger(bytes); + + // اگر منفی شد، مثبتش کن + if (number < 0) number = -number; + + // تبدیل به Base62 + StringBuilder result = new StringBuilder(); + while (number > 0) + { + result.Insert(0, Alphabet[(int)(number % 62)]); + number /= 62; + } + + return result.ToString(); + } + + public static string Decrypt(string encoded) + { + System.Numerics.BigInteger number = 0; + foreach (char c in encoded) + { + number = number * 62 + Alphabet.IndexOf(c); + } + + // تبدیل دوباره به بایت + byte[] bytes = number.ToByteArray(); + + // رشته اصلی + return Encoding.UTF8.GetString(bytes); + } +} diff --git a/VizhTalk.BuildingBlocks.Domain/Exceptions/BusinessRuleValidationException.cs b/VizhTalk.BuildingBlocks.Domain/Exceptions/BusinessRuleValidationException.cs index 589763d..c743c80 100644 --- a/VizhTalk.BuildingBlocks.Domain/Exceptions/BusinessRuleValidationException.cs +++ b/VizhTalk.BuildingBlocks.Domain/Exceptions/BusinessRuleValidationException.cs @@ -17,4 +17,6 @@ public class BusinessRuleValidationException : Exception { return $"{BrokenRule.GetType().FullName}: {BrokenRule.Message}"; } + public string GetFullName() + => BrokenRule.GetType()?.FullName!; } diff --git a/VizhTalk.BuildingBlocks.Domain/TypeIdValueBase.cs b/VizhTalk.BuildingBlocks.Domain/TypeIdValueBase.cs index ce33d09..e1cd925 100644 --- a/VizhTalk.BuildingBlocks.Domain/TypeIdValueBase.cs +++ b/VizhTalk.BuildingBlocks.Domain/TypeIdValueBase.cs @@ -2,13 +2,13 @@ namespace VizhTalk.BuildingBlocks.Domain; -public abstract class TypedIdValueBase : IEquatable> , ITypeIdValueBase +public abstract class TypedIdValueBase : IEquatable>, ITypeIdValueBase { public T Value { get; } protected TypedIdValueBase(T value) { - + if (EqualityComparer.Default.Equals(value, default(T))) { throw new InvalidOperationException($"Id value cannot be empty ! : id is {value}"); @@ -32,7 +32,7 @@ public abstract class TypedIdValueBase : IEquatable> , IT public bool Equals(TypedIdValueBase? other) { - if(other == null) + if (other == null) return false; return EqualityComparer.Default.Equals(this.Value, other.Value); @@ -59,5 +59,7 @@ public abstract class TypedIdValueBase : IEquatable> , IT return !(x == y); } } -public interface ITypeIdValueBase { } +public interface ITypeIdValueBase +{ +} diff --git a/VizhTalk.CDN.Domain/Entities/Media.cs b/VizhTalk.CDN.Domain/Entities/Media.cs new file mode 100644 index 0000000..b48fd5c --- /dev/null +++ b/VizhTalk.CDN.Domain/Entities/Media.cs @@ -0,0 +1,5 @@ +namespace VizhTalk.CDN.Domain.Entities; + +public class Media +{ +} diff --git a/VizhTalk.CDN.Domain/Entities/User.cs b/VizhTalk.CDN.Domain/Entities/User.cs new file mode 100644 index 0000000..6d210e8 --- /dev/null +++ b/VizhTalk.CDN.Domain/Entities/User.cs @@ -0,0 +1,7 @@ + +namespace VizhTalk.CDN.Domain.Entities +{ + internal class User + { + } +} diff --git a/VizhTalk.CDN.Domain/VizhTalk.CDN.Domain.csproj b/VizhTalk.CDN.Domain/VizhTalk.CDN.Domain.csproj new file mode 100644 index 0000000..4c51ca4 --- /dev/null +++ b/VizhTalk.CDN.Domain/VizhTalk.CDN.Domain.csproj @@ -0,0 +1,13 @@ + + + + net8.0 + enable + enable + + + + + + + diff --git a/VizhTalk.CDN.Infrastructure/VizhTalk.CDN.Infrastructure.csproj b/VizhTalk.CDN.Infrastructure/VizhTalk.CDN.Infrastructure.csproj new file mode 100644 index 0000000..7301f0a --- /dev/null +++ b/VizhTalk.CDN.Infrastructure/VizhTalk.CDN.Infrastructure.csproj @@ -0,0 +1,14 @@ + + + + net8.0 + enable + enable + + + + + + + + diff --git a/VizhTalk.Gateway.BFF/Program.cs b/VizhTalk.Gateway.BFF/Program.cs index b1c4cb6..4190ba2 100644 --- a/VizhTalk.Gateway.BFF/Program.cs +++ b/VizhTalk.Gateway.BFF/Program.cs @@ -14,6 +14,7 @@ builder.Services.AddScoped(); builder.AddGraphQL() .AddQueryType() .AddMutationType(); + var app = builder.Build(); app.MapGraphQL(); diff --git a/VizhTalk.Learning.Application/Mappers/MapperConfig.cs b/VizhTalk.Learning.Application/Mappers/MapperConfig.cs index 44defe3..a8a7d3d 100644 --- a/VizhTalk.Learning.Application/Mappers/MapperConfig.cs +++ b/VizhTalk.Learning.Application/Mappers/MapperConfig.cs @@ -1,4 +1,5 @@ using Mapster; +using VizhTalk.Grpc.Learning; using VizhTalk.Grpc.Learning.Language; using VizhTalk.Learning.Domain.Entities; @@ -10,8 +11,12 @@ namespace VizhTalk.Learning.Application.Mappers { var config = TypeAdapterConfig.NewConfig() - .Map(x => x.Title, des => des.Title.Value) - .Map(x=>x.Id, des=> des.Id == null ? 0 : des.Id.Value); + .MapWith(lan => new LanguageDTO() + { + Id = lan.Id.Value, + Title = lan.Title.Value, + }); + config.Compile(); } diff --git a/VizhTalk.Learning.Application/QueryHandlers/LanguageQueryHandler.cs b/VizhTalk.Learning.Application/QueryHandlers/LanguageQueryHandler.cs index 2ba0e4a..bae2988 100644 --- a/VizhTalk.Learning.Application/QueryHandlers/LanguageQueryHandler.cs +++ b/VizhTalk.Learning.Application/QueryHandlers/LanguageQueryHandler.cs @@ -12,7 +12,7 @@ public class GetAllLanguageQueryHandler(IGenericDapper languageDapp var result = new GetAllLanguageDTO(); var sql = $"select * from Learning.Languages"; var language = await languageDapper.GetAllAsync(sql); - result.Language.AddRange(language); + result.Languages.AddRange(language); return result; } } @@ -20,10 +20,20 @@ public class GetLanguageByTitleQueryHandler(IGenericDapper language { public async Task Handle(GetLanguageByTitleQuery request, CancellationToken cancellationToken) { - var sql = $"select * from Learning.Languages where Title = " + - $" @{nameof(Language.Title)}"; + var sql = $"select * from Learning.Languages where Title = @{nameof(Language.Title)}"; var language = await languageDapper.GetAsync(sql, new { request.Title }); return language; } } +public class GetLanguageByIdQueryHandler(IGenericDapper languageDapper) : IQueryHandler +{ + public async Task Handle(GetLanguageByIdQuery request, CancellationToken cancellationToken) + { + var sql = $"select * from Learning.Languages where Id = " + + $" @{nameof(Language.Id)}"; + + var language = await languageDapper.GetAsync(sql, new { request.Id }); + return language; + } +} diff --git a/VizhTalk.Learning.Domain/ValueObjects/LanguageTitle.cs b/VizhTalk.Learning.Domain/ValueObjects/LanguageTitle.cs index 9df5b89..cfddff4 100644 --- a/VizhTalk.Learning.Domain/ValueObjects/LanguageTitle.cs +++ b/VizhTalk.Learning.Domain/ValueObjects/LanguageTitle.cs @@ -14,7 +14,6 @@ namespace VizhTalk.Learning.Domain.ValueObjects title = title.Trim(); Guard.NullOrEmpty(title, nameof(LanguageTitle)); Guard.MaxLength(title, nameof(LanguageTitle), 60); - Value = title; } public string Value { get; private set; } diff --git a/VizhTalk.Learning.Presentation/Services/LanguageService.cs b/VizhTalk.Learning.Presentation/Services/LanguageService.cs index 2a93513..657ef32 100644 --- a/VizhTalk.Learning.Presentation/Services/LanguageService.cs +++ b/VizhTalk.Learning.Presentation/Services/LanguageService.cs @@ -23,4 +23,9 @@ public class LanguageService(ILearningModule learningModule) : LanguageGrpc.Lang { return await learningModule.Execute(request, context.CancellationToken); } + public override async Task GetById(GetLanguageByIdQuery request, ServerCallContext context) + { + return await learningModule.Execute(request, context.CancellationToken); + + } } diff --git a/VizhTalk.Services.Grpc.Learning/Helpers/CompressionHelper.cs b/VizhTalk.Services.Grpc.Learning/Helpers/CompressionHelper.cs new file mode 100644 index 0000000..b38dac5 --- /dev/null +++ b/VizhTalk.Services.Grpc.Learning/Helpers/CompressionHelper.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.IO.Compression; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace VizhTalk.Grpc.Learning.Helpers +{ + internal class CompressionHelper + { + public static byte[] Compress(byte[] data) + { + using var output = new MemoryStream(); + using (var gzip = new GZipStream(output, CompressionMode.Compress)) + { + gzip.Write(data, 0, data.Length); + } + return output.ToArray(); + } + + public static byte[] Decompress(byte[] compressedData) + { + using var input = new MemoryStream(compressedData); + using var gzip = new GZipStream(input, CompressionMode.Decompress); + using var output = new MemoryStream(); + gzip.CopyTo(output); + return output.ToArray(); + } + } +} diff --git a/VizhTalk.Services.Grpc.Learning/Helpers/EncryptionHelper.cs b/VizhTalk.Services.Grpc.Learning/Helpers/EncryptionHelper.cs new file mode 100644 index 0000000..f42f68c --- /dev/null +++ b/VizhTalk.Services.Grpc.Learning/Helpers/EncryptionHelper.cs @@ -0,0 +1,45 @@ +using System.Security.Cryptography; +using System.Text; + +namespace VizhTalk.Grpc.Learning.Helpers +{ + internal class EncryptionHelper + { + private const string Alphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + public static string Encrypt(string input) + { + byte[] bytes = Encoding.UTF8.GetBytes(input); + + // تبدیل بایت‌ها به عدد بزرگ + var number = new System.Numerics.BigInteger(bytes); + + // اگر منفی شد، مثبتش کن + if (number < 0) number = -number; + + // تبدیل به Base62 + StringBuilder result = new StringBuilder(); + while (number > 0) + { + result.Insert(0, Alphabet[(int)(number % 62)]); + number /= 62; + } + + return result.ToString(); + } + + public static string Decrypt(string encoded) + { + System.Numerics.BigInteger number = 0; + foreach (char c in encoded) + { + number = number * 62 + Alphabet.IndexOf(c); + } + + // تبدیل دوباره به بایت + byte[] bytes = number.ToByteArray(); + + // رشته اصلی + return Encoding.UTF8.GetString(bytes); + } + } +} diff --git a/VizhTalk.Services.Grpc.Learning/Id.cs b/VizhTalk.Services.Grpc.Learning/Id.cs new file mode 100644 index 0000000..05152ca --- /dev/null +++ b/VizhTalk.Services.Grpc.Learning/Id.cs @@ -0,0 +1,34 @@ +using VizhTalk.BuildingBlocks.Domain; + +namespace VizhTalk.Grpc.Learning; + +public sealed partial class Id +{ + public static implicit operator string(Id id) + => EncryptionIdHelper.Decrypt(id.Value); + + public static implicit operator Id(string id) + => new Id() + { + Value = EncryptionIdHelper.Encrypt(id) + }; + + public static implicit operator int(Id id) + => int.Parse(EncryptionIdHelper.Decrypt(id.Value)); + + public static implicit operator Guid(Id id) + => Guid.Parse(EncryptionIdHelper.Decrypt(id.Value)); + + public static implicit operator long(Id id) + => long.Parse(EncryptionIdHelper.Decrypt(id.Value)); + + + public static implicit operator Id(int id) + => (Id)id.ToString(); + + public static implicit operator Id(long id) + => (Id)id.ToString(); + + public static implicit operator Id(Guid id) + => (Id)id.ToString(); +} diff --git a/VizhTalk.Services.Grpc.Learning/Proto/Languages/Commands.proto b/VizhTalk.Services.Grpc.Learning/Proto/Languages/Commands.proto index bd3332b..aad1b83 100644 --- a/VizhTalk.Services.Grpc.Learning/Proto/Languages/Commands.proto +++ b/VizhTalk.Services.Grpc.Learning/Proto/Languages/Commands.proto @@ -3,6 +3,7 @@ syntax="proto3"; option csharp_namespace="VizhTalk.Grpc.Learning.Language"; package greet; +import "Proto/Shared.proto"; message CreateLanguageCommand{ @@ -10,6 +11,6 @@ message CreateLanguageCommand{ } message UpdateLanguageCommand{ - int32 Id = 1; + Id Id = 1; string title = 2; } \ No newline at end of file diff --git a/VizhTalk.Services.Grpc.Learning/Proto/Languages/DTOs.proto b/VizhTalk.Services.Grpc.Learning/Proto/Languages/DTOs.proto index e891b86..c895ba8 100644 --- a/VizhTalk.Services.Grpc.Learning/Proto/Languages/DTOs.proto +++ b/VizhTalk.Services.Grpc.Learning/Proto/Languages/DTOs.proto @@ -3,13 +3,14 @@ syntax="proto3"; option csharp_namespace="VizhTalk.Grpc.Learning.Language"; package greet; +import "Proto/Shared.proto"; message LanguageDTO { - string Id = 1; + Id Id = 1; string title = 2; } message GetAllLanguageDTO{ - repeated LanguageDTO language =1; + repeated LanguageDTO languages =1; } \ No newline at end of file diff --git a/VizhTalk.Services.Grpc.Learning/Proto/Languages/Queries.proto b/VizhTalk.Services.Grpc.Learning/Proto/Languages/Queries.proto index 70f28d9..3229a3f 100644 --- a/VizhTalk.Services.Grpc.Learning/Proto/Languages/Queries.proto +++ b/VizhTalk.Services.Grpc.Learning/Proto/Languages/Queries.proto @@ -4,9 +4,14 @@ option csharp_namespace="VizhTalk.Grpc.Learning.Language"; package greet; +import "Proto/Shared.proto"; message GetAllLanguageQuery{ } message GetLanguageByTitleQuery{ string title = 1; +} + +message GetLanguageByIdQuery{ + Id id=1; } \ No newline at end of file diff --git a/VizhTalk.Services.Grpc.Learning/Proto/Languages/Services.proto b/VizhTalk.Services.Grpc.Learning/Proto/Languages/Services.proto index 82fd288..ace2cd7 100644 --- a/VizhTalk.Services.Grpc.Learning/Proto/Languages/Services.proto +++ b/VizhTalk.Services.Grpc.Learning/Proto/Languages/Services.proto @@ -18,4 +18,5 @@ service LanguageGrpc { rpc Update (UpdateLanguageCommand) returns (LanguageDTO); rpc GetAll (GetAllLanguageQuery) returns (GetAllLanguageDTO); rpc GetByTitle (GetLanguageByTitleQuery) returns (LanguageDTO); + rpc GetById (GetLanguageByIdQuery) returns (LanguageDTO); } \ No newline at end of file diff --git a/VizhTalk.Services.Grpc.Learning/Proto/Shared.proto b/VizhTalk.Services.Grpc.Learning/Proto/Shared.proto index 7c2a643..3126306 100644 --- a/VizhTalk.Services.Grpc.Learning/Proto/Shared.proto +++ b/VizhTalk.Services.Grpc.Learning/Proto/Shared.proto @@ -6,4 +6,7 @@ package greet; message Empty{ +} +message Id{ + string value = 1; } \ No newline at end of file diff --git a/VizhTalk.Services.Grpc.Learning/Queries/LanguageQueries.cs b/VizhTalk.Services.Grpc.Learning/Queries/LanguageQueries.cs index 4a66564..449b7e5 100644 --- a/VizhTalk.Services.Grpc.Learning/Queries/LanguageQueries.cs +++ b/VizhTalk.Services.Grpc.Learning/Queries/LanguageQueries.cs @@ -5,3 +5,4 @@ namespace VizhTalk.Grpc.Learning.Language; public sealed partial class GetAllLanguageQuery : IQuery; public sealed partial class GetLanguageByTitleQuery : IQuery; +public sealed partial class GetLanguageByIdQuery : IQuery; diff --git a/VizhTalk.sln b/VizhTalk.sln index db65eea..a9a56da 100644 --- a/VizhTalk.sln +++ b/VizhTalk.sln @@ -47,6 +47,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "VizhTalk.Learning.Presentat EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "VizhTalk.Gateway.Web.BFF", "VizhTalk.Gateway.BFF\VizhTalk.Gateway.Web.BFF.csproj", "{21DDD623-E75A-4D16-894B-2DEA0CA5C073}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Vizhtalk.CDN.Presentation", "Vizhtalk.CDN.Presentation\Vizhtalk.CDN.Presentation.csproj", "{F6FDEF63-77D7-47AC-A5F5-2EB59D230158}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -89,6 +91,10 @@ Global {21DDD623-E75A-4D16-894B-2DEA0CA5C073}.Debug|Any CPU.Build.0 = Debug|Any CPU {21DDD623-E75A-4D16-894B-2DEA0CA5C073}.Release|Any CPU.ActiveCfg = Release|Any CPU {21DDD623-E75A-4D16-894B-2DEA0CA5C073}.Release|Any CPU.Build.0 = Release|Any CPU + {F6FDEF63-77D7-47AC-A5F5-2EB59D230158}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F6FDEF63-77D7-47AC-A5F5-2EB59D230158}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F6FDEF63-77D7-47AC-A5F5-2EB59D230158}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F6FDEF63-77D7-47AC-A5F5-2EB59D230158}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -112,6 +118,7 @@ Global {A3F0C4E9-3957-4B25-8AB5-6751ECB01F39} = {16D91427-4F51-48CD-A2F2-6CC1B9A9D8A5} {8327C994-F1D5-493B-8EF7-B60EB888E92A} = {1305E6AB-69E5-47BD-B03C-694832C4D46A} {21DDD623-E75A-4D16-894B-2DEA0CA5C073} = {FCA7DC48-FD9C-43FA-AC1B-464049337778} + {F6FDEF63-77D7-47AC-A5F5-2EB59D230158} = {9E64DF44-8256-40C8-8AFA-B841CE80DD0E} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {9BD8917D-0A5E-401B-B153-400D610B23E4} diff --git a/Vizhtalk.CDN.Application/Vizhtalk.CDN.Application.csproj b/Vizhtalk.CDN.Application/Vizhtalk.CDN.Application.csproj new file mode 100644 index 0000000..141c5e0 --- /dev/null +++ b/Vizhtalk.CDN.Application/Vizhtalk.CDN.Application.csproj @@ -0,0 +1,13 @@ + + + + net8.0 + enable + enable + + + + + + + diff --git a/Vizhtalk.CDN.Presentation/Entities/Media.cs b/Vizhtalk.CDN.Presentation/Entities/Media.cs new file mode 100644 index 0000000..77ac5f0 --- /dev/null +++ b/Vizhtalk.CDN.Presentation/Entities/Media.cs @@ -0,0 +1,14 @@ +using VizhTalk.BuildingBlocks.Domain; +using Vizhtalk.CDN.Presentation.TypeIds; +using Vizhtalk.CDN.Presentation.ValueObjects; + +namespace Vizhtalk.CDN.Presentation.Entities +{ + public class Media : Entity + { + public MediaName Name { get; set; } + public ContentType ContentType { get; private set; } + public string Path { get; private set; } + public string Hash { get; set; } + } +} diff --git a/Vizhtalk.CDN.Presentation/Program.cs b/Vizhtalk.CDN.Presentation/Program.cs new file mode 100644 index 0000000..1760df1 --- /dev/null +++ b/Vizhtalk.CDN.Presentation/Program.cs @@ -0,0 +1,6 @@ +var builder = WebApplication.CreateBuilder(args); +var app = builder.Build(); + +app.MapGet("/", () => "Hello World!"); + +app.Run(); diff --git a/Vizhtalk.CDN.Presentation/Properties/launchSettings.json b/Vizhtalk.CDN.Presentation/Properties/launchSettings.json new file mode 100644 index 0000000..f47485d --- /dev/null +++ b/Vizhtalk.CDN.Presentation/Properties/launchSettings.json @@ -0,0 +1,38 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:11814", + "sslPort": 44357 + } + }, + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "http://localhost:5041", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "https://localhost:7109;http://localhost:5041", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/Vizhtalk.CDN.Presentation/TypeIds/MediaId.cs b/Vizhtalk.CDN.Presentation/TypeIds/MediaId.cs new file mode 100644 index 0000000..c2b19f3 --- /dev/null +++ b/Vizhtalk.CDN.Presentation/TypeIds/MediaId.cs @@ -0,0 +1,11 @@ +using VizhTalk.BuildingBlocks.Domain; + +namespace Vizhtalk.CDN.Presentation.TypeIds +{ + public class MediaId : TypedIdValueBase + { + public MediaId(Guid value) : base(value) + { + } + } +} diff --git a/Vizhtalk.CDN.Presentation/ValueObjects/ContentType.cs b/Vizhtalk.CDN.Presentation/ValueObjects/ContentType.cs new file mode 100644 index 0000000..16a7ac4 --- /dev/null +++ b/Vizhtalk.CDN.Presentation/ValueObjects/ContentType.cs @@ -0,0 +1,16 @@ +using Vizhtalk.CDN.Presentation.ValueObjects.Rules; +using VizhTalk.BuildingBlocks.Domain; + +namespace Vizhtalk.CDN.Presentation.ValueObjects +{ + public class ContentType : ValueObject + { + public ContentType(string contentType, byte[] fileBytes) + { + CheckRule(new ValidContentTypeRule(contentType, fileBytes)); + + Value = contentType; + } + public string Value { get; set; } + } +} diff --git a/Vizhtalk.CDN.Presentation/ValueObjects/MediaName.cs b/Vizhtalk.CDN.Presentation/ValueObjects/MediaName.cs new file mode 100644 index 0000000..1ac99a7 --- /dev/null +++ b/Vizhtalk.CDN.Presentation/ValueObjects/MediaName.cs @@ -0,0 +1,17 @@ +using VizhTalk.BuildingBlocks.Domain; +using VizhTalk.BuildingBlocks.Domain.Guards; + +namespace Vizhtalk.CDN.Presentation.ValueObjects +{ + public class MediaName : ValueObject + { + public MediaName(string value) + { + Guard.NullOrEmpty(value, nameof(MediaName)); + Guard.MaxLength(value, nameof(MediaName), 150); + + Value = value; + } + public string Value { get; set; } + } +} diff --git a/Vizhtalk.CDN.Presentation/ValueObjects/Rules/ValidContentTypeRule.cs b/Vizhtalk.CDN.Presentation/ValueObjects/Rules/ValidContentTypeRule.cs new file mode 100644 index 0000000..24cdd70 --- /dev/null +++ b/Vizhtalk.CDN.Presentation/ValueObjects/Rules/ValidContentTypeRule.cs @@ -0,0 +1,101 @@ +using System.Text.RegularExpressions; +using System.Text; +using VizhTalk.BuildingBlocks.Domain.Exceptions; + +namespace Vizhtalk.CDN.Presentation.ValueObjects.Rules; + +public class ValidContentTypeRule(string contentType, byte[] fileBytes) : IBusinessRule +{ + + public string Message => $"{contentType} content type is not supported or file content is invalid"; + + public bool IsBroken() + { + // اول MimeType بررسی میشه + if (!_validContentTypes.Contains(contentType)) + return true; + + // بعد Magic Number بررسی میشه + if (!HasValidMagicNumber(contentType, fileBytes)) + return true; + + // اگر SVG باشه → محتوای XML رو بررسی کنیم + if (contentType.Equals("image/svg+xml", StringComparison.OrdinalIgnoreCase)) + { + if (ContainsDangerousSvgContent(fileBytes)) + return true; + } + + return false; // یعنی مشکلی نیست + } + + private static readonly HashSet _validContentTypes = new HashSet(StringComparer.OrdinalIgnoreCase) + { + // Images + "image/jpeg", + "image/png", + "image/gif", + "image/webp", + "image/bmp", + "image/tiff", + "image/svg+xml", + + // Videos + "video/mp4", + "video/webm", + "video/ogg", + "video/mpeg", + "video/3gpp", + "video/3gpp2", + "video/x-msvideo", + "video/x-matroska", + + // Audios + "audio/mpeg", + "audio/wav", + "audio/ogg", + "audio/webm", + "audio/aac", + "audio/flac", + "audio/mp4", + "audio/3gpp", + "audio/3gpp2" + }; + + private static bool HasValidMagicNumber(string contentType, byte[] fileBytes) + { + if (fileBytes == null || fileBytes.Length < 4) + return false; + + // فقط چند مورد رایج رو گذاشتم (می‌شه کامل‌ترش کرد) + if (contentType == "image/png" && fileBytes.Take(8).SequenceEqual(new byte[] { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A })) + return true; + + if (contentType == "image/jpeg" && fileBytes.Take(2).SequenceEqual(new byte[] { 0xFF, 0xD8 })) + return true; + + if (contentType == "image/gif" && Encoding.ASCII.GetString(fileBytes.Take(3).ToArray()) == "GIF") + return true; + + if (contentType == "video/mp4" && Encoding.ASCII.GetString(fileBytes.Skip(4).Take(4).ToArray()).Contains("ftyp")) + return true; + + if (contentType == "audio/mpeg" && (fileBytes.Take(3).SequenceEqual(new byte[] { 0x49, 0x44, 0x33 }) // ID3 + || (fileBytes[0] == 0xFF && (fileBytes[1] & 0xE0) == 0xE0))) // MPEG frame + return true; + + if (contentType == "image/svg+xml") + return true; + + return false; + } + + private static bool ContainsDangerousSvgContent(byte[] fileBytes) + { + string content = Encoding.UTF8.GetString(fileBytes); + + // اگر اسکریپت یا onload یا event handler باشه → خطرناک + string pattern = @" + + + net8.0 + enable + enable + + + + + + + + + + + + diff --git a/Vizhtalk.CDN.Presentation/appsettings.Development.json b/Vizhtalk.CDN.Presentation/appsettings.Development.json new file mode 100644 index 0000000..0c208ae --- /dev/null +++ b/Vizhtalk.CDN.Presentation/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/Vizhtalk.CDN.Presentation/appsettings.json b/Vizhtalk.CDN.Presentation/appsettings.json new file mode 100644 index 0000000..5844145 --- /dev/null +++ b/Vizhtalk.CDN.Presentation/appsettings.json @@ -0,0 +1,12 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "ConnectionStrings": { + "VizhTalk.CDN": "mongodb://localhost:27017/VizhTalk.CDN" + }, + "AllowedHosts": "*" +}