From 1a01d0dd311266b5349d9121abb559b1f41ae26b Mon Sep 17 00:00:00 2001
From: Lucca Santangelo <luccasant95@gmail.com>
Date: Sat, 28 Nov 2020 21:27:09 -0300
Subject: [PATCH] bedelia project

---
 Bedelia/.dockerignore                         |   2 +
 Bedelia/Bedelia.csproj                        |  18 +++
 Bedelia/Bedelia.csproj.user                   |   8 +
 Bedelia/Bedelia.sln                           |  25 +++
 Bedelia/Controllers/CoursesController.cs      | 148 ++++++++++++++++++
 Bedelia/Controllers/UsersController.cs        | 139 ++++++++++++++++
 Bedelia/Dockerfile                            |  16 ++
 Bedelia/Dtos/CloseRecordDto.cs                |  14 ++
 Bedelia/Dtos/CourseCreateDto.cs               |  10 ++
 Bedelia/Dtos/UserCreateDto.cs                 |  16 ++
 Bedelia/Dtos/UserDto.cs                       |  13 ++
 Bedelia/EF/BedeliaContext.cs                  |  65 ++++++++
 Bedelia/EF/Course.cs                          |  15 ++
 Bedelia/EF/User.cs                            |  17 ++
 Bedelia/EF/UserCourse.cs                      |  18 +++
 .../20201128224417_initial.Designer.cs        |  58 +++++++
 Bedelia/Migrations/20201128224417_initial.cs  |  44 ++++++
 .../20201128235147_courses.Designer.cs        | 126 +++++++++++++++
 Bedelia/Migrations/20201128235147_courses.cs  |  85 ++++++++++
 .../Migrations/BedeliaContextModelSnapshot.cs | 124 +++++++++++++++
 Bedelia/Program.cs                            |  27 ++++
 Bedelia/Properties/launchSettings.json        |  31 ++++
 Bedelia/Startup.cs                            |  70 +++++++++
 Bedelia/appsettings.Development.json          |   9 ++
 Bedelia/appsettings.json                      |  14 ++
 Bedelia/docker-compose.yml                    |  25 +++
 26 files changed, 1137 insertions(+)
 create mode 100644 Bedelia/.dockerignore
 create mode 100644 Bedelia/Bedelia.csproj
 create mode 100644 Bedelia/Bedelia.csproj.user
 create mode 100644 Bedelia/Bedelia.sln
 create mode 100644 Bedelia/Controllers/CoursesController.cs
 create mode 100644 Bedelia/Controllers/UsersController.cs
 create mode 100644 Bedelia/Dockerfile
 create mode 100644 Bedelia/Dtos/CloseRecordDto.cs
 create mode 100644 Bedelia/Dtos/CourseCreateDto.cs
 create mode 100644 Bedelia/Dtos/UserCreateDto.cs
 create mode 100644 Bedelia/Dtos/UserDto.cs
 create mode 100644 Bedelia/EF/BedeliaContext.cs
 create mode 100644 Bedelia/EF/Course.cs
 create mode 100644 Bedelia/EF/User.cs
 create mode 100644 Bedelia/EF/UserCourse.cs
 create mode 100644 Bedelia/Migrations/20201128224417_initial.Designer.cs
 create mode 100644 Bedelia/Migrations/20201128224417_initial.cs
 create mode 100644 Bedelia/Migrations/20201128235147_courses.Designer.cs
 create mode 100644 Bedelia/Migrations/20201128235147_courses.cs
 create mode 100644 Bedelia/Migrations/BedeliaContextModelSnapshot.cs
 create mode 100644 Bedelia/Program.cs
 create mode 100644 Bedelia/Properties/launchSettings.json
 create mode 100644 Bedelia/Startup.cs
 create mode 100644 Bedelia/appsettings.Development.json
 create mode 100644 Bedelia/appsettings.json
 create mode 100644 Bedelia/docker-compose.yml

diff --git a/Bedelia/.dockerignore b/Bedelia/.dockerignore
new file mode 100644
index 0000000..2e9693e
--- /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 0000000..e107ff4
--- /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 0000000..da34dfb
--- /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 0000000..67548d1
--- /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 0000000..fb73982
--- /dev/null
+++ b/Bedelia/Controllers/CoursesController.cs
@@ -0,0 +1,148 @@
+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", 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 courseId = await _context.Courses
+                .Where(x => x.Name == closeRecord.CourseName)
+                .Select(x => x.Id)
+                .FirstOrDefaultAsync();
+
+            var userIdentityCards = closeRecord.Users.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.Users
+                    .Where(x => x.IdentityCard == user.IdentityCard)
+                    .Select(x => x.Grade)
+                    .FirstOrDefault();
+
+                userCourses.Add(new UserCourse()
+                {
+                    CourseId = courseId,
+                    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 0000000..bffe066
--- /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("{id}")]
+        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", 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", 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 0000000..b4f2d55
--- /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 0000000..3493fb6
--- /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<UserDto> Users { get; set; }
+    }
+}
diff --git a/Bedelia/Dtos/CourseCreateDto.cs b/Bedelia/Dtos/CourseCreateDto.cs
new file mode 100644
index 0000000..65119ee
--- /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 0000000..7c6cca6
--- /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/UserDto.cs b/Bedelia/Dtos/UserDto.cs
new file mode 100644
index 0000000..bd2f6a1
--- /dev/null
+++ b/Bedelia/Dtos/UserDto.cs
@@ -0,0 +1,13 @@
+using System.ComponentModel.DataAnnotations;
+
+namespace Bedelia.Dtos
+{
+    public class UserDto
+    {
+        [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 0000000..3104af5
--- /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 0000000..65a6b5a
--- /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 0000000..4cfc401
--- /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 0000000..0b52e7c
--- /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 0000000..1879039
--- /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 0000000..9dffea5
--- /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 0000000..05bcced
--- /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 0000000..8203cc1
--- /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 0000000..4e00a71
--- /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 0000000..eaca41f
--- /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 0000000..f66813e
--- /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 0000000..f170f55
--- /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 0000000..8983e0f
--- /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 0000000..048c2f6
--- /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 0000000..430f5af
--- /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
-- 
GitLab