Skip to content

Commit

Permalink
Merge pull request #65 from skni-kod/feature/#7-crud-applications
Browse files Browse the repository at this point in the history
Crud job applications (without files)
  • Loading branch information
Comply5000 authored Feb 20, 2024
2 parents 47ad4c6 + 636facb commit 8b317bb
Show file tree
Hide file tree
Showing 23 changed files with 453 additions and 6 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
using Microsoft.AspNetCore.Mvc;
using skit.API.Attributes;
using skit.Application.JobApplications.Commands.DeleteJobApplication;
using skit.Application.JobApplications.Commands.UpdateJobApplication;
using skit.Application.JobApplications.Queries.BrowseJobApplications;
using skit.Application.JobApplications.Queries.GetJobApplication;
using skit.Core.Identity.Static;
using skit.Shared.Responses;

namespace skit.API.Controllers.Areas.CompanyOwner;

[Route($"{Endpoints.BaseUrl}/jobApplications")]
[ApiAuthorize(Roles = UserRoles.CompanyOwner)]
public sealed class C_JobApplicationsController : BaseController
{
/// <summary>
/// Browse job applications
/// </summary>
[HttpGet]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult<BrowseJobApplicationsResponse>> BrowseJobApplications(
[FromQuery] BrowseJobApplicationsQuery query, CancellationToken cancellationToken = default)
{
var response = await Mediator.Send(query, cancellationToken);
return Ok(response);
}

/// <summary>
/// Get job application by id
/// </summary>
[HttpGet("{jobApplicationId:guid}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult<GetJobApplicationResponse>> GetJobApplication([FromRoute] Guid jobApplicationId,
CancellationToken cancellationToken = default)
{
var response = await Mediator.Send(new GetJobApplicationQuery(jobApplicationId), cancellationToken);
return OkOrNotFound(response);
}

/// <summary>
/// Update job application
/// </summary>
[HttpPut("{jobApplicationId::guid}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<ActionResult<CreateOrUpdateResponse>> UpdateJobApplication([FromRoute] Guid jobApplicationId,
[FromBody] UpdateJobApplicationCommand command, CancellationToken cancellationToken = default)
{
var response = await Mediator.Send(command with {JobApplicationId = jobApplicationId}, cancellationToken);
return Ok(response);
}

/// <summary>
/// Delete job application
/// </summary>
[HttpDelete("{jobApplicationId::guid}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<IActionResult> DeleteJobApplication([FromRoute] Guid jobApplicationId,
CancellationToken cancellationToken = default)
{
await Mediator.Send(new DeleteJobApplicationCommand(jobApplicationId), cancellationToken);
return Ok();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ public async Task<ActionResult<BrowseOffersResponse>> BrowseOffers([FromQuery] B
[HttpGet("{offerId:guid}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<ActionResult<GetOfferResponse>> GetOffer([FromRoute] Guid offerId, CancellationToken cancellationToken = default)
public async Task<ActionResult<GetOfferResponse>> GetOffer([FromRoute] Guid offerId,
CancellationToken cancellationToken = default)
{
var response = await Mediator.Send(new GetOfferQuery(offerId), cancellationToken);

Expand Down
25 changes: 25 additions & 0 deletions skit.API/Controllers/Areas/Public/P_JobApplicationsController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using skit.Application.JobApplications.Commands.CreateJobApplication;
using skit.Application.JobApplications.Commands.UpdateJobApplication;
using skit.Shared.Responses;

namespace skit.API.Controllers.Areas.Public;

[AllowAnonymous]
[Route($"{Endpoints.BasePublicUrl}/jobApplications")]
public sealed class P_JobApplicationsController : BaseController
{
/// <summary>
/// Create application
/// </summary>
[HttpPost]
[ProducesResponseType(StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<ActionResult<CreateOrUpdateResponse>> CreateJobApplication([FromBody] CreateJobApplicationCommand command,
CancellationToken cancellationToken = default)
{
var response = await Mediator.Send(command, cancellationToken);
return Created(string.Empty, response);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using MediatR;
using skit.Shared.Responses;

namespace skit.Application.JobApplications.Commands.CreateJobApplication;

public sealed record CreateJobApplicationCommand(
Guid OfferId,
string FirstName,
string SurName,
string? Description) : IRequest<CreateOrUpdateResponse>;
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using MediatR;
using skit.Core.JobApplications.Entities;
using skit.Core.JobApplications.Repositories;
using skit.Core.Offers.Exceptions;
using skit.Core.Offers.Repositories;
using skit.Shared.Responses;

namespace skit.Application.JobApplications.Commands.CreateJobApplication;

internal sealed class CreateJobApplicationHandler : IRequestHandler<CreateJobApplicationCommand, CreateOrUpdateResponse>
{
private readonly IOfferRepository _offerRepository;
private readonly IJobApplicationRepository _jobApplicationRepository;

public CreateJobApplicationHandler(IOfferRepository offerRepository, IJobApplicationRepository jobApplicationRepository)
{
_offerRepository = offerRepository;
_jobApplicationRepository = jobApplicationRepository;
}

public async Task<CreateOrUpdateResponse> Handle(CreateJobApplicationCommand request, CancellationToken cancellationToken)
{
var offer = await _offerRepository.GetAsync(request.OfferId, cancellationToken)
?? throw new OfferNotFoundException();

var jobApplication = JobApplication.Create(
request.FirstName,
request.SurName,
request.Description,
offer.Id);

var entityId = await _jobApplicationRepository.AddAsync(jobApplication, cancellationToken);

return new CreateOrUpdateResponse(entityId);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
using MediatR;

namespace skit.Application.JobApplications.Commands.DeleteJobApplication;

public sealed record DeleteJobApplicationCommand(Guid JobApplicationId) : IRequest;
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using MediatR;
using skit.Core.Common.Services;
using skit.Core.JobApplications.Exceptions;
using skit.Core.JobApplications.Repositories;
using skit.Core.Offers.Exceptions;
using skit.Core.Offers.Repositories;

namespace skit.Application.JobApplications.Commands.DeleteJobApplication;

internal sealed class DeleteJobApplicationHandler : IRequestHandler<DeleteJobApplicationCommand>
{
private readonly IJobApplicationRepository _jobApplicationRepository;
private readonly IOfferRepository _offerRepository;
private readonly ICurrentUserService _currentUserService;

public DeleteJobApplicationHandler(IJobApplicationRepository jobApplicationRepository, IOfferRepository offerRepository, ICurrentUserService currentUserService)
{
_jobApplicationRepository = jobApplicationRepository;
_offerRepository = offerRepository;
_currentUserService = currentUserService;
}

public async Task Handle(DeleteJobApplicationCommand request, CancellationToken cancellationToken)
{
var jobApplication = await _jobApplicationRepository.GetAsync(request.JobApplicationId, cancellationToken)
?? throw new JobApplicationNotFoundException();

var offer = await _offerRepository.GetAsync(jobApplication.OfferId, cancellationToken)
?? throw new OfferNotFoundException();

if (offer.CompanyId != _currentUserService.CompanyId)
throw new JobApplicationNotFoundException();

await _jobApplicationRepository.DeleteAsync(jobApplication, cancellationToken);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using MediatR;
using skit.Shared.Responses;

namespace skit.Application.JobApplications.Commands.UpdateJobApplication;

public record UpdateJobApplicationCommand
(string FirstName, string SurName, string? Description) : IRequest<CreateOrUpdateResponse>
{
internal Guid JobApplicationId { get; init; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
using MediatR;
using skit.Core.Common.Services;
using skit.Core.JobApplications.Exceptions;
using skit.Core.JobApplications.Repositories;
using skit.Core.Offers.Exceptions;
using skit.Core.Offers.Repositories;
using skit.Shared.Responses;

namespace skit.Application.JobApplications.Commands.UpdateJobApplication;

internal sealed class UpdateJobApplicationHandler : IRequestHandler<UpdateJobApplicationCommand, CreateOrUpdateResponse>
{
private readonly IJobApplicationRepository _jobApplicationRepository;
private readonly IOfferRepository _offerRepository;
private readonly ICurrentUserService _currentUserService;

public UpdateJobApplicationHandler(IJobApplicationRepository jobApplicationRepository, IOfferRepository offerRepository, ICurrentUserService currentUserService)
{
_jobApplicationRepository = jobApplicationRepository;
_offerRepository = offerRepository;
_currentUserService = currentUserService;
}

public async Task<CreateOrUpdateResponse> Handle(UpdateJobApplicationCommand request, CancellationToken cancellationToken)
{
var jobApplication = await _jobApplicationRepository.GetAsync(request.JobApplicationId, cancellationToken)
?? throw new JobApplicationNotFoundException();

var offer = await _offerRepository.GetAsync(jobApplication.OfferId, cancellationToken)
?? throw new OfferNotFoundException();

if (offer.CompanyId != _currentUserService.CompanyId)
throw new JobApplicationNotFoundException();

jobApplication.Update(
request.FirstName,
request.SurName,
request.Description);

var resultId = await _jobApplicationRepository.UpdateAsync(jobApplication, cancellationToken);

return new CreateOrUpdateResponse(resultId);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
using MediatR;

namespace skit.Application.JobApplications.Queries.BrowseJobApplications;

public sealed record BrowseJobApplicationsQuery(string? Search) : IRequest<BrowseJobApplicationsResponse>;
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
using skit.Application.JobApplications.Queries.DTO;

namespace skit.Application.JobApplications.Queries.BrowseJobApplications;

public sealed record BrowseJobApplicationsResponse(List<JobApplicationDto> JobApplicationDto);
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace skit.Application.JobApplications.Queries.DTO;

public sealed class JobApplicationDetailsDto
{
public Guid Id { get; set; }
public string FirstName { get; set; }
public string SurName { get; set; }
public string? Description { get; set; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace skit.Application.JobApplications.Queries.DTO;

public sealed class JobApplicationDto
{
public Guid Id { get; set; }
public string FirstName { get; set; }
public string SurName { get; set; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
using MediatR;

namespace skit.Application.JobApplications.Queries.GetJobApplication;

public sealed record GetJobApplicationQuery(Guid JobApplicationId) : IRequest<GetJobApplicationResponse>;
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
using skit.Application.JobApplications.Queries.DTO;

namespace skit.Application.JobApplications.Queries.GetJobApplication;

public sealed record GetJobApplicationResponse(JobApplicationDetailsDto? JobApplicationDto);
30 changes: 25 additions & 5 deletions skit.Core/JobApplications/Entities/JobApplication.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,30 @@ namespace skit.Core.JobApplications.Entities;

public sealed class JobApplication : Entity
{
public string FirstName { get; set; }
public string SurName { get; set; }
public string? Description { get; set; }
public string FirstName { get; private set; }
public string SurName { get; private set; }
public string? Description { get; private set; }

public Guid OfferId { get; set; }
public Offer Offer { get; set; }
public Guid OfferId { get; private set; }
public Offer Offer { get; private set; }

private JobApplication() {}

private JobApplication(string firstName, string surName, string? description, Guid offerId)
{
FirstName = firstName;
SurName = surName;
Description = description;
OfferId = offerId;
}

public static JobApplication Create(string firstName, string surName, string? description, Guid offerId)
=> new(firstName, surName, description, offerId);

public void Update(string firstName, string surName, string? description)
{
FirstName = firstName;
SurName = surName;
Description = description;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using skit.Shared.Abstractions.Exceptions;

namespace skit.Core.JobApplications.Exceptions;

public sealed class JobApplicationNotFoundException : SkitException
{
public JobApplicationNotFoundException() : base("JobApplication not found") { }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using skit.Core.JobApplications.Entities;

namespace skit.Core.JobApplications.Repositories;

public interface IJobApplicationRepository
{
public Task<Guid> AddAsync(JobApplication jobApplication, CancellationToken cancellationToken);
public Task<JobApplication?> GetAsync(Guid jobApplicationId, CancellationToken cancellationToken);
public Task<Guid> UpdateAsync(JobApplication jobApplication, CancellationToken cancellationToken);
public Task DeleteAsync(JobApplication jobApplication, CancellationToken cancellationToken);
}
3 changes: 3 additions & 0 deletions skit.Infrastructure/DAL/Extension.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using skit.Core.Companies.Repositories;
using skit.Core.Files.Repositories;
using skit.Core.Identity.Services;
using skit.Core.JobApplications.Repositories;
using skit.Core.Offers.Repositories;
using skit.Core.Technologies.Repositories;
using skit.Infrastructure.DAL.Addresses.Repositories;
Expand All @@ -14,6 +15,7 @@
using skit.Infrastructure.DAL.EF.Seeder;
using skit.Infrastructure.DAL.Files.Repositories;
using skit.Infrastructure.DAL.Identity.Services;
using skit.Infrastructure.DAL.JobApplications.Repositories;
using skit.Infrastructure.DAL.Offers.Repositories;
using skit.Infrastructure.DAL.Technologies.Repositories;

Expand Down Expand Up @@ -47,6 +49,7 @@ public static IServiceCollection AddDal(this IServiceCollection services, IConfi
services.AddScoped<IAddressRepository, AddressRepository>();
services.AddScoped<IOfferRepository, OfferRepository>();
services.AddScoped<ITechnologyRepository, TechnologyRepository>();
services.AddScoped<IJobApplicationRepository, JobApplicationRepository>();
services.AddScoped<IFileRepository, FileRepository>();

return services;
Expand Down
Loading

0 comments on commit 8b317bb

Please sign in to comment.