From c08610b9fd558e35534b2bb9f4d2395da3b15932 Mon Sep 17 00:00:00 2001
From: Lucca Santangelo <luccasant95@gmail.com>
Date: Sat, 14 Nov 2020 17:17:36 -0300
Subject: [PATCH] course templates

---
 .../Tsi1.Api/Controllers/CourseController.cs  |  31 +-
 .../Dtos/CourseCreateDto.cs                   |   1 +
 .../Dtos/CourseModifyDto.cs                   |  13 +
 .../Dtos/CourseTemplateCreateDto.cs           |  12 +
 .../Helpers/ErrorMessages.cs                  |   3 +-
 .../Helpers/MappingProfile.cs                 |   7 +
 .../Interfaces/ICourseService.cs              |   9 +-
 .../Services/CourseService.cs                 |  66 +-
 Tsi1.Api/Tsi1.DataLayer/Entities/Course.cs    |   1 +
 .../CourseConfiguration.cs                    |   3 +
 ...20201112213633_course-template.Designer.cs | 611 ++++++++++++++++++
 .../20201112213633_course-template.cs         |  23 +
 .../Migrations/Tsi1ContextModelSnapshot.cs    |   3 +
 13 files changed, 762 insertions(+), 21 deletions(-)
 create mode 100644 Tsi1.Api/Tsi1.BusinessLayer/Dtos/CourseModifyDto.cs
 create mode 100644 Tsi1.Api/Tsi1.BusinessLayer/Dtos/CourseTemplateCreateDto.cs
 create mode 100644 Tsi1.Api/Tsi1.DataLayer/Migrations/20201112213633_course-template.Designer.cs
 create mode 100644 Tsi1.Api/Tsi1.DataLayer/Migrations/20201112213633_course-template.cs

diff --git a/Tsi1.Api/Tsi1.Api/Controllers/CourseController.cs b/Tsi1.Api/Tsi1.Api/Controllers/CourseController.cs
index 4a8d4b8..22545ba 100644
--- a/Tsi1.Api/Tsi1.Api/Controllers/CourseController.cs
+++ b/Tsi1.Api/Tsi1.Api/Controllers/CourseController.cs
@@ -170,10 +170,25 @@ namespace Tsi1.Api.Controllers
             return Ok(result.Data);
         }
 
+        [Authorize(Roles = UserTypes.FacultyAdmin)]
+        [HttpGet("GetAllTemplates")]
+        public async Task<IActionResult> GetAllTemplates()
+        {
+            var tenantId = int.Parse(HttpContext.User.Claims.FirstOrDefault(x => x.Type == "TenantId").Value);
+
+            var result = await _courseService.GetAll(tenantId, true);
+            if (result.HasError)
+            {
+                return BadRequest(result.Message);
+            }
+
+            return Ok(result.Data);
+        }
+
 
         [Authorize(Roles = UserTypes.Professor + ", " + UserTypes.FacultyAdmin)]
         [HttpPut("Modify/{courseId}")]
-        public async Task<IActionResult> Modify(int courseId, CourseCreateDto courseDto)
+        public async Task<IActionResult> Modify(int courseId, CourseModifyDto courseDto)
         {
             var tenantId = int.Parse(HttpContext.User.Claims.FirstOrDefault(x => x.Type == "TenantId").Value);
             courseDto.TenantId = tenantId;
@@ -238,5 +253,19 @@ namespace Tsi1.Api.Controllers
             return Ok(result.Data);
         }
 
+        [AllowAnonymous]
+        [Authorize(Roles = UserTypes.FacultyAdmin)]
+        [HttpPost("CreateFromTemplate")]
+        public async Task<IActionResult> CreateFromTemplate(CourseTemplateCreateDto courseTemplate)
+        {
+            var tenantId = int.Parse(HttpContext.User.Claims.FirstOrDefault(x => x.Type == "TenantId").Value);
+
+            var result = await _courseService.CreateFromTemplate(courseTemplate, tenantId);
+            if (result.HasError)
+            {
+                return BadRequest(result.Message);
+            }
+            return Ok();
+        }
     }
 }
