diff --git a/Bedelia/.dockerignore b/Bedelia/.dockerignore new file mode 100644 index 0000000000000000000000000000000000000000..2e9693ed1a9619a49dcd4c7f3e9ea26541ff27d7 --- /dev/null +++ b/Bedelia/.dockerignore @@ -0,0 +1,2 @@ +obj +bin \ No newline at end of file diff --git a/Bedelia/Bedelia.csproj b/Bedelia/Bedelia.csproj new file mode 100644 index 0000000000000000000000000000000000000000..e107ff40f0cf5a3da92284e01c9a03c54dc97595 --- /dev/null +++ b/Bedelia/Bedelia.csproj @@ -0,0 +1,18 @@ +<Project Sdk="Microsoft.NET.Sdk.Web"> + + <PropertyGroup> + <TargetFramework>net5.0</TargetFramework> + </PropertyGroup> + + <ItemGroup> + <PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="5.0.0" NoWarn="NU1605" /> + <PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="5.0.0" NoWarn="NU1605" /> + <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="5.0.0"> + <PrivateAssets>all</PrivateAssets> + <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> + </PackageReference> + <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="5.0.0" /> + <PackageReference Include="Swashbuckle.AspNetCore" Version="5.6.3" /> + </ItemGroup> + +</Project> diff --git a/Bedelia/Bedelia.csproj.user b/Bedelia/Bedelia.csproj.user new file mode 100644 index 0000000000000000000000000000000000000000..da34dfb3c7dfbdf3398ff0bdb2f5d654da7d56a9 --- /dev/null +++ b/Bedelia/Bedelia.csproj.user @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <PropertyGroup> + <Controller_SelectedScaffolderID>ApiControllerWithActionsScaffolder</Controller_SelectedScaffolderID> + <Controller_SelectedScaffolderCategoryPath>root/Common/Api</Controller_SelectedScaffolderCategoryPath> + <WebStackScaffolding_ControllerDialogWidth>600</WebStackScaffolding_ControllerDialogWidth> + </PropertyGroup> +</Project> \ No newline at end of file diff --git a/Bedelia/Bedelia.sln b/Bedelia/Bedelia.sln new file mode 100644 index 0000000000000000000000000000000000000000..67548d1d30f7b5f4772d28987a29323225a72553 --- /dev/null +++ b/Bedelia/Bedelia.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30717.126 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bedelia", "Bedelia.csproj", "{BB028517-233A-466B-A46C-420FC942BA48}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {BB028517-233A-466B-A46C-420FC942BA48}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BB028517-233A-466B-A46C-420FC942BA48}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BB028517-233A-466B-A46C-420FC942BA48}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BB028517-233A-466B-A46C-420FC942BA48}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {53DA0290-A5A4-422B-A6DD-837A8FFCF354} + EndGlobalSection +EndGlobal diff --git a/Bedelia/Controllers/CoursesController.cs b/Bedelia/Controllers/CoursesController.cs new file mode 100644 index 0000000000000000000000000000000000000000..56e4e8da60d9f5a601fd17532cc672a8a2fc0ee7 --- /dev/null +++ b/Bedelia/Controllers/CoursesController.cs @@ -0,0 +1,151 @@ +using Bedelia.Dtos; +using Bedelia.EF; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Bedelia.Controllers +{ + [Route("api/[controller]")] + [ApiController] + public class CoursesController : ControllerBase + { + private readonly BedeliaContext _context; + + public CoursesController(BedeliaContext context) + { + _context = context; + } + + [HttpGet] + public async Task<IActionResult> Get() + { + var courses = await _context.Courses.ToListAsync(); + return Ok(courses); + } + + [HttpGet("{name}")] + public async Task<IActionResult> Get(string name) + { + var course = await _context.Courses.FirstOrDefaultAsync(x => x.Name == name); + + if (course == null) + { + return BadRequest(string.Format("el curso con nombre {0} no existe", name)); + } + + return Ok(course); + } + + [HttpPost] + public async Task<IActionResult> Post([FromBody] string courseName) + { + var course = new Course() + { + Name = courseName + }; + + _context.Courses.Add(course); + await _context.SaveChangesAsync(); + + return Ok(course); + } + + [HttpPost("loadData")] + public async Task<IActionResult> Post([FromBody] List<string> courseNames) + { + var dbCourses = await _context.Courses + .Where(x=>courseNames.Contains(x.Name)) + .ToListAsync(); + + var courses = new List<Course>(); + foreach (var courseName in courseNames) + { + var dbCourse = dbCourses.FirstOrDefault(x => x.Name == courseName); + if (dbCourse == null) + { + courses.Add(new Course() + { + Name = courseName + }); + } + } + + _context.Courses.AddRange(courses); + await _context.SaveChangesAsync(); + + return Ok(courses); + } + + [HttpPut("{id}")] + public async Task<IActionResult> Put(int id, [FromBody] Course course) + { + var dbCourse = await _context.Courses.FirstOrDefaultAsync(x => x.Id == id); + + if (dbCourse == null) + { + return BadRequest(string.Format("el curso con id {0} no existe en bedelia", id)); + } + + dbCourse.Name = course.Name; + await _context.SaveChangesAsync(); + + return Ok(dbCourse); + } + + [HttpDelete("{id}")] + public async Task<IActionResult> Delete(int id) + { + var course = await _context.Courses.FirstOrDefaultAsync(x => x.Id == id); + + _context.Courses.Remove(course); + await _context.SaveChangesAsync(); + + return Ok(); + } + + [HttpPost("closeRecord")] + public async Task<IActionResult> CloseRecord(CloseRecordDto closeRecord) + { + var now = DateTime.Now; + var course = await _context.Courses + .FirstOrDefaultAsync(x => x.Name == closeRecord.CourseName); + + if (course == null) + { + return BadRequest(string.Format(@"No existe el curso con nombre {0} en bedelia", closeRecord.CourseName)); + } + + var userIdentityCards = closeRecord.UserGrades.Select(x => x.IdentityCard); + + var users = await _context.Users + .Where(x => userIdentityCards.Contains(x.IdentityCard)) + .ToListAsync(); + + var userCourses = new List<UserCourse>(); + foreach (var user in users) + { + var grade = closeRecord.UserGrades + .Where(x => x.IdentityCard == user.IdentityCard) + .Select(x => x.Grade) + .FirstOrDefault(); + + userCourses.Add(new UserCourse() + { + CourseId = course.Id, + UserId = user.Id, + Grade = grade, + GradeDate = now + }); + } + + _context.UserCourses.AddRange(userCourses); + await _context.SaveChangesAsync(); + + return Ok(); + } + } +} diff --git a/Bedelia/Controllers/UsersController.cs b/Bedelia/Controllers/UsersController.cs new file mode 100644 index 0000000000000000000000000000000000000000..3a384a373dccfb51aa1fab7f5af02d77dcb99e7f --- /dev/null +++ b/Bedelia/Controllers/UsersController.cs @@ -0,0 +1,139 @@ +using Bedelia.Dtos; +using Bedelia.EF; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Bedelia.Controllers +{ + [Route("api/[controller]")] + [ApiController] + public class UsersController : ControllerBase + { + private readonly BedeliaContext _context; + + public UsersController(BedeliaContext context) + { + _context = context; + } + + [HttpGet] + public async Task<IActionResult> Get() + { + var users = await _context.Users.ToListAsync(); + return Ok(users); + } + + [HttpGet("{identityCard}")] + public async Task<IActionResult> Get(string identityCard) + { + var user = await _context.Users.FirstOrDefaultAsync(x => x.IdentityCard == identityCard); + + if (user == null) + { + return BadRequest(string.Format("el usuario con cedula {0} no existe en bedelia", identityCard)); + } + + return Ok(user); + } + + [HttpPost] + public async Task<IActionResult> Post([FromBody] UserCreateDto userDto) + { + var user = new User() + { + IdentityCard = userDto.IdentityCard, + Name = userDto.Name, + Password = userDto.Password + }; + + _context.Users.Add(user); + await _context.SaveChangesAsync(); + + return Ok(user); + } + + [HttpPost("loadData")] + public async Task<IActionResult> Post([FromBody] List<UserCreateDto> userDtos) + { + var userIdentityCards = userDtos.Select(x => x.IdentityCard); + + var dbUsers = await _context.Users + .Where(x => userIdentityCards.Contains(x.IdentityCard)) + .ToListAsync(); + + var users = new List<User>(); + foreach (var userDto in userDtos) + { + var dbUser = dbUsers.FirstOrDefault(x => x.IdentityCard == userDto.IdentityCard); + if (dbUser == null) + { + users.Add(new User() + { + IdentityCard = userDto.IdentityCard, + Name = userDto.Name, + Password = userDto.Password + }); + } + } + + _context.Users.AddRange(users); + await _context.SaveChangesAsync(); + + return Ok(users); + } + + [HttpPut("{id}")] + public async Task<IActionResult> Put(int id, [FromBody] User user) + { + var dbUser = await _context.Users.FirstOrDefaultAsync(x => x.Id == id); + + if (dbUser == null) + { + return BadRequest(string.Format("el usuario con id {0} no existe en bedelia", id)); + } + + dbUser.IdentityCard = user.IdentityCard; + dbUser.Name = user.Name; + dbUser.Password = user.Password; + + await _context.SaveChangesAsync(); + + return Ok(dbUser); + } + + [HttpDelete("{id}")] + public async Task<IActionResult> Delete(int id) + { + var user = await _context.Users.FirstOrDefaultAsync(x => x.Id == id); + + _context.Users.Remove(user); + await _context.SaveChangesAsync(); + + return Ok(); + } + + [HttpGet("userNotes")] + public async Task<IActionResult> UserNotes() + { + var result = await (from user in _context.Users + join userCourse in _context.UserCourses + on user.Id equals userCourse.UserId + join course in _context.Courses + on userCourse.CourseId equals course.Id + select new + { + User = user.IdentityCard, + Course = course.Name, + Grade = userCourse.Grade, + GradeDate = userCourse.GradeDate + }).ToListAsync(); + + return Ok(result); + } + + } +} diff --git a/Bedelia/Dockerfile b/Bedelia/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..b4f2d55aa9108c20dcde071e65b879717b85d25f --- /dev/null +++ b/Bedelia/Dockerfile @@ -0,0 +1,16 @@ +FROM mcr.microsoft.com/dotnet/sdk:5.0 AS build +WORKDIR /app + +WORKDIR /src +COPY *.csproj ./ +RUN dotnet restore ./Bedelia.csproj + +COPY . ./ +WORKDIR /src +RUN dotnet publish -c release -o /app/publish + +FROM mcr.microsoft.com/dotnet/sdk:5.0 +WORKDIR /app +COPY --from=build /app/publish . +EXPOSE 5000 +ENTRYPOINT ["dotnet", "Bedelia.dll"] \ No newline at end of file diff --git a/Bedelia/Dtos/CloseRecordDto.cs b/Bedelia/Dtos/CloseRecordDto.cs new file mode 100644 index 0000000000000000000000000000000000000000..0b308b8185efc01ec83f2f6851987bad2f02aac9 --- /dev/null +++ b/Bedelia/Dtos/CloseRecordDto.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; + +namespace Bedelia.Dtos +{ + public class CloseRecordDto + { + [Required] + public string CourseName { get; set; } + + [Required] + public List<UserGradeDto> UserGrades { get; set; } + } +} diff --git a/Bedelia/Dtos/CourseCreateDto.cs b/Bedelia/Dtos/CourseCreateDto.cs new file mode 100644 index 0000000000000000000000000000000000000000..65119ee161ce38b9dcb7a26abad126092c2d99ff --- /dev/null +++ b/Bedelia/Dtos/CourseCreateDto.cs @@ -0,0 +1,10 @@ +using System.ComponentModel.DataAnnotations; + +namespace Bedelia +{ + public class CourseCreateDto + { + [Required] + public string Name { get; set; } + } +} diff --git a/Bedelia/Dtos/UserCreateDto.cs b/Bedelia/Dtos/UserCreateDto.cs new file mode 100644 index 0000000000000000000000000000000000000000..7c6cca67f99a53993111f7b2ea970f68e0884aae --- /dev/null +++ b/Bedelia/Dtos/UserCreateDto.cs @@ -0,0 +1,16 @@ +using System.ComponentModel.DataAnnotations; + +namespace Bedelia.Dtos +{ + public class UserCreateDto + { + [Required] + public string IdentityCard { get; set; } + + [Required] + public string Name { get; set; } + + [Required] + public string Password { get; set; } + } +} diff --git a/Bedelia/Dtos/UserGradeDto.cs b/Bedelia/Dtos/UserGradeDto.cs new file mode 100644 index 0000000000000000000000000000000000000000..e113b38530e4a7a936e74f9202bd8b4d900cb268 --- /dev/null +++ b/Bedelia/Dtos/UserGradeDto.cs @@ -0,0 +1,13 @@ +using System.ComponentModel.DataAnnotations; + +namespace Bedelia.Dtos +{ + public class UserGradeDto + { + [Required] + public string IdentityCard { get; set; } + + [Required] + public int Grade { get; set; } + } +} diff --git a/Bedelia/EF/BedeliaContext.cs b/Bedelia/EF/BedeliaContext.cs new file mode 100644 index 0000000000000000000000000000000000000000..3104af5b6de497e058331952b06f52096df92364 --- /dev/null +++ b/Bedelia/EF/BedeliaContext.cs @@ -0,0 +1,65 @@ +using Microsoft.EntityFrameworkCore; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Bedelia.EF +{ + public class BedeliaContext : DbContext + { + public DbSet<User> Users { get; set; } + public DbSet<Course> Courses { get; set; } + public DbSet<UserCourse> UserCourses { get; set; } + + public BedeliaContext(DbContextOptions options) : base(options) { } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity<User>(e => + { + e.HasKey(x => x.Id); + + e.HasIndex(x => x.IdentityCard) + .IsUnique(); + + e.HasIndex(x => x.Name) + .IsUnique(); + + e.Property(x => x.IdentityCard) + .IsRequired() + .HasMaxLength(50); + + e.Property(x => x.Name) + .IsRequired() + .HasMaxLength(50); + + e.Property(x => x.Password) + .IsRequired() + .HasMaxLength(255); + }); + + modelBuilder.Entity<Course>(e => + { + e.HasKey(x => x.Id); + + e.Property(x => x.Name) + .IsRequired() + .HasMaxLength(50); + }); + + modelBuilder.Entity<UserCourse>(e => + { + e.HasKey(x => new { x.UserId, x.CourseId }); + + e.HasOne(x => x.Course) + .WithMany(x => x.UserCourses) + .HasForeignKey(x => x.CourseId); + + e.HasOne(x => x.User) + .WithMany(x => x.UserCourses) + .HasForeignKey(x => x.UserId); + }); + } + } +} diff --git a/Bedelia/EF/Course.cs b/Bedelia/EF/Course.cs new file mode 100644 index 0000000000000000000000000000000000000000..65a6b5a6bf1e37b5204881a5a82e169190aa65a1 --- /dev/null +++ b/Bedelia/EF/Course.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Bedelia.EF +{ + public class Course + { + public int Id { get; set; } + public string Name { get; set; } + + public ICollection<UserCourse> UserCourses { get; set; } + } +} diff --git a/Bedelia/EF/User.cs b/Bedelia/EF/User.cs new file mode 100644 index 0000000000000000000000000000000000000000..4cfc40133510e9343554ac796aa152f1c95e8418 --- /dev/null +++ b/Bedelia/EF/User.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Bedelia.EF +{ + public class User + { + public int Id { get; set; } + public string IdentityCard { get; set; } + public string Name { get; set; } + public string Password { get; set; } + + public ICollection<UserCourse> UserCourses { get; set; } + } +} diff --git a/Bedelia/EF/UserCourse.cs b/Bedelia/EF/UserCourse.cs new file mode 100644 index 0000000000000000000000000000000000000000..0b52e7c4c4d3895fe88ec952b8cb5c91b6d8b8c8 --- /dev/null +++ b/Bedelia/EF/UserCourse.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Bedelia.EF +{ + public class UserCourse + { + public int UserId { get; set; } + public int CourseId { get; set; } + public int Grade { get; set; } + public DateTime GradeDate { get; set; } + + public User User { get; set; } + public Course Course { get; set; } + } +} diff --git a/Bedelia/Migrations/20201128224417_initial.Designer.cs b/Bedelia/Migrations/20201128224417_initial.Designer.cs new file mode 100644 index 0000000000000000000000000000000000000000..1879039ae8918655176d06d108b0ce4de436ded4 --- /dev/null +++ b/Bedelia/Migrations/20201128224417_initial.Designer.cs @@ -0,0 +1,58 @@ +// <auto-generated /> +using Bedelia.EF; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +namespace Bedelia.Migrations +{ + [DbContext(typeof(BedeliaContext))] + [Migration("20201128224417_initial")] + partial class initial + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityByDefaultColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 63) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.Entity("Bedelia.EF.User", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .UseIdentityByDefaultColumn(); + + b.Property<string>("IdentityCard") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property<string>("Password") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property<string>("Username") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.HasKey("Id"); + + b.HasIndex("IdentityCard") + .IsUnique(); + + b.HasIndex("Username") + .IsUnique(); + + b.ToTable("Users"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Bedelia/Migrations/20201128224417_initial.cs b/Bedelia/Migrations/20201128224417_initial.cs new file mode 100644 index 0000000000000000000000000000000000000000..9dffea5c79d1ac3380c3e3b88fa4c9d3f8d3d385 --- /dev/null +++ b/Bedelia/Migrations/20201128224417_initial.cs @@ -0,0 +1,44 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +namespace Bedelia.Migrations +{ + public partial class initial : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Users", + columns: table => new + { + Id = table.Column<int>(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + IdentityCard = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: false), + Username = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: false), + Password = table.Column<string>(type: "character varying(255)", maxLength: 255, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Users", x => x.Id); + }); + + migrationBuilder.CreateIndex( + name: "IX_Users_IdentityCard", + table: "Users", + column: "IdentityCard", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_Users_Username", + table: "Users", + column: "Username", + unique: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Users"); + } + } +} diff --git a/Bedelia/Migrations/20201128235147_courses.Designer.cs b/Bedelia/Migrations/20201128235147_courses.Designer.cs new file mode 100644 index 0000000000000000000000000000000000000000..05bcced32a524d9b4a8d5d063ff5f067eaf6589e --- /dev/null +++ b/Bedelia/Migrations/20201128235147_courses.Designer.cs @@ -0,0 +1,126 @@ +// <auto-generated /> +using System; +using Bedelia.EF; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +namespace Bedelia.Migrations +{ + [DbContext(typeof(BedeliaContext))] + [Migration("20201128235147_courses")] + partial class courses + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityByDefaultColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 63) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.Entity("Bedelia.EF.Course", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .UseIdentityByDefaultColumn(); + + b.Property<string>("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.HasKey("Id"); + + b.ToTable("Courses"); + }); + + modelBuilder.Entity("Bedelia.EF.User", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .UseIdentityByDefaultColumn(); + + b.Property<string>("IdentityCard") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property<string>("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property<string>("Password") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("IdentityCard") + .IsUnique(); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Bedelia.EF.UserCourse", b => + { + b.Property<int>("UserId") + .HasColumnType("integer"); + + b.Property<int>("CourseId") + .HasColumnType("integer"); + + b.Property<int>("Grade") + .HasColumnType("integer"); + + b.Property<DateTime>("GradeDate") + .HasColumnType("timestamp without time zone"); + + b.HasKey("UserId", "CourseId"); + + b.HasIndex("CourseId"); + + b.ToTable("UserCourses"); + }); + + modelBuilder.Entity("Bedelia.EF.UserCourse", b => + { + b.HasOne("Bedelia.EF.Course", "Course") + .WithMany("UserCourses") + .HasForeignKey("CourseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bedelia.EF.User", "User") + .WithMany("UserCourses") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Course"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bedelia.EF.Course", b => + { + b.Navigation("UserCourses"); + }); + + modelBuilder.Entity("Bedelia.EF.User", b => + { + b.Navigation("UserCourses"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Bedelia/Migrations/20201128235147_courses.cs b/Bedelia/Migrations/20201128235147_courses.cs new file mode 100644 index 0000000000000000000000000000000000000000..8203cc103960bf9eebbdd683ddc2f5c92e26304e --- /dev/null +++ b/Bedelia/Migrations/20201128235147_courses.cs @@ -0,0 +1,85 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +namespace Bedelia.Migrations +{ + public partial class courses : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.RenameColumn( + name: "Username", + table: "Users", + newName: "Name"); + + migrationBuilder.RenameIndex( + name: "IX_Users_Username", + table: "Users", + newName: "IX_Users_Name"); + + migrationBuilder.CreateTable( + name: "Courses", + columns: table => new + { + Id = table.Column<int>(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + Name = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Courses", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "UserCourses", + columns: table => new + { + UserId = table.Column<int>(type: "integer", nullable: false), + CourseId = table.Column<int>(type: "integer", nullable: false), + Grade = table.Column<int>(type: "integer", nullable: false), + GradeDate = table.Column<DateTime>(type: "timestamp without time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_UserCourses", x => new { x.UserId, x.CourseId }); + table.ForeignKey( + name: "FK_UserCourses_Courses_CourseId", + column: x => x.CourseId, + principalTable: "Courses", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_UserCourses_Users_UserId", + column: x => x.UserId, + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_UserCourses_CourseId", + table: "UserCourses", + column: "CourseId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "UserCourses"); + + migrationBuilder.DropTable( + name: "Courses"); + + migrationBuilder.RenameColumn( + name: "Name", + table: "Users", + newName: "Username"); + + migrationBuilder.RenameIndex( + name: "IX_Users_Name", + table: "Users", + newName: "IX_Users_Username"); + } + } +} diff --git a/Bedelia/Migrations/BedeliaContextModelSnapshot.cs b/Bedelia/Migrations/BedeliaContextModelSnapshot.cs new file mode 100644 index 0000000000000000000000000000000000000000..4e00a716072e9bcf6ffb73e32de70f318c6d7c8c --- /dev/null +++ b/Bedelia/Migrations/BedeliaContextModelSnapshot.cs @@ -0,0 +1,124 @@ +// <auto-generated /> +using System; +using Bedelia.EF; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +namespace Bedelia.Migrations +{ + [DbContext(typeof(BedeliaContext))] + partial class BedeliaContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityByDefaultColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 63) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.Entity("Bedelia.EF.Course", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .UseIdentityByDefaultColumn(); + + b.Property<string>("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.HasKey("Id"); + + b.ToTable("Courses"); + }); + + modelBuilder.Entity("Bedelia.EF.User", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .UseIdentityByDefaultColumn(); + + b.Property<string>("IdentityCard") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property<string>("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property<string>("Password") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("IdentityCard") + .IsUnique(); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Bedelia.EF.UserCourse", b => + { + b.Property<int>("UserId") + .HasColumnType("integer"); + + b.Property<int>("CourseId") + .HasColumnType("integer"); + + b.Property<int>("Grade") + .HasColumnType("integer"); + + b.Property<DateTime>("GradeDate") + .HasColumnType("timestamp without time zone"); + + b.HasKey("UserId", "CourseId"); + + b.HasIndex("CourseId"); + + b.ToTable("UserCourses"); + }); + + modelBuilder.Entity("Bedelia.EF.UserCourse", b => + { + b.HasOne("Bedelia.EF.Course", "Course") + .WithMany("UserCourses") + .HasForeignKey("CourseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bedelia.EF.User", "User") + .WithMany("UserCourses") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Course"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bedelia.EF.Course", b => + { + b.Navigation("UserCourses"); + }); + + modelBuilder.Entity("Bedelia.EF.User", b => + { + b.Navigation("UserCourses"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Bedelia/Program.cs b/Bedelia/Program.cs new file mode 100644 index 0000000000000000000000000000000000000000..eaca41ffc496e6b136a31e9421f64d4e5797bba8 --- /dev/null +++ b/Bedelia/Program.cs @@ -0,0 +1,27 @@ +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Bedelia +{ + public class Program + { + public static void Main(string[] args) + { + CreateHostBuilder(args).Build().Run(); + } + + public static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .ConfigureWebHostDefaults(webBuilder => + { + webBuilder.UseStartup<Startup>(); + webBuilder.UseUrls("http://*:5000/"); + }); + } +} diff --git a/Bedelia/Properties/launchSettings.json b/Bedelia/Properties/launchSettings.json new file mode 100644 index 0000000000000000000000000000000000000000..f66813ed228f570421e7108adf6e4ec5b748833a --- /dev/null +++ b/Bedelia/Properties/launchSettings.json @@ -0,0 +1,31 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:55438", + "sslPort": 0 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "Bedelia": { + "commandName": "Project", + "dotnetRunMessages": "true", + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "http://localhost:5000", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/Bedelia/Startup.cs b/Bedelia/Startup.cs new file mode 100644 index 0000000000000000000000000000000000000000..f170f55c9a870dedf2ab815e79f9e651f79114f3 --- /dev/null +++ b/Bedelia/Startup.cs @@ -0,0 +1,70 @@ +using Bedelia.EF; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Microsoft.OpenApi.Models; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Bedelia +{ + public class Startup + { + public Startup(IConfiguration configuration, IWebHostEnvironment env) + { + Configuration = configuration; + _env = env; + } + + public IConfiguration Configuration { get; } + private readonly IWebHostEnvironment _env; + + // This method gets called by the runtime. Use this method to add services to the container. + public void ConfigureServices(IServiceCollection services) + { + + services.AddControllers(); + services.AddSwaggerGen(c => + { + c.SwaggerDoc("v1", new OpenApiInfo { Title = "Bedelia", Version = "v1" }); + }); + + var postgreSqlSection = _env.IsProduction() ? "PostgreSqlCloud" : "PostgreSql"; + + services.AddDbContext<BedeliaContext>(x => x.UseNpgsql( + Configuration.GetConnectionString(postgreSqlSection))); + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IWebHostEnvironment env, BedeliaContext context) + { + context.Database.Migrate(); + + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + + app.UseSwagger(); + app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "Bedelia v1")); + + app.UseRouting(); + + app.UseCors(x => x.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader()); + + app.UseAuthorization(); + + app.UseEndpoints(endpoints => + { + endpoints.MapControllers(); + }); + } + } +} diff --git a/Bedelia/appsettings.Development.json b/Bedelia/appsettings.Development.json new file mode 100644 index 0000000000000000000000000000000000000000..8983e0fc1c5e2795ccfde0c771c6d66c88ef4a42 --- /dev/null +++ b/Bedelia/appsettings.Development.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + } +} diff --git a/Bedelia/appsettings.json b/Bedelia/appsettings.json new file mode 100644 index 0000000000000000000000000000000000000000..048c2f6d1e366b10da59de510dab3ecb86125fba --- /dev/null +++ b/Bedelia/appsettings.json @@ -0,0 +1,14 @@ +{ + "ConnectionStrings": { + "PostgreSql": "Host=localhost;Database=bedelia;Username=postgres;Password=111111", + "PostgreSqlCloud": "Host=db;Database=bedelia;Username=postgres;Password=postgres" + }, + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + }, + "AllowedHosts": "*" +} diff --git a/Bedelia/docker-compose.yml b/Bedelia/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..430f5afafaa0bd15f5434c45042321deec30bfe4 --- /dev/null +++ b/Bedelia/docker-compose.yml @@ -0,0 +1,25 @@ +version: '3.8' + +services: + bedelia: + image: esantangelo/bedelia + container_name: bedelia + depends_on: + - db + ports: + - 80:5000 + restart: on-failure + + db: + image: postgres + container_name: db + environment: + POSTGRES_PASSWORD: postgres + ports: + - 5432:5432 + volumes: + - db-data:/var/lib/postgresql/data + restart: on-failure + +volumes: + db-data: \ No newline at end of file diff --git a/Tsi1.Api/Tsi1.Api/Controllers/CourseController.cs b/Tsi1.Api/Tsi1.Api/Controllers/CourseController.cs index 3778365e1fe2c75f2b53f505f789767a2e7cfac3..dd4de595a500ee8c7db2d63f7ea44554be2c910b 100644 --- a/Tsi1.Api/Tsi1.Api/Controllers/CourseController.cs +++ b/Tsi1.Api/Tsi1.Api/Controllers/CourseController.cs @@ -16,10 +16,14 @@ namespace Tsi1.Api.Controllers public class CourseController : ControllerBase { private readonly ICourseService _courseService; + private readonly IUserService _userService; + private readonly IBedeliaService _bedeliaService; - public CourseController(ICourseService courseService) + public CourseController(ICourseService courseService, IUserService userService, IBedeliaService bedeliaService) { _courseService = courseService; + _userService = userService; + _bedeliaService = bedeliaService; } [Authorize(Roles = UserTypes.Student + ", " + UserTypes.Professor)] @@ -54,15 +58,20 @@ namespace Tsi1.Api.Controllers return Ok(result.Data); } - [Authorize(Roles = UserTypes.Student)] [HttpPost("Matriculate/{courseId}")] public async Task<IActionResult> Matriculate(int courseId) { var userId = int.Parse(HttpContext.User.Claims.FirstOrDefault(x => x.Type == "Id").Value); + var user = await _userService.GetById(userId); + + var response = await _bedeliaService.IsValidUser(user.Data.Student.IdentityCard); + if (response.HasError) + { + return BadRequest(response.Message); + } var result = await _courseService.Matriculate(userId, courseId); - if (result.HasError) { return BadRequest(result.Message); @@ -245,5 +254,33 @@ namespace Tsi1.Api.Controllers return Ok(result.Data); } + + [Authorize(Roles = UserTypes.Professor)] + [HttpPost("CloseRecord/{courseId}")] + public async Task<IActionResult> CloseRecord(int courseId) + { + var courseResult = await _courseService.GetById(courseId); + if (courseResult.HasError) + { + return BadRequest(courseResult.Message); + } + + var userResult = await _userService.GetUserGrades(courseId); + if (userResult.HasError) + { + return BadRequest(userResult.Message); + } + + var courseName = courseResult.Data.Name; + var userGrades = userResult.Data; + + var result = await _bedeliaService.CloseRecord(courseName, userGrades); + if (result.HasError) + { + return BadRequest(result.Message); + } + + return Ok(); + } } } diff --git a/Tsi1.Api/Tsi1.Api/Startup.cs b/Tsi1.Api/Tsi1.Api/Startup.cs index d27fa6ae2007f1b94d51e98e5f22d00c440ed32d..5718b4a826b7bb99f6b3183540f37e2dfb3855f4 100644 --- a/Tsi1.Api/Tsi1.Api/Startup.cs +++ b/Tsi1.Api/Tsi1.Api/Startup.cs @@ -46,17 +46,27 @@ namespace Tsi1.Api { string postgreSqlSection; string mongoDbSection; + string bedeliaBaseUrlSection; + if (_env.IsProduction()) { postgreSqlSection = "PostgreSqlCloud"; mongoDbSection = "Tsi1DatabaseSettingsCloud"; + bedeliaBaseUrlSection = "BedeliaBaseUrlCloud"; } else { postgreSqlSection = "PostgreSql"; mongoDbSection = "Tsi1DatabaseSettings"; + bedeliaBaseUrlSection = "BedeliaBaseUrl"; } + var bedeliaBaseUrl = Configuration.GetSection(bedeliaBaseUrlSection).Value; + services.AddHttpClient<IBedeliaService, BedeliaService>(configureClient => + { + configureClient.BaseAddress = new Uri(bedeliaBaseUrl); + }); + services.AddControllers(); services.AddSignalR(); diff --git a/Tsi1.Api/Tsi1.Api/appsettings.json b/Tsi1.Api/Tsi1.Api/appsettings.json index aeffabd1acc0a4a5296e376e85c35a1114d2770e..d77c42f3c0bdefb6c97c4c38fd5163408ae997c3 100644 --- a/Tsi1.Api/Tsi1.Api/appsettings.json +++ b/Tsi1.Api/Tsi1.Api/appsettings.json @@ -28,6 +28,8 @@ "Host": "smtp.gmail.com", "Port": 587 }, + "BedeliaBaseUrl": "http://localhost:55438/", + "BedeliaBaseUrlCloud": "http://tsi-bedelia.web.elasticloud.uy/", "Logging": { "LogLevel": { "Default": "Information", diff --git a/Tsi1.Api/Tsi1.BusinessLayer/Dtos/CloseRecordDto.cs b/Tsi1.Api/Tsi1.BusinessLayer/Dtos/CloseRecordDto.cs new file mode 100644 index 0000000000000000000000000000000000000000..f0964b6f3c0480dd9452a2dd4ba25ef130a7a7b2 --- /dev/null +++ b/Tsi1.Api/Tsi1.BusinessLayer/Dtos/CloseRecordDto.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Tsi1.BusinessLayer.Dtos +{ + public class CloseRecordDto + { + public string CourseName { get; set; } + public List<UserGradeDto> UserGrades { get; set; } + } +} diff --git a/Tsi1.Api/Tsi1.BusinessLayer/Dtos/UserGradeDto.cs b/Tsi1.Api/Tsi1.BusinessLayer/Dtos/UserGradeDto.cs new file mode 100644 index 0000000000000000000000000000000000000000..8018e6035c1f02da0609ef8a71a4bedcef5f4201 --- /dev/null +++ b/Tsi1.Api/Tsi1.BusinessLayer/Dtos/UserGradeDto.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Tsi1.BusinessLayer.Dtos +{ + public class UserGradeDto + { + public string IdentityCard { get; set; } + public int Grade { get; set; } + } +} diff --git a/Tsi1.Api/Tsi1.BusinessLayer/Helpers/ErrorMessages.cs b/Tsi1.Api/Tsi1.BusinessLayer/Helpers/ErrorMessages.cs index 1cffd0551686cd30f851493c7d64788b98fe0b2a..14da5d591e0f831251808c1033384f4b501a8d75 100644 --- a/Tsi1.Api/Tsi1.BusinessLayer/Helpers/ErrorMessages.cs +++ b/Tsi1.Api/Tsi1.BusinessLayer/Helpers/ErrorMessages.cs @@ -34,6 +34,7 @@ namespace Tsi1.BusinessLayer.Helpers public const string CourseDoesNotExist = "El curso con id '{0}' no existe"; public const string DuplicateCourseName = "Ya existe un curso con nombre '{0}'"; public const string CourseIsTemplate = "El curso con id '{0}' es un template"; + public const string CourseHasNoStudents = "El curso con id '{0}' no tiene estudiantes"; public const string TenantDoesNotExist = "La Facultad '{0}' no existe"; public const string DuplicateTenantName = "Ya existe una Facultad con nombre '{0}'"; diff --git a/Tsi1.Api/Tsi1.BusinessLayer/Interfaces/IBedeliaService.cs b/Tsi1.Api/Tsi1.BusinessLayer/Interfaces/IBedeliaService.cs new file mode 100644 index 0000000000000000000000000000000000000000..88a6f293779d4dd16356078565ec3010a95dfa37 --- /dev/null +++ b/Tsi1.Api/Tsi1.BusinessLayer/Interfaces/IBedeliaService.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; +using Tsi1.BusinessLayer.Dtos; +using Tsi1.BusinessLayer.Helpers; + +namespace Tsi1.BusinessLayer.Interfaces +{ + public interface IBedeliaService + { + Task<ServiceResult<bool>> IsValidUser(string identityCard); + + Task<ServiceResult<bool>> CloseRecord(string courseName, List<UserGradeDto> userGrades); + } +} diff --git a/Tsi1.Api/Tsi1.BusinessLayer/Interfaces/IUserService.cs b/Tsi1.Api/Tsi1.BusinessLayer/Interfaces/IUserService.cs index 1fa99023a8a11832ee567ce57fa10bbdfcf9286a..e3a89207701c5fb213da6b5365f9555bf446afca 100644 --- a/Tsi1.Api/Tsi1.BusinessLayer/Interfaces/IUserService.cs +++ b/Tsi1.Api/Tsi1.BusinessLayer/Interfaces/IUserService.cs @@ -34,5 +34,7 @@ namespace Tsi1.BusinessLayer.Interfaces Task<ServiceResult<List<UserPreviewDto>>> GetProfessors(int tenantId); Task<ServiceResult<List<UserPreviewDto>>> GetAdmins(int tenantId, string userType); + + Task<ServiceResult<List<UserGradeDto>>> GetUserGrades(int courseId); } } diff --git a/Tsi1.Api/Tsi1.BusinessLayer/Services/BedeliaService.cs b/Tsi1.Api/Tsi1.BusinessLayer/Services/BedeliaService.cs new file mode 100644 index 0000000000000000000000000000000000000000..cd7a08a470c3107dcb49d35dbbe0255cbe4e7dae --- /dev/null +++ b/Tsi1.Api/Tsi1.BusinessLayer/Services/BedeliaService.cs @@ -0,0 +1,61 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Http; +using System.Text; +using System.Threading.Tasks; +using Tsi1.BusinessLayer.Dtos; +using Tsi1.BusinessLayer.Helpers; +using Tsi1.BusinessLayer.Interfaces; + +namespace Tsi1.BusinessLayer.Services +{ + public class BedeliaService : IBedeliaService + { + private readonly HttpClient _httpClient; + + public BedeliaService(HttpClient httpClient) + { + _httpClient = httpClient; + } + + public async Task<ServiceResult<bool>> IsValidUser(string identityCard) + { + var result = new ServiceResult<bool>(); + + var response = await _httpClient.GetAsync($"api/Users/{identityCard}"); + if (response.StatusCode != HttpStatusCode.OK) + { + result.HasError = true; + var errorMessage = await response.Content.ReadAsStringAsync(); + result.AddMessage(errorMessage); + } + + return result; + } + + public async Task<ServiceResult<bool>> CloseRecord(string courseName, List<UserGradeDto> userGrades) + { + var result = new ServiceResult<bool>(); + var closeRecord = new CloseRecordDto() + { + CourseName = courseName, + UserGrades = userGrades + }; + + var jsonInString = JsonConvert.SerializeObject(closeRecord); + var model = new StringContent(jsonInString, Encoding.UTF8, "application/json"); + + var response = await _httpClient.PostAsync("api/Courses/closeRecord", model); + if (response.StatusCode != HttpStatusCode.OK) + { + result.HasError = true; + var errorMessage = await response.Content.ReadAsStringAsync(); + result.AddMessage(errorMessage); + } + + return result; + } + } +} diff --git a/Tsi1.Api/Tsi1.BusinessLayer/Services/UserService.cs b/Tsi1.Api/Tsi1.BusinessLayer/Services/UserService.cs index 1536b9635bc33adbf87e259503498e3eec976906..e2ad8bbba442fde1f299dccb7743c39ba443f4f2 100644 --- a/Tsi1.Api/Tsi1.BusinessLayer/Services/UserService.cs +++ b/Tsi1.Api/Tsi1.BusinessLayer/Services/UserService.cs @@ -358,5 +358,31 @@ namespace Tsi1.BusinessLayer.Services return result; } + + public async Task<ServiceResult<List<UserGradeDto>>> GetUserGrades(int courseId) + { + var result = new ServiceResult<List<UserGradeDto>>(); + + var course = await _context.Courses + .Include(x => x.StudentCourses) + .ThenInclude(x => x.Student) + .FirstOrDefaultAsync(x => x.Id == courseId); + + if (!course.StudentCourses.Any()) + { + result.HasError = true; + result.AddMessage(string.Format(ErrorMessages.CourseHasNoStudents, courseId)); + return result; + } + + // TODO: obtain the grade from StudentCourses + result.Data = course.StudentCourses.Select(x => new UserGradeDto() + { + Grade = 10, + IdentityCard = x.Student.IdentityCard + }).ToList(); + + return result; + } } } diff --git a/Tsi1.Api/Tsi1.BusinessLayer/Tsi1.BusinessLayer.csproj b/Tsi1.Api/Tsi1.BusinessLayer/Tsi1.BusinessLayer.csproj index a18735ed0c8c52887e85611f1b353106330f869f..d2c88dff5dce9ee2149d7e3b785110d593d0894d 100644 --- a/Tsi1.Api/Tsi1.BusinessLayer/Tsi1.BusinessLayer.csproj +++ b/Tsi1.Api/Tsi1.BusinessLayer/Tsi1.BusinessLayer.csproj @@ -11,6 +11,7 @@ <PackageReference Include="Microsoft.AspNetCore.Hosting.Abstractions" Version="2.2.0" /> <PackageReference Include="Microsoft.AspNetCore.Http.Features" Version="3.1.9" /> <PackageReference Include="MimeKit" Version="2.9.2" /> + <PackageReference Include="Newtonsoft.Json" Version="12.0.3" /> </ItemGroup> <ItemGroup>