Compare commits

...

129 Commits

Author SHA1 Message Date
974a8aa0ad plus 2025-05-28 09:57:00 +03:00
87782c3269 plus 2025-05-28 09:56:54 +03:00
b421e07926 strst model 2025-05-28 09:55:36 +03:00
818a173440 +sal 2025-05-27 20:04:31 +03:00
aeeb6e0d91 last ref 2025-05-27 20:04:20 +03:00
9eb2540c51 fix 2025-05-23 05:04:59 +03:00
cda6529985 gig front work#2 2025-05-23 04:22:59 +03:00
b5b7979cdf big 2025-05-23 02:43:09 +03:00
26b5ef7b73 big work 2025-05-23 02:42:49 +03:00
a85c557f43 Create lk 2025-05-23 00:45:05 +03:00
036e6d4e67 registration complited 2025-05-22 22:25:28 +03:00
1fc894921a fix 2025-05-22 19:42:37 +03:00
049ea0aa3c Reg Working 2025-05-22 19:41:20 +03:00
ab12053850 html правки 2025-05-22 18:56:05 +04:00
675d6a21aa ed 2025-05-21 14:27:55 +03:00
f912109eb4 Rec 2025-05-21 14:27:32 +03:00
581ae44776 ed?again 2025-05-21 12:45:18 +03:00
bda854b917 Backend end 2025-05-21 12:45:09 +03:00
8d26b95832 correct? 2025-05-21 12:16:21 +03:00
2450b0ba22 correct 434332 2025-05-21 12:16:14 +03:00
dfa9163ce9 fix 2025-05-21 12:03:58 +03:00
23c52f096e correct #090 2025-05-21 12:03:30 +03:00
c6516678fd Controller Complited 2025-05-21 11:44:41 +03:00
15074cc3d2 correct EducationStorage 2025-05-21 10:34:35 +03:00
f6c9cf3044 correct 2025-05-21 10:06:02 +03:00
ba9543df1b ? 2025-05-21 11:03:45 +04:00
e9090577d6 html / js 2025-05-21 11:03:33 +04:00
59749a4a67 correct with student stage + TeacherStage 2025-05-21 10:01:30 +03:00
6f32e2c251 programm 2025-05-21 09:43:35 +03:00
e8f925972c correct will be conflic with educ 2025-05-21 09:42:39 +03:00
a9790a9d88 salay dop 2025-05-21 09:59:24 +04:00
7e108ee216 create CalculateTotalPriceByEducation 2025-05-21 09:59:10 +04:00
a20bfbb3e2 correct 2025-05-21 08:44:55 +03:00
60029d7e1b fix Exception 2025-05-21 09:40:24 +04:00
da84992d30 rev#1 2025-05-21 08:34:42 +03:00
2f4efc2dba BusinessLogic without Lesson becouse sallary not compited in d->m->st 2025-05-21 07:48:04 +03:00
430319f50f interfaces 2025-05-21 02:02:49 +03:00
39af7fd785 oh got....#2 2025-05-21 01:42:27 +03:00
0dc5b4880a oh got... 2025-05-21 01:42:04 +03:00
ba16aa6f10 fix #1 ex 6 2025-05-21 00:42:35 +03:00
e5b9d581ca Alexfix 2025-05-21 00:38:13 +03:00
cacbcc5d53 corrects #2 2025-05-21 00:07:57 +03:00
df9d449e4c Very much corrects 2025-05-21 00:07:09 +03:00
19c217dd68 sc fix 2025-05-21 00:31:45 +04:00
854cbb2a87 Interfaces storageContract 2025-05-20 19:50:52 +03:00
845f96acb4 ISC ready 2025-05-20 19:48:27 +04:00
c137ef9606 corect 4 2025-05-20 19:38:43 +04:00
f77e81a6c6 corect 3 2025-05-20 19:38:28 +04:00
f6585ce267 corect 2 2025-05-20 19:34:57 +04:00
9590375c03 corect 2025-05-20 19:34:32 +04:00
4c06445980 fix conf 2025-05-20 19:14:55 +04:00
7b9d550445 fix M 2025-05-20 19:08:34 +03:00
1868b80b84 fix#3 2025-05-20 19:06:28 +03:00
d846a6b5ce fix #2 2025-05-20 19:05:57 +03:00
6558ef8f2d Client DataModel Correct 2025-05-20 18:22:40 +03:00
352701d94f phoneNumber 2025-05-20 19:12:13 +04:00
8c2612df4a Merge branch 'RefactoringAllProgectToWorkState' of https://git.is.ulstu.ru/Dmitriev.An/CourseWork_DmitrievA.A_ISEbd21_UniversityAllExpelled into RefactoringAllProgectToWorkState 2025-05-20 19:06:17 +04:00
b1a6d92df4 models/view 1 2025-05-20 19:05:33 +04:00
a3ff31280f remove v2 2025-05-20 17:53:35 +03:00
40e26a0120 Remove more files 2025-05-20 17:53:03 +03:00
aa58e0817d stop producting 2025-05-20 17:39:20 +03:00
0cedc4a00a fix 2025-05-20 00:13:23 +03:00
02bf7c34ef db 2025-05-19 23:02:29 +03:00
b1c96467cc dbcreate 2025-05-19 22:59:55 +03:00
ed481a5e62 fix 2025-05-18 23:10:16 +04:00
cee5905b67 not work edit 2025-05-18 11:32:29 +03:00
bf4e4a0d18 Merge branch 'WebStep7' of https://git.is.ulstu.ru/Dmitriev.An/CourseWork_ISEbd21_UniversityAllExpelled into WebStep7 2025-05-17 22:31:12 +03:00
9eb709a3ee fix 2025-05-17 22:30:49 +03:00
7ad22682fb win 2025-05-17 22:30:26 +03:00
011151dba0 mainLK.html/createStudentTraining.html/edu-create.css/createEdules 2025-05-17 20:00:43 +04:00
786f9ef7b1 fix 2025-05-17 18:46:19 +03:00
3620b7fe99 biggestWorkk 2025-05-17 18:46:02 +03:00
8b976b7523 big work complited. DataBase created And more fix 2025-05-17 17:36:31 +03:00
bb76ebcd83 html js and css. plus infrastructure in web db 2025-05-17 16:03:12 +03:00
33e2641318 Merge branch 'WebStep7' of https://git.is.ulstu.ru/Dmitriev.An/CourseWork_DmitrievA.A_ISEbd21_UniversityAllExpelled into WebStep7 2025-05-17 12:53:08 +04:00
b406dea953 Merge branch 'WebStep7Teacher' into WebStep7_Student 2025-05-17 12:47:28 +03:00
7b678ef445 lk, payment 1st path, reprts 2025-05-17 12:39:20 +03:00
4d44610c2f change 2025-05-17 12:52:59 +04:00
3fcaec39e9 logo + teacherlk 2025-05-17 12:37:24 +04:00
b554fcc2f3 index + registr 2025-05-16 19:59:28 +03:00
3facbf9d28 Project WebApi Created 2025-05-16 16:19:45 +03:00
443b29e70b fix 2025-05-16 16:15:34 +04:00
141d39b42e fix reqest 2025-04-27 11:42:49 +03:00
69392019bb Merge branch 'RealizationBusinessLogicContractClient' into MainRealizationBusinessLogicContract 2025-04-27 11:37:23 +03:00
2c62e282f9 fix 2025-04-27 11:33:25 +03:00
91951c9ef6 business logic realisation Client #1 : created 2025-04-27 11:31:39 +03:00
245cb9e87e Realization BusinessLogic 2025-04-27 11:40:00 +04:00
e1f5c49c8a fix 2025-04-27 09:33:33 +03:00
26cf1ae0c1 Project businessLogic created 2025-04-27 09:31:19 +03:00
b7038b508c fix 2025-04-26 23:46:10 +03:00
9c86534470 Refactoring models 2025-04-26 23:44:56 +03:00
655601f7c7 fix 2025-04-26 23:46:52 +04:00
545cacbe9a Complited pull reqest 2025-04-26 21:56:38 +03:00
e9c6b7ff3a Merge pull request 'Client StorageContract implimentations : Created and Complited' (#3) from MainRealizationStorageContractClient into MainRealizationStorageContract
Reviewed-on: #3
2025-04-26 21:50:10 +04:00
18af25be0b Storage/Models 2025-04-26 21:45:16 +04:00
adbf6eb004 Client StorageContract : Created and Complited
Models: Created and Complited
dbContext: Created
Interface configuration Database  complited
2025-04-26 18:56:05 +03:00
b9e8a3257f Create project database 2025-04-26 17:00:30 +04:00
e78f937fc8 Merge pull request 'BusinessLogic/StorageContracts for Worker in main branch' (#2) from StorageContractWorker into MainContract
Reviewed-on: #2
2025-04-26 15:21:39 +04:00
483b945269 Merge branch 'MainContract' into StorageContractWorker 2025-04-26 15:21:00 +04:00
5f77287037 Merge pull request 'StorageContractClient in main branch' (#1) from StorageContractClient into MainContract
Reviewed-on: #1
2025-04-26 15:07:01 +04:00
176786f5eb BusinessLogic/StorageContracts for Worker 2025-04-24 19:12:05 +04:00
Lazypr0ger
9de7e5e480 BusinessLogicContract for Client path and Lesson + User : Complited 2025-04-24 15:38:01 +04:00
Lazypr0ger
f98e2a9df8 StorageContract Client path : Complited 2025-04-24 13:54:48 +04:00
Lazypr0ger
50fabdaf55 Package for BusinessLogic and Storage Contracts: created 2025-04-24 13:07:09 +04:00
Lazypr0ger
e34abad173 tests Datamodel: deleted #2 (past true conflict) 2025-04-24 12:54:47 +04:00
Lazypr0ger
9a6963d2c5 Deleted tests. Start separate development 2025-04-24 12:53:52 +04:00
85c3a97d23 workertest part 1 2025-04-12 22:46:17 +04:00
b44784854a да емае 2025-04-12 21:44:09 +04:00
30852efca6 ЕЩЁРАЗ 2025-04-12 21:42:54 +04:00
9132f6d7c3 TESTS 2025-04-12 21:18:23 +04:00
651f3d5564 fix 2025-04-12 21:15:33 +04:00
1ec2a956d3 EducationDataModel tests 2025-04-12 21:12:21 +04:00
142d9d2bf8 Tests 1 2025-04-12 21:10:38 +04:00
Lazypr0ger
e66849ac08 Correcting Validation and class datamodels 2025-04-12 17:33:51 +04:00
Lazypr0ger
046921f18d students and payment validation 2025-04-12 17:07:29 +04:00
84b2f754d1 Project Tests 2025-04-12 16:31:46 +04:00
Lazypr0ger
d9123e9f2c User Validation complited 2025-04-12 16:23:24 +04:00
38ecc483c2 data modelky 2025-04-12 16:24:04 +04:00
Lazypr0ger
6156a55dd9 Validation classes complited 2025-04-12 15:52:43 +04:00
0688934faf TeacherPositionType 2 2025-04-12 15:50:57 +04:00
Lazypr0ger
d80e031d74 student Data Model and groop and faculty type complited 2025-04-12 15:45:13 +04:00
1925bb4fcc Teacher, Education 2025-04-12 15:45:43 +04:00
89e4449b96 TeacherPositionType 2025-04-12 15:29:20 +04:00
3a6f28ecfb TeacherDataModel 2025-04-12 15:19:18 +04:00
Lazypr0ger
f3cf508702 UserDataModel and Enums Role 2025-04-12 15:16:18 +04:00
Lazypr0ger
134f277016 FirstModels 2025-04-12 14:58:51 +04:00
Lazypr0ger
5bbf76d13b Qest 2025-04-12 14:55:34 +04:00
Lazypr0ger
0279d05a36 Refactoring 2025-04-12 14:42:45 +04:00
Lazypr0ger
d7109524d1 StartDevelopment 2025-04-12 14:22:44 +04:00
64 changed files with 2435 additions and 0 deletions

View File

@@ -0,0 +1,43 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.13.35806.99
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UniversityAllExpelled_Models", "UniversityAllExpelled_Models\UniversityAllExpelled_Models.csproj", "{B2364D1E-38B8-4DF7-90DB-49F8463A1B40}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UniversityAllExpelled_DataBase", "UniversityAllExpelled_DataBase\UniversityAllExpelled_DataBase.csproj", "{23807279-75F8-4BEC-831B-24AA27ABA57A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UniversityAllExpelled_BusinessLogic", "UniversityAllExpelled_BusinessLogic\UniversityAllExpelled_BusinessLogic.csproj", "{6C4551E0-879E-4D28-9C2A-019929E0211A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UniversityAllExpelled_WebApi", "UniversityAllExpelled_WebApi\UniversityAllExpelled_WebApi.csproj", "{F2CB59D0-8BC0-477E-8BC6-5A230AE04694}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{B2364D1E-38B8-4DF7-90DB-49F8463A1B40}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B2364D1E-38B8-4DF7-90DB-49F8463A1B40}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B2364D1E-38B8-4DF7-90DB-49F8463A1B40}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B2364D1E-38B8-4DF7-90DB-49F8463A1B40}.Release|Any CPU.Build.0 = Release|Any CPU
{23807279-75F8-4BEC-831B-24AA27ABA57A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{23807279-75F8-4BEC-831B-24AA27ABA57A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{23807279-75F8-4BEC-831B-24AA27ABA57A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{23807279-75F8-4BEC-831B-24AA27ABA57A}.Release|Any CPU.Build.0 = Release|Any CPU
{6C4551E0-879E-4D28-9C2A-019929E0211A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6C4551E0-879E-4D28-9C2A-019929E0211A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6C4551E0-879E-4D28-9C2A-019929E0211A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6C4551E0-879E-4D28-9C2A-019929E0211A}.Release|Any CPU.Build.0 = Release|Any CPU
{F2CB59D0-8BC0-477E-8BC6-5A230AE04694}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F2CB59D0-8BC0-477E-8BC6-5A230AE04694}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F2CB59D0-8BC0-477E-8BC6-5A230AE04694}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F2CB59D0-8BC0-477E-8BC6-5A230AE04694}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {256411BF-AC51-436B-B254-59E602A9AE79}
EndGlobalSection
EndGlobal

View File

@@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging" Version="9.0.4" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\UniversityAllExpelled_Models\UniversityAllExpelled_Models.csproj" />
</ItemGroup>
<ItemGroup>
<InternalsVisibleTo Include="UniversityAllExpelled_WebApi" />
<InternalsVisibleTo Include="DynamicProxyGenAssembly2" />
</ItemGroup>
<ItemGroup>
<Folder Include="Implementations\" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,19 @@
using Microsoft.EntityFrameworkCore;
using UniversityAllExpelled_Database.Models;
namespace UniversityAllExpelled_DataBase
{
public interface IUniversityDbContext
{
DbSet<User> Users { get; }
DbSet<Student> Students { get; }
DbSet<Teacher> Teachers { get; }
DbSet<Payment> Payments { get; }
DbSet<Lesson> Lessons { get; }
DbSet<Salary> Salaries { get; }
DbSet<Education> Educations { get; }
DbSet<EducationLessons> EducationLessons { get; }
Task<int> SaveChangesAsync(CancellationToken cancellationToken = default);
}
}

View File

@@ -0,0 +1,20 @@
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using UniversityAllExpelled_Models.DataModels.Worker;
namespace UniversityAllExpelled_Database.Models;
public class Education
{
public required string Id { get; set; }
public double Amount { get; set; }
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
public bool Active { get; set; }
[ForeignKey("EducationId")]
public virtual List<EducationLessons> EducationLessons { get; set; } = [];
public virtual List<Payment> Payments { get; set; } = [];
}

View File

@@ -0,0 +1,12 @@
using UniversityAllExpelled_Models.DataModels.Worker;
namespace UniversityAllExpelled_Database.Models;
public class EducationLessons()
{
public required string EducationId { get; set; }
public required string LessonId { get; set; }
public Education? Education { get; set; }
public Lesson? Lesson { get; set; }
}

View File

@@ -0,0 +1,16 @@
namespace UniversityAllExpelled_Database.Models;
public class Payment
{
public required string Id { get; set; }
public required string StudentUserId { get; set; }
public required string LessonId { get; set; }
public double PaidAmount { get; set; }
public double Arrears { get; set; }
public DateTime DateOfPayment { get; set; }
public bool IsPaid { get; set; }
public Student? Student { get; set; }
}

View File

@@ -0,0 +1,13 @@
namespace UniversityAllExpelled_Database.Models;
public class Student
{
public required string UserId { get; set; }
public string Faculty { get; set; } = "ФИСТ";
public int Course { get; set; }
public required User User { get; set; }
public List<Payment>? Payments { get; set; }
public List<Education>? Educations { get; set; }
}

View File

@@ -0,0 +1,13 @@

using UniversityAllExpelled_Models.DataModels.Worker;
namespace UniversityAllExpelled_Database.Models;
public class Teacher
{
public required string UserId { get; set; }
public string LevelPost { get; set; } = "Доцент";
public DateTime DateHiring { get; set; }
public required User User { get; set; }
public List<Lesson>? Lessons { get; set; }
}

View File

@@ -0,0 +1,20 @@
using System.ComponentModel.DataAnnotations.Schema;
using System.Data;
using UniversityAllExpelled_Models.DataModels;
using UniversityAllExpelled_Models.Enums;
namespace UniversityAllExpelled_Database.Models;
public class User
{
public required string Id { get; set;}
public required string Login { get; set; }
public required string Password { get; set; }
public required SystemRoleType Role { get; set; }
public required string FIO { get; set; }
public DateTime BirthDate { get; set; }
public required string PhoneNumber { get; set; }
public required string Email { get; set; }
public Student? Student { get; set; }
public Teacher? Teacher { get; set; }
}

View File

@@ -0,0 +1,103 @@
using Microsoft.EntityFrameworkCore;
using UniversityAllExpelled_Database.Models;
using UniversityAllExpelled_DataBase;
using UniversityAllExpelled_Models.Infrastructure;
namespace UniversityAllExpelled_Database;
public class UniversityAllExpelledDbContext : DbContext, IUniversityDbContext
{
private readonly IConfigurationDatabase? _configurationDatabase;
public UniversityAllExpelledDbContext(IConfigurationDatabase? configurationDatabase = null)
{
_configurationDatabase = configurationDatabase;
AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true);
AppContext.SetSwitch("Npgsql.DisableDateTimeInfinityConversions", true);
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (_configurationDatabase == null || string.IsNullOrEmpty(_configurationDatabase.ConnectionString))
throw new InvalidOperationException("Database configuration is not set");
optionsBuilder.UseNpgsql(_configurationDatabase.ConnectionString, o => o.SetPostgresVersion(12, 2));
base.OnConfiguring(optionsBuilder);
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<User>()
.HasIndex(u => u.PhoneNumber)
.IsUnique();
modelBuilder.Entity<User>()
.HasOne(u => u.Student)
.WithOne(s => s.User)
.HasForeignKey<Student>(s => s.UserId)
.OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<User>()
.HasOne(u => u.Teacher)
.WithOne(t => t.User)
.HasForeignKey<Teacher>(t => t.UserId)
.OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<Lesson>()
.HasOne(l => l.Student)
.WithMany(s => s.Lessons)
.HasForeignKey(l => l.StudentId)
.OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<Lesson>()
.HasOne(l => l.Salary)
.WithMany(s => s.Lessons)
.HasForeignKey(l => l.SalaryId)
.OnDelete(DeleteBehavior.SetNull);
modelBuilder.Entity<Lesson>()
.HasMany(l => l.EducationLessons)
.WithOne(el => el.Lesson)
.HasForeignKey(el => el.LessonId);
modelBuilder.Entity<Education>()
.HasOne(e => e.Teacher)
.WithMany(t => t.Educations)
.HasForeignKey(e => e.TeacherId)
.OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<Education>()
.HasMany(e => e.EducationLessons)
.WithOne(el => el.Education)
.HasForeignKey(el => el.EducationId);
modelBuilder.Entity<EducationLessons>()
.HasKey(el => new { el.EducationId, el.LessonId });
modelBuilder.Entity<Payment>()
.HasOne(p => p.Student)
.WithMany(s => s.Payments)
.HasForeignKey(p => p.StudentUserId)
.OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<Payment>()
.HasOne(p => p.Education)
.WithMany(e => e.Payments)
.HasForeignKey(p => p.EducationId)
.OnDelete(DeleteBehavior.Cascade);
}
public DbSet<User> Users { get; set; }
public DbSet<Student> Students { get; set; }
public DbSet<Teacher> Teachers { get; set; }
public DbSet<Lesson> Lessons { get; set; }
public DbSet<Payment> Payments { get; set; }
public DbSet<Salary> Salaries { get; set; }
public DbSet<Education> Educations { get; set; }
public DbSet<EducationLessons> EducationLessons { get; set; }
public async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
=> await base.SaveChangesAsync(cancellationToken);
}

View File

@@ -0,0 +1,28 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AutoMapper" Version="14.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.4" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.4" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\UniversityAllExpelled_Models\UniversityAllExpelled_Models.csproj" />
</ItemGroup>
<ItemGroup>
<InternalsVisibleTo Include="UniversityAllExpelled_WebApi" />
<InternalsVisibleTo Include="DynamicProxyGenAssembly2" />
</ItemGroup>
<ItemGroup>
<Folder Include="Implementations\" />
<Folder Include="Infrastructure\" />
<Folder Include="Migrations\" />
<Folder Include="Models\" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,20 @@
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using UniversityAllExpelled_Models.DataModels.Worker;
namespace UniversityAllExpelled_Database.Models;
public class Education
{
public required string Id { get; set; }
public double Amount { get; set; }
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
public bool Active { get; set; }
[ForeignKey("EducationId")]
public virtual List<EducationLessons> EducationLessons { get; set; } = [];
public virtual List<Payment> Payments { get; set; } = [];
}

View File

@@ -0,0 +1,12 @@
using UniversityAllExpelled_Models.DataModels.Worker;
namespace UniversityAllExpelled_Database.Models;
public class EducationLessons()
{
public required string EducationId { get; set; }
public required string LessonId { get; set; }
public Education? Education { get; set; }
public Lesson? Lesson { get; set; }
}

View File

@@ -0,0 +1,17 @@

using System.ComponentModel.DataAnnotations.Schema;
namespace UniversityAllExpelled_Database.Models;
public class Lesson
{
public required string Id { get; set;}
public required string TeacherUserId { get; set;}
public required string LessonName { get; set;}
public double Price { get; set; }
public DateTime LessonDate { get; set; }
public bool IsActive { get; set; }
[ForeignKey("LessonId")]
public List<EducationLessons>? EducationLessons { get; set; }
}

View File

@@ -0,0 +1,16 @@
namespace UniversityAllExpelled_Database.Models;
public class Payment
{
public required string Id { get; set; }
public required string StudentUserId { get; set; }
public required string LessonId { get; set; }
public double PaidAmount { get; set; }
public double Arrears { get; set; }
public DateTime DateOfPayment { get; set; }
public bool IsPaid { get; set; }
public Student? Student { get; set; }
}

View File

@@ -0,0 +1,13 @@
namespace UniversityAllExpelled_Database.Models;
public class Student
{
public required string UserId { get; set; }
public string Faculty { get; set; } = "ФИСТ";
public int Course { get; set; }
public required User User { get; set; }
public List<Payment>? Payments { get; set; }
public List<Education>? Educations { get; set; }
}

View File

@@ -0,0 +1,13 @@

using UniversityAllExpelled_Models.DataModels.Worker;
namespace UniversityAllExpelled_Database.Models;
public class Teacher
{
public required string UserId { get; set; }
public string LevelPost { get; set; } = "Доцент";
public DateTime DateHiring { get; set; }
public required User User { get; set; }
public List<Lesson>? Lessons { get; set; }
}

View File

@@ -0,0 +1,20 @@
using System.ComponentModel.DataAnnotations.Schema;
using System.Data;
using UniversityAllExpelled_Models.DataModels;
using UniversityAllExpelled_Models.Enums;
namespace UniversityAllExpelled_Database.Models;
public class User
{
public required string Id { get; set;}
public required string Login { get; set; }
public required string Password { get; set; }
public required SystemRoleType Role { get; set; }
public required string FIO { get; set; }
public DateTime BirthDate { get; set; }
public required string PhoneNumber { get; set; }
public required string Email { get; set; }
public Student? Student { get; set; }
public Teacher? Teacher { get; set; }
}

View File

@@ -0,0 +1,35 @@
using UniversityAllExpelled_Models.Exceptions;
using UniversityAllExpelled_Models.Infrastructure;
namespace UniversityAllExpelled_Models.DataModels.Worker
{
public class EducationDataModel(string id, double amount, DateTime startDate, DateTime endDate, bool active) : IValidation
{
public string Id { get; set; } = id;
public double Amount { get; set; } = amount;
public DateTime StartDate { get; set; } = startDate;
public DateTime EndDate { get; set; } = endDate;
public bool Active { get; set; } = active;
public void Validate()
{
if (string.IsNullOrEmpty(Id))
throw new ValidationException("Education record ID cannot be empty");
if (!Guid.TryParse(Id, out _))
throw new ValidationException("Education record ID must be in valid GUID format");
if (Amount <= 0)
throw new ValidationException("Education amount must be greater than zero");
if (StartDate == default)
throw new ValidationException("Start date must be specified");
if (EndDate == default)
throw new ValidationException("End date must be specified");
if (StartDate > EndDate)
throw new ValidationException("Start date cannot be after end date");
}
}
}

View File

@@ -0,0 +1,26 @@
using System.ComponentModel.DataAnnotations;
using UniversityAllExpelled_Models.Extensions;
using UniversityAllExpelled_Models.Infrastructure;
namespace UniversityAllExpelled_Models.DataModels.Worker;
public class EducationLessonsDataModel(string educationId, string lessonId) : IValidation
{
public virtual string EducationId { get; set; } = educationId;
public virtual string LessonId { get; set; } = lessonId;
public void Validate()
{
if (EducationId.IsEmpty())
throw new ValidationException("Education ID cannot be empty");
if (!EducationId.IsGuid())
throw new ValidationException("Education ID must be in valid GUID format");
if (LessonId.IsEmpty())
throw new ValidationException("Lesson ID cannot be empty");
if (!LessonId.IsGuid())
throw new ValidationException("Lesson ID must be in valid GUID format");
}
}

View File

@@ -0,0 +1,31 @@
using UniversityAllExpelled_Models.Exceptions;
using UniversityAllExpelled_Models.Extensions;
using UniversityAllExpelled_Models.Infrastructure;
namespace UniversityAllExpelled_Models.DataModels;
public class LessonDataModel(string id, string lessonName, double price,DateTime lessonDate, bool isActive) : IValidation
{
public string Id { get; set; } = id;
public string LessonName { get; set; } = lessonName;
public double Price { get; set; } = price;
public DateTime LessonDate { get; set; } = lessonDate;
public bool IsActive { get; set; } = isActive;
public void Validate()
{
if (Id.IsEmpty())
throw new ValidationException("Field Id is empty.");
if (!Id.IsGuid())
throw new ValidationException("Field Id is not a valid GUID.");
if (LessonName.IsEmpty())
throw new ValidationException("Field Name is empty.");
if (Price <= 0)
throw new ValidationException("Price must be greater than 0.");
if (LessonDate.Date > EndDate.Date)
throw new ValidationException("StartDate cannot be later than EndDate.");
}
}

View File

@@ -0,0 +1,42 @@
using UniversityAllExpelled_Models.Exceptions;
using UniversityAllExpelled_Models.Extensions;
using UniversityAllExpelled_Models.Infrastructure;
namespace UniversityAllExpelled_Models.DataModels;
public class PaymentDataModel(string id,string studentId,double paidAmount,
double arrears,DateTime dateOfPayment, bool isPaid) : IValidation
{
public string Id { get; set; } = id;
public string StudentId { get; set; } = studentId;
public string EducationId { get; set; } = string.Empty;
public double PaidAmount { get; set; } = paidAmount;
public double Arrears { get; set; } = arrears;
public DateTime DateOfPayment { get; set; } = dateOfPayment;
public bool IsPaid { get; set; } = isPaid;
public void Validate()
{
if (Id.IsEmpty())
throw new ValidationException("Field Id is empty.");
if (!Id.IsGuid())
throw new ValidationException("Field Id is not a valid GUID.");
if (StudentId.IsEmpty())
throw new ValidationException("Field StudentId is empty.");
if (!StudentId.IsGuid())
throw new ValidationException("Field StudentId is not a valid GUID.");
if (!EducationId.IsEmpty() && !EducationId.IsGuid())
throw new ValidationException("Field EducationId must be a valid GUID if provided.");
if (PaidAmount < 0)
throw new ValidationException("PaidAmount cannot be negative.");
if (Arrears < 0)
throw new ValidationException("Arrears cannot be negative.");
if (IsPaid && Arrears != 0)
throw new ValidationException("If IsPaid is true, Arrears must be 0.");
}
}

View File

@@ -0,0 +1,25 @@
using UniversityAllExpelled_Models.Exceptions;
using UniversityAllExpelled_Models.Extensions;
using UniversityAllExpelled_Models.Infrastructure;
namespace UniversityAllExpelled_Models.DataModels;
public class StudentDataModel(string userId, int course, string faculty = "ФИСТ") : IValidation
{
public string UserId { get; set; } = userId;
public int Course { get; set; } = course;
public string Faculty { get; set; } = faculty;
public void Validate()
{
if (UserId.IsEmpty())
throw new ValidationException("Field UserId is empty");
if (!UserId.IsGuid())
throw new ValidationException("Field UserId is not a valid GUID");
if (Course < 1 || Course > 6)
throw new ValidationException("Course must be between 1 and 6");
}
}

View File

@@ -0,0 +1,22 @@
using System.ComponentModel.DataAnnotations;
using UniversityAllExpelled_Models.Enums;
using UniversityAllExpelled_Models.Extensions;
using UniversityAllExpelled_Models.Infrastructure;
namespace UniversityAllExpelled_Models.DataModels;
public class TeacherDataModel(string userId, DateTime dateHiring, string levelPost = "Доцент") : IValidation
{
public string UserId { get; set; } = userId;
public string LevelPost { get; set; } = levelPost;
public DateTime DateHiring { get; set; } = dateHiring;
public void Validate()
{
if (UserId.IsEmpty())
throw new ValidationException("User ID cannot be empty");
if (!UserId.IsGuid())
throw new ValidationException("User ID must be a valid GUID");
}
}

View File

@@ -0,0 +1,54 @@
using System.Text.RegularExpressions;
using UniversityAllExpelled_Models.Enums;
using UniversityAllExpelled_Models.Exceptions;
using UniversityAllExpelled_Models.Extensions;
using UniversityAllExpelled_Models.Infrastructure;
namespace UniversityAllExpelled_Models.DataModels;
public class UserDataModel(string id, string login, string password, SystemRoleType role,
string fio, DateTime birthdate, string phoneNumber, string email, bool isDeleted) : IValidation
{
public string Id { get; set; } = id;
public string Login { get; set; } = login;
public string Password { get; set; } = password;
public SystemRoleType Role { get; set; } = role;
public string FIO { get; set; } = fio;
public DateTime BirthDate { get; set; } = birthdate;
public string PhoneNumber { get; set; } = phoneNumber;
public string Email { get; set; } = email;
public bool IsDeleted { get; set; } = isDeleted;
public void Validate()
{
if (Id.IsEmpty())
throw new ValidationException("Field Id is empty");
if (!Id.IsGuid())
throw new ValidationException("The value in the field Id is not a valid GUID");
if (Login.IsEmpty())
throw new ValidationException("Field Login is empty");
if (Password.IsEmpty())
throw new ValidationException("Field Password is empty");
if (!Regex.IsMatch(Password, @"^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[!@#$%^&*()_+{}\[\]:;'"",.<>?\/\\|`~\-=]).{8,}$"))
throw new ValidationException("The Password field was entered incorrectly. It must contain at least one lowercase letter, one uppercase letter, one digit, and one special character.");
if (Role == SystemRoleType.None)
throw new ValidationException("The Role field must not be None");
if (FIO.IsEmpty())
throw new ValidationException("Field FIO is empty");
if (BirthDate > DateTime.Now.AddYears(-16))
throw new ValidationException("User must be at least 16 years old");
if (!Regex.IsMatch(PhoneNumber, @"^(\+7|8)(-?\d{3}-?\d{3}-?\d{2}-?\d{2}|-?\d{10})$"))
throw new ValidationException("The PhoneNumber field was entered incorrectly");
if (!Regex.IsMatch(Email, @"^[a-zA-Z0-9._%+-]+@(?:yandex\.[a-z]{2,}|mail\.ru|inbox\.ru|list\.ru|bk\.ru|gmail\.com)$"))
throw new ValidationException("The Email field was entered incorrectly");
}
}

View File

@@ -0,0 +1,8 @@
namespace UniversityAllExpelled_Models.Enums;
public enum SystemRoleType
{
None = 0,
student = 1,
teacher = 2
}

View File

@@ -0,0 +1,8 @@
namespace UniversityAllExpelled_Models.Exceptions;
public class DuplicateEmployeeException : Exception
{
public DuplicateEmployeeException(string id)
: base($"Employee with ID {id} already exists") { }
}

View File

@@ -0,0 +1,35 @@
using System;
using System.Runtime.Serialization;
namespace UniversityAllExpelled_Models.Exceptions
{
[Serializable]
public class DuplicateException : Exception
{
public DuplicateException()
{
}
public DuplicateException(string message) : base(message)
{
}
public DuplicateException(string message, Exception innerException) : base(message, innerException)
{
}
protected DuplicateException(SerializationInfo info, StreamingContext context) : base(info, context)
{
}
/// <param name="entityName">Название сущности</param>
/// <param name="fieldName">Название поля</param>
/// <param name="fieldValue">Значение поля</param>
public static DuplicateException ForField(string entityName, string fieldName, string fieldValue)
{
return new DuplicateException(
$"{entityName} with {fieldName} '{fieldValue}' already exists. {fieldName} must be unique.");
}
}
}

View File

@@ -0,0 +1,7 @@
namespace UniversityAllExpelled_Models.Exceptions;
public class ElementDeletedException : Exception
{
public ElementDeletedException(string id)
: base($"Cannot modify a deleted item (id: {id})") { }
}

View File

@@ -0,0 +1,14 @@
namespace UniversityAllExpelled_Models.Exceptions;
public class ElementExistsException : Exception
{
public string ParamName { get; }
public string ParamValue { get; }
public ElementExistsException(string paramName, string paramValue)
: base($"An element with value '{paramValue}' already exists for parameter '{paramName}'")
{
ParamName = paramName;
ParamValue = paramValue;
}
}

View File

@@ -0,0 +1,12 @@
namespace UniversityAllExpelled_Models.Exceptions;
public class ElementNotFoundException : Exception
{
public string Value { get; }
public ElementNotFoundException(string value)
: base($"Element not found with value = {value}")
{
Value = value;
}
}

View File

@@ -0,0 +1,9 @@
namespace UniversityAllExpelled_Models.Exceptions;
public class IncorrectDatesException : Exception
{
public IncorrectDatesException(DateTime start, DateTime end)
: base($"The end date must be later than the start date. StartDate: {start:dd.MM.yyyy}, EndDate: {end:dd.MM.yyyy}")
{
}
}

View File

@@ -0,0 +1,9 @@
namespace UniversityAllExpelled_Models.Exceptions;
public class NullListException : Exception
{
public NullListException(string context)
: base($"Expected list was null or empty in context: {context}")
{
}
}

View File

@@ -0,0 +1,7 @@
namespace UniversityAllExpelled_Models.Exceptions;
public class StorageException : Exception
{
public StorageException(Exception ex)
: base($"Error while working in storage: {ex.Message}", ex) { }
}

View File

@@ -0,0 +1,6 @@
namespace UniversityAllExpelled_Models.Exceptions;
public class ValidationException : Exception
{
public ValidationException(string message) : base(message) { }
}

View File

@@ -0,0 +1,15 @@

namespace UniversityAllExpelled_Models.Extensions;
public static class StringExtensions
{
public static bool IsEmpty(this string str)
{
return string.IsNullOrWhiteSpace(str);
}
public static bool IsGuid(this string str)
{
return Guid.TryParse(str, out _);
}
}

View File

@@ -0,0 +1,6 @@
namespace UniversityAllExpelled_Models.Infrastructure;
public interface IConfigurationDatabase
{
string ConnectionString { get; }
}

View File

@@ -0,0 +1,7 @@

namespace UniversityAllExpelled_Models.Infrastructure;
public interface IValidation
{
void Validate();
}

View File

@@ -0,0 +1,27 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<InternalsVisibleTo Include="UniversityAllExpelled_DataBase" />
<InternalsVisibleTo Include="UniversityAllExpelled_BusinessLogic" />
<InternalsVisibleTo Include="DynamicProxyGenAssembly2" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging" Version="9.0.4" />
</ItemGroup>
<ItemGroup>
<InternalsVisibleTo Include="UniversityAllExpelled_WebApi" />
<InternalsVisibleTo Include="DynamicProxyGenAssembly2" />
</ItemGroup>
<ItemGroup>
<Folder Include="BusinessLogicContracts\" />
<Folder Include="StorageContracts\" />
<Folder Include="ViewModels\" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,27 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<LangVersion>latest</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="coverlet.collector" Version="6.0.2" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
<PackageReference Include="NUnit" Version="4.2.2" />
<PackageReference Include="NUnit.Analyzers" Version="4.4.0" />
<PackageReference Include="NUnit3TestAdapter" Version="4.6.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\UniversityAllExpelled_Models\UniversityAllExpelled_Models.csproj" />
</ItemGroup>
<ItemGroup>
<Using Include="NUnit.Framework" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,10 @@
using UniversityAllExpelled_Models.Infrastructure;
namespace UniversityAllExpelled_WebApi.Infrastructure;
public class ConfigurationDatabase(IConfiguration configuration) : IConfigurationDatabase
{
private readonly string _connectionString = configuration["DataBaseSettings:ConnectionString"] ?? throw new InvalidDataException("DataBaseSettings:ConnectionString не найден в конфигурации");
public string ConnectionString => _connectionString;
}

View File

@@ -0,0 +1,7 @@
namespace UniversityAllExpelled_WebApi.Infrastructure;
public class DataBaseSettings
{
public required string ConnectionString { get; set; }
}

View File

@@ -0,0 +1,106 @@
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.EntityFrameworkCore;
using Microsoft.IdentityModel.Tokens;
using UniversityAllExpelled_BusinessLogic.Implementations;
using UniversityAllExpelled_DataBase;
using UniversityAllExpelled_DataBase.Implementations;
using UniversityAllExpelled_Database.Implementations;
using UniversityAllExpelled_Models.BusinessLogicContracts;
using UniversityAllExpelled_Models.Infrastructure;
using UniversityAllExpelled_Models.StorageContracts;
using UniversityAllExpelled_WebApi.Infrastructure;
using UniversityAllExpelled_Database;
var builder = WebApplication.CreateBuilder(args);
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> enum
builder.Services.AddControllers()
.AddJsonOptions(options =>
{
options.JsonSerializerOptions.Converters.Add(new System.Text.Json.Serialization.JsonStringEnumConverter());
});
// JWT Authentication
builder.Services.AddAuthorization();
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = AuthOptions.ISSUER,
ValidateAudience = true,
ValidAudience = AuthOptions.AUDIENCE,
ValidateLifetime = true,
IssuerSigningKey = AuthOptions.GetSymmetricSecurityKey(),
ValidateIssuerSigningKey = true,
};
});
// DbContext
builder.Services.AddDbContext<UniversityAllExpelledDbContext>(options =>
options.UseNpgsql(
builder.Configuration["DataBaseSettings:ConnectionString"],
x => x.MigrationsAssembly("UniversityAllExpelled_WebApi"))
);
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>-<2D><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
builder.Services.AddTransient<IConfigurationDatabase, ConfigurationDatabase>();
builder.Services.AddTransient<IUserBusinessLogicContract, UserBusinessLogicContract>();
builder.Services.AddTransient<IStudentBusinessLogicContract, StudentBusinessLogicContract>();
builder.Services.AddTransient<ITeacherBusinessLogicContract, TeacherBusinessLogicContract>();
builder.Services.AddTransient<ILessonBusinessLogicContract, LessonBusinessLogicContract>();
builder.Services.AddTransient<ISalaryBusinessLogicContract, SalaryBusinessLogicContract>();
builder.Services.AddTransient<IEducationBusinessLogicContract, EducationBusinessLogicContract>();
builder.Services.AddTransient<IEducationLessonsBusinessLogicContract, EducationLessonsBusinessLogicContract>();
builder.Services.AddTransient<IPaymentBusinessLogicContract, PaymentBusinessLogicContract>();
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
builder.Services.AddTransient<IUserStorageContract, UserStorageContract>();
builder.Services.AddTransient<IStudentStorageContract, StudentStorageContract>();
builder.Services.AddTransient<ITeacherStorageContract, TeacherStorageContract>();
builder.Services.AddTransient<ILessonStorageContract, LessonStorageContract>();
builder.Services.AddTransient<ISalaryStorageContract, SalaryStorageContract>();
builder.Services.AddTransient<IEducationStorageContract, EducationStorageContract>();
builder.Services.AddTransient<IEducationLessonsStorageContract, EducationLessonsStorageContract>();
builder.Services.AddTransient<IPaymentStorageContract, PaymentStorageContract>();
builder.Services.AddScoped<IUniversityDbContext, UniversityAllExpelledDbContext>();
builder.Services.AddAutoMapper(typeof(Program));
builder.Services.AddLogging();
// Swagger (OpenAPI)
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
app.UseDefaultFiles();
app.UseStaticFiles(); // <20><><EFBFBD> wwwroot
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization();
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> Swagger UI
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "University All Expelled API V1");
c.RoutePrefix = "swagger";
});
app.MapControllers();
app.Run();
public class AuthOptions
{
public const string ISSUER = "MyAuthServer";
public const string AUDIENCE = "MyAuthClient";
const string KEY = "mysupersecret_secretsecretsecretkey!123";
public static SymmetricSecurityKey GetSymmetricSecurityKey() =>
new(Encoding.UTF8.GetBytes(KEY));
}

