diff --git a/Dentizone.Application/AutoMapper/Answers/AnswerProfile.cs b/Dentizone.Application/AutoMapper/Answers/AnswerProfile.cs new file mode 100644 index 0000000..7ad1976 --- /dev/null +++ b/Dentizone.Application/AutoMapper/Answers/AnswerProfile.cs @@ -0,0 +1,21 @@ +using AutoMapper; + +namespace Dentizone.Application.AutoMapper.Answers +{ + public class AnswerProfile : Profile + { + public AnswerProfile() + { + CreateMap() + .ForMember(dest => dest.Id, opt => opt.MapFrom(src => Guid.NewGuid().ToString())) + .ForMember(dest => dest.CreatedAt, opt => opt.MapFrom(src => DateTime.UtcNow)) + .ReverseMap(); + CreateMap() + .ForMember(dest => dest.Text, opt => opt.MapFrom(src => src.Text)) + .ForMember(dest => dest.UpdatedAt, opt => opt.MapFrom(src => DateTime.UtcNow)) + .ReverseMap(); + CreateMap() + .ReverseMap(); + } + } +} \ No newline at end of file diff --git a/Dentizone.Application/AutoMapper/Questions/QuestionProfile.cs b/Dentizone.Application/AutoMapper/Questions/QuestionProfile.cs new file mode 100644 index 0000000..6eed682 --- /dev/null +++ b/Dentizone.Application/AutoMapper/Questions/QuestionProfile.cs @@ -0,0 +1,27 @@ +using AutoMapper; +using Dentizone.Application.DTOs.Q_A.QuestionDTO; + +namespace Dentizone.Application.AutoMapper.Questions +{ + public class QuestionProfile : Profile + { + public QuestionProfile() + { + CreateMap() + .ForMember(dest => dest.Id, opt => opt.MapFrom(src => Guid.NewGuid().ToString())) + .ForMember(dest => dest.CreatedAt, opt => opt.MapFrom(src => DateTime.UtcNow)) + .ReverseMap(); + CreateMap() + .ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.Id)) + .ForMember(dest => dest.User, opt => opt.Ignore()) + .ForMember(dest => dest.Text, opt => opt.MapFrom(src => src.Text)) + .ForMember(dest => dest.CreatedAt, opt => opt.MapFrom(src => src.CreatedAt)) + .ReverseMap() + .ForMember(dest => dest.AskerName, opt => opt.MapFrom(src => src.User != null ? src.User.FullName : string.Empty)); + CreateMap() + .ForMember(dest => dest.Text, opt => opt.MapFrom(src => src.Text)) + .ForMember(dest => dest.UpdatedAt, opt => opt.MapFrom(src => DateTime.UtcNow)) + .ReverseMap(); + } + } +} \ No newline at end of file diff --git a/Dentizone.Application/AutoMapper/WithdrawalProfile.cs b/Dentizone.Application/AutoMapper/WithdrawalProfile.cs index 43e6ed0..cfaaafd 100644 --- a/Dentizone.Application/AutoMapper/WithdrawalProfile.cs +++ b/Dentizone.Application/AutoMapper/WithdrawalProfile.cs @@ -1,15 +1,10 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using AutoMapper; +using AutoMapper; using Dentizone.Application.DTOs.Withdrawal; using Dentizone.Domain.Entity; namespace Dentizone.Application.AutoMapper { - public class WithdrawalProfile: Profile + public class WithdrawalProfile : Profile { public WithdrawalProfile() { @@ -18,6 +13,5 @@ public WithdrawalProfile() .ForMember(dest => dest.UserId, opt => opt.MapFrom(src => src.Wallet.UserId.ToString())) .ReverseMap(); } - } -} +} \ No newline at end of file diff --git a/Dentizone.Application/DI/Services.cs b/Dentizone.Application/DI/Services.cs index 61debf4..0d4fff0 100644 --- a/Dentizone.Application/DI/Services.cs +++ b/Dentizone.Application/DI/Services.cs @@ -45,6 +45,7 @@ public static IServiceCollection AddApplicationServices(this IServiceCollection services.AddScoped(); services.AddScoped(); + services.AddScoped(); return services; } } diff --git a/Dentizone.Application/DTOs/Q&A/AnswerDTO/AnswerViewDto.cs b/Dentizone.Application/DTOs/Q&A/AnswerDTO/AnswerViewDto.cs new file mode 100644 index 0000000..4ca4fa4 --- /dev/null +++ b/Dentizone.Application/DTOs/Q&A/AnswerDTO/AnswerViewDto.cs @@ -0,0 +1,29 @@ +using FluentValidation; + +namespace Dentizone.Application.DTOs.Q_A.AnswerDTO +{ + public class AnswerViewDto + { + public string Id { get; set; } + public string ResponderName { get; set; } + public string Text { get; set; } + public DateTime CreatedAt { get; set; } + } + + public class AnswerViewDtoValidator : AbstractValidator + { + public AnswerViewDtoValidator() + { + RuleFor(x => x.Id).NotEmpty().WithMessage("Id is required."); + RuleFor(x => x.ResponderName) + .NotEmpty().WithMessage("ResponderName is required.") + .MaximumLength(100).WithMessage("ResponderName cannot exceed 100 characters."); + RuleFor(x => x.Text) + .NotEmpty().WithMessage("Text is required.") + .MaximumLength(500).WithMessage("Text cannot exceed 500 characters."); + RuleFor(x => x.CreatedAt) + .Must(date => date != default).WithMessage("CreatedAt must be a valid date.") + .LessThanOrEqualTo(DateTime.UtcNow).WithMessage("CreatedAt cannot be in the future."); + } + } +} \ No newline at end of file diff --git a/Dentizone.Application/DTOs/Q&A/AnswerDTO/CreateAnswerDto.cs b/Dentizone.Application/DTOs/Q&A/AnswerDTO/CreateAnswerDto.cs new file mode 100644 index 0000000..c07f497 --- /dev/null +++ b/Dentizone.Application/DTOs/Q&A/AnswerDTO/CreateAnswerDto.cs @@ -0,0 +1,20 @@ +using FluentValidation; + +namespace Dentizone.Application.DTOs.Q_A.AnswerDTO +{ + public class CreateAnswerDto + { + public string Text { get; set; } + } + + public class CreateAnswerDtoValidator : AbstractValidator + { + public CreateAnswerDtoValidator() + { + RuleFor(x => x.Text) + .NotEmpty().WithMessage("Text is required.") + .MaximumLength(500).WithMessage("Text cannot exceed 500 characters.") + .Must(text => !string.IsNullOrWhiteSpace(text)).WithMessage("Text cannot be whitespace only."); + } + } +} \ No newline at end of file diff --git a/Dentizone.Application/DTOs/Q&A/AnswerDTO/UpdateAnswerDto.cs b/Dentizone.Application/DTOs/Q&A/AnswerDTO/UpdateAnswerDto.cs new file mode 100644 index 0000000..e1fb393 --- /dev/null +++ b/Dentizone.Application/DTOs/Q&A/AnswerDTO/UpdateAnswerDto.cs @@ -0,0 +1,17 @@ +using FluentValidation; + +namespace Dentizone.Application.DTOs.Q_A.AnswerDTO +{ + public class UpdateAnswerDto + { + public string Text { get; set; } + } + + public class UpdateAnswerDtoValidator : AbstractValidator + { + public UpdateAnswerDtoValidator() + { + RuleFor(x => x.Text).NotEmpty().WithMessage("Text is required."); + } + } +} \ No newline at end of file diff --git a/Dentizone.Application/DTOs/Q&A/QuestionDTO/CreateQuestionDto.cs b/Dentizone.Application/DTOs/Q&A/QuestionDTO/CreateQuestionDto.cs new file mode 100644 index 0000000..4685c38 --- /dev/null +++ b/Dentizone.Application/DTOs/Q&A/QuestionDTO/CreateQuestionDto.cs @@ -0,0 +1,21 @@ +using Dentizone.Application.Validators; +using FluentValidation; + +namespace Dentizone.Application.DTOs.Q_A.QuestionDTO +{ +#nullable enable + public class CreateQuestionDto + { + public string PostId { get; set; } + public string? Text { get; set; } + } + + public class CreateQuestionDtoValidator : AbstractValidator + { + public CreateQuestionDtoValidator() + { + RuleFor(x => x.PostId).NotEmpty().WithMessage("PostId is required.").MustBeParsableGuid(); + RuleFor(x => x.Text).NotEmpty().WithMessage("Text is required."); + } + } +} \ No newline at end of file diff --git a/Dentizone.Application/DTOs/Q&A/QuestionDTO/QuestionViewDto.cs b/Dentizone.Application/DTOs/Q&A/QuestionDTO/QuestionViewDto.cs new file mode 100644 index 0000000..b25559a --- /dev/null +++ b/Dentizone.Application/DTOs/Q&A/QuestionDTO/QuestionViewDto.cs @@ -0,0 +1,25 @@ +using Dentizone.Application.DTOs.Q_A.AnswerDTO; +using FluentValidation; + +namespace Dentizone.Application.DTOs.Q_A.QuestionDTO +{ + public class QuestionViewDto + { + public string Id { get; set; } + public string AskerName { get; set; } + public string Text { get; set; } + public AnswerViewDto? Answer { get; set; } + public DateTime CreatedAt { get; set; } + } + + public class QuestionViewDtoValidator : AbstractValidator + { + public QuestionViewDtoValidator() + { + RuleFor(x => x.Id).NotEmpty().WithMessage("Id is required."); + RuleFor(x => x.AskerName).NotEmpty().WithMessage("AskerName is required."); + RuleFor(x => x.Text).NotEmpty().WithMessage("Text is required."); + RuleFor(x => x.CreatedAt).NotEmpty().WithMessage("CreatedAt is required."); + } + } +} \ No newline at end of file diff --git a/Dentizone.Application/DTOs/Q&A/QuestionDTO/UpdateQuestionDto.cs b/Dentizone.Application/DTOs/Q&A/QuestionDTO/UpdateQuestionDto.cs new file mode 100644 index 0000000..a9f6bec --- /dev/null +++ b/Dentizone.Application/DTOs/Q&A/QuestionDTO/UpdateQuestionDto.cs @@ -0,0 +1,19 @@ +using FluentValidation; + +namespace Dentizone.Application.DTOs.Q_A.QuestionDTO +{ + public class UpdateQuestionDto + { + public string? Text { get; set; } + } + + public class UpdateQuestionDtoValidator : AbstractValidator + { + public UpdateQuestionDtoValidator() + { + RuleFor(x => x.Text) + .NotEmpty().WithMessage("Text is required.") + .MaximumLength(500).WithMessage("Text cannot exceed 500 characters."); + } + } +} \ No newline at end of file diff --git a/Dentizone.Application/DTOs/Withdrawal/ApproveWithdrawalDto.cs b/Dentizone.Application/DTOs/Withdrawal/ApproveWithdrawalDto.cs index 9b150f1..8e91bda 100644 --- a/Dentizone.Application/DTOs/Withdrawal/ApproveWithdrawalDto.cs +++ b/Dentizone.Application/DTOs/Withdrawal/ApproveWithdrawalDto.cs @@ -1,13 +1,7 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Dentizone.Application.DTOs.Withdrawal +namespace Dentizone.Application.DTOs.Withdrawal { public class ApproveWithdrawalDto { public string AdminNote { get; set; } } -} +} \ No newline at end of file diff --git a/Dentizone.Application/DTOs/Withdrawal/WithdrawalRequestDto.cs b/Dentizone.Application/DTOs/Withdrawal/WithdrawalRequestDto.cs index d80a122..b051717 100644 --- a/Dentizone.Application/DTOs/Withdrawal/WithdrawalRequestDto.cs +++ b/Dentizone.Application/DTOs/Withdrawal/WithdrawalRequestDto.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using FluentValidation; +using FluentValidation; namespace Dentizone.Application.DTOs.Withdrawal { @@ -24,4 +19,4 @@ public WithdrawalRequestDtoValidator() .NotEmpty().WithMessage("Wallet ID is required."); } } -} +} \ No newline at end of file diff --git a/Dentizone.Application/Interfaces/IQAService.cs b/Dentizone.Application/Interfaces/IQAService.cs new file mode 100644 index 0000000..d1f743a --- /dev/null +++ b/Dentizone.Application/Interfaces/IQAService.cs @@ -0,0 +1,16 @@ +using Dentizone.Application.DTOs.Q_A.AnswerDTO; +using Dentizone.Application.DTOs.Q_A.QuestionDTO; + +namespace Dentizone.Application.Interfaces +{ + public interface IQAService + { + Task AskQuestionAsync(CreateQuestionDto dto, string askerId); + Task> GetQuestionsForPostAsync(string postId); + Task AnswerQuestionAsync(string questionId, CreateAnswerDto dto, string responderId); + Task UpdateQuestionAsync(string questionId, UpdateQuestionDto dto); + Task DeleteQuestionAsync(string questionId); + Task UpdateAnswerAsync(string answerId, UpdateAnswerDto dto); + Task DeleteAnswerAsync(string answerId); + } +} \ No newline at end of file diff --git a/Dentizone.Application/Interfaces/IWithdrawalService.cs b/Dentizone.Application/Interfaces/IWithdrawalService.cs index d548ea0..9124433 100644 --- a/Dentizone.Application/Interfaces/IWithdrawalService.cs +++ b/Dentizone.Application/Interfaces/IWithdrawalService.cs @@ -1,20 +1,14 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Dentizone.Application.DTOs.Withdrawal; -using Dentizone.Domain.Entity; +using Dentizone.Application.DTOs.Withdrawal; namespace Dentizone.Application.Interfaces { public interface IWithdrawalService { - Task CreateWithdrawalRequestAsync(string userId, WithdrawalRequestDto withdrawalRequestDto); + Task CreateWithdrawalRequestAsync(string userId, + WithdrawalRequestDto withdrawalRequestDto); + Task> GetWithdrawalHistoryAsync(string userId, int page); Task ApproveWithdrawalAsync(string id, string adminNote); Task RejectWithdrawalAsync(string id, string adminNote); } -} - - +} \ No newline at end of file diff --git a/Dentizone.Application/Interfaces/Order/IShippingService.cs b/Dentizone.Application/Interfaces/Order/IShippingService.cs index 6b1884d..d92b031 100644 --- a/Dentizone.Application/Interfaces/Order/IShippingService.cs +++ b/Dentizone.Application/Interfaces/Order/IShippingService.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Dentizone.Domain.Enums; +using Dentizone.Domain.Enums; namespace Dentizone.Application.Interfaces.Order { diff --git a/Dentizone.Application/Services/QAService.cs b/Dentizone.Application/Services/QAService.cs new file mode 100644 index 0000000..da42dcf --- /dev/null +++ b/Dentizone.Application/Services/QAService.cs @@ -0,0 +1,132 @@ +using System.Linq.Expressions; +using AutoMapper; +using Dentizone.Application.DTOs.Q_A.AnswerDTO; +using Dentizone.Application.DTOs.Q_A.QuestionDTO; +using Dentizone.Application.Interfaces; +using Dentizone.Domain.Entity; +using Dentizone.Domain.Enums; +using Dentizone.Domain.Exceptions; +using Dentizone.Domain.Interfaces.Repositories; + +namespace Dentizone.Application.Services +{ + public class QAService(IMapper mapper, IAnswerRepository answerRepository, IQuestionRepository questionRepository) + : IQAService + { + public async Task AnswerQuestionAsync(string questionId, CreateAnswerDto dto, string responderId) + { + var question = await questionRepository.GetByIdAsync(questionId); + if (question == null) + { + throw new NotFoundException("Question not found"); + } + + var post = question.Post; + if (post == null) + { + throw new NotFoundException("Post not found"); + } + + if (responderId != post.SellerId) + { + throw new UnauthorizedAccessException("You are not authorized to answer this question"); + } + + var existingAnswer = await answerRepository.FindBy(a => a.QuestionId == questionId && !a.IsDeleted); + if (existingAnswer != null) + { + throw new BadActionException("This question has already been answered."); + } + + var answer = mapper.Map(dto); + question.Status = QuestionStatus.Answered; + await answerRepository.CreateAsync(answer); + await questionRepository.UpdateAsync(question); + return mapper.Map(answer); + } + + public async Task AskQuestionAsync(CreateQuestionDto dto, string askerId) + { + var post = questionRepository.GetByIdAsync(dto.PostId); + if (post == null) + { + throw new NotFoundException("Post not found"); + } + + var question = mapper.Map(dto); + question.AskerId = askerId; + question.Status = QuestionStatus.Unanswered; + await questionRepository.CreateAsync(question); + return mapper.Map(question); + } + + public async Task DeleteAnswerAsync(string answerId) + { + var answer = await answerRepository.GetByIdAsync(answerId); + var question = await questionRepository.GetByIdAsync(answer.QuestionId); + var post = question.Post; + if (post == null) + { + throw new ArgumentException("Post not found for the question", nameof(answerId)); + } + + await answerRepository.DeleteAsync(answerId); + question.Status = QuestionStatus.Unanswered; + await questionRepository.UpdateAsync(question); + } + + public async Task DeleteQuestionAsync(string questionId) + { + var question = await questionRepository.GetByIdAsync(questionId); + if (question == null) + { + throw new ArgumentException("Question not found", nameof(questionId)); + } + + var post = question.Post; + if (post == null) + { + throw new ArgumentException("Post not found for the question", nameof(questionId)); + } + + await questionRepository.DeleteAsync(questionId); + } + + public async Task> GetQuestionsForPostAsync(string postId) + { + var includes = new Expression>[] + { + q => q.Answer + }; + + var questions = await questionRepository.FindAllBy( + q => q.PostId == postId && !q.IsDeleted, + includes); + if (questions == null || !questions.Any()) + { + return []; + } + + return mapper.Map>(questions); + } + + public async Task UpdateAnswerAsync(string answerId, UpdateAnswerDto dto) + { + var answer = await answerRepository.GetByIdAsync(answerId); + answer.Text = dto.Text; + await answerRepository.UpdateAsync(answer); + } + + public async Task UpdateQuestionAsync(string questionId, UpdateQuestionDto dto) + { + var question = await questionRepository.GetByIdAsync(questionId); + if (question == null) + { + throw new ArgumentException("Question not found", nameof(questionId)); + } + + question.Text = dto.Text; + await questionRepository.UpdateAsync(question); + } + } +} \ No newline at end of file diff --git a/Dentizone.Application/Services/ShippingService.cs b/Dentizone.Application/Services/ShippingService.cs index 22861cc..89b812f 100644 --- a/Dentizone.Application/Services/ShippingService.cs +++ b/Dentizone.Application/Services/ShippingService.cs @@ -1,18 +1,10 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Linq.Expressions; -using System.Text; -using System.Threading.Tasks; -using Dentizone.Application.Interfaces; -using Dentizone.Application.Interfaces.Order; +using Dentizone.Application.Interfaces.Order; using Dentizone.Application.Services.Authentication; using Dentizone.Domain.Entity; using Dentizone.Domain.Enums; using Dentizone.Domain.Exceptions; using Dentizone.Domain.Interfaces.Mail; using Dentizone.Domain.Interfaces.Repositories; -using Microsoft.Extensions.Hosting; namespace Dentizone.Application.Services { diff --git a/Dentizone.Application/Validators/GUIDValidator.cs b/Dentizone.Application/Validators/GUIDValidator.cs new file mode 100644 index 0000000..9b4fdbd --- /dev/null +++ b/Dentizone.Application/Validators/GUIDValidator.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using FluentValidation; + +namespace Dentizone.Application.Validators +{ + public static class GUIDValidator + { + public static IRuleBuilderOptions MustBeParsableGuid(this IRuleBuilder ruleBuilder) + { + return ruleBuilder + .Must(value => Guid.TryParse(value, out var g) && g != Guid.Empty) + .WithMessage("Invalid or empty GUID."); + } + } +} \ No newline at end of file diff --git a/Dentizone.Domain/Interfaces/Repositories/IBaseRepo.cs b/Dentizone.Domain/Interfaces/Repositories/IBaseRepo.cs index e2637d4..49ac43d 100644 --- a/Dentizone.Domain/Interfaces/Repositories/IBaseRepo.cs +++ b/Dentizone.Domain/Interfaces/Repositories/IBaseRepo.cs @@ -8,6 +8,6 @@ public interface IBaseRepo where TEntity : class Task CreateAsync(TEntity entity); Task FindBy(Expression> condition, - Expression>[]? includes = null); + Expression>[]? includes = null); } } \ No newline at end of file diff --git a/Dentizone.Domain/Interfaces/Repositories/IQuestionRepository.cs b/Dentizone.Domain/Interfaces/Repositories/IQuestionRepository.cs index 8dc25c3..fea350a 100644 --- a/Dentizone.Domain/Interfaces/Repositories/IQuestionRepository.cs +++ b/Dentizone.Domain/Interfaces/Repositories/IQuestionRepository.cs @@ -1,4 +1,5 @@ -using Dentizone.Domain.Entity; +using System.Linq.Expressions; +using Dentizone.Domain.Entity; namespace Dentizone.Domain.Interfaces.Repositories { @@ -6,5 +7,6 @@ public interface IQuestionRepository : IBaseRepo { Task UpdateAsync(Question entity); Task DeleteAsync(string id); + Task> FindAllBy(Expression> condition, Expression>[]? includes = null); } } \ No newline at end of file diff --git a/Dentizone.Infrastructure/DependencyInjection/AddRepositories.cs b/Dentizone.Infrastructure/DependencyInjection/AddRepositories.cs index 05c564a..1361146 100644 --- a/Dentizone.Infrastructure/DependencyInjection/AddRepositories.cs +++ b/Dentizone.Infrastructure/DependencyInjection/AddRepositories.cs @@ -25,6 +25,8 @@ public static IServiceCollection AddRepositories(this IServiceCollection service services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); + services.AddScoped(); services.AddScoped(); services.AddScoped(); diff --git a/Dentizone.Infrastructure/Repositories/AnswerRepository.cs b/Dentizone.Infrastructure/Repositories/AnswerRepository.cs index 5b9920f..a06ba9b 100644 --- a/Dentizone.Infrastructure/Repositories/AnswerRepository.cs +++ b/Dentizone.Infrastructure/Repositories/AnswerRepository.cs @@ -16,13 +16,13 @@ public async Task CreateAsync(Answer entity) public async Task FindBy(Expression> condition, - Expression>[]? includes) + Expression>[]? includes) { var query = dbContext.Answers.AsQueryable(); if (includes == null) return await dbContext.Answers - .FirstOrDefaultAsync(condition); + .FirstOrDefaultAsync(condition); foreach (var include in includes) diff --git a/Dentizone.Infrastructure/Repositories/AssetRepository.cs b/Dentizone.Infrastructure/Repositories/AssetRepository.cs index 7b75000..eeaece8 100644 --- a/Dentizone.Infrastructure/Repositories/AssetRepository.cs +++ b/Dentizone.Infrastructure/Repositories/AssetRepository.cs @@ -15,7 +15,7 @@ public async Task CreateAsync(Asset entity) } public async Task FindBy(Expression> condition, - Expression>[]? includes = null) + Expression>[]? includes = null) { var query = dbContext.Assets.AsQueryable(); if (includes == null) @@ -49,7 +49,7 @@ public async Task DeleteByIdAsync(string assetId) public async Task GetByIdAsync(string id) { return await dbContext.Assets - .FirstOrDefaultAsync(a => a.Id == id && !a.IsDeleted); + .FirstOrDefaultAsync(a => a.Id == id && !a.IsDeleted); } public async Task UpdateAsync(Asset entity) diff --git a/Dentizone.Infrastructure/Repositories/CartRepository.cs b/Dentizone.Infrastructure/Repositories/CartRepository.cs index ba9a44d..3239d9e 100644 --- a/Dentizone.Infrastructure/Repositories/CartRepository.cs +++ b/Dentizone.Infrastructure/Repositories/CartRepository.cs @@ -10,7 +10,7 @@ internal class CartRepository(AppDbContext dbContext) : AbstractRepository(dbCon public async Task GetByIdAsync(string id) { return await dbContext.Carts - .FirstOrDefaultAsync(c => c.Id == id && !c.IsDeleted); + .FirstOrDefaultAsync(c => c.Id == id && !c.IsDeleted); } @@ -22,7 +22,7 @@ public async Task CreateAsync(Cart entity) } public async Task FindBy(Expression> condition, - Expression>[]? includes) + Expression>[]? includes) { IQueryable query = dbContext.Carts; if (includes == null) return await query.FirstOrDefaultAsync(condition); @@ -48,7 +48,7 @@ public async Task CreateAsync(Cart entity) } public async Task> FindAllBy(Expression> condition, - Expression>[]? includes = null) + Expression>[]? includes = null) { IQueryable query = dbContext.Carts; @@ -66,11 +66,11 @@ public async Task> FindAllBy(Expression> cond public async Task> GetCartItemsByUserId(string userId) { var baseQuery = dbContext.Carts - .Where(c => c.UserId == userId && !c.IsDeleted) - .Include(c => c.User) - .Include(c => c.Post) - .ThenInclude(p => p.PostAssets) - .ThenInclude(pa => pa.Asset); + .Where(c => c.UserId == userId && !c.IsDeleted) + .Include(c => c.User) + .Include(c => c.Post) + .ThenInclude(p => p.PostAssets) + .ThenInclude(pa => pa.Asset); return await baseQuery.ToListAsync(); } diff --git a/Dentizone.Infrastructure/Repositories/CategoryRepository.cs b/Dentizone.Infrastructure/Repositories/CategoryRepository.cs index bd66255..c663e91 100644 --- a/Dentizone.Infrastructure/Repositories/CategoryRepository.cs +++ b/Dentizone.Infrastructure/Repositories/CategoryRepository.cs @@ -17,7 +17,7 @@ public async Task CreateAsync(Category entity) } public async Task FindBy(Expression> condition - , Expression>[]? includes) + , Expression>[]? includes) { IQueryable query = DbContext.Categories; if (includes == null) return await query.FirstOrDefaultAsync(condition); @@ -33,7 +33,7 @@ public async Task CreateAsync(Category entity) public async Task GetByIdAsync(string id) { var category = await DbContext.Categories.Where(c => c.Id == id && !c.IsDeleted) - .FirstOrDefaultAsync(); + .FirstOrDefaultAsync(); return category; } @@ -61,8 +61,8 @@ public async Task CreateAsync(Category entity) public async Task> GetAll() { return await DbContext.Categories - .Where(c => !c.IsDeleted) - .ToListAsync(); + .Where(c => !c.IsDeleted) + .ToListAsync(); } } } \ No newline at end of file diff --git a/Dentizone.Infrastructure/Repositories/FavouriteRepository.cs b/Dentizone.Infrastructure/Repositories/FavouriteRepository.cs index f1339e7..69dcc7f 100644 --- a/Dentizone.Infrastructure/Repositories/FavouriteRepository.cs +++ b/Dentizone.Infrastructure/Repositories/FavouriteRepository.cs @@ -10,7 +10,7 @@ public class FavouriteRepository(AppDbContext dbContext) : AbstractRepository(db public async Task GetByIdAsync(string id) { return await dbContext.Favourites - .FirstOrDefaultAsync(f => f.Id == id && !f.IsDeleted); + .FirstOrDefaultAsync(f => f.Id == id && !f.IsDeleted); } @@ -22,7 +22,7 @@ public async Task CreateAsync(Favourite entity) } public async Task FindBy(Expression> condition, - Expression>[]? incldues) + Expression>[]? incldues) { IQueryable query = dbContext.Favourites; if (incldues != null) @@ -54,10 +54,10 @@ public async Task> FindAllByAsync(Expression query = dbContext.Favourites; query = query.Include(f => f.Post) - .ThenInclude(pa => pa.PostAssets) - .ThenInclude(pa => pa.Asset) - .AsNoTracking() - .AsSplitQuery(); + .ThenInclude(pa => pa.PostAssets) + .ThenInclude(pa => pa.Asset) + .AsNoTracking() + .AsSplitQuery(); query = query.Include(f => f.Post.Seller); return await query.Where(condition).ToListAsync(); diff --git a/Dentizone.Infrastructure/Repositories/OrderItemRepository.cs b/Dentizone.Infrastructure/Repositories/OrderItemRepository.cs index dceb5d9..58de54b 100644 --- a/Dentizone.Infrastructure/Repositories/OrderItemRepository.cs +++ b/Dentizone.Infrastructure/Repositories/OrderItemRepository.cs @@ -11,7 +11,7 @@ public class OrderItemRepository(AppDbContext dbContext) : AbstractRepository(db { return await dbContext.OrderItems - .FirstOrDefaultAsync(o => o.Id == id); + .FirstOrDefaultAsync(o => o.Id == id); } @@ -23,7 +23,7 @@ public async Task CreateAsync(OrderItem entity) } public async Task FindBy(Expression> condition, - Expression>[]? includes) + Expression>[]? includes) { IQueryable query = dbContext.OrderItems; if (includes != null) diff --git a/Dentizone.Infrastructure/Repositories/OrderPickupRepository.cs b/Dentizone.Infrastructure/Repositories/OrderPickupRepository.cs index f876c65..3e92919 100644 --- a/Dentizone.Infrastructure/Repositories/OrderPickupRepository.cs +++ b/Dentizone.Infrastructure/Repositories/OrderPickupRepository.cs @@ -15,7 +15,7 @@ public async Task CreateAsync(OrderPickup entity) } public async Task FindBy(Expression> condition, - Expression>[]? includes) + Expression>[]? includes) { IQueryable query = dbContext.OrderPickups; if (includes != null) @@ -55,6 +55,6 @@ public async Task UpdateAsync(OrderPickup entity) public async Task GetByIdAsync(string id) { return await dbContext.OrderPickups - .FirstOrDefaultAsync(o => o.Id == id && !o.IsDeleted); + .FirstOrDefaultAsync(o => o.Id == id && !o.IsDeleted); } } \ No newline at end of file diff --git a/Dentizone.Infrastructure/Repositories/OrderStatusRepository.cs b/Dentizone.Infrastructure/Repositories/OrderStatusRepository.cs index b042d2c..7540ae4 100644 --- a/Dentizone.Infrastructure/Repositories/OrderStatusRepository.cs +++ b/Dentizone.Infrastructure/Repositories/OrderStatusRepository.cs @@ -10,7 +10,7 @@ public class OrderStatusRepository(AppDbContext dbContext) : AbstractRepository( public async Task GetByIdAsync(string id) { return await dbContext.OrderStatuses - .FirstOrDefaultAsync(o => o.Id == id); + .FirstOrDefaultAsync(o => o.Id == id); } @@ -22,7 +22,7 @@ public async Task CreateAsync(OrderStatus entity) } public async Task FindBy(Expression> condition, - Expression>[]? includes) + Expression>[]? includes) { IQueryable query = dbContext.OrderStatuses; if (includes != null) @@ -40,7 +40,7 @@ public async Task CreateAsync(OrderStatus entity) public async Task> Find(Expression> filter) { return await dbContext.OrderStatuses - .Where(filter) - .ToListAsync(); + .Where(filter) + .ToListAsync(); } } \ No newline at end of file diff --git a/Dentizone.Infrastructure/Repositories/PaymentRepository.cs b/Dentizone.Infrastructure/Repositories/PaymentRepository.cs index 6ff4f64..37dd762 100644 --- a/Dentizone.Infrastructure/Repositories/PaymentRepository.cs +++ b/Dentizone.Infrastructure/Repositories/PaymentRepository.cs @@ -15,7 +15,7 @@ public async Task CreateAsync(Payment entity) } public async Task FindBy(Expression> condition, - Expression>[]? includes) + Expression>[]? includes) { IQueryable query = dbContext.Payments; if (includes != null) diff --git a/Dentizone.Infrastructure/Repositories/PostAssetRepository.cs b/Dentizone.Infrastructure/Repositories/PostAssetRepository.cs index 965cac5..9c0a37d 100644 --- a/Dentizone.Infrastructure/Repositories/PostAssetRepository.cs +++ b/Dentizone.Infrastructure/Repositories/PostAssetRepository.cs @@ -15,7 +15,7 @@ public async Task CreateAsync(PostAsset entity) } public async Task FindBy(Expression> condition, - Expression>[]? includes) + Expression>[]? includes) { IQueryable query = dbContext.PostAssets; if (includes == null) return await query.FirstOrDefaultAsync(condition); diff --git a/Dentizone.Infrastructure/Repositories/PostRepsitory.cs b/Dentizone.Infrastructure/Repositories/PostRepsitory.cs index 05498f8..84ca4bb 100644 --- a/Dentizone.Infrastructure/Repositories/PostRepsitory.cs +++ b/Dentizone.Infrastructure/Repositories/PostRepsitory.cs @@ -16,7 +16,7 @@ public async Task CreateAsync(Post entity) } public async Task FindBy(Expression> condition, - Expression>[]? includes) + Expression>[]? includes) { IQueryable query = dbContext.Posts; if (includes != null) @@ -49,14 +49,14 @@ public async Task> GetAllAsync(int page) { int skippedPages = CalculatePagination(page); return await dbContext.Posts - .Skip(skippedPages) - .Take(DefaultPageSize) - .ToListAsync(); + .Skip(skippedPages) + .Take(DefaultPageSize) + .ToListAsync(); } public async Task> GetAllAsync(int page, Expression>? filter, - Expression>? orderBy, - Expression>[]? includes = null) + Expression>? orderBy, + Expression>[]? includes = null) { IQueryable query = dbContext.Posts; if (filter != null) @@ -82,8 +82,8 @@ public async Task> GetAllAsync(int page, Expression GetAllAsync(Expression>? filter, - Expression>? orderBy = null, - Expression>[]? includes = null) + Expression>? orderBy = null, + Expression>[]? includes = null) { IQueryable query = dbContext.Posts; if (filter != null) @@ -110,13 +110,13 @@ public IQueryable GetAllAsync(Expression>? filter, public async Task GetByIdAsync(string id) { return await dbContext.Posts - .Include(p => p.Seller) - .Include(p => p.Category) - .Include(p => p.SubCategory) - .Include(p => p.PostAssets) - .ThenInclude(p => p.Asset) - .ThenInclude(p => p.User.University) - .FirstOrDefaultAsync(p => p.Id == id); + .Include(p => p.Seller) + .Include(p => p.Category) + .Include(p => p.SubCategory) + .Include(p => p.PostAssets) + .ThenInclude(p => p.Asset) + .ThenInclude(p => p.User.University) + .FirstOrDefaultAsync(p => p.Id == id); } public async Task UpdateAsync(Post entity) @@ -140,20 +140,20 @@ public async Task UpdatePostStatus(string postId, PostStatus status) } public async Task> SearchAsync(string? keyword, string? city, string? category, - string? subcategory, PostItemCondition? condition, - decimal? minPrice, decimal? maxPrice, string? sortBy, - bool sortDirection, int page) + string? subcategory, PostItemCondition? condition, + decimal? minPrice, decimal? maxPrice, string? sortBy, + bool sortDirection, int page) { var posts = dbContext.Posts - .Where(p => !p.IsDeleted && p.Status == PostStatus.Active) - .AsQueryable(); + .Where(p => !p.IsDeleted && p.Status == PostStatus.Active) + .AsQueryable(); if (!string.IsNullOrWhiteSpace(keyword)) { var kw = keyword.Trim().ToLower(); posts = posts.Where(p => - p.Title.ToLower().Contains(kw) || - p.Description.ToLower().Contains(kw)); + p.Title.ToLower().Contains(kw) || + p.Description.ToLower().Contains(kw)); } if (!string.IsNullOrWhiteSpace(city)) @@ -209,12 +209,12 @@ public async Task AveragePostsPriceAsync() public async Task> GetPostCountPerCategoryAsync() { var result = await dbContext.Posts - .AsNoTracking() - .Include(p => p.Category) - .Where(p => !p.IsDeleted && !p.Category.IsDeleted) - .GroupBy(p => p.Category.Name) - .AsSplitQuery() - .ToDictionaryAsync(g => g.Key, g => g.Count()); + .AsNoTracking() + .Include(p => p.Category) + .Where(p => !p.IsDeleted && !p.Category.IsDeleted) + .GroupBy(p => p.Category.Name) + .AsSplitQuery() + .ToDictionaryAsync(g => g.Key, g => g.Count()); return result; } @@ -222,10 +222,10 @@ public async Task> GetPostCountPerCategoryAsync() public async Task> ValidatePostsByState(List postIds, PostStatus state) { var posts = await dbContext.Posts - .Where(p => postIds.Contains(p.Id) && p.Status == state) - .Include(p => p.Seller) - .ThenInclude(s => s.Wallet) - .ToListAsync(); + .Where(p => postIds.Contains(p.Id) && p.Status == state) + .Include(p => p.Seller) + .ThenInclude(s => s.Wallet) + .ToListAsync(); return posts; } diff --git a/Dentizone.Infrastructure/Repositories/QuestionRepository.cs b/Dentizone.Infrastructure/Repositories/QuestionRepository.cs index 9cbf509..6ea3580 100644 --- a/Dentizone.Infrastructure/Repositories/QuestionRepository.cs +++ b/Dentizone.Infrastructure/Repositories/QuestionRepository.cs @@ -15,7 +15,7 @@ public async Task CreateAsync(Question entity) } public async Task FindBy(Expression> condition, - Expression>[]? includes) + Expression>[]? includes) { IQueryable query = dbContext.Questions; if (includes != null) @@ -45,7 +45,9 @@ public async Task CreateAsync(Question entity) public async Task GetByIdAsync(string id) { - return await dbContext.Questions.FindAsync(id); + return await dbContext.Questions + .Include(p => p.Post) + .Where(q => q.Id == id).FirstOrDefaultAsync(); } public async Task UpdateAsync(Question entity) @@ -55,5 +57,21 @@ public async Task UpdateAsync(Question entity) return entity; } + + public async Task> FindAllBy(Expression> condition, + Expression>[]? includes = null) + { + IQueryable query = dbContext.Questions; + + if (includes != null) + { + foreach (var include in includes) + { + query = query.Include(include); + } + } + + return await query.Where(condition).ToListAsync(); + } } } \ No newline at end of file diff --git a/Dentizone.Infrastructure/Repositories/SaleTransactionRepository.cs b/Dentizone.Infrastructure/Repositories/SaleTransactionRepository.cs index 1d3620b..e1b10a4 100644 --- a/Dentizone.Infrastructure/Repositories/SaleTransactionRepository.cs +++ b/Dentizone.Infrastructure/Repositories/SaleTransactionRepository.cs @@ -11,7 +11,7 @@ public class SaleTransactionRepository(AppDbContext dbContext) public async Task GetByIdAsync(string id) { return await dbContext.SalesTransactions - .FirstOrDefaultAsync(st => st.Id == id); + .FirstOrDefaultAsync(st => st.Id == id); } @@ -23,7 +23,7 @@ public async Task CreateAsync(SalesTransaction entity) } public async Task FindBy(Expression> condition, - Expression>[]? includes) + Expression>[]? includes) { IQueryable query = dbContext.SalesTransactions; if (includes != null) diff --git a/Dentizone.Infrastructure/Repositories/ShipInfoRepository.cs b/Dentizone.Infrastructure/Repositories/ShipInfoRepository.cs index df607a3..dd5abae 100644 --- a/Dentizone.Infrastructure/Repositories/ShipInfoRepository.cs +++ b/Dentizone.Infrastructure/Repositories/ShipInfoRepository.cs @@ -10,7 +10,7 @@ internal class ShipInfoRepository(AppDbContext dbContext) : AbstractRepository(d public async Task GetByIdAsync(string id) { return await dbContext.ShipInfos - .FirstOrDefaultAsync(s => s.Id == id); + .FirstOrDefaultAsync(s => s.Id == id); } @@ -22,7 +22,7 @@ public async Task CreateAsync(ShipInfo entity) } public async Task FindBy(Expression> condition, - Expression>[]? includes) + Expression>[]? includes) { IQueryable query = dbContext.ShipInfos; if (includes != null) diff --git a/Dentizone.Infrastructure/Repositories/ShipmentActivityRepository.cs b/Dentizone.Infrastructure/Repositories/ShipmentActivityRepository.cs index 26d80ea..5221095 100644 --- a/Dentizone.Infrastructure/Repositories/ShipmentActivityRepository.cs +++ b/Dentizone.Infrastructure/Repositories/ShipmentActivityRepository.cs @@ -12,7 +12,7 @@ internal class ShipmentActivityRepository(AppDbContext dbContext) { return await dbContext.ShipmentActivities - .FirstOrDefaultAsync(s => s.Id == id); + .FirstOrDefaultAsync(s => s.Id == id); } @@ -24,7 +24,7 @@ public async Task CreateAsync(ShipmentActivity entity) } public async Task FindBy(Expression> condition, - Expression>[]? includes) + Expression>[]? includes) { IQueryable query = dbContext.ShipmentActivities; if (includes != null) diff --git a/Dentizone.Infrastructure/Repositories/SubCategoryRepository.cs b/Dentizone.Infrastructure/Repositories/SubCategoryRepository.cs index 82a19b6..d7d6669 100644 --- a/Dentizone.Infrastructure/Repositories/SubCategoryRepository.cs +++ b/Dentizone.Infrastructure/Repositories/SubCategoryRepository.cs @@ -11,7 +11,7 @@ internal class SubCategoryRepository(AppDbContext dbContext) : AbstractRepositor public async Task GetByIdAsync(string id) { return await dbContext.SubCategories - .FirstOrDefaultAsync(sc => sc.Id == id); + .FirstOrDefaultAsync(sc => sc.Id == id); } @@ -23,7 +23,7 @@ public async Task CreateAsync(SubCategory entity) } public async Task FindBy(Expression> condition, - Expression>[]? includes) + Expression>[]? includes) { IQueryable query = dbContext.SubCategories; if (includes != null) @@ -55,8 +55,8 @@ public async Task CreateAsync(SubCategory entity) public async Task> GetAll() { return await dbContext.SubCategories - .Include(sc => sc.Category) - .ToListAsync(); + .Include(sc => sc.Category) + .ToListAsync(); } public async Task Update(SubCategory entity) diff --git a/Dentizone.Infrastructure/Repositories/UniversityRepository.cs b/Dentizone.Infrastructure/Repositories/UniversityRepository.cs index 5a41811..91f275e 100644 --- a/Dentizone.Infrastructure/Repositories/UniversityRepository.cs +++ b/Dentizone.Infrastructure/Repositories/UniversityRepository.cs @@ -22,7 +22,7 @@ public async Task CreateAsync(University entity) } public async Task FindBy(Expression> condition, - Expression>[]? includes) + Expression>[]? includes) { IQueryable query = dbContext.Universities.Where(u => !u.IsDeleted); if (includes != null) @@ -62,17 +62,17 @@ public async Task Update(University entity) public async Task> GetAll() { return await dbContext.Universities - .Where(u => !u.IsDeleted) - .OrderByDescending(u => u.CreatedAt) - .AsNoTracking() - .ToListAsync(); + .Where(u => !u.IsDeleted) + .OrderByDescending(u => u.CreatedAt) + .AsNoTracking() + .ToListAsync(); } public async Task> GetAll(int page, Expression>? filter) { var query = dbContext.Universities.AsQueryable(); query = query.Where(u => !u.IsDeleted) - .OrderByDescending(u => u.CreatedAt); + .OrderByDescending(u => u.CreatedAt); var totalCount = await query.CountAsync(); @@ -83,9 +83,9 @@ public async Task> GetAll(int page, Expression { Items = items, diff --git a/Dentizone.Infrastructure/Repositories/UserActivityRepository.cs b/Dentizone.Infrastructure/Repositories/UserActivityRepository.cs index 2a02009..4dd2206 100644 --- a/Dentizone.Infrastructure/Repositories/UserActivityRepository.cs +++ b/Dentizone.Infrastructure/Repositories/UserActivityRepository.cs @@ -10,7 +10,7 @@ public class UserActivityRepository(AppDbContext dbContext) : AbstractRepository public async Task GetByIdAsync(string id) { return await dbContext.UserActivities - .FirstOrDefaultAsync(u => u.Id == id); + .FirstOrDefaultAsync(u => u.Id == id); } @@ -22,7 +22,7 @@ public async Task CreateAsync(UserActivity entity) } public async Task FindBy(Expression> condition, - Expression>[]? includes) + Expression>[]? includes) { IQueryable query = dbContext.UserActivities; if (includes != null) @@ -45,10 +45,10 @@ public async Task> GetAllBy(int page, Expression u.CreatedAt) - .Skip(CalculatePagination(page)) - .Take(DefaultPageSize) - .ToListAsync(); + .OrderByDescending(u => u.CreatedAt) + .Skip(CalculatePagination(page)) + .Take(DefaultPageSize) + .ToListAsync(); } } } \ No newline at end of file diff --git a/Dentizone.Infrastructure/Repositories/UserAssetRepository.cs b/Dentizone.Infrastructure/Repositories/UserAssetRepository.cs index 2ab88b8..4f60058 100644 --- a/Dentizone.Infrastructure/Repositories/UserAssetRepository.cs +++ b/Dentizone.Infrastructure/Repositories/UserAssetRepository.cs @@ -10,7 +10,7 @@ internal class UserAssetRepository(AppDbContext dbContext) : AbstractRepository( public async Task GetByIdAsync(string id) { return await dbContext.UserAssets - .FirstOrDefaultAsync(u => u.Id == id && !u.IsDeleted); + .FirstOrDefaultAsync(u => u.Id == id && !u.IsDeleted); } @@ -22,7 +22,7 @@ public async Task CreateAsync(UserAsset entity) } public async Task FindBy(Expression> condition, - Expression>[]? includes) + Expression>[]? includes) { IQueryable query = dbContext.UserAssets.Where(u => !u.IsDeleted); if (includes != null) @@ -58,10 +58,10 @@ public async Task> GetAllByAsync(int page, Expression u.CreatedAt) - .Skip(CalculatePagination(page)) - .Take(DefaultPageSize) - .ToListAsync(); + .OrderByDescending(u => u.CreatedAt) + .Skip(CalculatePagination(page)) + .Take(DefaultPageSize) + .ToListAsync(); } } } \ No newline at end of file diff --git a/Dentizone.Infrastructure/Repositories/UserRepository.cs b/Dentizone.Infrastructure/Repositories/UserRepository.cs index 7623d2c..90cf087 100644 --- a/Dentizone.Infrastructure/Repositories/UserRepository.cs +++ b/Dentizone.Infrastructure/Repositories/UserRepository.cs @@ -11,15 +11,15 @@ internal class UserRepository(AppDbContext dbContext) : AbstractRepository(dbCon { return await dbContext.AppUsers - .FirstOrDefaultAsync(u => u.Id == id && !u.IsDeleted); + .FirstOrDefaultAsync(u => u.Id == id && !u.IsDeleted); } public async Task> GetAllAsync(int page = 1, - Expression>? filter = null) + Expression>? filter = null) { var query = dbContext.AppUsers - .Skip(CalculatePagination(page)) - .Take(DefaultPageSize); + .Skip(CalculatePagination(page)) + .Take(DefaultPageSize); if (filter != null) { @@ -37,7 +37,7 @@ public async Task CreateAsync(AppUser entity) } public async Task FindBy(Expression> condition, - Expression>[]? includes) + Expression>[]? includes) { IQueryable query = dbContext.AppUsers.Where(u => !u.IsDeleted); if (includes != null) @@ -80,23 +80,23 @@ public async Task GetCountOfUsersAsync() public async Task GetCount7DaysAsync() { var count = await dbContext.AppUsers.Where(u => !u.IsDeleted && u.CreatedAt >= DateTime.UtcNow.AddDays(-7)) - .CountAsync(); + .CountAsync(); return count; } public async Task GetCount30DaysAsync() { var count = await dbContext.AppUsers.Where(u => !u.IsDeleted && u.CreatedAt >= DateTime.UtcNow.AddDays(-30)) - .CountAsync(); + .CountAsync(); return count; } public async Task> GetStudentCountPerUniversityAsync() { var result = await dbContext.AppUsers - .Where(a => !a.IsDeleted && a.University != null) - .GroupBy(a => a.University.Name) - .ToDictionaryAsync(g => g.Key, g => g.Count()); + .Where(a => !a.IsDeleted && a.University != null) + .GroupBy(a => a.University.Name) + .ToDictionaryAsync(g => g.Key, g => g.Count()); return result; } diff --git a/Dentizone.Infrastructure/Repositories/WithdrawalRequestRepository.cs b/Dentizone.Infrastructure/Repositories/WithdrawalRequestRepository.cs index c508942..d030424 100644 --- a/Dentizone.Infrastructure/Repositories/WithdrawalRequestRepository.cs +++ b/Dentizone.Infrastructure/Repositories/WithdrawalRequestRepository.cs @@ -18,7 +18,7 @@ public async Task CreateAsync(WithdrawalRequest entity) } public async Task FindBy(Expression> condition, - Expression>[]? includes) + Expression>[]? includes) { IQueryable query = DbContext.WithdrawalRequests; if (includes != null) @@ -42,10 +42,10 @@ public async Task> GetAllAsync( } return await query - .OrderByDescending(u => u.CreatedAt) - .Skip(CalculatePagination(page)) - .Take(DefaultPageSize) - .ToListAsync(); + .OrderByDescending(u => u.CreatedAt) + .Skip(CalculatePagination(page)) + .Take(DefaultPageSize) + .ToListAsync(); } public async Task DeleteAsync(string id) @@ -62,9 +62,9 @@ public async Task> GetAllAsync( public async Task GetByIdAsync(string id) { var request = await DbContext.WithdrawalRequests.Where(w => w.Id == id) - .Include(w => w.Wallet) - .Include(w => w.Wallet.User) - .FirstOrDefaultAsync(); + .Include(w => w.Wallet) + .Include(w => w.Wallet.User) + .FirstOrDefaultAsync(); return request; } diff --git a/Dentizone.Presentaion/Controllers/QAController.cs b/Dentizone.Presentaion/Controllers/QAController.cs new file mode 100644 index 0000000..bdf6dd2 --- /dev/null +++ b/Dentizone.Presentaion/Controllers/QAController.cs @@ -0,0 +1,70 @@ +using System.Runtime.CompilerServices; +using System.Security.Claims; +using Dentizone.Application.DTOs.Q_A.AnswerDTO; +using Dentizone.Application.DTOs.Q_A.QuestionDTO; +using Dentizone.Application.Interfaces; +using Dentizone.Application.Services; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace Dentizone.Presentaion.Controllers +{ + [Route("api/[controller]")] + [ApiController] + [Authorize] + public class QAController(IQAService QAService) : ControllerBase + { + [HttpPost()] + public async Task AskQuestion([FromBody] CreateQuestionDto dto) + { + var userId = User.Claims.First(c => c.Type == ClaimTypes.NameIdentifier).Value; + var question = await QAService.AskQuestionAsync(dto, userId); + return Ok(question); + } + + [HttpGet("questions/{postId}")] + public async Task GetQuestionsForPost(string postId) + { + var questions = await QAService.GetQuestionsForPostAsync(postId); + return Ok(questions); + } + + [HttpPost("answer/{questionId}")] + public async Task AnswerQuestion(string questionId, [FromBody] CreateAnswerDto dto) + { + var responderId = User.Claims.First(c => c.Type == ClaimTypes.NameIdentifier).Value; + + var answer = await QAService.AnswerQuestionAsync(questionId, dto, responderId); + return Ok(answer); + } + + [HttpPut("{questionId}")] + public async Task UpdateQuestion(string questionId, [FromBody] UpdateQuestionDto dto) + { + await QAService.UpdateQuestionAsync(questionId, dto); + return NoContent(); + } + + [HttpDelete("{questionId}")] + public async Task DeleteQuestion(string questionId) + { + await QAService.DeleteQuestionAsync(questionId); + return NoContent(); + } + + [HttpPut("answers/{answerId}")] + public async Task UpdateAnswer(string answerId, [FromBody] UpdateAnswerDto dto) + { + await QAService.UpdateAnswerAsync(answerId, dto); + return NoContent(); + } + + [HttpDelete("answers/{answerId}")] + public async Task DeleteAnswer(string answerId) + { + await QAService.DeleteAnswerAsync(answerId); + return NoContent(); + } + } +} \ No newline at end of file