Index: iknow-api/Controllers/AdminController.cs
===================================================================
--- iknow-api/Controllers/AdminController.cs	(revision ea405569ef29f56fbfc4d77708b6b3faf9286097)
+++ iknow-api/Controllers/AdminController.cs	(revision ea405569ef29f56fbfc4d77708b6b3faf9286097)
@@ -0,0 +1,119 @@
+using System.Security.Claims;
+using iknow_api.DTOs;
+using iknow_api.Services;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+
+namespace iknow_api.Controllers
+{
+    /// <summary>
+    /// UC012 subjects, UC013 active semesters, UC014 professor assignments.
+    /// Every action requires the admin role, checked against the validated token.
+    /// </summary>
+    [ApiController]
+    [Route("api/[controller]")]
+    [Authorize]
+    public class AdminController : ControllerBase
+    {
+        private readonly IAdminService _adminService;
+
+        public AdminController(IAdminService adminService)
+        {
+            _adminService = adminService;
+        }
+
+        // ---------- UC012 ----------
+
+        [HttpGet("subjects")]
+        public async Task<IActionResult> GetSubjects() =>
+            RequireAdmin(out var error) ? Ok(await _adminService.GetSubjectsAsync()) : error;
+
+        [HttpPost("subjects")]
+        public async Task<IActionResult> CreateSubject([FromBody] SubjectWriteDto request) =>
+            RequireAdmin(out var error) ? Result(await _adminService.CreateSubjectAsync(request)) : error;
+
+        [HttpPut("subjects/{id:int}")]
+        public async Task<IActionResult> UpdateSubject(int id, [FromBody] SubjectWriteDto request) =>
+            RequireAdmin(out var error) ? Result(await _adminService.UpdateSubjectAsync(id, request)) : error;
+
+        [HttpDelete("subjects/{id:int}")]
+        public async Task<IActionResult> DeleteSubject(int id) =>
+            RequireAdmin(out var error) ? Result(await _adminService.DeleteSubjectAsync(id)) : error;
+
+        [HttpPost("subjects/{id:int}/majors")]
+        public async Task<IActionResult> LinkMajor(int id, [FromBody] MajorLinkDto request) =>
+            RequireAdmin(out var error) ? Result(await _adminService.LinkMajorAsync(id, request)) : error;
+
+        [HttpDelete("subjects/{id:int}/majors/{majorId:int}")]
+        public async Task<IActionResult> UnlinkMajor(int id, int majorId) =>
+            RequireAdmin(out var error) ? Result(await _adminService.UnlinkMajorAsync(id, majorId)) : error;
+
+        [HttpPost("subjects/{id:int}/prerequisites")]
+        public async Task<IActionResult> AddPrerequisite(int id, [FromBody] PrerequisiteDto request) =>
+            RequireAdmin(out var error) ? Result(await _adminService.AddPrerequisiteAsync(id, request)) : error;
+
+        [HttpDelete("subjects/{id:int}/prerequisites/{dependencyId:int}")]
+        public async Task<IActionResult> RemovePrerequisite(int id, int dependencyId) =>
+            RequireAdmin(out var error) ? Result(await _adminService.RemovePrerequisiteAsync(id, dependencyId)) : error;
+
+        // ---------- UC013 ----------
+
+        [HttpGet("semesters")]
+        public async Task<IActionResult> GetSemesters() =>
+            RequireAdmin(out var error) ? Ok(await _adminService.GetSemestersAsync()) : error;
+
+        [HttpPost("semesters")]
+        public async Task<IActionResult> CreateSemester([FromBody] SemesterWriteDto request) =>
+            RequireAdmin(out var error) ? Result(await _adminService.CreateSemesterAsync(request)) : error;
+
+        // ---------- UC014 ----------
+
+        [HttpGet("schedule/{semesterId:int}")]
+        public async Task<IActionResult> GetSchedule(int semesterId)
+        {
+            if (!RequireAdmin(out var error)) return error;
+
+            var schedule = await _adminService.GetScheduleAsync(semesterId);
+            return schedule is null
+                ? NotFound(new AdminResultDto { Ok = false, Message = "Semester not found." })
+                : Ok(schedule);
+        }
+
+        [HttpPost("schedule")]
+        public async Task<IActionResult> Assign([FromBody] ScheduleWriteDto request) =>
+            RequireAdmin(out var error) ? Result(await _adminService.AssignAsync(request)) : error;
+
+        [HttpDelete("schedule")]
+        public async Task<IActionResult> Unassign([FromBody] ScheduleWriteDto request) =>
+            RequireAdmin(out var error) ? Result(await _adminService.UnassignAsync(request)) : error;
+
+        // ---------- shared ----------
+
+        [HttpGet("majors")]
+        public async Task<IActionResult> GetMajors() =>
+            RequireAdmin(out var error) ? Ok(await _adminService.GetMajorsAsync()) : error;
+
+        private IActionResult Result(AdminResultDto result) =>
+            result.Ok ? Ok(result) : BadRequest(result);
+
+        /// <summary>
+        /// The role comes from the validated token, so it cannot be set by the caller.
+        /// </summary>
+        private bool RequireAdmin(out IActionResult error)
+        {
+            var role = User.FindFirst(ClaimTypes.Role)?.Value ?? User.FindFirst("role")?.Value;
+            if (!string.Equals(role, nameof(Models.UserRole.Admin), StringComparison.OrdinalIgnoreCase))
+            {
+                error = StatusCode(403, new AdminResultDto
+                {
+                    Ok = false,
+                    Message = "This endpoint is for administrators."
+                });
+                return false;
+            }
+
+            error = Ok();
+            return true;
+        }
+    }
+}
Index: iknow-api/Controllers/DbHealthController.cs
===================================================================
--- iknow-api/Controllers/DbHealthController.cs	(revision ea405569ef29f56fbfc4d77708b6b3faf9286097)
+++ iknow-api/Controllers/DbHealthController.cs	(revision ea405569ef29f56fbfc4d77708b6b3faf9286097)
@@ -0,0 +1,49 @@
+using iknow_api.Data;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.EntityFrameworkCore;
+
+namespace iknow_api.Controllers;
+
+[ApiController]
+[Route("[controller]")]
+public class DbHealthController(AppDbContext db) : ControllerBase
+{
+    /// <summary>
+    /// Verifies that the SSH tunnel is up, the DB credentials work, and that
+    /// the entity mapping matches the schema created by sql/ddl.sql.
+    /// </summary>
+    [HttpGet]
+    public async Task<IActionResult> Get()
+    {
+        try
+        {
+            // EF projects SqlQuery onto a column named "Value", hence the alias.
+            var version = await db.Database
+                .SqlQuery<string>($"SELECT version() AS \"Value\"")
+                .SingleAsync();
+
+            return Ok(new
+            {
+                connected = true,
+                server = version,
+                counts = new
+                {
+                    users = await db.User.CountAsync(),
+                    subjects = await db.Subjects.CountAsync(),
+                    enrolledSemesters = await db.EnrolledSemesters.CountAsync(),
+                    semesterSubjects = await db.SemesterSubjects.CountAsync(),
+                    passedSubjects = await db.PassedSubjects.CountAsync(),
+                }
+            });
+        }
+        catch (Exception ex)
+        {
+            return StatusCode(503, new
+            {
+                connected = false,
+                error = ex.Message,
+                hint = "Is tunnel_scripta.cmd running and listening on localhost:9999?"
+            });
+        }
+    }
+}
Index: iknow-api/Controllers/EnrollmentController.cs
===================================================================
--- iknow-api/Controllers/EnrollmentController.cs	(revision ea405569ef29f56fbfc4d77708b6b3faf9286097)
+++ iknow-api/Controllers/EnrollmentController.cs	(revision ea405569ef29f56fbfc4d77708b6b3faf9286097)
@@ -0,0 +1,78 @@
+using System.Security.Claims;
+using iknow_api.DTOs;
+using iknow_api.Services;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+
+namespace iknow_api.Controllers
+{
+    [ApiController]
+    [Route("api/[controller]")]
+    [Authorize]
+    public class EnrollmentController : ControllerBase
+    {
+        private readonly IEnrollmentService _enrollmentService;
+
+        public EnrollmentController(IEnrollmentService enrollmentService)
+        {
+            _enrollmentService = enrollmentService;
+        }
+
+        [HttpGet("options")]
+        public async Task<IActionResult> GetOptions()
+        {
+            if (!TryGetStudentId(out var studentId, out var error))
+            {
+                return error;
+            }
+
+            return Ok(await _enrollmentService.GetOptionsAsync(studentId));
+        }
+
+        [HttpPost]
+        public async Task<IActionResult> Enroll([FromBody] EnrollSemesterRequestDto request)
+        {
+            if (!TryGetStudentId(out var studentId, out var error))
+            {
+                return error;
+            }
+
+            var result = await _enrollmentService.EnrollAsync(studentId, request);
+            return result.Ok ? Ok(result) : BadRequest(result);
+        }
+
+        /// <summary>
+        /// Reads the caller from the validated token, so a student can only ever
+        /// enrol themselves.
+        /// </summary>
+        private bool TryGetStudentId(out int studentId, out IActionResult error)
+        {
+            studentId = 0;
+            error = Forbid();
+
+            var role = User.FindFirst(ClaimTypes.Role)?.Value ?? User.FindFirst("role")?.Value;
+            if (!string.Equals(role, nameof(Models.UserRole.Student), StringComparison.OrdinalIgnoreCase))
+            {
+                error = StatusCode(403, new EnrollSemesterResultDto
+                {
+                    Ok = false,
+                    Message = "Only students can enrol in a semester."
+                });
+                return false;
+            }
+
+            var idClaim = User.FindFirst("id")?.Value;
+            if (!int.TryParse(idClaim, out studentId))
+            {
+                error = Unauthorized(new EnrollSemesterResultDto
+                {
+                    Ok = false,
+                    Message = "Token has no usable user id."
+                });
+                return false;
+            }
+
+            return true;
+        }
+    }
+}
Index: iknow-api/Controllers/ProfController.cs
===================================================================
--- iknow-api/Controllers/ProfController.cs	(revision ea405569ef29f56fbfc4d77708b6b3faf9286097)
+++ iknow-api/Controllers/ProfController.cs	(revision ea405569ef29f56fbfc4d77708b6b3faf9286097)
@@ -0,0 +1,93 @@
+using System.Security.Claims;
+using iknow_api.DTOs;
+using iknow_api.Services;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+
+namespace iknow_api.Controllers
+{
+    [ApiController]
+    [Route("api/[controller]")]
+    [Authorize]
+    public class ProfController : ControllerBase
+    {
+        private readonly IProfService _profService;
+
+        public ProfController(IProfService profService)
+        {
+            _profService = profService;
+        }
+
+        /// <summary>
+        /// The subjects this professor teaches, each with the students enrolled
+        /// in it and their grade so far.
+        /// </summary>
+        [HttpGet("students")]
+        public async Task<IActionResult> GetStudents()
+        {
+            if (!TryGetProfessorId(out var professorId, out var error))
+            {
+                return error;
+            }
+
+            return Ok(await _profService.GetStudentsBySubjectAsync(professorId));
+        }
+
+        [HttpPost("grade/add")]
+        public Task<IActionResult> AddGrade([FromBody] GradeRequestDto request) =>
+            SetGrade(request, GradeAction.Add);
+
+        [HttpPost("grade/edit")]
+        public Task<IActionResult> EditGrade([FromBody] GradeRequestDto request) =>
+            SetGrade(request, GradeAction.Edit);
+
+        [HttpPost("grade/remove")]
+        public Task<IActionResult> RemoveGrade([FromBody] GradeRequestDto request) =>
+            SetGrade(request, GradeAction.Remove);
+
+        private async Task<IActionResult> SetGrade(GradeRequestDto request, GradeAction action)
+        {
+            if (!TryGetProfessorId(out var professorId, out var error))
+            {
+                return error;
+            }
+
+            var result = await _profService.SetGradeAsync(professorId, request, action);
+            return result.Ok ? Ok(result) : BadRequest(result);
+        }
+
+        /// <summary>
+        /// Reads the caller from the validated token and requires the professor
+        /// role, so one professor can never act on another one's subjects.
+        /// </summary>
+        private bool TryGetProfessorId(out int professorId, out IActionResult error)
+        {
+            professorId = 0;
+            error = Forbid();
+
+            var role = User.FindFirst(ClaimTypes.Role)?.Value ?? User.FindFirst("role")?.Value;
+            if (!string.Equals(role, nameof(Models.UserRole.Professor), StringComparison.OrdinalIgnoreCase))
+            {
+                error = StatusCode(403, new GradeResultDto
+                {
+                    Ok = false,
+                    Message = "This endpoint is for professors."
+                });
+                return false;
+            }
+
+            var idClaim = User.FindFirst("id")?.Value;
+            if (!int.TryParse(idClaim, out professorId))
+            {
+                error = Unauthorized(new GradeResultDto
+                {
+                    Ok = false,
+                    Message = "Token has no usable user id."
+                });
+                return false;
+            }
+
+            return true;
+        }
+    }
+}
Index: iknow-api/Controllers/UserController.cs
===================================================================
--- iknow-api/Controllers/UserController.cs	(revision af4f801397ceece4e342e05b655d48237371367c)
+++ iknow-api/Controllers/UserController.cs	(revision ea405569ef29f56fbfc4d77708b6b3faf9286097)
@@ -1,3 +1,4 @@
-﻿using iknow_api.DTOs;
+using iknow_api.DTOs;
+using iknow_api.Models;
 using Microsoft.AspNetCore.Authorization;
 using Microsoft.AspNetCore.Mvc;