View File

@@ -0,0 +1,23 @@
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": false,
"applicationUrl": "http://localhost:5215",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": false,
"applicationUrl": "https://localhost:7226;http://localhost:5215",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

View File

@@ -0,0 +1,38 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<OutputType>Exe</OutputType>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="9.0.5" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.4">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="9.0.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="9.0.4">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.IdentityModel.Tokens" Version="8.10.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\UniversityAllExpelled_BusinessLogic\UniversityAllExpelled_BusinessLogic.csproj" />
<ProjectReference Include="..\UniversityAllExpelled_DataBase\UniversityAllExpelled_DataBase.csproj" />
<ProjectReference Include="..\UniversityAllExpelled_Models\UniversityAllExpelled_Models.csproj" />
</ItemGroup>
<ItemGroup>
<Folder Include="Controllers\" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,6 @@
@UniversityAllExpelled_WebApi_HostAddress = http://localhost:5215
GET {{UniversityAllExpelled_WebApi_HostAddress}}/weatherforecast/
Accept: application/json
###

View File

@@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}

View File

@@ -0,0 +1,12 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"DataBaseSettings": {
"ConnectionString": "Host=localhost;Port=5432;Database=university;Username=postgres;password=daa200513dr"
}
}

View File

@@ -0,0 +1,238 @@
/* Общие стили */
* {
box-sizing: border-box;
}
body, html {
margin: 0;
padding: 0;
font-family: Arial, sans-serif;
height: 100%;
background: linear-gradient(to right, #a2d4f1, #00c6d7);
}
/* Основная структура */
.dashboard-layout {
display: flex;
height: 100vh;
}
/* Левая панель */
.sidebar {
width: 300px;
background: rgba(255, 255, 255, 0.2);
border-right: 2px solid #fff;
display: flex;
flex-direction: column;
padding: 30px;
box-sizing: border-box;
}
.logo-section {
display: flex;
justify-content: center;
align-items: center;
margin-bottom: 20px;
width: 100%;
flex-shrink: 0;
}
.main-logo {
max-width: 80%;
height: auto;
max-height: 120px;
object-fit: contain;
}
/* Контейнер кнопок */
.nav-container {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
width: 100%;
}
.nav-button {
width: 100%;
flex: 1;
padding: 16px;
margin-bottom: 10px;
background-color: rgba(0, 0, 0, 0.2);
border: 2px solid #00BFFF;
color: #fff;
font-size: 16px;
font-weight: 500;
border-radius: 6px;
cursor: pointer;
transition: all 0.3s ease;
text-align: center;
display: flex;
align-items: center;
justify-content: center;
}
.nav-button:last-child {
margin-bottom: 0;
}
.nav-button:hover {
background-color: rgba(0, 0, 0, 0.35);
transform: translateY(-1px);
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
}
.nav-button i {
margin-right: 6px;
}
/* Правая часть */
.student-info {
flex: 1;
display: flex;
justify-content: center;
align-items: center;
padding: 40px;
box-sizing: border-box;
overflow-y: auto;
background: rgba(255, 255, 255, 0.2);
border-left: 2px solid #fff;
}
/* Контент секций */
.content-section {
background-color: rgba(0, 0, 0, 0.2);
color: #fff;
border-radius: 8px;
padding: 30px 40px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
max-width: 700px;
width: 100%;
display: none;
}
/* Только профиль показываем при запуске */
#profileSection {
display: block;
}
.student-info h1 {
text-align: center;
font-size: 28px;
margin-bottom: 30px;
color: #fff;
}
.info-group {
padding-left: 10px;
}
.info-group p {
margin: 12px 0;
font-size: 1rem;
color: #fff;
}
.info-group strong {
color: #fff;
min-width: 120px;
display: inline-block;
}
/* Адаптивность */
@media (max-width: 768px) {
.dashboard-layout {
flex-direction: column;
}
.sidebar {
width: 100%;
padding: 20px;
height: auto;
}
.nav-container {
flex-direction: row;
flex-wrap: wrap;
justify-content: center;
gap: 10px;
}
.nav-button {
flex: 1 1 120px;
justify-content: center;
}
.student-info {
padding: 20px;
}
.content-section {
padding: 20px;
max-width: 100%;
}
@keyframes slideInUp {
0% {
transform: translateY(100%);
opacity: 0;
}
100% {
transform: translateY(0);
opacity: 1;
}
}
@keyframes slideOutUp {
0% {
transform: translateY(0);
opacity: 1;
}
100% {
transform: translateY(-100%);
opacity: 0;
}
}
@keyframes slideInDown {
0% {
transform: translateY(-100%);
opacity: 0;
}
100% {
transform: translateY(0);
opacity: 1;
}
}
@keyframes slideOutDown {
0% {
transform: translateY(0);
opacity: 1;
}
100% {
transform: translateY(100%);
opacity: 0;
}
}
.slide-in-up {
animation: slideInUp 0.4s ease forwards;
}
.slide-out-up {
animation: slideOutUp 0.4s ease forwards;
}
.slide-in-down {
animation: slideInDown 0.4s ease forwards;
}
.slide-out-down {
animation: slideOutDown 0.4s ease forwards;
}
}

