Skip to content

Commit

Permalink
Implement QuestionSet CRUD
Browse files Browse the repository at this point in the history
  • Loading branch information
mdemrulkayes committed Apr 17, 2024
1 parent 4cf49bc commit fe52ca3
Show file tree
Hide file tree
Showing 27 changed files with 464 additions and 23 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,28 @@ public struct QuestionModuleConstants

public struct Route
{
public const string GetAllTags = "/api/question/tag";
public const string GetTagDetailsById = "/api/question/tag/{tagId}";
public const string CreateTag = "/api/question/tag";
public const string UpdateTag = "/api/question/tag/{tagId}";
public const string DeleteTag = "/api/question/tag/{tagId}";
public struct TagRoute
{
public const string GetAllTags = "/api/question/tag";
public const string GetTagDetailsById = "/api/question/tag/{tagId}";
public const string CreateTag = "/api/question/tag";
public const string UpdateTag = "/api/question/tag/{tagId}";
public const string DeleteTag = "/api/question/tag/{tagId}";
}

public struct QuestionSetRoute
{
public const string GetAllQuestionSets = "/api/question/questionSet";
public const string GetQuestionSetDetailsById = "/api/question/questionSet/{tagId}";
public const string CreateQuestionSet = "/api/question/questionSet";
public const string UpdateQuestionSet = "/api/question/questionSet/{setId}";
public const string DeleteQuestionSet = "/api/question/questionSet/{setId}";
}
}

