├── .gitignore ├── LICENSE ├── README.md ├── Tools └── ServerMySql │ ├── .gitignore │ ├── BuildServerAndRun.ps1 │ ├── Database │ ├── Version01.sql │ └── version02.sql │ └── Dockerfile └── src ├── BackEnd ├── Test │ ├── WebPersonal.BackEnd.IntegrationTest │ │ ├── Api │ │ │ └── PerfilPersonalFlow.cs │ │ └── WebPersonal.BackEnd.IntegrationTest.csproj │ ├── WebPersonal.BackEnd.UnitTest │ │ ├── Service │ │ │ └── PerfilPersonal │ │ │ │ ├── Test_PersonalProfile.cs │ │ │ │ └── Test_PutPersonalProfile.cs │ │ └── WebPersonal.BackEnd.UnitTest.csproj │ └── WebPersonal.Backend.ApiTest │ │ ├── TestAcademicProjectsController.cs │ │ ├── TestPerfilPersonalController.cs │ │ └── WebPersonal.Backend.ApiTest.csproj └── src │ ├── WebPersonal.BackEnd.API │ ├── Controllers │ │ ├── AcademicProjectsController.cs │ │ ├── ContactController.cs │ │ ├── EducationController.cs │ │ ├── ExampleErrorController.cs │ │ ├── HealthController.cs │ │ ├── PerfilPersonalController.cs │ │ ├── PersonalProjectsController.cs │ │ └── WorkExperienceController.cs │ ├── Dockerfile │ ├── Filters │ │ ├── AcceptedLanguageHeader.cs │ │ ├── CustomAttribute.cs │ │ ├── ICustomAttribute.cs │ │ └── OperationFilterContextExtensions.cs │ ├── Middlewares │ │ └── CustomHeaderValidatorMiddleware.cs │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── Samples.http │ ├── Settings │ │ └── DatabaseConnection.cs │ ├── Startup.cs │ ├── WebPersonal.BackEnd.API.csproj │ ├── appsettings.Development.json │ ├── appsettings.Production.json │ └── appsettings.json │ ├── WebPersonal.BackEnd.Data │ ├── Repositories │ │ ├── AcademicProjectRepository.cs │ │ ├── BaseRepository.cs │ │ ├── EducationRespository.cs │ │ ├── InterestsRepository.cs │ │ ├── PersonalProfileRepository.cs │ │ ├── PersonalProjectsRepository.cs │ │ ├── Queries │ │ │ └── TableNames.cs │ │ ├── SkillRepository.cs │ │ ├── UserIdRepository.cs │ │ ├── WorkExpereinceRepository.cs │ │ └── WorkProjectRepository.cs │ └── WebPersonal.BackEnd.Data.csproj │ ├── WebPersonal.BackEnd.Model │ ├── Entity │ │ ├── AcademicProjectEntity.cs │ │ ├── EducationEntity.cs │ │ ├── InterestEntity.cs │ │ ├── PersonalProfileEntity.cs │ │ ├── PersonalProjectEntity.cs │ │ ├── SkillEntity.cs │ │ ├── UserIdEntity.cs │ │ ├── WorkExperienceEntity.cs │ │ └── WorkProjectEntity.cs │ ├── Mappers │ │ └── EducationMapper.cs │ └── WebPersonal.BackEnd.Model.csproj │ ├── WebPersonal.BackEnd.Service │ ├── Extensions │ │ ├── IEnumerableUtils.cs │ │ └── Result_ThenCombine.cs │ ├── Mappers │ │ ├── InterestDtoMapper.cs │ │ ├── PersonalProfileDtoMapper.cs │ │ └── SkillDtoMapper.cs │ ├── PerfilPersonal │ │ ├── PersonalProfile.cs │ │ ├── PostPersonalProfile.cs │ │ └── PutPersonalProfile.cs │ ├── Validations │ │ └── PersonalProfileDtoValidation.cs │ └── WebPersonal.BackEnd.Service.csproj │ ├── WebPersonal.BackEnd.ServiceDependencies │ ├── Services │ │ └── PerfilPersonal │ │ │ ├── GetPersonalProfileDependencies.cs │ │ │ ├── PostPersonalProfileDependencies.cs │ │ │ └── PutPersonalProfileDependencies.cs │ └── WebPersonal.BackEnd.ServiceDependencies.csproj │ ├── WebPersonal.BackEnd.Translations │ ├── TraduccionErrores.cs │ ├── TraduccionErrores.en.resx │ ├── TraduccionErrores.es.resx │ ├── TraduccionErrores.resx │ └── WebPersonal.BackEnd.Translations.csproj │ └── WebPersonal.Backend.EmailService │ ├── EmailSender.cs │ └── WebPersonal.Backend.EmailService.csproj ├── Database ├── Version01.sql └── version02.sql ├── FrontEnd └── WebPersonal.FrontEnd.WebApp │ ├── App.razor │ ├── Componentes │ ├── Contacto.razor │ ├── Contacto.razor.cs │ ├── Educacion.razor │ ├── Educacion.razor.cs │ ├── ExperienciaLaboral.razor │ ├── ExperienciaLaboral.razor.cs │ ├── PerfilPersonal.razor │ └── PerfilPersonal.razor.cs │ ├── Pages │ ├── Counter.razor │ ├── FetchData.razor │ ├── Index.razor │ └── Profile.razor │ ├── Program.cs │ ├── Properties │ └── launchSettings.json │ ├── Shared │ ├── MainLayout.razor │ ├── ModoNocturno.razor │ ├── ModoNormal.razor │ ├── NavMenu.razor │ └── SurveyPrompt.razor │ ├── StateContainer.cs │ ├── WebPersonal.FrontEnd.WebApp.csproj │ ├── _Imports.razor │ └── wwwroot │ ├── css │ ├── app.css │ └── open-iconic │ │ ├── FONT-LICENSE │ │ ├── ICON-LICENSE │ │ ├── README.md │ │ └── font │ │ └── fonts │ │ ├── open-iconic.eot │ │ ├── open-iconic.otf │ │ ├── open-iconic.svg │ │ ├── open-iconic.ttf │ │ └── open-iconic.woff │ ├── favicon.ico │ ├── index.html │ ├── js │ └── script.js │ └── sample-data │ └── weather.json ├── Shared ├── Shared.DTO │ ├── AcademicProjectsDto.cs │ ├── ContactDto.cs │ ├── ContactResponse.cs │ ├── EducationDto.cs │ ├── PersonalProfileDto.cs │ ├── PersonalProjectsDto.cs │ ├── Shared.DTO.csproj │ └── WorkExperienceDto.cs ├── Shared.Data │ ├── Db │ │ ├── ConnectionWrapper.cs │ │ └── TransactionalWrapper.cs │ └── Shared.Data.csproj └── Shared.Language │ ├── CultureScope.cs │ ├── Extensions │ └── AcceptedLanguageExtension.cs │ ├── LocalizationUtils.cs │ └── Shared.Language.csproj └── WebPersonal.sln /.gitignore: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | # This .gitignore file was automatically created by Microsoft(R) Visual Studio. 3 | ################################################################################ 4 | 5 | *.orig 6 | Thumbs.db 7 | *.stackdump 8 | *.obj 9 | *.exe 10 | *.pdb 11 | *.user 12 | *.user.txt 13 | *.[Uu]ser.json 14 | *.aps 15 | *.pch 16 | *.vspscc 17 | *_i.c 18 | *_p.c 19 | *.ncb 20 | *.suo 21 | *.tlb 22 | *.tlh 23 | *.bak 24 | *.cache 25 | *.ilk 26 | *.log 27 | *.css.map 28 | *.min.css 29 | [Bb]in 30 | [Dd]ebug*/ 31 | *.lib 32 | *.sbr 33 | obj/ 34 | [Rr]elease*/ 35 | .vs 36 | .idea -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 ElectNewt 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WebPersonal 2 | 3 | Resultado del curso de la web https://www.netmentor.es/Curso/programacion-web-csharp 4 | 5 | Por ahora seguimos en proceso de la creación. 6 | -------------------------------------------------------------------------------- /Tools/ServerMySql/.gitignore: -------------------------------------------------------------------------------- 1 | Tools/ServerMysql/Database -------------------------------------------------------------------------------- /Tools/ServerMySql/BuildServerAndRun.ps1: -------------------------------------------------------------------------------- 1 | ##Copiar ficheros de la base de datos 2 | $source = "src/Database" 3 | $destino = "Tools/ServerMysql" 4 | 5 | Copy-Item -Path $source -Filter "*.sql" -Recurse -Destination $destino -Container -force 6 | 7 | ##Borrar la imagen vieja 8 | docker rm $(docker stop $(docker ps -a -q --filter ancestor='server-mysql' --format="{{.ID}}")) 9 | 10 | 11 | 12 | ##construir la imagen 13 | docker build -t server-mysql Tools\ServerMysql\. 14 | 15 | ##iniciar el contenedor 16 | docker run -d -p 4306:3306 server-mysql -------------------------------------------------------------------------------- /Tools/ServerMySql/Database/Version01.sql: -------------------------------------------------------------------------------- 1 | 2 | CREATE DATABASE IF NOT EXISTS `webpersonal`; 3 | USE `webpersonal`; 4 | CREATE USER 'webpersonaluser' IDENTIFIED BY 'webpersonalpass'; 5 | GRANT ALL PRIVILEGES ON * . * TO 'webpersonaluser'; 6 | 7 | 8 | 9 | CREATE TABLE IF NOT EXISTS `academicproject` ( 10 | `id` int(11) NOT NULL AUTO_INCREMENT, 11 | `userid` int(11) NOT NULL, 12 | `educationid` int(11) NOT NULL, 13 | `name` varchar(150) NOT NULL, 14 | `details` varchar(5000) NOT NULL, 15 | `environment` varchar(500) NOT NULL, 16 | `date` date DEFAULT NULL, 17 | PRIMARY KEY (`id`) 18 | ); 19 | 20 | CREATE TABLE IF NOT EXISTS `education` ( 21 | `Id` int(11) NOT NULL AUTO_INCREMENT, 22 | `userid` int(11) NOT NULL, 23 | `startdate` date NOT NULL, 24 | `enddate` date DEFAULT NULL, 25 | `coursename` varchar(250) NOT NULL, 26 | `collegename` varchar(250) DEFAULT NULL, 27 | `city` varchar(250) DEFAULT NULL, 28 | `country` varchar(250) DEFAULT NULL, 29 | PRIMARY KEY (`Id`) 30 | ); 31 | 32 | CREATE TABLE IF NOT EXISTS `interest` ( 33 | `id` int(11) NOT NULL AUTO_INCREMENT, 34 | `userid` int(11) NOT NULL, 35 | `description` varchar(250) DEFAULT NULL, 36 | PRIMARY KEY (`id`) 37 | ); 38 | 39 | CREATE TABLE IF NOT EXISTS `personalprofile` ( 40 | `id` int(11) NOT NULL AUTO_INCREMENT, 41 | `userid` int(11) NOT NULL, 42 | `firstname` varchar(50) NOT NULL, 43 | `description` varchar(50) DEFAULT NULL, 44 | `phone` varchar(50) DEFAULT NULL, 45 | `email` varchar(50) DEFAULT NULL, 46 | `lastname` varchar(50) NOT NULL, 47 | `website` varchar(50) DEFAULT NULL, 48 | `github` varchar(50) DEFAULT NULL, 49 | PRIMARY KEY (`id`) 50 | ); 51 | 52 | CREATE TABLE IF NOT EXISTS `personalproject` ( 53 | `id` int(11) NOT NULL AUTO_INCREMENT, 54 | `userid` int(11) NOT NULL, 55 | `name` varchar(150) NOT NULL, 56 | `details` varchar(5000) NOT NULL, 57 | `environment` varchar(500) DEFAULT NULL, 58 | `date` date DEFAULT NULL, 59 | PRIMARY KEY (`id`) 60 | ); 61 | 62 | 63 | CREATE TABLE IF NOT EXISTS `skill` ( 64 | `id` int(11) NOT NULL AUTO_INCREMENT, 65 | `userid` int(11) NOT NULL, 66 | `name` varchar(50) NOT NULL, 67 | `punctuation` decimal(4,2) DEFAULT NULL, 68 | PRIMARY KEY (`id`) 69 | ); 70 | 71 | CREATE TABLE IF NOT EXISTS `userid` ( 72 | `UserId` int(11) NOT NULL AUTO_INCREMENT, 73 | `UserName` varchar(50) NOT NULL, 74 | PRIMARY KEY (`UserId`), 75 | UNIQUE KEY `UserName` (`UserName`) 76 | ); 77 | 78 | CREATE TABLE IF NOT EXISTS `workexperience` ( 79 | `id` int(11) NOT NULL AUTO_INCREMENT, 80 | `userid` int(11) NOT NULL, 81 | `position` varchar(150) NOT NULL, 82 | `companyname` varchar(150) NOT NULL, 83 | `city` varchar(150) NOT NULL, 84 | `country` varchar(150) NOT NULL, 85 | `startdate` date DEFAULT NULL, 86 | `enddate` date DEFAULT NULL, 87 | `environment` varchar(500) DEFAULT NULL, 88 | PRIMARY KEY (`id`) 89 | ); 90 | 91 | CREATE TABLE IF NOT EXISTS `workproject` ( 92 | `id` int(11) NOT NULL AUTO_INCREMENT, 93 | `userid` int(11) NOT NULL, 94 | `workid` int(11) NOT NULL, 95 | `name` varchar(150) NOT NULL, 96 | `details` varchar(5000) NOT NULL, 97 | `date` date DEFAULT NULL, 98 | `environment` varchar(500) DEFAULT NULL, 99 | PRIMARY KEY (`id`) 100 | ); 101 | -------------------------------------------------------------------------------- /Tools/ServerMySql/Database/version02.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE `personalprofile` 2 | CHANGE COLUMN `phone` `phone` VARCHAR(200) NULL DEFAULT NULL AFTER `description`, 3 | CHANGE COLUMN `email` `email` VARCHAR(200) NULL DEFAULT NULL AFTER `phone`; 4 | SELECT `DEFAULT_COLLATION_NAME` FROM `information_schema`.`SCHEMATA` WHERE `SCHEMA_NAME`='webpersonal'; -------------------------------------------------------------------------------- /Tools/ServerMySql/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mysql:5.6 2 | 3 | ENV MYSQL_DATABASE webpersonal 4 | ENV MYSQL_ROOT_PASSWORD=test 5 | 6 | ## todos los scripts en docker-entrypoint-initdb.d/ se ejecutan automaticamente 7 | COPY ./Database/ ./docker-entrypoint-initdb.d/ -------------------------------------------------------------------------------- /src/BackEnd/Test/WebPersonal.BackEnd.IntegrationTest/Api/PerfilPersonalFlow.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.DataProtection; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using MySql.Data.MySqlClient; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Data.Common; 7 | using System.Linq; 8 | using System.Threading.Tasks; 9 | using Microsoft.AspNetCore.Mvc; 10 | using ROP.APIExtensions; 11 | using WebPersonal.BackEnd.API.Controllers; 12 | using WebPersonal.BackEnd.Model.Repositories; 13 | using WebPersonal.BackEnd.Service.PerfilPersonal; 14 | using WebPersonal.BackEnd.ServiceDependencies.Services.PerfilPersonal; 15 | using WebPersonal.Shared.Data.Db; 16 | using WebPersonal.Shared.Dto; 17 | using Xunit; 18 | 19 | namespace WebPersonal.BackEnd.IntegrationTest.Api 20 | { 21 | public class PerfilPersonalFlow 22 | { 23 | 24 | [Fact] 25 | public async Task TestInsertPerfilPersonal_Then_ModifyIt() 26 | { 27 | IServiceCollection services = BuildDependencies(); 28 | using (ServiceProvider serviceProvider = services.BuildServiceProvider()) 29 | { 30 | string username = Guid.NewGuid().ToString(); 31 | 32 | PersonalProfileDto defaultPRofile = BuildPersonalProfile(username); 33 | 34 | var departmentAppService = serviceProvider.GetRequiredService(); 35 | await departmentAppService.Post(defaultPRofile); 36 | 37 | IActionResult resultUserStep = await departmentAppService.Get(username); 38 | var resultUserStep1 = resultUserStep as ResultDto; 39 | Assert.Empty(resultUserStep1.Errors); 40 | PersonalProfileDto userStep1 = resultUserStep1.Value; 41 | Assert.Empty(userStep1.Skills); 42 | Assert.Equal(defaultPRofile.FirstName, userStep1.FirstName); 43 | Assert.Equal(defaultPRofile.Website, userStep1.Website); 44 | Assert.Equal(defaultPRofile.LastName, userStep1.LastName); 45 | 46 | SkillDto skill = new SkillDto() 47 | { 48 | Id = null, 49 | Name = "nombre1", 50 | Punctuation = 10m 51 | }; 52 | userStep1.Skills.Add(skill); 53 | 54 | InterestDto interest = new InterestDto() 55 | { 56 | Id = null, 57 | Interest = "interes pero debe contener 15 caracteres" 58 | }; 59 | userStep1.Interests.Add(interest); 60 | _ =await departmentAppService.Put(userStep1); 61 | var resultUserStep2o = await departmentAppService.Get(username); 62 | ResultDto resultUserStep2 = resultUserStep2o as ResultDto; 63 | Assert.Empty(resultUserStep2.Errors); 64 | PersonalProfileDto userStep2 = resultUserStep1.Value; 65 | Assert.Single(userStep2.Skills); 66 | Assert.Equal(skill.Name, userStep2.Skills.First().Name); 67 | Assert.Single(userStep2.Interests); 68 | Assert.Equal(interest.Interest, userStep2.Interests.First().Interest); 69 | Assert.Equal(defaultPRofile.FirstName, userStep2.FirstName); 70 | Assert.Equal(defaultPRofile.Website, userStep2.Website); 71 | Assert.Equal(defaultPRofile.LastName, userStep2.LastName); 72 | Assert.Equal(defaultPRofile.Email, userStep2.Email); 73 | } 74 | } 75 | 76 | private PersonalProfileDto BuildPersonalProfile(string uniqueUsername) 77 | { 78 | return new PersonalProfileDto() 79 | { 80 | Description = "Description", 81 | Email = "email", 82 | FirstName = "firstName", 83 | GitHub = "github", 84 | Id = null, 85 | Interests = new List(), 86 | LastName = "last name", 87 | Phone = "telefono", 88 | Skills = new List(), 89 | UserId = null, 90 | UserName = uniqueUsername, 91 | Website = "web" 92 | }; 93 | } 94 | 95 | 96 | 97 | private IServiceCollection BuildDependencies() 98 | { 99 | IServiceCollection services = new ServiceCollection(); 100 | services.AddScoped(x 101 | => new MySqlConnection("Server=127.0.0.1;Port=4306;Database=webpersonal;Uid=root;password=test;Allow User Variables=True")) 102 | .AddScoped() 103 | .AddScoped() 104 | .AddScoped() 105 | .AddScoped() 106 | .AddScoped() 107 | .AddScoped() 108 | .AddScoped() 109 | .AddScoped() 110 | .AddScoped() 111 | .AddScoped() 112 | .AddScoped() 113 | .AddScoped() 114 | .AddScoped< IDataProtectionProvider, EphemeralDataProtectionProvider>(); 115 | 116 | return services; 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/BackEnd/Test/WebPersonal.BackEnd.IntegrationTest/WebPersonal.BackEnd.IntegrationTest.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net8.0 5 | WebPersonal.BackEnd.IntegrationTest 6 | true 7 | false 8 | enable 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/BackEnd/Test/WebPersonal.BackEnd.UnitTest/Service/PerfilPersonal/Test_PersonalProfile.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.DataProtection; 2 | using Microsoft.AspNetCore.Http; 3 | using Moq; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | using WebPersonal.BackEnd.Model.Entity; 8 | using WebPersonal.BackEnd.Service.PerfilPersonal; 9 | using Xunit; 10 | 11 | namespace WebPersonal.BackEnd.UnitTest.Service.PerfilPersonal 12 | { 13 | public class Test_PersonalProfile 14 | { 15 | 16 | private class TestState 17 | { 18 | public Mock _dependencies; 19 | public PersonalProfile Subject; 20 | public string Username = "test"; 21 | public int UserId = 123; 22 | public readonly IDataProtector _protector; 23 | public TestState() 24 | { 25 | Mock dependencies = new Mock(); 26 | 27 | Mock httpContextAccessor = new Mock(); 28 | Mock headerDictionary = new Mock(); 29 | httpContextAccessor.Setup(a => a.HttpContext.Request.Headers) 30 | .Returns(headerDictionary.Object); 31 | 32 | IDataProtectionProvider provider = new EphemeralDataProtectionProvider(); 33 | _protector = provider.CreateProtector("PersonalProfile.Protector"); 34 | dependencies.Setup(a => a.GetUserId(Username)) 35 | .Returns(Task.FromResult(UserIdEntity.Create(Username, UserId))); 36 | 37 | dependencies.Setup(a => a.GetPersonalProfile(UserId)) 38 | .Returns(Task.FromResult(GetPersonalProfileEntity(UserId))); 39 | 40 | dependencies.Setup(a => a.GetSkills(UserId)) 41 | .Returns(Task.FromResult(GetSkills(UserId))); 42 | 43 | dependencies.Setup(a => a.GetInterests(UserId)) 44 | .Returns(Task.FromResult(GetInterests(UserId))); 45 | 46 | _dependencies = dependencies; 47 | 48 | Subject = new PersonalProfile(_dependencies.Object, provider, httpContextAccessor.Object); 49 | 50 | } 51 | 52 | private PersonalProfileEntity GetPersonalProfileEntity(int UserId) 53 | { 54 | return PersonalProfileEntity.Create(UserId, 1, 55 | "firstName", 56 | "LastName", 57 | "Descripción", 58 | _protector.Protect("Telefono"), 59 | _protector.Protect("Mail@mail.com"), 60 | "http://www.netmentor.es", 61 | "/ElectNewt"); 62 | } 63 | 64 | private List GetSkills(int UserId) 65 | { 66 | return new List() 67 | { 68 | SkillEntity.Create(UserId, 1, "skill1", 10), 69 | SkillEntity.Create(UserId, 2, "skill2", null) 70 | }; 71 | } 72 | 73 | private List GetInterests(int userId) 74 | { 75 | return new List() 76 | { 77 | InterestEntity.Create(1, userId, "interest1"), 78 | InterestEntity.Create(2, userId, "interest2") 79 | }; 80 | } 81 | 82 | } 83 | 84 | [Fact] 85 | public async Task Test_allCorrect_ThenSuccess() 86 | { 87 | var state = new TestState(); 88 | 89 | var result = await state.Subject.GetPersonalProfileDto(state.Username); 90 | 91 | Assert.True(result.Success); 92 | Assert.Equal("firstName", result.Value.FirstName); 93 | Assert.Equal(2, result.Value.Skills.Count); 94 | Assert.Equal("skill1", result.Value.Skills.First().Name); 95 | Assert.Equal(2, result.Value.Interests.Count); 96 | Assert.Equal("interest1", result.Value.Interests.First().Interest); 97 | 98 | } 99 | 100 | 101 | 102 | [Fact] 103 | public async Task Test_User_NonExistent_ThenError() 104 | { 105 | 106 | var state = new TestState(); 107 | 108 | state._dependencies.Setup(a => a.GetUserId(It.IsAny())) 109 | .Returns(Task.FromResult(null as UserIdEntity)); 110 | 111 | var result = await state.Subject.GetPersonalProfileDto("noExistent"); 112 | 113 | Assert.False(result.Success); 114 | Assert.Single(result.Errors); 115 | Assert.Equal("Usuario no encontrado", result.Errors.First().Message); 116 | } 117 | 118 | 119 | [Fact] 120 | public async Task Test_Profile_NonExistent_ThenError() 121 | { 122 | 123 | var state = new TestState(); 124 | 125 | state._dependencies.Setup(a => a.GetPersonalProfile(It.IsAny())) 126 | .Returns(Task.FromResult(null as PersonalProfileEntity)); 127 | 128 | var result = await state.Subject.GetPersonalProfileDto(state.Username); 129 | 130 | Assert.False(result.Success); 131 | Assert.Single(result.Errors); 132 | Assert.Equal("Perfil personal no encontrado", result.Errors.First().Message); 133 | } 134 | 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /src/BackEnd/Test/WebPersonal.BackEnd.UnitTest/Service/PerfilPersonal/Test_PutPersonalProfile.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.DataProtection; 2 | using Moq; 3 | using System.Collections.Generic; 4 | using System.Threading.Tasks; 5 | using ROP; 6 | using WebPersonal.BackEnd.Model.Entity; 7 | using WebPersonal.BackEnd.Service.Mappers; 8 | using WebPersonal.BackEnd.Service.PerfilPersonal; 9 | using WebPersonal.Shared.Dto; 10 | using Xunit; 11 | 12 | namespace WebPersonal.BackEnd.UnitTest.Service.PerfilPersonal 13 | { 14 | public class Test_PutPersonalProfile 15 | { 16 | public class TestState 17 | { 18 | public Mock _dependencies; 19 | public PutPersonalProfile Subject; 20 | public string Username = "NombreUsuario"; 21 | public int UserId = 123; 22 | public readonly PersonalProfileDto DefaultPersonalProfile; 23 | 24 | public TestState() 25 | { 26 | DefaultPersonalProfile = BuildPersonalProfile(); 27 | IDataProtectionProvider provider = new EphemeralDataProtectionProvider(); 28 | IDataProtector protector = provider.CreateProtector("test"); 29 | 30 | var entities = DefaultPersonalProfile.MapToWraperEntities(protector); 31 | 32 | Mock dependencies = new Mock(); 33 | //TODO: modify the scenario to test as well updates. 34 | dependencies.Setup(a => a.InsertPersonalProfile(It.IsAny())) 35 | .ReturnsAsync(entities.personalProfile); 36 | 37 | dependencies.Setup(a => a.UpdatePersonalProfile(It.IsAny())) 38 | .ReturnsAsync(entities.personalProfile); 39 | 40 | dependencies.Setup(a => a.InsertInterests(It.IsAny>())) 41 | .ReturnsAsync(entities.interestEntities); 42 | 43 | dependencies.Setup(a => a.UpdateInterests(It.IsAny>())) 44 | .ReturnsAsync(entities.interestEntities); 45 | 46 | dependencies.Setup(a => a.InsertSkills(It.IsAny>())) 47 | .ReturnsAsync(entities.skillEntities); 48 | 49 | dependencies.Setup(a => a.UpdateSkills(It.IsAny>())) 50 | .ReturnsAsync(entities.skillEntities); 51 | 52 | dependencies.Setup(a => a.CommitTransaction()) 53 | .Returns(Task.CompletedTask); 54 | 55 | dependencies.Setup(a => a.GetUser(Username)) 56 | .ReturnsAsync(UserIdEntity.Create(Username, UserId)); 57 | 58 | _dependencies = dependencies; 59 | 60 | Subject = new PutPersonalProfile(_dependencies.Object, provider); 61 | } 62 | 63 | private PersonalProfileDto BuildPersonalProfile() 64 | { 65 | return new PersonalProfileDto() 66 | { 67 | Description = "Description", 68 | Email = "email", 69 | FirstName = "firstName", 70 | GitHub = "github", 71 | Id = null, 72 | Interests = new List() 73 | { 74 | new InterestDto() 75 | { 76 | Id = null, 77 | Interest = "interest 1 es un test algo largo" 78 | } 79 | }, 80 | LastName = "last name", 81 | Phone = "telefono", 82 | Skills = new List() 83 | { 84 | new SkillDto() 85 | { 86 | Id = null, 87 | Name = "skill1", 88 | Punctuation = null 89 | } 90 | }, 91 | UserId = this.UserId, 92 | UserName = this.Username, 93 | Website = "web" 94 | }; 95 | } 96 | } 97 | 98 | 99 | [Fact] 100 | public async Task Test_CorrectUserAndSystem_ThenCorrectInformation() 101 | { 102 | var state = new TestState(); 103 | 104 | var result = await state.Subject.Create(state.DefaultPersonalProfile); 105 | 106 | Assert.True(result.Success); 107 | Assert.Equal("firstName", result.Value.FirstName); 108 | Assert.Single(result.Value.Skills); 109 | Assert.Single(result.Value.Interests); 110 | } 111 | } 112 | } -------------------------------------------------------------------------------- /src/BackEnd/Test/WebPersonal.BackEnd.UnitTest/WebPersonal.BackEnd.UnitTest.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | WebPersonal.BackEnd.UnitTest 6 | true 7 | false 8 | enable 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/BackEnd/Test/WebPersonal.Backend.ApiTest/TestAcademicProjectsController.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using System.Net.Http; 3 | using System.Net.Http.Json; 4 | using System.Text.Json; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore.Hosting; 7 | using Microsoft.AspNetCore.TestHost; 8 | using Microsoft.Extensions.Configuration; 9 | using WebPersonal.BackEnd.API; 10 | using WebPersonal.Shared.Dto; 11 | using Xunit; 12 | 13 | namespace WebPersonal.Backend.ApiTest; 14 | 15 | public class TestAcademicProjectsController 16 | { 17 | [Fact] 18 | public async Task WhenCallAPI_withID1_thenResult() 19 | { 20 | IWebHostBuilder webHostBuilder = 21 | new WebHostBuilder() 22 | .ConfigureAppConfiguration(x => x.AddJsonFile("appsettings.tests.json", optional: true)) 23 | .UseEnvironment("production") 24 | .UseStartup(); 25 | 26 | using (TestServer server = new TestServer(webHostBuilder)) 27 | using (HttpClient client = server.CreateClient()) 28 | { 29 | AcademicProjectsDto result = await client.GetFromJsonAsync("/api/AcademicProjects/1", 30 | new JsonSerializerOptions() { PropertyNameCaseInsensitive = true }); 31 | 32 | Assert.Equal(1, result.Projects.First().Id); 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /src/BackEnd/Test/WebPersonal.Backend.ApiTest/TestPerfilPersonalController.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using System.Net.Http; 4 | using System.Text; 5 | using System.Text.Json; 6 | using System.Threading.Tasks; 7 | using Microsoft.AspNetCore.Hosting; 8 | using Microsoft.AspNetCore.TestHost; 9 | using Microsoft.Extensions.DependencyInjection; 10 | using ROP; 11 | using WebPersonal.BackEnd.API; 12 | using WebPersonal.BackEnd.Model.Entity; 13 | using WebPersonal.BackEnd.Service.PerfilPersonal; 14 | using WebPersonal.Shared.Dto; 15 | using Xunit; 16 | 17 | namespace WebPersonal.Backend.ApiTest; 18 | 19 | public class TestPerfilPersonalController 20 | { 21 | [Fact] 22 | public async Task WhenInsertInformation_returnCorrect() 23 | { 24 | IWebHostBuilder webHostBuilder = 25 | new WebHostBuilder() 26 | .ConfigureTestServices(serviceCollection => 27 | { 28 | serviceCollection 29 | .AddScoped(); 30 | }) 31 | .UseStartup(); 32 | 33 | PersonalProfileDto defaultPersonalProfileDto = GetPersonalProfile(); 34 | string serializedProfile = JsonSerializer.Serialize(defaultPersonalProfileDto); 35 | 36 | using (TestServer server = new TestServer(webHostBuilder)) 37 | using (HttpClient client = server.CreateClient()) 38 | { 39 | var result = await client.PostAsync("/api/PerfilPersonal/returnonlyid", 40 | new StringContent(serializedProfile, Encoding.UTF8, "application/json")); 41 | 42 | result.EnsureSuccessStatusCode(); 43 | } 44 | } 45 | 46 | 47 | public PersonalProfileDto GetPersonalProfile() 48 | { 49 | return new PersonalProfileDto() 50 | { 51 | Description = "Description", 52 | Email = "email", 53 | FirstName = "firstName", 54 | GitHub = "github", 55 | Id = null, 56 | Interests = new List() 57 | { 58 | new InterestDto() 59 | { 60 | Id = 1, 61 | Interest = "interest 1 es un test algo largo" 62 | } 63 | }, 64 | LastName = "last name", 65 | Phone = "telefono", 66 | Skills = new List() 67 | { 68 | new SkillDto() 69 | { 70 | Id = 2, 71 | Name = "skill1", 72 | Punctuation = null 73 | } 74 | }, 75 | UserName = "username", 76 | Website = "web" 77 | }; 78 | } 79 | 80 | public class StubIPostPersonalProfileDependencies : IPostPersonalProfileDependencies 81 | { 82 | public Task InsertUserId(string name) 83 | => Task.FromResult(UserIdEntity.Create(name, 1)); 84 | 85 | public Task> InsertPersonalProfile(PersonalProfileEntity personalProfile) 86 | => PersonalProfileEntity.Create(personalProfile.UserId, personalProfile.Id, personalProfile.FirstName, 87 | personalProfile.LastName, personalProfile.Description, personalProfile.Phone, 88 | personalProfile.Email, personalProfile.Website, personalProfile.GitHub).Success().Async(); 89 | 90 | public Task>> InsertSkills(List skills) 91 | => skills.Select(a => SkillEntity.Create(a.UserId, a.Id, a.Name, a.Punctuation)).ToList().Success().Async(); 92 | 93 | public Task>> InsertInterests(List interests) 94 | => interests.Select(a => InterestEntity.Create(a.Id, a.UserId, a.Description)).ToList().Success().Async(); 95 | 96 | public Task> SendEmail(string to, string subject, string body) 97 | => true.Success().Async(); 98 | 99 | public Task CommitTransaction() 100 | => Task.CompletedTask; 101 | } 102 | 103 | 104 | } -------------------------------------------------------------------------------- /src/BackEnd/Test/WebPersonal.Backend.ApiTest/WebPersonal.Backend.ApiTest.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | WebPersonal.BackEnd.ApiTest 6 | true 7 | false 8 | enable 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | runtime; build; native; contentfiles; analyzers; buildtransitive 17 | all 18 | 19 | 20 | runtime; build; native; contentfiles; analyzers; buildtransitive 21 | all 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/BackEnd/src/WebPersonal.BackEnd.API/Controllers/AcademicProjectsController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Threading.Tasks; 5 | using WebPersonal.Shared.Dto; 6 | 7 | namespace WebPersonal.BackEnd.API.Controllers 8 | { 9 | [Route("api/[controller]")] 10 | [ApiController] 11 | public class AcademicProjectsController : ControllerBase 12 | { 13 | [HttpGet("{userId}")] 14 | public Task Get(int userId) 15 | { 16 | if (userId != 1) 17 | throw new NotImplementedException(); 18 | 19 | //TODO: Demo - this is to simulate a real scenario 20 | var academicProjects = new AcademicProjectsDto() 21 | { 22 | Projects = new List() 23 | { 24 | new AcademicProjectDto() 25 | { 26 | Id=1, 27 | Details = "Aplicación para suibr imagenes a internet, con la posiblidad de retocarlas con filtros y redimensionar", 28 | Environment = new List(){"PHP","JavaScript", "Bootstrap"}, 29 | Name = "IMGLovely" 30 | } 31 | } 32 | }; 33 | 34 | return Task.FromResult(academicProjects); 35 | } 36 | 37 | [HttpPost] 38 | public Task Post(AcademicProjectsDto projects) 39 | { 40 | throw new NotImplementedException(); 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /src/BackEnd/src/WebPersonal.BackEnd.API/Controllers/ContactController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using System.Threading.Tasks; 3 | using ROP; 4 | using ROP.APIExtensions; 5 | using WebPersonal.Shared.Dto; 6 | 7 | namespace WebPersonal.BackEnd.API.Controllers 8 | { 9 | [ApiController] 10 | [Route("api/[controller]")] 11 | public class ContactController : Controller 12 | { 13 | [HttpPost] 14 | public Task Post(ContactDto Contacto) 15 | { 16 | return Task.FromResult(new ContactResponse() 17 | { 18 | MessageSent = true 19 | }.Success() 20 | .ToActionResult()); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/BackEnd/src/WebPersonal.BackEnd.API/Controllers/EducationController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Threading.Tasks; 5 | using WebPersonal.Shared.Dto; 6 | 7 | namespace WebPersonal.BackEnd.API.Controllers 8 | { 9 | [ApiController] 10 | [Route("api/[controller]")] 11 | public class EducationController : ControllerBase 12 | { 13 | [HttpGet("{userId}")] 14 | public Task> Get(int userId) 15 | { 16 | if (userId != 1) 17 | throw new NotImplementedException(); 18 | 19 | //TODO: Demo - this is to simulate a real scenario 20 | var educationList = new List() 21 | { 22 | new EducationDto() 23 | { 24 | Id = 2, 25 | CourseName ="Master Curso 2", 26 | EndDate = new DateTime(2019,07,30), 27 | StartDate = new DateTime(2019, 01,23), 28 | UniversityName = "University 1" 29 | }, 30 | new EducationDto() 31 | { 32 | Id = 1, 33 | CourseName ="Curso 1", 34 | EndDate = new DateTime(2019,01,02), 35 | StartDate = new DateTime(2015, 09,14 ), 36 | UniversityName = "University 1" 37 | } 38 | }; 39 | return Task.FromResult(educationList); 40 | } 41 | 42 | [HttpPost] 43 | public Task Post(EducationDto education) 44 | { 45 | throw new NotImplementedException(); 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /src/BackEnd/src/WebPersonal.BackEnd.API/Controllers/ExampleErrorController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.AspNetCore.Mvc; 3 | using ROP; 4 | using ROP.APIExtensions; 5 | using WebPersonal.BackEnd.API.Filters; 6 | 7 | namespace WebPersonal.BackEnd.API.Controllers 8 | { 9 | [ApiController] 10 | [Route("api/[controller]")] 11 | public class ExampleErrorController : ControllerBase 12 | { 13 | [AcceptedLanguageHeader(true)] 14 | [HttpGet] 15 | public IActionResult Get() 16 | { 17 | return Result.Failure(Guid.Parse("ce6887fb-f8fa-49b7-bcb4-d8538b6c9932")) 18 | .ToActionResult(); 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /src/BackEnd/src/WebPersonal.BackEnd.API/Controllers/HealthController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using System; 3 | 4 | namespace WebPersonal.BackEnd.API.Controllers 5 | { 6 | 7 | [ApiController] 8 | [Route("api/[controller]")] 9 | public class HealthController : ControllerBase 10 | { 11 | [HttpGet("ejemplo-docker")] 12 | public string Get() 13 | { 14 | Console.WriteLine("docker"); 15 | return "true"; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/BackEnd/src/WebPersonal.BackEnd.API/Controllers/PerfilPersonalController.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using Microsoft.AspNetCore.Mvc; 4 | using System.Threading.Tasks; 5 | using ROP; 6 | using ROP.APIExtensions; 7 | using WebPersonal.BackEnd.Service.PerfilPersonal; 8 | using WebPersonal.Shared.Dto; 9 | 10 | namespace WebPersonal.BackEnd.API.Controllers 11 | { 12 | [ApiController] 13 | [Route("api/[controller]")] 14 | [Produces("application/json")] 15 | public class PerfilPersonalController : ControllerBase 16 | { 17 | private readonly PersonalProfile _getPersonalProfile; 18 | private readonly PutPersonalProfile _putPersonalProfile; 19 | private readonly PostPersonalProfile _postPersonalProfile; 20 | 21 | public PerfilPersonalController(PersonalProfile getPersonalProfile, PutPersonalProfile putPersonalProfile, 22 | PostPersonalProfile postPersonalProfile) 23 | { 24 | _getPersonalProfile = getPersonalProfile; 25 | _putPersonalProfile = putPersonalProfile; 26 | _postPersonalProfile = postPersonalProfile; 27 | } 28 | 29 | [HttpGet("{userName}")] 30 | public async Task Get(string userName) 31 | { 32 | return await GetProfile(userName).ToActionResult(); 33 | } 34 | 35 | private async Task> GetProfile(string userName) 36 | { 37 | return await _getPersonalProfile.GetPersonalProfileDto(userName); 38 | } 39 | 40 | [HttpPost] 41 | public async Task Post(PersonalProfileDto profileDto) 42 | { 43 | ProblemDetails problemDetails = new ProblemDetails() 44 | { 45 | Title = "Error en la validación de los datos", 46 | Detail = "Los parámetros no son correctos", 47 | Status = 400, 48 | Type = "https://website.net/code-error-1", 49 | }; 50 | 51 | List<(string, string)> errors = new(); 52 | //Validar email 53 | if (!IsValidEmail(profileDto.Email)) 54 | { 55 | errors.Add(("Email", "El email no es válido")); 56 | } 57 | 58 | //validar numero de telefono es un numero 59 | if (!long.TryParse(profileDto.Phone, out _)) 60 | { 61 | errors.Add(("Phone", "El número de teléfono no es válido")); 62 | } 63 | 64 | if (errors.Any()) 65 | { 66 | problemDetails.Extensions.Add("Errors", errors.ToDictionary()); 67 | return new ObjectResult(problemDetails); 68 | } 69 | 70 | return await _postPersonalProfile.Create(profileDto) 71 | .Bind(x => GetProfile(x.UserName)) 72 | .ToActionResult(); 73 | } 74 | 75 | [HttpPost("returnonlyid")] 76 | public async Task> PostId(PersonalProfileDto profileDto) 77 | { 78 | return await _postPersonalProfile.Create(profileDto) 79 | .Map(x => x.UserId); 80 | } 81 | 82 | [HttpPut] 83 | public async Task> Put(PersonalProfileDto profileDto) 84 | { 85 | return await _putPersonalProfile.Create(profileDto) 86 | .Bind(x => GetProfile(x.UserName)); 87 | } 88 | 89 | private bool IsValidEmail(string email) 90 | { 91 | try 92 | { 93 | var addr = new System.Net.Mail.MailAddress(email); 94 | return addr.Address == email; 95 | } 96 | catch 97 | { 98 | return false; 99 | } 100 | } 101 | } 102 | } -------------------------------------------------------------------------------- /src/BackEnd/src/WebPersonal.BackEnd.API/Controllers/PersonalProjectsController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Threading.Tasks; 5 | using WebPersonal.Shared.Dto; 6 | 7 | namespace WebPersonal.BackEnd.API.Controllers 8 | { 9 | [Route("api/[controller]")] 10 | [ApiController] 11 | public class PersonalProjectsController : ControllerBase 12 | { 13 | [HttpGet("{userId}")] 14 | public Task Get(int userId) 15 | { 16 | if (userId != 1) 17 | throw new NotImplementedException(); 18 | 19 | //TODO: Demo - this is to simulate a real scenario 20 | var personalPRojectsDto = new PersonalProjectsDto() 21 | { 22 | PersonalProjects = new List() 23 | { 24 | new PersonalProjectDto() 25 | { 26 | Id = 2, 27 | Description = "WEb para compartir conocimiento sobre programación", 28 | Name = "NetMentor", 29 | ProjectType = "Website", 30 | Environment = new List(){"c#", ".NET", "NetCore","Linux", "Mysql"} 31 | 32 | }, 33 | new PersonalProjectDto() 34 | { 35 | Id = 1, 36 | Description = "Aplicación para parsear Ficheros CSV en objetos C#", 37 | Name = "CSV Parser", 38 | ProjectType = "Library", 39 | Environment = new List(){"c#", ".NET", "Net Standard", "csv"} 40 | 41 | } 42 | } 43 | }; 44 | 45 | 46 | return Task.FromResult(personalPRojectsDto); 47 | 48 | } 49 | 50 | [HttpPost] 51 | public Task Post(PersonalProjectsDto projects) 52 | { 53 | throw new NotImplementedException(); 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /src/BackEnd/src/WebPersonal.BackEnd.API/Controllers/WorkExperienceController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Threading.Tasks; 5 | using WebPersonal.Shared.DTO; 6 | 7 | namespace WebPersonal.BackEnd.API.Controllers 8 | { 9 | [Route("api/[controller]")] 10 | [ApiController] 11 | public class WorkExperienceController : ControllerBase 12 | { 13 | [HttpGet("{userId}")] 14 | public Task Get(int userId) 15 | { 16 | if (userId != 1) 17 | throw new NotImplementedException(); 18 | 19 | //TODO: Demo - this is to simulate a real scenario 20 | var workExperienceDto = new WorkExperienceDto() 21 | { 22 | Positions = new List() 23 | { 24 | new PositionDto() 25 | { 26 | Id = 1 , 27 | City = "Zaragoza", 28 | Country = "Spain", 29 | CompanyName = "Simply Supermercados", 30 | EndDate = new DateTime(2014,06,30), 31 | PositionName = "Front-End Developer", 32 | StartDate = new DateTime(2014,01,05), 33 | Environment = new List(){"PHP", "JavaScript", "ExtJs", "Valence", "CodeIgniter", }, 34 | MainProjects = new List() 35 | { 36 | new WorkProjectDto() 37 | { 38 | Id = 2, 39 | Nombre = "Reportes Tienda", 40 | Description = "CReación de una aplicación utilizando ExtJS con gráficos y colorines que muestra las ventas de una tienda." 41 | }, 42 | new WorkProjectDto() 43 | { 44 | Id = 1, 45 | Nombre = "Calendario de vacaciones", 46 | Description = "Creación de una aplicación en las cuales las tiendas podian asignar vacaciones, tanto de la tienda en si (public holiday) como de los empleados, escirrta en extJs" 47 | } 48 | } 49 | } 50 | } 51 | }; 52 | 53 | 54 | return Task.FromResult(workExperienceDto); 55 | } 56 | 57 | [HttpPost] 58 | public Task Post(WorkExperienceDto workExperience) 59 | { 60 | //Guardar perfil en la base de datos. 61 | throw new NotImplementedException(); 62 | } 63 | } 64 | } -------------------------------------------------------------------------------- /src/BackEnd/src/WebPersonal.BackEnd.API/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mcr.microsoft.com/dotnet/core/aspnet:3.1 2 | 3 | WORKDIR /home/app 4 | COPY bin/Debug/netcoreapp3.1/publish . 5 | 6 | ENTRYPOINT ["dotnet", "WebPersonal.BackEnd.API.dll"] -------------------------------------------------------------------------------- /src/BackEnd/src/WebPersonal.BackEnd.API/Filters/AcceptedLanguageHeader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.OpenApi.Models; 3 | using Swashbuckle.AspNetCore.SwaggerGen; 4 | 5 | namespace WebPersonal.BackEnd.API.Filters 6 | { 7 | [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] 8 | public class AcceptedLanguageHeader : Attribute, ICustomAttribute, IOperationFilter 9 | { 10 | public static string HeaderName = "accept-language"; 11 | 12 | public bool IsMandatory { get; } 13 | 14 | public AcceptedLanguageHeader(bool isMandatory = false) 15 | { 16 | IsMandatory = isMandatory; 17 | } 18 | 19 | public void Apply(OpenApiOperation operation, OperationFilterContext context) 20 | { 21 | CustomAttribute acceptedLanguageHeader = context.RequireAttribute(); 22 | 23 | if (!acceptedLanguageHeader.ContainsAttribute) 24 | return; 25 | 26 | operation.Parameters.Add(new OpenApiParameter() 27 | { 28 | Name = HeaderName, 29 | In = ParameterLocation.Header, 30 | Required = acceptedLanguageHeader.Mandatory, 31 | Schema = new OpenApiSchema() { Type = "string" } 32 | }); 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /src/BackEnd/src/WebPersonal.BackEnd.API/Filters/CustomAttribute.cs: -------------------------------------------------------------------------------- 1 | namespace WebPersonal.BackEnd.API.Filters 2 | { 3 | public class CustomAttribute 4 | { 5 | public readonly bool ContainsAttribute; 6 | public readonly bool Mandatory; 7 | 8 | public CustomAttribute(bool containsAttribute, bool mandatory) 9 | { 10 | ContainsAttribute = containsAttribute; 11 | Mandatory = mandatory; 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /src/BackEnd/src/WebPersonal.BackEnd.API/Filters/ICustomAttribute.cs: -------------------------------------------------------------------------------- 1 | namespace WebPersonal.BackEnd.API.Filters 2 | { 3 | public interface ICustomAttribute 4 | { 5 | public bool IsMandatory { get; } 6 | } 7 | } -------------------------------------------------------------------------------- /src/BackEnd/src/WebPersonal.BackEnd.API/Filters/OperationFilterContextExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Microsoft.AspNetCore.Mvc.Filters; 5 | using Swashbuckle.AspNetCore.SwaggerGen; 6 | 7 | namespace WebPersonal.BackEnd.API.Filters 8 | { 9 | public static class OperationFilterContextExtensions 10 | { 11 | public static CustomAttribute RequireAttribute(this OperationFilterContext context) 12 | where T : ICustomAttribute 13 | { 14 | IEnumerable globalAttributes = context 15 | .ApiDescription 16 | .ActionDescriptor 17 | .FilterDescriptors 18 | .Select(p => p.Filter); 19 | 20 | object[] controllerAttributes = context 21 | .MethodInfo? 22 | .DeclaringType? 23 | .GetCustomAttributes(true) ?? Array.Empty(); 24 | 25 | object[] methodAttributes = context 26 | .MethodInfo? 27 | .GetCustomAttributes(true)?? Array.Empty(); 28 | 29 | List containsHeaderAttributes = globalAttributes 30 | .Union(controllerAttributes) 31 | .Union(methodAttributes) 32 | .OfType() 33 | .ToList(); 34 | 35 | return containsHeaderAttributes.Count == 0 36 | ? new CustomAttribute(false, false) 37 | : new CustomAttribute(true, containsHeaderAttributes.First().IsMandatory); 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /src/BackEnd/src/WebPersonal.BackEnd.API/Middlewares/CustomHeaderValidatorMiddleware.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Threading.Tasks; 4 | using Microsoft.AspNetCore.Http; 5 | using WebPersonal.BackEnd.API.Filters; 6 | 7 | namespace WebPersonal.BackEnd.API.Middlewares 8 | { 9 | public class CustomHeaderValidatorMiddleware 10 | { 11 | private readonly RequestDelegate _next; 12 | private readonly string _headerName; 13 | 14 | 15 | public CustomHeaderValidatorMiddleware(RequestDelegate next, string headerName) 16 | { 17 | _next = next; 18 | _headerName = headerName; 19 | } 20 | 21 | 22 | public async Task Invoke(HttpContext context) 23 | { 24 | if (IsHeaderValidated(context)) 25 | { 26 | await _next.Invoke(context); 27 | } 28 | else 29 | { 30 | throw new Exception($"the header {_headerName} is mandatory and it is missing"); 31 | } 32 | 33 | } 34 | 35 | private bool IsHeaderValidated(HttpContext context) 36 | { 37 | Endpoint? endpoint = context.GetEndpoint(); 38 | if (endpoint == null) 39 | return true; 40 | 41 | bool isRequired = IsHeaderRequired(endpoint); 42 | if (!isRequired) 43 | return true; 44 | 45 | bool isIncluded = IsHeaderIncluded(context); 46 | 47 | if (isRequired && isIncluded) 48 | return true; 49 | 50 | return false; 51 | } 52 | 53 | private bool IsHeaderIncluded(HttpContext context) 54 | => context.Request.Headers.Keys.Select(a=>a.ToLower()).Contains(_headerName.ToLower()); 55 | 56 | private static bool IsHeaderRequired(Endpoint endpoint) 57 | { 58 | var attribute = endpoint.Metadata.GetMetadata(); 59 | 60 | return attribute is { IsMandatory: true }; 61 | } 62 | } 63 | } -------------------------------------------------------------------------------- /src/BackEnd/src/WebPersonal.BackEnd.API/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Hosting; 2 | using Microsoft.Extensions.Hosting; 3 | 4 | namespace WebPersonal.BackEnd.API 5 | { 6 | public class Program 7 | { 8 | public static void Main(string[] args) 9 | { 10 | CreateHostBuilder(args).Build().Run(); 11 | } 12 | 13 | public static IHostBuilder CreateHostBuilder(string[] args) => 14 | Host.CreateDefaultBuilder(args) 15 | .ConfigureWebHostDefaults(webBuilder => 16 | { 17 | webBuilder.UseStartup(); 18 | }); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/BackEnd/src/WebPersonal.BackEnd.API/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:52862", 7 | "sslPort": 44363 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development" 16 | } 17 | }, 18 | "WebPersonal.BackEnd": { 19 | "commandName": "Project", 20 | "launchBrowser": true, 21 | "applicationUrl": "https://localhost:5001;http://localhost:5000", 22 | "environmentVariables": { 23 | "ASPNETCORE_ENVIRONMENT": "Development" 24 | } 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/BackEnd/src/WebPersonal.BackEnd.API/Samples.http: -------------------------------------------------------------------------------- 1 | @endpoint = https://localhost:5001 2 | 3 | ### create new user 4 | POST {{endpoint}}/api/PerfilPersonal 5 | Content-Type: application/json 6 | 7 | { 8 | "UserId": null, 9 | "Id": null, 10 | "UserName": "test-1", 11 | "FirstName": "firstName", 12 | "LastName": "last name", 13 | "Description": "Description", 14 | "Phone": "telefono", 15 | "Email": "email", 16 | "Website": "web", 17 | "GitHub": "github", 18 | "Interests": [], 19 | "Skills": [] 20 | } 21 | > {% client.global.set("userName", response.body.valor.userName) %} 22 | 23 | 24 | ### get user 25 | GET {{endpoint}}/api/PerfilPersonal/{{userName}} 26 | 27 | ### failure test (ProblemDetailsExample) 28 | GET {{endpoint}}/api/Education/test 29 | -------------------------------------------------------------------------------- /src/BackEnd/src/WebPersonal.BackEnd.API/Settings/DatabaseConnection.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | using System.Text; 3 | 4 | namespace WebPersonal.BackEnd.API.Settings 5 | { 6 | public static class Database 7 | { 8 | public static string BuildConnectionString(IConfiguration config) 9 | { 10 | DatabaseSettings dbSettings = new DatabaseSettings(); 11 | config.Bind("database", dbSettings); 12 | StringBuilder sb = new StringBuilder(); 13 | sb.Append($"Server={dbSettings.Server};"); 14 | sb.Append($"Port={dbSettings.Port};"); 15 | sb.Append($"Database={dbSettings.DatabaseName};"); 16 | sb.Append($"Uid={dbSettings.User};"); 17 | sb.Append($"password={dbSettings.Password};"); 18 | 19 | if (dbSettings.AllowUserVariables == true) 20 | { 21 | sb.Append("Allow User Variables=True;"); 22 | } 23 | 24 | return sb.ToString(); 25 | } 26 | 27 | 28 | 29 | public class DatabaseSettings 30 | { 31 | public string Server { get; set; } 32 | public int Port { get; set; } 33 | public string DatabaseName { get; set; } 34 | public string User { get; set; } 35 | public string Password { get; set; } 36 | public bool AllowUserVariables { get; set; } 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/BackEnd/src/WebPersonal.BackEnd.API/Startup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.AspNetCore.Builder; 3 | using Microsoft.AspNetCore.Hosting; 4 | using Microsoft.AspNetCore.Http; 5 | using Microsoft.Extensions.Configuration; 6 | using Microsoft.Extensions.DependencyInjection; 7 | using Microsoft.Extensions.Hosting; 8 | using MySql.Data.MySqlClient; 9 | using System.Data.Common; 10 | using ROP.ApiExtensions.Translations; 11 | using WebPersonal.BackEnd.API.Filters; 12 | using WebPersonal.BackEnd.API.Middlewares; 13 | using WebPersonal.BackEnd.API.Settings; 14 | using WebPersonal.Backend.EmailService; 15 | using WebPersonal.BackEnd.Model.Repositories; 16 | using WebPersonal.BackEnd.Service.PerfilPersonal; 17 | using WebPersonal.BackEnd.ServiceDependencies.Services.PerfilPersonal; 18 | using WebPersonal.BackEnd.Translations; 19 | using WebPersonal.Shared.Data.Db; 20 | 21 | namespace WebPersonal.BackEnd.API 22 | { 23 | public class Startup 24 | { 25 | public IConfiguration Configuration { get; } 26 | 27 | public Startup(IConfiguration configuration) 28 | { 29 | Configuration = configuration; 30 | } 31 | 32 | 33 | // This method gets called by the runtime. Use this method to add services to the container. 34 | // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 35 | public void ConfigureServices(IServiceCollection services) 36 | { 37 | services.AddCors(); 38 | services.AddControllers().AddJsonOptions(options => 39 | { 40 | options.JsonSerializerOptions.AddTranslation(services); 41 | } ); 42 | services.AddDataProtection(); 43 | 44 | //Todo:Move this to their respecives projects 45 | //Temporal connection until explained different environments. 46 | services 47 | .AddSingleton() 48 | .AddScoped(x => new MySqlConnection(Database.BuildConnectionString(Configuration))) 49 | .AddScoped() 50 | .AddScoped() 51 | .AddScoped() 52 | .AddScoped() 53 | .AddScoped() 54 | .AddScoped() 55 | .AddScoped() 56 | .AddScoped() 57 | .AddScoped() 58 | .AddScoped() 59 | .AddScoped() 60 | .AddScoped() 61 | .AddScoped() 62 | .AddScoped() 63 | .AddScoped() 64 | .AddScoped(); 65 | 66 | services.AddSwaggerGen(c => 67 | { 68 | c.OperationFilter(); 69 | }); 70 | services.AddHttpContextAccessor(); 71 | 72 | services.AddSingleton(x => 73 | { 74 | EmailConfiguration emailConfiguration = new EmailConfiguration(); 75 | Configuration.Bind("emailService", emailConfiguration); 76 | return emailConfiguration; 77 | }) 78 | .AddScoped(); 79 | services.Configure(Configuration.GetSection("EmailService")); 80 | services.PostConfigure(emailConfiguration => 81 | { 82 | if ( string.IsNullOrWhiteSpace(emailConfiguration.SmtpServer)) 83 | { 84 | throw new ApplicationException("el campo SmtpServer debe contner información"); 85 | } 86 | }); 87 | services.AddScoped(); 88 | } 89 | 90 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 91 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env) 92 | { 93 | if (env.IsDevelopment()) 94 | { 95 | app.UseDeveloperExceptionPage(); 96 | } 97 | 98 | app.UseSwagger(); 99 | app.UseSwaggerUI(); 100 | 101 | app.UseRouting(); 102 | app.UseMiddleware(AcceptedLanguageHeader.HeaderName); 103 | 104 | app.UseCors(x => x 105 | .AllowAnyMethod() 106 | .AllowAnyHeader() 107 | .SetIsOriginAllowed(origin => true) // allow any origin 108 | .AllowCredentials()); 109 | 110 | app.UseEndpoints(endpoints => 111 | { 112 | endpoints.MapGet("/", async context => { await context.Response.WriteAsync("Hello World!"); }); 113 | endpoints.MapControllers(); 114 | }); 115 | } 116 | } 117 | } -------------------------------------------------------------------------------- /src/BackEnd/src/WebPersonal.BackEnd.API/WebPersonal.BackEnd.API.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net8.0 5 | WebPersonal.BackEnd.API 6 | 9 | enable 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/BackEnd/src/WebPersonal.BackEnd.API/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/BackEnd/src/WebPersonal.BackEnd.API/appsettings.Production.json: -------------------------------------------------------------------------------- 1 | { 2 | "database": { 3 | "Server": "EnProduccion", 4 | "Password": "C0nTr$Se!Dif1c1L" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/BackEnd/src/WebPersonal.BackEnd.API/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | }, 9 | "database": { 10 | "Server": "127.0.0.1", 11 | "Port": "4306", 12 | "DatabaseName": "webpersonal", 13 | "User": "webpersonaluser", 14 | "Password": "webpersonalpass", 15 | "AllowUserVariables": "true" 16 | 17 | }, 18 | "EmailService": { 19 | "SmtpServer" : "server", 20 | "from": "from@email.com" 21 | }, 22 | "AllowedHosts": "*" 23 | } 24 | -------------------------------------------------------------------------------- /src/BackEnd/src/WebPersonal.BackEnd.Data/Repositories/AcademicProjectRepository.cs: -------------------------------------------------------------------------------- 1 | using Dapper; 2 | using System.Data.Common; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using WebPersonal.BackEnd.Model.Entity; 6 | using WebPersonal.BackEnd.Model.Repositories.Queries; 7 | using WebPersonal.Shared.Data.Db; 8 | 9 | namespace WebPersonal.BackEnd.Model.Repositories 10 | { 11 | public class AcademicProjectRepository : BaseRepository 12 | { 13 | public override string TableName => TableNames.AcademicProject; 14 | 15 | public AcademicProjectRepository(TransactionalWrapper conexion) : base(conexion) 16 | { 17 | } 18 | 19 | public override async Task InsertSingle(AcademicProjectEntity obj) 20 | { 21 | string sql = $"insert into {TableName} ({nameof(AcademicProjectEntity.UserId).ToLower()}, {nameof(AcademicProjectEntity.EducationId).ToLower()}, " + 22 | $"{nameof(AcademicProjectEntity.Name).ToLower()}, {nameof(AcademicProjectEntity.Details).ToLower()}, {nameof(AcademicProjectEntity.Environment).ToLower()}," + 23 | $"{nameof(AcademicProjectEntity.Date).ToLower()}" + 24 | $"values (@{nameof(AcademicProjectEntity.UserId)}, @{nameof(AcademicProjectEntity.EducationId)}, @{nameof(AcademicProjectEntity.Name)}," + 25 | $"@{nameof(AcademicProjectEntity.Details)}, @{nameof(AcademicProjectEntity.Environment)}, @{nameof(AcademicProjectEntity.Date)});" + 26 | $"SELECT LAST_INSERT_ID();"; 27 | DbConnection connection = await _conexionWrapper.GetConnectionAsync(); 28 | var newId = (await connection.QueryAsync(sql, new 29 | { 30 | UserId = obj.UserId, 31 | EducationId = obj.EducationId, 32 | Name = obj.Name, 33 | Details = obj.Details, 34 | Environment = obj.Environment, 35 | Date = obj.Date 36 | })).First(); 37 | return AcademicProjectEntity.UpdateId(newId, obj); 38 | } 39 | 40 | public override async Task UpdateSingle(AcademicProjectEntity obj) 41 | { 42 | string sql = $"Update {TableName} " + 43 | $"set {nameof(AcademicProjectEntity.EducationId).ToLower()} = @{nameof(AcademicProjectEntity.EducationId)}, " + 44 | $"{nameof(AcademicProjectEntity.Name).ToLower()} = @{nameof(AcademicProjectEntity.Name)}, " + 45 | $"{nameof(AcademicProjectEntity.Details).ToLower()} = @{nameof(AcademicProjectEntity.Details)}, " + 46 | $"{nameof(AcademicProjectEntity.Environment).ToLower()} = @{nameof(AcademicProjectEntity.Environment)}," + 47 | $"{nameof(AcademicProjectEntity.Date).ToLower()} = @{nameof(AcademicProjectEntity.Date)}" + 48 | $"Where {nameof(AcademicProjectEntity.Id).ToLower()} = @{nameof(PersonalProfileEntity.Id)}"; 49 | 50 | DbConnection connection = await _conexionWrapper.GetConnectionAsync(); 51 | int filasAfectadas = await connection.ExecuteAsync(sql, new 52 | { 53 | EducationId = obj.EducationId, 54 | Name = obj.Name, 55 | Details = obj.Details, 56 | Environment = obj.Environment, 57 | Date = obj.Date, 58 | Id = obj.Id 59 | }); 60 | return obj; 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/BackEnd/src/WebPersonal.BackEnd.Data/Repositories/BaseRepository.cs: -------------------------------------------------------------------------------- 1 | using Dapper; 2 | using System.Collections.Generic; 3 | using System.Data.Common; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using WebPersonal.Shared.Data.Db; 7 | 8 | namespace WebPersonal.BackEnd.Model.Repositories 9 | { 10 | public abstract class BaseRepository 11 | where T : class 12 | { 13 | 14 | protected readonly TransactionalWrapper _conexionWrapper; 15 | 16 | public BaseRepository(TransactionalWrapper conexion) 17 | { 18 | _conexionWrapper = conexion; 19 | } 20 | 21 | public abstract string TableName { get; } 22 | public abstract Task InsertSingle(T obj); 23 | public abstract Task UpdateSingle(T obj); 24 | 25 | public async Task GetByUserId(int userId) 26 | { 27 | DbConnection connection = await _conexionWrapper.GetConnectionAsync(); 28 | return await connection.QueryFirstOrDefaultAsync($"select * from {TableName} where UserId = @userId", new 29 | { 30 | userId = userId 31 | }); 32 | } 33 | 34 | public async Task> GetListByUserId(int userId) 35 | { 36 | DbConnection connection = await _conexionWrapper.GetConnectionAsync(); 37 | return (await connection.QueryAsync($"select * from {TableName} where UserId = @userId", new 38 | { 39 | userId = userId 40 | })).ToList(); 41 | } 42 | 43 | 44 | public async Task> InsertList(List obj) 45 | { 46 | return (await Task.WhenAll(obj.Select(a => InsertSingle(a)))).ToList(); 47 | } 48 | 49 | public async Task> UpdateList(List obj) 50 | { 51 | return (await Task.WhenAll(obj.Select(a => UpdateSingle(a)))).ToList(); 52 | } 53 | 54 | public async Task Delete(int id) 55 | { 56 | string sql = $"delete from {TableName} Where id = @id"; 57 | 58 | DbConnection connection = await _conexionWrapper.GetConnectionAsync(); 59 | await connection.ExecuteAsync(sql, new { id }); 60 | return id; 61 | } 62 | 63 | public async Task DeleteUnused(List ids, int userId) 64 | { 65 | string sql = $"delete from {TableName} Where userid = @userId and id not in (@ids)"; 66 | 67 | DbConnection connection = await _conexionWrapper.GetConnectionAsync(); 68 | await connection.ExecuteAsync(sql, new { 69 | userId = userId, 70 | ids = string.Join(",", ids) 71 | }); 72 | } 73 | 74 | public async Task CommitTransaction() 75 | { 76 | await _conexionWrapper.CommitTransactionAsync(); 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/BackEnd/src/WebPersonal.BackEnd.Data/Repositories/EducationRespository.cs: -------------------------------------------------------------------------------- 1 | using Dapper; 2 | using System.Data.Common; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using WebPersonal.BackEnd.Model.Entity; 6 | using WebPersonal.BackEnd.Model.Repositories.Queries; 7 | using WebPersonal.Shared.Data.Db; 8 | 9 | namespace WebPersonal.BackEnd.Model.Repositories 10 | { 11 | public class EducationRespository : BaseRepository 12 | { 13 | public override string TableName => TableNames.Education; 14 | 15 | public EducationRespository(TransactionalWrapper conexion) : base(conexion) 16 | { 17 | } 18 | 19 | public override async Task InsertSingle(EducationEntity obj) 20 | { 21 | string sql = $"insert into {TableName} ({nameof(EducationEntity.UserId).ToLower()}, {nameof(EducationEntity.StartDate).ToLower()}, " + 22 | $"{nameof(EducationEntity.EndDate).ToLower()}, {nameof(EducationEntity.CourseName)}, {nameof(EducationEntity.CollegeName).ToLower()}," + 23 | $"{nameof(EducationEntity.City).ToLower()}, {nameof(EducationEntity.Country).ToLower()}) " + 24 | $"values (@{nameof(EducationEntity.UserId)}, @{nameof(EducationEntity.StartDate)}, @{nameof(EducationEntity.EndDate)}," + 25 | $"@{nameof(EducationEntity.CourseName)}, @{nameof(EducationEntity.CollegeName)}, @{nameof(EducationEntity.City)}," + 26 | $"@{nameof(EducationEntity.Country)});" + 27 | $"SELECT LAST_INSERT_ID();"; 28 | DbConnection connection = await _conexionWrapper.GetConnectionAsync(); 29 | var newId = (await connection.QueryAsync(sql, new 30 | { 31 | obj.UserId, 32 | obj.StartDate, 33 | obj.EndDate, 34 | obj.CourseName, 35 | obj.CollegeName, 36 | obj.City, 37 | obj.Country 38 | })).First(); 39 | return EducationEntity.UpdateId(newId, obj); 40 | } 41 | 42 | public override async Task UpdateSingle(EducationEntity obj) 43 | { 44 | string sql = $"Update {TableName} " + 45 | $"set {nameof(EducationEntity.StartDate).ToLower()} = @{nameof(EducationEntity.StartDate)}, " + 46 | $"{nameof(EducationEntity.EndDate).ToLower()} = @{nameof(EducationEntity.EndDate)}, " + 47 | $"{nameof(EducationEntity.CourseName).ToLower()} = @{nameof(EducationEntity.CourseName)}, " + 48 | $"{nameof(EducationEntity.CollegeName).ToLower()} = @{nameof(EducationEntity.CollegeName)}," + 49 | $"{nameof(EducationEntity.City).ToLower()} = @{nameof(EducationEntity.City)}, " + 50 | $"{nameof(EducationEntity.Country).ToLower()} = @{nameof(EducationEntity.Country)} " + 51 | $"Where {nameof(EducationEntity.Id).ToLower()} = @{nameof(EducationEntity.Id)}"; 52 | 53 | DbConnection connection = await _conexionWrapper.GetConnectionAsync(); 54 | int filasAfectadas = await connection.ExecuteAsync(sql, new 55 | { 56 | obj.UserId, 57 | obj.StartDate, 58 | obj.EndDate, 59 | obj.CourseName, 60 | obj.CollegeName, 61 | obj.City, 62 | obj.Country, 63 | obj.Id 64 | }); 65 | return obj; 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/BackEnd/src/WebPersonal.BackEnd.Data/Repositories/InterestsRepository.cs: -------------------------------------------------------------------------------- 1 | using Dapper; 2 | using System.Data.Common; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using WebPersonal.BackEnd.Model.Entity; 6 | using WebPersonal.BackEnd.Model.Repositories.Queries; 7 | using WebPersonal.Shared.Data.Db; 8 | 9 | namespace WebPersonal.BackEnd.Model.Repositories 10 | { 11 | public class InterestsRepository : BaseRepository 12 | { 13 | public override string TableName => TableNames.Interest; 14 | 15 | public InterestsRepository(TransactionalWrapper conexion) : base(conexion) 16 | { 17 | } 18 | 19 | public override async Task InsertSingle(InterestEntity obj) 20 | { 21 | string sql = $"insert into {TableName} ({nameof(InterestEntity.UserId).ToLower()}, {nameof(InterestEntity.Description).ToLower()}) " + 22 | $" values (@{nameof(InterestEntity.UserId)}, @{nameof(InterestEntity.Description)});" + 23 | $"SELECT LAST_INSERT_ID();"; 24 | 25 | DbConnection connection = await _conexionWrapper.GetConnectionAsync(); 26 | var newId = (await connection.QueryAsync(sql, new 27 | { 28 | UserId = obj.UserId, 29 | Description = obj.Description 30 | })).First(); 31 | return InterestEntity.UpdateId(obj, newId); 32 | } 33 | 34 | public override async Task UpdateSingle(InterestEntity obj) 35 | { 36 | string sql = $"Update {TableName} " + 37 | $"set {nameof(InterestEntity.Description).ToLower()} = @{nameof(InterestEntity.Description).ToLower()} " + 38 | $"Where {nameof(InterestEntity.Id)} = @{nameof(InterestEntity.Id)}"; 39 | DbConnection connection = await _conexionWrapper.GetConnectionAsync(); 40 | int filasAfectadas = await connection.ExecuteAsync(sql, new 41 | { 42 | Id = obj.Id, 43 | Description = obj.Description 44 | }); 45 | return obj; 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/BackEnd/src/WebPersonal.BackEnd.Data/Repositories/PersonalProfileRepository.cs: -------------------------------------------------------------------------------- 1 | using Dapper; 2 | using System.Data.Common; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using WebPersonal.BackEnd.Model.Entity; 6 | using WebPersonal.BackEnd.Model.Repositories.Queries; 7 | using WebPersonal.Shared.Data.Db; 8 | 9 | namespace WebPersonal.BackEnd.Model.Repositories 10 | { 11 | public class PersonalProfileRepository : BaseRepository 12 | { 13 | public override string TableName => TableNames.PersonalProfile; 14 | 15 | public PersonalProfileRepository(TransactionalWrapper conexion) : base(conexion) 16 | { 17 | } 18 | 19 | 20 | public override async Task InsertSingle(PersonalProfileEntity obj) 21 | { 22 | string sql = $"insert into {TableName} ({nameof(PersonalProfileEntity.UserId).ToLower()}, {nameof(PersonalProfileEntity.FirstName).ToLower()}, " + 23 | $"{nameof(PersonalProfileEntity.LastName).ToLower()}, {nameof(PersonalProfileEntity.Description)}, {nameof(PersonalProfileEntity.Phone).ToLower()}," + 24 | $"{nameof(PersonalProfileEntity.Email).ToLower()}, {nameof(PersonalProfileEntity.Website).ToLower()}, {nameof(PersonalProfileEntity.GitHub).ToLower()}) " + 25 | $"values (@{nameof(PersonalProfileEntity.UserId)}, @{nameof(PersonalProfileEntity.FirstName)}, @{nameof(PersonalProfileEntity.LastName)}," + 26 | $"@{nameof(PersonalProfileEntity.Description)}, @{nameof(PersonalProfileEntity.Phone)}, @{nameof(PersonalProfileEntity.Email)}," + 27 | $"@{nameof(PersonalProfileEntity.Website)}, @{nameof(PersonalProfileEntity.GitHub)} );" + 28 | $"SELECT LAST_INSERT_ID();"; 29 | DbConnection connection = await _conexionWrapper.GetConnectionAsync(); 30 | var newId = (await connection.QueryAsync(sql, new 31 | { 32 | UserId = obj.UserId, 33 | FirstName = obj.FirstName, 34 | LastName = obj.LastName, 35 | Description = obj.Description, 36 | Phone = obj.Phone, 37 | Email = obj.Email, 38 | Website = obj.Website, 39 | GitHub = obj.GitHub 40 | })).First(); 41 | return PersonalProfileEntity.UpdateId(obj, newId); 42 | } 43 | 44 | public override async Task UpdateSingle(PersonalProfileEntity obj) 45 | { 46 | string sql = $"Update {TableName} " + 47 | $"set {nameof(PersonalProfileEntity.FirstName).ToLower()} = @{nameof(PersonalProfileEntity.FirstName)}, " + 48 | $"{nameof(PersonalProfileEntity.LastName).ToLower()} = @{nameof(PersonalProfileEntity.LastName)}, " + 49 | $"{nameof(PersonalProfileEntity.Description).ToLower()} = @{nameof(PersonalProfileEntity.Description)}, " + 50 | $"{nameof(PersonalProfileEntity.Phone).ToLower()} = @{nameof(PersonalProfileEntity.Phone)}," + 51 | $"{nameof(PersonalProfileEntity.Email).ToLower()} = @{nameof(PersonalProfileEntity.Email)}, " + 52 | $"{nameof(PersonalProfileEntity.Website).ToLower()} = @{nameof(PersonalProfileEntity.Website)}, " + 53 | $"{nameof(PersonalProfileEntity.GitHub).ToLower()} = @{nameof(PersonalProfileEntity.GitHub)} " + 54 | $"Where {nameof(PersonalProfileEntity.Id).ToLower()} = @{nameof(PersonalProfileEntity.Id)}"; 55 | 56 | DbConnection connection = await _conexionWrapper.GetConnectionAsync(); 57 | int filasAfectadas = await connection.ExecuteAsync(sql, new 58 | { 59 | FirstName = obj.FirstName, 60 | LastName = obj.LastName, 61 | Description = obj.Description, 62 | Phone = obj.Phone, 63 | Email = obj.Email, 64 | Website = obj.Website, 65 | GitHub = obj.GitHub, 66 | Id = obj.Id 67 | }); 68 | return obj; 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/BackEnd/src/WebPersonal.BackEnd.Data/Repositories/PersonalProjectsRepository.cs: -------------------------------------------------------------------------------- 1 | using Dapper; 2 | using System.Data.Common; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using WebPersonal.BackEnd.Model.Entity; 6 | using WebPersonal.BackEnd.Model.Repositories.Queries; 7 | using WebPersonal.Shared.Data.Db; 8 | 9 | namespace WebPersonal.BackEnd.Model.Repositories 10 | { 11 | public class PersonalProjectsRepository : BaseRepository 12 | { 13 | 14 | public override string TableName => TableNames.PersonalProject; 15 | 16 | public PersonalProjectsRepository(TransactionalWrapper conexion) : base(conexion) 17 | { 18 | } 19 | 20 | public override async Task InsertSingle(PersonalProjectEntity obj) 21 | { 22 | string sql = $"insert into {TableName} ({nameof(PersonalProjectEntity.UserId).ToLower()}, {nameof(PersonalProjectEntity.Name).ToLower()}, " + 23 | $"{nameof(PersonalProjectEntity.Details).ToLower()}, {nameof(PersonalProjectEntity.Environment)}, {nameof(PersonalProjectEntity.Date).ToLower()}) " + 24 | $"values (@{nameof(PersonalProjectEntity.UserId)}, @{nameof(PersonalProjectEntity.Name)}, @{nameof(PersonalProjectEntity.Details)}," + 25 | $"@{nameof(PersonalProjectEntity.Environment)}, @{nameof(PersonalProjectEntity.Date)});" + 26 | $"SELECT LAST_INSERT_ID();"; 27 | DbConnection connection = await _conexionWrapper.GetConnectionAsync(); 28 | var newId = (await connection.QueryAsync(sql, new 29 | { 30 | obj.UserId, 31 | obj.Name, 32 | obj.Details, 33 | obj.Environment, 34 | obj.Date 35 | })).First(); 36 | return PersonalProjectEntity.UpdateId(newId, obj); 37 | } 38 | 39 | public override async Task UpdateSingle(PersonalProjectEntity obj) 40 | { 41 | string sql = $"Update {TableName} " + 42 | $"set {nameof(PersonalProjectEntity.Name).ToLower()} = @{nameof(PersonalProjectEntity.Name)}, " + 43 | $"{nameof(PersonalProjectEntity.Details).ToLower()} = @{nameof(PersonalProjectEntity.Details)}, " + 44 | $"{nameof(PersonalProjectEntity.Environment).ToLower()} = @{nameof(PersonalProjectEntity.Environment)}, " + 45 | $"{nameof(PersonalProjectEntity.Date).ToLower()} = @{nameof(PersonalProjectEntity.Date)} " + 46 | $"Where {nameof(PersonalProjectEntity.Id).ToLower()} = @{nameof(PersonalProjectEntity.Id)}"; 47 | 48 | DbConnection connection = await _conexionWrapper.GetConnectionAsync(); 49 | int filasAfectadas = await connection.ExecuteAsync(sql, new 50 | { 51 | obj.UserId, 52 | obj.Name, 53 | obj.Details, 54 | obj.Environment, 55 | obj.Date, 56 | obj.Id 57 | }); 58 | return obj; 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/BackEnd/src/WebPersonal.BackEnd.Data/Repositories/Queries/TableNames.cs: -------------------------------------------------------------------------------- 1 | namespace WebPersonal.BackEnd.Model.Repositories.Queries 2 | { 3 | internal class TableNames 4 | { 5 | public const string UserId = "userid"; 6 | public const string PersonalProfile = "personalprofile"; 7 | public const string Skill = "skill"; 8 | public const string Interest = "interest"; 9 | public const string AcademicProject = "academicproject"; 10 | public const string PersonalProject = "personalproject"; 11 | public const string WorkProject = "workproject"; 12 | public const string Education = "education"; 13 | public const string WorkExperience = "workexperience"; 14 | 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/BackEnd/src/WebPersonal.BackEnd.Data/Repositories/SkillRepository.cs: -------------------------------------------------------------------------------- 1 | using Dapper; 2 | using System.Data.Common; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using WebPersonal.BackEnd.Model.Entity; 6 | using WebPersonal.BackEnd.Model.Repositories.Queries; 7 | using WebPersonal.Shared.Data.Db; 8 | 9 | namespace WebPersonal.BackEnd.Model.Repositories 10 | { 11 | public class SkillRepository : BaseRepository 12 | { 13 | public override string TableName => TableNames.Skill; 14 | 15 | public SkillRepository(TransactionalWrapper conexion) : base(conexion) 16 | { 17 | } 18 | 19 | public override async Task InsertSingle(SkillEntity obj) 20 | { 21 | string sql = $"insert into {TableName} ({nameof(SkillEntity.UserId).ToLower()}, {nameof(SkillEntity.Name).ToLower()}, " + 22 | $"{nameof(SkillEntity.Punctuation).ToLower()}) " + 23 | $"values (@{nameof(SkillEntity.UserId)}, @{nameof(SkillEntity.Name)}, @{nameof(SkillEntity.Punctuation)});" + 24 | $"SELECT LAST_INSERT_ID();"; 25 | DbConnection connection = await _conexionWrapper.GetConnectionAsync(); 26 | var newId = (await connection.QueryAsync(sql, new 27 | { 28 | Userid = obj.UserId, 29 | Name = obj.Name, 30 | Punctuation = obj.Punctuation 31 | })).First(); 32 | return SkillEntity.UpdateId(obj, newId); 33 | 34 | } 35 | 36 | public override async Task UpdateSingle(SkillEntity obj) 37 | { 38 | string sql = $"Update {TableName} " + 39 | $"set {nameof(SkillEntity.Name).ToLower()} = @{nameof(SkillEntity.Name)}, " + 40 | $"{nameof(SkillEntity.Punctuation).ToLower()} = @{nameof(SkillEntity.Punctuation)} " + 41 | $"Where {nameof(SkillEntity.Id).ToLower()} = @{nameof(SkillEntity.Id)}"; 42 | DbConnection connection = await _conexionWrapper.GetConnectionAsync(); 43 | int filasAfectadas = await connection.ExecuteAsync(sql, new 44 | { 45 | Name = obj.Name, 46 | Punctuation = obj.Punctuation, 47 | Id = obj.Id 48 | }); 49 | return obj; 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/BackEnd/src/WebPersonal.BackEnd.Data/Repositories/UserIdRepository.cs: -------------------------------------------------------------------------------- 1 | using Dapper; 2 | using System.Data.Common; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using WebPersonal.BackEnd.Model.Entity; 6 | using WebPersonal.BackEnd.Model.Repositories.Queries; 7 | using WebPersonal.Shared.Data.Db; 8 | 9 | namespace WebPersonal.BackEnd.Model.Repositories 10 | { 11 | public class UserIdRepository : BaseRepository 12 | { 13 | public override string TableName => TableNames.UserId; 14 | 15 | public UserIdRepository(TransactionalWrapper conexion) : base(conexion) 16 | { 17 | } 18 | 19 | public async Task GetByUserName(string userName) 20 | { 21 | DbConnection connection = await _conexionWrapper.GetConnectionAsync(); 22 | return await connection.QueryFirstOrDefaultAsync($"select * from {TableName} where UserName = @userName", new 23 | { 24 | userName = userName 25 | }); 26 | } 27 | 28 | public override async Task InsertSingle(UserIdEntity obj) 29 | { 30 | string sql = $"insert into {TableName} ({nameof(UserIdEntity.UserName).ToLower()}) " + 31 | $"values (@{nameof(UserIdEntity.UserName)});" + 32 | $"SELECT LAST_INSERT_ID();"; 33 | DbConnection connection = await _conexionWrapper.GetConnectionAsync(); 34 | var newId = (await connection.QueryAsync(sql, new 35 | { 36 | UserName = obj.UserName 37 | })).First(); 38 | return UserIdEntity.UpdateUserId(newId, obj); 39 | } 40 | 41 | public override async Task UpdateSingle(UserIdEntity obj) 42 | { 43 | string sql = $"Update {TableName} " + 44 | $"set {nameof(UserIdEntity.UserName).ToLower()} = @{nameof(UserIdEntity.UserName)} " + 45 | $"Where {nameof(UserIdEntity.UserId).ToLower()} = @{nameof(UserIdEntity.UserId)}"; 46 | DbConnection connection = await _conexionWrapper.GetConnectionAsync(); 47 | int filasAfectadas = await connection.ExecuteAsync(sql, new 48 | { 49 | UserName = obj.UserName, 50 | UserId = obj.UserId 51 | }); 52 | return obj; 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/BackEnd/src/WebPersonal.BackEnd.Data/Repositories/WorkExpereinceRepository.cs: -------------------------------------------------------------------------------- 1 | using Dapper; 2 | using System.Data.Common; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using WebPersonal.BackEnd.Model.Entity; 6 | using WebPersonal.BackEnd.Model.Repositories.Queries; 7 | using WebPersonal.Shared.Data.Db; 8 | 9 | 10 | namespace WebPersonal.BackEnd.Model.Repositories 11 | { 12 | public class WorkExpereinceRepository : BaseRepository 13 | { 14 | 15 | public override string TableName => TableNames.WorkExperience; 16 | 17 | public WorkExpereinceRepository(TransactionalWrapper conexion) : base(conexion) 18 | { 19 | } 20 | 21 | public override async Task InsertSingle(WorkExperienceEntity obj) 22 | { 23 | string sql = $"insert into {TableName} ({nameof(WorkExperienceEntity.UserId).ToLower()}, {nameof(WorkExperienceEntity.Position).ToLower()}, " + 24 | $"{nameof(WorkExperienceEntity.CompanyName).ToLower()}, {nameof(WorkExperienceEntity.City)}, {nameof(WorkExperienceEntity.Country).ToLower()}," + 25 | $"{nameof(WorkExperienceEntity.StartDate).ToLower()}, {nameof(WorkExperienceEntity.EndDate).ToLower()}, {nameof(WorkExperienceEntity.Environment).ToLower()}) " + 26 | $"values (@{nameof(WorkExperienceEntity.UserId)}, @{nameof(WorkExperienceEntity.Position)}, @{nameof(WorkExperienceEntity.CompanyName)}," + 27 | $"@{nameof(WorkExperienceEntity.City)}, @{nameof(WorkExperienceEntity.Country)}, @{nameof(WorkExperienceEntity.StartDate)}," + 28 | $"@{nameof(WorkExperienceEntity.EndDate)}, @{nameof(WorkExperienceEntity.Environment)} );" + 29 | $"SELECT LAST_INSERT_ID();"; 30 | DbConnection connection = await _conexionWrapper.GetConnectionAsync(); 31 | var newId = (await connection.QueryAsync(sql, new 32 | { 33 | obj.UserId, 34 | obj.Position, 35 | obj.CompanyName, 36 | obj.City, 37 | obj.Country, 38 | obj.StartDate, 39 | obj.EndDate, 40 | obj.Environment 41 | })).First(); 42 | return WorkExperienceEntity.UpdateId(newId, obj); 43 | } 44 | 45 | public override async Task UpdateSingle(WorkExperienceEntity obj) 46 | { 47 | string sql = $"Update {TableName} " + 48 | $"set {nameof(WorkExperienceEntity.Position).ToLower()} = @{nameof(WorkExperienceEntity.Position)}, " + 49 | $"{nameof(WorkExperienceEntity.CompanyName).ToLower()} = @{nameof(WorkExperienceEntity.CompanyName)}, " + 50 | $"{nameof(WorkExperienceEntity.City).ToLower()} = @{nameof(WorkExperienceEntity.City)}, " + 51 | $"{nameof(WorkExperienceEntity.Country).ToLower()} = @{nameof(WorkExperienceEntity.Country)}," + 52 | $"{nameof(WorkExperienceEntity.StartDate).ToLower()} = @{nameof(WorkExperienceEntity.StartDate)}, " + 53 | $"{nameof(WorkExperienceEntity.EndDate).ToLower()} = @{nameof(WorkExperienceEntity.EndDate)}, " + 54 | $"{nameof(WorkExperienceEntity.Environment).ToLower()} = @{nameof(WorkExperienceEntity.Environment)} " + 55 | $"Where {nameof(WorkExperienceEntity.Id).ToLower()} = @{nameof(WorkExperienceEntity.Id)}"; 56 | 57 | DbConnection connection = await _conexionWrapper.GetConnectionAsync(); 58 | int filasAfectadas = await connection.ExecuteAsync(sql, new 59 | { 60 | obj.Position, 61 | obj.CompanyName, 62 | obj.City, 63 | obj.Country, 64 | obj.StartDate, 65 | obj.EndDate, 66 | obj.Environment, 67 | obj.Id 68 | }); 69 | return obj; 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/BackEnd/src/WebPersonal.BackEnd.Data/Repositories/WorkProjectRepository.cs: -------------------------------------------------------------------------------- 1 | using Dapper; 2 | using System.Data.Common; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using WebPersonal.BackEnd.Model.Entity; 6 | using WebPersonal.BackEnd.Model.Repositories.Queries; 7 | using WebPersonal.Shared.Data.Db; 8 | 9 | namespace WebPersonal.BackEnd.Model.Repositories 10 | { 11 | public class WorkProjectRepository : BaseRepository 12 | { 13 | 14 | public override string TableName => TableNames.WorkProject; 15 | 16 | public WorkProjectRepository(TransactionalWrapper conexion) : base(conexion) 17 | { 18 | } 19 | 20 | public override async Task InsertSingle(WorkProjectEntity obj) 21 | { 22 | string sql = $"insert into {TableName} ({nameof(WorkProjectEntity.UserId).ToLower()}, {nameof(WorkProjectEntity.WorkId).ToLower()}, " + 23 | $"{nameof(WorkProjectEntity.Name).ToLower()}, {nameof(WorkProjectEntity.Details)}, {nameof(WorkProjectEntity.Date).ToLower()}," + 24 | $"{nameof(WorkProjectEntity.Environment).ToLower()}) " + 25 | $"values (@{nameof(WorkProjectEntity.UserId)}, @{nameof(WorkProjectEntity.WorkId)}, @{nameof(WorkProjectEntity.Name)}," + 26 | $"@{nameof(WorkProjectEntity.Details)}, @{nameof(WorkProjectEntity.Date)}, @{nameof(WorkProjectEntity.Environment)} );" + 27 | $"SELECT LAST_INSERT_ID();"; 28 | DbConnection connection = await _conexionWrapper.GetConnectionAsync(); 29 | var newId = (await connection.QueryAsync(sql, new 30 | { 31 | obj.UserId, 32 | obj.WorkId, 33 | obj.Name, 34 | obj.Details, 35 | obj.Date, 36 | obj.Environment, 37 | })).First(); 38 | return WorkProjectEntity.UpdateId(newId, obj); 39 | } 40 | 41 | public override async Task UpdateSingle(WorkProjectEntity obj) 42 | { 43 | string sql = $"Update {TableName} " + 44 | $"set {nameof(WorkProjectEntity.WorkId).ToLower()} = @{nameof(WorkProjectEntity.WorkId)}, " + 45 | $"{nameof(WorkProjectEntity.Name).ToLower()} = @{nameof(WorkProjectEntity.Name)}, " + 46 | $"{nameof(WorkProjectEntity.Details).ToLower()} = @{nameof(WorkProjectEntity.Details)}, " + 47 | $"{nameof(WorkProjectEntity.Date).ToLower()} = @{nameof(WorkProjectEntity.Date)}," + 48 | $"{nameof(WorkProjectEntity.Environment).ToLower()} = @{nameof(WorkProjectEntity.Environment)} " + 49 | $"Where {nameof(WorkProjectEntity.Id).ToLower()} = @{nameof(WorkProjectEntity.Id)}"; 50 | 51 | DbConnection connection = await _conexionWrapper.GetConnectionAsync(); 52 | int filasAfectadas = await connection.ExecuteAsync(sql, new 53 | { 54 | obj.WorkId, 55 | obj.Name, 56 | obj.Details, 57 | obj.Date, 58 | obj.Environment, 59 | obj.Id 60 | }); 61 | return obj; 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/BackEnd/src/WebPersonal.BackEnd.Data/WebPersonal.BackEnd.Data.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net8.0 5 | WebPersonal.BackEnd.Model 6 | true 7 | enable 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/BackEnd/src/WebPersonal.BackEnd.Model/Entity/AcademicProjectEntity.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace WebPersonal.BackEnd.Model.Entity 5 | { 6 | public class AcademicProjectEntity 7 | { 8 | public readonly int Id; 9 | public readonly int EducationId; 10 | public readonly int UserId; 11 | public readonly string Name; 12 | public readonly string Details; 13 | public readonly List Environment; 14 | public readonly DateTime? Date; 15 | 16 | 17 | private AcademicProjectEntity(int id, int educationId, int userId, string name, string details, List environment, DateTime? date) 18 | { 19 | Id = id; 20 | EducationId = educationId; 21 | Name = name; 22 | Details = details; 23 | Environment = environment; 24 | UserId = userId; 25 | Date = date; 26 | } 27 | 28 | public static AcademicProjectEntity Create(int id, int educationId, int userId, string name, string details, List environment, DateTime? date) 29 | => new AcademicProjectEntity(id, educationId, userId, name, details, environment, date); 30 | 31 | public static AcademicProjectEntity UpdateId(int id, AcademicProjectEntity academicProjectEntity) 32 | => new AcademicProjectEntity(id, academicProjectEntity.EducationId, academicProjectEntity.UserId, academicProjectEntity.Name, academicProjectEntity.Details, 33 | academicProjectEntity.Environment, academicProjectEntity.Date); 34 | 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/BackEnd/src/WebPersonal.BackEnd.Model/Entity/EducationEntity.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace WebPersonal.BackEnd.Model.Entity 4 | { 5 | public class EducationEntity 6 | { 7 | public readonly int Id; 8 | public readonly int UserId; 9 | public readonly DateTime StartDate; 10 | public readonly DateTime? EndDate; 11 | public readonly string CourseName; 12 | public readonly string? CollegeName; 13 | public readonly string? City; 14 | public readonly string? Country; 15 | 16 | private EducationEntity(int id, int userId, DateTime startDate, DateTime? endDate, string courseName, string? collegeName, string? city, string? country) 17 | { 18 | Id = id; 19 | StartDate = startDate; 20 | EndDate = endDate; 21 | CourseName = courseName; 22 | CollegeName = collegeName; 23 | UserId = userId; 24 | City = city; 25 | Country = country; 26 | } 27 | 28 | public static EducationEntity Create(int id, int userId, DateTime startDate, DateTime? endDate, string courseName, string collegeName, string? city, string? country) 29 | => new EducationEntity(id, userId, startDate, endDate, courseName, collegeName, city, country); 30 | public static EducationEntity UpdateId(int id, EducationEntity entity) => 31 | new EducationEntity(id, entity.UserId, entity.StartDate, entity.EndDate, entity.CourseName, entity.CollegeName, entity.City, entity.Country); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/BackEnd/src/WebPersonal.BackEnd.Model/Entity/InterestEntity.cs: -------------------------------------------------------------------------------- 1 | namespace WebPersonal.BackEnd.Model.Entity 2 | { 3 | public class InterestEntity 4 | { 5 | public readonly int? Id; 6 | public readonly int? UserId; 7 | public readonly string Description; 8 | 9 | protected InterestEntity(int? id, int? userid, string description) 10 | { 11 | UserId = userid; 12 | Description = description; 13 | Id = id; 14 | } 15 | 16 | public static InterestEntity Create(int? id, int? userId, string description) => 17 | new InterestEntity(id, userId, description); 18 | 19 | public static InterestEntity UpdateId(InterestEntity entity, int id ) => 20 | new InterestEntity(id, entity.UserId, entity.Description); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/BackEnd/src/WebPersonal.BackEnd.Model/Entity/PersonalProfileEntity.cs: -------------------------------------------------------------------------------- 1 |  2 | namespace WebPersonal.BackEnd.Model.Entity 3 | { 4 | public class PersonalProfileEntity 5 | { 6 | public readonly int UserId; 7 | public readonly int? Id; 8 | public readonly string FirstName; 9 | public readonly string LastName; 10 | public readonly string Description; 11 | public readonly string Phone; 12 | public readonly string Email; 13 | public readonly string Website; 14 | public readonly string GitHub; 15 | 16 | protected PersonalProfileEntity(int? id, int userid, string firstname, string description, string phone, string email, 17 | string lastname, string website, string github) 18 | { 19 | UserId = userid; 20 | Id = id; 21 | FirstName = firstname; 22 | LastName = lastname; 23 | Description = description; 24 | Phone = phone; 25 | Email = email; 26 | Website = website; 27 | GitHub = github; 28 | } 29 | 30 | public static PersonalProfileEntity Create(int userId, int? id, string firstName, string lastName, string description, 31 | string phone, string email, string website, string gitHub) 32 | => new PersonalProfileEntity(id, userId, firstName, description, phone, email, lastName, website, gitHub); 33 | 34 | public static PersonalProfileEntity UpdateId(PersonalProfileEntity perfilPersonal, int id)=> 35 | new PersonalProfileEntity(id, perfilPersonal.UserId, perfilPersonal.FirstName, perfilPersonal.Description, perfilPersonal.Phone, 36 | perfilPersonal.Email, perfilPersonal.LastName, perfilPersonal.Website, perfilPersonal.GitHub); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/BackEnd/src/WebPersonal.BackEnd.Model/Entity/PersonalProjectEntity.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace WebPersonal.BackEnd.Model.Entity 4 | { 5 | public class PersonalProjectEntity 6 | { 7 | public readonly int Id; 8 | public readonly int UserId; 9 | public readonly string Name; 10 | public readonly string Details; 11 | public readonly string? Environment; 12 | public readonly DateTime? Date; 13 | //Todo: group them by category? 14 | 15 | 16 | private PersonalProjectEntity(int id, int userId, string name, string details, string? environment, DateTime? date) 17 | { 18 | Id = id; 19 | Name = name; 20 | Details = details; 21 | Environment = environment; 22 | UserId = userId; 23 | Date = date; 24 | } 25 | 26 | public static PersonalProjectEntity Create(int id, int userId, string name, string details, string? environment, DateTime? date) 27 | => new PersonalProjectEntity(id, userId, name, details, environment, date); 28 | 29 | public static PersonalProjectEntity UpdateId(int id, PersonalProjectEntity entity) => 30 | new PersonalProjectEntity(id, entity.UserId, entity.Name, entity.Details, entity.Environment, entity.Date); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/BackEnd/src/WebPersonal.BackEnd.Model/Entity/SkillEntity.cs: -------------------------------------------------------------------------------- 1 | namespace WebPersonal.BackEnd.Model.Entity 2 | { 3 | public class SkillEntity 4 | { 5 | public readonly int? UserId; 6 | public readonly int? Id; 7 | public readonly string Name; 8 | public readonly decimal? Punctuation; 9 | 10 | protected SkillEntity(int? id, int? userid, string name, decimal? punctuation) 11 | { 12 | UserId = userid; 13 | Id = id; 14 | Name = name; 15 | Punctuation = punctuation; 16 | } 17 | 18 | public static SkillEntity Create(int? userId, int? id, string name, decimal? punctuation) 19 | => new SkillEntity(id, userId, name, punctuation); 20 | 21 | public static SkillEntity UpdateId(SkillEntity skill, int id) => 22 | new SkillEntity(id, skill.UserId, skill.Name, skill.Punctuation); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/BackEnd/src/WebPersonal.BackEnd.Model/Entity/UserIdEntity.cs: -------------------------------------------------------------------------------- 1 | namespace WebPersonal.BackEnd.Model.Entity 2 | { 3 | public class UserIdEntity 4 | { 5 | public readonly string UserName; 6 | public readonly int? UserId; 7 | 8 | protected UserIdEntity(int? userId, string userName) 9 | { 10 | UserName = userName; 11 | UserId = userId; 12 | } 13 | 14 | public static UserIdEntity Create(string userName, int? userId) => 15 | new UserIdEntity(userId, userName); 16 | 17 | public static UserIdEntity UpdateUserId(int userId, UserIdEntity userIdEntity) => 18 | new UserIdEntity(userId, userIdEntity.UserName); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/BackEnd/src/WebPersonal.BackEnd.Model/Entity/WorkExperienceEntity.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace WebPersonal.BackEnd.Model.Entity 4 | { 5 | public class WorkExperienceEntity 6 | { 7 | 8 | public readonly int Id; 9 | public readonly int UserId; 10 | public readonly string Position; 11 | public readonly string CompanyName; 12 | public readonly string City; 13 | public readonly string Country; 14 | public readonly DateTime? StartDate; 15 | public readonly DateTime? EndDate; 16 | public readonly string? Environment; 17 | 18 | public WorkExperienceEntity(int id, int userId, string position, string companyName, string city, string country, 19 | DateTime? startDate, DateTime? endDate, string? environment) 20 | { 21 | Id = id; 22 | UserId = userId; 23 | Position = position; 24 | CompanyName = companyName; 25 | City = city; 26 | Country = country; 27 | StartDate = startDate; 28 | EndDate = endDate; 29 | Environment = environment; 30 | } 31 | 32 | public static WorkExperienceEntity Create(int id, int userId, string position, string companyName, string city, string country, 33 | DateTime? startDate, DateTime? endDate, string? environment) => 34 | new WorkExperienceEntity(id, userId, position, companyName, city, country, startDate, endDate, environment); 35 | 36 | public static WorkExperienceEntity UpdateId(int id, WorkExperienceEntity entity) => 37 | new WorkExperienceEntity(id, entity.UserId, entity.Position, entity.CompanyName, entity.City, entity.Country, entity.StartDate, entity.EndDate, entity.Environment); 38 | 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/BackEnd/src/WebPersonal.BackEnd.Model/Entity/WorkProjectEntity.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace WebPersonal.BackEnd.Model.Entity 4 | { 5 | public class WorkProjectEntity 6 | { 7 | public readonly int Id; 8 | public readonly int WorkId; 9 | public readonly int UserId; 10 | public readonly string Name; 11 | public readonly string Details; 12 | public readonly string? Environment; 13 | public readonly DateTime? Date; 14 | 15 | 16 | private WorkProjectEntity(int id, int workId, int userId, string name, string details, string? environment, DateTime? date) 17 | { 18 | Id = id; 19 | WorkId = workId; 20 | Name = name; 21 | Details = details; 22 | Environment = environment; 23 | UserId = userId; 24 | Date = date; 25 | } 26 | 27 | public static WorkProjectEntity Create(int id, int workId, int userId, string name, string details, string? environment, DateTime? date) => 28 | new WorkProjectEntity(id, workId, userId, name, details, environment, date); 29 | 30 | public static WorkProjectEntity UpdateId(int id, WorkProjectEntity entity) => 31 | new WorkProjectEntity(id, entity.WorkId, entity.UserId, entity.Name, entity.Details, entity.Environment, entity.Date); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/BackEnd/src/WebPersonal.BackEnd.Model/Mappers/EducationMapper.cs: -------------------------------------------------------------------------------- 1 | using WebPersonal.BackEnd.Model.Entity; 2 | using WebPersonal.Shared.Dto; 3 | 4 | namespace WebPersonal.BackEnd.Model.Mappers 5 | { 6 | public static class EducationMapper 7 | { 8 | public static EducationDto Map(this EducationEntity entity) 9 | { 10 | return new EducationDto() 11 | { 12 | Id = entity.Id, 13 | CourseName = entity.CourseName, 14 | EndDate = entity.EndDate, 15 | StartDate = entity.StartDate, 16 | UniversityName = entity.CollegeName 17 | }; 18 | } 19 | 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/BackEnd/src/WebPersonal.BackEnd.Model/WebPersonal.BackEnd.Model.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net8.0 5 | WebPersonal.BackEnd.Model 6 | true 7 | enable 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/BackEnd/src/WebPersonal.BackEnd.Service/Extensions/IEnumerableUtils.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | 4 | namespace WebPersonal.BackEnd.Service.Extensions; 5 | 6 | 7 | /// 8 | /// This should not be here but i don't want to create a new project for this 9 | /// 10 | public static class IEnumerableUtils 11 | { 12 | public static string JoinStrings(this IEnumerable strings, string separator) 13 | { 14 | return string.Join(separator, strings); 15 | } 16 | 17 | public static List ListOrEmpty(this IEnumerable list) 18 | { 19 | return list.Any() ? list.ToList() : new List(); 20 | } 21 | } -------------------------------------------------------------------------------- /src/BackEnd/src/WebPersonal.BackEnd.Service/Extensions/Result_ThenCombine.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Immutable; 3 | using System.Linq; 4 | using System.Runtime.ExceptionServices; 5 | using System.Threading.Tasks; 6 | using ROP; 7 | 8 | namespace WebPersonal.BackEnd.Service.Extensions; 9 | 10 | public static class Result_ThenCombine 11 | { 12 | public class Combiner 13 | { 14 | public readonly T Identifier; 15 | public readonly TR Value; 16 | 17 | public Combiner(T identifier, TR value) 18 | { 19 | Identifier = identifier; 20 | Value = value; 21 | } 22 | } 23 | 24 | 25 | public static async Task> GetCombined(this Task>> result) 26 | { 27 | Result> t = await result; 28 | return !t.Success ? t.Errors : t.Value.Value.Success(); 29 | } 30 | 31 | 32 | 33 | public static async Task>> ThenCombine(this Task> result, Func>> method) 34 | { 35 | try 36 | { 37 | Result t = await result; 38 | if (!t.Success) 39 | return t.Errors; 40 | 41 | Result r2 = await method(t.Value); 42 | 43 | 44 | if (t.Success && r2.Success) 45 | return new Combiner(t.Value, r2.Value).Success(); 46 | 47 | if (!r2.Success) 48 | return r2.Errors; 49 | 50 | return Result.Failure>(t.Errors.Concat(r2.Errors).ToImmutableArray()); 51 | } 52 | catch (Exception e) 53 | { 54 | ExceptionDispatchInfo.Capture(e).Throw(); 55 | throw; 56 | } 57 | } 58 | 59 | public static async Task>> ThenCombine(this Task>> result, Func>> method) 60 | { 61 | try 62 | { 63 | Result> t = await result; 64 | if (!t.Success) 65 | return t.Errors; 66 | 67 | Result r2 = await method(t.Value.Identifier); 68 | 69 | 70 | if (t.Success && r2.Success) 71 | return new Combiner(t.Value.Identifier, (t.Value.Value, r2.Value)).Success(); 72 | 73 | if (!r2.Success) 74 | return r2.Errors; 75 | 76 | return Result.Failure>(t.Errors.Concat(r2.Errors).ToImmutableArray()); 77 | } 78 | catch (Exception e) 79 | { 80 | ExceptionDispatchInfo.Capture(e).Throw(); 81 | throw; 82 | } 83 | } 84 | 85 | public static async Task>> ThenCombine(this Task>> result, Func>> method) 86 | { 87 | try 88 | { 89 | Result> t = await result; 90 | if (!t.Success) 91 | return t.Errors; 92 | 93 | Result r3 = await method(t.Value.Identifier); 94 | 95 | 96 | if (t.Success && r3.Success) 97 | return new Combiner(t.Value.Identifier, (t.Value.Value.Item1, t.Value.Value.Item2, r3.Value)).Success(); 98 | 99 | if (!r3.Success) 100 | return r3.Errors; 101 | 102 | return Result.Failure>(t.Errors.Concat(r3.Errors).ToImmutableArray()); 103 | } 104 | catch (Exception e) 105 | { 106 | ExceptionDispatchInfo.Capture(e).Throw(); 107 | throw; 108 | } 109 | } 110 | 111 | public static async Task>> ThenCombine(this Task>> result, Func>> method) 112 | { 113 | try 114 | { 115 | Result> t = await result; 116 | if (!t.Success) 117 | return t.Errors; 118 | 119 | Result r4 = await method(t.Value.Identifier); 120 | 121 | 122 | if (t.Success && r4.Success) 123 | return new Combiner(t.Value.Identifier, (t.Value.Value.Item1, t.Value.Value.Item2, t.Value.Value.Item3, r4.Value)).Success(); 124 | 125 | if (!r4.Success) 126 | return r4.Errors; 127 | 128 | return Result.Failure>(t.Errors.Concat(r4.Errors).ToImmutableArray()); 129 | } 130 | catch (Exception e) 131 | { 132 | ExceptionDispatchInfo.Capture(e).Throw(); 133 | throw; 134 | } 135 | } 136 | } -------------------------------------------------------------------------------- /src/BackEnd/src/WebPersonal.BackEnd.Service/Mappers/InterestDtoMapper.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using WebPersonal.BackEnd.Model.Entity; 4 | using WebPersonal.Shared.Dto; 5 | 6 | namespace WebPersonal.BackEnd.Service.Mappers 7 | { 8 | public static class InterestDtoMapper 9 | { 10 | public static List Map(this List interests, int userId) 11 | { 12 | return interests.Select(a 13 | => InterestEntity.Create(a.Id, userId, a.Interest)).ToList(); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/BackEnd/src/WebPersonal.BackEnd.Service/Mappers/PersonalProfileDtoMapper.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.DataProtection; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using WebPersonal.BackEnd.Model.Entity; 6 | using WebPersonal.Shared.Dto; 7 | 8 | namespace WebPersonal.BackEnd.Service.Mappers 9 | { 10 | public static class PersonalProfileDtoMapper 11 | { 12 | public static PostPersonalProfileWrapper MapToWraperEntities(this PersonalProfileDto profileDto, IDataProtector protector) 13 | { 14 | if(profileDto.UserId == null) 15 | { 16 | throw new Exception("Your are trying to build an entity with a null user, that cannot be done"); 17 | } 18 | 19 | string encriptedPhone = protector.Protect(profileDto.Phone); 20 | string encriptedEmail = protector.Protect(profileDto.Email); 21 | 22 | PersonalProfileEntity personalProfile = PersonalProfileEntity.Create((int)profileDto.UserId, profileDto.Id, profileDto.FirstName, profileDto.LastName, profileDto.Description, 23 | encriptedPhone, encriptedEmail, profileDto.Website, profileDto.GitHub); 24 | 25 | List skills = profileDto.Skills.Select(a => SkillEntity.Create(profileDto.UserId, a.Id, a.Name, a.Punctuation)).ToList(); 26 | 27 | List interests = profileDto.Interests.Select(a => InterestEntity.Create(a.Id, profileDto.UserId, a.Interest)).ToList(); 28 | 29 | return new PostPersonalProfileWrapper(personalProfile, skills, interests); 30 | } 31 | 32 | public static PersonalProfileEntity Map(this PersonalProfileDto profileDto, int userId, IDataProtector protector) 33 | { 34 | string encriptedPhone = protector.Protect(profileDto.Phone); 35 | string encriptedEmail = protector.Protect(profileDto.Email); 36 | 37 | return PersonalProfileEntity.Create(userId, profileDto.Id, profileDto.FirstName, profileDto.LastName, profileDto.Description, 38 | encriptedPhone, encriptedEmail, profileDto.Website, profileDto.GitHub); 39 | } 40 | 41 | } 42 | public class PostPersonalProfileWrapper 43 | { 44 | public readonly PersonalProfileEntity personalProfile; 45 | public readonly List skillEntities; 46 | public readonly List interestEntities; 47 | 48 | public PostPersonalProfileWrapper(PersonalProfileEntity personalProfile, List skillEntities, List interestEntities) 49 | { 50 | this.personalProfile = personalProfile; 51 | this.skillEntities = skillEntities; 52 | this.interestEntities = interestEntities; 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/BackEnd/src/WebPersonal.BackEnd.Service/Mappers/SkillDtoMapper.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using WebPersonal.BackEnd.Model.Entity; 4 | using WebPersonal.Shared.Dto; 5 | 6 | namespace WebPersonal.BackEnd.Service.Mappers 7 | { 8 | public static class SkillDtoMapper 9 | { 10 | public static List Map(this List skills, int userId) 11 | { 12 | return skills.Select(a => SkillEntity.Create(userId, a.Id, a.Name, a.Punctuation)).ToList(); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/BackEnd/src/WebPersonal.BackEnd.Service/PerfilPersonal/PersonalProfile.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.DataProtection; 2 | using Microsoft.AspNetCore.Http; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | using ROP; 8 | using WebPersonal.BackEnd.Model.Entity; 9 | using WebPersonal.BackEnd.Service.Extensions; 10 | using WebPersonal.BackEnd.Translations; 11 | using WebPersonal.Shared.Dto; 12 | using WebPersonal.Shared.Language.Extensions; 13 | 14 | namespace WebPersonal.BackEnd.Service.PerfilPersonal 15 | { 16 | public interface IGetPersonalProfileDependencies 17 | { 18 | Task GetUserId(string name); 19 | Task> GetInterests(int userId); 20 | Task> GetSkills(int iduserId); 21 | Task GetPersonalProfile(int userId); 22 | } 23 | 24 | public class PersonalProfile 25 | { 26 | private readonly IGetPersonalProfileDependencies _dependencies; 27 | private readonly IDataProtector _protector; 28 | private readonly TraduccionErrores _traduccionErrores; 29 | 30 | public PersonalProfile(IGetPersonalProfileDependencies dependencies, IDataProtectionProvider provider, 31 | IHttpContextAccessor httpcontextAccessor) 32 | { 33 | _dependencies = dependencies; 34 | _protector = provider.CreateProtector("PersonalProfile.Protector"); 35 | _traduccionErrores = new TraduccionErrores(httpcontextAccessor.HttpContext.Request.Headers.GetCultureInfo()); 36 | } 37 | 38 | 39 | public async Task> GetPersonalProfileDto(string name) 40 | { 41 | 42 | Result userId = await GetUserId(name); 43 | 44 | return await userId.Async() 45 | .ThenCombine(GetPersonalProfileInfo) 46 | .ThenCombine(GetSkills) 47 | .ThenCombine(GetInterests) 48 | .GetCombined() 49 | .Map(x => Map(x, userId.Value)); 50 | } 51 | private async Task> GetUserId(string name) 52 | { 53 | var userIdentty = await _dependencies.GetUserId(name); 54 | return userIdentty == null || userIdentty.UserId == null? 55 | Result.Failure(Error.Create(_traduccionErrores.IdentityNotFound)) 56 | : userIdentty; 57 | } 58 | 59 | 60 | private async Task>> GetInterests(UserIdEntity user) => 61 | await _dependencies.GetInterests(Convert.ToInt32(user.UserId)); 62 | 63 | private async Task>> GetSkills(UserIdEntity user) => 64 | await _dependencies.GetSkills(Convert.ToInt32(user.UserId)); 65 | 66 | private async Task> GetPersonalProfileInfo(UserIdEntity user) 67 | { 68 | var personalProfile = await _dependencies.GetPersonalProfile(Convert.ToInt32(user.UserId)); 69 | 70 | return personalProfile == null ? 71 | Result.Failure(Error.Create(_traduccionErrores.PersonalProfile)) 72 | : personalProfile; 73 | } 74 | 75 | private Task Map((PersonalProfileEntity personalProfile, List skills, 76 | List interests) values, UserIdEntity userId) 77 | { 78 | 79 | PersonalProfileDto profile = new PersonalProfileDto() 80 | { 81 | Description = values.personalProfile.Description, 82 | Email = _protector.Unprotect(values.personalProfile.Email), 83 | FirstName = values.personalProfile.FirstName, 84 | LastName = values.personalProfile.LastName, 85 | GitHub = values.personalProfile.GitHub, 86 | UserId = userId.UserId, 87 | UserName = userId.UserName, 88 | Phone = _protector.Unprotect(values.personalProfile.Phone), 89 | Website = values.personalProfile.Website, 90 | Id = values.personalProfile.Id, 91 | Interests = values.interests.Select(a => new InterestDto() 92 | { 93 | Id = a.Id, 94 | Interest = a.Description 95 | }).ToList(), 96 | Skills = values.skills.Select(a => new SkillDto() 97 | { 98 | Id = a.Id, 99 | Name = a.Name, 100 | Punctuation = a.Punctuation 101 | }).ToList() 102 | }; 103 | return Task.FromResult(profile); 104 | } 105 | 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/BackEnd/src/WebPersonal.BackEnd.Service/PerfilPersonal/PostPersonalProfile.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.DataProtection; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Threading.Tasks; 5 | using ROP; 6 | using WebPersonal.BackEnd.Model.Entity; 7 | using WebPersonal.BackEnd.Service.Mappers; 8 | using WebPersonal.Shared.Dto; 9 | 10 | namespace WebPersonal.BackEnd.Service.PerfilPersonal 11 | { 12 | public interface IPostPersonalProfileDependencies 13 | { 14 | Task InsertUserId(string name); 15 | Task> InsertPersonalProfile(PersonalProfileEntity personalProfile); 16 | Task>> InsertSkills(List skills); 17 | Task>> InsertInterests(List interests); 18 | Task> SendEmail(string to, string subject, string body); 19 | Task CommitTransaction(); 20 | } 21 | 22 | public class PostPersonalProfile 23 | { 24 | private readonly IPostPersonalProfileDependencies _dependencies; 25 | private readonly IDataProtector _protector; 26 | 27 | public PostPersonalProfile(IPostPersonalProfileDependencies dependencies, IDataProtectionProvider provider) 28 | { 29 | _dependencies = dependencies; 30 | _protector = provider.CreateProtector("PersonalProfile.Protector"); 31 | } 32 | 33 | public async Task> Create(PersonalProfileDto personalProfile) 34 | { 35 | 36 | return await CreateNewUser(personalProfile) 37 | .Bind(x => SavePersonalProfile(x, personalProfile)) 38 | .Bind(x => SaveSkills(x, personalProfile)) 39 | .Bind(x => SaveInterests(x, personalProfile)) 40 | .Bind(CommitTransaction) 41 | .Then(_=>SendConfirmationEmail(personalProfile)); 42 | } 43 | 44 | private async Task> CreateNewUser(PersonalProfileDto personalProfile) 45 | { 46 | return await _dependencies.InsertUserId(personalProfile.UserName); 47 | } 48 | 49 | private async Task> SavePersonalProfile(UserIdEntity user, PersonalProfileDto personalProfile) 50 | { 51 | return await _dependencies.InsertPersonalProfile(personalProfile.Map(Convert.ToInt32(user.UserId), _protector)) 52 | .Map(_ => user); 53 | } 54 | 55 | private async Task> SaveSkills(UserIdEntity user, PersonalProfileDto personalProfile) 56 | { 57 | return await _dependencies.InsertSkills(personalProfile.Skills.Map(Convert.ToInt32(user.UserId))) 58 | .Map(_ => user); 59 | } 60 | 61 | private async Task> SaveInterests(UserIdEntity user, PersonalProfileDto personalProfile) 62 | { 63 | return await _dependencies.InsertInterests(personalProfile.Interests.Map(Convert.ToInt32(user.UserId))) 64 | .Map(_ => user); 65 | } 66 | private async Task> CommitTransaction(UserIdEntity user) 67 | { 68 | await _dependencies.CommitTransaction(); 69 | 70 | return user; 71 | } 72 | 73 | private Task>SendConfirmationEmail(PersonalProfileDto personalProfile) 74 | => _dependencies.SendEmail($"{personalProfile.UserName}@mail.com", "personal profile created", 75 | "congratulations"); 76 | 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/BackEnd/src/WebPersonal.BackEnd.Service/Validations/PersonalProfileDtoValidation.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Collections.Immutable; 3 | using System.Linq; 4 | using ROP; 5 | using WebPersonal.Shared.Dto; 6 | 7 | namespace WebPersonal.BackEnd.Service.Validations 8 | { 9 | public static class PersonalProfileDtoValidation 10 | { 11 | 12 | public static Result ValidateDto(this PersonalProfileDto profileDto) 13 | { 14 | 15 | return ValidateProfile(profileDto) 16 | .Bind(ValidateSkills) 17 | .Bind(ValidateInterests); 18 | 19 | } 20 | 21 | 22 | private static Result ValidateProfile(this PersonalProfileDto profileDto) 23 | { 24 | List errors = new List(); 25 | 26 | if (profileDto.Description.Length > 5000) 27 | { 28 | errors.Add(Error.Create("El campo descripción no puede ser mayor de 5000")); 29 | } 30 | 31 | if (profileDto.Email.Length > 50) 32 | { 33 | errors.Add(Error.Create("El campo email no puede ser mayor de 50")); 34 | } 35 | 36 | if (profileDto.FirstName.Length > 50) 37 | { 38 | errors.Add(Error.Create("El campo nombre propio no puede ser mayor de 50")); 39 | } 40 | 41 | if (profileDto.GitHub.Length > 50) 42 | { 43 | errors.Add(Error.Create("El campo Github no puede ser mayor de 50")); 44 | } 45 | 46 | if (profileDto.LastName.Length > 50) 47 | { 48 | errors.Add(Error.Create("El campo Apellido no puede ser mayor de 50")); 49 | } 50 | 51 | if (profileDto.Phone.Length > 50) 52 | { 53 | errors.Add(Error.Create("El campo Teléfono no puede ser mayor de 50")); 54 | } 55 | 56 | if (profileDto.UserName.Length > 50) 57 | { 58 | errors.Add(Error.Create("El campo Usuario no puede ser mayor de 50")); 59 | } 60 | 61 | if (string.IsNullOrWhiteSpace(profileDto.UserName)) 62 | { 63 | errors.Add(Error.Create("El campo Usuario debe contener algún valor")); 64 | } 65 | 66 | if (profileDto.UserName.Length < 5) 67 | { 68 | errors.Add(Error.Create("El campo usuario debe ser de almenos de 5 caracteres")); 69 | } 70 | 71 | if (profileDto.Website.Length > 50) 72 | { 73 | errors.Add(Error.Create("El campo website debe ser almenos de 5 caracteres")); 74 | 75 | } 76 | 77 | 78 | return errors.Any() ? 79 | Result.Failure(errors.ToImmutableArray()) 80 | : profileDto; 81 | } 82 | 83 | 84 | private static Result ValidateSkills(PersonalProfileDto profileDto) 85 | { 86 | return profileDto.Skills 87 | .Select(a => ValidateSkill(a)) 88 | .Traverse() 89 | .Map(_=>profileDto); 90 | } 91 | 92 | 93 | private static Result ValidateSkill(SkillDto skill) 94 | { 95 | List errors = new List(); 96 | 97 | if (string.IsNullOrWhiteSpace(skill.Name)) 98 | { 99 | errors.Add(Error.Create($"El valor {skill.Name} no es válido")); 100 | } 101 | 102 | if (skill.Name.Length > 50) 103 | { 104 | errors.Add(Error.Create($"El valor {skill.Name} es demasiado largo (max 50)")); 105 | } 106 | 107 | if(skill.Punctuation != null && (skill.Punctuation<0 || skill.Punctuation > 10)) 108 | { 109 | errors.Add(Error.Create($"La puntuacion de la skill {skill.Punctuation} debe estar entre 1 y 10")); 110 | } 111 | 112 | 113 | return errors.Any() ? 114 | Result.Failure(errors.ToImmutableArray()) 115 | : Result.Unit; 116 | } 117 | 118 | private static Result ValidateInterests(PersonalProfileDto profileDto) 119 | { 120 | return profileDto.Interests 121 | .Select(a => ValidateInterest(a.Interest)) 122 | .Traverse() 123 | .Map(_ => profileDto); 124 | 125 | Result ValidateInterest(string interest) 126 | { 127 | if(interest.Length>250 || interest.Length < 15) 128 | { 129 | return Result.Failure(Error.Create("Los intereses deben contener entre 15 y 250 caracteres")); 130 | } 131 | return Result.Unit; 132 | } 133 | } 134 | 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /src/BackEnd/src/WebPersonal.BackEnd.Service/WebPersonal.BackEnd.Service.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net8.0 5 | WebPersonal.BackEnd.Service 6 | true 7 | enable 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/BackEnd/src/WebPersonal.BackEnd.ServiceDependencies/Services/PerfilPersonal/GetPersonalProfileDependencies.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | using WebPersonal.BackEnd.Model.Entity; 4 | using WebPersonal.BackEnd.Model.Repositories; 5 | using WebPersonal.BackEnd.Service.PerfilPersonal; 6 | 7 | namespace WebPersonal.BackEnd.ServiceDependencies.Services.PerfilPersonal 8 | { 9 | public class GetPersonalProfileDependencies : IGetPersonalProfileDependencies 10 | { 11 | private readonly PersonalProfileRepository _personalProfileRepo; 12 | private readonly SkillRepository _skillRepo; 13 | private readonly InterestsRepository _interestsRepository; 14 | private readonly UserIdRepository _userIdRepository; 15 | 16 | public GetPersonalProfileDependencies(PersonalProfileRepository personalProfileRepo, SkillRepository skillRepo, 17 | InterestsRepository interestsRepository, UserIdRepository userIdRepository) 18 | { 19 | _personalProfileRepo = personalProfileRepo; 20 | _skillRepo = skillRepo; 21 | _interestsRepository = interestsRepository; 22 | _userIdRepository = userIdRepository; 23 | } 24 | 25 | public Task> GetInterests(int userId) => 26 | _interestsRepository.GetListByUserId(userId); 27 | 28 | public Task GetPersonalProfile(int userId) 29 | => _personalProfileRepo.GetByUserId(userId); 30 | 31 | public Task> GetSkills(int userId) 32 | => _skillRepo.GetListByUserId(userId); 33 | 34 | public Task GetUserId(string name) 35 | => _userIdRepository.GetByUserName(name); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/BackEnd/src/WebPersonal.BackEnd.ServiceDependencies/Services/PerfilPersonal/PostPersonalProfileDependencies.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | using ROP; 4 | using WebPersonal.Backend.EmailService; 5 | using WebPersonal.BackEnd.Model.Entity; 6 | using WebPersonal.BackEnd.Model.Repositories; 7 | using WebPersonal.BackEnd.Service.PerfilPersonal; 8 | 9 | namespace WebPersonal.BackEnd.ServiceDependencies.Services.PerfilPersonal 10 | { 11 | public class PostPersonalProfileDependencies : IPostPersonalProfileDependencies 12 | { 13 | private readonly PersonalProfileRepository _personalProfileRepo; 14 | private readonly SkillRepository _skillRepo; 15 | private readonly InterestsRepository _interestsRepository; 16 | private readonly UserIdRepository _userIdRepository; 17 | private readonly IEmailSender _emailSender; 18 | 19 | public PostPersonalProfileDependencies(PersonalProfileRepository personalProfileRepo, SkillRepository skillRepo, 20 | InterestsRepository interestsRepository, UserIdRepository userIdRepository, IEmailSender emailSender) 21 | { 22 | _personalProfileRepo = personalProfileRepo; 23 | _skillRepo = skillRepo; 24 | _interestsRepository = interestsRepository; 25 | _userIdRepository = userIdRepository; 26 | _emailSender = emailSender; 27 | } 28 | 29 | 30 | public async Task CommitTransaction() 31 | { 32 | await _personalProfileRepo.CommitTransaction(); 33 | await _skillRepo.CommitTransaction(); 34 | await _interestsRepository.CommitTransaction(); 35 | await _userIdRepository.CommitTransaction(); 36 | } 37 | 38 | public async Task> InsertPersonalProfile(PersonalProfileEntity personalProfile) => 39 | await _personalProfileRepo.InsertSingle(personalProfile); 40 | 41 | public async Task>> InsertSkills(List skills) => 42 | await _skillRepo.InsertList(skills); 43 | 44 | public async Task>> InsertInterests(List interests) => 45 | await _interestsRepository.InsertList(interests); 46 | 47 | public async Task DeleteUnusedInterests(List unusedIds, int userId) => 48 | await _interestsRepository.DeleteUnused(unusedIds, userId); 49 | 50 | public async Task InsertUserId(string name) => 51 | await _userIdRepository.InsertSingle(UserIdEntity.Create(name, null)); 52 | 53 | public async Task> SendEmail(string to, string subject, string body) => 54 | await _emailSender.SendEmail(to, subject, body); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/BackEnd/src/WebPersonal.BackEnd.ServiceDependencies/Services/PerfilPersonal/PutPersonalProfileDependencies.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | using ROP; 4 | using WebPersonal.BackEnd.Model.Entity; 5 | using WebPersonal.BackEnd.Model.Repositories; 6 | using WebPersonal.BackEnd.Service.PerfilPersonal; 7 | namespace WebPersonal.BackEnd.ServiceDependencies.Services.PerfilPersonal 8 | { 9 | public class PutPersonalProfileDependencies : IPutPersonalProfileDependencies 10 | { 11 | private readonly PersonalProfileRepository _personalProfileRepo; 12 | private readonly SkillRepository _skillRepo; 13 | private readonly InterestsRepository _interestsRepository; 14 | private readonly UserIdRepository _userIdRepository; 15 | 16 | public PutPersonalProfileDependencies(PersonalProfileRepository personalProfileRepo, SkillRepository skillRepo, 17 | InterestsRepository interestsRepository, UserIdRepository userIdRepository) 18 | { 19 | _personalProfileRepo = personalProfileRepo; 20 | _skillRepo = skillRepo; 21 | _interestsRepository = interestsRepository; 22 | _userIdRepository = userIdRepository; 23 | } 24 | 25 | public Task GetUser(string userName) => 26 | _userIdRepository.GetByUserName(userName); 27 | 28 | public async Task> InsertPersonalProfile(PersonalProfileEntity personalProfile) => 29 | await _personalProfileRepo.InsertSingle(personalProfile); 30 | 31 | public async Task> UpdatePersonalProfile(PersonalProfileEntity personalProfile) => 32 | await _personalProfileRepo.UpdateSingle(personalProfile); 33 | 34 | public async Task>> InsertSkills(List skills) => 35 | await _skillRepo.InsertList(skills); 36 | 37 | public async Task>> UpdateSkills(List skills) => 38 | await _skillRepo.UpdateList(skills); 39 | public async Task DeleteUnusedSkills(List unusedIds, int userId) => 40 | await _skillRepo.DeleteUnused(unusedIds, userId); 41 | 42 | public async Task>> InsertInterests(List interests) => 43 | await _interestsRepository.InsertList(interests); 44 | 45 | public async Task>> UpdateInterests(List interests) => 46 | await _interestsRepository.UpdateList(interests); 47 | 48 | public async Task DeleteUnusedInterests(List unusedIds, int userId) => 49 | await _interestsRepository.DeleteUnused(unusedIds, userId); 50 | 51 | public async Task CommitTransaction() 52 | { 53 | await _skillRepo.CommitTransaction(); 54 | await _personalProfileRepo.CommitTransaction(); 55 | await _interestsRepository.CommitTransaction(); 56 | } 57 | 58 | 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/BackEnd/src/WebPersonal.BackEnd.ServiceDependencies/WebPersonal.BackEnd.ServiceDependencies.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net8.0 5 | WebPersonal.BackEnd.ServiceDependencies 6 | true 7 | enable 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/BackEnd/src/WebPersonal.BackEnd.Translations/TraduccionErrores.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | using WebPersonal.Shared.Language; 3 | 4 | namespace WebPersonal.BackEnd.Translations 5 | { 6 | public class TraduccionErrores 7 | { 8 | private readonly CultureInfo _culture; 9 | public TraduccionErrores(CultureInfo culture) 10 | { 11 | _culture = culture; 12 | } 13 | 14 | public string PersonalProfile => LocalizationUtils.GetValue("PersonalProfileNotFound", _culture); 15 | public string IdentityNotFound => LocalizationUtils.GetValue("IdentityNotFound", _culture); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/BackEnd/src/WebPersonal.BackEnd.Translations/TraduccionErrores.en.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | text/microsoft-resx 91 | 92 | 93 | 1.3 94 | 95 | 96 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 97 | 98 | 99 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 100 | 101 | 102 | User not found 103 | 104 | 105 | Personal profile not found 106 | 107 | 108 | Example in english 109 | 110 | -------------------------------------------------------------------------------- /src/BackEnd/src/WebPersonal.BackEnd.Translations/TraduccionErrores.es.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | Usuario no encontrado 122 | 123 | 124 | Perfil personal no encontrado 125 | 126 | 127 | Ejemplo Error 128 | 129 | -------------------------------------------------------------------------------- /src/BackEnd/src/WebPersonal.BackEnd.Translations/WebPersonal.BackEnd.Translations.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | TraduccionErrores.es.Designer.cs 14 | ResXFileCodeGenerator 15 | 16 | 17 | TraduccionErrores.Designer.cs 18 | ResXFileCodeGenerator 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/BackEnd/src/WebPersonal.Backend.EmailService/EmailSender.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Microsoft.Extensions.Options; 4 | using ROP; 5 | 6 | namespace WebPersonal.Backend.EmailService 7 | { 8 | 9 | public interface IEmailSender 10 | { 11 | Task> SendEmail(string to, string subject, string body); 12 | } 13 | 14 | public class EmailSender : IEmailSender 15 | { 16 | private readonly IOptionsMonitor _options; 17 | private EmailConfiguration EmailConfiguration => _options.CurrentValue; 18 | 19 | public EmailSender(IOptionsMonitor options) 20 | { 21 | _options = options; 22 | } 23 | 24 | public async Task> SendEmail(string to, string subject, string body) 25 | { 26 | Console.WriteLine("this simulates the an email being Sent"); 27 | Console.WriteLine($"Email configuration Server: {EmailConfiguration.SmtpServer}, From: {EmailConfiguration.From}"); 28 | Console.WriteLine($"Email data To: {to}, subject: {subject}, body: {body}"); 29 | return true; 30 | } 31 | 32 | } 33 | 34 | 35 | public class EmailConfiguration 36 | { 37 | public string SmtpServer { get; set; } 38 | public string From { get; set; } 39 | } 40 | } 41 | 42 | -------------------------------------------------------------------------------- /src/BackEnd/src/WebPersonal.Backend.EmailService/WebPersonal.Backend.EmailService.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/Database/Version01.sql: -------------------------------------------------------------------------------- 1 | 2 | CREATE DATABASE IF NOT EXISTS `webpersonal`; 3 | USE `webpersonal`; 4 | CREATE USER 'webpersonaluser' IDENTIFIED BY 'webpersonalpass'; 5 | GRANT ALL PRIVILEGES ON * . * TO 'webpersonaluser'; 6 | 7 | 8 | 9 | CREATE TABLE IF NOT EXISTS `academicproject` ( 10 | `id` int(11) NOT NULL AUTO_INCREMENT, 11 | `userid` int(11) NOT NULL, 12 | `educationid` int(11) NOT NULL, 13 | `name` varchar(150) NOT NULL, 14 | `details` varchar(5000) NOT NULL, 15 | `environment` varchar(500) NOT NULL, 16 | `date` date DEFAULT NULL, 17 | PRIMARY KEY (`id`) 18 | ); 19 | 20 | CREATE TABLE IF NOT EXISTS `education` ( 21 | `Id` int(11) NOT NULL AUTO_INCREMENT, 22 | `userid` int(11) NOT NULL, 23 | `startdate` date NOT NULL, 24 | `enddate` date DEFAULT NULL, 25 | `coursename` varchar(250) NOT NULL, 26 | `collegename` varchar(250) DEFAULT NULL, 27 | `city` varchar(250) DEFAULT NULL, 28 | `country` varchar(250) DEFAULT NULL, 29 | PRIMARY KEY (`Id`) 30 | ); 31 | 32 | CREATE TABLE IF NOT EXISTS `interest` ( 33 | `id` int(11) NOT NULL AUTO_INCREMENT, 34 | `userid` int(11) NOT NULL, 35 | `description` varchar(250) DEFAULT NULL, 36 | PRIMARY KEY (`id`) 37 | ); 38 | 39 | CREATE TABLE IF NOT EXISTS `personalprofile` ( 40 | `id` int(11) NOT NULL AUTO_INCREMENT, 41 | `userid` int(11) NOT NULL, 42 | `firstname` varchar(50) NOT NULL, 43 | `description` varchar(50) DEFAULT NULL, 44 | `phone` varchar(50) DEFAULT NULL, 45 | `email` varchar(50) DEFAULT NULL, 46 | `lastname` varchar(50) NOT NULL, 47 | `website` varchar(50) DEFAULT NULL, 48 | `github` varchar(50) DEFAULT NULL, 49 | PRIMARY KEY (`id`) 50 | ); 51 | 52 | CREATE TABLE IF NOT EXISTS `personalproject` ( 53 | `id` int(11) NOT NULL AUTO_INCREMENT, 54 | `userid` int(11) NOT NULL, 55 | `name` varchar(150) NOT NULL, 56 | `details` varchar(5000) NOT NULL, 57 | `environment` varchar(500) DEFAULT NULL, 58 | `date` date DEFAULT NULL, 59 | PRIMARY KEY (`id`) 60 | ); 61 | 62 | 63 | CREATE TABLE IF NOT EXISTS `skill` ( 64 | `id` int(11) NOT NULL AUTO_INCREMENT, 65 | `userid` int(11) NOT NULL, 66 | `name` varchar(50) NOT NULL, 67 | `punctuation` decimal(4,2) DEFAULT NULL, 68 | PRIMARY KEY (`id`) 69 | ); 70 | 71 | CREATE TABLE IF NOT EXISTS `userid` ( 72 | `UserId` int(11) NOT NULL AUTO_INCREMENT, 73 | `UserName` varchar(50) NOT NULL, 74 | PRIMARY KEY (`UserId`), 75 | UNIQUE KEY `UserName` (`UserName`) 76 | ); 77 | 78 | CREATE TABLE IF NOT EXISTS `workexperience` ( 79 | `id` int(11) NOT NULL AUTO_INCREMENT, 80 | `userid` int(11) NOT NULL, 81 | `position` varchar(150) NOT NULL, 82 | `companyname` varchar(150) NOT NULL, 83 | `city` varchar(150) NOT NULL, 84 | `country` varchar(150) NOT NULL, 85 | `startdate` date DEFAULT NULL, 86 | `enddate` date DEFAULT NULL, 87 | `environment` varchar(500) DEFAULT NULL, 88 | PRIMARY KEY (`id`) 89 | ); 90 | 91 | CREATE TABLE IF NOT EXISTS `workproject` ( 92 | `id` int(11) NOT NULL AUTO_INCREMENT, 93 | `userid` int(11) NOT NULL, 94 | `workid` int(11) NOT NULL, 95 | `name` varchar(150) NOT NULL, 96 | `details` varchar(5000) NOT NULL, 97 | `date` date DEFAULT NULL, 98 | `environment` varchar(500) DEFAULT NULL, 99 | PRIMARY KEY (`id`) 100 | ); 101 | -------------------------------------------------------------------------------- /src/Database/version02.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE `personalprofile` 2 | CHANGE COLUMN `phone` `phone` VARCHAR(200) NULL DEFAULT NULL AFTER `description`, 3 | CHANGE COLUMN `email` `email` VARCHAR(200) NULL DEFAULT NULL AFTER `phone`; 4 | SELECT `DEFAULT_COLLATION_NAME` FROM `information_schema`.`SCHEMATA` WHERE `SCHEMA_NAME`='webpersonal'; -------------------------------------------------------------------------------- /src/FrontEnd/WebPersonal.FrontEnd.WebApp/App.razor: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

