Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions src/CodeClash.API/Controllers/Blogs/BlogsController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using Microsoft.AspNetCore.Mvc;

namespace CodeClash.API.Controllers.Blogs;
[Route("blogs")]
[ApiController]
public class BlogsController : ControllerBase
{
//[HttpGet]
//[Authorize]
//public async Task<ActionResult<IEnumerable<ProblemResponse>>> GetProblemsForBlog()
//{
// var problems = await blogRepository.GetProblemsForBlogAsync();

// var response = problems
// .Select(p => p.ToResponse())
// .ToList();

// return Ok(response);
//}
}
20 changes: 19 additions & 1 deletion src/CodeClash.API/Controllers/Contests/ContestController.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using CodeClash.Application.Contest.GetContest;
using CodeClash.Application.Contests.CreateContest;
using CodeClash.Application.Contests.GetContest;
using MediatR;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

namespace CodeClash.API.Controllers.Contests;
Expand All @@ -20,4 +22,20 @@ public async Task<IActionResult> GetContestProblems(
? Ok(result)
: BadRequest(result);
}

[Authorize]
[HttpPost]
public async Task<IActionResult> CreateContest(
CreateContestCommand command,
CancellationToken cancellationToken)
{
var result = await sender.Send(command, cancellationToken);

return result.IsSuccess
? CreatedAtAction(
nameof(GetContestProblems),
new { id = result.Value.Id },
result)
: BadRequest(result);
}
}
11 changes: 5 additions & 6 deletions src/CodeClash.API/Controllers/Problems/ProblemsController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public async Task<IActionResult> Create(
[HttpGet]
[Authorize]
public async Task<IActionResult> GetProblems(
[FromQuery] List<string>? topicsNames,
[FromQuery] List<Guid>? topics,
[FromQuery] string? problemName,
[FromQuery] ProblemStatus? status,
[FromQuery] Difficulty? difficulty,
Expand All @@ -55,15 +55,14 @@ public async Task<IActionResult> GetProblems(
[FromQuery] int pageSize = 10)
{
var query = new GetAllProblemsQuery(
topicsNames,
topics,
problemName,
difficulty,
pageNumber,
pageSize,
status,
sortBy,
order
);
order,
pageNumber,
pageSize);

var result = await sender.Send(query);

Expand Down
23 changes: 18 additions & 5 deletions src/CodeClash.API/Controllers/Submissions/SubmissionsController.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using CodeClash.Application.Submissions.GetProblemSubmissions;
using System.Security.Claims;
using CodeClash.Application.Submissions.GetProblemSubmissions;
using CodeClash.Application.Submissions.GetSubmissionData;
using MediatR;
using Microsoft.AspNetCore.Authorization;
Expand All @@ -14,17 +15,29 @@ public class SubmissionsController(
[HttpGet("problem/{problemId}")]
public async Task<IActionResult> GetProblemSubmissions(Guid problemId)
{
var query = new GetProblemSubmissionsQuery(problemId);
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
if (userId is null)
{
return Unauthorized();
}

var query = new GetProblemSubmissionsQuery(problemId, userId);

var response = await sender.Send(query);

return response.IsSuccess ? Ok(response.Value) : NotFound();
return response.IsSuccess
? Ok(response.Value)
: response.Error.Code switch
{
"Auth.Error" => Forbid(),
_ => NotFound()
};
}

[HttpGet("{id}")]
public async Task<IActionResult> GetSubmissionData(Guid submissionId)
public async Task<IActionResult> GetSubmissionData(Guid id)
{
var query = new GetSubmissionDataQuery(submissionId);
var query = new GetSubmissionDataQuery(id);

var response = await sender.Send(query);

Expand Down
2 changes: 1 addition & 1 deletion src/CodeClash.API/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@

await app.ApplyMigrationsAsync();

await app.InitializeElasticSearchAsync();
//await app.InitializeElasticSearchAsync();

// await app.SeedDataAsync();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,5 @@ Task CacheResponseAsync(
object Response,
TimeSpan timeToLive);

Task<IEnumerable<T>> GetCachedResponseAsync<T>(
string key) where T : class;
Task<string> GetCachedResponseAsync(string key);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using CodeClash.Application.Abstractions.Messaging;

namespace CodeClash.Application.Contests.CreateContest;
public sealed record CreateContestCommand(
string Name,
string Description,
DateTime StartTime,
DateTime EndTime) : ICommand<CreateContestResponse>;
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using System.Security.Claims;
using CodeClash.Application.Abstractions.Messaging;
using CodeClash.Application.Mapping;
using CodeClash.Domain.Abstractions;
using CodeClash.Domain.Premitives;
using Microsoft.AspNetCore.Http;

namespace CodeClash.Application.Contests.CreateContest;
internal sealed class CreateContestCommandHandler(
IContestRepository contestRepository,
IUnitOfWork unitOfWork,
IHttpContextAccessor contextAccessor)
: ICommandHandler<CreateContestCommand, CreateContestResponse>
{
public async Task<Result<CreateContestResponse>> Handle(
CreateContestCommand request,
CancellationToken cancellationToken)
{
var userId = contextAccessor.HttpContext?.User.FindFirstValue(ClaimTypes.NameIdentifier);

if (userId is null)
{
return Result.Failure<CreateContestResponse>(new Error("Auth.Error", "Unauthorized"));
}

if (request.StartTime >= request.EndTime)
{
return Result.Failure<CreateContestResponse>(
new Error("Contest.InvalidDates", "Start time must be before end time"));
}

var contest = request.ToContest(userId);

contestRepository.Add(contest);

await unitOfWork.SaveChangesAsync(cancellationToken);

return Result.Success(contest.ToCreateContestResponse(), "Contest Created Successfully");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using FluentValidation;

namespace CodeClash.Application.Contests.CreateContest;
public sealed class CreateContestCommandValidator
: AbstractValidator<CreateContestCommand>
{
public CreateContestCommandValidator()
{
RuleFor(x => x.Name).NotEmpty().NotNull();
RuleFor(x => x.Description).NotEmpty().NotNull();
RuleFor(x => x.StartTime).NotEmpty().NotNull();
RuleFor(x => x.EndTime).NotEmpty().NotNull();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
namespace CodeClash.Application.Contests.CreateContest;
public sealed record CreateContestResponse(
Guid Id,
string Name);
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using CodeClash.Application.Abstractions.Messaging;
using CodeClash.Domain.Premitives.Responses;

namespace CodeClash.Application.Contest.GetContest;
namespace CodeClash.Application.Contests.GetContest;
public sealed record GetContestQuery(
Guid Id) : IQuery<IReadOnlyList<ContestProblemResponse>>;
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
using CodeClash.Domain.Premitives.Responses;
using Microsoft.AspNetCore.Http;

namespace CodeClash.Application.Contest.GetContest;
namespace CodeClash.Application.Contests.GetContest;
internal sealed class GetContestQueryHandler(
IResponseCacheService cacheService,
IHttpContextAccessor httpContext,
Expand Down Expand Up @@ -36,14 +36,15 @@ public async Task<Result<IReadOnlyList<ContestProblemResponse>>> Handle(
string cacheKey = GenerateCacheKeyFromRequest();

// check cache
var cachedData = await cacheService.GetCachedResponseAsync<ContestProblemResponse>(
cacheKey);
var cachedData = await cacheService.GetCachedResponseAsync(cacheKey);

// cache hit → return cached data
if (cachedData is not null)
{
var serializedData = Helper.DeserializeCollection<ContestProblemResponse>(cachedData);

return Result.Success<IReadOnlyList<ContestProblemResponse>>(
cachedData.ToList(),
serializedData.ToList(),
"Contest Problems fetched successfully");
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using FluentValidation;

namespace CodeClash.Application.Contest.GetContest;
namespace CodeClash.Application.Contests.GetContest;
public sealed class GetContestQueryValidator
: AbstractValidator<GetContestQuery>
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
namespace CodeClash.Application.Contest.GetContest;
namespace CodeClash.Application.Contests.GetContest;
internal sealed class GetContestResponse
{
public Guid Id { get; set; }
Expand Down
21 changes: 21 additions & 0 deletions src/CodeClash.Application/Mapping/ContestMappings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using CodeClash.Application.Contests.CreateContest;
using CodeClash.Domain.Models.Contests;

namespace CodeClash.Application.Mapping;
public static class ContestMappings
{
public static Contest ToContest(this CreateContestCommand command, string userId)
=> new()
{
Name = command.Name,
SetterId = userId,
StartDate = command.StartTime,
EndDate = command.EndTime,
};

public static CreateContestResponse ToCreateContestResponse(this Contest contest)
=> new(
contest.Id,
contest.Name);

}
10 changes: 9 additions & 1 deletion src/CodeClash.Application/Mapping/ProblemMappings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,14 @@ public static CreateProblemResponse ToResponse(this Problem problem)
problem.Difficulty.ToString());
}

public static ProblemResponse ToProblemResponse(this Problem problem)
{
return new ProblemResponse(
problem.Id,
problem.Name
);
}

public static GetAllProblemResponse ToGetAllResponse(this ProblemDocument problem)
{
return new GetAllProblemResponse
Expand Down Expand Up @@ -86,7 +94,7 @@ public static ContestProblemResponse ToContestProblemResponse(this Problem probl
{
return new ContestProblemResponse
{
ContestId = problem.ContestId,
Id = problem.ContestId,
Name = problem.Name,
Difficulty = problem.Difficulty,
RunTimeLimit = problem.RunTimeLimit,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@

namespace CodeClash.Application.Problems.GetAll;
public record GetAllProblemsQuery(
List<string>? TopicsNames,
List<Guid>? Topics,
string? Name,
Difficulty? Difficulty,
int PageNumber,
int PageSize,
ProblemStatus? Status,
SortBy? SortBy,
Order? Order) : IQuery<PagedResult<GetAllProblemResponse>>;
SortBy SortBy,
Order Order,
int PageNumber = 1,
int PageSize = 10) : IQuery<PagedResult<GetAllProblemResponse>>;
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ namespace CodeClash.Application.Problems.GetAllProblem;
internal sealed class GetAllProblemsQueryHandler(
IElasticService elasticService,
ISubmissionRepository submissionRepository,
ITopicRepository topicRepository,
IHttpContextAccessor contextAccessor)
: IQueryHandler<GetAllProblemsQuery, PagedResult<GetAllProblemResponse>>
{
Expand All @@ -26,11 +25,9 @@ public async Task<Result<PagedResult<GetAllProblemResponse>>> Handle(
return Result.Failure<PagedResult<GetAllProblemResponse>>(new Error("Auth.Error", "Unauthorized"));
}

var topicsIds = await topicRepository.GetTopicIDsByNamesAsync(request.TopicsNames!);

var (problemDocuments, totalPages) = await elasticService.SearchProblemsAsync(
request.Name,
topicsIds,
request.Topics,
request.Difficulty,
request.SortBy,
request.Order,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ public GetAllProblemsQueryValidator()
.GreaterThanOrEqualTo(1)
.WithMessage("Page size must be greater than or equal to 1.");

RuleFor(x => x.TopicsNames)
RuleFor(x => x.Topics)
.Must(topicsNames => topicsNames == null || topicsNames.Count > 0)
.WithMessage("Topics names must be null or have at least one element.");
.WithMessage("Topics must be null or contain at least one id.");

RuleFor(x => x.Name)
.Must(problemName => problemName == null || problemName.Length > 0)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@

namespace CodeClash.Application.Submissions.GetProblemSubmissions;
public sealed record GetProblemSubmissionsQuery(
Guid ProblemId
) : IQuery<IReadOnlyList<GetProblemSubmissionsResponse>>;
Guid ProblemId,
string UserId) : IQuery<IReadOnlyList<GetProblemSubmissionsResponse>>;
Loading
Loading