Changeset 1b20b22 for iknow-api


Ignore:
Timestamp:
09/15/26 21:15:53 (8 days ago)
Author:
Stefan-Saveski <stefansaveski19@…>
Branches:
master
Children:
002cf5f, 20df1a3, b8093a0, bcacc91
Parents:
93c368a
Message:
  • ready for presentation
Location:
iknow-api
Files:
18 added
6 deleted
15 edited

Legend:

Unmodified
Added
Removed
  • iknow-api/Controllers/UserController.cs

    r93c368a r1b20b22  
    1 using iknow_api.DTOs;
     1using iknow_api.DTOs;
     2using iknow_api.Models;
    23using Microsoft.AspNetCore.Authorization;
    34using Microsoft.AspNetCore.Mvc;
     
    8384                var userData = await _userService.GetUserData(token);
    8485                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
    8596                var personalInfo = new
    8697                {
     
    8899                    embg = userData.EMBG ?? "",
    89100                    lastName = userData.Surname ?? "",
    90                     middleName = userData.MiddleName ?? "",
     101                    middleName = "", // Not an attribute of Users in the ER model
    91102                    firstName = userData.Name ?? "",
    92103                    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
    97108                    scholarship = "",
    98                     currentPlan = userData.EnrollmentInfo?.EnrollmentYear.ToString() ?? "",
     109                    currentPlan = userData.EnrollmentYear?.ToString() ?? "",
    99110                    registryNumber = "",
    100111                    notes = "",
     
    105116                    placeOfBirth = userData.ContactInfo?.City ?? "",
    106117                    municipalityOfBirth = userData.ContactInfo?.Municipality ?? "",
    107                     country = userData.Citizenship ?? ""
     118                    country = "" // Not an attribute of Users in the ER model
    108119                };
    109120                var previousEducation = new
     
    112123                    profession = "",
    113124                    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 = "",
    116127                    previousUniversity = userData.HighSchool?.HighSchoolType.ToString() ?? "",
    117128                    previousFaculty = "", // Property doesn't exist in HighSchool model
     
    120131                var enrollmentInfo = new
    121132                {
    122                     enrollmentYear = userData.EnrollmentInfo?.EnrollmentYear.ToString() ?? "",
    123                     status = userData.EnrollmentInfo?.StudyStatus ?? "",
     133                    enrollmentYear = userData.EnrollmentYear?.ToString() ?? "",
     134                    status = "", // Not an attribute of Users in the ER model
    124135                    cycle = "Прв циклус",
    125                     program = userData.EnrollmentInfo?.Major?.Name ?? "",
    126                     quota = userData.EnrollmentInfo?.Quota.ToString() ?? "",
     136                    program = currentEnrolment?.Major?.Name ?? "",
     137                    quota = userData.Quota?.ToString() ?? "",
    127138                    secondaryEducationNumber = "",
    128139                    previousEducationCredits = ""
     
    132143                    placeOfResidence = userData.ContactInfo?.City ?? "",
    133144                    municipalityOfResidence = userData.ContactInfo?.Municipality ?? "",
    134                     country = userData.Citizenship ?? "",
     145                    country = "", // Not an attribute of Users in the ER model
    135146                    address = userData.ContactInfo?.Address ?? "",
    136147                    temporaryAddress = "",
     
    165176                    {
    166177                        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})",
    168181                        direction = userData[i].Major?.Name ?? "",
    169182                        quota = userData[i].QuotaType.ToString(),
    170                         note = "",
    171                         studentCom = "",
     183                        note = userData[i].Note ?? "",
     184                        studentCom = userData[i].StudentComment ?? "",
    172185                        sum = "0,00",
    173186                        paid = "0,00",
    174187                        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") ?? "",
    177190                        credits = "0,00",
    178191                        type = "Ред.",
     
    183196                        signatures = "0/5",
    184197                        status = "валиден",
    185                         completed = "Не"
     198                        completed = userData[i].Verified.HasValue ? "Да" : "Не"
    186199                    };
    187200                    results.Add(newResult);
     
    208221                {
    209222                    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})",
    211224                    status = "валиден",
    212225                    serviceNumber = 1000000 + random.Next(10000, 100000)
     
    221234            {
    222235                // 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";
    224237                var semesterKey = $"{semesterType}_{enrollment.Semester.Year}_{enrollment.Semester.Year + 1}";
    225238
     
    258271            }
    259272            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";
    261274            var semesterData = new
    262275            {
    263276                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})",
    265278                status = "валиден",
    266279                serviceNumber = (1000000 + random.Next(10000, 100000)).ToString(),
     
    318331                        date = passed.DatePassed.ToString("dd.MM.yyyy"),
    319332                        semester = passed.SemesterSubject.EnrolledSemester?.Semester != null
    320                             ? (passed.SemesterSubject.EnrolledSemester.Semester.Type == 0 ? "Зимски" : "Летен") +
     333                            ? (passed.SemesterSubject.EnrolledSemester.Semester.Type == sType.winter ? "Зимски" : "Летен") +
    321334                              $" ({passed.SemesterSubject.EnrolledSemester.Semester.Year}/{passed.SemesterSubject.EnrolledSemester.Semester.Year + 1})"
    322335                            : "",
  • iknow-api/Data/AppDbContext.cs

    r93c368a r1b20b22  
    44namespace iknow_api.Data
    55{
     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>
    611    public class AppDbContext : DbContext
    712    {
     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
    819        public AppDbContext(DbContextOptions<AppDbContext> options)
    920            : base(options) { }
     
    1223        public DbSet<ContactInfo> ContactInfo { get; set; }
    1324        public DbSet<HighSchool> HighSchool { get; set; }
    14         public DbSet<EnrollmentInfo> EnrollmentInfo { get; set; }
    1525        public DbSet<RefreshToken> RefreshToken { get; set; }
    1626        public DbSet<Subject> Subjects { get; set; }
     
    2737        public DbSet<ProfessorSubjects> ProfessorSubjects { get; set; }
    2838
    29 
    3039        protected override void OnModelCreating(ModelBuilder modelBuilder)
    3140        {
    3241            base.OnModelCreating(modelBuilder);
    3342
    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            });
    192376        }
    193 
    194377    }
    195378}
  • iknow-api/Models/EnrolledSemesters.cs

    r93c368a r1b20b22  
    1414        public string? Note { get; set; }
    1515        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; }
    1818        public int SemesterId { get; set; }
    1919        public ActiveSemesters? Semester { get; set; }
  • iknow-api/Models/HighSchool.cs

    r93c368a r1b20b22  
    1 using System.ComponentModel.DataAnnotations.Schema;
    2 
    31namespace iknow_api.Models
    42{
     
    75        Strucen, Gimnazija
    86    }
     7
    98    public class HighSchool
    109    {
     
    1413        public int UserId { get; set; }
    1514        public User? User { get; set; }
    16         public string? StudyLanguage { get; set; }
    1715    }
    1816}
  • iknow-api/Models/MajorSubjects.cs

    r93c368a r1b20b22  
    66        public int SubjectId { get; set; }
    77        public int MandatorySemester { get; set; }
    8         public int Accreditation { get; set; }
    98        public Major? Majors { get; set; }
    109        public Subject? Subjects { get; set; }
  • iknow-api/Models/Payment.cs

    r93c368a r1b20b22  
    1010        public User? User { get; set; }
    1111        public int EnrollmentInfoId { get; set; }
     12        public int Amount { get; set; }
    1213        public EnrolledSemesters? EnrolledSemesters { get; set; }
    1314
  • iknow-api/Models/SemesterSubjects.cs

    r93c368a r1b20b22  
    11namespace iknow_api.Models
    22{
     3    // The student is reached through EnrolledSemester.UserId; the ER model
     4    // links SemestersSubjects to Users only through the professor.
    35    public class SemesterSubject
    46    {
    5         public int Id { get; set; }
    6        
    7         public int UserId { get; set; }                 
     7        public int Id { get; set; }
     8
    89        public int EnrolledSemesterId { get; set; }
    910        public int SubjectId { get; set; }
    1011        public int ProfessorId { get; set; }
    1112        public bool Signature { get; set; }
    12         public User? User { get; set; }                 
     13
    1314        public EnrolledSemesters? EnrolledSemester { get; set; }
    1415        public Subject? Subject { get; set; }
    15         public User? Professor { get; set; }           
     16        public User? Professor { get; set; }
    1617        public PassedSubject? PassedSubject { get; set; }
    1718    }
  • iknow-api/Models/User.cs

    r93c368a r1b20b22  
    1 
    2 
    31namespace iknow_api.Models
    42{
     
    108    }
    119
     10    // Attributes follow the Users entity in the ER model exactly.
    1211    public class User
    1312    {
    14         internal object Payments;
    15 
    1613        public int Id { get; set; }
    1714        public string? Name { get; set; }
    18         public string? EMBG {get; set; }
     15        public string? EMBG { get; set; }
    1916        public string? Surname { get; set; }
    2017        public string? Index { get; set; }
    2118        public string? Email { get; set; }
    2219        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; }
    2821        public DateTime CreatedAt { get; set; }
    2922        public UserRole Role { get; set; }
     23        public Quota? Quota { get; set; }
     24        public int? EnrollmentYear { get; set; }
     25
    3026        public HighSchool? HighSchool { get; set; }
    3127        public ContactInfo? ContactInfo { get; set; }
    32         public EnrollmentInfo? EnrollmentInfo { get; set; }
    3328        public ICollection<UserDocuments>? Documents { get; set; }
    34         public ICollection<Payment>? EnrolledSemesters { get; set; }
    35         public ICollection<SemesterSubject>? EnrolledSemesterUserSubjects { get; set; }
    3629
     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
    3737        public ICollection<SemesterSubject>? TeachingSubjects { get; set; }
    38 
    3938    }
    4039}
  • iknow-api/Program.cs

    r93c368a r1b20b22  
    22using System.Text.Json.Serialization;
    33using iknow_api.Data;
     4using iknow_api.Models;
    45using iknow_api.Repositories;
    56using iknow_api.Services;
     
    5960builder.Services.AddOpenApi();
    6061
    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.
    6268builder.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        }));
    6479
    6580// Register your services
     
    7186builder.Services.AddScoped<IUserRepository, UserRepository>();
    7287builder.Services.AddScoped<IRefreshTokenRepository, RefreshTokenRepository>();
     88builder.Services.AddScoped<IProfRepository, ProfRepository>();
     89builder.Services.AddScoped<IProfService, ProfService>();
     90builder.Services.AddScoped<IEnrollmentService, EnrollmentService>();
     91builder.Services.AddScoped<IAdminService, AdminService>();
    7392builder.Services.AddAuthorization();
    7493var app = builder.Build();
  • iknow-api/Repository/Implementations/RefreshTokenRepository.cs

    r93c368a r1b20b22  
    4141                    rt.Token == refreshTokenDto.token &&
    4242                    rt.IsValid &&
    43                     rt.ExpiresAt > DateTime.UtcNow);
     43                    rt.ExpiresAt > DateTime.SpecifyKind(DateTime.UtcNow, DateTimeKind.Unspecified));
    4444
    4545            return token != null;
  • iknow-api/Repository/Implementations/UserRepository.cs

    r93c368a r1b20b22  
    2828            return await _context.User
    2929                .Include(u => u.ContactInfo)
    30                 .Include(u => u.EnrollmentInfo)
    3130                .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()
    3238                .FirstOrDefaultAsync(u => u.Id == id);
    3339        }
     
    4854            await _context.SaveChangesAsync();
    4955        }
    50         public async Task AddEnrollmentAsync(EnrollmentInfo enrollment)
    51         {
    52             _context.EnrollmentInfo.Add(enrollment);
    53             await _context.SaveChangesAsync();
    54         }   
    5556        public async Task AddHighSchoolAsync(HighSchool highSchool)
    5657        {
     
    8990                .Include(ps => ps.SemesterSubject)
    9091                    .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)
    9295                .ToListAsync();
    9396        }
  • iknow-api/Repository/Interface/IUserRepository.cs

    r93c368a r1b20b22  
    1 using iknow_api.Models;
     1using iknow_api.Models;
    22namespace iknow_api.Repositories
    33{
     
    88        Task AddUserAsync(User user);
    99        Task AddContactAsync(ContactInfo contactInfo);
    10         Task AddEnrollmentAsync(EnrollmentInfo enrollment);
    1110        Task AddHighSchoolAsync(HighSchool highSchool);
    1211        Task<int> GetUsersCountAsync();
  • iknow-api/Services/Implementations/AuthService.cs

    r93c368a r1b20b22  
    1 using System.IdentityModel.Tokens.Jwt;
     1using System.IdentityModel.Tokens.Jwt;
    22using System.Security.Claims;
    33using System.Text;
     
    5151            try
    5252            {
     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.
    5355                var user = new User
    5456                {
    5557                    Name = registerDto.Name,
    56                     MiddleName = registerDto.MiddleName,
    5758                    Surname = registerDto.Surname,
    5859                    Index = registerDto.Index,
    5960                    Email = registerDto.Email,
    6061                    PasswordHash = BCrypt.Net.BCrypt.HashPassword(registerDto.Password),
    61                     Bday = DateTime.SpecifyKind(registerDto.Bday, DateTimeKind.Utc),
    62                     CreatedAt = DateTime.UtcNow,
     62                    Bday = DateTime.SpecifyKind(registerDto.Bday, DateTimeKind.Unspecified),
     63                    CreatedAt = DateTime.SpecifyKind(DateTime.UtcNow, DateTimeKind.Unspecified),
    6364                    Role = (Models.UserRole)registerDto.Role,
    6465                    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
    6868                };
    6969
     
    8383                };
    8484               
    85                 var enrollmentInfo = new EnrollmentInfo
    86                 {
    87                     UserId = userId,
    88                     EnrollmentYear = registerDto.enrollmentYear,
    89                     Quota = (Models.Quota)registerDto.quotaType,
    90                     MajorId = registerDto.majorType,
    91                     StudyStatus = registerDto.studyStatus,
    92                     StudyType = registerDto.studyType
    93                 };
    94                
    9585                var highSchool = new HighSchool
    9686                {
     
    10191
    10292                _context.ContactInfo.Add(contactInfo);
    103                 _context.EnrollmentInfo.Add(enrollmentInfo);
    10493                _context.HighSchool.Add(highSchool);
    10594               
  • iknow-api/Services/Implementations/RefreshTokenService.cs

    r93c368a r1b20b22  
    3131                Token = refreshToken,
    3232                UserId = userid,
    33                 ExpiresAt = DateTime.UtcNow.AddMonths(1),
     33                ExpiresAt = DateTime.SpecifyKind(DateTime.UtcNow.AddMonths(1), DateTimeKind.Unspecified),
    3434                IsValid = true
    3535            };
  • iknow-api/appsettings.json

    r93c368a r1b20b22  
    1010    "https://iknow-remaster.vercel.app"
    1111  ],
    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": "*"
    1813}
Note: See TracChangeset for help on using the changeset viewer.