diff --git a/Tsi1.Api/Tsi1.BusinessLayer/Dtos/CourseCreateDto.cs b/Tsi1.Api/Tsi1.BusinessLayer/Dtos/CourseCreateDto.cs
index ffd3608..a3540f4 100644
--- a/Tsi1.Api/Tsi1.BusinessLayer/Dtos/CourseCreateDto.cs
+++ b/Tsi1.Api/Tsi1.BusinessLayer/Dtos/CourseCreateDto.cs
@@ -8,6 +8,7 @@ namespace Tsi1.BusinessLayer.Dtos
     public class CourseCreateDto
     {
         public string Name { get; set; }
+        public bool IsTemplate { get; set; }
 
         [JsonIgnore]
         public int TenantId { get; set; }
diff --git a/Tsi1.Api/Tsi1.BusinessLayer/Dtos/CourseModifyDto.cs b/Tsi1.Api/Tsi1.BusinessLayer/Dtos/CourseModifyDto.cs
new file mode 100644
index 0000000..fe5599c
--- /dev/null
+++ b/Tsi1.Api/Tsi1.BusinessLayer/Dtos/CourseModifyDto.cs
@@ -0,0 +1,13 @@
+using System.Text.Json.Serialization;
+
+namespace Tsi1.BusinessLayer.Dtos
+{
+    public class CourseModifyDto
+    {
+        public string Name { get; set; }
+        public bool IsTemplate { get; set; }
+
+        [JsonIgnore]
+        public int TenantId { get; set; }
+    }
+}
diff --git a/Tsi1.Api/Tsi1.BusinessLayer/Dtos/CourseTemplateCreateDto.cs b/Tsi1.Api/Tsi1.BusinessLayer/Dtos/CourseTemplateCreateDto.cs
new file mode 100644
index 0000000..94924d7
--- /dev/null
+++ b/Tsi1.Api/Tsi1.BusinessLayer/Dtos/CourseTemplateCreateDto.cs
@@ -0,0 +1,12 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Tsi1.BusinessLayer.Dtos
+{
+    public class CourseTemplateCreateDto
+    {
+        public string Name { get; set; }
+        public int CourseTemplateId { get; set; }
+    }
+}
diff --git a/Tsi1.Api/Tsi1.BusinessLayer/Helpers/ErrorMessages.cs b/Tsi1.Api/Tsi1.BusinessLayer/Helpers/ErrorMessages.cs
index 41b7cf0..a004f7a 100644
--- a/Tsi1.Api/Tsi1.BusinessLayer/Helpers/ErrorMessages.cs
+++ b/Tsi1.Api/Tsi1.BusinessLayer/Helpers/ErrorMessages.cs
@@ -31,8 +31,9 @@ namespace Tsi1.BusinessLayer.Helpers
         public const string CannotAuthenticateToSmtpServer = "No se pudo autenticar en el servidor SMTP";
         public const string CannotSendEmail = "No se pudo mandar el mail con asunto {0}";
 
-        public const string CourseDoesNotExist = "El curso '{0}' no existe";
+        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 TenantDoesNotExist = "La Facultad '{0}' no existe";
         public const string DuplicateTenantName = "Ya existe una Facultad con nombre '{0}'";
diff --git a/Tsi1.Api/Tsi1.BusinessLayer/Helpers/MappingProfile.cs b/Tsi1.Api/Tsi1.BusinessLayer/Helpers/MappingProfile.cs
index 4d7d53b..da9824d 100644
--- a/Tsi1.Api/Tsi1.BusinessLayer/Helpers/MappingProfile.cs
+++ b/Tsi1.Api/Tsi1.BusinessLayer/Helpers/MappingProfile.cs
@@ -72,6 +72,13 @@ namespace Tsi1.BusinessLayer.Helpers
             CreateMap<SectionDetailDto, Section>();
             CreateMap<SectionItemDetailDto, SectionItem>();
             CreateMap<FileDetailDto, File>();
+
+            CreateMap<Course, Course>().ForMember(x => x.Id, opt => opt.Ignore());
+            CreateMap<Tenant, Tenant>().ForMember(x => x.Id, opt => opt.Ignore());
+            CreateMap<Section, Section>().ForMember(x => x.Id, opt => opt.Ignore());
+            CreateMap<SectionItem, SectionItem>().ForMember(x => x.Id, opt => opt.Ignore());
+            CreateMap<SectionItemType, SectionItemType>().ForMember(x => x.Id, opt => opt.Ignore());
+            CreateMap<Forum, Forum>().ForMember(x => x.Id, opt => opt.Ignore());
         }
     }
 }
diff --git a/Tsi1.Api/Tsi1.BusinessLayer/Interfaces/ICourseService.cs b/Tsi1.Api/Tsi1.BusinessLayer/Interfaces/ICourseService.cs
index 836612b..640e6ec 100644
--- a/Tsi1.Api/Tsi1.BusinessLayer/Interfaces/ICourseService.cs
+++ b/Tsi1.Api/Tsi1.BusinessLayer/Interfaces/ICourseService.cs
@@ -1,6 +1,4 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
+using System.Collections.Generic;
 using System.Threading.Tasks;
 using Tsi1.BusinessLayer.Dtos;
 using Tsi1.BusinessLayer.Helpers;
