Skip to content

Commit

Permalink
Add Classes (#52)
Browse files Browse the repository at this point in the history
* Add `Class` entity

* Add `IClassesService` DTOs for it

* Implement validation for `Class`

* Implement `ClassesService`

* Implement fake data generation

* Remove `Class.DurationTicks` property

Now classes duration is represented by `Class.Duration` property which is `TimeSpan`

* Extend classes service

* Added `Repeats` and `RepeatsDelayDays` properties to `ClassCreateDto`
* Overrided the `CreateAsync` method in `ClassService` so it now can create multiple classes at once.

* Update `ClassCreateDto` validation

* Add class types

* Update test data generation

* Update DTOs and tests

* Implement `ClassesFilter`

* Implement endpoints for classes
  • Loading branch information
romandykyi authored Dec 5, 2023
1 parent 0483739 commit 1bf63cf
Show file tree
Hide file tree
Showing 34 changed files with 4,164 additions and 5 deletions.
9 changes: 9 additions & 0 deletions Core/Dtos/University/ClassCreateDto.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;

namespace EUniversity.Core.Dtos.University;

[ValidateNever] // Remove data annotations validation
public record ClassCreateDto(int ClassTypeId,
int ClassroomId, int GroupId, string? SubstituteTeacherId,
DateTimeOffset StartDate, TimeSpan Duration,
int? Repeats, int? RepeatsDelayDays) : IClassWriteDto;
8 changes: 8 additions & 0 deletions Core/Dtos/University/ClassUpdateDto.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;

namespace EUniversity.Core.Dtos.University;

[ValidateNever] // Remove data annotations validation
public record ClassUpdateDto(int ClassTypeId,
int ClassroomId, int GroupId, string? SubstituteTeacherId,
DateTimeOffset StartDate, TimeSpan Duration) : IClassWriteDto;
14 changes: 14 additions & 0 deletions Core/Dtos/University/ClassViewDto.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using EUniversity.Core.Dtos.Users;

namespace EUniversity.Core.Dtos.University;

public record ClassViewDto(int Id,
DateTimeOffset StartDate, TimeSpan Duration,
DateTimeOffset CreationDate, DateTimeOffset UpdateDate,
ClassGroupViewDto Group,
TeacherPreviewDto? SubstituteTeacher);

public record ClassGroupViewDto(int Id, string Name,
TeacherPreviewDto? Teacher, ClassCourseViewDto Course);

public record ClassCourseViewDto(int Id, string Name, SemesterMinimalViewDto? Semester);
13 changes: 13 additions & 0 deletions Core/Dtos/University/IClassWriteDto.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
namespace EUniversity.Core.Dtos.University;

public interface IClassWriteDto
{
public int ClassTypeId { get; }
public int ClassroomId { get; }
public int GroupId { get; }
public string? SubstituteTeacherId { get; }

public DateTimeOffset StartDate { get; }

public TimeSpan Duration { get; }
}
6 changes: 6 additions & 0 deletions Core/Mapping/MappingGlobalSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ public static void Apply()
.IgnoreIf((src, dest) => src.TeacherId == null, dest => dest.Teacher!);
TypeAdapterConfig<Course, CoursePreviewDto>.NewConfig()
.IgnoreIf((src, dest) => src.SemesterId == null, dest => dest.Semester!);
TypeAdapterConfig<Class, ClassViewDto>.NewConfig()
.IgnoreIf((src, dest) => src.SubstituteTeacher == null, dest => dest.SubstituteTeacher!);
TypeAdapterConfig<Group, ClassGroupViewDto>.NewConfig()
.IgnoreIf((src, dest) => src.Teacher == null, dest => dest.Teacher!);
TypeAdapterConfig<Course, ClassCourseViewDto>.NewConfig()
.IgnoreIf((src, dest) => src.Semester == null, dest => dest.Semester!);

TypeAdapterConfig.GlobalSettings.Default
.AddDestinationTransform((string? dest) => string.IsNullOrWhiteSpace(dest) ? null : dest);
Expand Down
70 changes: 70 additions & 0 deletions Core/Models/University/Class.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
using System.ComponentModel.DataAnnotations.Schema;

namespace EUniversity.Core.Models.University;

/// <summary>
/// Represents a class schedule entity,
/// which contains date, duration, group and optional substitute teacher.
/// </summary>
public class Class : IEntity<int>, IHasCreationDate, IHasUpdateDate
{
[Key]
public int Id { get; set; }

/// <summary>
/// Foreign key of the type of this class.
/// </summary>
[ForeignKey(nameof(ClassType))]
public int ClassTypeId { get; set; }
/// <summary>
/// Foreign key of the classroom associated with this class.
/// </summary>
[ForeignKey(nameof(Classroom))]
public int ClassroomId { get; set; }
/// <summary>
/// Foreign key of the group associated with this class.
/// </summary>
[ForeignKey(nameof(Group))]
public int GroupId { get; set; }
/// <summary>
/// Foreign key of the substitute teacher associated with this class(can be null).
/// </summary>
[ForeignKey(nameof(SubstituteTeacher))]
public string? SubstituteTeacherId { get; set; }

/// <summary>
/// Date when class starts.
/// </summary>
public DateTimeOffset StartDate { get; set; }

/// <summary>
/// Duration of this class.
/// </summary>
public TimeSpan Duration { get; set; }

/// <summary>
/// Date when the class was created.
/// </summary>
public DateTimeOffset CreationDate { get; set; }
/// <summary>
/// Date when the class was last updated.
/// </summary>
public DateTimeOffset UpdateDate { get; set; }

/// <summary>
/// Navigation property for the type of this class.
/// </summary>
public ClassType? ClassType { get; set; }
/// <summary>
/// Navigation property for the classroom associated with this class.
/// </summary>
public Classroom? Classroom { get; set; }
/// <summary>
/// Navigation property for the group associated with this class.
/// </summary>
public Group? Group { get; set; }
/// <summary>
/// Navigation property for the substitute associated with this class(can be null).
/// </summary>
public ApplicationUser? SubstituteTeacher { get; set; }
}
26 changes: 26 additions & 0 deletions Core/Models/University/ClassType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
namespace EUniversity.Core.Models.University;

/// <summary>
/// Represents a class type(e.g. lecture).
/// </summary>
public class ClassType : IEntity<int>, IHasName, IHasCreationDate, IHasUpdateDate
{
public const int MaxNameLength = 100;

[Key]
public int Id { get; set; }
/// <summary>
/// Name of the class type.
/// </summary>
[StringLength(MaxNameLength)]
public string Name { get; set; } = null!;

/// <summary>
/// Date when the class type was created.
/// </summary>
public DateTimeOffset CreationDate { get; set; }
/// <summary>
/// Date when the class type was last updated.
/// </summary>
public DateTimeOffset UpdateDate { get; set; }
}
13 changes: 13 additions & 0 deletions Core/Services/University/IClassesService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using EUniversity.Core.Dtos.University;
using EUniversity.Core.Models.University;

namespace EUniversity.Core.Services.University;

/// <summary>
/// Service for classes.
/// </summary>
public interface IClassesService :
ICrudService<Class, int, ClassViewDto, ClassViewDto, ClassCreateDto, ClassUpdateDto>
{

}
35 changes: 35 additions & 0 deletions Core/Validation/University/ClassCreateDtoValidator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using EUniversity.Core.Dtos.University;
using EUniversity.Core.Models;
using EUniversity.Core.Services;
using FluentValidation;
using Microsoft.AspNetCore.Identity;

namespace EUniversity.Core.Validation.University;

public class ClassCreateDtoValidator : ClassWriteDtoValidator<ClassCreateDto>
{
public ClassCreateDtoValidator(IEntityExistenceChecker existenceChecker, UserManager<ApplicationUser> userManager) : base(existenceChecker, userManager)
{
RuleFor(c => c.Repeats)
.GreaterThan(0)
.When(c => c.Repeats != null)
.WithErrorCode(ValidationErrorCodes.InvalidRange)
.WithMessage("Repeats must be a positive number");
RuleFor(c => c.RepeatsDelayDays)
.GreaterThan(0)
.When(c => c.RepeatsDelayDays != null)
.WithErrorCode(ValidationErrorCodes.InvalidRange)
.WithMessage("RepeatsDelayDays must be a positive number");

RuleFor(c => c.Repeats)
.NotNull()
.When(c => c.RepeatsDelayDays != null)
.WithErrorCode(ValidationErrorCodes.PropertyRequired)
.WithMessage("Repeats must be specified when using RepeatsDelayDays");
RuleFor(c => c.RepeatsDelayDays)
.NotNull()
.When(c => c.Repeats != null)
.WithErrorCode(ValidationErrorCodes.PropertyRequired)
.WithMessage("RepeatsDelayDays must be specified when using Repeats");
}
}
13 changes: 13 additions & 0 deletions Core/Validation/University/ClassUpdateDtoValidator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using EUniversity.Core.Dtos.University;
using EUniversity.Core.Models;
using EUniversity.Core.Services;
using Microsoft.AspNetCore.Identity;

namespace EUniversity.Core.Validation.University;

public class ClassUpdateDtoValidator : ClassWriteDtoValidator<ClassUpdateDto>
{
public ClassUpdateDtoValidator(IEntityExistenceChecker existenceChecker, UserManager<ApplicationUser> userManager) : base(existenceChecker, userManager)
{
}
}
38 changes: 38 additions & 0 deletions Core/Validation/University/ClassWriteDtoValidator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using EUniversity.Core.Dtos.University;
using EUniversity.Core.Models;
using EUniversity.Core.Models.University;
using EUniversity.Core.Policy;
using EUniversity.Core.Services;
using EUniversity.Core.Validation.Extensions;
using FluentValidation;
using Microsoft.AspNetCore.Identity;

namespace EUniversity.Core.Validation.University;

public abstract class ClassWriteDtoValidator<T> : AbstractValidator<T>
where T : IClassWriteDto
{
public ClassWriteDtoValidator(IEntityExistenceChecker existenceChecker,
UserManager<ApplicationUser> userManager)
{
RuleFor(c => c.ClassroomId)
.MustAsync(async (id, _) =>
await existenceChecker.ExistsAsync<Classroom, int>(id))
.WithErrorCode(ValidationErrorCodes.InvalidForeignKey)
.WithMessage("Classroom does not exist");
RuleFor(c => c.GroupId)
.MustAsync(async (id, _) =>
await existenceChecker.ExistsAsync<Group, int>(id))
.WithErrorCode(ValidationErrorCodes.InvalidForeignKey)
.WithMessage("Group does not exist");

RuleFor(c => c.SubstituteTeacherId!)
.IsIdOfValidUserInRole(userManager, Roles.Teacher)
.When(c => !string.IsNullOrWhiteSpace(c.SubstituteTeacherId));

RuleFor(c => c.Duration)
.GreaterThan(TimeSpan.Zero)
.WithErrorCode(ValidationErrorCodes.InvalidRange)
.WithMessage("Duration cannot be negative or zero");
}
}
Loading

0 comments on commit 1bf63cf

Please sign in to comment.