diff --git a/OutOfSchool/OutOfSchool.BusinessLogic/Models/StudySubjects/StudySubjectDto.cs b/OutOfSchool/OutOfSchool.BusinessLogic/Models/StudySubjects/StudySubjectDto.cs new file mode 100644 index 000000000..f3dff27fe --- /dev/null +++ b/OutOfSchool/OutOfSchool.BusinessLogic/Models/StudySubjects/StudySubjectDto.cs @@ -0,0 +1,24 @@ +using System.ComponentModel.DataAnnotations; + +namespace OutOfSchool.BusinessLogic.Models; +public class StudySubjectDto +{ + public int Id { get; set; } + /// + /// Name in Ukrainian + /// + [Required(ErrorMessage = "The name in Ukrainian is required.")] + public string NameInUkrainian { get; set; } + + /// + /// Name in the language of instruction + /// + [Required(ErrorMessage = "The name in the language of instruction is required.")] + public string NameInInstructionLanguage { get; set; } + + /// + /// Language of instruction (allows multiple selection) + /// + [Required(ErrorMessage = "The language of instruction is required.")] + public List Languages { get; set; } +} diff --git a/OutOfSchool/OutOfSchool.BusinessLogic/Services/ISubjectService.cs b/OutOfSchool/OutOfSchool.BusinessLogic/Services/ISubjectService.cs new file mode 100644 index 000000000..0af18ee3d --- /dev/null +++ b/OutOfSchool/OutOfSchool.BusinessLogic/Services/ISubjectService.cs @@ -0,0 +1,43 @@ +using OutOfSchool.BusinessLogic.Enums; +using OutOfSchool.BusinessLogic.Models; + +namespace OutOfSchool.BusinessLogic.Services; +public interface ISubjectService +{ + /// + /// Get all entities. + /// + /// Localization: Ua - 0, En - 1. + /// List of all Subjects. + Task> GetAll(LocalizationType localization = LocalizationType.Ua); + + /// + /// Get entity by it's key. + /// + /// /// Key in the table. + /// Localization: Ua - 0, En - 1. + /// Subject. + Task GetById(long id, LocalizationType localization = LocalizationType.Ua); + + /// + /// Add entity. + /// + /// Tag entity to add. + /// A representing the result of the asynchronous operation. + Task Create(StudySubjectDto dto); + + /// + /// Update entity. + /// + /// /// Subject entity to add. + /// Localization: Ua - 0, En - 1. + /// A representing the result of the asynchronous operation. + Task Update(StudySubjectDto dto, LocalizationType localization = LocalizationType.Ua); + + /// + /// Delete entity. + /// + /// Subject key. + /// A representing the result of the asynchronous operation. + Task Delete(long id); +} diff --git a/OutOfSchool/OutOfSchool.BusinessLogic/Services/SubjectService.cs b/OutOfSchool/OutOfSchool.BusinessLogic/Services/SubjectService.cs new file mode 100644 index 000000000..c2db7980c --- /dev/null +++ b/OutOfSchool/OutOfSchool.BusinessLogic/Services/SubjectService.cs @@ -0,0 +1,128 @@ +using AutoMapper; +using Microsoft.Extensions.Localization; +using OutOfSchool.BusinessLogic.Enums; +using OutOfSchool.BusinessLogic.Models; +using OutOfSchool.BusinessLogic.Models.Tag; +using OutOfSchool.Services.Repository.Base.Api; + +namespace OutOfSchool.BusinessLogic.Services; +public class SubjectService : ISubjectService +{ + private readonly IEntityRepository repository; + private readonly ILogger logger; + private readonly IStringLocalizer localizer; + private readonly IMapper mapper; + + /// + /// Initializes a new instance of the class. + /// + /// Repository. + /// Logger. + /// Localizer. + /// Mapper. + public SubjectService( + IEntityRepository repository, + ILogger logger, + IStringLocalizer localizer, + IMapper mapper) + { + this.localizer = localizer ?? throw new ArgumentNullException(nameof(localizer)); + this.repository = repository ?? throw new ArgumentNullException(nameof(repository)); + this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); + this.mapper = mapper ?? throw new ArgumentNullException(nameof(mapper)); + } + + /// + public async Task Create(StudySubjectDto dto) + { + logger.LogDebug("StudySubject creating was started."); + + var tag = mapper.Map(dto); + + var newTag = await repository.Create(tag).ConfigureAwait(false); + + logger.LogDebug($"StudySubject with Id = {newTag?.Id} created successfully."); + + return mapper.Map(newTag); + } + + /// + public async Task Delete(long id) + { + logger.LogInformation($"Deleting Subject with Id = {id} started."); + + var entity = await repository.GetById(id).ConfigureAwait(false); + + if (entity is null) + { + logger.LogWarning($"Subject with Id = {id} was not found."); + throw new KeyNotFoundException($"Subject with Id = {id} does not exist."); + } + + try + { + await repository.Delete(entity).ConfigureAwait(false); + + logger.LogInformation($"Subject with Id = {id} successfully deleted."); + } + catch (DbUpdateConcurrencyException) + { + logger.LogError($"Deleting Subject with Id = {id} failed."); + throw; + } + } + + /// + public async Task> GetAll(LocalizationType localization = LocalizationType.Ua) + { + logger.LogInformation("Getting all Subjects started."); + + var subjects = await repository.GetAll().ConfigureAwait(false); + + logger.LogInformation(!subjects.Any() + ? "Subject table is empty." + : $"All {subjects.Count()} records were successfully received from the Subject table"); + + return subjects.Select(subject => mapper.Map(subject)).ToList(); + } + + /// + public async Task GetById(long id, LocalizationType localization = LocalizationType.Ua) + { + logger.LogInformation($"Getting Subject by Id started. Looking Id = {id}."); + + var subject = await repository.GetById(id).ConfigureAwait(false); + + if (subject == null) + { + throw new ArgumentException( + nameof(id), + paramName: $"There are no recors in subjects table with such id - {id}."); + } + + logger.LogInformation($"Got a Subject with Id = {id}."); + + return mapper.Map(subject); + } + + /// + public async Task Update(StudySubjectDto dto, LocalizationType localization = LocalizationType.Ua) + { + logger.LogDebug($"Updating StudySubject with Id = {dto.Id}, {localization} localization, started."); + + var Localized = await repository.GetById(dto.Id).ConfigureAwait(false); + + if (Localized == null) + { + logger.LogError($"Updating failed. Tag with Id = {dto.Id} doesn't exist in the system."); + + return null; + } + + var tag = await repository.Update(Localized).ConfigureAwait(false); + + logger.LogDebug($"Tag with Id = {tag.Id} updated succesfully."); + + return mapper.Map(tag); + } +} diff --git a/OutOfSchool/OutOfSchool.DataAccess/Extensions/ModelBuilderExtension.cs b/OutOfSchool/OutOfSchool.DataAccess/Extensions/ModelBuilderExtension.cs index 9f8df0cf5..49ffb0aa5 100644 --- a/OutOfSchool/OutOfSchool.DataAccess/Extensions/ModelBuilderExtension.cs +++ b/OutOfSchool/OutOfSchool.DataAccess/Extensions/ModelBuilderExtension.cs @@ -430,6 +430,20 @@ public static void Seed(this ModelBuilder builder) Title = "Певний місяць або місяці року", TitleEn = "A certain month or months of the year", }); + + builder.Entity().HasData( + new Language { + Id = 1, + Title = "English", + Code = "en", + }, + new Language + { + Id = 2, + Title = "Українська", + Code = "uk", + + }); } /// diff --git a/OutOfSchool/OutOfSchool.DataAccess/Models/Configurations/SubjectConfiguration.cs b/OutOfSchool/OutOfSchool.DataAccess/Models/Configurations/SubjectConfiguration.cs new file mode 100644 index 000000000..b904fbb68 --- /dev/null +++ b/OutOfSchool/OutOfSchool.DataAccess/Models/Configurations/SubjectConfiguration.cs @@ -0,0 +1,17 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace OutOfSchool.Services.Models.Configurations; +public class SubjectConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(x => x.Id); + + builder.HasIndex(x => x.IsDeleted); + + builder.Property(x => x.IsDeleted).HasDefaultValue(false); + + //TO DO + } +} diff --git a/OutOfSchool/OutOfSchool.DataAccess/Models/Language.cs b/OutOfSchool/OutOfSchool.DataAccess/Models/Language.cs new file mode 100644 index 000000000..c9a82f862 --- /dev/null +++ b/OutOfSchool/OutOfSchool.DataAccess/Models/Language.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; + +namespace OutOfSchool.Services.Models; + +/// +/// Represents a language used in the educational system. +/// +public class Language : IKeyedEntity +{ + public long Id { get; set; } + public string Code { get; set; } + public string Title { get; set; } + + public virtual List EducationalDisciplines { get; set; } +} + diff --git a/OutOfSchool/OutOfSchool.DataAccess/Models/StudySubject.cs b/OutOfSchool/OutOfSchool.DataAccess/Models/StudySubject.cs new file mode 100644 index 000000000..2e31da346 --- /dev/null +++ b/OutOfSchool/OutOfSchool.DataAccess/Models/StudySubject.cs @@ -0,0 +1,36 @@ + +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; + +namespace OutOfSchool.Services.Models; + +/// +/// Represents a subject in the educational system. +/// +public class StudySubject : IKeyedEntity, ISoftDeleted +{ + public long Id { get; set; } + public bool IsDeleted { get; set; } + + /// + /// Name in Ukrainian + /// + [Required(ErrorMessage = "The name in Ukrainian is required.")] + public string NameInUkrainian { get; set; } + + /// + /// Name in the language of instruction + /// + [Required(ErrorMessage = "The name in the language of instruction is required.")] + public string NameInInstructionLanguage { get; set; } + + /// + /// Language of instruction (allows multiple selection) + /// + [Required(ErrorMessage = "The language of instruction is required.")] + public virtual List Languages { get; set; } + + public virtual Workshop WorkshopID{ get; set; } + public virtual Workshop Workshop { get; set; } +} + diff --git a/OutOfSchool/OutOfSchool.WebApi/Controllers/V1/StudySubjectController.cs b/OutOfSchool/OutOfSchool.WebApi/Controllers/V1/StudySubjectController.cs new file mode 100644 index 000000000..25813a1d2 --- /dev/null +++ b/OutOfSchool/OutOfSchool.WebApi/Controllers/V1/StudySubjectController.cs @@ -0,0 +1,138 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Localization; +using OutOfSchool.BusinessLogic.Models; +using OutOfSchool.BusinessLogic.Services.ProviderServices; + +namespace OutOfSchool.WebApi.Controllers.V1; + +[ApiController] +[AspApiVersion(1)] +[Route("api/v{version:apiVersion}/[controller]/[action]")] +public class StudySubjectController : ControllerBase +{ + private readonly ISubjectService _studySubjectService; + private readonly IStringLocalizer _localizer; + private readonly IProviderService _providerService; + + /// + /// Initializes a new instance of the class. + /// + /// Service for StudySubject model. + /// Localizer. + /// Service for Provider. + public StudySubjectController( + ISubjectService studySubjectService, + IStringLocalizer localizer, + IProviderService providerService) + { + _providerService = providerService; + _localizer = localizer; + _studySubjectService = studySubjectService; + } + + /// + /// Get all StudySubjects from the database. + /// + /// List of StudySubjects. + [Authorize] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(IEnumerable))] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status500InternalServerError)] + [HttpGet] + public async Task Get() + { + var studySubjects = await _studySubjectService.GetAll().ConfigureAwait(false); + + if (!studySubjects.Any()) + { + return NoContent(); + } + + return Ok(studySubjects); + } + + /// + /// Get StudySubject by it's id. + /// + /// StudySubject's id. + /// StudySubject. + [Authorize] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(StudySubjectDto))] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status500InternalServerError)] + [HttpGet("{id}")] + public async Task GetById(long id) + { + try + { + var studySubjectDto = await _studySubjectService.GetById(id).ConfigureAwait(false); + return Ok(studySubjectDto); + } + catch (ArgumentException e) + { + return BadRequest(e.Message); + } + } + + /// + /// Add a new StudySubject to the database. + /// + /// Entity to add. + /// A representing the result of the asynchronous operation. + [Authorize] + [ProducesResponseType(StatusCodes.Status201Created)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status500InternalServerError)] + [HttpPost] + public async Task Create(StudySubjectDto dto) + { + if (!ModelState.IsValid) + { + return BadRequest(ModelState); + } + + var creationResult = await _studySubjectService.Create(dto).ConfigureAwait(false); + + return CreatedAtAction( + nameof(GetById), + new { id = creationResult.Id, }, + creationResult); + } + + /// + /// Update info about a specific StudySubject in the database. + /// + /// StudySubject to update. + /// StudySubject. + [Authorize] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(StudySubjectDto))] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status500InternalServerError)] + [HttpPut] + public async Task Update(StudySubjectDto dto) + { + if (!ModelState.IsValid) + { + return BadRequest(ModelState); + } + + return Ok(await _studySubjectService.Update(dto).ConfigureAwait(false)); + } + + /// + /// Delete a specific StudySubject entity from the database. + /// + /// StudySubject's id. + /// A representing the result of the asynchronous operation. + [Authorize] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status500InternalServerError)] + [HttpDelete("{id}")] + public async Task Delete(long id) + { + this.ValidateId(id, _localizer); + + await _studySubjectService.Delete(id).ConfigureAwait(false); + return NoContent(); + } +}