@@ -83,4 +84,14 @@
                 var userData = await _userService.GetUserData(token);
                 if (userData == null) return Ok(new { info = "can't get info" });
+
+                // The study programme comes from the student's most recent
+                // enrolment. Within an academic year winter precedes summer,
+                // so order by year first and put winter ahead of summer.
+                var currentEnrolment = userData.Enrolments?
+                    .Where(es => es.Semester != null)
+                    .OrderByDescending(es => es.Semester!.Year)
+                    .ThenByDescending(es => es.Semester!.Type == sType.summer)
+                    .FirstOrDefault();
+
                 var personalInfo = new
                 {
@@ -88,13 +99,13 @@
                     embg = userData.EMBG ?? "",
                     lastName = userData.Surname ?? "",
-                    middleName = userData.MiddleName ?? "",
+                    middleName = "", // Not an attribute of Users in the ER model
                     firstName = userData.Name ?? "",
                     maidenName = "", // Property doesn't exist in User model
-                    dateOfBirth = userData.Bday.ToString("dd.MM.yyyy"),
-                    gender = userData.Gender ?? "",
-                    nationality = userData.Nationality ?? "",
-                    citizenship = userData.Citizenship ?? "",
+                    dateOfBirth = userData.Bday?.ToString("dd.MM.yyyy") ?? "",
+                    gender = "", // Not an attribute of Users in the ER model
+                    nationality = "", // Not an attribute of Users in the ER model
+                    citizenship = "", // Not an attribute of Users in the ER model
                     scholarship = "",
-                    currentPlan = userData.EnrollmentInfo?.EnrollmentYear.ToString() ?? "",
+                    currentPlan = userData.EnrollmentYear?.ToString() ?? "",
                     registryNumber = "",
                     notes = "",
@@ -105,5 +116,5 @@
                     placeOfBirth = userData.ContactInfo?.City ?? "",
                     municipalityOfBirth = userData.ContactInfo?.Municipality ?? "",
-                    country = userData.Citizenship ?? ""
+                    country = "" // Not an attribute of Users in the ER model
                 };
                 var previousEducation = new
