- Timestamp:
- 09/15/26 21:15:53 (8 days ago)
- Branches:
- master
- Children:
- 002cf5f, 20df1a3, b8093a0, bcacc91
- Parents:
- 93c368a
- Location:
- iknow-api
- Files:
-
- 18 added
- 6 deleted
- 15 edited
-
Controllers/AdminController.cs (added)
-
Controllers/DbHealthController.cs (added)
-
Controllers/EnrollmentController.cs (added)
-
Controllers/ProfController.cs (added)
-
Controllers/UserController.cs (modified) (13 diffs)
-
Controllers/WeatherForecastController.cs (deleted)
-
DTOs/AdminDTO.cs (added)
-
DTOs/EnrollmentDTO.cs (added)
-
DTOs/ProfDTO.cs (added)
-
Data/AppDbContext.cs (modified) (3 diffs)
-
Data/PgEnumLabels.cs (added)
-
Migrations/20251216191800_InitialCreate.Designer.cs (deleted)
-
Migrations/20251216191800_InitialCreate.cs (deleted)
-
Migrations/AppDbContextModelSnapshot.cs (deleted)
-
Models/EnrolledSemesters.cs (modified) (1 diff)
-
Models/EnrollmentInfo.cs (deleted)
-
Models/HighSchool.cs (modified) (3 diffs)
-
Models/MajorSubjects.cs (modified) (1 diff)
-
Models/Payment.cs (modified) (1 diff)
-
Models/Quota.cs (added)
-
Models/SemesterSubjects.cs (modified) (1 diff)
-
Models/User.cs (modified) (2 diffs)
-
Program.cs (modified) (3 diffs)
-
Repository/Implementations/ProfRepository.cs (added)
-
Repository/Implementations/RefreshTokenRepository.cs (modified) (1 diff)
-
Repository/Implementations/UserRepository.cs (modified) (3 diffs)
-
Repository/Interface/IProfRepository.cs (added)
-
Repository/Interface/IUserRepository.cs (modified) (2 diffs)
-
Services/Implementations/AdminService.cs (added)
-
Services/Implementations/AuthService.cs (modified) (4 diffs)
-
Services/Implementations/EnrollmentService.cs (added)
-
Services/Implementations/ProfService.cs (added)
-
Services/Implementations/RefreshTokenService.cs (modified) (1 diff)
-
Services/Interfaces/IAdminService.cs (added)
-
Services/Interfaces/IEnrollmentService.cs (added)
-
Services/Interfaces/IProfService.cs (added)
-
WeatherForecast.cs (deleted)
-
appsettings.Development.json.example (added)
-
appsettings.json (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
-
iknow-api/Controllers/UserController.cs
r93c368a r1b20b22 1 using iknow_api.DTOs; 1 using iknow_api.DTOs; 2 using iknow_api.Models; 2 3 using Microsoft.AspNetCore.Authorization; 3 4 using Microsoft.AspNetCore.Mvc; … … 83 84 var userData = await _userService.GetUserData(token); 84 85 if (userData == null) return Ok(new { info = "can't get info" }); 86 87 // The study programme comes from the student's most recent 88 // enrolment. Within an academic year winter precedes summer, 89 // so order by year first and put winter ahead of summer. 90 var currentEnrolment = userData.Enrolments? 91 .Where(es => es.Semester != null) 92 .OrderByDescending(es => es.Semester!.Year) 93 .ThenByDescending(es => es.Semester!.Type == sType.summer) 94 .FirstOrDefault(); 95 85 96 var personalInfo = new 86 97 { … … 88 99 embg = userData.EMBG ?? "", 89 100 lastName = userData.Surname ?? "", 90 middleName = userData.MiddleName ?? "",101 middleName = "", // Not an attribute of Users in the ER model 91 102 firstName = userData.Name ?? "", 92 103 maidenName = "", // Property doesn't exist in User model 93 dateOfBirth = userData.Bday .ToString("dd.MM.yyyy"),94 gender = userData.Gender ?? "",95 nationality = userData.Nationality ?? "",96 citizenship = userData.Citizenship ?? "",104 dateOfBirth = userData.Bday?.ToString("dd.MM.yyyy") ?? "", 105 gender = "", // Not an attribute of Users in the ER model 106 nationality = "", // Not an attribute of Users in the ER model 107 citizenship = "", // Not an attribute of Users in the ER model 97 108 scholarship = "", 98 currentPlan = userData.Enrollment Info?.EnrollmentYear.ToString() ?? "",109 currentPlan = userData.EnrollmentYear?.ToString() ?? "", 99 110 registryNumber = "", 100 111 notes = "", … … 105 116 placeOfBirth = userData.ContactInfo?.City ?? "", 106 117 municipalityOfBirth = userData.ContactInfo?.Municipality ?? "", 107 country = userData.Citizenship ?? ""118 country = "" // Not an attribute of Users in the ER model 108 119 }; 109 120 var previousEducation = new … … 112 123 profession = "", 113 124 average = userData.HighSchool?.GPA.ToString() ?? "", 114 language = userData.Nationality ?? "",115 country = userData.Nationality ??"",125 language = "", // Not an attribute of HighSchool in the ER model 126 country = "", 116 127 previousUniversity = userData.HighSchool?.HighSchoolType.ToString() ?? "", 117 128 previousFaculty = "", // Property doesn't exist in HighSchool model … … 120 131 var enrollmentInfo = new 121 132 { 122 enrollmentYear = userData.Enrollment Info?.EnrollmentYear.ToString() ?? "",123 status = userData.EnrollmentInfo?.StudyStatus ?? "",133 enrollmentYear = userData.EnrollmentYear?.ToString() ?? "", 134 status = "", // Not an attribute of Users in the ER model 124 135 cycle = "Прв циклус", 125 program = userData.EnrollmentInfo?.Major?.Name ?? "",126 quota = userData. EnrollmentInfo?.Quota.ToString() ?? "",136 program = currentEnrolment?.Major?.Name ?? "", 137 quota = userData.Quota?.ToString() ?? "", 127 138 secondaryEducationNumber = "", 128 139 previousEducationCredits = "" … … 132 143 placeOfResidence = userData.ContactInfo?.City ?? "", 133 144 municipalityOfResidence = userData.ContactInfo?.Municipality ?? "", 134 country = userData.Citizenship ?? "",145 country = "", // Not an attribute of Users in the ER model 135 146 address = userData.ContactInfo?.Address ?? "", 136 147 temporaryAddress = "", … … 165 176 { 166 177 id = userData[i].Id, 167 semester = (userData[i].Semester.Type == 0 ? "Зимски" : "Летен") + $"({userData[i].Semester.Year}/{userData[i].Semester.Year + 1})", 178 // sType declares summer first, so compare against the member 179 // rather than the ordinal - == 0 meant summer, not winter. 180 semester = (userData[i].Semester.Type == sType.winter ? "Зимски" : "Летен") + $"({userData[i].Semester.Year}/{userData[i].Semester.Year + 1})", 168 181 direction = userData[i].Major?.Name ?? "", 169 182 quota = userData[i].QuotaType.ToString(), 170 note = "",171 studentCom = "",183 note = userData[i].Note ?? "", 184 studentCom = userData[i].StudentComment ?? "", 172 185 sum = "0,00", 173 186 paid = "0,00", 174 187 ukim = "", 175 createdOn = DateTime.Now.ToString("dd.MM.yyyy"),176 dateChanged = DateTime.Now.ToString("dd.MM.yyyy"),188 createdOn = userData[i].CratedAt.ToString("dd.MM.yyyy"), 189 dateChanged = userData[i].LastChange?.ToString("dd.MM.yyyy") ?? "", 177 190 credits = "0,00", 178 191 type = "Ред.", … … 183 196 signatures = "0/5", 184 197 status = "валиден", 185 completed = "Не"198 completed = userData[i].Verified.HasValue ? "Да" : "Не" 186 199 }; 187 200 results.Add(newResult); … … 208 221 { 209 222 id = userData[i].Id, 210 name = (userData[i].Semester.Type == 0? "Зимски" : "Летен") + $"({userData[i].Semester.Year}/{userData[i].Semester.Year + 1})",223 name = (userData[i].Semester.Type == sType.winter ? "Зимски" : "Летен") + $"({userData[i].Semester.Year}/{userData[i].Semester.Year + 1})", 211 224 status = "валиден", 212 225 serviceNumber = 1000000 + random.Next(10000, 100000) … … 221 234 { 222 235 // Create semester key like "winter_2025_2026" or "summer_2025_2026" 223 var semesterType = enrollment.Semester.Type == 0? "winter" : "summer";236 var semesterType = enrollment.Semester.Type == sType.winter ? "winter" : "summer"; 224 237 var semesterKey = $"{semesterType}_{enrollment.Semester.Year}_{enrollment.Semester.Year + 1}"; 225 238 … … 258 271 } 259 272 var currentSemester = userData.FirstOrDefault(); // Get the most recent/current semester 260 var semesterTypeGlobal = currentSemester.Semester.Type == 0? "winter" : "summer";273 var semesterTypeGlobal = currentSemester.Semester.Type == sType.winter ? "winter" : "summer"; 261 274 var semesterData = new 262 275 { 263 276 id = $"{semesterTypeGlobal}_{currentSemester.Semester.Year}_{currentSemester.Semester.Year + 1}", 264 name = (currentSemester.Semester.Type == 0? "Зимски" : "Летен") + $" ({currentSemester.Semester.Year}/{currentSemester.Semester.Year + 1})",277 name = (currentSemester.Semester.Type == sType.winter ? "Зимски" : "Летен") + $" ({currentSemester.Semester.Year}/{currentSemester.Semester.Year + 1})", 265 278 status = "валиден", 266 279 serviceNumber = (1000000 + random.Next(10000, 100000)).ToString(), … … 318 331 date = passed.DatePassed.ToString("dd.MM.yyyy"), 319 332 semester = passed.SemesterSubject.EnrolledSemester?.Semester != null 320 ? (passed.SemesterSubject.EnrolledSemester.Semester.Type == 0? "Зимски" : "Летен") +333 ? (passed.SemesterSubject.EnrolledSemester.Semester.Type == sType.winter ? "Зимски" : "Летен") + 321 334 $" ({passed.SemesterSubject.EnrolledSemester.Semester.Year}/{passed.SemesterSubject.EnrolledSemester.Semester.Year + 1})" 322 335 : "", -
iknow-api/Data/AppDbContext.cs
r93c368a r1b20b22 4 4 namespace iknow_api.Data 5 5 { 6 /// <summary> 7 /// The schema is owned by sql/ddl.sql, not by EF migrations. Every entity 8 /// below is mapped explicitly onto the table and column names in that 9 /// script; nothing here may rely on EF naming conventions. 10 /// </summary> 6 11 public class AppDbContext : DbContext 7 12 { 13 /// <summary> 14 /// The schema created by sql/schema_creation.sql. Every table and enum 15 /// type is qualified with it, so nothing depends on search_path. 16 /// </summary> 17 public const string ProjectSchema = "project"; 18 8 19 public AppDbContext(DbContextOptions<AppDbContext> options) 9 20 : base(options) { } … … 12 23 public DbSet<ContactInfo> ContactInfo { get; set; } 13 24 public DbSet<HighSchool> HighSchool { get; set; } 14 public DbSet<EnrollmentInfo> EnrollmentInfo { get; set; }15 25 public DbSet<RefreshToken> RefreshToken { get; set; } 16 26 public DbSet<Subject> Subjects { get; set; } … … 27 37 public DbSet<ProfessorSubjects> ProfessorSubjects { get; set; } 28 38 29 30 39 protected override void OnModelCreating(ModelBuilder modelBuilder) 31 40 { 32 41 base.OnModelCreating(modelBuilder); 33 42 34 modelBuilder.Entity<Major>().HasData( 35 new Major { Id = 1, Name = "PIT" }, 36 new Major { Id = 2, Name = "SIIS" }, 37 new Major { Id = 3, Name = "SEIS" }, 38 new Major { Id = 4, Name = "KN" } 39 ); 40 41 42 // ------------------------- 43 // DependencySubject (self-referencing many-to-many) 44 // ------------------------- 45 modelBuilder.Entity<DependencySubject>() 46 .HasKey(ds => new { ds.SubjectId, ds.DependencyId }); 47 48 modelBuilder.Entity<DependencySubject>() 49 .HasOne(ds => ds.Subject) 50 .WithMany(s => s.Dependencies) 51 .HasForeignKey(ds => ds.SubjectId) 52 .OnDelete(DeleteBehavior.Restrict); 53 54 modelBuilder.Entity<DependencySubject>() 55 .HasOne(ds => ds.Dependency) 56 .WithMany(s => s.Dependents) 57 .HasForeignKey(ds => ds.DependencyId) 58 .OnDelete(DeleteBehavior.Restrict); 59 60 // ------------------------- 61 // MajorSubjects (many-to-many) 62 // ------------------------- 63 modelBuilder.Entity<MajorSubjects>() 64 .HasKey(ab => new { ab.MajorId, ab.SubjectId }); 65 66 modelBuilder.Entity<MajorSubjects>() 67 .HasOne(ab => ab.Majors) 68 .WithMany(a => a.Subjects) 69 .HasForeignKey(ab => ab.MajorId) 70 .OnDelete(DeleteBehavior.Cascade); 71 72 modelBuilder.Entity<MajorSubjects>() 73 .HasOne(ab => ab.Subjects) 74 .WithMany(b => b.Majors) 75 .HasForeignKey(ab => ab.SubjectId) 76 .OnDelete(DeleteBehavior.Cascade); 77 78 // ------------------------- 79 // UserDocuments (many-to-many) 80 // ------------------------- 81 modelBuilder.Entity<UserDocuments>() 82 .HasKey(ab => new { ab.UserId, ab.DocumentId }); 83 84 modelBuilder.Entity<UserDocuments>() 85 .HasOne(ab => ab.User) 86 .WithMany(a => a.Documents) 87 .HasForeignKey(ab => ab.UserId) 88 .OnDelete(DeleteBehavior.Cascade); 89 90 modelBuilder.Entity<UserDocuments>() 91 .HasOne(ab => ab.Documents) 92 .WithMany(b => b.User) 93 .HasForeignKey(ab => ab.DocumentId) 94 .OnDelete(DeleteBehavior.Cascade); 95 96 // ------------------------- 97 // Payment (user enrollment) 98 // ------------------------- 99 modelBuilder.Entity<Payment>() 100 .HasKey(p => p.Id); 101 102 modelBuilder.Entity<Payment>() 103 .HasOne(p => p.User) 104 .WithMany(u => u.EnrolledSemesters) 105 .HasForeignKey(p => p.UserId) 106 .OnDelete(DeleteBehavior.Cascade); 107 108 modelBuilder.Entity<Payment>() 109 .HasOne(p => p.EnrolledSemesters) 110 .WithMany(es => es.Users) 111 .HasForeignKey(p => p.EnrollmentInfoId) 112 .OnDelete(DeleteBehavior.Cascade); 113 114 // ------------------------- 115 // SemesterSubject (student enrollments and professor assignments) 116 // ------------------------- 117 modelBuilder.Entity<SemesterSubject>() 118 .HasKey(ss => ss.Id); // or composite key if you prefer {UserId, EnrolledSemesterId, SubjectId} 119 120 // Student enrollment 121 modelBuilder.Entity<SemesterSubject>() 122 .HasOne(ss => ss.User) 123 .WithMany(u => u.EnrolledSemesterUserSubjects) 124 .HasForeignKey(ss => ss.UserId) 125 .OnDelete(DeleteBehavior.Restrict); 126 127 // Professor teaching 128 modelBuilder.Entity<SemesterSubject>() 129 .HasOne(ss => ss.Professor) 130 .WithMany(u => u.TeachingSubjects) 131 .HasForeignKey(ss => ss.ProfessorId) 132 .OnDelete(DeleteBehavior.Restrict); 133 134 // Subject relation 135 modelBuilder.Entity<SemesterSubject>() 136 .HasOne(ss => ss.Subject) 137 .WithMany(s => s.SemesterSubjects) 138 .HasForeignKey(ss => ss.SubjectId) 139 .OnDelete(DeleteBehavior.Restrict); 140 141 // EnrolledSemester relation 142 modelBuilder.Entity<SemesterSubject>() 143 .HasOne(ss => ss.EnrolledSemester) 144 .WithMany(es => es.SemesterSubjects) 145 .HasForeignKey(ss => ss.EnrolledSemesterId) 146 .OnDelete(DeleteBehavior.Restrict); 147 148 // ------------------------- 149 // PassedSubject (one-to-one with SemesterSubject) 150 // ------------------------- 151 modelBuilder.Entity<PassedSubject>() 152 .HasKey(ps => ps.Id); 153 154 modelBuilder.Entity<PassedSubject>() 155 .HasOne(ps => ps.SemesterSubject) 156 .WithOne(ss => ss.PassedSubject) 157 .HasForeignKey<PassedSubject>(ps => ps.SemesterSubjectId) 158 .OnDelete(DeleteBehavior.Cascade); 159 160 // ------------------------- 161 // ActiveSemesters to EnrolledSemesters (one-to-many) 162 // ------------------------- 163 modelBuilder.Entity<EnrolledSemesters>() 164 .HasOne(es => es.Semester) 165 .WithMany(a => a.EnrolledSemesters) 166 .HasForeignKey(es => es.Id) 167 .OnDelete(DeleteBehavior.Restrict); 168 169 // ------------------------- 170 // ProfessorSubjects (many-to-many: Professor -> Semester -> Subject) 171 // ------------------------- 172 modelBuilder.Entity<ProfessorSubjects>() 173 .HasKey(ps => new { ps.ProfessorId, ps.SemesterId, ps.SubjectId }); 174 175 modelBuilder.Entity<ProfessorSubjects>() 176 .HasOne(ps => ps.Professor) 177 .WithMany() 178 .HasForeignKey(ps => ps.ProfessorId) 179 .OnDelete(DeleteBehavior.Cascade); 180 181 modelBuilder.Entity<ProfessorSubjects>() 182 .HasOne(ps => ps.Semester) 183 .WithMany() 184 .HasForeignKey(ps => ps.SemesterId) 185 .OnDelete(DeleteBehavior.Cascade); 186 187 modelBuilder.Entity<ProfessorSubjects>() 188 .HasOne(ps => ps.Subject) 189 .WithMany() 190 .HasForeignKey(ps => ps.SubjectId) 191 .OnDelete(DeleteBehavior.Cascade); 43 // The enum types themselves are declared in ddl.sql; this tells EF 44 // they exist so it does not try to treat the columns as text. 45 modelBuilder.HasPostgresEnum<UserRole>(ProjectSchema, "user_role"); 46 modelBuilder.HasPostgresEnum<HighSchoolType>(ProjectSchema, "hs_type"); 47 modelBuilder.HasPostgresEnum<Quota>(ProjectSchema, "quota_type"); 48 modelBuilder.HasPostgresEnum<sType>(ProjectSchema, "semester_type"); 49 modelBuilder.HasPostgresEnum<Grade>(ProjectSchema, "grade_type"); 50 51 // ------------------------- 52 // users 53 // ------------------------- 54 modelBuilder.Entity<User>(e => 55 { 56 e.ToTable("users", ProjectSchema); 57 e.HasKey(u => u.Id); 58 e.Property(u => u.Id).HasColumnName("id"); 59 e.Property(u => u.Name).HasColumnName("name"); 60 e.Property(u => u.EMBG).HasColumnName("embg"); 61 e.Property(u => u.Surname).HasColumnName("surname"); 62 e.Property(u => u.Index).HasColumnName("index"); 63 e.Property(u => u.Bday).HasColumnName("bday").HasColumnType("timestamp without time zone"); 64 e.Property(u => u.Email).HasColumnName("email"); 65 e.Property(u => u.PasswordHash).HasColumnName("password"); 66 e.Property(u => u.Role).HasColumnName("role"); 67 e.Property(u => u.Quota).HasColumnName("quota"); 68 e.Property(u => u.EnrollmentYear).HasColumnName("enrollment_year"); 69 e.Property(u => u.CreatedAt).HasColumnName("created_at").HasColumnType("timestamp without time zone"); 70 e.HasIndex(u => u.Email).IsUnique(); 71 }); 72 73 // ------------------------- 74 // high_school 75 // ------------------------- 76 modelBuilder.Entity<HighSchool>(e => 77 { 78 e.ToTable("high_school", ProjectSchema); 79 e.HasKey(h => h.Id); 80 e.Property(h => h.Id).HasColumnName("id"); 81 e.Property(h => h.UserId).HasColumnName("user_id"); 82 e.Property(h => h.GPA).HasColumnName("gpa"); 83 e.Property(h => h.HighSchoolType).HasColumnName("type"); 84 e.HasOne(h => h.User) 85 .WithOne(u => u.HighSchool) 86 .HasForeignKey<HighSchool>(h => h.UserId) 87 .OnDelete(DeleteBehavior.Restrict); 88 }); 89 90 // ------------------------- 91 // contact 92 // ------------------------- 93 modelBuilder.Entity<ContactInfo>(e => 94 { 95 e.ToTable("contact", ProjectSchema); 96 e.HasKey(c => c.Id); 97 e.Property(c => c.Id).HasColumnName("id"); 98 e.Property(c => c.UserId).HasColumnName("user_id"); 99 e.Property(c => c.City).HasColumnName("city"); 100 e.Property(c => c.Municipality).HasColumnName("municipality"); 101 e.Property(c => c.Address).HasColumnName("address"); 102 e.Property(c => c.PhoneNumber).HasColumnName("number"); 103 e.Property(c => c.MicrosoftEmail).HasColumnName("microsoft_email"); 104 e.HasOne(c => c.User) 105 .WithOne(u => u.ContactInfo) 106 .HasForeignKey<ContactInfo>(c => c.UserId) 107 .OnDelete(DeleteBehavior.Restrict); 108 }); 109 110 // ------------------------- 111 // major 112 // ------------------------- 113 modelBuilder.Entity<Major>(e => 114 { 115 e.ToTable("major", ProjectSchema); 116 e.HasKey(m => m.Id); 117 e.Property(m => m.Id).HasColumnName("id"); 118 e.Property(m => m.Name).HasColumnName("name"); 119 }); 120 121 // ------------------------- 122 // active_semesters 123 // ------------------------- 124 modelBuilder.Entity<ActiveSemesters>(e => 125 { 126 e.ToTable("active_semesters", ProjectSchema); 127 e.HasKey(a => a.Id); 128 e.Property(a => a.Id).HasColumnName("id"); 129 e.Property(a => a.Year).HasColumnName("year"); 130 e.Property(a => a.Type).HasColumnName("type"); 131 }); 132 133 // ------------------------- 134 // subjects 135 // ------------------------- 136 modelBuilder.Entity<Subject>(e => 137 { 138 e.ToTable("subjects", ProjectSchema); 139 e.HasKey(s => s.Id); 140 e.Property(s => s.Id).HasColumnName("id"); 141 e.Property(s => s.Name).HasColumnName("name"); 142 e.Property(s => s.Code).HasColumnName("code"); 143 e.Property(s => s.AwardedCredits).HasColumnName("awarded_credits"); 144 e.Property(s => s.DependencyCredit).HasColumnName("dependency_credit"); 145 }); 146 147 // ------------------------- 148 // enrolled_semesters 149 // ------------------------- 150 modelBuilder.Entity<EnrolledSemesters>(e => 151 { 152 e.ToTable("enrolled_semesters", ProjectSchema); 153 e.HasKey(es => es.Id); 154 e.Property(es => es.Id).HasColumnName("id"); 155 e.Property(es => es.UserId).HasColumnName("user_id"); 156 e.Property(es => es.QuotaType).HasColumnName("quota"); 157 e.Property(es => es.MajorId).HasColumnName("major_id"); 158 e.Property(es => es.Note).HasColumnName("note"); 159 e.Property(es => es.StudentComment).HasColumnName("student_comment"); 160 e.Property(es => es.CratedAt).HasColumnName("created_at").HasColumnType("timestamp without time zone"); 161 e.Property(es => es.LastChange).HasColumnName("last_change").HasColumnType("timestamp without time zone"); 162 e.Property(es => es.Verified).HasColumnName("completed").HasColumnType("timestamp without time zone"); 163 e.Property(es => es.SemesterId).HasColumnName("semester_id"); 164 165 e.HasOne(es => es.User) 166 .WithMany(u => u.Enrolments) 167 .HasForeignKey(es => es.UserId) 168 .OnDelete(DeleteBehavior.Restrict); 169 170 // Note: this deliberately keys off SemesterId. An earlier version 171 // pointed the foreign key at es.Id, which silently tied every 172 // enrolment to the semester that happened to share its id. 173 e.HasOne(es => es.Semester) 174 .WithMany(a => a.EnrolledSemesters) 175 .HasForeignKey(es => es.SemesterId) 176 .OnDelete(DeleteBehavior.Restrict); 177 178 e.HasOne(es => es.Major) 179 .WithMany() 180 .HasForeignKey(es => es.MajorId) 181 .OnDelete(DeleteBehavior.Restrict); 182 }); 183 184 // ------------------------- 185 // semesters_subjects 186 // ------------------------- 187 modelBuilder.Entity<SemesterSubject>(e => 188 { 189 e.ToTable("semesters_subjects", ProjectSchema); 190 e.HasKey(ss => ss.Id); 191 e.Property(ss => ss.Id).HasColumnName("id"); 192 e.Property(ss => ss.EnrolledSemesterId).HasColumnName("enrolled_semesters_id"); 193 e.Property(ss => ss.SubjectId).HasColumnName("subjects_id"); 194 e.Property(ss => ss.ProfessorId).HasColumnName("professor_id"); 195 e.Property(ss => ss.Signature).HasColumnName("signature"); 196 197 e.HasOne(ss => ss.Professor) 198 .WithMany(u => u.TeachingSubjects) 199 .HasForeignKey(ss => ss.ProfessorId) 200 .OnDelete(DeleteBehavior.Restrict); 201 202 e.HasOne(ss => ss.Subject) 203 .WithMany(s => s.SemesterSubjects) 204 .HasForeignKey(ss => ss.SubjectId) 205 .OnDelete(DeleteBehavior.Restrict); 206 207 e.HasOne(ss => ss.EnrolledSemester) 208 .WithMany(es => es.SemesterSubjects) 209 .HasForeignKey(ss => ss.EnrolledSemesterId) 210 .OnDelete(DeleteBehavior.Restrict); 211 }); 212 213 // ------------------------- 214 // passed_subjects 215 // ------------------------- 216 modelBuilder.Entity<PassedSubject>(e => 217 { 218 e.ToTable("passed_subjects", ProjectSchema); 219 e.HasKey(ps => ps.Id); 220 e.Property(ps => ps.Id).HasColumnName("id"); 221 e.Property(ps => ps.SemesterSubjectId).HasColumnName("enrolled_id"); 222 e.Property(ps => ps.Grade).HasColumnName("grade"); 223 e.Property(ps => ps.DatePassed).HasColumnName("date_passed").HasColumnType("timestamp without time zone"); 224 225 e.HasOne(ps => ps.SemesterSubject) 226 .WithOne(ss => ss.PassedSubject) 227 .HasForeignKey<PassedSubject>(ps => ps.SemesterSubjectId) 228 .OnDelete(DeleteBehavior.Cascade); 229 }); 230 231 // ------------------------- 232 // documents 233 // ------------------------- 234 modelBuilder.Entity<Documents>(e => 235 { 236 e.ToTable("documents", ProjectSchema); 237 e.HasKey(d => d.Id); 238 e.Property(d => d.Id).HasColumnName("id"); 239 e.Property(d => d.Name).HasColumnName("type"); 240 e.Property(d => d.Body).HasColumnName("body"); 241 e.Property(d => d.Cost).HasColumnName("cost"); 242 }); 243 244 // ------------------------- 245 // user_documents 246 // ------------------------- 247 modelBuilder.Entity<UserDocuments>(e => 248 { 249 e.ToTable("user_documents", ProjectSchema); 250 e.HasKey(ud => new { ud.UserId, ud.DocumentId }); 251 e.Property(ud => ud.UserId).HasColumnName("user_id"); 252 e.Property(ud => ud.DocumentId).HasColumnName("document_id"); 253 254 e.HasOne(ud => ud.User) 255 .WithMany(u => u.Documents) 256 .HasForeignKey(ud => ud.UserId) 257 .OnDelete(DeleteBehavior.Cascade); 258 259 e.HasOne(ud => ud.Documents) 260 .WithMany(d => d.User) 261 .HasForeignKey(ud => ud.DocumentId) 262 .OnDelete(DeleteBehavior.Cascade); 263 }); 264 265 // ------------------------- 266 // token 267 // ------------------------- 268 modelBuilder.Entity<RefreshToken>(e => 269 { 270 e.ToTable("token", ProjectSchema); 271 e.HasKey(t => t.Id); 272 e.Property(t => t.Id).HasColumnName("id"); 273 e.Property(t => t.UserId).HasColumnName("user_id"); 274 e.Property(t => t.Token).HasColumnName("token"); 275 e.Property(t => t.ExpiresAt).HasColumnName("expires_at").HasColumnType("timestamp without time zone"); 276 e.Property(t => t.IsValid).HasColumnName("is_valid"); 277 278 e.HasOne(t => t.User) 279 .WithMany() 280 .HasForeignKey(t => t.UserId) 281 .OnDelete(DeleteBehavior.Cascade); 282 }); 283 284 // ------------------------- 285 // payment 286 // ------------------------- 287 modelBuilder.Entity<Payment>(e => 288 { 289 e.ToTable("payment", ProjectSchema); 290 e.HasKey(p => p.Id); 291 e.Property(p => p.Id).HasColumnName("id"); 292 e.Property(p => p.UserId).HasColumnName("user_id"); 293 e.Property(p => p.EnrollmentInfoId).HasColumnName("enrollment_id"); 294 e.Property(p => p.Amount).HasColumnName("amount"); 295 296 e.HasOne(p => p.User) 297 .WithMany(u => u.Payments) 298 .HasForeignKey(p => p.UserId) 299 .OnDelete(DeleteBehavior.Cascade); 300 301 e.HasOne(p => p.EnrolledSemesters) 302 .WithMany(es => es.Users) 303 .HasForeignKey(p => p.EnrollmentInfoId) 304 .OnDelete(DeleteBehavior.Cascade); 305 }); 306 307 // ------------------------- 308 // major_subjects 309 // ------------------------- 310 modelBuilder.Entity<MajorSubjects>(e => 311 { 312 e.ToTable("major_subjects", ProjectSchema); 313 e.HasKey(ms => new { ms.MajorId, ms.SubjectId }); 314 e.Property(ms => ms.MajorId).HasColumnName("major_id"); 315 e.Property(ms => ms.SubjectId).HasColumnName("subject_id"); 316 e.Property(ms => ms.MandatorySemester).HasColumnName("mandatory_semester"); 317 318 e.HasOne(ms => ms.Majors) 319 .WithMany(m => m.Subjects) 320 .HasForeignKey(ms => ms.MajorId) 321 .OnDelete(DeleteBehavior.Cascade); 322 323 e.HasOne(ms => ms.Subjects) 324 .WithMany(s => s.Majors) 325 .HasForeignKey(ms => ms.SubjectId) 326 .OnDelete(DeleteBehavior.Cascade); 327 }); 328 329 // ------------------------- 330 // dependency_subject 331 // ------------------------- 332 modelBuilder.Entity<DependencySubject>(e => 333 { 334 e.ToTable("dependency_subject", ProjectSchema); 335 e.HasKey(ds => new { ds.SubjectId, ds.DependencyId }); 336 e.Property(ds => ds.SubjectId).HasColumnName("subject_id"); 337 e.Property(ds => ds.DependencyId).HasColumnName("dependency_id"); 338 339 e.HasOne(ds => ds.Subject) 340 .WithMany(s => s.Dependencies) 341 .HasForeignKey(ds => ds.SubjectId) 342 .OnDelete(DeleteBehavior.Restrict); 343 344 e.HasOne(ds => ds.Dependency) 345 .WithMany(s => s.Dependents) 346 .HasForeignKey(ds => ds.DependencyId) 347 .OnDelete(DeleteBehavior.Restrict); 348 }); 349 350 // ------------------------- 351 // professour_subjects (spelling matches the table in ddl.sql) 352 // ------------------------- 353 modelBuilder.Entity<ProfessorSubjects>(e => 354 { 355 e.ToTable("professour_subjects", ProjectSchema); 356 e.HasKey(ps => new { ps.ProfessorId, ps.SemesterId, ps.SubjectId }); 357 e.Property(ps => ps.ProfessorId).HasColumnName("prof_id"); 358 e.Property(ps => ps.SemesterId).HasColumnName("active_semester_id"); 359 e.Property(ps => ps.SubjectId).HasColumnName("subject_id"); 360 361 e.HasOne(ps => ps.Professor) 362 .WithMany() 363 .HasForeignKey(ps => ps.ProfessorId) 364 .OnDelete(DeleteBehavior.Cascade); 365 366 e.HasOne(ps => ps.Semester) 367 .WithMany() 368 .HasForeignKey(ps => ps.SemesterId) 369 .OnDelete(DeleteBehavior.Cascade); 370 371 e.HasOne(ps => ps.Subject) 372 .WithMany() 373 .HasForeignKey(ps => ps.SubjectId) 374 .OnDelete(DeleteBehavior.Cascade); 375 }); 192 376 } 193 194 377 } 195 378 } -
iknow-api/Models/EnrolledSemesters.cs
r93c368a r1b20b22 14 14 public string? Note { get; set; } 15 15 public DateTime CratedAt { get; set; } 16 public DateTime LastChange { get; set; }17 public DateTime Verified { get; set; }16 public DateTime? LastChange { get; set; } 17 public DateTime? Verified { get; set; } 18 18 public int SemesterId { get; set; } 19 19 public ActiveSemesters? Semester { get; set; } -
iknow-api/Models/HighSchool.cs
r93c368a r1b20b22 1 using System.ComponentModel.DataAnnotations.Schema;2 3 1 namespace iknow_api.Models 4 2 { … … 7 5 Strucen, Gimnazija 8 6 } 7 9 8 public class HighSchool 10 9 { … … 14 13 public int UserId { get; set; } 15 14 public User? User { get; set; } 16 public string? StudyLanguage { get; set; }17 15 } 18 16 } -
iknow-api/Models/MajorSubjects.cs
r93c368a r1b20b22 6 6 public int SubjectId { get; set; } 7 7 public int MandatorySemester { get; set; } 8 public int Accreditation { get; set; }9 8 public Major? Majors { get; set; } 10 9 public Subject? Subjects { get; set; } -
iknow-api/Models/Payment.cs
r93c368a r1b20b22 10 10 public User? User { get; set; } 11 11 public int EnrollmentInfoId { get; set; } 12 public int Amount { get; set; } 12 13 public EnrolledSemesters? EnrolledSemesters { get; set; } 13 14 -
iknow-api/Models/SemesterSubjects.cs
r93c368a r1b20b22 1 1 namespace iknow_api.Models 2 2 { 3 // The student is reached through EnrolledSemester.UserId; the ER model 4 // links SemestersSubjects to Users only through the professor. 3 5 public class SemesterSubject 4 6 { 5 public int Id { get; set; } 6 7 public int UserId { get; set; } 7 public int Id { get; set; } 8 8 9 public int EnrolledSemesterId { get; set; } 9 10 public int SubjectId { get; set; } 10 11 public int ProfessorId { get; set; } 11 12 public bool Signature { get; set; } 12 public User? User { get; set; } 13 13 14 public EnrolledSemesters? EnrolledSemester { get; set; } 14 15 public Subject? Subject { get; set; } 15 public User? Professor { get; set; } 16 public User? Professor { get; set; } 16 17 public PassedSubject? PassedSubject { get; set; } 17 18 } -
iknow-api/Models/User.cs
r93c368a r1b20b22 1 2 3 1 namespace iknow_api.Models 4 2 { … … 10 8 } 11 9 10 // Attributes follow the Users entity in the ER model exactly. 12 11 public class User 13 12 { 14 internal object Payments;15 16 13 public int Id { get; set; } 17 14 public string? Name { get; set; } 18 public string? EMBG { get; set; }15 public string? EMBG { get; set; } 19 16 public string? Surname { get; set; } 20 17 public string? Index { get; set; } 21 18 public string? Email { get; set; } 22 19 public string? PasswordHash { get; set; } 23 public string? MiddleName { get; set; } 24 public string? Gender { get; set; } 25 public string? Nationality { get; set; } 26 public string? Citizenship { get; set; } 27 public DateTime Bday { get; set; } 20 public DateTime? Bday { get; set; } 28 21 public DateTime CreatedAt { get; set; } 29 22 public UserRole Role { get; set; } 23 public Quota? Quota { get; set; } 24 public int? EnrollmentYear { get; set; } 25 30 26 public HighSchool? HighSchool { get; set; } 31 27 public ContactInfo? ContactInfo { get; set; } 32 public EnrollmentInfo? EnrollmentInfo { get; set; }33 28 public ICollection<UserDocuments>? Documents { get; set; } 34 public ICollection<Payment>? EnrolledSemesters { get; set; }35 public ICollection<SemesterSubject>? EnrolledSemesterUserSubjects { get; set; }36 29 30 // Named for the relationship it carries: Users 1 --pays-- N Payment. 31 public ICollection<Payment>? Payments { get; set; } 32 33 // Users 1 --submits-- N EnrolledSemesters 34 public ICollection<EnrolledSemesters>? Enrolments { get; set; } 35 36 // Users 1 --teaches-- N SemestersSubjects 37 37 public ICollection<SemesterSubject>? TeachingSubjects { get; set; } 38 39 38 } 40 39 } -
iknow-api/Program.cs
r93c368a r1b20b22 2 2 using System.Text.Json.Serialization; 3 3 using iknow_api.Data; 4 using iknow_api.Models; 4 5 using iknow_api.Repositories; 5 6 using iknow_api.Services; … … 59 60 builder.Services.AddOpenApi(); 60 61 61 // Register DbContext 62 // Register DbContext. 63 // The database lives behind the SSH tunnel: tunnel_scripta.cmd maps 64 // localhost:9999 to the faculty Postgres server. Start it before the API. 65 // The enum labels come from sql/ddl.sql, so each CLR enum is mapped onto its 66 // Postgres type by name; PgEnumLabels covers the members whose label is not 67 // simply the lowercased member name. 62 68 builder.Services.AddDbContext<AppDbContext>(options => 63 options.UseNpgsql(builder.Configuration.GetConnectionString("DefaultConnection"))); 69 options.UseNpgsql( 70 builder.Configuration.GetConnectionString("DefaultConnection"), 71 npgsql => 72 { 73 npgsql.MapEnum<UserRole>("user_role", AppDbContext.ProjectSchema, PgEnumLabels.UserRole); 74 npgsql.MapEnum<HighSchoolType>("hs_type", AppDbContext.ProjectSchema, PgEnumLabels.Lowercase); 75 npgsql.MapEnum<Quota>("quota_type", AppDbContext.ProjectSchema, PgEnumLabels.Lowercase); 76 npgsql.MapEnum<sType>("semester_type", AppDbContext.ProjectSchema, PgEnumLabels.Lowercase); 77 npgsql.MapEnum<Grade>("grade_type", AppDbContext.ProjectSchema, PgEnumLabels.Grade); 78 })); 64 79 65 80 // Register your services … … 71 86 builder.Services.AddScoped<IUserRepository, UserRepository>(); 72 87 builder.Services.AddScoped<IRefreshTokenRepository, RefreshTokenRepository>(); 88 builder.Services.AddScoped<IProfRepository, ProfRepository>(); 89 builder.Services.AddScoped<IProfService, ProfService>(); 90 builder.Services.AddScoped<IEnrollmentService, EnrollmentService>(); 91 builder.Services.AddScoped<IAdminService, AdminService>(); 73 92 builder.Services.AddAuthorization(); 74 93 var app = builder.Build(); -
iknow-api/Repository/Implementations/RefreshTokenRepository.cs
r93c368a r1b20b22 41 41 rt.Token == refreshTokenDto.token && 42 42 rt.IsValid && 43 rt.ExpiresAt > DateTime. UtcNow);43 rt.ExpiresAt > DateTime.SpecifyKind(DateTime.UtcNow, DateTimeKind.Unspecified)); 44 44 45 45 return token != null; -
iknow-api/Repository/Implementations/UserRepository.cs
r93c368a r1b20b22 28 28 return await _context.User 29 29 .Include(u => u.ContactInfo) 30 .Include(u => u.EnrollmentInfo)31 30 .Include(u => u.HighSchool) 31 // The study programme is reached through the enrolment, since 32 // Major hangs off EnrolledSemesters rather than off Users. 33 .Include(u => u.Enrolments!) 34 .ThenInclude(es => es.Major) 35 .Include(u => u.Enrolments!) 36 .ThenInclude(es => es.Semester) 37 .AsSplitQuery() 32 38 .FirstOrDefaultAsync(u => u.Id == id); 33 39 } … … 48 54 await _context.SaveChangesAsync(); 49 55 } 50 public async Task AddEnrollmentAsync(EnrollmentInfo enrollment)51 {52 _context.EnrollmentInfo.Add(enrollment);53 await _context.SaveChangesAsync();54 }55 56 public async Task AddHighSchoolAsync(HighSchool highSchool) 56 57 { … … 89 90 .Include(ps => ps.SemesterSubject) 90 91 .ThenInclude(ss => ss.Professor) 91 .Where(ps => ps.SemesterSubject.UserId == id) 92 // The ER model reaches the student through the enrolment, 93 // not through a user_id on semesters_subjects. 94 .Where(ps => ps.SemesterSubject.EnrolledSemester.UserId == id) 92 95 .ToListAsync(); 93 96 } -
iknow-api/Repository/Interface/IUserRepository.cs
r93c368a r1b20b22 1 using iknow_api.Models;1 using iknow_api.Models; 2 2 namespace iknow_api.Repositories 3 3 { … … 8 8 Task AddUserAsync(User user); 9 9 Task AddContactAsync(ContactInfo contactInfo); 10 Task AddEnrollmentAsync(EnrollmentInfo enrollment);11 10 Task AddHighSchoolAsync(HighSchool highSchool); 12 11 Task<int> GetUsersCountAsync(); -
iknow-api/Services/Implementations/AuthService.cs
r93c368a r1b20b22 1 using System.IdentityModel.Tokens.Jwt;1 using System.IdentityModel.Tokens.Jwt; 2 2 using System.Security.Claims; 3 3 using System.Text; … … 51 51 try 52 52 { 53 // quota and enrollment_year are attributes of Users in the ER 54 // model, so they are set here rather than on a separate entity. 53 55 var user = new User 54 56 { 55 57 Name = registerDto.Name, 56 MiddleName = registerDto.MiddleName,57 58 Surname = registerDto.Surname, 58 59 Index = registerDto.Index, 59 60 Email = registerDto.Email, 60 61 PasswordHash = BCrypt.Net.BCrypt.HashPassword(registerDto.Password), 61 Bday = DateTime.SpecifyKind(registerDto.Bday, DateTimeKind.U tc),62 CreatedAt = DateTime. UtcNow,62 Bday = DateTime.SpecifyKind(registerDto.Bday, DateTimeKind.Unspecified), 63 CreatedAt = DateTime.SpecifyKind(DateTime.UtcNow, DateTimeKind.Unspecified), 63 64 Role = (Models.UserRole)registerDto.Role, 64 65 EMBG = registerDto.EMBG, 65 Nationality = registerDto.nationality, 66 Citizenship = registerDto.citizenship, 67 Gender = registerDto.gender 66 Quota = (Models.Quota)registerDto.quotaType, 67 EnrollmentYear = registerDto.enrollmentYear 68 68 }; 69 69 … … 83 83 }; 84 84 85 var enrollmentInfo = new EnrollmentInfo86 {87 UserId = userId,88 EnrollmentYear = registerDto.enrollmentYear,89 Quota = (Models.Quota)registerDto.quotaType,90 MajorId = registerDto.majorType,91 StudyStatus = registerDto.studyStatus,92 StudyType = registerDto.studyType93 };94 95 85 var highSchool = new HighSchool 96 86 { … … 101 91 102 92 _context.ContactInfo.Add(contactInfo); 103 _context.EnrollmentInfo.Add(enrollmentInfo);104 93 _context.HighSchool.Add(highSchool); 105 94 -
iknow-api/Services/Implementations/RefreshTokenService.cs
r93c368a r1b20b22 31 31 Token = refreshToken, 32 32 UserId = userid, 33 ExpiresAt = DateTime. UtcNow.AddMonths(1),33 ExpiresAt = DateTime.SpecifyKind(DateTime.UtcNow.AddMonths(1), DateTimeKind.Unspecified), 34 34 IsValid = true 35 35 }; -
iknow-api/appsettings.json
r93c368a r1b20b22 10 10 "https://iknow-remaster.vercel.app" 11 11 ], 12 "ConnectionStrings": { 13 "DefaultConnection": "Host=ep-divine-term-aghfotgc-pooler.c-2.eu-central-1.aws.neon.tech; Database=neondb; Username=neondb_owner; Password=REDACTED_ROTATE_ME; SSL Mode=VerifyFull; Channel Binding=Require;" 14 }, 15 "Jwt": { 16 "Key": "REDACTED_SET_VIA_USER_SECRETS" 17 } 12 "AllowedHosts": "*" 18 13 }
Note:
See TracChangeset
for help on using the changeset viewer.