View File

@@ -0,0 +1,177 @@
/* Общие базовые настройки */
* {
box-sizing: border-box;
}
body, html {
margin: 0;
padding: 0;
height: 100%;
font-family: Arial, sans-serif;
background: linear-gradient(to right, #a2d4f1, #00c6d7);
}
/* Контейнер всей страницы с двумя колонками */
.page-layout {
display: flex;
height: 100vh;
}
/* Левая часть — логотип */
.logo-section {
flex: 1;
display: flex;
justify-content: center;
align-items: center;
}
.main-logo {
max-width: 80%;
height: auto;
}
/* Правая часть — форма авторизации/регистрации */
.auth-container {
width: 300px;
background: rgba(255, 255, 255, 0.2);
border-left: 2px solid #fff;
padding: 50px 40px;
display: flex;
flex-direction: column;
min-height: 130vh;
box-sizing: border-box;
overflow-y: auto;
}
/* Контейнер формы */
.auth-box {
display: flex;
flex-direction: column;
width: 100%;
min-height: 130vh;
}
/* Заголовок формы */
.auth-box h2 {
text-align: center;
color: #fff;
margin-bottom: 25px;
}
/* Форма — вертикальный стек элементов */
.auth-form {
display: flex;
flex-direction: column;
width: 100%;
}
/* Универсальный стиль для всех input и select */
.auth-input {
width: 100%;
padding: 8px 10px;
border-radius: 6px;
border: none;
font-size: 14px;
margin-bottom: 10px;
background-color: rgba(255, 255, 255, 0.9);
color: #000;
box-sizing: border-box;
transition: box-shadow 0.3s ease;
}
.auth-input:focus {
outline: none;
box-shadow: 0 0 5px 2px #00c6d7;
}
/* Свитч роли — вертикальный стек радио */
.role-switch {
display: flex;
flex-direction: column;
color: #fff;
font-size: 14px;
margin-bottom: 10px;
}
.role-switch label {
display: flex;
align-items: center;
cursor: pointer;
margin-bottom: 10px;
}
.role-switch input[type="radio"] {
width: 16px;
height: 16px;
margin-right: 8px;
vertical-align: middle;
}
/* Контейнер для дополнительных полей */
.additional-fields {
display: flex;
flex-direction: column;
margin-bottom: 10px;
}
/* Подвал формы с кнопкой */
.auth-footer {
display: flex;
justify-content: flex-start;
align-items: center;
margin-top: 10px;
}
/* Кнопка отправки */
.login-btn {
padding: 8px 20px;
font-size: 14px;
border-radius: 6px;
cursor: pointer;
background-color: rgba(0, 0, 0, 0.2);
border: 2px solid #00BFFF;
color: #fff;
transition: all 0.3s ease;
}
.login-btn:hover {
background-color: #009ba0;
transform: translateY(-1px);
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
}
/* Сообщение об ошибке */
.alert-danger {
background-color: #f8d7da;
border-color: #f5c6cb;
color: #721c24;
padding: 0.75rem 1.25rem;
margin-bottom: 1rem;
border: 1px solid transparent;
border-radius: 0.25rem;
}
/* Адаптивность */
@media (max-width: 768px) {
.page-layout {
flex-direction: column;
}
.logo-section {
padding: 1rem;
}
.auth-container {
width: 100%;
padding: 1.5rem;
}
.auth-box {
padding: 1rem;
}
body {
text-size-adjust: 100%;
-webkit-text-size-adjust: 100%;
}
}

View File

@@ -0,0 +1,81 @@
/* Универсальные сбросы и базовая структура */
* {
box-sizing: border-box;
}
body, html {
margin: 0;
padding: 0;
height: 100%;
font-family: Arial, sans-serif;
background: linear-gradient(to right, #a2d4f1, #00c6d7);
}
/* Кнопки (универсальные на всех страницах) */
button,
.login-btn,
.nav-button {
padding: 10px 16px;
font-size: 14px;
border-radius: 6px;
cursor: pointer;
transition: all 0.3s ease;
border: none;
}
/* Основной стиль навигационных кнопок */
.nav-button {
background-color: rgba(0, 0, 0, 0.2);
border: 2px solid #00BFFF;
color: #fff;
font-weight: 500;
display: flex;
align-items: center;
gap: 10px;
}
.nav-button:hover {
background-color: rgba(0, 0, 0, 0.35);
}
.nav-button.active {
background: rgba(255, 255, 255, 0.3);
font-weight: bold;
}
.nav-button i {
font-size: 1.1rem;
}
/* Инпуты и поля */
input,
select,
.auth-input {
width: 100%;
padding: 8px 10px;
border-radius: 6px;
border: none;
font-size: 14px;
background-color: rgba(255, 255, 255, 0.9);
color: #000;
transition: box-shadow 0.3s ease;
}
input:focus,
.auth-input:focus {
outline: none;
box-shadow: 0 0 5px 2px #00c6d7;
}
/* Адаптивность */
@media (max-width: 768px) {
body {
text-size-adjust: 100%;
-webkit-text-size-adjust: 100%;
}
.nav-button {
flex: 1 1 150px;
justify-content: center;
}
}

View File

@@ -0,0 +1,159 @@
/* Общие стили */
body, html {
margin: 0;
padding: 0;
font-family: Arial, sans-serif;
height: 100%;
background: linear-gradient(to right, #a2d4f1, #00c6d7);
}
/* Разметка */
.payment-layout {
display: flex;
min-height: 100vh
}
/* Боковая панель */
.sidebar {
height: auto;
min-height: 100%;
width: 150px;
background: rgba(255, 255, 255, 0.1);
border-right: 1px solid #00BFFF;
display: flex;
flex-direction: column; /* изменено: вертикальное расположение */
align-items: center;
padding: 20px 10px;
box-sizing: border-box;
}
/* Секция логотипа */
.logo-section {
width: 100%;
display: flex;
justify-content: center;
margin-bottom: 20px; /* отступ от логотипа до кнопки */
}
.main-logo {
max-width: 100%;
max-height: 60px;
object-fit: contain;
}
/* Кнопка "На Главную" */
.back-button {
padding: 10px;
background-color: rgba(0, 0, 0, 0.2);
color: #fff;
border: 2px solid #003344;
border-radius: 6px;
font-size: 14px;
cursor: pointer;
transition: background-color 0.3s;
width: 100%;
text-align: center;
margin-bottom: 20px;
}
.back-button:hover {
background-color: rgba(0, 0, 0, 0.4);
}
/* Контент */
.payment-content {
flex: 1;
padding: 40px;
display: flex;
flex-direction: column;
align-items: center;
color: #fff;
}
/* Заголовок */
.section-title {
font-size: 28px;
margin-bottom: 30px;
}
/* Информация по оплате */
.payment-info-box {
background: rgba(255, 255, 255, 0.2);
padding: 25px 20px;
border-radius: 10px;
box-shadow: 0 0 8px rgba(0, 0, 0, 0.2);
margin-bottom: 25px;
width: 100%;
max-width: 450px;
}
.payment-info-box p {
margin: 10px 0;
}
/* Элементы ввода */
.payment-input {
padding: 10px;
font-size: 14px;
margin-top: 10px;
border: none;
border-radius: 6px;
width: 100%;
box-sizing: border-box;
}
/* Кнопка оплаты */
.pay-button {
padding: 12px;
background-color: rgba(0, 0, 0, 0.2);
color: #fff;
border: 2px solid #003344;
border-radius: 6px;
font-size: 16px;
cursor: pointer;
transition: background-color 0.3s;
width: 100%;
max-width: 450px;
}
.pay-button:hover {
background-color: rgba(0, 0, 0, 0.4);
}
/* Форма оплаты */
.payment-form {
background: rgba(255, 255, 255, 0.2);
padding: 20px;
border-radius: 10px;
margin-bottom: 20px;
box-shadow: 0 0 8px rgba(0, 0, 0, 0.2);
width: 100%;
max-width: 450px;
display: flex;
flex-direction: column;
gap: 15px;
}
/* Детали карты */
#cardDetails {
display: none;
flex-direction: column;
gap: 15px;
}
/* Номер карты — отдельной строкой */
#cardNumber {
width: 100%;
}
/* MM/YY и CSV — в строку */
.card-row {
display: flex;
gap: 15px;
}
#cardExpiry,
#cardCSV {
flex: 1;
min-width: 0;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

View File

@@ -0,0 +1,73 @@
// education.js
document.addEventListener("DOMContentLoaded", () => {
const studySection = document.getElementById("studySection");
if (!studySection) return;
const educationList = document.getElementById("educationList");
const createBtn = document.getElementById("createEducationBtn");
const userId = localStorage.getItem("userId");
if (!userId) return;
async function loadStudentEducations() {
educationList.innerHTML = "<div class='text-white'>Загрузка...</div>";
try {
const res = await fetch(`/api/education/by-student/${userId}`);
if (!res.ok) throw new Error("Ошибка запроса");
const educations = await res.json();
educationList.innerHTML = "";
if (educations.length === 0) {
educationList.innerHTML = "<div class='text-white'>У вас пока нет обучений.</div>";
return;
}
educations.forEach((edu, i) => {
const item = document.createElement("div");
item.className = "accordion-item";
item.innerHTML = `
<h2 class="accordion-header">
<button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#edu-${i}">
Обучение ${edu.id} | ${new Date(edu.startDate).toLocaleDateString()}${new Date(edu.endDate).toLocaleDateString()}
</button>
</h2>
<div id="edu-${i}" class="accordion-collapse collapse">
<div class="accordion-body text-white" id="edu-lessons-${i}">
Загрузка уроков...
</div>
</div>
`;
educationList.appendChild(item);
// загружаем уроки при открытии панели
const collapseEl = item.querySelector(`#edu-${i}`);
collapseEl.addEventListener("show.bs.collapse", async () => {
const lessonContainer = document.getElementById(`edu-lessons-${i}`);
lessonContainer.textContent = "Загрузка...";
const resp = await fetch(`/api/lesson/by-education/${edu.id}`);
const lessons = await resp.json();
if (lessons.length === 0) {
lessonContainer.textContent = "Нет уроков.";
return;
}
lessonContainer.innerHTML = lessons.map(l => `<div>${l.name} (${new Date(l.date).toLocaleDateString()})</div>`).join("");
});
});
} catch (e) {
educationList.innerHTML = `<div class='text-danger'>Ошибка загрузки обучений</div>`;
}
}
// загрузка при открытии секции "Обучение"
document.querySelector('[data-section="study"]').addEventListener("click", loadStudentEducations);
// кнопка "Создать обучение"
createBtn.addEventListener("click", () => {
alert("Создание обучения пока не реализовано.");
});
});

View File

@@ -0,0 +1,46 @@
document.addEventListener("DOMContentLoaded", () => {
const sectionBtn = document.querySelector('[data-section="teaching"]');
const userId = localStorage.getItem("userId");
if (!sectionBtn || !userId) return;
sectionBtn.addEventListener("click", async () => {
const list = document.getElementById("lessonList");
list.innerHTML = "Загрузка...";
try {
const res = await fetch(`/api/lesson/by-teacher/${userId}`);
const lessons = await res.json();
list.innerHTML = lessons.map(l =>
`<div class="accordion-item mb-2">
<div class="accordion-header p-2 bg-white text-dark">
Урок: ${new Date(l.date).toLocaleDateString()}${l.theme}
</div>
</div>`
).join("");
} catch {
list.innerHTML = "Не удалось загрузить уроки.";
}
});
document.getElementById("createLessonBtn").addEventListener("click", async () => {
const theme = prompt("Введите тему урока:");
const date = prompt("Введите дату (ГГГГ-ММ-ДД):");
if (!theme || !date) return;
const response = await fetch("/api/lesson/create", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ teacherId: userId, theme, date })
});
if (response.ok) {
alert("Урок создан");
sectionBtn.click();
} else {
alert("Ошибка при создании урока");
}
});
});

