From 3d9fdf1994d2f20afca17607ae360fcf7c61af8a Mon Sep 17 00:00:00 2001 From: maeosha Date: Thu, 2 Oct 2025 19:31:16 +0300 Subject: [PATCH 01/56] feat: add enums: BloodGroup, Gender and RhesusFactor --- Clinic/Clinic.Models/Enums/BloodGroup.cs | 10 ++++++++++ Clinic/Clinic.Models/Enums/Gender.cs | 8 ++++++++ Clinic/Clinic.Models/Enums/RhesusFactor.cs | 8 ++++++++ 3 files changed, 26 insertions(+) create mode 100644 Clinic/Clinic.Models/Enums/BloodGroup.cs create mode 100644 Clinic/Clinic.Models/Enums/Gender.cs create mode 100644 Clinic/Clinic.Models/Enums/RhesusFactor.cs diff --git a/Clinic/Clinic.Models/Enums/BloodGroup.cs b/Clinic/Clinic.Models/Enums/BloodGroup.cs new file mode 100644 index 000000000..325763c3a --- /dev/null +++ b/Clinic/Clinic.Models/Enums/BloodGroup.cs @@ -0,0 +1,10 @@ +namespace Clinic.Models.Enums +{ + public enum BloodGroup + { + First, + Second, + Third, + Fourth + } +} \ No newline at end of file diff --git a/Clinic/Clinic.Models/Enums/Gender.cs b/Clinic/Clinic.Models/Enums/Gender.cs new file mode 100644 index 000000000..5f211b905 --- /dev/null +++ b/Clinic/Clinic.Models/Enums/Gender.cs @@ -0,0 +1,8 @@ +namespace Clinic.Models.Enums +{ + public enum Gender + { + Male, + Female + } +} \ No newline at end of file diff --git a/Clinic/Clinic.Models/Enums/RhesusFactor.cs b/Clinic/Clinic.Models/Enums/RhesusFactor.cs new file mode 100644 index 000000000..7e29b61e5 --- /dev/null +++ b/Clinic/Clinic.Models/Enums/RhesusFactor.cs @@ -0,0 +1,8 @@ +namespace Clinic.Models.Enums +{ + public enum RhesusFactor + { + Positive, + Negative + } +} From b6a1c2394215b559f3aa8c769ac6fac5870db567 Mon Sep 17 00:00:00 2001 From: maeosha Date: Thu, 2 Oct 2025 19:32:00 +0300 Subject: [PATCH 02/56] feat: add contract Appointment --- Clinic/Clinic.Models/Contracts/Appointment.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 Clinic/Clinic.Models/Contracts/Appointment.cs diff --git a/Clinic/Clinic.Models/Contracts/Appointment.cs b/Clinic/Clinic.Models/Contracts/Appointment.cs new file mode 100644 index 000000000..a5c689f12 --- /dev/null +++ b/Clinic/Clinic.Models/Contracts/Appointment.cs @@ -0,0 +1,15 @@ +using Clinic.Models.Entities; + +namespace Clinic.Models.Contracts +{ + public class Appointment + { + public required Patient Patient { get; set; } + public required Doctor Doctor { get; set; } + public required DateTime DateTime { get; set; } + public required int RoomNumber { get; set; } + public required bool IsReturnVisit { get; set; } + + + } +} \ No newline at end of file From 44a437e8f4572f99b0ed0be645ea6729d7e1d0bb Mon Sep 17 00:00:00 2001 From: maeosha Date: Thu, 2 Oct 2025 19:32:40 +0300 Subject: [PATCH 03/56] feat: add reference book Specialisation --- Clinic/Clinic.Models/ReferenceBook/Specialisation.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 Clinic/Clinic.Models/ReferenceBook/Specialisation.cs diff --git a/Clinic/Clinic.Models/ReferenceBook/Specialisation.cs b/Clinic/Clinic.Models/ReferenceBook/Specialisation.cs new file mode 100644 index 000000000..5d4a7f943 --- /dev/null +++ b/Clinic/Clinic.Models/ReferenceBook/Specialisation.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; + +namespace Clinic.Models.ReferenceBooks +{ + public class Specialisation + { + public required int Id { get; set; } + public required string Name { get; set; } + } +} \ No newline at end of file From 628947b16f88ec3ed3d047ed863762fbacd8a605 Mon Sep 17 00:00:00 2001 From: maeosha Date: Thu, 2 Oct 2025 19:33:26 +0300 Subject: [PATCH 04/56] feat: add entities: Doctor and Patient --- Clinic/Clinic.Models/Entities/Doctor.cs | 25 ++++++++++++++++++++ Clinic/Clinic.Models/Entities/Patient.cs | 29 ++++++++++++++++++++++++ 2 files changed, 54 insertions(+) create mode 100644 Clinic/Clinic.Models/Entities/Doctor.cs create mode 100644 Clinic/Clinic.Models/Entities/Patient.cs diff --git a/Clinic/Clinic.Models/Entities/Doctor.cs b/Clinic/Clinic.Models/Entities/Doctor.cs new file mode 100644 index 000000000..e48e8f687 --- /dev/null +++ b/Clinic/Clinic.Models/Entities/Doctor.cs @@ -0,0 +1,25 @@ +using System.Collections.Generic; +using Clinic.Models.ReferenceBooks; + +namespace Clinic.Models.Entities +{ + public class Doctor + { + public required string PassportNumber { get; set; } + public required string LastName { get; set; } + public required string FirstName { get; set; } + public string? Patronymic { get; set; } + public required int BirthYear { get; set; } + public required List Specializations { get; set; } + public required int ExperienceYears { get; set; } + + public string GetFullName() + { + if (Patronymic == null) + { + return $"{LastName} {FirstName}"; + } + return $"{LastName} {FirstName} {Patronymic}"; + } + } +} \ No newline at end of file diff --git a/Clinic/Clinic.Models/Entities/Patient.cs b/Clinic/Clinic.Models/Entities/Patient.cs new file mode 100644 index 000000000..871a489bc --- /dev/null +++ b/Clinic/Clinic.Models/Entities/Patient.cs @@ -0,0 +1,29 @@ +using Clinic.Models.Enums; + +namespace Clinic.Models.Entities +{ + public class Patient + { + public required string PassportNumber { get; set; } + public required string LastName { get; set; } + public required string FirstName { get; set; } + public string? Patronymic { get; set; } + public required Gender Gender { get; set; } + public required DateTime BirthDate { get; set; } + public required string Address { get; set; } + public required BloodGroup BloodGroup { get; set; } + public required RhesusFactor RhesusFactor { get; set; } + public required string PhoneNumber { get; set; } + + public Patient() { } + + public string GetFullName() + { + if (Patronymic == null) + { + return $"{LastName} {FirstName}"; + } + return $"{LastName} {FirstName} {Patronymic}"; + } + } +} \ No newline at end of file From e96ed1c10eb69d9c7ccd9754ea15c167039b5b6a Mon Sep 17 00:00:00 2001 From: maeosha Date: Thu, 2 Oct 2025 19:33:50 +0300 Subject: [PATCH 05/56] feat: add main class Clinic --- Clinic/Clinic.Models/Clinic.cs | 45 ++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 Clinic/Clinic.Models/Clinic.cs diff --git a/Clinic/Clinic.Models/Clinic.cs b/Clinic/Clinic.Models/Clinic.cs new file mode 100644 index 000000000..b58a96255 --- /dev/null +++ b/Clinic/Clinic.Models/Clinic.cs @@ -0,0 +1,45 @@ +using Clinic.Models.Contracts; +using Clinic.Models.Entities; +using Clinic.Models.ReferenceBooks; +using System.Collections.Generic; + +namespace Clinic.Models +{ + public class ClinicInfo + { + public List Doctors { get; set; } = new(); + public List Patients { get; set; } = new(); + public List Appointments { get; set; } = new(); + public Dictionary Specialisations { get; set; } = new(); + + public ClinicInfo() + { + Doctors = new List(); + Patients = new List(); + Appointments = new List(); + Specialisations = new Dictionary(); + } + + public void AddDoctor(Doctor doctor) + { + Doctors.Add(doctor); + } + + public void AddPatient(Patient patient) + { + Patients.Add(patient); + } + + public void MakeAnAppointment(Appointment appointment) + { + Appointments.Add(appointment); + } + + public void AddSpecialization(string key, Specialisation specialization) + { + Specialisations[key] = specialization; + } + } +} + + From 7cd6a44d10ca752e68c2e4a8ece6a5be7f599ffc Mon Sep 17 00:00:00 2001 From: maeosha Date: Thu, 2 Oct 2025 19:34:20 +0300 Subject: [PATCH 06/56] feat: add dataseed for unit-tests --- Clinic/Clinic.Tests/DataSeed/dataseed.cs | 457 +++++++++++++++++++++++ 1 file changed, 457 insertions(+) create mode 100644 Clinic/Clinic.Tests/DataSeed/dataseed.cs diff --git a/Clinic/Clinic.Tests/DataSeed/dataseed.cs b/Clinic/Clinic.Tests/DataSeed/dataseed.cs new file mode 100644 index 000000000..bbcaccecf --- /dev/null +++ b/Clinic/Clinic.Tests/DataSeed/dataseed.cs @@ -0,0 +1,457 @@ +using Clinic.Models; +using Clinic.Models.Entities; +using Clinic.Models.Enums; +using Clinic.Models.ReferenceBooks; +using Clinic.Models.Contracts; +using Microsoft.VisualBasic; +using System; + +namespace Clinic.Tests +{ + public class ClinicDataSeed + { + public ClinicInfo clinic{ get; set; } = new ClinicInfo(); + public ClinicDataSeed() + { + var patient1 = new Patient + { + PassportNumber = "P001", + LastName = "Иванов", + FirstName = "Иван", + Patronymic = "Иванович", + Gender = Gender.Male, + BirthDate = new DateTime(1985, 5, 20), + Address = "г. Москва, ул. Ленина, д. 10", + BloodGroup = BloodGroup.Second, + RhesusFactor = RhesusFactor.Positive, + PhoneNumber = "+79991234567" + }; + clinic.AddPatient(patient1); + + var patient2 = new Patient + { + PassportNumber = "P002", + LastName = "Петрова", + FirstName = "Мария", + Patronymic = "Сергеевна", + Gender = Gender.Female, + BirthDate = new DateTime(1990, 8, 15), + Address = "г. Санкт-Петербург, ул. Пушкина, д. 25", + BloodGroup = BloodGroup.First, + RhesusFactor = RhesusFactor.Positive, + PhoneNumber = "+79992345678" + }; + clinic.AddPatient(patient2); + + var patient3 = new Patient + { + PassportNumber = "P003", + LastName = "Сидоров", + FirstName = "Алексей", + Patronymic = "Петрович", + Gender = Gender.Male, + BirthDate = new DateTime(1978, 12, 3), + Address = "г. Екатеринбург, ул. Мира, д. 15", + BloodGroup = BloodGroup.Third, + RhesusFactor = RhesusFactor.Negative, + PhoneNumber = "+79993456789" + }; + clinic.AddPatient(patient3); + + var patient4 = new Patient + { + PassportNumber = "P004", + LastName = "Кузнецова", + FirstName = "Ольга", + Patronymic = "Владимировна", + Gender = Gender.Female, + BirthDate = new DateTime(1995, 3, 10), + Address = "г. Новосибирск, ул. Советская, д. 8", + BloodGroup = BloodGroup.Fourth, + RhesusFactor = RhesusFactor.Positive, + PhoneNumber = "+79994567890" + }; + clinic.AddPatient(patient4); + + var patient5 = new Patient + { + PassportNumber = "P005", + LastName = "Васильев", + FirstName = "Дмитрий", + Patronymic = "Александрович", + Gender = Gender.Male, + BirthDate = new DateTime(1982, 7, 28), + Address = "г. Казань, ул. Гагарина, д. 12", + BloodGroup = BloodGroup.Second, + RhesusFactor = RhesusFactor.Negative, + PhoneNumber = "+79995678901" + }; + clinic.AddPatient(patient5); + + var patient6 = new Patient + { + PassportNumber = "P006", + LastName = "Николаева", + FirstName = "Елена", + Patronymic = "Игоревна", + Gender = Gender.Female, + BirthDate = new DateTime(1988, 11, 5), + Address = "г. Нижний Новгород, ул. Лермонтова, д. 30", + BloodGroup = BloodGroup.First, + RhesusFactor = RhesusFactor.Negative, + PhoneNumber = "+79996789012" + }; + clinic.AddPatient(patient6); + + var patient7 = new Patient + { + PassportNumber = "P007", + LastName = "Морозов", + FirstName = "Сергей", + Patronymic = "Викторович", + Gender = Gender.Male, + BirthDate = new DateTime(1975, 1, 18), + Address = "г. Самара, ул. Чехова, д. 7", + BloodGroup = BloodGroup.Third, + RhesusFactor = RhesusFactor.Positive, + PhoneNumber = "+79997890123" + }; + clinic.AddPatient(patient7); + + var patient8 = new Patient + { + PassportNumber = "P008", + LastName = "Орлова", + FirstName = "Анна", + Patronymic = "Дмитриевна", + Gender = Gender.Female, + BirthDate = new DateTime(1992, 9, 22), + Address = "г. Ростов-на-Дону, ул. Кирова, д. 18", + BloodGroup = BloodGroup.Fourth, + RhesusFactor = RhesusFactor.Negative, + PhoneNumber = "+79998901234" + }; + clinic.AddPatient(patient8); + + var patient9 = new Patient + { + PassportNumber = "P009", + LastName = "Павлов", + FirstName = "Михаил", + Patronymic = "Олегович", + Gender = Gender.Male, + BirthDate = new DateTime(1980, 4, 14), + Address = "г. Уфа, ул. Горького, д. 22", + BloodGroup = BloodGroup.First, + RhesusFactor = RhesusFactor.Positive, + PhoneNumber = "+79999012345" + }; + clinic.AddPatient(patient9); + + var patient10 = new Patient + { + PassportNumber = "P010", + LastName = "Федорова", + FirstName = "Татьяна", + Patronymic = "Николаевна", + Gender = Gender.Female, + BirthDate = new DateTime(1987, 6, 30), + Address = "г. Красноярск, ул. Ленина, д. 5", + BloodGroup = BloodGroup.Second, + RhesusFactor = RhesusFactor.Positive, + PhoneNumber = "+79990123456" + }; + clinic.AddPatient(patient10); + + clinic.AddSpecialization("Therapist", new Specialisation { Id = 1, Name = "Терапевт" }); + clinic.AddSpecialization("Dentist", new Specialisation { Id = 2, Name = "Стоматолог" }); + clinic.AddSpecialization("Cardiologist", new Specialisation { Id = 3, Name = "Кардиолог" }); + clinic.AddSpecialization("Neurologist", new Specialisation { Id = 4, Name = "Невролог" }); + clinic.AddSpecialization("Pediatrician", new Specialisation { Id = 5, Name = "Педиатр" }); + clinic.AddSpecialization("Dermatologist", new Specialisation { Id = 6, Name = "Дерматолог" }); + clinic.AddSpecialization("Psychiatrist", new Specialisation { Id = 7, Name = "Психиатр" }); + clinic.AddSpecialization("Ophthalmologist", new Specialisation { Id = 8, Name = "Офтальмолог" }); + clinic.AddSpecialization("ENTSpecialist", new Specialisation { Id = 9, Name = "Отоларинголог" }); + clinic.AddSpecialization("Gynecologist", new Specialisation { Id = 10, Name = "Гинеколог" }); + + var doctor1 = new Doctor + { + PassportNumber = "D001", + LastName = "Смирнов", + FirstName = "Андрей", + Patronymic = "Иванович", + BirthYear = 1970, + Specializations = new List { + clinic.Specialisations["Cardiologist"] + }, + ExperienceYears = 15 + }; + clinic.AddDoctor(doctor1); + + var doctor2 = new Doctor + { + PassportNumber = "D002", + LastName = "Коваленко", + FirstName = "Мария", + Patronymic = "Петровна", + BirthYear = 1985, + Specializations = new List { + clinic.Specialisations["Dentist"] + }, + ExperienceYears = 8 + }; + clinic.AddDoctor(doctor2); + + var doctor3 = new Doctor + { + PassportNumber = "D003", + LastName = "Петров", + FirstName = "Сергей", + Patronymic = "Викторович", + BirthYear = 1978, + Specializations = new List { + clinic.Specialisations["Cardiologist"], + clinic.Specialisations["Therapist"] + }, + ExperienceYears = 12 + }; + clinic.AddDoctor(doctor3); + + var doctor4 = new Doctor + { + PassportNumber = "D004", + LastName = "Орлова", + FirstName = "Елена", + Patronymic = "Александровна", + BirthYear = 1982, + Specializations = new List { + clinic.Specialisations["Neurologist"], + clinic.Specialisations["Psychiatrist"] + }, + ExperienceYears = 10 + }; + clinic.AddDoctor(doctor4); + + var doctor5 = new Doctor + { + PassportNumber = "D005", + LastName = "Волков", + FirstName = "Дмитрий", + Patronymic = "Сергеевич", + BirthYear = 1990, + Specializations = new List { + clinic.Specialisations["Pediatrician"], + clinic.Specialisations["Dermatologist"] + }, + ExperienceYears = 5 + }; + clinic.AddDoctor(doctor5); + + var doctor6 = new Doctor + { + PassportNumber = "D006", + LastName = "Никитина", + FirstName = "Ольга", + Patronymic = "Владимировна", + BirthYear = 1987, + Specializations = new List { + clinic.Specialisations["Dermatologist"], + clinic.Specialisations["Ophthalmologist"] + }, + ExperienceYears = 7 + }; + clinic.AddDoctor(doctor6); + + var doctor7 = new Doctor + { + PassportNumber = "D007", + LastName = "Лебедев", + FirstName = "Алексей", + Patronymic = "Игоревич", + BirthYear = 1975, + Specializations = new List { + clinic.Specialisations["Psychiatrist"], + clinic.Specialisations["Neurologist"] + }, + ExperienceYears = 18 + }; + clinic.AddDoctor(doctor7); + + var doctor8 = new Doctor + { + PassportNumber = "D008", + LastName = "Соколова", + FirstName = "Ирина", + Patronymic = "Михайловна", + BirthYear = 1980, + Specializations = new List { + clinic.Specialisations["Ophthalmologist"] + }, + ExperienceYears = 11 + }; + clinic.AddDoctor(doctor8); + + var doctor9 = new Doctor + { + PassportNumber = "D009", + LastName = "Козлов", + FirstName = "Михаил", + Patronymic = "Анатольевич", + BirthYear = 1983, + Specializations = new List { + clinic.Specialisations["Dentist"] + }, + ExperienceYears = 9 + }; + clinic.AddDoctor(doctor9); + + var doctor10 = new Doctor + { + PassportNumber = "D010", + LastName = "Григорьева", + FirstName = "Наталья", + Patronymic = "Валерьевна", + BirthYear = 1979, + Specializations = new List { + clinic.Specialisations["Gynecologist"], + clinic.Specialisations["Pediatrician"] + }, + ExperienceYears = 14 + }; + clinic.AddDoctor(doctor10); + + var appointment1 = new Appointment + { + Patient = patient1, + Doctor = doctor1, + DateTime = new DateTime(2025, 9, 15, 9, 0, 0), + RoomNumber = 101, + IsReturnVisit = false + }; + clinic.MakeAnAppointment(appointment1); + + var appointment2 = new Appointment + { + Patient = patient2, + Doctor = doctor2, + DateTime = new DateTime(2025, 9, 10, 10, 30, 0), + RoomNumber = 205, + IsReturnVisit = true + }; + clinic.MakeAnAppointment(appointment2); + + var appointment3 = new Appointment + { + Patient = patient3, + Doctor = doctor3, + DateTime = new DateTime(2025, 9, 25, 11, 0, 0), + RoomNumber = 102, + IsReturnVisit = false + }; + clinic.MakeAnAppointment(appointment3); + + var appointment4 = new Appointment + { + Patient = patient4, + Doctor = doctor4, + DateTime = new DateTime(2025, 9, 5, 14, 15, 0), + RoomNumber = 303, + IsReturnVisit = false + }; + clinic.MakeAnAppointment(appointment4); + + var appointment5 = new Appointment + { + Patient = patient5, + Doctor = doctor5, + DateTime = new DateTime(2025, 9, 18, 9, 30, 0), + RoomNumber = 303, + IsReturnVisit = true + }; + clinic.MakeAnAppointment(appointment5); + + var appointment6 = new Appointment + { + Patient = patient6, + Doctor = doctor6, + DateTime = new DateTime(2025, 9, 22, 15, 45, 0), + RoomNumber = 206, + IsReturnVisit = false + }; + clinic.MakeAnAppointment(appointment6); + + var appointment7 = new Appointment + { + Patient = patient7, + Doctor = doctor7, + DateTime = new DateTime(2025, 9, 2, 10, 0, 0), + RoomNumber = 303, + IsReturnVisit = true + }; + clinic.MakeAnAppointment(appointment7); + + var appointment8 = new Appointment + { + Patient = patient8, + Doctor = doctor8, + DateTime = new DateTime(2025, 9, 8, 13, 20, 0), + RoomNumber = 207, + IsReturnVisit = false + }; + clinic.MakeAnAppointment(appointment8); + + var appointment9 = new Appointment + { + Patient = patient9, + Doctor = doctor9, + DateTime = new DateTime(2025, 4, 15, 11, 30, 0), + RoomNumber = 208, + IsReturnVisit = false + }; + clinic.MakeAnAppointment(appointment9); + + var appointment10 = new Appointment + { + Patient = patient10, + Doctor = doctor10, + DateTime = new DateTime(2025, 9, 20, 16, 0, 0), + RoomNumber = 105, + IsReturnVisit = true + }; + clinic.MakeAnAppointment(appointment10); + + var appointment11 = new Appointment + { + Patient = patient3, + Doctor = doctor1, + DateTime = new DateTime(2025, 9, 25, 9, 0, 0), + RoomNumber = 101, + IsReturnVisit = true + }; + clinic.MakeAnAppointment(appointment11); + + var appointment12 = new Appointment + { + Patient = patient2, + Doctor = doctor3, + DateTime = new DateTime(2025, 9, 15, 11, 0, 0), + RoomNumber = 102, + IsReturnVisit = true + }; + clinic.MakeAnAppointment(appointment12); + + var appointment13 = new Appointment + { + Patient = patient7, + Doctor = doctor5, + DateTime = new DateTime(2025, 8, 23, 9, 0, 0), + RoomNumber = 101, + IsReturnVisit = true + }; + clinic.MakeAnAppointment(appointment13); + } + + + } + +} \ No newline at end of file From 95cf9fe4f573497feac6c61548010e11e70bfc47 Mon Sep 17 00:00:00 2001 From: maeosha Date: Thu, 2 Oct 2025 19:34:34 +0300 Subject: [PATCH 07/56] feat: add unit-tests --- Clinic/Clinic.Tests/UnitTest1.cs | 122 +++++++++++++++++++++++++++++++ 1 file changed, 122 insertions(+) create mode 100644 Clinic/Clinic.Tests/UnitTest1.cs diff --git a/Clinic/Clinic.Tests/UnitTest1.cs b/Clinic/Clinic.Tests/UnitTest1.cs new file mode 100644 index 000000000..9bf4588ab --- /dev/null +++ b/Clinic/Clinic.Tests/UnitTest1.cs @@ -0,0 +1,122 @@ +using System; +using Clinic.Models; +using Clinic.Models.Entities; +using Microsoft.VisualBasic; + +namespace Clinic.Tests +{ + public class ClinicTests + { + public readonly ClinicInfo clinic = new ClinicDataSeed().clinic; + + [Fact] + public void GetDoctors_ReturnsDoctorsWith10OrMoreYearsOfExperience() + { + List DoctorsWith10OrMoreYearsOfExperience = new List{ + "Смирнов Андрей Иванович", + "Петров Сергей Викторович", + "Орлова Елена Александровна", + "Лебедев Алексей Игоревич", + "Соколова Ирина Михайловна", + "Григорьева Наталья Валерьевна" + }; + + var res_doctors = clinic.Doctors.Where(d => d.ExperienceYears >= 10).ToList(); + var resDoctorsWith10OrMoreYearsOfExperience = new List(); + + foreach (var doctor in res_doctors) + { + resDoctorsWith10OrMoreYearsOfExperience.Add(doctor.GetFullName()); + } + + Assert.Equal(DoctorsWith10OrMoreYearsOfExperience, resDoctorsWith10OrMoreYearsOfExperience); + } + + [Fact] + public void GetTargerDoctor_ReturnsPatientsWhoHaveAppointmentWithTargetDoctor() + { + Doctor targetDoctor = clinic.Doctors.First(d => d.LastName == "Смирнов" && d.FirstName == "Андрей"); + List patientsWhoHaveAppointmentWithTargetDocor = [ + "Иванов Иван Иванович", + "Сидоров Алексей Петрович" + ]; + + var res_patients = clinic.Appointments + .Where(d => d.Doctor == targetDoctor) + .Select(p => p.Patient) + .Distinct() + .OrderBy(n => n.LastName) + .ThenBy(n => n.FirstName) + .ThenBy(n => n.Patronymic) + .ToList(); + + var result = new List(); + + foreach (var patient in res_patients) + { + result.Add(patient.GetFullName()); + } + + Assert.Equal(patientsWhoHaveAppointmentWithTargetDocor, result); + } + + [Fact] + public void GetAppointmentInTheLastMonths_ReturnsRepeatedAppointment() + { + var CountRepeatedAppointments = 6; + + DateTime now = DateTime.Now; + DateTime lastMonth = now.AddMonths(-1); + + var resCountRepeatedAppointments = clinic.Appointments + .Where(a => a.DateTime >= lastMonth && a.IsReturnVisit) + .Count(); + + Assert.Equal(CountRepeatedAppointments, resCountRepeatedAppointments); + } + + [Fact] + public void GetPationsOver30_ReturnsPationsWhoHaveAppointmentWithSeveralDoctors() + { + var PationsWhoHaveAppointmentWithSeveralDoctors = new List + { + "Морозов Сергей Викторович", + "Сидоров Алексей Петрович", + "Петрова Мария Сергеевна" + }; + + var currentDate = DateTime.Now; + var age30 = currentDate.AddYears(-30); + + var patients = clinic.Patients + .Where(a => a.BirthDate <= age30) + .Where(p => clinic.Appointments.Count(a => a.Patient.PassportNumber == p.PassportNumber) > 1) + .OrderBy(d => d.BirthDate) + .ToList(); + + var resPationsWhoHaveAppointmentWithSeveralDoctors = new List(); + foreach (var patient in patients) + { + resPationsWhoHaveAppointmentWithSeveralDoctors.Add(patient.GetFullName()); + } + Assert.Equal(PationsWhoHaveAppointmentWithSeveralDoctors, resPationsWhoHaveAppointmentWithSeveralDoctors); + } + [Fact] + public void GetTargetRoom_ReturnsCountAppointmentsInTheLastMonthThatTookPlaceInTargetRoom() + { + var CountAppointmentsInTheLastMonthThatTookPlaceInTargetRoom = 3; + + var targetRoom = 303; + DateTime now = DateTime.Now; + DateTime lastMonth = now.AddMonths(-1); + + + int resCountAppointmentsInTheLastMonthThatTookPlaceInTargetRoom = clinic.Appointments + .Where(a => a.RoomNumber == targetRoom && + a.DateTime >= lastMonth) + .Count(); + + Assert.Equal(CountAppointmentsInTheLastMonthThatTookPlaceInTargetRoom, resCountAppointmentsInTheLastMonthThatTookPlaceInTargetRoom); + } + } +} From 594985e3c6399a0c0637c2909eaa46faaf141357 Mon Sep 17 00:00:00 2001 From: maeosha Date: Thu, 2 Oct 2025 19:37:54 +0300 Subject: [PATCH 08/56] feat: add build configuration for C# project --- Clinic/Clinic.Models/Clinic.Models.csproj | 9 ++++++++ Clinic/Clinic.Tests/Clinic.Tests.csproj | 25 +++++++++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 Clinic/Clinic.Models/Clinic.Models.csproj create mode 100644 Clinic/Clinic.Tests/Clinic.Tests.csproj diff --git a/Clinic/Clinic.Models/Clinic.Models.csproj b/Clinic/Clinic.Models/Clinic.Models.csproj new file mode 100644 index 000000000..1cd3df193 --- /dev/null +++ b/Clinic/Clinic.Models/Clinic.Models.csproj @@ -0,0 +1,9 @@ + + + + net9.0 + enable + enable + + + \ No newline at end of file diff --git a/Clinic/Clinic.Tests/Clinic.Tests.csproj b/Clinic/Clinic.Tests/Clinic.Tests.csproj new file mode 100644 index 000000000..0ca980e9c --- /dev/null +++ b/Clinic/Clinic.Tests/Clinic.Tests.csproj @@ -0,0 +1,25 @@ + + + + net9.0 + enable + enable + false + + + + + + + + + + + + + + + + + + From f2e25758cf8b2553c1d6620c446590a41e0a9a5c Mon Sep 17 00:00:00 2001 From: maeosha Date: Sun, 9 Nov 2025 02:04:05 +0300 Subject: [PATCH 09/56] feat: add solution --- Clinic/Clinic.sln | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 Clinic/Clinic.sln diff --git a/Clinic/Clinic.sln b/Clinic/Clinic.sln new file mode 100644 index 000000000..fb0877eb2 --- /dev/null +++ b/Clinic/Clinic.sln @@ -0,0 +1,30 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.5.2.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Clinic.Models", "Clinic.Models\Clinic.Models.csproj", "{5351F009-4016-C1FF-B495-C469DDDFBE5E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Clinic.Tests", "Clinic.Tests\Clinic.Tests.csproj", "{74F8AF9E-B8C9-B2D3-9764-9BF8E80D866D}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {5351F009-4016-C1FF-B495-C469DDDFBE5E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5351F009-4016-C1FF-B495-C469DDDFBE5E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5351F009-4016-C1FF-B495-C469DDDFBE5E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5351F009-4016-C1FF-B495-C469DDDFBE5E}.Release|Any CPU.Build.0 = Release|Any CPU + {74F8AF9E-B8C9-B2D3-9764-9BF8E80D866D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {74F8AF9E-B8C9-B2D3-9764-9BF8E80D866D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {74F8AF9E-B8C9-B2D3-9764-9BF8E80D866D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {74F8AF9E-B8C9-B2D3-9764-9BF8E80D866D}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {6E454636-A821-46B9-A0BD-05DA9F1F5DE1} + EndGlobalSection +EndGlobal From ab3db8cb7b6f8eb722d23f6884c0cbda40451a19 Mon Sep 17 00:00:00 2001 From: maeosha Date: Sun, 9 Nov 2025 02:05:15 +0300 Subject: [PATCH 10/56] add summary and chage blood goup names --- Clinic/Clinic.Models/Enums/BloodGroup.cs | 20 +++++++++++--------- Clinic/Clinic.Models/Enums/Gender.cs | 16 +++++++++------- Clinic/Clinic.Models/Enums/RhesusFactor.cs | 14 ++++++++------ 3 files changed, 28 insertions(+), 22 deletions(-) diff --git a/Clinic/Clinic.Models/Enums/BloodGroup.cs b/Clinic/Clinic.Models/Enums/BloodGroup.cs index 325763c3a..795931ac5 100644 --- a/Clinic/Clinic.Models/Enums/BloodGroup.cs +++ b/Clinic/Clinic.Models/Enums/BloodGroup.cs @@ -1,10 +1,12 @@ -namespace Clinic.Models.Enums +namespace Clinic.Models.Enums; + +/// +/// Represents the blood group categories used in the clinic domain. +/// +public enum BloodGroup { - public enum BloodGroup - { - First, - Second, - Third, - Fourth - } -} \ No newline at end of file + A, + B, + AB, + O +} diff --git a/Clinic/Clinic.Models/Enums/Gender.cs b/Clinic/Clinic.Models/Enums/Gender.cs index 5f211b905..60d17c06d 100644 --- a/Clinic/Clinic.Models/Enums/Gender.cs +++ b/Clinic/Clinic.Models/Enums/Gender.cs @@ -1,8 +1,10 @@ -namespace Clinic.Models.Enums +namespace Clinic.Models.Enums; + +/// +/// Represents the gender of a person. +/// +public enum Gender { - public enum Gender - { - Male, - Female - } -} \ No newline at end of file + Male, + Female +} diff --git a/Clinic/Clinic.Models/Enums/RhesusFactor.cs b/Clinic/Clinic.Models/Enums/RhesusFactor.cs index 7e29b61e5..ef6930906 100644 --- a/Clinic/Clinic.Models/Enums/RhesusFactor.cs +++ b/Clinic/Clinic.Models/Enums/RhesusFactor.cs @@ -1,8 +1,10 @@ -namespace Clinic.Models.Enums +namespace Clinic.Models.Enums; + +/// +/// Rhesus factor of the patient indicating presence of the RhD antigen. +/// +public enum RhesusFactor { - public enum RhesusFactor - { - Positive, - Negative - } + Positive, + Negative } From faba51e00a532e6e19ab9abd92e886f29d309c60 Mon Sep 17 00:00:00 2001 From: maeosha Date: Sun, 9 Nov 2025 02:08:06 +0300 Subject: [PATCH 11/56] feat: extracted identical variables and functions from the doctors and patients classes into a separate class --- Clinic/Clinic.Models/Common/PersonInfo.cs | 57 +++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 Clinic/Clinic.Models/Common/PersonInfo.cs diff --git a/Clinic/Clinic.Models/Common/PersonInfo.cs b/Clinic/Clinic.Models/Common/PersonInfo.cs new file mode 100644 index 000000000..052a949ff --- /dev/null +++ b/Clinic/Clinic.Models/Common/PersonInfo.cs @@ -0,0 +1,57 @@ +using Clinic.Models.Enums; + +namespace Clinic.Models.Common; + +/// +/// Represents personal information for a person. +/// +public class PersonInfo +{ + /// + /// Gets or sets the unique identifier. + /// + required public int Id { get; set; } + + /// + /// Gets or sets the passport number. + /// + required public string PassportNumber { get; set; } + + /// + /// Gets or sets the year of birth. + /// + required public DateOnly BirthDate { get; set; } + + /// + /// Gets or sets the last name. + /// + required public string LastName { get; set; } + + /// + /// Gets or sets the first name. + /// + required public string FirstName { get; set; } + + /// + /// Gets or sets the patronymic. + /// + public string? Patronymic { get; set; } + + /// + /// Gets or sets the gender. + /// + required public Gender Gender { get; set; } + + /// + /// Gets the full name composed of last name, first name and optional patronymic. + /// + /// The full name as a single string. + public string GetFullName() + { + if (string.IsNullOrEmpty(Patronymic)) + { + return $"{LastName} {FirstName}"; + } + return $"{LastName} {FirstName} {Patronymic}"; + } +} \ No newline at end of file From 3e1eda4f5797ff33b60dfc61d6db311000a9a541 Mon Sep 17 00:00:00 2001 From: maeosha Date: Sun, 9 Nov 2025 02:09:04 +0300 Subject: [PATCH 12/56] feat: Now these classes inherit the basic characteristics from PersonInfo. fix: add summary --- Clinic/Clinic.Models/Entities/Doctor.cs | 37 ++++++++---------- Clinic/Clinic.Models/Entities/Patient.cs | 49 ++++++++++++------------ 2 files changed, 41 insertions(+), 45 deletions(-) diff --git a/Clinic/Clinic.Models/Entities/Doctor.cs b/Clinic/Clinic.Models/Entities/Doctor.cs index e48e8f687..59fe507d1 100644 --- a/Clinic/Clinic.Models/Entities/Doctor.cs +++ b/Clinic/Clinic.Models/Entities/Doctor.cs @@ -1,25 +1,20 @@ -using System.Collections.Generic; +using Clinic.Models.Common; using Clinic.Models.ReferenceBooks; -namespace Clinic.Models.Entities +namespace Clinic.Models.Entities; + +/// +/// Represents a doctor with personal and professional details. +/// +public class Doctor : PersonInfo { - public class Doctor - { - public required string PassportNumber { get; set; } - public required string LastName { get; set; } - public required string FirstName { get; set; } - public string? Patronymic { get; set; } - public required int BirthYear { get; set; } - public required List Specializations { get; set; } - public required int ExperienceYears { get; set; } + /// + /// Gets or sets the list of medical specializations the doctor holds. + /// + required public List Specializations { get; set; } - public string GetFullName() - { - if (Patronymic == null) - { - return $"{LastName} {FirstName}"; - } - return $"{LastName} {FirstName} {Patronymic}"; - } - } -} \ No newline at end of file + /// + /// Gets or sets the number of years of experience the doctor has. + /// + required public int ExperienceYears { get; set; } +} diff --git a/Clinic/Clinic.Models/Entities/Patient.cs b/Clinic/Clinic.Models/Entities/Patient.cs index 871a489bc..384e79d0d 100644 --- a/Clinic/Clinic.Models/Entities/Patient.cs +++ b/Clinic/Clinic.Models/Entities/Patient.cs @@ -1,29 +1,30 @@ +using Clinic.Models.Common; using Clinic.Models.Enums; -namespace Clinic.Models.Entities +namespace Clinic.Models.Entities; + +/// +/// Represents a patient entity with personal and medical information. +/// +public class Patient : PersonInfo { - public class Patient - { - public required string PassportNumber { get; set; } - public required string LastName { get; set; } - public required string FirstName { get; set; } - public string? Patronymic { get; set; } - public required Gender Gender { get; set; } - public required DateTime BirthDate { get; set; } - public required string Address { get; set; } - public required BloodGroup BloodGroup { get; set; } - public required RhesusFactor RhesusFactor { get; set; } - public required string PhoneNumber { get; set; } + /// + /// Gets or sets the patient's address. + /// + required public string Address { get; set; } + + /// + /// Gets or sets the patient's blood group. + /// + required public BloodGroup BloodGroup { get; set; } - public Patient() { } + /// + /// Gets or sets the patient's rhesus factor. + /// + required public RhesusFactor RhesusFactor { get; set; } - public string GetFullName() - { - if (Patronymic == null) - { - return $"{LastName} {FirstName}"; - } - return $"{LastName} {FirstName} {Patronymic}"; - } - } -} \ No newline at end of file + /// + /// Gets or sets the patient's phone number. + /// + required public string PhoneNumber { get; set; } +} From 06d6b89f542a7f3cc131f067f9c7b9264d0ba6e5 Mon Sep 17 00:00:00 2001 From: maeosha Date: Sun, 9 Nov 2025 02:15:16 +0300 Subject: [PATCH 13/56] fix: add summary --- Clinic/Clinic.Models/Clinic.cs | 95 ++++++++++++------- Clinic/Clinic.Models/Entities/Appointment.cs | 32 +++++++ .../ReferenceBook/Specialization.cs | 17 ++++ 3 files changed, 108 insertions(+), 36 deletions(-) create mode 100644 Clinic/Clinic.Models/Entities/Appointment.cs create mode 100644 Clinic/Clinic.Models/ReferenceBook/Specialization.cs diff --git a/Clinic/Clinic.Models/Clinic.cs b/Clinic/Clinic.Models/Clinic.cs index b58a96255..e49499882 100644 --- a/Clinic/Clinic.Models/Clinic.cs +++ b/Clinic/Clinic.Models/Clinic.cs @@ -1,45 +1,68 @@ -using Clinic.Models.Contracts; using Clinic.Models.Entities; using Clinic.Models.ReferenceBooks; using System.Collections.Generic; -namespace Clinic.Models +namespace Clinic.Models; + +/// +/// Central in-memory storage for clinic data. +/// +public class ClinicInfo { - public class ClinicInfo + /// + /// Gets or sets the list of doctors. + /// + public List Doctors { get; set; } = new(); + + /// + /// Gets or sets the list of patients. + /// + public List Patients { get; set; } = new(); + + /// + /// Gets or sets the list of appointments. + /// + public List Appointments { get; set; } = new(); + + /// + /// Gets or sets the dictionary of specializations. + /// + public Dictionary Specializations { get; set; } = new(); + + /// + /// Adds a doctor to the clinic. + /// + /// The doctor to add. + public void AddDoctor(Doctor doctor) + { + Doctors.Add(doctor); + } + + /// + /// Adds a patient to the clinic. + /// + /// The patient to add. + public void AddPatient(Patient patient) { - public List Doctors { get; set; } = new(); - public List Patients { get; set; } = new(); - public List Appointments { get; set; } = new(); - public Dictionary Specialisations { get; set; } = new(); - - public ClinicInfo() - { - Doctors = new List(); - Patients = new List(); - Appointments = new List(); - Specialisations = new Dictionary(); - } - - public void AddDoctor(Doctor doctor) - { - Doctors.Add(doctor); - } - - public void AddPatient(Patient patient) - { - Patients.Add(patient); - } - - public void MakeAnAppointment(Appointment appointment) - { - Appointments.Add(appointment); - } - - public void AddSpecialization(string key, Specialisation specialization) - { - Specialisations[key] = specialization; - } + Patients.Add(patient); } -} + /// + /// Schedules an appointment. + /// + /// The appointment to schedule. + public void MakeAnAppointment(Appointment appointment) + { + Appointments.Add(appointment); + } + /// + /// Adds or updates a specialization. + /// + /// The specialization code. + /// The specialization object. + public void AddSpecialization(string key, Specialization specialization) + { + Specializations[key] = specialization; + } +} \ No newline at end of file diff --git a/Clinic/Clinic.Models/Entities/Appointment.cs b/Clinic/Clinic.Models/Entities/Appointment.cs new file mode 100644 index 000000000..10cf8dd50 --- /dev/null +++ b/Clinic/Clinic.Models/Entities/Appointment.cs @@ -0,0 +1,32 @@ +namespace Clinic.Models.Entities; + +/// +/// Represents an appointment in the clinic, including patient, doctor, date/time, room, and visit type. +/// +public class Appointment +{ + /// + /// Gets or sets the patient for the appointment. + /// + required public Patient Patient { get; set; } + + /// + /// Gets or sets the doctor for the appointment. + /// + required public Doctor Doctor { get; set; } + + /// + /// Gets or sets the date and time of the appointment. + /// + required public DateTime DateTime { get; set; } + + /// + /// Gets or sets the room number for the appointment. + /// + required public int RoomNumber { get; set; } + + /// + /// Gets or sets a value indicating whether this appointment is a return visit. + /// + required public bool IsReturnVisit { get; set; } +} diff --git a/Clinic/Clinic.Models/ReferenceBook/Specialization.cs b/Clinic/Clinic.Models/ReferenceBook/Specialization.cs new file mode 100644 index 000000000..24190fcec --- /dev/null +++ b/Clinic/Clinic.Models/ReferenceBook/Specialization.cs @@ -0,0 +1,17 @@ +namespace Clinic.Models.ReferenceBooks; + +/// +/// Represents a medical specialisation in the clinic. +/// +public class Specialization +{ + /// + /// Gets or sets the unique identifier for the specialisation. + /// + required public int Id { get; set; } + + /// + /// Gets or sets the specialization name. + /// + required public string Name { get; set; } +} From 2fa6ec76fabe5d21c399bd0ff772c6c69dad3b37 Mon Sep 17 00:00:00 2001 From: maeosha Date: Sun, 9 Nov 2025 02:17:41 +0300 Subject: [PATCH 14/56] fix: Rewrite the tests and datasid, bring them to an adequate form. --- Clinic/Clinic.Tests/DataSeed/dataseed.cs | 925 ++++++++++++----------- Clinic/Clinic.Tests/UnitTest1.cs | 183 ++--- 2 files changed, 572 insertions(+), 536 deletions(-) diff --git a/Clinic/Clinic.Tests/DataSeed/dataseed.cs b/Clinic/Clinic.Tests/DataSeed/dataseed.cs index bbcaccecf..d7aec02c7 100644 --- a/Clinic/Clinic.Tests/DataSeed/dataseed.cs +++ b/Clinic/Clinic.Tests/DataSeed/dataseed.cs @@ -2,456 +2,483 @@ using Clinic.Models.Entities; using Clinic.Models.Enums; using Clinic.Models.ReferenceBooks; -using Clinic.Models.Contracts; -using Microsoft.VisualBasic; -using System; -namespace Clinic.Tests -{ - public class ClinicDataSeed - { - public ClinicInfo clinic{ get; set; } = new ClinicInfo(); - public ClinicDataSeed() - { - var patient1 = new Patient - { - PassportNumber = "P001", - LastName = "Иванов", - FirstName = "Иван", - Patronymic = "Иванович", - Gender = Gender.Male, - BirthDate = new DateTime(1985, 5, 20), - Address = "г. Москва, ул. Ленина, д. 10", - BloodGroup = BloodGroup.Second, - RhesusFactor = RhesusFactor.Positive, - PhoneNumber = "+79991234567" - }; - clinic.AddPatient(patient1); - - var patient2 = new Patient - { - PassportNumber = "P002", - LastName = "Петрова", - FirstName = "Мария", - Patronymic = "Сергеевна", - Gender = Gender.Female, - BirthDate = new DateTime(1990, 8, 15), - Address = "г. Санкт-Петербург, ул. Пушкина, д. 25", - BloodGroup = BloodGroup.First, - RhesusFactor = RhesusFactor.Positive, - PhoneNumber = "+79992345678" - }; - clinic.AddPatient(patient2); - - var patient3 = new Patient - { - PassportNumber = "P003", - LastName = "Сидоров", - FirstName = "Алексей", - Patronymic = "Петрович", - Gender = Gender.Male, - BirthDate = new DateTime(1978, 12, 3), - Address = "г. Екатеринбург, ул. Мира, д. 15", - BloodGroup = BloodGroup.Third, - RhesusFactor = RhesusFactor.Negative, - PhoneNumber = "+79993456789" - }; - clinic.AddPatient(patient3); - - var patient4 = new Patient - { - PassportNumber = "P004", - LastName = "Кузнецова", - FirstName = "Ольга", - Patronymic = "Владимировна", - Gender = Gender.Female, - BirthDate = new DateTime(1995, 3, 10), - Address = "г. Новосибирск, ул. Советская, д. 8", - BloodGroup = BloodGroup.Fourth, - RhesusFactor = RhesusFactor.Positive, - PhoneNumber = "+79994567890" - }; - clinic.AddPatient(patient4); - - var patient5 = new Patient - { - PassportNumber = "P005", - LastName = "Васильев", - FirstName = "Дмитрий", - Patronymic = "Александрович", - Gender = Gender.Male, - BirthDate = new DateTime(1982, 7, 28), - Address = "г. Казань, ул. Гагарина, д. 12", - BloodGroup = BloodGroup.Second, - RhesusFactor = RhesusFactor.Negative, - PhoneNumber = "+79995678901" - }; - clinic.AddPatient(patient5); - - var patient6 = new Patient - { - PassportNumber = "P006", - LastName = "Николаева", - FirstName = "Елена", - Patronymic = "Игоревна", - Gender = Gender.Female, - BirthDate = new DateTime(1988, 11, 5), - Address = "г. Нижний Новгород, ул. Лермонтова, д. 30", - BloodGroup = BloodGroup.First, - RhesusFactor = RhesusFactor.Negative, - PhoneNumber = "+79996789012" - }; - clinic.AddPatient(patient6); - - var patient7 = new Patient - { - PassportNumber = "P007", - LastName = "Морозов", - FirstName = "Сергей", - Patronymic = "Викторович", - Gender = Gender.Male, - BirthDate = new DateTime(1975, 1, 18), - Address = "г. Самара, ул. Чехова, д. 7", - BloodGroup = BloodGroup.Third, - RhesusFactor = RhesusFactor.Positive, - PhoneNumber = "+79997890123" - }; - clinic.AddPatient(patient7); - - var patient8 = new Patient - { - PassportNumber = "P008", - LastName = "Орлова", - FirstName = "Анна", - Patronymic = "Дмитриевна", - Gender = Gender.Female, - BirthDate = new DateTime(1992, 9, 22), - Address = "г. Ростов-на-Дону, ул. Кирова, д. 18", - BloodGroup = BloodGroup.Fourth, - RhesusFactor = RhesusFactor.Negative, - PhoneNumber = "+79998901234" - }; - clinic.AddPatient(patient8); - - var patient9 = new Patient - { - PassportNumber = "P009", - LastName = "Павлов", - FirstName = "Михаил", - Patronymic = "Олегович", - Gender = Gender.Male, - BirthDate = new DateTime(1980, 4, 14), - Address = "г. Уфа, ул. Горького, д. 22", - BloodGroup = BloodGroup.First, - RhesusFactor = RhesusFactor.Positive, - PhoneNumber = "+79999012345" - }; - clinic.AddPatient(patient9); - - var patient10 = new Patient - { - PassportNumber = "P010", - LastName = "Федорова", - FirstName = "Татьяна", - Patronymic = "Николаевна", - Gender = Gender.Female, - BirthDate = new DateTime(1987, 6, 30), - Address = "г. Красноярск, ул. Ленина, д. 5", - BloodGroup = BloodGroup.Second, - RhesusFactor = RhesusFactor.Positive, - PhoneNumber = "+79990123456" - }; - clinic.AddPatient(patient10); - - clinic.AddSpecialization("Therapist", new Specialisation { Id = 1, Name = "Терапевт" }); - clinic.AddSpecialization("Dentist", new Specialisation { Id = 2, Name = "Стоматолог" }); - clinic.AddSpecialization("Cardiologist", new Specialisation { Id = 3, Name = "Кардиолог" }); - clinic.AddSpecialization("Neurologist", new Specialisation { Id = 4, Name = "Невролог" }); - clinic.AddSpecialization("Pediatrician", new Specialisation { Id = 5, Name = "Педиатр" }); - clinic.AddSpecialization("Dermatologist", new Specialisation { Id = 6, Name = "Дерматолог" }); - clinic.AddSpecialization("Psychiatrist", new Specialisation { Id = 7, Name = "Психиатр" }); - clinic.AddSpecialization("Ophthalmologist", new Specialisation { Id = 8, Name = "Офтальмолог" }); - clinic.AddSpecialization("ENTSpecialist", new Specialisation { Id = 9, Name = "Отоларинголог" }); - clinic.AddSpecialization("Gynecologist", new Specialisation { Id = 10, Name = "Гинеколог" }); - - var doctor1 = new Doctor - { - PassportNumber = "D001", - LastName = "Смирнов", - FirstName = "Андрей", - Patronymic = "Иванович", - BirthYear = 1970, - Specializations = new List { - clinic.Specialisations["Cardiologist"] - }, - ExperienceYears = 15 - }; - clinic.AddDoctor(doctor1); - - var doctor2 = new Doctor - { - PassportNumber = "D002", - LastName = "Коваленко", - FirstName = "Мария", - Patronymic = "Петровна", - BirthYear = 1985, - Specializations = new List { - clinic.Specialisations["Dentist"] - }, - ExperienceYears = 8 - }; - clinic.AddDoctor(doctor2); - - var doctor3 = new Doctor - { - PassportNumber = "D003", - LastName = "Петров", - FirstName = "Сергей", - Patronymic = "Викторович", - BirthYear = 1978, - Specializations = new List { - clinic.Specialisations["Cardiologist"], - clinic.Specialisations["Therapist"] - }, - ExperienceYears = 12 - }; - clinic.AddDoctor(doctor3); - - var doctor4 = new Doctor - { - PassportNumber = "D004", - LastName = "Орлова", - FirstName = "Елена", - Patronymic = "Александровна", - BirthYear = 1982, - Specializations = new List { - clinic.Specialisations["Neurologist"], - clinic.Specialisations["Psychiatrist"] - }, - ExperienceYears = 10 - }; - clinic.AddDoctor(doctor4); - - var doctor5 = new Doctor - { - PassportNumber = "D005", - LastName = "Волков", - FirstName = "Дмитрий", - Patronymic = "Сергеевич", - BirthYear = 1990, - Specializations = new List { - clinic.Specialisations["Pediatrician"], - clinic.Specialisations["Dermatologist"] - }, - ExperienceYears = 5 - }; - clinic.AddDoctor(doctor5); - - var doctor6 = new Doctor - { - PassportNumber = "D006", - LastName = "Никитина", - FirstName = "Ольга", - Patronymic = "Владимировна", - BirthYear = 1987, - Specializations = new List { - clinic.Specialisations["Dermatologist"], - clinic.Specialisations["Ophthalmologist"] - }, - ExperienceYears = 7 - }; - clinic.AddDoctor(doctor6); - - var doctor7 = new Doctor - { - PassportNumber = "D007", - LastName = "Лебедев", - FirstName = "Алексей", - Patronymic = "Игоревич", - BirthYear = 1975, - Specializations = new List { - clinic.Specialisations["Psychiatrist"], - clinic.Specialisations["Neurologist"] - }, - ExperienceYears = 18 - }; - clinic.AddDoctor(doctor7); - - var doctor8 = new Doctor - { - PassportNumber = "D008", - LastName = "Соколова", - FirstName = "Ирина", - Patronymic = "Михайловна", - BirthYear = 1980, - Specializations = new List { - clinic.Specialisations["Ophthalmologist"] - }, - ExperienceYears = 11 - }; - clinic.AddDoctor(doctor8); - - var doctor9 = new Doctor - { - PassportNumber = "D009", - LastName = "Козлов", - FirstName = "Михаил", - Patronymic = "Анатольевич", - BirthYear = 1983, - Specializations = new List { - clinic.Specialisations["Dentist"] - }, - ExperienceYears = 9 - }; - clinic.AddDoctor(doctor9); - - var doctor10 = new Doctor - { - PassportNumber = "D010", - LastName = "Григорьева", - FirstName = "Наталья", - Patronymic = "Валерьевна", - BirthYear = 1979, - Specializations = new List { - clinic.Specialisations["Gynecologist"], - clinic.Specialisations["Pediatrician"] - }, - ExperienceYears = 14 - }; - clinic.AddDoctor(doctor10); - - var appointment1 = new Appointment - { - Patient = patient1, - Doctor = doctor1, - DateTime = new DateTime(2025, 9, 15, 9, 0, 0), - RoomNumber = 101, - IsReturnVisit = false - }; - clinic.MakeAnAppointment(appointment1); - - var appointment2 = new Appointment - { - Patient = patient2, - Doctor = doctor2, - DateTime = new DateTime(2025, 9, 10, 10, 30, 0), - RoomNumber = 205, - IsReturnVisit = true - }; - clinic.MakeAnAppointment(appointment2); - - var appointment3 = new Appointment - { - Patient = patient3, - Doctor = doctor3, - DateTime = new DateTime(2025, 9, 25, 11, 0, 0), - RoomNumber = 102, - IsReturnVisit = false - }; - clinic.MakeAnAppointment(appointment3); - - var appointment4 = new Appointment - { - Patient = patient4, - Doctor = doctor4, - DateTime = new DateTime(2025, 9, 5, 14, 15, 0), - RoomNumber = 303, - IsReturnVisit = false - }; - clinic.MakeAnAppointment(appointment4); - - var appointment5 = new Appointment - { - Patient = patient5, - Doctor = doctor5, - DateTime = new DateTime(2025, 9, 18, 9, 30, 0), - RoomNumber = 303, - IsReturnVisit = true - }; - clinic.MakeAnAppointment(appointment5); - - var appointment6 = new Appointment - { - Patient = patient6, - Doctor = doctor6, - DateTime = new DateTime(2025, 9, 22, 15, 45, 0), - RoomNumber = 206, - IsReturnVisit = false - }; - clinic.MakeAnAppointment(appointment6); - - var appointment7 = new Appointment - { - Patient = patient7, - Doctor = doctor7, - DateTime = new DateTime(2025, 9, 2, 10, 0, 0), - RoomNumber = 303, - IsReturnVisit = true - }; - clinic.MakeAnAppointment(appointment7); - - var appointment8 = new Appointment - { - Patient = patient8, - Doctor = doctor8, - DateTime = new DateTime(2025, 9, 8, 13, 20, 0), - RoomNumber = 207, - IsReturnVisit = false - }; - clinic.MakeAnAppointment(appointment8); - - var appointment9 = new Appointment - { - Patient = patient9, - Doctor = doctor9, - DateTime = new DateTime(2025, 4, 15, 11, 30, 0), - RoomNumber = 208, - IsReturnVisit = false - }; - clinic.MakeAnAppointment(appointment9); - - var appointment10 = new Appointment - { - Patient = patient10, - Doctor = doctor10, - DateTime = new DateTime(2025, 9, 20, 16, 0, 0), - RoomNumber = 105, - IsReturnVisit = true - }; - clinic.MakeAnAppointment(appointment10); - - var appointment11 = new Appointment - { - Patient = patient3, - Doctor = doctor1, - DateTime = new DateTime(2025, 9, 25, 9, 0, 0), - RoomNumber = 101, - IsReturnVisit = true - }; - clinic.MakeAnAppointment(appointment11); - - var appointment12 = new Appointment - { - Patient = patient2, - Doctor = doctor3, - DateTime = new DateTime(2025, 9, 15, 11, 0, 0), - RoomNumber = 102, - IsReturnVisit = true - }; - clinic.MakeAnAppointment(appointment12); - - var appointment13 = new Appointment - { - Patient = patient7, - Doctor = doctor5, - DateTime = new DateTime(2025, 8, 23, 9, 0, 0), - RoomNumber = 101, - IsReturnVisit = true - }; - clinic.MakeAnAppointment(appointment13); - } +namespace Clinic.Tests.DataSeed; +/// +/// Represents a static helper class responsible for seeding initial clinic data, +/// such as doctors, patients, specializations, and appointments, into a instance. +/// +public class ClinicDataSeed +{ + public ClinicInfo clinic{ get; set; } = new ClinicInfo(); + public ClinicDataSeed() + { + var patient1 = new Patient + { + Id = 1, + PassportNumber = "P001", + LastName = "Иванов", + FirstName = "Иван", + Patronymic = "Иванович", + Gender = Gender.Male, + BirthDate = new DateOnly(1985, 5, 20), + Address = "г. Москва, ул. Ленина, д. 10", + BloodGroup = BloodGroup.A, + RhesusFactor = RhesusFactor.Positive, + PhoneNumber = "+79991234567" + }; + clinic.AddPatient(patient1); + + var patient2 = new Patient + { + Id = 2, + PassportNumber = "P002", + LastName = "Петрова", + FirstName = "Мария", + Patronymic = "Сергеевна", + Gender = Gender.Female, + BirthDate = new DateOnly(1990, 8, 15), + Address = "г. Санкт-Петербург, ул. Пушкина, д. 25", + BloodGroup = BloodGroup.B, + RhesusFactor = RhesusFactor.Positive, + PhoneNumber = "+79992345678" + }; + clinic.AddPatient(patient2); + + var patient3 = new Patient + { + Id = 3, + PassportNumber = "P003", + LastName = "Сидоров", + FirstName = "Алексей", + Patronymic = "Петрович", + Gender = Gender.Male, + BirthDate = new DateOnly(1978, 12, 3), + Address = "г. Екатеринбург, ул. Мира, д. 15", + BloodGroup = BloodGroup.O, + RhesusFactor = RhesusFactor.Negative, + PhoneNumber = "+79993456789" + }; + clinic.AddPatient(patient3); + + var patient4 = new Patient + { + Id = 4, + PassportNumber = "P004", + LastName = "Кузнецова", + FirstName = "Ольга", + Patronymic = "Владимировна", + Gender = Gender.Female, + BirthDate = new DateOnly(1995, 3, 10), + Address = "г. Новосибирск, ул. Советская, д. 8", + BloodGroup = BloodGroup.AB, + RhesusFactor = RhesusFactor.Positive, + PhoneNumber = "+79994567890" + }; + clinic.AddPatient(patient4); + + var patient5 = new Patient + { + Id = 5, + PassportNumber = "P005", + LastName = "Васильев", + FirstName = "Дмитрий", + Patronymic = "Александрович", + Gender = Gender.Male, + BirthDate = new DateOnly(1982, 7, 28), + Address = "г. Казань, ул. Гагарина, д. 12", + BloodGroup = BloodGroup.A, + RhesusFactor = RhesusFactor.Negative, + PhoneNumber = "+79995678901" + }; + clinic.AddPatient(patient5); + + var patient6 = new Patient + { + Id = 6, + PassportNumber = "P006", + LastName = "Николаева", + FirstName = "Елена", + Patronymic = "Игоревна", + Gender = Gender.Female, + BirthDate = new DateOnly(1988, 11, 5), + Address = "г. Нижний Новгород, ул. Лермонтова, д. 30", + BloodGroup = BloodGroup.O, + RhesusFactor = RhesusFactor.Positive, + PhoneNumber = "+79996789012" + }; + clinic.AddPatient(patient6); + + var patient7 = new Patient + { + Id = 7, + PassportNumber = "P007", + LastName = "Морозов", + FirstName = "Сергей", + Patronymic = "Викторович", + Gender = Gender.Male, + BirthDate = new DateOnly(1975, 1, 18), + Address = "г. Самара, ул. Чехова, д. 7", + BloodGroup = BloodGroup.B, + RhesusFactor = RhesusFactor.Positive, + PhoneNumber = "+79997890123" + }; + clinic.AddPatient(patient7); + + var patient8 = new Patient + { + Id = 8, + PassportNumber = "P008", + LastName = "Орлова", + FirstName = "Анна", + Patronymic = "Дмитриевна", + Gender = Gender.Female, + BirthDate = new DateOnly(1992, 9, 22), + Address = "г. Ростов-на-Дону, ул. Кирова, д. 18", + BloodGroup = BloodGroup.AB, + RhesusFactor = RhesusFactor.Negative, + PhoneNumber = "+79998901234" + }; + clinic.AddPatient(patient8); + + var patient9 = new Patient + { + Id = 9, + PassportNumber = "P009", + LastName = "Павлов", + FirstName = "Михаил", + Patronymic = "Олегович", + Gender = Gender.Male, + BirthDate = new DateOnly(1980, 4, 14), + Address = "г. Уфа, ул. Горького, д. 22", + BloodGroup = BloodGroup.O, + RhesusFactor = RhesusFactor.Positive, + PhoneNumber = "+79999012345" + }; + clinic.AddPatient(patient9); + + var patient10 = new Patient + { + Id = 10, + PassportNumber = "P010", + LastName = "Федорова", + FirstName = "Татьяна", + Patronymic = "Николаевна", + Gender = Gender.Female, + BirthDate = new DateOnly(1987, 6, 30), + Address = "г. Красноярск, ул. Ленина, д. 5", + BloodGroup = BloodGroup.A, + RhesusFactor = RhesusFactor.Positive, + PhoneNumber = "+79990123456" + }; + clinic.AddPatient(patient10); + clinic.AddSpecialization("Therapist", new Specialization { Id = 1, Name = "Терапевт" }); + clinic.AddSpecialization("Dentist", new Specialization { Id = 2, Name = "Стоматолог" }); + clinic.AddSpecialization("Cardiologist", new Specialization { Id = 3, Name = "Кардиолог" }); + clinic.AddSpecialization("Neurologist", new Specialization { Id = 4, Name = "Невролог" }); + clinic.AddSpecialization("Pediatrician", new Specialization { Id = 5, Name = "Педиатр" }); + clinic.AddSpecialization("Dermatologist", new Specialization { Id = 6, Name = "Дерматолог" }); + clinic.AddSpecialization("Psychiatrist", new Specialization { Id = 7, Name = "Психиатр" }); + clinic.AddSpecialization("Ophthalmologist", new Specialization { Id = 8, Name = "Офтальмолог" }); + clinic.AddSpecialization("ENTSpecialist", new Specialization { Id = 9, Name = "Отоларинголог" }); + clinic.AddSpecialization("Gynecologist", new Specialization { Id = 10, Name = "Гинеколог" }); + + var doctor1 = new Doctor + { + Id = 1, + PassportNumber = "D001", + LastName = "Смирнов", + FirstName = "Андрей", + Patronymic = "Иванович", + Gender = Gender.Male, + BirthDate = new DateOnly(1970, 3, 18), + Specializations = new List { + clinic.Specializations["Cardiologist"] + }, + ExperienceYears = 15 + }; + clinic.AddDoctor(doctor1); + + var doctor2 = new Doctor + { + Id = 2, + PassportNumber = "D002", + LastName = "Коваленко", + FirstName = "Мария", + Patronymic = "Петровна", + Gender = Gender.Female, + BirthDate = new DateOnly(1985, 6, 12), + Specializations = new List { + clinic.Specializations["Dentist"] + }, + ExperienceYears = 8 + }; + clinic.AddDoctor(doctor2); + + var doctor3 = new Doctor + { + Id = 3, + PassportNumber = "D003", + LastName = "Петров", + FirstName = "Сергей", + Patronymic = "Викторович", + Gender = Gender.Male, + BirthDate = new DateOnly(1978, 9, 25), + Specializations = new List { + clinic.Specializations["Cardiologist"], + clinic.Specializations["Therapist"] + }, + ExperienceYears = 12 + }; + clinic.AddDoctor(doctor3); + + var doctor4 = new Doctor + { + Id = 4, + PassportNumber = "D004", + LastName = "Орлова", + FirstName = "Елена", + Patronymic = "Александровна", + Gender = Gender.Female, + BirthDate = new DateOnly(1982, 11, 7), + Specializations = new List { + clinic.Specializations["Neurologist"], + clinic.Specializations["Psychiatrist"] + }, + ExperienceYears = 10 + }; + clinic.AddDoctor(doctor4); + + var doctor5 = new Doctor + { + Id = 5, + PassportNumber = "D005", + LastName = "Волков", + FirstName = "Дмитрий", + Patronymic = "Сергеевич", + Gender = Gender.Male, + BirthDate = new DateOnly(1990, 2, 14), + Specializations = new List { + clinic.Specializations["Pediatrician"], + clinic.Specializations["Dermatologist"] + }, + ExperienceYears = 5 + }; + clinic.AddDoctor(doctor5); + + var doctor6 = new Doctor + { + Id = 6, + PassportNumber = "D006", + LastName = "Никитина", + FirstName = "Ольга", + Patronymic = "Владимировна", + Gender = Gender.Female, + BirthDate = new DateOnly(1987, 8, 30), + Specializations = new List { + clinic.Specializations["Dermatologist"], + clinic.Specializations["Ophthalmologist"] + }, + ExperienceYears = 7 + }; + clinic.AddDoctor(doctor6); + + var doctor7 = new Doctor + { + Id = 7, + PassportNumber = "D007", + LastName = "Лебедев", + FirstName = "Алексей", + Patronymic = "Игоревич", + Gender = Gender.Male, + BirthDate = new DateOnly(1975, 4, 5), + Specializations = new List { + clinic.Specializations["Psychiatrist"], + clinic.Specializations["Neurologist"] + }, + ExperienceYears = 18 + }; + clinic.AddDoctor(doctor7); + + var doctor8 = new Doctor + { + Id = 8, + PassportNumber = "D008", + LastName = "Соколова", + FirstName = "Ирина", + Patronymic = "Михайловна", + Gender = Gender.Female, + BirthDate = new DateOnly(1980, 10, 19), + Specializations = new List { + clinic.Specializations["Ophthalmologist"] + }, + ExperienceYears = 11 + }; + clinic.AddDoctor(doctor8); + + var doctor9 = new Doctor + { + Id = 9, + PassportNumber = "D009", + LastName = "Козлов", + FirstName = "Михаил", + Patronymic = "Анатольевич", + Gender = Gender.Male, + BirthDate = new DateOnly(1983, 7, 22), + Specializations = new List { + clinic.Specializations["Dentist"] + }, + ExperienceYears = 9 + }; + clinic.AddDoctor(doctor9); + + var doctor10 = new Doctor + { + Id = 10, + PassportNumber = "D010", + LastName = "Григорьева", + FirstName = "Наталья", + Patronymic = "Валерьевна", + Gender = Gender.Female, + BirthDate = new DateOnly(1979, 12, 1), + Specializations = new List { + clinic.Specializations["Gynecologist"], + clinic.Specializations["Pediatrician"] + }, + ExperienceYears = 14 + }; + clinic.AddDoctor(doctor10); + + var appointment1 = new Appointment + { + Patient = patient1, + Doctor = doctor1, + DateTime = new DateTime(2025, 9, 15, 9, 0, 0), + RoomNumber = 101, + IsReturnVisit = false + }; + clinic.MakeAnAppointment(appointment1); + + var appointment2 = new Appointment + { + Patient = patient2, + Doctor = doctor2, + DateTime = new DateTime(2025, 9, 10, 10, 30, 0), + RoomNumber = 205, + IsReturnVisit = true + }; + clinic.MakeAnAppointment(appointment2); + + var appointment3 = new Appointment + { + Patient = patient3, + Doctor = doctor3, + DateTime = new DateTime(2025, 9, 25, 11, 0, 0), + RoomNumber = 102, + IsReturnVisit = false + }; + clinic.MakeAnAppointment(appointment3); + + var appointment4 = new Appointment + { + Patient = patient4, + Doctor = doctor4, + DateTime = new DateTime(2025, 9, 5, 14, 15, 0), + RoomNumber = 303, + IsReturnVisit = false + }; + clinic.MakeAnAppointment(appointment4); + + var appointment5 = new Appointment + { + Patient = patient5, + Doctor = doctor5, + DateTime = new DateTime(2025, 9, 18, 9, 30, 0), + RoomNumber = 303, + IsReturnVisit = true + }; + clinic.MakeAnAppointment(appointment5); + + var appointment6 = new Appointment + { + Patient = patient6, + Doctor = doctor6, + DateTime = new DateTime(2025, 9, 22, 15, 45, 0), + RoomNumber = 206, + IsReturnVisit = false + }; + clinic.MakeAnAppointment(appointment6); + + var appointment7 = new Appointment + { + Patient = patient7, + Doctor = doctor7, + DateTime = new DateTime(2025, 9, 2, 10, 0, 0), + RoomNumber = 303, + IsReturnVisit = true + }; + clinic.MakeAnAppointment(appointment7); + + var appointment8 = new Appointment + { + Patient = patient8, + Doctor = doctor8, + DateTime = new DateTime(2025, 9, 8, 13, 20, 0), + RoomNumber = 207, + IsReturnVisit = false + }; + clinic.MakeAnAppointment(appointment8); + + var appointment9 = new Appointment + { + Patient = patient9, + Doctor = doctor9, + DateTime = new DateTime(2025, 4, 15, 11, 30, 0), + RoomNumber = 208, + IsReturnVisit = false + }; + clinic.MakeAnAppointment(appointment9); + + var appointment10 = new Appointment + { + Patient = patient10, + Doctor = doctor10, + DateTime = new DateTime(2025, 9, 20, 16, 0, 0), + RoomNumber = 105, + IsReturnVisit = true + }; + clinic.MakeAnAppointment(appointment10); + + var appointment11 = new Appointment + { + Patient = patient3, + Doctor = doctor1, + DateTime = new DateTime(2025, 9, 25, 9, 0, 0), + RoomNumber = 101, + IsReturnVisit = true + }; + clinic.MakeAnAppointment(appointment11); + + var appointment12 = new Appointment + { + Patient = patient2, + Doctor = doctor3, + DateTime = new DateTime(2025, 9, 15, 11, 0, 0), + RoomNumber = 102, + IsReturnVisit = true + }; + clinic.MakeAnAppointment(appointment12); + + var appointment13 = new Appointment + { + Patient = patient7, + Doctor = doctor5, + DateTime = new DateTime(2025, 8, 23, 9, 0, 0), + RoomNumber = 101, + IsReturnVisit = true + }; + clinic.MakeAnAppointment(appointment13); } - -} \ No newline at end of file +} diff --git a/Clinic/Clinic.Tests/UnitTest1.cs b/Clinic/Clinic.Tests/UnitTest1.cs index 9bf4588ab..6f505ebdd 100644 --- a/Clinic/Clinic.Tests/UnitTest1.cs +++ b/Clinic/Clinic.Tests/UnitTest1.cs @@ -1,18 +1,27 @@ using System; +using Clinic.Tests.DataSeed; using Clinic.Models; using Clinic.Models.Entities; -using Microsoft.VisualBasic; -namespace Clinic.Tests +namespace Clinic.Tests; + +/// +/// A set of integration tests verifying the business logic of the clinic system. +/// Uses the fixture to initialize test data. +/// All tests validate filtering and aggregation logic for doctors, patients, and appointments. +/// +public class ClinicTest(ClinicDataSeed clinicDataSeed) : IClassFixture { - public class ClinicTests + private readonly ClinicInfo clinic = clinicDataSeed.clinic; + + /// + /// Verifies that only doctors with 10 or more years of experience are returned. + /// Compares the full names from the result with the expected list. + /// + [Fact] + public void GetDoctors_ReturnsDoctorsWith10OrMoreYearsOfExperience() { - public readonly ClinicInfo clinic = new ClinicDataSeed().clinic; - - [Fact] - public void GetDoctors_ReturnsDoctorsWith10OrMoreYearsOfExperience() - { - List DoctorsWith10OrMoreYearsOfExperience = new List{ + var doctorsWith10OrMoreYearsOfExperience = new List { "Смирнов Андрей Иванович", "Петров Сергей Викторович", "Орлова Елена Александровна", @@ -21,102 +30,102 @@ public void GetDoctors_ReturnsDoctorsWith10OrMoreYearsOfExperience() "Григорьева Наталья Валерьевна" }; - var res_doctors = clinic.Doctors.Where(d => d.ExperienceYears >= 10).ToList(); - var resDoctorsWith10OrMoreYearsOfExperience = new List(); - - foreach (var doctor in res_doctors) - { - resDoctorsWith10OrMoreYearsOfExperience.Add(doctor.GetFullName()); - } + var resDoctors = clinic.Doctors + .Where(d => d.ExperienceYears >= 10) + .Select(d => d.GetFullName()) + .ToList(); - Assert.Equal(DoctorsWith10OrMoreYearsOfExperience, resDoctorsWith10OrMoreYearsOfExperience); - } + Assert.Equal(doctorsWith10OrMoreYearsOfExperience, resDoctors); + } - [Fact] - public void GetTargerDoctor_ReturnsPatientsWhoHaveAppointmentWithTargetDoctor() - { - Doctor targetDoctor = clinic.Doctors.First(d => d.LastName == "Смирнов" && d.FirstName == "Андрей"); - List patientsWhoHaveAppointmentWithTargetDocor = [ + /// + /// Ensures that all patients who have appointments with the target doctor (Id = 1) are returned. + /// Patients are selected uniquely and sorted by full name. + /// + [Fact] + public void GetTargerDoctor_ReturnsPatientsWhoHaveAppointmentWithTargetDoctor() + { + var targetDoctor = clinic.Doctors.First(d => d.Id == 1); + var patientsWhoHaveAppointmentWithTargetDocor = new List { "Иванов Иван Иванович", "Сидоров Алексей Петрович" - ]; - - var res_patients = clinic.Appointments - .Where(d => d.Doctor == targetDoctor) - .Select(p => p.Patient) - .Distinct() - .OrderBy(n => n.LastName) - .ThenBy(n => n.FirstName) - .ThenBy(n => n.Patronymic) - .ToList(); + }; - var result = new List(); + var resPatients = clinic.Appointments + .Where(d => d.Doctor == targetDoctor) + .Select(p => p.Patient) + .Distinct() + .Select(p => p.GetFullName()) + .OrderBy(n => n) + .ToList(); - foreach (var patient in res_patients) - { - result.Add(patient.GetFullName()); - } - - Assert.Equal(patientsWhoHaveAppointmentWithTargetDocor, result); - } + Assert.Equal(patientsWhoHaveAppointmentWithTargetDocor, resPatients); + } - [Fact] - public void GetAppointmentInTheLastMonths_ReturnsRepeatedAppointment() - { - var CountRepeatedAppointments = 6; + /// + /// Counts the number of return visits (IsReturnVisit = true) in the last month + /// relative to September 4, 2025. Expected count: 7. + /// + [Fact] + public void GetAppointmentInTheLastMonths_ReturnsRepeatedAppointment() + { + var countRepeatedAppointments = 7; - DateTime now = DateTime.Now; - DateTime lastMonth = now.AddMonths(-1); + var currentDate = new DateTime(2025, 9, 4); + var lastMonth = currentDate.AddMonths(-1); - var resCountRepeatedAppointments = clinic.Appointments - .Where(a => a.DateTime >= lastMonth && a.IsReturnVisit) - .Count(); + var resCount = clinic.Appointments + .Where(a => a.DateTime >= lastMonth && a.IsReturnVisit) + .Count(); - Assert.Equal(CountRepeatedAppointments, resCountRepeatedAppointments); - } + Assert.Equal(countRepeatedAppointments, resCount); + } - [Fact] - public void GetPationsOver30_ReturnsPationsWhoHaveAppointmentWithSeveralDoctors() - { - var PationsWhoHaveAppointmentWithSeveralDoctors = new List + /// + /// Finds patients over 30 years old (as of November 4, 2025) who have appointments + /// with multiple doctors. Results are ordered by birth date (oldest first). + /// + [Fact] + public void GetPationsOver30_ReturnsPationsWhoHaveAppointmentWithSeveralDoctors() + { + var pationsWhoHaveAppointmentWithSeveralDoctors = new List { "Морозов Сергей Викторович", "Сидоров Алексей Петрович", "Петрова Мария Сергеевна" }; - var currentDate = DateTime.Now; - var age30 = currentDate.AddYears(-30); + var currentDate = new DateOnly(2025, 11, 4); + var age30 = currentDate.AddYears(-30); - var patients = clinic.Patients - .Where(a => a.BirthDate <= age30) - .Where(p => clinic.Appointments.Count(a => a.Patient.PassportNumber == p.PassportNumber) > 1) - .OrderBy(d => d.BirthDate) - .ToList(); + var resPatients = clinic.Patients + .Where(a => a.BirthDate <= age30) + .Where(p => clinic.Appointments.Count(a => a.Patient.PassportNumber == p.PassportNumber) > 1) + .OrderBy(d => d.BirthDate) + .Select(p => p.GetFullName()) + .ToList(); - var resPationsWhoHaveAppointmentWithSeveralDoctors = new List(); - foreach (var patient in patients) - { - resPationsWhoHaveAppointmentWithSeveralDoctors.Add(patient.GetFullName()); - } - Assert.Equal(PationsWhoHaveAppointmentWithSeveralDoctors, resPationsWhoHaveAppointmentWithSeveralDoctors); - } - [Fact] - public void GetTargetRoom_ReturnsCountAppointmentsInTheLastMonthThatTookPlaceInTargetRoom() - { - var CountAppointmentsInTheLastMonthThatTookPlaceInTargetRoom = 3; - - var targetRoom = 303; - DateTime now = DateTime.Now; - DateTime lastMonth = now.AddMonths(-1); - - - int resCountAppointmentsInTheLastMonthThatTookPlaceInTargetRoom = clinic.Appointments - .Where(a => a.RoomNumber == targetRoom && - a.DateTime >= lastMonth) - .Count(); - - Assert.Equal(CountAppointmentsInTheLastMonthThatTookPlaceInTargetRoom, resCountAppointmentsInTheLastMonthThatTookPlaceInTargetRoom); - } + Assert.Equal(pationsWhoHaveAppointmentWithSeveralDoctors, resPatients); + } + + /// + /// Counts the number of appointments in room 303 during the last month + /// relative to September 4, 2025.. Expected count: 3. + /// + [Fact] + public void GetTargetRoom_ReturnsCountAppointmentsInTheLastMonthThatTookPlaceInTargetRoom() + { + var countAppointmentsInTheLastMonthThatTookPlaceInTargetRoom = 3; + + var targetRoom = 303; + var currentDate = new DateTime(2025, 9, 4); + var lastMonth = currentDate.AddMonths(-1); + + var resCount = clinic.Appointments + .Where(a => a.RoomNumber == targetRoom && + a.DateTime >= lastMonth) + .Count(); + + Assert.Equal(countAppointmentsInTheLastMonthThatTookPlaceInTargetRoom, resCount); } } From b7a94a77752c2cc45c43294efac1e556c12ad675 Mon Sep 17 00:00:00 2001 From: maeosha Date: Mon, 10 Nov 2025 15:16:31 +0400 Subject: [PATCH 15/56] fix: delete old Contracts/Appointment --- Clinic/Clinic.Models/Contracts/Appointment.cs | 15 --------------- 1 file changed, 15 deletions(-) delete mode 100644 Clinic/Clinic.Models/Contracts/Appointment.cs diff --git a/Clinic/Clinic.Models/Contracts/Appointment.cs b/Clinic/Clinic.Models/Contracts/Appointment.cs deleted file mode 100644 index a5c689f12..000000000 --- a/Clinic/Clinic.Models/Contracts/Appointment.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Clinic.Models.Entities; - -namespace Clinic.Models.Contracts -{ - public class Appointment - { - public required Patient Patient { get; set; } - public required Doctor Doctor { get; set; } - public required DateTime DateTime { get; set; } - public required int RoomNumber { get; set; } - public required bool IsReturnVisit { get; set; } - - - } -} \ No newline at end of file From e2c2f53487fb70e8703ca17ac9a3da4d0a4e08cf Mon Sep 17 00:00:00 2001 From: maeosha Date: Mon, 10 Nov 2025 15:21:05 +0400 Subject: [PATCH 16/56] fix: delete SpecialiSation.cs --- Clinic/Clinic.Models/ReferenceBook/Specialisation.cs | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100644 Clinic/Clinic.Models/ReferenceBook/Specialisation.cs diff --git a/Clinic/Clinic.Models/ReferenceBook/Specialisation.cs b/Clinic/Clinic.Models/ReferenceBook/Specialisation.cs deleted file mode 100644 index 5d4a7f943..000000000 --- a/Clinic/Clinic.Models/ReferenceBook/Specialisation.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System.Collections.Generic; - -namespace Clinic.Models.ReferenceBooks -{ - public class Specialisation - { - public required int Id { get; set; } - public required string Name { get; set; } - } -} \ No newline at end of file From 5027d2ecf5552d623fa8109cf378f1ddf136ab62 Mon Sep 17 00:00:00 2001 From: maeosha Date: Wed, 26 Nov 2025 18:14:08 +0300 Subject: [PATCH 17/56] feat: implement Patient management functionality with CRUD operations and DTOs --- Clinic.Api/Controllers/PatientControllers.cs | 101 +++++++++++++++++ Clinic.Api/DTOs/Patient/CreatePatientDto.cs | 21 ++++ Clinic.Api/DTOs/Patient/GetPatientDto.cs | 19 ++++ Clinic.Api/DTOs/Patient/UpdatePatientDto.cs | 19 ++++ Clinic.Api/Services/PatientServices.cs | 111 +++++++++++++++++++ Clinic.Models/Entities/Patient.cs | 25 +++++ 6 files changed, 296 insertions(+) create mode 100644 Clinic.Api/Controllers/PatientControllers.cs create mode 100644 Clinic.Api/DTOs/Patient/CreatePatientDto.cs create mode 100644 Clinic.Api/DTOs/Patient/GetPatientDto.cs create mode 100644 Clinic.Api/DTOs/Patient/UpdatePatientDto.cs create mode 100644 Clinic.Api/Services/PatientServices.cs create mode 100644 Clinic.Models/Entities/Patient.cs diff --git a/Clinic.Api/Controllers/PatientControllers.cs b/Clinic.Api/Controllers/PatientControllers.cs new file mode 100644 index 000000000..fc2bc7178 --- /dev/null +++ b/Clinic.Api/Controllers/PatientControllers.cs @@ -0,0 +1,101 @@ +using Microsoft.AspNetCore.Mvc; +using Clinic.Api.Services; +using Clinic.Api.DTOs.PatientDto; + +namespace Clinic.Api.Controllers; + +/// +/// Controller for managing patient operations such as retrieving, +/// creating, updating, and deleting patient records. +/// +[ApiController] +[Route("api/patients")] +public class PatientControllers : ControllerBase +{ + private readonly PatientServices _patientServices; + + /// + /// Constructor for PatientControllers. + /// + /// The service to manage patient data. + public PatientControllers(PatientServices patientServices) + { + _patientServices = patientServices; + } + + /// + /// Retrieves all patients from the database. + /// + /// A list of all patients. + [HttpGet("GetAllPatients")] + public IActionResult GetAll() + { + var patients = _patientServices.GetAllPatients(); + return Ok(patients); + } + + /// + /// Creates a new patient record. + /// + /// The patient data to create. + /// The created patient or a BadRequest if creation fails. + [HttpPost("CreatePatient")] + public IActionResult Create(CreatePatientDto dto) + { + var result = _patientServices.CreatePatient(dto); + if (result == null) + { + return BadRequest("Could not create patient."); + } + return Ok(result); + } + + /// + /// Retrieves a patient by their unique ID. + /// + /// The ID of the patient to retrieve. + /// The patient record or NotFound if the patient does not exist. + [HttpGet("GetPatient/{id}")] + public IActionResult Get(int id) + { + var patient = _patientServices.GetPatient(id); + if (patient == null) + { + return NotFound("Patient not found."); + } + return Ok(patient); + } + + /// + /// Updates an existing patient record. + /// + /// The ID of the patient to update. + /// The updated patient data. + /// The updated patient or NotFound if the patient does not exist. + [HttpPut("UpdatePatient/{id}")] + public IActionResult Update(int id, UpdatePatientDto dto) + { + var result = _patientServices.UpdatePatient(id, dto); + if (result == null) + { + return NotFound("Patient not found."); + } + return Ok(result); + } + + /// + /// Deletes a patient by their unique ID. + /// + /// The ID of the patient to delete. + /// Ok if deletion was successful; NotFound otherwise. + [HttpDelete("DeletePatient/{id}")] + public IActionResult Delete(int id) + { + var result = _patientServices.DeletePatient(id); + if (!result) + { + return NotFound(); + } + return Ok("Patient deleted successfully."); + } +} \ No newline at end of file diff --git a/Clinic.Api/DTOs/Patient/CreatePatientDto.cs b/Clinic.Api/DTOs/Patient/CreatePatientDto.cs new file mode 100644 index 000000000..0637ed0fa --- /dev/null +++ b/Clinic.Api/DTOs/Patient/CreatePatientDto.cs @@ -0,0 +1,21 @@ +using System.ComponentModel.DataAnnotations; + +namespace Clinic.Api.DTOs.PatientDto; + +/// +/// DTO for creating a new patient, including required personal information, +/// medical history, and contact details. +/// +public class CreatePatientDto +{ + [Required]public string FirstName { get; set; } = null!; + [Required]public string LastName { get; set; } = null!; + public string? Patronymic { get; set; } + [Required]public string PassportNumber { get; set; } = null!; + [Required]public DateOnly BirthDate { get; set; } + [Required]public String Gender { get; set; } = null!; + [Required]public string Address { get; set; } = null!; + [Required]public string PhoneNumber { get; set; } = null!; + [Required]public String BloodGroup { get; set; } = null!; + [Required]public String RhesusFactor { get; set; } = null!; +} \ No newline at end of file diff --git a/Clinic.Api/DTOs/Patient/GetPatientDto.cs b/Clinic.Api/DTOs/Patient/GetPatientDto.cs new file mode 100644 index 000000000..ccc6eaa30 --- /dev/null +++ b/Clinic.Api/DTOs/Patient/GetPatientDto.cs @@ -0,0 +1,19 @@ +using Clinic.Models.Enums; + +namespace Clinic.Api.DTOs.PatientDto; + +/// +/// DTO for retrieving detailed information about a patient, +/// including personal details, medical history, and contact details. +/// +public class GetPatientDto +{ + public int Id { get; set; } + public string FirstName { get; set; } = null!; + public string LastName { get; set; } = null!; + public string? Patronymic { get; set; } + public DateOnly BirthDate { get; set; } + public String Gender { get; set; } = null!; + public String BloodGroup { get; set; } = null!; + public String RhesusFactor { get; set; } = null!; +} \ No newline at end of file diff --git a/Clinic.Api/DTOs/Patient/UpdatePatientDto.cs b/Clinic.Api/DTOs/Patient/UpdatePatientDto.cs new file mode 100644 index 000000000..75076868f --- /dev/null +++ b/Clinic.Api/DTOs/Patient/UpdatePatientDto.cs @@ -0,0 +1,19 @@ +using Clinic.Models.Enums; + +namespace Clinic.Api.DTOs.PatientDto; + +/// +/// DTO for updating an existing patient, including optional personal information, +/// medical history, and contact details. +/// +public class UpdatePatientDto +{ + public string? FirstName { get; set; } = null; + public string? LastName { get; set; } = null; + public string? Patronymic { get; set; } = null; + public DateTime? DateOfBirth { get; set; } = null; + public string? Address { get; set; } = null; + public string? PhoneNumber { get; set; } = null; + public BloodGroup? BloodGroup { get; set; } = null; + public RhesusFactor? RhesusFactor { get; set; } = null; +} \ No newline at end of file diff --git a/Clinic.Api/Services/PatientServices.cs b/Clinic.Api/Services/PatientServices.cs new file mode 100644 index 000000000..cc6d29a98 --- /dev/null +++ b/Clinic.Api/Services/PatientServices.cs @@ -0,0 +1,111 @@ +using AutoMapper; +using Clinic.Api.DataBase; +using Clinic.Api.DTOs.PatientDto; +using Clinic.Models.Entities; + +namespace Clinic.Api.Services; + +/// +/// Service class for managing patient-related operations in the Clinic API. +/// Provides methods for creating, retrieving, updating, and deleting patients, +/// as well as listing all patients. Uses AutoMapper for entity-DTO mapping. +/// +public class PatientServices +{ + private readonly IClinicDataBase _db; + private readonly IMapper _mapper; + private int _patientId; + + /// + /// Initializes a new instance of the class. + /// Sets the initial patient identifier based on the count in the database. + /// + /// The database service for patient operations. + /// The AutoMapper instance for mapping objects. + public PatientServices(IClinicDataBase db, IMapper mapper) + { + _db = db; + _mapper = mapper; + _patientId = _db.PatientCount() + 1; + } + + /// + /// Retrieves all patients from the database and maps them to DTOs. + /// + /// A collection of representing all patients. + public IReadOnlyCollection GetAllPatients() + { + var patients = _db.GetAllPatients(); + var patientsDto = _mapper.Map>(patients); + return patientsDto; + } + + /// + /// Creates a new patient entity in the database. + /// + /// The DTO containing patient creation data. + /// The created patient as a if successful; otherwise, null. + public GetPatientDto? CreatePatient(CreatePatientDto patientCreateDto) + { + var patient = _mapper.Map(patientCreateDto); + patient.Id = _patientId; + if (!_db.AddPatient(patient)) + { + return null; + } + _patientId++; + var patientGetDto = _mapper.Map(patient); + return patientGetDto; + } + + /// + /// Updates an existing patient with the given id using the provided update DTO. + /// + /// The identifier of the patient to update. + /// The DTO containing updated patient data. + /// The updated patient as a if successful; otherwise, null. + public GetPatientDto? UpdatePatient(int id, UpdatePatientDto patientUpdateDto) + { + var patient = _db.GetPatient(id); + if (patient == null) + { + return null; + } + _mapper.Map(patientUpdateDto, patient); + _db.UpdatePatient(patient); + + var patientGetDto = _mapper.Map(patient); + return patientGetDto; + } + + /// + /// Retrieves a patient with the specified id. + /// + /// The identifier of the patient to retrieve. + /// The patient as a if found; otherwise, null. + public GetPatientDto? GetPatient(int id) + { + var patient = _db.GetPatient(id); + if (patient == null) + { + return null; + } + var patientGetDto = _mapper.Map(patient); + return patientGetDto; + } + + /// + /// Deletes the patient with the given id. + /// + /// The identifier of the patient to delete. + /// True if the patient was deleted; otherwise, false. + public bool DeletePatient(int id) + { + if (!_db.RemovePatient(id)) + { + return false; + } + _patientId--; + return true; + } +} \ No newline at end of file diff --git a/Clinic.Models/Entities/Patient.cs b/Clinic.Models/Entities/Patient.cs new file mode 100644 index 000000000..59cb254f0 --- /dev/null +++ b/Clinic.Models/Entities/Patient.cs @@ -0,0 +1,25 @@ +using Clinic.Models.Common; +using Clinic.Models.Enums; + +namespace Clinic.Models.Entities; + +/// +/// Represents a patient entity with personal and medical information. +/// +public class Patient : PersonInfo +{ + /// + /// Gets or sets the patient's address. + /// + required public string Address { get; set; } + + /// + /// Gets or sets the patient's blood group. + /// + required public BloodGroup BloodGroup { get; set; } + + /// + /// Gets or sets the patient's rhesus factor. + /// + required public RhesusFactor RhesusFactor { get; set; } +} From 9fa5d71c64fcdffd1bee83e28f86b8b45aaa74b2 Mon Sep 17 00:00:00 2001 From: maeosha Date: Wed, 26 Nov 2025 18:18:35 +0300 Subject: [PATCH 18/56] feat: add Specialization management functionality with CRUD operations and DTOs --- .../Controllers/SpecializationControllers.cs | 84 +++++++++++++++++ .../Specialization/CreateSpecializationDto.cs | 11 +++ .../Specialization/GetSpecializationDto.cs | 11 +++ Clinic.Api/Services/SpecializationServices.cs | 89 +++++++++++++++++++ Clinic.Models/Entities/Specialization.cs | 19 ++++ 5 files changed, 214 insertions(+) create mode 100644 Clinic.Api/Controllers/SpecializationControllers.cs create mode 100644 Clinic.Api/DTOs/Specialization/CreateSpecializationDto.cs create mode 100644 Clinic.Api/DTOs/Specialization/GetSpecializationDto.cs create mode 100644 Clinic.Api/Services/SpecializationServices.cs create mode 100644 Clinic.Models/Entities/Specialization.cs diff --git a/Clinic.Api/Controllers/SpecializationControllers.cs b/Clinic.Api/Controllers/SpecializationControllers.cs new file mode 100644 index 000000000..fe8ff95f2 --- /dev/null +++ b/Clinic.Api/Controllers/SpecializationControllers.cs @@ -0,0 +1,84 @@ +using Microsoft.AspNetCore.Mvc; +using Clinic.Api.Services; +using Clinic.Api.DTOs.SpecializationDto; + +namespace Clinic.Api.Controllers; + +/// +/// Controller for managing specialization operations such as retrieving, +/// creating, and deleting specializations. +/// +[ApiController] +[Route("api/specializations")] +public class SpecializationControllers : ControllerBase +{ + private readonly SpecializationServices _specializationServices; + + /// + /// Initializes a new instance of . + /// + /// Service for specialization operations. + public SpecializationControllers(SpecializationServices specializationServices) + { + _specializationServices = specializationServices; + } + + /// + /// Retrieves all specializations. + /// + /// A list of all specializations. + [HttpGet("GetAllSpecializations")] + public IActionResult GetAll() + { + var specializations = _specializationServices.GetAllSpecializations(); + return Ok(specializations); + } + + /// + /// Creates a new specialization. + /// + /// The details of the specialization to create. + /// The created specialization or a BadRequest if it already exists. + [HttpPost("CreateSpecialization")] + public IActionResult Create(CreateSpecializationDto createSpecializationDto) + { + var result = _specializationServices.CreateSpecialization(createSpecializationDto); + if (result == null) + { + return BadRequest("The specialization already exists."); + } + return Ok(result); + } + + /// + /// Gets a specialization by its id. + /// + /// The id of the specialization to retrieve. + /// The specialization if found, otherwise NotFound. + [HttpGet("GetSpecialization/{id}")] + public IActionResult Get(int id) + { + var specialization = _specializationServices.GetSpecialization(id); + if (specialization == null) + { + return NotFound("Specialization not found."); + } + return Ok(specialization); + } + + /// + /// Deletes a specialization by its id. + /// + /// The id of the specialization to delete. + /// A confirmation message or NotFound if the specialization does not exist. + [HttpDelete("DeleteSpecialization/{id}")] + public IActionResult Delete(int id) + { + var result = _specializationServices.DeleteSpecialization(id); + if (!result) + { + return NotFound("Specialization not found."); + } + return Ok("Specialization deleted successfully."); + } +} diff --git a/Clinic.Api/DTOs/Specialization/CreateSpecializationDto.cs b/Clinic.Api/DTOs/Specialization/CreateSpecializationDto.cs new file mode 100644 index 000000000..f0802dada --- /dev/null +++ b/Clinic.Api/DTOs/Specialization/CreateSpecializationDto.cs @@ -0,0 +1,11 @@ +using System.ComponentModel.DataAnnotations; + +namespace Clinic.Api.DTOs.SpecializationDto; + +/// +/// DTO for creating a new specialization, including required name. +/// +public class CreateSpecializationDto +{ + [Required]public string Name { get; set; } = null!; +} \ No newline at end of file diff --git a/Clinic.Api/DTOs/Specialization/GetSpecializationDto.cs b/Clinic.Api/DTOs/Specialization/GetSpecializationDto.cs new file mode 100644 index 000000000..e83a885a4 --- /dev/null +++ b/Clinic.Api/DTOs/Specialization/GetSpecializationDto.cs @@ -0,0 +1,11 @@ +namespace Clinic.Api.DTOs.SpecializationDto; + +/// +/// DTO for retrieving detailed information about a specialization, +/// including its unique identifier and name. +/// +public class GetSpecializationDto +{ + public int Id { get; set; } + public string Name { get; set; } = null!; +} \ No newline at end of file diff --git a/Clinic.Api/Services/SpecializationServices.cs b/Clinic.Api/Services/SpecializationServices.cs new file mode 100644 index 000000000..d8a6b14a1 --- /dev/null +++ b/Clinic.Api/Services/SpecializationServices.cs @@ -0,0 +1,89 @@ +using AutoMapper; +using Clinic.Models.Entities; +using Clinic.Api.DataBase; +using Clinic.Api.DTOs.SpecializationDto; + +namespace Clinic.Api.Services; + +/// +/// Service class for managing specialization-related operations in the Clinic API. +/// Provides methods to create, retrieve, and delete specializations, and maps entity objects to DTOs. +/// +public class SpecializationServices +{ + private readonly IClinicDataBase _db; + private readonly IMapper _mapper; + private int specializationId; + + /// + /// Initializes a new instance of the class. + /// Sets the initial specialization identifier based on the count in the database. + /// + /// The database service for specialization operations. + /// The AutoMapper instance used for object mapping. + public SpecializationServices(IClinicDataBase db, IMapper mapper) + { + _db = db; + _mapper = mapper; + specializationId = _db.SpecializationCount() + 1; + } + + /// + /// Retrieves all specializations from the database and maps them to DTOs. + /// + /// A collection of representing specializations. + public IReadOnlyCollection GetAllSpecializations() + { + var specializations = _db.GetAllSpecializations(); + var specializationDtos = _mapper.Map>(specializations); + return specializationDtos; + } + + /// + /// Creates a new specialization entity in the database. + /// + /// The DTO containing specialization creation data. + /// The created specialization as a if successful; otherwise, null. + public GetSpecializationDto? CreateSpecialization(CreateSpecializationDto createSpecializationDto) + { + var specialization = _mapper.Map(createSpecializationDto); + specialization.Id = specializationId; + + if (!_db.AddSpecialization(specialization)) + { + return null; + } + + var specializationDto = _mapper.Map(specialization); + specializationDto.Id = specializationId; + + specializationId++; + + return specializationDto; + } + + /// + /// Retrieves a single specialization by ID. + /// + /// The specialization identifier. + /// A if found; otherwise, null. + public GetSpecializationDto? GetSpecialization(int id) + { + var specialization = _db.GetSpecialization(id); + if (specialization == null) + { + return null; + } + return _mapper.Map(specialization); + } + + /// + /// Deletes a specialization by ID. + /// + /// The specialization identifier. + /// True if the specialization was deleted; otherwise, false. + public bool DeleteSpecialization(int id) + { + return _db.RemoveSpecialization(id); + } +} \ No newline at end of file diff --git a/Clinic.Models/Entities/Specialization.cs b/Clinic.Models/Entities/Specialization.cs new file mode 100644 index 000000000..35483633a --- /dev/null +++ b/Clinic.Models/Entities/Specialization.cs @@ -0,0 +1,19 @@ +using System.Dynamic; + +namespace Clinic.Models.Entities; + +/// +/// Represents a medical specialisation in the clinic. +/// +public class Specialization +{ + /// + /// Gets or sets the id specialization. + /// + required public int Id { get; set; } + + /// + /// Gets or sets the specialization name. + /// + required public string Name { get; set; } +} From 2c79b5350fb2dbe45cc5fc334c4a52d963322448 Mon Sep 17 00:00:00 2001 From: maeosha Date: Wed, 26 Nov 2025 18:19:22 +0300 Subject: [PATCH 19/56] feat: implement Doctor management functionality with CRUD operations and DTOs --- Clinic.Api/Controllers/DoctorControllers.cs | 97 +++++++++++++++++ Clinic.Api/DTOs/Doctor/CreateDoctorDto.cs | 21 ++++ Clinic.Api/DTOs/Doctor/GetDoctorDto.cs | 19 ++++ Clinic.Api/DTOs/Doctor/UpdateDoctorDto.cs | 20 ++++ Clinic.Api/Services/DoctorServices.cs | 110 ++++++++++++++++++++ Clinic.Models/Common/PersonInfo.cs | 62 +++++++++++ Clinic.Models/Entities/Doctor.cs | 19 ++++ 7 files changed, 348 insertions(+) create mode 100644 Clinic.Api/Controllers/DoctorControllers.cs create mode 100644 Clinic.Api/DTOs/Doctor/CreateDoctorDto.cs create mode 100644 Clinic.Api/DTOs/Doctor/GetDoctorDto.cs create mode 100644 Clinic.Api/DTOs/Doctor/UpdateDoctorDto.cs create mode 100644 Clinic.Api/Services/DoctorServices.cs create mode 100644 Clinic.Models/Common/PersonInfo.cs create mode 100644 Clinic.Models/Entities/Doctor.cs diff --git a/Clinic.Api/Controllers/DoctorControllers.cs b/Clinic.Api/Controllers/DoctorControllers.cs new file mode 100644 index 000000000..492f83db4 --- /dev/null +++ b/Clinic.Api/Controllers/DoctorControllers.cs @@ -0,0 +1,97 @@ +using Microsoft.AspNetCore.Mvc; +using Clinic.Api.Services; +using Clinic.Api.DTOs.DoctorDto; + +namespace Clinic.Api.Controllers; + +/// +/// Controller for handling doctor-related operations such as retrieving, +/// creating, updating, and deleting doctors in the clinic. +/// +[ApiController] +[Route("api/doctors")] +public class DoctorControllers : ControllerBase +{ + private readonly DoctorServices _doctorServices; + + /// + /// Initializes a new instance of . + /// + /// Service for doctor operations. + public DoctorControllers(DoctorServices doctorServices) + { + _doctorServices = doctorServices; + } + + /// + /// Gets all doctors. + /// + [HttpGet("GetAllDoctors")] + public IActionResult GetAll() + { + var doctors = _doctorServices.GetAllDoctors(); + return Ok(doctors); + } + + /// + /// Creates a new doctor. + /// + /// The doctor creation data. + [HttpPost("AddDoctor")] + public IActionResult Create([FromBody] CreateDoctorDto createDoctorDto) + { + var doctor = _doctorServices.CreateDoctor(createDoctorDto); + if (doctor == null) + { + return BadRequest("The doctor already exists."); + } + return Ok(doctor); + } + + /// + /// Gets details of a specific doctor by their id. + /// + /// The id of the doctor. + [HttpGet("GetDoctor/{id}")] + public IActionResult Get(int id) + { + var doctor = _doctorServices.GetDoctor(id); + if (doctor == null) + { + return NotFound("Doctor not found."); + } + return Ok(doctor); + } + + /// + /// Updates an existing doctor's information. + /// + /// The id of the doctor to update. + /// The update information. + [HttpPut("UpdateDoctor/{id}")] + public IActionResult Update(int id, UpdateDoctorDto updateDoctorDto) + { + var doctor = _doctorServices.UpdateDoctor(id, updateDoctorDto); + if (doctor == null) + { + return NotFound("Doctor not found."); + } + return Ok(doctor); + } + + /// + /// Deletes a doctor. + /// + /// Id of the doctor to delete. + [HttpDelete("DeleteDoctor/{id}")] + public IActionResult Delete(int id) + { + var result = _doctorServices.DeleteDoctor(id); + if (!result) + { + return NotFound("Doctor not found."); + } + return Ok("Doctor deleted successfully."); + } + +} \ No newline at end of file diff --git a/Clinic.Api/DTOs/Doctor/CreateDoctorDto.cs b/Clinic.Api/DTOs/Doctor/CreateDoctorDto.cs new file mode 100644 index 000000000..e3c9b80a9 --- /dev/null +++ b/Clinic.Api/DTOs/Doctor/CreateDoctorDto.cs @@ -0,0 +1,21 @@ +using System.ComponentModel.DataAnnotations; +using Clinic.Models.Entities; + +namespace Clinic.Api.DTOs.DoctorDto; + +/// +/// DTO for creating a new doctor, including required personal information, +/// specialization list, and experience years. +/// +public class CreateDoctorDto +{ + [Required]public string PassportNumber { get; set; } = null!; + [Required]public DateOnly BirthDate { get; set; } + [Required]public string LastName { get; set; } = null!; + [Required]public string FirstName { get; set; } = null!; + [Required]public string PhoneNumber { get; set; } = null!; + public string? Patronymic { get; set; } + [Required]public String Gender { get; set; } = null!; + [Required]public List Specializations { get; set; } = null!; + [Required]public int ExperienceYears { get; set; } +} diff --git a/Clinic.Api/DTOs/Doctor/GetDoctorDto.cs b/Clinic.Api/DTOs/Doctor/GetDoctorDto.cs new file mode 100644 index 000000000..88d98e2ee --- /dev/null +++ b/Clinic.Api/DTOs/Doctor/GetDoctorDto.cs @@ -0,0 +1,19 @@ +using Clinic.Models.Entities; +namespace Clinic.Api.DTOs.DoctorDto; + +/// +/// DTO for retrieving detailed information about a doctor, +/// including personal details, specialization list, and experience years. +/// +public class GetDoctorDto +{ + public int Id { get; set; } + public string PassportNumber { get; set; } = string.Empty; + public DateOnly BirthDate { get; set; } + public string LastName { get; set; } = string.Empty; + public string FirstName { get; set; } = string.Empty; + public string? Patronymic { get; set; } + public String Gender { get; set; } = null!; + public List Specializations { get; set; } = new(); + public int ExperienceYears { get; set; } +} \ No newline at end of file diff --git a/Clinic.Api/DTOs/Doctor/UpdateDoctorDto.cs b/Clinic.Api/DTOs/Doctor/UpdateDoctorDto.cs new file mode 100644 index 000000000..703a44bc3 --- /dev/null +++ b/Clinic.Api/DTOs/Doctor/UpdateDoctorDto.cs @@ -0,0 +1,20 @@ +using Clinic.Models.Entities; +using Clinic.Models.Enums; + +namespace Clinic.Api.DTOs.DoctorDto; + +/// +/// DTO for updating an existing doctor, including optional personal information, +/// specialization list, and experience years. +/// +public class UpdateDoctorDto +{ + public string? PassportNumber { get; set; } + public DateOnly? BirthDate { get; set; } + public string? LastName { get; set; } + public string? FirstName { get; set; } + public string? Patronymic { get; set; } + public Gender? Gender { get; set; } + public List? Specializations { get; set; } = null; + public int? ExperienceYears { get; set; } +} \ No newline at end of file diff --git a/Clinic.Api/Services/DoctorServices.cs b/Clinic.Api/Services/DoctorServices.cs new file mode 100644 index 000000000..76a403325 --- /dev/null +++ b/Clinic.Api/Services/DoctorServices.cs @@ -0,0 +1,110 @@ +using AutoMapper; +using Clinic.Api.DataBase; +using Clinic.Api.DTOs.DoctorDto; +using Clinic.Models.Entities; + +namespace Clinic.Api.Services; + +/// +/// Service class for managing doctor-related operations within the Clinic API. +/// Provides methods to create, retrieve, update, and delete doctors, +/// as well as functions to get all doctors from the underlying database. +/// Uses AutoMapper for mapping between entity and DTO objects. +/// +public class DoctorServices +{ + private readonly IClinicDataBase _db; + private readonly IMapper _mapper; + private int doctorId; + + /// + /// Initializes a new instance of the class. + /// Sets the initial doctor identifier based on the count in the database. + /// + /// The database service for doctor operations. + /// The AutoMapper instance used for object mapping. + public DoctorServices(IClinicDataBase db, IMapper mapper) + { + _db = db; + _mapper = mapper; + doctorId = _db.DoctorCount() + 1; + } + + /// + /// Retrieves all doctors from the database and maps them to DTOs. + /// + /// A collection of representing doctors. + public IReadOnlyCollection GetAllDoctors() + { + var doctors = _db.GetAllDoctors(); + var doctorsDto = _mapper.Map>(doctors); + return doctorsDto; + } + + /// + /// Creates a new doctor entity in the database. + /// + /// The DTO containing doctor creation data. + /// The created doctor as a if successful; otherwise, null. + public GetDoctorDto? CreateDoctor(CreateDoctorDto createDoctorDto) + { + var doctor = _mapper.Map(createDoctorDto); + doctor.Id = doctorId; + if (!_db.AddDoctor(doctor)) + { + return null; + } + return _mapper.Map(doctor); + } + + /// + /// Retrieves a single doctor by ID. + /// + /// The doctor identifier. + /// A if found; otherwise, null. + public GetDoctorDto? GetDoctor(int id) + { + var doctor = _db.GetDoctor(id); + if (doctor == null) + { + return null; + } + var doctorGetDto = _mapper.Map(doctor); + return doctorGetDto; + } + + /// + /// Updates an existing doctor's information. + /// + /// The doctor identifier. + /// DTO with updated doctor details. + /// The updated doctor as a if successful; otherwise, null. + public GetDoctorDto? UpdateDoctor(int id, UpdateDoctorDto updateDoctorDto) + { + var doctor = _db.GetDoctor(id); + if (doctor == null) + { + return null; + } + _mapper.Map(updateDoctorDto, doctor); + _db.UpdateDoctor(doctor); + + var doctorGetDto = _mapper.Map(doctor); + return doctorGetDto; + } + + /// + /// Deletes a doctor from the database by ID. + /// + /// The doctor identifier to delete. + /// True if the doctor was successfully deleted; otherwise, false. + public bool DeleteDoctor(int id) + { + if (!_db.RemoveDoctor(id)) + { + return false; + } + doctorId--; + return true; + } +} \ No newline at end of file diff --git a/Clinic.Models/Common/PersonInfo.cs b/Clinic.Models/Common/PersonInfo.cs new file mode 100644 index 000000000..6731f8178 --- /dev/null +++ b/Clinic.Models/Common/PersonInfo.cs @@ -0,0 +1,62 @@ +using Clinic.Models.Enums; + +namespace Clinic.Models.Common; + +/// +/// Represents personal information for a person. +/// +public class PersonInfo +{ + /// + /// Gets or sets the unique identifier. + /// + required public int Id { get; set; } + + /// + /// Gets or sets the passport number. + /// + required public string PassportNumber { get; set; } + + /// + /// Gets or sets the year of birth. + /// + required public DateOnly BirthDate { get; set; } + + /// + /// Gets or sets the last name. + /// + required public string LastName { get; set; } + + /// + /// Gets or sets the first name. + /// + required public string FirstName { get; set; } + + /// + /// Gets or sets the patronymic. + /// + public string? Patronymic { get; set; } + + /// + /// Gets or sets the patient's phone number. + /// + required public string PhoneNumber { get; set; } + + /// + /// Gets or sets the gender. + /// + required public Gender Gender { get; set; } + + /// + /// Gets the full name composed of last name, first name and optional patronymic. + /// + /// The full name as a single string. + public string GetFullName() + { + if (string.IsNullOrEmpty(Patronymic)) + { + return $"{LastName} {FirstName}"; + } + return $"{LastName} {FirstName} {Patronymic}"; + } +} \ No newline at end of file diff --git a/Clinic.Models/Entities/Doctor.cs b/Clinic.Models/Entities/Doctor.cs new file mode 100644 index 000000000..ef5481a63 --- /dev/null +++ b/Clinic.Models/Entities/Doctor.cs @@ -0,0 +1,19 @@ +using Clinic.Models.Common; + +namespace Clinic.Models.Entities; + +/// +/// Represents a doctor with personal and professional details. +/// +public class Doctor : PersonInfo +{ + /// + /// Gets or sets the list of medical specializations the doctor holds. + /// + required public List Specializations { get; set; } + + /// + /// Gets or sets the number of years of experience the doctor has. + /// + required public int ExperienceYears { get; set; } +} From 8223f2ffa0db20b9d8822c2676865ed44792eeb2 Mon Sep 17 00:00:00 2001 From: maeosha Date: Wed, 26 Nov 2025 18:19:59 +0300 Subject: [PATCH 20/56] feat: implement Appointment management functionality with CRUD operations and DTOs --- .../Controllers/AppointmentControllers.cs | 126 ++++++++++++++++++ .../DTOs/Appointment/CreateAppointmentDto.cs | 40 ++++++ .../DTOs/Appointment/GetAppointmentDto.cs | 18 +++ .../DTOs/Appointment/UpdateAppointment.cs | 14 ++ Clinic.Api/Services/AppointmentServices.cs | 107 +++++++++++++++ Clinic.Models/Entities/Appointment.cs | 47 +++++++ 6 files changed, 352 insertions(+) create mode 100644 Clinic.Api/Controllers/AppointmentControllers.cs create mode 100644 Clinic.Api/DTOs/Appointment/CreateAppointmentDto.cs create mode 100644 Clinic.Api/DTOs/Appointment/GetAppointmentDto.cs create mode 100644 Clinic.Api/DTOs/Appointment/UpdateAppointment.cs create mode 100644 Clinic.Api/Services/AppointmentServices.cs create mode 100644 Clinic.Models/Entities/Appointment.cs diff --git a/Clinic.Api/Controllers/AppointmentControllers.cs b/Clinic.Api/Controllers/AppointmentControllers.cs new file mode 100644 index 000000000..8cb0152c4 --- /dev/null +++ b/Clinic.Api/Controllers/AppointmentControllers.cs @@ -0,0 +1,126 @@ +using Microsoft.AspNetCore.Mvc; +using Clinic.Api.Services; +using Clinic.Api.DTOs.Appointment; + +namespace Clinic.Api.Controllers; + +[ApiController] +[Route("api/appointments")] +/// +/// Controller for handling appointment-related operations such as retrieving, creating, updating, +/// and deleting appointments in the clinic. +/// +public class AppointmentControllers : ControllerBase +{ + private readonly AppointmentServices _appointmentServices; + + /// + /// Initializes a new instance of . + /// + /// Service for appointment operations. + public AppointmentControllers(AppointmentServices appointmentServices) + { + _appointmentServices = appointmentServices; + } + + /// + /// Gets all appointments. + /// + [HttpGet("GetAllAppointments")] + public IActionResult GetAll() + { + var appointments = _appointmentServices.GetAllAppointments(); + return Ok(appointments); + } + + /// + /// Gets details of a specific appointment by its id. + /// + /// The id of the appointment. + [HttpGet("GetAppointment/{id}")] + public IActionResult Get(int id) + { + var appointment = _appointmentServices.GetAppointment(id); + if (appointment == null) + { + return NotFound("Appointment not found."); + } + return Ok(appointment); + } + + /// + /// Gets all appointments for a specific doctor. + /// + /// The doctor's id. + [HttpGet("GetAppointmentsByDoctor/{doctorId}")] + public IActionResult GetByDoctor(int doctorId) + { + var appointments = _appointmentServices.GetAppointmentsByDoctor(doctorId); + if (appointments == null) + { + return NotFound("Doctor not found."); + } + return Ok(appointments); + } + + /// + /// Gets all appointments for a specific patient. + /// + /// The patient's id. + [HttpGet("GetAppointmentsByPatient/{patientId}")] + public IActionResult GetByPatient(int patientId) + { + var appointments = _appointmentServices.GetAppointmentsByPatient(patientId); + if (appointments == null) + { + return NotFound("Patient not found."); + } + return Ok(appointments); + } + + /// + /// Creates a new appointment. + /// + /// The appointment creation information. + [HttpPost("CreateAppointment")] + public IActionResult Create([FromBody] CreateAppointmentDto dto) + { + var appointment = _appointmentServices.CreateAppointment(dto); + if (appointment == null) + { + return BadRequest("Could not create appointment (doctor or patient may not exist)."); + } + return Ok(appointment); + } + + /// + /// Updates an existing appointment. + /// + /// The id of the appointment to update. + /// The update information. + [HttpPut("UpdateAppointment/{id}")] + public IActionResult Update(int id, [FromBody] UpdateAppointmentDto dto) + { + var appointment = _appointmentServices.UpdateAppointment(id, dto); + if (appointment == null) + { + return NotFound("Appointment not found."); + } + return Ok(appointment); + } + + /// + /// Deletes an appointment. + /// + /// Id of the appointment to delete. + [HttpDelete("DeleteAppointment/{id}")] + public IActionResult Delete(int id) + { + var result = _appointmentServices.DeleteAppointment(id); + if (!result) + { + return NotFound("Appointment not found."); + } + return Ok("Appointment deleted successfully."); + } +} diff --git a/Clinic.Api/DTOs/Appointment/CreateAppointmentDto.cs b/Clinic.Api/DTOs/Appointment/CreateAppointmentDto.cs new file mode 100644 index 000000000..64066ed7e --- /dev/null +++ b/Clinic.Api/DTOs/Appointment/CreateAppointmentDto.cs @@ -0,0 +1,40 @@ +using System.ComponentModel.DataAnnotations; + +namespace Clinic.Api.DTOs.Appointment; + +/// +/// DTO for creating a new appointment, including required patient/doctor information, +/// scheduled date/time, room number, and return visit flag. +/// +public class CreateAppointmentDto +{ + /// + /// The full name of the patient for the appointment. + /// + [Required] + public string PatientFullName { get; set; } = null!; + + /// + /// The full name of the doctor for the appointment. + /// + [Required] + public string DoctorFullName { get; set; } = null!; + + /// + /// The date and time of the appointment. + /// + [Required] + public DateTime DateTime { get; set; } + + /// + /// The room number where the appointment will take place. + /// + [Required] + public int RoomNumber { get; set; } + + /// + /// Indicates whether the appointment is a return visit. + /// + [Required] + public bool IsReturnVisit { get; set; } +} diff --git a/Clinic.Api/DTOs/Appointment/GetAppointmentDto.cs b/Clinic.Api/DTOs/Appointment/GetAppointmentDto.cs new file mode 100644 index 000000000..f98f4a88e --- /dev/null +++ b/Clinic.Api/DTOs/Appointment/GetAppointmentDto.cs @@ -0,0 +1,18 @@ +namespace Clinic.Api.DTOs.Appointment; + +/// +/// DTO for retrieving detailed information about an appointment, +/// including patient and doctor IDs/names, appointment date, room number, and return visit flag. +/// + +public class GetAppointmentDto +{ + public int Id { get; set; } + public int PatientId { get; set; } + public int DoctorId { get; set; } + public string PatientFullName { get; set; } = null!; + public string DoctorFullName { get; set; } = null!; + public DateOnly DateTime { get; set; } + public int RoomNumber { get; set; } + public bool IsReturnVisit { get; set; } +} diff --git a/Clinic.Api/DTOs/Appointment/UpdateAppointment.cs b/Clinic.Api/DTOs/Appointment/UpdateAppointment.cs new file mode 100644 index 000000000..3c3985d81 --- /dev/null +++ b/Clinic.Api/DTOs/Appointment/UpdateAppointment.cs @@ -0,0 +1,14 @@ +namespace Clinic.Api.DTOs.Appointment; + +/// +/// DTO for updating an existing appointment, including optional patient/doctor information, +/// scheduled date/time, room number, and return visit flag. +/// +public class UpdateAppointmentDto +{ + public int? PatientId { get; set; } = null; + public int? DoctorId { get; set; } = null; + public DateTime? DateTime { get; set; } = null; + public int? RoomNumber { get; set; } = null; + public bool? IsReturnVisit { get; set; } = null; +} diff --git a/Clinic.Api/Services/AppointmentServices.cs b/Clinic.Api/Services/AppointmentServices.cs new file mode 100644 index 000000000..52498dd26 --- /dev/null +++ b/Clinic.Api/Services/AppointmentServices.cs @@ -0,0 +1,107 @@ +using AutoMapper; +using Clinic.Api.DataBase; +using Clinic.Api.DTOs.Appointment; +using Clinic.Models.Entities; + +namespace Clinic.Api.Services; + +/// +/// Service layer for managing appointments in the clinic. +/// Provides methods for creating, updating, retrieving, and deleting appointments. +/// Handles mapping between DTOs and entity models, and interacts with the appointment database. +/// +public class AppointmentServices +{ + private readonly IClinicDataBase _db; + private readonly IMapper _mapper; + private int _appointmentId; + + /// + /// Initializes a new instance of the class. + /// + /// The clinic database interface. + /// The AutoMapper interface for DTO and entity mapping. + public AppointmentServices(IClinicDataBase db, IMapper mapper) + { + _db = db; + _mapper = mapper; + _appointmentId = _db.AppointmentCount() + 1; + } + + /// + /// Retrieves all appointments from the database. + /// + /// A read-only collection of appointment DTOs. + public IReadOnlyCollection GetAllAppointments() + { + var appointments = _db.GetAllAppointments(); + return _mapper.Map>(appointments); + } + + public IReadOnlyCollection? GetAppointmentsByDoctor(int doctorId) + { + var doctor = _db.GetDoctor(doctorId); + if (doctor == null) + { + return null; + } + var appointments = _db.GetAppointmentsByDoctor(doctorId); + return _mapper.Map>(appointments); + } + + public IReadOnlyCollection? GetAppointmentsByPatient(int patientId) + { + var patient = _db.GetPatient(patientId); + if (patient == null) + { + return null; + } + var appointments = _db.GetAppointmentsByPatient(patientId); + return _mapper.Map>(appointments); + } + + public GetAppointmentDto? GetAppointment(int id) + { + var appointment = _db.GetAppointment(id); + return appointment == null ? null : _mapper.Map(appointment); + } + + public GetAppointmentDto? CreateAppointment(CreateAppointmentDto dto) + { + var appointment = _mapper.Map(dto); + appointment.Id = _appointmentId; + + if (!_db.AddAppointment(appointment)) + { + return null; + } + + _appointmentId++; + return _mapper.Map(appointment); + } + + public GetAppointmentDto? UpdateAppointment(int id, UpdateAppointmentDto dto) + { + var appointment = _db.GetAppointment(id); + if (appointment == null) + { + return null; + } + + _mapper.Map(dto, appointment); + _db.UpdateAppointment(appointment); + + return _mapper.Map(appointment); + } + + public bool DeleteAppointment(int id) + { + if (!_db.RemoveAppointment(id)) + { + return false; + } + + _appointmentId--; + return true; + } +} diff --git a/Clinic.Models/Entities/Appointment.cs b/Clinic.Models/Entities/Appointment.cs new file mode 100644 index 000000000..343fbb282 --- /dev/null +++ b/Clinic.Models/Entities/Appointment.cs @@ -0,0 +1,47 @@ +namespace Clinic.Models.Entities; + +/// +/// Represents an appointment in the clinic, including patient, doctor, date/time, room, and visit type. +/// +public class Appointment +{ + /// + /// Gets or sets the unique identifier for the appointment. + /// + required public int Id { get; set; } + + /// + /// Gets or sets the patient identifier for the appointment. + /// + required public int PatientId { get; set; } + + /// + /// Gets or sets the full name of the patient at the time of appointment. + /// + required public string PatientFullName { get; set; } = null!; + + /// + /// Gets or sets the doctor identifier for the appointment. + /// + required public int DoctorId { get; set; } + + /// + /// Gets or sets the full name of the doctor at the time of appointment. + /// + required public string DoctorFullName { get; set; } = null!; + + /// + /// Gets or sets the date and time of the appointment. + /// + required public DateTime DateTime { get; set; } + + /// + /// Gets or sets the room number for the appointment. + /// + required public int RoomNumber { get; set; } + + /// + /// Gets or sets a value indicating whether this appointment is a return visit. + /// + required public bool IsReturnVisit { get; set; } +} From 3f5f1aa397774b50d967ae44bc40503c37fc87e6 Mon Sep 17 00:00:00 2001 From: maeosha Date: Wed, 26 Nov 2025 18:20:16 +0300 Subject: [PATCH 21/56] feat: implement ClinicDataBase with CRUD operations for patients, doctors, specializations, and appointments --- Clinic.Api/DataBase/ClinicDataBase.cs | 149 +++++++++++++++++++++ Clinic.Api/DataBase/IClinicDataBase.cs | 177 +++++++++++++++++++++++++ 2 files changed, 326 insertions(+) create mode 100644 Clinic.Api/DataBase/ClinicDataBase.cs create mode 100644 Clinic.Api/DataBase/IClinicDataBase.cs diff --git a/Clinic.Api/DataBase/ClinicDataBase.cs b/Clinic.Api/DataBase/ClinicDataBase.cs new file mode 100644 index 000000000..91e924d9a --- /dev/null +++ b/Clinic.Api/DataBase/ClinicDataBase.cs @@ -0,0 +1,149 @@ +using Clinic.Api.DataSeed; +using Clinic.Models.Entities; + +namespace Clinic.Api.DataBase; +public sealed class ClinicDataBase : IClinicDataBase +{ + /// + /// In-memory storage for patients, doctors, and appointments. + /// + private readonly Dictionary _patients = new(); + private readonly Dictionary _doctors = new(); + private readonly Dictionary _appointments = new(); + private readonly Dictionary _specializations = new(); + + public ClinicDataBase() + { + var dataSeed = new DataSeed.DataSeed(); + dataSeed.TestDataSeed(this); + } + + public Patient? GetPatient(int Id) => _patients.GetValueOrDefault(Id); + + public IReadOnlyCollection GetAllPatients() => _patients.Values; + public bool AddPatient(Patient patient){ + if (_patients.ContainsValue(patient)){ + return false; + } + + _patients[patient.Id] = patient; + return true; + } + + public bool UpdatePatient(Patient patient){ + if (!_patients.ContainsKey(patient.Id)){ + return false; + } + _patients[patient.Id] = patient; + return true; + } + + public bool RemovePatient(int Id) => _patients.Remove(Id); + + public int PatientCount() => _patients.Count; + + + public IReadOnlyCollection GetAllSpecializations() => _specializations.Values; + + public bool AddSpecialization(Specialization specialization) + { + if (_specializations.ContainsValue(specialization)){ + return false; + } + _specializations[specialization.Id] = specialization; + return true; + } + + public bool RemoveSpecialization(int id) => _specializations.Remove(id); + public Specialization? GetSpecialization(int id) + { + if (!_specializations.ContainsKey(id)) + { + return null; + } + return _specializations[id]; + } + + public int SpecializationCount() => _specializations.Count; + + + public Doctor? GetDoctor(int Id) => _doctors.GetValueOrDefault(Id); + + public IReadOnlyCollection GetAllDoctors() => _doctors.Values; + + public bool AddDoctor(Doctor doctor){ + if (_doctors.ContainsKey(doctor.Id)){ + return false; + } + _doctors[doctor.Id] = doctor; + return true; + } + + public bool UpdateDoctor(Doctor doctor){ + if (!_doctors.ContainsKey(doctor.Id)){ + return false; + } + _doctors[doctor.Id] = doctor; + return true; + } + + public bool RemoveDoctor(int Id) => _doctors.Remove(Id); + + public int DoctorCount() => _doctors.Count(); + + + public Appointment? GetAppointment(int Id) => _appointments.GetValueOrDefault(Id); + + public IReadOnlyCollection GetAllAppointments() => _appointments.Values; + + public IReadOnlyCollection GetAppointmentsByDoctor(int Id) => + _appointments.Values.Where(a => a.DoctorId == Id).ToList(); + + public IReadOnlyCollection GetAppointmentsByPatient(int Id) => + _appointments.Values.Where(a => a.PatientId == Id).ToList(); + + public bool AddAppointment(Appointment appointment){ + if (_appointments.ContainsKey(appointment.Id)){ + return false; + } + + var patient = _patients.GetValueOrDefault(appointment.PatientId); + var doctor = _doctors.GetValueOrDefault(appointment.DoctorId); + + if (patient == null || doctor == null){ + return false; + } + + _appointments[appointment.Id] = appointment; + return true; + } + + public bool UpdateAppointment(Appointment appointment){ + if (!_appointments.ContainsKey(appointment.Id)){ + return false; + } + + if (appointment.PatientId == 0 || appointment.DoctorId == 0){ + return false; + } + + var patient = _patients.GetValueOrDefault(appointment.PatientId); + + if (patient.GetFullName() != appointment.PatientFullName){ + appointment.PatientFullName = patient.GetFullName(); + } + + var doctor = _doctors.GetValueOrDefault(appointment.DoctorId); + + if (doctor.GetFullName() != appointment.DoctorFullName){ + appointment.DoctorFullName = doctor.GetFullName(); + } + + _appointments[appointment.Id] = appointment; + return true; + } + + public bool RemoveAppointment(int Id) => _appointments.Remove(Id); + + public int AppointmentCount() => _appointments.Count(); +} diff --git a/Clinic.Api/DataBase/IClinicDataBase.cs b/Clinic.Api/DataBase/IClinicDataBase.cs new file mode 100644 index 000000000..6db878faf --- /dev/null +++ b/Clinic.Api/DataBase/IClinicDataBase.cs @@ -0,0 +1,177 @@ +using Clinic.Models.Entities; + +namespace Clinic.Api.DataBase; + +/// +/// Interface for a clinic database, providing CRUD operations and queries +/// for patients, doctors, specializations, and appointments. +/// +public interface IClinicDataBase +{ + /// + /// Retrieves a patient by their unique identifier. + /// + /// The patient's ID. + /// The patient if found; otherwise, null. + Patient? GetPatient(int Id); + + /// + /// Gets all patients in the database. + /// + /// A read-only collection of all patients. + IReadOnlyCollection GetAllPatients(); + + /// + /// Adds a new patient to the database. + /// + /// The patient to add. + /// True if added successfully; otherwise, false. + bool AddPatient(Patient patient); + + /// + /// Updates an existing patient's details. + /// + /// The patient with updated information. + /// True if updated successfully; otherwise, false. + bool UpdatePatient(Patient patient); + + /// + /// Removes a patient by their ID. + /// + /// The patient's ID to remove. + /// True if removed successfully; otherwise, false. + bool RemovePatient(int Id); + + /// + /// Gets the total number of patients. + /// + /// The patient count. + int PatientCount(); + + /// + /// Retrieves all specializations in the system. + /// + /// A read-only collection of all specializations. + IReadOnlyCollection GetAllSpecializations(); + + /// + /// Adds a new specialization. + /// + /// The specialization to add. + /// True if added; false if already exists. + bool AddSpecialization(Specialization specialization); + + /// + /// Retrieves a specialization by its unique identifier. + /// + /// The specialization's ID. + /// The specialization if found; otherwise, null. + Specialization? GetSpecialization(int id); + + /// + /// Removes a specialization by its ID. + /// + /// The specialization's ID. + /// True if removed; otherwise, false. + bool RemoveSpecialization(int id); + + /// + /// Gets the total specialization count. + /// + /// The number of specializations. + int SpecializationCount(); + + /// + /// Retrieves a doctor by their unique identifier. + /// + /// The doctor's ID. + /// The doctor if found; otherwise, null. + Doctor? GetDoctor(int Id); + + /// + /// Gets all doctors. + /// + /// A read-only collection of all doctors. + IReadOnlyCollection GetAllDoctors(); + + /// + /// Adds a new doctor. + /// + /// The doctor to add. + /// True if added; otherwise, false. + bool AddDoctor(Doctor doctor); + + /// + /// Updates details for an existing doctor. + /// + /// The doctor with updated details. + /// True if updated; otherwise, false. + bool UpdateDoctor(Doctor doctor); + + /// + /// Removes a doctor by their ID. + /// + /// The doctor's ID. + /// True if removed; otherwise, false. + bool RemoveDoctor(int Id); + + /// + /// Gets the total doctor count. + /// + /// The number of doctors. + int DoctorCount(); + + /// + /// Retrieves an appointment by its unique identifier. + /// + /// The appointment's ID. + /// The appointment if found; otherwise, null. + Appointment? GetAppointment(int Id); + + /// + /// Gets all appointments. + /// + /// A read-only collection of all appointments. + IReadOnlyCollection GetAllAppointments(); + + /// + /// Gets all appointments for a specific doctor. + /// + /// The doctor's ID. + /// A collection of appointments for the doctor. + IReadOnlyCollection GetAppointmentsByDoctor(int Id); + + /// + /// Gets all appointments for a specific patient. + /// + /// The patient's ID. + /// A collection of appointments for the patient. + IReadOnlyCollection GetAppointmentsByPatient(int Id); + + /// + /// Adds a new appointment. + /// + /// The appointment to add. + /// True if added; otherwise, false. + bool AddAppointment(Appointment appointment); + + /// + /// Updates an existing appointment. + /// + /// The appointment with updated details. + /// True if updated; otherwise, false. + bool UpdateAppointment(Appointment appointment); + + /// + /// Removes an appointment by its ID. + /// + /// The appointment's ID. + /// True if removed; otherwise, false. + bool RemoveAppointment(int Id); + + /// + /// Gets the total appointment count. + /// + /// The number of appointments. + int AppointmentCount(); +} \ No newline at end of file From 4f9e9a740979ac1db57415f163675d73620ff553 Mon Sep 17 00:00:00 2001 From: maeosha Date: Wed, 26 Nov 2025 18:20:38 +0300 Subject: [PATCH 22/56] feat: add DataSeed class to populate the database with test data for patients, doctors, specializations, and appointments --- Clinic.Api/DataSeed/DataSeed.cs | 504 ++++++++++++++++++++++++++++++++ 1 file changed, 504 insertions(+) create mode 100644 Clinic.Api/DataSeed/DataSeed.cs diff --git a/Clinic.Api/DataSeed/DataSeed.cs b/Clinic.Api/DataSeed/DataSeed.cs new file mode 100644 index 000000000..e1a2919a9 --- /dev/null +++ b/Clinic.Api/DataSeed/DataSeed.cs @@ -0,0 +1,504 @@ +using Clinic.Api.DataBase; +using Clinic.Models.Entities; +using Clinic.Models.Enums; + +namespace Clinic.Api.DataSeed; + +public class DataSeed +{ + public void TestDataSeed(IClinicDataBase _db) + { + var patient1 = new Patient + { + Id = 1, + PassportNumber = "P001", + LastName = "Иванов", + FirstName = "Иван", + Patronymic = "Иванович", + BloodGroup = BloodGroup.A, + RhesusFactor = RhesusFactor.Positive, + Address = "г. Москва, ул. Ленина, д. 10", + Gender = Gender.Male, + BirthDate = new DateOnly(1985, 5, 20), + PhoneNumber = "+79991234567" + }; + _db.AddPatient(patient1); + + var patient2 = new Patient + { + Id = 2, + PassportNumber = "P002", + LastName = "Петрова", + FirstName = "Мария", + Patronymic = "Сергеевна", + BloodGroup = BloodGroup.B, + RhesusFactor = RhesusFactor.Positive, + Address = "г. Санкт-Петербург, ул. Пушкина, д. 25", + Gender = Gender.Female, + BirthDate = new DateOnly(1990, 8, 15), + PhoneNumber = "+79992345678" + }; + _db.AddPatient(patient2); + + var patient3 = new Patient + { + Id = 3, + PassportNumber = "P003", + LastName = "Сидоров", + FirstName = "Алексей", + Patronymic = "Петрович", + BloodGroup = BloodGroup.O, + RhesusFactor = RhesusFactor.Negative, + Address = "г. Екатеринбург, ул. Мира, д. 15", + Gender = Gender.Male, + BirthDate = new DateOnly(1978, 12, 3), + PhoneNumber = "+79993456789" + }; + _db.AddPatient(patient3); + + var patient4 = new Patient + { + Id = 4, + PassportNumber = "P004", + LastName = "Кузнецова", + FirstName = "Ольга", + Patronymic = "Владимировна", + BloodGroup = BloodGroup.AB, + RhesusFactor = RhesusFactor.Positive, + Address = "г. Новосибирск, ул. Советская, д. 8", + Gender = Gender.Female, + BirthDate = new DateOnly(1995, 3, 10), + PhoneNumber = "+79994567890" + }; + _db.AddPatient(patient4); + + var patient5 = new Patient + { + Id = 5, + PassportNumber = "P005", + LastName = "Васильев", + FirstName = "Дмитрий", + Patronymic = "Александрович", + BloodGroup = BloodGroup.A, + RhesusFactor = RhesusFactor.Negative, + Address = "г. Казань, ул. Гагарина, д. 12", + Gender = Gender.Male, + BirthDate = new DateOnly(1982, 7, 28), + PhoneNumber = "+79995678901" + }; + _db.AddPatient(patient5); + + var patient6 = new Patient + { + Id = 6, + PassportNumber = "P006", + LastName = "Николаева", + FirstName = "Елена", + Patronymic = "Игоревна", + BloodGroup = BloodGroup.O, + RhesusFactor = RhesusFactor.Positive, + Address = "г. Нижний Новгород, ул. Лермонтова, д. 30", + Gender = Gender.Female, + BirthDate = new DateOnly(1988, 11, 5), + PhoneNumber = "+79996789012" + }; + _db.AddPatient(patient6); + + var patient7 = new Patient + { + Id = 7, + PassportNumber = "P007", + LastName = "Морозов", + FirstName = "Сергей", + Patronymic = "Викторович", + BloodGroup = BloodGroup.B, + RhesusFactor = RhesusFactor.Positive, + Address = "г. Самара, ул. Чехова, д. 7", + Gender = Gender.Male, + BirthDate = new DateOnly(1975, 1, 18), + PhoneNumber = "+79997890123" + }; + _db.AddPatient(patient7); + + var patient8 = new Patient + { + Id = 8, + PassportNumber = "P008", + LastName = "Орлова", + FirstName = "Анна", + Patronymic = "Дмитриевна", + BloodGroup = BloodGroup.AB, + RhesusFactor = RhesusFactor.Negative, + Address = "г. Ростов-на-Дону, ул. Кирова, д. 18", + Gender = Gender.Female, + BirthDate = new DateOnly(1992, 9, 22), + PhoneNumber = "+79998901234" + }; + _db.AddPatient(patient8); + + var patient9 = new Patient + { + Id = 9, + PassportNumber = "P009", + LastName = "Павлов", + FirstName = "Михаил", + Patronymic = "Олегович", + BloodGroup = BloodGroup.O, + RhesusFactor = RhesusFactor.Positive, + Address = "г. Уфа, ул. Горького, д. 22", + Gender = Gender.Male, + BirthDate = new DateOnly(1980, 4, 14), + PhoneNumber = "+79999012345" + }; + _db.AddPatient(patient9); + + var patient10 = new Patient + { + Id = 10, + PassportNumber = "P010", + LastName = "Федорова", + FirstName = "Татьяна", + Patronymic = "Николаевна", + BloodGroup = BloodGroup.A, + RhesusFactor = RhesusFactor.Positive, + Address = "г. Красноярск, ул. Ленина, д. 5", + Gender = Gender.Female, + BirthDate = new DateOnly(1987, 6, 30), + PhoneNumber = "+79990123456" + }; + _db.AddPatient(patient10); + + var specialization1 = new Specialization { Id = 1, Name = "Терапевт" }; + _db.AddSpecialization(specialization1); + + var specialization2 = new Specialization { Id = 2, Name = "Стоматолог" }; + _db.AddSpecialization(specialization2); + + var specialization3 = new Specialization { Id = 3, Name = "Кардиолог" }; + _db.AddSpecialization(specialization3); + + var specialization4 = new Specialization { Id = 4, Name = "Невролог" }; + _db.AddSpecialization(specialization4); + + var specialization5 = new Specialization { Id = 5, Name = "Педиатр" }; + _db.AddSpecialization(specialization5); + + var specialization6 = new Specialization { Id = 6, Name = "Дерматолог" }; + _db.AddSpecialization(specialization6); + + var specialization7 = new Specialization { Id = 7, Name = "Психиатр" }; + _db.AddSpecialization(specialization7); + + var specialization8 = new Specialization { Id = 8, Name = "Офтальмолог" }; + _db.AddSpecialization(specialization8); + + var specialization9 = new Specialization { Id = 9, Name = "Гинеколог" }; + _db.AddSpecialization(specialization9); + + var doctor1 = new Doctor + { + Id = 1, + PassportNumber = "D001", + LastName = "Смирнов", + FirstName = "Андрей", + Patronymic = "Николаевич", + Gender = Gender.Male, + BirthDate = new DateOnly(1980, 1, 15), + Specializations = new List { specialization1, specialization3 }, + ExperienceYears = 10, + PhoneNumber = "+79876543210" + }; + _db.AddDoctor(doctor1); + + var doctor2 = new Doctor + { + Id = 2, + PassportNumber = "D002", + LastName = "Егорова", + FirstName = "Екатерина", + Patronymic = "Александровна", + Gender = Gender.Female, + BirthDate = new DateOnly(1985, 4, 20), + Specializations = new List { specialization2, specialization6 }, + ExperienceYears = 8, + PhoneNumber = "+79876543211" + }; + _db.AddDoctor(doctor2); + + var doctor3 = new Doctor + { + Id = 3, + PassportNumber = "D003", + LastName = "Петров", + FirstName = "Дмитрий", + Patronymic = "Сергеевич", + Gender = Gender.Male, + BirthDate = new DateOnly(1978, 7, 12), + Specializations = new List { specialization4, specialization7 }, + ExperienceYears = 15, + PhoneNumber = "+79876543212" + }; + _db.AddDoctor(doctor3); + + var doctor4 = new Doctor + { + Id = 4, + PassportNumber = "D004", + LastName = "Соколова", + FirstName = "Ольга", + Patronymic = "Викторовна", + Gender = Gender.Female, + BirthDate = new DateOnly(1982, 11, 5), + Specializations = new List { specialization1, specialization5 }, + ExperienceYears = 12, + PhoneNumber = "+79876543213" + }; + _db.AddDoctor(doctor4); + + var doctor5 = new Doctor + { + Id = 5, + PassportNumber = "D005", + LastName = "Кузнецов", + FirstName = "Михаил", + Patronymic = "Андреевич", + Gender = Gender.Male, + BirthDate = new DateOnly(1990, 3, 25), + Specializations = new List { specialization3, specialization8 }, + ExperienceYears = 6, + PhoneNumber = "+79876543214" + }; + _db.AddDoctor(doctor5); + + var doctor6 = new Doctor + { + Id = 6, + PassportNumber = "D006", + LastName = "Иванова", + FirstName = "Анна", + Patronymic = "Дмитриевна", + Gender = Gender.Female, + BirthDate = new DateOnly(1987, 9, 18), + Specializations = new List { specialization2, specialization9 }, + ExperienceYears = 9, + PhoneNumber = "+79876543215" + }; + _db.AddDoctor(doctor6); + + var doctor7 = new Doctor + { + Id = 7, + PassportNumber = "D007", + LastName = "Морозов", + FirstName = "Сергей", + Patronymic = "Владимирович", + Gender = Gender.Male, + BirthDate = new DateOnly(1975, 12, 8), + Specializations = new List { specialization4, specialization9 }, + ExperienceYears = 20, + PhoneNumber = "+79876543216" + }; + _db.AddDoctor(doctor7); + + var doctor8 = new Doctor + { + Id = 8, + PassportNumber = "D008", + LastName = "Федорова", + FirstName = "Татьяна", + Patronymic = "Игоревна", + Gender = Gender.Female, + BirthDate = new DateOnly(1983, 6, 30), + Specializations = new List { specialization5, specialization7 }, + ExperienceYears = 11, + PhoneNumber = "+79876543217" + }; + _db.AddDoctor(doctor8); + + var doctor9 = new Doctor + { + Id = 9, + PassportNumber = "D009", + LastName = "Никитин", + FirstName = "Алексей", + Patronymic = "Павлович", + Gender = Gender.Male, + BirthDate = new DateOnly(1988, 2, 14), + Specializations = new List { specialization6, specialization8 }, + ExperienceYears = 7, + PhoneNumber = "+79876543218" + }; + _db.AddDoctor(doctor9); + + var doctor10 = new Doctor + { + Id = 10, + PassportNumber = "D010", + LastName = "Орлова", + FirstName = "Марина", + Patronymic = "Сергеевна", + Gender = Gender.Female, + BirthDate = new DateOnly(1981, 8, 22), + Specializations = new List { specialization1, specialization2, specialization3 }, + ExperienceYears = 14, + PhoneNumber = "+79876543219" + }; + _db.AddDoctor(doctor10); + + var appointment1 = new Appointment + { + Id = 1, + PatientId = patient1.Id, + PatientFullName = patient1.GetFullName(), + DoctorId = doctor1.Id, + DoctorFullName = doctor1.GetFullName(), + DateTime = new DateTime(2025, 1, 10, 9, 0, 0), + RoomNumber = 101, + IsReturnVisit = false + }; + _db.AddAppointment(appointment1); + + var appointment2 = new Appointment + { + Id = 2, + PatientId = patient2.Id, + PatientFullName = patient2.GetFullName(), + DoctorId = doctor2.Id, + DoctorFullName = doctor2.GetFullName(), + DateTime = new DateTime(2025, 1, 10, 9, 30, 0), + RoomNumber = 102, + IsReturnVisit = false + }; + _db.AddAppointment(appointment2); + + var appointment3 = new Appointment + { + Id = 3, + PatientId = patient3.Id, + PatientFullName = patient3.GetFullName(), + DoctorId = doctor3.Id, + DoctorFullName = doctor3.GetFullName(), + DateTime = new DateTime(2025, 1, 10, 10, 0, 0), + RoomNumber = 103, + IsReturnVisit = true + }; + _db.AddAppointment(appointment3); + + var appointment4 = new Appointment + { + Id = 4, + PatientId = patient4.Id, + PatientFullName = patient4.GetFullName(), + DoctorId = doctor4.Id, + DoctorFullName = doctor4.GetFullName(), + DateTime = new DateTime(2025, 1, 10, 10, 30, 0), + RoomNumber = 104, + IsReturnVisit = false + }; + _db.AddAppointment(appointment4); + + var appointment5 = new Appointment + { + Id = 5, + PatientId = patient5.Id, + PatientFullName = patient5.GetFullName(), + DoctorId = doctor5.Id, + DoctorFullName = doctor5.GetFullName(), + DateTime = new DateTime(2025, 1, 10, 11, 0, 0), + RoomNumber = 105, + IsReturnVisit = true + }; + _db.AddAppointment(appointment5); + + var appointment6 = new Appointment + { + Id = 6, + PatientId = patient6.Id, + PatientFullName = patient6.GetFullName(), + DoctorId = doctor6.Id, + DoctorFullName = doctor6.GetFullName(), + DateTime = new DateTime(2025, 1, 10, 11, 30, 0), + RoomNumber = 201, + IsReturnVisit = false + }; + _db.AddAppointment(appointment6); + + var appointment7 = new Appointment + { + Id = 7, + PatientId = patient7.Id, + PatientFullName = patient7.GetFullName(), + DoctorId = doctor7.Id, + DoctorFullName = doctor7.GetFullName(), + DateTime = new DateTime(2025, 1, 10, 12, 0, 0), + RoomNumber = 202, + IsReturnVisit = true + }; + _db.AddAppointment(appointment7); + + var appointment8 = new Appointment + { + Id = 8, + PatientId = patient8.Id, + PatientFullName = patient8.GetFullName(), + DoctorId = doctor8.Id, + DoctorFullName = doctor8.GetFullName(), + DateTime = new DateTime(2025, 1, 10, 12, 30, 0), + RoomNumber = 203, + IsReturnVisit = false + }; + _db.AddAppointment(appointment8); + + var appointment9 = new Appointment + { + Id = 9, + PatientId = patient9.Id, + PatientFullName = patient9.GetFullName(), + DoctorId = doctor9.Id, + DoctorFullName = doctor9.GetFullName(), + DateTime = new DateTime(2025, 1, 10, 13, 0, 0), + RoomNumber = 204, + IsReturnVisit = false + }; + _db.AddAppointment(appointment9); + + var appointment10 = new Appointment + { + Id = 10, + PatientId = patient10.Id, + PatientFullName = patient10.GetFullName(), + DoctorId = doctor10.Id, + DoctorFullName = doctor10.GetFullName(), + DateTime = new DateTime(2025, 1, 10, 13, 30, 0), + RoomNumber = 205, + IsReturnVisit = true + }; + _db.AddAppointment(appointment10); + + var appointment11 = new Appointment + { + Id = 11, + PatientId = patient1.Id, + PatientFullName = patient1.GetFullName(), + DoctorId = doctor2.Id, + DoctorFullName = doctor2.GetFullName(), + DateTime = new DateTime(2025, 1, 11, 9, 0, 0), + RoomNumber = 101, + IsReturnVisit = true + }; + _db.AddAppointment(appointment11); + + var appointment12 = new Appointment + { + Id = 12, + PatientId = patient3.Id, + PatientFullName = patient3.GetFullName(), + DoctorId = doctor1.Id, + DoctorFullName = doctor1.GetFullName(), + DateTime = new DateTime(2025, 1, 11, 9, 30, 0), + RoomNumber = 102, + IsReturnVisit = false + }; + _db.AddAppointment(appointment12); + } +} \ No newline at end of file From ad3ca397dbb2ac4c0626fab230fbf4106ee8041f Mon Sep 17 00:00:00 2001 From: maeosha Date: Wed, 26 Nov 2025 18:20:47 +0300 Subject: [PATCH 23/56] feat: add DateConverter for JSON serialization of DateOnly type --- Clinic.Api/Converter/DataConverter.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 Clinic.Api/Converter/DataConverter.cs diff --git a/Clinic.Api/Converter/DataConverter.cs b/Clinic.Api/Converter/DataConverter.cs new file mode 100644 index 000000000..82b77d496 --- /dev/null +++ b/Clinic.Api/Converter/DataConverter.cs @@ -0,0 +1,15 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Clinic.Api.Converter; + +public class DateConverter : JsonConverter +{ + private const string Format = "yyyy-MM-dd"; + + public override DateOnly Read(ref Utf8JsonReader reader, Type type, JsonSerializerOptions options) + => DateOnly.ParseExact(reader.GetString()!, Format); + + public override void Write(Utf8JsonWriter writer, DateOnly value, JsonSerializerOptions options) + => writer.WriteStringValue(value.ToString(Format)); +} \ No newline at end of file From 62c75e72806fd9ab16dd82a73a056331223bd45f Mon Sep 17 00:00:00 2001 From: maeosha Date: Wed, 26 Nov 2025 18:21:00 +0300 Subject: [PATCH 24/56] feat: add AutoMapper profile for mapping between DTOs and entity models in the Clinic API --- Clinic.Api/MappingProfile/MappingProfile.cs | 57 +++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 Clinic.Api/MappingProfile/MappingProfile.cs diff --git a/Clinic.Api/MappingProfile/MappingProfile.cs b/Clinic.Api/MappingProfile/MappingProfile.cs new file mode 100644 index 000000000..494718ff6 --- /dev/null +++ b/Clinic.Api/MappingProfile/MappingProfile.cs @@ -0,0 +1,57 @@ +using AutoMapper; +using Clinic.Api.DTOs.PatientDto; +using Clinic.Api.DTOs.DoctorDto; +using Clinic.Api.DTOs.SpecializationDto; +using Clinic.Api.DTOs.Appointment; +using Clinic.Models.Enums; +using Clinic.Models.Entities; + +namespace Clinic.Api.MappingProfile; + +/// +/// AutoMapper profile for mapping between DTOs and entity models in the Clinic API. +/// Defines the mapping rules for Patient, Doctor, Specialization, and Appointment objects, +/// including custom enum conversions and member mapping settings. +/// +public class MappingProfile : Profile +{ + public MappingProfile() + { + CreateMap() + .ForMember(dest => dest.Gender, opt => opt.MapFrom(src => Enum.Parse(src.Gender))) + .ForMember(dest => dest.BloodGroup, opt => opt.MapFrom(src => Enum.Parse(src.BloodGroup))) + .ForMember(dest => dest.RhesusFactor, opt => opt.MapFrom(src => Enum.Parse(src.RhesusFactor))); + + CreateMap() + .ForMember(dest => dest.Gender, opt => opt.MapFrom(src => src.Gender.ToString())) + .ForMember(dest => dest.BloodGroup, opt => opt.MapFrom(src => src.BloodGroup.ToString())) + .ForMember(dest => dest.RhesusFactor, opt => opt.MapFrom(src => src.RhesusFactor.ToString())); + + CreateMap() + .ForAllMembers(opts => opts.Condition((src, dest, srcMember) => srcMember != null)); + + CreateMap() + .ForMember(dest => dest.Gender, opt => opt.MapFrom(src => Enum.Parse(src.Gender))) + .ForMember(dest => dest.Specializations, opt => opt.MapFrom(src => src.Specializations)); + + CreateMap() + .ForMember(dest => dest.Specializations, opt => opt.MapFrom(src => src.Specializations.Select(s => s.Name.ToString()).ToList())); + + CreateMap() + .ForMember(d => d.Specializations, opt => opt.Ignore()) + .ForAllMembers(opts => opts.Condition((src, dest, srcMember) => srcMember != null)); + + CreateMap() + .ForMember(dest => dest.Name, opt => opt.MapFrom(src => src.Name.ToString())); + + CreateMap(); + + CreateMap() + .ForMember(dest => dest.DateTime, opt => opt.MapFrom(src => DateOnly.FromDateTime(src.DateTime))); + + CreateMap(); + + CreateMap() + .ForAllMembers(opts => opts.Condition((src, dest, srcMember) => srcMember != null)); + } +} From beb4bcbe6ae2d86ad441d163220ecc925187276d Mon Sep 17 00:00:00 2001 From: maeosha Date: Wed, 26 Nov 2025 18:21:25 +0300 Subject: [PATCH 25/56] feat: initialize Clinic API with main program setup, including service registrations and Swagger integration --- Clinic.Api/Clinic.Api.csproj | 21 +++++++++++++ Clinic.Api/Clinic.Api.http | 6 ++++ Clinic.Api/Program.cs | 36 +++++++++++++++++++++++ Clinic.Api/Properties/launchSettings.json | 23 +++++++++++++++ Clinic.Api/appsettings.Development.json | 8 +++++ Clinic.Api/appsettings.json | 9 ++++++ 6 files changed, 103 insertions(+) create mode 100644 Clinic.Api/Clinic.Api.csproj create mode 100644 Clinic.Api/Clinic.Api.http create mode 100644 Clinic.Api/Program.cs create mode 100644 Clinic.Api/Properties/launchSettings.json create mode 100644 Clinic.Api/appsettings.Development.json create mode 100644 Clinic.Api/appsettings.json diff --git a/Clinic.Api/Clinic.Api.csproj b/Clinic.Api/Clinic.Api.csproj new file mode 100644 index 000000000..fc42f7a59 --- /dev/null +++ b/Clinic.Api/Clinic.Api.csproj @@ -0,0 +1,21 @@ + + + + net9.0 + enable + enable + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Clinic.Api/Clinic.Api.http b/Clinic.Api/Clinic.Api.http new file mode 100644 index 000000000..6574707f4 --- /dev/null +++ b/Clinic.Api/Clinic.Api.http @@ -0,0 +1,6 @@ +@Clinic.Api_HostAddress = http://localhost:5042 + +GET {{Clinic.Api_HostAddress}}/weatherforecast/ +Accept: application/json + +### diff --git a/Clinic.Api/Program.cs b/Clinic.Api/Program.cs new file mode 100644 index 000000000..583f0a076 --- /dev/null +++ b/Clinic.Api/Program.cs @@ -0,0 +1,36 @@ +using Clinic.Api.DataBase; +using Clinic.Api.DataSeed; +using Clinic.Api.MappingProfile; +using Clinic.Api.Services; +using Clinic.Api.Converter; + +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddControllers() + .AddJsonOptions(options => + { + options.JsonSerializerOptions.Converters.Add(new DateConverter()); + options.JsonSerializerOptions.PropertyNamingPolicy = null; + }); +builder.Services.AddAutoMapper(cfg => cfg.AddProfile()); +builder.Services.AddSingleton(); +builder.Services.AddSingleton(); + +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); + +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); + +var app = builder.Build(); + +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(); +} + +app.MapControllers(); +app.Run(); diff --git a/Clinic.Api/Properties/launchSettings.json b/Clinic.Api/Properties/launchSettings.json new file mode 100644 index 000000000..663182850 --- /dev/null +++ b/Clinic.Api/Properties/launchSettings.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "http://localhost:5042", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "https://localhost:7015;http://localhost:5042", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/Clinic.Api/appsettings.Development.json b/Clinic.Api/appsettings.Development.json new file mode 100644 index 000000000..ff66ba6b2 --- /dev/null +++ b/Clinic.Api/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/Clinic.Api/appsettings.json b/Clinic.Api/appsettings.json new file mode 100644 index 000000000..4d566948d --- /dev/null +++ b/Clinic.Api/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} From 272ee8cf78bd6e7c45990d2a567b2bed5a7abdfe Mon Sep 17 00:00:00 2001 From: maeosha Date: Wed, 26 Nov 2025 22:48:46 +0300 Subject: [PATCH 26/56] feat: add TestServices and TestControllers for enhanced patient and doctor data retrieval in the Clinic API --- Clinic.Api/Controllers/TestControllers.cs | 44 ++++++++++ Clinic.Api/Program.cs | 1 + Clinic.Api/Services/TestServices.cs | 102 ++++++++++++++++++++++ 3 files changed, 147 insertions(+) create mode 100644 Clinic.Api/Controllers/TestControllers.cs create mode 100644 Clinic.Api/Services/TestServices.cs diff --git a/Clinic.Api/Controllers/TestControllers.cs b/Clinic.Api/Controllers/TestControllers.cs new file mode 100644 index 000000000..2deb430a9 --- /dev/null +++ b/Clinic.Api/Controllers/TestControllers.cs @@ -0,0 +1,44 @@ +using Microsoft.AspNetCore.Mvc; +using Clinic.Api.Services; + +[ApiController] +[Route("api/[controller]")] +public class TestControllers : ControllerBase +{ + private readonly TestServices _testServices; + + public TestControllers(TestServices testServices) + { + _testServices = testServices; + } + + [HttpGet("doctors/experience")] + public IActionResult GetDoctorsWithExperience() + { + return Ok(_testServices.GetDoctorsWithExperience10YearsOrMore()); + } + + [HttpGet("patients/doctor")] + public IActionResult GetPatientsByDoctor(int doctorId) + { + return Ok(_testServices.GetPatientsByDoctorOrderedByFullName(doctorId)); + } + + [HttpGet("patients/return-visits")] + public IActionResult GetReturnVisitsCountLastMonth() + { + return Ok(_testServices.GetReturnVisitsCountLastMonth()); + } + + [HttpGet("patients/over-30")] + public IActionResult GetPatientsOver30WithMultipleDoctors() + { + return Ok(_testServices.GetPatientsOver30WithMultipleDoctorsOrderedByBirthDate()); + } + + [HttpGet("appointments/room")] + public IActionResult GetAppointmentsInRoomForCurrentMonth(int roomNumber) + { + return Ok(_testServices.GetAppointmentsInRoomForCurrentMonth(roomNumber)); + } +} \ No newline at end of file diff --git a/Clinic.Api/Program.cs b/Clinic.Api/Program.cs index 583f0a076..9c006313f 100644 --- a/Clinic.Api/Program.cs +++ b/Clinic.Api/Program.cs @@ -23,6 +23,7 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); +builder.Services.AddScoped(); var app = builder.Build(); diff --git a/Clinic.Api/Services/TestServices.cs b/Clinic.Api/Services/TestServices.cs new file mode 100644 index 000000000..7e5106c61 --- /dev/null +++ b/Clinic.Api/Services/TestServices.cs @@ -0,0 +1,102 @@ +using AutoMapper; +using Clinic.Api.DataBase; +using Clinic.Api.DTOs.PatientDto; +using Clinic.Api.DTOs.DoctorDto; +using Clinic.Api.DTOs.Appointment; + +namespace Clinic.Api.Services; + +public class TestServices +{ + private readonly IClinicDataBase _db; + private readonly IMapper _mapper; + + public TestServices(IClinicDataBase db, IMapper mapper){ + _db = db; + _mapper = mapper; + } + + /// + /// Display information about all doctors with at least 10 years of experience. + /// + public IReadOnlyList GetDoctorsWithExperience10YearsOrMore(){ + var doctors = _db.GetAllDoctors() + .Where(d => d.ExperienceYears >= 10) + .ToList(); + return _mapper.Map>(doctors); + } + + /// + /// Display information about all patients scheduled to see a specified doctor, sorted by full name. + /// + public IReadOnlyList? GetPatientsByDoctorOrderedByFullName(int doctorId){ + var doctor = _db.GetDoctor(doctorId); + if (doctor == null) + { + return null; + } + + var appointments = _db.GetAppointmentsByDoctor(doctorId); + var patients = appointments.Where(a => a.DoctorId == doctorId).Select(a => _db.GetPatient(a.PatientId)).Distinct().ToList(); + + return _mapper.Map>(patients); + } + + /// + /// Display information about the number of repeat patient visits in the last month. + /// + public int GetReturnVisitsCountLastMonth(){ + var lastMonth = DateTime.Now.AddMonths(-1); + var startOfLastMonth = new DateTime(lastMonth.Year, lastMonth.Month, 1); + var startOfCurrentMonth = startOfLastMonth.AddMonths(1); + + var appointments = _db.GetAllAppointments() + .Where(a => a.DateTime >= startOfLastMonth && + a.DateTime < startOfCurrentMonth && + a.IsReturnVisit) + .Count(); + + return appointments; + } + + /// + /// Display information about patients over 30 years old who have appointments with multiple doctors, sorted by birth date. + /// + public IReadOnlyList GetPatientsOver30WithMultipleDoctorsOrderedByBirthDate(){ + var thirtyYearsAgo = DateOnly.FromDateTime(DateTime.Now.AddYears(-30)); + + var appointments = _db.GetAllAppointments(); + + var patientsWithMultipleDoctors = appointments + .GroupBy(a => a.PatientId) + .Where(g => g.Select(a => a.DoctorId).Distinct().Count() > 1) + .Select(g => g.Key) + .ToList(); + + var patients = patientsWithMultipleDoctors + .Select(id => _db.GetPatient(id)) + .Where(p => p != null && p.BirthDate < thirtyYearsAgo) + .OrderBy(p => p!.BirthDate) + .ToList(); + + return _mapper.Map>(patients); + } + + /// + /// Display information about appointments for the current month that are held in the selected room. + /// + public IReadOnlyList GetAppointmentsInRoomForCurrentMonth(int roomNumber){ + var now = DateTime.Now; + var startOfMonth = new DateTime(now.Year, now.Month, 1); + var startOfNextMonth = startOfMonth.AddMonths(1); + + var appointments = _db.GetAllAppointments() + .Where(a => a.RoomNumber == roomNumber && + a.DateTime >= startOfMonth && + a.DateTime < startOfNextMonth) + .OrderBy(a => a.DateTime) + .ToList(); + + return _mapper.Map>(appointments); + } +} \ No newline at end of file From a602e50e674b31282ea8bbc686571c14ced5f4e3 Mon Sep 17 00:00:00 2001 From: maeosha Date: Wed, 26 Nov 2025 22:49:20 +0300 Subject: [PATCH 27/56] feat: update DataSeed --- Clinic.Api/DataSeed/DataSeed.cs | 70 ++++++++++++++++++++++++++++++++- 1 file changed, 68 insertions(+), 2 deletions(-) diff --git a/Clinic.Api/DataSeed/DataSeed.cs b/Clinic.Api/DataSeed/DataSeed.cs index e1a2919a9..93c8de071 100644 --- a/Clinic.Api/DataSeed/DataSeed.cs +++ b/Clinic.Api/DataSeed/DataSeed.cs @@ -352,7 +352,7 @@ public void TestDataSeed(IClinicDataBase _db) PatientFullName = patient1.GetFullName(), DoctorId = doctor1.Id, DoctorFullName = doctor1.GetFullName(), - DateTime = new DateTime(2025, 1, 10, 9, 0, 0), + DateTime = new DateTime(2024, 12, 15, 9, 0, 0), RoomNumber = 101, IsReturnVisit = false }; @@ -365,7 +365,7 @@ public void TestDataSeed(IClinicDataBase _db) PatientFullName = patient2.GetFullName(), DoctorId = doctor2.Id, DoctorFullName = doctor2.GetFullName(), - DateTime = new DateTime(2025, 1, 10, 9, 30, 0), + DateTime = new DateTime(2024, 12, 16, 9, 30, 0), RoomNumber = 102, IsReturnVisit = false }; @@ -500,5 +500,71 @@ public void TestDataSeed(IClinicDataBase _db) IsReturnVisit = false }; _db.AddAppointment(appointment12); + + var appointment13 = new Appointment + { + Id = 13, + PatientId = patient2.Id, + PatientFullName = patient2.GetFullName(), + DoctorId = doctor3.Id, + DoctorFullName = doctor3.GetFullName(), + DateTime = new DateTime(2024, 12, 18, 14, 0, 0), + RoomNumber = 103, + IsReturnVisit = true + }; + _db.AddAppointment(appointment13); + + var appointment14 = new Appointment + { + Id = 14, + PatientId = patient4.Id, + PatientFullName = patient4.GetFullName(), + DoctorId = doctor5.Id, + DoctorFullName = doctor5.GetFullName(), + DateTime = new DateTime(2024, 12, 20, 10, 0, 0), + RoomNumber = 201, + IsReturnVisit = false + }; + _db.AddAppointment(appointment14); + + var appointment15 = new Appointment + { + Id = 15, + PatientId = patient6.Id, + PatientFullName = patient6.GetFullName(), + DoctorId = doctor7.Id, + DoctorFullName = doctor7.GetFullName(), + DateTime = new DateTime(2024, 12, 22, 11, 0, 0), + RoomNumber = 202, + IsReturnVisit = true + }; + _db.AddAppointment(appointment15); + + // Еще дополнительные повторные визиты + var appointment16 = new Appointment + { + Id = 16, + PatientId = patient8.Id, + PatientFullName = patient8.GetFullName(), + DoctorId = doctor9.Id, + DoctorFullName = doctor9.GetFullName(), + DateTime = new DateTime(2025, 1, 12, 15, 0, 0), + RoomNumber = 301, + IsReturnVisit = true + }; + _db.AddAppointment(appointment16); + + var appointment17 = new Appointment + { + Id = 17, + PatientId = patient5.Id, + PatientFullName = patient5.GetFullName(), + DoctorId = doctor4.Id, + DoctorFullName = doctor4.GetFullName(), + DateTime = new DateTime(2025, 1, 13, 16, 30, 0), + RoomNumber = 302, + IsReturnVisit = true + }; + _db.AddAppointment(appointment17); } } \ No newline at end of file From ca81d9ae317987d7bdc9b0e04a64809847f7607f Mon Sep 17 00:00:00 2001 From: maeosha Date: Wed, 26 Nov 2025 22:49:53 +0300 Subject: [PATCH 28/56] fix: change DateOnly to DateTime in GetAppointmentDto for correct date handling --- Clinic.Api/DTOs/Appointment/GetAppointmentDto.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Clinic.Api/DTOs/Appointment/GetAppointmentDto.cs b/Clinic.Api/DTOs/Appointment/GetAppointmentDto.cs index f98f4a88e..fb026bebc 100644 --- a/Clinic.Api/DTOs/Appointment/GetAppointmentDto.cs +++ b/Clinic.Api/DTOs/Appointment/GetAppointmentDto.cs @@ -12,7 +12,7 @@ public class GetAppointmentDto public int DoctorId { get; set; } public string PatientFullName { get; set; } = null!; public string DoctorFullName { get; set; } = null!; - public DateOnly DateTime { get; set; } + public DateTime DateTime { get; set; } public int RoomNumber { get; set; } public bool IsReturnVisit { get; set; } } From 6957ee2a39cff56ca94f156ba51b565c2420a72e Mon Sep 17 00:00:00 2001 From: maeosha Date: Thu, 27 Nov 2025 23:55:37 +0300 Subject: [PATCH 29/56] fix: The Clinic.APi folder has been moved --- {Clinic.Api => Clinic/Clinic.Api}/Clinic.Api.csproj | 0 {Clinic.Api => Clinic/Clinic.Api}/Clinic.Api.http | 0 .../Clinic.Api}/Controllers/AppointmentControllers.cs | 0 .../Clinic.Api}/Controllers/DoctorControllers.cs | 0 .../Clinic.Api}/Controllers/PatientControllers.cs | 0 .../Clinic.Api}/Controllers/SpecializationControllers.cs | 0 {Clinic.Api => Clinic/Clinic.Api}/Controllers/TestControllers.cs | 0 {Clinic.Api => Clinic/Clinic.Api}/Converter/DataConverter.cs | 0 .../Clinic.Api}/DTOs/Appointment/CreateAppointmentDto.cs | 0 .../Clinic.Api}/DTOs/Appointment/GetAppointmentDto.cs | 0 .../Clinic.Api}/DTOs/Appointment/UpdateAppointment.cs | 0 {Clinic.Api => Clinic/Clinic.Api}/DTOs/Doctor/CreateDoctorDto.cs | 0 {Clinic.Api => Clinic/Clinic.Api}/DTOs/Doctor/GetDoctorDto.cs | 0 {Clinic.Api => Clinic/Clinic.Api}/DTOs/Doctor/UpdateDoctorDto.cs | 0 .../Clinic.Api}/DTOs/Patient/CreatePatientDto.cs | 0 {Clinic.Api => Clinic/Clinic.Api}/DTOs/Patient/GetPatientDto.cs | 0 .../Clinic.Api}/DTOs/Patient/UpdatePatientDto.cs | 0 .../Clinic.Api}/DTOs/Specialization/CreateSpecializationDto.cs | 0 .../Clinic.Api}/DTOs/Specialization/GetSpecializationDto.cs | 0 {Clinic.Api => Clinic/Clinic.Api}/DataBase/ClinicDataBase.cs | 0 {Clinic.Api => Clinic/Clinic.Api}/DataBase/IClinicDataBase.cs | 0 {Clinic.Api => Clinic/Clinic.Api}/DataSeed/DataSeed.cs | 0 .../Clinic.Api}/MappingProfile/MappingProfile.cs | 0 {Clinic.Api => Clinic/Clinic.Api}/Program.cs | 0 {Clinic.Api => Clinic/Clinic.Api}/Properties/launchSettings.json | 0 {Clinic.Api => Clinic/Clinic.Api}/Services/AppointmentServices.cs | 0 {Clinic.Api => Clinic/Clinic.Api}/Services/DoctorServices.cs | 0 {Clinic.Api => Clinic/Clinic.Api}/Services/PatientServices.cs | 0 .../Clinic.Api}/Services/SpecializationServices.cs | 0 {Clinic.Api => Clinic/Clinic.Api}/Services/TestServices.cs | 0 {Clinic.Api => Clinic/Clinic.Api}/appsettings.Development.json | 0 {Clinic.Api => Clinic/Clinic.Api}/appsettings.json | 0 32 files changed, 0 insertions(+), 0 deletions(-) rename {Clinic.Api => Clinic/Clinic.Api}/Clinic.Api.csproj (100%) rename {Clinic.Api => Clinic/Clinic.Api}/Clinic.Api.http (100%) rename {Clinic.Api => Clinic/Clinic.Api}/Controllers/AppointmentControllers.cs (100%) rename {Clinic.Api => Clinic/Clinic.Api}/Controllers/DoctorControllers.cs (100%) rename {Clinic.Api => Clinic/Clinic.Api}/Controllers/PatientControllers.cs (100%) rename {Clinic.Api => Clinic/Clinic.Api}/Controllers/SpecializationControllers.cs (100%) rename {Clinic.Api => Clinic/Clinic.Api}/Controllers/TestControllers.cs (100%) rename {Clinic.Api => Clinic/Clinic.Api}/Converter/DataConverter.cs (100%) rename {Clinic.Api => Clinic/Clinic.Api}/DTOs/Appointment/CreateAppointmentDto.cs (100%) rename {Clinic.Api => Clinic/Clinic.Api}/DTOs/Appointment/GetAppointmentDto.cs (100%) rename {Clinic.Api => Clinic/Clinic.Api}/DTOs/Appointment/UpdateAppointment.cs (100%) rename {Clinic.Api => Clinic/Clinic.Api}/DTOs/Doctor/CreateDoctorDto.cs (100%) rename {Clinic.Api => Clinic/Clinic.Api}/DTOs/Doctor/GetDoctorDto.cs (100%) rename {Clinic.Api => Clinic/Clinic.Api}/DTOs/Doctor/UpdateDoctorDto.cs (100%) rename {Clinic.Api => Clinic/Clinic.Api}/DTOs/Patient/CreatePatientDto.cs (100%) rename {Clinic.Api => Clinic/Clinic.Api}/DTOs/Patient/GetPatientDto.cs (100%) rename {Clinic.Api => Clinic/Clinic.Api}/DTOs/Patient/UpdatePatientDto.cs (100%) rename {Clinic.Api => Clinic/Clinic.Api}/DTOs/Specialization/CreateSpecializationDto.cs (100%) rename {Clinic.Api => Clinic/Clinic.Api}/DTOs/Specialization/GetSpecializationDto.cs (100%) rename {Clinic.Api => Clinic/Clinic.Api}/DataBase/ClinicDataBase.cs (100%) rename {Clinic.Api => Clinic/Clinic.Api}/DataBase/IClinicDataBase.cs (100%) rename {Clinic.Api => Clinic/Clinic.Api}/DataSeed/DataSeed.cs (100%) rename {Clinic.Api => Clinic/Clinic.Api}/MappingProfile/MappingProfile.cs (100%) rename {Clinic.Api => Clinic/Clinic.Api}/Program.cs (100%) rename {Clinic.Api => Clinic/Clinic.Api}/Properties/launchSettings.json (100%) rename {Clinic.Api => Clinic/Clinic.Api}/Services/AppointmentServices.cs (100%) rename {Clinic.Api => Clinic/Clinic.Api}/Services/DoctorServices.cs (100%) rename {Clinic.Api => Clinic/Clinic.Api}/Services/PatientServices.cs (100%) rename {Clinic.Api => Clinic/Clinic.Api}/Services/SpecializationServices.cs (100%) rename {Clinic.Api => Clinic/Clinic.Api}/Services/TestServices.cs (100%) rename {Clinic.Api => Clinic/Clinic.Api}/appsettings.Development.json (100%) rename {Clinic.Api => Clinic/Clinic.Api}/appsettings.json (100%) diff --git a/Clinic.Api/Clinic.Api.csproj b/Clinic/Clinic.Api/Clinic.Api.csproj similarity index 100% rename from Clinic.Api/Clinic.Api.csproj rename to Clinic/Clinic.Api/Clinic.Api.csproj diff --git a/Clinic.Api/Clinic.Api.http b/Clinic/Clinic.Api/Clinic.Api.http similarity index 100% rename from Clinic.Api/Clinic.Api.http rename to Clinic/Clinic.Api/Clinic.Api.http diff --git a/Clinic.Api/Controllers/AppointmentControllers.cs b/Clinic/Clinic.Api/Controllers/AppointmentControllers.cs similarity index 100% rename from Clinic.Api/Controllers/AppointmentControllers.cs rename to Clinic/Clinic.Api/Controllers/AppointmentControllers.cs diff --git a/Clinic.Api/Controllers/DoctorControllers.cs b/Clinic/Clinic.Api/Controllers/DoctorControllers.cs similarity index 100% rename from Clinic.Api/Controllers/DoctorControllers.cs rename to Clinic/Clinic.Api/Controllers/DoctorControllers.cs diff --git a/Clinic.Api/Controllers/PatientControllers.cs b/Clinic/Clinic.Api/Controllers/PatientControllers.cs similarity index 100% rename from Clinic.Api/Controllers/PatientControllers.cs rename to Clinic/Clinic.Api/Controllers/PatientControllers.cs diff --git a/Clinic.Api/Controllers/SpecializationControllers.cs b/Clinic/Clinic.Api/Controllers/SpecializationControllers.cs similarity index 100% rename from Clinic.Api/Controllers/SpecializationControllers.cs rename to Clinic/Clinic.Api/Controllers/SpecializationControllers.cs diff --git a/Clinic.Api/Controllers/TestControllers.cs b/Clinic/Clinic.Api/Controllers/TestControllers.cs similarity index 100% rename from Clinic.Api/Controllers/TestControllers.cs rename to Clinic/Clinic.Api/Controllers/TestControllers.cs diff --git a/Clinic.Api/Converter/DataConverter.cs b/Clinic/Clinic.Api/Converter/DataConverter.cs similarity index 100% rename from Clinic.Api/Converter/DataConverter.cs rename to Clinic/Clinic.Api/Converter/DataConverter.cs diff --git a/Clinic.Api/DTOs/Appointment/CreateAppointmentDto.cs b/Clinic/Clinic.Api/DTOs/Appointment/CreateAppointmentDto.cs similarity index 100% rename from Clinic.Api/DTOs/Appointment/CreateAppointmentDto.cs rename to Clinic/Clinic.Api/DTOs/Appointment/CreateAppointmentDto.cs diff --git a/Clinic.Api/DTOs/Appointment/GetAppointmentDto.cs b/Clinic/Clinic.Api/DTOs/Appointment/GetAppointmentDto.cs similarity index 100% rename from Clinic.Api/DTOs/Appointment/GetAppointmentDto.cs rename to Clinic/Clinic.Api/DTOs/Appointment/GetAppointmentDto.cs diff --git a/Clinic.Api/DTOs/Appointment/UpdateAppointment.cs b/Clinic/Clinic.Api/DTOs/Appointment/UpdateAppointment.cs similarity index 100% rename from Clinic.Api/DTOs/Appointment/UpdateAppointment.cs rename to Clinic/Clinic.Api/DTOs/Appointment/UpdateAppointment.cs diff --git a/Clinic.Api/DTOs/Doctor/CreateDoctorDto.cs b/Clinic/Clinic.Api/DTOs/Doctor/CreateDoctorDto.cs similarity index 100% rename from Clinic.Api/DTOs/Doctor/CreateDoctorDto.cs rename to Clinic/Clinic.Api/DTOs/Doctor/CreateDoctorDto.cs diff --git a/Clinic.Api/DTOs/Doctor/GetDoctorDto.cs b/Clinic/Clinic.Api/DTOs/Doctor/GetDoctorDto.cs similarity index 100% rename from Clinic.Api/DTOs/Doctor/GetDoctorDto.cs rename to Clinic/Clinic.Api/DTOs/Doctor/GetDoctorDto.cs diff --git a/Clinic.Api/DTOs/Doctor/UpdateDoctorDto.cs b/Clinic/Clinic.Api/DTOs/Doctor/UpdateDoctorDto.cs similarity index 100% rename from Clinic.Api/DTOs/Doctor/UpdateDoctorDto.cs rename to Clinic/Clinic.Api/DTOs/Doctor/UpdateDoctorDto.cs diff --git a/Clinic.Api/DTOs/Patient/CreatePatientDto.cs b/Clinic/Clinic.Api/DTOs/Patient/CreatePatientDto.cs similarity index 100% rename from Clinic.Api/DTOs/Patient/CreatePatientDto.cs rename to Clinic/Clinic.Api/DTOs/Patient/CreatePatientDto.cs diff --git a/Clinic.Api/DTOs/Patient/GetPatientDto.cs b/Clinic/Clinic.Api/DTOs/Patient/GetPatientDto.cs similarity index 100% rename from Clinic.Api/DTOs/Patient/GetPatientDto.cs rename to Clinic/Clinic.Api/DTOs/Patient/GetPatientDto.cs diff --git a/Clinic.Api/DTOs/Patient/UpdatePatientDto.cs b/Clinic/Clinic.Api/DTOs/Patient/UpdatePatientDto.cs similarity index 100% rename from Clinic.Api/DTOs/Patient/UpdatePatientDto.cs rename to Clinic/Clinic.Api/DTOs/Patient/UpdatePatientDto.cs diff --git a/Clinic.Api/DTOs/Specialization/CreateSpecializationDto.cs b/Clinic/Clinic.Api/DTOs/Specialization/CreateSpecializationDto.cs similarity index 100% rename from Clinic.Api/DTOs/Specialization/CreateSpecializationDto.cs rename to Clinic/Clinic.Api/DTOs/Specialization/CreateSpecializationDto.cs diff --git a/Clinic.Api/DTOs/Specialization/GetSpecializationDto.cs b/Clinic/Clinic.Api/DTOs/Specialization/GetSpecializationDto.cs similarity index 100% rename from Clinic.Api/DTOs/Specialization/GetSpecializationDto.cs rename to Clinic/Clinic.Api/DTOs/Specialization/GetSpecializationDto.cs diff --git a/Clinic.Api/DataBase/ClinicDataBase.cs b/Clinic/Clinic.Api/DataBase/ClinicDataBase.cs similarity index 100% rename from Clinic.Api/DataBase/ClinicDataBase.cs rename to Clinic/Clinic.Api/DataBase/ClinicDataBase.cs diff --git a/Clinic.Api/DataBase/IClinicDataBase.cs b/Clinic/Clinic.Api/DataBase/IClinicDataBase.cs similarity index 100% rename from Clinic.Api/DataBase/IClinicDataBase.cs rename to Clinic/Clinic.Api/DataBase/IClinicDataBase.cs diff --git a/Clinic.Api/DataSeed/DataSeed.cs b/Clinic/Clinic.Api/DataSeed/DataSeed.cs similarity index 100% rename from Clinic.Api/DataSeed/DataSeed.cs rename to Clinic/Clinic.Api/DataSeed/DataSeed.cs diff --git a/Clinic.Api/MappingProfile/MappingProfile.cs b/Clinic/Clinic.Api/MappingProfile/MappingProfile.cs similarity index 100% rename from Clinic.Api/MappingProfile/MappingProfile.cs rename to Clinic/Clinic.Api/MappingProfile/MappingProfile.cs diff --git a/Clinic.Api/Program.cs b/Clinic/Clinic.Api/Program.cs similarity index 100% rename from Clinic.Api/Program.cs rename to Clinic/Clinic.Api/Program.cs diff --git a/Clinic.Api/Properties/launchSettings.json b/Clinic/Clinic.Api/Properties/launchSettings.json similarity index 100% rename from Clinic.Api/Properties/launchSettings.json rename to Clinic/Clinic.Api/Properties/launchSettings.json diff --git a/Clinic.Api/Services/AppointmentServices.cs b/Clinic/Clinic.Api/Services/AppointmentServices.cs similarity index 100% rename from Clinic.Api/Services/AppointmentServices.cs rename to Clinic/Clinic.Api/Services/AppointmentServices.cs diff --git a/Clinic.Api/Services/DoctorServices.cs b/Clinic/Clinic.Api/Services/DoctorServices.cs similarity index 100% rename from Clinic.Api/Services/DoctorServices.cs rename to Clinic/Clinic.Api/Services/DoctorServices.cs diff --git a/Clinic.Api/Services/PatientServices.cs b/Clinic/Clinic.Api/Services/PatientServices.cs similarity index 100% rename from Clinic.Api/Services/PatientServices.cs rename to Clinic/Clinic.Api/Services/PatientServices.cs diff --git a/Clinic.Api/Services/SpecializationServices.cs b/Clinic/Clinic.Api/Services/SpecializationServices.cs similarity index 100% rename from Clinic.Api/Services/SpecializationServices.cs rename to Clinic/Clinic.Api/Services/SpecializationServices.cs diff --git a/Clinic.Api/Services/TestServices.cs b/Clinic/Clinic.Api/Services/TestServices.cs similarity index 100% rename from Clinic.Api/Services/TestServices.cs rename to Clinic/Clinic.Api/Services/TestServices.cs diff --git a/Clinic.Api/appsettings.Development.json b/Clinic/Clinic.Api/appsettings.Development.json similarity index 100% rename from Clinic.Api/appsettings.Development.json rename to Clinic/Clinic.Api/appsettings.Development.json diff --git a/Clinic.Api/appsettings.json b/Clinic/Clinic.Api/appsettings.json similarity index 100% rename from Clinic.Api/appsettings.json rename to Clinic/Clinic.Api/appsettings.json From ee573212d006d7dcd0b728ecca3b13d947e08433 Mon Sep 17 00:00:00 2001 From: maeosha Date: Thu, 27 Nov 2025 23:58:22 +0300 Subject: [PATCH 30/56] fix: Now Specialization is also an entity --- Clinic.Models/Common/PersonInfo.cs | 62 ------------------- Clinic.Models/Entities/Appointment.cs | 47 -------------- Clinic.Models/Entities/Doctor.cs | 19 ------ Clinic.Models/Entities/Patient.cs | 25 -------- Clinic/Clinic.Models/Common/PersonInfo.cs | 5 ++ Clinic/Clinic.Models/Entities/Appointment.cs | 23 +++++-- Clinic/Clinic.Models/Entities/Doctor.cs | 1 - Clinic/Clinic.Models/Entities/Patient.cs | 5 -- .../Clinic.Models}/Entities/Specialization.cs | 0 .../ReferenceBook/Specialization.cs | 17 ----- 10 files changed, 24 insertions(+), 180 deletions(-) delete mode 100644 Clinic.Models/Common/PersonInfo.cs delete mode 100644 Clinic.Models/Entities/Appointment.cs delete mode 100644 Clinic.Models/Entities/Doctor.cs delete mode 100644 Clinic.Models/Entities/Patient.cs rename {Clinic.Models => Clinic/Clinic.Models}/Entities/Specialization.cs (100%) delete mode 100644 Clinic/Clinic.Models/ReferenceBook/Specialization.cs diff --git a/Clinic.Models/Common/PersonInfo.cs b/Clinic.Models/Common/PersonInfo.cs deleted file mode 100644 index 6731f8178..000000000 --- a/Clinic.Models/Common/PersonInfo.cs +++ /dev/null @@ -1,62 +0,0 @@ -using Clinic.Models.Enums; - -namespace Clinic.Models.Common; - -/// -/// Represents personal information for a person. -/// -public class PersonInfo -{ - /// - /// Gets or sets the unique identifier. - /// - required public int Id { get; set; } - - /// - /// Gets or sets the passport number. - /// - required public string PassportNumber { get; set; } - - /// - /// Gets or sets the year of birth. - /// - required public DateOnly BirthDate { get; set; } - - /// - /// Gets or sets the last name. - /// - required public string LastName { get; set; } - - /// - /// Gets or sets the first name. - /// - required public string FirstName { get; set; } - - /// - /// Gets or sets the patronymic. - /// - public string? Patronymic { get; set; } - - /// - /// Gets or sets the patient's phone number. - /// - required public string PhoneNumber { get; set; } - - /// - /// Gets or sets the gender. - /// - required public Gender Gender { get; set; } - - /// - /// Gets the full name composed of last name, first name and optional patronymic. - /// - /// The full name as a single string. - public string GetFullName() - { - if (string.IsNullOrEmpty(Patronymic)) - { - return $"{LastName} {FirstName}"; - } - return $"{LastName} {FirstName} {Patronymic}"; - } -} \ No newline at end of file diff --git a/Clinic.Models/Entities/Appointment.cs b/Clinic.Models/Entities/Appointment.cs deleted file mode 100644 index 343fbb282..000000000 --- a/Clinic.Models/Entities/Appointment.cs +++ /dev/null @@ -1,47 +0,0 @@ -namespace Clinic.Models.Entities; - -/// -/// Represents an appointment in the clinic, including patient, doctor, date/time, room, and visit type. -/// -public class Appointment -{ - /// - /// Gets or sets the unique identifier for the appointment. - /// - required public int Id { get; set; } - - /// - /// Gets or sets the patient identifier for the appointment. - /// - required public int PatientId { get; set; } - - /// - /// Gets or sets the full name of the patient at the time of appointment. - /// - required public string PatientFullName { get; set; } = null!; - - /// - /// Gets or sets the doctor identifier for the appointment. - /// - required public int DoctorId { get; set; } - - /// - /// Gets or sets the full name of the doctor at the time of appointment. - /// - required public string DoctorFullName { get; set; } = null!; - - /// - /// Gets or sets the date and time of the appointment. - /// - required public DateTime DateTime { get; set; } - - /// - /// Gets or sets the room number for the appointment. - /// - required public int RoomNumber { get; set; } - - /// - /// Gets or sets a value indicating whether this appointment is a return visit. - /// - required public bool IsReturnVisit { get; set; } -} diff --git a/Clinic.Models/Entities/Doctor.cs b/Clinic.Models/Entities/Doctor.cs deleted file mode 100644 index ef5481a63..000000000 --- a/Clinic.Models/Entities/Doctor.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Clinic.Models.Common; - -namespace Clinic.Models.Entities; - -/// -/// Represents a doctor with personal and professional details. -/// -public class Doctor : PersonInfo -{ - /// - /// Gets or sets the list of medical specializations the doctor holds. - /// - required public List Specializations { get; set; } - - /// - /// Gets or sets the number of years of experience the doctor has. - /// - required public int ExperienceYears { get; set; } -} diff --git a/Clinic.Models/Entities/Patient.cs b/Clinic.Models/Entities/Patient.cs deleted file mode 100644 index 59cb254f0..000000000 --- a/Clinic.Models/Entities/Patient.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Clinic.Models.Common; -using Clinic.Models.Enums; - -namespace Clinic.Models.Entities; - -/// -/// Represents a patient entity with personal and medical information. -/// -public class Patient : PersonInfo -{ - /// - /// Gets or sets the patient's address. - /// - required public string Address { get; set; } - - /// - /// Gets or sets the patient's blood group. - /// - required public BloodGroup BloodGroup { get; set; } - - /// - /// Gets or sets the patient's rhesus factor. - /// - required public RhesusFactor RhesusFactor { get; set; } -} diff --git a/Clinic/Clinic.Models/Common/PersonInfo.cs b/Clinic/Clinic.Models/Common/PersonInfo.cs index 052a949ff..6731f8178 100644 --- a/Clinic/Clinic.Models/Common/PersonInfo.cs +++ b/Clinic/Clinic.Models/Common/PersonInfo.cs @@ -37,6 +37,11 @@ public class PersonInfo /// public string? Patronymic { get; set; } + /// + /// Gets or sets the patient's phone number. + /// + required public string PhoneNumber { get; set; } + /// /// Gets or sets the gender. /// diff --git a/Clinic/Clinic.Models/Entities/Appointment.cs b/Clinic/Clinic.Models/Entities/Appointment.cs index 10cf8dd50..343fbb282 100644 --- a/Clinic/Clinic.Models/Entities/Appointment.cs +++ b/Clinic/Clinic.Models/Entities/Appointment.cs @@ -6,14 +6,29 @@ namespace Clinic.Models.Entities; public class Appointment { /// - /// Gets or sets the patient for the appointment. + /// Gets or sets the unique identifier for the appointment. /// - required public Patient Patient { get; set; } + required public int Id { get; set; } /// - /// Gets or sets the doctor for the appointment. + /// Gets or sets the patient identifier for the appointment. /// - required public Doctor Doctor { get; set; } + required public int PatientId { get; set; } + + /// + /// Gets or sets the full name of the patient at the time of appointment. + /// + required public string PatientFullName { get; set; } = null!; + + /// + /// Gets or sets the doctor identifier for the appointment. + /// + required public int DoctorId { get; set; } + + /// + /// Gets or sets the full name of the doctor at the time of appointment. + /// + required public string DoctorFullName { get; set; } = null!; /// /// Gets or sets the date and time of the appointment. diff --git a/Clinic/Clinic.Models/Entities/Doctor.cs b/Clinic/Clinic.Models/Entities/Doctor.cs index 59fe507d1..ef5481a63 100644 --- a/Clinic/Clinic.Models/Entities/Doctor.cs +++ b/Clinic/Clinic.Models/Entities/Doctor.cs @@ -1,5 +1,4 @@ using Clinic.Models.Common; -using Clinic.Models.ReferenceBooks; namespace Clinic.Models.Entities; diff --git a/Clinic/Clinic.Models/Entities/Patient.cs b/Clinic/Clinic.Models/Entities/Patient.cs index 384e79d0d..59cb254f0 100644 --- a/Clinic/Clinic.Models/Entities/Patient.cs +++ b/Clinic/Clinic.Models/Entities/Patient.cs @@ -22,9 +22,4 @@ public class Patient : PersonInfo /// Gets or sets the patient's rhesus factor. /// required public RhesusFactor RhesusFactor { get; set; } - - /// - /// Gets or sets the patient's phone number. - /// - required public string PhoneNumber { get; set; } } diff --git a/Clinic.Models/Entities/Specialization.cs b/Clinic/Clinic.Models/Entities/Specialization.cs similarity index 100% rename from Clinic.Models/Entities/Specialization.cs rename to Clinic/Clinic.Models/Entities/Specialization.cs diff --git a/Clinic/Clinic.Models/ReferenceBook/Specialization.cs b/Clinic/Clinic.Models/ReferenceBook/Specialization.cs deleted file mode 100644 index 24190fcec..000000000 --- a/Clinic/Clinic.Models/ReferenceBook/Specialization.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace Clinic.Models.ReferenceBooks; - -/// -/// Represents a medical specialisation in the clinic. -/// -public class Specialization -{ - /// - /// Gets or sets the unique identifier for the specialisation. - /// - required public int Id { get; set; } - - /// - /// Gets or sets the specialization name. - /// - required public string Name { get; set; } -} From 91e7fbd7ffdb09b4b7f73f1ab7b9199c5d4a1c0f Mon Sep 17 00:00:00 2001 From: maeosha Date: Thu, 27 Nov 2025 23:58:40 +0300 Subject: [PATCH 31/56] refactor: remove obsolete clinic model and associated test files --- Clinic/Clinic.Models/Clinic.cs | 68 ---- Clinic/Clinic.Tests/Clinic.Tests.csproj | 25 -- Clinic/Clinic.Tests/DataSeed/dataseed.cs | 484 ----------------------- Clinic/Clinic.Tests/UnitTest1.cs | 131 ------ 4 files changed, 708 deletions(-) delete mode 100644 Clinic/Clinic.Models/Clinic.cs delete mode 100644 Clinic/Clinic.Tests/Clinic.Tests.csproj delete mode 100644 Clinic/Clinic.Tests/DataSeed/dataseed.cs delete mode 100644 Clinic/Clinic.Tests/UnitTest1.cs diff --git a/Clinic/Clinic.Models/Clinic.cs b/Clinic/Clinic.Models/Clinic.cs deleted file mode 100644 index e49499882..000000000 --- a/Clinic/Clinic.Models/Clinic.cs +++ /dev/null @@ -1,68 +0,0 @@ -using Clinic.Models.Entities; -using Clinic.Models.ReferenceBooks; -using System.Collections.Generic; - -namespace Clinic.Models; - -/// -/// Central in-memory storage for clinic data. -/// -public class ClinicInfo -{ - /// - /// Gets or sets the list of doctors. - /// - public List Doctors { get; set; } = new(); - - /// - /// Gets or sets the list of patients. - /// - public List Patients { get; set; } = new(); - - /// - /// Gets or sets the list of appointments. - /// - public List Appointments { get; set; } = new(); - - /// - /// Gets or sets the dictionary of specializations. - /// - public Dictionary Specializations { get; set; } = new(); - - /// - /// Adds a doctor to the clinic. - /// - /// The doctor to add. - public void AddDoctor(Doctor doctor) - { - Doctors.Add(doctor); - } - - /// - /// Adds a patient to the clinic. - /// - /// The patient to add. - public void AddPatient(Patient patient) - { - Patients.Add(patient); - } - - /// - /// Schedules an appointment. - /// - /// The appointment to schedule. - public void MakeAnAppointment(Appointment appointment) - { - Appointments.Add(appointment); - } - - /// - /// Adds or updates a specialization. - /// - /// The specialization code. - /// The specialization object. - public void AddSpecialization(string key, Specialization specialization) - { - Specializations[key] = specialization; - } -} \ No newline at end of file diff --git a/Clinic/Clinic.Tests/Clinic.Tests.csproj b/Clinic/Clinic.Tests/Clinic.Tests.csproj deleted file mode 100644 index 0ca980e9c..000000000 --- a/Clinic/Clinic.Tests/Clinic.Tests.csproj +++ /dev/null @@ -1,25 +0,0 @@ - - - - net9.0 - enable - enable - false - - - - - - - - - - - - - - - - - - diff --git a/Clinic/Clinic.Tests/DataSeed/dataseed.cs b/Clinic/Clinic.Tests/DataSeed/dataseed.cs deleted file mode 100644 index d7aec02c7..000000000 --- a/Clinic/Clinic.Tests/DataSeed/dataseed.cs +++ /dev/null @@ -1,484 +0,0 @@ -using Clinic.Models; -using Clinic.Models.Entities; -using Clinic.Models.Enums; -using Clinic.Models.ReferenceBooks; - -namespace Clinic.Tests.DataSeed; - -/// -/// Represents a static helper class responsible for seeding initial clinic data, -/// such as doctors, patients, specializations, and appointments, into a instance. -/// -public class ClinicDataSeed -{ - public ClinicInfo clinic{ get; set; } = new ClinicInfo(); - - public ClinicDataSeed() - { - var patient1 = new Patient - { - Id = 1, - PassportNumber = "P001", - LastName = "Иванов", - FirstName = "Иван", - Patronymic = "Иванович", - Gender = Gender.Male, - BirthDate = new DateOnly(1985, 5, 20), - Address = "г. Москва, ул. Ленина, д. 10", - BloodGroup = BloodGroup.A, - RhesusFactor = RhesusFactor.Positive, - PhoneNumber = "+79991234567" - }; - clinic.AddPatient(patient1); - - var patient2 = new Patient - { - Id = 2, - PassportNumber = "P002", - LastName = "Петрова", - FirstName = "Мария", - Patronymic = "Сергеевна", - Gender = Gender.Female, - BirthDate = new DateOnly(1990, 8, 15), - Address = "г. Санкт-Петербург, ул. Пушкина, д. 25", - BloodGroup = BloodGroup.B, - RhesusFactor = RhesusFactor.Positive, - PhoneNumber = "+79992345678" - }; - clinic.AddPatient(patient2); - - var patient3 = new Patient - { - Id = 3, - PassportNumber = "P003", - LastName = "Сидоров", - FirstName = "Алексей", - Patronymic = "Петрович", - Gender = Gender.Male, - BirthDate = new DateOnly(1978, 12, 3), - Address = "г. Екатеринбург, ул. Мира, д. 15", - BloodGroup = BloodGroup.O, - RhesusFactor = RhesusFactor.Negative, - PhoneNumber = "+79993456789" - }; - clinic.AddPatient(patient3); - - var patient4 = new Patient - { - Id = 4, - PassportNumber = "P004", - LastName = "Кузнецова", - FirstName = "Ольга", - Patronymic = "Владимировна", - Gender = Gender.Female, - BirthDate = new DateOnly(1995, 3, 10), - Address = "г. Новосибирск, ул. Советская, д. 8", - BloodGroup = BloodGroup.AB, - RhesusFactor = RhesusFactor.Positive, - PhoneNumber = "+79994567890" - }; - clinic.AddPatient(patient4); - - var patient5 = new Patient - { - Id = 5, - PassportNumber = "P005", - LastName = "Васильев", - FirstName = "Дмитрий", - Patronymic = "Александрович", - Gender = Gender.Male, - BirthDate = new DateOnly(1982, 7, 28), - Address = "г. Казань, ул. Гагарина, д. 12", - BloodGroup = BloodGroup.A, - RhesusFactor = RhesusFactor.Negative, - PhoneNumber = "+79995678901" - }; - clinic.AddPatient(patient5); - - var patient6 = new Patient - { - Id = 6, - PassportNumber = "P006", - LastName = "Николаева", - FirstName = "Елена", - Patronymic = "Игоревна", - Gender = Gender.Female, - BirthDate = new DateOnly(1988, 11, 5), - Address = "г. Нижний Новгород, ул. Лермонтова, д. 30", - BloodGroup = BloodGroup.O, - RhesusFactor = RhesusFactor.Positive, - PhoneNumber = "+79996789012" - }; - clinic.AddPatient(patient6); - - var patient7 = new Patient - { - Id = 7, - PassportNumber = "P007", - LastName = "Морозов", - FirstName = "Сергей", - Patronymic = "Викторович", - Gender = Gender.Male, - BirthDate = new DateOnly(1975, 1, 18), - Address = "г. Самара, ул. Чехова, д. 7", - BloodGroup = BloodGroup.B, - RhesusFactor = RhesusFactor.Positive, - PhoneNumber = "+79997890123" - }; - clinic.AddPatient(patient7); - - var patient8 = new Patient - { - Id = 8, - PassportNumber = "P008", - LastName = "Орлова", - FirstName = "Анна", - Patronymic = "Дмитриевна", - Gender = Gender.Female, - BirthDate = new DateOnly(1992, 9, 22), - Address = "г. Ростов-на-Дону, ул. Кирова, д. 18", - BloodGroup = BloodGroup.AB, - RhesusFactor = RhesusFactor.Negative, - PhoneNumber = "+79998901234" - }; - clinic.AddPatient(patient8); - - var patient9 = new Patient - { - Id = 9, - PassportNumber = "P009", - LastName = "Павлов", - FirstName = "Михаил", - Patronymic = "Олегович", - Gender = Gender.Male, - BirthDate = new DateOnly(1980, 4, 14), - Address = "г. Уфа, ул. Горького, д. 22", - BloodGroup = BloodGroup.O, - RhesusFactor = RhesusFactor.Positive, - PhoneNumber = "+79999012345" - }; - clinic.AddPatient(patient9); - - var patient10 = new Patient - { - Id = 10, - PassportNumber = "P010", - LastName = "Федорова", - FirstName = "Татьяна", - Patronymic = "Николаевна", - Gender = Gender.Female, - BirthDate = new DateOnly(1987, 6, 30), - Address = "г. Красноярск, ул. Ленина, д. 5", - BloodGroup = BloodGroup.A, - RhesusFactor = RhesusFactor.Positive, - PhoneNumber = "+79990123456" - }; - clinic.AddPatient(patient10); - clinic.AddSpecialization("Therapist", new Specialization { Id = 1, Name = "Терапевт" }); - clinic.AddSpecialization("Dentist", new Specialization { Id = 2, Name = "Стоматолог" }); - clinic.AddSpecialization("Cardiologist", new Specialization { Id = 3, Name = "Кардиолог" }); - clinic.AddSpecialization("Neurologist", new Specialization { Id = 4, Name = "Невролог" }); - clinic.AddSpecialization("Pediatrician", new Specialization { Id = 5, Name = "Педиатр" }); - clinic.AddSpecialization("Dermatologist", new Specialization { Id = 6, Name = "Дерматолог" }); - clinic.AddSpecialization("Psychiatrist", new Specialization { Id = 7, Name = "Психиатр" }); - clinic.AddSpecialization("Ophthalmologist", new Specialization { Id = 8, Name = "Офтальмолог" }); - clinic.AddSpecialization("ENTSpecialist", new Specialization { Id = 9, Name = "Отоларинголог" }); - clinic.AddSpecialization("Gynecologist", new Specialization { Id = 10, Name = "Гинеколог" }); - - var doctor1 = new Doctor - { - Id = 1, - PassportNumber = "D001", - LastName = "Смирнов", - FirstName = "Андрей", - Patronymic = "Иванович", - Gender = Gender.Male, - BirthDate = new DateOnly(1970, 3, 18), - Specializations = new List { - clinic.Specializations["Cardiologist"] - }, - ExperienceYears = 15 - }; - clinic.AddDoctor(doctor1); - - var doctor2 = new Doctor - { - Id = 2, - PassportNumber = "D002", - LastName = "Коваленко", - FirstName = "Мария", - Patronymic = "Петровна", - Gender = Gender.Female, - BirthDate = new DateOnly(1985, 6, 12), - Specializations = new List { - clinic.Specializations["Dentist"] - }, - ExperienceYears = 8 - }; - clinic.AddDoctor(doctor2); - - var doctor3 = new Doctor - { - Id = 3, - PassportNumber = "D003", - LastName = "Петров", - FirstName = "Сергей", - Patronymic = "Викторович", - Gender = Gender.Male, - BirthDate = new DateOnly(1978, 9, 25), - Specializations = new List { - clinic.Specializations["Cardiologist"], - clinic.Specializations["Therapist"] - }, - ExperienceYears = 12 - }; - clinic.AddDoctor(doctor3); - - var doctor4 = new Doctor - { - Id = 4, - PassportNumber = "D004", - LastName = "Орлова", - FirstName = "Елена", - Patronymic = "Александровна", - Gender = Gender.Female, - BirthDate = new DateOnly(1982, 11, 7), - Specializations = new List { - clinic.Specializations["Neurologist"], - clinic.Specializations["Psychiatrist"] - }, - ExperienceYears = 10 - }; - clinic.AddDoctor(doctor4); - - var doctor5 = new Doctor - { - Id = 5, - PassportNumber = "D005", - LastName = "Волков", - FirstName = "Дмитрий", - Patronymic = "Сергеевич", - Gender = Gender.Male, - BirthDate = new DateOnly(1990, 2, 14), - Specializations = new List { - clinic.Specializations["Pediatrician"], - clinic.Specializations["Dermatologist"] - }, - ExperienceYears = 5 - }; - clinic.AddDoctor(doctor5); - - var doctor6 = new Doctor - { - Id = 6, - PassportNumber = "D006", - LastName = "Никитина", - FirstName = "Ольга", - Patronymic = "Владимировна", - Gender = Gender.Female, - BirthDate = new DateOnly(1987, 8, 30), - Specializations = new List { - clinic.Specializations["Dermatologist"], - clinic.Specializations["Ophthalmologist"] - }, - ExperienceYears = 7 - }; - clinic.AddDoctor(doctor6); - - var doctor7 = new Doctor - { - Id = 7, - PassportNumber = "D007", - LastName = "Лебедев", - FirstName = "Алексей", - Patronymic = "Игоревич", - Gender = Gender.Male, - BirthDate = new DateOnly(1975, 4, 5), - Specializations = new List { - clinic.Specializations["Psychiatrist"], - clinic.Specializations["Neurologist"] - }, - ExperienceYears = 18 - }; - clinic.AddDoctor(doctor7); - - var doctor8 = new Doctor - { - Id = 8, - PassportNumber = "D008", - LastName = "Соколова", - FirstName = "Ирина", - Patronymic = "Михайловна", - Gender = Gender.Female, - BirthDate = new DateOnly(1980, 10, 19), - Specializations = new List { - clinic.Specializations["Ophthalmologist"] - }, - ExperienceYears = 11 - }; - clinic.AddDoctor(doctor8); - - var doctor9 = new Doctor - { - Id = 9, - PassportNumber = "D009", - LastName = "Козлов", - FirstName = "Михаил", - Patronymic = "Анатольевич", - Gender = Gender.Male, - BirthDate = new DateOnly(1983, 7, 22), - Specializations = new List { - clinic.Specializations["Dentist"] - }, - ExperienceYears = 9 - }; - clinic.AddDoctor(doctor9); - - var doctor10 = new Doctor - { - Id = 10, - PassportNumber = "D010", - LastName = "Григорьева", - FirstName = "Наталья", - Patronymic = "Валерьевна", - Gender = Gender.Female, - BirthDate = new DateOnly(1979, 12, 1), - Specializations = new List { - clinic.Specializations["Gynecologist"], - clinic.Specializations["Pediatrician"] - }, - ExperienceYears = 14 - }; - clinic.AddDoctor(doctor10); - - var appointment1 = new Appointment - { - Patient = patient1, - Doctor = doctor1, - DateTime = new DateTime(2025, 9, 15, 9, 0, 0), - RoomNumber = 101, - IsReturnVisit = false - }; - clinic.MakeAnAppointment(appointment1); - - var appointment2 = new Appointment - { - Patient = patient2, - Doctor = doctor2, - DateTime = new DateTime(2025, 9, 10, 10, 30, 0), - RoomNumber = 205, - IsReturnVisit = true - }; - clinic.MakeAnAppointment(appointment2); - - var appointment3 = new Appointment - { - Patient = patient3, - Doctor = doctor3, - DateTime = new DateTime(2025, 9, 25, 11, 0, 0), - RoomNumber = 102, - IsReturnVisit = false - }; - clinic.MakeAnAppointment(appointment3); - - var appointment4 = new Appointment - { - Patient = patient4, - Doctor = doctor4, - DateTime = new DateTime(2025, 9, 5, 14, 15, 0), - RoomNumber = 303, - IsReturnVisit = false - }; - clinic.MakeAnAppointment(appointment4); - - var appointment5 = new Appointment - { - Patient = patient5, - Doctor = doctor5, - DateTime = new DateTime(2025, 9, 18, 9, 30, 0), - RoomNumber = 303, - IsReturnVisit = true - }; - clinic.MakeAnAppointment(appointment5); - - var appointment6 = new Appointment - { - Patient = patient6, - Doctor = doctor6, - DateTime = new DateTime(2025, 9, 22, 15, 45, 0), - RoomNumber = 206, - IsReturnVisit = false - }; - clinic.MakeAnAppointment(appointment6); - - var appointment7 = new Appointment - { - Patient = patient7, - Doctor = doctor7, - DateTime = new DateTime(2025, 9, 2, 10, 0, 0), - RoomNumber = 303, - IsReturnVisit = true - }; - clinic.MakeAnAppointment(appointment7); - - var appointment8 = new Appointment - { - Patient = patient8, - Doctor = doctor8, - DateTime = new DateTime(2025, 9, 8, 13, 20, 0), - RoomNumber = 207, - IsReturnVisit = false - }; - clinic.MakeAnAppointment(appointment8); - - var appointment9 = new Appointment - { - Patient = patient9, - Doctor = doctor9, - DateTime = new DateTime(2025, 4, 15, 11, 30, 0), - RoomNumber = 208, - IsReturnVisit = false - }; - clinic.MakeAnAppointment(appointment9); - - var appointment10 = new Appointment - { - Patient = patient10, - Doctor = doctor10, - DateTime = new DateTime(2025, 9, 20, 16, 0, 0), - RoomNumber = 105, - IsReturnVisit = true - }; - clinic.MakeAnAppointment(appointment10); - - var appointment11 = new Appointment - { - Patient = patient3, - Doctor = doctor1, - DateTime = new DateTime(2025, 9, 25, 9, 0, 0), - RoomNumber = 101, - IsReturnVisit = true - }; - clinic.MakeAnAppointment(appointment11); - - var appointment12 = new Appointment - { - Patient = patient2, - Doctor = doctor3, - DateTime = new DateTime(2025, 9, 15, 11, 0, 0), - RoomNumber = 102, - IsReturnVisit = true - }; - clinic.MakeAnAppointment(appointment12); - - var appointment13 = new Appointment - { - Patient = patient7, - Doctor = doctor5, - DateTime = new DateTime(2025, 8, 23, 9, 0, 0), - RoomNumber = 101, - IsReturnVisit = true - }; - clinic.MakeAnAppointment(appointment13); - } -} diff --git a/Clinic/Clinic.Tests/UnitTest1.cs b/Clinic/Clinic.Tests/UnitTest1.cs deleted file mode 100644 index 6f505ebdd..000000000 --- a/Clinic/Clinic.Tests/UnitTest1.cs +++ /dev/null @@ -1,131 +0,0 @@ -using System; -using Clinic.Tests.DataSeed; -using Clinic.Models; -using Clinic.Models.Entities; - -namespace Clinic.Tests; - -/// -/// A set of integration tests verifying the business logic of the clinic system. -/// Uses the fixture to initialize test data. -/// All tests validate filtering and aggregation logic for doctors, patients, and appointments. -/// -public class ClinicTest(ClinicDataSeed clinicDataSeed) : IClassFixture -{ - private readonly ClinicInfo clinic = clinicDataSeed.clinic; - - /// - /// Verifies that only doctors with 10 or more years of experience are returned. - /// Compares the full names from the result with the expected list. - /// - [Fact] - public void GetDoctors_ReturnsDoctorsWith10OrMoreYearsOfExperience() - { - var doctorsWith10OrMoreYearsOfExperience = new List { - "Смирнов Андрей Иванович", - "Петров Сергей Викторович", - "Орлова Елена Александровна", - "Лебедев Алексей Игоревич", - "Соколова Ирина Михайловна", - "Григорьева Наталья Валерьевна" - }; - - var resDoctors = clinic.Doctors - .Where(d => d.ExperienceYears >= 10) - .Select(d => d.GetFullName()) - .ToList(); - - Assert.Equal(doctorsWith10OrMoreYearsOfExperience, resDoctors); - } - - /// - /// Ensures that all patients who have appointments with the target doctor (Id = 1) are returned. - /// Patients are selected uniquely and sorted by full name. - /// - [Fact] - public void GetTargerDoctor_ReturnsPatientsWhoHaveAppointmentWithTargetDoctor() - { - var targetDoctor = clinic.Doctors.First(d => d.Id == 1); - var patientsWhoHaveAppointmentWithTargetDocor = new List { - "Иванов Иван Иванович", - "Сидоров Алексей Петрович" - }; - - var resPatients = clinic.Appointments - .Where(d => d.Doctor == targetDoctor) - .Select(p => p.Patient) - .Distinct() - .Select(p => p.GetFullName()) - .OrderBy(n => n) - .ToList(); - - Assert.Equal(patientsWhoHaveAppointmentWithTargetDocor, resPatients); - } - - /// - /// Counts the number of return visits (IsReturnVisit = true) in the last month - /// relative to September 4, 2025. Expected count: 7. - /// - [Fact] - public void GetAppointmentInTheLastMonths_ReturnsRepeatedAppointment() - { - var countRepeatedAppointments = 7; - - var currentDate = new DateTime(2025, 9, 4); - var lastMonth = currentDate.AddMonths(-1); - - var resCount = clinic.Appointments - .Where(a => a.DateTime >= lastMonth && a.IsReturnVisit) - .Count(); - - Assert.Equal(countRepeatedAppointments, resCount); - } - - /// - /// Finds patients over 30 years old (as of November 4, 2025) who have appointments - /// with multiple doctors. Results are ordered by birth date (oldest first). - /// - [Fact] - public void GetPationsOver30_ReturnsPationsWhoHaveAppointmentWithSeveralDoctors() - { - var pationsWhoHaveAppointmentWithSeveralDoctors = new List - { - "Морозов Сергей Викторович", - "Сидоров Алексей Петрович", - "Петрова Мария Сергеевна" - }; - - var currentDate = new DateOnly(2025, 11, 4); - var age30 = currentDate.AddYears(-30); - - var resPatients = clinic.Patients - .Where(a => a.BirthDate <= age30) - .Where(p => clinic.Appointments.Count(a => a.Patient.PassportNumber == p.PassportNumber) > 1) - .OrderBy(d => d.BirthDate) - .Select(p => p.GetFullName()) - .ToList(); - - Assert.Equal(pationsWhoHaveAppointmentWithSeveralDoctors, resPatients); - } - - /// - /// Counts the number of appointments in room 303 during the last month - /// relative to September 4, 2025.. Expected count: 3. - /// - [Fact] - public void GetTargetRoom_ReturnsCountAppointmentsInTheLastMonthThatTookPlaceInTargetRoom() - { - var countAppointmentsInTheLastMonthThatTookPlaceInTargetRoom = 3; - - var targetRoom = 303; - var currentDate = new DateTime(2025, 9, 4); - var lastMonth = currentDate.AddMonths(-1); - - var resCount = clinic.Appointments - .Where(a => a.RoomNumber == targetRoom && - a.DateTime >= lastMonth) - .Count(); - - Assert.Equal(countAppointmentsInTheLastMonthThatTookPlaceInTargetRoom, resCount); - } -} From 99b29a7d11236df666b8cbd047012828d5c46ab7 Mon Sep 17 00:00:00 2001 From: maeosha Date: Thu, 27 Nov 2025 23:58:59 +0300 Subject: [PATCH 32/56] fix: update solution file to include Clinic.Api project and remove Clinic.Tests project --- Clinic/Clinic.sln | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/Clinic/Clinic.sln b/Clinic/Clinic.sln index fb0877eb2..9e4a8919d 100644 --- a/Clinic/Clinic.sln +++ b/Clinic/Clinic.sln @@ -1,25 +1,46 @@ + Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.5.2.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Clinic.Models", "Clinic.Models\Clinic.Models.csproj", "{5351F009-4016-C1FF-B495-C469DDDFBE5E}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Clinic.Tests", "Clinic.Tests\Clinic.Tests.csproj", "{74F8AF9E-B8C9-B2D3-9764-9BF8E80D866D}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Clinic.Api", "Clinic.Api\Clinic.Api.csproj", "{7433D860-E79F-44AA-BA33-9E0F5901578D}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {5351F009-4016-C1FF-B495-C469DDDFBE5E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {5351F009-4016-C1FF-B495-C469DDDFBE5E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5351F009-4016-C1FF-B495-C469DDDFBE5E}.Debug|x64.ActiveCfg = Debug|Any CPU + {5351F009-4016-C1FF-B495-C469DDDFBE5E}.Debug|x64.Build.0 = Debug|Any CPU + {5351F009-4016-C1FF-B495-C469DDDFBE5E}.Debug|x86.ActiveCfg = Debug|Any CPU + {5351F009-4016-C1FF-B495-C469DDDFBE5E}.Debug|x86.Build.0 = Debug|Any CPU {5351F009-4016-C1FF-B495-C469DDDFBE5E}.Release|Any CPU.ActiveCfg = Release|Any CPU {5351F009-4016-C1FF-B495-C469DDDFBE5E}.Release|Any CPU.Build.0 = Release|Any CPU - {74F8AF9E-B8C9-B2D3-9764-9BF8E80D866D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {74F8AF9E-B8C9-B2D3-9764-9BF8E80D866D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {74F8AF9E-B8C9-B2D3-9764-9BF8E80D866D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {74F8AF9E-B8C9-B2D3-9764-9BF8E80D866D}.Release|Any CPU.Build.0 = Release|Any CPU + {5351F009-4016-C1FF-B495-C469DDDFBE5E}.Release|x64.ActiveCfg = Release|Any CPU + {5351F009-4016-C1FF-B495-C469DDDFBE5E}.Release|x64.Build.0 = Release|Any CPU + {5351F009-4016-C1FF-B495-C469DDDFBE5E}.Release|x86.ActiveCfg = Release|Any CPU + {5351F009-4016-C1FF-B495-C469DDDFBE5E}.Release|x86.Build.0 = Release|Any CPU + {7433D860-E79F-44AA-BA33-9E0F5901578D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7433D860-E79F-44AA-BA33-9E0F5901578D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7433D860-E79F-44AA-BA33-9E0F5901578D}.Debug|x64.ActiveCfg = Debug|Any CPU + {7433D860-E79F-44AA-BA33-9E0F5901578D}.Debug|x64.Build.0 = Debug|Any CPU + {7433D860-E79F-44AA-BA33-9E0F5901578D}.Debug|x86.ActiveCfg = Debug|Any CPU + {7433D860-E79F-44AA-BA33-9E0F5901578D}.Debug|x86.Build.0 = Debug|Any CPU + {7433D860-E79F-44AA-BA33-9E0F5901578D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7433D860-E79F-44AA-BA33-9E0F5901578D}.Release|Any CPU.Build.0 = Release|Any CPU + {7433D860-E79F-44AA-BA33-9E0F5901578D}.Release|x64.ActiveCfg = Release|Any CPU + {7433D860-E79F-44AA-BA33-9E0F5901578D}.Release|x64.Build.0 = Release|Any CPU + {7433D860-E79F-44AA-BA33-9E0F5901578D}.Release|x86.ActiveCfg = Release|Any CPU + {7433D860-E79F-44AA-BA33-9E0F5901578D}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From 42861b1d445cdb81f38e43a1545a9c09596c5628 Mon Sep 17 00:00:00 2001 From: maeosha Date: Sun, 30 Nov 2025 19:59:23 +0300 Subject: [PATCH 33/56] fix: remove custom mapping for DateTime in GetAppointmentDto --- Clinic/Clinic.Api/MappingProfile/MappingProfile.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Clinic/Clinic.Api/MappingProfile/MappingProfile.cs b/Clinic/Clinic.Api/MappingProfile/MappingProfile.cs index 494718ff6..bad2df911 100644 --- a/Clinic/Clinic.Api/MappingProfile/MappingProfile.cs +++ b/Clinic/Clinic.Api/MappingProfile/MappingProfile.cs @@ -46,8 +46,7 @@ public MappingProfile() CreateMap(); - CreateMap() - .ForMember(dest => dest.DateTime, opt => opt.MapFrom(src => DateOnly.FromDateTime(src.DateTime))); + CreateMap(); CreateMap(); From 3a6d5da1b04b07af0817c189fd9784a81e57b8a6 Mon Sep 17 00:00:00 2001 From: maeosha Date: Sun, 7 Dec 2025 00:34:00 +0300 Subject: [PATCH 34/56] feat: add service interfaces for appointment, doctor, patient, and specialization management in the Clinic API --- .../Services/IAppointmentService.cs | 26 ++++++++++ .../Interfaces/Services/IBaseService.cs | 51 +++++++++++++++++++ .../Interfaces/Services/IDoctorService.cs | 12 +++++ .../Interfaces/Services/IPatientService.cs | 11 ++++ .../Services/ISpecializationService.cs | 13 +++++ 5 files changed, 113 insertions(+) create mode 100644 Clinic/Clinic.Api/Interfaces/Services/IAppointmentService.cs create mode 100644 Clinic/Clinic.Api/Interfaces/Services/IBaseService.cs create mode 100644 Clinic/Clinic.Api/Interfaces/Services/IDoctorService.cs create mode 100644 Clinic/Clinic.Api/Interfaces/Services/IPatientService.cs create mode 100644 Clinic/Clinic.Api/Interfaces/Services/ISpecializationService.cs diff --git a/Clinic/Clinic.Api/Interfaces/Services/IAppointmentService.cs b/Clinic/Clinic.Api/Interfaces/Services/IAppointmentService.cs new file mode 100644 index 000000000..3d4be2d62 --- /dev/null +++ b/Clinic/Clinic.Api/Interfaces/Services/IAppointmentService.cs @@ -0,0 +1,26 @@ +using Clinic.Api.DTOs.Appointment; +using Clinic.Api.Interfaces; + +namespace Clinic.Api.Interfaces.Services; + +/// +/// Interface for appointment service operations. +/// Provides methods for managing appointments in the clinic system. +/// +public interface IAppointmentServices : IBaseServices +{ + /// + /// Retrieves all appointments for a specific doctor. + /// + /// The identifier of the doctor. + /// A collection of appointment DTOs if the doctor exists; otherwise, null. + public IReadOnlyCollection? GetAppointmentsByDoctor(int doctorId); + + /// + /// Retrieves all appointments for a specific patient. + /// + /// The identifier of the patient. + /// A collection of appointment DTOs if the patient exists; otherwise, null. + public IReadOnlyCollection? GetAppointmentsByPatient(int patientId); +} + diff --git a/Clinic/Clinic.Api/Interfaces/Services/IBaseService.cs b/Clinic/Clinic.Api/Interfaces/Services/IBaseService.cs new file mode 100644 index 000000000..c2e5a5728 --- /dev/null +++ b/Clinic/Clinic.Api/Interfaces/Services/IBaseService.cs @@ -0,0 +1,51 @@ +namespace Clinic.Api.Interfaces.Services; + +/// +/// Generic interface for application services that provides CRUD operations. +/// This interface can be used to type all service functions with specific DTOs and entities. +/// +/// The entity type used in the database layer. +/// The DTO type for retrieving entities. +/// The DTO type for creating new entities. +/// The DTO type for updating existing entities. +public interface IBaseServices + where TGetDto : class + where TCreateDto : class + where TUpdateDto : class +{ + /// + /// Retrieves all entities from the database and maps them to DTOs. + /// + /// A read-only collection of DTOs representing all entities. + public IReadOnlyCollection GetAll(); + + /// + /// Retrieves a single entity by its identifier. + /// + /// The identifier of the entity to retrieve. + /// The entity as a DTO if found; otherwise, null. + public TGetDto? Get(int id); + + /// + /// Creates a new entity in the database. + /// + /// The DTO containing entity creation data. + /// The created entity as a DTO if successful; otherwise, null. + public TGetDto? Create(TCreateDto createDto); + + /// + /// Updates an existing entity with the given identifier. + /// + /// The identifier of the entity to update. + /// The DTO containing updated entity data. + /// The updated entity as a DTO if successful; otherwise, null. + public TGetDto? Update(int id, TUpdateDto updateDto); + + /// + /// Deletes an entity from the database by its identifier. + /// + /// The identifier of the entity to delete. + /// True if the entity was successfully deleted; otherwise, false. + public bool Delete(int id); +} + diff --git a/Clinic/Clinic.Api/Interfaces/Services/IDoctorService.cs b/Clinic/Clinic.Api/Interfaces/Services/IDoctorService.cs new file mode 100644 index 000000000..bfe61b9fb --- /dev/null +++ b/Clinic/Clinic.Api/Interfaces/Services/IDoctorService.cs @@ -0,0 +1,12 @@ +using Clinic.Api.DTOs.DoctorDto; + +namespace Clinic.Api.Interfaces.Services; + +/// +/// Interface for doctor service operations. +/// Provides methods for managing doctors in the clinic system. +/// +public interface IDoctorServices : IBaseServices +{ +} + diff --git a/Clinic/Clinic.Api/Interfaces/Services/IPatientService.cs b/Clinic/Clinic.Api/Interfaces/Services/IPatientService.cs new file mode 100644 index 000000000..2bff86934 --- /dev/null +++ b/Clinic/Clinic.Api/Interfaces/Services/IPatientService.cs @@ -0,0 +1,11 @@ +using Clinic.Api.DTOs.PatientDto; + +namespace Clinic.Api.Interfaces.Services; + +/// +/// Interface for patient service operations. +/// Provides methods for managing patients in the clinic system. +/// +public interface IPatientServices : IBaseServices +{ +} \ No newline at end of file diff --git a/Clinic/Clinic.Api/Interfaces/Services/ISpecializationService.cs b/Clinic/Clinic.Api/Interfaces/Services/ISpecializationService.cs new file mode 100644 index 000000000..e2efe360e --- /dev/null +++ b/Clinic/Clinic.Api/Interfaces/Services/ISpecializationService.cs @@ -0,0 +1,13 @@ +using Clinic.Api.DTOs.SpecializationDto; + +namespace Clinic.Api.Interfaces.Services; + +/// +/// Interface for specialization service operations. +/// Provides methods for managing specializations in the clinic system. +/// Note: Specializations do not support update operations. +/// +public interface ISpecializationServices : IBaseServices +{ +} + From 20bb9677fed2e62f078e1014f0a83066b71c9a79 Mon Sep 17 00:00:00 2001 From: maeosha Date: Sun, 7 Dec 2025 00:34:30 +0300 Subject: [PATCH 35/56] refactor: implement service interfaces for Appointment, Doctor, Patient, and Specialization services, enhancing method naming and documentation --- .../Services/AppointmentServices.cs | 44 ++++++++++++++++--- Clinic/Clinic.Api/Services/DoctorServices.cs | 21 ++++----- Clinic/Clinic.Api/Services/PatientServices.cs | 13 +++--- .../Services/SpecializationServices.cs | 38 +++++++++++----- 4 files changed, 84 insertions(+), 32 deletions(-) diff --git a/Clinic/Clinic.Api/Services/AppointmentServices.cs b/Clinic/Clinic.Api/Services/AppointmentServices.cs index 52498dd26..314780608 100644 --- a/Clinic/Clinic.Api/Services/AppointmentServices.cs +++ b/Clinic/Clinic.Api/Services/AppointmentServices.cs @@ -2,6 +2,7 @@ using Clinic.Api.DataBase; using Clinic.Api.DTOs.Appointment; using Clinic.Models.Entities; +using Clinic.Api.Interfaces.Services; namespace Clinic.Api.Services; @@ -10,7 +11,7 @@ namespace Clinic.Api.Services; /// Provides methods for creating, updating, retrieving, and deleting appointments. /// Handles mapping between DTOs and entity models, and interacts with the appointment database. /// -public class AppointmentServices +public class AppointmentServices : IAppointmentServices { private readonly IClinicDataBase _db; private readonly IMapper _mapper; @@ -32,12 +33,17 @@ public AppointmentServices(IClinicDataBase db, IMapper mapper) /// Retrieves all appointments from the database. /// /// A read-only collection of appointment DTOs. - public IReadOnlyCollection GetAllAppointments() + public IReadOnlyCollection GetAll() { var appointments = _db.GetAllAppointments(); return _mapper.Map>(appointments); } + /// + /// Retrieves all appointments for a specific doctor. + /// + /// The identifier of the doctor. + /// A collection of appointment DTOs if the doctor exists; otherwise, null. public IReadOnlyCollection? GetAppointmentsByDoctor(int doctorId) { var doctor = _db.GetDoctor(doctorId); @@ -49,6 +55,11 @@ public IReadOnlyCollection GetAllAppointments() return _mapper.Map>(appointments); } + /// + /// Retrieves all appointments for a specific patient. + /// + /// The identifier of the patient. + /// A collection of appointment DTOs if the patient exists; otherwise, null. public IReadOnlyCollection? GetAppointmentsByPatient(int patientId) { var patient = _db.GetPatient(patientId); @@ -60,13 +71,23 @@ public IReadOnlyCollection GetAllAppointments() return _mapper.Map>(appointments); } - public GetAppointmentDto? GetAppointment(int id) + /// + /// Retrieves a single appointment by its identifier. + /// + /// The identifier of the appointment to retrieve. + /// The appointment as a DTO if found; otherwise, null. + public GetAppointmentDto? Get(int id) { var appointment = _db.GetAppointment(id); return appointment == null ? null : _mapper.Map(appointment); } - public GetAppointmentDto? CreateAppointment(CreateAppointmentDto dto) + /// + /// Creates a new appointment entity in the database. + /// + /// The DTO containing appointment creation data. + /// The created appointment as a DTO if successful; otherwise, null. + public GetAppointmentDto? Create(CreateAppointmentDto dto) { var appointment = _mapper.Map(dto); appointment.Id = _appointmentId; @@ -80,7 +101,13 @@ public IReadOnlyCollection GetAllAppointments() return _mapper.Map(appointment); } - public GetAppointmentDto? UpdateAppointment(int id, UpdateAppointmentDto dto) + /// + /// Updates an existing appointment with the given identifier. + /// + /// The identifier of the appointment to update. + /// The DTO containing updated appointment data. + /// The updated appointment as a DTO if successful; otherwise, null. + public GetAppointmentDto? Update(int id, UpdateAppointmentDto dto) { var appointment = _db.GetAppointment(id); if (appointment == null) @@ -94,7 +121,12 @@ public IReadOnlyCollection GetAllAppointments() return _mapper.Map(appointment); } - public bool DeleteAppointment(int id) + /// + /// Deletes an appointment from the database by its identifier. + /// + /// The identifier of the appointment to delete. + /// True if the appointment was successfully deleted; otherwise, false. + public bool Delete(int id) { if (!_db.RemoveAppointment(id)) { diff --git a/Clinic/Clinic.Api/Services/DoctorServices.cs b/Clinic/Clinic.Api/Services/DoctorServices.cs index 76a403325..e11f99a64 100644 --- a/Clinic/Clinic.Api/Services/DoctorServices.cs +++ b/Clinic/Clinic.Api/Services/DoctorServices.cs @@ -2,6 +2,7 @@ using Clinic.Api.DataBase; using Clinic.Api.DTOs.DoctorDto; using Clinic.Models.Entities; +using Clinic.Api.Interfaces.Services; namespace Clinic.Api.Services; @@ -11,11 +12,11 @@ namespace Clinic.Api.Services; /// as well as functions to get all doctors from the underlying database. /// Uses AutoMapper for mapping between entity and DTO objects. /// -public class DoctorServices +public class DoctorServices : IDoctorServices { private readonly IClinicDataBase _db; private readonly IMapper _mapper; - private int doctorId; + private int _doctorId; /// /// Initializes a new instance of the class. @@ -27,14 +28,14 @@ public DoctorServices(IClinicDataBase db, IMapper mapper) { _db = db; _mapper = mapper; - doctorId = _db.DoctorCount() + 1; + _doctorId = _db.DoctorCount() + 1; } /// /// Retrieves all doctors from the database and maps them to DTOs. /// /// A collection of representing doctors. - public IReadOnlyCollection GetAllDoctors() + public IReadOnlyCollection GetAll() { var doctors = _db.GetAllDoctors(); var doctorsDto = _mapper.Map>(doctors); @@ -46,10 +47,10 @@ public IReadOnlyCollection GetAllDoctors() /// /// The DTO containing doctor creation data. /// The created doctor as a if successful; otherwise, null. - public GetDoctorDto? CreateDoctor(CreateDoctorDto createDoctorDto) + public GetDoctorDto? Create(CreateDoctorDto createDoctorDto) { var doctor = _mapper.Map(createDoctorDto); - doctor.Id = doctorId; + doctor.Id = _doctorId; if (!_db.AddDoctor(doctor)) { return null; @@ -62,7 +63,7 @@ public IReadOnlyCollection GetAllDoctors() /// /// The doctor identifier. /// A if found; otherwise, null. - public GetDoctorDto? GetDoctor(int id) + public GetDoctorDto? Get(int id) { var doctor = _db.GetDoctor(id); if (doctor == null) @@ -79,7 +80,7 @@ public IReadOnlyCollection GetAllDoctors() /// The doctor identifier. /// DTO with updated doctor details. /// The updated doctor as a if successful; otherwise, null. - public GetDoctorDto? UpdateDoctor(int id, UpdateDoctorDto updateDoctorDto) + public GetDoctorDto? Update(int id, UpdateDoctorDto updateDoctorDto) { var doctor = _db.GetDoctor(id); if (doctor == null) @@ -98,13 +99,13 @@ public IReadOnlyCollection GetAllDoctors() /// /// The doctor identifier to delete. /// True if the doctor was successfully deleted; otherwise, false. - public bool DeleteDoctor(int id) + public bool Delete(int id) { if (!_db.RemoveDoctor(id)) { return false; } - doctorId--; + _doctorId--; return true; } } \ No newline at end of file diff --git a/Clinic/Clinic.Api/Services/PatientServices.cs b/Clinic/Clinic.Api/Services/PatientServices.cs index cc6d29a98..9e80860f9 100644 --- a/Clinic/Clinic.Api/Services/PatientServices.cs +++ b/Clinic/Clinic.Api/Services/PatientServices.cs @@ -2,6 +2,7 @@ using Clinic.Api.DataBase; using Clinic.Api.DTOs.PatientDto; using Clinic.Models.Entities; +using Clinic.Api.Interfaces.Services; namespace Clinic.Api.Services; @@ -10,7 +11,7 @@ namespace Clinic.Api.Services; /// Provides methods for creating, retrieving, updating, and deleting patients, /// as well as listing all patients. Uses AutoMapper for entity-DTO mapping. /// -public class PatientServices +public class PatientServices : IPatientServices { private readonly IClinicDataBase _db; private readonly IMapper _mapper; @@ -33,7 +34,7 @@ public PatientServices(IClinicDataBase db, IMapper mapper) /// Retrieves all patients from the database and maps them to DTOs. /// /// A collection of representing all patients. - public IReadOnlyCollection GetAllPatients() + public IReadOnlyCollection GetAll() { var patients = _db.GetAllPatients(); var patientsDto = _mapper.Map>(patients); @@ -45,7 +46,7 @@ public IReadOnlyCollection GetAllPatients() /// /// The DTO containing patient creation data. /// The created patient as a if successful; otherwise, null. - public GetPatientDto? CreatePatient(CreatePatientDto patientCreateDto) + public GetPatientDto? Create(CreatePatientDto patientCreateDto) { var patient = _mapper.Map(patientCreateDto); patient.Id = _patientId; @@ -64,7 +65,7 @@ public IReadOnlyCollection GetAllPatients() /// The identifier of the patient to update. /// The DTO containing updated patient data. /// The updated patient as a if successful; otherwise, null. - public GetPatientDto? UpdatePatient(int id, UpdatePatientDto patientUpdateDto) + public GetPatientDto? Update(int id, UpdatePatientDto patientUpdateDto) { var patient = _db.GetPatient(id); if (patient == null) @@ -83,7 +84,7 @@ public IReadOnlyCollection GetAllPatients() /// /// The identifier of the patient to retrieve. /// The patient as a if found; otherwise, null. - public GetPatientDto? GetPatient(int id) + public GetPatientDto? Get(int id) { var patient = _db.GetPatient(id); if (patient == null) @@ -99,7 +100,7 @@ public IReadOnlyCollection GetAllPatients() /// /// The identifier of the patient to delete. /// True if the patient was deleted; otherwise, false. - public bool DeletePatient(int id) + public bool Delete(int id) { if (!_db.RemovePatient(id)) { diff --git a/Clinic/Clinic.Api/Services/SpecializationServices.cs b/Clinic/Clinic.Api/Services/SpecializationServices.cs index d8a6b14a1..9ca7953c0 100644 --- a/Clinic/Clinic.Api/Services/SpecializationServices.cs +++ b/Clinic/Clinic.Api/Services/SpecializationServices.cs @@ -2,6 +2,7 @@ using Clinic.Models.Entities; using Clinic.Api.DataBase; using Clinic.Api.DTOs.SpecializationDto; +using Clinic.Api.Interfaces.Services; namespace Clinic.Api.Services; @@ -9,11 +10,11 @@ namespace Clinic.Api.Services; /// Service class for managing specialization-related operations in the Clinic API. /// Provides methods to create, retrieve, and delete specializations, and maps entity objects to DTOs. /// -public class SpecializationServices +public class SpecializationServices : ISpecializationServices { private readonly IClinicDataBase _db; private readonly IMapper _mapper; - private int specializationId; + private int _specializationId; /// /// Initializes a new instance of the class. @@ -25,29 +26,46 @@ public SpecializationServices(IClinicDataBase db, IMapper mapper) { _db = db; _mapper = mapper; - specializationId = _db.SpecializationCount() + 1; + _specializationId = _db.SpecializationCount() + 1; } /// /// Retrieves all specializations from the database and maps them to DTOs. /// /// A collection of representing specializations. - public IReadOnlyCollection GetAllSpecializations() + public IReadOnlyCollection GetAll() { var specializations = _db.GetAllSpecializations(); var specializationDtos = _mapper.Map>(specializations); return specializationDtos; } + /// + /// Updates an existing specialization with the given identifier. + /// + /// The identifier of the specialization to update. + /// The DTO containing updated specialization data. + /// The updated specialization as a DTO if successful; otherwise, null. + public GetSpecializationDto? Update(int id, UpdateSpecializationDto updateSpecializationDto) + { + var specialization = _mapper.Map(updateSpecializationDto); + var updatedSpecialization = _db.UpdateSpecialization(id, specialization); + if (updatedSpecialization == null) + { + return null; + } + return _mapper.Map(updatedSpecialization); + } + /// /// Creates a new specialization entity in the database. /// /// The DTO containing specialization creation data. /// The created specialization as a if successful; otherwise, null. - public GetSpecializationDto? CreateSpecialization(CreateSpecializationDto createSpecializationDto) + public GetSpecializationDto? Create(CreateSpecializationDto createSpecializationDto) { var specialization = _mapper.Map(createSpecializationDto); - specialization.Id = specializationId; + specialization.Id = _specializationId; if (!_db.AddSpecialization(specialization)) { @@ -55,9 +73,9 @@ public IReadOnlyCollection GetAllSpecializations() } var specializationDto = _mapper.Map(specialization); - specializationDto.Id = specializationId; + specializationDto.Id = _specializationId; - specializationId++; + _specializationId++; return specializationDto; } @@ -67,7 +85,7 @@ public IReadOnlyCollection GetAllSpecializations() /// /// The specialization identifier. /// A if found; otherwise, null. - public GetSpecializationDto? GetSpecialization(int id) + public GetSpecializationDto? Get(int id) { var specialization = _db.GetSpecialization(id); if (specialization == null) @@ -82,7 +100,7 @@ public IReadOnlyCollection GetAllSpecializations() /// /// The specialization identifier. /// True if the specialization was deleted; otherwise, false. - public bool DeleteSpecialization(int id) + public bool Delete(int id) { return _db.RemoveSpecialization(id); } From a72bf0be4e1512b40051978e85ab2452cc1f98db Mon Sep 17 00:00:00 2001 From: maeosha Date: Sun, 7 Dec 2025 00:34:59 +0300 Subject: [PATCH 36/56] feat: add base and specific controller interfaces for appointment, doctor, patient, and specialization management in the Clinic API --- .../Specialization/UpdateSpecializationDto.cs | 11 ++++ .../Controllers/IAppointmentController.cs | 26 ++++++++++ .../Interfaces/Controllers/IBaseController.cs | 51 +++++++++++++++++++ .../Controllers/IDoctorController.cs | 11 ++++ .../Controllers/IPatientController.cs | 11 ++++ .../Controllers/ISpecializationController.cs | 11 ++++ 6 files changed, 121 insertions(+) create mode 100644 Clinic/Clinic.Api/DTOs/Specialization/UpdateSpecializationDto.cs create mode 100644 Clinic/Clinic.Api/Interfaces/Controllers/IAppointmentController.cs create mode 100644 Clinic/Clinic.Api/Interfaces/Controllers/IBaseController.cs create mode 100644 Clinic/Clinic.Api/Interfaces/Controllers/IDoctorController.cs create mode 100644 Clinic/Clinic.Api/Interfaces/Controllers/IPatientController.cs create mode 100644 Clinic/Clinic.Api/Interfaces/Controllers/ISpecializationController.cs diff --git a/Clinic/Clinic.Api/DTOs/Specialization/UpdateSpecializationDto.cs b/Clinic/Clinic.Api/DTOs/Specialization/UpdateSpecializationDto.cs new file mode 100644 index 000000000..4e01fd732 --- /dev/null +++ b/Clinic/Clinic.Api/DTOs/Specialization/UpdateSpecializationDto.cs @@ -0,0 +1,11 @@ +namespace Clinic.Api.DTOs.SpecializationDto; + +/// +/// DTO for updating an existing specialization. +/// Note: Currently not used as specializations do not support update operations. +/// +public class UpdateSpecializationDto +{ + public string? Name { get; set; } +} + diff --git a/Clinic/Clinic.Api/Interfaces/Controllers/IAppointmentController.cs b/Clinic/Clinic.Api/Interfaces/Controllers/IAppointmentController.cs new file mode 100644 index 000000000..426ac2a6e --- /dev/null +++ b/Clinic/Clinic.Api/Interfaces/Controllers/IAppointmentController.cs @@ -0,0 +1,26 @@ +using Microsoft.AspNetCore.Mvc; +using Clinic.Api.DTOs.Appointment; + +namespace Clinic.Api.Interfaces.Controllers; + +/// +/// Interface for appointment controller operations. +/// Extends the base controller interface with appointment-specific methods. +/// +public interface IAppointmentController : IBaseController +{ + /// + /// Gets all appointments for a specific doctor. + /// + /// The doctor's id. + /// ActionResult containing appointments or NotFound if doctor not found. + public IActionResult GetByDoctor(int doctorId); + + /// + /// Gets all appointments for a specific patient. + /// + /// The patient's id. + /// ActionResult containing appointments or NotFound if patient not found. + public IActionResult GetByPatient(int patientId); +} + diff --git a/Clinic/Clinic.Api/Interfaces/Controllers/IBaseController.cs b/Clinic/Clinic.Api/Interfaces/Controllers/IBaseController.cs new file mode 100644 index 000000000..c6b7fbb24 --- /dev/null +++ b/Clinic/Clinic.Api/Interfaces/Controllers/IBaseController.cs @@ -0,0 +1,51 @@ +using Microsoft.AspNetCore.Mvc; + +namespace Clinic.Api.Interfaces.Controllers; + +/// +/// Base generic interface for controllers providing standard CRUD operations. +/// +/// DTO type for retrieving entity data. +/// DTO type for creating a new entity. +/// DTO type for updating an existing entity. +public interface IBaseController + where TGetDto : class + where TCreateDto : class + where TUpdateDto : class +{ + /// + /// Gets all entities. + /// + /// ActionResult containing a list of all entities. + public IActionResult GetAll(); + + /// + /// Gets a specific entity by its id. + /// + /// The id of the entity. + /// ActionResult containing the entity or NotFound if not found. + public IActionResult Get(int id); + + /// + /// Creates a new entity. + /// + /// The creation data. + /// ActionResult containing the created entity or BadRequest if creation fails. + public IActionResult Create(TCreateDto dto); + + /// + /// Updates an existing entity. + /// + /// The id of the entity to update. + /// The update data. + /// ActionResult containing the updated entity or NotFound if not found. + public IActionResult Update(int id, TUpdateDto dto); + + /// + /// Deletes an entity by its id. + /// + /// The id of the entity to delete. + /// ActionResult indicating success or NotFound if not found. + public IActionResult Delete(int id); +} + diff --git a/Clinic/Clinic.Api/Interfaces/Controllers/IDoctorController.cs b/Clinic/Clinic.Api/Interfaces/Controllers/IDoctorController.cs new file mode 100644 index 000000000..fff7b3211 --- /dev/null +++ b/Clinic/Clinic.Api/Interfaces/Controllers/IDoctorController.cs @@ -0,0 +1,11 @@ +using Clinic.Api.DTOs.DoctorDto; + +namespace Clinic.Api.Interfaces.Controllers; + +/// +/// Interface for doctor controller operations. +/// +public interface IDoctorController : IBaseController +{ +} + diff --git a/Clinic/Clinic.Api/Interfaces/Controllers/IPatientController.cs b/Clinic/Clinic.Api/Interfaces/Controllers/IPatientController.cs new file mode 100644 index 000000000..9fd8ecc4c --- /dev/null +++ b/Clinic/Clinic.Api/Interfaces/Controllers/IPatientController.cs @@ -0,0 +1,11 @@ +using Clinic.Api.DTOs.PatientDto; + +namespace Clinic.Api.Interfaces.Controllers; + +/// +/// Interface for patient controller operations. +/// +public interface IPatientController : IBaseController +{ +} + diff --git a/Clinic/Clinic.Api/Interfaces/Controllers/ISpecializationController.cs b/Clinic/Clinic.Api/Interfaces/Controllers/ISpecializationController.cs new file mode 100644 index 000000000..74cc7b454 --- /dev/null +++ b/Clinic/Clinic.Api/Interfaces/Controllers/ISpecializationController.cs @@ -0,0 +1,11 @@ +using Clinic.Api.DTOs.SpecializationDto; + +namespace Clinic.Api.Interfaces.Controllers; + +/// +/// Interface for specialization controller operations. +/// +public interface ISpecializationController : IBaseController +{ +} + From 4e4806571672d0a9588841e00eaec646f90d4e6b Mon Sep 17 00:00:00 2001 From: maeosha Date: Sun, 7 Dec 2025 00:35:22 +0300 Subject: [PATCH 37/56] feat: add UpdateSpecialization method to ClinicDataBase and update interface to include specialization update functionality --- Clinic/Clinic.Api/DataBase/ClinicDataBase.cs | 8 +++ Clinic/Clinic.Api/DataBase/IClinicDataBase.cs | 57 +++++++++++-------- 2 files changed, 40 insertions(+), 25 deletions(-) diff --git a/Clinic/Clinic.Api/DataBase/ClinicDataBase.cs b/Clinic/Clinic.Api/DataBase/ClinicDataBase.cs index 91e924d9a..f62adf14e 100644 --- a/Clinic/Clinic.Api/DataBase/ClinicDataBase.cs +++ b/Clinic/Clinic.Api/DataBase/ClinicDataBase.cs @@ -66,6 +66,14 @@ public bool AddSpecialization(Specialization specialization) public int SpecializationCount() => _specializations.Count; + public Specialization? UpdateSpecialization(int id, Specialization specialization){ + if (!_specializations.ContainsKey(id)){ + return null; + } + _specializations[id].Name = specialization.Name; + return _specializations[id]; + } + public Doctor? GetDoctor(int Id) => _doctors.GetValueOrDefault(Id); diff --git a/Clinic/Clinic.Api/DataBase/IClinicDataBase.cs b/Clinic/Clinic.Api/DataBase/IClinicDataBase.cs index 6db878faf..ef88ac6b4 100644 --- a/Clinic/Clinic.Api/DataBase/IClinicDataBase.cs +++ b/Clinic/Clinic.Api/DataBase/IClinicDataBase.cs @@ -13,165 +13,172 @@ public interface IClinicDataBase /// /// The patient's ID. /// The patient if found; otherwise, null. - Patient? GetPatient(int Id); + public Patient? GetPatient(int Id); /// /// Gets all patients in the database. /// /// A read-only collection of all patients. - IReadOnlyCollection GetAllPatients(); + public IReadOnlyCollection GetAllPatients(); /// /// Adds a new patient to the database. /// /// The patient to add. /// True if added successfully; otherwise, false. - bool AddPatient(Patient patient); + public bool AddPatient(Patient patient); /// /// Updates an existing patient's details. /// /// The patient with updated information. /// True if updated successfully; otherwise, false. - bool UpdatePatient(Patient patient); + public bool UpdatePatient(Patient patient); /// /// Removes a patient by their ID. /// /// The patient's ID to remove. /// True if removed successfully; otherwise, false. - bool RemovePatient(int Id); + public bool RemovePatient(int Id); /// /// Gets the total number of patients. /// /// The patient count. - int PatientCount(); + public int PatientCount(); /// /// Retrieves all specializations in the system. /// /// A read-only collection of all specializations. - IReadOnlyCollection GetAllSpecializations(); + public IReadOnlyCollection GetAllSpecializations(); /// /// Adds a new specialization. /// /// The specialization to add. /// True if added; false if already exists. - bool AddSpecialization(Specialization specialization); + public bool AddSpecialization(Specialization specialization); + + /// + /// Updates an existing specialization. + /// + /// The specialization with updated information. + /// True if updated; otherwise, false. + public Specialization? UpdateSpecialization(int id, Specialization specialization); /// /// Retrieves a specialization by its unique identifier. /// /// The specialization's ID. /// The specialization if found; otherwise, null. - Specialization? GetSpecialization(int id); + public Specialization? GetSpecialization(int id); /// /// Removes a specialization by its ID. /// /// The specialization's ID. /// True if removed; otherwise, false. - bool RemoveSpecialization(int id); + public bool RemoveSpecialization(int id); /// /// Gets the total specialization count. /// /// The number of specializations. - int SpecializationCount(); + public int SpecializationCount(); /// /// Retrieves a doctor by their unique identifier. /// /// The doctor's ID. /// The doctor if found; otherwise, null. - Doctor? GetDoctor(int Id); + public Doctor? GetDoctor(int Id); /// /// Gets all doctors. /// /// A read-only collection of all doctors. - IReadOnlyCollection GetAllDoctors(); + public IReadOnlyCollection GetAllDoctors(); /// /// Adds a new doctor. /// /// The doctor to add. /// True if added; otherwise, false. - bool AddDoctor(Doctor doctor); + public bool AddDoctor(Doctor doctor); /// /// Updates details for an existing doctor. /// /// The doctor with updated details. /// True if updated; otherwise, false. - bool UpdateDoctor(Doctor doctor); + public bool UpdateDoctor(Doctor doctor); /// /// Removes a doctor by their ID. /// /// The doctor's ID. /// True if removed; otherwise, false. - bool RemoveDoctor(int Id); + public bool RemoveDoctor(int Id); /// /// Gets the total doctor count. /// /// The number of doctors. - int DoctorCount(); + public int DoctorCount(); /// /// Retrieves an appointment by its unique identifier. /// /// The appointment's ID. /// The appointment if found; otherwise, null. - Appointment? GetAppointment(int Id); + public Appointment? GetAppointment(int Id); /// /// Gets all appointments. /// /// A read-only collection of all appointments. - IReadOnlyCollection GetAllAppointments(); + public IReadOnlyCollection GetAllAppointments(); /// /// Gets all appointments for a specific doctor. /// /// The doctor's ID. /// A collection of appointments for the doctor. - IReadOnlyCollection GetAppointmentsByDoctor(int Id); + public IReadOnlyCollection GetAppointmentsByDoctor(int Id); /// /// Gets all appointments for a specific patient. /// /// The patient's ID. /// A collection of appointments for the patient. - IReadOnlyCollection GetAppointmentsByPatient(int Id); + public IReadOnlyCollection GetAppointmentsByPatient(int Id); /// /// Adds a new appointment. /// /// The appointment to add. /// True if added; otherwise, false. - bool AddAppointment(Appointment appointment); + public bool AddAppointment(Appointment appointment); /// /// Updates an existing appointment. /// /// The appointment with updated details. /// True if updated; otherwise, false. - bool UpdateAppointment(Appointment appointment); + public bool UpdateAppointment(Appointment appointment); /// /// Removes an appointment by its ID. /// /// The appointment's ID. /// True if removed; otherwise, false. - bool RemoveAppointment(int Id); + public bool RemoveAppointment(int Id); /// /// Gets the total appointment count. /// /// The number of appointments. - int AppointmentCount(); + public int AppointmentCount(); } \ No newline at end of file From 65a26b1f65f5ba261ba568220a6d77cf4bee88e2 Mon Sep 17 00:00:00 2001 From: maeosha Date: Sun, 7 Dec 2025 00:35:31 +0300 Subject: [PATCH 38/56] refactor: implement base controller for appointment, doctor, patient, and specialization management, consolidating common CRUD operations and enhancing code reusability --- .../Controllers/AppointmentControllers.cs | 102 ++------------- .../Clinic.Api/Controllers/BaseController.cs | 118 ++++++++++++++++++ .../Controllers/DoctorControllers.cs | 85 +------------ .../Controllers/PatientControllers.cs | 90 +------------ .../Controllers/SpecializationControllers.cs | 73 +---------- 5 files changed, 149 insertions(+), 319 deletions(-) create mode 100644 Clinic/Clinic.Api/Controllers/BaseController.cs diff --git a/Clinic/Clinic.Api/Controllers/AppointmentControllers.cs b/Clinic/Clinic.Api/Controllers/AppointmentControllers.cs index 8cb0152c4..61be47598 100644 --- a/Clinic/Clinic.Api/Controllers/AppointmentControllers.cs +++ b/Clinic/Clinic.Api/Controllers/AppointmentControllers.cs @@ -1,65 +1,38 @@ using Microsoft.AspNetCore.Mvc; -using Clinic.Api.Services; +using Clinic.Api.Interfaces.Controllers; +using Clinic.Api.Interfaces.Services; using Clinic.Api.DTOs.Appointment; namespace Clinic.Api.Controllers; -[ApiController] -[Route("api/appointments")] /// /// Controller for handling appointment-related operations such as retrieving, creating, updating, /// and deleting appointments in the clinic. /// -public class AppointmentControllers : ControllerBase +[ApiController] +[Route("api/appointments")] +public class AppointmentControllers : BaseControllers, IAppointmentController { - private readonly AppointmentServices _appointmentServices; + private readonly IAppointmentServices _appointmentServices; /// /// Initializes a new instance of . /// - /// Service for appointment operations. - public AppointmentControllers(AppointmentServices appointmentServices) - { - _appointmentServices = appointmentServices; - } - - /// - /// Gets all appointments. - /// - [HttpGet("GetAllAppointments")] - public IActionResult GetAll() - { - var appointments = _appointmentServices.GetAllAppointments(); - return Ok(appointments); - } - - /// - /// Gets details of a specific appointment by its id. - /// - /// The id of the appointment. - [HttpGet("GetAppointment/{id}")] - public IActionResult Get(int id) + /// Service for appointment operations. + public AppointmentControllers(IAppointmentServices service) : base(service) { - var appointment = _appointmentServices.GetAppointment(id); - if (appointment == null) - { - return NotFound("Appointment not found."); - } - return Ok(appointment); + _appointmentServices = service; } /// /// Gets all appointments for a specific doctor. /// /// The doctor's id. - [HttpGet("GetAppointmentsByDoctor/{doctorId}")] + /// ActionResult containing appointments or NotFound if doctor not found. + [HttpGet("doctor/{doctorId}")] public IActionResult GetByDoctor(int doctorId) { var appointments = _appointmentServices.GetAppointmentsByDoctor(doctorId); - if (appointments == null) - { - return NotFound("Doctor not found."); - } return Ok(appointments); } @@ -67,60 +40,11 @@ public IActionResult GetByDoctor(int doctorId) /// Gets all appointments for a specific patient. /// /// The patient's id. - [HttpGet("GetAppointmentsByPatient/{patientId}")] + /// ActionResult containing appointments or NotFound if patient not found. + [HttpGet("patient/{patientId}")] public IActionResult GetByPatient(int patientId) { var appointments = _appointmentServices.GetAppointmentsByPatient(patientId); - if (appointments == null) - { - return NotFound("Patient not found."); - } return Ok(appointments); } - - /// - /// Creates a new appointment. - /// - /// The appointment creation information. - [HttpPost("CreateAppointment")] - public IActionResult Create([FromBody] CreateAppointmentDto dto) - { - var appointment = _appointmentServices.CreateAppointment(dto); - if (appointment == null) - { - return BadRequest("Could not create appointment (doctor or patient may not exist)."); - } - return Ok(appointment); - } - - /// - /// Updates an existing appointment. - /// - /// The id of the appointment to update. - /// The update information. - [HttpPut("UpdateAppointment/{id}")] - public IActionResult Update(int id, [FromBody] UpdateAppointmentDto dto) - { - var appointment = _appointmentServices.UpdateAppointment(id, dto); - if (appointment == null) - { - return NotFound("Appointment not found."); - } - return Ok(appointment); - } - - /// - /// Deletes an appointment. - /// - /// Id of the appointment to delete. - [HttpDelete("DeleteAppointment/{id}")] - public IActionResult Delete(int id) - { - var result = _appointmentServices.DeleteAppointment(id); - if (!result) - { - return NotFound("Appointment not found."); - } - return Ok("Appointment deleted successfully."); - } } diff --git a/Clinic/Clinic.Api/Controllers/BaseController.cs b/Clinic/Clinic.Api/Controllers/BaseController.cs new file mode 100644 index 000000000..c75cb7ed8 --- /dev/null +++ b/Clinic/Clinic.Api/Controllers/BaseController.cs @@ -0,0 +1,118 @@ +using Microsoft.AspNetCore.Mvc; +using Clinic.Api.Interfaces.Controllers; +using Clinic.Api.Interfaces.Services; + +namespace Clinic.Api.Controllers; + +/// +/// Base controller class that provides common CRUD operations for all controllers. +/// +/// Entity type for the controller. +/// DTO type for retrieving entity data. +/// DTO type for creating a new entity. +/// DTO type for updating an existing entity. +/// Service type that implements IBaseService. +public class BaseControllers : ControllerBase, IBaseController + where TGetDto : class + where TCreateDto : class + where TUpdateDto : class + where TService : IBaseServices +{ + protected readonly TService Service; + + /// + /// Initializes a new instance of the class. + /// + /// The service instance for CRUD operations. + protected BaseControllers(TService service) + { + Service = service; + } + + /// + /// Gets all entities. + /// + /// ActionResult containing a list of all entities. + [HttpGet] + public virtual IActionResult GetAll() + { + var entities = Service.GetAll(); + return Ok(entities); + } + + /// + /// Gets a specific entity by its id. + /// + /// The id of the entity. + /// ActionResult containing the entity or NotFound if not found. + [HttpGet("{id}")] + public virtual IActionResult Get(int id) + { + var entity = Service.Get(id); + if (entity == null) + { + return NotFound(GetEntityName() + " not found."); + } + return Ok(entity); + } + + /// + /// Creates a new entity. + /// + /// The creation data. + /// ActionResult containing the created entity or BadRequest if creation fails. + [HttpPost] + public virtual IActionResult Create(TCreateDto dto) + { + var entity = Service.Create(dto); + if (entity == null) + { + return BadRequest("Could not create " + GetEntityName() + "."); + } + return Ok(entity); + } + + /// + /// Updates an existing entity. + /// + /// The id of the entity to update. + /// The update data. + /// ActionResult containing the updated entity or NotFound if not found. + [HttpPut("{id}")] + public virtual IActionResult Update(int id, TUpdateDto dto) + { + var entity = Service.Update(id, dto); + if (entity == null) + { + return NotFound(GetEntityName() + " not found."); + } + return Ok(entity); + } + + /// + /// Deletes an entity by its id. + /// + /// The id of the entity to delete. + /// ActionResult indicating success or NotFound if not found. + [HttpDelete("{id}")] + public virtual IActionResult Delete(int id) + { + var result = Service.Delete(id); + if (!result) + { + return NotFound(GetEntityName() + " not found."); + } + return Ok(GetEntityName() + " deleted successfully."); + } + + /// + /// Gets the entity name for error messages. + /// + /// The entity name. + protected virtual string GetEntityName() + { + var name = typeof(TGetDto).Name; + return name.Replace("Get", "").Replace("Dto", ""); + } +} + diff --git a/Clinic/Clinic.Api/Controllers/DoctorControllers.cs b/Clinic/Clinic.Api/Controllers/DoctorControllers.cs index 492f83db4..03fd62f19 100644 --- a/Clinic/Clinic.Api/Controllers/DoctorControllers.cs +++ b/Clinic/Clinic.Api/Controllers/DoctorControllers.cs @@ -1,6 +1,8 @@ using Microsoft.AspNetCore.Mvc; using Clinic.Api.Services; using Clinic.Api.DTOs.DoctorDto; +using Clinic.Api.Interfaces.Controllers; +using Clinic.Api.Interfaces.Services; namespace Clinic.Api.Controllers; @@ -10,88 +12,13 @@ namespace Clinic.Api.Controllers; /// [ApiController] [Route("api/doctors")] -public class DoctorControllers : ControllerBase -{ - private readonly DoctorServices _doctorServices; - +public class DoctorControllers : BaseControllers, IDoctorController +{ /// /// Initializes a new instance of . /// - /// Service for doctor operations. - public DoctorControllers(DoctorServices doctorServices) - { - _doctorServices = doctorServices; - } - - /// - /// Gets all doctors. - /// - [HttpGet("GetAllDoctors")] - public IActionResult GetAll() - { - var doctors = _doctorServices.GetAllDoctors(); - return Ok(doctors); - } - - /// - /// Creates a new doctor. - /// - /// The doctor creation data. - [HttpPost("AddDoctor")] - public IActionResult Create([FromBody] CreateDoctorDto createDoctorDto) + /// Service for doctor operations. + public DoctorControllers(IDoctorServices service) : base(service) { - var doctor = _doctorServices.CreateDoctor(createDoctorDto); - if (doctor == null) - { - return BadRequest("The doctor already exists."); - } - return Ok(doctor); } - - /// - /// Gets details of a specific doctor by their id. - /// - /// The id of the doctor. - [HttpGet("GetDoctor/{id}")] - public IActionResult Get(int id) - { - var doctor = _doctorServices.GetDoctor(id); - if (doctor == null) - { - return NotFound("Doctor not found."); - } - return Ok(doctor); - } - - /// - /// Updates an existing doctor's information. - /// - /// The id of the doctor to update. - /// The update information. - [HttpPut("UpdateDoctor/{id}")] - public IActionResult Update(int id, UpdateDoctorDto updateDoctorDto) - { - var doctor = _doctorServices.UpdateDoctor(id, updateDoctorDto); - if (doctor == null) - { - return NotFound("Doctor not found."); - } - return Ok(doctor); - } - - /// - /// Deletes a doctor. - /// - /// Id of the doctor to delete. - [HttpDelete("DeleteDoctor/{id}")] - public IActionResult Delete(int id) - { - var result = _doctorServices.DeleteDoctor(id); - if (!result) - { - return NotFound("Doctor not found."); - } - return Ok("Doctor deleted successfully."); - } - } \ No newline at end of file diff --git a/Clinic/Clinic.Api/Controllers/PatientControllers.cs b/Clinic/Clinic.Api/Controllers/PatientControllers.cs index fc2bc7178..8ae5e5eab 100644 --- a/Clinic/Clinic.Api/Controllers/PatientControllers.cs +++ b/Clinic/Clinic.Api/Controllers/PatientControllers.cs @@ -1,6 +1,7 @@ using Microsoft.AspNetCore.Mvc; -using Clinic.Api.Services; using Clinic.Api.DTOs.PatientDto; +using Clinic.Api.Interfaces.Controllers; +using Clinic.Api.Interfaces.Services; namespace Clinic.Api.Controllers; @@ -10,92 +11,13 @@ namespace Clinic.Api.Controllers; /// [ApiController] [Route("api/patients")] -public class PatientControllers : ControllerBase +public class PatientControllers : BaseControllers { - private readonly PatientServices _patientServices; - - /// - /// Constructor for PatientControllers. - /// - /// The service to manage patient data. - public PatientControllers(PatientServices patientServices) - { - _patientServices = patientServices; - } - - /// - /// Retrieves all patients from the database. - /// - /// A list of all patients. - [HttpGet("GetAllPatients")] - public IActionResult GetAll() - { - var patients = _patientServices.GetAllPatients(); - return Ok(patients); - } - - /// - /// Creates a new patient record. - /// - /// The patient data to create. - /// The created patient or a BadRequest if creation fails. - [HttpPost("CreatePatient")] - public IActionResult Create(CreatePatientDto dto) - { - var result = _patientServices.CreatePatient(dto); - if (result == null) - { - return BadRequest("Could not create patient."); - } - return Ok(result); - } - - /// - /// Retrieves a patient by their unique ID. - /// - /// The ID of the patient to retrieve. - /// The patient record or NotFound if the patient does not exist. - [HttpGet("GetPatient/{id}")] - public IActionResult Get(int id) - { - var patient = _patientServices.GetPatient(id); - if (patient == null) - { - return NotFound("Patient not found."); - } - return Ok(patient); - } - - /// - /// Updates an existing patient record. - /// - /// The ID of the patient to update. - /// The updated patient data. - /// The updated patient or NotFound if the patient does not exist. - [HttpPut("UpdatePatient/{id}")] - public IActionResult Update(int id, UpdatePatientDto dto) - { - var result = _patientServices.UpdatePatient(id, dto); - if (result == null) - { - return NotFound("Patient not found."); - } - return Ok(result); - } - /// - /// Deletes a patient by their unique ID. + /// Initializes a new instance of . /// - /// The ID of the patient to delete. - /// Ok if deletion was successful; NotFound otherwise. - [HttpDelete("DeletePatient/{id}")] - public IActionResult Delete(int id) + /// Service for patient operations. + public PatientControllers(IPatientServices service) : base(service) { - var result = _patientServices.DeletePatient(id); - if (!result) - { - return NotFound(); - } - return Ok("Patient deleted successfully."); } } \ No newline at end of file diff --git a/Clinic/Clinic.Api/Controllers/SpecializationControllers.cs b/Clinic/Clinic.Api/Controllers/SpecializationControllers.cs index fe8ff95f2..67ada246f 100644 --- a/Clinic/Clinic.Api/Controllers/SpecializationControllers.cs +++ b/Clinic/Clinic.Api/Controllers/SpecializationControllers.cs @@ -1,6 +1,7 @@ using Microsoft.AspNetCore.Mvc; -using Clinic.Api.Services; using Clinic.Api.DTOs.SpecializationDto; +using Clinic.Api.Interfaces.Controllers; +using Clinic.Api.Interfaces.Services; namespace Clinic.Api.Controllers; @@ -10,75 +11,13 @@ namespace Clinic.Api.Controllers; /// [ApiController] [Route("api/specializations")] -public class SpecializationControllers : ControllerBase -{ - private readonly SpecializationServices _specializationServices; - +public class SpecializationControllers : BaseControllers, ISpecializationController +{ /// /// Initializes a new instance of . /// - /// Service for specialization operations. - public SpecializationControllers(SpecializationServices specializationServices) - { - _specializationServices = specializationServices; - } - - /// - /// Retrieves all specializations. - /// - /// A list of all specializations. - [HttpGet("GetAllSpecializations")] - public IActionResult GetAll() - { - var specializations = _specializationServices.GetAllSpecializations(); - return Ok(specializations); - } - - /// - /// Creates a new specialization. - /// - /// The details of the specialization to create. - /// The created specialization or a BadRequest if it already exists. - [HttpPost("CreateSpecialization")] - public IActionResult Create(CreateSpecializationDto createSpecializationDto) - { - var result = _specializationServices.CreateSpecialization(createSpecializationDto); - if (result == null) - { - return BadRequest("The specialization already exists."); - } - return Ok(result); - } - - /// - /// Gets a specialization by its id. - /// - /// The id of the specialization to retrieve. - /// The specialization if found, otherwise NotFound. - [HttpGet("GetSpecialization/{id}")] - public IActionResult Get(int id) - { - var specialization = _specializationServices.GetSpecialization(id); - if (specialization == null) - { - return NotFound("Specialization not found."); - } - return Ok(specialization); - } - - /// - /// Deletes a specialization by its id. - /// - /// The id of the specialization to delete. - /// A confirmation message or NotFound if the specialization does not exist. - [HttpDelete("DeleteSpecialization/{id}")] - public IActionResult Delete(int id) + /// Service for specialization operations. + public SpecializationControllers(ISpecializationServices service) : base(service) { - var result = _specializationServices.DeleteSpecialization(id); - if (!result) - { - return NotFound("Specialization not found."); - } - return Ok("Specialization deleted successfully."); } } From 18fe57d1eacf856f22f07189414d0fdecf7c7d84 Mon Sep 17 00:00:00 2001 From: maeosha Date: Sun, 7 Dec 2025 00:35:49 +0300 Subject: [PATCH 39/56] refactor: update service registrations to use interfaces for Patient, Doctor, Specialization, and Appointment services, and add mapping for UpdateSpecializationDto --- Clinic/Clinic.Api/MappingProfile/MappingProfile.cs | 2 ++ Clinic/Clinic.Api/Program.cs | 9 +++++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/Clinic/Clinic.Api/MappingProfile/MappingProfile.cs b/Clinic/Clinic.Api/MappingProfile/MappingProfile.cs index bad2df911..9fe57228f 100644 --- a/Clinic/Clinic.Api/MappingProfile/MappingProfile.cs +++ b/Clinic/Clinic.Api/MappingProfile/MappingProfile.cs @@ -44,6 +44,8 @@ public MappingProfile() CreateMap() .ForMember(dest => dest.Name, opt => opt.MapFrom(src => src.Name.ToString())); + CreateMap(); + CreateMap(); CreateMap(); diff --git a/Clinic/Clinic.Api/Program.cs b/Clinic/Clinic.Api/Program.cs index 9c006313f..98c8d9d3a 100644 --- a/Clinic/Clinic.Api/Program.cs +++ b/Clinic/Clinic.Api/Program.cs @@ -3,6 +3,7 @@ using Clinic.Api.MappingProfile; using Clinic.Api.Services; using Clinic.Api.Converter; +using Clinic.Api.Interfaces.Services; var builder = WebApplication.CreateBuilder(args); @@ -19,10 +20,10 @@ builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); -builder.Services.AddScoped(); -builder.Services.AddScoped(); -builder.Services.AddScoped(); -builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); builder.Services.AddScoped(); var app = builder.Build(); From e84698d06454859a05aaabe6319020b92520dc13 Mon Sep 17 00:00:00 2001 From: maeosha Date: Sun, 21 Dec 2025 14:14:11 +0300 Subject: [PATCH 40/56] feat: implement Entity Framework data access classes for Appointment, Doctor, Patient, and Specialization management --- .../EntityFramework/EfAppointmentDataBase.cs | 106 ++++++++++++++++++ .../EntityFramework/EfDoctorDataBase.cs | 93 +++++++++++++++ .../EntityFramework/EfPatientDataBase.cs | 87 ++++++++++++++ .../EfSpecializationDataBase.cs | 90 +++++++++++++++ 4 files changed, 376 insertions(+) create mode 100644 Clinic/Clinic.DataBase/EntityFramework/EfAppointmentDataBase.cs create mode 100644 Clinic/Clinic.DataBase/EntityFramework/EfDoctorDataBase.cs create mode 100644 Clinic/Clinic.DataBase/EntityFramework/EfPatientDataBase.cs create mode 100644 Clinic/Clinic.DataBase/EntityFramework/EfSpecializationDataBase.cs diff --git a/Clinic/Clinic.DataBase/EntityFramework/EfAppointmentDataBase.cs b/Clinic/Clinic.DataBase/EntityFramework/EfAppointmentDataBase.cs new file mode 100644 index 000000000..1572c55e9 --- /dev/null +++ b/Clinic/Clinic.DataBase/EntityFramework/EfAppointmentDataBase.cs @@ -0,0 +1,106 @@ +using Clinic.Models.Entities; +using Clinic.DataBase.Interfaces; +using Microsoft.EntityFrameworkCore; + +namespace Clinic.DataBase.EntityFramework; + +/// +/// Entity Framework implementation of that manages +/// appointment entities using a . +/// +public sealed class EfAppointmentDataBase : IAppointmentDataBase +{ + private readonly ClinicDbContext _context; + + /// + /// Initializes a new instance of the class. + /// + /// The database context used for data access. + public EfAppointmentDataBase(ClinicDbContext context) + { + _context = context; + } + + /// + /// Retrieves an appointment by identifier. + /// + public Appointment? GetAppointment(int id) => + _context.Appointments.Find(id); + + /// + /// Returns all appointments as a read-only collection. + /// + public IReadOnlyCollection GetAllAppointments() => + _context.Appointments.AsNoTracking().ToList(); + + /// + /// Returns appointments for the specified doctor. + /// + public IReadOnlyCollection GetAppointmentsByDoctor(int doctorId) => + _context.Appointments + .Where(a => a.DoctorId == doctorId) + .AsNoTracking() + .ToList(); + + /// + /// Returns appointments for the specified patient. + /// + public IReadOnlyCollection GetAppointmentsByPatient(int patientId) => + _context.Appointments + .Where(a => a.PatientId == patientId) + .AsNoTracking() + .ToList(); + + /// + /// Adds a new appointment. Returns true on success. + /// + public bool AddAppointment(Appointment appointment) + { + if (_context.Appointments.Any(a => a.Id == appointment.Id)) + { + return false; + } + + _context.Appointments.Add(appointment); + _context.SaveChanges(); + return true; + } + + /// + /// Updates an existing appointment. Returns true if updated. + /// + public bool UpdateAppointment(Appointment appointment) + { + if (!_context.Appointments.Any(a => a.Id == appointment.Id)) + { + return false; + } + + _context.Appointments.Update(appointment); + _context.SaveChanges(); + return true; + } + + /// + /// Removes an appointment by id. Returns true if removed. + /// + public bool RemoveAppointment(int id) + { + var entity = _context.Appointments.Find(id); + if (entity is null) + { + return false; + } + + _context.Appointments.Remove(entity); + _context.SaveChanges(); + return true; + } + + /// + /// Returns total number of appointments. + /// + public int AppointmentCount() => _context.Appointments.Count(); +} + + diff --git a/Clinic/Clinic.DataBase/EntityFramework/EfDoctorDataBase.cs b/Clinic/Clinic.DataBase/EntityFramework/EfDoctorDataBase.cs new file mode 100644 index 000000000..b49a270dc --- /dev/null +++ b/Clinic/Clinic.DataBase/EntityFramework/EfDoctorDataBase.cs @@ -0,0 +1,93 @@ +using Clinic.Models.Entities; +using Clinic.DataBase.Interfaces; +using Microsoft.EntityFrameworkCore; + +namespace Clinic.DataBase.EntityFramework; + +/// +/// Entity Framework implementation of that manages +/// doctor entities via . +/// +public sealed class EfDoctorDataBase : IDoctorDataBase +{ + private readonly ClinicDbContext _context; + + /// + /// Initializes a new instance of the class. + /// + /// The database context used for data access. + public EfDoctorDataBase(ClinicDbContext context) + { + _context = context; + } + + /// + /// Retrieves a doctor by identifier, including specializations. + /// + public Doctor? GetDoctor(int id) => + _context.Doctors + .Include(d => d.Specializations) + .FirstOrDefault(d => d.Id == id); + + /// + /// Returns all doctors including their specializations. + /// + public IReadOnlyCollection GetAllDoctors() => + _context.Doctors + .Include(d => d.Specializations) + .AsNoTracking() + .ToList(); + + /// + /// Adds a new doctor. Returns true on success. + /// + public bool AddDoctor(Doctor doctor) + { + if (_context.Doctors.Any(d => d.Id == doctor.Id)) + { + return false; + } + + _context.Doctors.Add(doctor); + _context.SaveChanges(); + return true; + } + + /// + /// Updates an existing doctor. Returns true if updated. + /// + public bool UpdateDoctor(Doctor doctor) + { + if (!_context.Doctors.Any(d => d.Id == doctor.Id)) + { + return false; + } + + _context.Doctors.Update(doctor); + _context.SaveChanges(); + return true; + } + + /// + /// Removes a doctor by id. Returns true if removed. + /// + public bool RemoveDoctor(int id) + { + var entity = _context.Doctors.Find(id); + if (entity is null) + { + return false; + } + + _context.Doctors.Remove(entity); + _context.SaveChanges(); + return true; + } + + /// + /// Returns total count of doctors. + /// + public int DoctorCount() => _context.Doctors.Count(); +} + + diff --git a/Clinic/Clinic.DataBase/EntityFramework/EfPatientDataBase.cs b/Clinic/Clinic.DataBase/EntityFramework/EfPatientDataBase.cs new file mode 100644 index 000000000..7e13364c0 --- /dev/null +++ b/Clinic/Clinic.DataBase/EntityFramework/EfPatientDataBase.cs @@ -0,0 +1,87 @@ +using Clinic.Models.Entities; +using Clinic.DataBase.Interfaces; +using Microsoft.EntityFrameworkCore; + +namespace Clinic.DataBase.EntityFramework; + +/// +/// Entity Framework implementation of that manages +/// patient entities using a . +/// +public sealed class EfPatientDataBase : IPatientDataBase +{ + private readonly ClinicDbContext _context; + + /// + /// Initializes a new instance of the class. + /// + /// The database context used for data access. + public EfPatientDataBase(ClinicDbContext context) + { + _context = context; + } + + /// + /// Retrieves a patient by identifier. + /// + public Patient? GetPatient(int id) => _context.Patients.Find(id); + + /// + /// Returns all patients from the database as a read-only collection. + /// + public IReadOnlyCollection GetAllPatients() => + _context.Patients.AsNoTracking().ToList(); + + /// + /// Adds a new patient to the database. Returns true on success. + /// + public bool AddPatient(Patient patient) + { + if (_context.Patients.Any(p => p.Id == patient.Id)) + { + return false; + } + + _context.Patients.Add(patient); + _context.SaveChanges(); + return true; + } + + /// + /// Updates an existing patient. Returns true if the patient existed and was updated. + /// + public bool UpdatePatient(Patient patient) + { + if (!_context.Patients.Any(p => p.Id == patient.Id)) + { + return false; + } + + _context.Patients.Update(patient); + _context.SaveChanges(); + return true; + } + + /// + /// Removes a patient by identifier. Returns true if removed. + /// + public bool RemovePatient(int id) + { + var entity = _context.Patients.Find(id); + if (entity is null) + { + return false; + } + + _context.Patients.Remove(entity); + _context.SaveChanges(); + return true; + } + + /// + /// Returns total number of patients in the database. + /// + public int PatientCount() => _context.Patients.Count(); +} + + diff --git a/Clinic/Clinic.DataBase/EntityFramework/EfSpecializationDataBase.cs b/Clinic/Clinic.DataBase/EntityFramework/EfSpecializationDataBase.cs new file mode 100644 index 000000000..5c0bf6933 --- /dev/null +++ b/Clinic/Clinic.DataBase/EntityFramework/EfSpecializationDataBase.cs @@ -0,0 +1,90 @@ +using Clinic.Models.Entities; +using Clinic.DataBase.Interfaces; +using Microsoft.EntityFrameworkCore; + +namespace Clinic.DataBase.EntityFramework; + +/// +/// Entity Framework implementation of that manages +/// specialization entities using . +/// +public sealed class EfSpecializationDataBase : ISpecializationDataBase +{ + private readonly ClinicDbContext _context; + + /// + /// Initializes a new instance of the class. + /// + /// The database context used for data access. + public EfSpecializationDataBase(ClinicDbContext context) + { + _context = context; + } + + /// + /// Returns all specializations as a read-only collection. + /// + public IReadOnlyCollection GetAllSpecializations() => + _context.Specializations.AsNoTracking().ToList(); + + /// + /// Adds a new specialization. Returns true on success. + /// + public bool AddSpecialization(Specialization specialization) + { + if (_context.Specializations.Any(s => s.Id == specialization.Id)) + { + return false; + } + + _context.Specializations.Add(specialization); + _context.SaveChanges(); + return true; + } + + /// + /// Updates a specialization by id and returns the updated entity, or null if not found. + /// + public Specialization? UpdateSpecialization(int id, Specialization specialization) + { + var existing = _context.Specializations.Find(id); + if (existing is null) + { + return null; + } + + existing.Name = specialization.Name; + _context.SaveChanges(); + return existing; + } + + + /// + /// Retrieves a specialization by identifier. + /// + public Specialization? GetSpecialization(int id) => + _context.Specializations.Find(id); + + /// + /// Removes a specialization by id. Returns true if removed. + /// + public bool RemoveSpecialization(int id) + { + var entity = _context.Specializations.Find(id); + if (entity is null) + { + return false; + } + + _context.Specializations.Remove(entity); + _context.SaveChanges(); + return true; + } + + /// + /// Returns total number of specializations. + /// + public int SpecializationCount() => _context.Specializations.Count(); +} + + From 0c1363c5ab5842b50a8fa524493c5e10c48ec093 Mon Sep 17 00:00:00 2001 From: maeosha Date: Sun, 21 Dec 2025 14:14:21 +0300 Subject: [PATCH 41/56] feat: add interfaces for appointment, doctor, patient, and specialization data access with CRUD operations --- .../Interfaces/IAppointmentDataBase.cs | 52 +++++++++++++++++++ .../Interfaces/IDoctorDataBase.cs | 42 +++++++++++++++ .../Interfaces/IPatientDataBase.cs | 42 +++++++++++++++ .../Interfaces/ISpecializationDataBase.cs | 42 +++++++++++++++ 4 files changed, 178 insertions(+) create mode 100644 Clinic/Clinic.DataBase/Interfaces/IAppointmentDataBase.cs create mode 100644 Clinic/Clinic.DataBase/Interfaces/IDoctorDataBase.cs create mode 100644 Clinic/Clinic.DataBase/Interfaces/IPatientDataBase.cs create mode 100644 Clinic/Clinic.DataBase/Interfaces/ISpecializationDataBase.cs diff --git a/Clinic/Clinic.DataBase/Interfaces/IAppointmentDataBase.cs b/Clinic/Clinic.DataBase/Interfaces/IAppointmentDataBase.cs new file mode 100644 index 000000000..c59be4bdb --- /dev/null +++ b/Clinic/Clinic.DataBase/Interfaces/IAppointmentDataBase.cs @@ -0,0 +1,52 @@ +using Clinic.Models.Entities; + +namespace Clinic.DataBase.Interfaces; + +/// +/// Abstraction for appointment persistence operations. +/// Provides methods to query and modify appointments in the data store. +/// +public interface IAppointmentDataBase +{ + /// + /// Retrieves an appointment by identifier. + /// + public Appointment? GetAppointment(int id); + + /// + /// Returns all appointments. + /// + public IReadOnlyCollection GetAllAppointments(); + + /// + /// Returns appointments for the specified doctor. + /// + public IReadOnlyCollection GetAppointmentsByDoctor(int doctorId); + + /// + /// Returns appointments for the specified patient. + /// + public IReadOnlyCollection GetAppointmentsByPatient(int patientId); + + /// + /// Adds a new appointment. Returns true on success. + /// + public bool AddAppointment(Appointment appointment); + + /// + /// Updates an appointment. Returns true if updated. + /// + public bool UpdateAppointment(Appointment appointment); + + /// + /// Removes an appointment by id. Returns true if removed. + /// + public bool RemoveAppointment(int id); + + /// + /// Returns total number of appointments. + /// + public int AppointmentCount(); +} + + diff --git a/Clinic/Clinic.DataBase/Interfaces/IDoctorDataBase.cs b/Clinic/Clinic.DataBase/Interfaces/IDoctorDataBase.cs new file mode 100644 index 000000000..d3b69a80d --- /dev/null +++ b/Clinic/Clinic.DataBase/Interfaces/IDoctorDataBase.cs @@ -0,0 +1,42 @@ +using Clinic.Models.Entities; + +namespace Clinic.DataBase.Interfaces; + +/// +/// Abstraction for doctor persistence operations. +/// Implementations should provide CRUD operations for doctor entities. +/// +public interface IDoctorDataBase +{ + /// + /// Retrieves a doctor by identifier, or null if not found. + /// + public Doctor? GetDoctor(int id); + + /// + /// Returns all doctors. + /// + public IReadOnlyCollection GetAllDoctors(); + + /// + /// Adds a new doctor. Returns true on success. + /// + public bool AddDoctor(Doctor doctor); + + /// + /// Updates an existing doctor. Returns true if updated. + /// + public bool UpdateDoctor(Doctor doctor); + + /// + /// Removes a doctor by id. Returns true if removed. + /// + public bool RemoveDoctor(int id); + + /// + /// Returns the total count of doctors. + /// + public int DoctorCount(); +} + + diff --git a/Clinic/Clinic.DataBase/Interfaces/IPatientDataBase.cs b/Clinic/Clinic.DataBase/Interfaces/IPatientDataBase.cs new file mode 100644 index 000000000..f4b5aa6ae --- /dev/null +++ b/Clinic/Clinic.DataBase/Interfaces/IPatientDataBase.cs @@ -0,0 +1,42 @@ +using Clinic.Models.Entities; + +namespace Clinic.DataBase.Interfaces; + +/// +/// Abstraction for patient persistence operations. +/// Implementations should provide methods to get, add, update and remove patients. +/// +public interface IPatientDataBase +{ + /// + /// Retrieves a patient by identifier. + /// + public Patient? GetPatient(int id); + + /// + /// Returns all patients. + /// + public IReadOnlyCollection GetAllPatients(); + + /// + /// Adds a new patient. Returns true on success. + /// + public bool AddPatient(Patient patient); + + /// + /// Updates an existing patient. Returns true if updated. + /// + public bool UpdatePatient(Patient patient); + + /// + /// Removes a patient by id. Returns true if removed. + /// + public bool RemovePatient(int id); + + /// + /// Returns the total count of patients. + /// + public int PatientCount(); +} + + diff --git a/Clinic/Clinic.DataBase/Interfaces/ISpecializationDataBase.cs b/Clinic/Clinic.DataBase/Interfaces/ISpecializationDataBase.cs new file mode 100644 index 000000000..91dc10a6d --- /dev/null +++ b/Clinic/Clinic.DataBase/Interfaces/ISpecializationDataBase.cs @@ -0,0 +1,42 @@ +using Clinic.Models.Entities; + +namespace Clinic.DataBase.Interfaces; + +/// +/// Abstraction for specialization persistence operations. +/// Provides methods to query and modify specializations. +/// +public interface ISpecializationDataBase +{ + /// + /// Returns all specializations. + /// + public IReadOnlyCollection GetAllSpecializations(); + + /// + /// Adds a new specialization. Returns true on success. + /// + public bool AddSpecialization(Specialization specialization); + + /// + /// Updates an existing specialization and returns the updated entity, or null if not found. + /// + public Specialization? UpdateSpecialization(int id, Specialization specialization); + + /// + /// Retrieves a specialization by identifier. + /// + public Specialization? GetSpecialization(int id); + + /// + /// Removes a specialization by id. Returns true if removed. + /// + public bool RemoveSpecialization(int id); + + /// + /// Returns total number of specializations. + /// + public int SpecializationCount(); +} + + From 0d1a0be90c0a2f4a0b01c2d78ef8042a93d0485d Mon Sep 17 00:00:00 2001 From: maeosha Date: Sun, 21 Dec 2025 14:14:35 +0300 Subject: [PATCH 42/56] Add initial database setup and seed data for clinic management system - Created ClinicDbFactory for design-time DbContext instantiation. - Added initial migration (20251219134315_InitialCreate) to set up database schema for Doctors, Patients, Specializations, Appointments, and DoctorSpecialization tables. - Implemented seed data migration (20251219134336_SeedData) to populate initial records for specializations, doctors, patients, and appointments. - Updated model snapshot to reflect the current state of the database schema. --- Clinic/Clinic.DataBase/Clinic.DataBase.csproj | 23 ++ Clinic/Clinic.DataBase/ClinicDbContext.cs | 108 ++++++++ Clinic/Clinic.DataBase/ClinicDbFactory.cs | 42 ++++ .../20251219134315_InitialCreate.Designer.cs | 235 ++++++++++++++++++ .../20251219134315_InitialCreate.cs | 182 ++++++++++++++ .../20251219134336_SeedData.Designer.cs | 235 ++++++++++++++++++ .../Migrations/20251219134336_SeedData.cs | 162 ++++++++++++ .../ClinicDbContextModelSnapshot.cs | 232 +++++++++++++++++ 8 files changed, 1219 insertions(+) create mode 100644 Clinic/Clinic.DataBase/Clinic.DataBase.csproj create mode 100644 Clinic/Clinic.DataBase/ClinicDbContext.cs create mode 100644 Clinic/Clinic.DataBase/ClinicDbFactory.cs create mode 100644 Clinic/Clinic.DataBase/Migrations/20251219134315_InitialCreate.Designer.cs create mode 100644 Clinic/Clinic.DataBase/Migrations/20251219134315_InitialCreate.cs create mode 100644 Clinic/Clinic.DataBase/Migrations/20251219134336_SeedData.Designer.cs create mode 100644 Clinic/Clinic.DataBase/Migrations/20251219134336_SeedData.cs create mode 100644 Clinic/Clinic.DataBase/Migrations/ClinicDbContextModelSnapshot.cs diff --git a/Clinic/Clinic.DataBase/Clinic.DataBase.csproj b/Clinic/Clinic.DataBase/Clinic.DataBase.csproj new file mode 100644 index 000000000..00d1ba461 --- /dev/null +++ b/Clinic/Clinic.DataBase/Clinic.DataBase.csproj @@ -0,0 +1,23 @@ + + + + net9.0 + enable + enable + + + + + + + + + + + + + + + + + diff --git a/Clinic/Clinic.DataBase/ClinicDbContext.cs b/Clinic/Clinic.DataBase/ClinicDbContext.cs new file mode 100644 index 000000000..f3548b319 --- /dev/null +++ b/Clinic/Clinic.DataBase/ClinicDbContext.cs @@ -0,0 +1,108 @@ +using Clinic.Models.Entities; +using Microsoft.EntityFrameworkCore; + +namespace Clinic.DataBase; + +/// +/// EF Core for the Clinic application. Configures entities +/// and relationships for patients, doctors, specializations and appointments. +/// +public class ClinicDbContext : DbContext +{ + public ClinicDbContext(DbContextOptions options) : base(options){} + + /// + /// DbSet of patients. + /// + public DbSet Patients => Set(); + + /// + /// DbSet of doctors. + /// + public DbSet Doctors => Set(); + + /// + /// DbSet of specializations. + /// + public DbSet Specializations => Set(); + + /// + /// DbSet of appointments. + /// + public DbSet Appointments => Set(); + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + // Configure Patient entity + modelBuilder.Entity(entity => + { + entity.HasKey(e => e.Id); + entity.Property(e => e.PassportNumber).IsRequired().HasMaxLength(50); + entity.Property(e => e.BirthDate).IsRequired(); + entity.Property(e => e.LastName).IsRequired().HasMaxLength(100); + entity.Property(e => e.FirstName).IsRequired().HasMaxLength(100); + entity.Property(e => e.Patronymic).HasMaxLength(100); + entity.Property(e => e.PhoneNumber).IsRequired().HasMaxLength(20); + entity.Property(e => e.Gender).IsRequired().HasConversion(); + entity.Property(e => e.Address).IsRequired().HasMaxLength(200); + entity.Property(e => e.BloodGroup).IsRequired().HasConversion(); + entity.Property(e => e.RhesusFactor).IsRequired().HasConversion(); + }); + + // Configure Doctor entity + modelBuilder.Entity(entity => + { + entity.HasKey(e => e.Id); + entity.Property(e => e.PassportNumber).IsRequired().HasMaxLength(50); + entity.Property(e => e.BirthDate).IsRequired(); + entity.Property(e => e.LastName).IsRequired().HasMaxLength(100); + entity.Property(e => e.FirstName).IsRequired().HasMaxLength(100); + entity.Property(e => e.Patronymic).HasMaxLength(100); + entity.Property(e => e.PhoneNumber).IsRequired().HasMaxLength(20); + entity.Property(e => e.Gender).IsRequired().HasConversion(); + entity.Property(e => e.ExperienceYears).IsRequired(); + + // Configure many-to-many relationship with Specialization + entity.HasMany(d => d.Specializations) + .WithMany() + .UsingEntity>( + "DoctorSpecialization", + j => j.HasOne().WithMany().HasForeignKey("SpecializationId"), + j => j.HasOne().WithMany().HasForeignKey("DoctorId"), + j => j.HasKey("DoctorId", "SpecializationId")); + }); + + // Configure Specialization entity + modelBuilder.Entity(entity => + { + entity.HasKey(e => e.Id); + entity.Property(e => e.Name).IsRequired().HasMaxLength(100); + }); + + // Configure Appointment entity + modelBuilder.Entity(entity => + { + entity.HasKey(e => e.Id); + entity.Property(e => e.PatientId).IsRequired(); + entity.Property(e => e.PatientFullName).IsRequired().HasMaxLength(300); + entity.Property(e => e.DoctorId).IsRequired(); + entity.Property(e => e.DoctorFullName).IsRequired().HasMaxLength(300); + entity.Property(e => e.DateTime).IsRequired(); + entity.Property(e => e.RoomNumber).IsRequired(); + entity.Property(e => e.IsReturnVisit).IsRequired(); + + // Configure foreign key relationships + entity.HasOne() + .WithMany() + .HasForeignKey(a => a.PatientId) + .OnDelete(DeleteBehavior.Restrict); + + entity.HasOne() + .WithMany() + .HasForeignKey(a => a.DoctorId) + .OnDelete(DeleteBehavior.Restrict); + }); + } +} + + diff --git a/Clinic/Clinic.DataBase/ClinicDbFactory.cs b/Clinic/Clinic.DataBase/ClinicDbFactory.cs new file mode 100644 index 000000000..9c8ce5e9d --- /dev/null +++ b/Clinic/Clinic.DataBase/ClinicDbFactory.cs @@ -0,0 +1,42 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Design; +using Microsoft.Extensions.Configuration; + +namespace Clinic.DataBase; + +/// +/// Factory used at design-time to create instances of . +/// This is used by EF Core tools (migrations, scaffolding) to create a context. +/// +public class ClinicDbFactory : IDesignTimeDbContextFactory +{ + + /// + /// Creates a new for design-time operations. + /// + /// Command-line arguments (unused). + /// A configured instance. + public ClinicDbContext CreateDbContext(string[] args) + { + var configuration = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json") + .Build(); + + var connectionString = configuration.GetConnectionString("ClinicDb"); + + if (string.IsNullOrEmpty(connectionString)) + { + throw new InvalidOperationException("Connection string 'ClinicDb' not found."); + } + + var optionsBuilder = new DbContextOptionsBuilder(); + optionsBuilder.UseMySql( + connectionString, + ServerVersion.AutoDetect(connectionString) + ); + + return new ClinicDbContext(optionsBuilder.Options); + } + +} \ No newline at end of file diff --git a/Clinic/Clinic.DataBase/Migrations/20251219134315_InitialCreate.Designer.cs b/Clinic/Clinic.DataBase/Migrations/20251219134315_InitialCreate.Designer.cs new file mode 100644 index 000000000..48140ae44 --- /dev/null +++ b/Clinic/Clinic.DataBase/Migrations/20251219134315_InitialCreate.Designer.cs @@ -0,0 +1,235 @@ +// +using System; +using Clinic.DataBase; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace Clinic.DataBase.Migrations +{ + [DbContext(typeof(ClinicDbContext))] + [Migration("20251219134315_InitialCreate")] + partial class InitialCreate + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.0") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder); + + modelBuilder.Entity("Clinic.Models.Entities.Appointment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("DateTime") + .HasColumnType("datetime(6)"); + + b.Property("DoctorFullName") + .IsRequired() + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("DoctorId") + .HasColumnType("int"); + + b.Property("IsReturnVisit") + .HasColumnType("tinyint(1)"); + + b.Property("PatientFullName") + .IsRequired() + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("PatientId") + .HasColumnType("int"); + + b.Property("RoomNumber") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("DoctorId"); + + b.HasIndex("PatientId"); + + b.ToTable("Appointments"); + }); + + modelBuilder.Entity("Clinic.Models.Entities.Doctor", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("BirthDate") + .HasColumnType("date"); + + b.Property("ExperienceYears") + .HasColumnType("int"); + + b.Property("FirstName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("Gender") + .HasColumnType("int"); + + b.Property("LastName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("PassportNumber") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Patronymic") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("PhoneNumber") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.HasKey("Id"); + + b.ToTable("Doctors"); + }); + + modelBuilder.Entity("Clinic.Models.Entities.Patient", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Address") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("BirthDate") + .HasColumnType("date"); + + b.Property("BloodGroup") + .HasColumnType("int"); + + b.Property("FirstName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("Gender") + .HasColumnType("int"); + + b.Property("LastName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("PassportNumber") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Patronymic") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("PhoneNumber") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("RhesusFactor") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("Patients"); + }); + + modelBuilder.Entity("Clinic.Models.Entities.Specialization", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.HasKey("Id"); + + b.ToTable("Specializations"); + }); + + modelBuilder.Entity("DoctorSpecialization", b => + { + b.Property("DoctorId") + .HasColumnType("int"); + + b.Property("SpecializationId") + .HasColumnType("int"); + + b.HasKey("DoctorId", "SpecializationId"); + + b.HasIndex("SpecializationId"); + + b.ToTable("DoctorSpecialization"); + }); + + modelBuilder.Entity("Clinic.Models.Entities.Appointment", b => + { + b.HasOne("Clinic.Models.Entities.Doctor", null) + .WithMany() + .HasForeignKey("DoctorId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Clinic.Models.Entities.Patient", null) + .WithMany() + .HasForeignKey("PatientId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("DoctorSpecialization", b => + { + b.HasOne("Clinic.Models.Entities.Doctor", null) + .WithMany() + .HasForeignKey("DoctorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Clinic.Models.Entities.Specialization", null) + .WithMany() + .HasForeignKey("SpecializationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Clinic/Clinic.DataBase/Migrations/20251219134315_InitialCreate.cs b/Clinic/Clinic.DataBase/Migrations/20251219134315_InitialCreate.cs new file mode 100644 index 000000000..306f5434f --- /dev/null +++ b/Clinic/Clinic.DataBase/Migrations/20251219134315_InitialCreate.cs @@ -0,0 +1,182 @@ +using System; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Clinic.DataBase.Migrations +{ + /// + public partial class InitialCreate : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterDatabase() + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateTable( + name: "Doctors", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + ExperienceYears = table.Column(type: "int", nullable: false), + PassportNumber = table.Column(type: "varchar(50)", maxLength: 50, nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + BirthDate = table.Column(type: "date", nullable: false), + LastName = table.Column(type: "varchar(100)", maxLength: 100, nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + FirstName = table.Column(type: "varchar(100)", maxLength: 100, nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + Patronymic = table.Column(type: "varchar(100)", maxLength: 100, nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + PhoneNumber = table.Column(type: "varchar(20)", maxLength: 20, nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + Gender = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Doctors", x => x.Id); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateTable( + name: "Patients", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + Address = table.Column(type: "varchar(200)", maxLength: 200, nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + BloodGroup = table.Column(type: "int", nullable: false), + RhesusFactor = table.Column(type: "int", nullable: false), + PassportNumber = table.Column(type: "varchar(50)", maxLength: 50, nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + BirthDate = table.Column(type: "date", nullable: false), + LastName = table.Column(type: "varchar(100)", maxLength: 100, nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + FirstName = table.Column(type: "varchar(100)", maxLength: 100, nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + Patronymic = table.Column(type: "varchar(100)", maxLength: 100, nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + PhoneNumber = table.Column(type: "varchar(20)", maxLength: 20, nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + Gender = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Patients", x => x.Id); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateTable( + name: "Specializations", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + Name = table.Column(type: "varchar(100)", maxLength: 100, nullable: false) + .Annotation("MySql:CharSet", "utf8mb4") + }, + constraints: table => + { + table.PrimaryKey("PK_Specializations", x => x.Id); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateTable( + name: "Appointments", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + PatientId = table.Column(type: "int", nullable: false), + PatientFullName = table.Column(type: "varchar(300)", maxLength: 300, nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + DoctorId = table.Column(type: "int", nullable: false), + DoctorFullName = table.Column(type: "varchar(300)", maxLength: 300, nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + DateTime = table.Column(type: "datetime(6)", nullable: false), + RoomNumber = table.Column(type: "int", nullable: false), + IsReturnVisit = table.Column(type: "tinyint(1)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Appointments", x => x.Id); + table.ForeignKey( + name: "FK_Appointments_Doctors_DoctorId", + column: x => x.DoctorId, + principalTable: "Doctors", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_Appointments_Patients_PatientId", + column: x => x.PatientId, + principalTable: "Patients", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateTable( + name: "DoctorSpecialization", + columns: table => new + { + DoctorId = table.Column(type: "int", nullable: false), + SpecializationId = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_DoctorSpecialization", x => new { x.DoctorId, x.SpecializationId }); + table.ForeignKey( + name: "FK_DoctorSpecialization_Doctors_DoctorId", + column: x => x.DoctorId, + principalTable: "Doctors", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_DoctorSpecialization_Specializations_SpecializationId", + column: x => x.SpecializationId, + principalTable: "Specializations", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateIndex( + name: "IX_Appointments_DoctorId", + table: "Appointments", + column: "DoctorId"); + + migrationBuilder.CreateIndex( + name: "IX_Appointments_PatientId", + table: "Appointments", + column: "PatientId"); + + migrationBuilder.CreateIndex( + name: "IX_DoctorSpecialization_SpecializationId", + table: "DoctorSpecialization", + column: "SpecializationId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Appointments"); + + migrationBuilder.DropTable( + name: "DoctorSpecialization"); + + migrationBuilder.DropTable( + name: "Patients"); + + migrationBuilder.DropTable( + name: "Doctors"); + + migrationBuilder.DropTable( + name: "Specializations"); + } + } +} diff --git a/Clinic/Clinic.DataBase/Migrations/20251219134336_SeedData.Designer.cs b/Clinic/Clinic.DataBase/Migrations/20251219134336_SeedData.Designer.cs new file mode 100644 index 000000000..58d8e2473 --- /dev/null +++ b/Clinic/Clinic.DataBase/Migrations/20251219134336_SeedData.Designer.cs @@ -0,0 +1,235 @@ +// +using System; +using Clinic.DataBase; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace Clinic.DataBase.Migrations +{ + [DbContext(typeof(ClinicDbContext))] + [Migration("20251219134336_SeedData")] + partial class SeedData + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.0") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder); + + modelBuilder.Entity("Clinic.Models.Entities.Appointment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("DateTime") + .HasColumnType("datetime(6)"); + + b.Property("DoctorFullName") + .IsRequired() + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("DoctorId") + .HasColumnType("int"); + + b.Property("IsReturnVisit") + .HasColumnType("tinyint(1)"); + + b.Property("PatientFullName") + .IsRequired() + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("PatientId") + .HasColumnType("int"); + + b.Property("RoomNumber") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("DoctorId"); + + b.HasIndex("PatientId"); + + b.ToTable("Appointments"); + }); + + modelBuilder.Entity("Clinic.Models.Entities.Doctor", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("BirthDate") + .HasColumnType("date"); + + b.Property("ExperienceYears") + .HasColumnType("int"); + + b.Property("FirstName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("Gender") + .HasColumnType("int"); + + b.Property("LastName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("PassportNumber") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Patronymic") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("PhoneNumber") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.HasKey("Id"); + + b.ToTable("Doctors"); + }); + + modelBuilder.Entity("Clinic.Models.Entities.Patient", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Address") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("BirthDate") + .HasColumnType("date"); + + b.Property("BloodGroup") + .HasColumnType("int"); + + b.Property("FirstName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("Gender") + .HasColumnType("int"); + + b.Property("LastName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("PassportNumber") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Patronymic") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("PhoneNumber") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("RhesusFactor") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("Patients"); + }); + + modelBuilder.Entity("Clinic.Models.Entities.Specialization", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.HasKey("Id"); + + b.ToTable("Specializations"); + }); + + modelBuilder.Entity("DoctorSpecialization", b => + { + b.Property("DoctorId") + .HasColumnType("int"); + + b.Property("SpecializationId") + .HasColumnType("int"); + + b.HasKey("DoctorId", "SpecializationId"); + + b.HasIndex("SpecializationId"); + + b.ToTable("DoctorSpecialization"); + }); + + modelBuilder.Entity("Clinic.Models.Entities.Appointment", b => + { + b.HasOne("Clinic.Models.Entities.Doctor", null) + .WithMany() + .HasForeignKey("DoctorId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Clinic.Models.Entities.Patient", null) + .WithMany() + .HasForeignKey("PatientId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("DoctorSpecialization", b => + { + b.HasOne("Clinic.Models.Entities.Doctor", null) + .WithMany() + .HasForeignKey("DoctorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Clinic.Models.Entities.Specialization", null) + .WithMany() + .HasForeignKey("SpecializationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Clinic/Clinic.DataBase/Migrations/20251219134336_SeedData.cs b/Clinic/Clinic.DataBase/Migrations/20251219134336_SeedData.cs new file mode 100644 index 000000000..4f10aa0fb --- /dev/null +++ b/Clinic/Clinic.DataBase/Migrations/20251219134336_SeedData.cs @@ -0,0 +1,162 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Clinic.DataBase.Migrations; + + /// + public partial class SeedData : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + // Seed Specializations + migrationBuilder.InsertData( + table: "Specializations", + columns: new[] { "Id", "Name" }, + values: new object[,] + { + { 1, "Терапевт" }, + { 2, "Хирург" }, + { 3, "Кардиолог" }, + { 4, "Невролог" }, + { 5, "Офтальмолог" }, + { 6, "Отоларинголог" }, + { 7, "Дерматолог" }, + { 8, "Педиатр" }, + { 9, "Гинеколог" }, + { 10, "Уролог" } + }); + + // Seed Doctors (10 doctors) + migrationBuilder.InsertData( + table: "Doctors", + columns: new[] { "Id", "PassportNumber", "BirthDate", "LastName", "FirstName", "Patronymic", "PhoneNumber", "Gender", "ExperienceYears" }, + values: new object[,] + { + { 1, "AB1234567", new DateOnly(1975, 5, 15), "Иванов", "Иван", "Иванович", "+7-900-123-45-67", 0, 15 }, + { 2, "CD2345678", new DateOnly(1980, 8, 22), "Петрова", "Мария", "Сергеевна", "+7-900-234-56-78", 1, 12 }, + { 3, "EF3456789", new DateOnly(1972, 3, 10), "Сидоров", "Александр", "Петрович", "+7-900-345-67-89", 0, 20 }, + { 4, "GH4567890", new DateOnly(1985, 11, 5), "Козлова", "Елена", "Владимировна", "+7-900-456-78-90", 1, 8 }, + { 5, "IJ5678901", new DateOnly(1978, 7, 30), "Морозов", "Дмитрий", "Александрович", "+7-900-567-89-01", 0, 14 }, + { 6, "KL6789012", new DateOnly(1982, 4, 18), "Волкова", "Анна", "Дмитриевна", "+7-900-678-90-12", 1, 10 }, + { 7, "MN7890123", new DateOnly(1976, 9, 25), "Лебедев", "Сергей", "Викторович", "+7-900-789-01-23", 0, 16 }, + { 8, "OP8901234", new DateOnly(1988, 1, 12), "Новикова", "Ольга", "Андреевна", "+7-900-890-12-34", 1, 6 }, + { 9, "QR9012345", new DateOnly(1974, 6, 8), "Федоров", "Максим", "Сергеевич", "+7-900-901-23-45", 0, 18 }, + { 10, "ST0123456", new DateOnly(1983, 12, 20), "Смирнова", "Татьяна", "Игоревна", "+7-900-012-34-56", 1, 11 } + }); + + // Seed Doctor-Specialization relationships + migrationBuilder.InsertData( + table: "DoctorSpecialization", + columns: new[] { "DoctorId", "SpecializationId" }, + values: new object[,] + { + { 1, 1 }, + { 1, 3 }, + { 2, 2 }, + { 3, 4 }, + { 3, 1 }, + { 4, 5 }, + { 5, 6 }, + { 5, 7 }, + { 6, 8 }, + { 7, 2 }, + { 7, 10 }, + { 8, 9 }, + { 9, 3 }, + { 9, 1 }, + { 10, 5 }, + { 10, 6 } + }); + + // Seed Patients (10 patients) + migrationBuilder.InsertData( + table: "Patients", + columns: new[] { "Id", "PassportNumber", "BirthDate", "LastName", "FirstName", "Patronymic", "PhoneNumber", "Gender", "Address", "BloodGroup", "RhesusFactor" }, + values: new object[,] + { + { 1, "KL6789012", new DateOnly(1970, 2, 14), "Смирнов", "Алексей", "Игоревич", "+7-901-111-11-11", 0, "г. Москва, ул. Ленина, д. 10, кв. 5", 0, 0 }, + { 2, "MN7890123", new DateOnly(1975, 6, 20), "Волкова", "Анна", "Дмитриевна", "+7-901-222-22-22", 1, "г. Москва, ул. Пушкина, д. 25, кв. 12", 1, 0 }, + { 3, "OP8901234", new DateOnly(1972, 9, 8), "Лебедев", "Сергей", "Викторович", "+7-901-333-33-33", 0, "г. Москва, пр. Мира, д. 50, кв. 8", 2, 1 }, + { 4, "QR9012345", new DateOnly(1988, 12, 3), "Новикова", "Ольга", "Андреевна", "+7-901-444-44-44", 1, "г. Москва, ул. Гагарина, д. 15, кв. 20", 3, 0 }, + { 5, "ST0123456", new DateOnly(1995, 4, 17), "Федоров", "Максим", "Сергеевич", "+7-901-555-55-55", 0, "г. Москва, ул. Чехова, д. 30, кв. 15", 0, 1 }, + { 6, "UV1234567", new DateOnly(1987, 7, 22), "Кузнецова", "Екатерина", "Александровна", "+7-901-666-66-66", 1, "г. Москва, ул. Тверская, д. 5, кв. 3", 1, 1 }, + { 7, "WX2345678", new DateOnly(1993, 11, 30), "Попов", "Андрей", "Николаевич", "+7-901-777-77-77", 0, "г. Москва, ул. Арбат, д. 20, кв. 7", 2, 0 }, + { 8, "YZ3456789", new DateOnly(1989, 3, 15), "Соколова", "Мария", "Владимировна", "+7-901-888-88-88", 1, "г. Москва, ул. Садовая, д. 12, кв. 9", 3, 1 }, + { 9, "AA4567890", new DateOnly(1991, 8, 5), "Михайлов", "Игорь", "Борисович", "+7-901-999-99-99", 0, "г. Москва, ул. Невский, д. 40, кв. 11", 0, 0 }, + { 10, "BB5678901", new DateOnly(1986, 1, 28), "Павлова", "Наталья", "Сергеевна", "+7-901-000-00-00", 1, "г. Москва, ул. Красная, д. 8, кв. 4", 1, 0 } + }); + + // Seed Appointments (15 appointments) + migrationBuilder.InsertData( + table: "Appointments", + columns: new[] { "Id", "PatientId", "PatientFullName", "DoctorId", "DoctorFullName", "DateTime", "RoomNumber", "IsReturnVisit" }, + values: new object[,] + { + { 1, 1, "Смирнов Алексей Игоревич", 1, "Иванов Иван Иванович", new DateTime(2025, 12, 10, 10, 0, 0), 101, false }, + { 2, 2, "Волкова Анна Дмитриевна", 2, "Петрова Мария Сергеевна", new DateTime(2025, 12, 10, 11, 30, 0), 205, false }, + { 3, 3, "Лебедев Сергей Викторович", 3, "Сидоров Александр Петрович", new DateTime(2025, 8, 21, 9, 0, 0), 302, true }, + { 4, 4, "Новикова Ольга Андреевна", 4, "Козлова Елена Владимировна", new DateTime(2025, 12, 21, 14, 0, 0), 401, false }, + { 5, 5, "Федоров Максим Сергеевич", 5, "Морозов Дмитрий Александрович", new DateTime(2025, 12, 22, 10, 30, 0), 503, false }, + { 6, 6, "Кузнецова Екатерина Александровна", 6, "Волкова Анна Дмитриевна", new DateTime(2025, 12, 22, 15, 0, 0), 201, false }, + { 7, 7, "Попов Андрей Николаевич", 7, "Лебедев Сергей Викторович", new DateTime(2025, 5, 13, 9, 30, 0), 305, true }, + { 8, 8, "Соколова Мария Владимировна", 8, "Новикова Ольга Андреевна", new DateTime(2025, 6, 23, 11, 0, 0), 402, false }, + { 9, 9, "Михайлов Игорь Борисович", 9, "Федоров Максим Сергеевич", new DateTime(2025, 12, 24, 10, 0, 0), 104, false }, + { 10, 10, "Павлова Наталья Сергеевна", 10, "Смирнова Татьяна Игоревна", new DateTime(2025, 12, 24, 13, 30, 0), 501, false }, + { 11, 1, "Смирнов Алексей Игоревич", 2, "Петрова Мария Сергеевна", new DateTime(2025, 3, 25, 10, 0, 0), 101, true }, + { 12, 3, "Лебедев Сергей Викторович", 5, "Морозов Дмитрий Александрович", new DateTime(2025, 4, 25, 14, 0, 0), 302, true }, + { 13, 5, "Федоров Максим Сергеевич", 5, "Морозов Дмитрий Александрович", new DateTime(2025, 11, 26, 9, 0, 0), 503, true }, + { 14, 7, "Попов Андрей Николаевич", 7, "Лебедев Сергей Викторович", new DateTime(2025, 11, 26, 11, 30, 0), 305, false }, + { 15, 9, "Михайлов Игорь Борисович", 9, "Федоров Максим Сергеевич", new DateTime(2025, 11, 27, 10, 30, 0), 104, true } + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + // Remove seed data in reverse order + migrationBuilder.DeleteData( + table: "Appointments", + keyColumn: "Id", + keyValues: new object[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }); + + migrationBuilder.DeleteData( + table: "Patients", + keyColumn: "Id", + keyValues: new object[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }); + + migrationBuilder.DeleteData( + table: "DoctorSpecialization", + keyColumns: new[] { "DoctorId", "SpecializationId" }, + keyValues: new object[,] + { + { 1, 1 }, + { 1, 3 }, + { 2, 2 }, + { 3, 4 }, + { 3, 1 }, + { 4, 5 }, + { 5, 6 }, + { 5, 7 }, + { 6, 8 }, + { 7, 2 }, + { 7, 10 }, + { 8, 9 }, + { 9, 3 }, + { 9, 1 }, + { 10, 5 }, + { 10, 6 } + }); + + migrationBuilder.DeleteData( + table: "Doctors", + keyColumn: "Id", + keyValues: new object[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }); + + migrationBuilder.DeleteData( + table: "Specializations", + keyColumn: "Id", + keyValues: new object[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }); + } +} diff --git a/Clinic/Clinic.DataBase/Migrations/ClinicDbContextModelSnapshot.cs b/Clinic/Clinic.DataBase/Migrations/ClinicDbContextModelSnapshot.cs new file mode 100644 index 000000000..d62f0d35e --- /dev/null +++ b/Clinic/Clinic.DataBase/Migrations/ClinicDbContextModelSnapshot.cs @@ -0,0 +1,232 @@ +// +using System; +using Clinic.DataBase; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace Clinic.DataBase.Migrations +{ + [DbContext(typeof(ClinicDbContext))] + partial class ClinicDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.0") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder); + + modelBuilder.Entity("Clinic.Models.Entities.Appointment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("DateTime") + .HasColumnType("datetime(6)"); + + b.Property("DoctorFullName") + .IsRequired() + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("DoctorId") + .HasColumnType("int"); + + b.Property("IsReturnVisit") + .HasColumnType("tinyint(1)"); + + b.Property("PatientFullName") + .IsRequired() + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("PatientId") + .HasColumnType("int"); + + b.Property("RoomNumber") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("DoctorId"); + + b.HasIndex("PatientId"); + + b.ToTable("Appointments"); + }); + + modelBuilder.Entity("Clinic.Models.Entities.Doctor", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("BirthDate") + .HasColumnType("date"); + + b.Property("ExperienceYears") + .HasColumnType("int"); + + b.Property("FirstName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("Gender") + .HasColumnType("int"); + + b.Property("LastName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("PassportNumber") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Patronymic") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("PhoneNumber") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.HasKey("Id"); + + b.ToTable("Doctors"); + }); + + modelBuilder.Entity("Clinic.Models.Entities.Patient", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Address") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("BirthDate") + .HasColumnType("date"); + + b.Property("BloodGroup") + .HasColumnType("int"); + + b.Property("FirstName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("Gender") + .HasColumnType("int"); + + b.Property("LastName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("PassportNumber") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Patronymic") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("PhoneNumber") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("RhesusFactor") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("Patients"); + }); + + modelBuilder.Entity("Clinic.Models.Entities.Specialization", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.HasKey("Id"); + + b.ToTable("Specializations"); + }); + + modelBuilder.Entity("DoctorSpecialization", b => + { + b.Property("DoctorId") + .HasColumnType("int"); + + b.Property("SpecializationId") + .HasColumnType("int"); + + b.HasKey("DoctorId", "SpecializationId"); + + b.HasIndex("SpecializationId"); + + b.ToTable("DoctorSpecialization"); + }); + + modelBuilder.Entity("Clinic.Models.Entities.Appointment", b => + { + b.HasOne("Clinic.Models.Entities.Doctor", null) + .WithMany() + .HasForeignKey("DoctorId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Clinic.Models.Entities.Patient", null) + .WithMany() + .HasForeignKey("PatientId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("DoctorSpecialization", b => + { + b.HasOne("Clinic.Models.Entities.Doctor", null) + .WithMany() + .HasForeignKey("DoctorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Clinic.Models.Entities.Specialization", null) + .WithMany() + .HasForeignKey("SpecializationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} From 7d33040c63372cb95f86319e2d1231297a33326d Mon Sep 17 00:00:00 2001 From: maeosha Date: Sun, 21 Dec 2025 14:15:29 +0300 Subject: [PATCH 43/56] feat: implement analytics controller and refactor existing controllers to use base controller pattern --- .../Controllers/AnalyticsControllers.cs | 76 +++++++++++++++++++ .../Controllers/AppointmentControllers.cs | 34 ++++----- .../{BaseController.cs => BaseControllers.cs} | 53 ++++++++----- .../Controllers/DoctorControllers.cs | 13 +--- .../Controllers/PatientControllers.cs | 12 +-- .../Controllers/SpecializationControllers.cs | 12 +-- .../Clinic.Api/Controllers/TestControllers.cs | 44 ----------- .../Controllers/IAppointmentController.cs | 26 ------- .../Interfaces/Controllers/IBaseController.cs | 51 ------------- .../Controllers/IDoctorController.cs | 11 --- .../Controllers/IPatientController.cs | 11 --- .../Controllers/ISpecializationController.cs | 11 --- 12 files changed, 130 insertions(+), 224 deletions(-) create mode 100644 Clinic/Clinic.Api/Controllers/AnalyticsControllers.cs rename Clinic/Clinic.Api/Controllers/{BaseController.cs => BaseControllers.cs} (67%) delete mode 100644 Clinic/Clinic.Api/Controllers/TestControllers.cs delete mode 100644 Clinic/Clinic.Api/Interfaces/Controllers/IAppointmentController.cs delete mode 100644 Clinic/Clinic.Api/Interfaces/Controllers/IBaseController.cs delete mode 100644 Clinic/Clinic.Api/Interfaces/Controllers/IDoctorController.cs delete mode 100644 Clinic/Clinic.Api/Interfaces/Controllers/IPatientController.cs delete mode 100644 Clinic/Clinic.Api/Interfaces/Controllers/ISpecializationController.cs diff --git a/Clinic/Clinic.Api/Controllers/AnalyticsControllers.cs b/Clinic/Clinic.Api/Controllers/AnalyticsControllers.cs new file mode 100644 index 000000000..8965443cc --- /dev/null +++ b/Clinic/Clinic.Api/Controllers/AnalyticsControllers.cs @@ -0,0 +1,76 @@ +using Microsoft.AspNetCore.Mvc; +using Clinic.Api.Services; +using Clinic.Api.DTOs.DoctorDto; +using Clinic.Api.DTOs.PatientDto; +using Clinic.Api.DTOs.Appointment; + +namespace Clinic.Api.Controllers; + +/// +/// Controller for analytics and business logic queries about doctors, patients, and appointments. +/// +[ApiController] +[Route("api/analytics")] +public class AnalyticsController(AnalyticsServices testServices) : ControllerBase +{ + /// + /// Retrieves a list of doctors with 10 or more years of experience. + /// Returns a 200 OK with a list of GetDoctorDto objects. + /// + [HttpGet("doctors/experience")] + [ProducesResponseType(typeof(IReadOnlyList), StatusCodes.Status200OK)] + public ActionResult> GetDoctorsWithExperience() + { + return Ok(testServices.GetDoctorsWithExperience10YearsOrMore()); + } + + /// + /// Fetches patients assigned to a specific doctor (by doctorId), ordered by their full name. + /// Returns 200 OK with a list of GetPatientDto objects if the doctor exists; otherwise, 404 Not Found with a message. + /// + [HttpGet("patients/doctor")] + [ProducesResponseType(typeof(IReadOnlyList), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public ActionResult> GetPatientsByDoctor(int doctorId) + { + var result = testServices.GetPatientsByDoctorOrderedByFullName(doctorId); + if (result == null) + { + return NotFound("Doctor not found."); + } + return Ok(result); + } + + /// + /// Returns the count of return visits (as an integer) that occurred in the last month. + /// Responds with 200 OK. + /// + [HttpGet("patients/return-visits")] + [ProducesResponseType(typeof(int), StatusCodes.Status200OK)] + public ActionResult GetReturnVisitsCountLastMonth() + { + return Ok(testServices.GetReturnVisitsCountLastMonth()); + } + + /// + /// Gets a list of patients over 30 years old who are associated with multiple doctors, ordered by birth date. + /// Returns 200 OK with a list of GetPatientDto objects. + /// + [HttpGet("patients/over-30")] + [ProducesResponseType(typeof(IReadOnlyList), StatusCodes.Status200OK)] + public ActionResult> GetPatientsOver30WithMultipleDoctors() + { + return Ok(testServices.GetPatientsOver30WithMultipleDoctorsOrderedByBirthDate()); + } + + /// + /// Retrieves appointments scheduled in a specific room (roomNumber) for the current month. + /// Returns 200 OK with a list of GetAppointmentDto objects. + /// + [HttpGet("appointments/room")] + [ProducesResponseType(typeof(IReadOnlyList), StatusCodes.Status200OK)] + public ActionResult> GetAppointmentsInRoomForCurrentMonth(int roomNumber) + { + return Ok(testServices.GetAppointmentsInRoomForCurrentMonth(roomNumber)); + } +} \ No newline at end of file diff --git a/Clinic/Clinic.Api/Controllers/AppointmentControllers.cs b/Clinic/Clinic.Api/Controllers/AppointmentControllers.cs index 61be47598..8c1c04cf1 100644 --- a/Clinic/Clinic.Api/Controllers/AppointmentControllers.cs +++ b/Clinic/Clinic.Api/Controllers/AppointmentControllers.cs @@ -1,5 +1,4 @@ using Microsoft.AspNetCore.Mvc; -using Clinic.Api.Interfaces.Controllers; using Clinic.Api.Interfaces.Services; using Clinic.Api.DTOs.Appointment; @@ -11,28 +10,23 @@ namespace Clinic.Api.Controllers; /// [ApiController] [Route("api/appointments")] -public class AppointmentControllers : BaseControllers, IAppointmentController +public class AppointmentControllers(IAppointmentServices appointmentServices) : BaseControllers(appointmentServices) { - private readonly IAppointmentServices _appointmentServices; - - /// - /// Initializes a new instance of . - /// - /// Service for appointment operations. - public AppointmentControllers(IAppointmentServices service) : base(service) - { - _appointmentServices = service; - } - /// /// Gets all appointments for a specific doctor. /// /// The doctor's id. /// ActionResult containing appointments or NotFound if doctor not found. [HttpGet("doctor/{doctorId}")] - public IActionResult GetByDoctor(int doctorId) + [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public ActionResult> GetByDoctor(int doctorId) { - var appointments = _appointmentServices.GetAppointmentsByDoctor(doctorId); + var appointments = appointmentServices.GetAppointmentsByDoctor(doctorId); + if (appointments == null) + { + return NotFound("Doctor not found."); + } return Ok(appointments); } @@ -42,9 +36,15 @@ public IActionResult GetByDoctor(int doctorId) /// The patient's id. /// ActionResult containing appointments or NotFound if patient not found. [HttpGet("patient/{patientId}")] - public IActionResult GetByPatient(int patientId) + [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public ActionResult> GetByPatient(int patientId) { - var appointments = _appointmentServices.GetAppointmentsByPatient(patientId); + var appointments = appointmentServices.GetAppointmentsByPatient(patientId); + if (appointments == null) + { + return NotFound("Patient not found."); + } return Ok(appointments); } } diff --git a/Clinic/Clinic.Api/Controllers/BaseController.cs b/Clinic/Clinic.Api/Controllers/BaseControllers.cs similarity index 67% rename from Clinic/Clinic.Api/Controllers/BaseController.cs rename to Clinic/Clinic.Api/Controllers/BaseControllers.cs index c75cb7ed8..4f147c281 100644 --- a/Clinic/Clinic.Api/Controllers/BaseController.cs +++ b/Clinic/Clinic.Api/Controllers/BaseControllers.cs @@ -1,6 +1,7 @@ using Microsoft.AspNetCore.Mvc; -using Clinic.Api.Interfaces.Controllers; using Clinic.Api.Interfaces.Services; +using Clinic.Api.Services; +using Microsoft.AspNetCore.Mvc.ApplicationModels; namespace Clinic.Api.Controllers; @@ -12,29 +13,19 @@ namespace Clinic.Api.Controllers; /// DTO type for creating a new entity. /// DTO type for updating an existing entity. /// Service type that implements IBaseService. -public class BaseControllers : ControllerBase, IBaseController +public class BaseControllers(IBaseServices Service) : ControllerBase where TGetDto : class where TCreateDto : class where TUpdateDto : class - where TService : IBaseServices { - protected readonly TService Service; - - /// - /// Initializes a new instance of the class. - /// - /// The service instance for CRUD operations. - protected BaseControllers(TService service) - { - Service = service; - } /// /// Gets all entities. /// /// ActionResult containing a list of all entities. [HttpGet] - public virtual IActionResult GetAll() + [ProducesResponseType(StatusCodes.Status200OK)] + public virtual ActionResult> GetAll() { var entities = Service.GetAll(); return Ok(entities); @@ -46,7 +37,9 @@ public virtual IActionResult GetAll() /// The id of the entity. /// ActionResult containing the entity or NotFound if not found. [HttpGet("{id}")] - public virtual IActionResult Get(int id) + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public virtual ActionResult Get(int id) { var entity = Service.Get(id); if (entity == null) @@ -62,14 +55,17 @@ public virtual IActionResult Get(int id) /// The creation data. /// ActionResult containing the created entity or BadRequest if creation fails. [HttpPost] - public virtual IActionResult Create(TCreateDto dto) + [ProducesResponseType(StatusCodes.Status201Created)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + public virtual ActionResult Create(TCreateDto dto) { var entity = Service.Create(dto); if (entity == null) { return BadRequest("Could not create " + GetEntityName() + "."); } - return Ok(entity); + var id = GetEntityId(entity); + return CreatedAtAction(nameof(Get), new { id }, entity); } /// @@ -79,7 +75,9 @@ public virtual IActionResult Create(TCreateDto dto) /// The update data. /// ActionResult containing the updated entity or NotFound if not found. [HttpPut("{id}")] - public virtual IActionResult Update(int id, TUpdateDto dto) + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public virtual ActionResult Update(int id, TUpdateDto dto) { var entity = Service.Update(id, dto); if (entity == null) @@ -95,7 +93,9 @@ public virtual IActionResult Update(int id, TUpdateDto dto) /// The id of the entity to delete. /// ActionResult indicating success or NotFound if not found. [HttpDelete("{id}")] - public virtual IActionResult Delete(int id) + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public virtual ActionResult Delete(int id) { var result = Service.Delete(id); if (!result) @@ -114,5 +114,20 @@ protected virtual string GetEntityName() var name = typeof(TGetDto).Name; return name.Replace("Get", "").Replace("Dto", ""); } + + /// + /// Gets the entity ID using reflection. + /// + /// The entity DTO. + /// The entity ID. + protected virtual int GetEntityId(TGetDto entity) + { + var idProperty = typeof(TGetDto).GetProperty("Id"); + if (idProperty != null && idProperty.GetValue(entity) is int id) + { + return id; + } + return 0; + } } diff --git a/Clinic/Clinic.Api/Controllers/DoctorControllers.cs b/Clinic/Clinic.Api/Controllers/DoctorControllers.cs index 03fd62f19..92c6966f1 100644 --- a/Clinic/Clinic.Api/Controllers/DoctorControllers.cs +++ b/Clinic/Clinic.Api/Controllers/DoctorControllers.cs @@ -1,7 +1,5 @@ using Microsoft.AspNetCore.Mvc; -using Clinic.Api.Services; using Clinic.Api.DTOs.DoctorDto; -using Clinic.Api.Interfaces.Controllers; using Clinic.Api.Interfaces.Services; namespace Clinic.Api.Controllers; @@ -12,13 +10,4 @@ namespace Clinic.Api.Controllers; /// [ApiController] [Route("api/doctors")] -public class DoctorControllers : BaseControllers, IDoctorController -{ - /// - /// Initializes a new instance of . - /// - /// Service for doctor operations. - public DoctorControllers(IDoctorServices service) : base(service) - { - } -} \ No newline at end of file +public class DoctorControllers(IDoctorServices doctorServices) : BaseControllers(doctorServices); diff --git a/Clinic/Clinic.Api/Controllers/PatientControllers.cs b/Clinic/Clinic.Api/Controllers/PatientControllers.cs index 8ae5e5eab..c2b22ede2 100644 --- a/Clinic/Clinic.Api/Controllers/PatientControllers.cs +++ b/Clinic/Clinic.Api/Controllers/PatientControllers.cs @@ -1,6 +1,5 @@ using Microsoft.AspNetCore.Mvc; using Clinic.Api.DTOs.PatientDto; -using Clinic.Api.Interfaces.Controllers; using Clinic.Api.Interfaces.Services; namespace Clinic.Api.Controllers; @@ -11,13 +10,4 @@ namespace Clinic.Api.Controllers; /// [ApiController] [Route("api/patients")] -public class PatientControllers : BaseControllers -{ - /// - /// Initializes a new instance of . - /// - /// Service for patient operations. - public PatientControllers(IPatientServices service) : base(service) - { - } -} \ No newline at end of file +public class PatientControllers(IPatientServices patientServices) : BaseControllers(patientServices); diff --git a/Clinic/Clinic.Api/Controllers/SpecializationControllers.cs b/Clinic/Clinic.Api/Controllers/SpecializationControllers.cs index 67ada246f..67f8c1277 100644 --- a/Clinic/Clinic.Api/Controllers/SpecializationControllers.cs +++ b/Clinic/Clinic.Api/Controllers/SpecializationControllers.cs @@ -1,6 +1,5 @@ using Microsoft.AspNetCore.Mvc; using Clinic.Api.DTOs.SpecializationDto; -using Clinic.Api.Interfaces.Controllers; using Clinic.Api.Interfaces.Services; namespace Clinic.Api.Controllers; @@ -11,13 +10,4 @@ namespace Clinic.Api.Controllers; /// [ApiController] [Route("api/specializations")] -public class SpecializationControllers : BaseControllers, ISpecializationController -{ - /// - /// Initializes a new instance of . - /// - /// Service for specialization operations. - public SpecializationControllers(ISpecializationServices service) : base(service) - { - } -} +public class SpecializationControllers(ISpecializationServices specializationServices) : BaseControllers(specializationServices); diff --git a/Clinic/Clinic.Api/Controllers/TestControllers.cs b/Clinic/Clinic.Api/Controllers/TestControllers.cs deleted file mode 100644 index 2deb430a9..000000000 --- a/Clinic/Clinic.Api/Controllers/TestControllers.cs +++ /dev/null @@ -1,44 +0,0 @@ -using Microsoft.AspNetCore.Mvc; -using Clinic.Api.Services; - -[ApiController] -[Route("api/[controller]")] -public class TestControllers : ControllerBase -{ - private readonly TestServices _testServices; - - public TestControllers(TestServices testServices) - { - _testServices = testServices; - } - - [HttpGet("doctors/experience")] - public IActionResult GetDoctorsWithExperience() - { - return Ok(_testServices.GetDoctorsWithExperience10YearsOrMore()); - } - - [HttpGet("patients/doctor")] - public IActionResult GetPatientsByDoctor(int doctorId) - { - return Ok(_testServices.GetPatientsByDoctorOrderedByFullName(doctorId)); - } - - [HttpGet("patients/return-visits")] - public IActionResult GetReturnVisitsCountLastMonth() - { - return Ok(_testServices.GetReturnVisitsCountLastMonth()); - } - - [HttpGet("patients/over-30")] - public IActionResult GetPatientsOver30WithMultipleDoctors() - { - return Ok(_testServices.GetPatientsOver30WithMultipleDoctorsOrderedByBirthDate()); - } - - [HttpGet("appointments/room")] - public IActionResult GetAppointmentsInRoomForCurrentMonth(int roomNumber) - { - return Ok(_testServices.GetAppointmentsInRoomForCurrentMonth(roomNumber)); - } -} \ No newline at end of file diff --git a/Clinic/Clinic.Api/Interfaces/Controllers/IAppointmentController.cs b/Clinic/Clinic.Api/Interfaces/Controllers/IAppointmentController.cs deleted file mode 100644 index 426ac2a6e..000000000 --- a/Clinic/Clinic.Api/Interfaces/Controllers/IAppointmentController.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Microsoft.AspNetCore.Mvc; -using Clinic.Api.DTOs.Appointment; - -namespace Clinic.Api.Interfaces.Controllers; - -/// -/// Interface for appointment controller operations. -/// Extends the base controller interface with appointment-specific methods. -/// -public interface IAppointmentController : IBaseController -{ - /// - /// Gets all appointments for a specific doctor. - /// - /// The doctor's id. - /// ActionResult containing appointments or NotFound if doctor not found. - public IActionResult GetByDoctor(int doctorId); - - /// - /// Gets all appointments for a specific patient. - /// - /// The patient's id. - /// ActionResult containing appointments or NotFound if patient not found. - public IActionResult GetByPatient(int patientId); -} - diff --git a/Clinic/Clinic.Api/Interfaces/Controllers/IBaseController.cs b/Clinic/Clinic.Api/Interfaces/Controllers/IBaseController.cs deleted file mode 100644 index c6b7fbb24..000000000 --- a/Clinic/Clinic.Api/Interfaces/Controllers/IBaseController.cs +++ /dev/null @@ -1,51 +0,0 @@ -using Microsoft.AspNetCore.Mvc; - -namespace Clinic.Api.Interfaces.Controllers; - -/// -/// Base generic interface for controllers providing standard CRUD operations. -/// -/// DTO type for retrieving entity data. -/// DTO type for creating a new entity. -/// DTO type for updating an existing entity. -public interface IBaseController - where TGetDto : class - where TCreateDto : class - where TUpdateDto : class -{ - /// - /// Gets all entities. - /// - /// ActionResult containing a list of all entities. - public IActionResult GetAll(); - - /// - /// Gets a specific entity by its id. - /// - /// The id of the entity. - /// ActionResult containing the entity or NotFound if not found. - public IActionResult Get(int id); - - /// - /// Creates a new entity. - /// - /// The creation data. - /// ActionResult containing the created entity or BadRequest if creation fails. - public IActionResult Create(TCreateDto dto); - - /// - /// Updates an existing entity. - /// - /// The id of the entity to update. - /// The update data. - /// ActionResult containing the updated entity or NotFound if not found. - public IActionResult Update(int id, TUpdateDto dto); - - /// - /// Deletes an entity by its id. - /// - /// The id of the entity to delete. - /// ActionResult indicating success or NotFound if not found. - public IActionResult Delete(int id); -} - diff --git a/Clinic/Clinic.Api/Interfaces/Controllers/IDoctorController.cs b/Clinic/Clinic.Api/Interfaces/Controllers/IDoctorController.cs deleted file mode 100644 index fff7b3211..000000000 --- a/Clinic/Clinic.Api/Interfaces/Controllers/IDoctorController.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Clinic.Api.DTOs.DoctorDto; - -namespace Clinic.Api.Interfaces.Controllers; - -/// -/// Interface for doctor controller operations. -/// -public interface IDoctorController : IBaseController -{ -} - diff --git a/Clinic/Clinic.Api/Interfaces/Controllers/IPatientController.cs b/Clinic/Clinic.Api/Interfaces/Controllers/IPatientController.cs deleted file mode 100644 index 9fd8ecc4c..000000000 --- a/Clinic/Clinic.Api/Interfaces/Controllers/IPatientController.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Clinic.Api.DTOs.PatientDto; - -namespace Clinic.Api.Interfaces.Controllers; - -/// -/// Interface for patient controller operations. -/// -public interface IPatientController : IBaseController -{ -} - diff --git a/Clinic/Clinic.Api/Interfaces/Controllers/ISpecializationController.cs b/Clinic/Clinic.Api/Interfaces/Controllers/ISpecializationController.cs deleted file mode 100644 index 74cc7b454..000000000 --- a/Clinic/Clinic.Api/Interfaces/Controllers/ISpecializationController.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Clinic.Api.DTOs.SpecializationDto; - -namespace Clinic.Api.Interfaces.Controllers; - -/// -/// Interface for specialization controller operations. -/// -public interface ISpecializationController : IBaseController -{ -} - From 95f8bcfb579ad478c45c0df60196d6239df56111 Mon Sep 17 00:00:00 2001 From: maeosha Date: Sun, 21 Dec 2025 14:17:01 +0300 Subject: [PATCH 44/56] Refactor clinic database structure and services - Removed IClinicDataBase interface and its implementation. - Deleted DataSeed class for test data. - Updated Program.cs to configure Entity Framework and database context. - Created AnalyticsServices for analytics-related functionalities. - Refactored AppointmentServices, DoctorServices, PatientServices, and SpecializationServices to use specific data interfaces. - Updated appsettings for database connection strings and logging levels. --- Clinic/Clinic.Api/Clinic.Api.csproj | 7 + Clinic/Clinic.Api/Clinic.Api.http | 6 - Clinic/Clinic.Api/Converter/DataConverter.cs | 16 + .../DTOs/Appointment/CreateAppointmentDto.cs | 17 +- .../DTOs/Appointment/GetAppointmentDto.cs | 31 + .../DTOs/Appointment/UpdateAppointment.cs | 21 +- .../Clinic.Api/DTOs/Doctor/CreateDoctorDto.cs | 55 +- Clinic/Clinic.Api/DTOs/Doctor/GetDoctorDto.cs | 35 ++ .../Clinic.Api/DTOs/Doctor/UpdateDoctorDto.cs | 48 +- .../DTOs/Patient/CreatePatientDto.cs | 59 +- .../Clinic.Api/DTOs/Patient/GetPatientDto.cs | 31 + .../DTOs/Patient/UpdatePatientDto.cs | 31 + .../Specialization/CreateSpecializationDto.cs | 7 +- .../Specialization/GetSpecializationDto.cs | 7 + .../Specialization/UpdateSpecializationDto.cs | 3 + Clinic/Clinic.Api/DataBase/ClinicDataBase.cs | 157 ----- Clinic/Clinic.Api/DataBase/IClinicDataBase.cs | 184 ------ Clinic/Clinic.Api/DataSeed/DataSeed.cs | 570 ------------------ Clinic/Clinic.Api/Program.cs | 32 +- .../{TestServices.cs => AnalyticsServices.cs} | 39 +- .../Services/AppointmentServices.cs | 42 +- Clinic/Clinic.Api/Services/DoctorServices.cs | 6 +- Clinic/Clinic.Api/Services/PatientServices.cs | 6 +- .../Services/SpecializationServices.cs | 6 +- .../Clinic.Api/appsettings.Development.json | 20 +- Clinic/Clinic.Api/appsettings.json | 19 +- 26 files changed, 435 insertions(+), 1020 deletions(-) delete mode 100644 Clinic/Clinic.Api/Clinic.Api.http delete mode 100644 Clinic/Clinic.Api/DataBase/ClinicDataBase.cs delete mode 100644 Clinic/Clinic.Api/DataBase/IClinicDataBase.cs delete mode 100644 Clinic/Clinic.Api/DataSeed/DataSeed.cs rename Clinic/Clinic.Api/Services/{TestServices.cs => AnalyticsServices.cs} (73%) diff --git a/Clinic/Clinic.Api/Clinic.Api.csproj b/Clinic/Clinic.Api/Clinic.Api.csproj index fc42f7a59..3bc882ae6 100644 --- a/Clinic/Clinic.Api/Clinic.Api.csproj +++ b/Clinic/Clinic.Api/Clinic.Api.csproj @@ -12,10 +12,17 @@ + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + \ No newline at end of file diff --git a/Clinic/Clinic.Api/Clinic.Api.http b/Clinic/Clinic.Api/Clinic.Api.http deleted file mode 100644 index 6574707f4..000000000 --- a/Clinic/Clinic.Api/Clinic.Api.http +++ /dev/null @@ -1,6 +0,0 @@ -@Clinic.Api_HostAddress = http://localhost:5042 - -GET {{Clinic.Api_HostAddress}}/weatherforecast/ -Accept: application/json - -### diff --git a/Clinic/Clinic.Api/Converter/DataConverter.cs b/Clinic/Clinic.Api/Converter/DataConverter.cs index 82b77d496..ac63b6a78 100644 --- a/Clinic/Clinic.Api/Converter/DataConverter.cs +++ b/Clinic/Clinic.Api/Converter/DataConverter.cs @@ -3,13 +3,29 @@ namespace Clinic.Api.Converter; +/// +/// Custom JSON converter for DateOnly type, handling serialization and deserialization using the "yyyy-MM-dd" format. +/// public class DateConverter : JsonConverter { private const string Format = "yyyy-MM-dd"; + /// + /// Deserializes a JSON string in "yyyy-MM-dd" format to a DateOnly object. + /// + /// The UTF-8 JSON reader. + /// The type to convert (DateOnly). + /// The serializer options. + /// The parsed DateOnly value. public override DateOnly Read(ref Utf8JsonReader reader, Type type, JsonSerializerOptions options) => DateOnly.ParseExact(reader.GetString()!, Format); + /// + /// Serializes a DateOnly object to a JSON string in "yyyy-MM-dd" format. + /// + /// The UTF-8 JSON writer. + /// The DateOnly value to serialize. + /// The serializer options. public override void Write(Utf8JsonWriter writer, DateOnly value, JsonSerializerOptions options) => writer.WriteStringValue(value.ToString(Format)); } \ No newline at end of file diff --git a/Clinic/Clinic.Api/DTOs/Appointment/CreateAppointmentDto.cs b/Clinic/Clinic.Api/DTOs/Appointment/CreateAppointmentDto.cs index 64066ed7e..9777f01c5 100644 --- a/Clinic/Clinic.Api/DTOs/Appointment/CreateAppointmentDto.cs +++ b/Clinic/Clinic.Api/DTOs/Appointment/CreateAppointmentDto.cs @@ -1,5 +1,3 @@ -using System.ComponentModel.DataAnnotations; - namespace Clinic.Api.DTOs.Appointment; /// @@ -11,30 +9,25 @@ public class CreateAppointmentDto /// /// The full name of the patient for the appointment. /// - [Required] - public string PatientFullName { get; set; } = null!; + public required string PatientFullName { get; set; } /// /// The full name of the doctor for the appointment. /// - [Required] - public string DoctorFullName { get; set; } = null!; + public required string DoctorFullName { get; set; } /// /// The date and time of the appointment. /// - [Required] - public DateTime DateTime { get; set; } + public required DateTime DateTime { get; set; } /// /// The room number where the appointment will take place. /// - [Required] - public int RoomNumber { get; set; } + public required int RoomNumber { get; set; } /// /// Indicates whether the appointment is a return visit. /// - [Required] - public bool IsReturnVisit { get; set; } + public required bool IsReturnVisit { get; set; } } diff --git a/Clinic/Clinic.Api/DTOs/Appointment/GetAppointmentDto.cs b/Clinic/Clinic.Api/DTOs/Appointment/GetAppointmentDto.cs index fb026bebc..d7bff0f62 100644 --- a/Clinic/Clinic.Api/DTOs/Appointment/GetAppointmentDto.cs +++ b/Clinic/Clinic.Api/DTOs/Appointment/GetAppointmentDto.cs @@ -7,12 +7,43 @@ namespace Clinic.Api.DTOs.Appointment; public class GetAppointmentDto { + /// + /// The unique identifier of the appointment. + /// public int Id { get; set; } + + /// + /// The ID of the patient associated with the appointment. + /// public int PatientId { get; set; } + + /// + /// The ID of the doctor associated with the appointment. + /// public int DoctorId { get; set; } + + /// + /// The full name of the patient. + /// public string PatientFullName { get; set; } = null!; + + /// + /// The full name of the doctor. + /// public string DoctorFullName { get; set; } = null!; + + /// + /// The date and time of the appointment. + /// public DateTime DateTime { get; set; } + + /// + /// The room number where the appointment takes place. + /// public int RoomNumber { get; set; } + + /// + /// Indicates whether this is a return visit for the patient. + /// public bool IsReturnVisit { get; set; } } diff --git a/Clinic/Clinic.Api/DTOs/Appointment/UpdateAppointment.cs b/Clinic/Clinic.Api/DTOs/Appointment/UpdateAppointment.cs index 3c3985d81..d3898c16a 100644 --- a/Clinic/Clinic.Api/DTOs/Appointment/UpdateAppointment.cs +++ b/Clinic/Clinic.Api/DTOs/Appointment/UpdateAppointment.cs @@ -6,9 +6,28 @@ namespace Clinic.Api.DTOs.Appointment; /// public class UpdateAppointmentDto { + /// + /// Optional: The ID of the patient to update for the appointment. + /// public int? PatientId { get; set; } = null; + + /// + /// Optional: The ID of the doctor to update for the appointment. + /// public int? DoctorId { get; set; } = null; + + /// + /// Optional: The new date and time for the appointment. + /// public DateTime? DateTime { get; set; } = null; + + /// + /// Optional: The new room number for the appointment. + /// public int? RoomNumber { get; set; } = null; + + /// + /// Optional: Flag indicating if this is a return visit. + /// public bool? IsReturnVisit { get; set; } = null; -} +} \ No newline at end of file diff --git a/Clinic/Clinic.Api/DTOs/Doctor/CreateDoctorDto.cs b/Clinic/Clinic.Api/DTOs/Doctor/CreateDoctorDto.cs index e3c9b80a9..e70491628 100644 --- a/Clinic/Clinic.Api/DTOs/Doctor/CreateDoctorDto.cs +++ b/Clinic/Clinic.Api/DTOs/Doctor/CreateDoctorDto.cs @@ -1,5 +1,5 @@ -using System.ComponentModel.DataAnnotations; using Clinic.Models.Entities; +using Clinic.Api.DTOs.SpecializationDto; namespace Clinic.Api.DTOs.DoctorDto; @@ -9,13 +9,48 @@ namespace Clinic.Api.DTOs.DoctorDto; /// public class CreateDoctorDto { - [Required]public string PassportNumber { get; set; } = null!; - [Required]public DateOnly BirthDate { get; set; } - [Required]public string LastName { get; set; } = null!; - [Required]public string FirstName { get; set; } = null!; - [Required]public string PhoneNumber { get; set; } = null!; + /// + /// Required: The passport number of the doctor. + /// + public required string PassportNumber { get; set; } + + /// + /// Required: The birth date of the doctor. + /// + public required DateOnly BirthDate { get; set; } + + /// + /// Required: The last name of the doctor. + /// + public required string LastName { get; set; } + + /// + /// Required: The first name of the doctor. + /// + public required string FirstName { get; set; } + + /// + /// Required: The phone number of the doctor. + /// + public required string PhoneNumber { get; set; } + + /// + /// Optional: The patronymic (middle name) of the doctor. + /// public string? Patronymic { get; set; } - [Required]public String Gender { get; set; } = null!; - [Required]public List Specializations { get; set; } = null!; - [Required]public int ExperienceYears { get; set; } -} + + /// + /// Required: The gender of the doctor. + /// + public required String Gender { get; set; } + + /// + /// Required: The list of specializations for the doctor. + /// + public required List Specializations { get; set; } + + /// + /// Required: The number of years of experience for the doctor. + /// + public required int ExperienceYears { get; set; } +} \ No newline at end of file diff --git a/Clinic/Clinic.Api/DTOs/Doctor/GetDoctorDto.cs b/Clinic/Clinic.Api/DTOs/Doctor/GetDoctorDto.cs index 88d98e2ee..aecb93529 100644 --- a/Clinic/Clinic.Api/DTOs/Doctor/GetDoctorDto.cs +++ b/Clinic/Clinic.Api/DTOs/Doctor/GetDoctorDto.cs @@ -7,13 +7,48 @@ namespace Clinic.Api.DTOs.DoctorDto; /// public class GetDoctorDto { + /// + /// The unique identifier of the doctor. + /// public int Id { get; set; } + + /// + /// The passport number of the doctor. + /// public string PassportNumber { get; set; } = string.Empty; + + /// + /// The birth date of the doctor. + /// public DateOnly BirthDate { get; set; } + + /// + /// The last name of the doctor. + /// public string LastName { get; set; } = string.Empty; + + /// + /// The first name of the doctor. + /// public string FirstName { get; set; } = string.Empty; + + /// + /// Optional: The patronymic (middle name) of the doctor. + /// public string? Patronymic { get; set; } + + /// + /// The gender of the doctor. + /// public String Gender { get; set; } = null!; + + /// + /// The list of specializations for the doctor. + /// public List Specializations { get; set; } = new(); + + /// + /// The number of years of experience for the doctor. + /// public int ExperienceYears { get; set; } } \ No newline at end of file diff --git a/Clinic/Clinic.Api/DTOs/Doctor/UpdateDoctorDto.cs b/Clinic/Clinic.Api/DTOs/Doctor/UpdateDoctorDto.cs index 703a44bc3..76b0a3150 100644 --- a/Clinic/Clinic.Api/DTOs/Doctor/UpdateDoctorDto.cs +++ b/Clinic/Clinic.Api/DTOs/Doctor/UpdateDoctorDto.cs @@ -9,12 +9,48 @@ namespace Clinic.Api.DTOs.DoctorDto; /// public class UpdateDoctorDto { - public string? PassportNumber { get; set; } + /// + /// The unique identifier of the doctor. + /// + public int? Id { get; set; } + + /// + /// The passport number of the doctor. + /// + public string? PassportNumber { get; set; } = string.Empty; + + /// + /// The birth date of the doctor. + /// public DateOnly? BirthDate { get; set; } - public string? LastName { get; set; } - public string? FirstName { get; set; } + + /// + /// The last name of the doctor. + /// + public string? LastName { get; set; } = string.Empty; + + /// + /// The first name of the doctor. + /// + public string? FirstName { get; set; } = string.Empty; + + /// + /// Optional: The patronymic (middle name) of the doctor. + /// public string? Patronymic { get; set; } - public Gender? Gender { get; set; } - public List? Specializations { get; set; } = null; - public int? ExperienceYears { get; set; } + + /// + /// The gender of the doctor. + /// + public String? Gender { get; set; } = null!; + + /// + /// The list of specializations for the doctor. + /// + public List? Specializations { get; set; } = null!; + + /// + /// The number of years of experience for the doctor. + /// + public int? ExperienceYears { get; set; } = null!; } \ No newline at end of file diff --git a/Clinic/Clinic.Api/DTOs/Patient/CreatePatientDto.cs b/Clinic/Clinic.Api/DTOs/Patient/CreatePatientDto.cs index 0637ed0fa..eafeb1f46 100644 --- a/Clinic/Clinic.Api/DTOs/Patient/CreatePatientDto.cs +++ b/Clinic/Clinic.Api/DTOs/Patient/CreatePatientDto.cs @@ -1,5 +1,3 @@ -using System.ComponentModel.DataAnnotations; - namespace Clinic.Api.DTOs.PatientDto; /// @@ -8,14 +6,53 @@ namespace Clinic.Api.DTOs.PatientDto; /// public class CreatePatientDto { - [Required]public string FirstName { get; set; } = null!; - [Required]public string LastName { get; set; } = null!; + /// + /// The patient's first name. + /// + public required string FirstName { get; set; } + + /// + /// The patient's last name. + /// + public required string LastName { get; set; } + + /// + /// Optional patronymic (middle name) of the patient. + /// public string? Patronymic { get; set; } - [Required]public string PassportNumber { get; set; } = null!; - [Required]public DateOnly BirthDate { get; set; } - [Required]public String Gender { get; set; } = null!; - [Required]public string Address { get; set; } = null!; - [Required]public string PhoneNumber { get; set; } = null!; - [Required]public String BloodGroup { get; set; } = null!; - [Required]public String RhesusFactor { get; set; } = null!; + + /// + /// The patient's passport number. + /// + public required string PassportNumber { get; set; } + + /// + /// The patient's birth date. + /// + public required DateOnly BirthDate { get; set; } + + /// + /// The patient's gender as a string (e.g. "Male", "Female"). + /// + public required String Gender { get; set; } + + /// + /// The patient's residential address. + /// + public required string Address { get; set; } + + /// + /// The patient's contact phone number. + /// + public required string PhoneNumber { get; set; } + + /// + /// The patient's blood group as a string (e.g. "A", "B", "AB", "O"). + /// + public required String BloodGroup { get; set; } + + /// + /// The patient's rhesus factor as a string (e.g. "Positive", "Negative"). + /// + public required String RhesusFactor { get; set; } } \ No newline at end of file diff --git a/Clinic/Clinic.Api/DTOs/Patient/GetPatientDto.cs b/Clinic/Clinic.Api/DTOs/Patient/GetPatientDto.cs index ccc6eaa30..da95c2ffb 100644 --- a/Clinic/Clinic.Api/DTOs/Patient/GetPatientDto.cs +++ b/Clinic/Clinic.Api/DTOs/Patient/GetPatientDto.cs @@ -8,12 +8,43 @@ namespace Clinic.Api.DTOs.PatientDto; /// public class GetPatientDto { + /// + /// The unique identifier of the patient. + /// public int Id { get; set; } + + /// + /// The patient's first name. + /// public string FirstName { get; set; } = null!; + + /// + /// The patient's last name. + /// public string LastName { get; set; } = null!; + + /// + /// Optional patronymic (middle name) of the patient. + /// public string? Patronymic { get; set; } + + /// + /// The patient's birth date. + /// public DateOnly BirthDate { get; set; } + + /// + /// The patient's gender as a string. + /// public String Gender { get; set; } = null!; + + /// + /// The patient's blood group as a string (A, B, AB, O). + /// public String BloodGroup { get; set; } = null!; + + /// + /// The patient's rhesus factor as a string (Positive/Negative). + /// public String RhesusFactor { get; set; } = null!; } \ No newline at end of file diff --git a/Clinic/Clinic.Api/DTOs/Patient/UpdatePatientDto.cs b/Clinic/Clinic.Api/DTOs/Patient/UpdatePatientDto.cs index 75076868f..202d0f499 100644 --- a/Clinic/Clinic.Api/DTOs/Patient/UpdatePatientDto.cs +++ b/Clinic/Clinic.Api/DTOs/Patient/UpdatePatientDto.cs @@ -8,12 +8,43 @@ namespace Clinic.Api.DTOs.PatientDto; /// public class UpdatePatientDto { + /// + /// Optional new first name for the patient. + /// public string? FirstName { get; set; } = null; + + /// + /// Optional new last name for the patient. + /// public string? LastName { get; set; } = null; + + /// + /// Optional new patronymic (middle name) for the patient. + /// public string? Patronymic { get; set; } = null; + + /// + /// Optional new date of birth for the patient. + /// public DateTime? DateOfBirth { get; set; } = null; + + /// + /// Optional new address for the patient. + /// public string? Address { get; set; } = null; + + /// + /// Optional new phone number for the patient. + /// public string? PhoneNumber { get; set; } = null; + + /// + /// Optional new blood group for the patient. + /// public BloodGroup? BloodGroup { get; set; } = null; + + /// + /// Optional new rhesus factor for the patient. + /// public RhesusFactor? RhesusFactor { get; set; } = null; } \ No newline at end of file diff --git a/Clinic/Clinic.Api/DTOs/Specialization/CreateSpecializationDto.cs b/Clinic/Clinic.Api/DTOs/Specialization/CreateSpecializationDto.cs index f0802dada..309064d75 100644 --- a/Clinic/Clinic.Api/DTOs/Specialization/CreateSpecializationDto.cs +++ b/Clinic/Clinic.Api/DTOs/Specialization/CreateSpecializationDto.cs @@ -1,5 +1,3 @@ -using System.ComponentModel.DataAnnotations; - namespace Clinic.Api.DTOs.SpecializationDto; /// @@ -7,5 +5,8 @@ namespace Clinic.Api.DTOs.SpecializationDto; /// public class CreateSpecializationDto { - [Required]public string Name { get; set; } = null!; + /// + /// The name of the specialization to create. + /// + public required string Name { get; set; } } \ No newline at end of file diff --git a/Clinic/Clinic.Api/DTOs/Specialization/GetSpecializationDto.cs b/Clinic/Clinic.Api/DTOs/Specialization/GetSpecializationDto.cs index e83a885a4..dcbbcca99 100644 --- a/Clinic/Clinic.Api/DTOs/Specialization/GetSpecializationDto.cs +++ b/Clinic/Clinic.Api/DTOs/Specialization/GetSpecializationDto.cs @@ -6,6 +6,13 @@ namespace Clinic.Api.DTOs.SpecializationDto; /// public class GetSpecializationDto { + /// + /// The unique identifier of the specialization. + /// public int Id { get; set; } + + /// + /// The name of the specialization. + /// public string Name { get; set; } = null!; } \ No newline at end of file diff --git a/Clinic/Clinic.Api/DTOs/Specialization/UpdateSpecializationDto.cs b/Clinic/Clinic.Api/DTOs/Specialization/UpdateSpecializationDto.cs index 4e01fd732..c67b11e69 100644 --- a/Clinic/Clinic.Api/DTOs/Specialization/UpdateSpecializationDto.cs +++ b/Clinic/Clinic.Api/DTOs/Specialization/UpdateSpecializationDto.cs @@ -6,6 +6,9 @@ namespace Clinic.Api.DTOs.SpecializationDto; /// public class UpdateSpecializationDto { + /// + /// The new name of the specialization. Optional. + /// public string? Name { get; set; } } diff --git a/Clinic/Clinic.Api/DataBase/ClinicDataBase.cs b/Clinic/Clinic.Api/DataBase/ClinicDataBase.cs deleted file mode 100644 index f62adf14e..000000000 --- a/Clinic/Clinic.Api/DataBase/ClinicDataBase.cs +++ /dev/null @@ -1,157 +0,0 @@ -using Clinic.Api.DataSeed; -using Clinic.Models.Entities; - -namespace Clinic.Api.DataBase; -public sealed class ClinicDataBase : IClinicDataBase -{ - /// - /// In-memory storage for patients, doctors, and appointments. - /// - private readonly Dictionary _patients = new(); - private readonly Dictionary _doctors = new(); - private readonly Dictionary _appointments = new(); - private readonly Dictionary _specializations = new(); - - public ClinicDataBase() - { - var dataSeed = new DataSeed.DataSeed(); - dataSeed.TestDataSeed(this); - } - - public Patient? GetPatient(int Id) => _patients.GetValueOrDefault(Id); - - public IReadOnlyCollection GetAllPatients() => _patients.Values; - public bool AddPatient(Patient patient){ - if (_patients.ContainsValue(patient)){ - return false; - } - - _patients[patient.Id] = patient; - return true; - } - - public bool UpdatePatient(Patient patient){ - if (!_patients.ContainsKey(patient.Id)){ - return false; - } - _patients[patient.Id] = patient; - return true; - } - - public bool RemovePatient(int Id) => _patients.Remove(Id); - - public int PatientCount() => _patients.Count; - - - public IReadOnlyCollection GetAllSpecializations() => _specializations.Values; - - public bool AddSpecialization(Specialization specialization) - { - if (_specializations.ContainsValue(specialization)){ - return false; - } - _specializations[specialization.Id] = specialization; - return true; - } - - public bool RemoveSpecialization(int id) => _specializations.Remove(id); - public Specialization? GetSpecialization(int id) - { - if (!_specializations.ContainsKey(id)) - { - return null; - } - return _specializations[id]; - } - - public int SpecializationCount() => _specializations.Count; - - public Specialization? UpdateSpecialization(int id, Specialization specialization){ - if (!_specializations.ContainsKey(id)){ - return null; - } - _specializations[id].Name = specialization.Name; - return _specializations[id]; - } - - - public Doctor? GetDoctor(int Id) => _doctors.GetValueOrDefault(Id); - - public IReadOnlyCollection GetAllDoctors() => _doctors.Values; - - public bool AddDoctor(Doctor doctor){ - if (_doctors.ContainsKey(doctor.Id)){ - return false; - } - _doctors[doctor.Id] = doctor; - return true; - } - - public bool UpdateDoctor(Doctor doctor){ - if (!_doctors.ContainsKey(doctor.Id)){ - return false; - } - _doctors[doctor.Id] = doctor; - return true; - } - - public bool RemoveDoctor(int Id) => _doctors.Remove(Id); - - public int DoctorCount() => _doctors.Count(); - - - public Appointment? GetAppointment(int Id) => _appointments.GetValueOrDefault(Id); - - public IReadOnlyCollection GetAllAppointments() => _appointments.Values; - - public IReadOnlyCollection GetAppointmentsByDoctor(int Id) => - _appointments.Values.Where(a => a.DoctorId == Id).ToList(); - - public IReadOnlyCollection GetAppointmentsByPatient(int Id) => - _appointments.Values.Where(a => a.PatientId == Id).ToList(); - - public bool AddAppointment(Appointment appointment){ - if (_appointments.ContainsKey(appointment.Id)){ - return false; - } - - var patient = _patients.GetValueOrDefault(appointment.PatientId); - var doctor = _doctors.GetValueOrDefault(appointment.DoctorId); - - if (patient == null || doctor == null){ - return false; - } - - _appointments[appointment.Id] = appointment; - return true; - } - - public bool UpdateAppointment(Appointment appointment){ - if (!_appointments.ContainsKey(appointment.Id)){ - return false; - } - - if (appointment.PatientId == 0 || appointment.DoctorId == 0){ - return false; - } - - var patient = _patients.GetValueOrDefault(appointment.PatientId); - - if (patient.GetFullName() != appointment.PatientFullName){ - appointment.PatientFullName = patient.GetFullName(); - } - - var doctor = _doctors.GetValueOrDefault(appointment.DoctorId); - - if (doctor.GetFullName() != appointment.DoctorFullName){ - appointment.DoctorFullName = doctor.GetFullName(); - } - - _appointments[appointment.Id] = appointment; - return true; - } - - public bool RemoveAppointment(int Id) => _appointments.Remove(Id); - - public int AppointmentCount() => _appointments.Count(); -} diff --git a/Clinic/Clinic.Api/DataBase/IClinicDataBase.cs b/Clinic/Clinic.Api/DataBase/IClinicDataBase.cs deleted file mode 100644 index ef88ac6b4..000000000 --- a/Clinic/Clinic.Api/DataBase/IClinicDataBase.cs +++ /dev/null @@ -1,184 +0,0 @@ -using Clinic.Models.Entities; - -namespace Clinic.Api.DataBase; - -/// -/// Interface for a clinic database, providing CRUD operations and queries -/// for patients, doctors, specializations, and appointments. -/// -public interface IClinicDataBase -{ - /// - /// Retrieves a patient by their unique identifier. - /// - /// The patient's ID. - /// The patient if found; otherwise, null. - public Patient? GetPatient(int Id); - - /// - /// Gets all patients in the database. - /// - /// A read-only collection of all patients. - public IReadOnlyCollection GetAllPatients(); - - /// - /// Adds a new patient to the database. - /// - /// The patient to add. - /// True if added successfully; otherwise, false. - public bool AddPatient(Patient patient); - - /// - /// Updates an existing patient's details. - /// - /// The patient with updated information. - /// True if updated successfully; otherwise, false. - public bool UpdatePatient(Patient patient); - - /// - /// Removes a patient by their ID. - /// - /// The patient's ID to remove. - /// True if removed successfully; otherwise, false. - public bool RemovePatient(int Id); - - /// - /// Gets the total number of patients. - /// - /// The patient count. - public int PatientCount(); - - /// - /// Retrieves all specializations in the system. - /// - /// A read-only collection of all specializations. - public IReadOnlyCollection GetAllSpecializations(); - - /// - /// Adds a new specialization. - /// - /// The specialization to add. - /// True if added; false if already exists. - public bool AddSpecialization(Specialization specialization); - - /// - /// Updates an existing specialization. - /// - /// The specialization with updated information. - /// True if updated; otherwise, false. - public Specialization? UpdateSpecialization(int id, Specialization specialization); - - /// - /// Retrieves a specialization by its unique identifier. - /// - /// The specialization's ID. - /// The specialization if found; otherwise, null. - public Specialization? GetSpecialization(int id); - - /// - /// Removes a specialization by its ID. - /// - /// The specialization's ID. - /// True if removed; otherwise, false. - public bool RemoveSpecialization(int id); - - /// - /// Gets the total specialization count. - /// - /// The number of specializations. - public int SpecializationCount(); - - /// - /// Retrieves a doctor by their unique identifier. - /// - /// The doctor's ID. - /// The doctor if found; otherwise, null. - public Doctor? GetDoctor(int Id); - - /// - /// Gets all doctors. - /// - /// A read-only collection of all doctors. - public IReadOnlyCollection GetAllDoctors(); - - /// - /// Adds a new doctor. - /// - /// The doctor to add. - /// True if added; otherwise, false. - public bool AddDoctor(Doctor doctor); - - /// - /// Updates details for an existing doctor. - /// - /// The doctor with updated details. - /// True if updated; otherwise, false. - public bool UpdateDoctor(Doctor doctor); - - /// - /// Removes a doctor by their ID. - /// - /// The doctor's ID. - /// True if removed; otherwise, false. - public bool RemoveDoctor(int Id); - - /// - /// Gets the total doctor count. - /// - /// The number of doctors. - public int DoctorCount(); - - /// - /// Retrieves an appointment by its unique identifier. - /// - /// The appointment's ID. - /// The appointment if found; otherwise, null. - public Appointment? GetAppointment(int Id); - - /// - /// Gets all appointments. - /// - /// A read-only collection of all appointments. - public IReadOnlyCollection GetAllAppointments(); - - /// - /// Gets all appointments for a specific doctor. - /// - /// The doctor's ID. - /// A collection of appointments for the doctor. - public IReadOnlyCollection GetAppointmentsByDoctor(int Id); - - /// - /// Gets all appointments for a specific patient. - /// - /// The patient's ID. - /// A collection of appointments for the patient. - public IReadOnlyCollection GetAppointmentsByPatient(int Id); - - /// - /// Adds a new appointment. - /// - /// The appointment to add. - /// True if added; otherwise, false. - public bool AddAppointment(Appointment appointment); - - /// - /// Updates an existing appointment. - /// - /// The appointment with updated details. - /// True if updated; otherwise, false. - public bool UpdateAppointment(Appointment appointment); - - /// - /// Removes an appointment by its ID. - /// - /// The appointment's ID. - /// True if removed; otherwise, false. - public bool RemoveAppointment(int Id); - - /// - /// Gets the total appointment count. - /// - /// The number of appointments. - public int AppointmentCount(); -} \ No newline at end of file diff --git a/Clinic/Clinic.Api/DataSeed/DataSeed.cs b/Clinic/Clinic.Api/DataSeed/DataSeed.cs deleted file mode 100644 index 93c8de071..000000000 --- a/Clinic/Clinic.Api/DataSeed/DataSeed.cs +++ /dev/null @@ -1,570 +0,0 @@ -using Clinic.Api.DataBase; -using Clinic.Models.Entities; -using Clinic.Models.Enums; - -namespace Clinic.Api.DataSeed; - -public class DataSeed -{ - public void TestDataSeed(IClinicDataBase _db) - { - var patient1 = new Patient - { - Id = 1, - PassportNumber = "P001", - LastName = "Иванов", - FirstName = "Иван", - Patronymic = "Иванович", - BloodGroup = BloodGroup.A, - RhesusFactor = RhesusFactor.Positive, - Address = "г. Москва, ул. Ленина, д. 10", - Gender = Gender.Male, - BirthDate = new DateOnly(1985, 5, 20), - PhoneNumber = "+79991234567" - }; - _db.AddPatient(patient1); - - var patient2 = new Patient - { - Id = 2, - PassportNumber = "P002", - LastName = "Петрова", - FirstName = "Мария", - Patronymic = "Сергеевна", - BloodGroup = BloodGroup.B, - RhesusFactor = RhesusFactor.Positive, - Address = "г. Санкт-Петербург, ул. Пушкина, д. 25", - Gender = Gender.Female, - BirthDate = new DateOnly(1990, 8, 15), - PhoneNumber = "+79992345678" - }; - _db.AddPatient(patient2); - - var patient3 = new Patient - { - Id = 3, - PassportNumber = "P003", - LastName = "Сидоров", - FirstName = "Алексей", - Patronymic = "Петрович", - BloodGroup = BloodGroup.O, - RhesusFactor = RhesusFactor.Negative, - Address = "г. Екатеринбург, ул. Мира, д. 15", - Gender = Gender.Male, - BirthDate = new DateOnly(1978, 12, 3), - PhoneNumber = "+79993456789" - }; - _db.AddPatient(patient3); - - var patient4 = new Patient - { - Id = 4, - PassportNumber = "P004", - LastName = "Кузнецова", - FirstName = "Ольга", - Patronymic = "Владимировна", - BloodGroup = BloodGroup.AB, - RhesusFactor = RhesusFactor.Positive, - Address = "г. Новосибирск, ул. Советская, д. 8", - Gender = Gender.Female, - BirthDate = new DateOnly(1995, 3, 10), - PhoneNumber = "+79994567890" - }; - _db.AddPatient(patient4); - - var patient5 = new Patient - { - Id = 5, - PassportNumber = "P005", - LastName = "Васильев", - FirstName = "Дмитрий", - Patronymic = "Александрович", - BloodGroup = BloodGroup.A, - RhesusFactor = RhesusFactor.Negative, - Address = "г. Казань, ул. Гагарина, д. 12", - Gender = Gender.Male, - BirthDate = new DateOnly(1982, 7, 28), - PhoneNumber = "+79995678901" - }; - _db.AddPatient(patient5); - - var patient6 = new Patient - { - Id = 6, - PassportNumber = "P006", - LastName = "Николаева", - FirstName = "Елена", - Patronymic = "Игоревна", - BloodGroup = BloodGroup.O, - RhesusFactor = RhesusFactor.Positive, - Address = "г. Нижний Новгород, ул. Лермонтова, д. 30", - Gender = Gender.Female, - BirthDate = new DateOnly(1988, 11, 5), - PhoneNumber = "+79996789012" - }; - _db.AddPatient(patient6); - - var patient7 = new Patient - { - Id = 7, - PassportNumber = "P007", - LastName = "Морозов", - FirstName = "Сергей", - Patronymic = "Викторович", - BloodGroup = BloodGroup.B, - RhesusFactor = RhesusFactor.Positive, - Address = "г. Самара, ул. Чехова, д. 7", - Gender = Gender.Male, - BirthDate = new DateOnly(1975, 1, 18), - PhoneNumber = "+79997890123" - }; - _db.AddPatient(patient7); - - var patient8 = new Patient - { - Id = 8, - PassportNumber = "P008", - LastName = "Орлова", - FirstName = "Анна", - Patronymic = "Дмитриевна", - BloodGroup = BloodGroup.AB, - RhesusFactor = RhesusFactor.Negative, - Address = "г. Ростов-на-Дону, ул. Кирова, д. 18", - Gender = Gender.Female, - BirthDate = new DateOnly(1992, 9, 22), - PhoneNumber = "+79998901234" - }; - _db.AddPatient(patient8); - - var patient9 = new Patient - { - Id = 9, - PassportNumber = "P009", - LastName = "Павлов", - FirstName = "Михаил", - Patronymic = "Олегович", - BloodGroup = BloodGroup.O, - RhesusFactor = RhesusFactor.Positive, - Address = "г. Уфа, ул. Горького, д. 22", - Gender = Gender.Male, - BirthDate = new DateOnly(1980, 4, 14), - PhoneNumber = "+79999012345" - }; - _db.AddPatient(patient9); - - var patient10 = new Patient - { - Id = 10, - PassportNumber = "P010", - LastName = "Федорова", - FirstName = "Татьяна", - Patronymic = "Николаевна", - BloodGroup = BloodGroup.A, - RhesusFactor = RhesusFactor.Positive, - Address = "г. Красноярск, ул. Ленина, д. 5", - Gender = Gender.Female, - BirthDate = new DateOnly(1987, 6, 30), - PhoneNumber = "+79990123456" - }; - _db.AddPatient(patient10); - - var specialization1 = new Specialization { Id = 1, Name = "Терапевт" }; - _db.AddSpecialization(specialization1); - - var specialization2 = new Specialization { Id = 2, Name = "Стоматолог" }; - _db.AddSpecialization(specialization2); - - var specialization3 = new Specialization { Id = 3, Name = "Кардиолог" }; - _db.AddSpecialization(specialization3); - - var specialization4 = new Specialization { Id = 4, Name = "Невролог" }; - _db.AddSpecialization(specialization4); - - var specialization5 = new Specialization { Id = 5, Name = "Педиатр" }; - _db.AddSpecialization(specialization5); - - var specialization6 = new Specialization { Id = 6, Name = "Дерматолог" }; - _db.AddSpecialization(specialization6); - - var specialization7 = new Specialization { Id = 7, Name = "Психиатр" }; - _db.AddSpecialization(specialization7); - - var specialization8 = new Specialization { Id = 8, Name = "Офтальмолог" }; - _db.AddSpecialization(specialization8); - - var specialization9 = new Specialization { Id = 9, Name = "Гинеколог" }; - _db.AddSpecialization(specialization9); - - var doctor1 = new Doctor - { - Id = 1, - PassportNumber = "D001", - LastName = "Смирнов", - FirstName = "Андрей", - Patronymic = "Николаевич", - Gender = Gender.Male, - BirthDate = new DateOnly(1980, 1, 15), - Specializations = new List { specialization1, specialization3 }, - ExperienceYears = 10, - PhoneNumber = "+79876543210" - }; - _db.AddDoctor(doctor1); - - var doctor2 = new Doctor - { - Id = 2, - PassportNumber = "D002", - LastName = "Егорова", - FirstName = "Екатерина", - Patronymic = "Александровна", - Gender = Gender.Female, - BirthDate = new DateOnly(1985, 4, 20), - Specializations = new List { specialization2, specialization6 }, - ExperienceYears = 8, - PhoneNumber = "+79876543211" - }; - _db.AddDoctor(doctor2); - - var doctor3 = new Doctor - { - Id = 3, - PassportNumber = "D003", - LastName = "Петров", - FirstName = "Дмитрий", - Patronymic = "Сергеевич", - Gender = Gender.Male, - BirthDate = new DateOnly(1978, 7, 12), - Specializations = new List { specialization4, specialization7 }, - ExperienceYears = 15, - PhoneNumber = "+79876543212" - }; - _db.AddDoctor(doctor3); - - var doctor4 = new Doctor - { - Id = 4, - PassportNumber = "D004", - LastName = "Соколова", - FirstName = "Ольга", - Patronymic = "Викторовна", - Gender = Gender.Female, - BirthDate = new DateOnly(1982, 11, 5), - Specializations = new List { specialization1, specialization5 }, - ExperienceYears = 12, - PhoneNumber = "+79876543213" - }; - _db.AddDoctor(doctor4); - - var doctor5 = new Doctor - { - Id = 5, - PassportNumber = "D005", - LastName = "Кузнецов", - FirstName = "Михаил", - Patronymic = "Андреевич", - Gender = Gender.Male, - BirthDate = new DateOnly(1990, 3, 25), - Specializations = new List { specialization3, specialization8 }, - ExperienceYears = 6, - PhoneNumber = "+79876543214" - }; - _db.AddDoctor(doctor5); - - var doctor6 = new Doctor - { - Id = 6, - PassportNumber = "D006", - LastName = "Иванова", - FirstName = "Анна", - Patronymic = "Дмитриевна", - Gender = Gender.Female, - BirthDate = new DateOnly(1987, 9, 18), - Specializations = new List { specialization2, specialization9 }, - ExperienceYears = 9, - PhoneNumber = "+79876543215" - }; - _db.AddDoctor(doctor6); - - var doctor7 = new Doctor - { - Id = 7, - PassportNumber = "D007", - LastName = "Морозов", - FirstName = "Сергей", - Patronymic = "Владимирович", - Gender = Gender.Male, - BirthDate = new DateOnly(1975, 12, 8), - Specializations = new List { specialization4, specialization9 }, - ExperienceYears = 20, - PhoneNumber = "+79876543216" - }; - _db.AddDoctor(doctor7); - - var doctor8 = new Doctor - { - Id = 8, - PassportNumber = "D008", - LastName = "Федорова", - FirstName = "Татьяна", - Patronymic = "Игоревна", - Gender = Gender.Female, - BirthDate = new DateOnly(1983, 6, 30), - Specializations = new List { specialization5, specialization7 }, - ExperienceYears = 11, - PhoneNumber = "+79876543217" - }; - _db.AddDoctor(doctor8); - - var doctor9 = new Doctor - { - Id = 9, - PassportNumber = "D009", - LastName = "Никитин", - FirstName = "Алексей", - Patronymic = "Павлович", - Gender = Gender.Male, - BirthDate = new DateOnly(1988, 2, 14), - Specializations = new List { specialization6, specialization8 }, - ExperienceYears = 7, - PhoneNumber = "+79876543218" - }; - _db.AddDoctor(doctor9); - - var doctor10 = new Doctor - { - Id = 10, - PassportNumber = "D010", - LastName = "Орлова", - FirstName = "Марина", - Patronymic = "Сергеевна", - Gender = Gender.Female, - BirthDate = new DateOnly(1981, 8, 22), - Specializations = new List { specialization1, specialization2, specialization3 }, - ExperienceYears = 14, - PhoneNumber = "+79876543219" - }; - _db.AddDoctor(doctor10); - - var appointment1 = new Appointment - { - Id = 1, - PatientId = patient1.Id, - PatientFullName = patient1.GetFullName(), - DoctorId = doctor1.Id, - DoctorFullName = doctor1.GetFullName(), - DateTime = new DateTime(2024, 12, 15, 9, 0, 0), - RoomNumber = 101, - IsReturnVisit = false - }; - _db.AddAppointment(appointment1); - - var appointment2 = new Appointment - { - Id = 2, - PatientId = patient2.Id, - PatientFullName = patient2.GetFullName(), - DoctorId = doctor2.Id, - DoctorFullName = doctor2.GetFullName(), - DateTime = new DateTime(2024, 12, 16, 9, 30, 0), - RoomNumber = 102, - IsReturnVisit = false - }; - _db.AddAppointment(appointment2); - - var appointment3 = new Appointment - { - Id = 3, - PatientId = patient3.Id, - PatientFullName = patient3.GetFullName(), - DoctorId = doctor3.Id, - DoctorFullName = doctor3.GetFullName(), - DateTime = new DateTime(2025, 1, 10, 10, 0, 0), - RoomNumber = 103, - IsReturnVisit = true - }; - _db.AddAppointment(appointment3); - - var appointment4 = new Appointment - { - Id = 4, - PatientId = patient4.Id, - PatientFullName = patient4.GetFullName(), - DoctorId = doctor4.Id, - DoctorFullName = doctor4.GetFullName(), - DateTime = new DateTime(2025, 1, 10, 10, 30, 0), - RoomNumber = 104, - IsReturnVisit = false - }; - _db.AddAppointment(appointment4); - - var appointment5 = new Appointment - { - Id = 5, - PatientId = patient5.Id, - PatientFullName = patient5.GetFullName(), - DoctorId = doctor5.Id, - DoctorFullName = doctor5.GetFullName(), - DateTime = new DateTime(2025, 1, 10, 11, 0, 0), - RoomNumber = 105, - IsReturnVisit = true - }; - _db.AddAppointment(appointment5); - - var appointment6 = new Appointment - { - Id = 6, - PatientId = patient6.Id, - PatientFullName = patient6.GetFullName(), - DoctorId = doctor6.Id, - DoctorFullName = doctor6.GetFullName(), - DateTime = new DateTime(2025, 1, 10, 11, 30, 0), - RoomNumber = 201, - IsReturnVisit = false - }; - _db.AddAppointment(appointment6); - - var appointment7 = new Appointment - { - Id = 7, - PatientId = patient7.Id, - PatientFullName = patient7.GetFullName(), - DoctorId = doctor7.Id, - DoctorFullName = doctor7.GetFullName(), - DateTime = new DateTime(2025, 1, 10, 12, 0, 0), - RoomNumber = 202, - IsReturnVisit = true - }; - _db.AddAppointment(appointment7); - - var appointment8 = new Appointment - { - Id = 8, - PatientId = patient8.Id, - PatientFullName = patient8.GetFullName(), - DoctorId = doctor8.Id, - DoctorFullName = doctor8.GetFullName(), - DateTime = new DateTime(2025, 1, 10, 12, 30, 0), - RoomNumber = 203, - IsReturnVisit = false - }; - _db.AddAppointment(appointment8); - - var appointment9 = new Appointment - { - Id = 9, - PatientId = patient9.Id, - PatientFullName = patient9.GetFullName(), - DoctorId = doctor9.Id, - DoctorFullName = doctor9.GetFullName(), - DateTime = new DateTime(2025, 1, 10, 13, 0, 0), - RoomNumber = 204, - IsReturnVisit = false - }; - _db.AddAppointment(appointment9); - - var appointment10 = new Appointment - { - Id = 10, - PatientId = patient10.Id, - PatientFullName = patient10.GetFullName(), - DoctorId = doctor10.Id, - DoctorFullName = doctor10.GetFullName(), - DateTime = new DateTime(2025, 1, 10, 13, 30, 0), - RoomNumber = 205, - IsReturnVisit = true - }; - _db.AddAppointment(appointment10); - - var appointment11 = new Appointment - { - Id = 11, - PatientId = patient1.Id, - PatientFullName = patient1.GetFullName(), - DoctorId = doctor2.Id, - DoctorFullName = doctor2.GetFullName(), - DateTime = new DateTime(2025, 1, 11, 9, 0, 0), - RoomNumber = 101, - IsReturnVisit = true - }; - _db.AddAppointment(appointment11); - - var appointment12 = new Appointment - { - Id = 12, - PatientId = patient3.Id, - PatientFullName = patient3.GetFullName(), - DoctorId = doctor1.Id, - DoctorFullName = doctor1.GetFullName(), - DateTime = new DateTime(2025, 1, 11, 9, 30, 0), - RoomNumber = 102, - IsReturnVisit = false - }; - _db.AddAppointment(appointment12); - - var appointment13 = new Appointment - { - Id = 13, - PatientId = patient2.Id, - PatientFullName = patient2.GetFullName(), - DoctorId = doctor3.Id, - DoctorFullName = doctor3.GetFullName(), - DateTime = new DateTime(2024, 12, 18, 14, 0, 0), - RoomNumber = 103, - IsReturnVisit = true - }; - _db.AddAppointment(appointment13); - - var appointment14 = new Appointment - { - Id = 14, - PatientId = patient4.Id, - PatientFullName = patient4.GetFullName(), - DoctorId = doctor5.Id, - DoctorFullName = doctor5.GetFullName(), - DateTime = new DateTime(2024, 12, 20, 10, 0, 0), - RoomNumber = 201, - IsReturnVisit = false - }; - _db.AddAppointment(appointment14); - - var appointment15 = new Appointment - { - Id = 15, - PatientId = patient6.Id, - PatientFullName = patient6.GetFullName(), - DoctorId = doctor7.Id, - DoctorFullName = doctor7.GetFullName(), - DateTime = new DateTime(2024, 12, 22, 11, 0, 0), - RoomNumber = 202, - IsReturnVisit = true - }; - _db.AddAppointment(appointment15); - - // Еще дополнительные повторные визиты - var appointment16 = new Appointment - { - Id = 16, - PatientId = patient8.Id, - PatientFullName = patient8.GetFullName(), - DoctorId = doctor9.Id, - DoctorFullName = doctor9.GetFullName(), - DateTime = new DateTime(2025, 1, 12, 15, 0, 0), - RoomNumber = 301, - IsReturnVisit = true - }; - _db.AddAppointment(appointment16); - - var appointment17 = new Appointment - { - Id = 17, - PatientId = patient5.Id, - PatientFullName = patient5.GetFullName(), - DoctorId = doctor4.Id, - DoctorFullName = doctor4.GetFullName(), - DateTime = new DateTime(2025, 1, 13, 16, 30, 0), - RoomNumber = 302, - IsReturnVisit = true - }; - _db.AddAppointment(appointment17); - } -} \ No newline at end of file diff --git a/Clinic/Clinic.Api/Program.cs b/Clinic/Clinic.Api/Program.cs index 98c8d9d3a..a0b262b58 100644 --- a/Clinic/Clinic.Api/Program.cs +++ b/Clinic/Clinic.Api/Program.cs @@ -1,9 +1,11 @@ -using Clinic.Api.DataBase; -using Clinic.Api.DataSeed; +using Clinic.DataBase; +using Clinic.DataBase.Interfaces; +using Clinic.DataBase.EntityFramework; using Clinic.Api.MappingProfile; using Clinic.Api.Services; using Clinic.Api.Converter; using Clinic.Api.Interfaces.Services; +using Microsoft.EntityFrameworkCore; var builder = WebApplication.CreateBuilder(args); @@ -13,9 +15,22 @@ options.JsonSerializerOptions.Converters.Add(new DateConverter()); options.JsonSerializerOptions.PropertyNamingPolicy = null; }); + + +var connectionString = builder.Configuration.GetConnectionString("ClinicDb") + ?? throw new InvalidOperationException("Connection string 'ClinicDb' is not configured."); + +var serverVersion = new MySqlServerVersion(new Version(8, 0, 36)); + +builder.Services.AddDbContext(options => + options.UseMySql(connectionString, serverVersion)); + + +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); builder.Services.AddAutoMapper(cfg => cfg.AddProfile()); -builder.Services.AddSingleton(); -builder.Services.AddSingleton(); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); @@ -24,10 +39,17 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); -builder.Services.AddScoped(); +builder.Services.AddScoped(); var app = builder.Build(); + +using (var scope = app.Services.CreateScope()) +{ + var db = scope.ServiceProvider.GetRequiredService(); + db.Database.Migrate(); +} + if (app.Environment.IsDevelopment()) { app.UseSwagger(); diff --git a/Clinic/Clinic.Api/Services/TestServices.cs b/Clinic/Clinic.Api/Services/AnalyticsServices.cs similarity index 73% rename from Clinic/Clinic.Api/Services/TestServices.cs rename to Clinic/Clinic.Api/Services/AnalyticsServices.cs index 7e5106c61..583a0492c 100644 --- a/Clinic/Clinic.Api/Services/TestServices.cs +++ b/Clinic/Clinic.Api/Services/AnalyticsServices.cs @@ -1,18 +1,27 @@ using AutoMapper; -using Clinic.Api.DataBase; +using Clinic.DataBase.Interfaces; using Clinic.Api.DTOs.PatientDto; using Clinic.Api.DTOs.DoctorDto; using Clinic.Api.DTOs.Appointment; namespace Clinic.Api.Services; -public class TestServices +public class AnalyticsServices { - private readonly IClinicDataBase _db; + private readonly IPatientDataBase _patients; + private readonly IDoctorDataBase _doctors; + private readonly IAppointmentDataBase _appointments; private readonly IMapper _mapper; - public TestServices(IClinicDataBase db, IMapper mapper){ - _db = db; + public AnalyticsServices( + IPatientDataBase patients, + IDoctorDataBase doctors, + IAppointmentDataBase appointments, + IMapper mapper) + { + _patients = patients; + _doctors = doctors; + _appointments = appointments; _mapper = mapper; } @@ -20,7 +29,7 @@ public TestServices(IClinicDataBase db, IMapper mapper){ /// Display information about all doctors with at least 10 years of experience. /// public IReadOnlyList GetDoctorsWithExperience10YearsOrMore(){ - var doctors = _db.GetAllDoctors() + var doctors = _doctors.GetAllDoctors() .Where(d => d.ExperienceYears >= 10) .ToList(); return _mapper.Map>(doctors); @@ -30,14 +39,18 @@ public IReadOnlyList GetDoctorsWithExperience10YearsOrMore(){ /// Display information about all patients scheduled to see a specified doctor, sorted by full name. /// public IReadOnlyList? GetPatientsByDoctorOrderedByFullName(int doctorId){ - var doctor = _db.GetDoctor(doctorId); + var doctor = _doctors.GetDoctor(doctorId); if (doctor == null) { return null; } - var appointments = _db.GetAppointmentsByDoctor(doctorId); - var patients = appointments.Where(a => a.DoctorId == doctorId).Select(a => _db.GetPatient(a.PatientId)).Distinct().ToList(); + var appointments = _appointments.GetAppointmentsByDoctor(doctorId); + var patients = appointments + .Where(a => a.DoctorId == doctorId) + .Select(a => _patients.GetPatient(a.PatientId)) + .Distinct() + .ToList(); return _mapper.Map>(patients); } @@ -50,7 +63,7 @@ public int GetReturnVisitsCountLastMonth(){ var startOfLastMonth = new DateTime(lastMonth.Year, lastMonth.Month, 1); var startOfCurrentMonth = startOfLastMonth.AddMonths(1); - var appointments = _db.GetAllAppointments() + var appointments = _appointments.GetAllAppointments() .Where(a => a.DateTime >= startOfLastMonth && a.DateTime < startOfCurrentMonth && a.IsReturnVisit) @@ -65,7 +78,7 @@ public int GetReturnVisitsCountLastMonth(){ public IReadOnlyList GetPatientsOver30WithMultipleDoctorsOrderedByBirthDate(){ var thirtyYearsAgo = DateOnly.FromDateTime(DateTime.Now.AddYears(-30)); - var appointments = _db.GetAllAppointments(); + var appointments = _appointments.GetAllAppointments(); var patientsWithMultipleDoctors = appointments .GroupBy(a => a.PatientId) @@ -74,7 +87,7 @@ public IReadOnlyList GetPatientsOver30WithMultipleDoctorsOrderedB .ToList(); var patients = patientsWithMultipleDoctors - .Select(id => _db.GetPatient(id)) + .Select(id => _patients.GetPatient(id)) .Where(p => p != null && p.BirthDate < thirtyYearsAgo) .OrderBy(p => p!.BirthDate) .ToList(); @@ -90,7 +103,7 @@ public IReadOnlyList GetAppointmentsInRoomForCurrentMonth(int var startOfMonth = new DateTime(now.Year, now.Month, 1); var startOfNextMonth = startOfMonth.AddMonths(1); - var appointments = _db.GetAllAppointments() + var appointments = _appointments.GetAllAppointments() .Where(a => a.RoomNumber == roomNumber && a.DateTime >= startOfMonth && a.DateTime < startOfNextMonth) diff --git a/Clinic/Clinic.Api/Services/AppointmentServices.cs b/Clinic/Clinic.Api/Services/AppointmentServices.cs index 314780608..bf09475b1 100644 --- a/Clinic/Clinic.Api/Services/AppointmentServices.cs +++ b/Clinic/Clinic.Api/Services/AppointmentServices.cs @@ -1,5 +1,5 @@ using AutoMapper; -using Clinic.Api.DataBase; +using Clinic.DataBase.Interfaces; using Clinic.Api.DTOs.Appointment; using Clinic.Models.Entities; using Clinic.Api.Interfaces.Services; @@ -13,20 +13,30 @@ namespace Clinic.Api.Services; /// public class AppointmentServices : IAppointmentServices { - private readonly IClinicDataBase _db; + private readonly IAppointmentDataBase _appointments; + private readonly IPatientDataBase _patients; + private readonly IDoctorDataBase _doctors; private readonly IMapper _mapper; private int _appointmentId; /// /// Initializes a new instance of the class. /// - /// The clinic database interface. + /// The appointment database interface. + /// The patient database interface. + /// The doctor database interface. /// The AutoMapper interface for DTO and entity mapping. - public AppointmentServices(IClinicDataBase db, IMapper mapper) + public AppointmentServices( + IAppointmentDataBase appointments, + IPatientDataBase patients, + IDoctorDataBase doctors, + IMapper mapper) { - _db = db; + _appointments = appointments; + _patients = patients; + _doctors = doctors; _mapper = mapper; - _appointmentId = _db.AppointmentCount() + 1; + _appointmentId = _appointments.AppointmentCount() + 1; } /// @@ -35,7 +45,7 @@ public AppointmentServices(IClinicDataBase db, IMapper mapper) /// A read-only collection of appointment DTOs. public IReadOnlyCollection GetAll() { - var appointments = _db.GetAllAppointments(); + var appointments = _appointments.GetAllAppointments(); return _mapper.Map>(appointments); } @@ -46,12 +56,12 @@ public IReadOnlyCollection GetAll() /// A collection of appointment DTOs if the doctor exists; otherwise, null. public IReadOnlyCollection? GetAppointmentsByDoctor(int doctorId) { - var doctor = _db.GetDoctor(doctorId); + var doctor = _doctors.GetDoctor(doctorId); if (doctor == null) { return null; } - var appointments = _db.GetAppointmentsByDoctor(doctorId); + var appointments = _appointments.GetAppointmentsByDoctor(doctorId); return _mapper.Map>(appointments); } @@ -62,12 +72,12 @@ public IReadOnlyCollection GetAll() /// A collection of appointment DTOs if the patient exists; otherwise, null. public IReadOnlyCollection? GetAppointmentsByPatient(int patientId) { - var patient = _db.GetPatient(patientId); + var patient = _patients.GetPatient(patientId); if (patient == null) { return null; } - var appointments = _db.GetAppointmentsByPatient(patientId); + var appointments = _appointments.GetAppointmentsByPatient(patientId); return _mapper.Map>(appointments); } @@ -78,7 +88,7 @@ public IReadOnlyCollection GetAll() /// The appointment as a DTO if found; otherwise, null. public GetAppointmentDto? Get(int id) { - var appointment = _db.GetAppointment(id); + var appointment = _appointments.GetAppointment(id); return appointment == null ? null : _mapper.Map(appointment); } @@ -92,7 +102,7 @@ public IReadOnlyCollection GetAll() var appointment = _mapper.Map(dto); appointment.Id = _appointmentId; - if (!_db.AddAppointment(appointment)) + if (!_appointments.AddAppointment(appointment)) { return null; } @@ -109,14 +119,14 @@ public IReadOnlyCollection GetAll() /// The updated appointment as a DTO if successful; otherwise, null. public GetAppointmentDto? Update(int id, UpdateAppointmentDto dto) { - var appointment = _db.GetAppointment(id); + var appointment = _appointments.GetAppointment(id); if (appointment == null) { return null; } _mapper.Map(dto, appointment); - _db.UpdateAppointment(appointment); + _appointments.UpdateAppointment(appointment); return _mapper.Map(appointment); } @@ -128,7 +138,7 @@ public IReadOnlyCollection GetAll() /// True if the appointment was successfully deleted; otherwise, false. public bool Delete(int id) { - if (!_db.RemoveAppointment(id)) + if (!_appointments.RemoveAppointment(id)) { return false; } diff --git a/Clinic/Clinic.Api/Services/DoctorServices.cs b/Clinic/Clinic.Api/Services/DoctorServices.cs index e11f99a64..3d2398291 100644 --- a/Clinic/Clinic.Api/Services/DoctorServices.cs +++ b/Clinic/Clinic.Api/Services/DoctorServices.cs @@ -1,5 +1,5 @@ using AutoMapper; -using Clinic.Api.DataBase; +using Clinic.DataBase.Interfaces; using Clinic.Api.DTOs.DoctorDto; using Clinic.Models.Entities; using Clinic.Api.Interfaces.Services; @@ -14,7 +14,7 @@ namespace Clinic.Api.Services; /// public class DoctorServices : IDoctorServices { - private readonly IClinicDataBase _db; + private readonly IDoctorDataBase _db; private readonly IMapper _mapper; private int _doctorId; @@ -24,7 +24,7 @@ public class DoctorServices : IDoctorServices /// /// The database service for doctor operations. /// The AutoMapper instance used for object mapping. - public DoctorServices(IClinicDataBase db, IMapper mapper) + public DoctorServices(IDoctorDataBase db, IMapper mapper) { _db = db; _mapper = mapper; diff --git a/Clinic/Clinic.Api/Services/PatientServices.cs b/Clinic/Clinic.Api/Services/PatientServices.cs index 9e80860f9..8b6e0198b 100644 --- a/Clinic/Clinic.Api/Services/PatientServices.cs +++ b/Clinic/Clinic.Api/Services/PatientServices.cs @@ -1,5 +1,5 @@ using AutoMapper; -using Clinic.Api.DataBase; +using Clinic.DataBase.Interfaces; using Clinic.Api.DTOs.PatientDto; using Clinic.Models.Entities; using Clinic.Api.Interfaces.Services; @@ -13,7 +13,7 @@ namespace Clinic.Api.Services; /// public class PatientServices : IPatientServices { - private readonly IClinicDataBase _db; + private readonly IPatientDataBase _db; private readonly IMapper _mapper; private int _patientId; @@ -23,7 +23,7 @@ public class PatientServices : IPatientServices /// /// The database service for patient operations. /// The AutoMapper instance for mapping objects. - public PatientServices(IClinicDataBase db, IMapper mapper) + public PatientServices(IPatientDataBase db, IMapper mapper) { _db = db; _mapper = mapper; diff --git a/Clinic/Clinic.Api/Services/SpecializationServices.cs b/Clinic/Clinic.Api/Services/SpecializationServices.cs index 9ca7953c0..bc4b24e84 100644 --- a/Clinic/Clinic.Api/Services/SpecializationServices.cs +++ b/Clinic/Clinic.Api/Services/SpecializationServices.cs @@ -1,6 +1,6 @@ using AutoMapper; using Clinic.Models.Entities; -using Clinic.Api.DataBase; +using Clinic.DataBase.Interfaces; using Clinic.Api.DTOs.SpecializationDto; using Clinic.Api.Interfaces.Services; @@ -12,7 +12,7 @@ namespace Clinic.Api.Services; /// public class SpecializationServices : ISpecializationServices { - private readonly IClinicDataBase _db; + private readonly ISpecializationDataBase _db; private readonly IMapper _mapper; private int _specializationId; @@ -22,7 +22,7 @@ public class SpecializationServices : ISpecializationServices /// /// The database service for specialization operations. /// The AutoMapper instance used for object mapping. - public SpecializationServices(IClinicDataBase db, IMapper mapper) + public SpecializationServices(ISpecializationDataBase db, IMapper mapper) { _db = db; _mapper = mapper; diff --git a/Clinic/Clinic.Api/appsettings.Development.json b/Clinic/Clinic.Api/appsettings.Development.json index ff66ba6b2..adfa46ac4 100644 --- a/Clinic/Clinic.Api/appsettings.Development.json +++ b/Clinic/Clinic.Api/appsettings.Development.json @@ -1,8 +1,12 @@ -{ - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft.AspNetCore": "Warning" - } - } -} +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning", + "Microsoft.EntityFrameworkCore": "Information" + } + }, + "ConnectionStrings": { + "ClinicDb": "Server=localhost;Port=3306;Database=ClinicDb;User=clinic_app;Password=clinic123;TreatTinyAsBoolean=true;AllowPublicKeyRetrieval=True;SslMode=None" + } +} diff --git a/Clinic/Clinic.Api/appsettings.json b/Clinic/Clinic.Api/appsettings.json index 4d566948d..46bdb4522 100644 --- a/Clinic/Clinic.Api/appsettings.json +++ b/Clinic/Clinic.Api/appsettings.json @@ -1,9 +1,10 @@ -{ - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft.AspNetCore": "Warning" - } - }, - "AllowedHosts": "*" -} +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning", + "Microsoft.EntityFrameworkCore": "Warning" + } + }, + "AllowedHosts": "*" +} From f48a48b0ce5d339e4a829396996f667146970a12 Mon Sep 17 00:00:00 2001 From: maeosha Date: Sun, 21 Dec 2025 14:17:38 +0300 Subject: [PATCH 45/56] feat: add unit tests and project configuration for Clinic.Tests --- Clinic/Clinic.Tests/Clinic.Tests.csproj | 32 +++++++++++++ Clinic/Clinic.Tests/DatabaseTests.cs | 62 +++++++++++++++++++++++++ Clinic/Clinic.sln | 28 +++++++++++ 3 files changed, 122 insertions(+) create mode 100644 Clinic/Clinic.Tests/Clinic.Tests.csproj create mode 100644 Clinic/Clinic.Tests/DatabaseTests.cs diff --git a/Clinic/Clinic.Tests/Clinic.Tests.csproj b/Clinic/Clinic.Tests/Clinic.Tests.csproj new file mode 100644 index 000000000..6511e7f56 --- /dev/null +++ b/Clinic/Clinic.Tests/Clinic.Tests.csproj @@ -0,0 +1,32 @@ + + + + net9.0 + enable + enable + false + true + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Clinic/Clinic.Tests/DatabaseTests.cs b/Clinic/Clinic.Tests/DatabaseTests.cs new file mode 100644 index 000000000..d4e7a3c1d --- /dev/null +++ b/Clinic/Clinic.Tests/DatabaseTests.cs @@ -0,0 +1,62 @@ +using Microsoft.EntityFrameworkCore; +using Clinic.DataBase; +using Clinic.Api.Services; +using Clinic.Models.Entities; +using Clinic.Models.Enums; +using Microsoft.AspNetCore.Http; + +namespace Clinic.Tests; + +/// +/// Базовый класс для тестирования Entity Framework контекста с InMemory Database +/// +public abstract class DatabaseTestBase(AnalyticsServices testServices) : IClassFixture +{ + [Fact] + public void GetDoctorsWithExperience_WhenExperienceAtLeast10Years_ReturnsExperiencedDoctorsOrderedByName() + { + var doctorsWithExperience10OrMore = new List {1, 2, 3, 4, 5, 6, 7, 9, 10}; + var result = testServices.GetDoctorsWithExperience10YearsOrMore().Select(d => d.Id); + Assert.Equal(doctorsWithExperience10OrMore, result); + } + + [Fact] + public void GetPatientsByDoctor_WhenDoctorIsSpecified_ReturnsPatientsOrderedByName() + { + var doctorId = 3; + var patientsByDoctor = new List {3}; + + var result = testServices.GetPatientsByDoctorOrderedByFullName(doctorId).Select(p => p.Id); + Assert.Equal(patientsByDoctor, result); + } + + [Fact] + public void CountAppointments_WhenRepeatVisitsInLastMonth_ReturnsCorrectCount() + { + var returnVisits = 2; + var result = testServices.GetReturnVisitsCountLastMonth(); + + Assert.Equal(returnVisits, result); + } + + [Fact] + public void GetPatients_WhenOver30WithMultipleDoctors_ReturnsPatientsOrderedByBirthDate() + { + var patientsOver30WithMultipleDoctors = new List {1, 3}; + var result = testServices.GetPatientsOver30WithMultipleDoctorsOrderedByBirthDate().Select(p => p.Id); + + Assert.Equal(patientsOver30WithMultipleDoctors, result); + } + + [Fact] + public void GetAppointments_WhenInSpecificRoomCurrentMonth_ReturnsAppointmentsOrderedByDateTime() + { + var roomNumber = 101; + var appointmentsInRoomCurrentMonth = new List {1}; + + var result = testServices.GetAppointmentsInRoomForCurrentMonth(roomNumber).Select(a => a.Id); + + Assert.Equal(appointmentsInRoomCurrentMonth, result); + + } +} diff --git a/Clinic/Clinic.sln b/Clinic/Clinic.sln index 9e4a8919d..af2465d41 100644 --- a/Clinic/Clinic.sln +++ b/Clinic/Clinic.sln @@ -7,6 +7,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Clinic.Models", "Clinic.Mod EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Clinic.Api", "Clinic.Api\Clinic.Api.csproj", "{7433D860-E79F-44AA-BA33-9E0F5901578D}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Clinic.DataBase", "Clinic.DataBase\Clinic.DataBase.csproj", "{9A5F5BA1-0C3F-4B3F-9AE7-4FD8E3C5F2C4}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Clinic.Tests", "Clinic.Tests\Clinic.Tests.csproj", "{D3E7E045-5C0D-4199-AE4A-7468F477DE74}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -41,6 +45,30 @@ Global {7433D860-E79F-44AA-BA33-9E0F5901578D}.Release|x64.Build.0 = Release|Any CPU {7433D860-E79F-44AA-BA33-9E0F5901578D}.Release|x86.ActiveCfg = Release|Any CPU {7433D860-E79F-44AA-BA33-9E0F5901578D}.Release|x86.Build.0 = Release|Any CPU + {9A5F5BA1-0C3F-4B3F-9AE7-4FD8E3C5F2C4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9A5F5BA1-0C3F-4B3F-9AE7-4FD8E3C5F2C4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9A5F5BA1-0C3F-4B3F-9AE7-4FD8E3C5F2C4}.Debug|x64.ActiveCfg = Debug|Any CPU + {9A5F5BA1-0C3F-4B3F-9AE7-4FD8E3C5F2C4}.Debug|x64.Build.0 = Debug|Any CPU + {9A5F5BA1-0C3F-4B3F-9AE7-4FD8E3C5F2C4}.Debug|x86.ActiveCfg = Debug|Any CPU + {9A5F5BA1-0C3F-4B3F-9AE7-4FD8E3C5F2C4}.Debug|x86.Build.0 = Debug|Any CPU + {9A5F5BA1-0C3F-4B3F-9AE7-4FD8E3C5F2C4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9A5F5BA1-0C3F-4B3F-9AE7-4FD8E3C5F2C4}.Release|Any CPU.Build.0 = Release|Any CPU + {9A5F5BA1-0C3F-4B3F-9AE7-4FD8E3C5F2C4}.Release|x64.ActiveCfg = Release|Any CPU + {9A5F5BA1-0C3F-4B3F-9AE7-4FD8E3C5F2C4}.Release|x64.Build.0 = Release|Any CPU + {9A5F5BA1-0C3F-4B3F-9AE7-4FD8E3C5F2C4}.Release|x86.ActiveCfg = Release|Any CPU + {9A5F5BA1-0C3F-4B3F-9AE7-4FD8E3C5F2C4}.Release|x86.Build.0 = Release|Any CPU + {D3E7E045-5C0D-4199-AE4A-7468F477DE74}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D3E7E045-5C0D-4199-AE4A-7468F477DE74}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D3E7E045-5C0D-4199-AE4A-7468F477DE74}.Debug|x64.ActiveCfg = Debug|Any CPU + {D3E7E045-5C0D-4199-AE4A-7468F477DE74}.Debug|x64.Build.0 = Debug|Any CPU + {D3E7E045-5C0D-4199-AE4A-7468F477DE74}.Debug|x86.ActiveCfg = Debug|Any CPU + {D3E7E045-5C0D-4199-AE4A-7468F477DE74}.Debug|x86.Build.0 = Debug|Any CPU + {D3E7E045-5C0D-4199-AE4A-7468F477DE74}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D3E7E045-5C0D-4199-AE4A-7468F477DE74}.Release|Any CPU.Build.0 = Release|Any CPU + {D3E7E045-5C0D-4199-AE4A-7468F477DE74}.Release|x64.ActiveCfg = Release|Any CPU + {D3E7E045-5C0D-4199-AE4A-7468F477DE74}.Release|x64.Build.0 = Release|Any CPU + {D3E7E045-5C0D-4199-AE4A-7468F477DE74}.Release|x86.ActiveCfg = Release|Any CPU + {D3E7E045-5C0D-4199-AE4A-7468F477DE74}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From 557ee162c47715a2b48860b8f935f1d0fb3d72d6 Mon Sep 17 00:00:00 2001 From: maeosha Date: Sun, 21 Dec 2025 14:17:49 +0300 Subject: [PATCH 46/56] feat: add CI workflow for build and test automation --- .github/workflows/ci.yml | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 000000000..8b3f0f53c --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,30 @@ +name: Build Check + +on: + push: + branches: [ main, develop ] + pull_request: + branches: [ main ] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 8.0.x + + - name: Restore dependencies + run: dotnet restore Clinic/Clinic.sln + + - name: Build solution + run: dotnet build Clinic/Clinic.sln --configuration Release --no-restore + + - name: Test solution + run: dotnet test ./Clinic/Clinic.Tests/Clinic.Tests.csproj --configuration Release --no-build --verbosity normal + From 1202caeb342a146441019cdaed3f044b13522ad7 Mon Sep 17 00:00:00 2001 From: maeosha Date: Sun, 21 Dec 2025 14:24:14 +0300 Subject: [PATCH 47/56] feat: configure Swagger UI and enable automatic launch in development environment --- Clinic/Clinic.Api/Program.cs | 6 +++++- Clinic/Clinic.Api/Properties/launchSettings.json | 6 ++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/Clinic/Clinic.Api/Program.cs b/Clinic/Clinic.Api/Program.cs index a0b262b58..b9ffa677d 100644 --- a/Clinic/Clinic.Api/Program.cs +++ b/Clinic/Clinic.Api/Program.cs @@ -53,7 +53,11 @@ if (app.Environment.IsDevelopment()) { app.UseSwagger(); - app.UseSwaggerUI(); + app.UseSwaggerUI(options => + { + options.SwaggerEndpoint("/swagger/v1/swagger.json", "Clinic API V1"); + options.RoutePrefix = "swagger"; + }); } app.MapControllers(); diff --git a/Clinic/Clinic.Api/Properties/launchSettings.json b/Clinic/Clinic.Api/Properties/launchSettings.json index 663182850..13035bd70 100644 --- a/Clinic/Clinic.Api/Properties/launchSettings.json +++ b/Clinic/Clinic.Api/Properties/launchSettings.json @@ -4,7 +4,8 @@ "http": { "commandName": "Project", "dotnetRunMessages": true, - "launchBrowser": false, + "launchBrowser": true, + "launchUrl": "swagger", "applicationUrl": "http://localhost:5042", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" @@ -13,7 +14,8 @@ "https": { "commandName": "Project", "dotnetRunMessages": true, - "launchBrowser": false, + "launchBrowser": true, + "launchUrl": "swagger", "applicationUrl": "https://localhost:7015;http://localhost:5042", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" From 257dd066c8dd8411cc5ab061d427b1c92d1edeca Mon Sep 17 00:00:00 2001 From: maeosha Date: Sun, 21 Dec 2025 14:29:14 +0300 Subject: [PATCH 48/56] chore: remove outdated issue and discussion templates, and PR template --- .github/DISCUSSION_TEMPLATE/questions.yml | 39 ------ ...20\276\321\200\320\275\320\276\320\271.md" | 33 ----- .github/PULL_REQUEST_TEMPLATE.md | 6 - .github/README.md | 131 ++++++++++++++++++ .github/workflows/setup_pr.yml | 77 ---------- 5 files changed, 131 insertions(+), 155 deletions(-) delete mode 100644 .github/DISCUSSION_TEMPLATE/questions.yml delete mode 100644 ".github/ISSUE_TEMPLATE/\320\262\320\276\320\277\321\200\320\276\321\201-\320\277\320\276-\320\273\320\260\320\261\320\276\321\200\320\260\321\202\320\276\321\200\320\275\320\276\320\271.md" delete mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 .github/README.md delete mode 100644 .github/workflows/setup_pr.yml diff --git a/.github/DISCUSSION_TEMPLATE/questions.yml b/.github/DISCUSSION_TEMPLATE/questions.yml deleted file mode 100644 index b94708065..000000000 --- a/.github/DISCUSSION_TEMPLATE/questions.yml +++ /dev/null @@ -1,39 +0,0 @@ -labels: [q&a] -body: - - type: input - id: fio - attributes: - label: Привет, меня зовут - description: | - Напиши свои ФИО и номер группы, чтобы тебе ответил преподаватель, который ведет у тебя пары - placeholder: | - Фамилия И.О. 641Х - validations: - required: true - - - type: dropdown - id: lab - attributes: - label: У меня вопрос по - description: | - Выбери лабораторную, которая вызвала трудности - multiple: true - options: - - 1 лабораторной работе (Классы) - - 2 лабораторной работе (Сервер) - - 3 лабораторной работе (ORM) - - 4 лабораторной работе (Инфраструктура) - - 5 лабораторной работе (Клиент) - validations: - required: true - - - type: textarea - id: details - attributes: - label: Описание проблемы - description: | - Подробно опиши проблему, с которой ты столкнулся при выполнении лабораторной - placeholder: | - Также было бы крайне полезно привести помимо текстового описания проблемы скриншоты и фрагменты кода - validations: - required: true \ No newline at end of file diff --git "a/.github/ISSUE_TEMPLATE/\320\262\320\276\320\277\321\200\320\276\321\201-\320\277\320\276-\320\273\320\260\320\261\320\276\321\200\320\260\321\202\320\276\321\200\320\275\320\276\320\271.md" "b/.github/ISSUE_TEMPLATE/\320\262\320\276\320\277\321\200\320\276\321\201-\320\277\320\276-\320\273\320\260\320\261\320\276\321\200\320\260\321\202\320\276\321\200\320\275\320\276\320\271.md" deleted file mode 100644 index d876c2378..000000000 --- "a/.github/ISSUE_TEMPLATE/\320\262\320\276\320\277\321\200\320\276\321\201-\320\277\320\276-\320\273\320\260\320\261\320\276\321\200\320\260\321\202\320\276\321\200\320\275\320\276\320\271.md" +++ /dev/null @@ -1,33 +0,0 @@ ---- -name: Вопрос по лабораторной -about: Этот шаблон предназначен для того, чтобы студенты могли задать вопрос по лабораторной -title: Вопрос по лабораторной -labels: '' -assignees: DmitryKrakhmalev, alxmcs, danlla - ---- - -**Меня зовут:** -Укажите свои ФИО - -**Я из группы:** -Укажите номер группы - -**У меня вопрос по лабе:** -Укажите номер и название лабораторной, по которой появился вопрос. - -**Мой вопрос:** -Максимально подробно опишите, что вы хотите узнать/что у вас не получается/что у вас не работает. При необходимости, добавьте примеры кода. Примеры кода должны быть оформлены с использованием md разметки, чтобы их можно было удобно воспринимать: - -```cs -public class Program -{ - public static void Main(string[] args) - { - System.Console.WriteLine("Hello, World!"); - } -} -``` - -**Дополнительная информация** -Опишите тут все, что не попадает под перечисленные ранее категории (если в том есть необходимость). \ No newline at end of file diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md deleted file mode 100644 index 13f73dbb5..000000000 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ /dev/null @@ -1,6 +0,0 @@ -**ФИО:** Фамилия Имя -**Номер группы:** 641Х -**Номер лабораторной:** Х -**Номер варианта:** ХХ -**Краткое описание предметной области:** Пункт велопроката/библиотека/т.д. -**Краткое описание добавленных фич:** Добавлена доменная модель/юнит-тесты/т.д. \ No newline at end of file diff --git a/.github/README.md b/.github/README.md new file mode 100644 index 000000000..e11125a3a --- /dev/null +++ b/.github/README.md @@ -0,0 +1,131 @@ +# GitHub Actions Workflows + +Данный репозиторий содержит набор GitHub Actions workflows для автоматизации CI/CD процессов для ASP.NET Core проекта клиники. + +## Доступные Workflows + +### 1. CI Pipeline (`ci.yml`) +**Триггеры:** Push в `main`/`develop`, Pull Request в `main`/`develop` + +**Функции:** +- Сборка .NET 9.0 проектов +- Восстановление NuGet зависимостей +- Запуск тестов (если есть) +- Кэширование NuGet пакетов для ускорения сборки + +**Использование:** +```bash +# Локальная сборка +dotnet restore Clinic/Clinic.sln +dotnet build Clinic/Clinic.sln --configuration Release +dotnet test Clinic/Clinic.sln --configuration Release +``` + +### 2. Docker Build and Deploy (`docker.yml`) +**Триггеры:** Push в `main`, теги `v*`, Pull Request в `main` + +**Функции:** +- Создание Docker образа для API +- Публикация образа в GitHub Container Registry +- Поддержка версионирования образов + +**Использование:** +```bash +# Сборка Docker образа +docker build -f Clinic/Clinic.Api/Dockerfile -t clinic-api:latest . + +# Запуск контейнера +docker run -p 8080:8080 clinic-api:latest +``` + +### 3. Azure Deploy (`azure-deploy.yml`) +**Триггеры:** Push в `main`, ручной запуск + +**Функции:** +- Публикация .NET проекта +- Деплой в Azure App Service + +**Необходимые Secrets:** +- `AZURE_WEBAPP_NAME` - имя Azure App Service +- `AZURE_WEBAPP_PUBLISH_PROFILE` - профиль публикации + +**Настройка Azure:** +1. Создайте Azure App Service +2. Скачайте профиль публикации +3. Добавьте Secrets в GitHub репозиторий + +## Структура файлов + +``` +.github/ +├── workflows/ +│ ├── ci.yml # Основной CI pipeline +│ ├── docker.yml # Docker сборка и деплой +│ ├── azure-deploy.yml # Azure App Service деплой +│ └── setup_pr.yml # Автоматическая настройка PR +├── ISSUE_TEMPLATE/ +│ └── вопрос-по-лабораторной.md +├── PULL_REQUEST_TEMPLATE.md +└── DISCUSSION_TEMPLATE/ + └── questions.yml + +Clinic/ +├── Clinic.Api/ +│ ├── Dockerfile # Docker образ для API +│ └── .dockerignore # Исключения для Docker сборки +└── ... +``` + +## Настройка проекта + +### Для локальной разработки: +```bash +# Восстановление зависимостей +dotnet restore Clinic/Clinic.sln + +# Сборка проекта +dotnet build Clinic/Clinic.sln + +# Запуск API +dotnet run --project Clinic/Clinic.Api + +# Запуск с настройками разработки +cd Clinic/Clinic.Api +dotnet run +``` + +### Для Docker: +```bash +# Сборка образа +docker build -f Clinic/Clinic.Api/Dockerfile -t clinic-api . + +# Запуск с базой данных (пример) +docker run -p 8080:8080 -e ConnectionStrings__DefaultConnection="Server=host.docker.internal;Database=ClinicDb;User=sa;Password=YourPassword;" clinic-api +``` + +## Переменные окружения + +Для работы проекта необходимо настроить следующие переменные окружения: + +### Development: +- `ASPNETCORE_ENVIRONMENT=Development` +- `ConnectionStrings__DefaultConnection` - строка подключения к БД + +### Production: +- `ASPNETCORE_ENVIRONMENT=Production` +- `ConnectionStrings__DefaultConnection` - строка подключения к продакшн БД + +## Дополнительные возможности + +### Добавление тестов: +1. Создайте тестовый проект: `dotnet new xunit -n Clinic.Tests` +2. Добавьте ссылку на основной проект +3. CI pipeline автоматически запустит тесты + +### Мониторинг: +- Логи Azure App Service доступны через Azure Portal +- Docker контейнеры можно мониторить через Docker Desktop или Azure Container Instances + +### Безопасность: +- Используйте GitHub Secrets для хранения чувствительных данных +- Не коммитьте ключи подключения к БД в репозиторий \ No newline at end of file diff --git a/.github/workflows/setup_pr.yml b/.github/workflows/setup_pr.yml deleted file mode 100644 index 6480c8052..000000000 --- a/.github/workflows/setup_pr.yml +++ /dev/null @@ -1,77 +0,0 @@ -name: Setup PR for code review - -on: - pull_request_target: - types: [opened, reopened] - -permissions: write-all - -jobs: - - assign: - runs-on: ubuntu-latest - steps: - - name: Parsing your PR title for lab number - env: - TITLE: ${{ github.event.pull_request.title }} - run: | - SUB='Лаб.' - for VAR in 1 2 3 4 5 - do - if (echo $TITLE | grep -iqF "$SUB$VAR" )|| (echo $TITLE | grep -iqF "$SUB $VAR"); then - echo "LABEL=Lab $VAR" >> "$GITHUB_ENV" - break - fi - done - for VAR in 6411 6412 6413 - do - if (echo $TITLE | grep -iqF "$VAR" ); then - echo "GROUP=$VAR" >> "$GITHUB_ENV" - break - fi - done - - - name: Checking your lab number - run: | - if [[ $LABEL == '' ]]; then - echo "Your PR caption is not composed correctly" - exit 1 - fi - echo Your number was parsed correctly - ${{ env.LABEL }} - - - name: Checking your group number - run: | - if [[ $GROUP == '' ]]; then - echo "Your PR caption is not composed correctly" - exit 1 - fi - echo Your group was parsed correctly - ${{ env.GROUP }} - - - name: Setting PR labels - uses: actions-ecosystem/action-add-labels@v1 - with: - labels: | - ${{ env.LABEL }} - In progress - - - name: Setting reviewer - if: env.GROUP == '6411' - uses: AveryCameronUofR/add-reviewer-gh-action@1.0.3 - with: - reviewers: "danlla" - token: ${{ secrets.GITHUB_TOKEN }} - - - name: Setting reviewer - if: env.GROUP == '6412' - uses: AveryCameronUofR/add-reviewer-gh-action@1.0.3 - with: - reviewers: "alxmcs" - token: ${{ secrets.GITHUB_TOKEN }} - - - name: Setting reviewer - if: env.GROUP == '6413' - uses: AveryCameronUofR/add-reviewer-gh-action@1.0.3 - with: - reviewers: "DmitryKrakhmalev" - token: ${{ secrets.GITHUB_TOKEN }} - \ No newline at end of file From 670e4fd78da96466ad8484bd70b4ee85bc9345dd Mon Sep 17 00:00:00 2001 From: maeosha Date: Sun, 21 Dec 2025 23:40:04 +0300 Subject: [PATCH 49/56] feat: update DTO namespaces and add service defaults for improved structure and functionality --- Clinic/Clinic.Api/Clinic.Api.csproj | 6 +- .../Controllers/AnalyticsControllers.cs | 4 +- .../Clinic.Api/Controllers/BaseControllers.cs | 2 - .../Controllers/DoctorControllers.cs | 2 +- .../Controllers/PatientControllers.cs | 2 +- .../Controllers/SpecializationControllers.cs | 2 +- .../Clinic.Api/DTOs/Doctor/CreateDoctorDto.cs | 5 +- Clinic/Clinic.Api/DTOs/Doctor/GetDoctorDto.cs | 3 +- .../Clinic.Api/DTOs/Doctor/UpdateDoctorDto.cs | 5 +- .../DTOs/Patient/CreatePatientDto.cs | 2 +- .../Clinic.Api/DTOs/Patient/GetPatientDto.cs | 4 +- .../DTOs/Patient/UpdatePatientDto.cs | 2 +- .../Specialization/CreateSpecializationDto.cs | 2 +- .../Specialization/GetSpecializationDto.cs | 2 +- .../Specialization/UpdateSpecializationDto.cs | 2 +- .../Services/IAppointmentService.cs | 1 - .../Interfaces/Services/IDoctorService.cs | 2 +- .../Interfaces/Services/IPatientService.cs | 2 +- .../Services/ISpecializationService.cs | 2 +- .../MappingProfile/MappingProfile.cs | 6 +- Clinic/Clinic.Api/Program.cs | 4 +- .../Clinic.Api/Services/AnalyticsServices.cs | 4 +- Clinic/Clinic.Api/Services/DoctorServices.cs | 2 +- Clinic/Clinic.Api/Services/PatientServices.cs | 2 +- .../Services/SpecializationServices.cs | 2 +- Clinic/Clinic.AppHost/AppHost.cs | 13 ++ Clinic/Clinic.AppHost/Clinic.AppHost.csproj | 19 +++ .../Properties/launchSettings.json | 31 +++++ .../appsettings.Development.json | 8 ++ Clinic/Clinic.AppHost/appsettings.json | 9 ++ .../Clinic.ServiceDefaults.csproj | 22 +++ Clinic/Clinic.ServiceDefaults/Extensions.cs | 127 ++++++++++++++++++ .../{DatabaseTests.cs => ClinicTests.cs} | 11 +- Clinic/Clinic.sln | 28 ++++ 34 files changed, 292 insertions(+), 48 deletions(-) create mode 100644 Clinic/Clinic.AppHost/AppHost.cs create mode 100644 Clinic/Clinic.AppHost/Clinic.AppHost.csproj create mode 100644 Clinic/Clinic.AppHost/Properties/launchSettings.json create mode 100644 Clinic/Clinic.AppHost/appsettings.Development.json create mode 100644 Clinic/Clinic.AppHost/appsettings.json create mode 100644 Clinic/Clinic.ServiceDefaults/Clinic.ServiceDefaults.csproj create mode 100644 Clinic/Clinic.ServiceDefaults/Extensions.cs rename Clinic/Clinic.Tests/{DatabaseTests.cs => ClinicTests.cs} (82%) diff --git a/Clinic/Clinic.Api/Clinic.Api.csproj b/Clinic/Clinic.Api/Clinic.Api.csproj index 3bc882ae6..ad02b079b 100644 --- a/Clinic/Clinic.Api/Clinic.Api.csproj +++ b/Clinic/Clinic.Api/Clinic.Api.csproj @@ -7,16 +7,18 @@ - + + + + - diff --git a/Clinic/Clinic.Api/Controllers/AnalyticsControllers.cs b/Clinic/Clinic.Api/Controllers/AnalyticsControllers.cs index 8965443cc..aa6c04d7a 100644 --- a/Clinic/Clinic.Api/Controllers/AnalyticsControllers.cs +++ b/Clinic/Clinic.Api/Controllers/AnalyticsControllers.cs @@ -1,7 +1,7 @@ using Microsoft.AspNetCore.Mvc; using Clinic.Api.Services; -using Clinic.Api.DTOs.DoctorDto; -using Clinic.Api.DTOs.PatientDto; +using Clinic.Api.DTOs.Doctor; +using Clinic.Api.DTOs.Patient; using Clinic.Api.DTOs.Appointment; namespace Clinic.Api.Controllers; diff --git a/Clinic/Clinic.Api/Controllers/BaseControllers.cs b/Clinic/Clinic.Api/Controllers/BaseControllers.cs index 4f147c281..945532c00 100644 --- a/Clinic/Clinic.Api/Controllers/BaseControllers.cs +++ b/Clinic/Clinic.Api/Controllers/BaseControllers.cs @@ -1,7 +1,5 @@ using Microsoft.AspNetCore.Mvc; using Clinic.Api.Interfaces.Services; -using Clinic.Api.Services; -using Microsoft.AspNetCore.Mvc.ApplicationModels; namespace Clinic.Api.Controllers; diff --git a/Clinic/Clinic.Api/Controllers/DoctorControllers.cs b/Clinic/Clinic.Api/Controllers/DoctorControllers.cs index 92c6966f1..7b0f770af 100644 --- a/Clinic/Clinic.Api/Controllers/DoctorControllers.cs +++ b/Clinic/Clinic.Api/Controllers/DoctorControllers.cs @@ -1,5 +1,5 @@ using Microsoft.AspNetCore.Mvc; -using Clinic.Api.DTOs.DoctorDto; +using Clinic.Api.DTOs.Doctor; using Clinic.Api.Interfaces.Services; namespace Clinic.Api.Controllers; diff --git a/Clinic/Clinic.Api/Controllers/PatientControllers.cs b/Clinic/Clinic.Api/Controllers/PatientControllers.cs index c2b22ede2..b88b4349d 100644 --- a/Clinic/Clinic.Api/Controllers/PatientControllers.cs +++ b/Clinic/Clinic.Api/Controllers/PatientControllers.cs @@ -1,5 +1,5 @@ using Microsoft.AspNetCore.Mvc; -using Clinic.Api.DTOs.PatientDto; +using Clinic.Api.DTOs.Patient; using Clinic.Api.Interfaces.Services; namespace Clinic.Api.Controllers; diff --git a/Clinic/Clinic.Api/Controllers/SpecializationControllers.cs b/Clinic/Clinic.Api/Controllers/SpecializationControllers.cs index 67f8c1277..de2f073e6 100644 --- a/Clinic/Clinic.Api/Controllers/SpecializationControllers.cs +++ b/Clinic/Clinic.Api/Controllers/SpecializationControllers.cs @@ -1,5 +1,5 @@ using Microsoft.AspNetCore.Mvc; -using Clinic.Api.DTOs.SpecializationDto; +using Clinic.Api.DTOs.Specialization; using Clinic.Api.Interfaces.Services; namespace Clinic.Api.Controllers; diff --git a/Clinic/Clinic.Api/DTOs/Doctor/CreateDoctorDto.cs b/Clinic/Clinic.Api/DTOs/Doctor/CreateDoctorDto.cs index e70491628..19f3d8f68 100644 --- a/Clinic/Clinic.Api/DTOs/Doctor/CreateDoctorDto.cs +++ b/Clinic/Clinic.Api/DTOs/Doctor/CreateDoctorDto.cs @@ -1,7 +1,6 @@ -using Clinic.Models.Entities; -using Clinic.Api.DTOs.SpecializationDto; +using Clinic.Api.DTOs.Specialization; -namespace Clinic.Api.DTOs.DoctorDto; +namespace Clinic.Api.DTOs.Doctor; /// /// DTO for creating a new doctor, including required personal information, diff --git a/Clinic/Clinic.Api/DTOs/Doctor/GetDoctorDto.cs b/Clinic/Clinic.Api/DTOs/Doctor/GetDoctorDto.cs index aecb93529..39471125f 100644 --- a/Clinic/Clinic.Api/DTOs/Doctor/GetDoctorDto.cs +++ b/Clinic/Clinic.Api/DTOs/Doctor/GetDoctorDto.cs @@ -1,5 +1,4 @@ -using Clinic.Models.Entities; -namespace Clinic.Api.DTOs.DoctorDto; +namespace Clinic.Api.DTOs.Doctor; /// /// DTO for retrieving detailed information about a doctor, diff --git a/Clinic/Clinic.Api/DTOs/Doctor/UpdateDoctorDto.cs b/Clinic/Clinic.Api/DTOs/Doctor/UpdateDoctorDto.cs index 76b0a3150..311f9ac47 100644 --- a/Clinic/Clinic.Api/DTOs/Doctor/UpdateDoctorDto.cs +++ b/Clinic/Clinic.Api/DTOs/Doctor/UpdateDoctorDto.cs @@ -1,7 +1,4 @@ -using Clinic.Models.Entities; -using Clinic.Models.Enums; - -namespace Clinic.Api.DTOs.DoctorDto; +namespace Clinic.Api.DTOs.Doctor; /// /// DTO for updating an existing doctor, including optional personal information, diff --git a/Clinic/Clinic.Api/DTOs/Patient/CreatePatientDto.cs b/Clinic/Clinic.Api/DTOs/Patient/CreatePatientDto.cs index eafeb1f46..8c95f98d7 100644 --- a/Clinic/Clinic.Api/DTOs/Patient/CreatePatientDto.cs +++ b/Clinic/Clinic.Api/DTOs/Patient/CreatePatientDto.cs @@ -1,4 +1,4 @@ -namespace Clinic.Api.DTOs.PatientDto; +namespace Clinic.Api.DTOs.Patient; /// /// DTO for creating a new patient, including required personal information, diff --git a/Clinic/Clinic.Api/DTOs/Patient/GetPatientDto.cs b/Clinic/Clinic.Api/DTOs/Patient/GetPatientDto.cs index da95c2ffb..829165d9b 100644 --- a/Clinic/Clinic.Api/DTOs/Patient/GetPatientDto.cs +++ b/Clinic/Clinic.Api/DTOs/Patient/GetPatientDto.cs @@ -1,6 +1,4 @@ -using Clinic.Models.Enums; - -namespace Clinic.Api.DTOs.PatientDto; +namespace Clinic.Api.DTOs.Patient; /// /// DTO for retrieving detailed information about a patient, diff --git a/Clinic/Clinic.Api/DTOs/Patient/UpdatePatientDto.cs b/Clinic/Clinic.Api/DTOs/Patient/UpdatePatientDto.cs index 202d0f499..46a9aa641 100644 --- a/Clinic/Clinic.Api/DTOs/Patient/UpdatePatientDto.cs +++ b/Clinic/Clinic.Api/DTOs/Patient/UpdatePatientDto.cs @@ -1,6 +1,6 @@ using Clinic.Models.Enums; -namespace Clinic.Api.DTOs.PatientDto; +namespace Clinic.Api.DTOs.Patient; /// /// DTO for updating an existing patient, including optional personal information, diff --git a/Clinic/Clinic.Api/DTOs/Specialization/CreateSpecializationDto.cs b/Clinic/Clinic.Api/DTOs/Specialization/CreateSpecializationDto.cs index 309064d75..5f00061b0 100644 --- a/Clinic/Clinic.Api/DTOs/Specialization/CreateSpecializationDto.cs +++ b/Clinic/Clinic.Api/DTOs/Specialization/CreateSpecializationDto.cs @@ -1,4 +1,4 @@ -namespace Clinic.Api.DTOs.SpecializationDto; +namespace Clinic.Api.DTOs.Specialization; /// /// DTO for creating a new specialization, including required name. diff --git a/Clinic/Clinic.Api/DTOs/Specialization/GetSpecializationDto.cs b/Clinic/Clinic.Api/DTOs/Specialization/GetSpecializationDto.cs index dcbbcca99..5268e91a6 100644 --- a/Clinic/Clinic.Api/DTOs/Specialization/GetSpecializationDto.cs +++ b/Clinic/Clinic.Api/DTOs/Specialization/GetSpecializationDto.cs @@ -1,4 +1,4 @@ -namespace Clinic.Api.DTOs.SpecializationDto; +namespace Clinic.Api.DTOs.Specialization; /// /// DTO for retrieving detailed information about a specialization, diff --git a/Clinic/Clinic.Api/DTOs/Specialization/UpdateSpecializationDto.cs b/Clinic/Clinic.Api/DTOs/Specialization/UpdateSpecializationDto.cs index c67b11e69..5db98afaa 100644 --- a/Clinic/Clinic.Api/DTOs/Specialization/UpdateSpecializationDto.cs +++ b/Clinic/Clinic.Api/DTOs/Specialization/UpdateSpecializationDto.cs @@ -1,4 +1,4 @@ -namespace Clinic.Api.DTOs.SpecializationDto; +namespace Clinic.Api.DTOs.Specialization; /// /// DTO for updating an existing specialization. diff --git a/Clinic/Clinic.Api/Interfaces/Services/IAppointmentService.cs b/Clinic/Clinic.Api/Interfaces/Services/IAppointmentService.cs index 3d4be2d62..016d8dfd7 100644 --- a/Clinic/Clinic.Api/Interfaces/Services/IAppointmentService.cs +++ b/Clinic/Clinic.Api/Interfaces/Services/IAppointmentService.cs @@ -1,5 +1,4 @@ using Clinic.Api.DTOs.Appointment; -using Clinic.Api.Interfaces; namespace Clinic.Api.Interfaces.Services; diff --git a/Clinic/Clinic.Api/Interfaces/Services/IDoctorService.cs b/Clinic/Clinic.Api/Interfaces/Services/IDoctorService.cs index bfe61b9fb..1a5ae5e41 100644 --- a/Clinic/Clinic.Api/Interfaces/Services/IDoctorService.cs +++ b/Clinic/Clinic.Api/Interfaces/Services/IDoctorService.cs @@ -1,4 +1,4 @@ -using Clinic.Api.DTOs.DoctorDto; +using Clinic.Api.DTOs.Doctor; namespace Clinic.Api.Interfaces.Services; diff --git a/Clinic/Clinic.Api/Interfaces/Services/IPatientService.cs b/Clinic/Clinic.Api/Interfaces/Services/IPatientService.cs index 2bff86934..3c75c902f 100644 --- a/Clinic/Clinic.Api/Interfaces/Services/IPatientService.cs +++ b/Clinic/Clinic.Api/Interfaces/Services/IPatientService.cs @@ -1,4 +1,4 @@ -using Clinic.Api.DTOs.PatientDto; +using Clinic.Api.DTOs.Patient; namespace Clinic.Api.Interfaces.Services; diff --git a/Clinic/Clinic.Api/Interfaces/Services/ISpecializationService.cs b/Clinic/Clinic.Api/Interfaces/Services/ISpecializationService.cs index e2efe360e..270100351 100644 --- a/Clinic/Clinic.Api/Interfaces/Services/ISpecializationService.cs +++ b/Clinic/Clinic.Api/Interfaces/Services/ISpecializationService.cs @@ -1,4 +1,4 @@ -using Clinic.Api.DTOs.SpecializationDto; +using Clinic.Api.DTOs.Specialization; namespace Clinic.Api.Interfaces.Services; diff --git a/Clinic/Clinic.Api/MappingProfile/MappingProfile.cs b/Clinic/Clinic.Api/MappingProfile/MappingProfile.cs index 9fe57228f..4779249dd 100644 --- a/Clinic/Clinic.Api/MappingProfile/MappingProfile.cs +++ b/Clinic/Clinic.Api/MappingProfile/MappingProfile.cs @@ -1,7 +1,7 @@ using AutoMapper; -using Clinic.Api.DTOs.PatientDto; -using Clinic.Api.DTOs.DoctorDto; -using Clinic.Api.DTOs.SpecializationDto; +using Clinic.Api.DTOs.Patient; +using Clinic.Api.DTOs.Doctor; +using Clinic.Api.DTOs.Specialization; using Clinic.Api.DTOs.Appointment; using Clinic.Models.Enums; using Clinic.Models.Entities; diff --git a/Clinic/Clinic.Api/Program.cs b/Clinic/Clinic.Api/Program.cs index b9ffa677d..34aef22aa 100644 --- a/Clinic/Clinic.Api/Program.cs +++ b/Clinic/Clinic.Api/Program.cs @@ -2,6 +2,7 @@ using Clinic.DataBase.Interfaces; using Clinic.DataBase.EntityFramework; using Clinic.Api.MappingProfile; +using Microsoft.Extensions.Hosting; using Clinic.Api.Services; using Clinic.Api.Converter; using Clinic.Api.Interfaces.Services; @@ -9,6 +10,8 @@ var builder = WebApplication.CreateBuilder(args); +builder.AddServiceDefaults(); + builder.Services.AddControllers() .AddJsonOptions(options => { @@ -25,7 +28,6 @@ builder.Services.AddDbContext(options => options.UseMySql(connectionString, serverVersion)); - builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); diff --git a/Clinic/Clinic.Api/Services/AnalyticsServices.cs b/Clinic/Clinic.Api/Services/AnalyticsServices.cs index 583a0492c..37cc8b852 100644 --- a/Clinic/Clinic.Api/Services/AnalyticsServices.cs +++ b/Clinic/Clinic.Api/Services/AnalyticsServices.cs @@ -1,7 +1,7 @@ using AutoMapper; using Clinic.DataBase.Interfaces; -using Clinic.Api.DTOs.PatientDto; -using Clinic.Api.DTOs.DoctorDto; +using Clinic.Api.DTOs.Patient; +using Clinic.Api.DTOs.Doctor; using Clinic.Api.DTOs.Appointment; namespace Clinic.Api.Services; diff --git a/Clinic/Clinic.Api/Services/DoctorServices.cs b/Clinic/Clinic.Api/Services/DoctorServices.cs index 3d2398291..12ec6566f 100644 --- a/Clinic/Clinic.Api/Services/DoctorServices.cs +++ b/Clinic/Clinic.Api/Services/DoctorServices.cs @@ -1,6 +1,6 @@ using AutoMapper; using Clinic.DataBase.Interfaces; -using Clinic.Api.DTOs.DoctorDto; +using Clinic.Api.DTOs.Doctor; using Clinic.Models.Entities; using Clinic.Api.Interfaces.Services; diff --git a/Clinic/Clinic.Api/Services/PatientServices.cs b/Clinic/Clinic.Api/Services/PatientServices.cs index 8b6e0198b..4fa8b1716 100644 --- a/Clinic/Clinic.Api/Services/PatientServices.cs +++ b/Clinic/Clinic.Api/Services/PatientServices.cs @@ -1,6 +1,6 @@ using AutoMapper; using Clinic.DataBase.Interfaces; -using Clinic.Api.DTOs.PatientDto; +using Clinic.Api.DTOs.Patient; using Clinic.Models.Entities; using Clinic.Api.Interfaces.Services; diff --git a/Clinic/Clinic.Api/Services/SpecializationServices.cs b/Clinic/Clinic.Api/Services/SpecializationServices.cs index bc4b24e84..f129c909d 100644 --- a/Clinic/Clinic.Api/Services/SpecializationServices.cs +++ b/Clinic/Clinic.Api/Services/SpecializationServices.cs @@ -1,7 +1,7 @@ using AutoMapper; using Clinic.Models.Entities; using Clinic.DataBase.Interfaces; -using Clinic.Api.DTOs.SpecializationDto; +using Clinic.Api.DTOs.Specialization; using Clinic.Api.Interfaces.Services; namespace Clinic.Api.Services; diff --git a/Clinic/Clinic.AppHost/AppHost.cs b/Clinic/Clinic.AppHost/AppHost.cs new file mode 100644 index 000000000..57def4295 --- /dev/null +++ b/Clinic/Clinic.AppHost/AppHost.cs @@ -0,0 +1,13 @@ +using Aspire.Hosting.MySql; + +var builder = DistributedApplication.CreateBuilder(args); + +var mysql = builder.AddMySql("mysql") + .AddDatabase("ClinicDb"); + +var api = builder.AddProject("clinic-api") + .WithReference(mysql) + .WaitFor(mysql) + .WithExternalHttpEndpoints(); + +builder.Build().Run(); \ No newline at end of file diff --git a/Clinic/Clinic.AppHost/Clinic.AppHost.csproj b/Clinic/Clinic.AppHost/Clinic.AppHost.csproj new file mode 100644 index 000000000..3c7d3e65d --- /dev/null +++ b/Clinic/Clinic.AppHost/Clinic.AppHost.csproj @@ -0,0 +1,19 @@ + + + + Exe + net9.0 + enable + enable + 1fced4c2-e45b-4353-84db-0376db0b16fc + + + + + + + + + + + diff --git a/Clinic/Clinic.AppHost/Properties/launchSettings.json b/Clinic/Clinic.AppHost/Properties/launchSettings.json new file mode 100644 index 000000000..29cc2b1ee --- /dev/null +++ b/Clinic/Clinic.AppHost/Properties/launchSettings.json @@ -0,0 +1,31 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "https://localhost:17260;http://localhost:15245", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "DOTNET_ENVIRONMENT": "Development", + "ASPIRE_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:21029", + "ASPIRE_DASHBOARD_MCP_ENDPOINT_URL": "https://localhost:23235", + "ASPIRE_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:22009" + } + }, + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "http://localhost:15245", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "DOTNET_ENVIRONMENT": "Development", + "ASPIRE_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:19118", + "ASPIRE_DASHBOARD_MCP_ENDPOINT_URL": "http://localhost:18229", + "ASPIRE_RESOURCE_SERVICE_ENDPOINT_URL": "http://localhost:20170" + } + } + } +} \ No newline at end of file diff --git a/Clinic/Clinic.AppHost/appsettings.Development.json b/Clinic/Clinic.AppHost/appsettings.Development.json new file mode 100644 index 000000000..ff66ba6b2 --- /dev/null +++ b/Clinic/Clinic.AppHost/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/Clinic/Clinic.AppHost/appsettings.json b/Clinic/Clinic.AppHost/appsettings.json new file mode 100644 index 000000000..2185f9551 --- /dev/null +++ b/Clinic/Clinic.AppHost/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning", + "Aspire.Hosting.Dcp": "Warning" + } + } +} diff --git a/Clinic/Clinic.ServiceDefaults/Clinic.ServiceDefaults.csproj b/Clinic/Clinic.ServiceDefaults/Clinic.ServiceDefaults.csproj new file mode 100644 index 000000000..c93b2fd96 --- /dev/null +++ b/Clinic/Clinic.ServiceDefaults/Clinic.ServiceDefaults.csproj @@ -0,0 +1,22 @@ + + + + net9.0 + enable + enable + true + + + + + + + + + + + + + + + diff --git a/Clinic/Clinic.ServiceDefaults/Extensions.cs b/Clinic/Clinic.ServiceDefaults/Extensions.cs new file mode 100644 index 000000000..63aa3ba3f --- /dev/null +++ b/Clinic/Clinic.ServiceDefaults/Extensions.cs @@ -0,0 +1,127 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Diagnostics.HealthChecks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Diagnostics.HealthChecks; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.ServiceDiscovery; +using OpenTelemetry; +using OpenTelemetry.Metrics; +using OpenTelemetry.Trace; + +namespace Microsoft.Extensions.Hosting; + +// Adds common Aspire services: service discqovery, resilience, health checks, and OpenTelemetry. +// This project should be referenced by each service project in your solution. +// To learn more about using this project, see https://aka.ms/dotnet/aspire/service-defaults +public static class Extensions +{ + private const string HealthEndpointPath = "/health"; + private const string AlivenessEndpointPath = "/alive"; + + public static TBuilder AddServiceDefaults(this TBuilder builder) where TBuilder : IHostApplicationBuilder + { + builder.ConfigureOpenTelemetry(); + + builder.AddDefaultHealthChecks(); + + builder.Services.AddServiceDiscovery(); + + builder.Services.ConfigureHttpClientDefaults(http => + { + // Turn on resilience by default + http.AddStandardResilienceHandler(); + + // Turn on service discovery by default + http.AddServiceDiscovery(); + }); + + // Uncomment the following to restrict the allowed schemes for service discovery. + // builder.Services.Configure(options => + // { + // options.AllowedSchemes = ["https"]; + // }); + + return builder; + } + + public static TBuilder ConfigureOpenTelemetry(this TBuilder builder) where TBuilder : IHostApplicationBuilder + { + builder.Logging.AddOpenTelemetry(logging => + { + logging.IncludeFormattedMessage = true; + logging.IncludeScopes = true; + }); + + builder.Services.AddOpenTelemetry() + .WithMetrics(metrics => + { + metrics.AddAspNetCoreInstrumentation() + .AddHttpClientInstrumentation() + .AddRuntimeInstrumentation(); + }) + .WithTracing(tracing => + { + tracing.AddSource(builder.Environment.ApplicationName) + .AddAspNetCoreInstrumentation(tracing => + // Exclude health check requests from tracing + tracing.Filter = context => + !context.Request.Path.StartsWithSegments(HealthEndpointPath) + && !context.Request.Path.StartsWithSegments(AlivenessEndpointPath) + ) + // Uncomment the following line to enable gRPC instrumentation (requires the OpenTelemetry.Instrumentation.GrpcNetClient package) + //.AddGrpcClientInstrumentation() + .AddHttpClientInstrumentation(); + }); + + builder.AddOpenTelemetryExporters(); + + return builder; + } + + private static TBuilder AddOpenTelemetryExporters(this TBuilder builder) where TBuilder : IHostApplicationBuilder + { + var useOtlpExporter = !string.IsNullOrWhiteSpace(builder.Configuration["OTEL_EXPORTER_OTLP_ENDPOINT"]); + + if (useOtlpExporter) + { + builder.Services.AddOpenTelemetry().UseOtlpExporter(); + } + + // Uncomment the following lines to enable the Azure Monitor exporter (requires the Azure.Monitor.OpenTelemetry.AspNetCore package) + //if (!string.IsNullOrEmpty(builder.Configuration["APPLICATIONINSIGHTS_CONNECTION_STRING"])) + //{ + // builder.Services.AddOpenTelemetry() + // .UseAzureMonitor(); + //} + + return builder; + } + + public static TBuilder AddDefaultHealthChecks(this TBuilder builder) where TBuilder : IHostApplicationBuilder + { + builder.Services.AddHealthChecks() + // Add a default liveness check to ensure app is responsive + .AddCheck("self", () => HealthCheckResult.Healthy(), ["live"]); + + return builder; + } + + public static WebApplication MapDefaultEndpoints(this WebApplication app) + { + // Adding health checks endpoints to applications in non-development environments has security implications. + // See https://aka.ms/dotnet/aspire/healthchecks for details before enabling these endpoints in non-development environments. + if (app.Environment.IsDevelopment()) + { + // All health checks must pass for app to be considered ready to accept traffic after starting + app.MapHealthChecks(HealthEndpointPath); + + // Only health checks tagged with the "live" tag must pass for app to be considered alive + app.MapHealthChecks(AlivenessEndpointPath, new HealthCheckOptions + { + Predicate = r => r.Tags.Contains("live") + }); + } + + return app; + } +} diff --git a/Clinic/Clinic.Tests/DatabaseTests.cs b/Clinic/Clinic.Tests/ClinicTests.cs similarity index 82% rename from Clinic/Clinic.Tests/DatabaseTests.cs rename to Clinic/Clinic.Tests/ClinicTests.cs index d4e7a3c1d..b760e287a 100644 --- a/Clinic/Clinic.Tests/DatabaseTests.cs +++ b/Clinic/Clinic.Tests/ClinicTests.cs @@ -1,16 +1,9 @@ -using Microsoft.EntityFrameworkCore; using Clinic.DataBase; using Clinic.Api.Services; -using Clinic.Models.Entities; -using Clinic.Models.Enums; -using Microsoft.AspNetCore.Http; -namespace Clinic.Tests; -/// -/// Базовый класс для тестирования Entity Framework контекста с InMemory Database -/// -public abstract class DatabaseTestBase(AnalyticsServices testServices) : IClassFixture +namespace Clinic.Tests; +public abstract class ClinicTests(AnalyticsServices testServices) : IClassFixture { [Fact] public void GetDoctorsWithExperience_WhenExperienceAtLeast10Years_ReturnsExperiencedDoctorsOrderedByName() diff --git a/Clinic/Clinic.sln b/Clinic/Clinic.sln index af2465d41..db7264881 100644 --- a/Clinic/Clinic.sln +++ b/Clinic/Clinic.sln @@ -11,6 +11,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Clinic.DataBase", "Clinic.D EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Clinic.Tests", "Clinic.Tests\Clinic.Tests.csproj", "{D3E7E045-5C0D-4199-AE4A-7468F477DE74}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Clinic.AppHost", "Clinic.AppHost\Clinic.AppHost.csproj", "{7BD8C638-56B5-438F-BE87-2A628C5F7C8F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Clinic.ServiceDefaults", "Clinic.ServiceDefaults\Clinic.ServiceDefaults.csproj", "{472C1C25-CCA0-4AA2-910C-150DD406AE5F}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -69,6 +73,30 @@ Global {D3E7E045-5C0D-4199-AE4A-7468F477DE74}.Release|x64.Build.0 = Release|Any CPU {D3E7E045-5C0D-4199-AE4A-7468F477DE74}.Release|x86.ActiveCfg = Release|Any CPU {D3E7E045-5C0D-4199-AE4A-7468F477DE74}.Release|x86.Build.0 = Release|Any CPU + {7BD8C638-56B5-438F-BE87-2A628C5F7C8F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7BD8C638-56B5-438F-BE87-2A628C5F7C8F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7BD8C638-56B5-438F-BE87-2A628C5F7C8F}.Debug|x64.ActiveCfg = Debug|Any CPU + {7BD8C638-56B5-438F-BE87-2A628C5F7C8F}.Debug|x64.Build.0 = Debug|Any CPU + {7BD8C638-56B5-438F-BE87-2A628C5F7C8F}.Debug|x86.ActiveCfg = Debug|Any CPU + {7BD8C638-56B5-438F-BE87-2A628C5F7C8F}.Debug|x86.Build.0 = Debug|Any CPU + {7BD8C638-56B5-438F-BE87-2A628C5F7C8F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7BD8C638-56B5-438F-BE87-2A628C5F7C8F}.Release|Any CPU.Build.0 = Release|Any CPU + {7BD8C638-56B5-438F-BE87-2A628C5F7C8F}.Release|x64.ActiveCfg = Release|Any CPU + {7BD8C638-56B5-438F-BE87-2A628C5F7C8F}.Release|x64.Build.0 = Release|Any CPU + {7BD8C638-56B5-438F-BE87-2A628C5F7C8F}.Release|x86.ActiveCfg = Release|Any CPU + {7BD8C638-56B5-438F-BE87-2A628C5F7C8F}.Release|x86.Build.0 = Release|Any CPU + {472C1C25-CCA0-4AA2-910C-150DD406AE5F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {472C1C25-CCA0-4AA2-910C-150DD406AE5F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {472C1C25-CCA0-4AA2-910C-150DD406AE5F}.Debug|x64.ActiveCfg = Debug|Any CPU + {472C1C25-CCA0-4AA2-910C-150DD406AE5F}.Debug|x64.Build.0 = Debug|Any CPU + {472C1C25-CCA0-4AA2-910C-150DD406AE5F}.Debug|x86.ActiveCfg = Debug|Any CPU + {472C1C25-CCA0-4AA2-910C-150DD406AE5F}.Debug|x86.Build.0 = Debug|Any CPU + {472C1C25-CCA0-4AA2-910C-150DD406AE5F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {472C1C25-CCA0-4AA2-910C-150DD406AE5F}.Release|Any CPU.Build.0 = Release|Any CPU + {472C1C25-CCA0-4AA2-910C-150DD406AE5F}.Release|x64.ActiveCfg = Release|Any CPU + {472C1C25-CCA0-4AA2-910C-150DD406AE5F}.Release|x64.Build.0 = Release|Any CPU + {472C1C25-CCA0-4AA2-910C-150DD406AE5F}.Release|x86.ActiveCfg = Release|Any CPU + {472C1C25-CCA0-4AA2-910C-150DD406AE5F}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From 33b472ca42a74f18c3ba4f0e7a8d2cdf9afeeae2 Mon Sep 17 00:00:00 2001 From: maeosha Date: Mon, 22 Dec 2025 13:02:15 +0400 Subject: [PATCH 50/56] feat: refactor repository interfaces and implementations for better structure and add Clinic.Application project --- Clinic/Clinic.Api/Clinic.Api.csproj | 49 ++++++++----------- Clinic/Clinic.Api/Program.cs | 12 ++--- .../Clinic.Api/Services/AnalyticsServices.cs | 16 +++--- .../Services/AppointmentServices.cs | 14 +++--- Clinic/Clinic.Api/Services/DoctorServices.cs | 6 +-- Clinic/Clinic.Api/Services/PatientServices.cs | 6 +-- .../Services/SpecializationServices.cs | 6 +-- .../Clinic.Application.csproj | 13 +++++ .../Ports/IAppointmentRepository.cs} | 4 +- .../Ports/IDoctorRepository.cs} | 4 +- .../Ports/IPatientRepository.cs} | 4 +- .../Ports/ISpecializationRepository.cs} | 4 +- .../Properties/launchSettings.json | 23 +++++++++ Clinic/Clinic.DataBase/Clinic.DataBase.csproj | 1 + ...DataBase.cs => EfAppointmentRepository.cs} | 10 ++-- ...octorDataBase.cs => EfDoctorRepository.cs} | 10 ++-- ...ientDataBase.cs => EfPatientRepository.cs} | 10 ++-- ...aBase.cs => EfSpecializationRepository.cs} | 10 ++-- Clinic/Clinic.Tests/Clinic.Tests.csproj | 1 + Clinic/Clinic.sln | 14 ++++++ 20 files changed, 130 insertions(+), 87 deletions(-) create mode 100644 Clinic/Clinic.Application/Clinic.Application.csproj rename Clinic/{Clinic.DataBase/Interfaces/IAppointmentDataBase.cs => Clinic.Application/Ports/IAppointmentRepository.cs} (94%) rename Clinic/{Clinic.DataBase/Interfaces/IDoctorDataBase.cs => Clinic.Application/Ports/IDoctorRepository.cs} (93%) rename Clinic/{Clinic.DataBase/Interfaces/IPatientDataBase.cs => Clinic.Application/Ports/IPatientRepository.cs} (93%) rename Clinic/{Clinic.DataBase/Interfaces/ISpecializationDataBase.cs => Clinic.Application/Ports/ISpecializationRepository.cs} (93%) create mode 100644 Clinic/Clinic.Application/Properties/launchSettings.json rename Clinic/Clinic.DataBase/EntityFramework/{EfAppointmentDataBase.cs => EfAppointmentRepository.cs} (91%) rename Clinic/Clinic.DataBase/EntityFramework/{EfDoctorDataBase.cs => EfDoctorRepository.cs} (87%) rename Clinic/Clinic.DataBase/EntityFramework/{EfPatientDataBase.cs => EfPatientRepository.cs} (86%) rename Clinic/Clinic.DataBase/EntityFramework/{EfSpecializationDataBase.cs => EfSpecializationRepository.cs} (91%) mode change 100644 => 100755 Clinic/Clinic.sln diff --git a/Clinic/Clinic.Api/Clinic.Api.csproj b/Clinic/Clinic.Api/Clinic.Api.csproj index ad02b079b..44fa91e56 100644 --- a/Clinic/Clinic.Api/Clinic.Api.csproj +++ b/Clinic/Clinic.Api/Clinic.Api.csproj @@ -1,30 +1,21 @@ - - - - net9.0 - enable - enable - - - - - - - - - - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - + + + + net9.0 + enable + enable + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Clinic/Clinic.Api/Program.cs b/Clinic/Clinic.Api/Program.cs index 34aef22aa..234f40746 100644 --- a/Clinic/Clinic.Api/Program.cs +++ b/Clinic/Clinic.Api/Program.cs @@ -1,5 +1,5 @@ using Clinic.DataBase; -using Clinic.DataBase.Interfaces; +using Clinic.Application.Ports; using Clinic.DataBase.EntityFramework; using Clinic.Api.MappingProfile; using Microsoft.Extensions.Hosting; @@ -16,7 +16,7 @@ .AddJsonOptions(options => { options.JsonSerializerOptions.Converters.Add(new DateConverter()); - options.JsonSerializerOptions.PropertyNamingPolicy = null; + options.JsonSerializerOptions.PropertyNamingPolicy = null; }); @@ -28,10 +28,10 @@ builder.Services.AddDbContext(options => options.UseMySql(connectionString, serverVersion)); -builder.Services.AddScoped(); -builder.Services.AddScoped(); -builder.Services.AddScoped(); -builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); builder.Services.AddAutoMapper(cfg => cfg.AddProfile()); builder.Services.AddEndpointsApiExplorer(); diff --git a/Clinic/Clinic.Api/Services/AnalyticsServices.cs b/Clinic/Clinic.Api/Services/AnalyticsServices.cs index 37cc8b852..73c4abbd8 100644 --- a/Clinic/Clinic.Api/Services/AnalyticsServices.cs +++ b/Clinic/Clinic.Api/Services/AnalyticsServices.cs @@ -1,5 +1,5 @@ using AutoMapper; -using Clinic.DataBase.Interfaces; +using Clinic.Application.Ports; using Clinic.Api.DTOs.Patient; using Clinic.Api.DTOs.Doctor; using Clinic.Api.DTOs.Appointment; @@ -8,15 +8,15 @@ namespace Clinic.Api.Services; public class AnalyticsServices { - private readonly IPatientDataBase _patients; - private readonly IDoctorDataBase _doctors; - private readonly IAppointmentDataBase _appointments; + private readonly IPatientRepository _patients; + private readonly IDoctorRepository _doctors; + private readonly IAppointmentRepository _appointments; private readonly IMapper _mapper; - + public AnalyticsServices( - IPatientDataBase patients, - IDoctorDataBase doctors, - IAppointmentDataBase appointments, + IPatientRepository patients, + IDoctorRepository doctors, + IAppointmentRepository appointments, IMapper mapper) { _patients = patients; diff --git a/Clinic/Clinic.Api/Services/AppointmentServices.cs b/Clinic/Clinic.Api/Services/AppointmentServices.cs index bf09475b1..ff2fc26a9 100644 --- a/Clinic/Clinic.Api/Services/AppointmentServices.cs +++ b/Clinic/Clinic.Api/Services/AppointmentServices.cs @@ -1,5 +1,5 @@ using AutoMapper; -using Clinic.DataBase.Interfaces; +using Clinic.Application.Ports; using Clinic.Api.DTOs.Appointment; using Clinic.Models.Entities; using Clinic.Api.Interfaces.Services; @@ -13,9 +13,9 @@ namespace Clinic.Api.Services; /// public class AppointmentServices : IAppointmentServices { - private readonly IAppointmentDataBase _appointments; - private readonly IPatientDataBase _patients; - private readonly IDoctorDataBase _doctors; + private readonly IAppointmentRepository _appointments; + private readonly IPatientRepository _patients; + private readonly IDoctorRepository _doctors; private readonly IMapper _mapper; private int _appointmentId; @@ -27,9 +27,9 @@ public class AppointmentServices : IAppointmentServices /// The doctor database interface. /// The AutoMapper interface for DTO and entity mapping. public AppointmentServices( - IAppointmentDataBase appointments, - IPatientDataBase patients, - IDoctorDataBase doctors, + IAppointmentRepository appointments, + IPatientRepository patients, + IDoctorRepository doctors, IMapper mapper) { _appointments = appointments; diff --git a/Clinic/Clinic.Api/Services/DoctorServices.cs b/Clinic/Clinic.Api/Services/DoctorServices.cs index 12ec6566f..2c45aad67 100644 --- a/Clinic/Clinic.Api/Services/DoctorServices.cs +++ b/Clinic/Clinic.Api/Services/DoctorServices.cs @@ -1,5 +1,5 @@ using AutoMapper; -using Clinic.DataBase.Interfaces; +using Clinic.Application.Ports; using Clinic.Api.DTOs.Doctor; using Clinic.Models.Entities; using Clinic.Api.Interfaces.Services; @@ -14,7 +14,7 @@ namespace Clinic.Api.Services; /// public class DoctorServices : IDoctorServices { - private readonly IDoctorDataBase _db; + private readonly IDoctorRepository _db; private readonly IMapper _mapper; private int _doctorId; @@ -24,7 +24,7 @@ public class DoctorServices : IDoctorServices /// /// The database service for doctor operations. /// The AutoMapper instance used for object mapping. - public DoctorServices(IDoctorDataBase db, IMapper mapper) + public DoctorServices(IDoctorRepository db, IMapper mapper) { _db = db; _mapper = mapper; diff --git a/Clinic/Clinic.Api/Services/PatientServices.cs b/Clinic/Clinic.Api/Services/PatientServices.cs index 4fa8b1716..a19f99dea 100644 --- a/Clinic/Clinic.Api/Services/PatientServices.cs +++ b/Clinic/Clinic.Api/Services/PatientServices.cs @@ -1,5 +1,5 @@ using AutoMapper; -using Clinic.DataBase.Interfaces; +using Clinic.Application.Ports; using Clinic.Api.DTOs.Patient; using Clinic.Models.Entities; using Clinic.Api.Interfaces.Services; @@ -13,7 +13,7 @@ namespace Clinic.Api.Services; /// public class PatientServices : IPatientServices { - private readonly IPatientDataBase _db; + private readonly IPatientRepository _db; private readonly IMapper _mapper; private int _patientId; @@ -23,7 +23,7 @@ public class PatientServices : IPatientServices /// /// The database service for patient operations. /// The AutoMapper instance for mapping objects. - public PatientServices(IPatientDataBase db, IMapper mapper) + public PatientServices(IPatientRepository db, IMapper mapper) { _db = db; _mapper = mapper; diff --git a/Clinic/Clinic.Api/Services/SpecializationServices.cs b/Clinic/Clinic.Api/Services/SpecializationServices.cs index f129c909d..9d6642c6c 100644 --- a/Clinic/Clinic.Api/Services/SpecializationServices.cs +++ b/Clinic/Clinic.Api/Services/SpecializationServices.cs @@ -1,6 +1,6 @@ using AutoMapper; using Clinic.Models.Entities; -using Clinic.DataBase.Interfaces; +using Clinic.Application.Ports; using Clinic.Api.DTOs.Specialization; using Clinic.Api.Interfaces.Services; @@ -12,7 +12,7 @@ namespace Clinic.Api.Services; /// public class SpecializationServices : ISpecializationServices { - private readonly ISpecializationDataBase _db; + private readonly ISpecializationRepository _db; private readonly IMapper _mapper; private int _specializationId; @@ -22,7 +22,7 @@ public class SpecializationServices : ISpecializationServices /// /// The database service for specialization operations. /// The AutoMapper instance used for object mapping. - public SpecializationServices(ISpecializationDataBase db, IMapper mapper) + public SpecializationServices(ISpecializationRepository db, IMapper mapper) { _db = db; _mapper = mapper; diff --git a/Clinic/Clinic.Application/Clinic.Application.csproj b/Clinic/Clinic.Application/Clinic.Application.csproj new file mode 100644 index 000000000..9078de21d --- /dev/null +++ b/Clinic/Clinic.Application/Clinic.Application.csproj @@ -0,0 +1,13 @@ + + + + net9.0 + enable + enable + + + + + + + diff --git a/Clinic/Clinic.DataBase/Interfaces/IAppointmentDataBase.cs b/Clinic/Clinic.Application/Ports/IAppointmentRepository.cs similarity index 94% rename from Clinic/Clinic.DataBase/Interfaces/IAppointmentDataBase.cs rename to Clinic/Clinic.Application/Ports/IAppointmentRepository.cs index c59be4bdb..c8f281194 100644 --- a/Clinic/Clinic.DataBase/Interfaces/IAppointmentDataBase.cs +++ b/Clinic/Clinic.Application/Ports/IAppointmentRepository.cs @@ -1,12 +1,12 @@ using Clinic.Models.Entities; -namespace Clinic.DataBase.Interfaces; +namespace Clinic.Application.Ports; /// /// Abstraction for appointment persistence operations. /// Provides methods to query and modify appointments in the data store. /// -public interface IAppointmentDataBase +public interface IAppointmentRepository { /// /// Retrieves an appointment by identifier. diff --git a/Clinic/Clinic.DataBase/Interfaces/IDoctorDataBase.cs b/Clinic/Clinic.Application/Ports/IDoctorRepository.cs similarity index 93% rename from Clinic/Clinic.DataBase/Interfaces/IDoctorDataBase.cs rename to Clinic/Clinic.Application/Ports/IDoctorRepository.cs index d3b69a80d..4a7367c7e 100644 --- a/Clinic/Clinic.DataBase/Interfaces/IDoctorDataBase.cs +++ b/Clinic/Clinic.Application/Ports/IDoctorRepository.cs @@ -1,12 +1,12 @@ using Clinic.Models.Entities; -namespace Clinic.DataBase.Interfaces; +namespace Clinic.Application.Ports; /// /// Abstraction for doctor persistence operations. /// Implementations should provide CRUD operations for doctor entities. /// -public interface IDoctorDataBase +public interface IDoctorRepository { /// /// Retrieves a doctor by identifier, or null if not found. diff --git a/Clinic/Clinic.DataBase/Interfaces/IPatientDataBase.cs b/Clinic/Clinic.Application/Ports/IPatientRepository.cs similarity index 93% rename from Clinic/Clinic.DataBase/Interfaces/IPatientDataBase.cs rename to Clinic/Clinic.Application/Ports/IPatientRepository.cs index f4b5aa6ae..539f0f571 100644 --- a/Clinic/Clinic.DataBase/Interfaces/IPatientDataBase.cs +++ b/Clinic/Clinic.Application/Ports/IPatientRepository.cs @@ -1,12 +1,12 @@ using Clinic.Models.Entities; -namespace Clinic.DataBase.Interfaces; +namespace Clinic.Application.Ports; /// /// Abstraction for patient persistence operations. /// Implementations should provide methods to get, add, update and remove patients. /// -public interface IPatientDataBase +public interface IPatientRepository { /// /// Retrieves a patient by identifier. diff --git a/Clinic/Clinic.DataBase/Interfaces/ISpecializationDataBase.cs b/Clinic/Clinic.Application/Ports/ISpecializationRepository.cs similarity index 93% rename from Clinic/Clinic.DataBase/Interfaces/ISpecializationDataBase.cs rename to Clinic/Clinic.Application/Ports/ISpecializationRepository.cs index 91dc10a6d..d07b612c0 100644 --- a/Clinic/Clinic.DataBase/Interfaces/ISpecializationDataBase.cs +++ b/Clinic/Clinic.Application/Ports/ISpecializationRepository.cs @@ -1,12 +1,12 @@ using Clinic.Models.Entities; -namespace Clinic.DataBase.Interfaces; +namespace Clinic.Application.Ports; /// /// Abstraction for specialization persistence operations. /// Provides methods to query and modify specializations. /// -public interface ISpecializationDataBase +public interface ISpecializationRepository { /// /// Returns all specializations. diff --git a/Clinic/Clinic.Application/Properties/launchSettings.json b/Clinic/Clinic.Application/Properties/launchSettings.json new file mode 100644 index 000000000..95a174177 --- /dev/null +++ b/Clinic/Clinic.Application/Properties/launchSettings.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "http://localhost:5233", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "https://localhost:7077;http://localhost:5233", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/Clinic/Clinic.DataBase/Clinic.DataBase.csproj b/Clinic/Clinic.DataBase/Clinic.DataBase.csproj index 00d1ba461..037dad52b 100644 --- a/Clinic/Clinic.DataBase/Clinic.DataBase.csproj +++ b/Clinic/Clinic.DataBase/Clinic.DataBase.csproj @@ -8,6 +8,7 @@ + diff --git a/Clinic/Clinic.DataBase/EntityFramework/EfAppointmentDataBase.cs b/Clinic/Clinic.DataBase/EntityFramework/EfAppointmentRepository.cs similarity index 91% rename from Clinic/Clinic.DataBase/EntityFramework/EfAppointmentDataBase.cs rename to Clinic/Clinic.DataBase/EntityFramework/EfAppointmentRepository.cs index 1572c55e9..d26efb17b 100644 --- a/Clinic/Clinic.DataBase/EntityFramework/EfAppointmentDataBase.cs +++ b/Clinic/Clinic.DataBase/EntityFramework/EfAppointmentRepository.cs @@ -1,22 +1,22 @@ using Clinic.Models.Entities; -using Clinic.DataBase.Interfaces; +using Clinic.Application.Ports; using Microsoft.EntityFrameworkCore; namespace Clinic.DataBase.EntityFramework; /// -/// Entity Framework implementation of that manages +/// Entity Framework implementation of that manages /// appointment entities using a . /// -public sealed class EfAppointmentDataBase : IAppointmentDataBase +public sealed class EfAppointmentRepository : IAppointmentRepository { private readonly ClinicDbContext _context; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The database context used for data access. - public EfAppointmentDataBase(ClinicDbContext context) + public EfAppointmentRepository(ClinicDbContext context) { _context = context; } diff --git a/Clinic/Clinic.DataBase/EntityFramework/EfDoctorDataBase.cs b/Clinic/Clinic.DataBase/EntityFramework/EfDoctorRepository.cs similarity index 87% rename from Clinic/Clinic.DataBase/EntityFramework/EfDoctorDataBase.cs rename to Clinic/Clinic.DataBase/EntityFramework/EfDoctorRepository.cs index b49a270dc..e3e8d198b 100644 --- a/Clinic/Clinic.DataBase/EntityFramework/EfDoctorDataBase.cs +++ b/Clinic/Clinic.DataBase/EntityFramework/EfDoctorRepository.cs @@ -1,22 +1,22 @@ using Clinic.Models.Entities; -using Clinic.DataBase.Interfaces; +using Clinic.Application.Ports; using Microsoft.EntityFrameworkCore; namespace Clinic.DataBase.EntityFramework; /// -/// Entity Framework implementation of that manages +/// Entity Framework implementation of that manages /// doctor entities via . /// -public sealed class EfDoctorDataBase : IDoctorDataBase +public sealed class EfDoctorRepository : IDoctorRepository { private readonly ClinicDbContext _context; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The database context used for data access. - public EfDoctorDataBase(ClinicDbContext context) + public EfDoctorRepository(ClinicDbContext context) { _context = context; } diff --git a/Clinic/Clinic.DataBase/EntityFramework/EfPatientDataBase.cs b/Clinic/Clinic.DataBase/EntityFramework/EfPatientRepository.cs similarity index 86% rename from Clinic/Clinic.DataBase/EntityFramework/EfPatientDataBase.cs rename to Clinic/Clinic.DataBase/EntityFramework/EfPatientRepository.cs index 7e13364c0..086f183a8 100644 --- a/Clinic/Clinic.DataBase/EntityFramework/EfPatientDataBase.cs +++ b/Clinic/Clinic.DataBase/EntityFramework/EfPatientRepository.cs @@ -1,22 +1,22 @@ using Clinic.Models.Entities; -using Clinic.DataBase.Interfaces; +using Clinic.Application.Ports; using Microsoft.EntityFrameworkCore; namespace Clinic.DataBase.EntityFramework; /// -/// Entity Framework implementation of that manages +/// Entity Framework implementation of that manages /// patient entities using a . /// -public sealed class EfPatientDataBase : IPatientDataBase +public sealed class EfPatientRepository : IPatientRepository { private readonly ClinicDbContext _context; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The database context used for data access. - public EfPatientDataBase(ClinicDbContext context) + public EfPatientRepository(ClinicDbContext context) { _context = context; } diff --git a/Clinic/Clinic.DataBase/EntityFramework/EfSpecializationDataBase.cs b/Clinic/Clinic.DataBase/EntityFramework/EfSpecializationRepository.cs similarity index 91% rename from Clinic/Clinic.DataBase/EntityFramework/EfSpecializationDataBase.cs rename to Clinic/Clinic.DataBase/EntityFramework/EfSpecializationRepository.cs index 5c0bf6933..fa2b69d8d 100644 --- a/Clinic/Clinic.DataBase/EntityFramework/EfSpecializationDataBase.cs +++ b/Clinic/Clinic.DataBase/EntityFramework/EfSpecializationRepository.cs @@ -1,22 +1,22 @@ using Clinic.Models.Entities; -using Clinic.DataBase.Interfaces; +using Clinic.Application.Ports; using Microsoft.EntityFrameworkCore; namespace Clinic.DataBase.EntityFramework; /// -/// Entity Framework implementation of that manages +/// Entity Framework implementation of that manages /// specialization entities using . /// -public sealed class EfSpecializationDataBase : ISpecializationDataBase +public sealed class EfSpecializationRepository : ISpecializationRepository { private readonly ClinicDbContext _context; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The database context used for data access. - public EfSpecializationDataBase(ClinicDbContext context) + public EfSpecializationRepository(ClinicDbContext context) { _context = context; } diff --git a/Clinic/Clinic.Tests/Clinic.Tests.csproj b/Clinic/Clinic.Tests/Clinic.Tests.csproj index 6511e7f56..0083090a3 100644 --- a/Clinic/Clinic.Tests/Clinic.Tests.csproj +++ b/Clinic/Clinic.Tests/Clinic.Tests.csproj @@ -25,6 +25,7 @@ + diff --git a/Clinic/Clinic.sln b/Clinic/Clinic.sln old mode 100644 new mode 100755 index db7264881..6250a3de0 --- a/Clinic/Clinic.sln +++ b/Clinic/Clinic.sln @@ -15,6 +15,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Clinic.AppHost", "Clinic.Ap EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Clinic.ServiceDefaults", "Clinic.ServiceDefaults\Clinic.ServiceDefaults.csproj", "{472C1C25-CCA0-4AA2-910C-150DD406AE5F}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Clinic.Application", "Clinic.Application\Clinic.Application.csproj", "{43A97106-90F1-4FC0-B66E-1D6DA5950260}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -97,6 +99,18 @@ Global {472C1C25-CCA0-4AA2-910C-150DD406AE5F}.Release|x64.Build.0 = Release|Any CPU {472C1C25-CCA0-4AA2-910C-150DD406AE5F}.Release|x86.ActiveCfg = Release|Any CPU {472C1C25-CCA0-4AA2-910C-150DD406AE5F}.Release|x86.Build.0 = Release|Any CPU + {43A97106-90F1-4FC0-B66E-1D6DA5950260}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {43A97106-90F1-4FC0-B66E-1D6DA5950260}.Debug|Any CPU.Build.0 = Debug|Any CPU + {43A97106-90F1-4FC0-B66E-1D6DA5950260}.Debug|x64.ActiveCfg = Debug|Any CPU + {43A97106-90F1-4FC0-B66E-1D6DA5950260}.Debug|x64.Build.0 = Debug|Any CPU + {43A97106-90F1-4FC0-B66E-1D6DA5950260}.Debug|x86.ActiveCfg = Debug|Any CPU + {43A97106-90F1-4FC0-B66E-1D6DA5950260}.Debug|x86.Build.0 = Debug|Any CPU + {43A97106-90F1-4FC0-B66E-1D6DA5950260}.Release|Any CPU.ActiveCfg = Release|Any CPU + {43A97106-90F1-4FC0-B66E-1D6DA5950260}.Release|Any CPU.Build.0 = Release|Any CPU + {43A97106-90F1-4FC0-B66E-1D6DA5950260}.Release|x64.ActiveCfg = Release|Any CPU + {43A97106-90F1-4FC0-B66E-1D6DA5950260}.Release|x64.Build.0 = Release|Any CPU + {43A97106-90F1-4FC0-B66E-1D6DA5950260}.Release|x86.ActiveCfg = Release|Any CPU + {43A97106-90F1-4FC0-B66E-1D6DA5950260}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From 28438c073b26573b2b0b930a19ad0fbd8367b2ef Mon Sep 17 00:00:00 2001 From: maeosha Date: Mon, 22 Dec 2025 15:09:54 +0400 Subject: [PATCH 51/56] fix: changed the database from MySQL to PostgreSQL --- Clinic/Clinic.Api/Clinic.Api.csproj | 2 +- Clinic/Clinic.Api/Program.cs | 4 +- .../Clinic.Api/appsettings.Development.json | 2 +- Clinic/Clinic.Api/appsettings.json | 5 +- Clinic/Clinic.AppHost/AppHost.cs | 22 ++-- Clinic/Clinic.AppHost/Clinic.AppHost.csproj | 38 +++---- .../Clinic.Application.csproj | 2 +- Clinic/Clinic.DataBase/Clinic.DataBase.csproj | 4 +- Clinic/Clinic.DataBase/ClinicDbFactory.cs | 5 +- ... 20251222091310_InitialCreate.Designer.cs} | 76 ++++++------- ...ate.cs => 20251222091310_InitialCreate.cs} | 102 +++++++----------- ...cs => 20251222091340_SeedData.Designer.cs} | 76 ++++++------- ...SeedData.cs => 20251222091340_SeedData.cs} | 67 ++++++------ .../ClinicDbContextModelSnapshot.cs | 74 ++++++------- Clinic/Clinic.Models/Clinic.Models.csproj | 2 +- .../Clinic.ServiceDefaults.csproj | 2 +- Clinic/Clinic.Tests/Clinic.Tests.csproj | 2 +- 17 files changed, 231 insertions(+), 254 deletions(-) rename Clinic/Clinic.DataBase/Migrations/{20251219134315_InitialCreate.Designer.cs => 20251222091310_InitialCreate.Designer.cs} (72%) rename Clinic/Clinic.DataBase/Migrations/{20251219134315_InitialCreate.cs => 20251222091310_InitialCreate.cs} (52%) rename Clinic/Clinic.DataBase/Migrations/{20251219134336_SeedData.Designer.cs => 20251222091340_SeedData.Designer.cs} (72%) rename Clinic/Clinic.DataBase/Migrations/{20251219134336_SeedData.cs => 20251222091340_SeedData.cs} (84%) diff --git a/Clinic/Clinic.Api/Clinic.Api.csproj b/Clinic/Clinic.Api/Clinic.Api.csproj index 44fa91e56..78fb6a5bd 100644 --- a/Clinic/Clinic.Api/Clinic.Api.csproj +++ b/Clinic/Clinic.Api/Clinic.Api.csproj @@ -1,7 +1,7 @@ - net9.0 + net8.0 enable enable diff --git a/Clinic/Clinic.Api/Program.cs b/Clinic/Clinic.Api/Program.cs index 234f40746..8c1d5b54c 100644 --- a/Clinic/Clinic.Api/Program.cs +++ b/Clinic/Clinic.Api/Program.cs @@ -23,10 +23,8 @@ var connectionString = builder.Configuration.GetConnectionString("ClinicDb") ?? throw new InvalidOperationException("Connection string 'ClinicDb' is not configured."); -var serverVersion = new MySqlServerVersion(new Version(8, 0, 36)); - builder.Services.AddDbContext(options => - options.UseMySql(connectionString, serverVersion)); + options.UseNpgsql(connectionString)); builder.Services.AddScoped(); builder.Services.AddScoped(); diff --git a/Clinic/Clinic.Api/appsettings.Development.json b/Clinic/Clinic.Api/appsettings.Development.json index adfa46ac4..fddce0725 100644 --- a/Clinic/Clinic.Api/appsettings.Development.json +++ b/Clinic/Clinic.Api/appsettings.Development.json @@ -7,6 +7,6 @@ } }, "ConnectionStrings": { - "ClinicDb": "Server=localhost;Port=3306;Database=ClinicDb;User=clinic_app;Password=clinic123;TreatTinyAsBoolean=true;AllowPublicKeyRetrieval=True;SslMode=None" + "ClinicDb": "Host=localhost;Database=ClinicDb;Username=clinic_app;Password=clinic123;Include Error Detail=true" } } diff --git a/Clinic/Clinic.Api/appsettings.json b/Clinic/Clinic.Api/appsettings.json index 46bdb4522..4e606cea4 100644 --- a/Clinic/Clinic.Api/appsettings.json +++ b/Clinic/Clinic.Api/appsettings.json @@ -6,5 +6,8 @@ "Microsoft.EntityFrameworkCore": "Warning" } }, - "AllowedHosts": "*" + "AllowedHosts": "*", + "ConnectionStrings": { + "ClinicDb": "Host=localhost;Database=ClinicDb;Username=clinic_app;Password=clinic123" + } } diff --git a/Clinic/Clinic.AppHost/AppHost.cs b/Clinic/Clinic.AppHost/AppHost.cs index 57def4295..0e2f2bd50 100644 --- a/Clinic/Clinic.AppHost/AppHost.cs +++ b/Clinic/Clinic.AppHost/AppHost.cs @@ -1,13 +1,11 @@ -using Aspire.Hosting.MySql; - -var builder = DistributedApplication.CreateBuilder(args); - -var mysql = builder.AddMySql("mysql") - .AddDatabase("ClinicDb"); - -var api = builder.AddProject("clinic-api") - .WithReference(mysql) - .WaitFor(mysql) - .WithExternalHttpEndpoints(); - +var builder = DistributedApplication.CreateBuilder(args); + +var postgresql = builder.AddPostgres("postgres") + .AddDatabase("ClinicDb"); + +var api = builder.AddProject("clinic-api") + .WithReference(postgresql) + .WaitFor(postgresql) + .WithExternalHttpEndpoints(); + builder.Build().Run(); \ No newline at end of file diff --git a/Clinic/Clinic.AppHost/Clinic.AppHost.csproj b/Clinic/Clinic.AppHost/Clinic.AppHost.csproj index 3c7d3e65d..9f0d91d99 100644 --- a/Clinic/Clinic.AppHost/Clinic.AppHost.csproj +++ b/Clinic/Clinic.AppHost/Clinic.AppHost.csproj @@ -1,19 +1,19 @@ - - - - Exe - net9.0 - enable - enable - 1fced4c2-e45b-4353-84db-0376db0b16fc - - - - - - - - - - - + + + + Exe + net8.0 + enable + enable + 1fced4c2-e45b-4353-84db-0376db0b16fc + + + + + + + + + + + diff --git a/Clinic/Clinic.Application/Clinic.Application.csproj b/Clinic/Clinic.Application/Clinic.Application.csproj index 9078de21d..18a1b78a8 100644 --- a/Clinic/Clinic.Application/Clinic.Application.csproj +++ b/Clinic/Clinic.Application/Clinic.Application.csproj @@ -1,7 +1,7 @@ - net9.0 + net8.0 enable enable diff --git a/Clinic/Clinic.DataBase/Clinic.DataBase.csproj b/Clinic/Clinic.DataBase/Clinic.DataBase.csproj index 037dad52b..84ced4c32 100644 --- a/Clinic/Clinic.DataBase/Clinic.DataBase.csproj +++ b/Clinic/Clinic.DataBase/Clinic.DataBase.csproj @@ -1,7 +1,7 @@ - net9.0 + net8.0 enable enable @@ -17,7 +17,7 @@ - + diff --git a/Clinic/Clinic.DataBase/ClinicDbFactory.cs b/Clinic/Clinic.DataBase/ClinicDbFactory.cs index 9c8ce5e9d..d251bd5ac 100644 --- a/Clinic/Clinic.DataBase/ClinicDbFactory.cs +++ b/Clinic/Clinic.DataBase/ClinicDbFactory.cs @@ -31,9 +31,8 @@ public ClinicDbContext CreateDbContext(string[] args) } var optionsBuilder = new DbContextOptionsBuilder(); - optionsBuilder.UseMySql( - connectionString, - ServerVersion.AutoDetect(connectionString) + optionsBuilder.UseNpgsql( + connectionString ); return new ClinicDbContext(optionsBuilder.Options); diff --git a/Clinic/Clinic.DataBase/Migrations/20251219134315_InitialCreate.Designer.cs b/Clinic/Clinic.DataBase/Migrations/20251222091310_InitialCreate.Designer.cs similarity index 72% rename from Clinic/Clinic.DataBase/Migrations/20251219134315_InitialCreate.Designer.cs rename to Clinic/Clinic.DataBase/Migrations/20251222091310_InitialCreate.Designer.cs index 48140ae44..557d05685 100644 --- a/Clinic/Clinic.DataBase/Migrations/20251219134315_InitialCreate.Designer.cs +++ b/Clinic/Clinic.DataBase/Migrations/20251222091310_InitialCreate.Designer.cs @@ -3,16 +3,16 @@ using Clinic.DataBase; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; #nullable disable namespace Clinic.DataBase.Migrations { [DbContext(typeof(ClinicDbContext))] - [Migration("20251219134315_InitialCreate")] + [Migration("20251222091310_InitialCreate")] partial class InitialCreate { /// @@ -21,42 +21,42 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) #pragma warning disable 612, 618 modelBuilder .HasAnnotation("ProductVersion", "9.0.0") - .HasAnnotation("Relational:MaxIdentifierLength", 64); + .HasAnnotation("Relational:MaxIdentifierLength", 63); - MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder); + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); modelBuilder.Entity("Clinic.Models.Entities.Appointment", b => { b.Property("Id") .ValueGeneratedOnAdd() - .HasColumnType("int"); + .HasColumnType("integer"); - MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); b.Property("DateTime") - .HasColumnType("datetime(6)"); + .HasColumnType("timestamp with time zone"); b.Property("DoctorFullName") .IsRequired() .HasMaxLength(300) - .HasColumnType("varchar(300)"); + .HasColumnType("character varying(300)"); b.Property("DoctorId") - .HasColumnType("int"); + .HasColumnType("integer"); b.Property("IsReturnVisit") - .HasColumnType("tinyint(1)"); + .HasColumnType("boolean"); b.Property("PatientFullName") .IsRequired() .HasMaxLength(300) - .HasColumnType("varchar(300)"); + .HasColumnType("character varying(300)"); b.Property("PatientId") - .HasColumnType("int"); + .HasColumnType("integer"); b.Property("RoomNumber") - .HasColumnType("int"); + .HasColumnType("integer"); b.HasKey("Id"); @@ -71,42 +71,42 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) { b.Property("Id") .ValueGeneratedOnAdd() - .HasColumnType("int"); + .HasColumnType("integer"); - MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); b.Property("BirthDate") .HasColumnType("date"); b.Property("ExperienceYears") - .HasColumnType("int"); + .HasColumnType("integer"); b.Property("FirstName") .IsRequired() .HasMaxLength(100) - .HasColumnType("varchar(100)"); + .HasColumnType("character varying(100)"); b.Property("Gender") - .HasColumnType("int"); + .HasColumnType("integer"); b.Property("LastName") .IsRequired() .HasMaxLength(100) - .HasColumnType("varchar(100)"); + .HasColumnType("character varying(100)"); b.Property("PassportNumber") .IsRequired() .HasMaxLength(50) - .HasColumnType("varchar(50)"); + .HasColumnType("character varying(50)"); b.Property("Patronymic") .HasMaxLength(100) - .HasColumnType("varchar(100)"); + .HasColumnType("character varying(100)"); b.Property("PhoneNumber") .IsRequired() .HasMaxLength(20) - .HasColumnType("varchar(20)"); + .HasColumnType("character varying(20)"); b.HasKey("Id"); @@ -117,50 +117,50 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) { b.Property("Id") .ValueGeneratedOnAdd() - .HasColumnType("int"); + .HasColumnType("integer"); - MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); b.Property("Address") .IsRequired() .HasMaxLength(200) - .HasColumnType("varchar(200)"); + .HasColumnType("character varying(200)"); b.Property("BirthDate") .HasColumnType("date"); b.Property("BloodGroup") - .HasColumnType("int"); + .HasColumnType("integer"); b.Property("FirstName") .IsRequired() .HasMaxLength(100) - .HasColumnType("varchar(100)"); + .HasColumnType("character varying(100)"); b.Property("Gender") - .HasColumnType("int"); + .HasColumnType("integer"); b.Property("LastName") .IsRequired() .HasMaxLength(100) - .HasColumnType("varchar(100)"); + .HasColumnType("character varying(100)"); b.Property("PassportNumber") .IsRequired() .HasMaxLength(50) - .HasColumnType("varchar(50)"); + .HasColumnType("character varying(50)"); b.Property("Patronymic") .HasMaxLength(100) - .HasColumnType("varchar(100)"); + .HasColumnType("character varying(100)"); b.Property("PhoneNumber") .IsRequired() .HasMaxLength(20) - .HasColumnType("varchar(20)"); + .HasColumnType("character varying(20)"); b.Property("RhesusFactor") - .HasColumnType("int"); + .HasColumnType("integer"); b.HasKey("Id"); @@ -171,14 +171,14 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) { b.Property("Id") .ValueGeneratedOnAdd() - .HasColumnType("int"); + .HasColumnType("integer"); - MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); b.Property("Name") .IsRequired() .HasMaxLength(100) - .HasColumnType("varchar(100)"); + .HasColumnType("character varying(100)"); b.HasKey("Id"); @@ -188,10 +188,10 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) modelBuilder.Entity("DoctorSpecialization", b => { b.Property("DoctorId") - .HasColumnType("int"); + .HasColumnType("integer"); b.Property("SpecializationId") - .HasColumnType("int"); + .HasColumnType("integer"); b.HasKey("DoctorId", "SpecializationId"); diff --git a/Clinic/Clinic.DataBase/Migrations/20251219134315_InitialCreate.cs b/Clinic/Clinic.DataBase/Migrations/20251222091310_InitialCreate.cs similarity index 52% rename from Clinic/Clinic.DataBase/Migrations/20251219134315_InitialCreate.cs rename to Clinic/Clinic.DataBase/Migrations/20251222091310_InitialCreate.cs index 306f5434f..c14857f80 100644 --- a/Clinic/Clinic.DataBase/Migrations/20251219134315_InitialCreate.cs +++ b/Clinic/Clinic.DataBase/Migrations/20251222091310_InitialCreate.cs @@ -1,6 +1,6 @@ using System; -using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; #nullable disable @@ -12,94 +12,74 @@ public partial class InitialCreate : Migration /// protected override void Up(MigrationBuilder migrationBuilder) { - migrationBuilder.AlterDatabase() - .Annotation("MySql:CharSet", "utf8mb4"); - migrationBuilder.CreateTable( name: "Doctors", columns: table => new { - Id = table.Column(type: "int", nullable: false) - .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), - ExperienceYears = table.Column(type: "int", nullable: false), - PassportNumber = table.Column(type: "varchar(50)", maxLength: 50, nullable: false) - .Annotation("MySql:CharSet", "utf8mb4"), + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + ExperienceYears = table.Column(type: "integer", nullable: false), + PassportNumber = table.Column(type: "character varying(50)", maxLength: 50, nullable: false), BirthDate = table.Column(type: "date", nullable: false), - LastName = table.Column(type: "varchar(100)", maxLength: 100, nullable: false) - .Annotation("MySql:CharSet", "utf8mb4"), - FirstName = table.Column(type: "varchar(100)", maxLength: 100, nullable: false) - .Annotation("MySql:CharSet", "utf8mb4"), - Patronymic = table.Column(type: "varchar(100)", maxLength: 100, nullable: true) - .Annotation("MySql:CharSet", "utf8mb4"), - PhoneNumber = table.Column(type: "varchar(20)", maxLength: 20, nullable: false) - .Annotation("MySql:CharSet", "utf8mb4"), - Gender = table.Column(type: "int", nullable: false) + LastName = table.Column(type: "character varying(100)", maxLength: 100, nullable: false), + FirstName = table.Column(type: "character varying(100)", maxLength: 100, nullable: false), + Patronymic = table.Column(type: "character varying(100)", maxLength: 100, nullable: true), + PhoneNumber = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), + Gender = table.Column(type: "integer", nullable: false) }, constraints: table => { table.PrimaryKey("PK_Doctors", x => x.Id); - }) - .Annotation("MySql:CharSet", "utf8mb4"); + }); migrationBuilder.CreateTable( name: "Patients", columns: table => new { - Id = table.Column(type: "int", nullable: false) - .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), - Address = table.Column(type: "varchar(200)", maxLength: 200, nullable: false) - .Annotation("MySql:CharSet", "utf8mb4"), - BloodGroup = table.Column(type: "int", nullable: false), - RhesusFactor = table.Column(type: "int", nullable: false), - PassportNumber = table.Column(type: "varchar(50)", maxLength: 50, nullable: false) - .Annotation("MySql:CharSet", "utf8mb4"), + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + Address = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), + BloodGroup = table.Column(type: "integer", nullable: false), + RhesusFactor = table.Column(type: "integer", nullable: false), + PassportNumber = table.Column(type: "character varying(50)", maxLength: 50, nullable: false), BirthDate = table.Column(type: "date", nullable: false), - LastName = table.Column(type: "varchar(100)", maxLength: 100, nullable: false) - .Annotation("MySql:CharSet", "utf8mb4"), - FirstName = table.Column(type: "varchar(100)", maxLength: 100, nullable: false) - .Annotation("MySql:CharSet", "utf8mb4"), - Patronymic = table.Column(type: "varchar(100)", maxLength: 100, nullable: true) - .Annotation("MySql:CharSet", "utf8mb4"), - PhoneNumber = table.Column(type: "varchar(20)", maxLength: 20, nullable: false) - .Annotation("MySql:CharSet", "utf8mb4"), - Gender = table.Column(type: "int", nullable: false) + LastName = table.Column(type: "character varying(100)", maxLength: 100, nullable: false), + FirstName = table.Column(type: "character varying(100)", maxLength: 100, nullable: false), + Patronymic = table.Column(type: "character varying(100)", maxLength: 100, nullable: true), + PhoneNumber = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), + Gender = table.Column(type: "integer", nullable: false) }, constraints: table => { table.PrimaryKey("PK_Patients", x => x.Id); - }) - .Annotation("MySql:CharSet", "utf8mb4"); + }); migrationBuilder.CreateTable( name: "Specializations", columns: table => new { - Id = table.Column(type: "int", nullable: false) - .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), - Name = table.Column(type: "varchar(100)", maxLength: 100, nullable: false) - .Annotation("MySql:CharSet", "utf8mb4") + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + Name = table.Column(type: "character varying(100)", maxLength: 100, nullable: false) }, constraints: table => { table.PrimaryKey("PK_Specializations", x => x.Id); - }) - .Annotation("MySql:CharSet", "utf8mb4"); + }); migrationBuilder.CreateTable( name: "Appointments", columns: table => new { - Id = table.Column(type: "int", nullable: false) - .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), - PatientId = table.Column(type: "int", nullable: false), - PatientFullName = table.Column(type: "varchar(300)", maxLength: 300, nullable: false) - .Annotation("MySql:CharSet", "utf8mb4"), - DoctorId = table.Column(type: "int", nullable: false), - DoctorFullName = table.Column(type: "varchar(300)", maxLength: 300, nullable: false) - .Annotation("MySql:CharSet", "utf8mb4"), - DateTime = table.Column(type: "datetime(6)", nullable: false), - RoomNumber = table.Column(type: "int", nullable: false), - IsReturnVisit = table.Column(type: "tinyint(1)", nullable: false) + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + PatientId = table.Column(type: "integer", nullable: false), + PatientFullName = table.Column(type: "character varying(300)", maxLength: 300, nullable: false), + DoctorId = table.Column(type: "integer", nullable: false), + DoctorFullName = table.Column(type: "character varying(300)", maxLength: 300, nullable: false), + DateTime = table.Column(type: "timestamp with time zone", nullable: false), + RoomNumber = table.Column(type: "integer", nullable: false), + IsReturnVisit = table.Column(type: "boolean", nullable: false) }, constraints: table => { @@ -116,15 +96,14 @@ protected override void Up(MigrationBuilder migrationBuilder) principalTable: "Patients", principalColumn: "Id", onDelete: ReferentialAction.Restrict); - }) - .Annotation("MySql:CharSet", "utf8mb4"); + }); migrationBuilder.CreateTable( name: "DoctorSpecialization", columns: table => new { - DoctorId = table.Column(type: "int", nullable: false), - SpecializationId = table.Column(type: "int", nullable: false) + DoctorId = table.Column(type: "integer", nullable: false), + SpecializationId = table.Column(type: "integer", nullable: false) }, constraints: table => { @@ -141,8 +120,7 @@ protected override void Up(MigrationBuilder migrationBuilder) principalTable: "Specializations", principalColumn: "Id", onDelete: ReferentialAction.Cascade); - }) - .Annotation("MySql:CharSet", "utf8mb4"); + }); migrationBuilder.CreateIndex( name: "IX_Appointments_DoctorId", diff --git a/Clinic/Clinic.DataBase/Migrations/20251219134336_SeedData.Designer.cs b/Clinic/Clinic.DataBase/Migrations/20251222091340_SeedData.Designer.cs similarity index 72% rename from Clinic/Clinic.DataBase/Migrations/20251219134336_SeedData.Designer.cs rename to Clinic/Clinic.DataBase/Migrations/20251222091340_SeedData.Designer.cs index 58d8e2473..6181cd4b4 100644 --- a/Clinic/Clinic.DataBase/Migrations/20251219134336_SeedData.Designer.cs +++ b/Clinic/Clinic.DataBase/Migrations/20251222091340_SeedData.Designer.cs @@ -3,16 +3,16 @@ using Clinic.DataBase; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; #nullable disable namespace Clinic.DataBase.Migrations { [DbContext(typeof(ClinicDbContext))] - [Migration("20251219134336_SeedData")] + [Migration("20251222091340_SeedData")] partial class SeedData { /// @@ -21,42 +21,42 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) #pragma warning disable 612, 618 modelBuilder .HasAnnotation("ProductVersion", "9.0.0") - .HasAnnotation("Relational:MaxIdentifierLength", 64); + .HasAnnotation("Relational:MaxIdentifierLength", 63); - MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder); + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); modelBuilder.Entity("Clinic.Models.Entities.Appointment", b => { b.Property("Id") .ValueGeneratedOnAdd() - .HasColumnType("int"); + .HasColumnType("integer"); - MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); b.Property("DateTime") - .HasColumnType("datetime(6)"); + .HasColumnType("timestamp with time zone"); b.Property("DoctorFullName") .IsRequired() .HasMaxLength(300) - .HasColumnType("varchar(300)"); + .HasColumnType("character varying(300)"); b.Property("DoctorId") - .HasColumnType("int"); + .HasColumnType("integer"); b.Property("IsReturnVisit") - .HasColumnType("tinyint(1)"); + .HasColumnType("boolean"); b.Property("PatientFullName") .IsRequired() .HasMaxLength(300) - .HasColumnType("varchar(300)"); + .HasColumnType("character varying(300)"); b.Property("PatientId") - .HasColumnType("int"); + .HasColumnType("integer"); b.Property("RoomNumber") - .HasColumnType("int"); + .HasColumnType("integer"); b.HasKey("Id"); @@ -71,42 +71,42 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) { b.Property("Id") .ValueGeneratedOnAdd() - .HasColumnType("int"); + .HasColumnType("integer"); - MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); b.Property("BirthDate") .HasColumnType("date"); b.Property("ExperienceYears") - .HasColumnType("int"); + .HasColumnType("integer"); b.Property("FirstName") .IsRequired() .HasMaxLength(100) - .HasColumnType("varchar(100)"); + .HasColumnType("character varying(100)"); b.Property("Gender") - .HasColumnType("int"); + .HasColumnType("integer"); b.Property("LastName") .IsRequired() .HasMaxLength(100) - .HasColumnType("varchar(100)"); + .HasColumnType("character varying(100)"); b.Property("PassportNumber") .IsRequired() .HasMaxLength(50) - .HasColumnType("varchar(50)"); + .HasColumnType("character varying(50)"); b.Property("Patronymic") .HasMaxLength(100) - .HasColumnType("varchar(100)"); + .HasColumnType("character varying(100)"); b.Property("PhoneNumber") .IsRequired() .HasMaxLength(20) - .HasColumnType("varchar(20)"); + .HasColumnType("character varying(20)"); b.HasKey("Id"); @@ -117,50 +117,50 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) { b.Property("Id") .ValueGeneratedOnAdd() - .HasColumnType("int"); + .HasColumnType("integer"); - MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); b.Property("Address") .IsRequired() .HasMaxLength(200) - .HasColumnType("varchar(200)"); + .HasColumnType("character varying(200)"); b.Property("BirthDate") .HasColumnType("date"); b.Property("BloodGroup") - .HasColumnType("int"); + .HasColumnType("integer"); b.Property("FirstName") .IsRequired() .HasMaxLength(100) - .HasColumnType("varchar(100)"); + .HasColumnType("character varying(100)"); b.Property("Gender") - .HasColumnType("int"); + .HasColumnType("integer"); b.Property("LastName") .IsRequired() .HasMaxLength(100) - .HasColumnType("varchar(100)"); + .HasColumnType("character varying(100)"); b.Property("PassportNumber") .IsRequired() .HasMaxLength(50) - .HasColumnType("varchar(50)"); + .HasColumnType("character varying(50)"); b.Property("Patronymic") .HasMaxLength(100) - .HasColumnType("varchar(100)"); + .HasColumnType("character varying(100)"); b.Property("PhoneNumber") .IsRequired() .HasMaxLength(20) - .HasColumnType("varchar(20)"); + .HasColumnType("character varying(20)"); b.Property("RhesusFactor") - .HasColumnType("int"); + .HasColumnType("integer"); b.HasKey("Id"); @@ -171,14 +171,14 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) { b.Property("Id") .ValueGeneratedOnAdd() - .HasColumnType("int"); + .HasColumnType("integer"); - MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); b.Property("Name") .IsRequired() .HasMaxLength(100) - .HasColumnType("varchar(100)"); + .HasColumnType("character varying(100)"); b.HasKey("Id"); @@ -188,10 +188,10 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) modelBuilder.Entity("DoctorSpecialization", b => { b.Property("DoctorId") - .HasColumnType("int"); + .HasColumnType("integer"); b.Property("SpecializationId") - .HasColumnType("int"); + .HasColumnType("integer"); b.HasKey("DoctorId", "SpecializationId"); diff --git a/Clinic/Clinic.DataBase/Migrations/20251219134336_SeedData.cs b/Clinic/Clinic.DataBase/Migrations/20251222091340_SeedData.cs similarity index 84% rename from Clinic/Clinic.DataBase/Migrations/20251219134336_SeedData.cs rename to Clinic/Clinic.DataBase/Migrations/20251222091340_SeedData.cs index 4f10aa0fb..972142329 100644 --- a/Clinic/Clinic.DataBase/Migrations/20251219134336_SeedData.cs +++ b/Clinic/Clinic.DataBase/Migrations/20251222091340_SeedData.cs @@ -2,8 +2,8 @@ #nullable disable -namespace Clinic.DataBase.Migrations; - +namespace Clinic.DataBase.Migrations +{ /// public partial class SeedData : Migration { @@ -52,22 +52,22 @@ protected override void Up(MigrationBuilder migrationBuilder) columns: new[] { "DoctorId", "SpecializationId" }, values: new object[,] { - { 1, 1 }, - { 1, 3 }, - { 2, 2 }, - { 3, 4 }, - { 3, 1 }, - { 4, 5 }, - { 5, 6 }, - { 5, 7 }, - { 6, 8 }, - { 7, 2 }, - { 7, 10 }, - { 8, 9 }, - { 9, 3 }, - { 9, 1 }, - { 10, 5 }, - { 10, 6 } + { 1, 1 }, + { 1, 3 }, + { 2, 2 }, + { 3, 4 }, + { 3, 1 }, + { 4, 5 }, + { 5, 6 }, + { 5, 7 }, + { 6, 8 }, + { 7, 2 }, + { 7, 10 }, + { 8, 9 }, + { 9, 3 }, + { 9, 1 }, + { 10, 5 }, + { 10, 6 } }); // Seed Patients (10 patients) @@ -94,21 +94,21 @@ protected override void Up(MigrationBuilder migrationBuilder) columns: new[] { "Id", "PatientId", "PatientFullName", "DoctorId", "DoctorFullName", "DateTime", "RoomNumber", "IsReturnVisit" }, values: new object[,] { - { 1, 1, "Смирнов Алексей Игоревич", 1, "Иванов Иван Иванович", new DateTime(2025, 12, 10, 10, 0, 0), 101, false }, - { 2, 2, "Волкова Анна Дмитриевна", 2, "Петрова Мария Сергеевна", new DateTime(2025, 12, 10, 11, 30, 0), 205, false }, - { 3, 3, "Лебедев Сергей Викторович", 3, "Сидоров Александр Петрович", new DateTime(2025, 8, 21, 9, 0, 0), 302, true }, - { 4, 4, "Новикова Ольга Андреевна", 4, "Козлова Елена Владимировна", new DateTime(2025, 12, 21, 14, 0, 0), 401, false }, - { 5, 5, "Федоров Максим Сергеевич", 5, "Морозов Дмитрий Александрович", new DateTime(2025, 12, 22, 10, 30, 0), 503, false }, - { 6, 6, "Кузнецова Екатерина Александровна", 6, "Волкова Анна Дмитриевна", new DateTime(2025, 12, 22, 15, 0, 0), 201, false }, - { 7, 7, "Попов Андрей Николаевич", 7, "Лебедев Сергей Викторович", new DateTime(2025, 5, 13, 9, 30, 0), 305, true }, - { 8, 8, "Соколова Мария Владимировна", 8, "Новикова Ольга Андреевна", new DateTime(2025, 6, 23, 11, 0, 0), 402, false }, - { 9, 9, "Михайлов Игорь Борисович", 9, "Федоров Максим Сергеевич", new DateTime(2025, 12, 24, 10, 0, 0), 104, false }, - { 10, 10, "Павлова Наталья Сергеевна", 10, "Смирнова Татьяна Игоревна", new DateTime(2025, 12, 24, 13, 30, 0), 501, false }, - { 11, 1, "Смирнов Алексей Игоревич", 2, "Петрова Мария Сергеевна", new DateTime(2025, 3, 25, 10, 0, 0), 101, true }, - { 12, 3, "Лебедев Сергей Викторович", 5, "Морозов Дмитрий Александрович", new DateTime(2025, 4, 25, 14, 0, 0), 302, true }, - { 13, 5, "Федоров Максим Сергеевич", 5, "Морозов Дмитрий Александрович", new DateTime(2025, 11, 26, 9, 0, 0), 503, true }, - { 14, 7, "Попов Андрей Николаевич", 7, "Лебедев Сергей Викторович", new DateTime(2025, 11, 26, 11, 30, 0), 305, false }, - { 15, 9, "Михайлов Игорь Борисович", 9, "Федоров Максим Сергеевич", new DateTime(2025, 11, 27, 10, 30, 0), 104, true } + { 1, 1, "Смирнов Алексей Игоревич", 1, "Иванов Иван Иванович", new DateTime(2025, 12, 10, 10, 0, 0, 0, DateTimeKind.Utc), 101, false }, + { 2, 2, "Волкова Анна Дмитриевна", 2, "Петрова Мария Сергеевна", new DateTime(2025, 12, 10, 11, 30, 0, 0, DateTimeKind.Utc), 205, false }, + { 3, 3, "Лебедев Сергей Викторович", 3, "Сидоров Александр Петрович", new DateTime(2025, 8, 21, 9, 0, 0, 0, DateTimeKind.Utc), 302, true }, + { 4, 4, "Новикова Ольга Андреевна", 4, "Козлова Елена Владимировна", new DateTime(2025, 12, 21, 14, 0, 0, 0, DateTimeKind.Utc), 401, false }, + { 5, 5, "Федоров Максим Сергеевич", 5, "Морозов Дмитрий Александрович", new DateTime(2025, 12, 22, 10, 30, 0, 0, DateTimeKind.Utc), 503, false }, + { 6, 6, "Кузнецова Екатерина Александровна", 6, "Волкова Анна Дмитриевна", new DateTime(2025, 12, 22, 15, 0, 0, 0, DateTimeKind.Utc), 201, false }, + { 7, 7, "Попов Андрей Николаевич", 7, "Лебедев Сергей Викторович", new DateTime(2025, 5, 13, 9, 30, 0, 0, DateTimeKind.Utc), 305, true }, + { 8, 8, "Соколова Мария Владимировна", 8, "Новикова Ольга Андреевна", new DateTime(2025, 6, 23, 11, 0, 0, 0, DateTimeKind.Utc), 402, false }, + { 9, 9, "Михайлов Игорь Борисович", 9, "Федоров Максим Сергеевич", new DateTime(2025, 12, 24, 10, 0, 0, 0, DateTimeKind.Utc), 104, false }, + { 10, 10, "Павлова Наталья Сергеевна", 10, "Смирнова Татьяна Игоревна", new DateTime(2025, 12, 24, 13, 30, 0, 0, DateTimeKind.Utc), 501, false }, + { 11, 1, "Смирнов Алексей Игоревич", 2, "Петрова Мария Сергеевна", new DateTime(2025, 3, 25, 10, 0, 0, 0, DateTimeKind.Utc), 101, true }, + { 12, 3, "Лебедев Сергей Викторович", 5, "Морозов Дмитрий Александрович", new DateTime(2025, 4, 25, 14, 0, 0, 0, DateTimeKind.Utc), 302, true }, + { 13, 5, "Федоров Максим Сергеевич", 5, "Морозов Дмитрий Александрович", new DateTime(2025, 11, 26, 9, 0, 0, 0, DateTimeKind.Utc), 503, true }, + { 14, 7, "Попов Андрей Николаевич", 7, "Лебедев Сергей Викторович", new DateTime(2025, 11, 26, 11, 30, 0, 0, DateTimeKind.Utc), 305, false }, + { 15, 9, "Михайлов Игорь Борисович", 9, "Федоров Максим Сергеевич", new DateTime(2025, 11, 27, 10, 30, 0, 0, DateTimeKind.Utc), 104, true } }); } @@ -158,5 +158,6 @@ protected override void Down(MigrationBuilder migrationBuilder) table: "Specializations", keyColumn: "Id", keyValues: new object[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }); + } } } diff --git a/Clinic/Clinic.DataBase/Migrations/ClinicDbContextModelSnapshot.cs b/Clinic/Clinic.DataBase/Migrations/ClinicDbContextModelSnapshot.cs index d62f0d35e..603e7633d 100644 --- a/Clinic/Clinic.DataBase/Migrations/ClinicDbContextModelSnapshot.cs +++ b/Clinic/Clinic.DataBase/Migrations/ClinicDbContextModelSnapshot.cs @@ -3,8 +3,8 @@ using Clinic.DataBase; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; #nullable disable @@ -18,42 +18,42 @@ protected override void BuildModel(ModelBuilder modelBuilder) #pragma warning disable 612, 618 modelBuilder .HasAnnotation("ProductVersion", "9.0.0") - .HasAnnotation("Relational:MaxIdentifierLength", 64); + .HasAnnotation("Relational:MaxIdentifierLength", 63); - MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder); + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); modelBuilder.Entity("Clinic.Models.Entities.Appointment", b => { b.Property("Id") .ValueGeneratedOnAdd() - .HasColumnType("int"); + .HasColumnType("integer"); - MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); b.Property("DateTime") - .HasColumnType("datetime(6)"); + .HasColumnType("timestamp with time zone"); b.Property("DoctorFullName") .IsRequired() .HasMaxLength(300) - .HasColumnType("varchar(300)"); + .HasColumnType("character varying(300)"); b.Property("DoctorId") - .HasColumnType("int"); + .HasColumnType("integer"); b.Property("IsReturnVisit") - .HasColumnType("tinyint(1)"); + .HasColumnType("boolean"); b.Property("PatientFullName") .IsRequired() .HasMaxLength(300) - .HasColumnType("varchar(300)"); + .HasColumnType("character varying(300)"); b.Property("PatientId") - .HasColumnType("int"); + .HasColumnType("integer"); b.Property("RoomNumber") - .HasColumnType("int"); + .HasColumnType("integer"); b.HasKey("Id"); @@ -68,42 +68,42 @@ protected override void BuildModel(ModelBuilder modelBuilder) { b.Property("Id") .ValueGeneratedOnAdd() - .HasColumnType("int"); + .HasColumnType("integer"); - MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); b.Property("BirthDate") .HasColumnType("date"); b.Property("ExperienceYears") - .HasColumnType("int"); + .HasColumnType("integer"); b.Property("FirstName") .IsRequired() .HasMaxLength(100) - .HasColumnType("varchar(100)"); + .HasColumnType("character varying(100)"); b.Property("Gender") - .HasColumnType("int"); + .HasColumnType("integer"); b.Property("LastName") .IsRequired() .HasMaxLength(100) - .HasColumnType("varchar(100)"); + .HasColumnType("character varying(100)"); b.Property("PassportNumber") .IsRequired() .HasMaxLength(50) - .HasColumnType("varchar(50)"); + .HasColumnType("character varying(50)"); b.Property("Patronymic") .HasMaxLength(100) - .HasColumnType("varchar(100)"); + .HasColumnType("character varying(100)"); b.Property("PhoneNumber") .IsRequired() .HasMaxLength(20) - .HasColumnType("varchar(20)"); + .HasColumnType("character varying(20)"); b.HasKey("Id"); @@ -114,50 +114,50 @@ protected override void BuildModel(ModelBuilder modelBuilder) { b.Property("Id") .ValueGeneratedOnAdd() - .HasColumnType("int"); + .HasColumnType("integer"); - MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); b.Property("Address") .IsRequired() .HasMaxLength(200) - .HasColumnType("varchar(200)"); + .HasColumnType("character varying(200)"); b.Property("BirthDate") .HasColumnType("date"); b.Property("BloodGroup") - .HasColumnType("int"); + .HasColumnType("integer"); b.Property("FirstName") .IsRequired() .HasMaxLength(100) - .HasColumnType("varchar(100)"); + .HasColumnType("character varying(100)"); b.Property("Gender") - .HasColumnType("int"); + .HasColumnType("integer"); b.Property("LastName") .IsRequired() .HasMaxLength(100) - .HasColumnType("varchar(100)"); + .HasColumnType("character varying(100)"); b.Property("PassportNumber") .IsRequired() .HasMaxLength(50) - .HasColumnType("varchar(50)"); + .HasColumnType("character varying(50)"); b.Property("Patronymic") .HasMaxLength(100) - .HasColumnType("varchar(100)"); + .HasColumnType("character varying(100)"); b.Property("PhoneNumber") .IsRequired() .HasMaxLength(20) - .HasColumnType("varchar(20)"); + .HasColumnType("character varying(20)"); b.Property("RhesusFactor") - .HasColumnType("int"); + .HasColumnType("integer"); b.HasKey("Id"); @@ -168,14 +168,14 @@ protected override void BuildModel(ModelBuilder modelBuilder) { b.Property("Id") .ValueGeneratedOnAdd() - .HasColumnType("int"); + .HasColumnType("integer"); - MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); b.Property("Name") .IsRequired() .HasMaxLength(100) - .HasColumnType("varchar(100)"); + .HasColumnType("character varying(100)"); b.HasKey("Id"); @@ -185,10 +185,10 @@ protected override void BuildModel(ModelBuilder modelBuilder) modelBuilder.Entity("DoctorSpecialization", b => { b.Property("DoctorId") - .HasColumnType("int"); + .HasColumnType("integer"); b.Property("SpecializationId") - .HasColumnType("int"); + .HasColumnType("integer"); b.HasKey("DoctorId", "SpecializationId"); diff --git a/Clinic/Clinic.Models/Clinic.Models.csproj b/Clinic/Clinic.Models/Clinic.Models.csproj index 1cd3df193..444d48ffe 100644 --- a/Clinic/Clinic.Models/Clinic.Models.csproj +++ b/Clinic/Clinic.Models/Clinic.Models.csproj @@ -1,7 +1,7 @@ - net9.0 + net8.0 enable enable diff --git a/Clinic/Clinic.ServiceDefaults/Clinic.ServiceDefaults.csproj b/Clinic/Clinic.ServiceDefaults/Clinic.ServiceDefaults.csproj index c93b2fd96..4084d8667 100644 --- a/Clinic/Clinic.ServiceDefaults/Clinic.ServiceDefaults.csproj +++ b/Clinic/Clinic.ServiceDefaults/Clinic.ServiceDefaults.csproj @@ -1,7 +1,7 @@ - net9.0 + net8.0 enable enable true diff --git a/Clinic/Clinic.Tests/Clinic.Tests.csproj b/Clinic/Clinic.Tests/Clinic.Tests.csproj index 0083090a3..8650b5bd6 100644 --- a/Clinic/Clinic.Tests/Clinic.Tests.csproj +++ b/Clinic/Clinic.Tests/Clinic.Tests.csproj @@ -1,7 +1,7 @@  - net9.0 + net8.0 enable enable false From c81f41cefa246de48ec5984a434ec12394c8e482 Mon Sep 17 00:00:00 2001 From: maeosha Date: Tue, 23 Dec 2025 22:51:39 +0300 Subject: [PATCH 52/56] feat: add in-memory repositories for appointments, doctors, patients, and specializations --- Clinic/Clinic.InMemory/Clinic.InMemory.csproj | 14 ++++ .../InMemoryAppointmentRepository.cs | 80 +++++++++++++++++++ .../InMemoryDoctorRepository.cs | 63 +++++++++++++++ .../InMemoryPatientRepository.cs | 56 +++++++++++++ .../InMemorySpecializationRepository.cs | 71 ++++++++++++++++ Clinic/Clinic.sln | 14 ++++ 6 files changed, 298 insertions(+) create mode 100644 Clinic/Clinic.InMemory/Clinic.InMemory.csproj create mode 100644 Clinic/Clinic.InMemory/InMemoryAppointmentRepository.cs create mode 100644 Clinic/Clinic.InMemory/InMemoryDoctorRepository.cs create mode 100644 Clinic/Clinic.InMemory/InMemoryPatientRepository.cs create mode 100644 Clinic/Clinic.InMemory/InMemorySpecializationRepository.cs diff --git a/Clinic/Clinic.InMemory/Clinic.InMemory.csproj b/Clinic/Clinic.InMemory/Clinic.InMemory.csproj new file mode 100644 index 000000000..c926e8752 --- /dev/null +++ b/Clinic/Clinic.InMemory/Clinic.InMemory.csproj @@ -0,0 +1,14 @@ + + + + net8.0 + enable + enable + + + + + + + + diff --git a/Clinic/Clinic.InMemory/InMemoryAppointmentRepository.cs b/Clinic/Clinic.InMemory/InMemoryAppointmentRepository.cs new file mode 100644 index 000000000..09d6cd15d --- /dev/null +++ b/Clinic/Clinic.InMemory/InMemoryAppointmentRepository.cs @@ -0,0 +1,80 @@ +using Clinic.Models.Entities; + +namespace Clinic.InMemory; +public sealed class InMemoryAppointmentRepository +{ + /// + /// In-memory storage for appointments. + /// + private readonly Dictionary _appointments = new(); + + /// + /// Retrieves an appointment by its ID. + /// + /// The ID of the appointment to retrieve. + /// The appointment with the specified ID, or null if not found. + public Appointment? GetAppointment(int Id) => _appointments.GetValueOrDefault(Id); + + /// + /// Retrieves all appointments from the in-memory storage. + /// + /// A read-only collection of all appointments. + public IReadOnlyCollection GetAllAppointments() => _appointments.Values; + + /// + /// Retrieves all appointments associated with a specific doctor. + /// + /// The ID of the doctor. + /// A read-only collection of appointments for the specified doctor. + public IReadOnlyCollection GetAppointmentsByDoctor(int Id) => + _appointments.Values.Where(a => a.DoctorId == Id).ToList(); + + /// + /// Retrieves all appointments associated with a specific patient. + /// + /// The ID of the patient. + /// A read-only collection of appointments for the specified patient. + public IReadOnlyCollection GetAppointmentsByPatient(int Id) => + _appointments.Values.Where(a => a.PatientId == Id).ToList(); + + /// + /// Adds a new appointment to the in-memory storage. + /// + /// The appointment to add. + /// True if the appointment was successfully added, false if it already exists. + public bool AddAppointment(Appointment appointment){ + if (_appointments.ContainsKey(appointment.Id)){ + return false; + } + + _appointments[appointment.Id] = appointment; + return true; + } + + /// + /// Updates an existing appointment in the in-memory storage. + /// + /// The appointment with updated information. + /// True if the appointment was successfully updated, false if it doesn't exist. + public bool UpdateAppointment(Appointment appointment){ + if (!_appointments.ContainsKey(appointment.Id)){ + return false; + } + + _appointments[appointment.Id] = appointment; + return true; + } + + /// + /// Removes an appointment from the in-memory storage. + /// + /// The ID of the appointment to remove. + /// True if the appointment was successfully removed, false if it doesn't exist. + public bool RemoveAppointment(int Id) => _appointments.Remove(Id); + + /// + /// Gets the count of appointments in the in-memory storage. + /// + /// The number of appointments. + public int AppointmentCount() => _appointments.Count(); +} \ No newline at end of file diff --git a/Clinic/Clinic.InMemory/InMemoryDoctorRepository.cs b/Clinic/Clinic.InMemory/InMemoryDoctorRepository.cs new file mode 100644 index 000000000..d478915e8 --- /dev/null +++ b/Clinic/Clinic.InMemory/InMemoryDoctorRepository.cs @@ -0,0 +1,63 @@ +using Clinic.Models.Entities; + +namespace Clinic.InMemory; +public sealed class InMemoryDoctorRepository +{ + /// + /// In-memory storage for doctors. + /// + private readonly Dictionary _doctors = new(); + + /// + /// Retrieves a doctor by their ID. + /// + /// The ID of the doctor to retrieve. + /// The doctor with the specified ID, or null if not found. + public Doctor? GetDoctor(int Id) => _doctors.GetValueOrDefault(Id); + + /// + /// Retrieves all doctors from the in-memory storage. + /// + /// A read-only collection of all doctors. + public IReadOnlyCollection GetAllDoctors() => _doctors.Values; + + /// + /// Adds a new doctor to the in-memory storage. + /// + /// The doctor to add. + /// True if the doctor was successfully added, false if it already exists. + public bool AddDoctor(Doctor doctor){ + if (_doctors.ContainsKey(doctor.Id)){ + return false; + } + _doctors[doctor.Id] = doctor; + return true; + } + + /// + /// Updates an existing doctor in the in-memory storage. + /// + /// The doctor with updated information. + /// True if the doctor was successfully updated, false if it doesn't exist. + public bool UpdateDoctor(Doctor doctor){ + if (!_doctors.ContainsKey(doctor.Id)){ + return false; + } + _doctors[doctor.Id] = doctor; + return true; + } + + /// + /// Removes a doctor from the in-memory storage. + /// + /// The ID of the doctor to remove. + /// True if the doctor was successfully removed, false if it doesn't exist. + public bool RemoveDoctor(int Id) => _doctors.Remove(Id); + + /// + /// Gets the count of doctors in the in-memory storage. + /// + /// The number of doctors. + public int DoctorCount() => _doctors.Count(); +} + diff --git a/Clinic/Clinic.InMemory/InMemoryPatientRepository.cs b/Clinic/Clinic.InMemory/InMemoryPatientRepository.cs new file mode 100644 index 000000000..1c568ad40 --- /dev/null +++ b/Clinic/Clinic.InMemory/InMemoryPatientRepository.cs @@ -0,0 +1,56 @@ +using Clinic.Models.Entities; + +namespace Clinic.InMemory; +public sealed class InMemoryPatientRepository +{ + /// + /// In-memory storage for patients. + /// + private readonly Dictionary _patients = new(); + + /// + /// Retrieves all patients from the in-memory storage. + /// + /// A read-only collection of all patients. + public IReadOnlyCollection GetAllPatients() => _patients.Values; + + /// + /// Adds a new patient to the in-memory storage. + /// + /// The patient to add. + /// True if the patient was successfully added, false if it already exists. + public bool AddPatient(Patient patient){ + if (_patients.ContainsKey(patient.Id)){ + return false; + } + + _patients[patient.Id] = patient; + return true; + } + + /// + /// Updates an existing patient in the in-memory storage. + /// + /// The patient with updated information. + /// True if the patient was successfully updated, false if it doesn't exist. + public bool UpdatePatient(Patient patient){ + if (!_patients.ContainsKey(patient.Id)){ + return false; + } + _patients[patient.Id] = patient; + return true; + } + + /// + /// Removes a patient from the in-memory storage. + /// + /// The ID of the patient to remove. + /// True if the patient was successfully removed, false if it doesn't exist. + public bool RemovePatient(int Id) => _patients.Remove(Id); + + /// + /// Gets the count of patients in the in-memory storage. + /// + /// The number of patients. + public int PatientCount() => _patients.Count; +} \ No newline at end of file diff --git a/Clinic/Clinic.InMemory/InMemorySpecializationRepository.cs b/Clinic/Clinic.InMemory/InMemorySpecializationRepository.cs new file mode 100644 index 000000000..837a030e4 --- /dev/null +++ b/Clinic/Clinic.InMemory/InMemorySpecializationRepository.cs @@ -0,0 +1,71 @@ +using Clinic.Models.Entities; + +namespace Clinic.InMemory; +public sealed class InMemorySpecializationRepository +{ + /// + /// In-memory storage for specializations. + /// + private readonly Dictionary _specializations = new(); + + /// + /// Retrieves all specializations from the in-memory storage. + /// + /// A read-only collection of all specializations. + public IReadOnlyCollection GetAllSpecializations() => _specializations.Values; + + /// + /// Adds a new specialization to the in-memory storage. + /// + /// The specialization to add. + /// True if the specialization was successfully added, false if it already exists. + public bool AddSpecialization(Specialization specialization) + { + if (_specializations.ContainsKey(specialization.Id)){ + return false; + } + _specializations[specialization.Id] = specialization; + return true; + } + + /// + /// Removes a specialization from the in-memory storage. + /// + /// The ID of the specialization to remove. + /// True if the specialization was successfully removed, false if it doesn't exist. + public bool RemoveSpecialization(int id) => _specializations.Remove(id); + + /// + /// Retrieves a specialization by its ID. + /// + /// The ID of the specialization to retrieve. + /// The specialization with the specified ID, or null if not found. + public Specialization? GetSpecialization(int id) + { + if (!_specializations.ContainsKey(id)) + { + return null; + } + return _specializations[id]; + } + + /// + /// Gets the count of specializations in the in-memory storage. + /// + /// The number of specializations. + public int SpecializationCount() => _specializations.Count; + + /// + /// Updates an existing specialization in the in-memory storage. + /// + /// The ID of the specialization to update. + /// The specialization with updated information. + /// The updated specialization, or null if it doesn't exist. + public Specialization? UpdateSpecialization(int id, Specialization specialization){ + if (!_specializations.ContainsKey(id)){ + return null; + } + _specializations[id].Name = specialization.Name; + return _specializations[id]; + } +} \ No newline at end of file diff --git a/Clinic/Clinic.sln b/Clinic/Clinic.sln index 6250a3de0..c8b3a7686 100755 --- a/Clinic/Clinic.sln +++ b/Clinic/Clinic.sln @@ -17,6 +17,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Clinic.ServiceDefaults", "C EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Clinic.Application", "Clinic.Application\Clinic.Application.csproj", "{43A97106-90F1-4FC0-B66E-1D6DA5950260}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Clinic.InMemory", "Clinic.InMemory\Clinic.InMemory.csproj", "{C77BBDCF-DF57-404C-B821-9DA7E6C06AD7}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -111,6 +113,18 @@ Global {43A97106-90F1-4FC0-B66E-1D6DA5950260}.Release|x64.Build.0 = Release|Any CPU {43A97106-90F1-4FC0-B66E-1D6DA5950260}.Release|x86.ActiveCfg = Release|Any CPU {43A97106-90F1-4FC0-B66E-1D6DA5950260}.Release|x86.Build.0 = Release|Any CPU + {C77BBDCF-DF57-404C-B821-9DA7E6C06AD7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C77BBDCF-DF57-404C-B821-9DA7E6C06AD7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C77BBDCF-DF57-404C-B821-9DA7E6C06AD7}.Debug|x64.ActiveCfg = Debug|Any CPU + {C77BBDCF-DF57-404C-B821-9DA7E6C06AD7}.Debug|x64.Build.0 = Debug|Any CPU + {C77BBDCF-DF57-404C-B821-9DA7E6C06AD7}.Debug|x86.ActiveCfg = Debug|Any CPU + {C77BBDCF-DF57-404C-B821-9DA7E6C06AD7}.Debug|x86.Build.0 = Debug|Any CPU + {C77BBDCF-DF57-404C-B821-9DA7E6C06AD7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C77BBDCF-DF57-404C-B821-9DA7E6C06AD7}.Release|Any CPU.Build.0 = Release|Any CPU + {C77BBDCF-DF57-404C-B821-9DA7E6C06AD7}.Release|x64.ActiveCfg = Release|Any CPU + {C77BBDCF-DF57-404C-B821-9DA7E6C06AD7}.Release|x64.Build.0 = Release|Any CPU + {C77BBDCF-DF57-404C-B821-9DA7E6C06AD7}.Release|x86.ActiveCfg = Release|Any CPU + {C77BBDCF-DF57-404C-B821-9DA7E6C06AD7}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From 0dc0c2a50153b5e76797500086b9a5a942e1aec2 Mon Sep 17 00:00:00 2001 From: maeosha <108515877+maeosha@users.noreply.github.com> Date: Wed, 24 Dec 2025 11:40:22 +0400 Subject: [PATCH 53/56] Fix project SDK declaration in csproj file --- Clinic/Clinic.InMemory/Clinic.InMemory.csproj | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Clinic/Clinic.InMemory/Clinic.InMemory.csproj b/Clinic/Clinic.InMemory/Clinic.InMemory.csproj index c926e8752..acbf5d8a3 100644 --- a/Clinic/Clinic.InMemory/Clinic.InMemory.csproj +++ b/Clinic/Clinic.InMemory/Clinic.InMemory.csproj @@ -1,4 +1,4 @@ - + net8.0 @@ -12,3 +12,4 @@ + From 19e5a9aa3a369b85574daea23d6037d4df08e05a Mon Sep 17 00:00:00 2001 From: maeosha Date: Tue, 24 Feb 2026 14:46:24 +0300 Subject: [PATCH 54/56] feat: Add DTOs for Doctor, Patient, and Specialization management - Created CreateUpdateDoctorDto and GetDoctorDto for doctor management. - Added CreateUpdatePatientDto and GetPatientDto for patient management. - Introduced CreateUpdateSpecializationDto and GetSpecializationDto for specialization handling. - Implemented service interfaces for appointment, doctor, patient, and specialization management. - Established base service interface for CRUD operations. - Updated in-memory repositories to implement new interfaces. - Removed obsolete ServiceDefaults project and related files. - Added gRPC contract for appointment ingestion. - Updated solution file to reflect new project structure and references. --- Clinic/Clinic.Api/Clinic.Api.csproj | 10 +- .../Controllers/AnalyticsControllers.cs | 8 +- .../Controllers/AppointmentControllers.cs | 6 +- .../Clinic.Api/Controllers/BaseControllers.cs | 17 +- .../Controllers/DoctorControllers.cs | 6 +- .../Controllers/PatientControllers.cs | 6 +- .../Controllers/SpecializationControllers.cs | 6 +- .../DTOs/Appointment/UpdateAppointment.cs | 33 --- .../Clinic.Api/DTOs/Doctor/UpdateDoctorDto.cs | 53 ----- .../DTOs/Patient/UpdatePatientDto.cs | 50 ---- .../Specialization/UpdateSpecializationDto.cs | 14 -- .../Clinic.Api/Grpc/ContractIngestService.cs | 146 ++++++++++++ Clinic/Clinic.Api/Program.cs | 18 +- .../Clinic.Api/appsettings.Development.json | 3 +- Clinic/Clinic.AppHost/AppHost.cs | 11 +- Clinic/Clinic.AppHost/Clinic.AppHost.csproj | 5 +- .../Clinic.AppiontmentGenerator.csproj | 27 +++ Clinic/Clinic.AppiontmentGenerator/Program.cs | 220 ++++++++++++++++++ .../appsettings.json | 12 + .../Clinic.Application.Services.csproj | 17 ++ .../Mapping}/MappingProfile.cs | 30 +-- .../Services/AnalyticsServices.cs | 8 +- .../Services/AppointmentServices.cs | 10 +- .../Services/DoctorServices.cs | 12 +- .../Services/PatientServices.cs | 12 +- .../Services/SpecializationServices.cs | 12 +- .../CreateUpdateAppointmentDto.cs} | 4 +- .../DTOs/Appointment/GetAppointmentDto.cs | 2 +- .../DTOs/Doctor/CreateUpdateDoctorDto.cs} | 8 +- .../DTOs/Doctor/GetDoctorDto.cs | 2 +- .../DTOs/Patient/CreateUpdatePatientDto.cs} | 4 +- .../DTOs/Patient/GetPatientDto.cs | 2 +- .../CreateUpdateSpecializationDto.cs} | 4 +- .../Specialization/GetSpecializationDto.cs | 2 +- .../Services/IAppointmentService.cs | 7 +- .../Interfaces/Services/IBaseService.cs | 15 +- .../Interfaces/Services/IDoctorService.cs | 9 +- .../Interfaces/Services/IPatientService.cs | 8 +- .../Services/ISpecializationService.cs | 9 +- .../Clinic.Contracts/Clinic.Contracts.csproj | 19 ++ .../Clinic.Contracts/Proto/appointment.proto | 26 +++ Clinic/Clinic.InMemory/Clinic.InMemory.csproj | 12 +- .../InMemoryAppointmentRepository.cs | 17 +- .../InMemoryDoctorRepository.cs | 8 +- .../InMemoryPatientRepository.cs | 14 +- .../InMemorySpecializationRepository.cs | 5 +- .../Clinic.ServiceDefaults.csproj | 22 -- Clinic/Clinic.ServiceDefaults/Extensions.cs | 127 ---------- Clinic/Clinic.Tests/Clinic.Tests.csproj | 1 + Clinic/Clinic.Tests/ClinicTests.cs | 6 +- Clinic/Clinic.sln | 56 +++-- 51 files changed, 670 insertions(+), 471 deletions(-) delete mode 100644 Clinic/Clinic.Api/DTOs/Appointment/UpdateAppointment.cs delete mode 100644 Clinic/Clinic.Api/DTOs/Doctor/UpdateDoctorDto.cs delete mode 100644 Clinic/Clinic.Api/DTOs/Patient/UpdatePatientDto.cs delete mode 100644 Clinic/Clinic.Api/DTOs/Specialization/UpdateSpecializationDto.cs create mode 100644 Clinic/Clinic.Api/Grpc/ContractIngestService.cs create mode 100644 Clinic/Clinic.AppiontmentGenerator/Clinic.AppiontmentGenerator.csproj create mode 100644 Clinic/Clinic.AppiontmentGenerator/Program.cs create mode 100644 Clinic/Clinic.AppiontmentGenerator/appsettings.json create mode 100644 Clinic/Clinic.Application.Services/Clinic.Application.Services.csproj rename Clinic/{Clinic.Api/MappingProfile => Clinic.Application.Services/Mapping}/MappingProfile.cs (64%) rename Clinic/{Clinic.Api => Clinic.Application.Services}/Services/AnalyticsServices.cs (96%) rename Clinic/{Clinic.Api => Clinic.Application.Services}/Services/AppointmentServices.cs (95%) rename Clinic/{Clinic.Api => Clinic.Application.Services}/Services/DoctorServices.cs (92%) rename Clinic/{Clinic.Api => Clinic.Application.Services}/Services/PatientServices.cs (92%) rename Clinic/{Clinic.Api => Clinic.Application.Services}/Services/SpecializationServices.cs (92%) rename Clinic/{Clinic.Api/DTOs/Appointment/CreateAppointmentDto.cs => Clinic.Application/DTOs/Appointment/CreateUpdateAppointmentDto.cs} (91%) rename Clinic/{Clinic.Api => Clinic.Application}/DTOs/Appointment/GetAppointmentDto.cs (96%) rename Clinic/{Clinic.Api/DTOs/Doctor/CreateDoctorDto.cs => Clinic.Application/DTOs/Doctor/CreateUpdateDoctorDto.cs} (86%) rename Clinic/{Clinic.Api => Clinic.Application}/DTOs/Doctor/GetDoctorDto.cs (97%) rename Clinic/{Clinic.Api/DTOs/Patient/CreatePatientDto.cs => Clinic.Application/DTOs/Patient/CreateUpdatePatientDto.cs} (95%) rename Clinic/{Clinic.Api => Clinic.Application}/DTOs/Patient/GetPatientDto.cs (96%) rename Clinic/{Clinic.Api/DTOs/Specialization/CreateSpecializationDto.cs => Clinic.Application/DTOs/Specialization/CreateUpdateSpecializationDto.cs} (71%) rename Clinic/{Clinic.Api => Clinic.Application}/DTOs/Specialization/GetSpecializationDto.cs (89%) rename Clinic/{Clinic.Api => Clinic.Application}/Interfaces/Services/IAppointmentService.cs (87%) rename Clinic/{Clinic.Api => Clinic.Application}/Interfaces/Services/IBaseService.cs (80%) rename Clinic/{Clinic.Api => Clinic.Application}/Interfaces/Services/IDoctorService.cs (64%) rename Clinic/{Clinic.Api => Clinic.Application}/Interfaces/Services/IPatientService.cs (64%) rename Clinic/{Clinic.Api => Clinic.Application}/Interfaces/Services/ISpecializationService.cs (65%) create mode 100644 Clinic/Clinic.Contracts/Clinic.Contracts.csproj create mode 100644 Clinic/Clinic.Contracts/Proto/appointment.proto delete mode 100644 Clinic/Clinic.ServiceDefaults/Clinic.ServiceDefaults.csproj delete mode 100644 Clinic/Clinic.ServiceDefaults/Extensions.cs diff --git a/Clinic/Clinic.Api/Clinic.Api.csproj b/Clinic/Clinic.Api/Clinic.Api.csproj index 78fb6a5bd..87340686c 100644 --- a/Clinic/Clinic.Api/Clinic.Api.csproj +++ b/Clinic/Clinic.Api/Clinic.Api.csproj @@ -3,19 +3,23 @@ net8.0 enable - enable + enable + true + $(NoWarn);1591 + + - + - \ No newline at end of file + diff --git a/Clinic/Clinic.Api/Controllers/AnalyticsControllers.cs b/Clinic/Clinic.Api/Controllers/AnalyticsControllers.cs index aa6c04d7a..76c1d928a 100644 --- a/Clinic/Clinic.Api/Controllers/AnalyticsControllers.cs +++ b/Clinic/Clinic.Api/Controllers/AnalyticsControllers.cs @@ -1,8 +1,8 @@ using Microsoft.AspNetCore.Mvc; -using Clinic.Api.Services; -using Clinic.Api.DTOs.Doctor; -using Clinic.Api.DTOs.Patient; -using Clinic.Api.DTOs.Appointment; +using Clinic.Application.Services; +using Clinic.Application.DTOs.Doctor; +using Clinic.Application.DTOs.Patient; +using Clinic.Application.DTOs.Appointment; namespace Clinic.Api.Controllers; diff --git a/Clinic/Clinic.Api/Controllers/AppointmentControllers.cs b/Clinic/Clinic.Api/Controllers/AppointmentControllers.cs index 8c1c04cf1..65512ce50 100644 --- a/Clinic/Clinic.Api/Controllers/AppointmentControllers.cs +++ b/Clinic/Clinic.Api/Controllers/AppointmentControllers.cs @@ -1,6 +1,6 @@ using Microsoft.AspNetCore.Mvc; -using Clinic.Api.Interfaces.Services; -using Clinic.Api.DTOs.Appointment; +using Clinic.Application.Interfaces.Services; +using Clinic.Application.DTOs.Appointment; namespace Clinic.Api.Controllers; @@ -10,7 +10,7 @@ namespace Clinic.Api.Controllers; /// [ApiController] [Route("api/appointments")] -public class AppointmentControllers(IAppointmentServices appointmentServices) : BaseControllers(appointmentServices) +public class AppointmentControllers(IAppointmentServices appointmentServices) : BaseControllers(appointmentServices) { /// /// Gets all appointments for a specific doctor. diff --git a/Clinic/Clinic.Api/Controllers/BaseControllers.cs b/Clinic/Clinic.Api/Controllers/BaseControllers.cs index 945532c00..739f20baa 100644 --- a/Clinic/Clinic.Api/Controllers/BaseControllers.cs +++ b/Clinic/Clinic.Api/Controllers/BaseControllers.cs @@ -1,20 +1,16 @@ using Microsoft.AspNetCore.Mvc; -using Clinic.Api.Interfaces.Services; +using Clinic.Application.Interfaces.Services; namespace Clinic.Api.Controllers; /// /// Base controller class that provides common CRUD operations for all controllers. /// -/// Entity type for the controller. /// DTO type for retrieving entity data. -/// DTO type for creating a new entity. -/// DTO type for updating an existing entity. -/// Service type that implements IBaseService. -public class BaseControllers(IBaseServices Service) : ControllerBase +/// DTO type for create and update operations. +public class BaseControllers(IBaseServices Service) : ControllerBase where TGetDto : class - where TCreateDto : class - where TUpdateDto : class + where TSaveDto : class { /// @@ -55,7 +51,7 @@ public virtual ActionResult Get(int id) [HttpPost] [ProducesResponseType(StatusCodes.Status201Created)] [ProducesResponseType(StatusCodes.Status400BadRequest)] - public virtual ActionResult Create(TCreateDto dto) + public virtual ActionResult Create(TSaveDto dto) { var entity = Service.Create(dto); if (entity == null) @@ -75,7 +71,7 @@ public virtual ActionResult Create(TCreateDto dto) [HttpPut("{id}")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public virtual ActionResult Update(int id, TUpdateDto dto) + public virtual ActionResult Update(int id, TSaveDto dto) { var entity = Service.Update(id, dto); if (entity == null) @@ -128,4 +124,3 @@ protected virtual int GetEntityId(TGetDto entity) return 0; } } - diff --git a/Clinic/Clinic.Api/Controllers/DoctorControllers.cs b/Clinic/Clinic.Api/Controllers/DoctorControllers.cs index 7b0f770af..51a0d018a 100644 --- a/Clinic/Clinic.Api/Controllers/DoctorControllers.cs +++ b/Clinic/Clinic.Api/Controllers/DoctorControllers.cs @@ -1,6 +1,6 @@ using Microsoft.AspNetCore.Mvc; -using Clinic.Api.DTOs.Doctor; -using Clinic.Api.Interfaces.Services; +using Clinic.Application.DTOs.Doctor; +using Clinic.Application.Interfaces.Services; namespace Clinic.Api.Controllers; @@ -10,4 +10,4 @@ namespace Clinic.Api.Controllers; /// [ApiController] [Route("api/doctors")] -public class DoctorControllers(IDoctorServices doctorServices) : BaseControllers(doctorServices); +public class DoctorControllers(IDoctorServices doctorServices) : BaseControllers(doctorServices); diff --git a/Clinic/Clinic.Api/Controllers/PatientControllers.cs b/Clinic/Clinic.Api/Controllers/PatientControllers.cs index b88b4349d..75180d8f8 100644 --- a/Clinic/Clinic.Api/Controllers/PatientControllers.cs +++ b/Clinic/Clinic.Api/Controllers/PatientControllers.cs @@ -1,6 +1,6 @@ using Microsoft.AspNetCore.Mvc; -using Clinic.Api.DTOs.Patient; -using Clinic.Api.Interfaces.Services; +using Clinic.Application.DTOs.Patient; +using Clinic.Application.Interfaces.Services; namespace Clinic.Api.Controllers; @@ -10,4 +10,4 @@ namespace Clinic.Api.Controllers; /// [ApiController] [Route("api/patients")] -public class PatientControllers(IPatientServices patientServices) : BaseControllers(patientServices); +public class PatientControllers(IPatientServices patientServices) : BaseControllers(patientServices); diff --git a/Clinic/Clinic.Api/Controllers/SpecializationControllers.cs b/Clinic/Clinic.Api/Controllers/SpecializationControllers.cs index de2f073e6..7909a33cb 100644 --- a/Clinic/Clinic.Api/Controllers/SpecializationControllers.cs +++ b/Clinic/Clinic.Api/Controllers/SpecializationControllers.cs @@ -1,6 +1,6 @@ using Microsoft.AspNetCore.Mvc; -using Clinic.Api.DTOs.Specialization; -using Clinic.Api.Interfaces.Services; +using Clinic.Application.DTOs.Specialization; +using Clinic.Application.Interfaces.Services; namespace Clinic.Api.Controllers; @@ -10,4 +10,4 @@ namespace Clinic.Api.Controllers; /// [ApiController] [Route("api/specializations")] -public class SpecializationControllers(ISpecializationServices specializationServices) : BaseControllers(specializationServices); +public class SpecializationControllers(ISpecializationServices specializationServices) : BaseControllers(specializationServices); diff --git a/Clinic/Clinic.Api/DTOs/Appointment/UpdateAppointment.cs b/Clinic/Clinic.Api/DTOs/Appointment/UpdateAppointment.cs deleted file mode 100644 index d3898c16a..000000000 --- a/Clinic/Clinic.Api/DTOs/Appointment/UpdateAppointment.cs +++ /dev/null @@ -1,33 +0,0 @@ -namespace Clinic.Api.DTOs.Appointment; - -/// -/// DTO for updating an existing appointment, including optional patient/doctor information, -/// scheduled date/time, room number, and return visit flag. -/// -public class UpdateAppointmentDto -{ - /// - /// Optional: The ID of the patient to update for the appointment. - /// - public int? PatientId { get; set; } = null; - - /// - /// Optional: The ID of the doctor to update for the appointment. - /// - public int? DoctorId { get; set; } = null; - - /// - /// Optional: The new date and time for the appointment. - /// - public DateTime? DateTime { get; set; } = null; - - /// - /// Optional: The new room number for the appointment. - /// - public int? RoomNumber { get; set; } = null; - - /// - /// Optional: Flag indicating if this is a return visit. - /// - public bool? IsReturnVisit { get; set; } = null; -} \ No newline at end of file diff --git a/Clinic/Clinic.Api/DTOs/Doctor/UpdateDoctorDto.cs b/Clinic/Clinic.Api/DTOs/Doctor/UpdateDoctorDto.cs deleted file mode 100644 index 311f9ac47..000000000 --- a/Clinic/Clinic.Api/DTOs/Doctor/UpdateDoctorDto.cs +++ /dev/null @@ -1,53 +0,0 @@ -namespace Clinic.Api.DTOs.Doctor; - -/// -/// DTO for updating an existing doctor, including optional personal information, -/// specialization list, and experience years. -/// -public class UpdateDoctorDto -{ - /// - /// The unique identifier of the doctor. - /// - public int? Id { get; set; } - - /// - /// The passport number of the doctor. - /// - public string? PassportNumber { get; set; } = string.Empty; - - /// - /// The birth date of the doctor. - /// - public DateOnly? BirthDate { get; set; } - - /// - /// The last name of the doctor. - /// - public string? LastName { get; set; } = string.Empty; - - /// - /// The first name of the doctor. - /// - public string? FirstName { get; set; } = string.Empty; - - /// - /// Optional: The patronymic (middle name) of the doctor. - /// - public string? Patronymic { get; set; } - - /// - /// The gender of the doctor. - /// - public String? Gender { get; set; } = null!; - - /// - /// The list of specializations for the doctor. - /// - public List? Specializations { get; set; } = null!; - - /// - /// The number of years of experience for the doctor. - /// - public int? ExperienceYears { get; set; } = null!; -} \ No newline at end of file diff --git a/Clinic/Clinic.Api/DTOs/Patient/UpdatePatientDto.cs b/Clinic/Clinic.Api/DTOs/Patient/UpdatePatientDto.cs deleted file mode 100644 index 46a9aa641..000000000 --- a/Clinic/Clinic.Api/DTOs/Patient/UpdatePatientDto.cs +++ /dev/null @@ -1,50 +0,0 @@ -using Clinic.Models.Enums; - -namespace Clinic.Api.DTOs.Patient; - -/// -/// DTO for updating an existing patient, including optional personal information, -/// medical history, and contact details. -/// -public class UpdatePatientDto -{ - /// - /// Optional new first name for the patient. - /// - public string? FirstName { get; set; } = null; - - /// - /// Optional new last name for the patient. - /// - public string? LastName { get; set; } = null; - - /// - /// Optional new patronymic (middle name) for the patient. - /// - public string? Patronymic { get; set; } = null; - - /// - /// Optional new date of birth for the patient. - /// - public DateTime? DateOfBirth { get; set; } = null; - - /// - /// Optional new address for the patient. - /// - public string? Address { get; set; } = null; - - /// - /// Optional new phone number for the patient. - /// - public string? PhoneNumber { get; set; } = null; - - /// - /// Optional new blood group for the patient. - /// - public BloodGroup? BloodGroup { get; set; } = null; - - /// - /// Optional new rhesus factor for the patient. - /// - public RhesusFactor? RhesusFactor { get; set; } = null; -} \ No newline at end of file diff --git a/Clinic/Clinic.Api/DTOs/Specialization/UpdateSpecializationDto.cs b/Clinic/Clinic.Api/DTOs/Specialization/UpdateSpecializationDto.cs deleted file mode 100644 index 5db98afaa..000000000 --- a/Clinic/Clinic.Api/DTOs/Specialization/UpdateSpecializationDto.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace Clinic.Api.DTOs.Specialization; - -/// -/// DTO for updating an existing specialization. -/// Note: Currently not used as specializations do not support update operations. -/// -public class UpdateSpecializationDto -{ - /// - /// The new name of the specialization. Optional. - /// - public string? Name { get; set; } -} - diff --git a/Clinic/Clinic.Api/Grpc/ContractIngestService.cs b/Clinic/Clinic.Api/Grpc/ContractIngestService.cs new file mode 100644 index 000000000..4f711f1f0 --- /dev/null +++ b/Clinic/Clinic.Api/Grpc/ContractIngestService.cs @@ -0,0 +1,146 @@ +using System.Globalization; +using Clinic.Application.Ports; +using Clinic.Contracts; +using Clinic.Models.Entities; +using Grpc.Core; + +namespace Clinic.Api.Grpc; + +/// +/// gRPC service that ingests appointment contracts from a client stream +/// and persists mapped appointments to storage. +/// +public class ContractIngestService : ContractIngestor.ContractIngestorBase +{ + private readonly IAppointmentRepository _appointments; + private readonly ILogger _logger; + + /// + /// Initializes a new instance of the class. + /// + /// Appointment repository used to persist mapped contracts. + /// Logger for ingest diagnostics and persistence errors. + public ContractIngestService(IAppointmentRepository appointments, ILogger logger) + { + _appointments = appointments; + _logger = logger; + } + + /// + /// Reads contracts from the incoming gRPC stream, validates and maps them to appointments, + /// and returns aggregated ingest statistics. + /// + /// Incoming stream of contracts. + /// Server call context for cancellation and metadata. + /// Ingest result with total received, saved, failed and error details. + public override async Task IngestContracts(IAsyncStreamReader requestStream, ServerCallContext context) + { + var received = 0; + var saved = 0; + var failed = 0; + var errors = new List(); + + await foreach (var contract in requestStream.ReadAllAsync(context.CancellationToken)) + { + received++; + if (!TryMap(contract, out var appointment, out var error)) + { + failed++; + errors.Add($"#{received}: {error}"); + continue; + } + + try + { + if (_appointments.AddAppointment(appointment)) + { + saved++; + _logger.LogInformation( + "Appointment added from stream. AppointmentId: {AppointmentId}, PatientId: {PatientId}, DoctorId: {DoctorId}, DateTime: {DateTime}", + appointment.Id, + appointment.PatientId, + appointment.DoctorId, + appointment.DateTime); + } + else + { + failed++; + errors.Add($"#{received}: Appointment already exists."); + } + } + catch (Exception ex) + { + failed++; + errors.Add($"#{received}: {ex.Message}"); + _logger.LogWarning(ex, "Failed to persist contract #{Index}", received); + } + } + + var result = new IngestResult + { + Received = received, + Saved = saved, + Failed = failed + }; + result.Errors.AddRange(errors); + + return result; + } + + /// + /// Validates contract payload and converts it to an . + /// + /// Source contract. + /// Mapped appointment if conversion succeeds. + /// Validation or conversion error message. + /// true when contract is valid and mapped; otherwise, false. + private static bool TryMap(Contract contract, out Appointment appointment, out string error) + { + appointment = null!; + error = string.Empty; + + if (contract.PatientId <= 0) + { + error = "PatientId must be positive."; + return false; + } + + if (contract.DoctorId <= 0) + { + error = "DoctorId must be positive."; + return false; + } + + if (string.IsNullOrWhiteSpace(contract.PatientFullName)) + { + error = "PatientFullName is required."; + return false; + } + + if (string.IsNullOrWhiteSpace(contract.DoctorFullName)) + { + error = "DoctorFullName is required."; + return false; + } + + if (!DateTimeOffset.TryParse(contract.AppointmentTime, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out var dateTimeOffset)) + { + error = "AppointmentTime must be ISO-8601."; + return false; + } + + appointment = new Appointment + { + Id = 0, + PatientId = contract.PatientId, + PatientFullName = contract.PatientFullName, + DoctorId = contract.DoctorId, + DoctorFullName = contract.DoctorFullName, + DateTime = dateTimeOffset.UtcDateTime, + RoomNumber = contract.RoomNumber, + IsReturnVisit = contract.IsReturnVisit + }; + + return true; + } +} diff --git a/Clinic/Clinic.Api/Program.cs b/Clinic/Clinic.Api/Program.cs index 8c1d5b54c..74d7d7ddb 100644 --- a/Clinic/Clinic.Api/Program.cs +++ b/Clinic/Clinic.Api/Program.cs @@ -1,17 +1,16 @@ using Clinic.DataBase; using Clinic.Application.Ports; using Clinic.DataBase.EntityFramework; -using Clinic.Api.MappingProfile; +using Clinic.Application.Services.Mapping; using Microsoft.Extensions.Hosting; -using Clinic.Api.Services; +using Clinic.Application.Services; using Clinic.Api.Converter; -using Clinic.Api.Interfaces.Services; +using Clinic.Application.Interfaces.Services; using Microsoft.EntityFrameworkCore; +using Clinic.Api.Grpc; var builder = WebApplication.CreateBuilder(args); -builder.AddServiceDefaults(); - builder.Services.AddControllers() .AddJsonOptions(options => { @@ -19,6 +18,7 @@ options.JsonSerializerOptions.PropertyNamingPolicy = null; }); +builder.Services.AddGrpc(); var connectionString = builder.Configuration.GetConnectionString("ClinicDb") ?? throw new InvalidOperationException("Connection string 'ClinicDb' is not configured."); @@ -33,7 +33,12 @@ builder.Services.AddAutoMapper(cfg => cfg.AddProfile()); builder.Services.AddEndpointsApiExplorer(); -builder.Services.AddSwaggerGen(); +builder.Services.AddSwaggerGen(options => +{ + var xmlFile = $"{System.Reflection.Assembly.GetExecutingAssembly().GetName().Name}.xml"; + var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile); + options.IncludeXmlComments(xmlPath, includeControllerXmlComments: true); +}); builder.Services.AddScoped(); builder.Services.AddScoped(); @@ -60,5 +65,6 @@ }); } +app.MapGrpcService(); app.MapControllers(); app.Run(); diff --git a/Clinic/Clinic.Api/appsettings.Development.json b/Clinic/Clinic.Api/appsettings.Development.json index fddce0725..3c7c27b74 100644 --- a/Clinic/Clinic.Api/appsettings.Development.json +++ b/Clinic/Clinic.Api/appsettings.Development.json @@ -3,7 +3,8 @@ "LogLevel": { "Default": "Information", "Microsoft.AspNetCore": "Warning", - "Microsoft.EntityFrameworkCore": "Information" + "Microsoft.EntityFrameworkCore": "Warning", + "Microsoft.EntityFrameworkCore.Database.Command": "Warning" } }, "ConnectionStrings": { diff --git a/Clinic/Clinic.AppHost/AppHost.cs b/Clinic/Clinic.AppHost/AppHost.cs index 0e2f2bd50..ba047e8b6 100644 --- a/Clinic/Clinic.AppHost/AppHost.cs +++ b/Clinic/Clinic.AppHost/AppHost.cs @@ -3,9 +3,16 @@ var postgresql = builder.AddPostgres("postgres") .AddDatabase("ClinicDb"); -var api = builder.AddProject("clinic-api") +var api = builder.AddProject("clinic-api", "../Clinic.Api/Clinic.Api.csproj") .WithReference(postgresql) .WaitFor(postgresql) .WithExternalHttpEndpoints(); -builder.Build().Run(); \ No newline at end of file +var appiontmentGenerator = builder.AddProject("clinic-appiontment-generator", "../Clinic.AppiontmentGenerator/Clinic.AppiontmentGenerator.csproj") + .WithReference(postgresql) + .WithReference(api) + .WaitFor(postgresql) + .WaitFor(api) + .WithEnvironment("Grpc__Endpoint", api.GetEndpoint("https")); + +builder.Build().Run(); diff --git a/Clinic/Clinic.AppHost/Clinic.AppHost.csproj b/Clinic/Clinic.AppHost/Clinic.AppHost.csproj index 9f0d91d99..40a19d8f4 100644 --- a/Clinic/Clinic.AppHost/Clinic.AppHost.csproj +++ b/Clinic/Clinic.AppHost/Clinic.AppHost.csproj @@ -9,11 +9,12 @@ - - + + + diff --git a/Clinic/Clinic.AppiontmentGenerator/Clinic.AppiontmentGenerator.csproj b/Clinic/Clinic.AppiontmentGenerator/Clinic.AppiontmentGenerator.csproj new file mode 100644 index 000000000..ea9fd7568 --- /dev/null +++ b/Clinic/Clinic.AppiontmentGenerator/Clinic.AppiontmentGenerator.csproj @@ -0,0 +1,27 @@ + + + Exe + net8.0 + enable + enable + + + + + + + + + + + + + + + + + + PreserveNewest + + + diff --git a/Clinic/Clinic.AppiontmentGenerator/Program.cs b/Clinic/Clinic.AppiontmentGenerator/Program.cs new file mode 100644 index 000000000..e70939204 --- /dev/null +++ b/Clinic/Clinic.AppiontmentGenerator/Program.cs @@ -0,0 +1,220 @@ +using System.Globalization; +using Clinic.Contracts; +using Grpc.Net.Client; +using Microsoft.Extensions.Configuration; +using Npgsql; + +var configuration = new ConfigurationBuilder() + .AddJsonFile("appsettings.json", optional: true) + .AddEnvironmentVariables() + .Build(); + +var endpoint = configuration["Grpc:Endpoint"]; +if (string.IsNullOrWhiteSpace(endpoint)) +{ + Console.Error.WriteLine("Missing Grpc:Endpoint configuration."); + return 1; +} + +var aspireHttpsEndpoint = Environment.GetEnvironmentVariable("services__clinic-api__https__0"); +var aspireHttpEndpoint = Environment.GetEnvironmentVariable("services__clinic-api__http__0"); +if (endpoint.Contains("clinic-api", StringComparison.OrdinalIgnoreCase)) +{ + endpoint = aspireHttpsEndpoint ?? aspireHttpEndpoint ?? endpoint; +} + +var connectionString = configuration.GetConnectionString("ClinicDb"); +if (string.IsNullOrWhiteSpace(connectionString)) +{ + Console.Error.WriteLine("Missing ConnectionStrings:ClinicDb configuration."); + return 1; +} + +if (endpoint.StartsWith("http://", StringComparison.OrdinalIgnoreCase)) +{ + AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true); +} + +var count = configuration.GetValue("Generator:Count", 10); +var roomNumbers = configuration.GetSection("Generator:RoomNumbers").Get() ?? [101, 102, 201, 202]; + +var patients = new List<(int Id, string FullName)>(); +var doctors = new List<(int Id, string FullName)>(); +var knownVisitPairs = new HashSet<(int PatientId, int DoctorId)>(); + +const int maxAttempts = 20; + +// Attempt to read patients, doctors and known visit pairs with retry logic for database readiness. +for (var attempt = 1; attempt <= maxAttempts; attempt++) +{ + try + { + patients.Clear(); + doctors.Clear(); + knownVisitPairs.Clear(); + + await using var connection = new NpgsqlConnection(connectionString); + await connection.OpenAsync(); + + await using (var command = new NpgsqlCommand("SELECT \"Id\", \"LastName\", \"FirstName\", \"Patronymic\" FROM \"Patients\"", connection)) + await using (var reader = await command.ExecuteReaderAsync()) + { + while (await reader.ReadAsync()) + { + var id = reader.GetInt32(0); + var lastName = reader.GetString(1); + var firstName = reader.GetString(2); + var patronymic = reader.IsDBNull(3) ? string.Empty : reader.GetString(3); + var fullName = BuildFullName(lastName, firstName, patronymic); + patients.Add((id, fullName)); + } + } + + await using (var command = new NpgsqlCommand("SELECT \"Id\", \"LastName\", \"FirstName\", \"Patronymic\" FROM \"Doctors\"", connection)) + await using (var reader = await command.ExecuteReaderAsync()) + { + while (await reader.ReadAsync()) + { + var id = reader.GetInt32(0); + var lastName = reader.GetString(1); + var firstName = reader.GetString(2); + var patronymic = reader.IsDBNull(3) ? string.Empty : reader.GetString(3); + var fullName = BuildFullName(lastName, firstName, patronymic); + doctors.Add((id, fullName)); + } + } + + await using (var command = new NpgsqlCommand("SELECT DISTINCT \"PatientId\", \"DoctorId\" FROM \"Appointments\"", connection)) + await using (var reader = await command.ExecuteReaderAsync()) + { + while (await reader.ReadAsync()) + { + knownVisitPairs.Add((reader.GetInt32(0), reader.GetInt32(1))); + } + } + + break; + } + catch (PostgresException ex) when (ex.SqlState == "42P01" && attempt < maxAttempts) + { + Console.WriteLine($"Database schema is not ready yet (attempt {attempt}/{maxAttempts}). Retrying..."); + await Task.Delay(TimeSpan.FromSeconds(2)); + } + catch (NpgsqlException) when (attempt < maxAttempts) + { + Console.WriteLine($"Database is not ready yet (attempt {attempt}/{maxAttempts}). Retrying..."); + await Task.Delay(TimeSpan.FromSeconds(2)); + } +} + +if (count <= 0) +{ + Console.Error.WriteLine("Generator:Count must be greater than 0."); + return 1; +} + +if (roomNumbers.Length == 0) +{ + Console.Error.WriteLine("Generator:RoomNumbers is empty."); + return 1; +} + +if (patients.Count == 0 || doctors.Count == 0) +{ + Console.Error.WriteLine("No patients or doctors found in the database after retries."); + return 1; +} + +//Create gRPC channel and client, then send generated contracts with retry logic for endpoint availability. +const int grpcAttempts = 10; +for (var attempt = 1; attempt <= grpcAttempts; attempt++) +{ + try + { + using var channel = GrpcChannel.ForAddress(endpoint); + var client = new ContractIngestor.ContractIngestorClient(channel); + + using var call = client.IngestContracts(); + var random = new Random(); + + for (var i = 0; i < count; i++) + { + var patient = patients[random.Next(patients.Count)]; + var doctor = doctors[random.Next(doctors.Count)]; + var contract = BuildRandomContract(patient, doctor, roomNumbers, random, knownVisitPairs); + + await call.RequestStream.WriteAsync(contract); + } + + await call.RequestStream.CompleteAsync(); + + var result = await call; + Console.WriteLine($"Received={result.Received} Saved={result.Saved} Failed={result.Failed}"); + foreach (var error in result.Errors) + { + Console.WriteLine(error); + } + + return 0; + } + catch (Grpc.Core.RpcException ex) when (ex.StatusCode == Grpc.Core.StatusCode.Unavailable && attempt < grpcAttempts) + { + Console.WriteLine($"gRPC endpoint is not ready yet (attempt {attempt}/{grpcAttempts}). Retrying..."); + await Task.Delay(TimeSpan.FromSeconds(2)); + } +} + +Console.Error.WriteLine("Failed to send contracts to gRPC endpoint after retries."); +return 1; + +/// +/// Builds a full name from separate name parts. +/// +/// Person last name. +/// Person first name. +/// Optional middle name. +/// Concatenated full name without extra whitespace. +static string BuildFullName(string lastName, string firstName, string? patronymic) +{ + return string.IsNullOrWhiteSpace(patronymic) + ? $"{lastName} {firstName}" + : $"{lastName} {firstName} {patronymic}"; +} + +/// +/// Creates a randomized appointment contract for gRPC streaming. +/// +/// Source patient identity tuple. +/// Source doctor identity tuple. +/// Available room numbers. +/// Random generator instance. +/// Known patient-doctor pairs that already had visits. +/// Prepared with randomized date and flags. +static Contract BuildRandomContract( + (int Id, string FullName) patient, + (int Id, string FullName) doctor, + int[] roomNumbers, + Random random, + HashSet<(int PatientId, int DoctorId)> knownVisitPairs) +{ + var pair = (patient.Id, doctor.Id); + var isReturnVisit = knownVisitPairs.Contains(pair); + knownVisitPairs.Add(pair); + + var appointmentTime = DateTimeOffset.UtcNow + .AddDays(random.Next(0, 30)) + .AddHours(random.Next(8, 18)) + .AddMinutes(random.Next(0, 60)) + .ToString("O", CultureInfo.InvariantCulture); + + return new Contract + { + PatientId = patient.Id, + PatientFullName = patient.FullName, + DoctorId = doctor.Id, + DoctorFullName = doctor.FullName, + AppointmentTime = appointmentTime, + RoomNumber = roomNumbers[random.Next(roomNumbers.Length)], + IsReturnVisit = isReturnVisit + }; +} diff --git a/Clinic/Clinic.AppiontmentGenerator/appsettings.json b/Clinic/Clinic.AppiontmentGenerator/appsettings.json new file mode 100644 index 000000000..5ae21f0f4 --- /dev/null +++ b/Clinic/Clinic.AppiontmentGenerator/appsettings.json @@ -0,0 +1,12 @@ +{ + "Grpc": { + "Endpoint": "https://clinic-api" + }, + "ConnectionStrings": { + "ClinicDb": "" + }, + "Generator": { + "Count": 10, + "RoomNumbers": [101, 102, 201, 202] + } +} diff --git a/Clinic/Clinic.Application.Services/Clinic.Application.Services.csproj b/Clinic/Clinic.Application.Services/Clinic.Application.Services.csproj new file mode 100644 index 000000000..8f0424312 --- /dev/null +++ b/Clinic/Clinic.Application.Services/Clinic.Application.Services.csproj @@ -0,0 +1,17 @@ + + + + net8.0 + enable + enable + + + + + + + + + + + diff --git a/Clinic/Clinic.Api/MappingProfile/MappingProfile.cs b/Clinic/Clinic.Application.Services/Mapping/MappingProfile.cs similarity index 64% rename from Clinic/Clinic.Api/MappingProfile/MappingProfile.cs rename to Clinic/Clinic.Application.Services/Mapping/MappingProfile.cs index 4779249dd..d19f3edee 100644 --- a/Clinic/Clinic.Api/MappingProfile/MappingProfile.cs +++ b/Clinic/Clinic.Application.Services/Mapping/MappingProfile.cs @@ -1,12 +1,12 @@ using AutoMapper; -using Clinic.Api.DTOs.Patient; -using Clinic.Api.DTOs.Doctor; -using Clinic.Api.DTOs.Specialization; -using Clinic.Api.DTOs.Appointment; +using Clinic.Application.DTOs.Patient; +using Clinic.Application.DTOs.Doctor; +using Clinic.Application.DTOs.Specialization; +using Clinic.Application.DTOs.Appointment; using Clinic.Models.Enums; using Clinic.Models.Entities; -namespace Clinic.Api.MappingProfile; +namespace Clinic.Application.Services.Mapping; /// /// AutoMapper profile for mapping between DTOs and entity models in the Clinic API. @@ -17,7 +17,7 @@ public class MappingProfile : Profile { public MappingProfile() { - CreateMap() + CreateMap() .ForMember(dest => dest.Gender, opt => opt.MapFrom(src => Enum.Parse(src.Gender))) .ForMember(dest => dest.BloodGroup, opt => opt.MapFrom(src => Enum.Parse(src.BloodGroup))) .ForMember(dest => dest.RhesusFactor, opt => opt.MapFrom(src => Enum.Parse(src.RhesusFactor))); @@ -27,32 +27,20 @@ public MappingProfile() .ForMember(dest => dest.BloodGroup, opt => opt.MapFrom(src => src.BloodGroup.ToString())) .ForMember(dest => dest.RhesusFactor, opt => opt.MapFrom(src => src.RhesusFactor.ToString())); - CreateMap() - .ForAllMembers(opts => opts.Condition((src, dest, srcMember) => srcMember != null)); - - CreateMap() + CreateMap() .ForMember(dest => dest.Gender, opt => opt.MapFrom(src => Enum.Parse(src.Gender))) .ForMember(dest => dest.Specializations, opt => opt.MapFrom(src => src.Specializations)); CreateMap() .ForMember(dest => dest.Specializations, opt => opt.MapFrom(src => src.Specializations.Select(s => s.Name.ToString()).ToList())); - CreateMap() - .ForMember(d => d.Specializations, opt => opt.Ignore()) - .ForAllMembers(opts => opts.Condition((src, dest, srcMember) => srcMember != null)); - CreateMap() .ForMember(dest => dest.Name, opt => opt.MapFrom(src => src.Name.ToString())); - CreateMap(); - - CreateMap(); + CreateMap(); CreateMap(); - CreateMap(); - - CreateMap() - .ForAllMembers(opts => opts.Condition((src, dest, srcMember) => srcMember != null)); + CreateMap(); } } diff --git a/Clinic/Clinic.Api/Services/AnalyticsServices.cs b/Clinic/Clinic.Application.Services/Services/AnalyticsServices.cs similarity index 96% rename from Clinic/Clinic.Api/Services/AnalyticsServices.cs rename to Clinic/Clinic.Application.Services/Services/AnalyticsServices.cs index 73c4abbd8..94712e0c0 100644 --- a/Clinic/Clinic.Api/Services/AnalyticsServices.cs +++ b/Clinic/Clinic.Application.Services/Services/AnalyticsServices.cs @@ -1,10 +1,10 @@ using AutoMapper; using Clinic.Application.Ports; -using Clinic.Api.DTOs.Patient; -using Clinic.Api.DTOs.Doctor; -using Clinic.Api.DTOs.Appointment; +using Clinic.Application.DTOs.Patient; +using Clinic.Application.DTOs.Doctor; +using Clinic.Application.DTOs.Appointment; -namespace Clinic.Api.Services; +namespace Clinic.Application.Services; public class AnalyticsServices { diff --git a/Clinic/Clinic.Api/Services/AppointmentServices.cs b/Clinic/Clinic.Application.Services/Services/AppointmentServices.cs similarity index 95% rename from Clinic/Clinic.Api/Services/AppointmentServices.cs rename to Clinic/Clinic.Application.Services/Services/AppointmentServices.cs index ff2fc26a9..6b7f02cd4 100644 --- a/Clinic/Clinic.Api/Services/AppointmentServices.cs +++ b/Clinic/Clinic.Application.Services/Services/AppointmentServices.cs @@ -1,10 +1,10 @@ using AutoMapper; using Clinic.Application.Ports; -using Clinic.Api.DTOs.Appointment; +using Clinic.Application.DTOs.Appointment; using Clinic.Models.Entities; -using Clinic.Api.Interfaces.Services; +using Clinic.Application.Interfaces.Services; -namespace Clinic.Api.Services; +namespace Clinic.Application.Services; /// /// Service layer for managing appointments in the clinic. @@ -97,7 +97,7 @@ public IReadOnlyCollection GetAll() /// /// The DTO containing appointment creation data. /// The created appointment as a DTO if successful; otherwise, null. - public GetAppointmentDto? Create(CreateAppointmentDto dto) + public GetAppointmentDto? Create(CreateUpdateAppointmentDto dto) { var appointment = _mapper.Map(dto); appointment.Id = _appointmentId; @@ -117,7 +117,7 @@ public IReadOnlyCollection GetAll() /// The identifier of the appointment to update. /// The DTO containing updated appointment data. /// The updated appointment as a DTO if successful; otherwise, null. - public GetAppointmentDto? Update(int id, UpdateAppointmentDto dto) + public GetAppointmentDto? Update(int id, CreateUpdateAppointmentDto dto) { var appointment = _appointments.GetAppointment(id); if (appointment == null) diff --git a/Clinic/Clinic.Api/Services/DoctorServices.cs b/Clinic/Clinic.Application.Services/Services/DoctorServices.cs similarity index 92% rename from Clinic/Clinic.Api/Services/DoctorServices.cs rename to Clinic/Clinic.Application.Services/Services/DoctorServices.cs index 2c45aad67..27dcac25a 100644 --- a/Clinic/Clinic.Api/Services/DoctorServices.cs +++ b/Clinic/Clinic.Application.Services/Services/DoctorServices.cs @@ -1,10 +1,10 @@ using AutoMapper; using Clinic.Application.Ports; -using Clinic.Api.DTOs.Doctor; +using Clinic.Application.DTOs.Doctor; using Clinic.Models.Entities; -using Clinic.Api.Interfaces.Services; +using Clinic.Application.Interfaces.Services; -namespace Clinic.Api.Services; +namespace Clinic.Application.Services; /// /// Service class for managing doctor-related operations within the Clinic API. @@ -47,7 +47,7 @@ public IReadOnlyCollection GetAll() /// /// The DTO containing doctor creation data. /// The created doctor as a if successful; otherwise, null. - public GetDoctorDto? Create(CreateDoctorDto createDoctorDto) + public GetDoctorDto? Create(CreateUpdateDoctorDto createDoctorDto) { var doctor = _mapper.Map(createDoctorDto); doctor.Id = _doctorId; @@ -80,7 +80,7 @@ public IReadOnlyCollection GetAll() /// The doctor identifier. /// DTO with updated doctor details. /// The updated doctor as a if successful; otherwise, null. - public GetDoctorDto? Update(int id, UpdateDoctorDto updateDoctorDto) + public GetDoctorDto? Update(int id, CreateUpdateDoctorDto updateDoctorDto) { var doctor = _db.GetDoctor(id); if (doctor == null) @@ -108,4 +108,4 @@ public bool Delete(int id) _doctorId--; return true; } -} \ No newline at end of file +} diff --git a/Clinic/Clinic.Api/Services/PatientServices.cs b/Clinic/Clinic.Application.Services/Services/PatientServices.cs similarity index 92% rename from Clinic/Clinic.Api/Services/PatientServices.cs rename to Clinic/Clinic.Application.Services/Services/PatientServices.cs index a19f99dea..8d908acdb 100644 --- a/Clinic/Clinic.Api/Services/PatientServices.cs +++ b/Clinic/Clinic.Application.Services/Services/PatientServices.cs @@ -1,10 +1,10 @@ using AutoMapper; using Clinic.Application.Ports; -using Clinic.Api.DTOs.Patient; +using Clinic.Application.DTOs.Patient; using Clinic.Models.Entities; -using Clinic.Api.Interfaces.Services; +using Clinic.Application.Interfaces.Services; -namespace Clinic.Api.Services; +namespace Clinic.Application.Services; /// /// Service class for managing patient-related operations in the Clinic API. @@ -46,7 +46,7 @@ public IReadOnlyCollection GetAll() /// /// The DTO containing patient creation data. /// The created patient as a if successful; otherwise, null. - public GetPatientDto? Create(CreatePatientDto patientCreateDto) + public GetPatientDto? Create(CreateUpdatePatientDto patientCreateDto) { var patient = _mapper.Map(patientCreateDto); patient.Id = _patientId; @@ -65,7 +65,7 @@ public IReadOnlyCollection GetAll() /// The identifier of the patient to update. /// The DTO containing updated patient data. /// The updated patient as a if successful; otherwise, null. - public GetPatientDto? Update(int id, UpdatePatientDto patientUpdateDto) + public GetPatientDto? Update(int id, CreateUpdatePatientDto patientUpdateDto) { var patient = _db.GetPatient(id); if (patient == null) @@ -109,4 +109,4 @@ public bool Delete(int id) _patientId--; return true; } -} \ No newline at end of file +} diff --git a/Clinic/Clinic.Api/Services/SpecializationServices.cs b/Clinic/Clinic.Application.Services/Services/SpecializationServices.cs similarity index 92% rename from Clinic/Clinic.Api/Services/SpecializationServices.cs rename to Clinic/Clinic.Application.Services/Services/SpecializationServices.cs index 9d6642c6c..6f0a5fc75 100644 --- a/Clinic/Clinic.Api/Services/SpecializationServices.cs +++ b/Clinic/Clinic.Application.Services/Services/SpecializationServices.cs @@ -1,10 +1,10 @@ using AutoMapper; using Clinic.Models.Entities; using Clinic.Application.Ports; -using Clinic.Api.DTOs.Specialization; -using Clinic.Api.Interfaces.Services; +using Clinic.Application.DTOs.Specialization; +using Clinic.Application.Interfaces.Services; -namespace Clinic.Api.Services; +namespace Clinic.Application.Services; /// /// Service class for managing specialization-related operations in the Clinic API. @@ -46,7 +46,7 @@ public IReadOnlyCollection GetAll() /// The identifier of the specialization to update. /// The DTO containing updated specialization data. /// The updated specialization as a DTO if successful; otherwise, null. - public GetSpecializationDto? Update(int id, UpdateSpecializationDto updateSpecializationDto) + public GetSpecializationDto? Update(int id, CreateUpdateSpecializationDto updateSpecializationDto) { var specialization = _mapper.Map(updateSpecializationDto); var updatedSpecialization = _db.UpdateSpecialization(id, specialization); @@ -62,7 +62,7 @@ public IReadOnlyCollection GetAll() /// /// The DTO containing specialization creation data. /// The created specialization as a if successful; otherwise, null. - public GetSpecializationDto? Create(CreateSpecializationDto createSpecializationDto) + public GetSpecializationDto? Create(CreateUpdateSpecializationDto createSpecializationDto) { var specialization = _mapper.Map(createSpecializationDto); specialization.Id = _specializationId; @@ -104,4 +104,4 @@ public bool Delete(int id) { return _db.RemoveSpecialization(id); } -} \ No newline at end of file +} diff --git a/Clinic/Clinic.Api/DTOs/Appointment/CreateAppointmentDto.cs b/Clinic/Clinic.Application/DTOs/Appointment/CreateUpdateAppointmentDto.cs similarity index 91% rename from Clinic/Clinic.Api/DTOs/Appointment/CreateAppointmentDto.cs rename to Clinic/Clinic.Application/DTOs/Appointment/CreateUpdateAppointmentDto.cs index 9777f01c5..27b2a73f7 100644 --- a/Clinic/Clinic.Api/DTOs/Appointment/CreateAppointmentDto.cs +++ b/Clinic/Clinic.Application/DTOs/Appointment/CreateUpdateAppointmentDto.cs @@ -1,10 +1,10 @@ -namespace Clinic.Api.DTOs.Appointment; +namespace Clinic.Application.DTOs.Appointment; /// /// DTO for creating a new appointment, including required patient/doctor information, /// scheduled date/time, room number, and return visit flag. /// -public class CreateAppointmentDto +public class CreateUpdateAppointmentDto { /// /// The full name of the patient for the appointment. diff --git a/Clinic/Clinic.Api/DTOs/Appointment/GetAppointmentDto.cs b/Clinic/Clinic.Application/DTOs/Appointment/GetAppointmentDto.cs similarity index 96% rename from Clinic/Clinic.Api/DTOs/Appointment/GetAppointmentDto.cs rename to Clinic/Clinic.Application/DTOs/Appointment/GetAppointmentDto.cs index d7bff0f62..974efe0a8 100644 --- a/Clinic/Clinic.Api/DTOs/Appointment/GetAppointmentDto.cs +++ b/Clinic/Clinic.Application/DTOs/Appointment/GetAppointmentDto.cs @@ -1,4 +1,4 @@ -namespace Clinic.Api.DTOs.Appointment; +namespace Clinic.Application.DTOs.Appointment; /// /// DTO for retrieving detailed information about an appointment, diff --git a/Clinic/Clinic.Api/DTOs/Doctor/CreateDoctorDto.cs b/Clinic/Clinic.Application/DTOs/Doctor/CreateUpdateDoctorDto.cs similarity index 86% rename from Clinic/Clinic.Api/DTOs/Doctor/CreateDoctorDto.cs rename to Clinic/Clinic.Application/DTOs/Doctor/CreateUpdateDoctorDto.cs index 19f3d8f68..bdca43a40 100644 --- a/Clinic/Clinic.Api/DTOs/Doctor/CreateDoctorDto.cs +++ b/Clinic/Clinic.Application/DTOs/Doctor/CreateUpdateDoctorDto.cs @@ -1,12 +1,12 @@ -using Clinic.Api.DTOs.Specialization; +using Clinic.Application.DTOs.Specialization; -namespace Clinic.Api.DTOs.Doctor; +namespace Clinic.Application.DTOs.Doctor; /// /// DTO for creating a new doctor, including required personal information, /// specialization list, and experience years. /// -public class CreateDoctorDto +public class CreateUpdateDoctorDto { /// /// Required: The passport number of the doctor. @@ -46,7 +46,7 @@ public class CreateDoctorDto /// /// Required: The list of specializations for the doctor. /// - public required List Specializations { get; set; } + public required List Specializations { get; set; } /// /// Required: The number of years of experience for the doctor. diff --git a/Clinic/Clinic.Api/DTOs/Doctor/GetDoctorDto.cs b/Clinic/Clinic.Application/DTOs/Doctor/GetDoctorDto.cs similarity index 97% rename from Clinic/Clinic.Api/DTOs/Doctor/GetDoctorDto.cs rename to Clinic/Clinic.Application/DTOs/Doctor/GetDoctorDto.cs index 39471125f..354a20167 100644 --- a/Clinic/Clinic.Api/DTOs/Doctor/GetDoctorDto.cs +++ b/Clinic/Clinic.Application/DTOs/Doctor/GetDoctorDto.cs @@ -1,4 +1,4 @@ -namespace Clinic.Api.DTOs.Doctor; +namespace Clinic.Application.DTOs.Doctor; /// /// DTO for retrieving detailed information about a doctor, diff --git a/Clinic/Clinic.Api/DTOs/Patient/CreatePatientDto.cs b/Clinic/Clinic.Application/DTOs/Patient/CreateUpdatePatientDto.cs similarity index 95% rename from Clinic/Clinic.Api/DTOs/Patient/CreatePatientDto.cs rename to Clinic/Clinic.Application/DTOs/Patient/CreateUpdatePatientDto.cs index 8c95f98d7..380d87779 100644 --- a/Clinic/Clinic.Api/DTOs/Patient/CreatePatientDto.cs +++ b/Clinic/Clinic.Application/DTOs/Patient/CreateUpdatePatientDto.cs @@ -1,10 +1,10 @@ -namespace Clinic.Api.DTOs.Patient; +namespace Clinic.Application.DTOs.Patient; /// /// DTO for creating a new patient, including required personal information, /// medical history, and contact details. /// -public class CreatePatientDto +public class CreateUpdatePatientDto { /// /// The patient's first name. diff --git a/Clinic/Clinic.Api/DTOs/Patient/GetPatientDto.cs b/Clinic/Clinic.Application/DTOs/Patient/GetPatientDto.cs similarity index 96% rename from Clinic/Clinic.Api/DTOs/Patient/GetPatientDto.cs rename to Clinic/Clinic.Application/DTOs/Patient/GetPatientDto.cs index 829165d9b..c6fb978e7 100644 --- a/Clinic/Clinic.Api/DTOs/Patient/GetPatientDto.cs +++ b/Clinic/Clinic.Application/DTOs/Patient/GetPatientDto.cs @@ -1,4 +1,4 @@ -namespace Clinic.Api.DTOs.Patient; +namespace Clinic.Application.DTOs.Patient; /// /// DTO for retrieving detailed information about a patient, diff --git a/Clinic/Clinic.Api/DTOs/Specialization/CreateSpecializationDto.cs b/Clinic/Clinic.Application/DTOs/Specialization/CreateUpdateSpecializationDto.cs similarity index 71% rename from Clinic/Clinic.Api/DTOs/Specialization/CreateSpecializationDto.cs rename to Clinic/Clinic.Application/DTOs/Specialization/CreateUpdateSpecializationDto.cs index 5f00061b0..ae9d97c90 100644 --- a/Clinic/Clinic.Api/DTOs/Specialization/CreateSpecializationDto.cs +++ b/Clinic/Clinic.Application/DTOs/Specialization/CreateUpdateSpecializationDto.cs @@ -1,9 +1,9 @@ -namespace Clinic.Api.DTOs.Specialization; +namespace Clinic.Application.DTOs.Specialization; /// /// DTO for creating a new specialization, including required name. /// -public class CreateSpecializationDto +public class CreateUpdateSpecializationDto { /// /// The name of the specialization to create. diff --git a/Clinic/Clinic.Api/DTOs/Specialization/GetSpecializationDto.cs b/Clinic/Clinic.Application/DTOs/Specialization/GetSpecializationDto.cs similarity index 89% rename from Clinic/Clinic.Api/DTOs/Specialization/GetSpecializationDto.cs rename to Clinic/Clinic.Application/DTOs/Specialization/GetSpecializationDto.cs index 5268e91a6..afb1ff261 100644 --- a/Clinic/Clinic.Api/DTOs/Specialization/GetSpecializationDto.cs +++ b/Clinic/Clinic.Application/DTOs/Specialization/GetSpecializationDto.cs @@ -1,4 +1,4 @@ -namespace Clinic.Api.DTOs.Specialization; +namespace Clinic.Application.DTOs.Specialization; /// /// DTO for retrieving detailed information about a specialization, diff --git a/Clinic/Clinic.Api/Interfaces/Services/IAppointmentService.cs b/Clinic/Clinic.Application/Interfaces/Services/IAppointmentService.cs similarity index 87% rename from Clinic/Clinic.Api/Interfaces/Services/IAppointmentService.cs rename to Clinic/Clinic.Application/Interfaces/Services/IAppointmentService.cs index 016d8dfd7..bf5df0f6e 100644 --- a/Clinic/Clinic.Api/Interfaces/Services/IAppointmentService.cs +++ b/Clinic/Clinic.Application/Interfaces/Services/IAppointmentService.cs @@ -1,12 +1,12 @@ -using Clinic.Api.DTOs.Appointment; +using Clinic.Application.DTOs.Appointment; -namespace Clinic.Api.Interfaces.Services; +namespace Clinic.Application.Interfaces.Services; /// /// Interface for appointment service operations. /// Provides methods for managing appointments in the clinic system. /// -public interface IAppointmentServices : IBaseServices +public interface IAppointmentServices : IBaseServices { /// /// Retrieves all appointments for a specific doctor. @@ -22,4 +22,3 @@ public interface IAppointmentServices : IBaseServicesA collection of appointment DTOs if the patient exists; otherwise, null. public IReadOnlyCollection? GetAppointmentsByPatient(int patientId); } - diff --git a/Clinic/Clinic.Api/Interfaces/Services/IBaseService.cs b/Clinic/Clinic.Application/Interfaces/Services/IBaseService.cs similarity index 80% rename from Clinic/Clinic.Api/Interfaces/Services/IBaseService.cs rename to Clinic/Clinic.Application/Interfaces/Services/IBaseService.cs index c2e5a5728..ba4fb107b 100644 --- a/Clinic/Clinic.Api/Interfaces/Services/IBaseService.cs +++ b/Clinic/Clinic.Application/Interfaces/Services/IBaseService.cs @@ -1,4 +1,4 @@ -namespace Clinic.Api.Interfaces.Services; +namespace Clinic.Application.Interfaces.Services; /// /// Generic interface for application services that provides CRUD operations. @@ -6,12 +6,10 @@ namespace Clinic.Api.Interfaces.Services; /// /// The entity type used in the database layer. /// The DTO type for retrieving entities. -/// The DTO type for creating new entities. -/// The DTO type for updating existing entities. -public interface IBaseServices +/// The DTO type for create and update operations. +public interface IBaseServices where TGetDto : class - where TCreateDto : class - where TUpdateDto : class + where TSaveDto : class { /// /// Retrieves all entities from the database and maps them to DTOs. @@ -31,7 +29,7 @@ public interface IBaseServices /// /// The DTO containing entity creation data. /// The created entity as a DTO if successful; otherwise, null. - public TGetDto? Create(TCreateDto createDto); + public TGetDto? Create(TSaveDto createDto); /// /// Updates an existing entity with the given identifier. @@ -39,7 +37,7 @@ public interface IBaseServices /// The identifier of the entity to update. /// The DTO containing updated entity data. /// The updated entity as a DTO if successful; otherwise, null. - public TGetDto? Update(int id, TUpdateDto updateDto); + public TGetDto? Update(int id, TSaveDto updateDto); /// /// Deletes an entity from the database by its identifier. @@ -48,4 +46,3 @@ public interface IBaseServices /// True if the entity was successfully deleted; otherwise, false. public bool Delete(int id); } - diff --git a/Clinic/Clinic.Api/Interfaces/Services/IDoctorService.cs b/Clinic/Clinic.Application/Interfaces/Services/IDoctorService.cs similarity index 64% rename from Clinic/Clinic.Api/Interfaces/Services/IDoctorService.cs rename to Clinic/Clinic.Application/Interfaces/Services/IDoctorService.cs index 1a5ae5e41..b342c5a2f 100644 --- a/Clinic/Clinic.Api/Interfaces/Services/IDoctorService.cs +++ b/Clinic/Clinic.Application/Interfaces/Services/IDoctorService.cs @@ -1,12 +1,9 @@ -using Clinic.Api.DTOs.Doctor; +using Clinic.Application.DTOs.Doctor; -namespace Clinic.Api.Interfaces.Services; +namespace Clinic.Application.Interfaces.Services; /// /// Interface for doctor service operations. /// Provides methods for managing doctors in the clinic system. /// -public interface IDoctorServices : IBaseServices -{ -} - +public interface IDoctorServices : IBaseServices; diff --git a/Clinic/Clinic.Api/Interfaces/Services/IPatientService.cs b/Clinic/Clinic.Application/Interfaces/Services/IPatientService.cs similarity index 64% rename from Clinic/Clinic.Api/Interfaces/Services/IPatientService.cs rename to Clinic/Clinic.Application/Interfaces/Services/IPatientService.cs index 3c75c902f..df7aaa9ad 100644 --- a/Clinic/Clinic.Api/Interfaces/Services/IPatientService.cs +++ b/Clinic/Clinic.Application/Interfaces/Services/IPatientService.cs @@ -1,11 +1,9 @@ -using Clinic.Api.DTOs.Patient; +using Clinic.Application.DTOs.Patient; -namespace Clinic.Api.Interfaces.Services; +namespace Clinic.Application.Interfaces.Services; /// /// Interface for patient service operations. /// Provides methods for managing patients in the clinic system. /// -public interface IPatientServices : IBaseServices -{ -} \ No newline at end of file +public interface IPatientServices : IBaseServices; diff --git a/Clinic/Clinic.Api/Interfaces/Services/ISpecializationService.cs b/Clinic/Clinic.Application/Interfaces/Services/ISpecializationService.cs similarity index 65% rename from Clinic/Clinic.Api/Interfaces/Services/ISpecializationService.cs rename to Clinic/Clinic.Application/Interfaces/Services/ISpecializationService.cs index 270100351..008b08c0e 100644 --- a/Clinic/Clinic.Api/Interfaces/Services/ISpecializationService.cs +++ b/Clinic/Clinic.Application/Interfaces/Services/ISpecializationService.cs @@ -1,13 +1,10 @@ -using Clinic.Api.DTOs.Specialization; +using Clinic.Application.DTOs.Specialization; -namespace Clinic.Api.Interfaces.Services; +namespace Clinic.Application.Interfaces.Services; /// /// Interface for specialization service operations. /// Provides methods for managing specializations in the clinic system. /// Note: Specializations do not support update operations. /// -public interface ISpecializationServices : IBaseServices -{ -} - +public interface ISpecializationServices : IBaseServices; diff --git a/Clinic/Clinic.Contracts/Clinic.Contracts.csproj b/Clinic/Clinic.Contracts/Clinic.Contracts.csproj new file mode 100644 index 000000000..f4556af88 --- /dev/null +++ b/Clinic/Clinic.Contracts/Clinic.Contracts.csproj @@ -0,0 +1,19 @@ + + + net8.0 + enable + enable + + + + + + + all + + + + + + + diff --git a/Clinic/Clinic.Contracts/Proto/appointment.proto b/Clinic/Clinic.Contracts/Proto/appointment.proto new file mode 100644 index 000000000..98d156a91 --- /dev/null +++ b/Clinic/Clinic.Contracts/Proto/appointment.proto @@ -0,0 +1,26 @@ +syntax = "proto3"; + +package clinic.contracts; + +option csharp_namespace = "Clinic.Contracts"; + +message Contract { + int32 patient_id = 1; + string patient_full_name = 2; + int32 doctor_id = 3; + string doctor_full_name = 4; + string appointment_time = 5; // ISO-8601 string + int32 room_number = 6; + bool is_return_visit = 7; +} + +message IngestResult { + int32 received = 1; + int32 saved = 2; + int32 failed = 3; + repeated string errors = 4; +} + +service ContractIngestor { + rpc IngestContracts (stream Contract) returns (IngestResult); +} diff --git a/Clinic/Clinic.InMemory/Clinic.InMemory.csproj b/Clinic/Clinic.InMemory/Clinic.InMemory.csproj index acbf5d8a3..8052819b6 100644 --- a/Clinic/Clinic.InMemory/Clinic.InMemory.csproj +++ b/Clinic/Clinic.InMemory/Clinic.InMemory.csproj @@ -6,10 +6,10 @@ enable - - - - - - + + + + + + diff --git a/Clinic/Clinic.InMemory/InMemoryAppointmentRepository.cs b/Clinic/Clinic.InMemory/InMemoryAppointmentRepository.cs index 09d6cd15d..c914865ea 100644 --- a/Clinic/Clinic.InMemory/InMemoryAppointmentRepository.cs +++ b/Clinic/Clinic.InMemory/InMemoryAppointmentRepository.cs @@ -1,7 +1,8 @@ +using Clinic.Application.Ports; using Clinic.Models.Entities; namespace Clinic.InMemory; -public sealed class InMemoryAppointmentRepository +public sealed class InMemoryAppointmentRepository : IAppointmentRepository { /// /// In-memory storage for appointments. @@ -13,7 +14,7 @@ public sealed class InMemoryAppointmentRepository /// /// The ID of the appointment to retrieve. /// The appointment with the specified ID, or null if not found. - public Appointment? GetAppointment(int Id) => _appointments.GetValueOrDefault(Id); + public Appointment? GetAppointment(int id) => _appointments.GetValueOrDefault(id); /// /// Retrieves all appointments from the in-memory storage. @@ -26,16 +27,16 @@ public sealed class InMemoryAppointmentRepository /// /// The ID of the doctor. /// A read-only collection of appointments for the specified doctor. - public IReadOnlyCollection GetAppointmentsByDoctor(int Id) => - _appointments.Values.Where(a => a.DoctorId == Id).ToList(); + public IReadOnlyCollection GetAppointmentsByDoctor(int doctorId) => + _appointments.Values.Where(a => a.DoctorId == doctorId).ToList(); /// /// Retrieves all appointments associated with a specific patient. /// /// The ID of the patient. /// A read-only collection of appointments for the specified patient. - public IReadOnlyCollection GetAppointmentsByPatient(int Id) => - _appointments.Values.Where(a => a.PatientId == Id).ToList(); + public IReadOnlyCollection GetAppointmentsByPatient(int patientId) => + _appointments.Values.Where(a => a.PatientId == patientId).ToList(); /// /// Adds a new appointment to the in-memory storage. @@ -70,11 +71,11 @@ public bool UpdateAppointment(Appointment appointment){ /// /// The ID of the appointment to remove. /// True if the appointment was successfully removed, false if it doesn't exist. - public bool RemoveAppointment(int Id) => _appointments.Remove(Id); + public bool RemoveAppointment(int id) => _appointments.Remove(id); /// /// Gets the count of appointments in the in-memory storage. /// /// The number of appointments. public int AppointmentCount() => _appointments.Count(); -} \ No newline at end of file +} diff --git a/Clinic/Clinic.InMemory/InMemoryDoctorRepository.cs b/Clinic/Clinic.InMemory/InMemoryDoctorRepository.cs index d478915e8..5cbfbaf8a 100644 --- a/Clinic/Clinic.InMemory/InMemoryDoctorRepository.cs +++ b/Clinic/Clinic.InMemory/InMemoryDoctorRepository.cs @@ -1,7 +1,8 @@ +using Clinic.Application.Ports; using Clinic.Models.Entities; namespace Clinic.InMemory; -public sealed class InMemoryDoctorRepository +public sealed class InMemoryDoctorRepository : IDoctorRepository { /// /// In-memory storage for doctors. @@ -13,7 +14,7 @@ public sealed class InMemoryDoctorRepository /// /// The ID of the doctor to retrieve. /// The doctor with the specified ID, or null if not found. - public Doctor? GetDoctor(int Id) => _doctors.GetValueOrDefault(Id); + public Doctor? GetDoctor(int id) => _doctors.GetValueOrDefault(id); /// /// Retrieves all doctors from the in-memory storage. @@ -52,7 +53,7 @@ public bool UpdateDoctor(Doctor doctor){ /// /// The ID of the doctor to remove. /// True if the doctor was successfully removed, false if it doesn't exist. - public bool RemoveDoctor(int Id) => _doctors.Remove(Id); + public bool RemoveDoctor(int id) => _doctors.Remove(id); /// /// Gets the count of doctors in the in-memory storage. @@ -60,4 +61,3 @@ public bool UpdateDoctor(Doctor doctor){ /// The number of doctors. public int DoctorCount() => _doctors.Count(); } - diff --git a/Clinic/Clinic.InMemory/InMemoryPatientRepository.cs b/Clinic/Clinic.InMemory/InMemoryPatientRepository.cs index 1c568ad40..74207d00c 100644 --- a/Clinic/Clinic.InMemory/InMemoryPatientRepository.cs +++ b/Clinic/Clinic.InMemory/InMemoryPatientRepository.cs @@ -1,13 +1,21 @@ +using Clinic.Application.Ports; using Clinic.Models.Entities; namespace Clinic.InMemory; -public sealed class InMemoryPatientRepository +public sealed class InMemoryPatientRepository : IPatientRepository { /// /// In-memory storage for patients. /// private readonly Dictionary _patients = new(); + /// + /// Retrieves a patient by their ID. + /// + /// The ID of the patient to retrieve. + /// The patient with the specified ID, or null if not found. + public Patient? GetPatient(int id) => _patients.GetValueOrDefault(id); + /// /// Retrieves all patients from the in-memory storage. /// @@ -46,11 +54,11 @@ public bool UpdatePatient(Patient patient){ /// /// The ID of the patient to remove. /// True if the patient was successfully removed, false if it doesn't exist. - public bool RemovePatient(int Id) => _patients.Remove(Id); + public bool RemovePatient(int id) => _patients.Remove(id); /// /// Gets the count of patients in the in-memory storage. /// /// The number of patients. public int PatientCount() => _patients.Count; -} \ No newline at end of file +} diff --git a/Clinic/Clinic.InMemory/InMemorySpecializationRepository.cs b/Clinic/Clinic.InMemory/InMemorySpecializationRepository.cs index 837a030e4..1e35f3fa0 100644 --- a/Clinic/Clinic.InMemory/InMemorySpecializationRepository.cs +++ b/Clinic/Clinic.InMemory/InMemorySpecializationRepository.cs @@ -1,7 +1,8 @@ +using Clinic.Application.Ports; using Clinic.Models.Entities; namespace Clinic.InMemory; -public sealed class InMemorySpecializationRepository +public sealed class InMemorySpecializationRepository : ISpecializationRepository { /// /// In-memory storage for specializations. @@ -68,4 +69,4 @@ public bool AddSpecialization(Specialization specialization) _specializations[id].Name = specialization.Name; return _specializations[id]; } -} \ No newline at end of file +} diff --git a/Clinic/Clinic.ServiceDefaults/Clinic.ServiceDefaults.csproj b/Clinic/Clinic.ServiceDefaults/Clinic.ServiceDefaults.csproj deleted file mode 100644 index 4084d8667..000000000 --- a/Clinic/Clinic.ServiceDefaults/Clinic.ServiceDefaults.csproj +++ /dev/null @@ -1,22 +0,0 @@ - - - - net8.0 - enable - enable - true - - - - - - - - - - - - - - - diff --git a/Clinic/Clinic.ServiceDefaults/Extensions.cs b/Clinic/Clinic.ServiceDefaults/Extensions.cs deleted file mode 100644 index 63aa3ba3f..000000000 --- a/Clinic/Clinic.ServiceDefaults/Extensions.cs +++ /dev/null @@ -1,127 +0,0 @@ -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Diagnostics.HealthChecks; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Diagnostics.HealthChecks; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.ServiceDiscovery; -using OpenTelemetry; -using OpenTelemetry.Metrics; -using OpenTelemetry.Trace; - -namespace Microsoft.Extensions.Hosting; - -// Adds common Aspire services: service discqovery, resilience, health checks, and OpenTelemetry. -// This project should be referenced by each service project in your solution. -// To learn more about using this project, see https://aka.ms/dotnet/aspire/service-defaults -public static class Extensions -{ - private const string HealthEndpointPath = "/health"; - private const string AlivenessEndpointPath = "/alive"; - - public static TBuilder AddServiceDefaults(this TBuilder builder) where TBuilder : IHostApplicationBuilder - { - builder.ConfigureOpenTelemetry(); - - builder.AddDefaultHealthChecks(); - - builder.Services.AddServiceDiscovery(); - - builder.Services.ConfigureHttpClientDefaults(http => - { - // Turn on resilience by default - http.AddStandardResilienceHandler(); - - // Turn on service discovery by default - http.AddServiceDiscovery(); - }); - - // Uncomment the following to restrict the allowed schemes for service discovery. - // builder.Services.Configure(options => - // { - // options.AllowedSchemes = ["https"]; - // }); - - return builder; - } - - public static TBuilder ConfigureOpenTelemetry(this TBuilder builder) where TBuilder : IHostApplicationBuilder - { - builder.Logging.AddOpenTelemetry(logging => - { - logging.IncludeFormattedMessage = true; - logging.IncludeScopes = true; - }); - - builder.Services.AddOpenTelemetry() - .WithMetrics(metrics => - { - metrics.AddAspNetCoreInstrumentation() - .AddHttpClientInstrumentation() - .AddRuntimeInstrumentation(); - }) - .WithTracing(tracing => - { - tracing.AddSource(builder.Environment.ApplicationName) - .AddAspNetCoreInstrumentation(tracing => - // Exclude health check requests from tracing - tracing.Filter = context => - !context.Request.Path.StartsWithSegments(HealthEndpointPath) - && !context.Request.Path.StartsWithSegments(AlivenessEndpointPath) - ) - // Uncomment the following line to enable gRPC instrumentation (requires the OpenTelemetry.Instrumentation.GrpcNetClient package) - //.AddGrpcClientInstrumentation() - .AddHttpClientInstrumentation(); - }); - - builder.AddOpenTelemetryExporters(); - - return builder; - } - - private static TBuilder AddOpenTelemetryExporters(this TBuilder builder) where TBuilder : IHostApplicationBuilder - { - var useOtlpExporter = !string.IsNullOrWhiteSpace(builder.Configuration["OTEL_EXPORTER_OTLP_ENDPOINT"]); - - if (useOtlpExporter) - { - builder.Services.AddOpenTelemetry().UseOtlpExporter(); - } - - // Uncomment the following lines to enable the Azure Monitor exporter (requires the Azure.Monitor.OpenTelemetry.AspNetCore package) - //if (!string.IsNullOrEmpty(builder.Configuration["APPLICATIONINSIGHTS_CONNECTION_STRING"])) - //{ - // builder.Services.AddOpenTelemetry() - // .UseAzureMonitor(); - //} - - return builder; - } - - public static TBuilder AddDefaultHealthChecks(this TBuilder builder) where TBuilder : IHostApplicationBuilder - { - builder.Services.AddHealthChecks() - // Add a default liveness check to ensure app is responsive - .AddCheck("self", () => HealthCheckResult.Healthy(), ["live"]); - - return builder; - } - - public static WebApplication MapDefaultEndpoints(this WebApplication app) - { - // Adding health checks endpoints to applications in non-development environments has security implications. - // See https://aka.ms/dotnet/aspire/healthchecks for details before enabling these endpoints in non-development environments. - if (app.Environment.IsDevelopment()) - { - // All health checks must pass for app to be considered ready to accept traffic after starting - app.MapHealthChecks(HealthEndpointPath); - - // Only health checks tagged with the "live" tag must pass for app to be considered alive - app.MapHealthChecks(AlivenessEndpointPath, new HealthCheckOptions - { - Predicate = r => r.Tags.Contains("live") - }); - } - - return app; - } -} diff --git a/Clinic/Clinic.Tests/Clinic.Tests.csproj b/Clinic/Clinic.Tests/Clinic.Tests.csproj index 8650b5bd6..0ba0d0ca2 100644 --- a/Clinic/Clinic.Tests/Clinic.Tests.csproj +++ b/Clinic/Clinic.Tests/Clinic.Tests.csproj @@ -26,6 +26,7 @@ + diff --git a/Clinic/Clinic.Tests/ClinicTests.cs b/Clinic/Clinic.Tests/ClinicTests.cs index b760e287a..fcb2142df 100644 --- a/Clinic/Clinic.Tests/ClinicTests.cs +++ b/Clinic/Clinic.Tests/ClinicTests.cs @@ -1,5 +1,5 @@ using Clinic.DataBase; -using Clinic.Api.Services; +using Clinic.Application.Services; namespace Clinic.Tests; @@ -19,7 +19,9 @@ public void GetPatientsByDoctor_WhenDoctorIsSpecified_ReturnsPatientsOrderedByNa var doctorId = 3; var patientsByDoctor = new List {3}; - var result = testServices.GetPatientsByDoctorOrderedByFullName(doctorId).Select(p => p.Id); + var patients = testServices.GetPatientsByDoctorOrderedByFullName(doctorId); + Assert.NotNull(patients); + var result = patients!.Select(p => p.Id); Assert.Equal(patientsByDoctor, result); } diff --git a/Clinic/Clinic.sln b/Clinic/Clinic.sln index c8b3a7686..1ea9afa5d 100755 --- a/Clinic/Clinic.sln +++ b/Clinic/Clinic.sln @@ -13,12 +13,16 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Clinic.Tests", "Clinic.Test EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Clinic.AppHost", "Clinic.AppHost\Clinic.AppHost.csproj", "{7BD8C638-56B5-438F-BE87-2A628C5F7C8F}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Clinic.ServiceDefaults", "Clinic.ServiceDefaults\Clinic.ServiceDefaults.csproj", "{472C1C25-CCA0-4AA2-910C-150DD406AE5F}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Clinic.Application", "Clinic.Application\Clinic.Application.csproj", "{43A97106-90F1-4FC0-B66E-1D6DA5950260}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Clinic.InMemory", "Clinic.InMemory\Clinic.InMemory.csproj", "{C77BBDCF-DF57-404C-B821-9DA7E6C06AD7}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Clinic.Contracts", "Clinic.Contracts\Clinic.Contracts.csproj", "{A17E9A18-4BE4-4C25-88F8-3E0B6C264B55}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Clinic.AppiontmentGenerator", "Clinic.AppiontmentGenerator\Clinic.AppiontmentGenerator.csproj", "{8D7E3ED3-6C45-4A9F-8C09-A0F4D50F1B9F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Clinic.Application.Services", "Clinic.Application.Services\Clinic.Application.Services.csproj", "{E53E900D-1022-490F-B559-E74774E27FE3}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -89,18 +93,6 @@ Global {7BD8C638-56B5-438F-BE87-2A628C5F7C8F}.Release|x64.Build.0 = Release|Any CPU {7BD8C638-56B5-438F-BE87-2A628C5F7C8F}.Release|x86.ActiveCfg = Release|Any CPU {7BD8C638-56B5-438F-BE87-2A628C5F7C8F}.Release|x86.Build.0 = Release|Any CPU - {472C1C25-CCA0-4AA2-910C-150DD406AE5F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {472C1C25-CCA0-4AA2-910C-150DD406AE5F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {472C1C25-CCA0-4AA2-910C-150DD406AE5F}.Debug|x64.ActiveCfg = Debug|Any CPU - {472C1C25-CCA0-4AA2-910C-150DD406AE5F}.Debug|x64.Build.0 = Debug|Any CPU - {472C1C25-CCA0-4AA2-910C-150DD406AE5F}.Debug|x86.ActiveCfg = Debug|Any CPU - {472C1C25-CCA0-4AA2-910C-150DD406AE5F}.Debug|x86.Build.0 = Debug|Any CPU - {472C1C25-CCA0-4AA2-910C-150DD406AE5F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {472C1C25-CCA0-4AA2-910C-150DD406AE5F}.Release|Any CPU.Build.0 = Release|Any CPU - {472C1C25-CCA0-4AA2-910C-150DD406AE5F}.Release|x64.ActiveCfg = Release|Any CPU - {472C1C25-CCA0-4AA2-910C-150DD406AE5F}.Release|x64.Build.0 = Release|Any CPU - {472C1C25-CCA0-4AA2-910C-150DD406AE5F}.Release|x86.ActiveCfg = Release|Any CPU - {472C1C25-CCA0-4AA2-910C-150DD406AE5F}.Release|x86.Build.0 = Release|Any CPU {43A97106-90F1-4FC0-B66E-1D6DA5950260}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {43A97106-90F1-4FC0-B66E-1D6DA5950260}.Debug|Any CPU.Build.0 = Debug|Any CPU {43A97106-90F1-4FC0-B66E-1D6DA5950260}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -125,6 +117,42 @@ Global {C77BBDCF-DF57-404C-B821-9DA7E6C06AD7}.Release|x64.Build.0 = Release|Any CPU {C77BBDCF-DF57-404C-B821-9DA7E6C06AD7}.Release|x86.ActiveCfg = Release|Any CPU {C77BBDCF-DF57-404C-B821-9DA7E6C06AD7}.Release|x86.Build.0 = Release|Any CPU + {A17E9A18-4BE4-4C25-88F8-3E0B6C264B55}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A17E9A18-4BE4-4C25-88F8-3E0B6C264B55}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A17E9A18-4BE4-4C25-88F8-3E0B6C264B55}.Debug|x64.ActiveCfg = Debug|Any CPU + {A17E9A18-4BE4-4C25-88F8-3E0B6C264B55}.Debug|x64.Build.0 = Debug|Any CPU + {A17E9A18-4BE4-4C25-88F8-3E0B6C264B55}.Debug|x86.ActiveCfg = Debug|Any CPU + {A17E9A18-4BE4-4C25-88F8-3E0B6C264B55}.Debug|x86.Build.0 = Debug|Any CPU + {A17E9A18-4BE4-4C25-88F8-3E0B6C264B55}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A17E9A18-4BE4-4C25-88F8-3E0B6C264B55}.Release|Any CPU.Build.0 = Release|Any CPU + {A17E9A18-4BE4-4C25-88F8-3E0B6C264B55}.Release|x64.ActiveCfg = Release|Any CPU + {A17E9A18-4BE4-4C25-88F8-3E0B6C264B55}.Release|x64.Build.0 = Release|Any CPU + {A17E9A18-4BE4-4C25-88F8-3E0B6C264B55}.Release|x86.ActiveCfg = Release|Any CPU + {A17E9A18-4BE4-4C25-88F8-3E0B6C264B55}.Release|x86.Build.0 = Release|Any CPU + {8D7E3ED3-6C45-4A9F-8C09-A0F4D50F1B9F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8D7E3ED3-6C45-4A9F-8C09-A0F4D50F1B9F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8D7E3ED3-6C45-4A9F-8C09-A0F4D50F1B9F}.Debug|x64.ActiveCfg = Debug|Any CPU + {8D7E3ED3-6C45-4A9F-8C09-A0F4D50F1B9F}.Debug|x64.Build.0 = Debug|Any CPU + {8D7E3ED3-6C45-4A9F-8C09-A0F4D50F1B9F}.Debug|x86.ActiveCfg = Debug|Any CPU + {8D7E3ED3-6C45-4A9F-8C09-A0F4D50F1B9F}.Debug|x86.Build.0 = Debug|Any CPU + {8D7E3ED3-6C45-4A9F-8C09-A0F4D50F1B9F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8D7E3ED3-6C45-4A9F-8C09-A0F4D50F1B9F}.Release|Any CPU.Build.0 = Release|Any CPU + {8D7E3ED3-6C45-4A9F-8C09-A0F4D50F1B9F}.Release|x64.ActiveCfg = Release|Any CPU + {8D7E3ED3-6C45-4A9F-8C09-A0F4D50F1B9F}.Release|x64.Build.0 = Release|Any CPU + {8D7E3ED3-6C45-4A9F-8C09-A0F4D50F1B9F}.Release|x86.ActiveCfg = Release|Any CPU + {8D7E3ED3-6C45-4A9F-8C09-A0F4D50F1B9F}.Release|x86.Build.0 = Release|Any CPU + {E53E900D-1022-490F-B559-E74774E27FE3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E53E900D-1022-490F-B559-E74774E27FE3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E53E900D-1022-490F-B559-E74774E27FE3}.Debug|x64.ActiveCfg = Debug|Any CPU + {E53E900D-1022-490F-B559-E74774E27FE3}.Debug|x64.Build.0 = Debug|Any CPU + {E53E900D-1022-490F-B559-E74774E27FE3}.Debug|x86.ActiveCfg = Debug|Any CPU + {E53E900D-1022-490F-B559-E74774E27FE3}.Debug|x86.Build.0 = Debug|Any CPU + {E53E900D-1022-490F-B559-E74774E27FE3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E53E900D-1022-490F-B559-E74774E27FE3}.Release|Any CPU.Build.0 = Release|Any CPU + {E53E900D-1022-490F-B559-E74774E27FE3}.Release|x64.ActiveCfg = Release|Any CPU + {E53E900D-1022-490F-B559-E74774E27FE3}.Release|x64.Build.0 = Release|Any CPU + {E53E900D-1022-490F-B559-E74774E27FE3}.Release|x86.ActiveCfg = Release|Any CPU + {E53E900D-1022-490F-B559-E74774E27FE3}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From eba2b969005224fad8e56f1ef105b55dafd0fe28 Mon Sep 17 00:00:00 2001 From: maeosha Date: Tue, 24 Feb 2026 14:53:02 +0300 Subject: [PATCH 55/56] feat: Add AppointmentGenerator project and update references in solution --- Clinic/Clinic.AppHost/AppHost.cs | 2 +- .../Clinic.AppointmentGenerator.csproj} | 0 .../Program.cs | 0 .../appsettings.json | 0 Clinic/Clinic.sln | 2 +- 5 files changed, 2 insertions(+), 2 deletions(-) rename Clinic/{Clinic.AppiontmentGenerator/Clinic.AppiontmentGenerator.csproj => Clinic.AppointmentGenerator/Clinic.AppointmentGenerator.csproj} (100%) rename Clinic/{Clinic.AppiontmentGenerator => Clinic.AppointmentGenerator}/Program.cs (100%) rename Clinic/{Clinic.AppiontmentGenerator => Clinic.AppointmentGenerator}/appsettings.json (100%) diff --git a/Clinic/Clinic.AppHost/AppHost.cs b/Clinic/Clinic.AppHost/AppHost.cs index ba047e8b6..36241512a 100644 --- a/Clinic/Clinic.AppHost/AppHost.cs +++ b/Clinic/Clinic.AppHost/AppHost.cs @@ -8,7 +8,7 @@ .WaitFor(postgresql) .WithExternalHttpEndpoints(); -var appiontmentGenerator = builder.AddProject("clinic-appiontment-generator", "../Clinic.AppiontmentGenerator/Clinic.AppiontmentGenerator.csproj") +var appointmentGenerator = builder.AddProject("clinic-appointment-generator", "../Clinic.AppointmentGenerator/Clinic.AppointmentGenerator.csproj") .WithReference(postgresql) .WithReference(api) .WaitFor(postgresql) diff --git a/Clinic/Clinic.AppiontmentGenerator/Clinic.AppiontmentGenerator.csproj b/Clinic/Clinic.AppointmentGenerator/Clinic.AppointmentGenerator.csproj similarity index 100% rename from Clinic/Clinic.AppiontmentGenerator/Clinic.AppiontmentGenerator.csproj rename to Clinic/Clinic.AppointmentGenerator/Clinic.AppointmentGenerator.csproj diff --git a/Clinic/Clinic.AppiontmentGenerator/Program.cs b/Clinic/Clinic.AppointmentGenerator/Program.cs similarity index 100% rename from Clinic/Clinic.AppiontmentGenerator/Program.cs rename to Clinic/Clinic.AppointmentGenerator/Program.cs diff --git a/Clinic/Clinic.AppiontmentGenerator/appsettings.json b/Clinic/Clinic.AppointmentGenerator/appsettings.json similarity index 100% rename from Clinic/Clinic.AppiontmentGenerator/appsettings.json rename to Clinic/Clinic.AppointmentGenerator/appsettings.json diff --git a/Clinic/Clinic.sln b/Clinic/Clinic.sln index 1ea9afa5d..07efce8b8 100755 --- a/Clinic/Clinic.sln +++ b/Clinic/Clinic.sln @@ -19,7 +19,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Clinic.InMemory", "Clinic.I EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Clinic.Contracts", "Clinic.Contracts\Clinic.Contracts.csproj", "{A17E9A18-4BE4-4C25-88F8-3E0B6C264B55}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Clinic.AppiontmentGenerator", "Clinic.AppiontmentGenerator\Clinic.AppiontmentGenerator.csproj", "{8D7E3ED3-6C45-4A9F-8C09-A0F4D50F1B9F}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Clinic.AppointmentGenerator", "Clinic.AppointmentGenerator\Clinic.AppointmentGenerator.csproj", "{8D7E3ED3-6C45-4A9F-8C09-A0F4D50F1B9F}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Clinic.Application.Services", "Clinic.Application.Services\Clinic.Application.Services.csproj", "{E53E900D-1022-490F-B559-E74774E27FE3}" EndProject From 5ed8f2785af9ee2b03f18fc066a3ecf7f61c6b34 Mon Sep 17 00:00:00 2001 From: maeosha Date: Tue, 24 Feb 2026 19:24:29 +0300 Subject: [PATCH 56/56] feat: Add discussion and pull request templates, enhance appointment generator configuration --- .github/DISCUSSION_TEMPLATE/questions.yml | 39 +++++ ...20\276\321\200\320\275\320\276\320\271.md" | 33 ++++ .github/PULL_REQUEST_TEMPLATE.md | 6 + .github/workflows/setup_pr.yml | 77 ++++++++ .../Clinic.Api/Grpc/ContractIngestService.cs | 6 - Clinic/Clinic.AppHost/AppHost.cs | 16 +- .../Clinic.AppointmentGenerator.csproj | 2 +- Clinic/Clinic.AppointmentGenerator/Program.cs | 164 +++++++----------- .../appsettings.json | 33 +++- 9 files changed, 259 insertions(+), 117 deletions(-) create mode 100644 .github/DISCUSSION_TEMPLATE/questions.yml create mode 100644 ".github/ISSUE_TEMPLATE/\320\262\320\276\320\277\321\200\320\276\321\201-\320\277\320\276-\320\273\320\260\320\261\320\276\321\200\320\260\321\202\320\276\321\200\320\275\320\276\320\271.md" create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 .github/workflows/setup_pr.yml diff --git a/.github/DISCUSSION_TEMPLATE/questions.yml b/.github/DISCUSSION_TEMPLATE/questions.yml new file mode 100644 index 000000000..b94708065 --- /dev/null +++ b/.github/DISCUSSION_TEMPLATE/questions.yml @@ -0,0 +1,39 @@ +labels: [q&a] +body: + - type: input + id: fio + attributes: + label: Привет, меня зовут + description: | + Напиши свои ФИО и номер группы, чтобы тебе ответил преподаватель, который ведет у тебя пары + placeholder: | + Фамилия И.О. 641Х + validations: + required: true + + - type: dropdown + id: lab + attributes: + label: У меня вопрос по + description: | + Выбери лабораторную, которая вызвала трудности + multiple: true + options: + - 1 лабораторной работе (Классы) + - 2 лабораторной работе (Сервер) + - 3 лабораторной работе (ORM) + - 4 лабораторной работе (Инфраструктура) + - 5 лабораторной работе (Клиент) + validations: + required: true + + - type: textarea + id: details + attributes: + label: Описание проблемы + description: | + Подробно опиши проблему, с которой ты столкнулся при выполнении лабораторной + placeholder: | + Также было бы крайне полезно привести помимо текстового описания проблемы скриншоты и фрагменты кода + validations: + required: true \ No newline at end of file diff --git "a/.github/ISSUE_TEMPLATE/\320\262\320\276\320\277\321\200\320\276\321\201-\320\277\320\276-\320\273\320\260\320\261\320\276\321\200\320\260\321\202\320\276\321\200\320\275\320\276\320\271.md" "b/.github/ISSUE_TEMPLATE/\320\262\320\276\320\277\321\200\320\276\321\201-\320\277\320\276-\320\273\320\260\320\261\320\276\321\200\320\260\321\202\320\276\321\200\320\275\320\276\320\271.md" new file mode 100644 index 000000000..d876c2378 --- /dev/null +++ "b/.github/ISSUE_TEMPLATE/\320\262\320\276\320\277\321\200\320\276\321\201-\320\277\320\276-\320\273\320\260\320\261\320\276\321\200\320\260\321\202\320\276\321\200\320\275\320\276\320\271.md" @@ -0,0 +1,33 @@ +--- +name: Вопрос по лабораторной +about: Этот шаблон предназначен для того, чтобы студенты могли задать вопрос по лабораторной +title: Вопрос по лабораторной +labels: '' +assignees: DmitryKrakhmalev, alxmcs, danlla + +--- + +**Меня зовут:** +Укажите свои ФИО + +**Я из группы:** +Укажите номер группы + +**У меня вопрос по лабе:** +Укажите номер и название лабораторной, по которой появился вопрос. + +**Мой вопрос:** +Максимально подробно опишите, что вы хотите узнать/что у вас не получается/что у вас не работает. При необходимости, добавьте примеры кода. Примеры кода должны быть оформлены с использованием md разметки, чтобы их можно было удобно воспринимать: + +```cs +public class Program +{ + public static void Main(string[] args) + { + System.Console.WriteLine("Hello, World!"); + } +} +``` + +**Дополнительная информация** +Опишите тут все, что не попадает под перечисленные ранее категории (если в том есть необходимость). \ No newline at end of file diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 000000000..13f73dbb5 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,6 @@ +**ФИО:** Фамилия Имя +**Номер группы:** 641Х +**Номер лабораторной:** Х +**Номер варианта:** ХХ +**Краткое описание предметной области:** Пункт велопроката/библиотека/т.д. +**Краткое описание добавленных фич:** Добавлена доменная модель/юнит-тесты/т.д. \ No newline at end of file diff --git a/.github/workflows/setup_pr.yml b/.github/workflows/setup_pr.yml new file mode 100644 index 000000000..6480c8052 --- /dev/null +++ b/.github/workflows/setup_pr.yml @@ -0,0 +1,77 @@ +name: Setup PR for code review + +on: + pull_request_target: + types: [opened, reopened] + +permissions: write-all + +jobs: + + assign: + runs-on: ubuntu-latest + steps: + - name: Parsing your PR title for lab number + env: + TITLE: ${{ github.event.pull_request.title }} + run: | + SUB='Лаб.' + for VAR in 1 2 3 4 5 + do + if (echo $TITLE | grep -iqF "$SUB$VAR" )|| (echo $TITLE | grep -iqF "$SUB $VAR"); then + echo "LABEL=Lab $VAR" >> "$GITHUB_ENV" + break + fi + done + for VAR in 6411 6412 6413 + do + if (echo $TITLE | grep -iqF "$VAR" ); then + echo "GROUP=$VAR" >> "$GITHUB_ENV" + break + fi + done + + - name: Checking your lab number + run: | + if [[ $LABEL == '' ]]; then + echo "Your PR caption is not composed correctly" + exit 1 + fi + echo Your number was parsed correctly - ${{ env.LABEL }} + + - name: Checking your group number + run: | + if [[ $GROUP == '' ]]; then + echo "Your PR caption is not composed correctly" + exit 1 + fi + echo Your group was parsed correctly - ${{ env.GROUP }} + + - name: Setting PR labels + uses: actions-ecosystem/action-add-labels@v1 + with: + labels: | + ${{ env.LABEL }} + In progress + + - name: Setting reviewer + if: env.GROUP == '6411' + uses: AveryCameronUofR/add-reviewer-gh-action@1.0.3 + with: + reviewers: "danlla" + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Setting reviewer + if: env.GROUP == '6412' + uses: AveryCameronUofR/add-reviewer-gh-action@1.0.3 + with: + reviewers: "alxmcs" + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Setting reviewer + if: env.GROUP == '6413' + uses: AveryCameronUofR/add-reviewer-gh-action@1.0.3 + with: + reviewers: "DmitryKrakhmalev" + token: ${{ secrets.GITHUB_TOKEN }} + \ No newline at end of file diff --git a/Clinic/Clinic.Api/Grpc/ContractIngestService.cs b/Clinic/Clinic.Api/Grpc/ContractIngestService.cs index 4f711f1f0..13a875a8a 100644 --- a/Clinic/Clinic.Api/Grpc/ContractIngestService.cs +++ b/Clinic/Clinic.Api/Grpc/ContractIngestService.cs @@ -55,12 +55,6 @@ public override async Task IngestContracts(IAsyncStreamReader - + diff --git a/Clinic/Clinic.AppointmentGenerator/Program.cs b/Clinic/Clinic.AppointmentGenerator/Program.cs index e70939204..3bf620f21 100644 --- a/Clinic/Clinic.AppointmentGenerator/Program.cs +++ b/Clinic/Clinic.AppointmentGenerator/Program.cs @@ -2,7 +2,19 @@ using Clinic.Contracts; using Grpc.Net.Client; using Microsoft.Extensions.Configuration; -using Npgsql; +using Microsoft.Extensions.Logging; + +using var loggerFactory = LoggerFactory.Create(builder => +{ + builder + .SetMinimumLevel(LogLevel.Information) + .AddSimpleConsole(options => + { + options.SingleLine = true; + options.TimestampFormat = "HH:mm:ss "; + }); +}); +var logger = loggerFactory.CreateLogger("Clinic.AppointmentGenerator"); var configuration = new ConfigurationBuilder() .AddJsonFile("appsettings.json", optional: true) @@ -12,7 +24,7 @@ var endpoint = configuration["Grpc:Endpoint"]; if (string.IsNullOrWhiteSpace(endpoint)) { - Console.Error.WriteLine("Missing Grpc:Endpoint configuration."); + logger.LogError("Missing Grpc:Endpoint configuration."); return 1; } @@ -23,108 +35,54 @@ endpoint = aspireHttpsEndpoint ?? aspireHttpEndpoint ?? endpoint; } -var connectionString = configuration.GetConnectionString("ClinicDb"); -if (string.IsNullOrWhiteSpace(connectionString)) -{ - Console.Error.WriteLine("Missing ConnectionStrings:ClinicDb configuration."); - return 1; -} - if (endpoint.StartsWith("http://", StringComparison.OrdinalIgnoreCase)) { + logger.LogWarning("Using insecure gRPC endpoint {Endpoint}. Enabling Http2UnencryptedSupport.", endpoint); AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true); } var count = configuration.GetValue("Generator:Count", 10); var roomNumbers = configuration.GetSection("Generator:RoomNumbers").Get() ?? [101, 102, 201, 202]; +var patients = configuration.GetSection("Generator:Patients").Get() ?? +[ + new PersonSeed(1, "Иванов Иван Иванович"), + new PersonSeed(2, "Петров Петр Петрович"), + new PersonSeed(3, "Сидорова Мария Алексеевна") +]; +var doctors = configuration.GetSection("Generator:Doctors").Get() ?? +[ + new PersonSeed(1, "Смирнов Алексей Николаевич"), + new PersonSeed(2, "Кузнецова Елена Сергеевна"), + new PersonSeed(3, "Волков Дмитрий Олегович") +]; -var patients = new List<(int Id, string FullName)>(); -var doctors = new List<(int Id, string FullName)>(); var knownVisitPairs = new HashSet<(int PatientId, int DoctorId)>(); -const int maxAttempts = 20; - -// Attempt to read patients, doctors and known visit pairs with retry logic for database readiness. -for (var attempt = 1; attempt <= maxAttempts; attempt++) -{ - try - { - patients.Clear(); - doctors.Clear(); - knownVisitPairs.Clear(); - - await using var connection = new NpgsqlConnection(connectionString); - await connection.OpenAsync(); - - await using (var command = new NpgsqlCommand("SELECT \"Id\", \"LastName\", \"FirstName\", \"Patronymic\" FROM \"Patients\"", connection)) - await using (var reader = await command.ExecuteReaderAsync()) - { - while (await reader.ReadAsync()) - { - var id = reader.GetInt32(0); - var lastName = reader.GetString(1); - var firstName = reader.GetString(2); - var patronymic = reader.IsDBNull(3) ? string.Empty : reader.GetString(3); - var fullName = BuildFullName(lastName, firstName, patronymic); - patients.Add((id, fullName)); - } - } - - await using (var command = new NpgsqlCommand("SELECT \"Id\", \"LastName\", \"FirstName\", \"Patronymic\" FROM \"Doctors\"", connection)) - await using (var reader = await command.ExecuteReaderAsync()) - { - while (await reader.ReadAsync()) - { - var id = reader.GetInt32(0); - var lastName = reader.GetString(1); - var firstName = reader.GetString(2); - var patronymic = reader.IsDBNull(3) ? string.Empty : reader.GetString(3); - var fullName = BuildFullName(lastName, firstName, patronymic); - doctors.Add((id, fullName)); - } - } - - await using (var command = new NpgsqlCommand("SELECT DISTINCT \"PatientId\", \"DoctorId\" FROM \"Appointments\"", connection)) - await using (var reader = await command.ExecuteReaderAsync()) - { - while (await reader.ReadAsync()) - { - knownVisitPairs.Add((reader.GetInt32(0), reader.GetInt32(1))); - } - } - - break; - } - catch (PostgresException ex) when (ex.SqlState == "42P01" && attempt < maxAttempts) - { - Console.WriteLine($"Database schema is not ready yet (attempt {attempt}/{maxAttempts}). Retrying..."); - await Task.Delay(TimeSpan.FromSeconds(2)); - } - catch (NpgsqlException) when (attempt < maxAttempts) - { - Console.WriteLine($"Database is not ready yet (attempt {attempt}/{maxAttempts}). Retrying..."); - await Task.Delay(TimeSpan.FromSeconds(2)); - } -} - if (count <= 0) { - Console.Error.WriteLine("Generator:Count must be greater than 0."); + logger.LogError("Generator:Count must be greater than 0."); return 1; } if (roomNumbers.Length == 0) { - Console.Error.WriteLine("Generator:RoomNumbers is empty."); + logger.LogError("Generator:RoomNumbers is empty."); return 1; } -if (patients.Count == 0 || doctors.Count == 0) +if (patients.Length == 0 || doctors.Length == 0) { - Console.Error.WriteLine("No patients or doctors found in the database after retries."); + logger.LogError("Generator:Patients and Generator:Doctors must contain at least one item."); return 1; } +logger.LogInformation( + "Generator started. Endpoint={Endpoint}; Count={Count}; Patients={PatientsCount}; Doctors={DoctorsCount}.", + endpoint, + count, + patients.Length, + doctors.Length); + //Create gRPC channel and client, then send generated contracts with retry logic for endpoint availability. const int grpcAttempts = 10; for (var attempt = 1; attempt <= grpcAttempts; attempt++) @@ -139,48 +97,43 @@ for (var i = 0; i < count; i++) { - var patient = patients[random.Next(patients.Count)]; - var doctor = doctors[random.Next(doctors.Count)]; + var patient = patients[random.Next(patients.Length)]; + var doctor = doctors[random.Next(doctors.Length)]; var contract = BuildRandomContract(patient, doctor, roomNumbers, random, knownVisitPairs); await call.RequestStream.WriteAsync(contract); + logger.LogInformation( + "Sent contract {Index}/{Total}: PatientId={PatientId}; DoctorId={DoctorId}; Room={Room}; IsReturnVisit={IsReturnVisit}; Time={Time}.", + i + 1, + count, + contract.PatientId, + contract.DoctorId, + contract.RoomNumber, + contract.IsReturnVisit, + contract.AppointmentTime); } await call.RequestStream.CompleteAsync(); var result = await call; - Console.WriteLine($"Received={result.Received} Saved={result.Saved} Failed={result.Failed}"); + logger.LogInformation("Ingest result: Received={Received}; Saved={Saved}; Failed={Failed}.", result.Received, result.Saved, result.Failed); foreach (var error in result.Errors) { - Console.WriteLine(error); + logger.LogWarning("Ingest error: {Error}", error); } return 0; } catch (Grpc.Core.RpcException ex) when (ex.StatusCode == Grpc.Core.StatusCode.Unavailable && attempt < grpcAttempts) { - Console.WriteLine($"gRPC endpoint is not ready yet (attempt {attempt}/{grpcAttempts}). Retrying..."); + logger.LogWarning("gRPC endpoint is not ready yet (attempt {Attempt}/{MaxAttempts}). Retrying...", attempt, grpcAttempts); await Task.Delay(TimeSpan.FromSeconds(2)); } } -Console.Error.WriteLine("Failed to send contracts to gRPC endpoint after retries."); +logger.LogError("Failed to send contracts to gRPC endpoint after retries."); return 1; -/// -/// Builds a full name from separate name parts. -/// -/// Person last name. -/// Person first name. -/// Optional middle name. -/// Concatenated full name without extra whitespace. -static string BuildFullName(string lastName, string firstName, string? patronymic) -{ - return string.IsNullOrWhiteSpace(patronymic) - ? $"{lastName} {firstName}" - : $"{lastName} {firstName} {patronymic}"; -} - /// /// Creates a randomized appointment contract for gRPC streaming. /// @@ -191,8 +144,8 @@ static string BuildFullName(string lastName, string firstName, string? patronymi /// Known patient-doctor pairs that already had visits. /// Prepared with randomized date and flags. static Contract BuildRandomContract( - (int Id, string FullName) patient, - (int Id, string FullName) doctor, + PersonSeed patient, + PersonSeed doctor, int[] roomNumbers, Random random, HashSet<(int PatientId, int DoctorId)> knownVisitPairs) @@ -218,3 +171,10 @@ static Contract BuildRandomContract( IsReturnVisit = isReturnVisit }; } + +/// +/// Seed entity used by the autonomous generator to avoid direct database dependency. +/// +/// Domain identifier used in generated contracts. +/// Full name included in generated contracts. +readonly record struct PersonSeed(int Id, string FullName); diff --git a/Clinic/Clinic.AppointmentGenerator/appsettings.json b/Clinic/Clinic.AppointmentGenerator/appsettings.json index 5ae21f0f4..da85bfcb1 100644 --- a/Clinic/Clinic.AppointmentGenerator/appsettings.json +++ b/Clinic/Clinic.AppointmentGenerator/appsettings.json @@ -2,11 +2,36 @@ "Grpc": { "Endpoint": "https://clinic-api" }, - "ConnectionStrings": { - "ClinicDb": "" - }, "Generator": { "Count": 10, - "RoomNumbers": [101, 102, 201, 202] + "RoomNumbers": [101, 102, 201, 202], + "Patients": [ + { + "Id": 1, + "FullName": "Иванов Иван Иванович" + }, + { + "Id": 2, + "FullName": "Петров Петр Петрович" + }, + { + "Id": 3, + "FullName": "Сидорова Мария Алексеевна" + } + ], + "Doctors": [ + { + "Id": 1, + "FullName": "Смирнов Алексей Николаевич" + }, + { + "Id": 2, + "FullName": "Кузнецова Елена Сергеевна" + }, + { + "Id": 3, + "FullName": "Волков Дмитрий Олегович" + } + ] } }