diff --git a/.gitignore b/.gitignore index a9183b55b68a71ca47809af2e9361e03903aae0c..0a8e0559ee40f980c98cf9af85ceef89e5cdc385 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -################################################################################ +################################################################################ # This .gitignore file was automatically created by Microsoft(R) Visual Studio. ################################################################################ diff --git a/Tsi1.Api/Tsi1.Api/Controllers/CourseController.cs b/Tsi1.Api/Tsi1.Api/Controllers/CourseController.cs index 19b56f853a2c8cb1db658771f40e43ca5a466815..cb90fae29fb465e9298dddbe750edfbf5e5677c3 100644 --- a/Tsi1.Api/Tsi1.Api/Controllers/CourseController.cs +++ b/Tsi1.Api/Tsi1.Api/Controllers/CourseController.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Security.Claims; using System.Threading.Tasks; @@ -19,9 +20,12 @@ namespace Tsi1.Api.Controllers { private readonly ICourseService _courseService; - public CourseController(ICourseService courseService) + private readonly IFileService _fileService; + + public CourseController(ICourseService courseService, IFileService fileService) { _courseService = courseService; + _fileService = fileService; } [Authorize(Roles = UserTypes.Student + ", " + UserTypes.Professor)] @@ -54,6 +58,10 @@ namespace Tsi1.Api.Controllers return BadRequest(result.Message); } + var path = Path.Combine(tenantId.ToString(), result.Data.Id.ToString()); + + _fileService.CreateFolder(path); + return Ok(); } @@ -65,11 +73,38 @@ namespace Tsi1.Api.Controllers var userId = int.Parse(HttpContext.User.Claims.FirstOrDefault(x => x.Type == "Id").Value); var result = await _courseService.Matriculate(userId, courseId); + if (result.HasError) { return BadRequest(result.Message); } + if (result.Data == false) + { + return NotFound(result.Message); + } + + return Ok(); + } + + [Authorize(Roles = UserTypes.Student)] + [HttpPost("DropOutFromCourse/{courseId}")] + public async Task<IActionResult> DropOutFromCourse(int courseId) + { + var userId = int.Parse(HttpContext.User.Claims.FirstOrDefault(x => x.Type == "Id").Value); + + var result = await _courseService.DropOutFromCourse(userId, courseId); + + if (result.HasError) + { + return BadRequest(result.Message); + } + + if (result.Data == false) + { + return NotFound(result.Message); + } + return Ok(); } @@ -78,12 +113,112 @@ namespace Tsi1.Api.Controllers public async Task<IActionResult> AddProfessorToCourse(ProfessorCourseDto professorCourseDto) { var result = await _courseService.AddProfessorToCourse(professorCourseDto); + + if (result.HasError) + { + return BadRequest(result.Message); + } + + if (result.Data == false) + { + return NotFound(result.Message); + } + + return Ok(); + } + + [Authorize(Roles = UserTypes.FacultyAdmin)] + [HttpPost("RemoveProfessorToCourse")] + public async Task<IActionResult> RemoveProfessorFromCourse(ProfessorCourseDto professorCourseDto) + { + var result = await _courseService.RemoveProfessorToCourse(professorCourseDto); + + if (result.HasError) + { + return BadRequest(result.Message); + } + + if (result.Data == false) + { + return NotFound(result.Message); + } + + return Ok(); + } + + [Authorize(Roles = UserTypes.Student + ", " + UserTypes.Professor + ", " + UserTypes.FacultyAdmin)] + [HttpGet("GetAll")] + public async Task<IActionResult> GetAll() + { + var tenantId = int.Parse(HttpContext.User.Claims.FirstOrDefault(x => x.Type == "TenantId").Value); + + var result = await _courseService.GetAll(tenantId); if (result.HasError) { return BadRequest(result.Message); } + return Ok(result.Data); + } + + [Authorize(Roles = UserTypes.UdelarAdmin)] + [HttpGet("GetAll/{tenantId}")] + public async Task<IActionResult> GetAll(int tenantId) + { + var result = await _courseService.GetAll(tenantId); + if (result.HasError) + { + return BadRequest(result.Message); + } + + return Ok(result.Data); + } + + + [Authorize(Roles = UserTypes.Professor + ", " + UserTypes.FacultyAdmin)] + [HttpPut("Modify/{courseId}")] + public async Task<IActionResult> Modify(int courseId, CourseCreateDto courseDto) + { + var tenantId = int.Parse(HttpContext.User.Claims.FirstOrDefault(x => x.Type == "TenantId").Value); + courseDto.TenantId = tenantId; + + var result = await _courseService.Modify(courseId, courseDto); + + if (result.HasError) + { + return BadRequest(result.Message); + } + + if (result.Data == false) + { + return NotFound(result.Message); + } + + return Ok(); + } + + [Authorize(Roles = UserTypes.FacultyAdmin)] + [HttpDelete("Delete/{courseId}")] + public async Task<IActionResult> Delete(int courseId) + { + var result = await _courseService.Delete(courseId); + + if (result.HasError) + { + return BadRequest(result.Message); + } + + if (result.Data == null) + { + return NotFound(result.Message); + } + + var path = Path.Combine(result.Data.TenantId.ToString(), result.Data.Id.ToString()); + + _fileService.DeleteFolder(path); + return Ok(); } + } } diff --git a/Tsi1.Api/Tsi1.Api/Controllers/FileController.cs b/Tsi1.Api/Tsi1.Api/Controllers/FileController.cs new file mode 100644 index 0000000000000000000000000000000000000000..144a8298f82892a2caafe88b4957da0c01b96b24 --- /dev/null +++ b/Tsi1.Api/Tsi1.Api/Controllers/FileController.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Tsi1.BusinessLayer.Helpers; +using Tsi1.BusinessLayer.Interfaces; + +namespace Tsi1.Api.Controllers +{ + [Route("api/[controller]")] + [ApiController] + public class FileController : ControllerBase + { + private readonly IFileService _fileService; + + public FileController(IFileService fileService) + { + _fileService = fileService; + } + + [Authorize(Roles = UserTypes.Professor)] + [HttpPost("Create/{courseId}")] + public async Task<IActionResult> Create(IFormFile file, int courseId) + { + var tenantId = int.Parse(HttpContext.User.Claims.FirstOrDefault(x => x.Type == "TenantId").Value); + + var result = await _fileService.Create(file, tenantId, courseId); + + if (result.HasError) + { + return BadRequest(result.Message); + } + + return Ok(result.Data); + } + + } +} diff --git a/Tsi1.Api/Tsi1.Api/Controllers/TenantController.cs b/Tsi1.Api/Tsi1.Api/Controllers/TenantController.cs index f89bb6beb7a7a425bda20d76669b2315ba465083..92a0665ffad2a0892a1365f1b16fbbf9730a92e6 100644 --- a/Tsi1.Api/Tsi1.Api/Controllers/TenantController.cs +++ b/Tsi1.Api/Tsi1.Api/Controllers/TenantController.cs @@ -17,9 +17,12 @@ namespace Tsi1.Api.Controllers { private readonly ITenantService _tenantService; - public TenantController(ITenantService tenantService) + private readonly IFileService _fileService; + + public TenantController(ITenantService tenantService, IFileService fileService) { _tenantService = tenantService; + _fileService = fileService; } [Authorize(Roles = UserTypes.UdelarAdmin)] @@ -47,6 +50,76 @@ namespace Tsi1.Api.Controllers return BadRequest(result.Message); } + _fileService.CreateFolder(result.Data.Id.ToString()); + + return Ok(result.Data); + } + + [Authorize(Roles = UserTypes.UdelarAdmin)] + [HttpPut("Modify/{tenantId}")] + public async Task<IActionResult> Modify(int tenantId, TenantCreateDto tenantDto) + { + var result = await _tenantService.Modify(tenantId, tenantDto); + + if (result.HasError) + { + return BadRequest(result.Message); + } + + if (result.Data == false) + { + return NotFound(result.Message); + } + + return Ok(); + } + + [Authorize(Roles = UserTypes.UdelarAdmin)] + [HttpDelete("Delete/{tenantId}")] + public async Task<IActionResult> Delete(int tenantId) + { + var result = await _tenantService.Delete(tenantId); + + if (result.HasError) + { + return BadRequest(result.Message); + } + + if (result.Data == false) + { + return NotFound(result.Message); + } + + _fileService.DeleteFolder(tenantId.ToString()); + + return Ok(); + } + + [Authorize(Roles = UserTypes.UdelarAdmin)] + [HttpGet("FacultyList")] + public async Task<IActionResult> FacultyList() + { + var result = await _tenantService.FacultyList(); + + if (result.HasError) + { + return BadRequest(result.Message); + } + + return Ok(result.Data); + } + + [Authorize(Roles = UserTypes.UdelarAdmin)] + [HttpGet("CourseList")] + public async Task<IActionResult> CourseList() + { + var result = await _tenantService.CourseList(); + + if (result.HasError) + { + return BadRequest(result.Message); + } + return Ok(result.Data); } } diff --git a/Tsi1.Api/Tsi1.Api/Controllers/UserController.cs b/Tsi1.Api/Tsi1.Api/Controllers/UserController.cs index 1fbbe5ca1f7cdb42755c0717dbfc42a9035983aa..ac1b1b8b0df52c850b35dd1c4c1c15e5a6153b4f 100644 --- a/Tsi1.Api/Tsi1.Api/Controllers/UserController.cs +++ b/Tsi1.Api/Tsi1.Api/Controllers/UserController.cs @@ -23,21 +23,27 @@ namespace Tsi1.Api.Controllers private readonly IUserService _userService; private readonly IUserTypeService _userTypeService; private readonly ITenantService _tenantService; - - public UserController(IJwtAuthManager jwtAuthManager, IUserService userService, - IUserTypeService userTypeService, ITenantService tenantService) + private readonly IEmailService _emailService; + + public UserController( + IJwtAuthManager jwtAuthManager, + IUserService userService, + IUserTypeService userTypeService, + ITenantService tenantService, + IEmailService emailService) { _jwtAuthManager = jwtAuthManager; _userService = userService; _userTypeService = userTypeService; _tenantService = tenantService; + _emailService = emailService; } [AllowAnonymous] [HttpPost("Login")] public async Task<IActionResult> Login(LoginRequest request) { - var resultSplit = request.UserName.Split("@"); + var resultSplit = request.Username.Split("@"); if (resultSplit.Count() != 2) { @@ -45,9 +51,7 @@ namespace Tsi1.Api.Controllers } var userName = resultSplit[0]; - var tenantName = resultSplit[1]; - var tenantId = await _tenantService.GetByName(tenantName); if (tenantId.HasError) @@ -56,7 +60,6 @@ namespace Tsi1.Api.Controllers } var result = await _userService.Authenticate(userName, request.Password, tenantId.Data); - if (result.HasError) { return BadRequest(result.Message); @@ -110,68 +113,178 @@ namespace Tsi1.Api.Controllers } } - [Authorize(Roles = UserTypes.FacultyAdmin)] + [Authorize(Roles = UserTypes.FacultyAdmin + ", " + UserTypes.UdelarAdmin)] [HttpPost("Register")] - public async Task<IActionResult> Register(UserRegisterDto dto) + public async Task<IActionResult> Register(UserRegisterDto dto, [FromQuery] int? tenantId = null) { - var tenantId = int.Parse(HttpContext.User.Claims.FirstOrDefault(x => x.Type == "TenantId").Value); + var myUserType = HttpContext.User.Claims.FirstOrDefault(x => x.Type == ClaimTypes.Role).Value; + + if (myUserType == UserTypes.UdelarAdmin && tenantId == null) + { + return BadRequest(string.Format(ErrorMessages.TenantDoesNotExist, tenantId)); + } + + if (myUserType == UserTypes.FacultyAdmin) + { + tenantId = int.Parse(HttpContext.User.Claims.FirstOrDefault(x => x.Type == "TenantId").Value); + } var userTypeResult = await _userTypeService.GetById(dto.UserTypeId); + if (userTypeResult.HasError) + { + return BadRequest(userTypeResult.Message); + } + + var userType = userTypeResult.Data; + + if (myUserType == UserTypes.UdelarAdmin && + (userType.Name == UserTypes.Student || + userType.Name == UserTypes.Professor)) + { + return BadRequest(string.Format(ErrorMessages.InvalidUserType, userType.Name)); + } + + if (myUserType == UserTypes.FacultyAdmin && + (userType.Name == UserTypes.UdelarAdmin || + userType.Name == UserTypes.FacultyAdmin)) + { + return BadRequest(string.Format(ErrorMessages.InvalidUserType, userType.Name)); + } + + var userServiceResult = await _userService.Create(dto, userType.Name, (int) tenantId); + + if (userServiceResult.HasError) + { + BadRequest(userServiceResult.Message); + } + + return Ok(); + } + + [Authorize(Roles = UserTypes.FacultyAdmin + ", " + UserTypes.UdelarAdmin)] + [HttpPut("Modify/{userId}")] + public async Task<IActionResult> Modify(UserModifyDto dto, int userId) + { + var myUserType = HttpContext.User.Claims.FirstOrDefault(x => x.Type == ClaimTypes.Role).Value; + + var userTypeResult = await _userService.GetUserType(userId); + if (userTypeResult.HasError) { return BadRequest(userTypeResult.Message); } + if (myUserType == UserTypes.FacultyAdmin) + { + var tenantId = int.Parse(HttpContext.User.Claims.FirstOrDefault(x => x.Type == "TenantId").Value); + + var userTenant = await _userService.GetTenant(userId); + + if (userTenant.HasError) + { + return BadRequest(userTenant.Message); + } + + if (userTenant.Data != tenantId) + { + return BadRequest("No se puede modificar un usuario de otra facultad"); + } + } + var userType = userTypeResult.Data; - if (userType.Name == UserTypes.UdelarAdmin || - userType.Name == UserTypes.FacultyAdmin) + if (myUserType == UserTypes.UdelarAdmin && + (userType.Name == UserTypes.Student || + userType.Name == UserTypes.Professor)) + { + return BadRequest(string.Format(ErrorMessages.InvalidUserType, userType.Name)); + } + + if (myUserType == UserTypes.FacultyAdmin && + (userType.Name == UserTypes.UdelarAdmin || + userType.Name == UserTypes.FacultyAdmin)) { return BadRequest(string.Format(ErrorMessages.InvalidUserType, userType.Name)); } - var userServiceResult = await _userService.Create(dto, userType.Name, tenantId); + var userServiceResult = await _userService.Modify(dto, userType.Name, userId); if (userServiceResult.HasError) { return BadRequest(userServiceResult.Message); } + if (userServiceResult.Data == false) + { + return NotFound(userServiceResult.Message); + } + return Ok(); } - [Authorize(Roles = UserTypes.UdelarAdmin)] - [HttpPost("RegisterAdmin/{tenantId}")] - public async Task<IActionResult> RegisterAdmin(UserRegisterDto dto, int tenantId) + [Authorize(Roles = UserTypes.FacultyAdmin + ", " + UserTypes.UdelarAdmin)] + [HttpDelete("Delete/{userId}")] + public async Task<IActionResult> Delete(int userId) { - var userTypeResult = await _userTypeService.GetById(dto.UserTypeId); + var myUserType = HttpContext.User.Claims.FirstOrDefault(x => x.Type == ClaimTypes.Role).Value; + + if (myUserType == UserTypes.FacultyAdmin) + { + var tenantId = int.Parse(HttpContext.User.Claims.FirstOrDefault(x => x.Type == "TenantId").Value); + + var userTenant = await _userService.GetTenant(userId); + + if (userTenant.HasError) + { + return BadRequest(userTenant.Message); + } + + if (userTenant.Data != tenantId) + { + return BadRequest("No se puede borrar un usuario de otra facultad"); + } + } + + var userTypeResult = await _userService.GetUserType(userId); if (userTypeResult.HasError) { - BadRequest(userTypeResult.Message); + return BadRequest(userTypeResult.Message); } var userType = userTypeResult.Data; - if (userType.Name == UserTypes.Student || - userType.Name == UserTypes.Professor) + if (myUserType == UserTypes.UdelarAdmin && + (userType.Name == UserTypes.Student || + userType.Name == UserTypes.Professor)) { return BadRequest(string.Format(ErrorMessages.InvalidUserType, userType.Name)); } - var userServiceResult = await _userService.Create(dto, userType.Name, tenantId); + if (myUserType == UserTypes.FacultyAdmin && + (userType.Name == UserTypes.UdelarAdmin || + userType.Name == UserTypes.FacultyAdmin)) + { + return BadRequest(string.Format(ErrorMessages.InvalidUserType, userType.Name)); + } + + var userServiceResult = await _userService.Delete(userId); if (userServiceResult.HasError) { - BadRequest(userServiceResult.Message); + return BadRequest(userServiceResult.Message); + } + + if (userServiceResult.Data == false) + { + return NotFound(userServiceResult.Message); } return Ok(); } - - [Authorize(Roles = UserTypes.Student + ", " + UserTypes.Professor)] + [Authorize(Roles = UserTypes.Student + ", " + UserTypes.Professor + ", " + UserTypes.FacultyAdmin)] [HttpGet("GetAll")] public async Task<IActionResult> GetAll() { @@ -217,5 +330,108 @@ namespace Tsi1.Api.Controllers return Ok(result.Data); } + [AllowAnonymous] + [HttpGet("ForgotPassword/{username}")] + public async Task<IActionResult> ForgotPassword(string username) + { + var resultSplit = username.Split("@"); + + if (resultSplit.Count() != 2) + { + return BadRequest(ErrorMessages.InvalidUsername); + } + + username = resultSplit[0]; + var tenantName = resultSplit[1]; + var tenantId = await _tenantService.GetByName(tenantName); + + if (tenantId.HasError) + { + return BadRequest(tenantId.Message); + } + + var userResult = await _userService.GetByUsername(username, tenantId.Data); + + if (userResult.HasError) + { + return BadRequest(userResult.Message); + } + + var code = _jwtAuthManager.GenerateVerificationCode(username, DateTime.Now); + + var result = await _emailService.SendVerificationCode(userResult.Data.Email, code); + if (result.HasError) + { + return BadRequest("Ha ocurrido un error"); + } + + return Ok(); + } + + [AllowAnonymous] + [HttpGet("VerificationCode/{username}/{code}")] + public async Task<IActionResult> VerificationCode(string username, int code) + { + var resultSplit = username.Split("@"); + + if (resultSplit.Count() != 2) + { + return BadRequest(ErrorMessages.InvalidUsername); + } + + username = resultSplit[0]; + var tenantName = resultSplit[1]; + var tenantId = await _tenantService.GetByName(tenantName); + + if (tenantId.HasError) + { + return BadRequest(tenantId.Message); + } + + if (!_jwtAuthManager.ValidateVerificationCode(username, code)) + { + return BadRequest("Código de verificación incorrecto"); + } + + var userResult = await _userService.GetByUsername(username, tenantId.Data); + if (userResult.HasError) + { + return BadRequest(userResult.Message); + } + + var user = userResult.Data; + var claims = new[] + { + new Claim("Id", user.Id.ToString()), + new Claim("Username", user.Username), + new Claim("TenantId", user.TenantId.ToString()), + new Claim(ClaimTypes.Role, user.UserType.Name) + }; + + var jwtResult = _jwtAuthManager.GenerateTokens(user.Username, claims, DateTime.Now); + + return Ok(new LoginResult + { + Id = user.Id, + UserName = user.Username, + Role = user.UserType.Name, + AccessToken = jwtResult.AccessToken, + RefreshToken = jwtResult.RefreshToken.TokenString + }); + } + + [HttpPost("RestorePassword")] + public async Task<IActionResult> RestorePassword(RestorePasswordDto dto) + { + var userId = int.Parse(HttpContext.User.Claims.FirstOrDefault(x => x.Type == "Id").Value); + + var result = await _userService.UpdatePassword(userId, dto.Password); + if (result.HasError) + { + return BadRequest(result.Message); + } + + return Ok(); + } } } diff --git a/Tsi1.Api/Tsi1.Api/Infrastructure/IJwtAuthManager.cs b/Tsi1.Api/Tsi1.Api/Infrastructure/IJwtAuthManager.cs index e9342c6dbb7d95fbf866b95abdd4a4fe6142e254..ab8bf680e9aa66084d608abaa9cc6b9a57c182ee 100644 --- a/Tsi1.Api/Tsi1.Api/Infrastructure/IJwtAuthManager.cs +++ b/Tsi1.Api/Tsi1.Api/Infrastructure/IJwtAuthManager.cs @@ -14,5 +14,8 @@ namespace Tsi1.Api.Infrastructure void RemoveExpiredRefreshTokens(DateTime now); void RemoveRefreshTokenByUserName(string userName); (ClaimsPrincipal, JwtSecurityToken) DecodeJwtToken(string token); + int GenerateVerificationCode(string username, DateTime now); + bool ValidateVerificationCode(string username, int code); + public void RemoveExpiredVerificationCodes(DateTime now); } } diff --git a/Tsi1.Api/Tsi1.Api/Infrastructure/JwtAuthManager.cs b/Tsi1.Api/Tsi1.Api/Infrastructure/JwtAuthManager.cs index 672247e7bf45a0cd68789cd77290da0f2f2bf606..f1bf057d038a817c83884e80eb99bece1e185bf2 100644 --- a/Tsi1.Api/Tsi1.Api/Infrastructure/JwtAuthManager.cs +++ b/Tsi1.Api/Tsi1.Api/Infrastructure/JwtAuthManager.cs @@ -15,6 +15,7 @@ namespace Tsi1.Api.Infrastructure { public IImmutableDictionary<string, RefreshToken> UsersRefreshTokensReadOnlyDictionary => _usersRefreshTokens.ToImmutableDictionary(); private readonly ConcurrentDictionary<string, RefreshToken> _usersRefreshTokens; // can store in a database or a distributed cache + private readonly ConcurrentDictionary<string, VerificationCode> _usersVerificationCodes; private readonly JwtTokenConfig _jwtTokenConfig; private readonly byte[] _secret; @@ -22,6 +23,7 @@ namespace Tsi1.Api.Infrastructure { _jwtTokenConfig = jwtTokenConfig; _usersRefreshTokens = new ConcurrentDictionary<string, RefreshToken>(); + _usersVerificationCodes = new ConcurrentDictionary<string, VerificationCode>(); _secret = Encoding.ASCII.GetBytes(jwtTokenConfig.Secret); } @@ -118,6 +120,36 @@ namespace Tsi1.Api.Infrastructure return (principal, validatedToken as JwtSecurityToken); } + public int GenerateVerificationCode(string username, DateTime now) + { + var code = new Random().Next(1000000); + + var verficationCode = new VerificationCode() + { + Username = username, + Code = code, + ExpireAt = now.AddMinutes(_jwtTokenConfig.VerificationCodeExpiration) + }; + + _usersVerificationCodes.AddOrUpdate(username, verficationCode, (s, t) => verficationCode); + + return code; + } + + public bool ValidateVerificationCode(string username, int code) + { + return _usersVerificationCodes.TryGetValue(username, out _); + } + + public void RemoveExpiredVerificationCodes(DateTime now) + { + var expiredCodes = _usersVerificationCodes.Where(x => x.Value.ExpireAt < now).ToList(); + foreach (var expiredCode in expiredCodes) + { + _usersVerificationCodes.TryRemove(expiredCode.Key, out _); + } + } + private static string GenerateRefreshTokenString() { var randomNumber = new byte[32]; diff --git a/Tsi1.Api/Tsi1.Api/Infrastructure/JwtTokenConfig.cs b/Tsi1.Api/Tsi1.Api/Infrastructure/JwtTokenConfig.cs index 854d596cf716e1388a8c507ba3c9426b77031016..5eacf5d9d79ecab406e690f602e7a8789c21c357 100644 --- a/Tsi1.Api/Tsi1.Api/Infrastructure/JwtTokenConfig.cs +++ b/Tsi1.Api/Tsi1.Api/Infrastructure/JwtTokenConfig.cs @@ -4,19 +4,11 @@ namespace Tsi1.Api.Infrastructure { public class JwtTokenConfig { - [JsonPropertyName("secret")] public string Secret { get; set; } - - [JsonPropertyName("issuer")] public string Issuer { get; set; } - - [JsonPropertyName("audience")] public string Audience { get; set; } - - [JsonPropertyName("accessTokenExpiration")] public int AccessTokenExpiration { get; set; } - - [JsonPropertyName("refreshTokenExpiration")] public int RefreshTokenExpiration { get; set; } + public int VerificationCodeExpiration { get; set; } } } diff --git a/Tsi1.Api/Tsi1.Api/Infrastructure/JwtVerificationCodeCache.cs b/Tsi1.Api/Tsi1.Api/Infrastructure/JwtVerificationCodeCache.cs new file mode 100644 index 0000000000000000000000000000000000000000..7805cedf35a8d75c22f860acd5f849ecf7d0a4fb --- /dev/null +++ b/Tsi1.Api/Tsi1.Api/Infrastructure/JwtVerificationCodeCache.cs @@ -0,0 +1,41 @@ +using Microsoft.Extensions.Hosting; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Tsi1.Api.Infrastructure +{ + public class JwtVerificationCodeCache : IHostedService, IDisposable + { + private Timer _timer; + private readonly IJwtAuthManager _jwtAuthManager; + + public JwtVerificationCodeCache(IJwtAuthManager jwtAuthManager) + { + _jwtAuthManager = jwtAuthManager; + } + + public Task StartAsync(CancellationToken stoppingToken) + { + // remove expired refresh tokens from cache every minute + _timer = new Timer(DoWork, null, TimeSpan.Zero, TimeSpan.FromMinutes(1)); + return Task.CompletedTask; + } + + private void DoWork(object state) + { + _jwtAuthManager.RemoveExpiredVerificationCodes(DateTime.Now); + } + + public Task StopAsync(CancellationToken stoppingToken) + { + _timer?.Change(Timeout.Infinite, 0); + return Task.CompletedTask; + } + + public void Dispose() + { + _timer?.Dispose(); + } + } +} diff --git a/Tsi1.Api/Tsi1.Api/Models/LoginRequest.cs b/Tsi1.Api/Tsi1.Api/Models/LoginRequest.cs index 96e87b18d03e7185430f760fb0a5c9caefe6611f..5408e8b422bb74b8ad6257bacefdc6aedef08259 100644 --- a/Tsi1.Api/Tsi1.Api/Models/LoginRequest.cs +++ b/Tsi1.Api/Tsi1.Api/Models/LoginRequest.cs @@ -11,7 +11,7 @@ namespace Tsi1.Api.Models { [Required] [JsonPropertyName("username")] - public string UserName { get; set; } + public string Username { get; set; } [Required] [JsonPropertyName("password")] diff --git a/Tsi1.Api/Tsi1.Api/Models/VerificationCode.cs b/Tsi1.Api/Tsi1.Api/Models/VerificationCode.cs new file mode 100644 index 0000000000000000000000000000000000000000..253d88390adeeadd50843b579cc03d3e5372d153 --- /dev/null +++ b/Tsi1.Api/Tsi1.Api/Models/VerificationCode.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Tsi1.Api.Models +{ + public class VerificationCode + { + public string Username { get; set; } + public int Code { get; set; } + public DateTime ExpireAt { get; set; } + } +} diff --git a/Tsi1.Api/Tsi1.Api/SignalR/ChatHub.cs b/Tsi1.Api/Tsi1.Api/SignalR/ChatHub.cs new file mode 100644 index 0000000000000000000000000000000000000000..0c54af61a8bd5ac751eec8dd173a9f1140b4f5c8 --- /dev/null +++ b/Tsi1.Api/Tsi1.Api/SignalR/ChatHub.cs @@ -0,0 +1,146 @@ +using AutoMapper; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.SignalR; +using Microsoft.EntityFrameworkCore; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Cryptography.X509Certificates; +using System.Threading.Tasks; +using Tsi1.BusinessLayer.Dtos; +using Tsi1.BusinessLayer.Interfaces; +using Tsi1.DataLayer; +using Tsi1.DataLayer.Entities; + +namespace Tsi1.Api.SignalR +{ + [Authorize] + public class ChatHub : Hub + { + private readonly IMapper _mapper; + private readonly IHubContext<PresenceHub> _presenceHub; + private readonly PresenceTracker _tracker; + private readonly IMessageService _messageService; + private readonly IChatService _chatService; + + public ChatHub(IMapper mapper, IHubContext<PresenceHub> presenceHub, + PresenceTracker tracker, IMessageService messageService, IChatService chatService) + { + _tracker = tracker; + _presenceHub = presenceHub; + _mapper = mapper; + _messageService = messageService; + _chatService = chatService; + } + + public override async Task OnConnectedAsync() + { + var httpContext = Context.GetHttpContext(); + + var otherUserId = int.Parse(httpContext.Request.Query["id"].ToString()); + + var userId = int.Parse(Context.User.Claims.FirstOrDefault(x => x.Type == "Id").Value); + var tenantId = int.Parse(Context.User.Claims.FirstOrDefault(x => x.Type == "TenantId").Value); + + var groupName = GetGroupName(userId, otherUserId); + await Groups.AddToGroupAsync(Context.ConnectionId, groupName); + + await AddToGroup(groupName); + + var messages = await _messageService.GetMessages(userId, otherUserId, tenantId); + + await Clients.Caller.SendAsync("ReceiveMessages", messages); + } + + public override async Task OnDisconnectedAsync(Exception exception) + { + + await RemoveFromGroup(); + + await base.OnDisconnectedAsync(exception); + } + + public async Task SendMessage(MessageCreateDto newMessage) + { + var userId = int.Parse(Context.User.Claims.FirstOrDefault(x => x.Type == "Id").Value); + var tenantId = int.Parse(Context.User.Claims.FirstOrDefault(x => x.Type == "TenantId").Value); + + if (userId == newMessage.ReceiverId) + throw new HubException("You cannot send messages to yourself"); + + var groupName = GetGroupName(userId, newMessage.ReceiverId); + + var group = await _chatService.GetGroupByName(groupName); + + if (group == null) + { + throw new HubException($"No existe el grupo {groupName}"); + } + + if (!group.Connections.Any(x => x.UserId == newMessage.ReceiverId)) + { + var connections = await _tracker.GetConnectionsForUser(newMessage.ReceiverId); + if (connections != null) + { + await _presenceHub.Clients.Clients(connections).SendAsync("NewMessageReceived", userId); + } + } + + newMessage.SenderId = userId; + var messageDto = await _messageService.Send(newMessage); + + await Clients.Group(groupName).SendAsync("NewMessage", messageDto); + } + + private async Task<Group> AddToGroup(string groupName) + { + var group = await _chatService.GetGroupByName(groupName); + + if (group == null) + { + throw new HubException($"No existe el grupo {groupName}"); + } + + var userId = int.Parse(Context.User.Claims.FirstOrDefault(x => x.Type == "Id").Value); + + var connection = new Connection + { + ConnectionId = Context.ConnectionId, + UserId = userId, + }; + + if (group == null) + { + group = new Group + { + Name = groupName, + }; + } + + group.Connections.Add(connection); + + var result = await _chatService.CreateGroup(group); + + if (result.HasError) + { + throw new HubException($"Error al crear el grupo {group}"); + } + + return group; + } + + private async Task<bool> RemoveFromGroup() + { + await _chatService.RemoveConnectionFromGroup(Context.ConnectionId); + + return true; + } + + private string GetGroupName(int callerId, int otherId) + { + var compare = callerId < otherId; + return compare ? $"{callerId}-{otherId}" : $"{otherId}-{callerId}"; + } + } +} diff --git a/Tsi1.Api/Tsi1.Api/SignalR/PresenceHub.cs b/Tsi1.Api/Tsi1.Api/SignalR/PresenceHub.cs new file mode 100644 index 0000000000000000000000000000000000000000..e606bc97df59fe20017906c434941e3b40ca28a8 --- /dev/null +++ b/Tsi1.Api/Tsi1.Api/SignalR/PresenceHub.cs @@ -0,0 +1,41 @@ +using Microsoft.AspNetCore.SignalR; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Tsi1.Api.SignalR +{ + public class PresenceHub : Hub + { + private readonly PresenceTracker _tracker; + public PresenceHub(PresenceTracker tracker) + { + _tracker = tracker; + } + + public override async Task OnConnectedAsync() + { + var userId = int.Parse(Context.User.Claims.FirstOrDefault(x => x.Type == "Id").Value); + + var isOnline = await _tracker.UserConnected(userId, Context.ConnectionId); + if (isOnline) + await Clients.Others.SendAsync("UserIsOnline", userId); + + var currentUsers = await _tracker.GetOnlineUsers(); + await Clients.Caller.SendAsync("GetOnlineUsers", currentUsers); + } + + public override async Task OnDisconnectedAsync(Exception exception) + { + var userId = int.Parse(Context.User.Claims.FirstOrDefault(x => x.Type == "Id").Value); + + var isOffline = await _tracker.UserDisconnected(userId, Context.ConnectionId); + + if (isOffline) + await Clients.Others.SendAsync("UserIsOffline", userId); + + await base.OnDisconnectedAsync(exception); + } + } +} diff --git a/Tsi1.Api/Tsi1.Api/SignalR/PresenceTracker.cs b/Tsi1.Api/Tsi1.Api/SignalR/PresenceTracker.cs new file mode 100644 index 0000000000000000000000000000000000000000..f11f18db7d18e01b3a2f8b82d65f8a07dcb3db47 --- /dev/null +++ b/Tsi1.Api/Tsi1.Api/SignalR/PresenceTracker.cs @@ -0,0 +1,72 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Tsi1.Api.SignalR +{ + public class PresenceTracker + { + private static readonly Dictionary<int, List<string>> OnlineUsers = + new Dictionary<int, List<string>>(); + + public Task<bool> UserConnected(int userId, string connectionId) + { + bool isOnline = false; + lock (OnlineUsers) + { + if (OnlineUsers.ContainsKey(userId)) + { + OnlineUsers[userId].Add(connectionId); + } + else + { + OnlineUsers.Add(userId, new List<string> { connectionId }); + isOnline = true; + } + } + + return Task.FromResult(isOnline); + } + + public Task<bool> UserDisconnected(int userId, string connectionId) + { + bool isOffline = false; + lock (OnlineUsers) + { + if (!OnlineUsers.ContainsKey(userId)) return Task.FromResult(isOffline); + + OnlineUsers[userId].Remove(connectionId); + if (OnlineUsers[userId].Count == 0) + { + OnlineUsers.Remove(userId); + isOffline = true; + } + } + + return Task.FromResult(isOffline); + } + + public Task<int[]> GetOnlineUsers() + { + int[] onlineUsers; + lock (OnlineUsers) + { + onlineUsers = OnlineUsers.OrderBy(k => k.Key).Select(k => k.Key).ToArray(); + } + + return Task.FromResult(onlineUsers); + } + + public Task<List<string>> GetConnectionsForUser(int userId) + { + List<string> connectionIds; + lock (OnlineUsers) + { + connectionIds = OnlineUsers.GetValueOrDefault(userId); + } + + return Task.FromResult(connectionIds); + } + } +} diff --git a/Tsi1.Api/Tsi1.Api/Startup.cs b/Tsi1.Api/Tsi1.Api/Startup.cs index 9e0df6280b29f68a2954b9cb32413a6b44cedd46..135212e330271889277770863aa98754ea3dcd31 100644 --- a/Tsi1.Api/Tsi1.Api/Startup.cs +++ b/Tsi1.Api/Tsi1.Api/Startup.cs @@ -1,17 +1,21 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; using AutoMapper; using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.HttpsPolicy; using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.StaticFiles; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; @@ -48,6 +52,8 @@ namespace Tsi1.Api var postgreSqlSection = isElasticCloud ? "PostgreSqlCloud" : "PostgreSql"; var mongoDbSection = isElasticCloud ? "Tsi1DatabaseSettingsCloud" : "Tsi1DatabaseSettings"; + var jwtTokenConfig = Configuration.GetSection("jwtTokenConfig").Get<JwtTokenConfig>(); + services.AddDbContext<Tsi1Context>(x => x.UseNpgsql(Configuration.GetConnectionString(postgreSqlSection))); services.Configure<Tsi1DatabaseSettings>( @@ -56,6 +62,13 @@ namespace Tsi1.Api services.AddSingleton<ITsi1DatabaseSettings>(sp => sp.GetRequiredService<IOptions<Tsi1DatabaseSettings>>().Value); + services.AddSingleton(jwtTokenConfig); + + services.AddSingleton<IJwtAuthManager, JwtAuthManager>(); + services.AddHostedService<JwtRefreshTokenCache>(); + services.AddHostedService<JwtVerificationCodeCache>(); + + services.AddSingleton<IMessageService, MessageService>(); services.AddScoped<IUserService, UserService>(); @@ -65,14 +78,19 @@ namespace Tsi1.Api services.AddScoped<IPostService, PostService>(); services.AddScoped<IPostMessageService, PostMessageService>(); services.AddScoped<ITenantService, TenantService>(); + services.AddScoped<IFileService, FileService>(); services.Configure<MailSettings>(Configuration.GetSection("MailSettings")); services.AddScoped<IEmailService, EmailService>(); services.AddCors(); - var jwtTokenConfig = Configuration.GetSection("jwtTokenConfig").Get<JwtTokenConfig>(); - services.AddSingleton(jwtTokenConfig); + services.AddAuthorization(options => + { + options.FallbackPolicy = new AuthorizationPolicyBuilder() + .RequireAuthenticatedUser() + .Build(); + }); services.AddAuthentication(x => { @@ -112,9 +130,6 @@ namespace Tsi1.Api }; }); - services.AddSingleton<IJwtAuthManager, JwtAuthManager>(); - services.AddHostedService<JwtRefreshTokenCache>(); - services.AddSwaggerGen(c => { c.SwaggerDoc("v1", new OpenApiInfo { Title = "Tsi1 api", Version = "v1" }); @@ -145,7 +160,7 @@ namespace Tsi1.Api mc.AddProfile(new MappingProfile()); }); - IMapper mapper = mappingConfig.CreateMapper(); + var mapper = mappingConfig.CreateMapper(); services.AddSingleton(mapper); } @@ -172,10 +187,22 @@ namespace Tsi1.Api app.UseAuthentication(); app.UseAuthorization(); + var provider = new FileExtensionContentTypeProvider(); + app.UseStaticFiles(new StaticFileOptions() + { + ContentTypeProvider = provider, + FileProvider = new PhysicalFileProvider( + Path.Combine(env.ContentRootPath, "StaticFiles")), + RequestPath = "/static" + } + ); + app.UseEndpoints(endpoints => { endpoints.MapControllers(); endpoints.MapHub<VideoHub>("hubs/video"); + endpoints.MapHub<PresenceHub>("hubs/presence"); + endpoints.MapHub<ChatHub>("hubs/chat"); }); } } diff --git a/Tsi1.Api/Tsi1.Api/Tsi1.Api.csproj b/Tsi1.Api/Tsi1.Api/Tsi1.Api.csproj index 1ed4b443527a84acaf750eab3427ac11002a946a..7fa4dab4f5b7683e724fdac0596793f7c778db45 100644 --- a/Tsi1.Api/Tsi1.Api/Tsi1.Api.csproj +++ b/Tsi1.Api/Tsi1.Api/Tsi1.Api.csproj @@ -14,5 +14,9 @@ <ProjectReference Include="..\Tsi1.BusinessLayer\Tsi1.BusinessLayer.csproj" /> </ItemGroup> + <ItemGroup> + <Folder Include="StaticFiles\" /> + </ItemGroup> + </Project> diff --git a/Tsi1.Api/Tsi1.Api/appsettings.json b/Tsi1.Api/Tsi1.Api/appsettings.json index 45af97fda74df094689e136d9938c4e8affeab61..810e99c8b712f19dba9b1aa60a3397e8533f5881 100644 --- a/Tsi1.Api/Tsi1.Api/appsettings.json +++ b/Tsi1.Api/Tsi1.Api/appsettings.json @@ -14,12 +14,13 @@ "ConnectionString": "mongodb://mongo:27017", "DatabaseName": "Tsi1Db" }, - "jwtTokenConfig": { - "secret": "1234567890123456789", - "issuer": "https://localhost:5000", - "audience": "https://localhost:5000", - "accessTokenExpiration": 20, - "refreshTokenExpiration": 60 + "JwtTokenConfig": { + "Secret": "1234567890123456789", + "Issuer": "https://localhost:5000", + "Audience": "https://localhost:5000", + "AccessTokenExpiration": 20, + "RefreshTokenExpiration": 60, + "VerificationCodeExpiration": 5 }, "MailSettings": { "Mail": "tsi1.grupo2.2020@gmail.com", diff --git a/Tsi1.Api/Tsi1.BusinessLayer/Dtos/CoursePreviewDto.cs b/Tsi1.Api/Tsi1.BusinessLayer/Dtos/CoursePreviewDto.cs index de761a5eb908f333c2ceb7c394341f9febb165c8..c4a7e56fa3c89bcfbddfeb21ddf9156e329bcd93 100644 --- a/Tsi1.Api/Tsi1.BusinessLayer/Dtos/CoursePreviewDto.cs +++ b/Tsi1.Api/Tsi1.BusinessLayer/Dtos/CoursePreviewDto.cs @@ -8,5 +8,6 @@ namespace Tsi1.BusinessLayer.Dtos { public int Id { get; set; } public string Name { get; set; } + public int StudentQuantity { get; set; } } } diff --git a/Tsi1.Api/Tsi1.BusinessLayer/Dtos/FileDto.cs b/Tsi1.Api/Tsi1.BusinessLayer/Dtos/FileDto.cs new file mode 100644 index 0000000000000000000000000000000000000000..977a0596daafe431f74776ab1dca5530a569655b --- /dev/null +++ b/Tsi1.Api/Tsi1.BusinessLayer/Dtos/FileDto.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Tsi1.BusinessLayer.Dtos +{ + public class FileDto + { + public int Id { get; set; } + + public string Name { get; set; } + + public string Path { get; set; } + } +} diff --git a/Tsi1.Api/Tsi1.BusinessLayer/Dtos/RestorePasswordDto.cs b/Tsi1.Api/Tsi1.BusinessLayer/Dtos/RestorePasswordDto.cs new file mode 100644 index 0000000000000000000000000000000000000000..b1e4c4d35789e8edc7413f1e4fe3e8eb245e4dfe --- /dev/null +++ b/Tsi1.Api/Tsi1.BusinessLayer/Dtos/RestorePasswordDto.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Tsi1.BusinessLayer.Dtos +{ + public class RestorePasswordDto + { + public string Password { get; set; } + } +} diff --git a/Tsi1.Api/Tsi1.BusinessLayer/Dtos/TenantCourseDto.cs b/Tsi1.Api/Tsi1.BusinessLayer/Dtos/TenantCourseDto.cs new file mode 100644 index 0000000000000000000000000000000000000000..549efe2b0f218f6bf7b989d7fc9c7318bf72e619 --- /dev/null +++ b/Tsi1.Api/Tsi1.BusinessLayer/Dtos/TenantCourseDto.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Tsi1.BusinessLayer.Dtos +{ + public class TenantCourseDto + { + public TenantCourseDto() + { + Courses = new List<CoursePreviewDto>(); + } + + public int Id { get; set; } + + public string Name { get; set; } + + public List<CoursePreviewDto> Courses { get; set; } + } +} diff --git a/Tsi1.Api/Tsi1.BusinessLayer/Dtos/TenantPreviewDto.cs b/Tsi1.Api/Tsi1.BusinessLayer/Dtos/TenantPreviewDto.cs index 74b8300acafec93c16aac87e34da1af457055766..273d9f5b5deedc458ab30fdb2333a594a889f731 100644 --- a/Tsi1.Api/Tsi1.BusinessLayer/Dtos/TenantPreviewDto.cs +++ b/Tsi1.Api/Tsi1.BusinessLayer/Dtos/TenantPreviewDto.cs @@ -9,5 +9,7 @@ namespace Tsi1.BusinessLayer.Dtos public int Id { get; set; } public string Name { get; set; } + + public int StudentQuantity { get; set; } } } diff --git a/Tsi1.Api/Tsi1.BusinessLayer/Dtos/UserModifyDto.cs b/Tsi1.Api/Tsi1.BusinessLayer/Dtos/UserModifyDto.cs new file mode 100644 index 0000000000000000000000000000000000000000..c3e9894b71f0a081a566e718800561479a75b076 --- /dev/null +++ b/Tsi1.Api/Tsi1.BusinessLayer/Dtos/UserModifyDto.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Text; + +namespace Tsi1.BusinessLayer.Dtos +{ + public class UserModifyDto + { + [Required] + public string Username { get; set; } + + [Required] + public string Password { get; set; } + + [Required] + public string FirstName { get; set; } + + [Required] + public string LastName { get; set; } + + [Required] + public string Email { get; set; } + + [Required] + public string IdentityCard { get; set; } + + public int Age { get; set; } + } +} diff --git a/Tsi1.Api/Tsi1.BusinessLayer/Helpers/ErrorMessages.cs b/Tsi1.Api/Tsi1.BusinessLayer/Helpers/ErrorMessages.cs index 99c521a4d670fd66eb3eee6753c7b97c4afc902b..8dc7c9fb645af7ec4886187388f9ec2c5e662b8e 100644 --- a/Tsi1.Api/Tsi1.BusinessLayer/Helpers/ErrorMessages.cs +++ b/Tsi1.Api/Tsi1.BusinessLayer/Helpers/ErrorMessages.cs @@ -10,10 +10,13 @@ namespace Tsi1.BusinessLayer.Helpers public const string IncorrectPassword = "Contraseña incorrecta"; public const string UserTypeDoesNotExist = "El tipo de usuario con id '{0}' no existe"; 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 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'"; + public const string DuplicateUsername = "Ya existe un usuario con username '{0}'"; public const string ForumDoesNotExist = "El foro con id '{0}' no existe"; @@ -36,5 +39,8 @@ namespace Tsi1.BusinessLayer.Helpers public const string InvalidUserType = "Tipo de usuario invalido '{0}'"; + public const string ErrorSavingFile = "No se pudo guardar el archivo '{0}'"; + public const string ErrorDeletingFile = "No se pudo borrar el archivo '{0}'"; + public const string DuplicateFile = "Ya existe el archivo '{0}'"; } } diff --git a/Tsi1.Api/Tsi1.BusinessLayer/Helpers/MappingProfile.cs b/Tsi1.Api/Tsi1.BusinessLayer/Helpers/MappingProfile.cs index 5d666d16c6f9578c22a8c22b867329345ee444ae..00a67f4141fbdb357d1dc3552ed02951b200b20c 100644 --- a/Tsi1.Api/Tsi1.BusinessLayer/Helpers/MappingProfile.cs +++ b/Tsi1.Api/Tsi1.BusinessLayer/Helpers/MappingProfile.cs @@ -22,13 +22,16 @@ namespace Tsi1.BusinessLayer.Helpers CreateMap<Message, MessageCreateDto>(); CreateMap<User, UserPreviewDto>(); CreateMap<User, UserRegisterDto>(); + CreateMap<User, UserModifyDto>(); CreateMap<Student, StudentPreviewDto>(); CreateMap<Professor, ProfessorPreviewDto>(); CreateMap<Course, CourseCreateDto>(); CreateMap<Course, CoursePreviewDto>(); CreateMap<Tenant, TenantPreviewDto>(); CreateMap<Tenant, TenantCreateDto>(); + CreateMap<Tenant, TenantCourseDto>().ForMember(x => x.Courses, opt => opt.Ignore()); CreateMap<UserType, UserTypeDto>(); + CreateMap<File, FileDto>(); CreateMap<ForumCreateDto, Forum>(); CreateMap<ForumPreviewDto, Forum>(); @@ -40,13 +43,16 @@ namespace Tsi1.BusinessLayer.Helpers CreateMap<MessageCreateDto, Message>(); CreateMap<UserPreviewDto, User>(); CreateMap<UserRegisterDto, User>(); + CreateMap<UserModifyDto, User>(); CreateMap<StudentPreviewDto, Student>(); CreateMap<ProfessorPreviewDto, Professor>(); CreateMap<CourseCreateDto, Course>(); CreateMap<CoursePreviewDto, Course>(); CreateMap<TenantPreviewDto, Tenant>(); CreateMap<TenantCreateDto, Tenant>(); + CreateMap<TenantCourseDto, Tenant>(); CreateMap<UserTypeDto, UserType>(); + CreateMap<FileDto, File>(); } } } diff --git a/Tsi1.Api/Tsi1.BusinessLayer/Helpers/TenantAdmin.cs b/Tsi1.Api/Tsi1.BusinessLayer/Helpers/TenantAdmin.cs new file mode 100644 index 0000000000000000000000000000000000000000..4672c8518bed0f807b1b449625ee79209bc5dddb --- /dev/null +++ b/Tsi1.Api/Tsi1.BusinessLayer/Helpers/TenantAdmin.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Tsi1.BusinessLayer.Helpers +{ + public static class TenantAdmin + { + public const string Name = "admin"; + } +} diff --git a/Tsi1.Api/Tsi1.BusinessLayer/Interfaces/IChatService.cs b/Tsi1.Api/Tsi1.BusinessLayer/Interfaces/IChatService.cs new file mode 100644 index 0000000000000000000000000000000000000000..cf332b0a11d11b5b77f9058d9d1a95488293b9ea --- /dev/null +++ b/Tsi1.Api/Tsi1.BusinessLayer/Interfaces/IChatService.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; +using Tsi1.BusinessLayer.Helpers; +using Tsi1.DataLayer.Entities; + +namespace Tsi1.BusinessLayer.Interfaces +{ + public interface IChatService + { + Task<Group> GetGroupByName(string groupName); + + Task<ServiceResult<Group>> CreateGroup(Group group); + + Task<bool> RemoveConnectionFromGroup(string connectionId); + } +} diff --git a/Tsi1.Api/Tsi1.BusinessLayer/Interfaces/ICourseService.cs b/Tsi1.Api/Tsi1.BusinessLayer/Interfaces/ICourseService.cs index 082798fedb9a2cc7a9802f2266558a00e8111c8d..f13641f98f895d1f846b25bda95135e3a6b5be64 100644 --- a/Tsi1.Api/Tsi1.BusinessLayer/Interfaces/ICourseService.cs +++ b/Tsi1.Api/Tsi1.BusinessLayer/Interfaces/ICourseService.cs @@ -17,5 +17,15 @@ namespace Tsi1.BusinessLayer.Interfaces Task<ServiceResult<bool>> Matriculate(int userId, int courseId); Task<ServiceResult<bool>> AddProfessorToCourse(ProfessorCourseDto professorCourseDto); + + Task<ServiceResult<List<CoursePreviewDto>>> GetAll(int tenantId); + + Task<ServiceResult<bool>> Modify(int courseId, CourseCreateDto courseDto); + + Task<ServiceResult<Course>> Delete(int courseId); + + Task<ServiceResult<bool>> DropOutFromCourse(int userId, int courseId); + + Task<ServiceResult<bool>> RemoveProfessorToCourse(ProfessorCourseDto professorCourseDto); } } diff --git a/Tsi1.Api/Tsi1.BusinessLayer/Interfaces/IEmailService.cs b/Tsi1.Api/Tsi1.BusinessLayer/Interfaces/IEmailService.cs index a97ea5c312cf5448689c731402cf293ea49ebca4..3cafba23552d1fb40adb046cebae313398b60098 100644 --- a/Tsi1.Api/Tsi1.BusinessLayer/Interfaces/IEmailService.cs +++ b/Tsi1.Api/Tsi1.BusinessLayer/Interfaces/IEmailService.cs @@ -14,5 +14,7 @@ namespace Tsi1.BusinessLayer.Interfaces Task<ServiceResult<bool>> SendEmailAsync(MimeMessage message); Task<ServiceResult<bool>> NotifyNewPostOrMessage(PostCreateDto postCreateDto, List<string> mails); + + public Task<ServiceResult<bool>> SendVerificationCode(string mail, int code); } } diff --git a/Tsi1.Api/Tsi1.BusinessLayer/Interfaces/IFileService.cs b/Tsi1.Api/Tsi1.BusinessLayer/Interfaces/IFileService.cs new file mode 100644 index 0000000000000000000000000000000000000000..a42bbf693f410735a37715369574479ae29b6551 --- /dev/null +++ b/Tsi1.Api/Tsi1.BusinessLayer/Interfaces/IFileService.cs @@ -0,0 +1,19 @@ +using Microsoft.AspNetCore.Http; +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 IFileService + { + Task<ServiceResult<FileDto>> Create(IFormFile file, int tenantId, int courseId); + + void CreateFolder(string folderPath); + + void DeleteFolder(string folderPath); + } +} diff --git a/Tsi1.Api/Tsi1.BusinessLayer/Interfaces/ITenantService.cs b/Tsi1.Api/Tsi1.BusinessLayer/Interfaces/ITenantService.cs index 90bf95b9d814421c99c54eff4ff0a2e20885af0f..a1b88ed057e0435a0c6ff68901bdccd86d235ca4 100644 --- a/Tsi1.Api/Tsi1.BusinessLayer/Interfaces/ITenantService.cs +++ b/Tsi1.Api/Tsi1.BusinessLayer/Interfaces/ITenantService.cs @@ -11,8 +11,18 @@ namespace Tsi1.BusinessLayer.Interfaces { Task<ServiceResult<int>> GetByName(string tenantName); + Task<ServiceResult<int>> GetById(int tenantId); + Task<ServiceResult<List<TenantPreviewDto>>> GetAll(); Task<ServiceResult<TenantPreviewDto>> Create(TenantCreateDto newTenant); + + Task<ServiceResult<bool>> Modify(int tenantId, TenantCreateDto tenantDto); + + Task<ServiceResult<bool>> Delete(int tenantId); + + Task<ServiceResult<List<TenantPreviewDto>>> FacultyList(); + + Task<ServiceResult<List<TenantCourseDto>>> CourseList(); } } diff --git a/Tsi1.Api/Tsi1.BusinessLayer/Interfaces/IUserService.cs b/Tsi1.Api/Tsi1.BusinessLayer/Interfaces/IUserService.cs index 12444e66d7fe3e492bbcbdb8b0d7353730c89beb..54550814463757e24870ae34bb6b51a68217ce11 100644 --- a/Tsi1.Api/Tsi1.BusinessLayer/Interfaces/IUserService.cs +++ b/Tsi1.Api/Tsi1.BusinessLayer/Interfaces/IUserService.cs @@ -17,5 +17,17 @@ namespace Tsi1.BusinessLayer.Interfaces Task<ServiceResult<List<UserPreviewDto>>> GetAll(int tenantId); Task<ServiceResult<UserPreviewDto>> GetById(int userId); + + Task<ServiceResult<User>> GetByUsername(string username, int tenantId); + + Task<ServiceResult<bool>> UpdatePassword(int userId, string password); + + Task<ServiceResult<bool>> Modify(UserModifyDto dto, string type, int userId); + + Task<ServiceResult<int>> GetTenant(int userId); + + Task<ServiceResult<bool>> Delete(int userId); + + Task<ServiceResult<UserTypeDto>> GetUserType(int userId); } } diff --git a/Tsi1.Api/Tsi1.BusinessLayer/Services/ChatService.cs b/Tsi1.Api/Tsi1.BusinessLayer/Services/ChatService.cs new file mode 100644 index 0000000000000000000000000000000000000000..4e4ac8e76aaca0017a0d35dca73cd1e57503da3e --- /dev/null +++ b/Tsi1.Api/Tsi1.BusinessLayer/Services/ChatService.cs @@ -0,0 +1,60 @@ +using Microsoft.EntityFrameworkCore; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; +using Tsi1.BusinessLayer.Helpers; +using Tsi1.BusinessLayer.Interfaces; +using Tsi1.DataLayer; +using Tsi1.DataLayer.Entities; + +namespace Tsi1.BusinessLayer.Services +{ + public class ChatService : IChatService + { + private readonly Tsi1Context _tsi1Context; + + public ChatService(Tsi1Context tsi1Context) + { + _tsi1Context = tsi1Context; + } + public async Task<ServiceResult<Group>> CreateGroup(Group group) + { + var result = new ServiceResult<Group>(); + + _tsi1Context.Groups.Add(group); + try + { + await _tsi1Context.SaveChangesAsync(); + } + catch (Exception) + { + result.HasError = true; + } + + return result; + } + + public async Task<Group> GetGroupByName(string groupName) + { + var result = new ServiceResult<Group>(); + + var group = await _tsi1Context.Groups.FirstOrDefaultAsync(x => x.Name == groupName); + + return group; + } + + public async Task<bool> RemoveConnectionFromGroup(string connectionId) + { + var connection = await _tsi1Context.Connections.FirstOrDefaultAsync(x => x.ConnectionId == connectionId); + + if (connection != null) + { + connection.Group = null; + await _tsi1Context.SaveChangesAsync(); + } + + return true; + } + } +} diff --git a/Tsi1.Api/Tsi1.BusinessLayer/Services/CourseService.cs b/Tsi1.Api/Tsi1.BusinessLayer/Services/CourseService.cs index 4c1f4ceec956384a2df10f16c45ab66fb0f6cc8f..6930b541c7218a1262f4c772951d1d29ef787be3 100644 --- a/Tsi1.Api/Tsi1.BusinessLayer/Services/CourseService.cs +++ b/Tsi1.Api/Tsi1.BusinessLayer/Services/CourseService.cs @@ -41,7 +41,7 @@ namespace Tsi1.BusinessLayer.Services } var course = _mapper.Map<Course>(newCourse); - + _context.Courses.Add(course); await _context.SaveChangesAsync(); @@ -97,7 +97,6 @@ namespace Tsi1.BusinessLayer.Services if (user == null || user.Student == null) { - result.HasError = true; result.Message = string.Format(ErrorMessages.UserDoesNotExist, userId); return result; } @@ -106,7 +105,6 @@ namespace Tsi1.BusinessLayer.Services if (course == null) { - result.HasError = true; result.Message = string.Format(ErrorMessages.CourseDoesNotExist, courseId); return result; } @@ -130,7 +128,9 @@ namespace Tsi1.BusinessLayer.Services _context.StudentCourses.Add(studentCourse); await _context.SaveChangesAsync(); - + + result.Data = true; + return result; } @@ -144,7 +144,6 @@ namespace Tsi1.BusinessLayer.Services if (user == null || user.Professor == null) { - result.HasError = true; result.Message = string.Format(ErrorMessages.UserDoesNotExist, user.Username); return result; } @@ -154,7 +153,6 @@ namespace Tsi1.BusinessLayer.Services if (course == null) { - result.HasError = true; result.Message = string.Format(ErrorMessages.CourseDoesNotExist, professorCourseDto.CourseId); return result; } @@ -179,6 +177,150 @@ namespace Tsi1.BusinessLayer.Services await _context.SaveChangesAsync(); + result.Data = true; + + return result; + } + + public async Task<ServiceResult<List<CoursePreviewDto>>> GetAll(int tenantId) + { + var result = new ServiceResult<List<CoursePreviewDto>>(); + + var courses = await _context.Courses + .Where(x => x.TenantId == tenantId) + .ToListAsync(); + + var coursesDto = _mapper.Map<List<CoursePreviewDto>>(courses); + + result.Data = coursesDto; + + return result; + } + + public async Task<ServiceResult<bool>> Modify(int courseId, CourseCreateDto courseDto) + { + var result = new ServiceResult<bool>(); + + var course = await _context.Courses + .FirstOrDefaultAsync(x => x.Id == courseId); + + if (course == null) + { + result.Message = string.Format(ErrorMessages.CourseDoesNotExist, courseId); + return result; + } + + _mapper.Map(courseDto, course); + + await _context.SaveChangesAsync(); + + result.Data = true; + + return result; + } + + public async Task<ServiceResult<Course>> Delete(int courseId) + { + var result = new ServiceResult<Course>(); + + var course = await _context.Courses + .FirstOrDefaultAsync(x => x.Id == courseId); + + if (course == null) + { + result.Message = string.Format(ErrorMessages.CourseDoesNotExist, courseId); + return result; + } + + _context.Courses.Remove(course); + + await _context.SaveChangesAsync(); + + result.Data = course; + + return result; + } + + public async Task<ServiceResult<bool>> DropOutFromCourse(int userId, int courseId) + { + var result = new ServiceResult<bool>(); + + var user = await _context.Users + .Include(x => x.Student) + .FirstOrDefaultAsync(x => x.Id == userId); + + if (user == null || user.Student == null) + { + result.Message = string.Format(ErrorMessages.UserDoesNotExist, userId); + return result; + } + + var course = await _context.Courses.FirstOrDefaultAsync(x => x.Id == courseId); + + if (course == null) + { + result.Message = string.Format(ErrorMessages.CourseDoesNotExist, courseId); + return result; + } + + var studentCourse = await _context.StudentCourses + .FirstOrDefaultAsync(x => x.StudentId == user.StudentId && x.CourseId == course.Id); + + if (studentCourse == null) + { + result.HasError = true; + result.Message = string.Format(ErrorMessages.StudentCourseDoesNotExists, user.Username, course.Name); + return result; + } + + _context.StudentCourses.Remove(studentCourse); + + await _context.SaveChangesAsync(); + + result.Data = true; + + return result; + } + + public async Task<ServiceResult<bool>> RemoveProfessorToCourse(ProfessorCourseDto professorCourseDto) + { + var result = new ServiceResult<bool>(); + + var user = await _context.Users + .Include(x => x.Professor) + .FirstOrDefaultAsync(x => x.Id == professorCourseDto.UserId); + + if (user == null || user.Professor == null) + { + result.Message = string.Format(ErrorMessages.UserDoesNotExist, user.Username); + return result; + } + + var course = await _context.Courses + .FirstOrDefaultAsync(x => x.Id == professorCourseDto.CourseId); + + if (course == null) + { + result.Message = string.Format(ErrorMessages.CourseDoesNotExist, professorCourseDto.CourseId); + return result; + } + + var professorCourse = await _context.ProfessorCourses + .FirstOrDefaultAsync(x => x.ProfessorId == user.ProfessorId && x.CourseId == course.Id); + + if (professorCourse == null) + { + result.HasError = true; + result.Message = string.Format(ErrorMessages.ProfessorCourseDoesNotExists, user.Username, course.Name); + return result; + } + + _context.ProfessorCourses.Remove(professorCourse); + + await _context.SaveChangesAsync(); + + result.Data = true; + return result; } } diff --git a/Tsi1.Api/Tsi1.BusinessLayer/Services/EmailService.cs b/Tsi1.Api/Tsi1.BusinessLayer/Services/EmailService.cs index d00e398d81ef6e05d34547d90d87d5a6e11f7ffd..9519fc5b0cf3037535e101a866a297392b786825 100644 --- a/Tsi1.Api/Tsi1.BusinessLayer/Services/EmailService.cs +++ b/Tsi1.Api/Tsi1.BusinessLayer/Services/EmailService.cs @@ -86,9 +86,22 @@ namespace Tsi1.BusinessLayer.Services }; var result = await SendEmailAsync(message); - return result; } + public async Task<ServiceResult<bool>> SendVerificationCode(string mail, int code) + { + var message = new MimeMessage(); + + message.To.Add(MailboxAddress.Parse(mail)); + message.Subject = "Código de verificación"; + message.Body = new TextPart("html") + { + Text = $"<p>Su código de verificación es {code}</p>" + }; + + var result = await SendEmailAsync(message); + return result; + } } } diff --git a/Tsi1.Api/Tsi1.BusinessLayer/Services/FileService.cs b/Tsi1.Api/Tsi1.BusinessLayer/Services/FileService.cs new file mode 100644 index 0000000000000000000000000000000000000000..315250148761311cbd020f1da7863798800a7793 --- /dev/null +++ b/Tsi1.Api/Tsi1.BusinessLayer/Services/FileService.cs @@ -0,0 +1,139 @@ +using AutoMapper; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.EntityFrameworkCore; +using System; +using System.Collections.Generic; +using System.IO; +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 FileService : IFileService + { + private readonly Tsi1Context _context; + + private readonly IMapper _mapper; + + private readonly string _path; + + public FileService(Tsi1Context context, IMapper mapper, IHostingEnvironment hostingEnvironment) + { + _context = context; + _mapper = mapper; + _path = hostingEnvironment.ContentRootPath + "/StaticFiles"; + } + + public async Task<ServiceResult<string>> SaveFile(IFormFile file, string filePath) + { + var result = new ServiceResult<string>(); + + var fileStream = new FileStream(filePath, FileMode.Create); + + try + { + await file.CopyToAsync(fileStream); + } + catch (Exception) + { + result.HasError = true; + result.Message = string.Format(ErrorMessages.ErrorSavingFile, filePath); + return result; + } + finally + { + fileStream.Close(); + } + + return result; + } + + + + public async Task<ServiceResult<FileDto>> Create(IFormFile file, int tenantId, int courseId) + { + var result = new ServiceResult<FileDto>(); + + var tenant = await _context.Tenants + .FirstOrDefaultAsync(x => x.Id == tenantId); + + var course = await _context.Courses + .FirstOrDefaultAsync(x => x.Id == courseId); + + var filePath = Path.Combine(_path, tenant.Name, course.Name, file.FileName); + + var existingFile = await _context.Files.FirstOrDefaultAsync(x => x.Path == filePath); + if (existingFile != null) + { + result.HasError = true; + result.Message = string.Format(ErrorMessages.DuplicateFile, filePath); + return result; + } + + var newFile = new DataLayer.Entities.File + { + Name = file.FileName, + Path = filePath.Replace(_path, ""), + }; + + _context.Add(newFile); + await _context.SaveChangesAsync(); + + var resultSaveFile = await this.SaveFile(file, filePath); + if (resultSaveFile.HasError) + { + result.HasError = true; + result.Message = resultSaveFile.Message; + + _context.Remove(newFile); + await _context.SaveChangesAsync(); + + return result; + } + + var fileDto = _mapper.Map<FileDto>(newFile); + result.Data = fileDto; + + return result; + } + + + public async Task<ServiceResult<bool>> DeleteFile(string filePath) + { + var result = new ServiceResult<bool>(); + + try + { + using (var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.None, 4096, FileOptions.DeleteOnClose)) {} + } + catch (Exception) + { + result.HasError = true; + result.Message = string.Format(ErrorMessages.ErrorDeletingFile, filePath); + return result; + } + + return result; + } + + public void CreateFolder(string folderPath) + { + var path = Path.Combine(_path, folderPath); + + Directory.CreateDirectory(path); + } + + public void DeleteFolder(string folderPath) + { + var path = Path.Combine(_path, folderPath); + + Directory.Delete(path); + } + } +} diff --git a/Tsi1.Api/Tsi1.BusinessLayer/Services/TenantService.cs b/Tsi1.Api/Tsi1.BusinessLayer/Services/TenantService.cs index c2659069a5eacd7516d4c4c6d49cf6dcb06d749a..17d6668ac6a2234260d9a64fb1007dcdeefa7437 100644 --- a/Tsi1.Api/Tsi1.BusinessLayer/Services/TenantService.cs +++ b/Tsi1.Api/Tsi1.BusinessLayer/Services/TenantService.cs @@ -1,7 +1,8 @@ -using AutoMapper; +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; @@ -79,5 +80,119 @@ namespace Tsi1.BusinessLayer.Services return result; } + public async Task<ServiceResult<bool>> Modify(int tenantId, TenantCreateDto tenantDto) + { + var result = new ServiceResult<bool>(); + + var tenant = await _context.Tenants + .FirstOrDefaultAsync(x => x.Id == tenantId); + + if (tenant == null) + { + result.Message = string.Format(ErrorMessages.TenantDoesNotExist, tenantId); + return result; + } + + _mapper.Map(tenantDto, tenant); + + await _context.SaveChangesAsync(); + + result.Data = true; + + return result; + } + + public async Task<ServiceResult<bool>> Delete(int tenantId) + { + var result = new ServiceResult<bool>(); + + var tenant = await _context.Tenants + .FirstOrDefaultAsync(x => x.Id == tenantId); + + if (tenant == null) + { + result.Message = string.Format(ErrorMessages.TenantDoesNotExist, tenantId); + return result; + } + + _context.Tenants.Remove(tenant); + + await _context.SaveChangesAsync(); + + result.Data = true; + + return result; + } + + public async Task<ServiceResult<List<TenantPreviewDto>>> FacultyList() + { + var result = new ServiceResult<List<TenantPreviewDto>>(); + + var tenants = await _context.Tenants + .Include(x => x.Students) + .Where(x => x.Name != TenantAdmin.Name) + .ToListAsync(); + + var tenantDtos = new List<TenantPreviewDto>(); + foreach (var tenant in tenants) + { + var tenantDto = _mapper.Map<TenantPreviewDto>(tenant); + tenantDto.StudentQuantity = tenant.Students.Count(); + + tenantDtos.Add(tenantDto); + } + + result.Data = tenantDtos; + return result; + } + + public async Task<ServiceResult<List<TenantCourseDto>>> CourseList() + { + var result = new ServiceResult<List<TenantCourseDto>>(); + + var tenants = await _context.Tenants + .Include(x => x.Courses) + .ThenInclude(x => x.StudentCourses) + .Where(x => x.Name != TenantAdmin.Name) + .ToListAsync(); + + var tenantDtos = new List<TenantCourseDto>(); + foreach (var tenant in tenants) + { + var tenantDto = _mapper.Map<TenantCourseDto>(tenant); + + foreach (var course in tenant.Courses) + { + var courseDto = _mapper.Map<CoursePreviewDto>(course); + courseDto.StudentQuantity = course.StudentCourses.Count(); + + tenantDto.Courses.Add(courseDto); + } + + tenantDtos.Add(tenantDto); + } + + result.Data = tenantDtos; + return result; + } + + public async Task<ServiceResult<int>> GetById(int tenantId) + { + var result = new ServiceResult<int>(); + + var tenant = await _context.Tenants + .FirstOrDefaultAsync(x => x.Id == tenantId); + + if (tenant == null) + { + result.HasError = true; + result.Message = string.Format(ErrorMessages.TenantDoesNotExist, tenantId); + return result; + } + + result.Data = tenant.Id; + + return result; + } } } diff --git a/Tsi1.Api/Tsi1.BusinessLayer/Services/UserService.cs b/Tsi1.Api/Tsi1.BusinessLayer/Services/UserService.cs index 35b14f29c797130271be0d4adacb9190d0ffbc70..3a54107291fd41bd09bd26976997cfe64e3788e7 100644 --- a/Tsi1.Api/Tsi1.BusinessLayer/Services/UserService.cs +++ b/Tsi1.Api/Tsi1.BusinessLayer/Services/UserService.cs @@ -57,6 +57,7 @@ namespace Tsi1.BusinessLayer.Services var result = new ServiceResult<User>(); var user = _mapper.Map<User>(dto); + user.TenantId = tenantId; if (type == UserTypes.Student) @@ -85,6 +86,55 @@ namespace Tsi1.BusinessLayer.Services return result; } + public async Task<ServiceResult<bool>> Modify(UserModifyDto dto, string type, int userId) + { + var result = new ServiceResult<bool>(); + + var user = await _context.Users + .Include(x => x.Student) + .Include(x => x.Professor) + .FirstOrDefaultAsync(x => x.Id == userId); + + if (user == null) + { + result.Message = string.Format(ErrorMessages.UserDoesNotExist, userId); + return result; + } + + if (dto.Username != user.Username) + { + var existingUser = await _context.Users + .FirstOrDefaultAsync(x => x.Username == dto.Username + && x.TenantId == user.TenantId); + + if (existingUser != null) + { + result.HasError = true; + result.Message = string.Format(ErrorMessages.DuplicateUsername, dto.Username); + return result; + } + } + + _mapper.Map(dto, user); + + if (type == UserTypes.Student) + { + user.Student.IdentityCard = dto.IdentityCard; + user.Student.Age = dto.Age; + } + + if (type == UserTypes.Professor) + { + user.Professor.IdentityCard = dto.IdentityCard; + } + + await _context.SaveChangesAsync(); + + result.Data = true; + + return result; + } + public async Task<ServiceResult<List<UserPreviewDto>>> GetAll(int tenantId) { var result = new ServiceResult<List<UserPreviewDto>>(); @@ -138,5 +188,119 @@ namespace Tsi1.BusinessLayer.Services return result; } + + public async Task<ServiceResult<User>> GetByUsername(string username, int tenantId) + { + var result = new ServiceResult<User>(); + + var user = await _context.Users + .Include(x => x.UserType) + .Include(x => x.Student) + .Include(x => x.Professor) + .Where(x => x.TenantId == tenantId + && x.Username == username) + .FirstOrDefaultAsync(); + + if (user == null) + { + result.HasError = true; + result.Message = string.Format(ErrorMessages.UserDoesNotExist, username); + return result; + } + + var userType = user.UserType.Name; + + if (userType == UserTypes.Student && user.Student == null) + { + result.HasError = true; + result.Message = string.Format(ErrorMessages.StudentDoesNotExist, username); + return result; + } + else if (userType == UserTypes.Professor && user.Professor == null) + { + result.HasError = true; + result.Message = string.Format(ErrorMessages.ProffesorDoesNotExist, username); + return result; + } + + result.Data = user; + return result; + } + + public async Task<ServiceResult<bool>> UpdatePassword(int userId, string password) + { + var result = new ServiceResult<bool>(); + var user = await _context.Users.FirstOrDefaultAsync(); + + if (user == null) + { + result.HasError = true; + result.Message = string.Format(ErrorMessages.UserDoesNotExist, userId); + return result; + } + + user.Password = password; + await _context.SaveChangesAsync(); + + return result; + } + + public async Task<ServiceResult<int>> GetTenant(int userId) + { + var result = new ServiceResult<int>(); + + var user = await _context.Users.FirstOrDefaultAsync(x => x.Id == userId); + + if (user == null) + { + result.HasError = true; + result.Message = string.Format(ErrorMessages.UserDoesNotExist, userId); + return result; + } + + result.Data = user.TenantId; + + return result; + } + + public async Task<ServiceResult<bool>> Delete(int userId) + { + var result = new ServiceResult<bool>(); + + var user = await _context.Users.FirstOrDefaultAsync(x => x.Id == userId); + + if (user == null) + { + result.Message = string.Format(ErrorMessages.UserDoesNotExist, userId); + return result; + } + + _context.Users.Remove(user); + + await _context.SaveChangesAsync(); + + result.Data = true; + + return result; + } + + public async Task<ServiceResult<UserTypeDto>> GetUserType(int userId) + { + var result = new ServiceResult<UserTypeDto>(); + + var user = await _context.Users + .Include(x => x.UserType) + .FirstOrDefaultAsync(x => x.Id == userId); + + if (user == null) + { + result.HasError = true; + result.Message = string.Format(ErrorMessages.UserDoesNotExist, userId); + } + + result.Data = _mapper.Map<UserTypeDto>(user.UserType); + + return result; + } } } diff --git a/Tsi1.Api/Tsi1.BusinessLayer/Services/UserTypeService.cs b/Tsi1.Api/Tsi1.BusinessLayer/Services/UserTypeService.cs index e11f550b6dd13589a7379b8579efa715632b989b..be8f6786758d37071c7db5025954e0e9fa1c6673 100644 --- a/Tsi1.Api/Tsi1.BusinessLayer/Services/UserTypeService.cs +++ b/Tsi1.Api/Tsi1.BusinessLayer/Services/UserTypeService.cs @@ -67,6 +67,5 @@ namespace Tsi1.BusinessLayer.Services return result; } - } } diff --git a/Tsi1.Api/Tsi1.BusinessLayer/Tsi1.BusinessLayer.csproj b/Tsi1.Api/Tsi1.BusinessLayer/Tsi1.BusinessLayer.csproj index a21bff7c2af2796490606eab2c96c0b6c51598ae..a18735ed0c8c52887e85611f1b353106330f869f 100644 --- a/Tsi1.Api/Tsi1.BusinessLayer/Tsi1.BusinessLayer.csproj +++ b/Tsi1.Api/Tsi1.BusinessLayer/Tsi1.BusinessLayer.csproj @@ -8,6 +8,8 @@ <PackageReference Include="AutoMapper" Version="10.1.1" /> <PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="8.1.0" /> <PackageReference Include="MailKit" Version="2.9.0" /> + <PackageReference Include="Microsoft.AspNetCore.Hosting.Abstractions" Version="2.2.0" /> + <PackageReference Include="Microsoft.AspNetCore.Http.Features" Version="3.1.9" /> <PackageReference Include="MimeKit" Version="2.9.2" /> </ItemGroup> diff --git a/Tsi1.Api/Tsi1.DataLayer/Entities/Connection.cs b/Tsi1.Api/Tsi1.DataLayer/Entities/Connection.cs new file mode 100644 index 0000000000000000000000000000000000000000..2e2e9366bdd5dd35d36650f805c1672806f08f69 --- /dev/null +++ b/Tsi1.Api/Tsi1.DataLayer/Entities/Connection.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Tsi1.DataLayer.Entities +{ + public class Connection + { + public string ConnectionId { get; set; } + public int UserId { get; set; } + public Group Group { get; set; } + } +} diff --git a/Tsi1.Api/Tsi1.DataLayer/Entities/File.cs b/Tsi1.Api/Tsi1.DataLayer/Entities/File.cs new file mode 100644 index 0000000000000000000000000000000000000000..4643d9c27c0f79917b66a41b0bff4435b6cfdc21 --- /dev/null +++ b/Tsi1.Api/Tsi1.DataLayer/Entities/File.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Tsi1.DataLayer.Entities +{ + public class File + { + public int Id { get; set; } + + public string Name { get; set; } + + public string Path { get; set; } + } +} diff --git a/Tsi1.Api/Tsi1.DataLayer/Entities/Group.cs b/Tsi1.Api/Tsi1.DataLayer/Entities/Group.cs new file mode 100644 index 0000000000000000000000000000000000000000..38291ebfd52def7d9d2e21d177a72fc191b7129c --- /dev/null +++ b/Tsi1.Api/Tsi1.DataLayer/Entities/Group.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Tsi1.DataLayer.Entities +{ + public class Group + { + public Group() + { + Connections = new List<Connection>(); + } + + public string Name { get; set; } + public ICollection<Connection> Connections { get; set; } + } +} diff --git a/Tsi1.Api/Tsi1.DataLayer/EntityConfiguration/ConnectionConfiguration.cs b/Tsi1.Api/Tsi1.DataLayer/EntityConfiguration/ConnectionConfiguration.cs new file mode 100644 index 0000000000000000000000000000000000000000..7c085f952b6ad8859207d0eb4371c69d0edb2301 --- /dev/null +++ b/Tsi1.Api/Tsi1.DataLayer/EntityConfiguration/ConnectionConfiguration.cs @@ -0,0 +1,24 @@ +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 ConnectionConfiguration : IEntityTypeConfiguration<Connection> + { + public void Configure(EntityTypeBuilder<Connection> builder) + { + builder.HasKey(x => x.ConnectionId); + + builder.Property(x => x.UserId) + .IsRequired(); + + builder.HasOne(x => x.Group) + .WithMany(x => x.Connections); + + } + } +} diff --git a/Tsi1.Api/Tsi1.DataLayer/EntityConfiguration/FileConfiguration.cs b/Tsi1.Api/Tsi1.DataLayer/EntityConfiguration/FileConfiguration.cs new file mode 100644 index 0000000000000000000000000000000000000000..ee21323fe9a845cfc5d780b24877022c0b09b2c2 --- /dev/null +++ b/Tsi1.Api/Tsi1.DataLayer/EntityConfiguration/FileConfiguration.cs @@ -0,0 +1,28 @@ +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 FileConfiguration : IEntityTypeConfiguration<File> + { + public void Configure(EntityTypeBuilder<File> builder) + { + builder.HasKey(x => x.Id); + + builder.Property(x => x.Name) + .IsRequired() + .HasColumnType("character varying(50)"); + + builder.Property(x => x.Path) + .IsRequired() + .HasColumnType("character varying(1000)"); + + builder.HasIndex(x => x.Path) + .IsUnique(); + } + } +} diff --git a/Tsi1.Api/Tsi1.DataLayer/EntityConfiguration/GroupConfiguration.cs b/Tsi1.Api/Tsi1.DataLayer/EntityConfiguration/GroupConfiguration.cs new file mode 100644 index 0000000000000000000000000000000000000000..9e64b95e6eb1fca0fff085e53679025dfa95728a --- /dev/null +++ b/Tsi1.Api/Tsi1.DataLayer/EntityConfiguration/GroupConfiguration.cs @@ -0,0 +1,17 @@ +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 GroupConfiguration : IEntityTypeConfiguration<Group> + { + public void Configure(EntityTypeBuilder<Group> builder) + { + builder.HasKey(x => x.Name); + } + } +} diff --git a/Tsi1.Api/Tsi1.DataLayer/Migrations/20201029004502_added-entities-Group-and-Connection.Designer.cs b/Tsi1.Api/Tsi1.DataLayer/Migrations/20201029004502_added-entities-Group-and-Connection.Designer.cs new file mode 100644 index 0000000000000000000000000000000000000000..5dba9e6375b1de286038bffe7e78d016f6cdd791 --- /dev/null +++ b/Tsi1.Api/Tsi1.DataLayer/Migrations/20201029004502_added-entities-Group-and-Connection.Designer.cs @@ -0,0 +1,491 @@ +// <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("20201029004502_added-entities-Group-and-Connection")] + partial class addedentitiesGroupandConnection + { + 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.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<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.Forum", 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(50)"); + + b.HasKey("Id"); + + b.HasIndex("CourseId", "Name") + .IsUnique(); + + 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.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.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.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.Forum", b => + { + b.HasOne("Tsi1.DataLayer.Entities.Course", "Course") + .WithMany("Forums") + .HasForeignKey("CourseId") + .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.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.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/20201029004502_added-entities-Group-and-Connection.cs b/Tsi1.Api/Tsi1.DataLayer/Migrations/20201029004502_added-entities-Group-and-Connection.cs new file mode 100644 index 0000000000000000000000000000000000000000..dfece7bb6d2fa79e3edd1afe268602d00477a253 --- /dev/null +++ b/Tsi1.Api/Tsi1.DataLayer/Migrations/20201029004502_added-entities-Group-and-Connection.cs @@ -0,0 +1,54 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Tsi1.DataLayer.Migrations +{ + public partial class addedentitiesGroupandConnection : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Groups", + columns: table => new + { + Name = table.Column<string>(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Groups", x => x.Name); + }); + + migrationBuilder.CreateTable( + name: "Connections", + columns: table => new + { + ConnectionId = table.Column<string>(nullable: false), + UserId = table.Column<int>(nullable: false), + GroupName = table.Column<string>(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Connections", x => x.ConnectionId); + table.ForeignKey( + name: "FK_Connections_Groups_GroupName", + column: x => x.GroupName, + principalTable: "Groups", + principalColumn: "Name", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + name: "IX_Connections_GroupName", + table: "Connections", + column: "GroupName"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Connections"); + + migrationBuilder.DropTable( + name: "Groups"); + } + } +} diff --git a/Tsi1.Api/Tsi1.DataLayer/Migrations/20201101163611_add-File-entity.Designer.cs b/Tsi1.Api/Tsi1.DataLayer/Migrations/20201101163611_add-File-entity.Designer.cs new file mode 100644 index 0000000000000000000000000000000000000000..13074745a6636f6ee5f1fb3231bbc45b2c8aada6 --- /dev/null +++ b/Tsi1.Api/Tsi1.DataLayer/Migrations/20201101163611_add-File-entity.Designer.cs @@ -0,0 +1,479 @@ +// <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("20201101163611_add-File-entity")] + partial class addFileentity + { + 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.Course", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + 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(100)"); + + 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<int>("CourseId") + .HasColumnType("integer"); + + b.Property<string>("Name") + .IsRequired() + .HasColumnType("character varying(50)"); + + b.HasKey("Id"); + + b.HasIndex("CourseId", "Name") + .IsUnique(); + + 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.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.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.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.Course", b => + { + b.HasOne("Tsi1.DataLayer.Entities.Tenant", "Tenant") + .WithMany("Courses") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Tsi1.DataLayer.Entities.Forum", b => + { + b.HasOne("Tsi1.DataLayer.Entities.Course", "Course") + .WithMany("Forums") + .HasForeignKey("CourseId") + .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.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.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/20201101163611_add-File-entity.cs b/Tsi1.Api/Tsi1.DataLayer/Migrations/20201101163611_add-File-entity.cs new file mode 100644 index 0000000000000000000000000000000000000000..ace7d115279d1651eb0cfa5bc55035751cda87a2 --- /dev/null +++ b/Tsi1.Api/Tsi1.DataLayer/Migrations/20201101163611_add-File-entity.cs @@ -0,0 +1,37 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +namespace Tsi1.DataLayer.Migrations +{ + public partial class addFileentity : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Files", + columns: table => new + { + Id = table.Column<int>(nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + Name = table.Column<string>(type: "character varying(50)", nullable: false), + Path = table.Column<string>(type: "character varying(100)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Files", x => x.Id); + }); + + migrationBuilder.CreateIndex( + name: "IX_Files_Path", + table: "Files", + column: "Path", + unique: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Files"); + } + } +} diff --git a/Tsi1.Api/Tsi1.DataLayer/Migrations/20201101164258_change-File-Path-Property.Designer.cs b/Tsi1.Api/Tsi1.DataLayer/Migrations/20201101164258_change-File-Path-Property.Designer.cs new file mode 100644 index 0000000000000000000000000000000000000000..44695b906c006a5365bb3a1ea89a00e5d0b5425c --- /dev/null +++ b/Tsi1.Api/Tsi1.DataLayer/Migrations/20201101164258_change-File-Path-Property.Designer.cs @@ -0,0 +1,479 @@ +// <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("20201101164258_change-File-Path-Property")] + partial class changeFilePathProperty + { + 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.Course", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + 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<int>("CourseId") + .HasColumnType("integer"); + + b.Property<string>("Name") + .IsRequired() + .HasColumnType("character varying(50)"); + + b.HasKey("Id"); + + b.HasIndex("CourseId", "Name") + .IsUnique(); + + 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.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.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.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.Course", b => + { + b.HasOne("Tsi1.DataLayer.Entities.Tenant", "Tenant") + .WithMany("Courses") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Tsi1.DataLayer.Entities.Forum", b => + { + b.HasOne("Tsi1.DataLayer.Entities.Course", "Course") + .WithMany("Forums") + .HasForeignKey("CourseId") + .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.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.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/20201101164258_change-File-Path-Property.cs b/Tsi1.Api/Tsi1.DataLayer/Migrations/20201101164258_change-File-Path-Property.cs new file mode 100644 index 0000000000000000000000000000000000000000..a348b6798398c41cc70117d6a0f4594c06784eee --- /dev/null +++ b/Tsi1.Api/Tsi1.DataLayer/Migrations/20201101164258_change-File-Path-Property.cs @@ -0,0 +1,29 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Tsi1.DataLayer.Migrations +{ + public partial class changeFilePathProperty : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn<string>( + name: "Path", + table: "Files", + type: "character varying(1000)", + nullable: false, + oldClrType: typeof(string), + oldType: "character varying(100)"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn<string>( + name: "Path", + table: "Files", + type: "character varying(100)", + nullable: false, + oldClrType: typeof(string), + oldType: "character varying(1000)"); + } + } +} diff --git a/Tsi1.Api/Tsi1.DataLayer/Migrations/Tsi1ContextModelSnapshot.cs b/Tsi1.Api/Tsi1.DataLayer/Migrations/Tsi1ContextModelSnapshot.cs index ad82bacaa2af11e43014e891983a77d56bc0255b..9f2b80da778b9cfc5bb392f6faa481cab6f4eea7 100644 --- a/Tsi1.Api/Tsi1.DataLayer/Migrations/Tsi1ContextModelSnapshot.cs +++ b/Tsi1.Api/Tsi1.DataLayer/Migrations/Tsi1ContextModelSnapshot.cs @@ -19,6 +19,24 @@ namespace Tsi1.DataLayer.Migrations .HasAnnotation("ProductVersion", "3.1.4") .HasAnnotation("Relational:MaxIdentifierLength", 63); + 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") @@ -43,6 +61,29 @@ namespace Tsi1.DataLayer.Migrations 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") @@ -80,6 +121,16 @@ namespace Tsi1.DataLayer.Migrations 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") @@ -315,6 +366,13 @@ namespace Tsi1.DataLayer.Migrations b.ToTable("UserTypes"); }); + 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") diff --git a/Tsi1.Api/Tsi1.DataLayer/Tsi1Context.cs b/Tsi1.Api/Tsi1.DataLayer/Tsi1Context.cs index c9fb14f74a1844a4fdc09fb7a9dea866975c2430..d442517b50b82f0ebd6f86812ac69f463cd16239 100644 --- a/Tsi1.Api/Tsi1.DataLayer/Tsi1Context.cs +++ b/Tsi1.Api/Tsi1.DataLayer/Tsi1Context.cs @@ -21,6 +21,9 @@ namespace Tsi1.DataLayer public DbSet<PostMessage> PostMessages { get; set; } public DbSet<Tenant> Tenants { get; set; } public DbSet<ForumUser> ForumUsers { get; set; } + public DbSet<Group> Groups { get; set; } + public DbSet<Connection> Connections { get; set; } + public DbSet<File> Files { get; set; } public Tsi1Context(DbContextOptions options) : base(options) { } @@ -39,6 +42,9 @@ namespace Tsi1.DataLayer modelBuilder.ApplyConfiguration(new PostMessageConfiguration()); modelBuilder.ApplyConfiguration(new TenantConfiguration()); modelBuilder.ApplyConfiguration(new ForumUserConfiguration()); + modelBuilder.ApplyConfiguration(new GroupConfiguration()); + modelBuilder.ApplyConfiguration(new ConnectionConfiguration()); + modelBuilder.ApplyConfiguration(new FileConfiguration()); } } }