View File

@@ -0,0 +1,111 @@
document.addEventListener("DOMContentLoaded", () => {
const role = localStorage.getItem("userRole");
// Скрываем все секции кроме профиля
document.querySelectorAll(".content-section").forEach(sec => sec.style.display = "none");
const profile = document.getElementById("profileSection");
if (profile) profile.style.display = "block";
// Подставляем данные в профиль
document.getElementById("userFio").textContent = localStorage.getItem("userFio");
document.getElementById("userEmail").textContent = localStorage.getItem("userEmail");
document.getElementById("userPhone").textContent = localStorage.getItem("userPhone");
document.getElementById("userRole").textContent = role === "student" ? "Студент" : "Преподаватель";
if (role === "student") {
document.getElementById("studentCourse").textContent = localStorage.getItem("userCourse") ?? "—";
} else if (role === "teacher") {
const hireDate = new Date(localStorage.getItem("userDateHiring"));
const now = new Date();
let years = now.getFullYear() - hireDate.getFullYear();
if (now.getMonth() < hireDate.getMonth() || (now.getMonth() === hireDate.getMonth() && now.getDate() < hireDate.getDate())) {
years--;
}
document.getElementById("teacherExperience").textContent = `${years} лет`;
}
// Навигация по секциям
document.querySelectorAll(".nav-button").forEach(btn => {
btn.addEventListener("click", () => {
const sectionId = btn.dataset.section;
document.querySelectorAll(".content-section").forEach(sec => sec.style.display = "none");
const target = document.getElementById(sectionId + "Section");
if (target) target.style.display = "block";
document.querySelectorAll(".nav-button").forEach(b => b.classList.remove("active"));
btn.classList.add("active");
});
});
// Отображение блоков по ролям
document.querySelectorAll("[data-role]").forEach(el => {
el.style.display = el.dataset.role === role ? "" : "none";
});
// Выход из аккаунта
document.getElementById("logoutBtn").addEventListener("click", () => {
localStorage.clear();
window.location.href = "/login.html";
});
// Выбор отчета
const reportType = document.getElementById("reportType");
const reportsBtn = document.querySelector('[data-section="reports"]');
if (reportsBtn && reportType) {
reportsBtn.addEventListener("click", () => {
reportType.innerHTML = role === "student"
? `<option value="education">По обучениям</option><option value="payment">По оплатам</option>`
: `<option value="lesson">По дисциплинам</option><option value="salary">По заработку</option>`;
});
}
// Формирование отчета
document.getElementById("generateReportBtn")?.addEventListener("click", async () => {
const reportType = document.getElementById("reportType").value;
const format = document.getElementById("formatSelect").value;
const startDate = document.getElementById("startDate").value;
const endDate = document.getElementById("endDate").value;
const statusBox = document.getElementById("reportStatus");
const userId = localStorage.getItem("userId");
if (!startDate || !endDate) {
statusBox.textContent = "Выберите даты начала и конца периода.";
return;
}
try {
const response = await fetch("/api/report/generate", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ userId, role, type: reportType, format, startDate, endDate })
});
if (response.ok) {
const blob = await response.blob();
const fileName = `Отчет_${reportType}_${startDate}_${endDate}.${format}`;
const link = document.createElement("a");
link.href = URL.createObjectURL(blob);
link.download = fileName;
document.body.appendChild(link);
link.click();
link.remove();
statusBox.textContent = "Отчет успешно сформирован.";
} else {
statusBox.textContent = "Ошибка при формировании отчета.";
}
} catch (err) {
statusBox.textContent = "Ошибка соединения: " + err.message;
}
});
// Отображение полей карты при выборе способа оплаты
const methodSelect = document.getElementById("paymentMethod");
const cardDetails = document.getElementById("cardDetails");
if (methodSelect && cardDetails) {
const updateCardVisibility = () => {
cardDetails.style.display = methodSelect.value === "card" ? "block" : "none";
};
methodSelect.addEventListener("change", updateCardVisibility);
updateCardVisibility(); // Инициализация при загрузке
}
});