@@ -112,6 +123,6 @@
                     profession = "",
                     average = userData.HighSchool?.GPA.ToString() ?? "",
-                    language = userData.Nationality ?? "",
-                    country = userData.Nationality ?? "",
+                    language = "", // Not an attribute of HighSchool in the ER model
+                    country = "",
                     previousUniversity = userData.HighSchool?.HighSchoolType.ToString() ?? "",
                     previousFaculty = "", // Property doesn't exist in HighSchool model
@@ -120,9 +131,9 @@
                 var enrollmentInfo = new
                 {
-                    enrollmentYear = userData.EnrollmentInfo?.EnrollmentYear.ToString() ?? "",
-                    status = userData.EnrollmentInfo?.StudyStatus ?? "",
+                    enrollmentYear = userData.EnrollmentYear?.ToString() ?? "",
+                    status = "", // Not an attribute of Users in the ER model
                     cycle = "Прв циклус",
-                    program = userData.EnrollmentInfo?.Major?.Name ?? "",
-                    quota = userData.EnrollmentInfo?.Quota.ToString() ?? "",
+                    program = currentEnrolment?.Major?.Name ?? "",
+                    quota = userData.Quota?.ToString() ?? "",
                     secondaryEducationNumber = "",
                     previousEducationCredits = ""
@@ -132,5 +143,5 @@
                     placeOfResidence = userData.ContactInfo?.City ?? "",
                     municipalityOfResidence = userData.ContactInfo?.Municipality ?? "",
-                    country = userData.Citizenship ?? "",
+                    country = "", // Not an attribute of Users in the ER model
                     address = userData.ContactInfo?.Address ?? "",
                     temporaryAddress = "",
@@ -165,14 +176,16 @@
                     {
                         id = userData[i].Id,
-                        semester = (userData[i].Semester.Type == 0 ? "Зимски" : "Летен") + $"({userData[i].Semester.Year}/{userData[i].Semester.Year + 1})",
+                        // sType declares summer first, so compare against the member
+                        // rather than the ordinal - == 0 meant summer, not winter.
+                        semester = (userData[i].Semester.Type == sType.winter ? "Зимски" : "Летен") + $"({userData[i].Semester.Year}/{userData[i].Semester.Year + 1})",
                         direction = userData[i].Major?.Name ?? "",
                         quota = userData[i].QuotaType.ToString(),
-                        note = "",
-                        studentCom = "",
+                        note = userData[i].Note ?? "",
+                        studentCom = userData[i].StudentComment ?? "",
                         sum = "0,00",
                         paid = "0,00",
                         ukim = "",
-                        createdOn = DateTime.Now.ToString("dd.MM.yyyy"),
-                        dateChanged = DateTime.Now.ToString("dd.MM.yyyy"),
+                        createdOn = userData[i].CratedAt.ToString("dd.MM.yyyy"),
+                        dateChanged = userData[i].LastChange?.ToString("dd.MM.yyyy") ?? "",
                         credits = "0,00",
                         type = "Ред.",
@@ -183,5 +196,5 @@
                         signatures = "0/5",
                         status = "валиден",
-                        completed = "Не"
+                        completed = userData[i].Verified.HasValue ? "Да" : "Не"
                     };
                     results.Add(newResult);
@@ -208,5 +221,5 @@
                 {
                     id = userData[i].Id,
-                    name = (userData[i].Semester.Type == 0 ? "Зимски" : "Летен") + $"({userData[i].Semester.Year}/{userData[i].Semester.Year + 1})",
+                    name = (userData[i].Semester.Type == sType.winter ? "Зимски" : "Летен") + $"({userData[i].Semester.Year}/{userData[i].Semester.Year + 1})",
                     status = "валиден",
                     serviceNumber = 1000000 + random.Next(10000, 100000)
@@ -221,5 +234,5 @@
             {
                 // Create semester key like "winter_2025_2026" or "summer_2025_2026"
-                var semesterType = enrollment.Semester.Type == 0 ? "winter" : "summer";
+                var semesterType = enrollment.Semester.Type == sType.winter ? "winter" : "summer";
                 var semesterKey = $"{semesterType}_{enrollment.Semester.Year}_{enrollment.Semester.Year + 1}";
 
@@ -258,9 +271,9 @@
             }
             var currentSemester = userData.FirstOrDefault(); // Get the most recent/current semester
-            var semesterTypeGlobal = currentSemester.Semester.Type == 0 ? "winter" : "summer";
+            var semesterTypeGlobal = currentSemester.Semester.Type == sType.winter ? "winter" : "summer";
             var semesterData = new
             {
                 id = $"{semesterTypeGlobal}_{currentSemester.Semester.Year}_{currentSemester.Semester.Year + 1}",
-                name = (currentSemester.Semester.Type == 0 ? "Зимски" : "Летен") + $" ({currentSemester.Semester.Year}/{currentSemester.Semester.Year + 1})",
+                name = (currentSemester.Semester.Type == sType.winter ? "Зимски" : "Летен") + $" ({currentSemester.Semester.Year}/{currentSemester.Semester.Year + 1})",
                 status = "валиден",
                 serviceNumber = (1000000 + random.Next(10000, 100000)).ToString(),
@@ -318,5 +331,5 @@
                         date = passed.DatePassed.ToString("dd.MM.yyyy"),
                         semester = passed.SemesterSubject.EnrolledSemester?.Semester != null
-                            ? (passed.SemesterSubject.EnrolledSemester.Semester.Type == 0 ? "Зимски" : "Летен") + 
+                            ? (passed.SemesterSubject.EnrolledSemester.Semester.Type == sType.winter ? "Зимски" : "Летен") + 
                               $" ({passed.SemesterSubject.EnrolledSemester.Semester.Year}/{passed.SemesterSubject.EnrolledSemester.Semester.Year + 1})"
                             : "",
Index: iknow-api/Controllers/WeatherForecastController.cs
===================================================================
--- iknow-api/Controllers/WeatherForecastController.cs	(revision af4f801397ceece4e342e05b655d48237371367c)
+++ 	(revision )
@@ -1,33 +1,0 @@
-using Microsoft.AspNetCore.Mvc;
-
-namespace iknow_api.Controllers
-{
-    [ApiController]
-    [Route("[controller]")]
-    public class WeatherForecastController : ControllerBase
-    {
-        private static readonly string[] Summaries = new[]
-        {
-            "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
-        };
-
-        private readonly ILogger<WeatherForecastController> _logger;
-
-        public WeatherForecastController(ILogger<WeatherForecastController> logger)
-        {
-            _logger = logger;
-        }
-
-        [HttpGet(Name = "GetWeatherForecast")]
-        public IEnumerable<WeatherForecast> Get()
-        {
-            return Enumerable.Range(1, 5).Select(index => new WeatherForecast
-            {
-                Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
-                TemperatureC = Random.Shared.Next(-20, 55),
-                Summary = Summaries[Random.Shared.Next(Summaries.Length)]
-            })
-            .ToArray();
-        }
-    }
-}
