Skip to content

Commit 776c77a

Browse files
authored
Merge pull request #4 from yazilimacademy/Application
[Feature]: Category/GetAll and Create operations completed.
2 parents 5731171 + b172f6d commit 776c77a

23 files changed

+334
-8
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,17 @@
1+
using Microsoft.EntityFrameworkCore;
2+
using YazilimAcademy.Domain.Entities;
3+
14
namespace YazilimAcademy.Application.Common.Interfaces;
25

36
public interface IApplicationDbContext
47
{
8+
DbSet<Category> Categories { get; }
9+
DbSet<Course> Courses { get; }
10+
DbSet<CourseCategory> CourseCategories { get; }
11+
DbSet<CourseSection> CourseSections { get; }
12+
DbSet<CourseLecture> CourseLectures { get; }
13+
DbSet<CourseLectureResource> CourseLectureResources { get; }
514

15+
Task<int> SaveChangesAsync(CancellationToken cancellationToken);
16+
int SaveChanges();
617
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
using System.Data;
2+
3+
namespace YazilimAcademy.Application.Common.Interfaces;
4+
5+
public interface ISqlConnectionFactory
6+
{
7+
IDbConnection CreateConnection();
8+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
namespace YazilimAcademy.Application.Common.Models.Errors;
2+
3+
public sealed record ValidationError
4+
{
5+
public string PropertyName { get; init; }
6+
public IEnumerable<string> ErrorMessages { get; init; }
7+
8+
public ValidationError(string propertyName, IEnumerable<string> errorMessages)
9+
{
10+
PropertyName = propertyName;
11+
ErrorMessages = errorMessages;
12+
}
13+
14+
public ValidationError(string propertyName, string errorMessage)
15+
: this(propertyName, new List<string> { errorMessage })
16+
{
17+
}
18+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
using System.Text.Json.Serialization;
2+
using Microsoft.EntityFrameworkCore;
3+
4+
namespace YazilimAcademy.Application.Common.Models.Pagination;
5+
6+
public sealed record PaginatedList<T>
7+
{
8+
public IReadOnlyCollection<T> Items { get; }
9+
public int PageNumber { get; }
10+
public int TotalPages { get; }
11+
public int TotalCount { get; }
12+
public int PageSize { get; set; }
13+
14+
public PaginatedList(IEnumerable<T> items, int totalCount, int pageNumber, int pageSize)
15+
{
16+
Items = items.ToList().AsReadOnly();
17+
18+
TotalCount = totalCount;
19+
20+
PageNumber = pageNumber;
21+
22+
PageSize = pageSize;
23+
24+
TotalPages = (int)Math.Ceiling(totalCount / (double)pageSize);
25+
}
26+
27+
[JsonConstructor]
28+
public PaginatedList(IReadOnlyCollection<T> items, int totalCount, int pageNumber, int pageSize)
29+
{
30+
PageNumber = pageNumber;
31+
TotalPages = (int)Math.Ceiling(totalCount / (double)pageSize);
32+
TotalCount = totalCount;
33+
Items = items;
34+
PageSize = pageSize;
35+
}
36+
37+
public bool HasPreviousPage => PageNumber > 1;
38+
39+
public bool HasNextPage => PageNumber < TotalPages;
40+
41+
public static async Task<PaginatedList<T>> CreateAsync(IQueryable<T> source, int pageNumber, int pageSize)
42+
{
43+
var count = await source.CountAsync();
44+
45+
var items = await source
46+
.Skip((pageNumber - 1) * pageSize)
47+
.Take(pageSize)
48+
.ToListAsync();
49+
50+
return new PaginatedList<T>(items, count, pageNumber, pageSize);
51+
}
52+
53+
public static PaginatedList<T> Create(IEnumerable<T> source, int pageNumber, int pageSize)
54+
{
55+
var count = source.Count();
56+
57+
var items = source.Skip((pageNumber - 1) * pageSize).Take(pageSize).ToList();
58+
59+
return new PaginatedList<T>(items, count, pageNumber, pageSize);
60+
}
61+
62+
public static PaginatedList<T> Create(List<T> source, int totalCount, int pageNumber, int pageSize)
63+
{
64+
return new PaginatedList<T>(source, totalCount, pageNumber, pageSize);
65+
}
66+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
using YazilimAcademy.Application.Common.Models.Errors;
2+
3+
namespace YazilimAcademy.Application.Common.Models.Responses;
4+
5+
public sealed record ResponseDto<T>
6+
{
7+
public T? Data { get; init; }
8+
public string? Message { get; init; }
9+
public bool IsSuccess { get; init; }
10+
public IReadOnlyList<ValidationError> ValidationErrors { get; init; }
11+
12+
public ResponseDto(T? data, string? message, bool isSuccess, IReadOnlyList<ValidationError>? validationErrors = null)
13+
{
14+
Data = data;
15+
Message = message;
16+
IsSuccess = isSuccess;
17+
ValidationErrors = validationErrors ?? Array.Empty<ValidationError>();
18+
}
19+
20+
public static ResponseDto<T> Success(T data, string message)
21+
=> new(data, message, true);
22+
23+
public static ResponseDto<T?> Success(string message)
24+
=> new(default, message, true);
25+
26+
public static ResponseDto<T> Error(string message, List<ValidationError>? validationErrors = null)
27+
=> new(default, message, false, validationErrors);
28+
29+
public static ResponseDto<T> Error(string message, ValidationError validationError)
30+
=> new(default, message, false, new[] { validationError });
31+
}
32+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
using MediatR;
2+
3+
namespace YazilimAcademy.Application.Features.Categories.Commands.Create;
4+
5+
public sealed record CreateCategoryCommand(string Name, string Description) : IRequest<Guid>;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
using System;
2+
using MediatR;
3+
using YazilimAcademy.Application.Common.Interfaces;
4+
using YazilimAcademy.Domain.Entities;
5+
6+
namespace YazilimAcademy.Application.Features.Categories.Commands.Create;
7+
8+
public sealed class CreateCategoryCommandHandler : IRequestHandler<CreateCategoryCommand, Guid>
9+
{
10+
private readonly IApplicationDbContext _dbContext;
11+
12+
public CreateCategoryCommandHandler(IApplicationDbContext dbContext)
13+
{
14+
_dbContext = dbContext;
15+
}
16+
17+
public async Task<Guid> Handle(CreateCategoryCommand request, CancellationToken cancellationToken)
18+
{
19+
var category = Category.Create(request.Name, request.Description);
20+
21+
_dbContext.Categories.Add(category);
22+
23+
await _dbContext.SaveChangesAsync(cancellationToken);
24+
25+
return category.Id;
26+
}
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
using System;
2+
using FluentValidation;
3+
using Microsoft.EntityFrameworkCore;
4+
using YazilimAcademy.Application.Common.Interfaces;
5+
namespace YazilimAcademy.Application.Features.Categories.Commands.Create;
6+
7+
public sealed class CreateCategoryCommandValidator : AbstractValidator<CreateCategoryCommand>
8+
{
9+
private readonly IApplicationDbContext _dbContext;
10+
11+
public CreateCategoryCommandValidator(IApplicationDbContext dbContext)
12+
{
13+
_dbContext = dbContext;
14+
15+
RuleFor(c => c.Name)
16+
.NotEmpty()
17+
.WithMessage("Kategori adı boş olamaz.")
18+
.MaximumLength(100)
19+
.WithMessage("Kategori adı en fazla 100 karakter olmalıdır.")
20+
.MinimumLength(3)
21+
.WithMessage("Kategori adı en az 3 karakter olmalıdır.");
22+
23+
RuleFor(c => c.Description)
24+
.NotEmpty()
25+
.When(c => !string.IsNullOrWhiteSpace(c.Description))
26+
.WithMessage("Kategori açıklaması boş olamaz.")
27+
.MaximumLength(1000)
28+
.WithMessage("Kategori açıklaması en fazla 1000 karakter olmalıdır.")
29+
.MinimumLength(10)
30+
.WithMessage("Kategori açıklaması en az 10 karakter olmalıdır.");
31+
32+
RuleFor(c => c.Name)
33+
.MustAsync(IsCategoryNameUniqueAsync)
34+
.WithMessage("Bu kategori adı zaten mevcuttur.");
35+
}
36+
37+
private async Task<bool> IsCategoryNameUniqueAsync(string name, CancellationToken cancellationToken)
38+
{
39+
return !await _dbContext
40+
.Categories
41+
.AnyAsync(c => c.Name.ToLower() == name.ToLower(), cancellationToken);
42+
}
43+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
namespace YazilimAcademy.Application.Features.Categories.Queries.GetAll;
2+
3+
public sealed record GetAllCategoriesDto
4+
{
5+
public Guid Id { get; set; }
6+
public string Name { get; set; }
7+
8+
public GetAllCategoriesDto(Guid id, string name)
9+
{
10+
Id = id;
11+
Name = name;
12+
}
13+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
using MediatR;
2+
using YazilimAcademy.Application.Common.Models.Pagination;
3+
4+
namespace YazilimAcademy.Application.Features.Categories.Queries.GetAll;
5+
6+
public sealed record GetAllCategoriesQuery(int PageNumber, int PageSize) : IRequest<PaginatedList<GetAllCategoriesDto>>;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
using Dapper;
2+
using MediatR;
3+
using YazilimAcademy.Application.Common.Interfaces;
4+
using YazilimAcademy.Application.Common.Models.Pagination;
5+
6+
namespace YazilimAcademy.Application.Features.Categories.Queries.GetAll;
7+
8+
public sealed class GetAllCategoriesQueryHandler : IRequestHandler<GetAllCategoriesQuery, PaginatedList<GetAllCategoriesDto>>
9+
{
10+
private readonly ISqlConnectionFactory _sqlConnectionFactory;
11+
12+
public GetAllCategoriesQueryHandler(ISqlConnectionFactory sqlConnectionFactory)
13+
{
14+
_sqlConnectionFactory = sqlConnectionFactory;
15+
}
16+
17+
public async Task<PaginatedList<GetAllCategoriesDto>> Handle(GetAllCategoriesQuery request, CancellationToken cancellationToken)
18+
{
19+
var connection = _sqlConnectionFactory.CreateConnection();
20+
21+
var offset = (request.PageNumber - 1) * request.PageSize;
22+
var pageSize = request.PageSize;
23+
24+
var sql = @"
25+
SELECT COUNT(*) FROM ""categories"";
26+
27+
SELECT ""Id"", ""Name""
28+
FROM ""categories""
29+
ORDER BY ""Name""
30+
OFFSET @Offset LIMIT @PageSize;
31+
";
32+
33+
using var multi = await connection.QueryMultipleAsync(sql, new { Offset = offset, PageSize = pageSize });
34+
35+
var totalCount = await multi.ReadSingleAsync<int>();
36+
37+
var categories = (await multi.ReadAsync<GetAllCategoriesDto>()).ToList();
38+
39+
return new PaginatedList<GetAllCategoriesDto>(categories, totalCount, request.PageNumber, request.PageSize);
40+
}
41+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
using FluentValidation;
2+
3+
namespace YazilimAcademy.Application.Features.Categories.Queries.GetAll;
4+
5+
public sealed class GetAllCategoriesQueryValidator : AbstractValidator<GetAllCategoriesQuery>
6+
{
7+
public GetAllCategoriesQueryValidator()
8+
{
9+
RuleFor(x => x.PageNumber)
10+
.GreaterThan(0)
11+
.WithMessage("Sayfa numarası 1'den küçük olamaz.");
12+
13+
14+
RuleFor(x => x.PageSize)
15+
.GreaterThan(0)
16+
.WithMessage("Sayfa boyutu 1'den küçük olamaz.")
17+
.LessThan(100)
18+
.WithMessage("Sayfa boyutu 100'den büyük olamaz.");
19+
}
20+
}

src/YazilimAcademy.Application/YazilimAcademy.Application.csproj

+11
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,15 @@
1010
<ProjectReference Include="..\YazilimAcademy.Domain\YazilimAcademy.Domain.csproj" />
1111
</ItemGroup>
1212

13+
<ItemGroup>
14+
<PackageReference Include="Dapper" Version="2.1.35" />
15+
<PackageReference Include="FluentValidation.DependencyInjectionExtensions" Version="11.11.0" />
16+
<PackageReference Include="MediatR" Version="12.4.1" />
17+
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.0" />
18+
</ItemGroup>
19+
20+
<ItemGroup>
21+
<Folder Include="Features\Categories\Queries\GetById\" />
22+
</ItemGroup>
23+
1324
</Project>

src/YazilimAcademy.Domain/Entities/Category.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
namespace YazilimAcademy.Domain.Entities;
44

5-
public class Category : EntityBase
5+
public sealed class Category : EntityBase
66
{
77
public string Name { get; private set; }
88
public string? Description { get; private set; }

src/YazilimAcademy.Domain/Entities/Course.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
namespace YazilimAcademy.Domain.Entities;
55

6-
public class Course : EntityBase
6+
public sealed class Course : EntityBase
77
{
88
public string Title { get; private set; }
99
public string? SubTitle { get; private set; }

src/YazilimAcademy.Domain/Entities/CourseCategory.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
namespace YazilimAcademy.Domain.Entities;
55

6-
public class CourseCategory : EntityBase
6+
public sealed class CourseCategory : EntityBase
77
{
88
public Guid CourseId { get; set; }
99
public Course Course { get; set; }

src/YazilimAcademy.Domain/Entities/CourseLecture.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
namespace YazilimAcademy.Domain.Entities;
55

6-
public class CourseLecture : EntityBase
6+
public sealed class CourseLecture : EntityBase
77
{
88
public Guid SectionId { get; set; }
99
public CourseSection Section { get; set; }

src/YazilimAcademy.Domain/Entities/CourseLectureResource.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
namespace YazilimAcademy.Domain.Entities;
44

5-
public class CourseLectureResource : EntityBase
5+
public sealed class CourseLectureResource : EntityBase
66
{
77
public Guid LectureId { get; set; }
88
public CourseLecture Lecture { get; set; }

src/YazilimAcademy.Domain/Entities/CourseSection.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
namespace YazilimAcademy.Domain.Entities;
55

6-
public class CourseSection : EntityBase
6+
public sealed class CourseSection : EntityBase
77
{
88
public Guid CourseId { get; set; }
99
public Course Course { get; set; }

src/YazilimAcademy.Domain/Entities/User.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
namespace YazilimAcademy.Domain.Entities;
55

6-
public class User : EntityBase
6+
public sealed class User : EntityBase
77
{
88
public Email Email { get; set; }
99
public FullName FullName { get; set; }

src/YazilimAcademy.Domain/YazilimAcademy.Domain.csproj

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
</ItemGroup>
1313

1414
<ItemGroup>
15-
<PackageReference Include="IdGen" Version="3.0.7" />
15+
<PackageReference Include="MediatR.Contracts" Version="2.0.1" />
1616
</ItemGroup>
1717

1818
</Project>

0 commit comments

Comments
 (0)