View File

@@ -0,0 +1,46 @@
document.addEventListener("DOMContentLoaded", () => {
const form = document.getElementById("loginForm");
const errorBox = document.getElementById("errorMessage");
form.addEventListener("submit", async (e) => {
e.preventDefault();
const login = document.getElementById("loginInput").value;
const password = document.getElementById("passwordInput").value;
try {
const response = await fetch("/api/user/authorize", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ login, password })
});
if (!response.ok) {
const msg = await response.text();
errorBox.classList.remove("d-none");
errorBox.innerText = msg || "Ошибка входа";
return;
}
const user = await response.json();
localStorage.setItem("userId", user.id);
localStorage.setItem("userLogin", user.login);
localStorage.setItem("userFio", user.fio);
localStorage.setItem("userEmail", user.email);
localStorage.setItem("userPhone", user.phoneNumber);
localStorage.setItem("userRole", user.role);
if (user.course !== null && user.course !== undefined)
localStorage.setItem("userCourse", user.course);
if (user.dateHiring !== null && user.dateHiring !== undefined)
localStorage.setItem("userDateHiring", user.dateHiring);
window.location.href = "/lk.html";
} catch (error) {
errorBox.classList.remove("d-none");
errorBox.innerText = "Ошибка подключения: " + error.message;
}
});
});