@@ -13,14 +11,15 @@ namespace Tsi1.BusinessLayer.Interfaces
         Task<ServiceResult<List<CoursePreviewDto>>> GetCoursePreviews(int userId, string userType);
 
         Task<ServiceResult<Course>> Create(CourseCreateDto newCourse);
+        Task<ServiceResult<bool>> CreateFromTemplate(CourseTemplateCreateDto courseTemplate, int tenantId);
 
         Task<ServiceResult<bool>> Matriculate(int userId, int courseId);
 
         Task<ServiceResult<bool>> AddProfessorToCourse(ProfessorCourseDto professorCourseDto);
 
-        Task<ServiceResult<List<CoursePreviewDto>>> GetAll(int tenantId);
+        Task<ServiceResult<List<CoursePreviewDto>>> GetAll(int tenantId, bool isTemplate = false);
 
-        Task<ServiceResult<bool>> Modify(int courseId, CourseCreateDto courseDto);
+        Task<ServiceResult<bool>> Modify(int courseId, CourseModifyDto courseDto);
 
         Task<ServiceResult<Course>> Delete(int courseId);
 
diff --git a/Tsi1.Api/Tsi1.BusinessLayer/Services/CourseService.cs b/Tsi1.Api/Tsi1.BusinessLayer/Services/CourseService.cs
index fffcc93..8152ca0 100644
--- a/Tsi1.Api/Tsi1.BusinessLayer/Services/CourseService.cs
+++ b/Tsi1.Api/Tsi1.BusinessLayer/Services/CourseService.cs
@@ -97,6 +97,7 @@ namespace Tsi1.BusinessLayer.Services
 
             if (user == null || user.Student == null)
             {
+                result.HasError = true;
                 result.Message = string.Format(ErrorMessages.UserDoesNotExist, userId);
                 return result;
             }
@@ -105,10 +106,18 @@ namespace Tsi1.BusinessLayer.Services
 
             if (course == null)
             {
+                result.HasError = true;
                 result.Message = string.Format(ErrorMessages.CourseDoesNotExist, courseId);
                 return result;
             }
 
+            if (course.IsTemplate)
+            {
+                result.HasError = true;
+                result.AddMessage(string.Format(ErrorMessages.CourseIsTemplate, courseId));
+                return result;
+            }
+
             var existingStudentCourse = await _context.StudentCourses
                 .FirstOrDefaultAsync(x => x.StudentId == user.StudentId && x.CourseId == course.Id);
 
@@ -144,6 +153,7 @@ namespace Tsi1.BusinessLayer.Services
 
             if (user == null || user.Professor == null)
             {
+                result.HasError = true;
                 result.Message = string.Format(ErrorMessages.UserDoesNotExist, user.Username);
                 return result;
             }
@@ -153,10 +163,18 @@ namespace Tsi1.BusinessLayer.Services
 
             if (course == null)
             {
+                result.HasError = true;
                 result.Message = string.Format(ErrorMessages.CourseDoesNotExist, professorCourseDto.CourseId);
                 return result;
             }
 
+            if (course.IsTemplate)
+            {
+                result.HasError = true;
+                result.AddMessage(string.Format(ErrorMessages.CourseIsTemplate, professorCourseDto.CourseId));
+                return result;
+            }
+
             var existingProfessorCourse = await _context.ProfessorCourses
                 .FirstOrDefaultAsync(x => x.ProfessorId == user.ProfessorId && x.CourseId == course.Id);
 
@@ -174,30 +192,28 @@ namespace Tsi1.BusinessLayer.Services
             };
 
             _context.ProfessorCourses.Add(professorCourse);
-
             await _context.SaveChangesAsync();
 
             result.Data = true;
-
             return result;
         }
 
-        public async Task<ServiceResult<List<CoursePreviewDto>>> GetAll(int tenantId)
+        public async Task<ServiceResult<List<CoursePreviewDto>>> GetAll(int tenantId, bool isTemplate = false)
         {
             var result = new ServiceResult<List<CoursePreviewDto>>();
 
             var courses = await _context.Courses
-                .Where(x => x.TenantId == tenantId)
+                .Where(x => x.TenantId == tenantId
+                    && x.IsTemplate == isTemplate)
                 .ToListAsync();
 
             var coursesDto = _mapper.Map<List<CoursePreviewDto>>(courses);
 
             result.Data = coursesDto;
-
             return result;
         }
 
-        public async Task<ServiceResult<bool>> Modify(int courseId, CourseCreateDto courseDto)
+        public async Task<ServiceResult<bool>> Modify(int courseId, CourseModifyDto courseDto)
         {
             var result = new ServiceResult<bool>();
 
@@ -210,12 +226,10 @@ namespace Tsi1.BusinessLayer.Services
                 return result;
             }
 
