From f9e073b8287dc325162ef65a44a8e72cc42f0d40 Mon Sep 17 00:00:00 2001 From: "Alexey.Berezhnykh" Date: Thu, 24 Apr 2025 04:33:23 +0300 Subject: [PATCH 1/5] =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=BF=D0=BE=D0=B4=D0=B0?= =?UTF-8?q?=D0=B2=D0=B0=D1=82=D0=B5=D0=BB=D0=B8:=20=D0=B2=D1=8B=D0=BD?= =?UTF-8?q?=D0=B5=D1=81=D1=82=D0=B8=20=D0=B2=20=D0=BE=D1=82=D0=B4=D0=B5?= =?UTF-8?q?=D0=BB=D1=8C=D0=BD=D1=83=D1=8E=20=D0=BC=D0=BE=D0=B4=D0=B5=D0=BB?= =?UTF-8?q?=D1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...20250424012738_'CourseMentors'.Designer.cs | 350 ++++++++++++++++++ .../20250424012738_'CourseMentors'.cs | 63 ++++ .../Models/Course.cs | 6 +- .../Models/CourseContext.cs | 4 + .../Models/CourseMentor.cs | 8 + 5 files changed, 429 insertions(+), 2 deletions(-) create mode 100644 HwProj.CoursesService/HwProj.CoursesService.API/Migrations/20250424012738_'CourseMentors'.Designer.cs create mode 100644 HwProj.CoursesService/HwProj.CoursesService.API/Migrations/20250424012738_'CourseMentors'.cs create mode 100644 HwProj.CoursesService/HwProj.CoursesService.API/Models/CourseMentor.cs diff --git a/HwProj.CoursesService/HwProj.CoursesService.API/Migrations/20250424012738_'CourseMentors'.Designer.cs b/HwProj.CoursesService/HwProj.CoursesService.API/Migrations/20250424012738_'CourseMentors'.Designer.cs new file mode 100644 index 000000000..315d4b145 --- /dev/null +++ b/HwProj.CoursesService/HwProj.CoursesService.API/Migrations/20250424012738_'CourseMentors'.Designer.cs @@ -0,0 +1,350 @@ +// +using System; +using HwProj.CoursesService.API.Models; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +namespace HwProj.CoursesService.API.Migrations +{ + [DbContext(typeof(CourseContext))] + [Migration("20250424012738_'CourseMentors'")] + partial class CourseMentors + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.2.6-servicing-10079") + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.Assignment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("CourseId"); + + b.Property("MentorId"); + + b.Property("StudentId"); + + b.HasKey("Id"); + + b.HasIndex("CourseId"); + + b.ToTable("Assignments"); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.Course", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("GroupName"); + + b.Property("InviteCode"); + + b.Property("IsCompleted"); + + b.Property("IsOpen"); + + b.Property("MentorIds"); + + b.Property("Name"); + + b.HasKey("Id"); + + b.ToTable("Courses"); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.CourseFilter", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("FilterJson"); + + b.HasKey("Id"); + + b.ToTable("CourseFilters"); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.CourseMate", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("CourseId"); + + b.Property("IsAccepted"); + + b.Property("StudentId"); + + b.HasKey("Id"); + + b.HasIndex("CourseId"); + + b.ToTable("CourseMates"); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.CourseMentor", b => + { + b.Property("UserId"); + + b.Property("CourseId"); + + b.HasKey("UserId", "CourseId"); + + b.HasIndex("CourseId"); + + b.HasIndex("UserId"); + + b.ToTable("CourseMentor"); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("CourseId"); + + b.Property("Name"); + + b.HasKey("Id"); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.GroupMate", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("GroupId"); + + b.Property("StudentId") + .IsRequired(); + + b.HasKey("Id"); + + b.HasAlternateKey("GroupId", "StudentId"); + + b.ToTable("GroupMates"); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.Homework", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("CourseId"); + + b.Property("DeadlineDate"); + + b.Property("Description"); + + b.Property("HasDeadline"); + + b.Property("IsDeadlineStrict"); + + b.Property("PublicationDate"); + + b.Property("Tags"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("CourseId"); + + b.ToTable("Homeworks"); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.HomeworkTask", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("DeadlineDate"); + + b.Property("Description"); + + b.Property("HasDeadline"); + + b.Property("HomeworkId"); + + b.Property("IsDeadlineStrict"); + + b.Property("MaxRating"); + + b.Property("PublicationDate"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("HomeworkId"); + + b.ToTable("Tasks"); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.StudentCharacteristics", b => + { + b.Property("CourseMateId"); + + b.Property("Description"); + + b.Property("Tags"); + + b.HasKey("CourseMateId"); + + b.ToTable("StudentCharacteristics"); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.TaskModel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("GroupId"); + + b.Property("TaskId"); + + b.HasKey("Id"); + + b.HasIndex("GroupId"); + + b.ToTable("TasksModels"); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.TaskQuestion", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Answer") + .HasMaxLength(1000); + + b.Property("IsPrivate"); + + b.Property("LecturerId"); + + b.Property("StudentId"); + + b.Property("TaskId"); + + b.Property("Text") + .HasMaxLength(1000); + + b.HasKey("Id"); + + b.HasIndex("TaskId"); + + b.ToTable("Questions"); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.UserToCourseFilter", b => + { + b.Property("CourseId"); + + b.Property("UserId"); + + b.Property("CourseFilterId"); + + b.HasKey("CourseId", "UserId"); + + b.HasIndex("CourseFilterId"); + + b.ToTable("UserToCourseFilters"); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.Assignment", b => + { + b.HasOne("HwProj.CoursesService.API.Models.Course") + .WithMany("Assignments") + .HasForeignKey("CourseId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.CourseMate", b => + { + b.HasOne("HwProj.CoursesService.API.Models.Course") + .WithMany("CourseMates") + .HasForeignKey("CourseId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.CourseMentor", b => + { + b.HasOne("HwProj.CoursesService.API.Models.Course") + .WithMany("Mentors") + .HasForeignKey("CourseId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.GroupMate", b => + { + b.HasOne("HwProj.CoursesService.API.Models.Group") + .WithMany("GroupMates") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.Homework", b => + { + b.HasOne("HwProj.CoursesService.API.Models.Course") + .WithMany("Homeworks") + .HasForeignKey("CourseId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.HomeworkTask", b => + { + b.HasOne("HwProj.CoursesService.API.Models.Homework", "Homework") + .WithMany("Tasks") + .HasForeignKey("HomeworkId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.StudentCharacteristics", b => + { + b.HasOne("HwProj.CoursesService.API.Models.CourseMate") + .WithOne("Characteristics") + .HasForeignKey("HwProj.CoursesService.API.Models.StudentCharacteristics", "CourseMateId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.TaskModel", b => + { + b.HasOne("HwProj.CoursesService.API.Models.Group") + .WithMany("Tasks") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.UserToCourseFilter", b => + { + b.HasOne("HwProj.CoursesService.API.Models.CourseFilter", "CourseFilter") + .WithMany() + .HasForeignKey("CourseFilterId") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/HwProj.CoursesService/HwProj.CoursesService.API/Migrations/20250424012738_'CourseMentors'.cs b/HwProj.CoursesService/HwProj.CoursesService.API/Migrations/20250424012738_'CourseMentors'.cs new file mode 100644 index 000000000..974e37435 --- /dev/null +++ b/HwProj.CoursesService/HwProj.CoursesService.API/Migrations/20250424012738_'CourseMentors'.cs @@ -0,0 +1,63 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace HwProj.CoursesService.API.Migrations +{ + public partial class CourseMentors : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "CourseMentor", + columns: table => new + { + UserId = table.Column(nullable: false), + CourseId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CourseMentor", x => new { x.UserId, x.CourseId }); + table.ForeignKey( + name: "FK_CourseMentor_Courses_CourseId", + column: x => x.CourseId, + principalTable: "Courses", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_CourseMentor_CourseId", + table: "CourseMentor", + column: "CourseId"); + + migrationBuilder.CreateIndex( + name: "IX_CourseMentor_UserId", + table: "CourseMentor", + column: "UserId"); + + // Миграция данных: Перенос MentorIds в таблицу CourseMentor + // language=SQL + migrationBuilder.Sql(@" +-- Разделим MentorIds на элементы, преобразуем в строки, удалим дубликаты и вставим в таблицу CourseMentor +WITH SplitMentorIds AS ( + SELECT + c.Id AS CourseId, + LTRIM(RTRIM(value)) AS MentorId + FROM Courses c + CROSS APPLY STRING_SPLIT(c.MentorIds, '/') -- Разделяем MentorIds по запятой +) +, DistinctMentors AS ( + SELECT DISTINCT + MentorId, + CourseId + FROM SplitMentorIds + WHERE MentorId <> '' -- Исключаем пустые строки +) +INSERT INTO CourseMentor (UserId, CourseId) +SELECT + MentorId, + CourseId +FROM DistinctMentors; +"); + } + } +} diff --git a/HwProj.CoursesService/HwProj.CoursesService.API/Models/Course.cs b/HwProj.CoursesService/HwProj.CoursesService.API/Models/Course.cs index 255241668..7d58da0ff 100644 --- a/HwProj.CoursesService/HwProj.CoursesService.API/Models/Course.cs +++ b/HwProj.CoursesService/HwProj.CoursesService.API/Models/Course.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using HwProj.Repositories.Net8; @@ -12,7 +13,8 @@ public class Course : IEntity public bool IsOpen { get; set; } public string InviteCode { get; set; } public bool IsCompleted { get; set; } - public string MentorIds { get; set; } + [Obsolete] public string MentorIds { get; set; } + public List Mentors { get; set; } = new List(); public List CourseMates { get; set; } = new List(); public List Homeworks { get; set; } = new List(); public List Assignments { get; set; } = new List(); diff --git a/HwProj.CoursesService/HwProj.CoursesService.API/Models/CourseContext.cs b/HwProj.CoursesService/HwProj.CoursesService.API/Models/CourseContext.cs index c32220254..6016d2d89 100644 --- a/HwProj.CoursesService/HwProj.CoursesService.API/Models/CourseContext.cs +++ b/HwProj.CoursesService/HwProj.CoursesService.API/Models/CourseContext.cs @@ -30,6 +30,10 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) modelBuilder.Entity().HasIndex(a => a.CourseId); modelBuilder.Entity().HasKey(u => new { u.CourseId, u.Id }); modelBuilder.Entity().HasIndex(t => t.TaskId); + + modelBuilder.Entity().HasKey(m => new { m.UserId, m.CourseId }); + modelBuilder.Entity().HasIndex(m => m.UserId); + modelBuilder.Entity().HasIndex(m => m.CourseId); } } } diff --git a/HwProj.CoursesService/HwProj.CoursesService.API/Models/CourseMentor.cs b/HwProj.CoursesService/HwProj.CoursesService.API/Models/CourseMentor.cs new file mode 100644 index 000000000..0f0b51420 --- /dev/null +++ b/HwProj.CoursesService/HwProj.CoursesService.API/Models/CourseMentor.cs @@ -0,0 +1,8 @@ +namespace HwProj.CoursesService.API.Models +{ + public class CourseMentor + { + public string UserId { get; set; } + public long CourseId { get; set; } + } +} From a6305010438dbe2762b4bfff99333d4ae0092d39 Mon Sep 17 00:00:00 2001 From: Andrew_Baranov Date: Thu, 22 Oct 2026 17:20:22 +0300 Subject: [PATCH 2/5] feat(CoursesService): add CourseMentor entity and migration --- ...20240104183735_HomeworkCommonProperties.cs | 45 ++ ...021133302_AddCourseMentorTable.Designer.cs | 516 ++++++++++++++++++ .../20261021133302_AddCourseMentorTable.cs | 59 ++ .../Migrations/CourseContextModelSnapshot.cs | 34 +- .../Models/CourseContext.cs | 1 + .../Repositories/CoursesRepository.cs | 58 -- .../Services/CoursesService.cs | 377 ------------- 7 files changed, 652 insertions(+), 438 deletions(-) create mode 100644 HwProj.CoursesService/HwProj.CoursesService.API/Migrations/20261021133302_AddCourseMentorTable.Designer.cs create mode 100644 HwProj.CoursesService/HwProj.CoursesService.API/Migrations/20261021133302_AddCourseMentorTable.cs delete mode 100644 HwProj.CoursesService/HwProj.CoursesService.API/Repositories/CoursesRepository.cs delete mode 100644 HwProj.CoursesService/HwProj.CoursesService.API/Services/CoursesService.cs diff --git a/HwProj.CoursesService/HwProj.CoursesService.API/Migrations/20240104183735_HomeworkCommonProperties.cs b/HwProj.CoursesService/HwProj.CoursesService.API/Migrations/20240104183735_HomeworkCommonProperties.cs index eec4c39bd..00265b893 100644 --- a/HwProj.CoursesService/HwProj.CoursesService.API/Migrations/20240104183735_HomeworkCommonProperties.cs +++ b/HwProj.CoursesService/HwProj.CoursesService.API/Migrations/20240104183735_HomeworkCommonProperties.cs @@ -7,6 +7,18 @@ public partial class HomeworkCommonProperties : Migration { protected override void Up(MigrationBuilder migrationBuilder) { + migrationBuilder.Sql(@" + DECLARE @constraint NVARCHAR(200); + SELECT @constraint = dc.name + FROM sys.default_constraints dc + JOIN sys.columns c ON dc.parent_column_id = c.column_id + AND dc.parent_object_id = c.object_id + WHERE c.object_id = OBJECT_ID('Homeworks') AND c.name = 'Date'; + + IF @constraint IS NOT NULL + EXEC('ALTER TABLE Homeworks DROP CONSTRAINT ' + @constraint); + "); + migrationBuilder.RenameColumn( name: "Date", table: "Homeworks", @@ -47,5 +59,38 @@ protected override void Up(MigrationBuilder migrationBuilder) nullable: false, defaultValue: false); } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn(name: "IsDeadlineStrict", table: "Homeworks"); + migrationBuilder.DropColumn(name: "HasDeadline", table: "Homeworks"); + migrationBuilder.DropColumn(name: "DeadlineDate", table: "Homeworks"); + + migrationBuilder.AlterColumn( + name: "HasDeadline", + table: "Tasks", + nullable: false, + oldClrType: typeof(bool), + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "IsDeadlineStrict", + table: "Tasks", + nullable: false, + oldClrType: typeof(bool), + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "PublicationDate", + table: "Tasks", + nullable: false, + oldClrType: typeof(DateTime), + oldNullable: true); + + migrationBuilder.RenameColumn( + name: "PublicationDate", + table: "Homeworks", + newName: "Date"); + } } } diff --git a/HwProj.CoursesService/HwProj.CoursesService.API/Migrations/20261021133302_AddCourseMentorTable.Designer.cs b/HwProj.CoursesService/HwProj.CoursesService.API/Migrations/20261021133302_AddCourseMentorTable.Designer.cs new file mode 100644 index 000000000..9d28fcba4 --- /dev/null +++ b/HwProj.CoursesService/HwProj.CoursesService.API/Migrations/20261021133302_AddCourseMentorTable.Designer.cs @@ -0,0 +1,516 @@ +// +using System; +using HwProj.CoursesService.API.Models; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace HwProj.CoursesService.API.Migrations +{ + [DbContext(typeof(CourseContext))] + [Migration("20261021133302_AddCourseMentorTable")] + partial class AddCourseMentorTable + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.9") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.Assignment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CourseId") + .HasColumnType("bigint"); + + b.Property("MentorId") + .HasColumnType("nvarchar(max)"); + + b.Property("StudentId") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("CourseId"); + + b.ToTable("Assignments"); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.Course", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("GroupName") + .HasColumnType("nvarchar(max)"); + + b.Property("InviteCode") + .HasColumnType("nvarchar(max)"); + + b.Property("IsCompleted") + .HasColumnType("bit"); + + b.Property("IsOpen") + .HasColumnType("bit"); + + b.Property("MentorIds") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Courses"); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.CourseFilter", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("FilterJson") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("CourseFilters"); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.CourseMate", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CourseId") + .HasColumnType("bigint"); + + b.Property("IsAccepted") + .HasColumnType("bit"); + + b.Property("StudentId") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("CourseId"); + + b.ToTable("CourseMates"); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.CourseMentor", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("CourseId") + .HasColumnType("bigint"); + + b.HasKey("UserId", "CourseId"); + + b.HasIndex("CourseId"); + + b.HasIndex("UserId"); + + b.ToTable("CourseMentors"); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.Criterion", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Arguments") + .HasColumnType("nvarchar(max)"); + + b.Property("MaxPoints") + .HasColumnType("int"); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.Property("TaskId") + .HasColumnType("bigint"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("TaskId"); + + b.ToTable("Criteria"); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CourseId") + .HasColumnType("bigint"); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.GroupMate", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("GroupId") + .HasColumnType("bigint"); + + b.Property("StudentId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasAlternateKey("GroupId", "StudentId"); + + b.ToTable("GroupMates"); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.Homework", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CourseId") + .HasColumnType("bigint"); + + b.Property("DeadlineDate") + .HasColumnType("datetime2"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("GroupId") + .HasColumnType("bigint"); + + b.Property("HasDeadline") + .HasColumnType("bit"); + + b.Property("IsDeadlineStrict") + .HasColumnType("bit"); + + b.Property("PublicationDate") + .HasColumnType("datetime2"); + + b.Property("Tags") + .HasColumnType("nvarchar(max)"); + + b.Property("Title") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("CourseId"); + + b.ToTable("Homeworks"); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.HomeworkTask", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("DeadlineDate") + .HasColumnType("datetime2"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("HasDeadline") + .HasColumnType("bit"); + + b.Property("HomeworkId") + .HasColumnType("bigint"); + + b.Property("IsBonusExplicit") + .HasColumnType("bit"); + + b.Property("IsDeadlineStrict") + .HasColumnType("bit"); + + b.Property("MaxRating") + .HasColumnType("int"); + + b.Property("PublicationDate") + .HasColumnType("datetime2"); + + b.Property("Title") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("HomeworkId"); + + b.ToTable("Tasks"); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.StudentCharacteristics", b => + { + b.Property("CourseMateId") + .HasColumnType("bigint"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Tags") + .HasColumnType("nvarchar(max)"); + + b.HasKey("CourseMateId"); + + b.ToTable("StudentCharacteristics"); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.TaskModel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("GroupId") + .HasColumnType("bigint"); + + b.Property("TaskId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("GroupId"); + + b.ToTable("TasksModels"); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.TaskQuestion", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Answer") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("IsPrivate") + .HasColumnType("bit"); + + b.Property("LecturerId") + .HasColumnType("nvarchar(max)"); + + b.Property("StudentId") + .HasColumnType("nvarchar(max)"); + + b.Property("TaskId") + .HasColumnType("bigint"); + + b.Property("Text") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.HasKey("Id"); + + b.HasIndex("TaskId"); + + b.ToTable("Questions"); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.UserToCourseFilter", b => + { + b.Property("CourseId") + .HasColumnType("bigint"); + + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("CourseFilterId") + .HasColumnType("bigint"); + + b.HasKey("CourseId", "Id"); + + b.HasIndex("CourseFilterId"); + + b.ToTable("UserToCourseFilters"); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.Assignment", b => + { + b.HasOne("HwProj.CoursesService.API.Models.Course", null) + .WithMany("Assignments") + .HasForeignKey("CourseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.CourseMate", b => + { + b.HasOne("HwProj.CoursesService.API.Models.Course", null) + .WithMany("CourseMates") + .HasForeignKey("CourseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.CourseMentor", b => + { + b.HasOne("HwProj.CoursesService.API.Models.Course", null) + .WithMany("Mentors") + .HasForeignKey("CourseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.Criterion", b => + { + b.HasOne("HwProj.CoursesService.API.Models.HomeworkTask", "Task") + .WithMany("Criteria") + .HasForeignKey("TaskId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Task"); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.GroupMate", b => + { + b.HasOne("HwProj.CoursesService.API.Models.Group", null) + .WithMany("GroupMates") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.Homework", b => + { + b.HasOne("HwProj.CoursesService.API.Models.Course", null) + .WithMany("Homeworks") + .HasForeignKey("CourseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.HomeworkTask", b => + { + b.HasOne("HwProj.CoursesService.API.Models.Homework", "Homework") + .WithMany("Tasks") + .HasForeignKey("HomeworkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Homework"); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.StudentCharacteristics", b => + { + b.HasOne("HwProj.CoursesService.API.Models.CourseMate", null) + .WithOne("Characteristics") + .HasForeignKey("HwProj.CoursesService.API.Models.StudentCharacteristics", "CourseMateId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.TaskModel", b => + { + b.HasOne("HwProj.CoursesService.API.Models.Group", null) + .WithMany("Tasks") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.UserToCourseFilter", b => + { + b.HasOne("HwProj.CoursesService.API.Models.CourseFilter", "CourseFilter") + .WithMany() + .HasForeignKey("CourseFilterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CourseFilter"); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.Course", b => + { + b.Navigation("Assignments"); + + b.Navigation("CourseMates"); + + b.Navigation("Homeworks"); + + b.Navigation("Mentors"); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.CourseMate", b => + { + b.Navigation("Characteristics"); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.Group", b => + { + b.Navigation("GroupMates"); + + b.Navigation("Tasks"); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.Homework", b => + { + b.Navigation("Tasks"); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.HomeworkTask", b => + { + b.Navigation("Criteria"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/HwProj.CoursesService/HwProj.CoursesService.API/Migrations/20261021133302_AddCourseMentorTable.cs b/HwProj.CoursesService/HwProj.CoursesService.API/Migrations/20261021133302_AddCourseMentorTable.cs new file mode 100644 index 000000000..dbe525e64 --- /dev/null +++ b/HwProj.CoursesService/HwProj.CoursesService.API/Migrations/20261021133302_AddCourseMentorTable.cs @@ -0,0 +1,59 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace HwProj.CoursesService.API.Migrations +{ + /// + public partial class AddCourseMentorTable : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "CourseMentors", + columns: table => new + { + UserId = table.Column(type: "nvarchar(450)", nullable: false), + CourseId = table.Column(type: "bigint", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CourseMentors", x => new { x.UserId, x.CourseId }); + table.ForeignKey( + name: "FK_CourseMentors_Courses_CourseId", + column: x => x.CourseId, + principalTable: "Courses", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_CourseMentors_CourseId", + table: "CourseMentors", + column: "CourseId"); + + migrationBuilder.CreateIndex( + name: "IX_CourseMentors_UserId", + table: "CourseMentors", + column: "UserId"); + + migrationBuilder.Sql(@" + INSERT INTO CourseMentors (UserId, CourseId) + SELECT LTRIM(RTRIM(value)), c.Id + FROM Courses c + CROSS APPLY STRING_SPLIT(c.MentorIds, '/') + WHERE c.MentorIds IS NOT NULL + AND c.MentorIds != '' + AND LTRIM(RTRIM(value)) != '' + "); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "CourseMentors"); + } + } +} diff --git a/HwProj.CoursesService/HwProj.CoursesService.API/Migrations/CourseContextModelSnapshot.cs b/HwProj.CoursesService/HwProj.CoursesService.API/Migrations/CourseContextModelSnapshot.cs index 199917cf8..b28d1a701 100644 --- a/HwProj.CoursesService/HwProj.CoursesService.API/Migrations/CourseContextModelSnapshot.cs +++ b/HwProj.CoursesService/HwProj.CoursesService.API/Migrations/CourseContextModelSnapshot.cs @@ -117,6 +117,23 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("CourseMates"); }); + modelBuilder.Entity("HwProj.CoursesService.API.Models.CourseMentor", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("CourseId") + .HasColumnType("bigint"); + + b.HasKey("UserId", "CourseId"); + + b.HasIndex("CourseId"); + + b.HasIndex("UserId"); + + b.ToTable("CourseMentors"); + }); + modelBuilder.Entity("HwProj.CoursesService.API.Models.Criterion", b => { b.Property("Id") @@ -125,12 +142,12 @@ protected override void BuildModel(ModelBuilder modelBuilder) SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); - b.Property("MaxPoints") - .HasColumnType("int"); - b.Property("Arguments") .HasColumnType("nvarchar(max)"); + b.Property("MaxPoints") + .HasColumnType("int"); + b.Property("Name") .HasColumnType("nvarchar(max)"); @@ -380,6 +397,15 @@ protected override void BuildModel(ModelBuilder modelBuilder) .IsRequired(); }); + modelBuilder.Entity("HwProj.CoursesService.API.Models.CourseMentor", b => + { + b.HasOne("HwProj.CoursesService.API.Models.Course", null) + .WithMany("Mentors") + .HasForeignKey("CourseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + modelBuilder.Entity("HwProj.CoursesService.API.Models.Criterion", b => { b.HasOne("HwProj.CoursesService.API.Models.HomeworkTask", "Task") @@ -456,6 +482,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("CourseMates"); b.Navigation("Homeworks"); + + b.Navigation("Mentors"); }); modelBuilder.Entity("HwProj.CoursesService.API.Models.CourseMate", b => diff --git a/HwProj.CoursesService/HwProj.CoursesService.API/Models/CourseContext.cs b/HwProj.CoursesService/HwProj.CoursesService.API/Models/CourseContext.cs index 6016d2d89..c4f3418a9 100644 --- a/HwProj.CoursesService/HwProj.CoursesService.API/Models/CourseContext.cs +++ b/HwProj.CoursesService/HwProj.CoursesService.API/Models/CourseContext.cs @@ -18,6 +18,7 @@ public sealed class CourseContext : DbContext public DbSet UserToCourseFilters { get; set; } public DbSet Questions { get; set; } public DbSet Criteria { get; set; } + public DbSet CourseMentors { get; set; } public CourseContext(DbContextOptions options) : base(options) diff --git a/HwProj.CoursesService/HwProj.CoursesService.API/Repositories/CoursesRepository.cs b/HwProj.CoursesService/HwProj.CoursesService.API/Repositories/CoursesRepository.cs deleted file mode 100644 index 18c8286de..000000000 --- a/HwProj.CoursesService/HwProj.CoursesService.API/Repositories/CoursesRepository.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System.Linq; -using System.Threading.Tasks; -using HwProj.CoursesService.API.Models; -using HwProj.Repositories.Net8; -using Microsoft.EntityFrameworkCore; - -namespace HwProj.CoursesService.API.Repositories -{ - public class CoursesRepository : CrudRepository, ICoursesRepository - { - public CoursesRepository(CourseContext context) - : base(context) - { - } - - public Task GetWithCourseMates(long id) => - Context.Set() - .Include(c => c.CourseMates) - .ThenInclude(c => c.Characteristics) - .AsNoTracking() - .FirstOrDefaultAsync(c => c.Id == id); - - public async Task GetWithHomeworksAsync(long id) - => await Context.Set() - .Include(c => c.Homeworks) - .ThenInclude(h => h.Tasks) - .AsNoTracking() - .FirstOrDefaultAsync(c => c.Id == id); - - public async Task GetWithCourseMatesAndHomeworksAsync(long id, bool withCriteria) - { - var query = Context.Set() - .AsNoTracking() - .Include(c => c.CourseMates) - .ThenInclude(c => c.Characteristics) - .Include(c => c.Homeworks) - .ThenInclude(c => c.Tasks); - - var course = withCriteria - ? await query.ThenInclude(x => x.Criteria).FirstOrDefaultAsync(c => c.Id == id) - : await query.FirstOrDefaultAsync(c => c.Id == id); - - // todo: перенести OrderBy в Include после обновления до EF Core 5.x - course.Homeworks = course.Homeworks.OrderBy(h => h.PublicationDate).ToList(); - return course; - } - - public IQueryable GetAllWithCourseMatesAndHomeworks() - { - return Context.Set() - .Include(c => c.CourseMates) - .ThenInclude(c => c.Characteristics) - .Include(c => c.Homeworks) - .ThenInclude(c => c.Tasks) - .AsNoTracking(); - } - } -} diff --git a/HwProj.CoursesService/HwProj.CoursesService.API/Services/CoursesService.cs b/HwProj.CoursesService/HwProj.CoursesService.API/Services/CoursesService.cs deleted file mode 100644 index dca5492ec..000000000 --- a/HwProj.CoursesService/HwProj.CoursesService.API/Services/CoursesService.cs +++ /dev/null @@ -1,377 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Transactions; -using System.Threading.Tasks; -using HwProj.CoursesService.API.Models; -using HwProj.CoursesService.API.Repositories; -using HwProj.CoursesService.API.Repositories.Groups; -using HwProj.Models.ContentService.DTO; -using HwProj.ContentService.Client; -using HwProj.EventBus.Client.Interfaces; -using HwProj.Models.CoursesService.ViewModels; -using HwProj.CoursesService.API.Domains; -using HwProj.Exceptions; -using HwProj.Models.CoursesService.DTO; -using HwProj.Models.Roles; -using HwProj.NotificationService.Events.CoursesService; -using Microsoft.EntityFrameworkCore; - -namespace HwProj.CoursesService.API.Services -{ - public class CoursesService : ICoursesService - { - private readonly ICoursesRepository _coursesRepository; - private readonly IHomeworksRepository _homeworksRepository; - private readonly ITasksRepository _tasksRepository; - private readonly ICourseMatesRepository _courseMatesRepository; - private readonly IEventBus _eventBus; - private readonly IContentServiceClient _contentServiceClient; - private readonly IGroupsRepository _groupsRepository; - private readonly ICourseFilterService _courseFilterService; - private readonly CourseContext _context; - - public CoursesService(ICoursesRepository coursesRepository, - IHomeworksRepository homeworksRepository, - ITasksRepository tasksRepository, - ICourseMatesRepository courseMatesRepository, - IEventBus eventBus, - IContentServiceClient contentServiceClient, - IGroupsRepository groupsRepository, - ICourseFilterService courseFilterService, - CourseContext context) - { - _coursesRepository = coursesRepository; - _homeworksRepository = homeworksRepository; - _tasksRepository = tasksRepository; - _courseMatesRepository = courseMatesRepository; - _eventBus = eventBus; - _contentServiceClient = contentServiceClient; - _groupsRepository = groupsRepository; - _courseFilterService = courseFilterService; - _context = context; - } - - public async Task GetAllAsync() - { - var courses = await _coursesRepository.GetAllWithCourseMatesAndHomeworks().ToArrayAsync(); - CourseDomain.FillTasksInCourses(courses); - return courses; - } - - public async Task GetByTaskAsync(long taskId, string userId) - { - var task = await _tasksRepository.GetWithHomeworkAsync(taskId); - if (task == null) return null; - - return await GetAsync(task.Homework.CourseId, userId); - } - - public async Task GetAsync(long id, string userId = "", bool asView = false) - { - var course = await _coursesRepository.GetWithCourseMatesAndHomeworksAsync(id, withCriteria: asView); - if (course == null) return null; - - CourseDomain.FillTasksInCourses(course); - - var groups = await _groupsRepository.GetGroupsWithGroupMatesByCourse(course.Id).ToListAsync(); - var courseDto = course.ToCourseDto(); - courseDto.Groups = groups.Select(g => - new GroupViewModel - { - Id = g.Id, - Name = g.Name, - StudentsIds = g.GroupMates.Select(t => t.StudentId).ToArray() - }).ToArray(); - - var result = userId == string.Empty ? courseDto : await _courseFilterService.ApplyFilter(courseDto, userId); - return result; - } - - public async Task GetForEditingAsync(long id) - { - var course = await _coursesRepository.GetWithHomeworksAsync(id); - return course?.ToCourseDto(); - } - - public async Task AddAsync(CreateCourseViewModel courseViewModel, string mentorId) - { - var courseTemplate = courseViewModel.ToCourseTemplate(); - Course? baseCourse = null; - - if (courseViewModel.BaseCourseId != null) - { - baseCourse = await _coursesRepository.GetWithHomeworksAsync(courseViewModel.BaseCourseId.Value); - if (baseCourse == null) throw new InvalidOperationException("Базовый курс не найден"); - if (!baseCourse.MentorIds.Contains(mentorId)) - throw new ForbiddenException( - $"Пользователь с id{mentorId} не является ментором указанного базового курса"); - } - - courseTemplate.Homeworks = - baseCourse?.Homeworks.Select(h => h.ToHomeworkTemplate()).ToList() ?? - new List(); - - using var transactionScope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled); - - var newCourse = await AddFromTemplateAsync(courseTemplate, courseViewModel.StudentIDs, mentorId); - - if (baseCourse != null) - { - var sourceHwIds = baseCourse.Homeworks.Select(hw => hw.Id); - var targetHwIds = newCourse.Homeworks.Select(hw => hw.Id); - - var homeworksMapping = sourceHwIds - .Zip(targetHwIds, (source, target) => new ScopeMappingDto - { - Source = source, - Target = target - }).ToList(); - - if (homeworksMapping.Count > 0) - { - var result = await _contentServiceClient.TransferFilesFromCourse(new CourseFilesTransferDto - { - SourceCourseId = baseCourse.Id, - TargetCourseId = newCourse.Id, - HomeworksMapping = homeworksMapping - }); - - if (!result.Succeeded) throw new TransactionAbortedException(string.Join("\n", result.Errors)); - } - } - - transactionScope.Complete(); - return newCourse.Id; - } - - private async Task AddFromTemplateAsync(CourseTemplate courseTemplate, List studentIds, - string mentorId) - { - using var transactionScope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled); - - var course = courseTemplate.ToCourse(); - course.MentorIds = mentorId; - course.InviteCode = Guid.NewGuid().ToString(); - var courseId = await _coursesRepository.AddAsync(course); - - var homeworks = courseTemplate.Homeworks.Select(hwTemplate => hwTemplate.ToHomework(courseId)); - var homeworkIds = await _homeworksRepository.AddRangeAsync(homeworks); - - var tasks = courseTemplate.Homeworks.SelectMany((hwTemplate, i) => - hwTemplate.Tasks.Select(taskTemplate => taskTemplate.ToHomeworkTask(homeworkIds[i]))); - await _tasksRepository.AddRangeAsync(tasks); - - if (studentIds.Any()) - { - var students = studentIds.Select(studentId => new CourseMate - { - CourseId = courseId, - StudentId = studentId, - IsAccepted = true - }).ToArray(); - - await _courseMatesRepository.AddRangeAsync(students); - - foreach (var student in students) - { - _eventBus.Publish(new LecturerAcceptToCourseEvent - { - CourseId = courseId, - CourseName = course.Name, - MentorIds = course.MentorIds, - StudentId = student.StudentId - }); - } - } - - transactionScope.Complete(); - - var result = await _coursesRepository.GetAsync(courseId); - return result; - } - - public async Task DeleteAsync(long id) - { - await _coursesRepository.DeleteAsync(id); - } - - public async Task UpdateAsync(long courseId, Course updated) - { - await _coursesRepository.UpdateAsync(courseId, c => new Course - { - Name = updated.Name, - GroupName = updated.GroupName, - IsCompleted = updated.IsCompleted, - IsOpen = updated.IsOpen - }); - } - - public async Task AddStudentAsync(long courseId, string studentId) - { - var course = await _coursesRepository.GetAsync(courseId); - var cm = await _courseMatesRepository.FindAsync(cm => cm.CourseId == courseId && cm.StudentId == studentId); - - if (course == null || cm != null) - return false; - - var courseMate = new CourseMate - { - CourseId = courseId, - StudentId = studentId, - IsAccepted = false - }; - - await _courseMatesRepository.AddAsync(courseMate); - _eventBus.Publish(new NewCourseMateEvent - { - CourseId = courseId, - CourseName = course.Name, - MentorIds = course.MentorIds, - StudentId = studentId, - IsAccepted = false - }); - - return true; - } - - public async Task AcceptCourseMateAsync(long courseId, string studentId) - { - var course = await _coursesRepository.GetAsync(courseId); - var cm = await _courseMatesRepository.FindAsync(cm => cm.CourseId == courseId && cm.StudentId == studentId); - - if (course == null || cm == null) - return false; - - await _courseMatesRepository.UpdateAsync(cm.Id, cm => new CourseMate { IsAccepted = true }); - - _eventBus.Publish(new LecturerAcceptToCourseEvent - { - CourseId = courseId, - CourseName = course.Name, - MentorIds = course.MentorIds, - StudentId = studentId - }); - - return true; - } - - public async Task RejectCourseMateAsync(long courseId, string studentId) - { - var course = await _coursesRepository.GetAsync(courseId); - var cm = await _courseMatesRepository.FindAsync(cm => cm.CourseId == courseId && cm.StudentId == studentId); - - if (course == null || cm == null) - return false; - - await _courseMatesRepository.DeleteAsync(cm.Id); - - _eventBus.Publish(new LecturerRejectToCourseEvent - { - CourseId = courseId, - CourseName = course.Name, - MentorIds = course.MentorIds, - StudentId = studentId - }); - return true; - } - - public async Task GetUserCoursesAsync(string userId, string role) - { - var isMentor = role == Roles.LecturerRole || role == Roles.ExpertRole; - - var courses = isMentor - ? _coursesRepository.FindAll(c => c.MentorIds.Contains(userId)) - : _coursesRepository.FindAll(c => c.CourseMates.Any(cm => cm.IsAccepted && cm.StudentId == userId)); - - var coursesWithValues = await courses - .Include(c => c.CourseMates) - .Include(c => c.Homeworks).ThenInclude(t => t.Tasks) - .ToArrayAsync(); - - CourseDomain.FillTasksInCourses(coursesWithValues); - - var result = await _courseFilterService.ApplyFiltersToCourses( - userId, coursesWithValues.Select(c => c.ToCourseDto()).ToArray()); - - if (role == Roles.ExpertRole) - { - foreach (var courseDto in result) - { - courseDto.TaskId = courseDto.Homeworks - .SelectMany(h => h.Tasks) - .FirstOrDefault()?.Id; - } - } - - return result; - } - - public async Task AcceptLecturerAsync(long courseId, string lecturerEmail, string lecturerId) - { - var course = await _coursesRepository.GetAsync(courseId); - if (course == null) return false; - if (!course.MentorIds.Contains(lecturerId)) - { - var newMentors = course.MentorIds + "/" + lecturerId; - await _coursesRepository.UpdateAsync(courseId, с => new Course - { - MentorIds = newMentors, - }); - - _eventBus.Publish(new LecturerInvitedToCourseEvent - { - CourseId = courseId, - CourseName = course.Name, - MentorId = lecturerId, - MentorEmail = lecturerEmail - }); - //TODO: remove - await RejectCourseMateAsync(courseId, lecturerId); - } - - return true; - } - - public async Task GetCourseLecturers(long courseId) - { - var course = await _coursesRepository.GetAsync(courseId); - return course.MentorIds.Split('/'); - } - - public async Task HasStudent(long courseId, string studentId) - { - var student = - await _courseMatesRepository.FindAsync(x => x.CourseId == courseId && x.StudentId == studentId); - return student != null; - } - - public async Task UpdateStudentCharacteristics(long courseId, string studentId, - StudentCharacteristicsDto characteristics) - { - var courseMate = - await _courseMatesRepository.FindAll(x => x.CourseId == courseId && x.StudentId == studentId) - .Include(x => x.Characteristics) - .SingleOrDefaultAsync(); - - if (courseMate == null) return false; - - var tags = string.Join(";", characteristics.Tags.Where(t => !string.IsNullOrWhiteSpace(t)).Distinct()); - - var hasCharacteristic = courseMate.Characteristics != null; - - courseMate.Characteristics ??= new StudentCharacteristics(); - courseMate.Characteristics.CourseMateId = courseMate.Id; - courseMate.Characteristics.Description = characteristics.Description; - courseMate.Characteristics.Tags = tags; - - _context.Attach(courseMate); - _context.Entry(courseMate).State = EntityState.Modified; - _context.Entry(courseMate.Characteristics).State = - hasCharacteristic ? EntityState.Modified : EntityState.Added; - - await _context.SaveChangesAsync(); - return true; - } - } -} From cbe8d1e6f7d7d4bf3c6eb88f43c35529a1c2fbe5 Mon Sep 17 00:00:00 2001 From: Andrew_Baranov Date: Thu, 22 Oct 2026 17:52:31 +0300 Subject: [PATCH 3/5] refactor(CoursesService): use Mentors.Any for filtering instead of LIKE --- .../Domains/MappingExtensions.cs | 4 +- ...20250424012738_'CourseMentors'.Designer.cs | 350 --------------- .../20250424012738_'CourseMentors'.cs | 63 --- .../Repositories/CoursesRepository.cs | 62 +++ .../Services/CoursesService.cs | 401 ++++++++++++++++++ 5 files changed, 465 insertions(+), 415 deletions(-) delete mode 100644 HwProj.CoursesService/HwProj.CoursesService.API/Migrations/20250424012738_'CourseMentors'.Designer.cs delete mode 100644 HwProj.CoursesService/HwProj.CoursesService.API/Migrations/20250424012738_'CourseMentors'.cs create mode 100644 HwProj.CoursesService/HwProj.CoursesService.API/Repositories/CoursesRepository.cs create mode 100644 HwProj.CoursesService/HwProj.CoursesService.API/Services/CoursesService.cs diff --git a/HwProj.CoursesService/HwProj.CoursesService.API/Domains/MappingExtensions.cs b/HwProj.CoursesService/HwProj.CoursesService.API/Domains/MappingExtensions.cs index 48ce7a8ea..0b536de7b 100644 --- a/HwProj.CoursesService/HwProj.CoursesService.API/Domains/MappingExtensions.cs +++ b/HwProj.CoursesService/HwProj.CoursesService.API/Domains/MappingExtensions.cs @@ -99,7 +99,7 @@ public static CourseDTO ToCourseDto(this Course course) Name = course.Name, GroupName = course.GroupName, IsCompleted = course.IsCompleted, - MentorIds = course.MentorIds.Split('/', SplitOptions), + MentorIds = course.Mentors.Select(x => x.UserId).ToArray(), IsOpen = course.IsOpen, InviteCode = course.InviteCode, CourseMates = course.CourseMates.Select(cm => cm.ToCourseMateViewModel()).ToArray(), @@ -113,7 +113,7 @@ public static CoursePreview ToCoursePreview(this Course course) Name = course.Name, GroupName = course.GroupName, IsCompleted = course.IsCompleted, - MentorIds = course.MentorIds.Split('/', SplitOptions), + MentorIds = course.Mentors.Select(x => x.UserId).ToArray(), }; public static Criterion ToCriterion(this CriterionViewModel criterion) => new Criterion diff --git a/HwProj.CoursesService/HwProj.CoursesService.API/Migrations/20250424012738_'CourseMentors'.Designer.cs b/HwProj.CoursesService/HwProj.CoursesService.API/Migrations/20250424012738_'CourseMentors'.Designer.cs deleted file mode 100644 index 315d4b145..000000000 --- a/HwProj.CoursesService/HwProj.CoursesService.API/Migrations/20250424012738_'CourseMentors'.Designer.cs +++ /dev/null @@ -1,350 +0,0 @@ -// -using System; -using HwProj.CoursesService.API.Models; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; - -namespace HwProj.CoursesService.API.Migrations -{ - [DbContext(typeof(CourseContext))] - [Migration("20250424012738_'CourseMentors'")] - partial class CourseMentors - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "2.2.6-servicing-10079") - .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - modelBuilder.Entity("HwProj.CoursesService.API.Models.Assignment", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("CourseId"); - - b.Property("MentorId"); - - b.Property("StudentId"); - - b.HasKey("Id"); - - b.HasIndex("CourseId"); - - b.ToTable("Assignments"); - }); - - modelBuilder.Entity("HwProj.CoursesService.API.Models.Course", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("GroupName"); - - b.Property("InviteCode"); - - b.Property("IsCompleted"); - - b.Property("IsOpen"); - - b.Property("MentorIds"); - - b.Property("Name"); - - b.HasKey("Id"); - - b.ToTable("Courses"); - }); - - modelBuilder.Entity("HwProj.CoursesService.API.Models.CourseFilter", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("FilterJson"); - - b.HasKey("Id"); - - b.ToTable("CourseFilters"); - }); - - modelBuilder.Entity("HwProj.CoursesService.API.Models.CourseMate", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("CourseId"); - - b.Property("IsAccepted"); - - b.Property("StudentId"); - - b.HasKey("Id"); - - b.HasIndex("CourseId"); - - b.ToTable("CourseMates"); - }); - - modelBuilder.Entity("HwProj.CoursesService.API.Models.CourseMentor", b => - { - b.Property("UserId"); - - b.Property("CourseId"); - - b.HasKey("UserId", "CourseId"); - - b.HasIndex("CourseId"); - - b.HasIndex("UserId"); - - b.ToTable("CourseMentor"); - }); - - modelBuilder.Entity("HwProj.CoursesService.API.Models.Group", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("CourseId"); - - b.Property("Name"); - - b.HasKey("Id"); - - b.ToTable("Groups"); - }); - - modelBuilder.Entity("HwProj.CoursesService.API.Models.GroupMate", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("GroupId"); - - b.Property("StudentId") - .IsRequired(); - - b.HasKey("Id"); - - b.HasAlternateKey("GroupId", "StudentId"); - - b.ToTable("GroupMates"); - }); - - modelBuilder.Entity("HwProj.CoursesService.API.Models.Homework", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("CourseId"); - - b.Property("DeadlineDate"); - - b.Property("Description"); - - b.Property("HasDeadline"); - - b.Property("IsDeadlineStrict"); - - b.Property("PublicationDate"); - - b.Property("Tags"); - - b.Property("Title"); - - b.HasKey("Id"); - - b.HasIndex("CourseId"); - - b.ToTable("Homeworks"); - }); - - modelBuilder.Entity("HwProj.CoursesService.API.Models.HomeworkTask", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("DeadlineDate"); - - b.Property("Description"); - - b.Property("HasDeadline"); - - b.Property("HomeworkId"); - - b.Property("IsDeadlineStrict"); - - b.Property("MaxRating"); - - b.Property("PublicationDate"); - - b.Property("Title"); - - b.HasKey("Id"); - - b.HasIndex("HomeworkId"); - - b.ToTable("Tasks"); - }); - - modelBuilder.Entity("HwProj.CoursesService.API.Models.StudentCharacteristics", b => - { - b.Property("CourseMateId"); - - b.Property("Description"); - - b.Property("Tags"); - - b.HasKey("CourseMateId"); - - b.ToTable("StudentCharacteristics"); - }); - - modelBuilder.Entity("HwProj.CoursesService.API.Models.TaskModel", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("GroupId"); - - b.Property("TaskId"); - - b.HasKey("Id"); - - b.HasIndex("GroupId"); - - b.ToTable("TasksModels"); - }); - - modelBuilder.Entity("HwProj.CoursesService.API.Models.TaskQuestion", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("Answer") - .HasMaxLength(1000); - - b.Property("IsPrivate"); - - b.Property("LecturerId"); - - b.Property("StudentId"); - - b.Property("TaskId"); - - b.Property("Text") - .HasMaxLength(1000); - - b.HasKey("Id"); - - b.HasIndex("TaskId"); - - b.ToTable("Questions"); - }); - - modelBuilder.Entity("HwProj.CoursesService.API.Models.UserToCourseFilter", b => - { - b.Property("CourseId"); - - b.Property("UserId"); - - b.Property("CourseFilterId"); - - b.HasKey("CourseId", "UserId"); - - b.HasIndex("CourseFilterId"); - - b.ToTable("UserToCourseFilters"); - }); - - modelBuilder.Entity("HwProj.CoursesService.API.Models.Assignment", b => - { - b.HasOne("HwProj.CoursesService.API.Models.Course") - .WithMany("Assignments") - .HasForeignKey("CourseId") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("HwProj.CoursesService.API.Models.CourseMate", b => - { - b.HasOne("HwProj.CoursesService.API.Models.Course") - .WithMany("CourseMates") - .HasForeignKey("CourseId") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("HwProj.CoursesService.API.Models.CourseMentor", b => - { - b.HasOne("HwProj.CoursesService.API.Models.Course") - .WithMany("Mentors") - .HasForeignKey("CourseId") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("HwProj.CoursesService.API.Models.GroupMate", b => - { - b.HasOne("HwProj.CoursesService.API.Models.Group") - .WithMany("GroupMates") - .HasForeignKey("GroupId") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("HwProj.CoursesService.API.Models.Homework", b => - { - b.HasOne("HwProj.CoursesService.API.Models.Course") - .WithMany("Homeworks") - .HasForeignKey("CourseId") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("HwProj.CoursesService.API.Models.HomeworkTask", b => - { - b.HasOne("HwProj.CoursesService.API.Models.Homework", "Homework") - .WithMany("Tasks") - .HasForeignKey("HomeworkId") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("HwProj.CoursesService.API.Models.StudentCharacteristics", b => - { - b.HasOne("HwProj.CoursesService.API.Models.CourseMate") - .WithOne("Characteristics") - .HasForeignKey("HwProj.CoursesService.API.Models.StudentCharacteristics", "CourseMateId") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("HwProj.CoursesService.API.Models.TaskModel", b => - { - b.HasOne("HwProj.CoursesService.API.Models.Group") - .WithMany("Tasks") - .HasForeignKey("GroupId") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("HwProj.CoursesService.API.Models.UserToCourseFilter", b => - { - b.HasOne("HwProj.CoursesService.API.Models.CourseFilter", "CourseFilter") - .WithMany() - .HasForeignKey("CourseFilterId") - .OnDelete(DeleteBehavior.Cascade); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/HwProj.CoursesService/HwProj.CoursesService.API/Migrations/20250424012738_'CourseMentors'.cs b/HwProj.CoursesService/HwProj.CoursesService.API/Migrations/20250424012738_'CourseMentors'.cs deleted file mode 100644 index 974e37435..000000000 --- a/HwProj.CoursesService/HwProj.CoursesService.API/Migrations/20250424012738_'CourseMentors'.cs +++ /dev/null @@ -1,63 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -namespace HwProj.CoursesService.API.Migrations -{ - public partial class CourseMentors : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "CourseMentor", - columns: table => new - { - UserId = table.Column(nullable: false), - CourseId = table.Column(nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_CourseMentor", x => new { x.UserId, x.CourseId }); - table.ForeignKey( - name: "FK_CourseMentor_Courses_CourseId", - column: x => x.CourseId, - principalTable: "Courses", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateIndex( - name: "IX_CourseMentor_CourseId", - table: "CourseMentor", - column: "CourseId"); - - migrationBuilder.CreateIndex( - name: "IX_CourseMentor_UserId", - table: "CourseMentor", - column: "UserId"); - - // Миграция данных: Перенос MentorIds в таблицу CourseMentor - // language=SQL - migrationBuilder.Sql(@" --- Разделим MentorIds на элементы, преобразуем в строки, удалим дубликаты и вставим в таблицу CourseMentor -WITH SplitMentorIds AS ( - SELECT - c.Id AS CourseId, - LTRIM(RTRIM(value)) AS MentorId - FROM Courses c - CROSS APPLY STRING_SPLIT(c.MentorIds, '/') -- Разделяем MentorIds по запятой -) -, DistinctMentors AS ( - SELECT DISTINCT - MentorId, - CourseId - FROM SplitMentorIds - WHERE MentorId <> '' -- Исключаем пустые строки -) -INSERT INTO CourseMentor (UserId, CourseId) -SELECT - MentorId, - CourseId -FROM DistinctMentors; -"); - } - } -} diff --git a/HwProj.CoursesService/HwProj.CoursesService.API/Repositories/CoursesRepository.cs b/HwProj.CoursesService/HwProj.CoursesService.API/Repositories/CoursesRepository.cs new file mode 100644 index 000000000..36f40a527 --- /dev/null +++ b/HwProj.CoursesService/HwProj.CoursesService.API/Repositories/CoursesRepository.cs @@ -0,0 +1,62 @@ +using System.Linq; +using System.Threading.Tasks; +using HwProj.CoursesService.API.Models; +using HwProj.Repositories.Net8; +using Microsoft.EntityFrameworkCore; + +namespace HwProj.CoursesService.API.Repositories +{ + public class CoursesRepository : CrudRepository, ICoursesRepository + { + public CoursesRepository(CourseContext context) + : base(context) + { + } + + public Task GetWithCourseMates(long id) => + Context.Set() + .Include(c => c.Mentors) + .Include(c => c.CourseMates) + .ThenInclude(c => c.Characteristics) + .AsNoTracking() + .FirstOrDefaultAsync(c => c.Id == id); + + public async Task GetWithHomeworksAsync(long id) + => await Context.Set() + .Include(c => c.Mentors) + .Include(c => c.Homeworks) + .ThenInclude(h => h.Tasks) + .AsNoTracking() + .FirstOrDefaultAsync(c => c.Id == id); + + public async Task GetWithCourseMatesAndHomeworksAsync(long id, bool withCriteria) + { + var query = Context.Set() + .AsNoTracking() + .Include(c => c.Mentors) + .Include(c => c.CourseMates) + .ThenInclude(c => c.Characteristics) + .Include(c => c.Homeworks) + .ThenInclude(c => c.Tasks); + + var course = withCriteria + ? await query.ThenInclude(x => x.Criteria).FirstOrDefaultAsync(c => c.Id == id) + : await query.FirstOrDefaultAsync(c => c.Id == id); + + // todo: перенести OrderBy в Include после обновления до EF Core 5.x + course.Homeworks = course.Homeworks.OrderBy(h => h.PublicationDate).ToList(); + return course; + } + + public IQueryable GetAllWithCourseMatesAndHomeworks() + { + return Context.Set() + .Include(c => c.Mentors) + .Include(c => c.CourseMates) + .ThenInclude(c => c.Characteristics) + .Include(c => c.Homeworks) + .ThenInclude(c => c.Tasks) + .AsNoTracking(); + } + } +} diff --git a/HwProj.CoursesService/HwProj.CoursesService.API/Services/CoursesService.cs b/HwProj.CoursesService/HwProj.CoursesService.API/Services/CoursesService.cs new file mode 100644 index 000000000..fe412a300 --- /dev/null +++ b/HwProj.CoursesService/HwProj.CoursesService.API/Services/CoursesService.cs @@ -0,0 +1,401 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Transactions; +using System.Threading.Tasks; +using HwProj.CoursesService.API.Models; +using HwProj.CoursesService.API.Repositories; +using HwProj.CoursesService.API.Repositories.Groups; +using HwProj.Models.ContentService.DTO; +using HwProj.ContentService.Client; +using HwProj.EventBus.Client.Interfaces; +using HwProj.Models.CoursesService.ViewModels; +using HwProj.CoursesService.API.Domains; +using HwProj.Exceptions; +using HwProj.Models.CoursesService.DTO; +using HwProj.Models.Roles; +using HwProj.NotificationService.Events.CoursesService; +using Microsoft.EntityFrameworkCore; + +namespace HwProj.CoursesService.API.Services +{ + public class CoursesService : ICoursesService + { + private readonly ICoursesRepository _coursesRepository; + private readonly IHomeworksRepository _homeworksRepository; + private readonly ITasksRepository _tasksRepository; + private readonly ICourseMatesRepository _courseMatesRepository; + private readonly IEventBus _eventBus; + private readonly IContentServiceClient _contentServiceClient; + private readonly IGroupsRepository _groupsRepository; + private readonly ICourseFilterService _courseFilterService; + private readonly CourseContext _context; + + public CoursesService(ICoursesRepository coursesRepository, + IHomeworksRepository homeworksRepository, + ITasksRepository tasksRepository, + ICourseMatesRepository courseMatesRepository, + IEventBus eventBus, + IContentServiceClient contentServiceClient, + IGroupsRepository groupsRepository, + ICourseFilterService courseFilterService, + CourseContext context) + { + _coursesRepository = coursesRepository; + _homeworksRepository = homeworksRepository; + _tasksRepository = tasksRepository; + _courseMatesRepository = courseMatesRepository; + _eventBus = eventBus; + _contentServiceClient = contentServiceClient; + _groupsRepository = groupsRepository; + _courseFilterService = courseFilterService; + _context = context; + } + + public async Task GetAllAsync() + { + var courses = await _coursesRepository.GetAllWithCourseMatesAndHomeworks().ToArrayAsync(); + CourseDomain.FillTasksInCourses(courses); + return courses; + } + + public async Task GetByTaskAsync(long taskId, string userId) + { + var task = await _tasksRepository.GetWithHomeworkAsync(taskId); + if (task == null) return null; + + return await GetAsync(task.Homework.CourseId, userId); + } + + public async Task GetAsync(long id, string userId = "", bool asView = false) + { + var course = await _coursesRepository.GetWithCourseMatesAndHomeworksAsync(id, withCriteria: asView); + if (course == null) return null; + + CourseDomain.FillTasksInCourses(course); + + var groups = await _groupsRepository.GetGroupsWithGroupMatesByCourse(course.Id).ToListAsync(); + var courseDto = course.ToCourseDto(); + courseDto.Groups = groups.Select(g => + new GroupViewModel + { + Id = g.Id, + Name = g.Name, + StudentsIds = g.GroupMates.Select(t => t.StudentId).ToArray() + }).ToArray(); + + var result = userId == string.Empty ? courseDto : await _courseFilterService.ApplyFilter(courseDto, userId); + return result; + } + + public async Task GetForEditingAsync(long id) + { + var course = await _coursesRepository.GetWithHomeworksAsync(id); + return course?.ToCourseDto(); + } + + public async Task AddAsync(CreateCourseViewModel courseViewModel, string mentorId) + { + var courseTemplate = courseViewModel.ToCourseTemplate(); + Course? baseCourse = null; + + if (courseViewModel.BaseCourseId != null) + { + baseCourse = await _coursesRepository.GetWithHomeworksAsync(courseViewModel.BaseCourseId.Value); + if (baseCourse == null) throw new InvalidOperationException("Базовый курс не найден"); + if (!baseCourse.Mentors.Any(m => m.UserId == mentorId)) + throw new ForbiddenException( + $"Пользователь с id{mentorId} не является ментором указанного базового курса"); + } + + courseTemplate.Homeworks = + baseCourse?.Homeworks.Select(h => h.ToHomeworkTemplate()).ToList() ?? + new List(); + + using var transactionScope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled); + + var newCourse = await AddFromTemplateAsync(courseTemplate, courseViewModel.StudentIDs, mentorId); + + if (baseCourse != null) + { + var sourceHwIds = baseCourse.Homeworks.Select(hw => hw.Id); + var targetHwIds = newCourse.Homeworks.Select(hw => hw.Id); + + var homeworksMapping = sourceHwIds + .Zip(targetHwIds, (source, target) => new ScopeMappingDto + { + Source = source, + Target = target + }).ToList(); + + if (homeworksMapping.Count > 0) + { + var result = await _contentServiceClient.TransferFilesFromCourse(new CourseFilesTransferDto + { + SourceCourseId = baseCourse.Id, + TargetCourseId = newCourse.Id, + HomeworksMapping = homeworksMapping + }); + + if (!result.Succeeded) throw new TransactionAbortedException(string.Join("\n", result.Errors)); + } + } + + transactionScope.Complete(); + return newCourse.Id; + } + + private async Task AddFromTemplateAsync(CourseTemplate courseTemplate, List studentIds, + string mentorId) + { + using var transactionScope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled); + + var course = courseTemplate.ToCourse(); + course.Mentors = new List { new CourseMentor { UserId = mentorId } }; + course.InviteCode = Guid.NewGuid().ToString(); + var courseId = await _coursesRepository.AddAsync(course); + + var homeworks = courseTemplate.Homeworks.Select(hwTemplate => hwTemplate.ToHomework(courseId)); + var homeworkIds = await _homeworksRepository.AddRangeAsync(homeworks); + + var tasks = courseTemplate.Homeworks.SelectMany((hwTemplate, i) => + hwTemplate.Tasks.Select(taskTemplate => taskTemplate.ToHomeworkTask(homeworkIds[i]))); + await _tasksRepository.AddRangeAsync(tasks); + + if (studentIds.Any()) + { + var students = studentIds.Select(studentId => new CourseMate + { + CourseId = courseId, + StudentId = studentId, + IsAccepted = true + }).ToArray(); + + await _courseMatesRepository.AddRangeAsync(students); + + foreach (var student in students) + { + _eventBus.Publish(new LecturerAcceptToCourseEvent + { + CourseId = courseId, + CourseName = course.Name, + MentorIds = string.Join("/", course.Mentors.Select(m => m.UserId)), + StudentId = student.StudentId + }); + } + } + + transactionScope.Complete(); + + var result = await _coursesRepository.GetAsync(courseId); + return result; + } + + public async Task DeleteAsync(long id) + { + await _coursesRepository.DeleteAsync(id); + } + + public async Task UpdateAsync(long courseId, Course updated) + { + await _coursesRepository.UpdateAsync(courseId, c => new Course + { + Name = updated.Name, + GroupName = updated.GroupName, + IsCompleted = updated.IsCompleted, + IsOpen = updated.IsOpen + }); + } + + public async Task AddStudentAsync(long courseId, string studentId) + { + var course = await _coursesRepository.GetAsync(courseId); + var cm = await _courseMatesRepository.FindAsync(cm => cm.CourseId == courseId && cm.StudentId == studentId); + + if (course == null || cm != null) + return false; + + var courseMate = new CourseMate + { + CourseId = courseId, + StudentId = studentId, + IsAccepted = false + }; + + await _courseMatesRepository.AddAsync(courseMate); + + var mentorIds = await _context.CourseMentors + .Where(m => m.CourseId == courseId) + .Select(m => m.UserId) + .ToArrayAsync(); + + _eventBus.Publish(new NewCourseMateEvent + { + CourseId = courseId, + CourseName = course.Name, + MentorIds = string.Join("/", mentorIds), + StudentId = studentId, + IsAccepted = false + }); + + return true; + } + + public async Task AcceptCourseMateAsync(long courseId, string studentId) + { + var course = await _coursesRepository.GetAsync(courseId); + var cm = await _courseMatesRepository.FindAsync(cm => cm.CourseId == courseId && cm.StudentId == studentId); + + if (course == null || cm == null) + return false; + + await _courseMatesRepository.UpdateAsync(cm.Id, cm => new CourseMate { IsAccepted = true }); + + var mentorIds = await _context.CourseMentors + .Where(m => m.CourseId == courseId) + .Select(m => m.UserId) + .ToArrayAsync(); + + _eventBus.Publish(new LecturerAcceptToCourseEvent + { + CourseId = courseId, + CourseName = course.Name, + MentorIds = string.Join("/", mentorIds), + StudentId = studentId + }); + + return true; + } + + public async Task RejectCourseMateAsync(long courseId, string studentId) + { + var course = await _coursesRepository.GetAsync(courseId); + var cm = await _courseMatesRepository.FindAsync(cm => cm.CourseId == courseId && cm.StudentId == studentId); + + if (course == null || cm == null) + return false; + + await _courseMatesRepository.DeleteAsync(cm.Id); + + var mentorIds = await _context.CourseMentors + .Where(m => m.CourseId == courseId) + .Select(m => m.UserId) + .ToArrayAsync(); + + _eventBus.Publish(new LecturerRejectToCourseEvent + { + CourseId = courseId, + CourseName = course.Name, + MentorIds = string.Join("/", mentorIds), + StudentId = studentId + }); + return true; + } + + public async Task GetUserCoursesAsync(string userId, string role) + { + var isMentor = role == Roles.LecturerRole || role == Roles.ExpertRole; + + var courses = isMentor + ? _coursesRepository.FindAll(c => c.Mentors.Any(m => m.UserId == userId)) + : _coursesRepository.FindAll(c => c.CourseMates.Any(cm => cm.IsAccepted && cm.StudentId == userId)); + + var coursesWithValues = await courses + .Include(c => c.Mentors) + .Include(c => c.CourseMates) + .Include(c => c.Homeworks).ThenInclude(t => t.Tasks) + .ToArrayAsync(); + + CourseDomain.FillTasksInCourses(coursesWithValues); + + var result = await _courseFilterService.ApplyFiltersToCourses( + userId, coursesWithValues.Select(c => c.ToCourseDto()).ToArray()); + + if (role == Roles.ExpertRole) + { + foreach (var courseDto in result) + { + courseDto.TaskId = courseDto.Homeworks + .SelectMany(h => h.Tasks) + .FirstOrDefault()?.Id; + } + } + + return result; + } + + public async Task AcceptLecturerAsync(long courseId, string lecturerEmail, string lecturerId) + { + var course = await _coursesRepository.GetAsync(courseId); + if (course == null) return false; + + var alreadyMentor = await _context.CourseMentors + .AnyAsync(m => m.CourseId == courseId && m.UserId == lecturerId); + + if (!alreadyMentor) + { + await _context.CourseMentors.AddAsync(new CourseMentor + { + CourseId = courseId, + UserId = lecturerId + }); + await _context.SaveChangesAsync(); + + _eventBus.Publish(new LecturerInvitedToCourseEvent + { + CourseId = courseId, + CourseName = course.Name, + MentorId = lecturerId, + MentorEmail = lecturerEmail + }); + //TODO: remove + await RejectCourseMateAsync(courseId, lecturerId); + } + + return true; + } + + public async Task GetCourseLecturers(long courseId) + { + return await _context.CourseMentors + .Where(m => m.CourseId == courseId) + .Select(m => m.UserId) + .ToArrayAsync(); + } + + public async Task HasStudent(long courseId, string studentId) + { + var student = + await _courseMatesRepository.FindAsync(x => x.CourseId == courseId && x.StudentId == studentId); + return student != null; + } + + public async Task UpdateStudentCharacteristics(long courseId, string studentId, + StudentCharacteristicsDto characteristics) + { + var courseMate = + await _courseMatesRepository.FindAll(x => x.CourseId == courseId && x.StudentId == studentId) + .Include(x => x.Characteristics) + .SingleOrDefaultAsync(); + + if (courseMate == null) return false; + + var tags = string.Join(";", characteristics.Tags.Where(t => !string.IsNullOrWhiteSpace(t)).Distinct()); + + var hasCharacteristic = courseMate.Characteristics != null; + + courseMate.Characteristics ??= new StudentCharacteristics(); + courseMate.Characteristics.CourseMateId = courseMate.Id; + courseMate.Characteristics.Description = characteristics.Description; + courseMate.Characteristics.Tags = tags; + + _context.Attach(courseMate); + _context.Entry(courseMate).State = EntityState.Modified; + _context.Entry(courseMate.Characteristics).State = + hasCharacteristic ? EntityState.Modified : EntityState.Added; + + await _context.SaveChangesAsync(); + return true; + } + } +} From b55db47eb71cfdaca0292e56af3abc3c1edad450 Mon Sep 17 00:00:00 2001 From: Andrew_Baranov Date: Thu, 22 Oct 2026 18:11:58 +0300 Subject: [PATCH 4/5] refactor(CoursesService): drop legacy Course.MentorIds column --- .../20261022145922_DropMentorIds.Designer.cs | 513 ++++++++++++++++++ .../20261022145922_DropMentorIds.cs | 28 + .../Migrations/CourseContextModelSnapshot.cs | 3 - .../Models/Course.cs | 1 - 4 files changed, 541 insertions(+), 4 deletions(-) create mode 100644 HwProj.CoursesService/HwProj.CoursesService.API/Migrations/20261022145922_DropMentorIds.Designer.cs create mode 100644 HwProj.CoursesService/HwProj.CoursesService.API/Migrations/20261022145922_DropMentorIds.cs diff --git a/HwProj.CoursesService/HwProj.CoursesService.API/Migrations/20261022145922_DropMentorIds.Designer.cs b/HwProj.CoursesService/HwProj.CoursesService.API/Migrations/20261022145922_DropMentorIds.Designer.cs new file mode 100644 index 000000000..cce404a3a --- /dev/null +++ b/HwProj.CoursesService/HwProj.CoursesService.API/Migrations/20261022145922_DropMentorIds.Designer.cs @@ -0,0 +1,513 @@ +// +using System; +using HwProj.CoursesService.API.Models; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace HwProj.CoursesService.API.Migrations +{ + [DbContext(typeof(CourseContext))] + [Migration("20261022145922_DropMentorIds")] + partial class DropMentorIds + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.9") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.Assignment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CourseId") + .HasColumnType("bigint"); + + b.Property("MentorId") + .HasColumnType("nvarchar(max)"); + + b.Property("StudentId") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("CourseId"); + + b.ToTable("Assignments"); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.Course", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("GroupName") + .HasColumnType("nvarchar(max)"); + + b.Property("InviteCode") + .HasColumnType("nvarchar(max)"); + + b.Property("IsCompleted") + .HasColumnType("bit"); + + b.Property("IsOpen") + .HasColumnType("bit"); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Courses"); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.CourseFilter", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("FilterJson") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("CourseFilters"); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.CourseMate", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CourseId") + .HasColumnType("bigint"); + + b.Property("IsAccepted") + .HasColumnType("bit"); + + b.Property("StudentId") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("CourseId"); + + b.ToTable("CourseMates"); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.CourseMentor", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("CourseId") + .HasColumnType("bigint"); + + b.HasKey("UserId", "CourseId"); + + b.HasIndex("CourseId"); + + b.HasIndex("UserId"); + + b.ToTable("CourseMentors"); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.Criterion", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Arguments") + .HasColumnType("nvarchar(max)"); + + b.Property("MaxPoints") + .HasColumnType("int"); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.Property("TaskId") + .HasColumnType("bigint"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("TaskId"); + + b.ToTable("Criteria"); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CourseId") + .HasColumnType("bigint"); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.GroupMate", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("GroupId") + .HasColumnType("bigint"); + + b.Property("StudentId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasAlternateKey("GroupId", "StudentId"); + + b.ToTable("GroupMates"); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.Homework", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CourseId") + .HasColumnType("bigint"); + + b.Property("DeadlineDate") + .HasColumnType("datetime2"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("GroupId") + .HasColumnType("bigint"); + + b.Property("HasDeadline") + .HasColumnType("bit"); + + b.Property("IsDeadlineStrict") + .HasColumnType("bit"); + + b.Property("PublicationDate") + .HasColumnType("datetime2"); + + b.Property("Tags") + .HasColumnType("nvarchar(max)"); + + b.Property("Title") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("CourseId"); + + b.ToTable("Homeworks"); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.HomeworkTask", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("DeadlineDate") + .HasColumnType("datetime2"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("HasDeadline") + .HasColumnType("bit"); + + b.Property("HomeworkId") + .HasColumnType("bigint"); + + b.Property("IsBonusExplicit") + .HasColumnType("bit"); + + b.Property("IsDeadlineStrict") + .HasColumnType("bit"); + + b.Property("MaxRating") + .HasColumnType("int"); + + b.Property("PublicationDate") + .HasColumnType("datetime2"); + + b.Property("Title") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("HomeworkId"); + + b.ToTable("Tasks"); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.StudentCharacteristics", b => + { + b.Property("CourseMateId") + .HasColumnType("bigint"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Tags") + .HasColumnType("nvarchar(max)"); + + b.HasKey("CourseMateId"); + + b.ToTable("StudentCharacteristics"); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.TaskModel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("GroupId") + .HasColumnType("bigint"); + + b.Property("TaskId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("GroupId"); + + b.ToTable("TasksModels"); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.TaskQuestion", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Answer") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("IsPrivate") + .HasColumnType("bit"); + + b.Property("LecturerId") + .HasColumnType("nvarchar(max)"); + + b.Property("StudentId") + .HasColumnType("nvarchar(max)"); + + b.Property("TaskId") + .HasColumnType("bigint"); + + b.Property("Text") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.HasKey("Id"); + + b.HasIndex("TaskId"); + + b.ToTable("Questions"); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.UserToCourseFilter", b => + { + b.Property("CourseId") + .HasColumnType("bigint"); + + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("CourseFilterId") + .HasColumnType("bigint"); + + b.HasKey("CourseId", "Id"); + + b.HasIndex("CourseFilterId"); + + b.ToTable("UserToCourseFilters"); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.Assignment", b => + { + b.HasOne("HwProj.CoursesService.API.Models.Course", null) + .WithMany("Assignments") + .HasForeignKey("CourseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.CourseMate", b => + { + b.HasOne("HwProj.CoursesService.API.Models.Course", null) + .WithMany("CourseMates") + .HasForeignKey("CourseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.CourseMentor", b => + { + b.HasOne("HwProj.CoursesService.API.Models.Course", null) + .WithMany("Mentors") + .HasForeignKey("CourseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.Criterion", b => + { + b.HasOne("HwProj.CoursesService.API.Models.HomeworkTask", "Task") + .WithMany("Criteria") + .HasForeignKey("TaskId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Task"); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.GroupMate", b => + { + b.HasOne("HwProj.CoursesService.API.Models.Group", null) + .WithMany("GroupMates") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.Homework", b => + { + b.HasOne("HwProj.CoursesService.API.Models.Course", null) + .WithMany("Homeworks") + .HasForeignKey("CourseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.HomeworkTask", b => + { + b.HasOne("HwProj.CoursesService.API.Models.Homework", "Homework") + .WithMany("Tasks") + .HasForeignKey("HomeworkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Homework"); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.StudentCharacteristics", b => + { + b.HasOne("HwProj.CoursesService.API.Models.CourseMate", null) + .WithOne("Characteristics") + .HasForeignKey("HwProj.CoursesService.API.Models.StudentCharacteristics", "CourseMateId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.TaskModel", b => + { + b.HasOne("HwProj.CoursesService.API.Models.Group", null) + .WithMany("Tasks") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.UserToCourseFilter", b => + { + b.HasOne("HwProj.CoursesService.API.Models.CourseFilter", "CourseFilter") + .WithMany() + .HasForeignKey("CourseFilterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CourseFilter"); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.Course", b => + { + b.Navigation("Assignments"); + + b.Navigation("CourseMates"); + + b.Navigation("Homeworks"); + + b.Navigation("Mentors"); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.CourseMate", b => + { + b.Navigation("Characteristics"); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.Group", b => + { + b.Navigation("GroupMates"); + + b.Navigation("Tasks"); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.Homework", b => + { + b.Navigation("Tasks"); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.HomeworkTask", b => + { + b.Navigation("Criteria"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/HwProj.CoursesService/HwProj.CoursesService.API/Migrations/20261022145922_DropMentorIds.cs b/HwProj.CoursesService/HwProj.CoursesService.API/Migrations/20261022145922_DropMentorIds.cs new file mode 100644 index 000000000..969202d5c --- /dev/null +++ b/HwProj.CoursesService/HwProj.CoursesService.API/Migrations/20261022145922_DropMentorIds.cs @@ -0,0 +1,28 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace HwProj.CoursesService.API.Migrations +{ + /// + public partial class DropMentorIds : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "MentorIds", + table: "Courses"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "MentorIds", + table: "Courses", + type: "nvarchar(max)", + nullable: true); + } + } +} diff --git a/HwProj.CoursesService/HwProj.CoursesService.API/Migrations/CourseContextModelSnapshot.cs b/HwProj.CoursesService/HwProj.CoursesService.API/Migrations/CourseContextModelSnapshot.cs index b28d1a701..881609de1 100644 --- a/HwProj.CoursesService/HwProj.CoursesService.API/Migrations/CourseContextModelSnapshot.cs +++ b/HwProj.CoursesService/HwProj.CoursesService.API/Migrations/CourseContextModelSnapshot.cs @@ -66,9 +66,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("IsOpen") .HasColumnType("bit"); - b.Property("MentorIds") - .HasColumnType("nvarchar(max)"); - b.Property("Name") .HasColumnType("nvarchar(max)"); diff --git a/HwProj.CoursesService/HwProj.CoursesService.API/Models/Course.cs b/HwProj.CoursesService/HwProj.CoursesService.API/Models/Course.cs index 7d58da0ff..f09d9713a 100644 --- a/HwProj.CoursesService/HwProj.CoursesService.API/Models/Course.cs +++ b/HwProj.CoursesService/HwProj.CoursesService.API/Models/Course.cs @@ -13,7 +13,6 @@ public class Course : IEntity public bool IsOpen { get; set; } public string InviteCode { get; set; } public bool IsCompleted { get; set; } - [Obsolete] public string MentorIds { get; set; } public List Mentors { get; set; } = new List(); public List CourseMates { get; set; } = new List(); public List Homeworks { get; set; } = new List(); From 8672791a6dba29da176b3cb8c69e6e6e7b1794f9 Mon Sep 17 00:00:00 2001 From: Andrew_Baranov Date: Sun, 1 Nov 2026 17:08:51 +0300 Subject: [PATCH 5/5] chore(CoursesService): remove obsolete GetMentorIdsAsync declaration after merge --- .../Repositories/ICoursesRepository.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HwProj.CoursesService/HwProj.CoursesService.API/Repositories/ICoursesRepository.cs b/HwProj.CoursesService/HwProj.CoursesService.API/Repositories/ICoursesRepository.cs index 9b9a9817e..adc8b0740 100644 --- a/HwProj.CoursesService/HwProj.CoursesService.API/Repositories/ICoursesRepository.cs +++ b/HwProj.CoursesService/HwProj.CoursesService.API/Repositories/ICoursesRepository.cs @@ -9,6 +9,6 @@ public interface ICoursesRepository : ICrudRepository Task GetWithCourseMates(long id); Task GetWithHomeworksAsync(long id); Task GetWithCourseMatesAndHomeworksAsync(long id, bool withCriteria = false); - Task GetMentorIdsAsync(long id); + } }