View File

@@ -0,0 +1,58 @@
document.addEventListener("DOMContentLoaded", () => {
const sectionBtn = document.querySelector('[data-section="payments"]');
const userId = localStorage.getItem("userId");
if (!sectionBtn || !userId) return;
sectionBtn.addEventListener("click", async () => {
const eduSelect = document.getElementById("educationSelect");
const paymentInfo = document.getElementById("paymentInfo");
eduSelect.innerHTML = "<option disabled selected>Загрузка...</option>";
paymentInfo.innerHTML = "";
try {
const res = await fetch(`/api/education/by-student/${userId}`);
const educations = await res.json();
eduSelect.innerHTML = educations.map(e =>
`<option value="${e.id}">Обучение ${e.id} (${new Date(e.startDate).toLocaleDateString()}${new Date(e.endDate).toLocaleDateString()})</option>`
).join("");
eduSelect.onchange = async () => {
const educationId = eduSelect.value;
const r = await fetch(`/api/payment/status/${educationId}`);
const { total, paid, due } = await r.json();
paymentInfo.innerHTML = `
<p>Общая стоимость: <strong>${total} руб.</strong></p>
<p>Уже оплачено: <strong>${paid} руб.</strong></p>
<p>Осталось оплатить: <strong>${due} руб.</strong></p>
`;
};
eduSelect.dispatchEvent(new Event("change"));
document.getElementById("payBtn").onclick = async () => {
const educationId = eduSelect.value;
const amount = parseFloat(document.getElementById("paymentAmount").value);
const method = document.getElementById("paymentMethod").value;
const response = await fetch(`/api/payment/make`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ studentId: userId, educationId, amount, method })
});
if (response.ok) {
alert("Оплата успешно проведена");
eduSelect.dispatchEvent(new Event("change"));
} else {
alert("Ошибка оплаты");
}
};
} catch {
eduSelect.innerHTML = `<option disabled selected>Ошибка загрузки</option>`;
}
});
});