-            _mapper.Map(courseDto, course);
-          
+            _mapper.Map(courseDto, course);   
             await _context.SaveChangesAsync();
 
             result.Data = true;
-
             return result;
         }
 
@@ -233,11 +247,9 @@ namespace Tsi1.BusinessLayer.Services
             }
 
             _context.Courses.Remove(course);
-
             await _context.SaveChangesAsync();
 
             result.Data = course;
-
             return result;
         }
 
@@ -316,11 +328,9 @@ namespace Tsi1.BusinessLayer.Services
             }
 
             _context.ProfessorCourses.Remove(professorCourse);
-
             await _context.SaveChangesAsync();
 
             result.Data = true;
-
             return result;
         }
 
@@ -338,7 +348,6 @@ namespace Tsi1.BusinessLayer.Services
             var userDtos = _mapper.Map<List<UserPreviewDto>>(users);
 
             result.Data = userDtos;
-
             return result;
         }
 
@@ -366,5 +375,34 @@ namespace Tsi1.BusinessLayer.Services
 
             return result;
         }
+
+        public async Task<ServiceResult<bool>> CreateFromTemplate(CourseTemplateCreateDto courseTemplate, int tenantId)
+        {
+            var result = new ServiceResult<bool>();
+
+            var course = await _context.Courses.AsNoTracking()
+                .Include(x => x.Sections)
+                    .ThenInclude(x => x.SectionItems)
+                        .ThenInclude(x => x.Forum)
+                .FirstOrDefaultAsync(x => 
+                    x.TenantId == tenantId
+                    && x.IsTemplate
+                    && x.Id == courseTemplate.CourseTemplateId);
+
+            if (course == null)
+            {
+                result.HasError = true;
+                result.AddMessage(string.Format(ErrorMessages.CourseDoesNotExist, courseTemplate.CourseTemplateId));
+                return result;
+            }
+
+            var newCourse = _mapper.Map<Course>(course);
+            newCourse.Name = courseTemplate.Name;
+
+            _context.Add(newCourse);
+            await _context.SaveChangesAsync();
+
+            return result;
+        }
     }
 }
diff --git a/Tsi1.Api/Tsi1.DataLayer/Entities/Course.cs b/Tsi1.Api/Tsi1.DataLayer/Entities/Course.cs
index 942858f..80c4291 100644
--- a/Tsi1.Api/Tsi1.DataLayer/Entities/Course.cs
+++ b/Tsi1.Api/Tsi1.DataLayer/Entities/Course.cs
@@ -16,6 +16,7 @@ namespace Tsi1.DataLayer.Entities
         public int Id { get; set; }
         public string Name { get; set; }
         public int TenantId { get; set; }
+        public bool IsTemplate { get; set; }
 
         public Tenant Tenant { get; set; }
         public ICollection<StudentCourse> StudentCourses { get; set; }
diff --git a/Tsi1.Api/Tsi1.DataLayer/EntityConfiguration/CourseConfiguration.cs b/Tsi1.Api/Tsi1.DataLayer/EntityConfiguration/CourseConfiguration.cs
index 4cea401..0676afd 100644
--- a/Tsi1.Api/Tsi1.DataLayer/EntityConfiguration/CourseConfiguration.cs
+++ b/Tsi1.Api/Tsi1.DataLayer/EntityConfiguration/CourseConfiguration.cs
@@ -20,6 +20,9 @@ namespace Tsi1.DataLayer.EntityConfiguration
                 .IsRequired()
                 .HasColumnType("character varying(50)");
 
+            builder.Property(x => x.IsTemplate)
+                .IsRequired();
+
             builder.HasOne(x => x.Tenant)
                 .WithMany(x => x.Courses)
                 .HasForeignKey(x => x.TenantId);