Sorry, there's nothing at this address.

8 |
9 |
10 |
11 | -------------------------------------------------------------------------------- /src/FrontEnd/WebPersonal.FrontEnd.WebApp/Componentes/Contacto.razor: -------------------------------------------------------------------------------- 1 |  10 | 11 |

Contacto

12 | 16 |
17 | 18 | 19 |
20 | 21 |
22 | 23 | 24 |
25 |
26 |
27 | 28 |
29 | 30 |
31 |
32 |
33 | 34 |
35 | 36 | 37 |
38 |
39 |
40 | 41 |
42 | 43 | 44 |
45 |
46 | 47 |
48 |
-------------------------------------------------------------------------------- /src/FrontEnd/WebPersonal.FrontEnd.WebApp/Componentes/Contacto.razor.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Components; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Net.Http; 6 | using System.Net.Http.Json; 7 | using System.Text.Json; 8 | using System.Threading.Tasks; 9 | using ROP.APIExtensions; 10 | using WebPersonal.Shared.Dto; 11 | 12 | namespace WebPersonal.FrontEnd.WebApp.Componentes 13 | { 14 | public partial class Contacto 15 | { 16 | [Inject] 17 | private IHttpClientFactory ClientFactory { get; set; } 18 | private ContactDto _contact { get; set; } = new ContactDto(); 19 | 20 | private string MessageBoxCss { get; set; } = "oculto"; 21 | private string FormCss { get; set; } = "visible"; 22 | private async Task Enviar() 23 | { 24 | 25 | HttpClient client = ClientFactory.CreateClient("BackendApi"); 26 | 27 | HttpResponseMessage result = await client.PostAsJsonAsync($"api/contact", _contact); 28 | 29 | ResultDto contactResponse = await result.Content.ReadFromJsonAsync>(); 30 | 31 | if (contactResponse.Success) 32 | { 33 | MessageBoxCss = "visible"; 34 | FormCss = "oculto"; 35 | } 36 | 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/FrontEnd/WebPersonal.FrontEnd.WebApp/Componentes/Educacion.razor: -------------------------------------------------------------------------------- 1 | 

@Titulo

2 | -------------------------------------------------------------------------------- /src/FrontEnd/WebPersonal.FrontEnd.WebApp/Componentes/Educacion.razor.cs: -------------------------------------------------------------------------------- 1 | namespace WebPersonal.FrontEnd.WebApp.Componentes 2 | { 3 | public partial class Educacion 4 | { 5 | public string Titulo => "Estudios académicos"; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/FrontEnd/WebPersonal.FrontEnd.WebApp/Componentes/ExperienciaLaboral.razor: -------------------------------------------------------------------------------- 1 | 

@Titulo

2 | 3 | -------------------------------------------------------------------------------- /src/FrontEnd/WebPersonal.FrontEnd.WebApp/Componentes/ExperienciaLaboral.razor.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Components; 2 | 3 | namespace WebPersonal.FrontEnd.WebApp.Componentes 4 | { 5 | public partial class ExperienciaLaboral : ComponentBase 6 | { 7 | public string Titulo => "Experiencia Laboral"; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/FrontEnd/WebPersonal.FrontEnd.WebApp/Componentes/PerfilPersonal.razor: -------------------------------------------------------------------------------- 1 | @if (PersonalProfile != null) 2 | { 3 | 4 |
5 |
6 |
@PersonalProfile.FirstName @PersonalProfile.LastName
7 |
8 |
9 | 10 |

Perfil Personal

11 |
12 |
13 | @PersonalProfile.Description 14 |
15 |
16 |
17 |
18 | Teléfono: @PersonalProfile.Phone 19 |
20 |
21 | Email: @PersonalProfile.Email 22 |
23 |
24 | GitHub: @PersonalProfile.GitHub 25 |
26 |
27 | Página web: @PersonalProfile.Website 28 |
29 |
30 | 31 | } 32 | 33 | @code{ 34 | 35 | [JSInvokable] 36 | public static string GetNombreCompleto(string nombre) 37 | { 38 | return $"el nombre es {nombre}"; 39 | } 40 | 41 | } -------------------------------------------------------------------------------- /src/FrontEnd/WebPersonal.FrontEnd.WebApp/Componentes/PerfilPersonal.razor.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Components; 2 | using System.Collections.Generic; 3 | using System.Net.Http; 4 | using System.Threading.Tasks; 5 | using WebPersonal.Shared.Dto; 6 | using System.Text.Json; 7 | using System.Net.Http.Json; 8 | using System.Linq; 9 | using ROP.APIExtensions; 10 | 11 | namespace WebPersonal.FrontEnd.WebApp.Componentes 12 | { 13 | public partial class PerfilPersonal 14 | { 15 | [Inject] 16 | private IHttpClientFactory ClientFactory { get; set; } 17 | [Parameter] 18 | public string Profile { get; set; } 19 | [Parameter] 20 | public EventCallback OnClick { get; set; } 21 | private string _profileValue { get; set; } //Propiedad privada para almacenar el valor actual 22 | public PersonalProfileDto PersonalProfile { get; set; } 23 | public List Erros { get; set; } 24 | 25 | protected override async Task OnParametersSetAsync() 26 | { 27 | if (_profileValue != Profile) //Comparamos el valor, y si es distinto, consultamos la información 28 | { 29 | await CalculateProfile(); 30 | } 31 | 32 | await base.OnParametersSetAsync(); 33 | } 34 | 35 | private async Task CalculateProfile() 36 | { 37 | _profileValue = Profile; //ASignamos el valor 38 | var result = await GetPersonalProfile(Profile); 39 | if (!result.Errors.Any()) 40 | PersonalProfile = result.Value; 41 | else 42 | Erros = result.Errors.ToList(); 43 | } 44 | 45 | private async Task> GetPersonalProfile(string profileCode) 46 | { 47 | var client = ClientFactory.CreateClient("BackendApi"); 48 | return await client.GetFromJsonAsync>($"api/perfilpersonal/{profileCode}", new JsonSerializerOptions 49 | { 50 | PropertyNameCaseInsensitive = true 51 | }); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/FrontEnd/WebPersonal.FrontEnd.WebApp/Pages/Counter.razor: -------------------------------------------------------------------------------- 1 | @page "/counter" 2 | 3 |

Counter

4 | 5 |

Current count: @currentCount

6 | 7 | 8 | 9 | @code { 10 | private int currentCount = 0; 11 | 12 | private void IncrementCount() 13 | { 14 | currentCount++; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/FrontEnd/WebPersonal.FrontEnd.WebApp/Pages/FetchData.razor: -------------------------------------------------------------------------------- 1 | @page "/fetchdata" 2 | @inject HttpClient Http 3 | 4 |

Weather forecast

5 | 6 |

This component demonstrates fetching data from the server.

7 | 8 | @if (forecasts == null) 9 | { 10 |

Loading...

11 | } 12 | else 13 | { 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | @foreach (var forecast in forecasts) 25 | { 26 | 27 | 28 | 29 | 30 | 31 | 32 | } 33 | 34 |
DateTemp. (C)Temp. (F)Summary
@forecast.Date.ToShortDateString()@forecast.TemperatureC@forecast.TemperatureF@forecast.Summary
35 | } 36 | 37 | @code { 38 | private WeatherForecast[] forecasts; 39 | 40 | protected override async Task OnInitializedAsync() 41 | { 42 | forecasts = await Http.GetFromJsonAsync("sample-data/weather.json"); 43 | } 44 | 45 | public class WeatherForecast 46 | { 47 | public DateTime Date { get; set; } 48 | 49 | public int TemperatureC { get; set; } 50 | 51 | public string Summary { get; set; } 52 | 53 | public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/FrontEnd/WebPersonal.FrontEnd.WebApp/Pages/Index.razor: -------------------------------------------------------------------------------- 1 | @page "/" 2 | 3 |

Esto es el index principal

-------------------------------------------------------------------------------- /src/FrontEnd/WebPersonal.FrontEnd.WebApp/Pages/Profile.razor: -------------------------------------------------------------------------------- 1 | @page "/perfil/{Name}" 2 | @using WebPersonal.FrontEnd.WebApp.Componentes 3 | @inject StateContainer stateContainer 4 | @inject IJSRuntime jsRuntime 5 | @implements IDisposable 6 | 7 |
@Mensaje
8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | @code{ 17 | [Parameter] 18 | public string Name { get; set; } 19 | 20 | public string Mensaje = "Este es el mensaje en el elemento padre"; 21 | 22 | void ClickCallback(string mensajeNuevo) 23 | { 24 | Mensaje = mensajeNuevo; 25 | } 26 | 27 | 28 | protected override void OnInitialized() 29 | { 30 | stateContainer.CambiarColor += StateHasChanged; 31 | } 32 | 33 | public void Dispose() 34 | { 35 | stateContainer.CambiarColor -= StateHasChanged; 36 | } 37 | 38 | public void EjecutarJs() 39 | { 40 | jsRuntime.InvokeVoidAsync("MostrarAlerta", "Mensaje enviado desde c# a Js"); 41 | } 42 | 43 | } -------------------------------------------------------------------------------- /src/FrontEnd/WebPersonal.FrontEnd.WebApp/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Microsoft.AspNetCore.Components.WebAssembly.Hosting; 4 | using Microsoft.Extensions.DependencyInjection; 5 | 6 | namespace WebPersonal.FrontEnd.WebApp 7 | { 8 | public class Program 9 | { 10 | public static async Task Main(string[] args) 11 | { 12 | var builder = WebAssemblyHostBuilder.CreateDefault(args); 13 | builder.RootComponents.Add("app"); 14 | 15 | builder.Services.AddHttpClient("BackendApi", client => 16 | { 17 | client.BaseAddress = new Uri("https://localhost:44363"); 18 | } 19 | ); 20 | 21 | builder.Services.AddSingleton(); 22 | 23 | await builder.Build().RunAsync(); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/FrontEnd/WebPersonal.FrontEnd.WebApp/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:57863", 7 | "sslPort": 44323 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", 15 | "environmentVariables": { 16 | "ASPNETCORE_ENVIRONMENT": "Development" 17 | } 18 | }, 19 | "WebPersonal.FrontEnd.WebApp": { 20 | "commandName": "Project", 21 | "launchBrowser": true, 22 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", 23 | "applicationUrl": "https://localhost:5001;http://localhost:5000", 24 | "environmentVariables": { 25 | "ASPNETCORE_ENVIRONMENT": "Development" 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/FrontEnd/WebPersonal.FrontEnd.WebApp/Shared/MainLayout.razor: -------------------------------------------------------------------------------- 1 | @inherits LayoutComponentBase 2 | 3 | 6 | 7 |
8 |
9 | 10 | 11 | About 12 |
13 | 14 |
15 | 16 | 17 | @Body 18 | 19 | 20 |
21 |
22 | @code{ 23 | private readonly string Bordercolor="red"; 24 | private readonly string BackgroundColor = "cyan"; 25 | } -------------------------------------------------------------------------------- /src/FrontEnd/WebPersonal.FrontEnd.WebApp/Shared/ModoNocturno.razor: -------------------------------------------------------------------------------- 1 | @inject StateContainer stateContainer 2 | 3 | 4 | 5 | @code { 6 | 7 | void SetCssNormal() 8 | { 9 | stateContainer.AsignarColorCss("rojo"); 10 | } 11 | } -------------------------------------------------------------------------------- /src/FrontEnd/WebPersonal.FrontEnd.WebApp/Shared/ModoNormal.razor: -------------------------------------------------------------------------------- 1 | @inject StateContainer stateContainer 2 | 3 | 4 | 5 | @code { 6 | 7 | void SetCssNormal() 8 | { 9 | stateContainer.AsignarColorCss("negro"); 10 | } 11 | } -------------------------------------------------------------------------------- /src/FrontEnd/WebPersonal.FrontEnd.WebApp/Shared/NavMenu.razor: -------------------------------------------------------------------------------- 1 |  7 | 8 |
9 | 26 |
27 | 28 | @code { 29 | private bool collapseNavMenu = true; 30 | 31 | private string NavMenuCssClass => collapseNavMenu ? "collapse" : null; 32 | 33 | private void ToggleNavMenu() 34 | { 35 | collapseNavMenu = !collapseNavMenu; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/FrontEnd/WebPersonal.FrontEnd.WebApp/Shared/SurveyPrompt.razor: -------------------------------------------------------------------------------- 1 |  11 | 12 | @code { 13 | // Demonstrates how a parent component can supply parameters 14 | [Parameter] 15 | public string Title { get; set; } 16 | } 17 | -------------------------------------------------------------------------------- /src/FrontEnd/WebPersonal.FrontEnd.WebApp/StateContainer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace WebPersonal.FrontEnd.WebApp 4 | { 5 | public class StateContainer 6 | { 7 | public string SelectedCssClass { get; private set; } = "rojo"; 8 | 9 | public event Action CambiarColor; 10 | 11 | public void AsignarColorCss(string newCssClass) 12 | { 13 | SelectedCssClass = newCssClass; 14 | ExecuteAction(); 15 | } 16 | 17 | private void ExecuteAction() => CambiarColor?.Invoke(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/FrontEnd/WebPersonal.FrontEnd.WebApp/WebPersonal.FrontEnd.WebApp.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.1 5 | 3.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/FrontEnd/WebPersonal.FrontEnd.WebApp/_Imports.razor: -------------------------------------------------------------------------------- 1 | @using System.Net.Http 2 | @using System.Net.Http.Json 3 | @using Microsoft.AspNetCore.Components.Forms 4 | @using Microsoft.AspNetCore.Components.Routing 5 | @using Microsoft.AspNetCore.Components.Web 6 | @using Microsoft.AspNetCore.Components.WebAssembly.Http 7 | @using Microsoft.JSInterop 8 | @using WebPersonal.FrontEnd.WebApp 9 | @using WebPersonal.FrontEnd.WebApp.Shared 10 | -------------------------------------------------------------------------------- /src/FrontEnd/WebPersonal.FrontEnd.WebApp/wwwroot/css/app.css: -------------------------------------------------------------------------------- 1 | @import url('open-iconic/font/css/open-iconic-bootstrap.min.css'); 2 | 3 | html, body { 4 | font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; 5 | } 6 | 7 | a, .btn-link { 8 | color: #0366d6; 9 | } 10 | 11 | .btn-primary { 12 | color: #fff; 13 | background-color: #1b6ec2; 14 | border-color: #1861ac; 15 | } 16 | 17 | app { 18 | position: relative; 19 | display: flex; 20 | flex-direction: column; 21 | } 22 | 23 | .top-row { 24 | height: 3.5rem; 25 | display: flex; 26 | align-items: center; 27 | } 28 | 29 | .main { 30 | flex: 1; 31 | } 32 | 33 | .main .top-row { 34 | background-color: #f7f7f7; 35 | border-bottom: 1px solid #d6d5d5; 36 | justify-content: flex-end; 37 | } 38 | 39 | .main .top-row > a, .main .top-row .btn-link { 40 | white-space: nowrap; 41 | margin-left: 1.5rem; 42 | } 43 | 44 | .main .top-row a:first-child { 45 | overflow: hidden; 46 | text-overflow: ellipsis; 47 | } 48 | 49 | .sidebar { 50 | background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%); 51 | } 52 | 53 | .sidebar .top-row { 54 | background-color: rgba(0,0,0,0.4); 55 | } 56 | 57 | .sidebar .navbar-brand { 58 | font-size: 1.1rem; 59 | } 60 | 61 | .sidebar .oi { 62 | width: 2rem; 63 | font-size: 1.1rem; 64 | vertical-align: text-top; 65 | top: -2px; 66 | } 67 | 68 | .sidebar .nav-item { 69 | font-size: 0.9rem; 70 | padding-bottom: 0.5rem; 71 | } 72 | 73 | .sidebar .nav-item:first-of-type { 74 | padding-top: 1rem; 75 | } 76 | 77 | .sidebar .nav-item:last-of-type { 78 | padding-bottom: 1rem; 79 | } 80 | 81 | .sidebar .nav-item a { 82 | color: #d7d7d7; 83 | border-radius: 4px; 84 | height: 3rem; 85 | display: flex; 86 | align-items: center; 87 | line-height: 3rem; 88 | } 89 | 90 | .sidebar .nav-item a.active { 91 | background-color: rgba(255,255,255,0.25); 92 | color: white; 93 | } 94 | 95 | .sidebar .nav-item a:hover { 96 | background-color: rgba(255,255,255,0.1); 97 | color: white; 98 | } 99 | 100 | .content { 101 | padding-top: 1.1rem; 102 | } 103 | 104 | .navbar-toggler { 105 | background-color: rgba(255, 255, 255, 0.1); 106 | } 107 | 108 | .valid.modified:not([type=checkbox]) { 109 | outline: 1px solid #26b050; 110 | } 111 | 112 | .invalid { 113 | outline: 1px solid red; 114 | } 115 | 116 | .validation-message { 117 | color: red; 118 | } 119 | 120 | #blazor-error-ui { 121 | background: lightyellow; 122 | bottom: 0; 123 | box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2); 124 | display: none; 125 | left: 0; 126 | padding: 0.6rem 1.25rem 0.7rem 1.25rem; 127 | position: fixed; 128 | width: 100%; 129 | z-index: 1000; 130 | } 131 | 132 | #blazor-error-ui .dismiss { 133 | cursor: pointer; 134 | position: absolute; 135 | right: 0.75rem; 136 | top: 0.5rem; 137 | } 138 | 139 | @media (max-width: 767.98px) { 140 | .main .top-row:not(.auth) { 141 | display: none; 142 | } 143 | 144 | .main .top-row.auth { 145 | justify-content: space-between; 146 | } 147 | 148 | .main .top-row a, .main .top-row .btn-link { 149 | margin-left: 0; 150 | } 151 | } 152 | 153 | @media (min-width: 768px) { 154 | app { 155 | flex-direction: row; 156 | } 157 | 158 | .sidebar { 159 | width: 250px; 160 | height: 100vh; 161 | position: sticky; 162 | top: 0; 163 | } 164 | 165 | .main .top-row { 166 | position: sticky; 167 | top: 0; 168 | } 169 | 170 | .main > div { 171 | padding-left: 2rem !important; 172 | padding-right: 1.5rem !important; 173 | } 174 | 175 | .navbar-toggler { 176 | display: none; 177 | } 178 | 179 | .sidebar .collapse { 180 | /* Never collapse the sidebar for wide screens */ 181 | display: block; 182 | } 183 | } 184 | 185 | 186 | .negro{ 187 | color: black; 188 | } 189 | .rojo { 190 | color: red; 191 | } -------------------------------------------------------------------------------- /src/FrontEnd/WebPersonal.FrontEnd.WebApp/wwwroot/css/open-iconic/FONT-LICENSE: -------------------------------------------------------------------------------- 1 | SIL OPEN FONT LICENSE Version 1.1 2 | 3 | Copyright (c) 2014 Waybury 4 | 5 | PREAMBLE 6 | The goals of the Open Font License (OFL) are to stimulate worldwide 7 | development of collaborative font projects, to support the font creation 8 | efforts of academic and linguistic communities, and to provide a free and 9 | open framework in which fonts may be shared and improved in partnership 10 | with others. 11 | 12 | The OFL allows the licensed fonts to be used, studied, modified and 13 | redistributed freely as long as they are not sold by themselves. The 14 | fonts, including any derivative works, can be bundled, embedded, 15 | redistributed and/or sold with any software provided that any reserved 16 | names are not used by derivative works. The fonts and derivatives, 17 | however, cannot be released under any other type of license. The 18 | requirement for fonts to remain under this license does not apply 19 | to any document created using the fonts or their derivatives. 20 | 21 | DEFINITIONS 22 | "Font Software" refers to the set of files released by the Copyright 23 | Holder(s) under this license and clearly marked as such. This may 24 | include source files, build scripts and documentation. 25 | 26 | "Reserved Font Name" refers to any names specified as such after the 27 | copyright statement(s). 28 | 29 | "Original Version" refers to the collection of Font Software components as 30 | distributed by the Copyright Holder(s). 31 | 32 | "Modified Version" refers to any derivative made by adding to, deleting, 33 | or substituting -- in part or in whole -- any of the components of the 34 | Original Version, by changing formats or by porting the Font Software to a 35 | new environment. 36 | 37 | "Author" refers to any designer, engineer, programmer, technical 38 | writer or other person who contributed to the Font Software. 39 | 40 | PERMISSION & CONDITIONS 41 | Permission is hereby granted, free of charge, to any person obtaining 42 | a copy of the Font Software, to use, study, copy, merge, embed, modify, 43 | redistribute, and sell modified and unmodified copies of the Font 44 | Software, subject to the following conditions: 45 | 46 | 1) Neither the Font Software nor any of its individual components, 47 | in Original or Modified Versions, may be sold by itself. 48 | 49 | 2) Original or Modified Versions of the Font Software may be bundled, 50 | redistributed and/or sold with any software, provided that each copy 51 | contains the above copyright notice and this license. These can be 52 | included either as stand-alone text files, human-readable headers or 53 | in the appropriate machine-readable metadata fields within text or 54 | binary files as long as those fields can be easily viewed by the user. 55 | 56 | 3) No Modified Version of the Font Software may use the Reserved Font 57 | Name(s) unless explicit written permission is granted by the corresponding 58 | Copyright Holder. This restriction only applies to the primary font name as 59 | presented to the users. 60 | 61 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 62 | Software shall not be used to promote, endorse or advertise any 63 | Modified Version, except to acknowledge the contribution(s) of the 64 | Copyright Holder(s) and the Author(s) or with their explicit written 65 | permission. 66 | 67 | 5) The Font Software, modified or unmodified, in part or in whole, 68 | must be distributed entirely under this license, and must not be 69 | distributed under any other license. The requirement for fonts to 70 | remain under this license does not apply to any document created 71 | using the Font Software. 72 | 73 | TERMINATION 74 | This license becomes null and void if any of the above conditions are 75 | not met. 76 | 77 | DISCLAIMER 78 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 79 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 80 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 81 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 82 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 83 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 84 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 85 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 86 | OTHER DEALINGS IN THE FONT SOFTWARE. 87 | -------------------------------------------------------------------------------- /src/FrontEnd/WebPersonal.FrontEnd.WebApp/wwwroot/css/open-iconic/ICON-LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Waybury 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /src/FrontEnd/WebPersonal.FrontEnd.WebApp/wwwroot/css/open-iconic/README.md: -------------------------------------------------------------------------------- 1 | [Open Iconic v1.1.1](http://useiconic.com/open) 2 | =========== 3 | 4 | ### Open Iconic is the open source sibling of [Iconic](http://useiconic.com). It is a hyper-legible collection of 223 icons with a tiny footprint—ready to use with Bootstrap and Foundation. [View the collection](http://useiconic.com/open#icons) 5 | 6 | 7 | 8 | ## What's in Open Iconic? 9 | 10 | * 223 icons designed to be legible down to 8 pixels 11 | * Super-light SVG files - 61.8 for the entire set 12 | * SVG sprite—the modern replacement for icon fonts 13 | * Webfont (EOT, OTF, SVG, TTF, WOFF), PNG and WebP formats 14 | * Webfont stylesheets (including versions for Bootstrap and Foundation) in CSS, LESS, SCSS and Stylus formats 15 | * PNG and WebP raster images in 8px, 16px, 24px, 32px, 48px and 64px. 16 | 17 | 18 | ## Getting Started 19 | 20 | #### For code samples and everything else you need to get started with Open Iconic, check out our [Icons](http://useiconic.com/open#icons) and [Reference](http://useiconic.com/open#reference) sections. 21 | 22 | ### General Usage 23 | 24 | #### Using Open Iconic's SVGs 25 | 26 | We like SVGs and we think they're the way to display icons on the web. Since Open Iconic are just basic SVGs, we suggest you display them like you would any other image (don't forget the `alt` attribute). 27 | 28 | ``` 29 | icon name 30 | ``` 31 | 32 | #### Using Open Iconic's SVG Sprite 33 | 34 | Open Iconic also comes in a SVG sprite which allows you to display all the icons in the set with a single request. It's like an icon font, without being a hack. 35 | 36 | Adding an icon from an SVG sprite is a little different than what you're used to, but it's still a piece of cake. *Tip: To make your icons easily style able, we suggest adding a general class to the* `` *tag and a unique class name for each different icon in the* `` *tag.* 37 | 38 | ``` 39 | 40 | 41 | 42 | ``` 43 | 44 | Sizing icons only needs basic CSS. All the icons are in a square format, so just set the `` tag with equal width and height dimensions. 45 | 46 | ``` 47 | .icon { 48 | width: 16px; 49 | height: 16px; 50 | } 51 | ``` 52 | 53 | Coloring icons is even easier. All you need to do is set the `fill` rule on the `` tag. 54 | 55 | ``` 56 | .icon-account-login { 57 | fill: #f00; 58 | } 59 | ``` 60 | 61 | To learn more about SVG Sprites, read [Chris Coyier's guide](http://css-tricks.com/svg-sprites-use-better-icon-fonts/). 62 | 63 | #### Using Open Iconic's Icon Font... 64 | 65 | 66 | ##### …with Bootstrap 67 | 68 | You can find our Bootstrap stylesheets in `font/css/open-iconic-bootstrap.{css, less, scss, styl}` 69 | 70 | 71 | ``` 72 | 73 | ``` 74 | 75 | 76 | ``` 77 | 78 | ``` 79 | 80 | ##### …with Foundation 81 | 82 | You can find our Foundation stylesheets in `font/css/open-iconic-foundation.{css, less, scss, styl}` 83 | 84 | ``` 85 | 86 | ``` 87 | 88 | 89 | ``` 90 | 91 | ``` 92 | 93 | ##### …on its own 94 | 95 | You can find our default stylesheets in `font/css/open-iconic.{css, less, scss, styl}` 96 | 97 | ``` 98 | 99 | ``` 100 | 101 | ``` 102 | 103 | ``` 104 | 105 | 106 | ## License 107 | 108 | ### Icons 109 | 110 | All code (including SVG markup) is under the [MIT License](http://opensource.org/licenses/MIT). 111 | 112 | ### Fonts 113 | 114 | All fonts are under the [SIL Licensed](http://scripts.sil.org/cms/scripts/page.php?item_id=OFL_web). 115 | -------------------------------------------------------------------------------- /src/FrontEnd/WebPersonal.FrontEnd.WebApp/wwwroot/css/open-iconic/font/fonts/open-iconic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElectNewt/WebPersonal/5eeac47e4b9bee7e901fc783e25ae1ce8b3be753/src/FrontEnd/WebPersonal.FrontEnd.WebApp/wwwroot/css/open-iconic/font/fonts/open-iconic.eot -------------------------------------------------------------------------------- /src/FrontEnd/WebPersonal.FrontEnd.WebApp/wwwroot/css/open-iconic/font/fonts/open-iconic.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElectNewt/WebPersonal/5eeac47e4b9bee7e901fc783e25ae1ce8b3be753/src/FrontEnd/WebPersonal.FrontEnd.WebApp/wwwroot/css/open-iconic/font/fonts/open-iconic.otf -------------------------------------------------------------------------------- /src/FrontEnd/WebPersonal.FrontEnd.WebApp/wwwroot/css/open-iconic/font/fonts/open-iconic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElectNewt/WebPersonal/5eeac47e4b9bee7e901fc783e25ae1ce8b3be753/src/FrontEnd/WebPersonal.FrontEnd.WebApp/wwwroot/css/open-iconic/font/fonts/open-iconic.ttf -------------------------------------------------------------------------------- /src/FrontEnd/WebPersonal.FrontEnd.WebApp/wwwroot/css/open-iconic/font/fonts/open-iconic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElectNewt/WebPersonal/5eeac47e4b9bee7e901fc783e25ae1ce8b3be753/src/FrontEnd/WebPersonal.FrontEnd.WebApp/wwwroot/css/open-iconic/font/fonts/open-iconic.woff -------------------------------------------------------------------------------- /src/FrontEnd/WebPersonal.FrontEnd.WebApp/wwwroot/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElectNewt/WebPersonal/5eeac47e4b9bee7e901fc783e25ae1ce8b3be753/src/FrontEnd/WebPersonal.FrontEnd.WebApp/wwwroot/favicon.ico -------------------------------------------------------------------------------- /src/FrontEnd/WebPersonal.FrontEnd.WebApp/wwwroot/index.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | WebPersonal.FrontEnd.WebApp 8 | 9 | 10 | 11 | 12 | 13 | 14 | Loading... 15 | 16 |
17 | An unhandled error has occurred. 18 | Reload 19 | 🗙 20 |
21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/FrontEnd/WebPersonal.FrontEnd.WebApp/wwwroot/js/script.js: -------------------------------------------------------------------------------- 1 | window.MostrarAlerta = function(message) { 2 | alert(message); 3 | } 4 | 5 | 6 | window.LlamarCSharp = function () { 7 | var result = window.DotNet.invokeMethod("WebPersonal.FrontEnd.WebApp", 8 | "GetNombreCompleto", "NetMentor"); 9 | alert(result); 10 | } -------------------------------------------------------------------------------- /src/FrontEnd/WebPersonal.FrontEnd.WebApp/wwwroot/sample-data/weather.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "date": "2018-05-06", 4 | "temperatureC": 1, 5 | "summary": "Freezing" 6 | }, 7 | { 8 | "date": "2018-05-07", 9 | "temperatureC": 14, 10 | "summary": "Bracing" 11 | }, 12 | { 13 | "date": "2018-05-08", 14 | "temperatureC": -13, 15 | "summary": "Freezing" 16 | }, 17 | { 18 | "date": "2018-05-09", 19 | "temperatureC": -16, 20 | "summary": "Balmy" 21 | }, 22 | { 23 | "date": "2018-05-10", 24 | "temperatureC": -2, 25 | "summary": "Chilly" 26 | } 27 | ] 28 | -------------------------------------------------------------------------------- /src/Shared/Shared.DTO/AcademicProjectsDto.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace WebPersonal.Shared.Dto 4 | { 5 | public class AcademicProjectsDto 6 | { 7 | public List Projects { get; set; } 8 | } 9 | public class AcademicProjectDto 10 | { 11 | public int Id { get; set; } 12 | public string Name { get; set; } 13 | public string Details { get; set; } 14 | public List Environment { get; set; } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Shared/Shared.DTO/ContactDto.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace WebPersonal.Shared.Dto 4 | { 5 | public class ContactDto 6 | { 7 | [Required(ErrorMessage = "Por favor indica tu nombre")] 8 | public string Name { get; set; } 9 | public string Surname { get; set; } 10 | [RegularExpression("^[a-zA-Z0-9_\\.-]+@([a-zA-Z0-9-]+\\.)+[a-zA-Z]{2,6}$", ErrorMessage = "Debes indicar un email válido")] 11 | public string Email { get; set; } 12 | [Required(ErrorMessage = "El contenido del mensaje es obligatorio")] 13 | public string Message { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/Shared/Shared.DTO/ContactResponse.cs: -------------------------------------------------------------------------------- 1 | namespace WebPersonal.Shared.Dto 2 | { 3 | public class ContactResponse 4 | { 5 | public bool MessageSent { get; set; } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/Shared/Shared.DTO/EducationDto.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace WebPersonal.Shared.Dto 4 | { 5 | public class EducationDto 6 | { 7 | public int Id { get; set; } 8 | public DateTime StartDate { get; set; } 9 | public DateTime? EndDate { get; set; } 10 | public string CourseName { get; set; } 11 | public string UniversityName { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Shared/Shared.DTO/PersonalProfileDto.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace WebPersonal.Shared.Dto 4 | { 5 | public class PersonalProfileDto 6 | { 7 | public int? UserId { get; set; } 8 | public int? Id { get; set; } 9 | public string UserName { get; set; } 10 | public string FirstName { get; set; } 11 | public string LastName { get; set; } 12 | public string Description { get; set; } 13 | public string Phone { get; set; } 14 | public string Email { get; set; } 15 | public string Website { get; set; } 16 | public string GitHub { get; set; } 17 | public List Interests { get; set; } 18 | public List Skills { get; set; } 19 | } 20 | 21 | public class SkillDto 22 | { 23 | public int? Id { get; set; } 24 | public string Name { get; set; } 25 | /// 26 | /// How good do you consider yourself at this skill. 27 | /// 28 | public decimal? Punctuation { get; set; } 29 | } 30 | 31 | public class InterestDto 32 | { 33 | public int? Id { get; set; } 34 | public string Interest { get; set; } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Shared/Shared.DTO/PersonalProjectsDto.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace WebPersonal.Shared.Dto 4 | { 5 | public class PersonalProjectsDto 6 | { 7 | public List PersonalProjects { get; set; } 8 | } 9 | 10 | public class PersonalProjectDto 11 | { 12 | public int Id { get; set; } 13 | public string ProjectType { get; set; } 14 | public string Name { get; set; } 15 | public string Description { get; set; } 16 | public List Environment { get; set; } 17 | } 18 | 19 | 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/Shared/Shared.DTO/Shared.DTO.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | WebPersonal.Shared.Dto 6 | true 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/Shared/Shared.DTO/WorkExperienceDto.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace WebPersonal.Shared.DTO 5 | { 6 | public class WorkExperienceDto 7 | { 8 | public List Positions { get; set; } 9 | 10 | } 11 | 12 | public class PositionDto 13 | { 14 | public int Id { get; set; } 15 | public string PositionName { get; set; } 16 | public DateTime StartDate { get; set; } 17 | public DateTime? EndDate { get; set; } 18 | public string CompanyName { get; set; } 19 | public string Country { get; set; } 20 | public string City { get; set; } 21 | public List Environment { get; set; } 22 | public List MainProjects { get; set; } 23 | } 24 | 25 | public class WorkProjectDto 26 | { 27 | public int Id { get; set; } 28 | public string Nombre { get; set; } 29 | public string Description { get; set; } 30 | 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Shared/Shared.Data/Db/ConnectionWrapper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data.Common; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | 6 | namespace WebPersonal.Shared.Data.Db 7 | { 8 | public class ConnectionWrapper : IDisposable 9 | { 10 | private readonly object _lock = new object(); 11 | 12 | private readonly DbConnection _connection; 13 | private Task _openConnection; 14 | private static readonly Task _noConnection = Task.FromResult((DbConnection)null); 15 | 16 | public ConnectionWrapper(DbConnection connection) 17 | { 18 | _connection = connection; 19 | } 20 | 21 | public async Task GetConnectionAsync(CancellationToken c = default(CancellationToken)) 22 | { 23 | if (c != default(CancellationToken)) 24 | { 25 | return await GetOpenConnectionOrOpenNewConnectionAsync(true, true, c); 26 | } 27 | 28 | // if cancellation token not specified, create one for 10 seconds 29 | using (var cts = new CancellationTokenSource()) 30 | { 31 | cts.CancelAfter(10000); 32 | return await GetOpenConnectionOrOpenNewConnectionAsync(true, true, cts.Token); 33 | } 34 | } 35 | 36 | public DbConnection GetConnection() 37 | { 38 | var connTask = GetOpenConnectionOrOpenNewConnectionAsync(false, true, default(CancellationToken)); 39 | connTask.Wait(); 40 | 41 | return connTask.Result; 42 | } 43 | 44 | /// Get connection using async or non async model 45 | /// If true, will run async. If false, will run synchronusly and wrap the result in a task. 46 | /// If true, will open the connection and return it if there is none. If false, returns null if the connection is not open 47 | protected Task GetOpenConnectionOrOpenNewConnectionAsync(bool async, bool openIfNotOpenAlready, CancellationToken c)// = default(CancellationToken)) 48 | { 49 | lock (_lock) 50 | { 51 | if (_openConnection != null) 52 | { 53 | return _openConnection; 54 | } 55 | 56 | if (openIfNotOpenAlready) 57 | { 58 | _openConnection = async 59 | ? BuildConnectionAsync() 60 | : BuildConnection(); 61 | 62 | return NotifyConnectionOpened(_openConnection); 63 | } 64 | 65 | return _noConnection; 66 | } 67 | 68 | // open a connection async and return 69 | async Task BuildConnectionAsync() 70 | { 71 | await _connection.OpenAsync(c); 72 | return _connection; 73 | } 74 | 75 | // open a connection NOT async and return, wrapped in a task 76 | Task BuildConnection() 77 | { 78 | _connection.Open(); 79 | return Task.FromResult(_connection); 80 | } 81 | 82 | async Task NotifyConnectionOpened(Task conn) 83 | { 84 | var cn = await conn; 85 | await OnConnectionOpened(cn, async); 86 | return cn; 87 | } 88 | } 89 | 90 | protected virtual Task OnConnectionOpened(DbConnection connection, bool async) => Task.CompletedTask; 91 | 92 | public virtual void Dispose() 93 | { 94 | _connection?.Dispose(); 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/Shared/Shared.Data/Db/TransactionalWrapper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data.Common; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | 6 | namespace WebPersonal.Shared.Data.Db 7 | { 8 | public class TransactionalWrapper : ConnectionWrapper 9 | { 10 | private readonly object _lock = new object(); 11 | 12 | private DbTransaction _openTransaction; 13 | 14 | public TransactionalWrapper(DbConnection connection) 15 | : base(connection) 16 | { 17 | } 18 | 19 | protected override async Task OnConnectionOpened(DbConnection connection, bool async) 20 | { 21 | await base.OnConnectionOpened(connection, async); 22 | 23 | lock (_lock) 24 | { 25 | if (_openTransaction != null) 26 | { 27 | throw new InvalidOperationException("You can only have 1 connection/transaction open at a time."); 28 | } 29 | 30 | _openTransaction = connection.BeginTransaction(); 31 | } 32 | } 33 | 34 | public async Task GetTransactionAsync(CancellationToken c = default(CancellationToken)) 35 | { 36 | // actual work done in OnNewConnectionOpened method 37 | await GetOpenConnectionOrOpenNewConnectionAsync(true, true, c); 38 | return _openTransaction; 39 | } 40 | 41 | public DbTransaction GetTransaction() 42 | { 43 | // actual work done in OnNewConnectionOpened method 44 | var connTask = GetOpenConnectionOrOpenNewConnectionAsync(false, true, default(CancellationToken)); 45 | connTask.Wait(); 46 | return _openTransaction; 47 | } 48 | 49 | public async Task CommitTransactionAsync(CancellationToken c = default(CancellationToken)) 50 | { 51 | var result = await GetOpenConnectionOrOpenNewConnectionAsync(true, false, c); 52 | CommitTransactionInternal(result); 53 | } 54 | 55 | public void CommitTransaction() 56 | { 57 | var conn = GetOpenConnectionOrOpenNewConnectionAsync(false, false, default(CancellationToken)); 58 | conn.Wait(); 59 | 60 | CommitTransactionInternal(conn.Result); 61 | } 62 | 63 | private void CommitTransactionInternal(DbConnection conn) 64 | { 65 | // if this is null, a connection has not been openend yet 66 | if (_openTransaction == null) 67 | { 68 | return; 69 | } 70 | 71 | lock (_lock) 72 | { 73 | _openTransaction.Commit(); 74 | _openTransaction.Dispose(); 75 | _openTransaction = conn.BeginTransaction(); 76 | } 77 | } 78 | 79 | public override void Dispose() 80 | { 81 | base.Dispose(); 82 | 83 | try 84 | { 85 | // call GetOpenConnectionOrOpenNewConnectionAsync to ensure that a transaction 86 | // is not in the process of being created 87 | GetOpenConnectionOrOpenNewConnectionAsync(false, false, default(CancellationToken)).Wait(); 88 | } 89 | catch (Exception e) 90 | { 91 | // may occur if there was a timeout when opening the connection 92 | if (!IsTaskCanceledException(e)) 93 | { 94 | throw; 95 | } 96 | } 97 | 98 | _openTransaction?.Dispose(); 99 | } 100 | 101 | private static bool IsTaskCanceledException(Exception e) 102 | { 103 | if (e == null) 104 | { 105 | return false; 106 | } 107 | 108 | if (e is TaskCanceledException) 109 | { 110 | return true; 111 | } 112 | 113 | if (IsTaskCanceledException(e.InnerException)) 114 | { 115 | return true; 116 | } 117 | 118 | 119 | return false; 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/Shared/Shared.Data/Shared.Data.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | WebPersonal.Shared.Data 6 | true 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/Shared/Shared.Language/CultureScope.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Threading; 4 | 5 | namespace WebPersonal.Shared.Language 6 | { 7 | public class CultureScope : IDisposable 8 | { 9 | private readonly CultureInfo _originalCulture; 10 | private readonly CultureInfo _originalUICulture; 11 | 12 | public CultureScope(CultureInfo culture) 13 | { 14 | _originalCulture = Thread.CurrentThread.CurrentCulture; 15 | _originalUICulture = Thread.CurrentThread.CurrentUICulture; 16 | 17 | Thread.CurrentThread.CurrentCulture = culture; 18 | Thread.CurrentThread.CurrentUICulture = culture; 19 | } 20 | 21 | public void Dispose() 22 | { 23 | Thread.CurrentThread.CurrentCulture = _originalCulture; 24 | Thread.CurrentThread.CurrentUICulture = _originalUICulture; 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Shared/Shared.Language/Extensions/AcceptedLanguageExtension.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Http; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Globalization; 5 | using System.Linq; 6 | 7 | namespace WebPersonal.Shared.Language.Extensions 8 | { 9 | public static class AcceptedLanguageExtension 10 | { 11 | /// 12 | /// Pick the favorite supported Language by the user browser. 13 | /// if that language does not exist, the translation will pick the default one, English. 14 | /// based on https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Language 15 | /// the header always follow the next pattern "ES;q=0.1", doesn't matter which country is the browser from. 16 | /// Need to set up the scope to a country which the decimals are dots. 17 | /// 18 | /// 19 | /// 20 | public static CultureInfo GetCultureInfo(this IHeaderDictionary header) 21 | { 22 | using (new CultureScope(new CultureInfo("es"))) 23 | { 24 | var languages = new List<(string, decimal)>(); 25 | string acceptedLanguage = header["Accept-Language"]; 26 | if (acceptedLanguage == null || acceptedLanguage.Length == 0) 27 | { 28 | return new CultureInfo("es"); 29 | } 30 | string[] acceptedLanguages = acceptedLanguage.Split(','); 31 | foreach (string accLang in acceptedLanguages) 32 | { 33 | var languageDetails = accLang.Split(';'); 34 | if (languageDetails.Length == 1) 35 | { 36 | languages.Add((languageDetails[0], 1)); 37 | } 38 | else 39 | { 40 | languages.Add((languageDetails[0], Convert.ToDecimal(languageDetails[1].Replace("q=", "")))); 41 | } 42 | } 43 | string languageToSet = languages.OrderByDescending(a => a.Item2).First().Item1; 44 | return new CultureInfo(languageToSet); 45 | } 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Shared/Shared.Language/LocalizationUtils.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Localization; 2 | using Microsoft.Extensions.Logging.Abstractions; 3 | using Microsoft.Extensions.Options; 4 | using System.Globalization; 5 | 6 | namespace WebPersonal.Shared.Language 7 | { 8 | public class LocalizationUtils 9 | { 10 | 11 | private static readonly IStringLocalizer _localizer; 12 | 13 | static LocalizationUtils() 14 | { 15 | var options = Options.Create(new LocalizationOptions()); 16 | var factory = new ResourceManagerStringLocalizerFactory(options, NullLoggerFactory.Instance); 17 | var type = typeof(TEntity); 18 | 19 | _localizer = factory.Create(type); 20 | } 21 | 22 | 23 | public static string GetValue(string field) 24 | { 25 | return _localizer[field]; 26 | } 27 | 28 | public static string GetValue(string field, CultureInfo cultureinfo) 29 | { 30 | using (new CultureScope(cultureinfo)) 31 | { 32 | return GetValue(field); 33 | } 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Shared/Shared.Language/Shared.Language.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | WebPersonal.Shared.Language 6 | true 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | --------------------------------------------------------------------------------