View File

@@ -0,0 +1,70 @@
document.addEventListener("DOMContentLoaded", () => {
const form = document.getElementById("registerForm");
const courseField = document.getElementById("studentFields");
const teacherField = document.getElementById("teacherFields");
const roleInputs = document.querySelectorAll("input[name='role']");
// Переключение видимости полей по роли
function toggleRoleFields() {
const role = document.querySelector("input[name='role']:checked").value;
courseField.style.display = role === "student" ? "block" : "none";
teacherField.style.display = role === "teacher" ? "block" : "none";
}
roleInputs.forEach(input => {
input.addEventListener("change", toggleRoleFields);
});
toggleRoleFields(); // инициализация на старте
form.addEventListener("submit", async function (e) {
e.preventDefault();
const role = form.role.value;
const data = {
login: form.login.value,
password: form.password.value,
email: form.email.value,
phone: form.phone.value,
fio: form.fio.value,
birthDate: form.birthDate.value,
role: role
};
if (role === "student") {
const course = parseInt(form.course.value);
if (isNaN(course) || course < 1 || course > 6) {
alert("Некорректное значение курса");
return;
}
data.course = course;
} else if (role === "teacher") {
const hiringDate = form.dateHiring.value;
if (!hiringDate) {
alert("Введите дату приёма на работу");
return;
}
data.dateHiring = hiringDate;
}
try {
const response = await fetch("/api/user/register", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(data)
});
if (!response.ok) {
const text = await response.text();
alert("Ошибка регистрации: " + text);
return;
}
alert("Регистрация прошла успешно");
window.location.href = "/login.html";
} catch (error) {
alert("Ошибка подключения: " + error.message);
}
});
});

View File

@@ -0,0 +1,59 @@
document.addEventListener("DOMContentLoaded", () => {
const reportsButton = document.querySelector('[data-section="reports"]');
const userRole = localStorage.getItem("userRole");
const userId = localStorage.getItem("userId");
if (!reportsButton || !userRole || !userId) return;
reportsButton.addEventListener("click", () => {
const reportTypeSelect = document.getElementById("reportType");
reportTypeSelect.innerHTML = userRole === "student"
? `<option value="education">По обучениям</option><option value="payment">По оплатам</option>`
: `<option value="education">По дисциплинам</option><option value="salary">По зарплате</option>`;
});
document.getElementById("generateReportBtn").addEventListener("click", async () => {
const reportType = document.getElementById("reportType").value;
const format = document.getElementById("formatSelect").value;
const startDate = document.getElementById("startDate").value;
const endDate = document.getElementById("endDate").value;
const statusBox = document.getElementById("reportStatus");
if (!startDate || !endDate) {
statusBox.textContent = "Выберите даты начала и конца периода.";
return;
}
try {
const response = await fetch(`/api/report/generate`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
userId,
role: userRole,
type: reportType,
format,
startDate,
endDate
})
});
if (response.ok) {
const blob = await response.blob();
const fileName = `Отчет_${reportType}_${startDate}_${endDate}.${format}`;
const link = document.createElement("a");
link.href = URL.createObjectURL(blob);
link.download = fileName;
document.body.appendChild(link);
link.click();
link.remove();
statusBox.textContent = "Отчет успешно сформирован.";
} else {
statusBox.textContent = "Ошибка при формировании отчета.";
}
} catch (err) {
statusBox.textContent = "Ошибка соединения: " + err.message;
}
});
});