diff --git a/Tsi1.Api/Tsi1.DataLayer/Migrations/20201112213633_course-template.Designer.cs b/Tsi1.Api/Tsi1.DataLayer/Migrations/20201112213633_course-template.Designer.cs
new file mode 100644
index 0000000..506ca64
--- /dev/null
+++ b/Tsi1.Api/Tsi1.DataLayer/Migrations/20201112213633_course-template.Designer.cs
@@ -0,0 +1,611 @@
+// <auto-generated />
+using System;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
+using Tsi1.DataLayer;
+
+namespace Tsi1.DataLayer.Migrations
+{
+    [DbContext(typeof(Tsi1Context))]
+    [Migration("20201112213633_course-template")]
+    partial class coursetemplate
+    {
+        protected override void BuildTargetModel(ModelBuilder modelBuilder)
+        {
+#pragma warning disable 612, 618
+            modelBuilder
+                .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn)
+                .HasAnnotation("ProductVersion", "3.1.4")
+                .HasAnnotation("Relational:MaxIdentifierLength", 63);
+
+            modelBuilder.Entity("Tsi1.DataLayer.Entities.Connection", b =>
+                {
+                    b.Property<string>("ConnectionId")
+                        .HasColumnType("text");
+
+                    b.Property<string>("GroupName")
+                        .HasColumnType("text");
+
+                    b.Property<int>("UserId")
+                        .HasColumnType("integer");
+
+                    b.HasKey("ConnectionId");
+
+                    b.HasIndex("GroupName");
+
+                    b.ToTable("Connections");
+                });
+
+            modelBuilder.Entity("Tsi1.DataLayer.Entities.Course", b =>
+                {
+                    b.Property<int>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("integer")
+                        .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
+
+                    b.Property<bool>("IsTemplate")
+                        .HasColumnType("boolean");
+
+                    b.Property<string>("Name")
+                        .IsRequired()
+                        .HasColumnType("character varying(50)");
+
+                    b.Property<int>("TenantId")
+                        .HasColumnType("integer");
+
+                    b.HasKey("Id");
+
+                    b.HasIndex("TenantId");
+
+                    b.HasIndex("Name", "TenantId")
+                        .IsUnique();
+
+                    b.ToTable("Courses");
+                });
+
+            modelBuilder.Entity("Tsi1.DataLayer.Entities.File", b =>
+                {
+                    b.Property<int>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("integer")
+                        .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
+
+                    b.Property<string>("Name")
+                        .IsRequired()
+                        .HasColumnType("character varying(50)");
+
+                    b.Property<string>("Path")
+                        .IsRequired()
+                        .HasColumnType("character varying(1000)");
+
+                    b.HasKey("Id");
+
+                    b.HasIndex("Path")
+                        .IsUnique();
+
+                    b.ToTable("Files");
+                });
+
+            modelBuilder.Entity("Tsi1.DataLayer.Entities.Forum", b =>
+                {
+                    b.Property<int>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("integer")
+                        .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
+
+                    b.Property<string>("Name")
+                        .IsRequired()
+                        .HasColumnType("character varying(50)");
+
+                    b.HasKey("Id");
+
+                    b.ToTable("Forums");
+                });
+
+            modelBuilder.Entity("Tsi1.DataLayer.Entities.ForumUser", b =>
+                {
+                    b.Property<int>("ForumId")
+                        .HasColumnType("integer");
+
+                    b.Property<int>("UserId")
+                        .HasColumnType("integer");
+
+                    b.HasKey("ForumId", "UserId");
+
+                    b.HasIndex("UserId");
+
+                    b.ToTable("ForumUsers");
+                });
+
+            modelBuilder.Entity("Tsi1.DataLayer.Entities.Group", b =>
+                {
+                    b.Property<string>("Name")
+                        .HasColumnType("text");
+
+                    b.HasKey("Name");
+
+                    b.ToTable("Groups");
+                });
+
+            modelBuilder.Entity("Tsi1.DataLayer.Entities.Post", b =>
+                {
+                    b.Property<int>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("integer")
+                        .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
+
+                    b.Property<DateTime>("Date")
+                        .HasColumnType("timestamp without time zone");
+
+                    b.Property<int>("ForumId")
+                        .HasColumnType("integer");
+
+                    b.Property<string>("Title")
+                        .IsRequired()
+                        .HasColumnType("character varying(100)");
+
+                    b.Property<int>("UserId")
+                        .HasColumnType("integer");
+
+                    b.HasKey("Id");
+
+                    b.HasIndex("UserId");
+
+                    b.HasIndex("ForumId", "Title")
+                        .IsUnique();
+
+                    b.ToTable("Posts");
+                });
+
+            modelBuilder.Entity("Tsi1.DataLayer.Entities.PostMessage", b =>
+                {
+                    b.Property<int>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("integer")
+                        .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
+
+                    b.Property<string>("Content")
+                        .IsRequired()
+                        .HasColumnType("character varying(10485760)");
+
+                    b.Property<DateTime>("Date")
+                        .HasColumnType("timestamp without time zone");
+
+                    b.Property<int>("PostId")
+                        .HasColumnType("integer");
+
+                    b.Property<int>("UserId")
+                        .HasColumnType("integer");
+
+                    b.HasKey("Id");
+
+                    b.HasIndex("PostId");
+
+                    b.HasIndex("UserId");
+
+                    b.ToTable("PostMessages");
+                });
+
+            modelBuilder.Entity("Tsi1.DataLayer.Entities.Professor", b =>
+                {
+                    b.Property<int>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("integer")
+                        .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
+
+                    b.Property<string>("IdentityCard")
+                        .IsRequired()
+                        .HasColumnType("character varying(50)");
+
+                    b.Property<int>("TenantId")
+                        .HasColumnType("integer");
+
+                    b.HasKey("Id");
+
+                    b.HasIndex("TenantId");
+
+                    b.HasIndex("IdentityCard", "TenantId")
+                        .IsUnique();
+
+                    b.ToTable("Professors");
+                });
+
+            modelBuilder.Entity("Tsi1.DataLayer.Entities.ProfessorCourse", b =>
+                {
+                    b.Property<int>("ProfessorId")
+                        .HasColumnType("integer");
+
+                    b.Property<int>("CourseId")
+                        .HasColumnType("integer");
+
+                    b.HasKey("ProfessorId", "CourseId");
+
+                    b.HasIndex("CourseId");
+
+                    b.ToTable("ProfessorCourses");
+                });
+
+            modelBuilder.Entity("Tsi1.DataLayer.Entities.Section", b =>
+                {
+                    b.Property<int>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("integer")
+                        .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
+
+                    b.Property<int>("CourseId")
+                        .HasColumnType("integer");
+
+                    b.Property<string>("Name")
+                        .IsRequired()
+                        .HasColumnType("character varying(255)");
+
+                    b.Property<int>("Order")
+                        .HasColumnType("integer");
+
+                    b.HasKey("Id");
+
+                    b.HasIndex("CourseId");
+
+                    b.ToTable("Sections");
+                });
+
+            modelBuilder.Entity("Tsi1.DataLayer.Entities.SectionItem", b =>
+                {
+                    b.Property<int>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("integer")
+                        .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
+
+                    b.Property<int?>("FileId")
+                        .HasColumnType("integer");
+
+                    b.Property<int?>("ForumId")
+                        .HasColumnType("integer");
+
+                    b.Property<int>("Order")
+                        .HasColumnType("integer");
+
+                    b.Property<int>("SectionId")
+                        .HasColumnType("integer");
+
+                    b.Property<int>("SectionItemTypeId")
+                        .HasColumnType("integer");
+
+                    b.HasKey("Id");
+
+                    b.HasIndex("FileId")
+                        .IsUnique();
+
+                    b.HasIndex("ForumId")
+                        .IsUnique();
+
+                    b.HasIndex("SectionId");
+
+                    b.HasIndex("SectionItemTypeId");
+
+                    b.ToTable("SectionItems");
+                });
+
+            modelBuilder.Entity("Tsi1.DataLayer.Entities.SectionItemType", b =>
+                {
+                    b.Property<int>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("integer")
+                        .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
+
+                    b.Property<string>("Name")
+                        .IsRequired()
+                        .HasColumnType("character varying(100)");
+
+                    b.HasKey("Id");
+
+                    b.ToTable("SectionItemTypes");
+                });
+
+            modelBuilder.Entity("Tsi1.DataLayer.Entities.Student", b =>
+                {
+                    b.Property<int>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("integer")
+                        .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
+
+                    b.Property<int>("Age")
+                        .HasColumnType("integer");
+
+                    b.Property<string>("IdentityCard")
+                        .IsRequired()
+                        .HasColumnType("character varying(50)");
+
+                    b.Property<int>("TenantId")
+                        .HasColumnType("integer");
+
+                    b.HasKey("Id");
+
+                    b.HasIndex("TenantId");
+
+                    b.HasIndex("IdentityCard", "TenantId")
+                        .IsUnique();
+
+                    b.ToTable("Students");
+                });
+
+            modelBuilder.Entity("Tsi1.DataLayer.Entities.StudentCourse", b =>
+                {
+                    b.Property<int>("StudentId")
+                        .HasColumnType("integer");
+
+                    b.Property<int>("CourseId")
+                        .HasColumnType("integer");
+
+                    b.HasKey("StudentId", "CourseId");
+
+                    b.HasIndex("CourseId");
+
+                    b.ToTable("StudentCourses");
+                });
+
+            modelBuilder.Entity("Tsi1.DataLayer.Entities.Tenant", b =>
+                {
+                    b.Property<int>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("integer")
+                        .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
+
+                    b.Property<string>("Name")
+                        .IsRequired()
+                        .HasColumnType("character varying(50)");
+
+                    b.HasKey("Id");
+
+                    b.HasIndex("Name")
+                        .IsUnique();
+
+                    b.ToTable("Tenants");
+                });
+
+            modelBuilder.Entity("Tsi1.DataLayer.Entities.User", b =>
+                {
+                    b.Property<int>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("integer")
+                        .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
+
+                    b.Property<string>("Email")
+                        .IsRequired()
+                        .HasColumnType("character varying(255)");
+
+                    b.Property<string>("FirstName")
+                        .IsRequired()
+                        .HasColumnType("character varying(255)");
+
+                    b.Property<string>("LastName")
+                        .IsRequired()
+                        .HasColumnType("character varying(255)");
+
+                    b.Property<string>("Password")
+                        .IsRequired()
+                        .HasColumnType("character varying(255)");
+
+                    b.Property<int?>("ProfessorId")
+                        .HasColumnType("integer");
+
+                    b.Property<int?>("StudentId")
+                        .HasColumnType("integer");
+
+                    b.Property<int>("TenantId")
+                        .HasColumnType("integer");
+
+                    b.Property<int>("UserTypeId")
+                        .HasColumnType("integer");
+
+                    b.Property<string>("Username")
+                        .IsRequired()
+                        .HasColumnType("character varying(50)");
+
+                    b.HasKey("Id");
+
+                    b.HasIndex("ProfessorId")
+                        .IsUnique();
+
+                    b.HasIndex("StudentId")
+                        .IsUnique();
+
+                    b.HasIndex("TenantId");
+
+                    b.HasIndex("UserTypeId");
+
+                    b.HasIndex("Username", "TenantId")
+                        .IsUnique();
+
+                    b.ToTable("Users");
+                });
+
+            modelBuilder.Entity("Tsi1.DataLayer.Entities.UserType", b =>
+                {
+                    b.Property<int>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("integer")
+                        .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
+
+                    b.Property<string>("Name")
+                        .IsRequired()
+                        .HasColumnType("character varying(50)");
+
+                    b.HasKey("Id");
+
+                    b.HasIndex("Name")
+                        .IsUnique();
+
+                    b.ToTable("UserTypes");
+                });
+
+            modelBuilder.Entity("Tsi1.DataLayer.Entities.Connection", b =>
+                {
+                    b.HasOne("Tsi1.DataLayer.Entities.Group", "Group")
+                        .WithMany("Connections")
+                        .HasForeignKey("GroupName");
+                });
+
+            modelBuilder.Entity("Tsi1.DataLayer.Entities.Course", b =>
+                {
+                    b.HasOne("Tsi1.DataLayer.Entities.Tenant", "Tenant")
+                        .WithMany("Courses")
+                        .HasForeignKey("TenantId")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired();
+                });
+
+            modelBuilder.Entity("Tsi1.DataLayer.Entities.ForumUser", b =>
+                {
+                    b.HasOne("Tsi1.DataLayer.Entities.Forum", "Forum")
+                        .WithMany("ForumUsers")
+                        .HasForeignKey("ForumId")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired();
+
+                    b.HasOne("Tsi1.DataLayer.Entities.User", "User")
+                        .WithMany("ForumUsers")
+                        .HasForeignKey("UserId")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired();
+                });
+
+            modelBuilder.Entity("Tsi1.DataLayer.Entities.Post", b =>
+                {
+                    b.HasOne("Tsi1.DataLayer.Entities.Forum", "Forum")
+                        .WithMany("Posts")
+                        .HasForeignKey("ForumId")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired();
+
+                    b.HasOne("Tsi1.DataLayer.Entities.User", "User")
+                        .WithMany("Posts")
+                        .HasForeignKey("UserId")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired();
+                });
+
+            modelBuilder.Entity("Tsi1.DataLayer.Entities.PostMessage", b =>
+                {
+                    b.HasOne("Tsi1.DataLayer.Entities.Post", "Post")
+                        .WithMany("PostMessages")
+                        .HasForeignKey("PostId")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired();
+
+                    b.HasOne("Tsi1.DataLayer.Entities.User", "User")
+                        .WithMany("PostMessages")
+                        .HasForeignKey("UserId")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired();
+                });
+
+            modelBuilder.Entity("Tsi1.DataLayer.Entities.Professor", b =>
+                {
+                    b.HasOne("Tsi1.DataLayer.Entities.Tenant", "Tenant")
+                        .WithMany("Professors")
+                        .HasForeignKey("TenantId")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired();
+                });
+
+            modelBuilder.Entity("Tsi1.DataLayer.Entities.ProfessorCourse", b =>
+                {
+                    b.HasOne("Tsi1.DataLayer.Entities.Course", "Course")
+                        .WithMany("ProfessorCourses")
+                        .HasForeignKey("CourseId")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired();
+
+                    b.HasOne("Tsi1.DataLayer.Entities.Professor", "Professor")
+                        .WithMany("ProfessorCourses")
+                        .HasForeignKey("ProfessorId")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired();
+                });
+
+            modelBuilder.Entity("Tsi1.DataLayer.Entities.Section", b =>
+                {
+                    b.HasOne("Tsi1.DataLayer.Entities.Course", "Course")
+                        .WithMany("Sections")
+                        .HasForeignKey("CourseId")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired();
+                });
+
+            modelBuilder.Entity("Tsi1.DataLayer.Entities.SectionItem", b =>
+                {
+                    b.HasOne("Tsi1.DataLayer.Entities.File", "File")
+                        .WithOne("SectionItem")
+                        .HasForeignKey("Tsi1.DataLayer.Entities.SectionItem", "FileId");
+
+                    b.HasOne("Tsi1.DataLayer.Entities.Forum", "Forum")
+                        .WithOne("SectionItem")
+                        .HasForeignKey("Tsi1.DataLayer.Entities.SectionItem", "ForumId");
+
+                    b.HasOne("Tsi1.DataLayer.Entities.Section", "Section")
+                        .WithMany("SectionItems")
+                        .HasForeignKey("SectionId")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired();
+
+                    b.HasOne("Tsi1.DataLayer.Entities.SectionItemType", "SectionItemType")
+                        .WithMany("SectionItems")
+                        .HasForeignKey("SectionItemTypeId")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired();
+                });
+
+            modelBuilder.Entity("Tsi1.DataLayer.Entities.Student", b =>
+                {
+                    b.HasOne("Tsi1.DataLayer.Entities.Tenant", "Tenant")
+                        .WithMany("Students")
+                        .HasForeignKey("TenantId")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired();
+                });
+
+            modelBuilder.Entity("Tsi1.DataLayer.Entities.StudentCourse", b =>
+                {
+                    b.HasOne("Tsi1.DataLayer.Entities.Course", "Course")
+                        .WithMany("StudentCourses")
+                        .HasForeignKey("CourseId")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired();
+
+                    b.HasOne("Tsi1.DataLayer.Entities.Student", "Student")
+                        .WithMany("StudentCourses")
+                        .HasForeignKey("StudentId")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired();
+                });
+
+            modelBuilder.Entity("Tsi1.DataLayer.Entities.User", b =>
+                {
+                    b.HasOne("Tsi1.DataLayer.Entities.Professor", "Professor")
+                        .WithOne("User")
+                        .HasForeignKey("Tsi1.DataLayer.Entities.User", "ProfessorId");
+
+                    b.HasOne("Tsi1.DataLayer.Entities.Student", "Student")
+                        .WithOne("User")
+                        .HasForeignKey("Tsi1.DataLayer.Entities.User", "StudentId");
+
+                    b.HasOne("Tsi1.DataLayer.Entities.Tenant", "Tenant")
+                        .WithMany("Users")
+                        .HasForeignKey("TenantId")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired();
+
+                    b.HasOne("Tsi1.DataLayer.Entities.UserType", "UserType")
+                        .WithMany()
+                        .HasForeignKey("UserTypeId")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired();
+                });
+#pragma warning restore 612, 618
+        }
+    }
+}
diff --git a/Tsi1.Api/Tsi1.DataLayer/Migrations/20201112213633_course-template.cs b/Tsi1.Api/Tsi1.DataLayer/Migrations/20201112213633_course-template.cs
new file mode 100644
index 0000000..36d655a
--- /dev/null
+++ b/Tsi1.Api/Tsi1.DataLayer/Migrations/20201112213633_course-template.cs
@@ -0,0 +1,23 @@
+using Microsoft.EntityFrameworkCore.Migrations;
+
+namespace Tsi1.DataLayer.Migrations
+{
+    public partial class coursetemplate : Migration
+    {
+        protected override void Up(MigrationBuilder migrationBuilder)
+        {
+            migrationBuilder.AddColumn<bool>(
+                name: "IsTemplate",
+                table: "Courses",
+                nullable: false,
+                defaultValue: false);
+        }
+
+        protected override void Down(MigrationBuilder migrationBuilder)
+        {
+            migrationBuilder.DropColumn(
+                name: "IsTemplate",
+                table: "Courses");
+        }
+    }
+}
diff --git a/Tsi1.Api/Tsi1.DataLayer/Migrations/Tsi1ContextModelSnapshot.cs b/Tsi1.Api/Tsi1.DataLayer/Migrations/Tsi1ContextModelSnapshot.cs
index e661f62..e07d884 100644
--- a/Tsi1.Api/Tsi1.DataLayer/Migrations/Tsi1ContextModelSnapshot.cs
+++ b/Tsi1.Api/Tsi1.DataLayer/Migrations/Tsi1ContextModelSnapshot.cs
@@ -44,6 +44,9 @@ namespace Tsi1.DataLayer.Migrations
                         .HasColumnType("integer")
                         .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
 
+                    b.Property<bool>("IsTemplate")
+                        .HasColumnType("boolean");
+
                     b.Property<string>("Name")
                         .IsRequired()
                         .HasColumnType("character varying(50)");
-- 
GitLab