diff --git a/VizhTalk.Grpc.CDN/Commands.proto b/VizhTalk.Grpc.CDN/Commands.proto index 32d5c17..46e96e9 100644 --- a/VizhTalk.Grpc.CDN/Commands.proto +++ b/VizhTalk.Grpc.CDN/Commands.proto @@ -10,7 +10,6 @@ message UploadFileCommand{ UploadFile file = 1; Id mediaGroupId = 2; string clientId = 3; - string fileName = 4; } message CreateMediaGroupCommand{ string key = 1; diff --git a/VizhTalk.Learning.Application/CommandHandlers/CourseCommandHandler.cs b/VizhTalk.Learning.Application/CommandHandlers/CourseCommandHandler.cs new file mode 100644 index 0000000..b5d7fc5 --- /dev/null +++ b/VizhTalk.Learning.Application/CommandHandlers/CourseCommandHandler.cs @@ -0,0 +1,61 @@ +using Mapster; +using System.Net.Http.Headers; +using System.Net.Sockets; +using VizhTalk.BuildingBlocks.Application.Configuration.Commnad; +using VizhTalk.Grpc.CDN; +using VizhTalk.Grpc.Learning.Course; +using VizhTalk.Learning.Application.Exceptions; +using VizhTalk.Learning.Domain.Entities; +using VizhTalk.Learning.Domain.TypeIds; +using VizhTalk.Learning.Domain.ValueObjects; + +namespace VizhTalk.Learning.Application.CommandHandlers; + + +public class CreateCourseCommandHandler(IUnitOfWork unitOfWork, CDNGrpc.CDNGrpcClient cdnClient) : ICommandHandler +{ + public async Task Handle(CreateCourseCommand request, CancellationToken cancellationToken) + { + var language = await unitOfWork.Language.GetLanguageWithCoursesById(new LanguageId(request.Languageid), cancellationToken); + if(language == null) + throw new LanguageNotFoundException(); + + var fileGroup = await unitOfWork.FileGroup.GetByFileGroupKey(FileGroupKey.ImageCourse); + if(fileGroup == null) + { + var group = await cdnClient.CreateMediaGroupAsync(new CreateMediaGroupCommand() + { + IsSecure = false, + Key = FileGroupKey.ImageCourse.Value, + Title = nameof(FileGroupKey.ImageCourse) + }); + + fileGroup = FileGroup.CreateNew(FileGroupKey.IconLanguage, new FileGroupTitle(nameof(FileGroupKey.IconLanguage)), false, group.Id); + await unitOfWork.FileGroup.AddAsync(fileGroup, cancellationToken); + } + var upload = await cdnClient.UploadFileAsync(new UploadFileCommand() + { + File = new UploadFile() + { + ContentType = request.Image.ContentType, + File = request.Image.File, + Name = request.Image.Name, + }, + MediaGroupId = fileGroup.MediaGroupId, + }, cancellationToken: cancellationToken); + + var media = Media.CreateNew(request.Image.Name,upload.ContentType, upload.Size, upload.MediaGroupMediaId, new EntityType(EntityTypeEnum.Course)); + var course = language.AddCourse(request.Title, media, request.Description); + await unitOfWork.Language.UpdateAsync(language,cancellationToken); + await unitOfWork.SaveChangeAsync(cancellationToken); + return course.Adapt(); + } +} + +public class UpdateCourseCommandHandler(IUnitOfWork unitOfWork) : ICommandHandler +{ + public Task Handle(UpdateCourseCommand request, CancellationToken cancellationToken) + { + + } +} diff --git a/VizhTalk.Learning.Application/CommandHandlers/LanguageCommandHandler.cs b/VizhTalk.Learning.Application/CommandHandlers/LanguageCommandHandler.cs index 6b01168..ebf60f1 100644 --- a/VizhTalk.Learning.Application/CommandHandlers/LanguageCommandHandler.cs +++ b/VizhTalk.Learning.Application/CommandHandlers/LanguageCommandHandler.cs @@ -47,7 +47,6 @@ public class CreateLanguageCommandHandler(IUnitOfWork unitOfWork, File = request.Icon.File, Name = request.Icon.Name, }, - FileName = title, MediaGroupId = fileGroup.MediaGroupId }, cancellationToken: cancellationToken); @@ -86,7 +85,6 @@ public class UpdateLanguageCommandHandler(IUnitOfWork unitOfWork, CDNGrpc.CDNGrp File = request.Icon.File, Name = request.Icon.Name, }, - FileName = request.Title, MediaGroupId = fileGroup.MediaGroupId }, cancellationToken: cancellationToken); diff --git a/VizhTalk.Learning.Application/Mappers/MapperConfig.cs b/VizhTalk.Learning.Application/Mappers/MapperConfig.cs index a8a7d3d..5ec5231 100644 --- a/VizhTalk.Learning.Application/Mappers/MapperConfig.cs +++ b/VizhTalk.Learning.Application/Mappers/MapperConfig.cs @@ -1,5 +1,6 @@ using Mapster; using VizhTalk.Grpc.Learning; +using VizhTalk.Grpc.Learning.Course; using VizhTalk.Grpc.Learning.Language; using VizhTalk.Learning.Domain.Entities; @@ -9,16 +10,20 @@ namespace VizhTalk.Learning.Application.Mappers { public static void Process() { + TypeAdapterConfig.NewConfig() + .MapWith(lan => new LanguageDTO() + { + Id = lan.Id.Value, + Title = lan.Title.Value, + }).Compile(); - var config = TypeAdapterConfig.NewConfig() - .MapWith(lan => new LanguageDTO() - { - Id = lan.Id.Value, - Title = lan.Title.Value, - }); + TypeAdapterConfig.NewConfig() + .MapWith(lan => new CourseDTO() + { + Id = lan.Id.Value, + Title = lan.Title.Value, + }).Compile(); - - config.Compile(); } } } diff --git a/VizhTalk.Learning.Domain/Entities/Course.cs b/VizhTalk.Learning.Domain/Entities/Course.cs index 5611202..9752de9 100644 --- a/VizhTalk.Learning.Domain/Entities/Course.cs +++ b/VizhTalk.Learning.Domain/Entities/Course.cs @@ -10,21 +10,26 @@ public class Course : Entity { } - private Course(CourseTitle title, int order) + private Course(CourseTitle title, int order, Media image, CourseDescription? description) { Title = title; Order = order; + Image = image; + Description = description; } public CourseTitle Title { get; private set; } public Media Image { get; private set; } + public CourseDescription? Description { get; set; } public int Order { get; private set; } - internal static Course CreateNew(CourseTitle title, int order) - => new(title, order); + internal static Course CreateNew(CourseTitle title, int order, Media image, CourseDescription? description) + => new(title, order, image, description); public void SetTitle(CourseTitle title) => Title = title; public void SetOrder(int order) => Order = order; public void SetImage(Media media) => Image = media; + public void SetDescription(CourseDescription? description) + => Description = description; } diff --git a/VizhTalk.Learning.Domain/Entities/Language.cs b/VizhTalk.Learning.Domain/Entities/Language.cs index 2d8f7d6..0fea5b6 100644 --- a/VizhTalk.Learning.Domain/Entities/Language.cs +++ b/VizhTalk.Learning.Domain/Entities/Language.cs @@ -20,7 +20,7 @@ public class Language : Entity, IAggregateRoot public LanguageTitle Title { get; private set; } public Media Icon { get; private set; } public IReadOnlyList Courses => _courses; - public static Language CreateNew(LanguageTitle title,Media icon) + public static Language CreateNew(LanguageTitle title, Media icon) => new(title, icon); public void SetTitle(LanguageTitle title) @@ -32,21 +32,22 @@ public class Language : Entity, IAggregateRoot Icon = icon; } - public Course AddCourse(CourseTitle courseTitle) + public Course AddCourse(CourseTitle courseTitle, Media image, CourseDescription? courseDescription) { var order = Courses.Count; - var course = Course.CreateNew(courseTitle, order); + var course = Course.CreateNew(courseTitle, order, image, courseDescription); _courses.Add(course); return course; } public Course UpdateCourse(Course course) { var _course = _courses.FirstOrDefault(x => x.Id == course.Id) ?? throw new CourseNotFoundException(Title); - if(course.Image != null) + if (course.Image != null) _course.SetImage(course.Image); _course.SetTitle(course.Title); + _course.SetDescription(course.Description); - if(_course.Order != course.Order) + if (_course.Order != course.Order) { var orderCourses = _courses.Where(x => x.Id != course.Id) .OrderBy(x => x.Order) diff --git a/VizhTalk.Learning.Domain/Repositories/ILanguageRepository.cs b/VizhTalk.Learning.Domain/Repositories/ILanguageRepository.cs index b8c8f68..bba2fa2 100644 --- a/VizhTalk.Learning.Domain/Repositories/ILanguageRepository.cs +++ b/VizhTalk.Learning.Domain/Repositories/ILanguageRepository.cs @@ -6,5 +6,5 @@ namespace VizhTalk.Learning.Domain.Repositories; public interface ILanguageRepository : IRepositoryBase { - + Task GetLanguageWithCoursesById(LanguageId languageId ,CancellationToken cancellationToken = default); } diff --git a/VizhTalk.Learning.Domain/ValueObjects/CourseDescription.cs b/VizhTalk.Learning.Domain/ValueObjects/CourseDescription.cs new file mode 100644 index 0000000..53aaf87 --- /dev/null +++ b/VizhTalk.Learning.Domain/ValueObjects/CourseDescription.cs @@ -0,0 +1,19 @@ +using VizhTalk.BuildingBlocks.Domain; +using VizhTalk.BuildingBlocks.Domain.Guards; + +namespace VizhTalk.Learning.Domain.ValueObjects; + +public class CourseDescription : ValueObject +{ + public const short MaxLength = 800; + public CourseDescription(string description) + { + Guard.NullOrEmpty(description,nameof(CourseDescription)); + Guard.MaxLength(description, nameof(CourseDescription), MaxLength); + Value = description; + } + public string Value { get; set; } + + public static implicit operator string(CourseDescription title) => title.Value; + public static implicit operator CourseDescription(string title) => new(title); +} diff --git a/VizhTalk.Learning.Domain/ValueObjects/CourseTitle.cs b/VizhTalk.Learning.Domain/ValueObjects/CourseTitle.cs index 2440ea2..0327671 100644 --- a/VizhTalk.Learning.Domain/ValueObjects/CourseTitle.cs +++ b/VizhTalk.Learning.Domain/ValueObjects/CourseTitle.cs @@ -14,5 +14,6 @@ public class CourseTitle public string Value { get; private set; } public static implicit operator string(CourseTitle title) => title.Value; + public static implicit operator CourseTitle(string title) => new(title); } diff --git a/VizhTalk.Learning.Domain/ValueObjects/EntityType.cs b/VizhTalk.Learning.Domain/ValueObjects/EntityType.cs index 598cbae..16ca747 100644 --- a/VizhTalk.Learning.Domain/ValueObjects/EntityType.cs +++ b/VizhTalk.Learning.Domain/ValueObjects/EntityType.cs @@ -12,5 +12,6 @@ public class EntityType : ValueObject } public enum EntityTypeEnum { - Language = 0 + Language = 0, + Course = 1 } diff --git a/VizhTalk.Learning.Domain/ValueObjects/FileGroupKey.cs b/VizhTalk.Learning.Domain/ValueObjects/FileGroupKey.cs index 6971304..2bcd379 100644 --- a/VizhTalk.Learning.Domain/ValueObjects/FileGroupKey.cs +++ b/VizhTalk.Learning.Domain/ValueObjects/FileGroupKey.cs @@ -11,6 +11,7 @@ namespace VizhTalk.Learning.Domain.ValueObjects public class FileGroupKey : ValueObject { public static FileGroupKey IconLanguage = new("IconLanguage"); + public static FileGroupKey ImageCourse = new("ImageCourse"); public FileGroupKey(string entityName, string? entityId = null) { Guard.NullOrEmpty(entityName, entityName); diff --git a/VizhTalk.Learning.Infrastructure/EF/Configurations/CourseConfiguration.cs b/VizhTalk.Learning.Infrastructure/EF/Configurations/CourseConfiguration.cs index 9950c94..1339149 100644 --- a/VizhTalk.Learning.Infrastructure/EF/Configurations/CourseConfiguration.cs +++ b/VizhTalk.Learning.Infrastructure/EF/Configurations/CourseConfiguration.cs @@ -1,11 +1,7 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using VizhTalk.Learning.Domain.Entities; +using VizhTalk.Learning.Domain.ValueObjects; namespace VizhTalk.Learning.Infrastructure.EF.Configurations { @@ -23,6 +19,12 @@ namespace VizhTalk.Learning.Infrastructure.EF.Configurations builder.Property(x => x.Title) .HasConversion(x => x.Value, x => new(x)); + builder.Property(x => x.Description) + .HasMaxLength(CourseDescription.MaxLength) + .HasConversion(x => x.Value, x => new(x)); + + builder.Navigation(x => x.Image) + .AutoInclude(); } } } diff --git a/VizhTalk.Learning.Infrastructure/EF/Configurations/LanguageConfiguration.cs b/VizhTalk.Learning.Infrastructure/EF/Configurations/LanguageConfiguration.cs index a7d6182..f5f16e5 100644 --- a/VizhTalk.Learning.Infrastructure/EF/Configurations/LanguageConfiguration.cs +++ b/VizhTalk.Learning.Infrastructure/EF/Configurations/LanguageConfiguration.cs @@ -24,6 +24,9 @@ namespace VizhTalk.Learning.Infrastructure.EF.Configurations builder.Property(x => x.Title) .HasConversion(x => x.Value, x => new LanguageTitle(x)) .HasMaxLength(60); + + builder.Navigation(x => x.Icon) + .AutoInclude(); } } } diff --git a/VizhTalk.Learning.Infrastructure/EF/LearningDbContext.cs b/VizhTalk.Learning.Infrastructure/EF/LearningDbContext.cs index 825a4b8..15c6ecf 100644 --- a/VizhTalk.Learning.Infrastructure/EF/LearningDbContext.cs +++ b/VizhTalk.Learning.Infrastructure/EF/LearningDbContext.cs @@ -2,6 +2,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using VizhTalk.BuildingBlocks.Domain; using VizhTalk.BuildingBlocks.Infrastructure.EF.Helpers; +using VizhTalk.Learning.Domain.Entities; using VizhTalk.Learning.Infrastructure.Configuration; namespace VizhTalk.Learning.Infrastructure.EF; @@ -22,5 +23,6 @@ public class LearningDbContext : DbContext modelBuilder.FindEntities(Assemblies.Domain) .ApplyConfigurationsFromAssembly(Assemblies.Infrastructure, x => x.FullName.Contains("EF.Configurations")) .PluralizeEntities(); + } } \ No newline at end of file diff --git a/VizhTalk.Learning.Infrastructure/Repositories/LanguageRepository.cs b/VizhTalk.Learning.Infrastructure/Repositories/LanguageRepository.cs index bae94ae..26fec21 100644 --- a/VizhTalk.Learning.Infrastructure/Repositories/LanguageRepository.cs +++ b/VizhTalk.Learning.Infrastructure/Repositories/LanguageRepository.cs @@ -1,4 +1,5 @@ -using VizhTalk.BuildingBlocks.Infrastructure; +using Microsoft.EntityFrameworkCore; +using VizhTalk.BuildingBlocks.Infrastructure; using VizhTalk.Learning.Domain.Entities; using VizhTalk.Learning.Domain.Repositories; using VizhTalk.Learning.Domain.TypeIds; @@ -8,8 +9,18 @@ namespace VizhTalk.Learning.Infrastructure.Repositories { internal class LanguageRepository : RepositoryBase, ILanguageRepository { + private readonly LearningDbContext dbContext; + public LanguageRepository(LearningDbContext dbContext) : base(dbContext) { + this.dbContext = dbContext; + } + + public async Task GetLanguageWithCoursesById(LanguageId languageId, CancellationToken cancellationToken = default) + { + return await dbContext.Set() + .Include(x => x.Courses) + .FirstOrDefaultAsync(x => x.Id == languageId, cancellationToken); } } } diff --git a/VizhTalk.Services.Grpc.Learning/Commands/CourseCommands.cs b/VizhTalk.Services.Grpc.Learning/Commands/CourseCommands.cs new file mode 100644 index 0000000..4176b0e --- /dev/null +++ b/VizhTalk.Services.Grpc.Learning/Commands/CourseCommands.cs @@ -0,0 +1,6 @@ +using VizhTalk.BuildingBlocks.Domain.Contract; + +namespace VizhTalk.Grpc.Learning.Course; + +public sealed partial class CreateCourseCommand: ICommand; +public sealed partial class UpdateCourseCommand : ICommand; \ No newline at end of file diff --git a/VizhTalk.Services.Grpc.Learning/Commands/LanguageCommands.cs b/VizhTalk.Services.Grpc.Learning/Commands/LanguageCommands.cs index 1d57466..9a261e2 100644 --- a/VizhTalk.Services.Grpc.Learning/Commands/LanguageCommands.cs +++ b/VizhTalk.Services.Grpc.Learning/Commands/LanguageCommands.cs @@ -1,5 +1,4 @@ using VizhTalk.BuildingBlocks.Domain.Contract; -using VizhTalk.Grpc.Learning.Language; namespace VizhTalk.Grpc.Learning.Language; diff --git a/VizhTalk.Services.Grpc.Learning/Proto/Courses/Commands.proto b/VizhTalk.Services.Grpc.Learning/Proto/Courses/Commands.proto index 47c08ed..cb26ff4 100644 --- a/VizhTalk.Services.Grpc.Learning/Proto/Courses/Commands.proto +++ b/VizhTalk.Services.Grpc.Learning/Proto/Courses/Commands.proto @@ -8,11 +8,15 @@ import "Proto/Shared.proto"; message CreateCourseCommand{ string title = 1; - bytes icon = 2; + string description = 2; + UploadFile image = 3; + Id languageid = 4; } message UpdateCourseCommand{ Id Id = 1; string title = 2; - optional bytes icon = 3; + string description = 3; + optional UploadFile image = 4; + Id languageid = 5; } \ No newline at end of file diff --git a/Vizhtalk.CDN.Presentation/Controllers/FileController.cs b/Vizhtalk.CDN.Presentation/Controllers/FileController.cs index 3b53e83..e7dbf9d 100644 --- a/Vizhtalk.CDN.Presentation/Controllers/FileController.cs +++ b/Vizhtalk.CDN.Presentation/Controllers/FileController.cs @@ -1,20 +1,30 @@ using Microsoft.AspNetCore.Mvc; +using Vizhtalk.CDN.Presentation.Application; using Vizhtalk.CDN.Presentation.Domain.TypeIds; namespace Vizhtalk.CDN.Presentation.Controllers { - [Route("api/[controller]")] + [Route("[controller]")] [ApiController] - public class FileController : ControllerBase + public class FileController(IUnitOfWork unitOfWork) : ControllerBase { [HttpGet("{id}")] - public IActionResult Get(MediaGroupMediaId id) + public async Task Get(Guid id) { + var mediaGroupMedia = await unitOfWork.MediaGroupMedia + .GetDetailsById(new MediaGroupMediaId(id)); - + if (mediaGroupMedia == null) + return NotFound(); - return Unauthorized(); + if(!mediaGroupMedia.MediaGroup.IsSecure) + { + var fileBytes = System.IO.File.ReadAllBytes(mediaGroupMedia.Media.Path); + return File(fileBytes, mediaGroupMedia.Media.ContentType.Value); + } + + return NotFound(); } } diff --git a/Vizhtalk.CDN.Presentation/Domain/Entities/MediaGroupMedia.cs b/Vizhtalk.CDN.Presentation/Domain/Entities/MediaGroupMedia.cs index 8df2ed2..a3d6974 100644 --- a/Vizhtalk.CDN.Presentation/Domain/Entities/MediaGroupMedia.cs +++ b/Vizhtalk.CDN.Presentation/Domain/Entities/MediaGroupMedia.cs @@ -23,8 +23,8 @@ public class MediaGroupMedia : Entity public FileName Name { get; private set; } public MediaId MediaId { get; private set; } public MediaGroupId MediaGroupId { get; private set; } - public Media Media { get; private set; } - public MediaGroup MediaGroup { get; private set; } + public Media Media { get; set; } + public MediaGroup MediaGroup { get; set; } public IReadOnlyList MediaPermissions => _mediaPermissions; internal static MediaGroupMedia CreateNew(FileName fileName, diff --git a/Vizhtalk.CDN.Presentation/Domain/Repositories/IMediaGroupMediaRepository.cs b/Vizhtalk.CDN.Presentation/Domain/Repositories/IMediaGroupMediaRepository.cs index fb21569..5671655 100644 --- a/Vizhtalk.CDN.Presentation/Domain/Repositories/IMediaGroupMediaRepository.cs +++ b/Vizhtalk.CDN.Presentation/Domain/Repositories/IMediaGroupMediaRepository.cs @@ -4,4 +4,7 @@ using VizhTalk.BuildingBlocks.Domain; namespace Vizhtalk.CDN.Presentation.Domain.Repositories; -public interface IMediaGroupMediaRepository : IRepositoryBase; +public interface IMediaGroupMediaRepository : IRepositoryBase +{ + Task GetDetailsById(MediaGroupMediaId mediaGroupMediaId,CancellationToken cancellationToken = default); +} diff --git a/Vizhtalk.CDN.Presentation/Infrastructure/Repositories/MediaGroupMediaRepository.cs b/Vizhtalk.CDN.Presentation/Infrastructure/Repositories/MediaGroupMediaRepository.cs index e53315a..ad960a5 100644 --- a/Vizhtalk.CDN.Presentation/Infrastructure/Repositories/MediaGroupMediaRepository.cs +++ b/Vizhtalk.CDN.Presentation/Infrastructure/Repositories/MediaGroupMediaRepository.cs @@ -9,8 +9,29 @@ namespace Vizhtalk.CDN.Presentation.Infrastructure.Repositories { public class MediaGroupMediaRepository : RepositoryBase, IMediaGroupMediaRepository { + private readonly CDNDbContext dbContext; + public MediaGroupMediaRepository(CDNDbContext dbContext) : base(dbContext) { + this.dbContext = dbContext; + } + + public async Task GetDetailsById(MediaGroupMediaId mediaGroupMediaId, CancellationToken cancellationToken = default) + { + var mediaGroupMedia = await dbContext.Set() + .FirstOrDefaultAsync(x => x.Id == mediaGroupMediaId, cancellationToken); + + if (mediaGroupMedia == null) + return null; + + // پر کردن دستی navigation properties + mediaGroupMedia.MediaGroup = await dbContext.Set() + .FirstOrDefaultAsync(mg => mg.Id == mediaGroupMedia.MediaGroupId, cancellationToken); + + mediaGroupMedia.Media = await dbContext.Set() + .FirstOrDefaultAsync(m => m.Id == mediaGroupMedia.MediaId, cancellationToken); + + return mediaGroupMedia; } } } diff --git a/Vizhtalk.CDN.Presentation/Program.cs b/Vizhtalk.CDN.Presentation/Program.cs index 8b31ab0..fa8b835 100644 --- a/Vizhtalk.CDN.Presentation/Program.cs +++ b/Vizhtalk.CDN.Presentation/Program.cs @@ -8,6 +8,7 @@ var builder = WebApplication.CreateBuilder(args); var connectionString = builder.Configuration.GetConnectionString("VizhTalk.CDN")!; var databaseName = builder.Configuration.GetConnectionString("VizhTalk.CDN.DatabaseName")!; +builder.Services.AddControllers(); builder.Services.Initialize(connectionString, databaseName); builder.Services.AddGrpc(op => { @@ -18,10 +19,10 @@ var app = builder.Build(); -app.MapGet("/", () => "Hello World!"); +app.UseRouting(); app.MapGrpcService(); - +app.MapControllers(); //app.UseCDNModule(); app.Run();