public struct RouteTag
{
public const string TagEndPointTagName = "Tag";
public const string TagEndPointQuestionSetName = "QuestionSet";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
using Modules.Question.Application.Question.QuestionSet.Dtos;
using SharedKernel.Core;

namespace Modules.Question.Application.Question.QuestionSet.Create;

public sealed record CreateQuestionSetCommand(string Name, string? SetCode , string? Details) : ICommand<Result<QuestionSetResponse>>;
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using AutoMapper;
using Modules.Question.Application.Question.QuestionSet.Dtos;
using Modules.Question.Core.QuestionAggregate;
using SharedKernel.Core;

namespace Modules.Question.Application.Question.QuestionSet.Create;
internal sealed class CreateQuestionSetCommandHandler(IQuestionSetRepository repository, IUnitOfWork unitOfWork, IMapper mapper) : ICommandHandler<CreateQuestionSetCommand, Result<QuestionSetResponse>>
{
/// <summary>Handles a request</summary>
/// <param name="command">The request</param>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>Response from the request</returns>
public async Task<Result<QuestionSetResponse>> Handle(CreateQuestionSetCommand command, CancellationToken cancellationToken)
{
var questionSet = Core.QuestionAggregate.QuestionSet.Create(command.Name, command.SetCode, command.Details);

if (!questionSet.IsSuccess || questionSet.Value is null)
{
return questionSet.Error;
}

var set = questionSet.Value;

repository.Add(set);
await unitOfWork.CommitAsync(cancellationToken);

return mapper.Map<Core.QuestionAggregate.QuestionSet, QuestionSetResponse>(set);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using FluentValidation;
using Modules.Question.Core.QuestionAggregate;

namespace Modules.Question.Application.Question.QuestionSet.Create;
public sealed class CreateQuestionSetCommandValidator : AbstractValidator<CreateQuestionSetCommand>
{
public CreateQuestionSetCommandValidator(IQuestionSetRepository repository)
{
RuleFor(x => x.Name)
.NotEmpty()
.WithMessage("Name can not be empty")
.Length(5, 50)
.WithMessage("Name can not be less than 5 characters and can not be more than 50 characters")
.MustAsync(async (name, token) =>
{
var isNameAlreadyExists = await repository.AnyAsync(x =>
x.Name.ToLower() == name.ToLower());
return !isNameAlreadyExists;
})
.WithMessage("Question Set name already exists");

RuleFor(x => x.Details)
.Length(10, 150)
.When(x => !string.IsNullOrWhiteSpace(x.Details))
.WithMessage("Description can not be less than 10 characters and can not be more than 150 characters");

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
using SharedKernel.Core;

namespace Modules.Question.Application.Question.QuestionSet.Delete;
public sealed record DeleteQuestionSetCommand(long QuestionSetId) : ICommand<Result<bool>>;
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using Modules.Question.Core.QuestionAggregate;
using Modules.Question.Core.Tag;
using SharedKernel.Core;

namespace Modules.Question.Application.Question.QuestionSet.Delete;
internal sealed class DeleteQuestionSetCommandHandler(IQuestionSetRepository repository, IUnitOfWork unitOfWork) : ICommandHandler<DeleteQuestionSetCommand, Result<bool>>
{
public async Task<Result<bool>> Handle(DeleteQuestionSetCommand request, CancellationToken cancellationToken = default)
{
var questionSet = await repository.FirstOrDefaultAsync(x => x.QuestionSetId == request.QuestionSetId);

if (questionSet == null)
{
return TagErrors.TagNotFound;
}

repository.Delete(questionSet);
await unitOfWork.CommitAsync(cancellationToken);

return true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
namespace Modules.Question.Application.Question.QuestionSet.Dtos;

public sealed record QuestionSetResponse(long QuestionSetId, string Name, string? SetCode, string? Details);
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
using common;
using Modules.Question.Application.Question.QuestionSet.Dtos;
using SharedKernel.Core;

namespace Modules.Question.Application.Question.QuestionSet.Query;

public sealed record GetAllQuestionSetQuery : QueryStringParameter, IQuery<Result<PagedListDto<QuestionSetResponse>>>;
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using AutoMapper;
using common;
using Modules.Question.Application.Question.QuestionSet.Dtos;
using Modules.Question.Core.QuestionAggregate;
using SharedKernel.Core;

namespace Modules.Question.Application.Question.QuestionSet.Query;
internal sealed class GetAllQuestionSetQueryHandler(IQuestionSetRepository repository, IMapper mapper)
: IQueryHandler<GetAllQuestionSetQuery, Result<PagedListDto<QuestionSetResponse>>>
{
public async Task<Result<PagedListDto<QuestionSetResponse>>> Handle(GetAllQuestionSetQuery request, CancellationToken cancellationToken)
{
var sets = await repository.GetAllAsync(pageNumber: request.PageNumber, pageSize: request.PageSize,
cancellationToken: cancellationToken);

return mapper.Map<PagedListDto<QuestionSetResponse>>(sets);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
using Modules.Question.Application.Question.QuestionSet.Dtos;
using SharedKernel.Core;

namespace Modules.Question.Application.Question.QuestionSet.Query;
public sealed record GetQuestionSetByIdQuery(long QuestionSetId) : IQuery<Result<QuestionSetResponse>>;
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using AutoMapper;
using Modules.Question.Application.Question.QuestionSet.Dtos;
using Modules.Question.Core.QuestionAggregate;
using SharedKernel.Core;

namespace Modules.Question.Application.Question.QuestionSet.Query;
internal class GetQuestionSetByIdQueryHandler(IQuestionSetRepository repository,
IMapper mapper) : IQueryHandler<GetQuestionSetByIdQuery, Result<QuestionSetResponse>>
{
public async Task<Result<QuestionSetResponse>> Handle(GetQuestionSetByIdQuery request, CancellationToken cancellationToken)
{
var setDetails = await repository.FirstOrDefaultAsync(x => x.QuestionSetId == request.QuestionSetId);
if (setDetails == null)
{
return QuestionErrors.QuestionSetNotFound;
}

return mapper.Map<QuestionSetResponse>(setDetails);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using AutoMapper;
using Modules.Question.Application.Question.QuestionSet.Dtos;

namespace Modules.Question.Application.Question.QuestionSet;
internal sealed class QuestionSetMappingProfile : Profile
{
public QuestionSetMappingProfile()
{
CreateMap<Core.QuestionAggregate.QuestionSet, QuestionSetResponse>()
.ConstructUsing(set => new QuestionSetResponse(
set.QuestionSetId,
set.Name,
set.SetCode,
set.Details
))
.ReverseMap();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
using Modules.Question.Application.Question.QuestionSet.Dtos;
using SharedKernel.Core;

namespace Modules.Question.Application.Question.QuestionSet.Update;

public sealed record UpdateQuestionSetCommand(long QuestionSetId, string Name, string? SetCode, string? Details) : ICommand<Result<QuestionSetResponse>>;
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using AutoMapper;
using Modules.Question.Application.Question.QuestionSet.Dtos;
using Modules.Question.Core.QuestionAggregate;
using SharedKernel.Core;

namespace Modules.Question.Application.Question.QuestionSet.Update;
internal sealed class UpdateQuestionSetCommandHandler(IQuestionSetRepository repository,
IUnitOfWork unitOfWork,
IMapper mapper) : ICommandHandler<UpdateQuestionSetCommand, Result<QuestionSetResponse>>
{
public async Task<Result<QuestionSetResponse>> Handle(UpdateQuestionSetCommand request, CancellationToken cancellationToken = default)
{
var set = await repository.FirstOrDefaultAsync(x => x.QuestionSetId == request.QuestionSetId);
if (set == null)
{
return QuestionErrors.QuestionSetNotFound;
}

var updatedSet = set.Update(request.Name, request.SetCode, request.Details);

if (!updatedSet.IsSuccess || updatedSet.Value is null)
{
return updatedSet.Error;
}

repository.Update(updatedSet.Value);
await unitOfWork.CommitAsync(cancellationToken);

return mapper.Map<QuestionSetResponse>(updatedSet.Value);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using FluentValidation;
using Modules.Question.Core.QuestionAggregate;

namespace Modules.Question.Application.Question.QuestionSet.Update;
internal sealed class UpdateQuestionSetCommandValidator : AbstractValidator<UpdateQuestionSetCommand>
{
public UpdateQuestionSetCommandValidator(IQuestionSetRepository repository)
{
RuleFor(x => x.QuestionSetId)
.NotNull()
.WithMessage("Question Set Id can not null")
.GreaterThan(0)
.WithMessage("Question Set Id value can not be 0 or less than 0");

RuleFor(x => x.Name)
.NotEmpty()
.WithMessage("Name can not be empty")
.Length(5, 50)
.WithMessage("Name can not be less than 5 characters and can not be more than 50 characters")
.MustAsync(async (command, name, token) =>
{
var isNameAlreadyExists = await repository.AnyAsync(x =>
x.Name.ToLower() == name.ToLower() && x.QuestionSetId != command.QuestionSetId);
return !isNameAlreadyExists;
})
.WithMessage("Question Set name is already exists");

RuleFor(x => x.Details)
.Length(10, 150)
.When(x => !string.IsNullOrWhiteSpace(x.Details))
.WithMessage("Details can not be less than 10 characters and can not be more than 150 characters");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
using SharedKernel.Core;

namespace Modules.Question.Core.QuestionAggregate;
public interface IQuestionSetRepository : IRepository<QuestionSet>;
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
using SharedKernel.Core;

namespace Modules.Question.Core.QuestionAggregate;
public struct QuestionErrors
{
public static Error QuestionSetNotFound => Error.NotFound("QuestionSet.QuestionSetNotFound", "Question Set not found.");
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,27 @@ public sealed class QuestionSet : BaseAuditableEntity, IAggregateRoot

internal List<Question> _questions = [];

private QuestionSet(string name, string setCode = "", string details = "")
private QuestionSet(string name, string? setCode = "", string? details = "")
{
Name = name;
SetCode = setCode;
Details = details;
}

public static Result<QuestionSet> Create(string name, string setCode, string details)
public static Result<QuestionSet> Create(string name, string? setCode, string? details)
{
return new QuestionSet(name, setCode, details);
}

public Result<QuestionSet> Update(string name, string? setCode, string? details)
{
Name = name;
SetCode = setCode;
Details = details;

return this;
}

public void AddQuestion(string askedQuestion, Dictionary<string, bool> options,string discussion = "", int? mark = null)
{
var addedQuestion = Question.Create(askedQuestion, discussion, mark);
Expand Down
Loading

0 comments on commit fe52ca3

Please sign in to comment.