View File

@@ -0,0 +1,28 @@
document.addEventListener("DOMContentLoaded", () => {
const salaryBtn = document.getElementById("calculateSalaryBtn");
const resultBox = document.getElementById("salaryResult");
const monthInput = document.getElementById("salaryMonth");
const userId = localStorage.getItem("userId");
if (!salaryBtn || !monthInput || !userId) return;
salaryBtn.addEventListener("click", async () => {
const month = monthInput.value;
if (!month) {
resultBox.textContent = "Выберите месяц.";
return;
}
try {
const response = await fetch(`/api/salary/by-month?teacherId=${userId}&month=${month}`);
if (response.ok) {
const data = await response.json();
resultBox.innerHTML = `<p>Зарплата за ${month}: <strong>${data.amount} руб.</strong></p>`;
} else {
resultBox.textContent = "Ошибка при расчете зарплаты.";
}
} catch {
resultBox.textContent = "Сервер не отвечает.";
}
});
});

View File

@@ -0,0 +1,135 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Личный кабинет</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet" />
<link href="/css/lk.css" rel="stylesheet" />
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.8.1/font/bootstrap-icons.css" rel="stylesheet" />
</head>
<body>
<div class="dashboard-layout">
<aside class="sidebar">
<div class="logo-section">
<img src="/image/logo.png" alt="Логотип" class="main-logo" />
</div>
<div class="nav-container">
<button class="nav-button active" data-section="profile"><i class="bi bi-person"></i> Профиль</button>
<button class="nav-button" data-section="study" data-role="student"><i class="bi bi-journal-bookmark"></i> Обучение</button>
<button class="nav-button" data-section="payments" data-role="student"><i class="bi bi-credit-card"></i> Оплата</button>
<button class="nav-button" data-section="teaching" data-role="teacher"><i class="bi bi-mortarboard"></i> Преподавание</button>
<button class="nav-button" data-section="salary" data-role="teacher"><i class="bi bi-cash"></i> Моя зарплата</button>
<button class="nav-button" data-section="reports"><i class="bi bi-graph-up"></i> Отчеты</button>
<button class="nav-button" id="logoutBtn"><i class="bi bi-box-arrow-right"></i> Выход</button>
</div>
</aside>
<main class="student-info">
<div id="profileSection" class="content-section">
<h1>Профиль</h1>
<div class="info-group">
<p><strong>ФИО:</strong> <span id="userFio"></span></p>
<p><strong>Email:</strong> <span id="userEmail"></span></p>
<p><strong>Телефон:</strong> <span id="userPhone"></span></p>
<p><strong>Роль:</strong> <span id="userRole"></span></p>
<div data-role="student">
<p><strong>Курс:</strong> <span id="studentCourse"></span></p>
</div>
<div data-role="teacher">
<p><strong>Стаж:</strong> <span id="teacherExperience"></span></p>
</div>
</div>
</div>
<div id="studySection" class="content-section" data-role="student">
<h2 style="color: #fff;">Мои обучения</h2>
<div id="educationList" class="accordion mb-4"></div>
<button id="createEducationBtn" class="btn btn-outline-light">Создать обучение</button>
</div>
<div id="paymentsSection" class="content-section" data-role="student">
<h2 style="color: #fff;">Оплата обучения</h2>
<div class="mb-3">
<label for="educationSelect" class="form-label text-white">Выберите обучение:</label>
<select id="educationSelect" class="form-select mb-3"></select>
</div>
<div id="paymentInfo" class="text-white mb-3"></div>
<div id="paymentForm">
<label class="form-label text-white">Сумма оплаты:</label>
<input type="number" id="paymentAmount" class="form-control mb-2" />
<label class="form-label text-white">Способ оплаты:</label>
<select id="paymentMethod" class="form-select mb-2">
<option value="card">Банковская карта</option>
<option value="sbp">СБП</option>
</select>
<div id="cardDetails" style="display: none;">
<label class="form-label text-white">Номер карты:</label>
<input type="text" id="cardNumber" class="form-control mb-2" placeholder="1234 5678 9012 3456" maxlength="19" />
<label class="form-label text-white">Срок действия (MM/YY):</label>
<input type="text" id="cardExpiry" class="form-control mb-2" placeholder="MM/YY" maxlength="5" />
<label class="form-label text-white">CSV:</label>
<input type="text" id="cardCsv" class="form-control mb-2" placeholder="123" maxlength="3" />
</div>
<button id="payBtn" class="btn btn-success">Оплатить</button>
</div>
</div>
<div id="teachingSection" class="content-section" data-role="teacher">
<h2 style="color: #fff;">Мои занятия</h2>
<div id="lessonList" class="accordion mb-4"></div>
<button id="createLessonBtn" class="btn btn-outline-light">Создать урок</button>
</div>
<div id="salarySection" class="content-section" data-role="teacher">
<h2 style="color: #fff;">Моя зарплата</h2>
<div class="mb-3">
<label for="salaryMonth" class="form-label text-white">Выберите месяц:</label>
<input type="month" id="salaryMonth" class="form-control mb-2" />
<button id="calculateSalaryBtn" class="btn btn-outline-light">Рассчитать зарплату за месяц</button>
</div>
<div id="salaryResult" class="text-white mt-3"></div>
</div>
<div id="reportsSection" class="content-section">
<h2 style="color: #fff;">Отчеты</h2>
<div class="mb-3">
<label for="reportType" class="form-label text-white">Тип отчета:</label>
<select id="reportType" class="form-select"></select>
</div>
<div class="mb-3 d-flex flex-wrap gap-2">
<div>
<label class="form-label text-white">С:</label>
<input type="date" id="startDate" class="form-control" />
</div>
<div>
<label class="form-label text-white">По:</label>
<input type="date" id="endDate" class="form-control" />
</div>
</div>
<div class="mb-3">
<label class="form-label text-white">Формат отчета:</label>
<select id="formatSelect" class="form-select">
<option value="doc">.doc</option>
<option value="pdf">.pdf</option>
<option value="xls">.xls</option>
</select>
</div>
<div class="mb-3">
<button id="generateReportBtn" class="btn btn-outline-light">Сформировать</button>
</div>
<div id="reportStatus" class="text-white mt-3"></div>
</div>
</main>
</div>
<script type="module" src="/js/lk.js"></script>
<script type="module" src="/js/education.js"></script>
<script type="module" src="/js/payment.js"></script>
<script type="module" src="/js/report.js"></script>
<script type="module" src="/js/lesson.js"></script>
<script type="module" src="/js/salary.js"></script>
</body>
</html>

View File

@@ -0,0 +1,32 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>УГУ - Вход</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet" />
<link href="/css/login.css" rel="stylesheet" />
</head>
<body>
<div class="page-layout">
<div class="logo-section">
<img src="/image/logo.png" alt="Логотип УГУ" class="main-logo" />
</div>
<div class="auth-container">
<div class="auth-box">
<h2>Вход</h2>
<form id="loginForm" class="auth-form">
<div id="errorMessage" class="alert alert-danger d-none"></div>
<input type="text" id="loginInput" class="auth-input" placeholder="Логин" required />
<input type="password" id="passwordInput" class="auth-input" placeholder="Пароль" required />
<div class="auth-footer">
<a href="/register.html" class="register-link">Регистрация</a>
<button type="submit" class="btn btn-primary login-btn"style="margin-left: 30px;":>Войти</button>
</div>
</form>
</div>
</div>
</div>
<script type="module" src="/js/login.js"></script>
</body>
</html>

View File

@@ -0,0 +1,59 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Регистрация — УГУ</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet" />
<link href="/css/login.css" rel="stylesheet" />
<style>
body {
text-size-adjust: 100%;
-webkit-text-size-adjust: 100%;
}
th {
text-align: match-parent;
}
</style>
</head>
<body>
<div class="page-layout">
<div class="logo-section">
<img src="/image/logo.png" alt="Логотип" class="main-logo" />
</div>
<div class="auth-container">
<div class="auth-box">
<h2>Регистрация</h2>
<form id="registerForm" class="auth-form" novalidate>
<div class="role-switch">
<label><input type="radio" name="role" value="student" checked /> Студент</label>
<label><input type="radio" name="role" value="teacher" /> Преподаватель</label>
</div>
<input type="text" name="fio" class="auth-input" placeholder="ФИО" required />
<input type="text" name="login" class="auth-input" placeholder="Логин" required />
<input type="email" name="email" class="auth-input" placeholder="Email" required />
<input type="tel" name="phone" class="auth-input" placeholder="Телефон" required />
<input type="date" name="birthDate" class="auth-input" required placeholder="Дата рождения" />
<input type="password" name="password" class="auth-input" placeholder="Пароль" required />
<div id="studentFields" class="additional-fields">
<input type="number" id="courseInput" name="course" class="auth-input"
placeholder="Курс (16)" min="1" max="6" />
</div>
<div id="teacherFields" class="additional-fields" style="display:none;">
<input type="date" name="dateHiring" class="auth-input" placeholder="Дата приёма на работу" />
</div>
<div class="auth-footer">
<button type="submit" class="btn btn-primary login-btn">Зарегистрироваться</button>
</div>
</form>
</div>
</div>
</div>
<script type="module" src="/js/register.js"></script>
</body>
</html>