diff --git a/Tsi1.Api/Tsi1.Api/Controllers/ActivityController.cs b/Tsi1.Api/Tsi1.Api/Controllers/ActivityController.cs new file mode 100644 index 0000000000000000000000000000000000000000..d8593231480eb1fd0522e128aaf581bd75f34cd9 --- /dev/null +++ b/Tsi1.Api/Tsi1.Api/Controllers/ActivityController.cs @@ -0,0 +1,84 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Tsi1.BusinessLayer.Dtos; +using Tsi1.BusinessLayer.Helpers; +using Tsi1.BusinessLayer.Interfaces; + +namespace Tsi1.Api.Controllers +{ + [Route("api/[controller]")] + [ApiController] + public class ActivityController : ControllerBase + { + private readonly IActivityService _activityService; + + public ActivityController(IActivityService activityService) + { + _activityService = activityService; + } + + [Authorize(Roles = UserTypes.FacultyAdmin + ", " + UserTypes.Professor + ", " + UserTypes.Student)] + [HttpGet("GetAll/{courseId}")] + public async Task<IActionResult> GetAll(int courseId) + { + var result = await _activityService.GetAll(courseId); + + if (result.HasError) + { + return BadRequest(result.Message); + } + + return Ok(result.Data); + } + + [Authorize(Roles = UserTypes.FacultyAdmin + ", " + UserTypes.Professor)] + [HttpPost("Create")] + public async Task<IActionResult> Create(ActivityCreateDto newActivity) + { + var userId = int.Parse(HttpContext.User.Claims.FirstOrDefault(x => x.Type == "Id").Value); + var result = await _activityService.Create(newActivity, userId); + + if (result.HasError) + { + return BadRequest(result.Message); + } + + return Ok(result.Data); + } + + [Authorize(Roles = UserTypes.FacultyAdmin + ", " + UserTypes.Professor)] + [HttpPut("Modify/{activityId}")] + public async Task<IActionResult> Modify(int activityId, ActivityModifyDto activityDto) + { + var userId = int.Parse(HttpContext.User.Claims.FirstOrDefault(x => x.Type == "Id").Value); + var result = await _activityService.Modify(activityId, activityDto, userId); + + if (result.HasError) + { + return BadRequest(result.Message); + } + + return Ok(); + } + + [Authorize(Roles = UserTypes.FacultyAdmin + ", " + UserTypes.Professor)] + [HttpDelete("Delete/{activityId}")] + public async Task<IActionResult> Delete(int activityId) + { + var userId = int.Parse(HttpContext.User.Claims.FirstOrDefault(x => x.Type == "Id").Value); + var result = await _activityService.Delete(activityId, userId); + + if (result.HasError) + { + return BadRequest(result.Message); + } + + return Ok(); + } + } +} diff --git a/Tsi1.Api/Tsi1.Api/Controllers/TenantController.cs b/Tsi1.Api/Tsi1.Api/Controllers/TenantController.cs index 12046260b0b893041955a44bd2388bf916f083ad..9ffbc2b3037b88c37929ffa00ef8036999aafa3a 100644 --- a/Tsi1.Api/Tsi1.Api/Controllers/TenantController.cs +++ b/Tsi1.Api/Tsi1.Api/Controllers/TenantController.cs @@ -106,7 +106,7 @@ namespace Tsi1.Api.Controllers [HttpGet("AllCourseStudentQuantity")] public async Task<IActionResult> AllCourseStudentQuantity() { - var result = await _tenantService.CourseStudentQuantity(); + var result = await _tenantService.AllCourseStudentQuantity(); if (result.HasError) { @@ -145,5 +145,21 @@ namespace Tsi1.Api.Controllers return Ok(result.Data); } + + [Authorize(Roles = UserTypes.FacultyAdmin)] + [HttpGet("CourseAttendance")] + public async Task<IActionResult> CourseAttendance() + { + var tenantId = int.Parse(HttpContext.User.Claims.FirstOrDefault(x => x.Type == "TenantId").Value); + + var result = await _tenantService.CourseAttendance(tenantId); + + if (result.HasError) + { + return BadRequest(result.Message); + } + + return Ok(result.Data); + } } } diff --git a/Tsi1.Api/Tsi1.Api/SignalR/VideoHub.cs b/Tsi1.Api/Tsi1.Api/SignalR/VideoHub.cs index f0c0eeeb4f242aea7a4343fac04136bc42c982fa..4744e3c5304f6f3e848bc92fbd163c60d84a93e7 100644 --- a/Tsi1.Api/Tsi1.Api/SignalR/VideoHub.cs +++ b/Tsi1.Api/Tsi1.Api/SignalR/VideoHub.cs @@ -5,12 +5,20 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Tsi1.BusinessLayer.Helpers; +using Tsi1.BusinessLayer.Interfaces; namespace Tsi1.Api.SignalR { [Authorize] public class VideoHub : Hub { + private readonly IActivityService _activityService; + + public VideoHub(IActivityService activityService) + { + _activityService = activityService; + } + public override async Task OnConnectedAsync() { await Groups.AddToGroupAsync(Context.ConnectionId, Context.ConnectionId); @@ -19,18 +27,34 @@ namespace Tsi1.Api.SignalR } [Authorize(Roles = UserTypes.Professor)] - public async Task CreateRoom(string roomName) + public async Task CreateRoom(int activityId) { - await Groups.AddToGroupAsync(Context.ConnectionId, roomName); + var result = await _activityService.ActivityValidation(activityId); + + if (result.HasError) + { + throw new HubException(result.Message); + } - await Clients.Caller.SendAsync("CreateRoom", roomName); + await Groups.AddToGroupAsync(Context.ConnectionId, activityId.ToString()); + + await Clients.Caller.SendAsync("CreateRoom", activityId); } - public async Task Join(string roomName, string offer) + public async Task Join(int activityId, string offer) { - await Clients.Group(roomName).SendAsync("Join", Context.ConnectionId, offer); + var userId = int.Parse(Context.User.Claims.FirstOrDefault(x => x.Type == "Id").Value); + + var result = await _activityService.AttendVirtualClass(activityId, userId); + + if (result.HasError) + { + throw new HubException(result.Message); + } + + await Clients.Group(activityId.ToString()).SendAsync("Join", Context.ConnectionId, offer); - await Groups.AddToGroupAsync(Context.ConnectionId, roomName); + await Groups.AddToGroupAsync(Context.ConnectionId, activityId.ToString()); } public async Task Leave(string roomName) diff --git a/Tsi1.Api/Tsi1.Api/Startup.cs b/Tsi1.Api/Tsi1.Api/Startup.cs index 90bca24e58e0280359f8a1a93b2ecf7f173109cb..d27fa6ae2007f1b94d51e98e5f22d00c440ed32d 100644 --- a/Tsi1.Api/Tsi1.Api/Startup.cs +++ b/Tsi1.Api/Tsi1.Api/Startup.cs @@ -95,6 +95,7 @@ namespace Tsi1.Api services.AddScoped<ISurveyService, SurveyService>(); services.AddScoped<ICommunicationService, CommunicationService>(); services.AddScoped<IChatService, ChatService>(); + services.AddScoped<IActivityService, ActivityService>(); services.AddSingleton<PresenceTracker>(); diff --git a/Tsi1.Api/Tsi1.BusinessLayer/Dtos/ActivityCreateDto.cs b/Tsi1.Api/Tsi1.BusinessLayer/Dtos/ActivityCreateDto.cs new file mode 100644 index 0000000000000000000000000000000000000000..c320b4602d2b2bdfe628999a22f53a36eb728650 --- /dev/null +++ b/Tsi1.Api/Tsi1.BusinessLayer/Dtos/ActivityCreateDto.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Tsi1.BusinessLayer.Dtos +{ + public class ActivityCreateDto + { + public string Name { get; set; } + + public bool IsVideoConference { get; set; } + + public int CourseId { get; set; } + } +} diff --git a/Tsi1.Api/Tsi1.BusinessLayer/Dtos/ActivityDto.cs b/Tsi1.Api/Tsi1.BusinessLayer/Dtos/ActivityDto.cs new file mode 100644 index 0000000000000000000000000000000000000000..ce33fcc5be89071ca759f3085112e5743c3a444b --- /dev/null +++ b/Tsi1.Api/Tsi1.BusinessLayer/Dtos/ActivityDto.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Tsi1.BusinessLayer.Dtos +{ + public class ActivityDto + { + public int Id { get; set; } + + public string Name { get; set; } + + public bool IsVideoConference { get; set; } + } +} diff --git a/Tsi1.Api/Tsi1.BusinessLayer/Dtos/ActivityModifyDto.cs b/Tsi1.Api/Tsi1.BusinessLayer/Dtos/ActivityModifyDto.cs new file mode 100644 index 0000000000000000000000000000000000000000..084adde789d42cc7542cd6585e793690323a13d3 --- /dev/null +++ b/Tsi1.Api/Tsi1.BusinessLayer/Dtos/ActivityModifyDto.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Tsi1.BusinessLayer.Dtos +{ + public class ActivityModifyDto + { + public string Name { get; set; } + + public bool IsVideoConference { get; set; } + } +} diff --git a/Tsi1.Api/Tsi1.BusinessLayer/Dtos/CourseAttendanceDto.cs b/Tsi1.Api/Tsi1.BusinessLayer/Dtos/CourseAttendanceDto.cs new file mode 100644 index 0000000000000000000000000000000000000000..608b1cdc3c112e139f2789ee499bfd3ba54f7ae0 --- /dev/null +++ b/Tsi1.Api/Tsi1.BusinessLayer/Dtos/CourseAttendanceDto.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Tsi1.BusinessLayer.Dtos +{ + public class CourseAttendanceDto + { + public int Id { get; set; } + + public string Name { get; set; } + + public List<UserAttendanceDto> Users { get; set; } + } +} diff --git a/Tsi1.Api/Tsi1.BusinessLayer/Dtos/UserAttendanceDto.cs b/Tsi1.Api/Tsi1.BusinessLayer/Dtos/UserAttendanceDto.cs new file mode 100644 index 0000000000000000000000000000000000000000..f46bfda426217a11e19cabbe96ace8f802e3b369 --- /dev/null +++ b/Tsi1.Api/Tsi1.BusinessLayer/Dtos/UserAttendanceDto.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Tsi1.BusinessLayer.Dtos +{ + public class UserAttendanceDto + { + public string FirstName { get; set; } + + public string LastName { get; set; } + + public string Email { get; set; } + + public int Quantity { get; set; } + } +} diff --git a/Tsi1.Api/Tsi1.BusinessLayer/Helpers/ErrorMessages.cs b/Tsi1.Api/Tsi1.BusinessLayer/Helpers/ErrorMessages.cs index b049c0244d6ec63c379544c1c0da7f90a475a6ad..1cffd0551686cd30f851493c7d64788b98fe0b2a 100644 --- a/Tsi1.Api/Tsi1.BusinessLayer/Helpers/ErrorMessages.cs +++ b/Tsi1.Api/Tsi1.BusinessLayer/Helpers/ErrorMessages.cs @@ -12,7 +12,7 @@ namespace Tsi1.BusinessLayer.Helpers public const string StudentDoesNotExist = "El estudiante con Id de usuario: '{0}' no existe"; public const string StudentCourseDoesNotExists = "El estudiante '{0}' no se encuentra matriculado en el curso '{1}'"; public const string StudentCourseAlreadyExists = "El estudiante '{0}' ya se encuentra matriculado en el curso '{1}'"; - public const string ProffesorDoesNotExist = "El profesor con Id de usuario: '{0}' no existe"; + public const string ProfessorDoesNotExist = "El profesor con Id de usuario: '{0}' no existe"; public const string ProfessorCourseAlreadyExists = "El profesor '{0}' ya es docente del curso '{1}'"; public const string ProfessorCourseDoesNotExists = "El profesor '{0}' no es docente del curso '{1}'"; public const string InvalidUsername = "El nombre de usuario debe ser de la forma: 'usuario@facultad'"; @@ -64,5 +64,7 @@ namespace Tsi1.BusinessLayer.Helpers public const string CommunicationDoesNotExist = "La comunicación con id '{0}' no existe"; public const string InvalidTenant = "El usuario no pertenece a la facultad con id '{0}'"; + + public const string ActivityDoesNotExist = "La actividad con id '{0}' no existe"; } } diff --git a/Tsi1.Api/Tsi1.BusinessLayer/Helpers/MappingProfile.cs b/Tsi1.Api/Tsi1.BusinessLayer/Helpers/MappingProfile.cs index a72d596c8c6f48de65abdf53c03fd9c2fee4e78a..8116ab99f8b1205330ca9a3e615ea49ff68fb9c3 100644 --- a/Tsi1.Api/Tsi1.BusinessLayer/Helpers/MappingProfile.cs +++ b/Tsi1.Api/Tsi1.BusinessLayer/Helpers/MappingProfile.cs @@ -54,6 +54,9 @@ namespace Tsi1.BusinessLayer.Helpers CreateMap<AnswerOption, AnswerOptionDetailDto>(); CreateMap<Tenant, RegistrationDto>(); CreateMap<Course, RegistrationDto>(); + CreateMap<Activity, ActivityCreateDto>(); + CreateMap<Activity, ActivityModifyDto>(); + CreateMap<Activity, ActivityDto>(); CreateMap<ForumCreateDto, Forum>(); CreateMap<ForumPreviewDto, Forum>(); @@ -97,6 +100,9 @@ namespace Tsi1.BusinessLayer.Helpers CreateMap<AnswerOptionDetailDto, AnswerOption>(); CreateMap<RegistrationDto, Tenant>(); CreateMap<RegistrationDto, Course>(); + CreateMap<ActivityCreateDto, Activity>(); + CreateMap<ActivityModifyDto, Activity>(); + CreateMap<ActivityDto, Activity>(); } } } diff --git a/Tsi1.Api/Tsi1.BusinessLayer/Interfaces/IActivityService.cs b/Tsi1.Api/Tsi1.BusinessLayer/Interfaces/IActivityService.cs new file mode 100644 index 0000000000000000000000000000000000000000..4b0e88ad468dc25357ddc96aef56ce4b4143ead4 --- /dev/null +++ b/Tsi1.Api/Tsi1.BusinessLayer/Interfaces/IActivityService.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; +using Tsi1.BusinessLayer.Dtos; +using Tsi1.BusinessLayer.Helpers; + +namespace Tsi1.BusinessLayer.Interfaces +{ + public interface IActivityService + { + Task<ServiceResult<bool>> AttendVirtualClass(int activityId, int userId); + Task<ServiceResult<List<ActivityDto>>> GetAll(int courseId); + Task<ServiceResult<int>> Create(ActivityCreateDto newActivity, int userId); + Task<ServiceResult<int>> Modify(int activityId, ActivityModifyDto activityDto, int userId); + Task<ServiceResult<int>> Delete(int activityId, int userId); + + Task<ServiceResult<int>> ActivityValidation(int activityId); + } +} diff --git a/Tsi1.Api/Tsi1.BusinessLayer/Interfaces/ITenantService.cs b/Tsi1.Api/Tsi1.BusinessLayer/Interfaces/ITenantService.cs index fb19c6e5f9572ae47fdf81b69074e699ada0828a..4390132671345531ee26f7a02b93ccda724d30f4 100644 --- a/Tsi1.Api/Tsi1.BusinessLayer/Interfaces/ITenantService.cs +++ b/Tsi1.Api/Tsi1.BusinessLayer/Interfaces/ITenantService.cs @@ -23,10 +23,11 @@ namespace Tsi1.BusinessLayer.Interfaces Task<ServiceResult<List<TenantPreviewDto>>> FacultyList(); - Task<ServiceResult<List<TenantCourseDto>>> CourseStudentQuantity(); + Task<ServiceResult<List<TenantCourseDto>>> AllCourseStudentQuantity(); Task<ServiceResult<List<RegistrationDto>>> FacultyRegistrations(); Task<ServiceResult<List<RegistrationDto>>> CourseStudentQuantity(int tenantId); + Task<ServiceResult<List<CourseAttendanceDto>>> CourseAttendance(int tenantId); } } diff --git a/Tsi1.Api/Tsi1.BusinessLayer/Services/ActivityService.cs b/Tsi1.Api/Tsi1.BusinessLayer/Services/ActivityService.cs new file mode 100644 index 0000000000000000000000000000000000000000..9ab568e205b73ebae1f82ae0844478cb6f772fc7 --- /dev/null +++ b/Tsi1.Api/Tsi1.BusinessLayer/Services/ActivityService.cs @@ -0,0 +1,227 @@ +using AutoMapper; +using Microsoft.EntityFrameworkCore; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Tsi1.BusinessLayer.Dtos; +using Tsi1.BusinessLayer.Helpers; +using Tsi1.BusinessLayer.Interfaces; +using Tsi1.DataLayer; +using Tsi1.DataLayer.Entities; + +namespace Tsi1.BusinessLayer.Services +{ + public class ActivityService : IActivityService + { + private readonly Tsi1Context _context; + private readonly IMapper _mapper; + + public ActivityService(Tsi1Context context, IMapper mapper) + { + _context = context; + _mapper = mapper; + } + + public async Task<ServiceResult<bool>> AttendVirtualClass(int activityId, int userId) + { + var result = new ServiceResult<bool>(); + + var activity = await _context.Activities.FirstOrDefaultAsync(x => x.Id == activityId); + + if (activity == null) + { + result.HasError = true; + result.AddMessage(string.Format(ErrorMessages.ActivityDoesNotExist, activityId)); + } + + var attendance = await _context.Attendances + .FirstOrDefaultAsync(x => x.ActivityId == activityId && x.UserId == userId); + + if (attendance == null) + { + attendance = new Attendance + { + Activity = activity, + UserId = userId, + Date = DateTime.Now, + }; + } + + return result; + } + + public async Task<ServiceResult<int>> ActivityValidation(int activityId) + { + var result = new ServiceResult<int>(); + + var activity = await _context.Activities.AsNoTracking().FirstOrDefaultAsync(x => x.Id == activityId); + + if (activity == null) + { + result.HasError = true; + result.AddMessage(string.Format(ErrorMessages.ActivityDoesNotExist, activityId)); + } + + return result; + } + + private async Task<ServiceResult<bool>> UserTypeValidation(int courseId, int userId) + { + var result = new ServiceResult<bool>(); + + var user = await _context.Users.AsNoTracking() + .Include(x => x.UserType) + .FirstOrDefaultAsync(x => x.Id == userId); + + if (user == null) + { + result.HasError = true; + result.AddMessage(string.Format(ErrorMessages.UserDoesNotExist, userId)); + return result; + } + + if (user.UserType.Name == UserTypes.Professor) + { + var isProfessorCourse = await _context.ProfessorCourses + .AsNoTracking() + .AnyAsync(x => x.ProfessorId == user.ProfessorId && x.CourseId == courseId); + + if (!isProfessorCourse) + { + result.HasError = true; + result.AddMessage(string.Format(ErrorMessages.ProfessorCourseDoesNotExists, user.Id, courseId)); + + } + } + + return result; + } + + private async Task<ServiceResult<bool>> CourseValidation(int courseId) + { + var result = new ServiceResult<bool>(); + + var course = await _context.Courses.AsNoTracking().FirstOrDefaultAsync(x => x.Id == courseId); + + if (course == null) + { + result.HasError = true; + result.AddMessage(string.Format(ErrorMessages.CourseDoesNotExist, courseId)); + } + + return result; + } + + public async Task<ServiceResult<List<ActivityDto>>> GetAll(int courseId) + { + var result = new ServiceResult<List<ActivityDto>>(); + + var validation = await this.CourseValidation(courseId); + + if (validation.HasError) + { + result.HasError = true; + result.AddMessage(validation.Message); + return result; + } + + var activities = await _context.Activities.AsNoTracking().Where(x => x.CourseId == courseId).ToListAsync(); + + result.Data = _mapper.Map<List<ActivityDto>>(activities); + return result; + } + + public async Task<ServiceResult<int>> Create(ActivityCreateDto newActivity, int userId) + { + var result = new ServiceResult<int>(); + + var courseValidation = await this.CourseValidation(newActivity.CourseId); + + if (courseValidation.HasError) + { + result.HasError = true; + result.AddMessage(courseValidation.Message); + return result; + } + + var userTypeValidation = await this.UserTypeValidation(newActivity.CourseId, userId); + + if (userTypeValidation.HasError) + { + result.HasError = true; + result.AddMessage(userTypeValidation.Message); + return result; + } + + var activity = _mapper.Map<Activity>(newActivity); + _context.Activities.Add(activity); + + await _context.SaveChangesAsync(); + + result.Data = activity.Id; + return result; + } + + public async Task<ServiceResult<int>> Modify(int activityId, ActivityModifyDto activityDto, int userId) + { + var result = new ServiceResult<int>(); + + var activity = await _context.Activities.FirstOrDefaultAsync(x => x.Id == activityId); + + if (activity == null) + { + result.HasError = true; + result.AddMessage(string.Format(ErrorMessages.ActivityDoesNotExist, activityId)); + return result; + } + + var userTypeValidation = await this.UserTypeValidation(activity.CourseId, userId); + + if (userTypeValidation.HasError) + { + result.HasError = true; + result.AddMessage(userTypeValidation.Message); + return result; + } + + _mapper.Map(activityDto, activity); + + await _context.SaveChangesAsync(); + + result.Data = activity.Id; + return result; + } + + public async Task<ServiceResult<int>> Delete(int activityId, int userId) + { + var result = new ServiceResult<int>(); + + var activity = await _context.Activities.FirstOrDefaultAsync(x => x.Id == activityId); + + if (activity == null) + { + result.HasError = true; + result.AddMessage(string.Format(ErrorMessages.ActivityDoesNotExist, activityId)); + return result; + } + + var userTypeValidation = await this.UserTypeValidation(activity.CourseId, userId); + + if (userTypeValidation.HasError) + { + result.HasError = true; + result.AddMessage(userTypeValidation.Message); + return result; + } + + _context.Activities.Remove(activity); + + await _context.SaveChangesAsync(); + + result.Data = activity.Id; + return result; + } + } +} diff --git a/Tsi1.Api/Tsi1.BusinessLayer/Services/TenantService.cs b/Tsi1.Api/Tsi1.BusinessLayer/Services/TenantService.cs index e38b78b8b60391907877802ec9f7b4f3da6e4e81..2a41d0d352248bc47e19f1a1430a644396a11f83 100644 --- a/Tsi1.Api/Tsi1.BusinessLayer/Services/TenantService.cs +++ b/Tsi1.Api/Tsi1.BusinessLayer/Services/TenantService.cs @@ -158,7 +158,7 @@ namespace Tsi1.BusinessLayer.Services return result; } - public async Task<ServiceResult<List<TenantCourseDto>>> CourseStudentQuantity() + public async Task<ServiceResult<List<TenantCourseDto>>> AllCourseStudentQuantity() { var result = new ServiceResult<List<TenantCourseDto>>(); @@ -238,5 +238,69 @@ namespace Tsi1.BusinessLayer.Services return result; } + + public async Task<ServiceResult<List<CourseAttendanceDto>>> CourseAttendance(int tenantId) + { + var result = new ServiceResult<List<CourseAttendanceDto>>(); + + var tenant = await _context.Tenants.FirstOrDefaultAsync(x => x.Id == tenantId && x.Name != TenantAdmin.Name); + + if (tenant == null) + { + result.HasError = true; + result.AddMessage(string.Format(ErrorMessages.TenantDoesNotExist, tenantId)); + return result; + } + + var courses = await _context.Courses + .AsNoTracking() + .Include(x => x.StudentCourses) + .Where(x => x.TenantId == tenantId && !x.IsTemplate) + .ToListAsync(); + + var attendances = await _context.Attendances + .AsNoTracking() + .Include(x => x.Activity) + .Where(x => x.User.TenantId == tenantId) + .ToListAsync(); + + var courseAttendanceDtos = new List<CourseAttendanceDto>(); + foreach (var course in courses) + { + var courseAttendanceDto = new CourseAttendanceDto + { + Id = course.Id, + Name = course.Name, + Users = new List<UserAttendanceDto>(), + }; + + var studentIds = course.StudentCourses.Select(x => x.StudentId).ToList(); + + var users = await _context.Users + .AsNoTracking() + .Where(x => x.TenantId == tenantId && studentIds.Contains((int)x.StudentId)) + .ToListAsync(); + + foreach (var user in users) + { + var attendanceQuantity = attendances.Where(x => x.UserId == user.Id && x.Activity.CourseId == course.Id).Count(); + + var userAttendanceDto = new UserAttendanceDto + { + Email = user.Email, + FirstName = user.FirstName, + LastName = user.LastName, + Quantity = attendanceQuantity + }; + + courseAttendanceDto.Users.Add(userAttendanceDto); + } + + courseAttendanceDtos.Add(courseAttendanceDto); + } + + result.Data = courseAttendanceDtos; + return result; + } } } diff --git a/Tsi1.Api/Tsi1.BusinessLayer/Services/UserService.cs b/Tsi1.Api/Tsi1.BusinessLayer/Services/UserService.cs index ca09556ce683038f99c0841051b99a58b7e6cb49..1536b9635bc33adbf87e259503498e3eec976906 100644 --- a/Tsi1.Api/Tsi1.BusinessLayer/Services/UserService.cs +++ b/Tsi1.Api/Tsi1.BusinessLayer/Services/UserService.cs @@ -178,7 +178,7 @@ namespace Tsi1.BusinessLayer.Services else if(userType == UserTypes.Professor && user.Professor == null) { result.HasError = true; - result.Message = string.Format(ErrorMessages.ProffesorDoesNotExist, userId); + result.Message = string.Format(ErrorMessages.ProfessorDoesNotExist, userId); return result; } @@ -219,7 +219,7 @@ namespace Tsi1.BusinessLayer.Services else if (userType == UserTypes.Professor && user.Professor == null) { result.HasError = true; - result.Message = string.Format(ErrorMessages.ProffesorDoesNotExist, username); + result.Message = string.Format(ErrorMessages.ProfessorDoesNotExist, username); return result; } diff --git a/Tsi1.Api/Tsi1.DataLayer/Entities/Activity.cs b/Tsi1.Api/Tsi1.DataLayer/Entities/Activity.cs new file mode 100644 index 0000000000000000000000000000000000000000..4a0ad033eb9e53de2188c8f90652f206fe309ef3 --- /dev/null +++ b/Tsi1.Api/Tsi1.DataLayer/Entities/Activity.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Tsi1.DataLayer.Entities +{ + public class Activity + { + public Activity() + { + Attendances = new HashSet<Attendance>(); + } + + public int Id { get; set; } + + public string Name { get; set; } + + public bool IsVideoConference { get; set; } + + public int CourseId { get; set; } + + public Course Course { get; set; } + + public ICollection<Attendance> Attendances { get; set; } + } +} diff --git a/Tsi1.Api/Tsi1.DataLayer/Entities/Attendance.cs b/Tsi1.Api/Tsi1.DataLayer/Entities/Attendance.cs new file mode 100644 index 0000000000000000000000000000000000000000..4c2257aefd62a77cafff5ba058d7b50a4b0de595 --- /dev/null +++ b/Tsi1.Api/Tsi1.DataLayer/Entities/Attendance.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Tsi1.DataLayer.Entities +{ + public class Attendance + { + public int Id { get; set; } + + public DateTime Date { get; set; } + + public int UserId { get; set; } + + public int ActivityId { get; set; } + + public User User { get; set; } + + public Activity Activity { get; set; } + } +} diff --git a/Tsi1.Api/Tsi1.DataLayer/Entities/Course.cs b/Tsi1.Api/Tsi1.DataLayer/Entities/Course.cs index f7efedf6afcfa34052001aa393799e23773d8eb3..c09ae3aa10f4233b77cc2c99e2c06fc9983619bb 100644 --- a/Tsi1.Api/Tsi1.DataLayer/Entities/Course.cs +++ b/Tsi1.Api/Tsi1.DataLayer/Entities/Course.cs @@ -12,6 +12,7 @@ namespace Tsi1.DataLayer.Entities ProfessorCourses = new HashSet<ProfessorCourse>(); Sections = new HashSet<Section>(); Communications = new HashSet<Communication>(); + Activities = new HashSet<Activity>(); } public int Id { get; set; } @@ -25,5 +26,7 @@ namespace Tsi1.DataLayer.Entities public ICollection<Section> Sections { get; set; } public ICollection<Communication> Communications { get; set; } + + public ICollection<Activity> Activities { get; set; } } } diff --git a/Tsi1.Api/Tsi1.DataLayer/Entities/User.cs b/Tsi1.Api/Tsi1.DataLayer/Entities/User.cs index e3656322faa236d10a84b18c1fc4985cb240280d..fb5427eb77f6c5ea543ea0b1bdb210617d8631ff 100644 --- a/Tsi1.Api/Tsi1.DataLayer/Entities/User.cs +++ b/Tsi1.Api/Tsi1.DataLayer/Entities/User.cs @@ -12,6 +12,7 @@ namespace Tsi1.DataLayer.Entities PostMessages = new HashSet<PostMessage>(); ForumUsers = new HashSet<ForumUser>(); SurveyResponses = new HashSet<SurveyResponse>(); + Attendances = new HashSet<Attendance>(); } public int Id { get; set; } @@ -43,5 +44,6 @@ namespace Tsi1.DataLayer.Entities public ICollection<PostMessage> PostMessages { get; set; } public ICollection<ForumUser> ForumUsers { get; set; } public ICollection<SurveyResponse> SurveyResponses { get; set; } + public ICollection<Attendance> Attendances { get; set; } } } diff --git a/Tsi1.Api/Tsi1.DataLayer/EntityConfiguration/ActivityConfiguration.cs b/Tsi1.Api/Tsi1.DataLayer/EntityConfiguration/ActivityConfiguration.cs new file mode 100644 index 0000000000000000000000000000000000000000..bb240989a2fd94ea21c55cf70fe94ffb6f144f78 --- /dev/null +++ b/Tsi1.Api/Tsi1.DataLayer/EntityConfiguration/ActivityConfiguration.cs @@ -0,0 +1,25 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using System; +using System.Collections.Generic; +using System.Text; +using Tsi1.DataLayer.Entities; + +namespace Tsi1.DataLayer.EntityConfiguration +{ + public class ActivityConfiguration : IEntityTypeConfiguration<Activity> + { + public void Configure(EntityTypeBuilder<Activity> builder) + { + builder.HasKey(x => x.Id); + + builder.Property(x => x.Name) + .IsRequired() + .HasColumnType("character varying(50)"); + + builder.HasOne(x => x.Course) + .WithMany(x => x.Activities) + .HasForeignKey(x => x.CourseId); + } + } +} diff --git a/Tsi1.Api/Tsi1.DataLayer/EntityConfiguration/AttendanceConfiguration.cs b/Tsi1.Api/Tsi1.DataLayer/EntityConfiguration/AttendanceConfiguration.cs new file mode 100644 index 0000000000000000000000000000000000000000..d8e88def61900fd6ef56bd068896877b4b53d0e0 --- /dev/null +++ b/Tsi1.Api/Tsi1.DataLayer/EntityConfiguration/AttendanceConfiguration.cs @@ -0,0 +1,25 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using System; +using System.Collections.Generic; +using System.Text; +using Tsi1.DataLayer.Entities; + +namespace Tsi1.DataLayer.EntityConfiguration +{ + public class AttendanceConfiguration : IEntityTypeConfiguration<Attendance> + { + public void Configure(EntityTypeBuilder<Attendance> builder) + { + builder.HasKey(x => x.Id); + + builder.HasOne(x => x.User) + .WithMany(x => x.Attendances) + .HasForeignKey(x => x.UserId); + + builder.HasOne(x => x.Activity) + .WithMany(x => x.Attendances) + .HasForeignKey(x => x.ActivityId); + } + } +} diff --git a/Tsi1.Api/Tsi1.DataLayer/Migrations/20201129171433_add-activity-attendance-entities.Designer.cs b/Tsi1.Api/Tsi1.DataLayer/Migrations/20201129171433_add-activity-attendance-entities.Designer.cs new file mode 100644 index 0000000000000000000000000000000000000000..41dcf6ded41a333032e7d0b4c60628fc48f05521 --- /dev/null +++ b/Tsi1.Api/Tsi1.DataLayer/Migrations/20201129171433_add-activity-attendance-entities.Designer.cs @@ -0,0 +1,905 @@ +// <auto-generated /> +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Tsi1.DataLayer; + +namespace Tsi1.DataLayer.Migrations +{ + [DbContext(typeof(Tsi1Context))] + [Migration("20201129171433_add-activity-attendance-entities")] + partial class addactivityattendanceentities + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn) + .HasAnnotation("ProductVersion", "3.1.4") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + modelBuilder.Entity("Tsi1.DataLayer.Entities.Activity", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property<int>("CourseId") + .HasColumnType("integer"); + + b.Property<bool>("IsVideoConference") + .HasColumnType("boolean"); + + b.Property<string>("Name") + .IsRequired() + .HasColumnType("character varying(50)"); + + b.HasKey("Id"); + + b.HasIndex("CourseId"); + + b.ToTable("Activities"); + }); + + modelBuilder.Entity("Tsi1.DataLayer.Entities.AnswerOption", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property<string>("Name") + .IsRequired() + .HasColumnType("character varying(100)"); + + b.HasKey("Id"); + + b.HasIndex("Name"); + + b.ToTable("AnswerOptions"); + }); + + modelBuilder.Entity("Tsi1.DataLayer.Entities.Attendance", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property<int>("ActivityId") + .HasColumnType("integer"); + + b.Property<DateTime>("Date") + .HasColumnType("timestamp without time zone"); + + b.Property<int>("UserId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ActivityId"); + + b.HasIndex("UserId"); + + b.ToTable("Attendances"); + }); + + modelBuilder.Entity("Tsi1.DataLayer.Entities.Communication", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property<int?>("CourseId") + .HasColumnType("integer"); + + b.Property<bool>("IsGlobal") + .HasColumnType("boolean"); + + b.Property<int?>("TenantId") + .HasColumnType("integer"); + + b.Property<string>("Text") + .IsRequired() + .HasColumnType("character varying(1000)"); + + b.Property<DateTime>("ValidUntil") + .HasColumnType("timestamp without time zone"); + + b.HasKey("Id"); + + b.HasIndex("CourseId"); + + b.HasIndex("TenantId"); + + b.HasIndex("ValidUntil"); + + b.ToTable("Communications"); + }); + + modelBuilder.Entity("Tsi1.DataLayer.Entities.Connection", b => + { + b.Property<string>("ConnectionId") + .HasColumnType("text"); + + b.Property<string>("GroupName") + .HasColumnType("text"); + + b.Property<int>("UserId") + .HasColumnType("integer"); + + b.HasKey("ConnectionId"); + + b.HasIndex("GroupName"); + + b.ToTable("Connections"); + }); + + modelBuilder.Entity("Tsi1.DataLayer.Entities.Course", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property<bool>("IsTemplate") + .HasColumnType("boolean"); + + b.Property<string>("Name") + .IsRequired() + .HasColumnType("character varying(50)"); + + b.Property<int>("TenantId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.HasIndex("Name", "TenantId") + .IsUnique(); + + b.ToTable("Courses"); + }); + + modelBuilder.Entity("Tsi1.DataLayer.Entities.File", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property<string>("Name") + .IsRequired() + .HasColumnType("character varying(50)"); + + b.Property<string>("Path") + .IsRequired() + .HasColumnType("character varying(1000)"); + + b.HasKey("Id"); + + b.HasIndex("Path") + .IsUnique(); + + b.ToTable("Files"); + }); + + modelBuilder.Entity("Tsi1.DataLayer.Entities.Forum", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property<string>("Name") + .IsRequired() + .HasColumnType("character varying(50)"); + + b.HasKey("Id"); + + b.ToTable("Forums"); + }); + + modelBuilder.Entity("Tsi1.DataLayer.Entities.ForumUser", b => + { + b.Property<int>("ForumId") + .HasColumnType("integer"); + + b.Property<int>("UserId") + .HasColumnType("integer"); + + b.HasKey("ForumId", "UserId"); + + b.HasIndex("UserId"); + + b.ToTable("ForumUsers"); + }); + + modelBuilder.Entity("Tsi1.DataLayer.Entities.Group", b => + { + b.Property<string>("Name") + .HasColumnType("text"); + + b.HasKey("Name"); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("Tsi1.DataLayer.Entities.Post", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property<DateTime>("Date") + .HasColumnType("timestamp without time zone"); + + b.Property<int>("ForumId") + .HasColumnType("integer"); + + b.Property<string>("Title") + .IsRequired() + .HasColumnType("character varying(100)"); + + b.Property<int>("UserId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.HasIndex("ForumId", "Title") + .IsUnique(); + + b.ToTable("Posts"); + }); + + modelBuilder.Entity("Tsi1.DataLayer.Entities.PostMessage", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property<string>("Content") + .IsRequired() + .HasColumnType("character varying(10485760)"); + + b.Property<DateTime>("Date") + .HasColumnType("timestamp without time zone"); + + b.Property<int>("PostId") + .HasColumnType("integer"); + + b.Property<int>("UserId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("PostId"); + + b.HasIndex("UserId"); + + b.ToTable("PostMessages"); + }); + + modelBuilder.Entity("Tsi1.DataLayer.Entities.Professor", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property<string>("IdentityCard") + .IsRequired() + .HasColumnType("character varying(50)"); + + b.Property<int>("TenantId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.HasIndex("IdentityCard", "TenantId") + .IsUnique(); + + b.ToTable("Professors"); + }); + + modelBuilder.Entity("Tsi1.DataLayer.Entities.ProfessorCourse", b => + { + b.Property<int>("ProfessorId") + .HasColumnType("integer"); + + b.Property<int>("CourseId") + .HasColumnType("integer"); + + b.HasKey("ProfessorId", "CourseId"); + + b.HasIndex("CourseId"); + + b.ToTable("ProfessorCourses"); + }); + + modelBuilder.Entity("Tsi1.DataLayer.Entities.Section", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property<int>("CourseId") + .HasColumnType("integer"); + + b.Property<string>("Name") + .IsRequired() + .HasColumnType("character varying(255)"); + + b.Property<int>("Order") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CourseId"); + + b.ToTable("Sections"); + }); + + modelBuilder.Entity("Tsi1.DataLayer.Entities.SectionItem", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property<int?>("FileId") + .HasColumnType("integer"); + + b.Property<int?>("ForumId") + .HasColumnType("integer"); + + b.Property<int>("Order") + .HasColumnType("integer"); + + b.Property<int>("SectionId") + .HasColumnType("integer"); + + b.Property<int>("SectionItemTypeId") + .HasColumnType("integer"); + + b.Property<int?>("SurveyId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("FileId") + .IsUnique(); + + b.HasIndex("ForumId") + .IsUnique(); + + b.HasIndex("SectionId"); + + b.HasIndex("SectionItemTypeId"); + + b.HasIndex("SurveyId") + .IsUnique(); + + b.ToTable("SectionItems"); + }); + + modelBuilder.Entity("Tsi1.DataLayer.Entities.SectionItemType", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property<string>("Name") + .IsRequired() + .HasColumnType("character varying(100)"); + + b.HasKey("Id"); + + b.ToTable("SectionItemTypes"); + }); + + modelBuilder.Entity("Tsi1.DataLayer.Entities.Student", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property<int>("Age") + .HasColumnType("integer"); + + b.Property<string>("IdentityCard") + .IsRequired() + .HasColumnType("character varying(50)"); + + b.Property<int>("TenantId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.HasIndex("IdentityCard", "TenantId") + .IsUnique(); + + b.ToTable("Students"); + }); + + modelBuilder.Entity("Tsi1.DataLayer.Entities.StudentCourse", b => + { + b.Property<int>("StudentId") + .HasColumnType("integer"); + + b.Property<int>("CourseId") + .HasColumnType("integer"); + + b.HasKey("StudentId", "CourseId"); + + b.HasIndex("CourseId"); + + b.ToTable("StudentCourses"); + }); + + modelBuilder.Entity("Tsi1.DataLayer.Entities.Survey", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property<bool>("IsGlobal") + .HasColumnType("boolean"); + + b.Property<string>("Name") + .IsRequired() + .HasColumnType("character varying(255)"); + + b.Property<int?>("TenantId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("Name"); + + b.HasIndex("TenantId"); + + b.ToTable("Surveys"); + }); + + modelBuilder.Entity("Tsi1.DataLayer.Entities.SurveyAnswer", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property<int>("AnswerOptionId") + .HasColumnType("integer"); + + b.Property<int>("SurveyQuestionId") + .HasColumnType("integer"); + + b.Property<int>("SurveyResponseId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("AnswerOptionId"); + + b.HasIndex("SurveyQuestionId"); + + b.HasIndex("SurveyResponseId"); + + b.ToTable("SurveyAnswers"); + }); + + modelBuilder.Entity("Tsi1.DataLayer.Entities.SurveyQuestion", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property<string>("Question") + .IsRequired() + .HasColumnType("character varying(255)"); + + b.Property<int>("SurveyId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("SurveyId"); + + b.ToTable("SurveyQuestions"); + }); + + modelBuilder.Entity("Tsi1.DataLayer.Entities.SurveyResponse", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property<int>("SurveyId") + .HasColumnType("integer"); + + b.Property<int>("UserId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("SurveyId"); + + b.HasIndex("UserId"); + + b.ToTable("SurveyResponses"); + }); + + modelBuilder.Entity("Tsi1.DataLayer.Entities.Tenant", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property<string>("Name") + .IsRequired() + .HasColumnType("character varying(50)"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("Tenants"); + }); + + modelBuilder.Entity("Tsi1.DataLayer.Entities.User", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property<string>("Email") + .IsRequired() + .HasColumnType("character varying(255)"); + + b.Property<string>("FirstName") + .IsRequired() + .HasColumnType("character varying(255)"); + + b.Property<string>("LastName") + .IsRequired() + .HasColumnType("character varying(255)"); + + b.Property<string>("Password") + .IsRequired() + .HasColumnType("character varying(255)"); + + b.Property<int?>("ProfessorId") + .HasColumnType("integer"); + + b.Property<int?>("StudentId") + .HasColumnType("integer"); + + b.Property<int>("TenantId") + .HasColumnType("integer"); + + b.Property<int>("UserTypeId") + .HasColumnType("integer"); + + b.Property<string>("Username") + .IsRequired() + .HasColumnType("character varying(50)"); + + b.HasKey("Id"); + + b.HasIndex("ProfessorId") + .IsUnique(); + + b.HasIndex("StudentId") + .IsUnique(); + + b.HasIndex("TenantId"); + + b.HasIndex("UserTypeId"); + + b.HasIndex("Username", "TenantId") + .IsUnique(); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Tsi1.DataLayer.Entities.UserType", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property<string>("Name") + .IsRequired() + .HasColumnType("character varying(50)"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("UserTypes"); + }); + + modelBuilder.Entity("Tsi1.DataLayer.Entities.Activity", b => + { + b.HasOne("Tsi1.DataLayer.Entities.Course", "Course") + .WithMany("Activities") + .HasForeignKey("CourseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Tsi1.DataLayer.Entities.Attendance", b => + { + b.HasOne("Tsi1.DataLayer.Entities.Activity", "Activity") + .WithMany("Attendances") + .HasForeignKey("ActivityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tsi1.DataLayer.Entities.User", "User") + .WithMany("Attendances") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Tsi1.DataLayer.Entities.Communication", b => + { + b.HasOne("Tsi1.DataLayer.Entities.Course", "Course") + .WithMany("Communications") + .HasForeignKey("CourseId"); + + b.HasOne("Tsi1.DataLayer.Entities.Tenant", "Tenant") + .WithMany("Communications") + .HasForeignKey("TenantId"); + }); + + modelBuilder.Entity("Tsi1.DataLayer.Entities.Connection", b => + { + b.HasOne("Tsi1.DataLayer.Entities.Group", "Group") + .WithMany("Connections") + .HasForeignKey("GroupName"); + }); + + modelBuilder.Entity("Tsi1.DataLayer.Entities.Course", b => + { + b.HasOne("Tsi1.DataLayer.Entities.Tenant", "Tenant") + .WithMany("Courses") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Tsi1.DataLayer.Entities.ForumUser", b => + { + b.HasOne("Tsi1.DataLayer.Entities.Forum", "Forum") + .WithMany("ForumUsers") + .HasForeignKey("ForumId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tsi1.DataLayer.Entities.User", "User") + .WithMany("ForumUsers") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Tsi1.DataLayer.Entities.Post", b => + { + b.HasOne("Tsi1.DataLayer.Entities.Forum", "Forum") + .WithMany("Posts") + .HasForeignKey("ForumId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tsi1.DataLayer.Entities.User", "User") + .WithMany("Posts") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Tsi1.DataLayer.Entities.PostMessage", b => + { + b.HasOne("Tsi1.DataLayer.Entities.Post", "Post") + .WithMany("PostMessages") + .HasForeignKey("PostId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tsi1.DataLayer.Entities.User", "User") + .WithMany("PostMessages") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Tsi1.DataLayer.Entities.Professor", b => + { + b.HasOne("Tsi1.DataLayer.Entities.Tenant", "Tenant") + .WithMany("Professors") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Tsi1.DataLayer.Entities.ProfessorCourse", b => + { + b.HasOne("Tsi1.DataLayer.Entities.Course", "Course") + .WithMany("ProfessorCourses") + .HasForeignKey("CourseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tsi1.DataLayer.Entities.Professor", "Professor") + .WithMany("ProfessorCourses") + .HasForeignKey("ProfessorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Tsi1.DataLayer.Entities.Section", b => + { + b.HasOne("Tsi1.DataLayer.Entities.Course", "Course") + .WithMany("Sections") + .HasForeignKey("CourseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Tsi1.DataLayer.Entities.SectionItem", b => + { + b.HasOne("Tsi1.DataLayer.Entities.File", "File") + .WithOne("SectionItem") + .HasForeignKey("Tsi1.DataLayer.Entities.SectionItem", "FileId"); + + b.HasOne("Tsi1.DataLayer.Entities.Forum", "Forum") + .WithOne("SectionItem") + .HasForeignKey("Tsi1.DataLayer.Entities.SectionItem", "ForumId"); + + b.HasOne("Tsi1.DataLayer.Entities.Section", "Section") + .WithMany("SectionItems") + .HasForeignKey("SectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tsi1.DataLayer.Entities.SectionItemType", "SectionItemType") + .WithMany("SectionItems") + .HasForeignKey("SectionItemTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tsi1.DataLayer.Entities.Survey", "Survey") + .WithOne("SectionItem") + .HasForeignKey("Tsi1.DataLayer.Entities.SectionItem", "SurveyId"); + }); + + modelBuilder.Entity("Tsi1.DataLayer.Entities.Student", b => + { + b.HasOne("Tsi1.DataLayer.Entities.Tenant", "Tenant") + .WithMany("Students") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Tsi1.DataLayer.Entities.StudentCourse", b => + { + b.HasOne("Tsi1.DataLayer.Entities.Course", "Course") + .WithMany("StudentCourses") + .HasForeignKey("CourseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tsi1.DataLayer.Entities.Student", "Student") + .WithMany("StudentCourses") + .HasForeignKey("StudentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Tsi1.DataLayer.Entities.Survey", b => + { + b.HasOne("Tsi1.DataLayer.Entities.Tenant", "Tenant") + .WithMany("Surveys") + .HasForeignKey("TenantId"); + }); + + modelBuilder.Entity("Tsi1.DataLayer.Entities.SurveyAnswer", b => + { + b.HasOne("Tsi1.DataLayer.Entities.AnswerOption", "AnswerOption") + .WithMany("SurveyAnswers") + .HasForeignKey("AnswerOptionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tsi1.DataLayer.Entities.SurveyQuestion", "SurveyQuestion") + .WithMany("SurveyAnswers") + .HasForeignKey("SurveyQuestionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tsi1.DataLayer.Entities.SurveyResponse", "SurveyResponse") + .WithMany("SurveyAnswers") + .HasForeignKey("SurveyResponseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Tsi1.DataLayer.Entities.SurveyQuestion", b => + { + b.HasOne("Tsi1.DataLayer.Entities.Survey", "Survey") + .WithMany("SurveyQuestions") + .HasForeignKey("SurveyId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Tsi1.DataLayer.Entities.SurveyResponse", b => + { + b.HasOne("Tsi1.DataLayer.Entities.Survey", "Survey") + .WithMany("SurveyResponses") + .HasForeignKey("SurveyId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tsi1.DataLayer.Entities.User", "User") + .WithMany("SurveyResponses") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Tsi1.DataLayer.Entities.User", b => + { + b.HasOne("Tsi1.DataLayer.Entities.Professor", "Professor") + .WithOne("User") + .HasForeignKey("Tsi1.DataLayer.Entities.User", "ProfessorId"); + + b.HasOne("Tsi1.DataLayer.Entities.Student", "Student") + .WithOne("User") + .HasForeignKey("Tsi1.DataLayer.Entities.User", "StudentId"); + + b.HasOne("Tsi1.DataLayer.Entities.Tenant", "Tenant") + .WithMany("Users") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tsi1.DataLayer.Entities.UserType", "UserType") + .WithMany() + .HasForeignKey("UserTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Tsi1.Api/Tsi1.DataLayer/Migrations/20201129171433_add-activity-attendance-entities.cs b/Tsi1.Api/Tsi1.DataLayer/Migrations/20201129171433_add-activity-attendance-entities.cs new file mode 100644 index 0000000000000000000000000000000000000000..cc65e21a03d7809ded0edbe10be674b098b7ce7c --- /dev/null +++ b/Tsi1.Api/Tsi1.DataLayer/Migrations/20201129171433_add-activity-attendance-entities.cs @@ -0,0 +1,84 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +namespace Tsi1.DataLayer.Migrations +{ + public partial class addactivityattendanceentities : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Activities", + columns: table => new + { + Id = table.Column<int>(nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + Name = table.Column<string>(type: "character varying(50)", nullable: false), + IsVideoConference = table.Column<bool>(nullable: false), + CourseId = table.Column<int>(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Activities", x => x.Id); + table.ForeignKey( + name: "FK_Activities_Courses_CourseId", + column: x => x.CourseId, + principalTable: "Courses", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "Attendances", + columns: table => new + { + Id = table.Column<int>(nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + Date = table.Column<DateTime>(nullable: false), + UserId = table.Column<int>(nullable: false), + ActivityId = table.Column<int>(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Attendances", x => x.Id); + table.ForeignKey( + name: "FK_Attendances_Activities_ActivityId", + column: x => x.ActivityId, + principalTable: "Activities", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_Attendances_Users_UserId", + column: x => x.UserId, + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_Activities_CourseId", + table: "Activities", + column: "CourseId"); + + migrationBuilder.CreateIndex( + name: "IX_Attendances_ActivityId", + table: "Attendances", + column: "ActivityId"); + + migrationBuilder.CreateIndex( + name: "IX_Attendances_UserId", + table: "Attendances", + column: "UserId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Attendances"); + + migrationBuilder.DropTable( + name: "Activities"); + } + } +} diff --git a/Tsi1.Api/Tsi1.DataLayer/Migrations/Tsi1ContextModelSnapshot.cs b/Tsi1.Api/Tsi1.DataLayer/Migrations/Tsi1ContextModelSnapshot.cs index 58d7225c9f4c36aae6f9178010638a5c4ec352b0..08b6205591575da1bd95ae3d6fa4276b1e64a3d4 100644 --- a/Tsi1.Api/Tsi1.DataLayer/Migrations/Tsi1ContextModelSnapshot.cs +++ b/Tsi1.Api/Tsi1.DataLayer/Migrations/Tsi1ContextModelSnapshot.cs @@ -19,6 +19,30 @@ namespace Tsi1.DataLayer.Migrations .HasAnnotation("ProductVersion", "3.1.4") .HasAnnotation("Relational:MaxIdentifierLength", 63); + modelBuilder.Entity("Tsi1.DataLayer.Entities.Activity", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property<int>("CourseId") + .HasColumnType("integer"); + + b.Property<bool>("IsVideoConference") + .HasColumnType("boolean"); + + b.Property<string>("Name") + .IsRequired() + .HasColumnType("character varying(50)"); + + b.HasKey("Id"); + + b.HasIndex("CourseId"); + + b.ToTable("Activities"); + }); + modelBuilder.Entity("Tsi1.DataLayer.Entities.AnswerOption", b => { b.Property<int>("Id") @@ -37,6 +61,31 @@ namespace Tsi1.DataLayer.Migrations b.ToTable("AnswerOptions"); }); + modelBuilder.Entity("Tsi1.DataLayer.Entities.Attendance", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property<int>("ActivityId") + .HasColumnType("integer"); + + b.Property<DateTime>("Date") + .HasColumnType("timestamp without time zone"); + + b.Property<int>("UserId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ActivityId"); + + b.HasIndex("UserId"); + + b.ToTable("Attendances"); + }); + modelBuilder.Entity("Tsi1.DataLayer.Entities.Communication", b => { b.Property<int>("Id") @@ -594,6 +643,30 @@ namespace Tsi1.DataLayer.Migrations b.ToTable("UserTypes"); }); + modelBuilder.Entity("Tsi1.DataLayer.Entities.Activity", b => + { + b.HasOne("Tsi1.DataLayer.Entities.Course", "Course") + .WithMany("Activities") + .HasForeignKey("CourseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Tsi1.DataLayer.Entities.Attendance", b => + { + b.HasOne("Tsi1.DataLayer.Entities.Activity", "Activity") + .WithMany("Attendances") + .HasForeignKey("ActivityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tsi1.DataLayer.Entities.User", "User") + .WithMany("Attendances") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + modelBuilder.Entity("Tsi1.DataLayer.Entities.Communication", b => { b.HasOne("Tsi1.DataLayer.Entities.Course", "Course") diff --git a/Tsi1.Api/Tsi1.DataLayer/Tsi1Context.cs b/Tsi1.Api/Tsi1.DataLayer/Tsi1Context.cs index b35e7b2f0949cb8086fb134fe60f1d66e79e658b..bda60bbf54d151e5d680b5a78772624a78456bfb 100644 --- a/Tsi1.Api/Tsi1.DataLayer/Tsi1Context.cs +++ b/Tsi1.Api/Tsi1.DataLayer/Tsi1Context.cs @@ -32,8 +32,9 @@ namespace Tsi1.DataLayer public DbSet<SurveyQuestion> SurveyQuestions { get; set; } public DbSet<SurveyResponse> SurveyResponses { get; set; } public DbSet<AnswerOption> AnswerOptions { get; set; } - public DbSet<Communication> Communications { get; set; } + public DbSet<Activity> Activities { get; set; } + public DbSet<Attendance> Attendances { get; set; } @@ -65,6 +66,8 @@ namespace Tsi1.DataLayer modelBuilder.ApplyConfiguration(new SurveyResponseConfiguration()); modelBuilder.ApplyConfiguration(new AnswerOptionConfiguration()); modelBuilder.ApplyConfiguration(new CommunicationConfiguration()); + modelBuilder.ApplyConfiguration(new ActivityConfiguration()); + modelBuilder.ApplyConfiguration(new AttendanceConfiguration()); } } }