This commit is contained in:
2025-09-12 19:04:54 +04:30
parent 338895a1ad
commit c7d97fe9bd
23 changed files with 193 additions and 40 deletions
-1
View File
@@ -10,7 +10,6 @@ message UploadFileCommand{
UploadFile file = 1;
Id mediaGroupId = 2;
string clientId = 3;
string fileName = 4;
}
message CreateMediaGroupCommand{
string key = 1;
@@ -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<CreateCourseCommand, CourseDTO>
{
public async Task<CourseDTO> 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<CourseDTO>();
}
}
public class UpdateCourseCommandHandler(IUnitOfWork unitOfWork) : ICommandHandler<UpdateCourseCommand, CourseDTO>
{
public Task<CourseDTO> Handle(UpdateCourseCommand request, CancellationToken cancellationToken)
{
}
}
@@ -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);
@@ -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()
{
var config = TypeAdapterConfig<Language, LanguageDTO>.NewConfig()
TypeAdapterConfig<Language, LanguageDTO>.NewConfig()
.MapWith(lan => new LanguageDTO()
{
Id = lan.Id.Value,
Title = lan.Title.Value,
});
}).Compile();
TypeAdapterConfig<Course, CourseDTO>.NewConfig()
.MapWith(lan => new CourseDTO()
{
Id = lan.Id.Value,
Title = lan.Title.Value,
}).Compile();
config.Compile();
}
}
}
+8 -3
View File
@@ -10,21 +10,26 @@ public class Course : Entity<CourseId>
{
}
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;
}
@@ -32,10 +32,10 @@ public class Language : Entity<LanguageId>, 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;
}
@@ -45,6 +45,7 @@ public class Language : Entity<LanguageId>, IAggregateRoot
if (course.Image != null)
_course.SetImage(course.Image);
_course.SetTitle(course.Title);
_course.SetDescription(course.Description);
if (_course.Order != course.Order)
{
@@ -6,5 +6,5 @@ namespace VizhTalk.Learning.Domain.Repositories;
public interface ILanguageRepository : IRepositoryBase<Language,LanguageId>
{
Task<Language?> GetLanguageWithCoursesById(LanguageId languageId ,CancellationToken cancellationToken = default);
}
@@ -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);
}
@@ -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);
}
@@ -12,5 +12,6 @@ public class EntityType : ValueObject
}
public enum EntityTypeEnum
{
Language = 0
Language = 0,
Course = 1
}
@@ -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);
@@ -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();
}
}
}
@@ -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();
}
}
}
@@ -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();
}
}
@@ -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<Language, LanguageId>, ILanguageRepository
{
private readonly LearningDbContext dbContext;
public LanguageRepository(LearningDbContext dbContext) : base(dbContext)
{
this.dbContext = dbContext;
}
public async Task<Language?> GetLanguageWithCoursesById(LanguageId languageId, CancellationToken cancellationToken = default)
{
return await dbContext.Set<Language>()
.Include(x => x.Courses)
.FirstOrDefaultAsync(x => x.Id == languageId, cancellationToken);
}
}
}
@@ -0,0 +1,6 @@
using VizhTalk.BuildingBlocks.Domain.Contract;
namespace VizhTalk.Grpc.Learning.Course;
public sealed partial class CreateCourseCommand: ICommand<CourseDTO>;
public sealed partial class UpdateCourseCommand : ICommand<CourseDTO>;
@@ -1,5 +1,4 @@
using VizhTalk.BuildingBlocks.Domain.Contract;
using VizhTalk.Grpc.Learning.Language;
namespace VizhTalk.Grpc.Learning.Language;
@@ -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;
}
@@ -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<IActionResult> Get(Guid id)
{
var mediaGroupMedia = await unitOfWork.MediaGroupMedia
.GetDetailsById(new MediaGroupMediaId(id));
if (mediaGroupMedia == null)
return NotFound();
if(!mediaGroupMedia.MediaGroup.IsSecure)
{
var fileBytes = System.IO.File.ReadAllBytes(mediaGroupMedia.Media.Path);
return File(fileBytes, mediaGroupMedia.Media.ContentType.Value);
}
return Unauthorized();
return NotFound();
}
}
@@ -23,8 +23,8 @@ public class MediaGroupMedia : Entity<MediaGroupMediaId>
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<MediaPermission> MediaPermissions => _mediaPermissions;
internal static MediaGroupMedia CreateNew(FileName fileName,
@@ -4,4 +4,7 @@ using VizhTalk.BuildingBlocks.Domain;
namespace Vizhtalk.CDN.Presentation.Domain.Repositories;
public interface IMediaGroupMediaRepository : IRepositoryBase<MediaGroupMedia, MediaGroupMediaId>;
public interface IMediaGroupMediaRepository : IRepositoryBase<MediaGroupMedia, MediaGroupMediaId>
{
Task<MediaGroupMedia?> GetDetailsById(MediaGroupMediaId mediaGroupMediaId,CancellationToken cancellationToken = default);
}
@@ -9,8 +9,29 @@ namespace Vizhtalk.CDN.Presentation.Infrastructure.Repositories
{
public class MediaGroupMediaRepository : RepositoryBase<MediaGroupMedia, MediaGroupMediaId>, IMediaGroupMediaRepository
{
private readonly CDNDbContext dbContext;
public MediaGroupMediaRepository(CDNDbContext dbContext) : base(dbContext)
{
this.dbContext = dbContext;
}
public async Task<MediaGroupMedia?> GetDetailsById(MediaGroupMediaId mediaGroupMediaId, CancellationToken cancellationToken = default)
{
var mediaGroupMedia = await dbContext.Set<MediaGroupMedia>()
.FirstOrDefaultAsync(x => x.Id == mediaGroupMediaId, cancellationToken);
if (mediaGroupMedia == null)
return null;
// پر کردن دستی navigation properties
mediaGroupMedia.MediaGroup = await dbContext.Set<MediaGroup>()
.FirstOrDefaultAsync(mg => mg.Id == mediaGroupMedia.MediaGroupId, cancellationToken);
mediaGroupMedia.Media = await dbContext.Set<Media>()
.FirstOrDefaultAsync(m => m.Id == mediaGroupMedia.MediaId, cancellationToken);
return mediaGroupMedia;
}
}
}
+3 -2
View File
@@ -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<CDNService>();
app.MapControllers();
//app.UseCDNModule();
app.Run();